Skip to main content

zebra_chain/parameters/
network.rs

1//! Consensus parameters for each Zcash network.
2
3use std::{fmt, str::FromStr, sync::Arc};
4
5use thiserror::Error;
6
7use crate::{
8    amount::{Amount, NonNegative},
9    block::{self, Height},
10    parameters::NetworkUpgrade,
11    transparent,
12};
13
14mod error;
15pub mod magic;
16pub mod subsidy;
17pub mod testnet;
18
19#[cfg(test)]
20mod tests;
21
22// Mainnet temporary Orchard-disabling soft-fork height, shipped publicly in Zebra v4.5.3.
23// This is DISTINCT from the NU6.2 *activation* (re-enable) height (3_364_600, see
24// `network_upgrade.rs`), which lands 1_174 blocks later. Do NOT change this value: it is
25// already deployed, so changing it would fork from live v4.5.3 nodes in the disable window.
26const MAINNET_TEMPORARY_ORCHARD_DISABLING_SOFT_FORK_HEIGHT: Height = Height(3_363_426);
27
28// Default Testnet temporary Orchard-disabling soft-fork height. As on Mainnet, this is DISTINCT
29// from the NU6.2 *activation* (re-enable) height (4_052_000, see `network_upgrade.rs`), which
30// lands 3_500 blocks later.
31const TESTNET_TEMPORARY_ORCHARD_DISABLING_SOFT_FORK_HEIGHT: Height = Height(4_048_500);
32
33/// An enum describing the kind of network, whether it's the production mainnet or a testnet.
34// Note: The order of these variants is important for correct bincode (de)serialization
35//       of history trees in the db format.
36// TODO: Replace bincode (de)serialization of `HistoryTreeParts` in a db format upgrade?
37#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
38pub enum NetworkKind {
39    /// The production mainnet.
40    #[default]
41    Mainnet,
42
43    /// A test network.
44    Testnet,
45
46    /// Regtest mode
47    Regtest,
48}
49
50impl From<Network> for NetworkKind {
51    fn from(net: Network) -> Self {
52        NetworkKind::from(&net)
53    }
54}
55
56impl From<&Network> for NetworkKind {
57    fn from(net: &Network) -> Self {
58        net.kind()
59    }
60}
61
62/// An enum describing the possible network choices.
63#[derive(Clone, Default, Eq, PartialEq, Serialize)]
64#[serde(into = "NetworkKind")]
65pub enum Network {
66    /// The production mainnet.
67    #[default]
68    Mainnet,
69
70    /// A test network such as the default public testnet,
71    /// a configured testnet, or Regtest.
72    Testnet(Arc<testnet::Parameters>),
73}
74
75impl NetworkKind {
76    /// Returns the human-readable prefix for Base58Check-encoded transparent
77    /// pay-to-public-key-hash payment addresses for the network.
78    pub fn b58_pubkey_address_prefix(self) -> [u8; 2] {
79        match self {
80            Self::Mainnet => zcash_protocol::constants::mainnet::B58_PUBKEY_ADDRESS_PREFIX,
81            Self::Testnet | Self::Regtest => {
82                zcash_protocol::constants::testnet::B58_PUBKEY_ADDRESS_PREFIX
83            }
84        }
85    }
86
87    /// Returns the human-readable prefix for Base58Check-encoded transparent pay-to-script-hash
88    /// payment addresses for the network.
89    pub fn b58_script_address_prefix(self) -> [u8; 2] {
90        match self {
91            Self::Mainnet => zcash_protocol::constants::mainnet::B58_SCRIPT_ADDRESS_PREFIX,
92            Self::Testnet | Self::Regtest => {
93                zcash_protocol::constants::testnet::B58_SCRIPT_ADDRESS_PREFIX
94            }
95        }
96    }
97
98    /// Return the network name as defined in
99    /// [BIP70](https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki#paymentdetailspaymentrequest)
100    pub fn bip70_network_name(&self) -> String {
101        if *self == Self::Mainnet {
102            "main".to_string()
103        } else {
104            "test".to_string()
105        }
106    }
107
108    /// Returns the 2 bytes prefix for Bech32m-encoded transparent TEX
109    /// payment addresses for the network as defined in [ZIP-320](https://zips.z.cash/zip-0320.html).
110    pub fn tex_address_prefix(self) -> [u8; 2] {
111        // TODO: Add this bytes to `zcash_protocol::constants`?
112        match self {
113            Self::Mainnet => [0x1c, 0xb8],
114            Self::Testnet | Self::Regtest => [0x1d, 0x25],
115        }
116    }
117}
118
119impl From<NetworkKind> for &'static str {
120    fn from(network: NetworkKind) -> &'static str {
121        // These should be different from the `Display` impl for `Network` so that its lowercase form
122        // can't be parsed as the default Testnet in the `Network` `FromStr` impl, it's easy to
123        // distinguish them in logs, and so it's generally harder to confuse the two.
124        match network {
125            NetworkKind::Mainnet => "MainnetKind",
126            NetworkKind::Testnet => "TestnetKind",
127            NetworkKind::Regtest => "RegtestKind",
128        }
129    }
130}
131
132impl fmt::Display for NetworkKind {
133    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134        f.write_str((*self).into())
135    }
136}
137
138impl<'a> From<&'a Network> for &'a str {
139    fn from(network: &'a Network) -> &'a str {
140        match network {
141            Network::Mainnet => "Mainnet",
142            Network::Testnet(params) => params.network_name(),
143        }
144    }
145}
146
147impl fmt::Display for Network {
148    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149        f.write_str(self.into())
150    }
151}
152
153impl std::fmt::Debug for Network {
154    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155        match self {
156            Self::Mainnet => write!(f, "{self}"),
157            Self::Testnet(params) if params.is_regtest() => f
158                .debug_struct("Regtest")
159                .field("activation_heights", params.activation_heights())
160                .field("funding_streams", params.funding_streams())
161                .field("lockbox_disbursements", &params.lockbox_disbursements())
162                .field("checkpoints", &params.checkpoints())
163                .finish(),
164            Self::Testnet(params) if params.is_default_testnet() => {
165                write!(f, "{self}")
166            }
167            Self::Testnet(params) => f.debug_tuple("ConfiguredTestnet").field(params).finish(),
168        }
169    }
170}
171
172impl Network {
173    /// Creates a new [`Network::Testnet`] with the default Testnet [`testnet::Parameters`].
174    pub fn new_default_testnet() -> Self {
175        Self::Testnet(Arc::new(testnet::Parameters::default()))
176    }
177
178    /// Creates a new configured [`Network::Testnet`] with the provided Testnet [`testnet::Parameters`].
179    pub fn new_configured_testnet(params: testnet::Parameters) -> Self {
180        Self::Testnet(Arc::new(params))
181    }
182
183    /// Creates a new [`Network::Testnet`] with `Regtest` parameters and the provided network upgrade activation heights.
184    pub fn new_regtest(params: testnet::RegtestParameters) -> Self {
185        Self::new_configured_testnet(
186            testnet::Parameters::new_regtest(params)
187                .expect("regtest parameters should always be valid"),
188        )
189    }
190
191    /// Returns true if the network is the default Testnet, or false otherwise.
192    pub fn is_default_testnet(&self) -> bool {
193        if let Self::Testnet(params) = self {
194            params.is_default_testnet()
195        } else {
196            false
197        }
198    }
199
200    /// Returns true if the network is Regtest, or false otherwise.
201    pub fn is_regtest(&self) -> bool {
202        if let Self::Testnet(params) = self {
203            params.is_regtest()
204        } else {
205            false
206        }
207    }
208
209    /// Returns the [`NetworkKind`] for this network.
210    pub fn kind(&self) -> NetworkKind {
211        match self {
212            Network::Mainnet => NetworkKind::Mainnet,
213            Network::Testnet(params) if params.is_regtest() => NetworkKind::Regtest,
214            Network::Testnet(_) => NetworkKind::Testnet,
215        }
216    }
217
218    /// Returns [`NetworkKind::Testnet`] on Testnet and Regtest, or [`NetworkKind::Mainnet`] on Mainnet.
219    ///
220    /// This is used for transparent addresses, as the address prefix is the same on Regtest as it is on Testnet.
221    pub fn t_addr_kind(&self) -> NetworkKind {
222        match self {
223            Network::Mainnet => NetworkKind::Mainnet,
224            Network::Testnet(_) => NetworkKind::Testnet,
225        }
226    }
227
228    /// Returns an iterator over [`Network`] variants.
229    pub fn iter() -> impl Iterator<Item = Self> {
230        [Self::Mainnet, Self::new_default_testnet()].into_iter()
231    }
232
233    /// Returns true if the maximum block time rule is active for `network` and `height`.
234    ///
235    /// Always returns true if `network` is the Mainnet.
236    /// If `network` is the Testnet, the `height` should be at least
237    /// TESTNET_MAX_TIME_START_HEIGHT to return true.
238    /// Returns false otherwise.
239    ///
240    /// Part of the consensus rules at <https://zips.z.cash/protocol/protocol.pdf#blockheader>
241    pub fn is_max_block_time_enforced(&self, height: block::Height) -> bool {
242        match self {
243            Network::Mainnet => true,
244            // TODO: Move `TESTNET_MAX_TIME_START_HEIGHT` to a field on testnet::Parameters (#8364)
245            Network::Testnet(_params) => height >= super::TESTNET_MAX_TIME_START_HEIGHT,
246        }
247    }
248
249    /// Get the default port associated to this network.
250    pub fn default_port(&self) -> u16 {
251        match self {
252            Network::Mainnet => 8233,
253            // TODO: Add a `default_port` field to `testnet::Parameters` to return here. (zcashd uses 18344 for Regtest)
254            Network::Testnet(_params) => 18233,
255        }
256    }
257
258    /// Get the mandatory minimum checkpoint height for this network.
259    ///
260    /// Mandatory checkpoints are a Zebra-specific feature.
261    /// If a Zcash consensus rule only applies before the mandatory checkpoint,
262    /// Zebra can skip validation of that rule.
263    /// This is necessary because Zebra can't fully validate the blocks prior to Canopy.
264    // TODO:
265    // - Support constructing pre-Canopy coinbase tx and block templates and return `Height::MAX` instead of panicking
266    //   when Canopy activation height is `None` (#8434)
267    pub fn mandatory_checkpoint_height(&self) -> Height {
268        // Currently this is just before Canopy activation
269        NetworkUpgrade::Canopy
270            .activation_height(self)
271            .expect("Canopy activation height must be present on all networks")
272            .previous()
273            .expect("Canopy activation height must be above min height")
274    }
275
276    /// Return the network name as defined in
277    /// [BIP70](https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki#paymentdetailspaymentrequest)
278    pub fn bip70_network_name(&self) -> String {
279        self.kind().bip70_network_name()
280    }
281
282    /// Return the lowercase network name.
283    pub fn lowercase_name(&self) -> String {
284        self.to_string().to_ascii_lowercase()
285    }
286
287    /// Returns `true` if this network is a testing network.
288    pub fn is_a_test_network(&self) -> bool {
289        *self != Network::Mainnet
290    }
291
292    /// Returns the Sapling activation height for this network.
293    // TODO: Return an `Option` here now that network upgrade activation heights are configurable on Regtest and custom Testnets
294    pub fn sapling_activation_height(&self) -> Height {
295        super::NetworkUpgrade::Sapling
296            .activation_height(self)
297            .expect("Sapling activation height needs to be set")
298    }
299
300    /// Returns the expected total value of the sum of all NU6.1 one-time lockbox disbursement output values for this network at
301    /// the provided height.
302    pub fn lockbox_disbursement_total_amount(&self, height: Height) -> Amount<NonNegative> {
303        if Some(height) != NetworkUpgrade::Nu6_1.activation_height(self) {
304            return Amount::zero();
305        };
306
307        match self {
308            Self::Mainnet => {
309                subsidy::constants::mainnet::EXPECTED_NU6_1_LOCKBOX_DISBURSEMENTS_TOTAL
310            }
311            Self::Testnet(params) if params.is_default_testnet() => {
312                subsidy::constants::testnet::EXPECTED_NU6_1_LOCKBOX_DISBURSEMENTS_TOTAL
313            }
314            Self::Testnet(params) => params.lockbox_disbursement_total_amount(),
315        }
316    }
317
318    /// Returns the expected NU6.1 lockbox disbursement outputs for this network at the provided height.
319    pub fn lockbox_disbursements(
320        &self,
321        height: Height,
322    ) -> Vec<(transparent::Address, Amount<NonNegative>)> {
323        if Some(height) != NetworkUpgrade::Nu6_1.activation_height(self) {
324            return Vec::new();
325        };
326
327        let expected_lockbox_disbursements = match self {
328            Self::Mainnet => subsidy::constants::mainnet::NU6_1_LOCKBOX_DISBURSEMENTS.to_vec(),
329            Self::Testnet(params) if params.is_default_testnet() => {
330                subsidy::constants::testnet::NU6_1_LOCKBOX_DISBURSEMENTS.to_vec()
331            }
332            Self::Testnet(params) => return params.lockbox_disbursements(),
333        };
334
335        expected_lockbox_disbursements
336            .into_iter()
337            .map(|(addr, amount)| {
338                (
339                    addr.parse().expect("hard-coded address must deserialize"),
340                    amount,
341                )
342            })
343            .collect()
344    }
345
346    /// Returns the height at which the soft fork that temporarily disables Orchard
347    /// actions in transactions activates, if it is configured for this network.
348    pub fn temporary_orchard_disabling_soft_fork_height(&self) -> Option<Height> {
349        match self {
350            Network::Mainnet => Some(MAINNET_TEMPORARY_ORCHARD_DISABLING_SOFT_FORK_HEIGHT),
351            Network::Testnet(parameters) => {
352                parameters.temporary_orchard_disabling_soft_fork_height()
353            }
354        }
355    }
356
357    /// Returns whether Orchard has been temporarily disabled in transactions.
358    pub fn temporary_orchard_disabling_soft_fork_active(&self, height: Height) -> bool {
359        self.temporary_orchard_disabling_soft_fork_height()
360            .is_some_and(|h| height >= h)
361    }
362
363    /// Returns whether Orchard is temporarily disabled in transactions at `height`.
364    ///
365    /// The temporary-disable soft fork is bounded above by NU6.2, which re-enables
366    /// Orchard actions: once NU6.2 is active the temporary-disable rule no longer
367    /// applies. On networks where NU6.2 is unscheduled this matches
368    /// [`Self::temporary_orchard_disabling_soft_fork_active`].
369    pub fn is_orchard_temporarily_disabled(&self, height: Height) -> bool {
370        self.temporary_orchard_disabling_soft_fork_active(height)
371            && NetworkUpgrade::Nu6_2
372                .activation_height(self)
373                .is_none_or(|nu6_2| height < nu6_2)
374    }
375
376    /// Returns whether `height` is the first height at which the soft fork that
377    /// temporarily disables Orchard actions applies.
378    ///
379    /// This is the boundary at which the mempool must revalidate its contents, to drop
380    /// any transactions containing Orchard actions that were accepted before the soft
381    /// fork activated.
382    pub fn is_temporary_orchard_disabling_soft_fork_activation_height(
383        &self,
384        height: Height,
385    ) -> bool {
386        self.temporary_orchard_disabling_soft_fork_height() == Some(height)
387    }
388
389    /// Returns whether the consensus rule requiring a canonically-sized Orchard proof
390    /// is active at `height`.
391    ///
392    /// This rule activates with the network upgrade that re-enables Orchard actions
393    /// (NU6.2). On networks where NU6.2 is unscheduled the rule is always inactive. It is a
394    /// constricting rule, so it must stay height-gated, or it would reject historical
395    /// Orchard actions mined before the soft fork that temporarily disabled them, and
396    /// prevent syncing.
397    pub fn orchard_canonical_proof_size_rule_active(&self, height: Height) -> bool {
398        NetworkUpgrade::Nu6_2
399            .activation_height(self)
400            .is_some_and(|h| height >= h)
401    }
402}
403
404// This is used for parsing a command-line argument for the `TipHeight` command in zebrad.
405impl FromStr for Network {
406    type Err = InvalidNetworkError;
407
408    fn from_str(string: &str) -> Result<Self, Self::Err> {
409        match string.to_lowercase().as_str() {
410            "mainnet" => Ok(Network::Mainnet),
411            "testnet" => Ok(Network::new_default_testnet()),
412            _ => Err(InvalidNetworkError(string.to_owned())),
413        }
414    }
415}
416
417#[derive(Clone, Debug, Error)]
418#[error("Invalid network: {0}")]
419pub struct InvalidNetworkError(String);
420
421impl zcash_protocol::consensus::Parameters for Network {
422    fn network_type(&self) -> zcash_protocol::consensus::NetworkType {
423        self.kind().into()
424    }
425
426    fn activation_height(
427        &self,
428        nu: zcash_protocol::consensus::NetworkUpgrade,
429    ) -> Option<zcash_protocol::consensus::BlockHeight> {
430        NetworkUpgrade::from(nu)
431            .activation_height(self)
432            .map(|Height(h)| zcash_protocol::consensus::BlockHeight::from_u32(h))
433    }
434}