fedimint_server/net/
p2p_connection.rs1#[cfg(test)]
2mod tests;
3
4use std::io::Cursor;
5use std::marker::PhantomData;
6use std::pin::Pin;
7use std::sync::Arc;
8use std::time::Duration;
9
10use anyhow::Context;
11use async_trait::async_trait;
12use bytes::{Bytes, BytesMut};
13use fedimint_core::encoding::{Decodable, Encodable};
14use fedimint_core::module::registry::ModuleDecoderRegistry;
15use fedimint_server_core::dashboard_ui::ConnectionType;
16use futures::stream::{SplitSink, SplitStream};
17use futures::{SinkExt, Stream, StreamExt};
18use iroh_next::endpoint::{Connection as IrohV1Connection, RecvStream as IrohV1RecvStream};
19use serde::Serialize;
20use serde::de::DeserializeOwned;
21use tokio::net::TcpStream;
22use tokio_rustls::TlsStream;
23use tokio_util::codec::{Framed, LengthDelimitedCodec};
24
25pub const MAX_P2P_MESSAGE_SIZE: usize = 10_000_000;
28
29pub type DynP2PConnection<M> = Arc<dyn IP2PConnection<M>>;
30
31pub type DynIP2PFrame<M> = Box<dyn IP2PFrame<M>>;
32
33pub type DynConnectionStatusUpdates = Pin<Box<dyn Stream<Item = ()> + Send + 'static>>;
36
37#[async_trait]
38pub trait IP2PFrame<M>: Send + 'static {
39 async fn read_to_end(&mut self) -> anyhow::Result<M>;
42
43 fn into_dyn(self) -> DynIP2PFrame<M>
44 where
45 Self: Sized,
46 {
47 Box::new(self)
48 }
49}
50
51#[async_trait]
52pub trait IP2PConnection<M>: Send + Sync + 'static {
53 async fn send(&self, message: M) -> anyhow::Result<()>;
66
67 async fn receive(&self) -> anyhow::Result<DynIP2PFrame<M>>;
70
71 fn rtt(&self) -> Option<Duration>;
73
74 fn connection_type(&self) -> Option<ConnectionType> {
76 None
77 }
78
79 fn connection_status_updates(&self) -> Option<DynConnectionStatusUpdates> {
87 None
88 }
89
90 fn into_dyn(self) -> DynP2PConnection<M>
91 where
92 Self: Sized,
93 {
94 Arc::new(self)
95 }
96}
97
98#[async_trait]
101impl<M> IP2PFrame<M> for BytesMut
102where
103 M: Decodable + DeserializeOwned + Send + 'static,
104{
105 async fn read_to_end(&mut self) -> anyhow::Result<M> {
106 if let Ok(message) = M::consensus_decode_whole(self, &ModuleDecoderRegistry::default()) {
107 return Ok(message);
108 }
109
110 Ok(bincode::deserialize_from(Cursor::new(&**self))?)
111 }
112}
113
114type TlsFramed = Framed<TlsStream<TcpStream>, LengthDelimitedCodec>;
115
116pub struct TlsP2PConnection<M> {
122 sink: tokio::sync::Mutex<SplitSink<TlsFramed, Bytes>>,
123 stream: tokio::sync::Mutex<SplitStream<TlsFramed>>,
124 _message: PhantomData<fn(M) -> M>,
127}
128
129impl<M> TlsP2PConnection<M> {
130 pub fn new(framed: TlsFramed) -> Self {
131 let (sink, stream) = framed.split();
132
133 Self {
134 sink: tokio::sync::Mutex::new(sink),
135 stream: tokio::sync::Mutex::new(stream),
136 _message: PhantomData,
137 }
138 }
139}
140
141#[async_trait]
142impl<M> IP2PConnection<M> for TlsP2PConnection<M>
143where
144 M: Encodable + Decodable + Serialize + DeserializeOwned + Send + 'static,
145{
146 async fn send(&self, message: M) -> anyhow::Result<()> {
147 let mut bytes = Vec::new();
148
149 bincode::serialize_into(&mut bytes, &message)?;
150
151 let mut sink = self
155 .sink
156 .try_lock()
157 .expect("send has at most one caller at a time");
158
159 SinkExt::send(&mut *sink, Bytes::from_owner(bytes)).await?;
160
161 Ok(())
162 }
163
164 async fn receive(&self) -> anyhow::Result<DynIP2PFrame<M>> {
165 let mut stream = self
166 .stream
167 .try_lock()
168 .expect("receive has at most one caller at a time");
169
170 let message = stream
171 .next()
172 .await
173 .context("Framed stream is closed")??
174 .into_dyn();
175
176 Ok(message)
177 }
178
179 fn rtt(&self) -> Option<Duration> {
180 None
181 }
182
183 fn connection_type(&self) -> Option<ConnectionType> {
184 Some(ConnectionType::Direct)
185 }
186}
187
188#[async_trait]
191impl<M> IP2PFrame<M> for iroh::endpoint::RecvStream
192where
193 M: Decodable + DeserializeOwned + Send + 'static,
194{
195 async fn read_to_end(&mut self) -> anyhow::Result<M> {
196 let bytes = self.read_to_end(MAX_P2P_MESSAGE_SIZE).await?;
197
198 if let Ok(message) = M::consensus_decode_whole(&bytes, &ModuleDecoderRegistry::default()) {
199 return Ok(message);
200 }
201
202 Ok(bincode::deserialize_from(Cursor::new(&bytes))?)
203 }
204}
205
206#[async_trait]
207impl<M> IP2PConnection<M> for iroh::endpoint::Connection
208where
209 M: Encodable + Decodable + Serialize + DeserializeOwned + Send + 'static,
210{
211 async fn send(&self, message: M) -> anyhow::Result<()> {
212 let mut bytes = Vec::new();
213
214 bincode::serialize_into(&mut bytes, &message)?;
215
216 let mut sink = self.open_uni().await?;
217
218 sink.write_all(&bytes).await?;
219 sink.finish()?;
220
221 Ok(())
222 }
223
224 async fn receive(&self) -> anyhow::Result<DynIP2PFrame<M>> {
225 Ok(self.accept_uni().await?.into_dyn())
226 }
227
228 fn rtt(&self) -> Option<Duration> {
229 Some(iroh::endpoint::Connection::rtt(self))
230 }
231}
232
233#[async_trait]
236impl<M> IP2PFrame<M> for IrohV1RecvStream
237where
238 M: Decodable + DeserializeOwned + Send + 'static,
239{
240 async fn read_to_end(&mut self) -> anyhow::Result<M> {
241 let bytes = self.read_to_end(MAX_P2P_MESSAGE_SIZE).await?;
242
243 if let Ok(message) = M::consensus_decode_whole(&bytes, &ModuleDecoderRegistry::default()) {
244 return Ok(message);
245 }
246
247 Ok(bincode::deserialize_from(Cursor::new(&bytes))?)
248 }
249}
250
251#[async_trait]
252impl<M> IP2PConnection<M> for IrohV1Connection
253where
254 M: Encodable + Decodable + Serialize + DeserializeOwned + Send + 'static,
255{
256 async fn send(&self, message: M) -> anyhow::Result<()> {
257 let mut bytes = Vec::new();
258
259 bincode::serialize_into(&mut bytes, &message)?;
260
261 let mut sink = self.open_uni().await?;
262
263 sink.write_all(&bytes).await?;
264
265 sink.finish()?;
266
267 Ok(())
268 }
269
270 async fn receive(&self) -> anyhow::Result<DynIP2PFrame<M>> {
271 Ok(self.accept_uni().await?.into_dyn())
272 }
273
274 fn rtt(&self) -> Option<Duration> {
275 self.paths()
276 .iter()
277 .find(iroh_next::endpoint::Path::is_selected)
278 .and_then(|path| IrohV1Connection::rtt(self, path.id()))
279 }
280
281 fn connection_type(&self) -> Option<ConnectionType> {
282 connection_type_from_paths(self.paths().iter().map(|path| IrohPath {
283 selected: path.is_selected(),
284 kind: if path.is_ip() {
285 IrohPathKind::Direct
286 } else if path.is_relay() {
287 IrohPathKind::Relay
288 } else {
289 IrohPathKind::Unknown
290 },
291 }))
292 }
293
294 fn connection_status_updates(&self) -> Option<DynConnectionStatusUpdates> {
295 Some(Box::pin(self.path_events().map(|_| ())))
296 }
297}
298
299#[derive(Clone, Copy)]
300enum IrohPathKind {
301 Direct,
302 Relay,
303 Unknown,
304}
305
306#[derive(Clone, Copy)]
307struct IrohPath {
308 selected: bool,
309 kind: IrohPathKind,
310}
311
312fn connection_type_from_paths(paths: impl IntoIterator<Item = IrohPath>) -> Option<ConnectionType> {
313 let mut direct = false;
314 let mut relay = false;
315 for path in paths {
316 if path.selected {
317 direct |= matches!(path.kind, IrohPathKind::Direct);
318 relay |= matches!(path.kind, IrohPathKind::Relay);
319 }
320 }
321
322 match (direct, relay) {
323 (true, true) => Some(ConnectionType::Mixed),
324 (true, false) => Some(ConnectionType::Direct),
325 (false, true) => Some(ConnectionType::Relay),
326 (false, false) => None,
327 }
328}