lumina_node/
network.rs

1//! Primitives and constants related to the networks supported by Celestia nodes.
2
3use std::fmt;
4use std::ops::Deref;
5use std::str::FromStr;
6
7use libp2p::Multiaddr;
8use serde::{Deserialize, Serialize};
9use thiserror::Error;
10
11/// Supported Celestia networks.
12#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
13#[derive(Debug, Default, Clone, PartialEq, Eq)]
14pub enum Network {
15    /// Celestia mainnet.
16    #[default]
17    Mainnet,
18    /// Arabica testnet.
19    Arabica,
20    /// Mocha testnet.
21    Mocha,
22    /// Custom network.
23    Custom(NetworkId),
24}
25
26/// Error for invalid network id.
27#[derive(Debug, Error)]
28#[error("Invalid network id: {0}")]
29pub struct InvalidNetworkId(String);
30
31/// Valid network id
32#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct NetworkId {
35    /// The network identifier string
36    pub id: String,
37}
38
39impl NetworkId {
40    /// Creates validated network id.
41    pub fn new(id: &str) -> Result<NetworkId, InvalidNetworkId> {
42        if id.contains('/') {
43            Err(InvalidNetworkId(id.to_owned()))
44        } else {
45            Ok(NetworkId { id: id.to_owned() })
46        }
47    }
48}
49
50impl AsRef<str> for NetworkId {
51    fn as_ref(&self) -> &str {
52        &self.id
53    }
54}
55
56impl Deref for NetworkId {
57    type Target = str;
58
59    fn deref(&self) -> &str {
60        &self.id
61    }
62}
63
64impl FromStr for NetworkId {
65    type Err = InvalidNetworkId;
66
67    fn from_str(s: &str) -> Result<Self, Self::Err> {
68        NetworkId::new(s)
69    }
70}
71
72impl fmt::Display for NetworkId {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        f.write_str(&self.id)
75    }
76}
77
78impl Network {
79    /// Creates a `Network::Custom` value.
80    pub fn custom(id: &str) -> Result<Network, InvalidNetworkId> {
81        Ok(Network::Custom(NetworkId::new(id)?))
82    }
83
84    /// Returns true if value is `Network::Custom` variant.
85    pub fn is_custom(&self) -> bool {
86        matches!(self, Network::Custom(_))
87    }
88
89    /// Get the network id.
90    pub fn id(&self) -> &str {
91        match self {
92            Network::Mainnet => "celestia",
93            Network::Arabica => "arabica-11",
94            Network::Mocha => "mocha-4",
95            Network::Custom(ref s) => &s.id,
96        }
97    }
98
99    /// Get official Celestia and Lumina bootnodes for the given network.
100    pub fn canonical_bootnodes(&self) -> impl Iterator<Item = Multiaddr> {
101        let peers: &[_] = match self {
102            Network::Mainnet => &[
103                "/dnsaddr/da-bootstrapper-1.celestia-bootstrap.net/p2p/12D3KooWSqZaLcn5Guypo2mrHr297YPJnV8KMEMXNjs3qAS8msw8",
104                "/dnsaddr/da-bootstrapper-2.celestia-bootstrap.net/p2p/12D3KooWQpuTFELgsUypqp9N4a1rKBccmrmQVY8Em9yhqppTJcXf",
105                "/dnsaddr/da-bootstrapper-3.celestia-bootstrap.net/p2p/12D3KooWKZCMcwGCYbL18iuw3YVpAZoyb1VBGbx9Kapsjw3soZgr",
106                "/dnsaddr/da-bootstrapper-4.celestia-bootstrap.net/p2p/12D3KooWE3fmRtHgfk9DCuQFfY3H3JYEnTU3xZozv1Xmo8KWrWbK",
107                "/dnsaddr/boot.celestia.pops.one/p2p/12D3KooWBBzzGy5hAHUQVh2vBvL25CKwJ7wbmooPcz4amQhzJHJq",
108                "/dnsaddr/celestia.qubelabs.io/p2p/12D3KooWAzucnC7yawvLbmVxv53ihhjbHFSVZCsPuuSzTg6A7wgx",
109                "/dnsaddr/celestia-bootstrapper.binary.builders/p2p/12D3KooWDKvTzMnfh9j7g4RpvU6BXwH3AydTrzr1HTW6TMBQ61HF",
110            ],
111            Network::Arabica => &[
112                "/dnsaddr/da-bridge-1.celestia-arabica-11.com/p2p/12D3KooWGqwzdEqM54Dce6LXzfFr97Bnhvm6rN7KM7MFwdomfm4S",
113                "/dnsaddr/da-full-1.celestia-arabica-11.com/p2p/12D3KooWCMGM5eZWVfCN9ZLAViGfLUWAfXP5pCm78NFKb9jpBtua",
114            ],
115            Network::Mocha => &[
116                "/dnsaddr/da-bridge-1-mocha-4.celestia-mocha.com/p2p/12D3KooWCBAbQbJSpCpCGKzqz3rAN4ixYbc63K68zJg9aisuAajg",
117                "/dnsaddr/da-full-1-mocha-4.celestia-mocha.com/p2p/12D3KooWCUHPLqQXZzpTx1x3TAsdn3vYmTNDhzg66yG8hqoxGGN8",
118                "/dnsaddr/mocha-boot.pops.one/p2p/12D3KooWDzNyDSvTBdKQAmnsUdAyQCQWwM3ReXTmPaaf6LzfNwRs",
119                "/dnsaddr/celestia-mocha.qubelabs.io/p2p/12D3KooWQVmHy7JpfxpKZfLjvn12GjvMgKrWdsHkFbV2kKqQFBCG",
120                "/dnsaddr/celestia-mocha4-bootstrapper.binary.builders/p2p/12D3KooWK6AYaPSe2EP99NP5G2DKwWLfMi6zHMYdD65KRJwdJSVU"
121            ],
122            Network::Custom(_) => &[],
123        };
124        peers
125            .iter()
126            .map(|s| s.parse().expect("Invalid bootstrap address"))
127    }
128}
129
130impl FromStr for Network {
131    type Err = InvalidNetworkId;
132
133    fn from_str(value: &str) -> Result<Self, InvalidNetworkId> {
134        match value {
135            "Mainnet" | "MainNet" | "mainnet" | "celestia" => Ok(Network::Mainnet),
136            "Arabica" | "arabica" | "arabica-11" => Ok(Network::Arabica),
137            "Mocha" | "mocha" | "mocha-4" => Ok(Network::Mocha),
138            custom_id => Network::custom(custom_id),
139        }
140    }
141}
142
143impl fmt::Display for Network {
144    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145        let s = match self {
146            Network::Mainnet => "Mainnet",
147            Network::Arabica => "Arabica",
148            Network::Mocha => "Mocha",
149            Network::Custom(ref s) => s,
150        };
151
152        f.write_str(s)
153    }
154}
155
156impl<'de> Deserialize<'de> for Network {
157    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
158    where
159        D: serde::Deserializer<'de>,
160    {
161        let s = String::deserialize(deserializer)?;
162        s.parse().map_err(serde::de::Error::custom)
163    }
164}
165
166impl Serialize for Network {
167    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
168    where
169        S: serde::Serializer,
170    {
171        self.to_string().serialize(serializer)
172    }
173}
174
175impl TryFrom<String> for Network {
176    type Error = InvalidNetworkId;
177
178    fn try_from(value: String) -> Result<Self, Self::Error> {
179        value.parse()
180    }
181}
182
183impl<'a> TryFrom<&'a String> for Network {
184    type Error = InvalidNetworkId;
185
186    fn try_from(value: &'a String) -> Result<Self, Self::Error> {
187        value.parse()
188    }
189}
190
191impl<'a> TryFrom<&'a str> for Network {
192    type Error = InvalidNetworkId;
193
194    fn try_from(value: &'a str) -> Result<Self, Self::Error> {
195        value.parse()
196    }
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202
203    #[test]
204    fn test_canonical_network_bootnodes() {
205        // canonical_network_bootnodes works on const data, test it doesn't panic and the data is there
206        let mainnet = Network::Mainnet.canonical_bootnodes();
207        assert_ne!(mainnet.count(), 0);
208
209        let arabica = Network::Arabica.canonical_bootnodes();
210        assert_ne!(arabica.count(), 0);
211
212        let mocha = Network::Mocha.canonical_bootnodes();
213        assert_ne!(mocha.count(), 0);
214
215        let id = NetworkId::new("private").unwrap();
216        let private = Network::Custom(id).canonical_bootnodes();
217        assert_eq!(private.count(), 0);
218    }
219
220    #[test]
221    fn check_network_id() {
222        Network::custom("foo").unwrap();
223        Network::custom("foo/bar").unwrap_err();
224    }
225}