1use crate::FarmerProtocolInfo;
10use crate::sector::{
11 EncodedChunksUsed, RawSector, RecordMetadata, SectorContentsMap, SectorMetadata,
12 SectorMetadataChecksummed, sector_record_chunks_size, sector_size,
13};
14use crate::segment_reconstruction::recover_missing_piece;
15use async_lock::{Mutex as AsyncMutex, Semaphore};
16use backon::{ExponentialBuilder, Retryable};
17use futures::stream::FuturesUnordered;
18use futures::{StreamExt, select};
19use parity_scale_codec::{Decode, Encode};
20use parking_lot::Mutex;
21use rayon::prelude::*;
22use std::collections::HashMap;
23use std::simd::Simd;
24use std::sync::Arc;
25use std::sync::atomic::{AtomicBool, Ordering};
26use std::time::Duration;
27use subspace_core_primitives::hashes::{Blake3Hash, blake3_hash, blake3_hash_parallel};
28use subspace_core_primitives::pieces::{Piece, PieceIndex, PieceOffset, Record};
29use subspace_core_primitives::pos::PosSeed;
30use subspace_core_primitives::sectors::{SBucket, SectorId, SectorIndex};
31use subspace_core_primitives::segments::HistorySize;
32use subspace_core_primitives::{PublicKey, ScalarBytes};
33use subspace_data_retrieval::piece_getter::PieceGetter;
34use subspace_erasure_coding::ErasureCoding;
35use subspace_kzg::{Kzg, Scalar};
36use subspace_proof_of_space::{Table, TableGenerator};
37use thiserror::Error;
38use tracing::{debug, trace, warn};
39
40const RECONSTRUCTION_CONCURRENCY_LIMIT: usize = 1;
41
42#[derive(Debug, Clone, Encode, Decode)]
44pub struct PlottedSector {
45 pub sector_id: SectorId,
47 pub sector_index: SectorIndex,
49 pub sector_metadata: SectorMetadataChecksummed,
51 pub piece_indexes: Vec<PieceIndex>,
53}
54
55#[derive(Debug, Error)]
57pub enum PlottingError {
58 #[error("Records encoder error: {error}")]
60 RecordsEncoderError {
61 error: anyhow::Error,
63 },
64 #[error("Bad sector output size: provided {provided}, expected {expected}")]
66 BadSectorOutputSize {
67 provided: usize,
69 expected: usize,
71 },
72 #[error("Can't recover missing piece {piece_index}: {error}")]
74 PieceRecoveryFailed {
75 piece_index: PieceIndex,
77 error: anyhow::Error,
79 },
80 #[error("Failed to retrieve pieces: {error}")]
82 FailedToRetrievePieces {
83 error: anyhow::Error,
85 },
86 #[error("Abort early")]
88 AbortEarly,
89}
90
91#[derive(Debug)]
97pub struct PlotSectorOptions<'a, RE, PG> {
98 pub public_key: &'a PublicKey,
100 pub sector_index: SectorIndex,
102 pub piece_getter: &'a PG,
104 pub farmer_protocol_info: FarmerProtocolInfo,
106 pub kzg: &'a Kzg,
108 pub erasure_coding: &'a ErasureCoding,
110 pub pieces_in_sector: u16,
112 pub sector_output: &'a mut Vec<u8>,
115 pub downloading_semaphore: Option<Arc<Semaphore>>,
118 pub encoding_semaphore: Option<&'a Semaphore>,
121 pub records_encoder: &'a mut RE,
123 pub abort_early: &'a AtomicBool,
125}
126
127pub async fn plot_sector<RE, PG>(
134 options: PlotSectorOptions<'_, RE, PG>,
135) -> Result<PlottedSector, PlottingError>
136where
137 RE: RecordsEncoder,
138 PG: PieceGetter + Send + Sync,
139{
140 let PlotSectorOptions {
141 public_key,
142 sector_index,
143 piece_getter,
144 farmer_protocol_info,
145 kzg,
146 erasure_coding,
147 pieces_in_sector,
148 sector_output,
149 downloading_semaphore,
150 encoding_semaphore,
151 records_encoder,
152 abort_early,
153 } = options;
154
155 let _downloading_permit = match downloading_semaphore {
156 Some(downloading_semaphore) => Some(downloading_semaphore.acquire_arc().await),
157 None => None,
158 };
159
160 let download_sector_fut = download_sector(DownloadSectorOptions {
161 public_key,
162 sector_index,
163 piece_getter,
164 farmer_protocol_info,
165 kzg,
166 erasure_coding,
167 pieces_in_sector,
168 });
169
170 let _encoding_permit = match encoding_semaphore {
171 Some(encoding_semaphore) => Some(encoding_semaphore.acquire().await),
172 None => None,
173 };
174
175 let encoded_sector = encode_sector(
176 download_sector_fut.await?,
177 EncodeSectorOptions::<RE> {
178 sector_index,
179 records_encoder,
180 abort_early,
181 },
182 )?;
183
184 if abort_early.load(Ordering::Acquire) {
185 return Err(PlottingError::AbortEarly);
186 }
187
188 write_sector(&encoded_sector, sector_output)?;
189
190 Ok(encoded_sector.plotted_sector)
191}
192
193#[derive(Debug)]
195pub struct DownloadedSector {
196 sector_id: SectorId,
197 piece_indices: Vec<PieceIndex>,
198 raw_sector: RawSector,
199 history_size: HistorySize,
200}
201
202#[derive(Debug)]
204pub struct DownloadSectorOptions<'a, PG> {
205 pub public_key: &'a PublicKey,
207 pub sector_index: SectorIndex,
209 pub piece_getter: &'a PG,
211 pub farmer_protocol_info: FarmerProtocolInfo,
213 pub kzg: &'a Kzg,
215 pub erasure_coding: &'a ErasureCoding,
217 pub pieces_in_sector: u16,
219}
220
221pub async fn download_sector<PG>(
226 options: DownloadSectorOptions<'_, PG>,
227) -> Result<DownloadedSector, PlottingError>
228where
229 PG: PieceGetter + Send + Sync,
230{
231 let DownloadSectorOptions {
232 public_key,
233 sector_index,
234 piece_getter,
235 farmer_protocol_info,
236 kzg,
237 erasure_coding,
238 pieces_in_sector,
239 } = options;
240
241 let sector_id = SectorId::new(
242 public_key.hash(),
243 sector_index,
244 farmer_protocol_info.history_size,
245 );
246
247 let piece_indices = (PieceOffset::ZERO..)
248 .take(pieces_in_sector.into())
249 .map(|piece_offset| {
250 sector_id.derive_piece_index(
251 piece_offset,
252 farmer_protocol_info.history_size,
253 farmer_protocol_info.max_pieces_in_sector,
254 farmer_protocol_info.recent_segments,
255 farmer_protocol_info.recent_history_fraction,
256 )
257 })
258 .collect::<Vec<_>>();
259
260 let raw_sector = {
261 let mut raw_sector = RawSector::new(pieces_in_sector);
262 let mut pieces_to_download =
263 HashMap::<PieceIndex, Vec<_>>::with_capacity(usize::from(pieces_in_sector));
264 for (piece_index, (record, metadata)) in piece_indices
265 .iter()
266 .copied()
267 .zip(raw_sector.records.iter_mut().zip(&mut raw_sector.metadata))
268 {
269 pieces_to_download
270 .entry(piece_index)
271 .or_default()
272 .push((record, metadata));
273 }
274 let pieces_to_download = AsyncMutex::new(pieces_to_download);
276
277 (|| async {
278 let mut pieces_to_download = pieces_to_download.lock().await;
279
280 if let Err(error) =
281 download_sector_internal(&mut pieces_to_download, piece_getter, kzg, erasure_coding)
282 .await
283 {
284 warn!(
285 %sector_index,
286 %error,
287 %pieces_in_sector,
288 remaining_pieces = %pieces_to_download.len(),
289 "Sector downloading attempt failed, will retry later"
290 );
291
292 return Err(error);
293 }
294
295 debug!(%sector_index, "Sector downloaded successfully");
296
297 Ok(())
298 })
299 .retry(
300 ExponentialBuilder::default()
301 .with_min_delay(Duration::from_secs(15))
302 .with_max_delay(Duration::from_mins(10))
303 .without_max_times(),
305 )
306 .await?;
307
308 raw_sector
309 };
310
311 Ok(DownloadedSector {
312 sector_id,
313 piece_indices,
314 raw_sector,
315 history_size: farmer_protocol_info.history_size,
316 })
317}
318
319pub trait RecordsEncoder {
321 fn encode_records(
323 &mut self,
324 sector_id: &SectorId,
325 records: &mut [Record],
326 abort_early: &AtomicBool,
327 ) -> anyhow::Result<SectorContentsMap>;
328}
329
330#[derive(Debug)]
332pub struct CpuRecordsEncoder<'a, PosTable>
333where
334 PosTable: Table,
335{
336 table_generators: &'a mut [PosTable::Generator],
337 erasure_coding: &'a ErasureCoding,
338 global_mutex: &'a AsyncMutex<()>,
339}
340
341impl<PosTable> RecordsEncoder for CpuRecordsEncoder<'_, PosTable>
342where
343 PosTable: Table,
344{
345 fn encode_records(
346 &mut self,
347 sector_id: &SectorId,
348 records: &mut [Record],
349 abort_early: &AtomicBool,
350 ) -> anyhow::Result<SectorContentsMap> {
351 if self.erasure_coding.max_shards() < Record::NUM_S_BUCKETS {
352 return Err(anyhow::anyhow!(
353 "Invalid erasure coding instance: {} shards needed, {} supported",
354 Record::NUM_S_BUCKETS,
355 self.erasure_coding.max_shards()
356 ));
357 }
358
359 if self.table_generators.is_empty() {
360 return Err(anyhow::anyhow!("No table generators"));
361 }
362
363 let pieces_in_sector = records
364 .len()
365 .try_into()
366 .map_err(|error| anyhow::anyhow!("Failed to convert pieces in sector: {error}"))?;
367 let mut sector_contents_map = SectorContentsMap::new(pieces_in_sector);
368
369 {
370 let table_generators = &mut *self.table_generators;
371 let global_mutex = self.global_mutex;
372 let erasure_coding = self.erasure_coding;
373
374 let iter = Mutex::new(
375 (PieceOffset::ZERO..)
376 .zip(records.iter_mut())
377 .zip(sector_contents_map.iter_record_bitfields_mut()),
378 );
379
380 rayon::scope(|scope| {
381 for table_generator in table_generators {
382 scope.spawn(|_scope| {
383 let mut chunks_scratch = Vec::with_capacity(Record::NUM_S_BUCKETS);
384
385 loop {
386 global_mutex.lock_blocking();
388
389 let Some(((piece_offset, record), encoded_chunks_used)) =
392 iter.lock().next()
393 else {
394 return;
395 };
396 let pos_seed = sector_id.derive_evaluation_seed(piece_offset);
397
398 record_encoding::<PosTable>(
399 &pos_seed,
400 record,
401 encoded_chunks_used,
402 table_generator,
403 erasure_coding,
404 &mut chunks_scratch,
405 );
406
407 if abort_early.load(Ordering::Relaxed) {
408 return;
409 }
410 }
411 });
412 }
413 });
414 }
415
416 Ok(sector_contents_map)
417 }
418}
419
420impl<'a, PosTable> CpuRecordsEncoder<'a, PosTable>
421where
422 PosTable: Table,
423{
424 pub fn new(
426 table_generators: &'a mut [PosTable::Generator],
427 erasure_coding: &'a ErasureCoding,
428 global_mutex: &'a AsyncMutex<()>,
429 ) -> Self {
430 Self {
431 table_generators,
432 erasure_coding,
433 global_mutex,
434 }
435 }
436}
437
438#[derive(Debug)]
444pub struct EncodeSectorOptions<'a, RE>
445where
446 RE: RecordsEncoder,
447{
448 pub sector_index: SectorIndex,
450 pub records_encoder: &'a mut RE,
452 pub abort_early: &'a AtomicBool,
454}
455
456#[derive(Debug)]
458pub struct EncodedSector {
459 pub plotted_sector: PlottedSector,
461 raw_sector: RawSector,
462 sector_contents_map: SectorContentsMap,
463}
464
465pub fn encode_sector<RE>(
470 downloaded_sector: DownloadedSector,
471 encoding_options: EncodeSectorOptions<'_, RE>,
472) -> Result<EncodedSector, PlottingError>
473where
474 RE: RecordsEncoder,
475{
476 let DownloadedSector {
477 sector_id,
478 piece_indices,
479 mut raw_sector,
480 history_size,
481 } = downloaded_sector;
482 let EncodeSectorOptions {
483 sector_index,
484 records_encoder,
485 abort_early,
486 } = encoding_options;
487
488 let pieces_in_sector = raw_sector.records.len().try_into().expect(
489 "Raw sector can only be created in this crate and it is always done correctly; qed",
490 );
491
492 let sector_contents_map = records_encoder
493 .encode_records(§or_id, &mut raw_sector.records, abort_early)
494 .map_err(|error| PlottingError::RecordsEncoderError { error })?;
495
496 let sector_metadata = SectorMetadataChecksummed::from(SectorMetadata {
497 sector_index,
498 pieces_in_sector,
499 s_bucket_sizes: sector_contents_map.s_bucket_sizes(),
500 history_size,
501 });
502
503 Ok(EncodedSector {
504 plotted_sector: PlottedSector {
505 sector_id,
506 sector_index,
507 sector_metadata,
508 piece_indexes: piece_indices,
509 },
510 raw_sector,
511 sector_contents_map,
512 })
513}
514
515pub fn write_sector(
517 encoded_sector: &EncodedSector,
518 sector_output: &mut Vec<u8>,
519) -> Result<(), PlottingError> {
520 let EncodedSector {
521 plotted_sector: _,
522 raw_sector,
523 sector_contents_map,
524 } = encoded_sector;
525
526 let pieces_in_sector = raw_sector.records.len().try_into().expect(
527 "Raw sector can only be created in this crate and it is always done correctly; qed",
528 );
529
530 let sector_size = sector_size(pieces_in_sector);
531
532 if !sector_output.is_empty() && sector_output.len() != sector_size {
533 return Err(PlottingError::BadSectorOutputSize {
534 provided: sector_output.len(),
535 expected: sector_size,
536 });
537 }
538
539 sector_output.resize(sector_size, 0);
540
541 {
547 let (sector_contents_map_region, remaining_bytes) =
548 sector_output.split_at_mut(SectorContentsMap::encoded_size(pieces_in_sector));
549 let (s_buckets_region, metadata_region) =
551 remaining_bytes.split_at_mut(sector_record_chunks_size(pieces_in_sector));
552
553 sector_contents_map
555 .encode_into(sector_contents_map_region)
556 .expect("Chunked into correct size above; qed");
557
558 let num_encoded_record_chunks = sector_contents_map.num_encoded_record_chunks();
559 let mut next_encoded_record_chunks_offset = vec![0_usize; pieces_in_sector.into()];
560 let mut next_unencoded_record_chunks_offset = vec![0_usize; pieces_in_sector.into()];
561 for ((piece_offset, encoded_chunk_used), output) in (SBucket::ZERO..=SBucket::MAX)
563 .flat_map(|s_bucket| {
564 sector_contents_map
565 .iter_s_bucket_records(s_bucket)
566 .expect("S-bucket guaranteed to be in range; qed")
567 })
568 .zip(
569 s_buckets_region
570 .as_chunks_mut::<{ ScalarBytes::FULL_BYTES }>()
571 .0,
572 )
573 {
574 let num_encoded_record_chunks =
575 usize::from(num_encoded_record_chunks[usize::from(piece_offset)]);
576 let next_encoded_record_chunks_offset =
577 &mut next_encoded_record_chunks_offset[usize::from(piece_offset)];
578 let next_unencoded_record_chunks_offset =
579 &mut next_unencoded_record_chunks_offset[usize::from(piece_offset)];
580
581 let chunk_position;
585 if encoded_chunk_used {
586 chunk_position = *next_encoded_record_chunks_offset;
587 *next_encoded_record_chunks_offset += 1;
588 } else {
589 chunk_position = num_encoded_record_chunks + *next_unencoded_record_chunks_offset;
590 *next_unencoded_record_chunks_offset += 1;
591 }
592 output.copy_from_slice(&raw_sector.records[usize::from(piece_offset)][chunk_position]);
593 }
594
595 let metadata_chunks = metadata_region
596 .as_chunks_mut::<{ RecordMetadata::encoded_size() }>()
597 .0;
598 for (record_metadata, output) in raw_sector.metadata.iter().zip(metadata_chunks) {
599 record_metadata.encode_to(&mut output.as_mut_slice());
600 }
601
602 let (sector_contents, sector_checksum) =
605 sector_output.split_at_mut(sector_size - Blake3Hash::SIZE);
606 sector_checksum.copy_from_slice(blake3_hash_parallel(sector_contents).as_ref());
607 }
608
609 Ok(())
610}
611
612fn record_encoding<PosTable>(
613 pos_seed: &PosSeed,
614 record: &mut Record,
615 mut encoded_chunks_used: EncodedChunksUsed<'_>,
616 table_generator: &mut PosTable::Generator,
617 erasure_coding: &ErasureCoding,
618 chunks_scratch: &mut Vec<[u8; ScalarBytes::FULL_BYTES]>,
619) where
620 PosTable: Table,
621{
622 let pos_table = table_generator.generate_parallel(pos_seed);
624
625 let parity_record_chunks = erasure_coding
627 .extend(
628 &record
629 .iter()
630 .map(|scalar_bytes| {
631 Scalar::try_from(scalar_bytes).expect(
632 "Piece getter must returns valid pieces of history that contain \
633 proper scalar bytes; qed",
634 )
635 })
636 .collect::<Vec<_>>(),
637 )
638 .expect("Instance was verified to be able to work with this many values earlier; qed")
639 .into_iter()
640 .map(<[u8; ScalarBytes::FULL_BYTES]>::from)
641 .collect::<Vec<_>>();
642 let source_record_chunks = record.to_vec();
643
644 chunks_scratch.clear();
645 (u16::from(SBucket::ZERO)..=u16::from(SBucket::MAX))
648 .into_par_iter()
649 .map(SBucket::from)
650 .zip(
651 source_record_chunks
652 .par_iter()
653 .interleave(&parity_record_chunks),
654 )
655 .map(|(s_bucket, record_chunk)| {
656 if let Some(proof) = pos_table.find_proof(s_bucket.into()) {
657 (Simd::from(*record_chunk) ^ Simd::from(*proof.hash())).to_array()
658 } else {
659 [0; ScalarBytes::FULL_BYTES]
661 }
662 })
663 .collect_into_vec(chunks_scratch);
664 let num_successfully_encoded_chunks = chunks_scratch
665 .drain(..)
666 .zip(encoded_chunks_used.iter_mut())
667 .filter_map(|(maybe_encoded_chunk, mut encoded_chunk_used)| {
668 if maybe_encoded_chunk == [0; ScalarBytes::FULL_BYTES] {
670 None
671 } else {
672 *encoded_chunk_used = true;
673
674 Some(maybe_encoded_chunk)
675 }
676 })
677 .take(record.len())
682 .zip(record.iter_mut())
683 .map(|(input_chunk, output_chunk)| {
685 *output_chunk = input_chunk;
686 })
687 .count();
688
689 source_record_chunks
692 .iter()
693 .zip(&parity_record_chunks)
694 .flat_map(|(a, b)| [a, b])
695 .zip(encoded_chunks_used.iter())
696 .filter_map(|(record_chunk, encoded_chunk_used)| {
698 if *encoded_chunk_used {
699 None
700 } else {
701 Some(record_chunk)
702 }
703 })
704 .zip(record.iter_mut().skip(num_successfully_encoded_chunks))
706 .for_each(|(input_chunk, output_chunk)| {
708 *output_chunk = *input_chunk;
709 });
710}
711
712async fn download_sector_internal<PG>(
713 pieces_to_download: &mut HashMap<PieceIndex, Vec<(&mut Record, &mut RecordMetadata)>>,
714 piece_getter: &PG,
715 kzg: &Kzg,
716 erasure_coding: &ErasureCoding,
717) -> Result<(), PlottingError>
718where
719 PG: PieceGetter + Send + Sync,
720{
721 let recovery_semaphore = &Semaphore::new(RECONSTRUCTION_CONCURRENCY_LIMIT);
724
725 let piece_indices = pieces_to_download.keys().copied().collect::<Vec<_>>();
727 let mut downloaded_pieces = piece_getter
728 .get_pieces(piece_indices)
729 .await
730 .map_err(|error| PlottingError::FailedToRetrievePieces { error })?
731 .fuse();
732 let mut reconstructed_pieces = FuturesUnordered::new();
733
734 let mut final_result = Ok(());
735
736 loop {
737 let (piece_index, result) = select! {
738 (piece_index, result) = downloaded_pieces.select_next_some() => {
739 match result {
740 Ok(Some(piece)) => (piece_index, Ok(piece)),
741 Ok(None) => {
742 trace!(%piece_index, "Piece was not found, trying reconstruction");
743
744 reconstructed_pieces.push(reconstruct_piece(
745 piece_index,
746 recovery_semaphore,
747 piece_getter,
748 kzg,
749 erasure_coding,
750 ));
751 continue;
752 }
753 Err(error) => {
754 trace!(
755 %error,
756 %piece_index,
757 "Failed to download piece, trying reconstruction"
758 );
759
760 reconstructed_pieces.push(reconstruct_piece(
761 piece_index,
762 recovery_semaphore,
763 piece_getter,
764 kzg,
765 erasure_coding,
766 ));
767 continue;
768 }
769 }
770 },
771 (piece_index, result) = reconstructed_pieces.select_next_some() => {
772 (piece_index, result)
773 },
774 complete => {
775 break;
776 }
777 };
778
779 match result {
780 Ok(piece) => {
781 process_piece(piece_index, piece, pieces_to_download);
782 }
783 Err(error) => {
784 trace!(%error, %piece_index, "Failed to download piece");
785
786 if final_result.is_ok() {
787 final_result = Err(error);
788 }
789 }
790 }
791 }
792
793 if final_result.is_ok() && !pieces_to_download.is_empty() {
794 return Err(PlottingError::FailedToRetrievePieces {
795 error: anyhow::anyhow!(
796 "Successful result, but not all pieces were downloaded, this is likely a piece \
797 getter implementation bug"
798 ),
799 });
800 }
801
802 final_result
803}
804
805async fn reconstruct_piece<PG>(
806 piece_index: PieceIndex,
807 recovery_semaphore: &Semaphore,
808 piece_getter: &PG,
809 kzg: &Kzg,
810 erasure_coding: &ErasureCoding,
811) -> (PieceIndex, Result<Piece, PlottingError>)
812where
813 PG: PieceGetter + Send + Sync,
814{
815 let _permit = recovery_semaphore.acquire().await;
816 let recovered_piece_fut = recover_missing_piece(
817 piece_getter,
818 kzg.clone(),
819 erasure_coding.clone(),
820 piece_index,
821 );
822
823 (
824 piece_index,
825 recovered_piece_fut
826 .await
827 .map_err(|error| PlottingError::PieceRecoveryFailed {
828 piece_index,
829 error: error.into(),
830 }),
831 )
832}
833
834fn process_piece(
835 piece_index: PieceIndex,
836 piece: Piece,
837 pieces_to_download: &mut HashMap<PieceIndex, Vec<(&mut Record, &mut RecordMetadata)>>,
838) {
839 for (record, metadata) in pieces_to_download.remove(&piece_index).unwrap_or_default() {
840 record
844 .as_flattened_mut()
845 .copy_from_slice(piece.record().as_flattened());
846 *metadata = RecordMetadata {
847 commitment: *piece.commitment(),
848 witness: *piece.witness(),
849 piece_checksum: blake3_hash(piece.as_ref()),
850 };
851 }
852}