1use crate::auditing::ChunkCandidate;
7use crate::reading::{
8 ReadingError, read_record_metadata, read_sector_record_chunks, recover_extended_record_chunks,
9};
10use crate::sector::{
11 SectorContentsMap, SectorContentsMapFromBytesError, SectorMetadataChecksummed,
12};
13use crate::{ReadAt, ReadAtSync};
14use ab_core_primitives::hashes::Blake3Hash;
15use ab_core_primitives::pieces::{PieceOffset, Record, RecordChunk};
16use ab_core_primitives::pos::PosSeed;
17use ab_core_primitives::sectors::{SBucket, SectorId};
18use ab_core_primitives::solutions::{ChunkProof, Solution, SolutionDistance};
19use ab_erasure_coding::ErasureCoding;
20use ab_merkle_tree::balanced::BalancedMerkleTree;
21use ab_proof_of_space::PosProofs;
22use futures::FutureExt;
23use std::collections::VecDeque;
24use std::io;
25use thiserror::Error;
26
27pub trait ProvableSolutions: ExactSizeIterator {
31 fn best_solution_distance(&self) -> Option<SolutionDistance>;
33}
34
35#[derive(Debug, Error)]
37pub enum ProvingError {
38 #[error("Failed to create polynomial for record at offset {piece_offset}: {error}")]
40 FailedToCreatePolynomialForRecord {
41 piece_offset: PieceOffset,
43 error: String,
45 },
46 #[error("Failed to decode sector contents map: {0}")]
48 FailedToDecodeSectorContentsMap(#[from] SectorContentsMapFromBytesError),
49 #[error("Proving I/O error: {0}")]
51 Io(#[from] io::Error),
52 #[error("Record reading error: {0}")]
54 RecordReadingError(#[from] ReadingError),
55}
56
57impl ProvingError {
58 pub fn is_fatal(&self) -> bool {
60 match self {
61 ProvingError::FailedToCreatePolynomialForRecord { .. } => false,
62 ProvingError::FailedToDecodeSectorContentsMap(_) => false,
63 ProvingError::Io(_) => true,
64 ProvingError::RecordReadingError(error) => error.is_fatal(),
65 }
66 }
67}
68
69#[derive(Debug, Clone)]
70struct WinningChunk {
71 piece_offset: PieceOffset,
73 solution_distance: SolutionDistance,
75}
76
77#[derive(Debug)]
82pub struct SolutionCandidates<'a, Sector>
83where
84 Sector: 'a,
85{
86 public_key_hash: &'a Blake3Hash,
87 sector_id: SectorId,
88 s_bucket: SBucket,
89 sector: Sector,
90 sector_metadata: &'a SectorMetadataChecksummed,
91 chunk_candidates: VecDeque<ChunkCandidate>,
92}
93
94impl<'a, Sector> Clone for SolutionCandidates<'a, Sector>
95where
96 Sector: Clone + 'a,
97{
98 fn clone(&self) -> Self {
99 Self {
100 public_key_hash: self.public_key_hash,
101 sector_id: self.sector_id,
102 s_bucket: self.s_bucket,
103 sector: self.sector.clone(),
104 sector_metadata: self.sector_metadata,
105 chunk_candidates: self.chunk_candidates.clone(),
106 }
107 }
108}
109
110impl<'a, Sector> SolutionCandidates<'a, Sector>
111where
112 Sector: ReadAtSync + 'a,
113{
114 pub(crate) fn new(
115 public_key_hash: &'a Blake3Hash,
116 sector_id: SectorId,
117 s_bucket: SBucket,
118 sector: Sector,
119 sector_metadata: &'a SectorMetadataChecksummed,
120 chunk_candidates: VecDeque<ChunkCandidate>,
121 ) -> Self {
122 Self {
123 public_key_hash,
124 sector_id,
125 s_bucket,
126 sector,
127 sector_metadata,
128 chunk_candidates,
129 }
130 }
131
132 pub fn len(&self) -> usize {
134 self.chunk_candidates.len()
135 }
136
137 pub fn is_empty(&self) -> bool {
139 self.chunk_candidates.is_empty()
140 }
141
142 pub fn into_solutions<PosProofGenerator>(
144 self,
145 erasure_coding: &'a ErasureCoding,
146 table_generator: PosProofGenerator,
147 ) -> Result<impl ProvableSolutions<Item = MaybeSolution> + 'a, ProvingError>
148 where
149 PosProofGenerator: (FnMut(&PosSeed) -> Box<PosProofs>) + 'a,
150 {
151 SolutionsIterator::<'a, _, _>::new(
152 self.public_key_hash,
153 self.sector_id,
154 self.s_bucket,
155 self.sector,
156 self.sector_metadata,
157 erasure_coding,
158 self.chunk_candidates,
159 table_generator,
160 )
161 }
162}
163
164type MaybeSolution = Result<Solution, ProvingError>;
165
166struct SolutionsIterator<'a, PosProofGenerator, Sector>
167where
168 Sector: ReadAtSync + 'a,
169 PosProofGenerator: (FnMut(&PosSeed) -> Box<PosProofs>) + 'a,
170{
171 public_key_hash: &'a Blake3Hash,
172 sector_id: SectorId,
173 s_bucket: SBucket,
174 sector_metadata: &'a SectorMetadataChecksummed,
175 s_bucket_offsets: Box<[u32; Record::NUM_S_BUCKETS]>,
176 erasure_coding: &'a ErasureCoding,
177 sector_contents_map: SectorContentsMap,
178 sector: ReadAt<Sector, !>,
179 winning_chunks: VecDeque<WinningChunk>,
180 count: usize,
181 best_solution_distance: Option<SolutionDistance>,
182 table_generator: PosProofGenerator,
183}
184
185impl<'a, PosProofGenerator, Sector> ExactSizeIterator
186 for SolutionsIterator<'a, PosProofGenerator, Sector>
187where
188 Sector: ReadAtSync + 'a,
189 PosProofGenerator: (FnMut(&PosSeed) -> Box<PosProofs>) + 'a,
190{
191}
192
193impl<'a, PosProofGenerator, Sector> Iterator for SolutionsIterator<'a, PosProofGenerator, Sector>
194where
195 Sector: ReadAtSync + 'a,
196 PosProofGenerator: (FnMut(&PosSeed) -> Box<PosProofs>) + 'a,
197{
198 type Item = MaybeSolution;
199
200 fn next(&mut self) -> Option<Self::Item> {
201 let WinningChunk {
202 piece_offset,
203 solution_distance: _,
204 } = self.winning_chunks.pop_front()?;
205
206 self.count -= 1;
207
208 let pos_proofs =
210 (self.table_generator)(&self.sector_id.derive_evaluation_seed(piece_offset));
211
212 let maybe_solution: Result<_, ProvingError> = try {
213 let sector_record_chunks_fut = read_sector_record_chunks(
214 piece_offset,
215 self.sector_metadata.pieces_in_sector,
216 &self.s_bucket_offsets,
217 &self.sector_contents_map,
218 &pos_proofs,
219 &self.sector,
220 );
221 let sector_record_chunks = sector_record_chunks_fut
222 .now_or_never()
223 .expect("Sync reader; qed")?;
224
225 let chunk = sector_record_chunks
226 .get(usize::from(self.s_bucket))
227 .expect("Within s-bucket range; qed")
228 .expect("Winning chunk was plotted; qed");
229
230 let chunks = recover_extended_record_chunks(
231 §or_record_chunks,
232 piece_offset,
233 self.erasure_coding,
234 )?;
235 drop(sector_record_chunks);
236
237 const _: () = {
241 assert!(Record::NUM_S_BUCKETS == 65536);
242 };
243 let record_merkle_tree = BalancedMerkleTree::<65536>::new_boxed(
244 RecordChunk::slice_to_repr(chunks.as_slice())
245 .try_into()
246 .expect("Statically guaranteed to have correct length; qed"),
247 );
248
249 let record_metadata_fut = read_record_metadata(
252 piece_offset,
253 self.sector_metadata.pieces_in_sector,
254 &self.sector,
255 );
256 let record_metadata = record_metadata_fut
257 .now_or_never()
258 .expect("Sync reader; qed")?;
259
260 let proof_of_space = pos_proofs.for_s_bucket(self.s_bucket).expect(
261 "Proof exists for this s-bucket, otherwise it wouldn't be a winning chunk; qed",
262 );
263
264 let chunk_proof = record_merkle_tree
265 .all_proofs()
266 .nth(usize::from(self.s_bucket))
267 .expect("Chunk offset is valid, hence corresponding proof exists; qed");
268
269 Solution {
270 public_key_hash: *self.public_key_hash,
271 record_root: record_metadata.root,
272 record_proof: record_metadata.proof,
273 chunk,
274 chunk_proof: ChunkProof::from(chunk_proof),
275 proof_of_space,
276 history_size: self.sector_metadata.history_size,
277 sector_index: self.sector_metadata.sector_index,
278 piece_offset,
279 padding: [0; _],
280 }
281 };
282
283 match maybe_solution {
284 Ok(solution) => Some(Ok(solution)),
285 Err(error) => Some(Err(error)),
286 }
287 }
288
289 fn size_hint(&self) -> (usize, Option<usize>) {
290 (self.count, Some(self.count))
291 }
292}
293
294impl<'a, PosProofGenerator, Sector> ProvableSolutions
295 for SolutionsIterator<'a, PosProofGenerator, Sector>
296where
297 Sector: ReadAtSync + 'a,
298 PosProofGenerator: (FnMut(&PosSeed) -> Box<PosProofs>) + 'a,
299{
300 fn best_solution_distance(&self) -> Option<SolutionDistance> {
301 self.best_solution_distance
302 }
303}
304
305impl<'a, PosProofGenerator, Sector> SolutionsIterator<'a, PosProofGenerator, Sector>
306where
307 Sector: ReadAtSync + 'a,
308 PosProofGenerator: (FnMut(&PosSeed) -> Box<PosProofs>) + 'a,
309{
310 #[allow(clippy::too_many_arguments)]
311 fn new(
312 public_key_hash: &'a Blake3Hash,
313 sector_id: SectorId,
314 s_bucket: SBucket,
315 sector: Sector,
316 sector_metadata: &'a SectorMetadataChecksummed,
317 erasure_coding: &'a ErasureCoding,
318 chunk_candidates: VecDeque<ChunkCandidate>,
319 table_generator: PosProofGenerator,
320 ) -> Result<Self, ProvingError> {
321 let sector_contents_map = {
322 let mut sector_contents_map_bytes =
323 vec![0; SectorContentsMap::encoded_size(sector_metadata.pieces_in_sector)];
324
325 sector.read_at(&mut sector_contents_map_bytes, 0)?;
326
327 SectorContentsMap::from_bytes(
328 §or_contents_map_bytes,
329 sector_metadata.pieces_in_sector,
330 )?
331 };
332
333 let s_bucket_piece_offsets = sector_contents_map
334 .iter_s_bucket_piece_offsets(s_bucket)
335 .expect("S-bucket audit index is guaranteed to be in range; qed")
336 .collect::<Vec<_>>();
337 let winning_chunks = chunk_candidates
338 .into_iter()
339 .map(move |chunk_candidate| {
340 let piece_offset = s_bucket_piece_offsets
341 .get(chunk_candidate.chunk_offset as usize)
342 .expect("Wouldn't be a candidate if wasn't within s-bucket; qed");
343
344 WinningChunk {
345 piece_offset: *piece_offset,
346 solution_distance: chunk_candidate.solution_distance,
347 }
348 })
349 .collect::<VecDeque<_>>();
350
351 let best_solution_distance = winning_chunks
352 .front()
353 .map(|winning_chunk| winning_chunk.solution_distance);
354
355 let s_bucket_offsets = sector_metadata.s_bucket_offsets();
356
357 let count = winning_chunks.len();
358
359 Ok(Self {
360 public_key_hash,
361 sector_id,
362 s_bucket,
363 sector_metadata,
364 s_bucket_offsets,
365 erasure_coding,
366 sector_contents_map,
367 sector: ReadAt::from_sync(sector),
368 winning_chunks,
369 count,
370 best_solution_distance,
371 table_generator,
372 })
373 }
374}