Skip to main content

ab_core_primitives/
sectors.rs

1//! Sectors-related data structures.
2
3#[cfg(test)]
4mod tests;
5
6use crate::hashes::Blake3Hash;
7use crate::nano_u256::NanoU256;
8use crate::pieces::{PieceIndex, PieceOffset, Record};
9use crate::pos::PosSeed;
10use crate::segments::{HistorySize, SuperSegmentRoot};
11use crate::solutions::ShardCommitmentHash;
12use ab_blake3::{single_block_hash, single_block_keyed_hash};
13use ab_io_type::trivial_type::TrivialType;
14use core::hash::Hash;
15use core::iter::Step;
16use core::num::{NonZeroU64, TryFromIntError};
17use core::simd::Simd;
18use derive_more::{Add, AddAssign, Deref, Display, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
19#[cfg(feature = "scale-codec")]
20use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
21#[cfg(feature = "serde")]
22use serde::{Deserialize, Serialize};
23
24/// Sector index in consensus
25#[derive(
26    Debug,
27    Display,
28    Default,
29    Copy,
30    Clone,
31    Ord,
32    PartialOrd,
33    Eq,
34    PartialEq,
35    Hash,
36    Add,
37    AddAssign,
38    Sub,
39    SubAssign,
40    Mul,
41    MulAssign,
42    Div,
43    DivAssign,
44    TrivialType,
45)]
46#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
47#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
48#[repr(C)]
49pub struct SectorIndex(u16);
50
51impl Step for SectorIndex {
52    #[inline(always)]
53    fn steps_between(start: &Self, end: &Self) -> (usize, Option<usize>) {
54        u16::steps_between(&start.0, &end.0)
55    }
56
57    #[inline(always)]
58    fn forward_checked(start: Self, count: usize) -> Option<Self> {
59        u16::forward_checked(start.0, count).map(Self)
60    }
61
62    #[inline(always)]
63    fn forward_overflowing(start: Self, count: usize) -> (Self, bool) {
64        let (n, overflowing) = u16::forward_overflowing(start.0, count);
65        (Self(n), overflowing)
66    }
67
68    #[inline(always)]
69    fn backward_checked(start: Self, count: usize) -> Option<Self> {
70        u16::backward_checked(start.0, count).map(Self)
71    }
72
73    #[inline(always)]
74    fn backward_overflowing(start: Self, count: usize) -> (Self, bool) {
75        let (n, overflowing) = u16::backward_overflowing(start.0, count);
76        (Self(n), overflowing)
77    }
78}
79
80const impl From<u16> for SectorIndex {
81    #[inline(always)]
82    fn from(value: u16) -> Self {
83        Self(value)
84    }
85}
86
87const impl From<SectorIndex> for u16 {
88    #[inline(always)]
89    fn from(value: SectorIndex) -> Self {
90        value.0
91    }
92}
93
94const impl From<SectorIndex> for u32 {
95    #[inline(always)]
96    fn from(original: SectorIndex) -> Self {
97        u32::from(original.0)
98    }
99}
100
101const impl From<SectorIndex> for u64 {
102    #[inline(always)]
103    fn from(original: SectorIndex) -> Self {
104        u64::from(original.0)
105    }
106}
107
108const impl From<SectorIndex> for usize {
109    #[inline(always)]
110    fn from(original: SectorIndex) -> Self {
111        usize::from(original.0)
112    }
113}
114
115impl SectorIndex {
116    /// Size in bytes
117    pub const SIZE: usize = size_of::<u16>();
118    /// Sector index 0
119    pub const ZERO: Self = Self(0);
120    /// Max sector index
121    pub const MAX: Self = Self(u16::MAX);
122
123    /// Create sector index from bytes.
124    #[inline(always)]
125    pub const fn from_bytes(bytes: [u8; const { Self::SIZE }]) -> Self {
126        Self(u16::from_le_bytes(bytes))
127    }
128
129    /// Convert sector index to bytes.
130    #[inline(always)]
131    pub const fn to_bytes(self) -> [u8; const { Self::SIZE }] {
132        self.0.to_le_bytes()
133    }
134}
135
136/// Challenge used for a particular sector for particular slot
137#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deref)]
138pub struct SectorSlotChallenge(Blake3Hash);
139
140impl SectorSlotChallenge {
141    /// Index of s-bucket within sector to be audited
142    #[inline]
143    pub fn s_bucket_audit_index(&self) -> SBucket {
144        // As long as number of s-buckets is 2^16, we can pick first two bytes instead of actually
145        // calculating `U256::from_le_bytes(self.0) % Record::NUM_S_BUCKETS)`
146        const {
147            assert!(Record::NUM_S_BUCKETS == 1 << u16::BITS as usize);
148        }
149        SBucket::from(u16::from_le_bytes([self.0[0], self.0[1]]))
150    }
151}
152
153/// Data structure representing sector ID in farmer's plot
154#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
155#[cfg_attr(feature = "scale-codec", derive(Encode, Decode))]
156#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
157pub struct SectorId(Blake3Hash);
158
159impl AsRef<[u8]> for SectorId {
160    #[inline]
161    fn as_ref(&self) -> &[u8] {
162        self.0.as_ref()
163    }
164}
165
166impl SectorId {
167    /// Size in bytes
168    const SIZE: usize = Blake3Hash::SIZE;
169
170    /// Create a new sector ID by deriving it from public key and sector index
171    #[inline]
172    pub fn new(
173        public_key_hash: &Blake3Hash,
174        shard_commitments_root: &ShardCommitmentHash,
175        sector_index: SectorIndex,
176        history_size: HistorySize,
177    ) -> Self {
178        let mut bytes_to_hash = [0; const {
179            SectorIndex::SIZE + HistorySize::SIZE as usize + ShardCommitmentHash::SIZE
180        }];
181        bytes_to_hash[..SectorIndex::SIZE].copy_from_slice(&sector_index.to_bytes());
182        bytes_to_hash[SectorIndex::SIZE..][..HistorySize::SIZE as usize]
183            .copy_from_slice(&history_size.as_non_zero_u64().get().to_le_bytes());
184        bytes_to_hash[SectorIndex::SIZE + HistorySize::SIZE as usize..]
185            .copy_from_slice(shard_commitments_root.as_bytes());
186        // TODO: Is keyed hash really needed here?
187        Self(Blake3Hash::new(
188            single_block_keyed_hash(public_key_hash, &bytes_to_hash)
189                .expect("Less than a single block worth of bytes; qed"),
190        ))
191    }
192
193    /// Derive piece index that should be stored in sector at `piece_offset` for specified size of
194    /// blockchain history
195    pub fn derive_piece_index(
196        &self,
197        piece_offset: PieceOffset,
198        history_size: HistorySize,
199        max_pieces_in_sector: u16,
200        recent_segments: HistorySize,
201        recent_history_fraction: (HistorySize, HistorySize),
202    ) -> PieceIndex {
203        let recent_segments_in_pieces = recent_segments.in_pieces().get();
204        // Recent history must be at most `recent_history_fraction` of all history to use separate
205        // policy for recent pieces
206        let min_history_size_in_pieces = recent_segments_in_pieces
207            * recent_history_fraction.1.in_pieces().get()
208            / recent_history_fraction.0.in_pieces().get();
209        let input_hash = {
210            let piece_offset_bytes = piece_offset.to_bytes();
211            let mut key = [0; 32];
212            key[..piece_offset_bytes.len()].copy_from_slice(&piece_offset_bytes);
213            // TODO: Is keyed hash really needed here?
214            NanoU256::from_le_bytes(
215                single_block_keyed_hash(&key, self.as_ref())
216                    .expect("Less than a single block worth of bytes; qed"),
217            )
218        };
219        let history_size_in_pieces = history_size.in_pieces().get();
220        let num_interleaved_pieces = 1.max(
221            u64::from(max_pieces_in_sector) * recent_history_fraction.0.in_pieces().get()
222                / recent_history_fraction.1.in_pieces().get()
223                * 2,
224        );
225
226        let piece_index = if history_size_in_pieces > min_history_size_in_pieces
227            && u64::from(piece_offset) < num_interleaved_pieces
228            && u16::from(piece_offset) % 2 == 1
229        {
230            // For odd piece offsets at the beginning of the sector pick pieces at random from
231            // recent history only
232            (input_hash % recent_segments_in_pieces)
233                + (history_size_in_pieces - recent_segments_in_pieces)
234        } else {
235            input_hash % history_size_in_pieces
236        };
237
238        PieceIndex::from(piece_index)
239    }
240
241    /// Derive sector slot challenge for this sector from provided global challenge
242    pub fn derive_sector_slot_challenge(
243        &self,
244        global_challenge: &Blake3Hash,
245    ) -> SectorSlotChallenge {
246        let sector_slot_challenge = Simd::from(*self.0) ^ Simd::from(**global_challenge);
247        SectorSlotChallenge(sector_slot_challenge.to_array().into())
248    }
249
250    /// Derive evaluation seed
251    pub fn derive_evaluation_seed(&self, piece_offset: PieceOffset) -> PosSeed {
252        let mut bytes_to_hash = [0; const { Self::SIZE + PieceOffset::SIZE }];
253        bytes_to_hash[..Self::SIZE].copy_from_slice(self.as_ref());
254        bytes_to_hash[Self::SIZE..].copy_from_slice(&piece_offset.to_bytes());
255        let evaluation_seed = single_block_hash(&bytes_to_hash)
256            .expect("Less than a single block worth of bytes; qed");
257
258        PosSeed::from(evaluation_seed)
259    }
260
261    /// Derive history size when sector created at `history_size` expires.
262    ///
263    /// Returns `None` on overflow.
264    pub fn derive_expiration_history_size(
265        &self,
266        history_size: HistorySize,
267        sector_expiration_check_super_segment_root: &SuperSegmentRoot,
268        min_sector_lifetime: HistorySize,
269    ) -> Option<HistorySize> {
270        let sector_expiration_check_history_size = history_size
271            .sector_expiration_check(min_sector_lifetime)?
272            .as_non_zero_u64();
273
274        let input_hash = NanoU256::from_le_bytes(
275            single_block_hash(
276                [*self.0, **sector_expiration_check_super_segment_root].as_flattened(),
277            )
278            .expect("Less than a single block worth of bytes; qed"),
279        );
280
281        let last_possible_expiration = min_sector_lifetime
282            .as_non_zero_u64()
283            .checked_add(history_size.as_non_zero_u64().get().checked_mul(4u64)?)?;
284        let expires_in = input_hash
285            % last_possible_expiration
286                .get()
287                .checked_sub(sector_expiration_check_history_size.get())?;
288
289        let expiration_history_size = sector_expiration_check_history_size.get() + expires_in;
290        let expiration_history_size = NonZeroU64::try_from(expiration_history_size).expect(
291            "History size is not zero, so result is not zero even if expires immediately; qed",
292        );
293        Some(HistorySize::new(expiration_history_size))
294    }
295}
296
297/// S-bucket used in consensus
298#[derive(
299    Debug,
300    Display,
301    Default,
302    Copy,
303    Clone,
304    Ord,
305    PartialOrd,
306    Eq,
307    PartialEq,
308    Hash,
309    Add,
310    AddAssign,
311    Sub,
312    SubAssign,
313    Mul,
314    MulAssign,
315    Div,
316    DivAssign,
317)]
318#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
319#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
320#[repr(C)]
321pub struct SBucket(u16);
322
323impl Step for SBucket {
324    #[inline]
325    fn steps_between(start: &Self, end: &Self) -> (usize, Option<usize>) {
326        u16::steps_between(&start.0, &end.0)
327    }
328
329    #[inline]
330    fn forward_checked(start: Self, count: usize) -> Option<Self> {
331        u16::forward_checked(start.0, count).map(Self)
332    }
333
334    #[inline(always)]
335    fn forward_overflowing(start: Self, count: usize) -> (Self, bool) {
336        let (n, overflowing) = u16::forward_overflowing(start.0, count);
337        (Self(n), overflowing)
338    }
339
340    #[inline]
341    fn backward_checked(start: Self, count: usize) -> Option<Self> {
342        u16::backward_checked(start.0, count).map(Self)
343    }
344
345    #[inline(always)]
346    fn backward_overflowing(start: Self, count: usize) -> (Self, bool) {
347        let (n, overflowing) = u16::backward_overflowing(start.0, count);
348        (Self(n), overflowing)
349    }
350}
351
352const impl From<u16> for SBucket {
353    #[inline(always)]
354    fn from(value: u16) -> Self {
355        Self(value)
356    }
357}
358
359const impl From<SBucket> for u16 {
360    #[inline(always)]
361    fn from(value: SBucket) -> Self {
362        value.0
363    }
364}
365
366impl TryFrom<usize> for SBucket {
367    type Error = TryFromIntError;
368
369    #[inline]
370    fn try_from(value: usize) -> Result<Self, Self::Error> {
371        Ok(Self(u16::try_from(value)?))
372    }
373}
374
375impl From<SBucket> for u32 {
376    #[inline]
377    fn from(original: SBucket) -> Self {
378        u32::from(original.0)
379    }
380}
381
382impl From<SBucket> for usize {
383    #[inline]
384    fn from(original: SBucket) -> Self {
385        usize::from(original.0)
386    }
387}
388
389impl SBucket {
390    /// S-bucket 0.
391    pub const ZERO: SBucket = SBucket(0);
392    /// Max s-bucket index
393    pub const MAX: SBucket = SBucket((Record::NUM_S_BUCKETS - 1) as u16);
394}