Skip to main content

fedimint_server/net/p2p_connector/iroh/
endpoint.rs

1//! Guardian P2P endpoint construction for Iroh 1.0.
2//!
3//! This server-private builder is intentionally additive. The shared
4//! `fedimint_core::net::iroh` builder remains on Iroh 0.35 for guardian API and
5//! gateway callers while guardian P2P migrates independently.
6
7use std::borrow::Cow;
8use std::net::SocketAddr;
9
10use anyhow::Context as _;
11use fedimint_core::envs::{
12    FM_IROH_DHT_ENABLE_ENV, FM_IROH_N0_DISCOVERY_ENABLE_ENV, FM_IROH_PKARR_PUBLISHER_ENABLE_ENV,
13    FM_IROH_PKARR_RESOLVER_ENABLE_ENV, FM_IROH_RELAYS_ENABLE_ENV, is_env_var_set,
14    is_env_var_set_opt,
15};
16use fedimint_core::net::iroh::{IROH_IDLE_TIMEOUT, IROH_KEEP_ALIVE_INTERVAL};
17use fedimint_core::util::SafeUrl;
18use fedimint_logging::LOG_NET_IROH;
19use iroh_next::address_lookup::{AddrFilter, DnsAddressLookup, PkarrPublisher, PkarrResolver};
20use iroh_next::endpoint::presets::Minimal;
21use iroh_next::endpoint::{Builder, QuicTransportConfig};
22use iroh_next::{Endpoint, RelayMode, RelayUrl, SecretKey, TransportAddr};
23use tracing::{debug, info, warn};
24
25/// Build and bind an Iroh 1.0 endpoint using guardian P2P policy.
26pub(super) async fn build_iroh_endpoint(
27    secret_key: SecretKey,
28    bind_addr: SocketAddr,
29    iroh_dns: Option<SafeUrl>,
30    iroh_relays: Vec<SafeUrl>,
31    alpn: &[u8],
32) -> anyhow::Result<Endpoint> {
33    let relay_mode = if !is_env_var_set_opt(FM_IROH_RELAYS_ENABLE_ENV).unwrap_or(true) {
34        warn!(target: LOG_NET_IROH, "Iroh relays are disabled");
35        RelayMode::Disabled
36    } else if iroh_relays.is_empty() {
37        RelayMode::Default
38    } else {
39        RelayMode::Custom(
40            iroh_relays
41                .into_iter()
42                .map(|url| RelayUrl::from(url.to_unsafe()))
43                .collect(),
44        )
45    };
46
47    let mut builder = Endpoint::builder(Minimal);
48
49    if let Some(iroh_dns) = iroh_dns.map(SafeUrl::to_unsafe) {
50        if is_env_var_set_opt(FM_IROH_PKARR_PUBLISHER_ENABLE_ENV).unwrap_or(true) {
51            builder = builder.address_lookup(
52                PkarrPublisher::builder(iroh_dns.clone()).addr_filter(guardian_pkarr_addr_filter()),
53            );
54        } else {
55            warn!(target: LOG_NET_IROH, "Iroh pkarr publisher is disabled");
56        }
57
58        if is_env_var_set_opt(FM_IROH_PKARR_RESOLVER_ENABLE_ENV).unwrap_or(true) {
59            builder = builder.address_lookup(PkarrResolver::builder(iroh_dns));
60        } else {
61            warn!(target: LOG_NET_IROH, "Iroh pkarr resolver is disabled");
62        }
63    }
64
65    if is_env_var_set(FM_IROH_DHT_ENABLE_ENV) {
66        debug!(target: LOG_NET_IROH, "Iroh DHT is enabled");
67        builder = builder.address_lookup(iroh_mainline_address_lookup::DhtAddressLookup::builder());
68    } else {
69        info!(target: LOG_NET_IROH, "Iroh DHT is disabled");
70    }
71
72    builder = add_n0_discovery(builder);
73
74    let transport_config = QuicTransportConfig::builder()
75        .max_idle_timeout(Some(
76            IROH_IDLE_TIMEOUT
77                .try_into()
78                .expect("idle timeout fits in IdleTimeout"),
79        ))
80        .keep_alive_interval(IROH_KEEP_ALIVE_INTERVAL)
81        .build();
82
83    let endpoint = Box::pin(
84        builder
85            .relay_mode(relay_mode)
86            .secret_key(secret_key)
87            .alpns(vec![alpn.to_vec()])
88            .transport_config(transport_config)
89            // The Iroh builder defaults to wildcard IPv4 and IPv6 sockets.
90            // Clear both so the configured bind address remains authoritative.
91            .clear_ip_transports()
92            .bind_addr(bind_addr)
93            .context("Invalid Iroh bind address")?
94            .bind(),
95    )
96    .await
97    .context("Failed to bind Iroh endpoint")?;
98
99    info!(
100        target: LOG_NET_IROH,
101        %bind_addr,
102        endpoint_id = %endpoint.id(),
103        endpoint_id_pkarr = %z32::encode(endpoint.id().as_bytes()),
104        "Iroh P2P server endpoint"
105    );
106
107    Ok(endpoint)
108}
109
110fn add_n0_discovery(mut builder: Builder) -> Builder {
111    if !is_env_var_set_opt(FM_IROH_N0_DISCOVERY_ENABLE_ENV).unwrap_or(true) {
112        warn!(target: LOG_NET_IROH, "Iroh n0 discovery is disabled");
113        return builder;
114    }
115
116    // Publish our address as well as resolving peer addresses. Starting from
117    // `Minimal` installs neither half automatically.
118    builder = builder
119        .address_lookup(PkarrPublisher::n0_dns().addr_filter(guardian_pkarr_addr_filter()))
120        .address_lookup(DnsAddressLookup::n0_dns());
121
122    if is_env_var_set_opt(FM_IROH_PKARR_RESOLVER_ENABLE_ENV).unwrap_or(true) {
123        builder.address_lookup(PkarrResolver::n0_dns())
124    } else {
125        warn!(target: LOG_NET_IROH, "Iroh pkarr resolver is disabled");
126        builder
127    }
128}
129
130/// Preserve Iroh 0.35's Pkarr publication policy for guardian connectivity.
131///
132/// Prefer relays when available to avoid publishing direct IP addresses. When
133/// no relay is available, direct addresses must be published so relay-disabled
134/// guardians remain discoverable by endpoint ID.
135pub(super) fn guardian_pkarr_addr_filter() -> AddrFilter {
136    AddrFilter::new(|addrs| {
137        let publish_relay = addrs.iter().any(TransportAddr::is_relay);
138        Cow::Owned(
139            addrs
140                .iter()
141                .filter(|addr| {
142                    if publish_relay {
143                        addr.is_relay()
144                    } else {
145                        matches!(addr, TransportAddr::Ip(_))
146                    }
147                })
148                .cloned()
149                .collect(),
150        )
151    })
152}