1use crate::auditing::ChunkCandidate;
7use crate::reading::{
8 ReadSectorRecordChunksMode, ReadingError, read_record_metadata, read_sector_record_chunks,
9};
10use crate::sector::{
11 SectorContentsMap, SectorContentsMapFromBytesError, SectorMetadataChecksummed,
12};
13use crate::{ReadAt, ReadAtSync};
14use futures::FutureExt;
15use std::collections::VecDeque;
16use std::io;
17use subspace_core_primitives::pieces::{PieceOffset, Record};
18use subspace_core_primitives::pos::PosSeed;
19use subspace_core_primitives::sectors::{SBucket, SectorId};
20use subspace_core_primitives::segments::HistorySize;
21use subspace_core_primitives::solutions::{ChunkWitness, Solution, SolutionRange};
22use subspace_core_primitives::{PublicKey, ScalarBytes};
23use subspace_erasure_coding::ErasureCoding;
24use subspace_kzg::Kzg;
25use subspace_proof_of_space::Table;
26use thiserror::Error;
27
28pub trait ProvableSolutions: ExactSizeIterator {
32 fn best_solution_distance(&self) -> Option<SolutionRange>;
34}
35
36#[derive(Debug, Error)]
38pub enum ProvingError {
39 #[error("Invalid erasure coding instance")]
41 InvalidErasureCodingInstance,
42 #[error("Failed to create polynomial for record at offset {piece_offset}: {error}")]
44 FailedToCreatePolynomialForRecord {
45 piece_offset: PieceOffset,
47 error: String,
49 },
50 #[error(
52 "Failed to create chunk witness for record at offset {piece_offset} chunk {chunk_offset}: \
53 {error}"
54 )]
55 FailedToCreateChunkWitness {
56 piece_offset: PieceOffset,
58 chunk_offset: u32,
60 error: String,
62 },
63 #[error("Failed to decode sector contents map: {0}")]
65 FailedToDecodeSectorContentsMap(#[from] SectorContentsMapFromBytesError),
66 #[error("Proving I/O error: {0}")]
68 Io(#[from] io::Error),
69 #[error("Record reading error: {0}")]
71 RecordReadingError(#[from] ReadingError),
72}
73
74impl ProvingError {
75 pub fn is_fatal(&self) -> bool {
77 match self {
78 ProvingError::InvalidErasureCodingInstance => true,
79 ProvingError::FailedToCreatePolynomialForRecord { .. } => false,
80 ProvingError::FailedToCreateChunkWitness { .. } => false,
81 ProvingError::FailedToDecodeSectorContentsMap(_) => false,
82 ProvingError::Io(_) => true,
83 ProvingError::RecordReadingError(error) => error.is_fatal(),
84 }
85 }
86}
87
88#[derive(Debug, Clone)]
89struct WinningChunk {
90 chunk_offset: u32,
92 piece_offset: PieceOffset,
94 solution_distance: SolutionRange,
96}
97
98#[derive(Debug)]
103pub struct SolutionCandidates<'a, Sector>
104where
105 Sector: 'a,
106{
107 public_key: &'a PublicKey,
108 sector_id: SectorId,
109 s_bucket: SBucket,
110 sector: Sector,
111 sector_metadata: &'a SectorMetadataChecksummed,
112 chunk_candidates: VecDeque<ChunkCandidate>,
113}
114
115impl<'a, Sector> Clone for SolutionCandidates<'a, Sector>
116where
117 Sector: Clone + 'a,
118{
119 fn clone(&self) -> Self {
120 Self {
121 public_key: self.public_key,
122 sector_id: self.sector_id,
123 s_bucket: self.s_bucket,
124 sector: self.sector.clone(),
125 sector_metadata: self.sector_metadata,
126 chunk_candidates: self.chunk_candidates.clone(),
127 }
128 }
129}
130
131impl<'a, Sector> SolutionCandidates<'a, Sector>
132where
133 Sector: ReadAtSync + 'a,
134{
135 pub(crate) fn new(
136 public_key: &'a PublicKey,
137 sector_id: SectorId,
138 s_bucket: SBucket,
139 sector: Sector,
140 sector_metadata: &'a SectorMetadataChecksummed,
141 chunk_candidates: VecDeque<ChunkCandidate>,
142 ) -> Self {
143 Self {
144 public_key,
145 sector_id,
146 s_bucket,
147 sector,
148 sector_metadata,
149 chunk_candidates,
150 }
151 }
152
153 pub fn len(&self) -> usize {
155 self.chunk_candidates.len()
156 }
157
158 pub fn is_empty(&self) -> bool {
160 self.chunk_candidates.is_empty()
161 }
162
163 pub fn history_size(&self) -> HistorySize {
165 self.sector_metadata.history_size
166 }
167
168 pub fn into_solutions<RewardAddress, PosTable, TableGenerator>(
170 self,
171 reward_address: &'a RewardAddress,
172 kzg: &'a Kzg,
173 erasure_coding: &'a ErasureCoding,
174 mode: ReadSectorRecordChunksMode,
175 table_generator: TableGenerator,
176 ) -> Result<impl ProvableSolutions<Item = MaybeSolution<RewardAddress>> + 'a, ProvingError>
177 where
178 RewardAddress: Copy,
179 PosTable: Table,
180 TableGenerator: (FnMut(&PosSeed) -> PosTable) + 'a,
181 {
182 SolutionsIterator::<'a, _, PosTable, _, _>::new(
183 self.public_key,
184 reward_address,
185 self.sector_id,
186 self.s_bucket,
187 self.sector,
188 self.sector_metadata,
189 kzg,
190 erasure_coding,
191 self.chunk_candidates,
192 mode,
193 table_generator,
194 )
195 }
196}
197
198type MaybeSolution<RewardAddress> = Result<Solution<RewardAddress>, ProvingError>;
199
200struct SolutionsIterator<'a, RewardAddress, PosTable, TableGenerator, Sector>
201where
202 Sector: ReadAtSync + 'a,
203 PosTable: Table,
204 TableGenerator: (FnMut(&PosSeed) -> PosTable) + 'a,
205{
206 public_key: &'a PublicKey,
207 reward_address: &'a RewardAddress,
208 sector_id: SectorId,
209 s_bucket: SBucket,
210 sector_metadata: &'a SectorMetadataChecksummed,
211 s_bucket_offsets: Box<[u32; Record::NUM_S_BUCKETS]>,
212 kzg: &'a Kzg,
213 erasure_coding: &'a ErasureCoding,
214 sector_contents_map: SectorContentsMap,
215 sector: ReadAt<Sector, !>,
216 winning_chunks: VecDeque<WinningChunk>,
217 count: usize,
218 best_solution_distance: Option<SolutionRange>,
219 mode: ReadSectorRecordChunksMode,
220 table_generator: TableGenerator,
221}
222
223impl<'a, RewardAddress, PosTable, TableGenerator, Sector> ExactSizeIterator
224 for SolutionsIterator<'a, RewardAddress, PosTable, TableGenerator, Sector>
225where
226 RewardAddress: Copy,
227 Sector: ReadAtSync + 'a,
228 PosTable: Table,
229 TableGenerator: (FnMut(&PosSeed) -> PosTable) + 'a,
230{
231}
232
233impl<'a, RewardAddress, PosTable, TableGenerator, Sector> Iterator
234 for SolutionsIterator<'a, RewardAddress, PosTable, TableGenerator, Sector>
235where
236 RewardAddress: Copy,
237 Sector: ReadAtSync + 'a,
238 PosTable: Table,
239 TableGenerator: (FnMut(&PosSeed) -> PosTable) + 'a,
240{
241 type Item = MaybeSolution<RewardAddress>;
242
243 fn next(&mut self) -> Option<Self::Item> {
244 let WinningChunk {
245 chunk_offset,
246 piece_offset,
247 solution_distance: _,
248 } = self.winning_chunks.pop_front()?;
249
250 self.count -= 1;
251
252 let pos_table =
254 (self.table_generator)(&self.sector_id.derive_evaluation_seed(piece_offset));
255
256 let maybe_solution: Result<_, ProvingError> = try {
257 let sector_record_chunks_fut = read_sector_record_chunks(
258 piece_offset,
259 self.sector_metadata.pieces_in_sector,
260 &self.s_bucket_offsets,
261 &self.sector_contents_map,
262 &pos_table,
263 &self.sector,
264 self.mode,
265 );
266 let sector_record_chunks = sector_record_chunks_fut
267 .now_or_never()
268 .expect("Sync reader; qed")
269 .map_err(ProvingError::RecordReadingError)?;
270
271 let chunk = ScalarBytes::from(
272 sector_record_chunks
273 .get(usize::from(self.s_bucket))
274 .expect("Within s-bucket range; qed")
275 .expect("Winning chunk was plotted; qed"),
276 );
277
278 let source_chunks_polynomial = self
279 .erasure_coding
280 .recover_poly(sector_record_chunks.as_slice())
281 .map_err(|error| ReadingError::FailedToErasureDecodeRecord {
282 piece_offset,
283 error,
284 })
285 .map_err(ProvingError::RecordReadingError)?;
286 drop(sector_record_chunks);
287
288 let record_metadata_fut = read_record_metadata(
291 piece_offset,
292 self.sector_metadata.pieces_in_sector,
293 &self.sector,
294 );
295 let record_metadata = record_metadata_fut
296 .now_or_never()
297 .expect("Sync reader; qed")
298 .map_err(ProvingError::RecordReadingError)?;
299
300 let proof_of_space = pos_table.find_proof(self.s_bucket.into()).expect(
301 "Quality exists for this s-bucket, otherwise it wouldn't be a winning chunk; qed",
302 );
303
304 let chunk_witness = self
305 .kzg
306 .create_witness(
307 &source_chunks_polynomial,
308 Record::NUM_S_BUCKETS,
309 self.s_bucket.into(),
310 )
311 .map_err(|error| ProvingError::FailedToCreateChunkWitness {
312 piece_offset,
313 chunk_offset,
314 error,
315 })?;
316
317 Solution {
318 public_key: *self.public_key,
319 reward_address: *self.reward_address,
320 sector_index: self.sector_metadata.sector_index,
321 history_size: self.sector_metadata.history_size,
322 piece_offset,
323 record_commitment: record_metadata.commitment,
324 record_witness: record_metadata.witness,
325 chunk,
326 chunk_witness: ChunkWitness::from(chunk_witness),
327 proof_of_space,
328 }
329 };
330
331 match maybe_solution {
332 Ok(solution) => Some(Ok(solution)),
333 Err(error) => Some(Err(error)),
334 }
335 }
336
337 fn size_hint(&self) -> (usize, Option<usize>) {
338 (self.count, Some(self.count))
339 }
340}
341
342impl<'a, RewardAddress, PosTable, TableGenerator, Sector> ProvableSolutions
343 for SolutionsIterator<'a, RewardAddress, PosTable, TableGenerator, Sector>
344where
345 RewardAddress: Copy,
346 Sector: ReadAtSync + 'a,
347 PosTable: Table,
348 TableGenerator: (FnMut(&PosSeed) -> PosTable) + 'a,
349{
350 fn best_solution_distance(&self) -> Option<SolutionRange> {
351 self.best_solution_distance
352 }
353}
354
355impl<'a, RewardAddress, PosTable, TableGenerator, Sector>
356 SolutionsIterator<'a, RewardAddress, PosTable, TableGenerator, Sector>
357where
358 RewardAddress: Copy,
359 Sector: ReadAtSync + 'a,
360 PosTable: Table,
361 TableGenerator: (FnMut(&PosSeed) -> PosTable) + 'a,
362{
363 #[allow(clippy::too_many_arguments)]
364 fn new(
365 public_key: &'a PublicKey,
366 reward_address: &'a RewardAddress,
367 sector_id: SectorId,
368 s_bucket: SBucket,
369 sector: Sector,
370 sector_metadata: &'a SectorMetadataChecksummed,
371 kzg: &'a Kzg,
372 erasure_coding: &'a ErasureCoding,
373 chunk_candidates: VecDeque<ChunkCandidate>,
374 mode: ReadSectorRecordChunksMode,
375 table_generator: TableGenerator,
376 ) -> Result<Self, ProvingError> {
377 if erasure_coding.max_shards() < Record::NUM_S_BUCKETS {
378 return Err(ProvingError::InvalidErasureCodingInstance);
379 }
380
381 let sector_contents_map = {
382 let mut sector_contents_map_bytes =
383 vec![0; SectorContentsMap::encoded_size(sector_metadata.pieces_in_sector)];
384
385 sector.read_at(&mut sector_contents_map_bytes, 0)?;
386
387 SectorContentsMap::from_bytes(
388 §or_contents_map_bytes,
389 sector_metadata.pieces_in_sector,
390 )?
391 };
392
393 let s_bucket_records = sector_contents_map
394 .iter_s_bucket_records(s_bucket)
395 .expect("S-bucket audit index is guaranteed to be in range; qed")
396 .collect::<Vec<_>>();
397 let winning_chunks = chunk_candidates
398 .into_iter()
399 .filter_map(move |chunk_candidate| {
400 let (piece_offset, encoded_chunk_used) = s_bucket_records
401 .get(chunk_candidate.chunk_offset as usize)
402 .expect("Wouldn't be a candidate if wasn't within s-bucket; qed");
403
404 encoded_chunk_used.then_some(WinningChunk {
405 chunk_offset: chunk_candidate.chunk_offset,
406 piece_offset: *piece_offset,
407 solution_distance: chunk_candidate.solution_distance,
408 })
409 })
410 .collect::<VecDeque<_>>();
411
412 let best_solution_distance = winning_chunks
413 .front()
414 .map(|winning_chunk| winning_chunk.solution_distance);
415
416 let s_bucket_offsets = sector_metadata.s_bucket_offsets();
417
418 let count = winning_chunks.len();
419
420 Ok(Self {
421 public_key,
422 reward_address,
423 sector_id,
424 s_bucket,
425 sector_metadata,
426 s_bucket_offsets,
427 kzg,
428 erasure_coding,
429 sector_contents_map,
430 sector: ReadAt::from_sync(sector),
431 winning_chunks,
432 count,
433 best_solution_distance,
434 mode,
435 table_generator,
436 })
437 }
438}