Skip to main content

rust_ipfs/
builder.rs

1use crate::context::IpfsContext;
2use crate::p2p::{
3    create_create_behaviour, AddressBookConfig, IdentifyConfiguration, PubsubConfig, RelayConfig,
4    TSwarm,
5};
6use crate::repo::{DefaultKeystore, DefaultStorage, GCConfig, GCTrigger, Repo};
7use crate::{
8    context, ipns_to_dht_key, p2p, to_dht_key, ConnectionLimits, FDLimit, Ipfs, IpfsEvent,
9    IpfsOptions, Keypair, Multiaddr, NetworkBehaviour, RecordKey, RepoProvider, TSwarmEvent, TSwarmEventFn,
10};
11use anyhow::Error;
12use async_rt::AbortableJoinHandle;
13use connexa::behaviour::peer_store::store::memory::MemoryStore;
14use connexa::behaviour::request_response::RequestResponseConfig;
15use connexa::builder::{ConnexaBuilder, FileDescLimit, IntoKeypair};
16use connexa::keystore::Keychain;
17use connexa::prelude::identify::Event;
18use connexa::prelude::swarm::SwarmEvent;
19#[cfg(not(target_arch = "wasm32"))]
20#[cfg(feature = "pnet")]
21use connexa::prelude::transport::pnet::PreSharedKey;
22use connexa::prelude::{gossipsub, ping, swarm};
23use connexa::{behaviour, dummy};
24use futures::{stream::FuturesUnordered, StreamExt, TryStreamExt};
25use ipld_core::cid::Cid;
26use std::collections::{BTreeSet, HashMap, HashSet};
27use std::convert::Infallible;
28use std::sync::Arc;
29use std::task::Poll;
30use std::time::Duration;
31use tracing::Span;
32use tracing_futures::Instrument;
33
34/// Configured Ipfs which can only be started.
35#[allow(clippy::type_complexity)]
36pub struct IpfsBuilder<C: NetworkBehaviour<ToSwarm = Infallible> + Send + Sync + 'static> {
37    init: ConnexaBuilder<p2p::Behaviour<C>, IpfsContext, IpfsEvent, MemoryStore, DefaultKeystore>,
38    options: IpfsOptions,
39
40    repo_handle: Repo<DefaultStorage>,
41    swarm_event: Option<TSwarmEventFn<C>>,
42    record_key_validator:
43        HashMap<String, Box<dyn Fn(&str) -> anyhow::Result<RecordKey> + Sync + Send>>,
44    gc_config: Option<GCConfig>,
45    custom_behaviour: Option<Box<dyn FnOnce(&Keypair) -> std::io::Result<C>>>,
46    gc_repo_duration: Option<Duration>,
47}
48
49pub type DefaultIpfsBuilder = IpfsBuilder<dummy::Behaviour>;
50
51impl<C: NetworkBehaviour<ToSwarm = Infallible> + Send + Sync + 'static> Default for IpfsBuilder<C> {
52    fn default() -> Self {
53        Self::new()
54    }
55}
56
57impl<C: NetworkBehaviour<ToSwarm = Infallible> + Send + Sync + 'static> IpfsBuilder<C> {
58    /// New uninitualized instance
59    pub fn new() -> Self {
60        let keypair = Keypair::generate_ed25519();
61        Self::with_keypair(&keypair).expect("keypair is valid")
62    }
63
64    /// New instance with an existing keypair
65    pub fn with_keypair(keypair: impl IntoKeypair) -> std::io::Result<Self> {
66        let builder = ConnexaBuilder::with_existing_identity(keypair)?;
67        Ok(Self::from_identity(builder))
68    }
69
70    /// Create an instance that resolves its identity from `keychain` under `label`
71    /// loading it, or generating and storing a new identity if none exists yet.
72    pub fn with_keychain_identity(
73        keychain: Keychain<DefaultKeystore>,
74        label: impl Into<String>,
75    ) -> Self {
76        let builder = ConnexaBuilder::with_keychain_identity(keychain, label);
77        Self::from_identity(builder)
78    }
79
80    /// Create an instance that loads its identity from `keychain` under `label`.
81    pub fn with_existing_keychain_identity(
82        keychain: Keychain<DefaultKeystore>,
83        label: impl Into<String>,
84    ) -> Self {
85        let builder = ConnexaBuilder::with_existing_keychain_identity(keychain, label);
86        Self::from_identity(builder)
87    }
88
89    fn from_identity(
90        builder: ConnexaBuilder<
91            p2p::Behaviour<C>,
92            IpfsContext,
93            IpfsEvent,
94            MemoryStore,
95            DefaultKeystore,
96        >,
97    ) -> Self {
98        Self {
99            init: builder,
100            options: Default::default(),
101            repo_handle: Repo::new_memory(),
102            record_key_validator: Default::default(),
103            swarm_event: None,
104            gc_config: None,
105            gc_repo_duration: None,
106            custom_behaviour: None,
107        }
108    }
109
110    /// Set default listening unspecified ipv4 and ipv6 addresses for tcp and quic
111    /// Note that this still requires for the transports to be enabled to be usable
112    pub fn set_default_listener(self) -> Self {
113        self.add_listening_addrs(vec![
114            "/ip4/0.0.0.0/tcp/0".parse().unwrap(),
115            "/ip4/0.0.0.0/udp/0/quic-v1".parse().unwrap(),
116        ])
117    }
118
119    /// Adds a listening address
120    pub fn add_listening_addr(mut self, addr: Multiaddr) -> Self {
121        if !self.options.listening_addrs.contains(&addr) {
122            self.options.listening_addrs.push(addr)
123        }
124        self
125    }
126
127    /// Set a connection limit
128    pub fn set_connection_limits<F>(mut self, f: F) -> Self
129    where
130        F: Fn(ConnectionLimits) -> ConnectionLimits + Send + Sync + 'static,
131    {
132        self.init = self.init.with_connection_limits_with_config(f);
133        self
134    }
135
136    /// Adds a listening addresses
137    pub fn add_listening_addrs(mut self, addrs: Vec<Multiaddr>) -> Self {
138        self.options.listening_addrs.extend(addrs);
139        self
140    }
141
142    /// Set a list of listening addresses
143    pub fn set_listening_addrs(mut self, addrs: Vec<Multiaddr>) -> Self {
144        self.options.listening_addrs = addrs;
145        self
146    }
147
148    /// Adds a bootstrap node
149    pub fn add_bootstrap(mut self, addr: Multiaddr) -> Self {
150        if !self.options.bootstrap.contains(&addr) {
151            self.options.bootstrap.push(addr)
152        }
153        self
154    }
155
156    /// Load default behaviour for basic functionality
157    pub fn with_default(self) -> Self {
158        self.with_identify(Default::default())
159            .with_autonat()
160            .with_bitswap()
161            .with_kademlia()
162            .with_ping(Default::default())
163            .with_pubsub(Default::default())
164    }
165
166    /// Enables kademlia
167    pub fn with_kademlia(mut self) -> Self {
168        self.init = self.init.with_kademlia();
169        self
170    }
171
172    /// Enables bitswap
173    pub fn with_bitswap(mut self) -> Self {
174        self.options.protocols.bitswap = true;
175        self
176    }
177
178    /// Enables bitswap with explicit configuration
179    pub fn with_bitswap_config<F>(mut self, f: F) -> Self
180    where
181        F: Fn(p2p::bitswap::Config) -> p2p::bitswap::Config + 'static,
182    {
183        self.options.protocols.bitswap = true;
184        self.options.bitswap_config = Box::new(f);
185        self
186    }
187
188    /// Enables trustless HTTP gateway retrieval.
189    #[cfg(feature = "gateway")]
190    pub fn enable_gateway_retrieval(
191        mut self,
192        gateways: impl IntoIterator<Item = impl Into<String>>,
193    ) -> Self {
194        self.options.gateway = Some(gateways.into_iter().map(Into::into).collect());
195        self
196    }
197
198    /// Enables delegated routing V1 HTTP provider discovery.
199    #[cfg(feature = "routing")]
200    pub fn enable_delegated_routing(
201        mut self,
202        routers: impl IntoIterator<Item = impl Into<String>>,
203    ) -> Self {
204        self.options.router = Some(routers.into_iter().map(Into::into).collect());
205        self
206    }
207
208    /// Enable mdns
209    #[cfg(not(target_arch = "wasm32"))]
210    pub fn with_mdns(mut self) -> Self {
211        self.init = self.init.with_mdns();
212        self
213    }
214
215    /// Enable relay client
216    pub fn with_relay(mut self, with_dcutr: bool) -> Self {
217        self.options.protocols.relay = true;
218        self.init = self.init.with_relay();
219        if with_dcutr {
220            #[cfg(not(target_arch = "wasm32"))]
221            {
222                self.init = self.init.with_dcutr();
223            }
224        }
225        self
226    }
227
228    /// Enable autorelay
229    pub fn with_autorelay(mut self) -> Self {
230        self.init = self.init.with_autorelay();
231        self
232    }
233
234    /// Enable autorelay with configuration option
235    pub fn with_autorelay_with_config<F>(mut self, f: F) -> Self
236    where
237        F: FnOnce(behaviour::autorelay::Config) -> behaviour::autorelay::Config + 'static,
238    {
239        self.init = self.init.with_autorelay_with_config(f);
240        self
241    }
242
243    /// Enable relay server
244    pub fn with_relay_server(mut self, config: RelayConfig) -> Self {
245        self.init = self
246            .init
247            .with_relay_server_with_config(move |_| config.into());
248        self
249    }
250
251    /// Enable port mapping (AKA UPnP)
252    #[cfg(not(target_arch = "wasm32"))]
253    pub fn with_upnp(mut self) -> Self {
254        self.init = self.init.with_upnp();
255        self
256    }
257
258    /// Enables rendezvous server
259    pub fn with_rendezvous_server(mut self) -> Self {
260        self.init = self.init.with_rendezvous_server();
261        self
262    }
263
264    /// Enables rendezvous client
265    pub fn with_rendezvous_client(mut self) -> Self {
266        self.init = self.init.with_rendezvous_client();
267        self
268    }
269
270    /// Enables identify
271    pub fn with_identify(mut self, config: IdentifyConfiguration) -> Self {
272        self.init = self
273            .init
274            .with_identify_with_config(config.protocol_version, move |cfg| {
275                cfg.with_agent_version(config.agent_version)
276                    .with_interval(config.interval)
277                    .with_push_listen_addr_updates(config.push_update)
278                    .with_cache_size(config.cache)
279            });
280        self
281    }
282
283    #[cfg(feature = "stream")]
284    pub fn with_streams(mut self) -> Self {
285        self.init = self.init.with_streams();
286        self
287    }
288
289    /// Enables pubsub
290    pub fn with_pubsub(mut self, config: PubsubConfig) -> Self {
291        self.init = self
292            .init
293            .with_gossipsub_with_config(move |keypair, mut builder| {
294                if let Some(protocol) = config.custom_protocol_id {
295                    builder.protocol_id(protocol, gossipsub::Version::V1_1);
296                }
297
298                builder.max_transmit_size(config.max_transmit_size);
299
300                if config.floodsub_compat {
301                    builder.support_floodsub();
302                }
303
304                builder.validation_mode(config.validate.into());
305                let auth =
306                    connexa::prelude::gossipsub::MessageAuthenticity::Signed(keypair.clone());
307                (builder, auth)
308            });
309        self
310    }
311
312    /// Enables request response.
313    /// Note: At this time, this option will only support up to 10 request-response behaviours.
314    ///       with any additional being ignored. Additionally, any duplicated protocols that are
315    ///       provided will be ignored.
316    pub fn with_request_response(mut self, config: Vec<RequestResponseConfig>) -> Self {
317        self.init = self.init.with_request_response(config);
318
319        self
320    }
321
322    /// Enables autonat
323    pub fn with_autonat(mut self) -> Self {
324        self.init = self.init.with_autonat_v1();
325        self
326    }
327
328    /// Enables ping
329    pub fn with_ping(mut self, config: ping::Config) -> Self {
330        self.init = self.init.with_ping_with_config(move |_| config);
331        self
332    }
333
334    /// Set a custom behaviour
335    pub fn with_custom_behaviour<F>(mut self, f: F) -> Self
336    where
337        F: FnOnce(&Keypair) -> std::io::Result<C> + 'static,
338    {
339        self.custom_behaviour.replace(Box::new(f));
340        self
341    }
342
343    /// Enables automatic garbage collection
344    pub fn with_gc(mut self, config: GCConfig) -> Self {
345        self.gc_config = Some(config);
346        self
347    }
348
349    /// Set a duration for which blocks are not removed due to the garbage collector
350    /// Defaults: 2 mins
351    pub fn set_temp_pin_duration(mut self, duration: Duration) -> Self {
352        self.gc_repo_duration = Some(duration);
353        self
354    }
355
356    /// Sets a path
357    #[cfg(not(target_arch = "wasm32"))]
358    pub fn set_path<P: AsRef<std::path::Path>>(mut self, path: P) -> Self {
359        let path = path.as_ref().to_path_buf();
360        self.options.ipfs_path = Some(path);
361        self
362    }
363
364    /// Sets a namespace
365    #[cfg(target_arch = "wasm32")]
366    pub fn set_namespace(mut self, ns: Option<String>) -> Self {
367        self.options.namespace = Some(ns);
368        self
369    }
370
371    /// Set timeout for idle connections
372    pub fn set_idle_connection_timeout(mut self, duration: u64) -> Self {
373        self.init = self.init.set_swarm_config(move |swarm| {
374            swarm.with_idle_connection_timeout(Duration::from_secs(duration))
375        });
376        self
377    }
378
379    /// Set swarm configuration
380    pub fn set_swarm_configuration<F>(mut self, f: F) -> Self
381    where
382        F: FnOnce(swarm::Config) -> swarm::Config + Send + Sync + 'static,
383    {
384        self.init = self.init.set_swarm_config(f);
385        self
386    }
387
388    /// Set default record validator for IPFS
389    /// Note: This will override any keys set for `ipns` prefix
390    pub fn default_record_key_validator(mut self) -> Self {
391        self.record_key_validator.insert(
392            "ipns".into(),
393            Box::new(|key| to_dht_key(("ipns", |key| ipns_to_dht_key(key)), key)),
394        );
395        self
396    }
397
398    pub fn set_record_prefix_validator<F>(mut self, key: &str, callback: F) -> Self
399    where
400        F: Fn(&str) -> anyhow::Result<RecordKey> + Sync + Send + 'static,
401    {
402        self.record_key_validator
403            .insert(key.to_string(), Box::new(callback));
404        self
405    }
406
407    /// Set address book configuration
408    pub fn set_addrbook_configuration(mut self, config: AddressBookConfig) -> Self {
409        self.options.addr_config = config;
410        self
411    }
412
413    /// Set RepoProvider option to provide blocks automatically
414    pub fn set_provider(mut self, opt: RepoProvider) -> Self {
415        self.options.provider = opt;
416        self
417    }
418
419    /// Set block and data repo
420    pub fn set_repo(mut self, repo: &Repo<DefaultStorage>) -> Self {
421        self.repo_handle = Repo::clone(repo);
422        self
423    }
424
425    /// Enables quic transport
426    #[cfg(feature = "quic")]
427    #[cfg(not(target_arch = "wasm32"))]
428    pub fn enable_quic(mut self) -> Self {
429        self.init = self.init.enable_quic();
430        self
431    }
432
433    /// Enables quic transport with custom configuration
434    #[cfg(feature = "quic")]
435    #[cfg(not(target_arch = "wasm32"))]
436    pub fn enable_quic_with_config<F>(mut self, f: F) -> Self
437    where
438        F: FnOnce(
439                connexa::prelude::transport::quic::Config,
440            ) -> connexa::prelude::transport::quic::Config
441            + 'static,
442    {
443        self.init = self.init.enable_quic_with_config(f);
444        self
445    }
446
447    /// Enables tcp transport
448    #[cfg(feature = "tcp")]
449    #[cfg(not(target_arch = "wasm32"))]
450    pub fn enable_tcp(mut self) -> Self {
451        self.init = self.init.enable_tcp();
452        self
453    }
454
455    /// Enables tcp transport with custom configuration
456    #[cfg(feature = "tcp")]
457    #[cfg(not(target_arch = "wasm32"))]
458    pub fn enable_tcp_with_config<F>(mut self, f: F) -> Self
459    where
460        F: FnOnce(
461                connexa::prelude::transport::tcp::Config,
462            ) -> connexa::prelude::transport::tcp::Config
463            + 'static,
464    {
465        self.init = self.init.enable_tcp_with_config(f);
466        self
467    }
468
469    // /// Enables pnet transport
470    #[cfg(feature = "pnet")]
471    #[cfg(not(target_arch = "wasm32"))]
472    pub fn enable_pnet(mut self, psk: PreSharedKey) -> Self {
473        self.init = self.init.enable_pnet(psk);
474        self
475    }
476
477    /// Enables websocket transport
478    #[cfg(feature = "websocket")]
479    pub fn enable_websocket(mut self) -> Self {
480        self.init = self.init.enable_websocket();
481        self
482    }
483
484    /// Enables secure websocket transport
485    #[cfg(feature = "websocket")]
486    #[cfg(not(target_arch = "wasm32"))]
487    pub fn enable_secure_websocket(mut self) -> Self {
488        self.init = self.init.enable_secure_websocket();
489        self
490    }
491
492    /// Enables secure websocket transport
493    #[cfg(feature = "websocket")]
494    #[cfg(not(target_arch = "wasm32"))]
495    pub fn enable_secure_websocket_with_pem(mut self, keypair: String, certs: Vec<String>) -> Self {
496        self.init = self.init.enable_secure_websocket_with_pem(keypair, certs);
497        self
498    }
499
500    /// Enables secure websocket transport
501    #[cfg(feature = "websocket")]
502    #[cfg(not(target_arch = "wasm32"))]
503    pub fn enable_secure_websocket_with_config<F>(mut self, f: F) -> Self
504    where
505        F: FnOnce(&Keypair) -> std::io::Result<(Vec<String>, String)> + 'static,
506    {
507        self.init = self.init.enable_secure_websocket_with_config(f);
508        self
509    }
510
511    /// Enables DNS
512    #[cfg(feature = "dns")]
513    pub fn enable_dns(self) -> Self {
514        self.enable_dns_with_resolver(connexa::prelude::transport::dns::DnsResolver::default())
515    }
516
517    /// Enables DNS with a specific resolver
518    #[cfg(feature = "dns")]
519    pub fn enable_dns_with_resolver(
520        mut self,
521        resolver: connexa::prelude::transport::dns::DnsResolver,
522    ) -> Self {
523        self.init = self.init.enable_dns_with_resolver(resolver);
524        self
525    }
526
527    /// Enables WebRTC transport
528    #[cfg(feature = "webrtc")]
529    pub fn enable_webrtc(mut self) -> Self {
530        self.init = self.init.enable_webrtc();
531        self
532    }
533
534    /// Enables WebRTC transport, allowing one to generate a certificate using the provided keypair in the closure.
535    #[cfg(feature = "webrtc")]
536    #[cfg(not(target_arch = "wasm32"))]
537    pub fn enable_webrtc_with_config<F>(mut self, f: F) -> Self
538    where
539        F: FnOnce(&Keypair) -> std::io::Result<String> + 'static,
540    {
541        self.init = self.init.enable_webrtc_with_config(f);
542        self
543    }
544
545    /// Enable WebRTC transport with a provided pre-generated pem.
546    #[cfg(feature = "webrtc")]
547    #[cfg(not(target_arch = "wasm32"))]
548    pub fn enable_webrtc_with_pem(self, pem: impl Into<String>) -> Self {
549        let pem = pem.into();
550        self.enable_webrtc_with_config(move |_| Ok(pem))
551    }
552
553    /// Enables memory transport
554    pub fn enable_memory_transport(mut self) -> Self {
555        self.init = self.init.enable_memory_transport();
556        self
557    }
558
559    /// Set file desc limit
560    pub fn fd_limit(mut self, limit: FDLimit) -> Self {
561        let limit = match limit {
562            FDLimit::Max => FileDescLimit::Max,
563            FDLimit::Custom(n) => FileDescLimit::Custom(n),
564        };
565        self.init = self.init.set_file_descriptor_limit(limit);
566        self
567    }
568
569    /// Set tracing span
570    pub fn set_span(mut self, span: Span) -> Self {
571        self.options.span = Some(span);
572        self
573    }
574
575    /// Handle libp2p swarm events
576    pub fn swarm_events<F>(mut self, func: F) -> Self
577    where
578        F: Fn(&mut TSwarm<C>, &TSwarmEvent<C>) + Sync + Send + 'static,
579    {
580        self.swarm_event = Some(Arc::new(func));
581        self
582    }
583
584    /// Initialize the ipfs node. The returned `Ipfs` value is cloneable, send and sync.
585    pub async fn start(self) -> Result<Ipfs, Error> {
586        let IpfsBuilder {
587            mut options,
588            record_key_validator,
589            repo_handle,
590            gc_config,
591            init,
592            custom_behaviour,
593            swarm_event,
594            ..
595        } = self;
596
597        let root_span = Option::take(&mut options.span)
598            // not sure what would be the best practice with tracing and spans
599            .unwrap_or_else(|| tracing::trace_span!(parent: &Span::current(), "ipfs"));
600
601        // the "current" span which is not entered but the awaited futures are instrumented with it
602        let init_span = tracing::trace_span!(parent: &root_span, "init");
603
604        // stored in the Ipfs, instrumenting every method call
605        let facade_span = tracing::trace_span!("facade");
606
607        // stored in the executor given to libp2p, used to spawn at least the connections,
608        // instrumenting each of those.
609        // let exec_span = tracing::trace_span!(parent: &root_span, "exec");
610        //
611        // // instruments the IpfsFuture, the background task.
612        // let swarm_span = tracing::trace_span!(parent: &root_span, "swarm");
613
614        let mut repo = repo_handle;
615
616        if repo.is_online() {
617            anyhow::bail!("Repo is already initialized");
618        }
619
620        #[cfg(not(target_arch = "wasm32"))]
621        {
622            repo = match &options.ipfs_path {
623                Some(path) => {
624                    if !path.is_dir() {
625                        tokio::fs::create_dir_all(path).await?;
626                    }
627                    Repo::<DefaultStorage>::new_fs(path)
628                }
629                None => repo,
630            };
631        }
632
633        #[cfg(target_arch = "wasm32")]
634        {
635            repo = match options.namespace.take() {
636                Some(ns) => Repo::<DefaultStorage>::new_idb(ns),
637                None => repo,
638            };
639        }
640
641        repo.init().instrument(init_span.clone()).await?;
642
643        let repo_events = repo.initialize_channel();
644
645        //Note: If `All` or `Pinned` are used, we would have to auto adjust the amount of
646        //      provider records by adding the amount of blocks to the config.
647        //TODO: Add persistent layer for kad store
648        let blocks = match options.provider {
649            RepoProvider::None => vec![],
650            RepoProvider::All => repo.list_blocks().await.collect::<Vec<_>>().await,
651            RepoProvider::Pinned => {
652                repo.list_pins(None)
653                    .await
654                    .filter_map(|result| futures::future::ready(result.map(|(cid, _)| cid).ok()))
655                    .collect()
656                    .await
657            }
658            RepoProvider::Roots => {
659                //TODO: Scan blockstore for root unixfs blocks
660                warn!("RepoProvider::Roots is not implemented... ignoring...");
661                vec![]
662            }
663        };
664
665        // TODO: use to calculate store records limits when it is implemented in connexa
666        let _count = blocks.len();
667
668        let listening_addrs = options.listening_addrs.clone();
669
670        let gc_handle = gc_config.map(|config| {
671            async_rt::task::spawn_abortable({
672                let repo = Repo::clone(&repo);
673                async move {
674                    let GCConfig { duration, trigger } = config;
675                    let use_config_timer = duration != Duration::ZERO;
676                    if trigger == GCTrigger::None && !use_config_timer {
677                        tracing::warn!("GC does not have a set timer or a trigger. Disabling GC");
678                        return;
679                    }
680
681                    let time = match use_config_timer {
682                        true => duration,
683                        false => Duration::from_secs(60 * 60),
684                    };
685
686                    let mut interval = futures_timer::Delay::new(time);
687
688                    loop {
689                        tokio::select! {
690                            _ = &mut interval => {
691                                let _g = repo.inner.gclock.write().await;
692                                tracing::debug!("preparing gc operation");
693                                let pinned = repo
694                                    .list_pins(None)
695                                    .await
696                                    .try_filter_map(|(cid, _)| futures::future::ready(Ok(Some(cid))))
697                                    .try_collect::<BTreeSet<_>>()
698                                    .await
699                                    .unwrap_or_default();
700                                let pinned = Vec::from_iter(pinned);
701                                let total_size = repo.get_total_size().await.unwrap_or_default();
702                                let pinned_size = repo
703                                    .get_blocks_size(&pinned)
704                                    .await
705                                    .ok()
706                                    .flatten()
707                                    .unwrap_or_default();
708
709                                let unpinned_blocks = total_size.saturating_sub(pinned_size);
710
711                                tracing::debug!(total_size = %total_size, ?trigger, unpinned_blocks);
712
713                                let cleanup = match trigger {
714                                    GCTrigger::At { size } => {
715                                        total_size > 0 && unpinned_blocks >= size
716                                    }
717                                    GCTrigger::AtStorage => {
718                                        unpinned_blocks > 0
719                                            && unpinned_blocks >= repo.max_storage_size()
720                                    }
721                                    GCTrigger::None => unpinned_blocks > 0,
722                                };
723
724                                tracing::debug!(will_run = %cleanup);
725
726                                if cleanup {
727                                    tracing::debug!("running cleanup of unpinned blocks");
728                                    match repo.cleanup().await {
729                                        Ok(blocks) => {
730                                            tracing::debug!(
731                                                removed_blocks = blocks.len(),
732                                                "blocks removed"
733                                            );
734                                            tracing::debug!("cleanup finished");
735                                        }
736                                        Err(e) => {
737                                            tracing::error!(error = %e, "gc cleanup failed");
738                                        }
739                                    }
740                                }
741
742                                interval.reset(time);
743                            }
744                        }
745                    }
746                }
747            })
748        }).unwrap_or(AbortableJoinHandle::empty());
749
750        let (discovery_tx, discovery_rx) = futures::channel::mpsc::channel::<Cid>(256);
751
752        let mut context = context::IpfsContext::new(&repo);
753        context.repo_events.replace(repo_events);
754        context.discovery_tx.replace(discovery_tx);
755
756        #[cfg(feature = "gateway")]
757        let (gateways, gateway_guard) = match options.gateway.take() {
758            Some(config) => {
759                let gateways = crate::gateway::GatewayList::new(config);
760                let (gateway_tx, gateway_rx) = futures::channel::mpsc::channel::<Cid>(256);
761                context.gateway_tx.replace(gateway_tx);
762                let guard = async_rt::task::spawn_abortable({
763                    let repo = repo.clone();
764                    let gateways = gateways.clone();
765                    async move { crate::gateway::run(gateway_rx, repo, gateways).await }
766                });
767                (Some(gateways), guard)
768            }
769            None => (None, AbortableJoinHandle::empty()),
770        };
771
772        #[cfg(feature = "routing")]
773        let routing_setup = options.router.take().map(|config| {
774            let routers = crate::routing::RouterList::new(config);
775            let (router_tx, router_rx) = futures::channel::mpsc::channel::<Cid>(256);
776            context.router_tx.replace(router_tx);
777            (routers, router_rx)
778        });
779
780        let connexa = init
781            .with_custom_behaviour_with_context((options, repo.clone()), |keys, (options, repo)| {
782                let custom_behaviour = match custom_behaviour {
783                    Some(custom_behaviour) => Some(custom_behaviour(keys)?),
784                    None => None,
785                };
786                Ok(create_create_behaviour(
787                    keys,
788                    &options,
789                    &repo,
790                    custom_behaviour,
791                ))
792            })
793            .set_context(context)
794            .set_custom_task_callback(|swarm, _, context, event| context.handle_event(swarm, event))
795            .set_custom_event_callback(|_swarm, _, context, event| {
796                if let crate::p2p::BehaviourEvent::Bitswap(
797                    crate::p2p::bitswap::Event::NeedBlock { cid },
798                ) = event
799                {
800                    if let Some(tx) = context.discovery_tx.as_mut() {
801                        let _ = tx.try_send(cid);
802                    }
803                    if let Some(tx) = context.gateway_tx.as_mut() {
804                        let _ = tx.try_send(cid);
805                    }
806                    if let Some(tx) = context.router_tx.as_mut() {
807                        let _ = tx.try_send(cid);
808                    }
809                }
810            })
811            .set_swarm_event_callback(move |swarm, _, event, context| {
812                if let Some(callback) = swarm_event.as_ref() {
813                    callback(swarm, event);
814                }
815                if let SwarmEvent::Behaviour(connexa::behaviour::BehaviourEvent::Identify(event)) =
816                    event
817                {
818                    match event {
819                        Event::Received { info, .. } => {
820                            let peer_id = info.public_key.to_peer_id();
821                            if let Some(chs) = context.find_peer_identify.remove(&peer_id) {
822                                for ch in chs {
823                                    let _ = ch.send(Ok(info.clone()));
824                                }
825                            }
826                        }
827                        Event::Sent { .. } => {}
828                        Event::Pushed { .. } => {}
829                        Event::Error { .. } => {}
830                    }
831                }
832            })
833            .set_pollable_callback(|cx, swarm, _, context| {
834                let custom = swarm
835                    .behaviour_mut()
836                    .custom
837                    .as_mut()
838                    .expect("behaviour enabled");
839                while let Poll::Ready(Some(event)) = context.repo_events.poll_next_unpin(cx) {
840                    context.handle_repo_event(custom, event);
841                }
842                Poll::Pending
843            })
844            .set_preload(|_, swarm, _, _| {
845                for addr in listening_addrs {
846                    if let Err(e) = swarm.listen_on(addr.clone()) {
847                        tracing::error!(%addr, %e, "failed to listen on address");
848                    }
849                }
850
851                for block in blocks {
852                    if let Some(kad) = swarm.behaviour_mut().kademlia.as_mut() {
853                        let key = RecordKey::from(block.hash().to_bytes());
854                        if let Err(e) = kad.start_providing(key) {
855                            match e {
856                                connexa::prelude::dht::store::Error::MaxProvidedKeys => break,
857                                _ => unreachable!(),
858                            }
859                        }
860                    }
861                }
862            })
863            .build()
864            .await?;
865
866        let discovery_guard = async_rt::task::spawn_abortable({
867            let connexa = connexa.clone();
868            async move {
869                let mut rx = discovery_rx;
870                let mut inflight: HashSet<Cid> = HashSet::new();
871                let mut lookups = FuturesUnordered::new();
872                loop {
873                    tokio::select! {
874                        maybe_cid = rx.next() => {
875                            let Some(cid) = maybe_cid else { break };
876                            if !inflight.insert(cid) {
877                                continue;
878                            }
879                            if lookups.len() >= 8 {
880                                inflight.remove(&cid);
881                                continue;
882                            }
883                            let connexa = connexa.clone();
884                            lookups.push(async move {
885                                if let Ok(mut providers) = connexa.dht().get_providers(cid).await {
886                                    let mut deadline =
887                                        futures_timer::Delay::new(Duration::from_secs(10));
888                                    let mut dialed = 0usize;
889                                    loop {
890                                        tokio::select! {
891                                            item = providers.next() => {
892                                                let Some(Ok(set)) = item else { break };
893                                                for peer in set {
894                                                    let opts = crate::DialOpts::peer_id(peer).build();
895                                                    let _ = connexa.swarm().dial(opts).await;
896                                                    dialed += 1;
897                                                    if dialed >= 8 {
898                                                        break;
899                                                    }
900                                                }
901                                                if dialed >= 8 {
902                                                    break;
903                                                }
904                                            }
905                                            _ = &mut deadline => break,
906                                        }
907                                    }
908                                }
909                                cid
910                            });
911                        }
912                        Some(cid) = lookups.next(), if !lookups.is_empty() => {
913                            inflight.remove(&cid);
914                        }
915                    }
916                }
917            }
918        });
919
920        #[cfg(feature = "routing")]
921        let (routers, routing_guard) = match routing_setup {
922            Some((routers, router_rx)) => {
923                let guard = async_rt::task::spawn_abortable({
924                    let connexa = connexa.clone();
925                    let routers = routers.clone();
926                    async move { crate::routing::run(router_rx, connexa, routers).await }
927                });
928                (Some(routers), guard)
929            }
930            None => (None, AbortableJoinHandle::empty()),
931        };
932
933        let ipfs = Ipfs {
934            span: facade_span,
935            repo,
936            connexa,
937            record_key_validator: Arc::new(record_key_validator),
938            _gc_guard: gc_handle,
939            _discovery_guard: discovery_guard,
940            #[cfg(feature = "gateway")]
941            gateways,
942            #[cfg(feature = "gateway")]
943            _gateway_guard: gateway_guard,
944            #[cfg(feature = "routing")]
945            routers,
946            #[cfg(feature = "routing")]
947            _routing_guard: routing_guard,
948        };
949
950        Ok(ipfs)
951    }
952}