/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2024 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 smol::{lock::Mutex, stream::StreamExt};
use url::Url;
use super::{
channel::ChannelPtr,
dnet::DnetEvent,
hosts::{
refinery::{GreylistRefinery, GreylistRefineryPtr},
store::{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,
/// 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,
/// Greylist refinery process
greylist_refinery: Arc,
}
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,
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(),
greylist_refinery: GreylistRefinery::new(),
});
self_.session_manual.p2p.init(self_.clone());
self_.session_inbound.p2p.init(self_.clone());
self_.session_outbound.p2p.init(self_.clone());
self_.greylist_refinery.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().connect(peer.clone()).await;
}
// Start the inbound session
if let Err(err) = self.session_inbound().start().await {
error!(target: "net::p2p::start()", "Failed to start inbound session!: {}", err);
self.session_manual().stop().await;
return Err(err)
}
info!(target: "net::p2p::start()", "Starting greylist refinery process");
self.greylist_refinery.clone().start().await;
// Start the outbound session
self.session_outbound().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().stop().await;
self.session_inbound().stop().await;
self.session_outbound().stop().await;
// Stop greylist refinery process
self.greylist_refinery().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 mut channels = Vec::new();
for channel in self.hosts().channels().await {
if exclude_list.contains(channel.address()) {
continue
}
channels.push(channel);
}
self.broadcast_to(message, &channels).await
}
/// Broadcast a message concurrently to all given peers.
pub async fn broadcast_to(&self, message: &M, channel_list: &[ChannelPtr]) {
if channel_list.is_empty() {
warn!(target: "net::p2p::broadcast()", "[P2P] No connected channels found for broadcast");
return
}
let futures = FuturesUnordered::new();
for channel in channel_list {
futures.push(channel.send(message).map_err(|e| {
error!(
target: "net::p2p::broadcast()",
"[P2P] Broadcasting message to {} failed: {}",
channel.address(), e
);
// If the channel is stopped then it should automatically die
// and the session will remove it from p2p.
assert!(channel.is_stopped());
}));
}
let _results: Vec<_> = futures.collect().await;
}
pub async fn is_connected(&self) -> bool {
!self.hosts().channels().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 fn session_manual(&self) -> ManualSessionPtr {
self.session_manual.clone()
}
/// Get pointer to inbound session
pub fn session_inbound(&self) -> InboundSessionPtr {
self.session_inbound.clone()
}
/// Get pointer to outbound session
pub fn session_outbound(&self) -> OutboundSessionPtr {
self.session_outbound.clone()
}
/// Get pointer to greylist refinery
pub fn greylist_refinery(&self) -> GreylistRefineryPtr {
self.greylist_refinery.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(super) async fn dnet_notify(&self, event: DnetEvent) {
self.dnet_subscriber.notify(event).await;
}
}