fedimint_ln_common/
iroh.rs1use std::collections::BTreeMap;
2
3use fedimint_core::envs::{FM_IROH_ENABLE_DHT_ENV, is_env_var_set};
4use fedimint_core::iroh_prod::FM_IROH_DNS_FEDIMINT_PROD;
5use fedimint_core::util::SafeUrl;
6use fedimint_logging::LOG_NET_IROH;
7use iroh::discovery::pkarr::PkarrResolver;
8use iroh::endpoint::Connection;
9use iroh::{Endpoint, NodeAddr, NodeId};
10use serde::{Deserialize, Serialize};
11use tracing::{info, trace};
12
13pub const FEDIMINT_GATEWAY_ALPN: &[u8] = b"FEDIMINT_GATEWAY_ALPN";
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct IrohGatewayRequest {
17 pub route: String,
19
20 pub params: Option<serde_json::Value>,
22
23 pub password: Option<String>,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct IrohGatewayResponse {
29 pub status: u16,
30 pub body: serde_json::Value,
31}
32
33#[derive(Debug, Clone)]
34pub struct GatewayIrohConnector {
35 node_id: iroh::NodeId,
36 endpoint: Endpoint,
37 password: Option<String>,
38 connection_overrides: BTreeMap<NodeId, NodeAddr>,
39}
40
41impl GatewayIrohConnector {
42 pub async fn new(
43 iroh_pk: iroh::PublicKey,
44 password: Option<String>,
45 iroh_dns: Option<SafeUrl>,
46 ) -> anyhow::Result<Self> {
47 let mut builder = Endpoint::builder();
48
49 let iroh_dns_servers: Vec<_> = iroh_dns.map_or_else(
50 || {
51 FM_IROH_DNS_FEDIMINT_PROD
52 .into_iter()
53 .map(|url| {
54 SafeUrl::parse(url)
55 .expect("Hardcoded, can't fail")
56 .to_unsafe()
57 })
58 .collect()
59 },
60 |url| vec![url.to_unsafe()],
61 );
62
63 for iroh_dns in iroh_dns_servers {
64 builder = builder.add_discovery(|_| Some(PkarrResolver::new(iroh_dns)));
65 }
66
67 let mut builder = builder.relay_mode(iroh::RelayMode::Disabled);
69
70 if is_env_var_set(FM_IROH_ENABLE_DHT_ENV) {
72 #[cfg(not(target_family = "wasm"))]
73 {
74 builder = builder.discovery_dht();
75 }
76 } else {
77 info!(
78 target: LOG_NET_IROH,
79 "Iroh DHT is disabled"
80 );
81 }
82
83 {
85 #[cfg(target_family = "wasm")]
86 {
87 builder = builder.add_discovery(move |_| Some(PkarrResolver::n0_dns()));
88 }
89
90 #[cfg(not(target_family = "wasm"))]
91 {
92 builder = builder
93 .add_discovery(move |_| Some(iroh::discovery::dns::DnsDiscovery::n0_dns()));
94 }
95 }
96
97 let endpoint = builder.bind().await?;
98
99 Ok(Self {
100 node_id: iroh_pk,
101 endpoint,
102 password,
103 connection_overrides: BTreeMap::new(),
104 })
105 }
106
107 #[must_use]
108 pub fn with_connection_override(mut self, node: NodeId, addr: NodeAddr) -> Self {
109 self.connection_overrides.insert(node, addr);
110 self
111 }
112
113 async fn connect(&self) -> anyhow::Result<Connection> {
114 let connection = match self.connection_overrides.get(&self.node_id) {
115 Some(node_addr) => {
116 trace!(target: LOG_NET_IROH, node_id = %self.node_id, "Using a connectivity override for connection");
117 self.endpoint
118 .connect(node_addr.clone(), FEDIMINT_GATEWAY_ALPN)
119 .await?
120 }
121 None => {
122 self.endpoint
123 .connect(self.node_id, FEDIMINT_GATEWAY_ALPN)
124 .await?
125 }
126 };
127
128 Ok(connection)
130 }
131
132 pub async fn request(
133 &self,
134 route: &str,
135 payload: Option<serde_json::Value>,
136 ) -> anyhow::Result<IrohGatewayResponse> {
137 let iroh_request = IrohGatewayRequest {
138 route: route.to_string(),
139 params: payload,
140 password: self.password.clone(),
141 };
142 let json = serde_json::to_vec(&iroh_request).expect("serialization cant fail");
143 let connection = self.connect().await?;
144 let (mut sink, mut stream) = connection.open_bi().await?;
145 sink.write_all(&json).await?;
146 sink.finish()?;
147 let response = stream.read_to_end(1_000_000).await?;
148 let iroh_response = serde_json::from_slice::<IrohGatewayResponse>(&response)?;
149 Ok(iroh_response)
150 }
151}