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#[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 pub fn new() -> Self {
60 let keypair = Keypair::generate_ed25519();
61 Self::with_keypair(&keypair).expect("keypair is valid")
62 }
63
64 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 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 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 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 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 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 pub fn add_listening_addrs(mut self, addrs: Vec<Multiaddr>) -> Self {
138 self.options.listening_addrs.extend(addrs);
139 self
140 }
141
142 pub fn set_listening_addrs(mut self, addrs: Vec<Multiaddr>) -> Self {
144 self.options.listening_addrs = addrs;
145 self
146 }
147
148 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 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 pub fn with_kademlia(mut self) -> Self {
168 self.init = self.init.with_kademlia();
169 self
170 }
171
172 pub fn with_bitswap(mut self) -> Self {
174 self.options.protocols.bitswap = true;
175 self
176 }
177
178 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 #[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 #[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 #[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 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 pub fn with_autorelay(mut self) -> Self {
230 self.init = self.init.with_autorelay();
231 self
232 }
233
234 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 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 #[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 pub fn with_rendezvous_server(mut self) -> Self {
260 self.init = self.init.with_rendezvous_server();
261 self
262 }
263
264 pub fn with_rendezvous_client(mut self) -> Self {
266 self.init = self.init.with_rendezvous_client();
267 self
268 }
269
270 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 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 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 pub fn with_autonat(mut self) -> Self {
324 self.init = self.init.with_autonat_v1();
325 self
326 }
327
328 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 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 pub fn with_gc(mut self, config: GCConfig) -> Self {
345 self.gc_config = Some(config);
346 self
347 }
348
349 pub fn set_temp_pin_duration(mut self, duration: Duration) -> Self {
352 self.gc_repo_duration = Some(duration);
353 self
354 }
355
356 #[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 #[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 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 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 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 pub fn set_addrbook_configuration(mut self, config: AddressBookConfig) -> Self {
409 self.options.addr_config = config;
410 self
411 }
412
413 pub fn set_provider(mut self, opt: RepoProvider) -> Self {
415 self.options.provider = opt;
416 self
417 }
418
419 pub fn set_repo(mut self, repo: &Repo<DefaultStorage>) -> Self {
421 self.repo_handle = Repo::clone(repo);
422 self
423 }
424
425 #[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 #[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 #[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 #[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 #[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 #[cfg(feature = "websocket")]
479 pub fn enable_websocket(mut self) -> Self {
480 self.init = self.init.enable_websocket();
481 self
482 }
483
484 #[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 #[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 #[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 #[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 #[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 #[cfg(feature = "webrtc")]
529 pub fn enable_webrtc(mut self) -> Self {
530 self.init = self.init.enable_webrtc();
531 self
532 }
533
534 #[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 #[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 pub fn enable_memory_transport(mut self) -> Self {
555 self.init = self.init.enable_memory_transport();
556 self
557 }
558
559 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 pub fn set_span(mut self, span: Span) -> Self {
571 self.options.span = Some(span);
572 self
573 }
574
575 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 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 .unwrap_or_else(|| tracing::trace_span!(parent: &Span::current(), "ipfs"));
600
601 let init_span = tracing::trace_span!(parent: &root_span, "init");
603
604 let facade_span = tracing::trace_span!("facade");
606
607 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 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 warn!("RepoProvider::Roots is not implemented... ignoring...");
661 vec![]
662 }
663 };
664
665 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}