Skip to main content

ant_core/
network_defaults.rs

1//! Release-bundled bootstrap multiaddresses and portable payment defaults.
2
3use ant_protocol::{evm::Network, transport::MultiAddr};
4use serde::{Deserialize, Serialize};
5use std::collections::HashSet;
6
7use crate::browser::{parse_webrtc_direct_multiaddr, BrowserPaymentNetwork};
8
9const BOOTSTRAP: &str = include_str!("../resources/bootstrap_peers.toml");
10
11/// Validated bootstrap addresses, separated by transport.
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct BootstrapSeeds {
14    /// Native QUIC multiaddresses, including any supplied peer identity.
15    pub quic: Vec<MultiAddr>,
16    /// Complete certificate-pinned WebRTC Direct multiaddresses.
17    pub webrtc: Vec<String>,
18}
19
20/// A configuration error detected before opening any network connection.
21#[derive(Debug, thiserror::Error)]
22#[error("invalid bootstrap configuration: {0}")]
23pub struct NetworkDefaultsError(String);
24
25/// Parse a QUIC multiaddress, accepting legacy socket addresses for migration.
26pub fn parse_quic_seed(value: &str) -> Result<MultiAddr, NetworkDefaultsError> {
27    let address = if let Ok(socket) = value.parse::<std::net::SocketAddr>() {
28        MultiAddr::quic(socket)
29    } else {
30        value
31            .parse::<MultiAddr>()
32            .map_err(|e| NetworkDefaultsError(e.to_string()))?
33    };
34    if !address.is_quic() || address.socket_addr().is_none_or(|a| a.port() == 0) {
35        return Err(NetworkDefaultsError(
36            "expected a QUIC multiaddress with a nonzero port".into(),
37        ));
38    }
39    Ok(address)
40}
41
42/// Read both transport lists. Legacy `peers` is an alias for `quic`.
43pub fn parse_bootstrap_seeds(text: &str) -> Result<BootstrapSeeds, NetworkDefaultsError> {
44    #[derive(Deserialize)]
45    #[serde(deny_unknown_fields)]
46    struct Input {
47        #[serde(default, alias = "peers")]
48        quic: Vec<String>,
49        #[serde(default)]
50        webrtc: Vec<String>,
51    }
52    let input: Input = toml::from_str(text).map_err(|e| NetworkDefaultsError(e.to_string()))?;
53    let mut seen = HashSet::new();
54    let mut quic = Vec::new();
55    for value in input.quic {
56        let address = parse_quic_seed(&value)?;
57        if !seen.insert(address.to_string()) {
58            return Err(NetworkDefaultsError("duplicate QUIC seed".into()));
59        }
60        quic.push(address);
61    }
62    let mut webrtc = Vec::new();
63    for value in input.webrtc {
64        let address = parse_webrtc_direct_multiaddr(&value)
65            .map_err(|e| NetworkDefaultsError(e.to_string()))?
66            .multiaddr;
67        if !seen.insert(address.clone()) {
68            return Err(NetworkDefaultsError("duplicate WebRTC seed".into()));
69        }
70        webrtc.push(address);
71    }
72    Ok(BootstrapSeeds { quic, webrtc })
73}
74
75/// The same resource shipped by native release archives.
76pub fn bundled_bootstrap_seeds() -> Result<BootstrapSeeds, NetworkDefaultsError> {
77    parse_bootstrap_seeds(BOOTSTRAP)
78}
79
80/// Browser-only projection of the trusted mainnet configuration.
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct BrowserNetworkDefaults {
83    pub id: String,
84    pub seeds: Vec<String>,
85    pub payment: BrowserPaymentNetwork,
86    pub rpc_url: String,
87}
88
89/// Return defaults without connecting, even before WebRTC seeds are published.
90pub fn browser_mainnet_defaults() -> Result<BrowserNetworkDefaults, NetworkDefaultsError> {
91    let network = Network::ArbitrumOne;
92    Ok(BrowserNetworkDefaults {
93        id: "mainnet".into(),
94        seeds: bundled_bootstrap_seeds()?.webrtc,
95        payment: BrowserPaymentNetwork {
96            chain_id: 42161,
97            payment_token_address: network.payment_token_address().to_string().to_lowercase(),
98            payment_vault_address: network.payment_vault_address().to_string().to_lowercase(),
99        },
100        rpc_url: network.rpc_url().to_string(),
101    })
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn bundled_defaults_share_evm_identity_and_exclude_quic() {
110        let seeds = bundled_bootstrap_seeds().unwrap();
111        assert!(!seeds.quic.is_empty());
112        let browser = browser_mainnet_defaults().unwrap();
113        assert_eq!(browser.seeds, seeds.webrtc);
114        assert_eq!(browser.payment.chain_id, 42161);
115        assert_eq!(browser.rpc_url, Network::ArbitrumOne.rpc_url().as_str());
116        assert_eq!(
117            browser.payment.payment_vault_address,
118            Network::ArbitrumOne
119                .payment_vault_address()
120                .to_string()
121                .to_lowercase()
122        );
123    }
124
125    #[test]
126    fn preserves_quic_peer_pins_and_legacy_sockets() {
127        let pin = format!("/ip6/::1/udp/10000/quic/p2p/{}", "ab".repeat(32));
128        let seeds = parse_bootstrap_seeds(&format!("quic = [\"{pin}\"]\nwebrtc = []")).unwrap();
129        assert_eq!(seeds.quic[0].to_string(), pin);
130        let legacy = parse_bootstrap_seeds("peers = [\"127.0.0.1:10000\"]").unwrap();
131        assert_eq!(legacy.quic[0].to_string(), "/ip4/127.0.0.1/udp/10000/quic");
132    }
133
134    #[test]
135    fn keeps_valid_webrtc_seeds_separate_and_rejects_duplicates() {
136        use base64::Engine;
137        let mut hash = vec![0x12, 0x20];
138        hash.extend([0xbb; 32]);
139        let cert = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(hash);
140        let endpoint = format!(
141            "/ip4/127.0.0.1/udp/24000/webrtc-direct/certhash/u{cert}/p2p/{}",
142            "ab".repeat(32)
143        );
144        let input =
145            format!("quic = [\"/ip4/127.0.0.1/udp/10000/quic\"]\nwebrtc = [\"{endpoint}\"]");
146        let parsed = parse_bootstrap_seeds(&input).unwrap();
147        assert_eq!(parsed.quic.len(), 1);
148        assert_eq!(parsed.webrtc, vec![endpoint.clone()]);
149        assert!(
150            parse_bootstrap_seeds(&format!("webrtc = [\"{endpoint}\", \"{endpoint}\"]")).is_err()
151        );
152        assert!(parse_bootstrap_seeds(&format!("quic = [\"{endpoint}\"]")).is_err());
153    }
154
155    #[test]
156    fn rejects_wrong_transport_duplicates_and_invalid_seeds() {
157        for input in [
158            "quic = [\"/ip4/127.0.0.1/tcp/10000\"]",
159            "quic = [\"127.0.0.1:0\"]",
160            "quic = [\"invalid\"]",
161            "quic = [\"127.0.0.1:10000\", \"/ip4/127.0.0.1/udp/10000/quic\"]",
162            "webrtc = [\"/ip4/127.0.0.1/udp/10000/quic\"]",
163            "webrtc = [\"127.0.0.1:10000\"]",
164        ] {
165            assert!(parse_bootstrap_seeds(input).is_err(), "{input}");
166        }
167    }
168}