Skip to main content

ab_archiving/
piece_reconstructor.rs

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/// Reconstructor-related instantiation error
16#[derive(Debug, Clone, PartialEq, thiserror::Error)]
17pub enum ReconstructorError {
18    /// Segment size is not bigger than record size
19    #[error("Error during data shards reconstruction: {0}")]
20    DataShardsReconstruction(#[from] ErasureCodingError),
21    /// Not enough shards
22    #[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/// Piece reconstructor helps to reconstruct missing pieces.
36#[derive(Debug, Clone)]
37pub struct PiecesReconstructor {
38    /// Erasure coding data structure
39    erasure_coding: ErasureCoding,
40}
41
42impl PiecesReconstructor {
43    /// Create a new instance
44    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        // TODO: Fix up piece metadata
60        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                    // Fancy way to insert value to avoid going through stack (if naive
86                    // dereferencing is used) and potentially causing stack overflow as the result
87                    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            // `parity_chunks` is only used for deriving a root and is fully overwritten by erasure
131            // coding, so it is reused between pieces.
132            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    /// Returns all the pieces for a segment using a given set of pieces of a segment of the
211    /// archived history.
212    ///
213    /// Any half of all pieces are required to be present, the rest will be recovered automatically
214    /// due to use of erasure coding if needed.
215    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    /// Returns the missing piece for a segment using given set of pieces of a segment of the
225    /// archived history (any half of all pieces are required to be present).
226    pub fn reconstruct_piece(
227        &self,
228        segment_pieces: &[Option<Piece>],
229        piece_position: PiecePosition,
230    ) -> Result<Piece, ReconstructorError> {
231        // TODO: Early exit if position already exists and doesn't need reconstruction
232        // TODO: It is now inefficient to recover all shards if only one piece is needed, especially
233        //  source piece
234        let pieces = self.reconstruct_shards(segment_pieces)?;
235
236        let piece = Piece::from(&pieces[piece_position]);
237
238        Ok(piece.to_shared())
239    }
240}