fedimint_server/net/p2p_connector/
iroh.rs1use std::collections::BTreeMap;
2use std::net::SocketAddr;
3
4use anyhow::{Context as _, ensure};
5use async_trait::async_trait;
6use fedimint_core::PeerId;
7use fedimint_core::encoding::{Decodable, Encodable};
8use fedimint_core::envs::{FM_IROH_CONNECT_OVERRIDES_PLAIN_ENV, parse_kv_list_from_env};
9use fedimint_core::net::STANDARD_FEDIMINT_P2P_PORT;
10use fedimint_core::net::iroh::build_iroh_endpoint;
11use fedimint_core::util::SafeUrl;
12use fedimint_logging::LOG_NET_IROH;
13use fedimint_server_core::dashboard_ui::ConnectionType;
14use iroh::{Endpoint, NodeAddr, NodeId, SecretKey};
15use serde::Serialize;
16use serde::de::DeserializeOwned;
17use tracing::trace;
18
19use super::IP2PConnector;
20use crate::net::p2p_connection::{DynP2PConnection, IP2PConnection as _};
21
22pub fn parse_p2p(url: &SafeUrl) -> anyhow::Result<String> {
24 ensure!(url.scheme() == "fedimint", "p2p url has invalid scheme");
25
26 let host = url.host_str().context("p2p url is missing host")?;
27
28 let port = url.port().unwrap_or(STANDARD_FEDIMINT_P2P_PORT);
29
30 Ok(format!("{host}:{port}"))
31}
32
33#[derive(Debug, Clone)]
34pub struct IrohConnector {
35 pub(crate) node_ids: BTreeMap<PeerId, NodeId>,
37 pub(crate) endpoint: Endpoint,
39 pub(crate) connection_overrides: BTreeMap<NodeId, NodeAddr>,
43}
44
45pub(crate) const FEDIMINT_P2P_ALPN: &[u8] = b"FEDIMINT_P2P_ALPN";
46
47impl IrohConnector {
48 pub async fn new(
49 secret_key: SecretKey,
50 p2p_bind_addr: SocketAddr,
51 iroh_dns: Option<SafeUrl>,
52 iroh_relays: Vec<SafeUrl>,
53 node_ids: BTreeMap<PeerId, NodeId>,
54 ) -> anyhow::Result<Self> {
55 let mut s =
56 Self::new_no_overrides(secret_key, p2p_bind_addr, iroh_dns, iroh_relays, node_ids)
57 .await?;
58
59 for (k, v) in
65 parse_kv_list_from_env::<NodeId, SocketAddr>(FM_IROH_CONNECT_OVERRIDES_PLAIN_ENV)?
66 {
67 s = s.with_connection_override(k, NodeAddr::new(k).with_direct_addresses([v]));
68 }
69
70 Ok(s)
71 }
72
73 pub async fn new_no_overrides(
74 secret_key: SecretKey,
75 bind_addr: SocketAddr,
76 iroh_dns: Option<SafeUrl>,
77 iroh_relays: Vec<SafeUrl>,
78 node_ids: BTreeMap<PeerId, NodeId>,
79 ) -> anyhow::Result<Self> {
80 let identity = *node_ids
81 .iter()
82 .find(|entry| entry.1 == &secret_key.public())
83 .expect("Our public key is not part of the keyset")
84 .0;
85
86 let endpoint = build_iroh_endpoint(
87 secret_key,
88 bind_addr,
89 iroh_dns,
90 iroh_relays,
91 FEDIMINT_P2P_ALPN,
92 )
93 .await?;
94
95 Ok(Self {
96 node_ids: node_ids
97 .into_iter()
98 .filter(|entry| entry.0 != identity)
99 .collect(),
100 endpoint,
101 connection_overrides: BTreeMap::default(),
102 })
103 }
104
105 pub fn with_connection_override(mut self, node: NodeId, addr: NodeAddr) -> Self {
106 self.connection_overrides.insert(node, addr);
107 self
108 }
109}
110
111#[async_trait]
112impl<M> IP2PConnector<M> for IrohConnector
113where
114 M: Encodable + Decodable + Serialize + DeserializeOwned + Send + 'static,
115{
116 fn peers(&self) -> Vec<PeerId> {
117 self.node_ids.keys().copied().collect()
118 }
119
120 async fn connect(&self, peer: PeerId) -> anyhow::Result<DynP2PConnection<M>> {
121 let node_id = *self.node_ids.get(&peer).expect("No node id found for peer");
122
123 let connection = match self.connection_overrides.get(&node_id) {
124 Some(node_addr) => {
125 trace!(target: LOG_NET_IROH, %node_id, "Using a connectivity override for connection");
126 self.endpoint
127 .connect(node_addr.clone(), FEDIMINT_P2P_ALPN)
128 .await?
129 }
130 None => self.endpoint.connect(node_id, FEDIMINT_P2P_ALPN).await?,
131 };
132
133 Ok(connection.into_dyn())
134 }
135
136 async fn accept(&self) -> anyhow::Result<(PeerId, DynP2PConnection<M>)> {
137 let connection = self
138 .endpoint
139 .accept()
140 .await
141 .context("Listener closed unexpectedly")?
142 .accept()?
143 .await?;
144
145 let node_id = connection.remote_node_id()?;
146
147 let auth_peer = self
148 .node_ids
149 .iter()
150 .find(|entry| entry.1 == &node_id)
151 .with_context(|| format!("Node id {node_id} is unknown"))?
152 .0;
153
154 Ok((*auth_peer, connection.into_dyn()))
155 }
156
157 fn connection_type(&self, peer: PeerId) -> Option<ConnectionType> {
158 let node_id = *self.node_ids.get(&peer).expect("No node id found for peer");
159
160 match self.endpoint.conn_type(node_id).ok()?.get().ok()? {
161 iroh::endpoint::ConnectionType::None => None,
162 iroh::endpoint::ConnectionType::Direct(..) => Some(ConnectionType::Direct),
163 iroh::endpoint::ConnectionType::Relay(..) => Some(ConnectionType::Relay),
164 iroh::endpoint::ConnectionType::Mixed(..) => Some(ConnectionType::Mixed),
165 }
166 }
167}