subspace_core_primitives/
sectors.rs

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