use std::collections::HashSet;
use std::fmt::Debug;
use p2panda_core::{SigningKey, Topic};
use p2panda_net::address_book::AddressBookError;
use p2panda_net::addrs::{NodeInfo, TrustedTransportInfo};
use p2panda_net::discovery::{DiscoveryConfig, DiscoveryError};
use p2panda_net::gossip::{GossipConfig, GossipError};
use p2panda_net::iroh_endpoint::{EndpointAddr, EndpointError, IrohConfig, RelayUrl};
use p2panda_net::iroh_mdns::MdnsDiscoveryError;
use p2panda_net::sync::LogSyncError;
use p2panda_net::utils::from_verifying_key;
use p2panda_net::{
AddressBook, DEFAULT_NETWORK_ID, Discovery, Endpoint, Gossip, LogSync, MdnsDiscovery,
NetworkId, NodeId,
};
use p2panda_store::SqliteStore;
use thiserror::Error;
use crate::operation::Extensions;
#[derive(Clone, Debug)]
pub(crate) struct Network {
pub address_book: AddressBook,
#[allow(unused)]
pub mdns: Option<MdnsDiscovery>,
pub endpoint: Endpoint,
pub discovery: Discovery,
pub gossip: Gossip,
pub log_sync: LogSync<SqliteStore, Topic, Extensions>,
}
impl Network {
pub async fn spawn(
config: NetworkConfig,
signing_key: SigningKey,
store: SqliteStore,
) -> Result<Self, NetworkError> {
let address_book = AddressBook::builder().store(store.clone()).spawn().await?;
for (node_id, transport_info) in config.bootstraps {
let mut node_info = NodeInfo::new(node_id).bootstrap();
if node_info.update_transports(transport_info.into()).is_ok() {
address_book.insert_node_info(node_info).await?;
}
}
let mut endpoint = Endpoint::builder(address_book.clone())
.config(config.iroh)
.signing_key(signing_key)
.network_id(config.network_id);
for url in &config.relay_urls {
endpoint = endpoint.relay_url(url.clone());
}
let endpoint = endpoint.spawn().await?;
let mdns = match config.mdns_mode {
MdnsDiscoveryMode::Active | MdnsDiscoveryMode::Passive => {
let mdns = MdnsDiscovery::builder(address_book.clone(), endpoint.clone())
.mode(config.mdns_mode.into())
.spawn()
.await?;
Some(mdns)
}
MdnsDiscoveryMode::Disabled => None,
};
let discovery = Discovery::builder(address_book.clone(), endpoint.clone())
.config(config.discovery)
.spawn()
.await?;
let gossip = Gossip::builder(address_book.clone(), endpoint.clone())
.config(config.gossip)
.spawn()
.await?;
let log_sync = LogSync::builder(store.clone(), endpoint.clone(), gossip.clone())
.spawn()
.await?;
Ok(Self {
address_book,
endpoint,
mdns,
discovery,
gossip,
log_sync,
})
}
#[allow(unused)]
pub fn id(&self) -> NodeId {
self.endpoint.node_id()
}
pub fn network_id(&self) -> NetworkId {
self.endpoint.network_id()
}
pub async fn insert_bootstrap(
&self,
node_id: NodeId,
relay_url: RelayUrl,
) -> Result<(), NetworkError> {
let mut node_info = NodeInfo::new(node_id).bootstrap();
let endpoint_addr =
EndpointAddr::new(from_verifying_key(node_id)).with_relay_url(relay_url);
let transport_info = TrustedTransportInfo::from(endpoint_addr);
if node_info.update_transports(transport_info.into()).is_ok() {
self.address_book.insert_node_info(node_info).await?;
}
Ok(())
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum MdnsDiscoveryMode {
Disabled,
#[default]
Active,
Passive,
}
impl From<MdnsDiscoveryMode> for p2panda_net::iroh_mdns::MdnsDiscoveryMode {
fn from(value: MdnsDiscoveryMode) -> Self {
match value {
MdnsDiscoveryMode::Disabled => unreachable!(),
MdnsDiscoveryMode::Active => Self::Active,
MdnsDiscoveryMode::Passive => Self::Passive,
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct NetworkConfig {
pub network_id: NetworkId,
pub relay_urls: HashSet<RelayUrl>,
pub bootstraps: HashSet<(NodeId, TrustedTransportInfo)>,
pub mdns_mode: MdnsDiscoveryMode,
pub discovery: DiscoveryConfig,
pub gossip: GossipConfig,
pub iroh: IrohConfig,
}
impl Default for NetworkConfig {
fn default() -> Self {
Self {
network_id: DEFAULT_NETWORK_ID,
relay_urls: HashSet::new(),
bootstraps: HashSet::new(),
mdns_mode: MdnsDiscoveryMode::Active,
discovery: DiscoveryConfig::default(),
gossip: GossipConfig::default(),
iroh: IrohConfig::default(),
}
}
}
#[derive(Debug, Error)]
pub enum NetworkError {
#[error(transparent)]
AddressBook(#[from] AddressBookError),
#[error(transparent)]
Endpoint(#[from] EndpointError),
#[error(transparent)]
Mdns(#[from] MdnsDiscoveryError),
#[error(transparent)]
Discovery(#[from] DiscoveryError),
#[error(transparent)]
Gossip(#[from] GossipError),
#[error(transparent)]
LogSync(#[from] LogSyncError<Extensions>),
}