Skip to main content

devimint/vars/
net_overrides.rs

1use std::collections::{BTreeMap, BTreeSet};
2use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
3
4use fedimint_core::envs::{
5    FM_GW_IROH_CONNECT_OVERRIDES_ENV, FM_GW_IROH_CONNECT_OVERRIDES_PLAIN_ENV,
6    FM_IROH_CONNECT_OVERRIDES_ENV, FM_IROH_CONNECT_OVERRIDES_PLAIN_ENV,
7};
8use fedimint_core::{NumPeers, PeerId};
9use iroh_base::NodeAddr;
10use iroh_base::ticket::{NodeTicket, Ticket};
11
12use super::ToEnvVar;
13use crate::federation::{
14    FEDIMINTD_API_PORT_OFFSET, FEDIMINTD_P2P_PORT_OFFSET, PORTS_PER_FEDIMINTD,
15};
16
17#[derive(Debug, Clone)]
18pub struct FedimintIrohEndpoint {
19    node_id: iroh_base::NodeId,
20    secret_key: iroh_base::SecretKey,
21    port: u16,
22}
23
24impl FedimintIrohEndpoint {
25    fn new(port: u16) -> Self {
26        let secret_key = iroh_base::SecretKey::generate(&mut rand::thread_rng());
27
28        Self {
29            node_id: secret_key.public(),
30            secret_key,
31            port,
32        }
33    }
34
35    pub fn secret_key(&self) -> String {
36        self.secret_key.to_string()
37    }
38
39    pub fn port(&self) -> u16 {
40        self.port
41    }
42
43    pub fn node_id(&self) -> iroh_base::NodeId {
44        self.node_id
45    }
46
47    fn to_override(&self) -> String {
48        // The override is a `<node-id>=<socket-addr>` pair. Only used for local
49        // testing, so there is no relay and a single localhost address. iroh
50        // 1.0 no longer ships the `NodeTicket` format, so this stays version
51        // agnostic and the consumer rebuilds the address from its parts.
52        let addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, self.port));
53        format!("{}={},", self.node_id, addr)
54    }
55
56    /// The legacy iroh 0.35 `NodeTicket` override format, for binaries older
57    /// than 0.12 that still parse `FM_IROH_CONNECT_OVERRIDES` that way.
58    fn to_override_legacy(&self) -> String {
59        let node_addr = NodeAddr {
60            node_id: self.node_id,
61            relay_url: None,
62            direct_addresses: BTreeSet::from([SocketAddr::V4(SocketAddrV4::new(
63                Ipv4Addr::LOCALHOST,
64                self.port,
65            ))]),
66        };
67        format!(
68            "{}={},",
69            self.node_id,
70            Ticket::serialize(&NodeTicket::from(node_addr))
71        )
72    }
73}
74
75#[derive(Debug, Clone)]
76pub struct FedimintdPeerOverrides {
77    pub p2p: FedimintIrohEndpoint,
78    pub api: FedimintIrohEndpoint,
79    pub base_port: u16,
80}
81
82impl FedimintdPeerOverrides {
83    fn new(base_port: u16) -> Self {
84        Self {
85            p2p: FedimintIrohEndpoint::new(base_port + FEDIMINTD_P2P_PORT_OFFSET),
86            api: FedimintIrohEndpoint::new(base_port + FEDIMINTD_API_PORT_OFFSET),
87            base_port,
88        }
89    }
90}
91
92#[derive(Debug, Clone)]
93pub struct FederationNetOverrides {
94    pub base_port: u16,
95    pub num_peers: NumPeers,
96    pub peers: BTreeMap<PeerId, FedimintdPeerOverrides>,
97}
98
99impl FederationNetOverrides {
100    pub fn new(base_port: u16, num_peers: NumPeers) -> Self {
101        let peers = num_peers
102            .peer_ids()
103            .map(|peer_id| {
104                (peer_id, {
105                    FedimintdPeerOverrides::new(
106                        base_port
107                            + u16::try_from(peer_id.to_usize()).expect("Can't fail")
108                                * PORTS_PER_FEDIMINTD,
109                    )
110                })
111            })
112            .collect();
113        Self {
114            base_port,
115            num_peers,
116            peers,
117        }
118    }
119
120    pub fn peer_expect(&self, peer_id: PeerId) -> &FedimintdPeerOverrides {
121        self.peers.get(&peer_id).expect("Wrong peer_id?")
122    }
123}
124
125#[derive(Debug, Clone)]
126pub struct FederationsNetOverrides {
127    federations: Vec<FederationNetOverrides>,
128}
129
130impl FederationsNetOverrides {
131    pub fn new(base_port: u16, num_federations: usize, num_peers: NumPeers) -> Self {
132        Self {
133            federations: (0..num_federations)
134                .map(|fed_i| {
135                    FederationNetOverrides::new(
136                        base_port + fed_i as u16 * PORTS_PER_FEDIMINTD * num_peers.total() as u16,
137                        num_peers,
138                    )
139                })
140                .collect(),
141        }
142    }
143
144    pub fn fed_expect(&self, fed_i: usize) -> &FederationNetOverrides {
145        self.federations.get(fed_i).expect("Wrong fed_i")
146    }
147
148    pub fn peer_expect(&self, fed_i: usize, peer_id: PeerId) -> &FedimintdPeerOverrides {
149        self.federations
150            .get(fed_i)
151            .expect("Wrong fed_i")
152            .peers
153            .get(&peer_id)
154            .expect("Wrong peer_id?")
155    }
156
157    /// Guardian api/p2p overrides in the plain `<id>=<addr>` format, for
158    /// current binaries.
159    fn overrides_plain(&self) -> String {
160        self.federations
161            .iter()
162            .flat_map(|f| f.peers.values())
163            .map(|peer| format!("{},{}", peer.p2p.to_override(), peer.api.to_override()))
164            .collect::<Vec<String>>()
165            .join(",")
166    }
167
168    /// Guardian api/p2p overrides in the legacy `NodeTicket` format, for
169    /// binaries older than 0.12 that still parse the override that way.
170    fn overrides_legacy(&self) -> String {
171        self.federations
172            .iter()
173            .flat_map(|f| f.peers.values())
174            .map(|peer| {
175                format!(
176                    "{},{}",
177                    peer.p2p.to_override_legacy(),
178                    peer.api.to_override_legacy(),
179                )
180            })
181            .collect::<Vec<String>>()
182            .join(",")
183    }
184}
185
186impl ToEnvVar for FederationsNetOverrides {
187    fn to_env_values(&self, _base_env: &str) -> impl Iterator<Item = (String, String)> {
188        // Emit both formats under their own env vars so a single devimint run
189        // serves current binaries (plain) and pre-0.12 ones (legacy) at the
190        // same time; each reads only the var it understands.
191        vec![
192            (
193                FM_IROH_CONNECT_OVERRIDES_PLAIN_ENV.to_string(),
194                self.overrides_plain(),
195            ),
196            (
197                FM_IROH_CONNECT_OVERRIDES_ENV.to_string(),
198                self.overrides_legacy(),
199            ),
200        ]
201        .into_iter()
202    }
203}
204
205#[derive(Debug, Clone)]
206pub struct GatewaydNetOverrides {
207    pub gateway_iroh_endpoints: Vec<FedimintIrohEndpoint>,
208}
209
210impl GatewaydNetOverrides {
211    pub fn new(base_port: u16, num_gateways: usize) -> Self {
212        Self {
213            gateway_iroh_endpoints: (0..num_gateways)
214                .map(|gw_i| FedimintIrohEndpoint::new(base_port + gw_i as u16))
215                .collect(),
216        }
217    }
218}
219
220impl GatewaydNetOverrides {
221    /// Gateway overrides in the plain `<id>=<addr>` format, for current
222    /// binaries.
223    fn overrides_plain(&self) -> String {
224        self.gateway_iroh_endpoints
225            .iter()
226            .map(FedimintIrohEndpoint::to_override)
227            .collect::<Vec<String>>()
228            .join(",")
229    }
230
231    /// Gateway overrides in the legacy `NodeTicket` format, for binaries older
232    /// than 0.12 that still parse the override that way.
233    fn overrides_legacy(&self) -> String {
234        self.gateway_iroh_endpoints
235            .iter()
236            .map(FedimintIrohEndpoint::to_override_legacy)
237            .collect::<Vec<String>>()
238            .join(",")
239    }
240}
241
242impl ToEnvVar for GatewaydNetOverrides {
243    fn to_env_values(&self, _base_env: &str) -> impl Iterator<Item = (String, String)> {
244        // See `FederationsNetOverrides`: both formats are emitted side by side.
245        vec![
246            (
247                FM_GW_IROH_CONNECT_OVERRIDES_PLAIN_ENV.to_string(),
248                self.overrides_plain(),
249            ),
250            (
251                FM_GW_IROH_CONNECT_OVERRIDES_ENV.to_string(),
252                self.overrides_legacy(),
253            ),
254        ]
255        .into_iter()
256    }
257}