Skip to main content

karyon_p2p/
peer_pool.rs

1use std::{
2    collections::{HashMap, HashSet},
3    sync::Arc,
4};
5
6use log::{error, info, warn};
7
8use karyon_core::{
9    async_runtime::{lock::RwLock, Executor},
10    async_util::{TaskGroup, TaskResult},
11};
12
13use karyon_eventemitter::{EventEmitter, EventListener, EventTopic, EventValue};
14
15use karyon_net::Endpoint;
16
17use crate::{
18    access_control::{Action, PeerCandidate, Subject},
19    config::Config,
20    conn_queue::{ConnQueue, QueuedConn},
21    handshake::{handshake, HandshakeParams},
22    monitor::{Monitor, PoolEvent},
23    peer::{ConnDirection, Peer},
24    protocol::{Protocol, ProtocolConstructor, ProtocolID, ProtocolMeta},
25    Error, PeerID, Result,
26};
27
28/// Topic key for the peer-lifecycle event emitter.
29#[derive(Hash, PartialEq, Eq, Debug, Clone)]
30pub enum PeerEventTopic {
31    Lifecycle,
32}
33
34/// Peer-lifecycle events. Each registered listener receives every
35/// event independently.
36#[derive(Debug, Clone, EventValue)]
37pub enum PeerEvent {
38    /// Peer added after a successful handshake.
39    Added(PeerID),
40    /// Previously-added peer removed.
41    Removed(PeerID),
42    /// Handshake failed before the peer was added.
43    HandshakeFailed(Option<PeerID>),
44}
45
46impl EventTopic for PeerEvent {
47    type Topic = PeerEventTopic;
48    fn topic() -> Self::Topic {
49        PeerEventTopic::Lifecycle
50    }
51}
52
53/// Listener returned by `register_peer_events`.
54pub type PeerEventListener = EventListener<PeerEventTopic, PeerEvent>;
55
56pub struct PeerPool {
57    /// Peer's ID
58    pub id: PeerID,
59
60    /// Connection queue
61    conn_queue: Arc<ConnQueue>,
62
63    /// Holds the running peers.
64    peers: RwLock<HashMap<PeerID, Arc<Peer>>>,
65
66    /// Hashmap contains protocol constructors.
67    pub(crate) protocols: RwLock<HashMap<ProtocolID, Box<ProtocolConstructor>>>,
68
69    /// Per-protocol metadata (version + kind, extensible). Keyed by
70    /// protocol id. Source of truth for the handshake's mandatory check
71    /// and version negotiation.
72    pub(crate) protocol_meta: RwLock<HashMap<ProtocolID, ProtocolMeta>>,
73
74    /// Peer-lifecycle event emitter. Each registered listener gets its
75    /// own copy of every event.
76    peer_emitter: Arc<EventEmitter<PeerEventTopic>>,
77
78    /// Managing spawned tasks.
79    task_group: TaskGroup,
80
81    /// A global Executor
82    pub(crate) executor: Executor,
83
84    /// The Configuration for the P2P network.
85    pub(crate) config: Arc<Config>,
86
87    /// Responsible for network and system monitoring.
88    monitor: Arc<Monitor>,
89}
90
91impl PeerPool {
92    /// Creates a new PeerPool
93    pub fn new(
94        id: &PeerID,
95        conn_queue: Arc<ConnQueue>,
96        config: Arc<Config>,
97        monitor: Arc<Monitor>,
98        executor: Executor,
99    ) -> Arc<Self> {
100        Arc::new(Self {
101            id: id.clone(),
102            conn_queue,
103            peers: RwLock::new(HashMap::new()),
104            protocols: RwLock::new(HashMap::new()),
105            protocol_meta: RwLock::new(HashMap::new()),
106            peer_emitter: EventEmitter::new(),
107            task_group: TaskGroup::with_executor(executor.clone()),
108            executor,
109            monitor,
110            config,
111        })
112    }
113
114    /// Register a listener for the peer-lifecycle events.
115    pub fn register_peer_events(&self) -> EventListener<PeerEventTopic, PeerEvent> {
116        self.peer_emitter.register(&PeerEventTopic::Lifecycle)
117    }
118
119    /// Starts the [`PeerPool`]
120    pub async fn start(self: &Arc<Self>) -> Result<()> {
121        self.task_group.spawn(self.clone().run());
122        Ok(())
123    }
124
125    /// Shuts down
126    pub async fn shutdown(&self) {
127        // Cancel the task group first. This waits for the run loop to
128        // actually stop, so no new peer can be admitted while we close
129        // the ones already in the map.
130        self.task_group.cancel().await;
131
132        for peer in self.peers.read().await.values() {
133            let _ = peer.shutdown().await;
134        }
135    }
136
137    /// Register a protocol's user-supplied constructor and metadata.
138    /// Bloom advertising is handled by `Node::attach_protocol`.
139    pub async fn attach_protocol<P: Protocol>(&self, c: Box<ProtocolConstructor>) -> Result<()> {
140        let id = P::id();
141        self.protocols.write().await.insert(id.clone(), c);
142        self.protocol_meta.write().await.insert(
143            id,
144            ProtocolMeta {
145                version: P::version()?,
146                flags: P::flags(),
147            },
148        );
149        Ok(())
150    }
151
152    /// Broadcast a message to all connected peers.
153    pub async fn broadcast(&self, proto_id: &ProtocolID, msg: Vec<u8>) {
154        for (pid, peer) in self.peers.read().await.iter() {
155            if let Err(err) = peer.send(proto_id.to_string(), msg.clone()).await {
156                error!("failed to send msg to {pid}: {err}");
157                continue;
158            }
159        }
160    }
161
162    /// Broadcast a message to a specific set of peers.
163    pub async fn broadcast_to(
164        &self,
165        proto_id: &ProtocolID,
166        msg: Vec<u8>,
167        targets: &HashSet<PeerID>,
168    ) {
169        for (pid, peer) in self.peers.read().await.iter() {
170            if !targets.contains(pid) {
171                continue;
172            }
173            if let Err(err) = peer.send(proto_id.to_string(), msg.clone()).await {
174                error!("failed to send msg to {pid}: {err}");
175            }
176        }
177    }
178
179    /// Send a message to a specific peer on the given protocol. Returns
180    /// `PeerNotFound` if the peer is not currently in the pool.
181    pub async fn send_to(
182        &self,
183        peer_id: &PeerID,
184        proto_id: &ProtocolID,
185        msg: Vec<u8>,
186    ) -> Result<()> {
187        let peers = self.peers.read().await;
188        let peer = peers
189            .get(peer_id)
190            .ok_or_else(|| Error::PeerNotFound(peer_id.to_string()))?;
191        peer.send(proto_id.to_string(), msg).await
192    }
193
194    /// Returns the negotiated protocol set for a peer.
195    pub async fn peer_protocol_set(&self, pid: &PeerID) -> Option<HashSet<ProtocolID>> {
196        self.peers
197            .read()
198            .await
199            .get(pid)
200            .map(|p| p.negotiated_protocols().clone())
201    }
202
203    /// Checks if the peer list contains a peer with the given peer id
204    pub async fn contains_peer(&self, pid: &PeerID) -> bool {
205        self.peers.read().await.contains_key(pid)
206    }
207
208    /// Returns the number of currently connected peers.
209    pub async fn peers_len(&self) -> usize {
210        self.peers.read().await.len()
211    }
212
213    /// Returns a map of inbound peers with their endpoints.
214    pub async fn inbound_peers(&self) -> HashMap<PeerID, Endpoint> {
215        let mut peers = HashMap::new();
216        for (id, peer) in self.peers.read().await.iter() {
217            if peer.is_inbound() {
218                peers.insert(id.clone(), peer.remote_endpoint().clone());
219            }
220        }
221        peers
222    }
223
224    /// Returns a map of outbound peers with their endpoints.
225    pub async fn outbound_peers(&self) -> HashMap<PeerID, Endpoint> {
226        let mut peers = HashMap::new();
227        for (id, peer) in self.peers.read().await.iter() {
228            if !peer.is_inbound() {
229                peers.insert(id.clone(), peer.remote_endpoint().clone());
230            }
231        }
232        peers
233    }
234
235    async fn run(self: Arc<Self>) {
236        loop {
237            let mut queued = self.conn_queue.next().await;
238
239            // Snapshot the protocol metadata so we don't hold a lock
240            // across the handshake. Drives both version negotiation
241            // and the mandatory-subset check.
242            let meta = self.protocol_meta.read().await.clone();
243
244            let params = HandshakeParams {
245                own_id: &self.id,
246                is_inbound: matches!(queued.direction, ConnDirection::Inbound),
247                config_version: &self.config.version,
248                protocols: &meta,
249                timeout_secs: self.config.handshake_timeout,
250                verified_peer_id: queued.verified_peer_id.as_ref(),
251            };
252            let handshake = handshake(&mut queued.reader, &mut queued.writer, &params).await;
253
254            let (pid, negotiated) = match handshake {
255                Ok(v) => v,
256                Err(err) => {
257                    let pid = queued.verified_peer_id.clone();
258                    self.monitor
259                        .notify(PoolEvent::HandshakeFailed(pid.clone()))
260                        .await;
261                    let _ = self
262                        .peer_emitter
263                        .emit(&PeerEvent::HandshakeFailed(pid))
264                        .await;
265                    let _ = queued.disconnect_signal.send(Err(err)).await;
266                    continue;
267                }
268            };
269
270            // Admission gate. Runs before the Peer is built, so a denied
271            // peer never gets tasks spawned or QUIC streams opened.
272            let candidate = PeerCandidate {
273                peer_id: &pid,
274                remote_endpoint: &queued.remote_endpoint,
275                negotiated_protocols: &negotiated,
276            };
277            let allowed = self.config.access_control.allow(
278                &Subject::Peer(&candidate),
279                Action::Connect(queued.direction.clone()),
280            );
281
282            if !allowed {
283                warn!("Access denied for peer {pid}");
284                self.monitor
285                    .notify(PoolEvent::HandshakeFailed(Some(pid.clone())))
286                    .await;
287                let _ = self
288                    .peer_emitter
289                    .emit(&PeerEvent::HandshakeFailed(Some(pid)))
290                    .await;
291                let _ = queued
292                    .disconnect_signal
293                    .send(Err(Error::AccessDenied))
294                    .await;
295                continue;
296            }
297
298            if let Err(err) = self.new_peer(queued, pid, negotiated).await {
299                error!("new_peer failed: {err}");
300            }
301        }
302    }
303
304    /// Build a Peer from a post-handshake `QueuedConn` and run it.
305    async fn new_peer(
306        self: &Arc<Self>,
307        queued: QueuedConn,
308        pid: PeerID,
309        negotiated: Vec<ProtocolID>,
310    ) -> Result<()> {
311        if self.contains_peer(&pid).await {
312            self.monitor
313                .notify(PoolEvent::PeerAlreadyConnected(pid.clone()))
314                .await;
315            let _ = queued
316                .disconnect_signal
317                .send(Err(Error::PeerAlreadyConnected))
318                .await;
319            return Err(Error::PeerAlreadyConnected);
320        }
321
322        let protocol_ids: Vec<ProtocolID> = self.protocols.read().await.keys().cloned().collect();
323        let negotiated: HashSet<ProtocolID> = negotiated.into_iter().collect();
324
325        let peer = Peer::new(self.clone(), queued, pid.clone(), negotiated, protocol_ids).await?;
326
327        self.peers.write().await.insert(pid.clone(), peer.clone());
328
329        let on_disconnect = {
330            let this = self.clone();
331            let pid = pid.clone();
332            |result| async move {
333                if let TaskResult::Completed(_) = result {
334                    if let Err(err) = this.remove_peer(&pid).await {
335                        error!("Failed to remove peer {pid}: {err}");
336                    }
337                }
338            }
339        };
340
341        self.task_group
342            .spawn_then(peer.clone().run(), on_disconnect);
343
344        info!("Add new peer {pid}");
345        self.monitor.notify(PoolEvent::NewPeer(pid.clone())).await;
346        let _ = self.peer_emitter.emit(&PeerEvent::Added(pid)).await;
347
348        Ok(())
349    }
350
351    /// Shuts down the peer and remove it from the peer list.
352    async fn remove_peer(&self, pid: &PeerID) -> Result<()> {
353        let result = self.peers.write().await.remove(pid);
354
355        let peer = match result {
356            Some(p) => p,
357            None => return Ok(()),
358        };
359
360        let _ = peer.shutdown().await;
361
362        self.monitor
363            .notify(PoolEvent::RemovePeer(pid.clone()))
364            .await;
365        let _ = self
366            .peer_emitter
367            .emit(&PeerEvent::Removed(pid.clone()))
368            .await;
369
370        warn!("Peer {pid} removed",);
371        Ok(())
372    }
373}