use std::{fmt, fmt::Display, str::FromStr};
use anyhow::anyhow;
use bitcoin::{
blockdata::constants::{self, ChainHash},
hash_types::BlockHash,
hashes::Hash as _,
};
use lightning_invoice::Currency;
#[cfg(any(test, feature = "test-utils"))]
use proptest_derive::Arbitrary;
use serde::Serialize;
use serde_with::DeserializeFromStr;
use strum::VariantArray;
#[derive(Copy, Clone, Debug, Eq, PartialEq, DeserializeFromStr, VariantArray)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub enum Network {
Mainnet,
Testnet3,
Testnet4,
Regtest,
Signet,
}
impl Network {
#[inline]
pub fn to_bitcoin(self) -> bitcoin::Network {
bitcoin::Network::from(self)
}
pub fn as_str(self) -> &'static str {
match self {
Self::Mainnet => "mainnet",
Self::Testnet3 => "testnet3",
Self::Testnet4 => "testnet4",
Self::Regtest => "regtest",
Self::Signet => "signet",
}
}
pub fn genesis_block_hash(self) -> BlockHash {
let chain_hash = Self::genesis_chain_hash(self);
BlockHash::from_byte_array(chain_hash.to_bytes())
}
#[inline]
pub fn genesis_chain_hash(self) -> ChainHash {
constants::ChainHash::using_genesis_block(self.to_bitcoin())
}
}
impl FromStr for Network {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"mainnet" => Ok(Self::Mainnet),
"testnet3" => Ok(Self::Testnet3),
"testnet4" => Ok(Self::Testnet4),
"regtest" => Ok(Self::Regtest),
"signet" => Ok(Self::Signet),
_ => Err(anyhow!("Invalid `Network`")),
}
}
}
impl Display for Network {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl From<bitcoin::Network> for Network {
fn from(network: bitcoin::Network) -> Self {
match network {
bitcoin::Network::Bitcoin => Self::Mainnet,
bitcoin::Network::Testnet => Self::Testnet3,
bitcoin::Network::Testnet4 => Self::Testnet4,
bitcoin::Network::Signet => Self::Signet,
bitcoin::Network::Regtest => Self::Regtest,
}
}
}
impl From<Network> for bitcoin::Network {
fn from(lx: Network) -> Self {
match lx {
Network::Mainnet => Self::Bitcoin,
Network::Testnet3 => Self::Testnet,
Network::Testnet4 => Self::Testnet4,
Network::Regtest => Self::Regtest,
Network::Signet => Self::Signet,
}
}
}
impl From<Network> for Currency {
fn from(lx: Network) -> Self {
match lx {
Network::Mainnet => Self::Bitcoin,
Network::Testnet3 | Network::Testnet4 => Self::BitcoinTestnet,
Network::Regtest => Self::Regtest,
Network::Signet => Self::Signet,
}
}
}
impl Serialize for Network {
fn serialize<S: serde::Serializer>(
&self,
serializer: S,
) -> Result<S::Ok, S::Error> {
self.as_str().serialize(serializer)
}
}
#[cfg(test)]
mod test {
use lexe_hex::hex;
use super::*;
use crate::test_utils::roundtrip;
#[test]
fn network_roundtrip() {
let expected_ser =
r#"["mainnet","testnet3","testnet4","regtest","signet"]"#;
roundtrip::json_unit_enum_backwards_compat::<Network>(expected_ser);
roundtrip::fromstr_display_roundtrip_proptest::<Network>();
}
#[test]
fn check_precomputed_genesis_block_hashes() {
for network in Network::VARIANTS {
let precomputed = network.genesis_block_hash();
let computed = constants::genesis_block(network.to_bitcoin())
.header
.block_hash();
assert_eq!(precomputed, computed);
}
}
#[test]
fn absolutely_check_mainnet_genesis_hash() {
let expected = hex::decode(
"6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000",
)
.unwrap();
let actual = Network::Mainnet.genesis_chain_hash();
assert_eq!(actual.as_bytes().as_slice(), expected.as_slice());
}
}