Skip to main content

lexe_common/ln/
network.rs

1use std::{fmt, fmt::Display, str::FromStr};
2
3use anyhow::anyhow;
4use bitcoin::{
5    blockdata::constants::{self, ChainHash},
6    hash_types::BlockHash,
7    hashes::Hash as _,
8};
9use lightning_invoice::Currency;
10#[cfg(any(test, feature = "test-utils"))]
11use proptest_derive::Arbitrary;
12use serde::Serialize;
13use serde_with::DeserializeFromStr;
14use strum::VariantArray;
15
16/// A simple version of [`bitcoin::Network`] which impls [`FromStr`] and
17/// [`Display`] in a consistent way, and which isn't `#[non_exhaustive]`.
18///
19/// NOTE: [`bitcoin::Network`] serializes their mainnet variant as "bitcoin",
20/// while we serialize it as "mainnet". Be sure to use *our* [`serde`] impls
21/// when (de)serializing this network.
22#[derive(Copy, Clone, Debug, Eq, PartialEq, DeserializeFromStr, VariantArray)]
23#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
24pub enum Network {
25    Mainnet,
26    Testnet3,
27    Testnet4,
28    Regtest,
29    Signet,
30}
31
32impl Network {
33    /// Convert to a [`bitcoin::Network`].
34    /// Equivalent to using the [`From`] impl.
35    #[inline]
36    pub fn to_bitcoin(self) -> bitcoin::Network {
37        bitcoin::Network::from(self)
38    }
39
40    pub fn as_str(self) -> &'static str {
41        match self {
42            Self::Mainnet => "mainnet",
43            Self::Testnet3 => "testnet3",
44            Self::Testnet4 => "testnet4",
45            Self::Regtest => "regtest",
46            Self::Signet => "signet",
47        }
48    }
49
50    /// Gets the [`BlockHash`] of the genesis block for this [`Network`].
51    pub fn genesis_block_hash(self) -> BlockHash {
52        let chain_hash = Self::genesis_chain_hash(self);
53        BlockHash::from_byte_array(chain_hash.to_bytes())
54    }
55
56    /// Gets the block hash of the genesis block for this [`Network`], but
57    /// returns the other [`ChainHash`] newtype.
58    #[inline]
59    pub fn genesis_chain_hash(self) -> ChainHash {
60        constants::ChainHash::using_genesis_block(self.to_bitcoin())
61    }
62}
63
64impl FromStr for Network {
65    type Err = anyhow::Error;
66    fn from_str(s: &str) -> Result<Self, Self::Err> {
67        match s {
68            "mainnet" => Ok(Self::Mainnet),
69            "testnet3" => Ok(Self::Testnet3),
70            "testnet4" => Ok(Self::Testnet4),
71            "regtest" => Ok(Self::Regtest),
72            "signet" => Ok(Self::Signet),
73            _ => Err(anyhow!("Invalid `Network`")),
74        }
75    }
76}
77
78impl Display for Network {
79    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
80        write!(f, "{}", self.as_str())
81    }
82}
83
84impl From<bitcoin::Network> for Network {
85    fn from(network: bitcoin::Network) -> Self {
86        match network {
87            bitcoin::Network::Bitcoin => Self::Mainnet,
88            bitcoin::Network::Testnet => Self::Testnet3,
89            bitcoin::Network::Testnet4 => Self::Testnet4,
90            bitcoin::Network::Signet => Self::Signet,
91            bitcoin::Network::Regtest => Self::Regtest,
92        }
93    }
94}
95
96impl From<Network> for bitcoin::Network {
97    fn from(lx: Network) -> Self {
98        match lx {
99            Network::Mainnet => Self::Bitcoin,
100            Network::Testnet3 => Self::Testnet,
101            Network::Testnet4 => Self::Testnet4,
102            Network::Regtest => Self::Regtest,
103            Network::Signet => Self::Signet,
104        }
105    }
106}
107
108impl From<Network> for Currency {
109    fn from(lx: Network) -> Self {
110        match lx {
111            Network::Mainnet => Self::Bitcoin,
112            Network::Testnet3 | Network::Testnet4 => Self::BitcoinTestnet,
113            Network::Regtest => Self::Regtest,
114            Network::Signet => Self::Signet,
115        }
116    }
117}
118
119impl Serialize for Network {
120    fn serialize<S: serde::Serializer>(
121        &self,
122        serializer: S,
123    ) -> Result<S::Ok, S::Error> {
124        self.as_str().serialize(serializer)
125    }
126}
127
128#[cfg(test)]
129mod test {
130    use lexe_hex::hex;
131
132    use super::*;
133    use crate::test_utils::roundtrip;
134
135    #[test]
136    fn network_roundtrip() {
137        let expected_ser =
138            r#"["mainnet","testnet3","testnet4","regtest","signet"]"#;
139        roundtrip::json_unit_enum_backwards_compat::<Network>(expected_ser);
140        roundtrip::fromstr_display_roundtrip_proptest::<Network>();
141    }
142
143    // Sanity check that Hash(genesis_block) == precomputed hash
144    #[test]
145    fn check_precomputed_genesis_block_hashes() {
146        for network in Network::VARIANTS {
147            let precomputed = network.genesis_block_hash();
148            let computed = constants::genesis_block(network.to_bitcoin())
149                .header
150                .block_hash();
151            assert_eq!(precomputed, computed);
152        }
153    }
154
155    // Sanity check mainnet genesis block hash
156    #[test]
157    fn absolutely_check_mainnet_genesis_hash() {
158        let expected = hex::decode(
159            "6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000",
160        )
161        .unwrap();
162        let actual = Network::Mainnet.genesis_chain_hash();
163        assert_eq!(actual.as_bytes().as_slice(), expected.as_slice());
164    }
165}