1use ab_core_primitives::pieces::{
2 InnerPiece, Piece, PieceHeader, PiecePosition, Record, RecordChunksRoot, RecordProof,
3 SegmentProof,
4};
5use ab_core_primitives::segments::{
6 ArchivedHistorySegment, LocalSegmentIndex, RecordedHistorySegment, SegmentPosition,
7 SegmentRoot, SuperSegmentIndex,
8};
9use ab_core_primitives::shard::ShardIndex;
10use ab_erasure_coding::{ErasureCoding, ErasureCodingError, ShardsPresent};
11use ab_merkle_tree::blake3::BalancedMerkleTree;
12#[cfg(feature = "parallel")]
13use rayon::prelude::*;
14
15#[derive(Debug, Clone, PartialEq, thiserror::Error)]
17pub enum ReconstructorError {
18 #[error("Error during data shards reconstruction: {0}")]
20 DataShardsReconstruction(#[from] ErasureCodingError),
21 #[error("Not enough shards: {num_shards}")]
23 NotEnoughShards { num_shards: usize },
24}
25
26struct SharedPieceDetails {
27 shard_index: ShardIndex,
28 local_segment_index: LocalSegmentIndex,
29 super_segment_index: SuperSegmentIndex,
30 segment_position: SegmentPosition,
31 segment_root: SegmentRoot,
32 segment_proof: SegmentProof,
33}
34
35#[derive(Debug, Clone)]
37pub struct PiecesReconstructor {
38 erasure_coding: ErasureCoding,
40}
41
42impl PiecesReconstructor {
43 pub fn new(erasure_coding: ErasureCoding) -> Self {
45 Self { erasure_coding }
46 }
47
48 fn reconstruct_shards(
49 &self,
50 input_pieces: &[Option<Piece>],
51 ) -> Result<ArchivedHistorySegment, ReconstructorError> {
52 if input_pieces.len() < ArchivedHistorySegment::NUM_PIECES {
53 return Err(ReconstructorError::NotEnoughShards {
54 num_shards: input_pieces.len(),
55 });
56 }
57 let mut reconstructed_pieces = ArchivedHistorySegment::default();
58
59 let mut shared_piece_details = None;
61 {
62 let (source_input_pieces, parity_input_pieces) =
63 input_pieces.split_at(RecordedHistorySegment::NUM_RAW_RECORDS);
64 let (source_reconstructed_pieces, parity_reconstructed_pieces) =
65 reconstructed_pieces.split_at_mut(RecordedHistorySegment::NUM_RAW_RECORDS);
66
67 let mut present = ShardsPresent::none();
68
69 for (index, (maybe_input_piece, output_piece)) in source_input_pieces
70 .iter()
71 .zip(source_reconstructed_pieces.iter_mut())
72 .enumerate()
73 {
74 if let Some(input_piece) = maybe_input_piece {
75 if shared_piece_details.is_none() {
76 shared_piece_details.replace(SharedPieceDetails {
77 shard_index: input_piece.header.shard_index.as_inner(),
78 local_segment_index: input_piece.header.local_segment_index.as_inner(),
79 super_segment_index: input_piece.header.super_segment_index.as_inner(),
80 segment_position: input_piece.header.segment_position.as_inner(),
81 segment_root: input_piece.header.segment_root,
82 segment_proof: input_piece.header.segment_proof,
83 });
84 }
85 output_piece.record.copy_from_slice(&*input_piece.record);
88 present.source.set(index);
89 }
90 }
91
92 for (index, (maybe_input_piece, output_piece)) in parity_input_pieces
93 .iter()
94 .zip(parity_reconstructed_pieces.iter_mut())
95 .enumerate()
96 {
97 if let Some(input_piece) = maybe_input_piece {
98 output_piece.record.copy_from_slice(&*input_piece.record);
99 present.parity.set(index);
100 }
101 }
102
103 let (source_records, parity_records) = reconstructed_pieces.split_records_mut();
104
105 #[cfg(not(feature = "parallel"))]
106 let recover_all = ErasureCoding::recover_all_scattered;
107 #[cfg(feature = "parallel")]
108 let recover_all = ErasureCoding::par_recover_all_scattered;
109
110 recover_all(
111 &self.erasure_coding,
112 source_records,
113 parity_records,
114 &present,
115 )?;
116 }
117 let SharedPieceDetails {
118 shard_index,
119 local_segment_index,
120 super_segment_index,
121 segment_position,
122 segment_root,
123 segment_proof,
124 } = shared_piece_details.expect(
125 "Sucessful recovery means there was at least one piece to fill this Option; qed",
126 );
127
128 let mut record_roots = [[0; _]; ArchivedHistorySegment::NUM_PIECES];
129 {
130 let process_piece = |parity_chunks: &mut Record,
133 piece: &mut InnerPiece,
134 maybe_input_piece: &Option<Piece>,
135 output_record_root: &mut [u8; _]| {
136 let (record_root, parity_chunks_root) = if let Some(input_piece) = maybe_input_piece
137 {
138 (
139 *input_piece.record_root(),
140 *input_piece.header.parity_chunks_root,
141 )
142 } else {
143 self.erasure_coding.extend(&piece.record, parity_chunks)?;
144
145 let source_chunks_root = *piece.record.source_chunks_root();
146 let parity_chunks_root = BalancedMerkleTree::compute_root_only(parity_chunks);
147
148 let record_root =
149 BalancedMerkleTree::new(&[source_chunks_root, parity_chunks_root]).root();
150
151 (record_root, parity_chunks_root)
152 };
153
154 piece.header.parity_chunks_root = RecordChunksRoot::from(parity_chunks_root);
155 *output_record_root = record_root;
156
157 Ok::<_, ReconstructorError>(())
158 };
159
160 #[cfg(not(feature = "parallel"))]
161 {
162 let mut parity_chunks = Record::new_boxed();
163 reconstructed_pieces
164 .iter_mut()
165 .zip(input_pieces)
166 .zip(&mut record_roots)
167 .try_for_each(|((piece, maybe_input_piece), output_record_root)| {
168 process_piece(
169 &mut parity_chunks,
170 piece,
171 maybe_input_piece,
172 output_record_root,
173 )
174 })?;
175 }
176 #[cfg(feature = "parallel")]
177 reconstructed_pieces
178 .par_iter_mut()
179 .zip_eq(input_pieces)
180 .zip_eq(&mut record_roots)
181 .try_for_each_init(
182 Record::new_boxed,
183 |parity_chunks, ((piece, maybe_input_piece), output_record_root)| {
184 process_piece(parity_chunks, piece, maybe_input_piece, output_record_root)
185 },
186 )?;
187 }
188
189 let segment_merkle_tree = BalancedMerkleTree::new_boxed(&record_roots);
190
191 reconstructed_pieces
192 .iter_mut()
193 .zip(segment_merkle_tree.all_proofs())
194 .for_each(|(piece, record_proof)| {
195 piece.header = PieceHeader {
196 shard_index: shard_index.into(),
197 local_segment_index: local_segment_index.into(),
198 super_segment_index: super_segment_index.into(),
199 segment_position: segment_position.into(),
200 segment_root,
201 segment_proof,
202 parity_chunks_root: piece.header.parity_chunks_root,
203 record_proof: RecordProof::from(record_proof),
204 };
205 });
206
207 Ok(reconstructed_pieces)
208 }
209
210 pub fn reconstruct_segment(
216 &self,
217 segment_pieces: &[Option<Piece>],
218 ) -> Result<ArchivedHistorySegment, ReconstructorError> {
219 let pieces = self.reconstruct_shards(segment_pieces)?;
220
221 Ok(pieces.to_shared())
222 }
223
224 pub fn reconstruct_piece(
227 &self,
228 segment_pieces: &[Option<Piece>],
229 piece_position: PiecePosition,
230 ) -> Result<Piece, ReconstructorError> {
231 let pieces = self.reconstruct_shards(segment_pieces)?;
235
236 let piece = Piece::from(&pieces[piece_position]);
237
238 Ok(piece.to_shared())
239 }
240}