ant_core/
network_defaults.rs1use 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#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct BootstrapSeeds {
14 pub quic: Vec<MultiAddr>,
16 pub webrtc: Vec<String>,
18}
19
20#[derive(Debug, thiserror::Error)]
22#[error("invalid bootstrap configuration: {0}")]
23pub struct NetworkDefaultsError(String);
24
25pub 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
42pub 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
75pub fn bundled_bootstrap_seeds() -> Result<BootstrapSeeds, NetworkDefaultsError> {
77 parse_bootstrap_seeds(BOOTSTRAP)
78}
79
80#[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
89pub 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}