Skip to main content

subspace_core_primitives/
sectors.rs

1//! Sectors-related data structures.
2
3#[cfg(test)]
4mod tests;
5
6use crate::U256;
7use crate::hashes::{
8    Blake3Hash, blake3_hash_list, blake3_hash_list_with_key, blake3_hash_with_key,
9};
10use crate::pieces::{PieceIndex, PieceOffset, Record};
11use crate::pos::PosSeed;
12use crate::segments::{HistorySize, SegmentCommitment};
13use core::hash::Hash;
14use core::iter::Step;
15use core::num::{NonZeroU64, TryFromIntError};
16use core::simd::Simd;
17use derive_more::{Add, AddAssign, Deref, Display, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
18use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
19use scale_info::TypeInfo;
20#[cfg(feature = "serde")]
21use serde::{Deserialize, Serialize};
22use static_assertions::const_assert_eq;
23
24/// Sector index in consensus
25pub type SectorIndex = u16;
26
27/// Challenge used for a particular sector for particular slot
28#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deref)]
29pub struct SectorSlotChallenge(Blake3Hash);
30
31impl SectorSlotChallenge {
32    /// Index of s-bucket within sector to be audited
33    #[inline]
34    pub fn s_bucket_audit_index(&self) -> SBucket {
35        // As long as number of s-buckets is 2^16, we can pick first two bytes instead of actually
36        // calculating `U256::from_le_bytes(self.0) % Record::NUM_S_BUCKETS)`
37        const_assert_eq!(Record::NUM_S_BUCKETS, 1 << u16::BITS as usize);
38        SBucket::from(u16::from_le_bytes([self.0[0], self.0[1]]))
39    }
40}
41
42/// Data structure representing sector ID in farmer's plot
43#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Encode, Decode, TypeInfo)]
44#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
45pub struct SectorId(Blake3Hash);
46
47impl AsRef<[u8]> for SectorId {
48    #[inline]
49    fn as_ref(&self) -> &[u8] {
50        self.0.as_ref()
51    }
52}
53
54impl SectorId {
55    /// Create new sector ID by deriving it from public key and sector index
56    pub fn new(
57        public_key_hash: Blake3Hash,
58        sector_index: SectorIndex,
59        history_size: HistorySize,
60    ) -> Self {
61        Self(blake3_hash_list_with_key(
62            &public_key_hash,
63            &[
64                &sector_index.to_le_bytes(),
65                &history_size.get().to_le_bytes(),
66            ],
67        ))
68    }
69
70    /// Derive piece index that should be stored in sector at `piece_offset` for specified size of
71    /// blockchain history
72    pub fn derive_piece_index(
73        &self,
74        piece_offset: PieceOffset,
75        history_size: HistorySize,
76        max_pieces_in_sector: u16,
77        recent_segments: HistorySize,
78        recent_history_fraction: (HistorySize, HistorySize),
79    ) -> PieceIndex {
80        let recent_segments_in_pieces = recent_segments.in_pieces().get();
81        // Recent history must be at most `recent_history_fraction` of all history to use separate
82        // policy for recent pieces
83        let min_history_size_in_pieces = recent_segments_in_pieces
84            * recent_history_fraction.1.in_pieces().get()
85            / recent_history_fraction.0.in_pieces().get();
86        let input_hash = {
87            let piece_offset_bytes = piece_offset.to_bytes();
88            let mut key = [0; 32];
89            key[..piece_offset_bytes.len()].copy_from_slice(&piece_offset_bytes);
90            U256::from_le_bytes(*blake3_hash_with_key(&key, self.as_ref()))
91        };
92        let history_size_in_pieces = history_size.in_pieces().get();
93        let num_interleaved_pieces = 1.max(
94            u64::from(max_pieces_in_sector) * recent_history_fraction.0.in_pieces().get()
95                / recent_history_fraction.1.in_pieces().get()
96                * 2,
97        );
98
99        let piece_index = if history_size_in_pieces > min_history_size_in_pieces
100            && u64::from(piece_offset) < num_interleaved_pieces
101            && u16::from(piece_offset) % 2 == 1
102        {
103            // For odd piece offsets at the beginning of the sector pick pieces at random from
104            // recent history only
105            input_hash % U256::from(recent_segments_in_pieces)
106                + U256::from(history_size_in_pieces - recent_segments_in_pieces)
107        } else {
108            input_hash % U256::from(history_size_in_pieces)
109        };
110
111        PieceIndex::from(u64::try_from(piece_index).expect(
112            "Remainder of division by PieceIndex is guaranteed to fit into PieceIndex; qed",
113        ))
114    }
115
116    /// Derive sector slot challenge for this sector from provided global challenge
117    pub fn derive_sector_slot_challenge(
118        &self,
119        global_challenge: &Blake3Hash,
120    ) -> SectorSlotChallenge {
121        let sector_slot_challenge = Simd::from(*self.0) ^ Simd::from(**global_challenge);
122        SectorSlotChallenge(sector_slot_challenge.to_array().into())
123    }
124
125    /// Derive evaluation seed
126    pub fn derive_evaluation_seed(&self, piece_offset: PieceOffset) -> PosSeed {
127        let evaluation_seed = blake3_hash_list(&[self.as_ref(), &piece_offset.to_bytes()]);
128
129        PosSeed::from(*evaluation_seed)
130    }
131
132    /// Derive history size when sector created at `history_size` expires.
133    ///
134    /// Returns `None` on overflow.
135    pub fn derive_expiration_history_size(
136        &self,
137        history_size: HistorySize,
138        sector_expiration_check_segment_commitment: &SegmentCommitment,
139        min_sector_lifetime: HistorySize,
140    ) -> Option<HistorySize> {
141        let sector_expiration_check_history_size =
142            history_size.sector_expiration_check(min_sector_lifetime)?;
143
144        let input_hash = U256::from_le_bytes(*blake3_hash_list(&[
145            self.as_ref(),
146            sector_expiration_check_segment_commitment.as_ref(),
147        ]));
148
149        let last_possible_expiration =
150            min_sector_lifetime.checked_add(history_size.get().checked_mul(4u64)?)?;
151        let expires_in = input_hash
152            % U256::from(
153                last_possible_expiration
154                    .get()
155                    .checked_sub(sector_expiration_check_history_size.get())?,
156            );
157        let expires_in = u64::try_from(expires_in).expect("Number modulo u64 fits into u64; qed");
158
159        let expiration_history_size = sector_expiration_check_history_size.get() + expires_in;
160        let expiration_history_size = NonZeroU64::try_from(expiration_history_size).expect(
161            "History size is not zero, so result is not zero even if expires immediately; qed",
162        );
163        Some(HistorySize::from(expiration_history_size))
164    }
165}
166
167/// S-bucket used in consensus
168#[derive(
169    Debug,
170    Display,
171    Default,
172    Copy,
173    Clone,
174    Ord,
175    PartialOrd,
176    Eq,
177    PartialEq,
178    Hash,
179    Encode,
180    Decode,
181    Add,
182    AddAssign,
183    Sub,
184    SubAssign,
185    Mul,
186    MulAssign,
187    Div,
188    DivAssign,
189    TypeInfo,
190    MaxEncodedLen,
191)]
192#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
193#[repr(transparent)]
194pub struct SBucket(u16);
195
196impl Step for SBucket {
197    #[inline]
198    fn steps_between(start: &Self, end: &Self) -> (usize, Option<usize>) {
199        u16::steps_between(&start.0, &end.0)
200    }
201
202    #[inline]
203    fn forward_checked(start: Self, count: usize) -> Option<Self> {
204        u16::forward_checked(start.0, count).map(Self)
205    }
206
207    #[inline]
208    fn backward_checked(start: Self, count: usize) -> Option<Self> {
209        u16::backward_checked(start.0, count).map(Self)
210    }
211}
212
213impl TryFrom<usize> for SBucket {
214    type Error = TryFromIntError;
215
216    #[inline]
217    fn try_from(value: usize) -> Result<Self, Self::Error> {
218        Ok(Self(u16::try_from(value)?))
219    }
220}
221
222impl From<u16> for SBucket {
223    #[inline]
224    fn from(original: u16) -> Self {
225        Self(original)
226    }
227}
228
229impl From<SBucket> for u16 {
230    #[inline]
231    fn from(original: SBucket) -> Self {
232        original.0
233    }
234}
235
236impl From<SBucket> for u32 {
237    #[inline]
238    fn from(original: SBucket) -> Self {
239        u32::from(original.0)
240    }
241}
242
243impl From<SBucket> for usize {
244    #[inline]
245    fn from(original: SBucket) -> Self {
246        usize::from(original.0)
247    }
248}
249
250impl SBucket {
251    /// S-bucket 0.
252    pub const ZERO: SBucket = SBucket(0);
253    /// Max s-bucket index
254    pub const MAX: SBucket = SBucket((Record::NUM_S_BUCKETS - 1) as u16);
255}