/* This file is part of DarkFi (https://dark.fi) * * Copyright (C) 2020-2023 Dyne.org foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ use std::{ collections::{HashMap, HashSet}, sync::Arc, }; use futures::{stream::FuturesUnordered, TryFutureExt}; use log::{debug, error, info, warn}; use rand::{prelude::IteratorRandom, rngs::OsRng}; use smol::{lock::Mutex, stream::StreamExt}; use url::Url; use super::{ channel::ChannelPtr, dnet::DnetEvent, hosts::{Hosts, HostsPtr}, message::Message, protocol::{protocol_registry::ProtocolRegistry, register_default_protocols}, session::{ InboundSession, InboundSessionPtr, ManualSession, ManualSessionPtr, OutboundSession, OutboundSessionPtr, SeedSyncSession, }, settings::{Settings, SettingsPtr}, }; use crate::{ system::{ExecutorPtr, Subscriber, SubscriberPtr, Subscription}, Result, }; /// Set of channels that are awaiting connection pub type PendingChannels = Mutex>; /// Set of connected channels pub type ConnectedChannels = Mutex>; /// Atomic pointer to the p2p interface pub type P2pPtr = Arc; /// Toplevel peer-to-peer networking interface pub struct P2p { /// Global multithreaded executor reference executor: ExecutorPtr, /// Channels pending connection pending: PendingChannels, /// Connected channels channels: ConnectedChannels, /// Subscriber for notifications of new channels channel_subscriber: SubscriberPtr>, /// Known hosts (peers) hosts: HostsPtr, /// Protocol registry protocol_registry: ProtocolRegistry, /// P2P network settings settings: SettingsPtr, /// Boolean lock marking if peer discovery is active pub peer_discovery_running: Mutex, /// Reference to configured [`ManualSession`] session_manual: ManualSessionPtr, /// Reference to configured [`InboundSession`] session_inbound: InboundSessionPtr, /// Reference to configured [`OutboundSession`] session_outbound: OutboundSessionPtr, /// Enable network debugging pub dnet_enabled: Mutex, /// The subscriber for which we can give dnet info over dnet_subscriber: SubscriberPtr, } impl P2p { /// Initialize a new p2p network. /// /// Initializes all sessions and protocols. Adds the protocols to the protocol /// registry, along with a bitflag session selector that includes or excludes /// sessions from seed, version, and address protocols. /// /// Creates a weak pointer to self that is used by all sessions to access the /// p2p parent class. pub async fn new(settings: Settings, executor: ExecutorPtr) -> P2pPtr { let settings = Arc::new(settings); let self_ = Arc::new(Self { executor, pending: Mutex::new(HashSet::new()), channels: Mutex::new(HashMap::new()), channel_subscriber: Subscriber::new(), hosts: Hosts::new(settings.clone()), protocol_registry: ProtocolRegistry::new(), settings, peer_discovery_running: Mutex::new(false), session_manual: ManualSession::new(), session_inbound: InboundSession::new(), session_outbound: OutboundSession::new(), dnet_enabled: Mutex::new(false), dnet_subscriber: Subscriber::new(), }); self_.session_manual.p2p.init(self_.clone()); self_.session_inbound.p2p.init(self_.clone()); self_.session_outbound.p2p.init(self_.clone()); register_default_protocols(self_.clone()).await; self_ } /// Starts inbound, outbound, and manual sessions. pub async fn start(self: Arc) -> Result<()> { debug!(target: "net::p2p::start()", "P2P::start() [BEGIN]"); info!(target: "net::p2p::start()", "[P2P] Starting P2P subsystem"); // First attempt any set manual connections for peer in &self.settings.peers { self.session_manual().await.connect(peer.clone()).await; } // Start the inbound session let inbound = self.session_inbound().await; if let Err(err) = inbound.start().await { error!(target: "net::p2p::start()", "Failed to start inbound session!: {}", err); self.session_manual().await.stop().await; return Err(err) } // Start the outbound session self.session_outbound().await.start().await; info!(target: "net::p2p::start()", "[P2P] P2P subsystem started"); Ok(()) } /// Reseed the P2P network. pub async fn seed(self: Arc) -> Result<()> { debug!(target: "net::p2p::seed()", "P2P::seed() [BEGIN]"); info!(target: "net::p2p::seed()", "[P2P] Seeding P2P subsystem"); // Start seed session let seed = SeedSyncSession::new(Arc::downgrade(&self)); // This will block until all seed queries have finished seed.start().await?; debug!(target: "net::p2p::seed()", "P2P::seed() [END]"); Ok(()) } /// Stop the running P2P subsystem pub async fn stop(&self) { // Stop the sessions self.session_manual().await.stop().await; self.session_inbound().await.stop().await; self.session_outbound().await.stop().await; } /// Broadcasts a message concurrently across all active channels. pub async fn broadcast(&self, message: &M) { self.broadcast_with_exclude(message, &[]).await } /// Broadcasts a message concurrently across active channels, excluding /// the ones provided in `exclude_list`. pub async fn broadcast_with_exclude(&self, message: &M, exclude_list: &[Url]) { let chans = self.channels.lock().await; let iter = chans.values(); let mut futures = FuturesUnordered::new(); for channel in iter { if exclude_list.contains(channel.address()) { continue } futures.push(channel.send(message).map_err(|e| { ( format!("[P2P] Broadcasting message to {} failed: {}", channel.address(), e), channel.clone(), ) })); } if futures.is_empty() { warn!(target: "net::p2p::broadcast()", "[P2P] No connected channels found for broadcast"); return } while let Some(entry) = futures.next().await { if let Err((e, chan)) = entry { error!(target: "net::p2p::broadcast()", "{}", e); self.remove(chan).await; } } } /// Check whether we're connected to a given address pub async fn exists(&self, addr: &Url) -> bool { self.channels.lock().await.contains_key(addr) } /// Add a channel to the set of connected channels pub(super) async fn store(&self, channel: ChannelPtr) { // TODO: Check the code path for this, and potentially also insert the remote // into the hosts list? self.channels.lock().await.insert(channel.address().clone(), channel.clone()); self.channel_subscriber.notify(Ok(channel)).await; } /// Remove a channel from the set of connected channels pub(super) async fn remove(&self, channel: ChannelPtr) { self.channels.lock().await.remove(channel.address()); } /// Add an address to the list of pending channels. pub(super) async fn add_pending(&self, addr: &Url) -> bool { self.pending.lock().await.insert(addr.clone()) } /// Remove a channel from the list of pending channels. pub(super) async fn remove_pending(&self, addr: &Url) { self.pending.lock().await.remove(addr); } /// Return reference to connected channels map pub async fn channels(&self) -> &ConnectedChannels { &self.channels } /// Retrieve a random connected channel from the pub async fn random_channel(&self) -> Option { let channels = self.channels().await.lock().await; channels.values().choose(&mut OsRng).cloned() } pub async fn is_connected(&self) -> bool { !self.channels().await.lock().await.is_empty() } /// Return an atomic pointer to the set network settings pub fn settings(&self) -> SettingsPtr { self.settings.clone() } /// Return an atomic pointer to the list of hosts pub fn hosts(&self) -> HostsPtr { self.hosts.clone() } /// Reference the global executor pub fn executor(&self) -> ExecutorPtr { self.executor.clone() } /// Return a reference to the internal protocol registry pub fn protocol_registry(&self) -> &ProtocolRegistry { &self.protocol_registry } /// Get pointer to manual session pub async fn session_manual(&self) -> ManualSessionPtr { self.session_manual.clone() } /// Get pointer to inbound session pub async fn session_inbound(&self) -> InboundSessionPtr { self.session_inbound.clone() } /// Get pointer to outbound session pub async fn session_outbound(&self) -> OutboundSessionPtr { self.session_outbound.clone() } /// Enable network debugging pub async fn dnet_enable(&self) { *self.dnet_enabled.lock().await = true; warn!("[P2P] Network debugging enabled!"); } /// Disable network debugging pub async fn dnet_disable(&self) { *self.dnet_enabled.lock().await = false; warn!("[P2P] Network debugging disabled!"); } /// Subscribe to dnet events pub async fn dnet_subscribe(&self) -> Subscription { self.dnet_subscriber.clone().subscribe().await } /// Send a dnet notification over the subscriber pub async fn dnet_notify(&self, event: DnetEvent) { self.dnet_subscriber.notify(event).await; } }