Skip to main content

ab_archiving/
reconstructor.rs

1use crate::archiver::{Segment, SegmentItem};
2use ab_core_primitives::block::BlockNumber;
3use ab_core_primitives::pieces::Piece;
4use ab_core_primitives::segments::{
5    ArchivedHistorySegment, LastArchivedBlock, LocalSegmentIndex, RecordedHistorySegment,
6    SegmentHeader,
7};
8use ab_erasure_coding::{ErasureCoding, ErasureCodingError, ShardsBitmap};
9use alloc::vec::Vec;
10use core::{array, mem};
11use parity_scale_codec::Decode;
12
13/// Reconstructor-related instantiation error
14#[derive(Debug, Clone, PartialEq, thiserror::Error)]
15pub enum ReconstructorError {
16    /// Error during data shards reconstruction
17    #[error("Error during data shards reconstruction: {0}")]
18    DataShardsReconstruction(#[from] ErasureCodingError),
19    /// Not enough shards
20    #[error("Not enough shards: {num_shards}")]
21    NotEnoughShards { num_shards: usize },
22    /// Segment size is not bigger than record size
23    #[error("Error during segment decoding: {0}")]
24    SegmentDecoding(parity_scale_codec::Error),
25    /// Incorrect segment order, each next segment must directly follow the previous one
26    #[error(
27        "Incorrect segment order, expected index {expected_segment_index}, actual \
28        {actual_segment_index}"
29    )]
30    IncorrectSegmentOrder {
31        expected_segment_index: LocalSegmentIndex,
32        actual_segment_index: LocalSegmentIndex,
33    },
34    /// Parent segment header is not the first item of the segment
35    #[error("Parent segment header is not the first item of the segment")]
36    UnexpectedParentSegmentHeader,
37    /// Segment index overflow, no segment can follow the segment with the maximum index
38    #[error("Segment index overflow, no segment can follow the segment with the maximum index")]
39    SegmentIndexOverflow,
40    /// Block number overflow
41    #[error("Block number overflow")]
42    BlockNumberOverflow,
43}
44
45/// Data structure that contains information reconstructed from given segment (potentially using
46/// information from segments that were added previously)
47#[derive(Debug, Default, Clone, Eq, PartialEq)]
48pub struct ReconstructedContents {
49    /// Segment header stored in a segment
50    pub segment_header: Option<SegmentHeader>,
51    /// Reconstructed encoded blocks with their block numbers
52    pub blocks: Vec<(BlockNumber, Vec<u8>)>,
53}
54
55/// Reconstructor helps to retrieve blocks from archived pieces.
56#[derive(Debug, Clone)]
57pub struct Reconstructor {
58    /// Erasure coding data structure
59    erasure_coding: ErasureCoding,
60    /// Index of the last segment added to reconstructor
61    last_segment_index: Option<LocalSegmentIndex>,
62    /// Partially reconstructed block waiting for more data, empty if there is none
63    partial_block: Vec<u8>,
64}
65
66impl Reconstructor {
67    /// Create a new instance
68    pub fn new(erasure_coding: ErasureCoding) -> Self {
69        Self {
70            erasure_coding,
71            last_segment_index: None,
72            partial_block: Vec::new(),
73        }
74    }
75
76    /// Given a set of pieces of a segment of the archived history (any half of all pieces are
77    /// required to be present, the rest will be recovered automatically due to use of erasure
78    /// coding if needed), reconstructs and returns the segment itself.
79    ///
80    /// Does not modify the internal state of the reconstructor.
81    pub fn reconstruct_segment(
82        &self,
83        // TODO: Improve API to not use `Option` anymore
84        segment_pieces: &[Option<Piece>],
85    ) -> Result<Segment, ReconstructorError> {
86        if segment_pieces.len() < ArchivedHistorySegment::NUM_PIECES {
87            return Err(ReconstructorError::NotEnoughShards {
88                num_shards: segment_pieces.len(),
89            });
90        }
91        let mut segment_data = RecordedHistorySegment::new_boxed();
92
93        if !segment_pieces
94            .iter()
95            .zip(segment_data.iter_mut())
96            .all(|(maybe_piece, record)| {
97                if let Some(piece) = maybe_piece {
98                    // Fancy way to insert value to avoid going through stack (if naive
99                    // dereferencing is used) and potentially causing stack overflow as the result
100                    record.copy_from_slice(&*piece.record);
101                    true
102                } else {
103                    false
104                }
105            })
106        {
107            // If not all data pieces are available, need to reconstruct data shards using erasure
108            // coding
109            let (source_segment_pieces, parity_segment_pieces) =
110                segment_pieces.split_at(RecordedHistorySegment::NUM_RAW_RECORDS);
111
112            let mut source_present = ShardsBitmap::none();
113            for (index, (output_record, maybe_source_piece)) in segment_data
114                .iter_mut()
115                .zip(source_segment_pieces)
116                .enumerate()
117            {
118                if let Some(input_piece) = maybe_source_piece {
119                    // Fancy way to insert value to avoid going through stack (if naive
120                    // dereferencing is used) and potentially causing stack overflow as the result
121                    output_record.copy_from_slice(&*input_piece.record);
122                    source_present.set(index);
123                }
124            }
125
126            // Parity records are read-only here, so missing ones simply have no memory
127            let parity_records = array::from_fn(|index| {
128                parity_segment_pieces[index]
129                    .as_ref()
130                    .map(|input_piece| &input_piece.record)
131            });
132
133            #[cfg(not(feature = "parallel"))]
134            let recover_source = ErasureCoding::recover_source_scattered;
135            #[cfg(feature = "parallel")]
136            let recover_source = ErasureCoding::par_recover_source_scattered;
137
138            recover_source(
139                &self.erasure_coding,
140                {
141                    let records: &mut [_; RecordedHistorySegment::NUM_RAW_RECORDS] =
142                        segment_data.as_mut();
143                    let mut records = records.iter_mut();
144
145                    array::from_fn::<_, { RecordedHistorySegment::NUM_RAW_RECORDS }, _>(|_| {
146                        records
147                            .next()
148                            .expect("Number of records matches the array size; qed")
149                    })
150                },
151                &source_present,
152                parity_records,
153            )?;
154        }
155
156        let segment = Segment::decode(&mut AsRef::<[u8]>::as_ref(segment_data.as_ref()))
157            .map_err(ReconstructorError::SegmentDecoding)?;
158
159        Ok(segment)
160    }
161
162    /// Given a set of pieces of a segment of the archived history (any half of all pieces are
163    /// required to be present, the rest will be recovered automatically due to use of erasure
164    /// coding if needed), reconstructs and returns segment header and a list of encoded blocks with
165    /// corresponding block numbers.
166    ///
167    /// The reconstructor is consumed and returned together with reconstructed contents if the
168    /// segment was added successfully. It is possible to start with any segment, but when next
169    /// segment is pushed, it needs to follow the previous one or else error will be returned.
170    pub fn add_segment(
171        mut self,
172        segment_pieces: &[Option<Piece>],
173    ) -> Result<(Self, ReconstructedContents), ReconstructorError> {
174        let segment = self.reconstruct_segment(segment_pieces)?;
175        let mut segment_items = segment.items.into_iter().peekable();
176
177        let mut reconstructed_contents = ReconstructedContents::default();
178        let mut next_block_number = BlockNumber::ZERO;
179
180        // Only the first segment has no parent segment header, all other segments start with it
181        let segment_index = if let Some(&SegmentItem::ParentSegmentHeader(parent_segment_header)) =
182            segment_items.peek()
183        {
184            segment_items.next();
185
186            let LastArchivedBlock {
187                number,
188                archived_progress,
189            } = parent_segment_header.last_archived_block;
190
191            reconstructed_contents
192                .segment_header
193                .replace(parent_segment_header);
194
195            match archived_progress.partial() {
196                None => {
197                    // The last archived block ended exactly with the previous segment, so if its
198                    // beginning was reconstructed from previous segments, it is complete now
199                    if !self.partial_block.is_empty() {
200                        reconstructed_contents
201                            .blocks
202                            .push((number.as_inner(), mem::take(&mut self.partial_block)));
203                    }
204
205                    next_block_number = number
206                        .as_inner()
207                        .checked_add(BlockNumber::ONE)
208                        .ok_or(ReconstructorError::BlockNumberOverflow)?;
209                }
210                Some(_bytes) => {
211                    next_block_number = number.as_inner();
212
213                    if self.partial_block.is_empty() {
214                        // Will not be able to recover full block, bump right away
215                        next_block_number = next_block_number
216                            .checked_add(BlockNumber::ONE)
217                            .ok_or(ReconstructorError::BlockNumberOverflow)?;
218                    }
219                }
220            }
221
222            parent_segment_header
223                .index
224                .as_inner()
225                .checked_add(LocalSegmentIndex::ONE)
226                .ok_or(ReconstructorError::SegmentIndexOverflow)?
227        } else {
228            LocalSegmentIndex::ZERO
229        };
230
231        if let Some(last_segment_index) = self.last_segment_index {
232            let expected_segment_index = last_segment_index
233                .checked_add(LocalSegmentIndex::ONE)
234                .ok_or(ReconstructorError::SegmentIndexOverflow)?;
235
236            if segment_index != expected_segment_index {
237                return Err(ReconstructorError::IncorrectSegmentOrder {
238                    expected_segment_index,
239                    actual_segment_index: segment_index,
240                });
241            }
242        }
243
244        self.last_segment_index.replace(segment_index);
245
246        for segment_item in segment_items {
247            #[expect(
248                clippy::rest_pattern_accessible_field,
249                reason = "Do not need other fields"
250            )]
251            match segment_item {
252                SegmentItem::Padding => {
253                    // Doesn't contain anything
254                }
255                SegmentItem::Block { bytes, .. } => {
256                    if !self.partial_block.is_empty() {
257                        reconstructed_contents
258                            .blocks
259                            .push((next_block_number, mem::take(&mut self.partial_block)));
260
261                        next_block_number = next_block_number
262                            .checked_add(BlockNumber::ONE)
263                            .ok_or(ReconstructorError::BlockNumberOverflow)?;
264                    }
265
266                    reconstructed_contents
267                        .blocks
268                        .push((next_block_number, Vec::from(bytes)));
269
270                    next_block_number = next_block_number
271                        .checked_add(BlockNumber::ONE)
272                        .ok_or(ReconstructorError::BlockNumberOverflow)?;
273                }
274                SegmentItem::BlockStart { bytes, .. } => {
275                    if !self.partial_block.is_empty() {
276                        reconstructed_contents
277                            .blocks
278                            .push((next_block_number, mem::take(&mut self.partial_block)));
279
280                        next_block_number = next_block_number
281                            .checked_add(BlockNumber::ONE)
282                            .ok_or(ReconstructorError::BlockNumberOverflow)?;
283                    }
284
285                    self.partial_block = Vec::from(bytes);
286                }
287                SegmentItem::BlockContinuation { bytes, .. } => {
288                    if self.partial_block.is_empty() {
289                        // This is continuation from previous segment, we don't have the beginning
290                        // of the block to continue.
291                        continue;
292                    }
293
294                    self.partial_block.extend_from_slice(&bytes);
295                }
296                SegmentItem::ParentSegmentHeader(_) => {
297                    return Err(ReconstructorError::UnexpectedParentSegmentHeader);
298                }
299            }
300        }
301
302        Ok((self, reconstructed_contents))
303    }
304}