subspace_core_primitives/
solutions.rs

1//! Solutions-related data structures and functions.
2
3use crate::pieces::{PieceOffset, Record, RecordCommitment, RecordWitness};
4use crate::pos::{PosProof, PosSeed};
5use crate::sectors::SectorIndex;
6use crate::segments::{HistorySize, SegmentIndex};
7use crate::{PublicKey, ScalarBytes};
8use core::array::TryFromSliceError;
9use core::fmt;
10use derive_more::{AsMut, AsRef, Deref, DerefMut, From, Into};
11use num_traits::WrappingSub;
12use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
13use scale_info::TypeInfo;
14#[cfg(feature = "serde")]
15use serde::{Deserialize, Serialize};
16#[cfg(feature = "serde")]
17use serde::{Deserializer, Serializer};
18#[cfg(feature = "serde")]
19use serde_big_array::BigArray;
20use static_assertions::const_assert;
21
22// TODO: Add related methods to `SolutionRange`.
23/// Type of solution range.
24pub type SolutionRange = u64;
25
26/// Computes the following:
27/// ```text
28/// MAX * slot_probability / chunks * s_buckets / pieces
29/// ```
30pub const fn pieces_to_solution_range(pieces: u64, slot_probability: (u64, u64)) -> SolutionRange {
31    let solution_range = SolutionRange::MAX
32        // Account for slot probability
33        / slot_probability.1 * slot_probability.0
34        // Now take probability of hitting occupied s-bucket in a piece into account
35        / Record::NUM_CHUNKS as u64
36        * Record::NUM_S_BUCKETS as u64;
37
38    // Take number of pieces into account
39    solution_range / pieces
40}
41
42/// Computes the following:
43/// ```text
44/// MAX * slot_probability / chunks * s_buckets / solution_range
45/// ```
46pub const fn solution_range_to_pieces(
47    solution_range: SolutionRange,
48    slot_probability: (u64, u64),
49) -> u64 {
50    let pieces = SolutionRange::MAX
51        // Account for slot probability
52        / slot_probability.1 * slot_probability.0
53        // Now take probability of hitting occupied s-bucket in sector into account
54        / Record::NUM_CHUNKS as u64
55        * Record::NUM_S_BUCKETS as u64;
56
57    // Take solution range into account
58    pieces / solution_range
59}
60
61// Quick test to ensure functions above are the inverse of each other
62const_assert!(solution_range_to_pieces(pieces_to_solution_range(1, (1, 6)), (1, 6)) == 1);
63const_assert!(solution_range_to_pieces(pieces_to_solution_range(3, (1, 6)), (1, 6)) == 3);
64const_assert!(solution_range_to_pieces(pieces_to_solution_range(5, (1, 6)), (1, 6)) == 5);
65
66/// A Ristretto Schnorr signature as bytes produced by `schnorrkel` crate.
67#[derive(
68    Copy,
69    Clone,
70    PartialEq,
71    Eq,
72    Ord,
73    PartialOrd,
74    Hash,
75    Encode,
76    Decode,
77    TypeInfo,
78    Deref,
79    From,
80    Into,
81    DecodeWithMemTracking,
82)]
83pub struct RewardSignature([u8; RewardSignature::SIZE]);
84
85impl fmt::Debug for RewardSignature {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        write!(f, "{}", hex::encode(self.0))
88    }
89}
90
91#[cfg(feature = "serde")]
92#[derive(Serialize, Deserialize)]
93#[serde(transparent)]
94struct RewardSignatureBinary(#[serde(with = "BigArray")] [u8; RewardSignature::SIZE]);
95
96#[cfg(feature = "serde")]
97#[derive(Serialize, Deserialize)]
98#[serde(transparent)]
99struct RewardSignatureHex(#[serde(with = "hex")] [u8; RewardSignature::SIZE]);
100
101#[cfg(feature = "serde")]
102impl Serialize for RewardSignature {
103    #[inline]
104    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
105    where
106        S: Serializer,
107    {
108        if serializer.is_human_readable() {
109            RewardSignatureHex(self.0).serialize(serializer)
110        } else {
111            RewardSignatureBinary(self.0).serialize(serializer)
112        }
113    }
114}
115
116#[cfg(feature = "serde")]
117impl<'de> Deserialize<'de> for RewardSignature {
118    #[inline]
119    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
120    where
121        D: Deserializer<'de>,
122    {
123        Ok(Self(if deserializer.is_human_readable() {
124            RewardSignatureHex::deserialize(deserializer)?.0
125        } else {
126            RewardSignatureBinary::deserialize(deserializer)?.0
127        }))
128    }
129}
130
131impl AsRef<[u8]> for RewardSignature {
132    #[inline]
133    fn as_ref(&self) -> &[u8] {
134        &self.0
135    }
136}
137
138impl RewardSignature {
139    /// Reward signature size in bytes
140    pub const SIZE: usize = 64;
141}
142
143/// Witness for chunk contained within a record.
144#[derive(
145    Copy,
146    Clone,
147    Eq,
148    PartialEq,
149    Hash,
150    Deref,
151    DerefMut,
152    From,
153    Into,
154    Encode,
155    Decode,
156    TypeInfo,
157    MaxEncodedLen,
158    DecodeWithMemTracking,
159)]
160#[repr(transparent)]
161pub struct ChunkWitness([u8; ChunkWitness::SIZE]);
162
163impl fmt::Debug for ChunkWitness {
164    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165        write!(f, "{}", hex::encode(self.0))
166    }
167}
168
169#[cfg(feature = "serde")]
170#[derive(Serialize, Deserialize)]
171#[serde(transparent)]
172struct ChunkWitnessBinary(#[serde(with = "BigArray")] [u8; ChunkWitness::SIZE]);
173
174#[cfg(feature = "serde")]
175#[derive(Serialize, Deserialize)]
176#[serde(transparent)]
177struct ChunkWitnessHex(#[serde(with = "hex")] [u8; ChunkWitness::SIZE]);
178
179#[cfg(feature = "serde")]
180impl Serialize for ChunkWitness {
181    #[inline]
182    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
183    where
184        S: Serializer,
185    {
186        if serializer.is_human_readable() {
187            ChunkWitnessHex(self.0).serialize(serializer)
188        } else {
189            ChunkWitnessBinary(self.0).serialize(serializer)
190        }
191    }
192}
193
194#[cfg(feature = "serde")]
195impl<'de> Deserialize<'de> for ChunkWitness {
196    #[inline]
197    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
198    where
199        D: Deserializer<'de>,
200    {
201        Ok(Self(if deserializer.is_human_readable() {
202            ChunkWitnessHex::deserialize(deserializer)?.0
203        } else {
204            ChunkWitnessBinary::deserialize(deserializer)?.0
205        }))
206    }
207}
208
209impl Default for ChunkWitness {
210    #[inline]
211    fn default() -> Self {
212        Self([0; Self::SIZE])
213    }
214}
215
216impl TryFrom<&[u8]> for ChunkWitness {
217    type Error = TryFromSliceError;
218
219    #[inline]
220    fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
221        <[u8; Self::SIZE]>::try_from(slice).map(Self)
222    }
223}
224
225impl AsRef<[u8]> for ChunkWitness {
226    #[inline]
227    fn as_ref(&self) -> &[u8] {
228        &self.0
229    }
230}
231
232impl AsMut<[u8]> for ChunkWitness {
233    #[inline]
234    fn as_mut(&mut self) -> &mut [u8] {
235        &mut self.0
236    }
237}
238
239impl ChunkWitness {
240    /// Size of chunk witness in bytes.
241    pub const SIZE: usize = 48;
242}
243
244/// Proof-of-time verifier trait, used in abundance backports.
245pub trait SolutionPotVerifier {
246    /// Check whether proof created earlier is valid
247    fn is_proof_valid(seed: &PosSeed, challenge_index: u32, proof: &PosProof) -> bool;
248}
249
250/// Farmer solution for slot challenge.
251#[derive(Clone, Debug, Eq, PartialEq, Encode, Decode, TypeInfo, DecodeWithMemTracking)]
252#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
253#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
254pub struct Solution<RewardAddress> {
255    /// Public key of the farmer that created the solution
256    pub public_key: PublicKey,
257    /// Address for receiving block reward
258    pub reward_address: RewardAddress,
259    /// Index of the sector where solution was found
260    pub sector_index: SectorIndex,
261    /// Size of the blockchain history at time of sector creation
262    pub history_size: HistorySize,
263    /// Pieces offset within sector
264    pub piece_offset: PieceOffset,
265    /// Record commitment that can use used to verify that piece was included in blockchain history
266    pub record_commitment: RecordCommitment,
267    /// Witness for above record commitment
268    pub record_witness: RecordWitness,
269    /// Chunk at above offset
270    pub chunk: ScalarBytes,
271    /// Witness for above chunk
272    pub chunk_witness: ChunkWitness,
273    /// Proof of space for piece offset
274    pub proof_of_space: PosProof,
275}
276
277impl<RewardAddressA> Solution<RewardAddressA> {
278    /// Transform solution with one reward address type into solution with another compatible
279    /// reward address type.
280    pub fn into_reward_address_format<T, RewardAddressB>(self) -> Solution<RewardAddressB>
281    where
282        RewardAddressA: Into<T>,
283        T: Into<RewardAddressB>,
284    {
285        let Solution {
286            public_key,
287            reward_address,
288            sector_index,
289            history_size,
290            piece_offset,
291            record_commitment,
292            record_witness,
293            chunk,
294            chunk_witness,
295            proof_of_space,
296        } = self;
297        Solution {
298            public_key,
299            reward_address: Into::<T>::into(reward_address).into(),
300            sector_index,
301            history_size,
302            piece_offset,
303            record_commitment,
304            record_witness,
305            chunk,
306            chunk_witness,
307            proof_of_space,
308        }
309    }
310}
311
312impl<RewardAddress> Solution<RewardAddress> {
313    /// Dummy solution for the genesis block
314    pub fn genesis_solution(public_key: PublicKey, reward_address: RewardAddress) -> Self {
315        Self {
316            public_key,
317            reward_address,
318            sector_index: 0,
319            history_size: HistorySize::from(SegmentIndex::ZERO),
320            piece_offset: PieceOffset::default(),
321            record_commitment: RecordCommitment::default(),
322            record_witness: RecordWitness::default(),
323            chunk: ScalarBytes::default(),
324            chunk_witness: ChunkWitness::default(),
325            proof_of_space: PosProof::default(),
326        }
327    }
328}
329
330/// Bidirectional distance metric implemented on top of subtraction
331#[inline(always)]
332pub fn bidirectional_distance<T: WrappingSub + Ord>(a: &T, b: &T) -> T {
333    let diff = a.wrapping_sub(b);
334    let diff2 = b.wrapping_sub(a);
335    // Find smaller diff between 2 directions.
336    diff.min(diff2)
337}