ab_core_primitives/
segments.rs

1//! Segments-related data structures.
2
3#[cfg(feature = "alloc")]
4mod archival_history_segment;
5
6use crate::block::BlockNumber;
7use crate::hashes::Blake3Hash;
8use crate::pieces::{PieceIndex, Record};
9#[cfg(feature = "alloc")]
10pub use crate::segments::archival_history_segment::ArchivedHistorySegment;
11use ab_blake3::single_chunk_hash;
12use ab_io_type::trivial_type::TrivialType;
13use ab_io_type::unaligned::Unaligned;
14#[cfg(feature = "alloc")]
15use alloc::boxed::Box;
16use blake3::CHUNK_LEN;
17use core::iter::Step;
18use core::num::{NonZeroU32, NonZeroU64};
19use core::{fmt, mem};
20use derive_more::{
21    Add, AddAssign, Deref, DerefMut, Display, Div, DivAssign, From, Into, Mul, MulAssign, Sub,
22    SubAssign,
23};
24#[cfg(feature = "scale-codec")]
25use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
26#[cfg(feature = "scale-codec")]
27use scale_info::TypeInfo;
28#[cfg(feature = "serde")]
29use serde::{Deserialize, Deserializer, Serialize, Serializer};
30#[cfg(feature = "serde")]
31use serde_big_array::BigArray;
32
33/// Super segment root contained within beacon chain block
34#[derive(Copy, Clone, Eq, PartialEq, Hash, Deref, DerefMut, From, Into, TrivialType)]
35#[cfg_attr(
36    feature = "scale-codec",
37    derive(Encode, Decode, TypeInfo, MaxEncodedLen)
38)]
39#[repr(C)]
40pub struct SuperSegmentRoot([u8; SuperSegmentRoot::SIZE]);
41
42impl fmt::Debug for SuperSegmentRoot {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        for byte in self.0 {
45            write!(f, "{byte:02x}")?;
46        }
47        Ok(())
48    }
49}
50
51#[cfg(feature = "serde")]
52#[derive(Serialize, Deserialize)]
53#[serde(transparent)]
54struct SuperSegmentRootBinary(#[serde(with = "BigArray")] [u8; SuperSegmentRoot::SIZE]);
55
56#[cfg(feature = "serde")]
57#[derive(Serialize, Deserialize)]
58#[serde(transparent)]
59struct SuperSegmentRootHex(#[serde(with = "hex")] [u8; SuperSegmentRoot::SIZE]);
60
61#[cfg(feature = "serde")]
62impl Serialize for SuperSegmentRoot {
63    #[inline]
64    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
65    where
66        S: Serializer,
67    {
68        if serializer.is_human_readable() {
69            SuperSegmentRootHex(self.0).serialize(serializer)
70        } else {
71            SuperSegmentRootBinary(self.0).serialize(serializer)
72        }
73    }
74}
75
76#[cfg(feature = "serde")]
77impl<'de> Deserialize<'de> for SuperSegmentRoot {
78    #[inline]
79    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
80    where
81        D: Deserializer<'de>,
82    {
83        Ok(Self(if deserializer.is_human_readable() {
84            SuperSegmentRootHex::deserialize(deserializer)?.0
85        } else {
86            SuperSegmentRootBinary::deserialize(deserializer)?.0
87        }))
88    }
89}
90
91impl Default for SuperSegmentRoot {
92    #[inline]
93    fn default() -> Self {
94        Self([0; Self::SIZE])
95    }
96}
97
98impl AsRef<[u8]> for SuperSegmentRoot {
99    #[inline]
100    fn as_ref(&self) -> &[u8] {
101        &self.0
102    }
103}
104
105impl AsMut<[u8]> for SuperSegmentRoot {
106    #[inline]
107    fn as_mut(&mut self) -> &mut [u8] {
108        &mut self.0
109    }
110}
111
112impl SuperSegmentRoot {
113    /// Size in bytes
114    pub const SIZE: usize = 32;
115}
116
117/// Segment index type.
118#[derive(
119    Debug,
120    Display,
121    Default,
122    Copy,
123    Clone,
124    Ord,
125    PartialOrd,
126    Eq,
127    PartialEq,
128    Hash,
129    From,
130    Into,
131    Add,
132    AddAssign,
133    Sub,
134    SubAssign,
135    Mul,
136    MulAssign,
137    Div,
138    DivAssign,
139    TrivialType,
140)]
141#[cfg_attr(
142    feature = "scale-codec",
143    derive(Encode, Decode, TypeInfo, MaxEncodedLen)
144)]
145#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
146#[repr(C)]
147pub struct SegmentIndex(u64);
148
149impl Step for SegmentIndex {
150    #[inline]
151    fn steps_between(start: &Self, end: &Self) -> (usize, Option<usize>) {
152        u64::steps_between(&start.0, &end.0)
153    }
154
155    #[inline]
156    fn forward_checked(start: Self, count: usize) -> Option<Self> {
157        u64::forward_checked(start.0, count).map(Self)
158    }
159
160    #[inline]
161    fn backward_checked(start: Self, count: usize) -> Option<Self> {
162        u64::backward_checked(start.0, count).map(Self)
163    }
164}
165
166impl SegmentIndex {
167    /// Segment index 0.
168    pub const ZERO: SegmentIndex = SegmentIndex(0);
169    /// Segment index 1.
170    pub const ONE: SegmentIndex = SegmentIndex(1);
171
172    /// Create a new instance
173    #[inline]
174    pub const fn new(n: u64) -> Self {
175        Self(n)
176    }
177
178    /// Get internal representation
179    #[inline(always)]
180    pub const fn as_u64(self) -> u64 {
181        self.0
182    }
183
184    /// Get the first piece index in this segment.
185    #[inline]
186    pub const fn first_piece_index(&self) -> PieceIndex {
187        PieceIndex::new(self.0 * RecordedHistorySegment::NUM_PIECES as u64)
188    }
189
190    /// Get the last piece index in this segment.
191    #[inline]
192    pub const fn last_piece_index(&self) -> PieceIndex {
193        PieceIndex::new((self.0 + 1) * RecordedHistorySegment::NUM_PIECES as u64 - 1)
194    }
195
196    /// List of piece indexes that belong to this segment.
197    #[inline]
198    pub fn segment_piece_indexes(&self) -> [PieceIndex; RecordedHistorySegment::NUM_PIECES] {
199        let mut piece_indices = [PieceIndex::ZERO; RecordedHistorySegment::NUM_PIECES];
200        (self.first_piece_index()..=self.last_piece_index())
201            .zip(&mut piece_indices)
202            .for_each(|(input, output)| {
203                *output = input;
204            });
205
206        piece_indices
207    }
208
209    /// Checked integer subtraction. Computes `self - rhs`, returning `None` if underflow occurred.
210    #[inline]
211    pub fn checked_sub(self, rhs: Self) -> Option<Self> {
212        self.0.checked_sub(rhs.0).map(Self)
213    }
214
215    /// Saturating integer subtraction. Computes `self - rhs`, returning zero if underflow
216    /// occurred.
217    #[inline]
218    pub const fn saturating_sub(self, rhs: Self) -> Self {
219        Self(self.0.saturating_sub(rhs.0))
220    }
221}
222
223/// Segment root contained within segment header.
224#[derive(Copy, Clone, Eq, PartialEq, Hash, Deref, DerefMut, From, Into, TrivialType)]
225#[cfg_attr(
226    feature = "scale-codec",
227    derive(Encode, Decode, TypeInfo, MaxEncodedLen)
228)]
229#[repr(C)]
230pub struct SegmentRoot([u8; SegmentRoot::SIZE]);
231
232impl fmt::Debug for SegmentRoot {
233    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234        for byte in self.0 {
235            write!(f, "{byte:02x}")?;
236        }
237        Ok(())
238    }
239}
240
241#[cfg(feature = "serde")]
242#[derive(Serialize, Deserialize)]
243#[serde(transparent)]
244struct SegmentRootBinary(#[serde(with = "BigArray")] [u8; SegmentRoot::SIZE]);
245
246#[cfg(feature = "serde")]
247#[derive(Serialize, Deserialize)]
248#[serde(transparent)]
249struct SegmentRootHex(#[serde(with = "hex")] [u8; SegmentRoot::SIZE]);
250
251#[cfg(feature = "serde")]
252impl Serialize for SegmentRoot {
253    #[inline]
254    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
255    where
256        S: Serializer,
257    {
258        if serializer.is_human_readable() {
259            SegmentRootHex(self.0).serialize(serializer)
260        } else {
261            SegmentRootBinary(self.0).serialize(serializer)
262        }
263    }
264}
265
266#[cfg(feature = "serde")]
267impl<'de> Deserialize<'de> for SegmentRoot {
268    #[inline]
269    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
270    where
271        D: Deserializer<'de>,
272    {
273        Ok(Self(if deserializer.is_human_readable() {
274            SegmentRootHex::deserialize(deserializer)?.0
275        } else {
276            SegmentRootBinary::deserialize(deserializer)?.0
277        }))
278    }
279}
280
281impl Default for SegmentRoot {
282    #[inline(always)]
283    fn default() -> Self {
284        Self([0; Self::SIZE])
285    }
286}
287
288impl AsRef<[u8]> for SegmentRoot {
289    #[inline(always)]
290    fn as_ref(&self) -> &[u8] {
291        &self.0
292    }
293}
294
295impl AsMut<[u8]> for SegmentRoot {
296    #[inline(always)]
297    fn as_mut(&mut self) -> &mut [u8] {
298        &mut self.0
299    }
300}
301
302impl SegmentRoot {
303    /// Size in bytes
304    pub const SIZE: usize = 32;
305
306    /// Convenient conversion from slice of underlying representation for efficiency purposes
307    #[inline(always)]
308    pub const fn slice_from_repr(value: &[[u8; Self::SIZE]]) -> &[Self] {
309        // SAFETY: `SegmentRoot` is `#[repr(C)]` and guaranteed to have the same memory layout
310        unsafe { mem::transmute(value) }
311    }
312
313    /// Convenient conversion to slice of underlying representation for efficiency purposes
314    #[inline(always)]
315    pub const fn repr_from_slice(value: &[Self]) -> &[[u8; Self::SIZE]] {
316        // SAFETY: `SegmentRoot` is `#[repr(C)]` and guaranteed to have the same memory layout
317        unsafe { mem::transmute(value) }
318    }
319}
320
321/// Size of blockchain history in segments.
322#[derive(
323    Debug,
324    Display,
325    Copy,
326    Clone,
327    Ord,
328    PartialOrd,
329    Eq,
330    PartialEq,
331    Hash,
332    From,
333    Into,
334    Deref,
335    DerefMut,
336    TrivialType,
337)]
338#[cfg_attr(
339    feature = "scale-codec",
340    derive(Encode, Decode, TypeInfo, MaxEncodedLen)
341)]
342#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
343#[repr(C)]
344// Storing `SegmentIndex` to make all invariants valid
345pub struct HistorySize(SegmentIndex);
346
347impl HistorySize {
348    /// History size of one
349    pub const ONE: Self = Self(SegmentIndex::ZERO);
350
351    /// Create a new instance
352    #[inline(always)]
353    pub const fn new(value: NonZeroU64) -> Self {
354        Self(SegmentIndex::new(value.get() - 1))
355    }
356
357    /// Get internal representation
358    pub const fn as_segment_index(&self) -> SegmentIndex {
359        self.0
360    }
361
362    /// Get internal representation
363    pub const fn as_non_zero_u64(&self) -> NonZeroU64 {
364        NonZeroU64::new(self.0.as_u64().saturating_add(1)).expect("Not zero; qed")
365    }
366
367    /// Size of blockchain history in pieces.
368    #[inline(always)]
369    pub const fn in_pieces(&self) -> NonZeroU64 {
370        NonZeroU64::new(
371            self.0
372                .as_u64()
373                .saturating_add(1)
374                .saturating_mul(RecordedHistorySegment::NUM_PIECES as u64),
375        )
376        .expect("Not zero; qed")
377    }
378
379    /// Segment index that corresponds to this history size.
380    #[inline(always)]
381    pub fn segment_index(&self) -> SegmentIndex {
382        self.0
383    }
384
385    /// History size at which expiration check for sector happens.
386    ///
387    /// Returns `None` on overflow.
388    #[inline(always)]
389    pub fn sector_expiration_check(&self, min_sector_lifetime: Self) -> Option<Self> {
390        self.as_non_zero_u64()
391            .checked_add(min_sector_lifetime.as_non_zero_u64().get())
392            .map(Self::new)
393    }
394}
395
396/// Progress of an archived block.
397#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, TrivialType)]
398#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, TypeInfo))]
399#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
400#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
401#[repr(C)]
402pub struct ArchivedBlockProgress {
403    /// Number of partially archived bytes of a block, `0` for full block
404    bytes: u32,
405}
406
407impl Default for ArchivedBlockProgress {
408    /// We assume a block can always fit into the segment initially, but it is definitely possible
409    /// to be transitioned into the partial state after some overflow checking.
410    #[inline(always)]
411    fn default() -> Self {
412        Self::new_complete()
413    }
414}
415
416impl ArchivedBlockProgress {
417    /// Block is archived fully
418    #[inline(always)]
419    pub const fn new_complete() -> Self {
420        Self { bytes: 0 }
421    }
422
423    /// Block is partially archived with provided number of bytes
424    #[inline(always)]
425    pub const fn new_partial(new_partial: NonZeroU32) -> Self {
426        Self {
427            bytes: new_partial.get(),
428        }
429    }
430
431    /// Return the number of partially archived bytes if the progress is not complete
432    #[inline(always)]
433    pub const fn partial(&self) -> Option<NonZeroU32> {
434        NonZeroU32::new(self.bytes)
435    }
436}
437
438/// Last archived block
439#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, TrivialType)]
440#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, TypeInfo))]
441#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
442#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
443#[repr(C)]
444pub struct LastArchivedBlock {
445    /// Block number
446    pub number: Unaligned<BlockNumber>,
447    /// Progress of an archived block.
448    pub archived_progress: ArchivedBlockProgress,
449}
450
451impl LastArchivedBlock {
452    /// Returns the number of partially archived bytes for a block.
453    #[inline(always)]
454    pub fn partial_archived(&self) -> Option<NonZeroU32> {
455        self.archived_progress.partial()
456    }
457
458    /// Sets the number of partially archived bytes if block progress was archived partially
459    #[inline(always)]
460    pub fn set_partial_archived(&mut self, new_partial: NonZeroU32) {
461        self.archived_progress = ArchivedBlockProgress::new_partial(new_partial);
462    }
463
464    /// Indicate last archived block was archived fully
465    #[inline(always)]
466    pub fn set_complete(&mut self) {
467        self.archived_progress = ArchivedBlockProgress::new_complete();
468    }
469
470    /// Get block number (unwrap `Unaligned`)
471    pub const fn number(&self) -> BlockNumber {
472        self.number.as_inner()
473    }
474}
475
476/// Segment header for a specific segment.
477///
478/// Each segment will have corresponding [`SegmentHeader`] included as the first item in the next
479/// segment. Each `SegmentHeader` includes hash of the previous one and all together form a chain of
480/// segment headers that is used for quick and efficient verification that some `Piece`
481/// corresponds to the actual archival history of the blockchain.
482#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, TrivialType)]
483#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, TypeInfo))]
484#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
485#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
486#[repr(C)]
487pub struct SegmentHeader {
488    /// Segment index
489    pub segment_index: Unaligned<SegmentIndex>,
490    /// Root of roots of all records in a segment.
491    pub segment_root: SegmentRoot,
492    /// Hash of the segment header of the previous segment
493    pub prev_segment_header_hash: Blake3Hash,
494    /// Last archived block
495    pub last_archived_block: LastArchivedBlock,
496}
497
498impl SegmentHeader {
499    /// Hash of the whole segment header
500    #[inline(always)]
501    pub fn hash(&self) -> Blake3Hash {
502        const {
503            assert!(size_of::<Self>() <= CHUNK_LEN);
504        }
505        Blake3Hash::new(
506            single_chunk_hash(self.as_bytes())
507                .expect("Less than a single chunk worth of bytes; qed"),
508        )
509    }
510
511    /// Get segment index (unwrap `Unaligned`)
512    #[inline(always)]
513    pub const fn segment_index(&self) -> SegmentIndex {
514        self.segment_index.as_inner()
515    }
516}
517
518/// Recorded history segment before archiving is applied.
519///
520/// NOTE: This is a stack-allocated data structure and can cause stack overflow!
521#[derive(Copy, Clone, Eq, PartialEq, Deref, DerefMut)]
522#[repr(C)]
523pub struct RecordedHistorySegment([Record; Self::NUM_RAW_RECORDS]);
524
525impl fmt::Debug for RecordedHistorySegment {
526    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
527        f.debug_struct("RecordedHistorySegment")
528            .finish_non_exhaustive()
529    }
530}
531
532impl Default for RecordedHistorySegment {
533    #[inline]
534    fn default() -> Self {
535        Self([Record::default(); Self::NUM_RAW_RECORDS])
536    }
537}
538
539impl AsRef<[u8]> for RecordedHistorySegment {
540    #[inline]
541    fn as_ref(&self) -> &[u8] {
542        Record::slice_to_repr(&self.0).as_flattened().as_flattened()
543    }
544}
545
546impl AsMut<[u8]> for RecordedHistorySegment {
547    #[inline]
548    fn as_mut(&mut self) -> &mut [u8] {
549        Record::slice_mut_to_repr(&mut self.0)
550            .as_flattened_mut()
551            .as_flattened_mut()
552    }
553}
554
555impl RecordedHistorySegment {
556    /// Number of raw records in one segment of recorded history.
557    pub const NUM_RAW_RECORDS: usize = 128;
558    /// Erasure coding rate for records during archiving process.
559    pub const ERASURE_CODING_RATE: (usize, usize) = (1, 2);
560    /// Number of pieces in one segment of archived history (taking erasure coding rate into
561    /// account)
562    pub const NUM_PIECES: usize =
563        Self::NUM_RAW_RECORDS * Self::ERASURE_CODING_RATE.1 / Self::ERASURE_CODING_RATE.0;
564    /// Size of recorded history segment in bytes.
565    ///
566    /// It includes half of the records (just source records) that will later be erasure coded and
567    /// together with corresponding roots and proofs will result in
568    /// [`Self::NUM_PIECES`] `Piece`s of archival history.
569    pub const SIZE: usize = Record::SIZE * Self::NUM_RAW_RECORDS;
570
571    /// Create boxed value without hitting stack overflow
572    #[inline]
573    #[cfg(feature = "alloc")]
574    pub fn new_boxed() -> Box<Self> {
575        // TODO: Should have been just `::new()`, but https://github.com/rust-lang/rust/issues/53827
576        // SAFETY: Data structure filled with zeroes is a valid invariant
577        unsafe { Box::<Self>::new_zeroed().assume_init() }
578    }
579}