Skip to main content

syncweb_core/node/
iroh_node.rs

1use iroh::address_lookup::memory::MemoryLookup;
2use iroh::protocol::Router;
3use iroh_blobs::BlobsProtocol;
4use iroh_docs::protocol::Docs;
5use iroh_gossip::net::Gossip;
6use std::collections::HashSet;
7use std::path::PathBuf;
8use std::sync::{Arc, RwLock};
9
10use crate::error::{Result, SyncwebError};
11
12use super::discovery::TopicTracker;
13use super::identity::IdentityManager;
14use super::membership_hook::MembershipHook;
15use super::{blob_store::BlobStore, docs_engine::DocsEngine, gossip_service::GossipService};
16
17#[non_exhaustive]
18#[derive(Clone, Debug)]
19pub enum RelayMode {
20    Default,
21    Custom { map: iroh::RelayMap, insecure: bool },
22    None,
23}
24
25pub struct IrohNode {
26    endpoint: iroh::Endpoint,
27    router: Arc<Router>,
28    blobs: iroh_blobs::BlobsProtocol,
29    docs: Docs,
30    gossip: Arc<Gossip>,
31    blob_store: BlobStore,
32    docs_engine: DocsEngine,
33    gossip_service: GossipService,
34    topic_tracker: TopicTracker,
35}
36
37impl IrohNode {
38    /// Creates a node and starts accepting the blobs, docs, and gossip protocols.
39    ///
40    /// # Errors
41    ///
42    /// Returns an error if the directory structure cannot be created, if binding the endpoint fails, or if starting the background services fails.
43    pub async fn new(
44        identity: IdentityManager,
45        data_dir: PathBuf,
46        relay_mode: RelayMode,
47        member_keys: Arc<RwLock<HashSet<iroh::PublicKey>>>,
48    ) -> Result<Self> {
49        Self::new_with_address_lookup(identity, data_dir, relay_mode, MemoryLookup::new(), member_keys).await
50    }
51
52    /// # Errors
53    ///
54    /// Returns an error if the directory structure cannot be created, if binding the endpoint fails, or if starting the background services fails.
55    pub async fn new_with_address_lookup(
56        identity: IdentityManager,
57        data_dir: PathBuf,
58        relay_mode: RelayMode,
59        address_lookup: MemoryLookup,
60        member_keys: Arc<RwLock<HashSet<iroh::PublicKey>>>,
61    ) -> Result<Self> {
62        tokio::fs::create_dir_all(&data_dir)
63            .await
64            .map_err(|error| SyncwebError::operation("failed to create node data directory", error))?;
65        let docs_dir = data_dir.join("docs");
66        tokio::fs::create_dir_all(&docs_dir)
67            .await
68            .map_err(|error| SyncwebError::operation("failed to create docs directory", error))?;
69
70        let builder = match relay_mode {
71            RelayMode::Default => iroh::Endpoint::builder(iroh::endpoint::presets::N0),
72            RelayMode::Custom { map, insecure } => {
73                let mut b = iroh::Endpoint::builder(iroh::endpoint::presets::N0)
74                    .relay_mode(iroh::endpoint::RelayMode::Custom(map));
75                if insecure {
76                    b = b.ca_tls_config(iroh::tls::CaTlsConfig::insecure_skip_verify());
77                }
78                b
79            }
80            RelayMode::None => {
81                iroh::Endpoint::builder(iroh::endpoint::presets::N0).relay_mode(iroh::endpoint::RelayMode::Disabled)
82            }
83        };
84
85        let hook = MembershipHook {
86            member_keys: member_keys.clone(),
87        };
88        let endpoint = builder
89            .address_lookup(address_lookup.clone())
90            .secret_key(identity.secret_key().clone())
91            .hooks(hook)
92            .bind()
93            .await
94            .map_err(|error| SyncwebError::operation("failed to bind Iroh endpoint", error))?;
95
96        // Register mDNS address lookup for local network peer discovery.
97        if let Ok(_mdns) = iroh_mdns_address_lookup::MdnsAddressLookup::builder()
98            .build(endpoint.id())
99            .map_err(|error| SyncwebError::operation("failed to build mDNS address lookup", error))
100            .and_then(|mdns| {
101                endpoint
102                    .address_lookup()
103                    .map_err(|error| SyncwebError::operation("no address lookup service available", error))
104                    .map(|lookup| lookup.add(mdns))
105            })
106        {
107            tracing::debug!("mDNS address lookup registered");
108        }
109
110        let fs_blob_store = iroh_blobs::store::fs::FsStore::load(data_dir.join("blobs"))
111            .await
112            .map_err(|error| SyncwebError::operation("failed to open blob store", error))?;
113        let blobs = BlobsProtocol::new(fs_blob_store.as_ref(), None);
114
115        let gossip = Arc::new(Gossip::builder().spawn(endpoint.clone()));
116
117        let docs = Docs::persistent(docs_dir)
118            .spawn(endpoint.clone(), blobs.store().clone(), gossip.as_ref().clone())
119            .await
120            .map_err(|error| SyncwebError::operation("failed to open docs store", error))?;
121
122        let router = Router::builder(endpoint.clone())
123            .accept(iroh_blobs::protocol::ALPN, blobs.clone())
124            .accept(iroh_docs::ALPN, docs.clone())
125            .accept(iroh_gossip::ALPN, gossip.clone())
126            .spawn();
127
128        let blob_store = BlobStore::new_with_address_lookup(&blobs, address_lookup);
129        let docs_engine = DocsEngine::new(&docs);
130        let gossip_service = GossipService::new(&gossip);
131        let topic_tracker = TopicTracker::new(&gossip, &endpoint);
132
133        Ok(Self {
134            endpoint,
135            router: Arc::new(router),
136            blobs,
137            docs,
138            gossip,
139            blob_store,
140            docs_engine,
141            gossip_service,
142            topic_tracker,
143        })
144    }
145
146    #[must_use]
147    pub const fn endpoint(&self) -> &iroh::Endpoint {
148        &self.endpoint
149    }
150
151    #[must_use]
152    pub const fn blobs(&self) -> &BlobsProtocol {
153        &self.blobs
154    }
155
156    #[must_use]
157    pub const fn docs(&self) -> &Docs {
158        &self.docs
159    }
160
161    #[must_use]
162    pub fn gossip(&self) -> &Gossip {
163        &self.gossip
164    }
165
166    #[must_use]
167    pub const fn blob_store(&self) -> &BlobStore {
168        &self.blob_store
169    }
170
171    #[must_use]
172    pub const fn docs_engine(&self) -> &DocsEngine {
173        &self.docs_engine
174    }
175
176    #[must_use]
177    pub const fn gossip_service(&self) -> &GossipService {
178        &self.gossip_service
179    }
180
181    #[must_use]
182    pub const fn topic_tracker(&self) -> &TopicTracker {
183        &self.topic_tracker
184    }
185
186    #[must_use]
187    pub fn is_running(&self) -> bool {
188        !self.router.is_shutdown()
189    }
190
191    /// # Errors
192    ///
193    /// Returns an error if the router fails to shutdown properly.
194    pub async fn stop(&self) -> Result<()> {
195        self.router
196            .shutdown()
197            .await
198            .map_err(|error| SyncwebError::operation("failed to stop node router", error))?;
199        Ok(())
200    }
201
202    /// # Errors
203    ///
204    /// Returns an error if stopping the node fails.
205    pub async fn shutdown(self) -> Result<()> {
206        self.stop().await
207    }
208}