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