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    block::{self, Height},
9    parameters::NetworkUpgrade,
10};
11
12pub mod magic;
13pub mod subsidy;
14pub mod testnet;
15
16#[cfg(test)]
17mod tests;
18
19/// An enum describing the kind of network, whether it's the production mainnet or a testnet.
20// Note: The order of these variants is important for correct bincode (de)serialization
21//       of history trees in the db format.
22// TODO: Replace bincode (de)serialization of `HistoryTreeParts` in a db format upgrade?
23#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
24pub enum NetworkKind {
25    /// The production mainnet.
26    #[default]
27    Mainnet,
28
29    /// A test network.
30    Testnet,
31
32    /// Regtest mode, not yet implemented
33    // TODO: Add `new_regtest()` and `is_regtest` methods on `Network`.
34    Regtest,
35}
36
37impl From<Network> for NetworkKind {
38    fn from(network: Network) -> Self {
39        network.kind()
40    }
41}
42
43/// An enum describing the possible network choices.
44#[derive(Clone, Default, Eq, PartialEq, Serialize)]
45#[serde(into = "NetworkKind")]
46pub enum Network {
47    /// The production mainnet.
48    #[default]
49    Mainnet,
50
51    /// A test network such as the default public testnet,
52    /// a configured testnet, or Regtest.
53    Testnet(Arc<testnet::Parameters>),
54}
55
56impl NetworkKind {
57    /// Returns the human-readable prefix for Base58Check-encoded transparent
58    /// pay-to-public-key-hash payment addresses for the network.
59    pub fn b58_pubkey_address_prefix(self) -> [u8; 2] {
60        match self {
61            Self::Mainnet => zcash_primitives::constants::mainnet::B58_PUBKEY_ADDRESS_PREFIX,
62            Self::Testnet | Self::Regtest => {
63                zcash_primitives::constants::testnet::B58_PUBKEY_ADDRESS_PREFIX
64            }
65        }
66    }
67
68    /// Returns the human-readable prefix for Base58Check-encoded transparent pay-to-script-hash
69    /// payment addresses for the network.
70    pub fn b58_script_address_prefix(self) -> [u8; 2] {
71        match self {
72            Self::Mainnet => zcash_primitives::constants::mainnet::B58_SCRIPT_ADDRESS_PREFIX,
73            Self::Testnet | Self::Regtest => {
74                zcash_primitives::constants::testnet::B58_SCRIPT_ADDRESS_PREFIX
75            }
76        }
77    }
78
79    /// Return the network name as defined in
80    /// [BIP70](https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki#paymentdetailspaymentrequest)
81    pub fn bip70_network_name(&self) -> String {
82        if *self == Self::Mainnet {
83            "main".to_string()
84        } else {
85            "test".to_string()
86        }
87    }
88}
89
90impl From<NetworkKind> for &'static str {
91    fn from(network: NetworkKind) -> &'static str {
92        // These should be different from the `Display` impl for `Network` so that its lowercase form
93        // can't be parsed as the default Testnet in the `Network` `FromStr` impl, it's easy to
94        // distinguish them in logs, and so it's generally harder to confuse the two.
95        match network {
96            NetworkKind::Mainnet => "MainnetKind",
97            NetworkKind::Testnet => "TestnetKind",
98            NetworkKind::Regtest => "RegtestKind",
99        }
100    }
101}
102
103impl fmt::Display for NetworkKind {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        f.write_str((*self).into())
106    }
107}
108
109impl<'a> From<&'a Network> for &'a str {
110    fn from(network: &'a Network) -> &'a str {
111        match network {
112            Network::Mainnet => "Mainnet",
113            Network::Testnet(params) => params.network_name(),
114        }
115    }
116}
117
118impl fmt::Display for Network {
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        f.write_str(self.into())
121    }
122}
123
124impl std::fmt::Debug for Network {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        match self {
127            Self::Mainnet => write!(f, "{self}"),
128            Self::Testnet(params) if params.is_regtest() => f
129                .debug_struct("Regtest")
130                .field("activation_heights", params.activation_heights())
131                .finish(),
132            Self::Testnet(params) if params.is_default_testnet() => {
133                write!(f, "{self}")
134            }
135            Self::Testnet(params) => f.debug_tuple("ConfiguredTestnet").field(params).finish(),
136        }
137    }
138}
139
140impl Network {
141    /// Creates a new [`Network::Testnet`] with the default Testnet [`testnet::Parameters`].
142    pub fn new_default_testnet() -> Self {
143        Self::Testnet(Arc::new(testnet::Parameters::default()))
144    }
145
146    /// Creates a new configured [`Network::Testnet`] with the provided Testnet [`testnet::Parameters`].
147    pub fn new_configured_testnet(params: testnet::Parameters) -> Self {
148        Self::Testnet(Arc::new(params))
149    }
150
151    /// Creates a new [`Network::Testnet`] with `Regtest` parameters and the provided network upgrade activation heights.
152    pub fn new_regtest(
153        nu5_activation_height: Option<u32>,
154        nu6_activation_height: Option<u32>,
155    ) -> Self {
156        Self::new_configured_testnet(testnet::Parameters::new_regtest(
157            nu5_activation_height,
158            nu6_activation_height,
159        ))
160    }
161
162    /// Returns true if the network is the default Testnet, or false otherwise.
163    pub fn is_default_testnet(&self) -> bool {
164        if let Self::Testnet(params) = self {
165            params.is_default_testnet()
166        } else {
167            false
168        }
169    }
170
171    /// Returns true if the network is Regtest, or false otherwise.
172    pub fn is_regtest(&self) -> bool {
173        if let Self::Testnet(params) = self {
174            params.is_regtest()
175        } else {
176            false
177        }
178    }
179
180    /// Returns the [`NetworkKind`] for this network.
181    pub fn kind(&self) -> NetworkKind {
182        match self {
183            Network::Mainnet => NetworkKind::Mainnet,
184            Network::Testnet(params) if params.is_regtest() => NetworkKind::Regtest,
185            Network::Testnet(_) => NetworkKind::Testnet,
186        }
187    }
188
189    /// Returns an iterator over [`Network`] variants.
190    pub fn iter() -> impl Iterator<Item = Self> {
191        [Self::Mainnet, Self::new_default_testnet()].into_iter()
192    }
193
194    /// Returns true if the maximum block time rule is active for `network` and `height`.
195    ///
196    /// Always returns true if `network` is the Mainnet.
197    /// If `network` is the Testnet, the `height` should be at least
198    /// TESTNET_MAX_TIME_START_HEIGHT to return true.
199    /// Returns false otherwise.
200    ///
201    /// Part of the consensus rules at <https://zips.z.cash/protocol/protocol.pdf#blockheader>
202    pub fn is_max_block_time_enforced(&self, height: block::Height) -> bool {
203        match self {
204            Network::Mainnet => true,
205            // TODO: Move `TESTNET_MAX_TIME_START_HEIGHT` to a field on testnet::Parameters (#8364)
206            Network::Testnet(_params) => height >= super::TESTNET_MAX_TIME_START_HEIGHT,
207        }
208    }
209
210    /// Get the default port associated to this network.
211    pub fn default_port(&self) -> u16 {
212        match self {
213            Network::Mainnet => 8233,
214            // TODO: Add a `default_port` field to `testnet::Parameters` to return here. (zcashd uses 18344 for Regtest)
215            Network::Testnet(_params) => 18233,
216        }
217    }
218
219    /// Get the mandatory minimum checkpoint height for this network.
220    ///
221    /// Mandatory checkpoints are a Zebra-specific feature.
222    /// If a Zcash consensus rule only applies before the mandatory checkpoint,
223    /// Zebra can skip validation of that rule.
224    /// This is necessary because Zebra can't fully validate the blocks prior to Canopy.
225    // TODO:
226    // - Support constructing pre-Canopy coinbase tx and block templates and return `Height::MAX` instead of panicking
227    //   when Canopy activation height is `None` (#8434)
228    pub fn mandatory_checkpoint_height(&self) -> Height {
229        // Currently this is just before Canopy activation
230        NetworkUpgrade::Canopy
231            .activation_height(self)
232            .expect("Canopy activation height must be present on all networks")
233            .previous()
234            .expect("Canopy activation height must be above min height")
235    }
236
237    /// Return the network name as defined in
238    /// [BIP70](https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki#paymentdetailspaymentrequest)
239    pub fn bip70_network_name(&self) -> String {
240        self.kind().bip70_network_name()
241    }
242
243    /// Return the lowercase network name.
244    pub fn lowercase_name(&self) -> String {
245        self.to_string().to_ascii_lowercase()
246    }
247
248    /// Returns `true` if this network is a testing network.
249    pub fn is_a_test_network(&self) -> bool {
250        *self != Network::Mainnet
251    }
252
253    /// Returns the Sapling activation height for this network.
254    // TODO: Return an `Option` here now that network upgrade activation heights are configurable on Regtest and custom Testnets
255    pub fn sapling_activation_height(&self) -> Height {
256        super::NetworkUpgrade::Sapling
257            .activation_height(self)
258            .expect("Sapling activation height needs to be set")
259    }
260}
261
262// This is used for parsing a command-line argument for the `TipHeight` command in zebrad.
263impl FromStr for Network {
264    type Err = InvalidNetworkError;
265
266    fn from_str(string: &str) -> Result<Self, Self::Err> {
267        match string.to_lowercase().as_str() {
268            "mainnet" => Ok(Network::Mainnet),
269            "testnet" => Ok(Network::new_default_testnet()),
270            _ => Err(InvalidNetworkError(string.to_owned())),
271        }
272    }
273}
274
275#[derive(Clone, Debug, Error)]
276#[error("Invalid network: {0}")]
277pub struct InvalidNetworkError(String);
278
279impl zcash_protocol::consensus::Parameters for Network {
280    fn network_type(&self) -> zcash_address::Network {
281        self.kind().into()
282    }
283
284    fn activation_height(
285        &self,
286        nu: zcash_protocol::consensus::NetworkUpgrade,
287    ) -> Option<zcash_protocol::consensus::BlockHeight> {
288        NetworkUpgrade::from(nu)
289            .activation_height(self)
290            .map(|Height(h)| zcash_protocol::consensus::BlockHeight::from_u32(h))
291    }
292}