use std::{fmt, str::FromStr, sync::Arc};
use thiserror::Error;
use crate::{
amount::{Amount, NonNegative},
block::{self, Height},
parameters::NetworkUpgrade,
transparent,
};
mod error;
pub mod magic;
pub mod subsidy;
pub mod testnet;
#[cfg(test)]
mod tests;
const MAINNET_TEMPORARY_ORCHARD_DISABLING_SOFT_FORK_HEIGHT: Height = Height(3_363_426);
const TESTNET_TEMPORARY_ORCHARD_DISABLING_SOFT_FORK_HEIGHT: Height = Height(4_048_500);
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub enum NetworkKind {
#[default]
Mainnet,
Testnet,
Regtest,
}
impl From<Network> for NetworkKind {
fn from(net: Network) -> Self {
NetworkKind::from(&net)
}
}
impl From<&Network> for NetworkKind {
fn from(net: &Network) -> Self {
net.kind()
}
}
#[derive(Clone, Default, Eq, PartialEq, Serialize)]
#[serde(into = "NetworkKind")]
pub enum Network {
#[default]
Mainnet,
Testnet(Arc<testnet::Parameters>),
}
impl NetworkKind {
pub fn b58_pubkey_address_prefix(self) -> [u8; 2] {
match self {
Self::Mainnet => zcash_protocol::constants::mainnet::B58_PUBKEY_ADDRESS_PREFIX,
Self::Testnet | Self::Regtest => {
zcash_protocol::constants::testnet::B58_PUBKEY_ADDRESS_PREFIX
}
}
}
pub fn b58_script_address_prefix(self) -> [u8; 2] {
match self {
Self::Mainnet => zcash_protocol::constants::mainnet::B58_SCRIPT_ADDRESS_PREFIX,
Self::Testnet | Self::Regtest => {
zcash_protocol::constants::testnet::B58_SCRIPT_ADDRESS_PREFIX
}
}
}
pub fn bip70_network_name(&self) -> String {
if *self == Self::Mainnet {
"main".to_string()
} else {
"test".to_string()
}
}
pub fn tex_address_prefix(self) -> [u8; 2] {
match self {
Self::Mainnet => [0x1c, 0xb8],
Self::Testnet | Self::Regtest => [0x1d, 0x25],
}
}
}
impl From<NetworkKind> for &'static str {
fn from(network: NetworkKind) -> &'static str {
match network {
NetworkKind::Mainnet => "MainnetKind",
NetworkKind::Testnet => "TestnetKind",
NetworkKind::Regtest => "RegtestKind",
}
}
}
impl fmt::Display for NetworkKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str((*self).into())
}
}
impl<'a> From<&'a Network> for &'a str {
fn from(network: &'a Network) -> &'a str {
match network {
Network::Mainnet => "Mainnet",
Network::Testnet(params) => params.network_name(),
}
}
}
impl fmt::Display for Network {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.into())
}
}
impl std::fmt::Debug for Network {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Mainnet => write!(f, "{self}"),
Self::Testnet(params) if params.is_regtest() => f
.debug_struct("Regtest")
.field("activation_heights", params.activation_heights())
.field("funding_streams", params.funding_streams())
.field("lockbox_disbursements", ¶ms.lockbox_disbursements())
.field("checkpoints", ¶ms.checkpoints())
.finish(),
Self::Testnet(params) if params.is_default_testnet() => {
write!(f, "{self}")
}
Self::Testnet(params) => f.debug_tuple("ConfiguredTestnet").field(params).finish(),
}
}
}
impl Network {
pub fn new_default_testnet() -> Self {
Self::Testnet(Arc::new(testnet::Parameters::default()))
}
pub fn new_configured_testnet(params: testnet::Parameters) -> Self {
Self::Testnet(Arc::new(params))
}
pub fn new_regtest(params: testnet::RegtestParameters) -> Self {
Self::new_configured_testnet(
testnet::Parameters::new_regtest(params)
.expect("regtest parameters should always be valid"),
)
}
pub fn is_default_testnet(&self) -> bool {
if let Self::Testnet(params) = self {
params.is_default_testnet()
} else {
false
}
}
pub fn is_regtest(&self) -> bool {
if let Self::Testnet(params) = self {
params.is_regtest()
} else {
false
}
}
pub fn kind(&self) -> NetworkKind {
match self {
Network::Mainnet => NetworkKind::Mainnet,
Network::Testnet(params) if params.is_regtest() => NetworkKind::Regtest,
Network::Testnet(_) => NetworkKind::Testnet,
}
}
pub fn t_addr_kind(&self) -> NetworkKind {
match self {
Network::Mainnet => NetworkKind::Mainnet,
Network::Testnet(_) => NetworkKind::Testnet,
}
}
pub fn iter() -> impl Iterator<Item = Self> {
[Self::Mainnet, Self::new_default_testnet()].into_iter()
}
pub fn is_max_block_time_enforced(&self, height: block::Height) -> bool {
match self {
Network::Mainnet => true,
Network::Testnet(_params) => height >= super::TESTNET_MAX_TIME_START_HEIGHT,
}
}
pub fn default_port(&self) -> u16 {
match self {
Network::Mainnet => 8233,
Network::Testnet(_params) => 18233,
}
}
pub fn mandatory_checkpoint_height(&self) -> Height {
NetworkUpgrade::Canopy
.activation_height(self)
.expect("Canopy activation height must be present on all networks")
.previous()
.expect("Canopy activation height must be above min height")
}
pub fn bip70_network_name(&self) -> String {
self.kind().bip70_network_name()
}
pub fn lowercase_name(&self) -> String {
self.to_string().to_ascii_lowercase()
}
pub fn is_a_test_network(&self) -> bool {
*self != Network::Mainnet
}
pub fn sapling_activation_height(&self) -> Height {
super::NetworkUpgrade::Sapling
.activation_height(self)
.expect("Sapling activation height needs to be set")
}
pub fn lockbox_disbursement_total_amount(&self, height: Height) -> Amount<NonNegative> {
if Some(height) != NetworkUpgrade::Nu6_1.activation_height(self) {
return Amount::zero();
};
match self {
Self::Mainnet => {
subsidy::constants::mainnet::EXPECTED_NU6_1_LOCKBOX_DISBURSEMENTS_TOTAL
}
Self::Testnet(params) if params.is_default_testnet() => {
subsidy::constants::testnet::EXPECTED_NU6_1_LOCKBOX_DISBURSEMENTS_TOTAL
}
Self::Testnet(params) => params.lockbox_disbursement_total_amount(),
}
}
pub fn lockbox_disbursements(
&self,
height: Height,
) -> Vec<(transparent::Address, Amount<NonNegative>)> {
if Some(height) != NetworkUpgrade::Nu6_1.activation_height(self) {
return Vec::new();
};
let expected_lockbox_disbursements = match self {
Self::Mainnet => subsidy::constants::mainnet::NU6_1_LOCKBOX_DISBURSEMENTS.to_vec(),
Self::Testnet(params) if params.is_default_testnet() => {
subsidy::constants::testnet::NU6_1_LOCKBOX_DISBURSEMENTS.to_vec()
}
Self::Testnet(params) => return params.lockbox_disbursements(),
};
expected_lockbox_disbursements
.into_iter()
.map(|(addr, amount)| {
(
addr.parse().expect("hard-coded address must deserialize"),
amount,
)
})
.collect()
}
pub fn temporary_orchard_disabling_soft_fork_height(&self) -> Option<Height> {
match self {
Network::Mainnet => Some(MAINNET_TEMPORARY_ORCHARD_DISABLING_SOFT_FORK_HEIGHT),
Network::Testnet(parameters) => {
parameters.temporary_orchard_disabling_soft_fork_height()
}
}
}
pub fn temporary_orchard_disabling_soft_fork_active(&self, height: Height) -> bool {
self.temporary_orchard_disabling_soft_fork_height()
.is_some_and(|h| height >= h)
}
pub fn is_orchard_temporarily_disabled(&self, height: Height) -> bool {
self.temporary_orchard_disabling_soft_fork_active(height)
&& NetworkUpgrade::Nu6_2
.activation_height(self)
.is_none_or(|nu6_2| height < nu6_2)
}
pub fn is_temporary_orchard_disabling_soft_fork_activation_height(
&self,
height: Height,
) -> bool {
self.temporary_orchard_disabling_soft_fork_height() == Some(height)
}
pub fn orchard_canonical_proof_size_rule_active(&self, height: Height) -> bool {
NetworkUpgrade::Nu6_2
.activation_height(self)
.is_some_and(|h| height >= h)
}
}
impl FromStr for Network {
type Err = InvalidNetworkError;
fn from_str(string: &str) -> Result<Self, Self::Err> {
match string.to_lowercase().as_str() {
"mainnet" => Ok(Network::Mainnet),
"testnet" => Ok(Network::new_default_testnet()),
_ => Err(InvalidNetworkError(string.to_owned())),
}
}
}
#[derive(Clone, Debug, Error)]
#[error("Invalid network: {0}")]
pub struct InvalidNetworkError(String);
impl zcash_protocol::consensus::Parameters for Network {
fn network_type(&self) -> zcash_protocol::consensus::NetworkType {
self.kind().into()
}
fn activation_height(
&self,
nu: zcash_protocol::consensus::NetworkUpgrade,
) -> Option<zcash_protocol::consensus::BlockHeight> {
NetworkUpgrade::from(nu)
.activation_height(self)
.map(|Height(h)| zcash_protocol::consensus::BlockHeight::from_u32(h))
}
}