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; 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; 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 =
179            [0; SectorIndex::SIZE + HistorySize::SIZE as usize + ShardCommitmentHash::SIZE];
180        bytes_to_hash[..SectorIndex::SIZE].copy_from_slice(&sector_index.to_bytes());
181        bytes_to_hash[SectorIndex::SIZE..][..HistorySize::SIZE as usize]
182            .copy_from_slice(&history_size.as_non_zero_u64().get().to_le_bytes());
183        bytes_to_hash[SectorIndex::SIZE + HistorySize::SIZE as usize..]
184            .copy_from_slice(shard_commitments_root.as_bytes());
185        // TODO: Is keyed hash really needed here?
186        Self(Blake3Hash::new(
187            single_block_keyed_hash(public_key_hash, &bytes_to_hash)
188                .expect("Less than a single block worth of bytes; qed"),
189        ))
190    }
191
192    /// Derive piece index that should be stored in sector at `piece_offset` for specified size of
193    /// blockchain history
194    pub fn derive_piece_index(
195        &self,
196        piece_offset: PieceOffset,
197        history_size: HistorySize,
198        max_pieces_in_sector: u16,
199        recent_segments: HistorySize,
200        recent_history_fraction: (HistorySize, HistorySize),
201    ) -> PieceIndex {
202        let recent_segments_in_pieces = recent_segments.in_pieces().get();
203        // Recent history must be at most `recent_history_fraction` of all history to use separate
204        // policy for recent pieces
205        let min_history_size_in_pieces = recent_segments_in_pieces
206            * recent_history_fraction.1.in_pieces().get()
207            / recent_history_fraction.0.in_pieces();
208        let input_hash = {
209            let piece_offset_bytes = piece_offset.to_bytes();
210            let mut key = [0; 32];
211            key[..piece_offset_bytes.len()].copy_from_slice(&piece_offset_bytes);
212            // TODO: Is keyed hash really needed here?
213            NanoU256::from_le_bytes(
214                single_block_keyed_hash(&key, self.as_ref())
215                    .expect("Less than a single block worth of bytes; qed"),
216            )
217        };
218        let history_size_in_pieces = history_size.in_pieces().get();
219        let num_interleaved_pieces = 1.max(
220            u64::from(max_pieces_in_sector) * recent_history_fraction.0.in_pieces().get()
221                / recent_history_fraction.1.in_pieces()
222                * 2,
223        );
224
225        let piece_index = if history_size_in_pieces > min_history_size_in_pieces
226            && u64::from(piece_offset) < num_interleaved_pieces
227            && u16::from(piece_offset) % 2 == 1
228        {
229            // For odd piece offsets at the beginning of the sector pick pieces at random from
230            // recent history only
231            (input_hash % recent_segments_in_pieces)
232                + (history_size_in_pieces - recent_segments_in_pieces)
233        } else {
234            input_hash % history_size_in_pieces
235        };
236
237        PieceIndex::from(piece_index)
238    }
239
240    /// Derive sector slot challenge for this sector from provided global challenge
241    pub fn derive_sector_slot_challenge(
242        &self,
243        global_challenge: &Blake3Hash,
244    ) -> SectorSlotChallenge {
245        let sector_slot_challenge = Simd::from(*self.0) ^ Simd::from(**global_challenge);
246        SectorSlotChallenge(sector_slot_challenge.to_array().into())
247    }
248
249    /// Derive evaluation seed
250    pub fn derive_evaluation_seed(&self, piece_offset: PieceOffset) -> PosSeed {
251        let mut bytes_to_hash = [0; Self::SIZE + PieceOffset::SIZE];
252        bytes_to_hash[..Self::SIZE].copy_from_slice(self.as_ref());
253        bytes_to_hash[Self::SIZE..].copy_from_slice(&piece_offset.to_bytes());
254        let evaluation_seed = single_block_hash(&bytes_to_hash)
255            .expect("Less than a single block worth of bytes; qed");
256
257        PosSeed::from(evaluation_seed)
258    }
259
260    /// Derive history size when sector created at `history_size` expires.
261    ///
262    /// Returns `None` on overflow.
263    pub fn derive_expiration_history_size(
264        &self,
265        history_size: HistorySize,
266        sector_expiration_check_super_segment_root: &SuperSegmentRoot,
267        min_sector_lifetime: HistorySize,
268    ) -> Option<HistorySize> {
269        let sector_expiration_check_history_size = history_size
270            .sector_expiration_check(min_sector_lifetime)?
271            .as_non_zero_u64();
272
273        let input_hash = NanoU256::from_le_bytes(
274            single_block_hash(
275                [*self.0, **sector_expiration_check_super_segment_root].as_flattened(),
276            )
277            .expect("Less than a single block worth of bytes; qed"),
278        );
279
280        let last_possible_expiration = min_sector_lifetime
281            .as_non_zero_u64()
282            .checked_add(history_size.as_non_zero_u64().get().checked_mul(4u64)?)?;
283        let expires_in = input_hash
284            % last_possible_expiration
285                .get()
286                .checked_sub(sector_expiration_check_history_size.get())?;
287
288        let expiration_history_size = sector_expiration_check_history_size.get() + expires_in;
289        let expiration_history_size = NonZeroU64::try_from(expiration_history_size).expect(
290            "History size is not zero, so result is not zero even if expires immediately; qed",
291        );
292        Some(HistorySize::new(expiration_history_size))
293    }
294}
295
296/// S-bucket used in consensus
297#[derive(
298    Debug,
299    Display,
300    Default,
301    Copy,
302    Clone,
303    Ord,
304    PartialOrd,
305    Eq,
306    PartialEq,
307    Hash,
308    Add,
309    AddAssign,
310    Sub,
311    SubAssign,
312    Mul,
313    MulAssign,
314    Div,
315    DivAssign,
316)]
317#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
318#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
319#[repr(C)]
320pub struct SBucket(u16);
321
322impl Step for SBucket {
323    #[inline]
324    fn steps_between(start: &Self, end: &Self) -> (usize, Option<usize>) {
325        u16::steps_between(&start.0, &end.0)
326    }
327
328    #[inline]
329    fn forward_checked(start: Self, count: usize) -> Option<Self> {
330        u16::forward_checked(start.0, count).map(Self)
331    }
332
333    #[inline(always)]
334    fn forward_overflowing(start: Self, count: usize) -> (Self, bool) {
335        let (n, overflowing) = u16::forward_overflowing(start.0, count);
336        (Self(n), overflowing)
337    }
338
339    #[inline]
340    fn backward_checked(start: Self, count: usize) -> Option<Self> {
341        u16::backward_checked(start.0, count).map(Self)
342    }
343
344    #[inline(always)]
345    fn backward_overflowing(start: Self, count: usize) -> (Self, bool) {
346        let (n, overflowing) = u16::backward_overflowing(start.0, count);
347        (Self(n), overflowing)
348    }
349}
350
351const impl From<u16> for SBucket {
352    #[inline(always)]
353    fn from(value: u16) -> Self {
354        Self(value)
355    }
356}
357
358const impl From<SBucket> for u16 {
359    #[inline(always)]
360    fn from(value: SBucket) -> Self {
361        value.0
362    }
363}
364
365impl TryFrom<usize> for SBucket {
366    type Error = TryFromIntError;
367
368    #[inline]
369    fn try_from(value: usize) -> Result<Self, Self::Error> {
370        Ok(Self(u16::try_from(value)?))
371    }
372}
373
374impl From<SBucket> for u32 {
375    #[inline]
376    fn from(original: SBucket) -> Self {
377        u32::from(original.0)
378    }
379}
380
381impl From<SBucket> for usize {
382    #[inline]
383    fn from(original: SBucket) -> Self {
384        usize::from(original.0)
385    }
386}
387
388impl SBucket {
389    /// S-bucket 0.
390    pub const ZERO: SBucket = SBucket(0);
391    /// Max s-bucket index
392    pub const MAX: SBucket = SBucket((Record::NUM_S_BUCKETS - 1) as u16);
393}