masp_primitives/
consensus.rs

1//! Consensus logic and parameters.
2
3use borsh::schema::add_definition;
4use borsh::schema::Definition;
5use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
6use memuse::DynamicUsage;
7use std::cmp::{Ord, Ordering};
8use std::collections::BTreeMap;
9use std::convert::TryFrom;
10use std::fmt;
11use std::io::{Error, ErrorKind, Read, Write};
12use std::ops::{Add, Bound, RangeBounds, Sub};
13
14/// A wrapper type representing blockchain heights. Safe conversion from
15/// various integer types, as well as addition and subtraction, are provided.
16#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
17#[repr(transparent)]
18#[derive(
19    Clone, Copy, Debug, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize, BorshSchema,
20)]
21pub struct BlockHeight(u32);
22
23memuse::impl_no_dynamic_usage!(BlockHeight);
24
25pub const H0: BlockHeight = BlockHeight(0);
26
27impl BlockHeight {
28    pub const fn from_u32(v: u32) -> BlockHeight {
29        BlockHeight(v)
30    }
31}
32
33impl fmt::Display for BlockHeight {
34    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
35        self.0.fmt(formatter)
36    }
37}
38
39impl Ord for BlockHeight {
40    fn cmp(&self, other: &Self) -> Ordering {
41        self.0.cmp(&other.0)
42    }
43}
44
45impl PartialOrd for BlockHeight {
46    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
47        Some(self.cmp(other))
48    }
49}
50
51impl From<u32> for BlockHeight {
52    fn from(value: u32) -> Self {
53        BlockHeight(value)
54    }
55}
56
57impl From<BlockHeight> for u32 {
58    fn from(value: BlockHeight) -> u32 {
59        value.0
60    }
61}
62
63impl TryFrom<u64> for BlockHeight {
64    type Error = std::num::TryFromIntError;
65
66    fn try_from(value: u64) -> Result<Self, Self::Error> {
67        u32::try_from(value).map(BlockHeight)
68    }
69}
70
71impl From<BlockHeight> for u64 {
72    fn from(value: BlockHeight) -> u64 {
73        value.0 as u64
74    }
75}
76
77impl TryFrom<i32> for BlockHeight {
78    type Error = std::num::TryFromIntError;
79
80    fn try_from(value: i32) -> Result<Self, Self::Error> {
81        u32::try_from(value).map(BlockHeight)
82    }
83}
84
85impl TryFrom<i64> for BlockHeight {
86    type Error = std::num::TryFromIntError;
87
88    fn try_from(value: i64) -> Result<Self, Self::Error> {
89        u32::try_from(value).map(BlockHeight)
90    }
91}
92
93impl From<BlockHeight> for i64 {
94    fn from(value: BlockHeight) -> i64 {
95        value.0 as i64
96    }
97}
98
99impl Add<u32> for BlockHeight {
100    type Output = Self;
101
102    fn add(self, other: u32) -> Self {
103        BlockHeight(self.0 + other)
104    }
105}
106
107impl Add for BlockHeight {
108    type Output = Self;
109
110    fn add(self, other: Self) -> Self {
111        self + other.0
112    }
113}
114
115impl Sub<u32> for BlockHeight {
116    type Output = Self;
117
118    fn sub(self, other: u32) -> Self {
119        if other > self.0 {
120            panic!("Subtraction resulted in negative block height.");
121        }
122
123        BlockHeight(self.0 - other)
124    }
125}
126
127impl Sub for BlockHeight {
128    type Output = Self;
129
130    fn sub(self, other: Self) -> Self {
131        self - other.0
132    }
133}
134
135/// MASP consensus parameters.
136pub trait Parameters: Clone {
137    /// Returns the activation height for a particular network upgrade,
138    /// if an activation height has been set.
139    fn activation_height(&self, nu: NetworkUpgrade) -> Option<BlockHeight>;
140
141    /// Determines whether the specified network upgrade is active as of the
142    /// provided block height on the network to which this Parameters value applies.
143    fn is_nu_active(&self, nu: NetworkUpgrade, height: BlockHeight) -> bool {
144        self.activation_height(nu).map_or(false, |h| h <= height)
145    }
146}
147
148/// Marker struct for the production network.
149#[derive(PartialEq, Eq, Copy, Clone, Debug)]
150pub struct MainNetwork;
151
152memuse::impl_no_dynamic_usage!(MainNetwork);
153
154pub const MAIN_NETWORK: MainNetwork = MainNetwork;
155
156impl Parameters for MainNetwork {
157    fn activation_height(&self, nu: NetworkUpgrade) -> Option<BlockHeight> {
158        match nu {
159            NetworkUpgrade::MASP => Some(H0),
160        }
161    }
162}
163
164/// Marker struct for the test network.
165#[derive(PartialEq, Eq, Copy, Clone, Debug)]
166pub struct TestNetwork;
167
168memuse::impl_no_dynamic_usage!(TestNetwork);
169
170pub const TEST_NETWORK: TestNetwork = TestNetwork;
171
172impl Parameters for TestNetwork {
173    fn activation_height(&self, nu: NetworkUpgrade) -> Option<BlockHeight> {
174        match nu {
175            NetworkUpgrade::MASP => Some(BlockHeight(1)), // Activate MASP at height 1 so pre-ZIP 212 tests work at height 0
176        }
177    }
178}
179
180#[derive(PartialEq, Eq, Copy, Clone, Debug)]
181pub enum Network {
182    MainNetwork,
183    TestNetwork,
184}
185
186memuse::impl_no_dynamic_usage!(Network);
187
188impl Parameters for Network {
189    fn activation_height(&self, nu: NetworkUpgrade) -> Option<BlockHeight> {
190        match self {
191            Network::MainNetwork => MAIN_NETWORK.activation_height(nu),
192            Network::TestNetwork => TEST_NETWORK.activation_height(nu),
193        }
194    }
195}
196
197/// An event that occurs at a specified height on the Zcash chain, at which point the
198/// consensus rules enforced by the network are altered.
199///
200/// See [ZIP 200](https://zips.z.cash/zip-0200) for more details.
201#[derive(Clone, Copy, Debug)]
202pub enum NetworkUpgrade {
203    /// The [MASP] network upgrade.
204    ///
205    /// [MASP]: https://github.com/anoma/masp/
206    MASP,
207}
208
209memuse::impl_no_dynamic_usage!(NetworkUpgrade);
210
211impl fmt::Display for NetworkUpgrade {
212    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213        match self {
214            NetworkUpgrade::MASP => write!(f, "MASP"),
215        }
216    }
217}
218
219impl NetworkUpgrade {
220    fn branch_id(self) -> BranchId {
221        match self {
222            NetworkUpgrade::MASP => BranchId::MASP,
223        }
224    }
225}
226
227/// The network upgrades on the Zcash chain in order of activation.
228///
229/// This order corresponds to the activation heights, but because Rust enums are
230/// full-fledged algebraic data types, we need to define it manually.
231const UPGRADES_IN_ORDER: &[NetworkUpgrade] = &[NetworkUpgrade::MASP];
232
233pub const ZIP212_GRACE_PERIOD: u32 = 0;
234
235/// A globally-unique identifier for a set of consensus rules within the Zcash chain.
236///
237/// Each branch ID in this enum corresponds to one of the epochs between a pair of Zcash
238/// network upgrades. For example, `BranchId::Overwinter` corresponds to the blocks
239/// starting at Overwinter activation, and ending the block before Sapling activation.
240///
241/// The main use of the branch ID is in signature generation: transactions commit to a
242/// specific branch ID by including it as part of [`signature_hash`]. This ensures
243/// two-way replay protection for transactions across network upgrades.
244///
245/// See [ZIP 200](https://zips.z.cash/zip-0200) for more details.
246///
247/// [`signature_hash`]: crate::transaction::sighash::signature_hash
248#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
249#[derive(Clone, Copy, Debug, PartialEq, Eq)]
250pub enum BranchId {
251    /// The consensus rules deployed by [`NetworkUpgrade::MASP`].
252    MASP,
253}
254
255memuse::impl_no_dynamic_usage!(BranchId);
256
257impl TryFrom<u32> for BranchId {
258    type Error = &'static str;
259
260    fn try_from(value: u32) -> Result<Self, Self::Error> {
261        match value {
262            0xe9ff_75a6 => Ok(BranchId::MASP),
263            _ => Err("Unknown consensus branch ID"),
264        }
265    }
266}
267
268impl From<BranchId> for u32 {
269    fn from(consensus_branch_id: BranchId) -> u32 {
270        match consensus_branch_id {
271            BranchId::MASP => 0xe9ff_75a6,
272        }
273    }
274}
275
276impl BorshSerialize for BranchId {
277    fn serialize<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
278        u32::from(*self).serialize(writer)
279    }
280}
281
282impl BorshDeserialize for BranchId {
283    fn deserialize_reader<R: Read>(reader: &mut R) -> std::io::Result<Self> {
284        u32::deserialize_reader(reader)?
285            .try_into()
286            .map_err(|x| Error::new(ErrorKind::InvalidInput, x))
287    }
288}
289
290impl BorshSchema for BranchId {
291    fn add_definitions_recursively(
292        definitions: &mut BTreeMap<borsh::schema::Declaration, borsh::schema::Definition>,
293    ) {
294        let definition = Definition::Enum {
295            tag_width: 4,
296            variants: vec![(0xe9ff_75a6, "MASP".into(), <()>::declaration())],
297        };
298        add_definition(Self::declaration(), definition, definitions);
299        <()>::add_definitions_recursively(definitions);
300    }
301
302    fn declaration() -> borsh::schema::Declaration {
303        "BranchId".into()
304    }
305}
306
307impl BranchId {
308    /// Returns the branch ID corresponding to the consensus rule set that is active at
309    /// the given height.
310    ///
311    /// This is the branch ID that should be used when creating transactions.
312    pub fn for_height<P: Parameters>(parameters: &P, height: BlockHeight) -> Self {
313        for nu in UPGRADES_IN_ORDER.iter().rev() {
314            if parameters.is_nu_active(*nu, height) {
315                return nu.branch_id();
316            }
317        }
318
319        // Sapling rules apply before any network upgrade
320        BranchId::MASP
321    }
322
323    /// Returns the range of heights for the consensus epoch associated with this branch id.
324    ///
325    /// The resulting tuple implements the [`RangeBounds<BlockHeight>`] trait.
326    pub fn height_range<P: Parameters>(&self, params: &P) -> Option<impl RangeBounds<BlockHeight>> {
327        self.height_bounds(params).map(|(lower, upper)| {
328            (
329                Bound::Included(lower),
330                upper.map_or(Bound::Unbounded, Bound::Excluded),
331            )
332        })
333    }
334
335    /// Returns the range of heights for the consensus epoch associated with this branch id.
336    ///
337    /// The return type of this value is slightly more precise than [`Self::height_range`]:
338    /// - `Some((x, Some(y)))` means that the consensus rules corresponding to this branch id
339    ///   are in effect for the range `x..y`
340    /// - `Some((x, None))` means that the consensus rules corresponding to this branch id are
341    ///   in effect for the range `x..`
342    /// - `None` means that the consensus rules corresponding to this branch id are never in effect.
343    pub fn height_bounds<P: Parameters>(
344        &self,
345        params: &P,
346    ) -> Option<(BlockHeight, Option<BlockHeight>)> {
347        match self {
348            BranchId::MASP => params.activation_height(NetworkUpgrade::MASP).map(|lower| {
349                let upper = None;
350                (lower, upper)
351            }),
352        }
353    }
354}
355
356#[cfg(any(test, feature = "test-dependencies"))]
357pub mod testing {
358    use proptest::sample::select;
359    use proptest::strategy::{Just, Strategy};
360
361    use super::{BlockHeight, BranchId, Parameters};
362
363    pub fn arb_branch_id() -> impl Strategy<Value = BranchId> {
364        select(vec![BranchId::MASP])
365    }
366
367    pub fn arb_height<P: Parameters>(
368        branch_id: BranchId,
369        params: &P,
370    ) -> impl Strategy<Value = Option<BlockHeight>> {
371        branch_id
372            .height_bounds(params)
373            .map_or(Strategy::boxed(Just(None)), |(lower, upper)| {
374                Strategy::boxed(
375                    (lower.0..upper.map_or(u32::MAX, |u| u.0)).prop_map(|h| Some(BlockHeight(h))),
376                )
377            })
378    }
379}
380
381#[cfg(test)]
382mod tests {
383    use std::convert::TryFrom;
384
385    use super::{
386        BlockHeight, BranchId, NetworkUpgrade, Parameters, MAIN_NETWORK, UPGRADES_IN_ORDER,
387    };
388
389    #[test]
390    fn nu_ordering() {
391        for i in 1..UPGRADES_IN_ORDER.len() {
392            let nu_a = UPGRADES_IN_ORDER[i - 1];
393            let nu_b = UPGRADES_IN_ORDER[i];
394            match (
395                MAIN_NETWORK.activation_height(nu_a),
396                MAIN_NETWORK.activation_height(nu_b),
397            ) {
398                (Some(a), Some(b)) if a < b => (),
399                (Some(_), None) => (),
400                (None, None) => (),
401                _ => panic!(
402                    "{} should not be before {} in UPGRADES_IN_ORDER",
403                    nu_a, nu_b
404                ),
405            }
406        }
407    }
408
409    #[test]
410    fn nu_is_active() {
411        assert!(MAIN_NETWORK.is_nu_active(NetworkUpgrade::MASP, BlockHeight(0)));
412    }
413
414    #[test]
415    fn branch_id_from_u32() {
416        assert_eq!(BranchId::try_from(3925833126), Ok(BranchId::MASP));
417        assert!(BranchId::try_from(1).is_err());
418    }
419
420    #[test]
421    fn branch_id_for_height() {
422        assert_eq!(
423            BranchId::for_height(&MAIN_NETWORK, BlockHeight(0)),
424            BranchId::MASP,
425        );
426    }
427}