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