fedimint_server/net/
p2p_connection.rs1#[cfg(test)]
2mod tests;
3
4use std::io::Cursor;
5use std::pin::Pin;
6use std::time::Duration;
7
8use anyhow::Context;
9use async_trait::async_trait;
10use bytes::{Bytes, BytesMut};
11use fedimint_core::encoding::{Decodable, Encodable};
12use fedimint_core::module::registry::ModuleDecoderRegistry;
13use fedimint_server_core::dashboard_ui::ConnectionType;
14use futures::{SinkExt, Stream, StreamExt};
15use iroh_next::endpoint::{Connection as IrohV1Connection, RecvStream as IrohV1RecvStream};
16use serde::Serialize;
17use serde::de::DeserializeOwned;
18use tokio::net::TcpStream;
19use tokio_rustls::TlsStream;
20use tokio_util::codec::{Framed, LengthDelimitedCodec};
21
22const MAX_P2P_MESSAGE_SIZE: usize = 10_000_000;
25
26pub type DynP2PConnection<M> = Box<dyn IP2PConnection<M>>;
27
28pub type DynIP2PFrame<M> = Box<dyn IP2PFrame<M>>;
29
30pub type DynConnectionStatusUpdates = Pin<Box<dyn Stream<Item = ()> + Send + 'static>>;
33
34#[async_trait]
35pub trait IP2PFrame<M>: Send + 'static {
36 async fn read_to_end(&mut self) -> anyhow::Result<M>;
39
40 fn into_dyn(self) -> DynIP2PFrame<M>
41 where
42 Self: Sized,
43 {
44 Box::new(self)
45 }
46}
47
48#[async_trait]
49pub trait IP2PConnection<M>: Send + 'static {
50 async fn send(&mut self, message: M) -> anyhow::Result<()>;
53
54 async fn receive(&mut self) -> anyhow::Result<DynIP2PFrame<M>>;
57
58 fn rtt(&self) -> Option<Duration>;
60
61 fn connection_type(&self) -> Option<ConnectionType> {
63 None
64 }
65
66 fn connection_status_updates(&self) -> Option<DynConnectionStatusUpdates> {
74 None
75 }
76
77 fn into_dyn(self) -> DynP2PConnection<M>
78 where
79 Self: Sized,
80 {
81 Box::new(self)
82 }
83}
84
85#[async_trait]
88impl<M> IP2PFrame<M> for BytesMut
89where
90 M: Decodable + DeserializeOwned + Send + 'static,
91{
92 async fn read_to_end(&mut self) -> anyhow::Result<M> {
93 if let Ok(message) = M::consensus_decode_whole(self, &ModuleDecoderRegistry::default()) {
94 return Ok(message);
95 }
96
97 Ok(bincode::deserialize_from(Cursor::new(&**self))?)
98 }
99}
100
101#[async_trait]
102impl<M> IP2PConnection<M> for Framed<TlsStream<TcpStream>, LengthDelimitedCodec>
103where
104 M: Encodable + Decodable + Serialize + DeserializeOwned + Send + 'static,
105{
106 async fn send(&mut self, message: M) -> anyhow::Result<()> {
107 let mut bytes = Vec::new();
108
109 bincode::serialize_into(&mut bytes, &message)?;
110
111 SinkExt::send(self, Bytes::from_owner(bytes)).await?;
112
113 Ok(())
114 }
115
116 async fn receive(&mut self) -> anyhow::Result<DynIP2PFrame<M>> {
117 let message = self
118 .next()
119 .await
120 .context("Framed stream is closed")??
121 .into_dyn();
122
123 Ok(message)
124 }
125
126 fn rtt(&self) -> Option<Duration> {
127 None
128 }
129
130 fn connection_type(&self) -> Option<ConnectionType> {
131 Some(ConnectionType::Direct)
132 }
133}
134
135#[async_trait]
138impl<M> IP2PFrame<M> for iroh::endpoint::RecvStream
139where
140 M: Decodable + DeserializeOwned + Send + 'static,
141{
142 async fn read_to_end(&mut self) -> anyhow::Result<M> {
143 let bytes = self.read_to_end(MAX_P2P_MESSAGE_SIZE).await?;
144
145 if let Ok(message) = M::consensus_decode_whole(&bytes, &ModuleDecoderRegistry::default()) {
146 return Ok(message);
147 }
148
149 Ok(bincode::deserialize_from(Cursor::new(&bytes))?)
150 }
151}
152
153#[async_trait]
154impl<M> IP2PConnection<M> for iroh::endpoint::Connection
155where
156 M: Encodable + Decodable + Serialize + DeserializeOwned + Send + 'static,
157{
158 async fn send(&mut self, message: M) -> anyhow::Result<()> {
159 let mut bytes = Vec::new();
160
161 bincode::serialize_into(&mut bytes, &message)?;
162
163 let mut sink = self.open_uni().await?;
164
165 sink.write_all(&bytes).await?;
166 sink.finish()?;
167
168 Ok(())
169 }
170
171 async fn receive(&mut self) -> anyhow::Result<DynIP2PFrame<M>> {
172 Ok(self.accept_uni().await?.into_dyn())
173 }
174
175 fn rtt(&self) -> Option<Duration> {
176 Some(iroh::endpoint::Connection::rtt(self))
177 }
178}
179
180#[async_trait]
183impl<M> IP2PFrame<M> for IrohV1RecvStream
184where
185 M: Decodable + DeserializeOwned + Send + 'static,
186{
187 async fn read_to_end(&mut self) -> anyhow::Result<M> {
188 let bytes = self.read_to_end(MAX_P2P_MESSAGE_SIZE).await?;
189
190 if let Ok(message) = M::consensus_decode_whole(&bytes, &ModuleDecoderRegistry::default()) {
191 return Ok(message);
192 }
193
194 Ok(bincode::deserialize_from(Cursor::new(&bytes))?)
195 }
196}
197
198#[async_trait]
199impl<M> IP2PConnection<M> for IrohV1Connection
200where
201 M: Encodable + Decodable + Serialize + DeserializeOwned + Send + 'static,
202{
203 async fn send(&mut self, message: M) -> anyhow::Result<()> {
204 let mut bytes = Vec::new();
205
206 bincode::serialize_into(&mut bytes, &message)?;
207
208 let mut sink = self.open_uni().await?;
209
210 sink.write_all(&bytes).await?;
211
212 sink.finish()?;
213
214 Ok(())
215 }
216
217 async fn receive(&mut self) -> anyhow::Result<DynIP2PFrame<M>> {
218 Ok(self.accept_uni().await?.into_dyn())
219 }
220
221 fn rtt(&self) -> Option<Duration> {
222 self.paths()
223 .iter()
224 .find(iroh_next::endpoint::Path::is_selected)
225 .and_then(|path| IrohV1Connection::rtt(self, path.id()))
226 }
227
228 fn connection_type(&self) -> Option<ConnectionType> {
229 connection_type_from_paths(self.paths().iter().map(|path| IrohPath {
230 selected: path.is_selected(),
231 kind: if path.is_ip() {
232 IrohPathKind::Direct
233 } else if path.is_relay() {
234 IrohPathKind::Relay
235 } else {
236 IrohPathKind::Unknown
237 },
238 }))
239 }
240
241 fn connection_status_updates(&self) -> Option<DynConnectionStatusUpdates> {
242 Some(Box::pin(self.path_events().map(|_| ())))
243 }
244}
245
246#[derive(Clone, Copy)]
247enum IrohPathKind {
248 Direct,
249 Relay,
250 Unknown,
251}
252
253#[derive(Clone, Copy)]
254struct IrohPath {
255 selected: bool,
256 kind: IrohPathKind,
257}
258
259fn connection_type_from_paths(paths: impl IntoIterator<Item = IrohPath>) -> Option<ConnectionType> {
260 let mut direct = false;
261 let mut relay = false;
262 for path in paths {
263 if path.selected {
264 direct |= matches!(path.kind, IrohPathKind::Direct);
265 relay |= matches!(path.kind, IrohPathKind::Relay);
266 }
267 }
268
269 match (direct, relay) {
270 (true, true) => Some(ConnectionType::Mixed),
271 (true, false) => Some(ConnectionType::Direct),
272 (false, true) => Some(ConnectionType::Relay),
273 (false, false) => None,
274 }
275}