Skip to main content

rust_ipfs/
lib.rs

1//! IPFS node implementation
2//!
3//! [Ipfs](https://ipfs.io) is a peer-to-peer system with content addressed functionality. The main
4//! entry point for users of this crate is the [`Ipfs`] facade, which allows access to most of the
5//! implemented functionality.
6//!
7//! This crate passes a lot of the [interface-ipfs-core] test suite; most of that functionality is
8//! in `ipfs-http` crate. The crate has some interoperability with the [go-ipfs] and [js-ipfs]
9//! implementations.
10//!
11//! `ipfs` is an early alpha level crate: APIs and their implementation are subject to change in
12//! any upcoming release at least for now. The aim of the crate is to become a library-first
13//! production ready implementation of an Ipfs node.
14//!
15//! [interface-ipfs-core]: https://www.npmjs.com/package/interface-ipfs-core
16//! [go-ipfs]: https://github.com/ipfs/go-ipfs/
17//! [js-ipfs]: https://github.com/ipfs/js-ipfs/
18// We are not done yet, but uncommenting this makes it easier to hunt down for missing docs.
19//#![deny(missing_docs)]
20//
21// This isn't recognized in stable yet, but we should disregard any nags on these to keep making
22// the docs better.
23//#![allow(private_intra_doc_links)]
24
25#[macro_use]
26extern crate tracing;
27pub mod block;
28pub mod builder;
29pub mod config;
30mod context;
31pub mod dag;
32pub mod error;
33#[cfg(feature = "gateway")]
34mod gateway;
35pub mod ipns;
36pub mod mfs;
37pub mod p2p;
38pub mod path;
39#[cfg(feature = "pinning")]
40pub mod pinning;
41pub mod refs;
42pub mod repo;
43#[cfg(feature = "routing")]
44mod routing;
45pub mod unixfs;
46
47pub use block::Block;
48
49use anyhow::anyhow;
50use bytes::Bytes;
51use dag::{DagGet, DagPut};
52use futures::{
53    channel::oneshot::{self, channel as oneshot_channel, Sender as OneshotSender},
54    future::BoxFuture,
55    stream::BoxStream,
56    StreamExt,
57};
58
59use p2p::{MultiaddrExt, PeerInfo};
60use repo::{DefaultStorage, RepoFetch, RepoInsertPin, RepoRemovePin};
61
62use tracing::Span;
63use tracing_futures::Instrument;
64
65use unixfs::UnixfsGet;
66use unixfs::{AddOpt, IpfsUnixfs, UnixfsAdd, UnixfsCat, UnixfsLs};
67
68use self::{dag::IpldDag, ipns::Ipns, p2p::TSwarm, repo::Repo};
69pub use self::{
70    error::Error,
71    p2p::BehaviourEvent,
72    p2p::KadResult,
73    path::IpfsPath,
74    repo::{PinKind, PinMode},
75};
76use async_rt::AbortableJoinHandle;
77use connexa::handle::Connexa;
78pub use connexa::prelude::dht::{Mode, Quorum, Record, RecordKey, ToRecordKey};
79pub use connexa::prelude::request_response::{
80    InboundRequestId, IntoRequest, OptionalStreamProtocol,
81};
82pub use connexa::prelude::swarm::derive_prelude::{ConnectionId, ListenerId};
83pub use connexa::prelude::swarm::dial_opts::{DialOpts, PeerCondition};
84pub use connexa::prelude::{
85    connection_limits::ConnectionLimits, gossipsub,
86    identify,
87    ping, swarm::{self, NetworkBehaviour}, GossipsubMessage,
88    Stream,
89};
90pub use connexa::prelude::{
91    identity::Keypair, ConnexaSwarmEvent, Multiaddr, PeerId, Protocol, StreamProtocol,
92};
93pub use connexa::{behaviour::request_response::RequestResponseConfig, dummy};
94use ipld_core::cid::Cid;
95use ipld_core::ipld::Ipld;
96
97use connexa::keystore::Keychain;
98use connexa::prelude::gossipsub::IntoGossipsubTopic;
99use connexa::prelude::rendezvous::IntoNamespace;
100#[cfg(feature = "stream")]
101use connexa::prelude::stream::IntoStreamProtocol;
102pub use connexa::prelude::transport::ConnectedPoint;
103use serde::Serialize;
104use std::{borrow::Borrow, path::PathBuf};
105use std::{
106    collections::{HashMap, HashSet},
107    fmt,
108    path::Path,
109    sync::Arc,
110    time::Duration,
111};
112
113/// Ipfs node options used to configure the node to be created with [`UninitializedIpfs`].
114struct IpfsOptions {
115    /// The path of the ipfs repo (blockstore and datastore).
116    ///
117    /// This is always required but can be any path with in-memory backends. The filesystem backend
118    /// creates a directory structure alike but not compatible to other ipfs implementations.
119    ///
120    /// # Incompatiblity and interop warning
121    ///
122    /// It is **not** recommended to set this to IPFS_PATH without first at least backing up your
123    /// existing repository.
124    pub ipfs_path: Option<PathBuf>,
125
126    /// Enables and supply a name of the namespace used for indexeddb
127    #[cfg(target_arch = "wasm32")]
128    pub namespace: Option<Option<String>>,
129
130    /// Nodes used as bootstrap peers.
131    pub bootstrap: Vec<Multiaddr>,
132
133    /// Bound listening addresses; by default the node will not listen on any address.
134    pub listening_addrs: Vec<Multiaddr>,
135
136    pub bitswap_config: Box<dyn Fn(p2p::bitswap::Config) -> p2p::bitswap::Config>,
137
138    /// Address book configuration
139    pub addr_config: AddressBookConfig,
140
141    /// Repo Provider option
142    pub provider: RepoProvider,
143
144    /// Trustless HTTP gateways used as a retrieval fallback.
145    #[cfg(feature = "gateway")]
146    pub gateway: Option<Vec<String>>,
147
148    /// Delegated routing V1 HTTP endpoints used for provider discovery.
149    #[cfg(feature = "routing")]
150    pub router: Option<Vec<String>>,
151
152    /// The span for tracing purposes, `None` value is converted to `tracing::trace_span!("ipfs")`.
153    ///
154    /// All futures returned by `Ipfs`, background task actions and swarm actions are instrumented
155    /// with this span or spans referring to this as their parent. Setting this other than `None`
156    /// default is useful when running multiple nodes.
157    pub span: Option<Span>,
158
159    pub(crate) protocols: Libp2pProtocol,
160}
161
162#[derive(Default, Clone, Copy)]
163pub(crate) struct Libp2pProtocol {
164    pub(crate) bitswap: bool,
165    pub(crate) relay: bool,
166}
167
168#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
169pub enum RepoProvider {
170    /// Dont provide any blocks automatically
171    #[default]
172    None,
173
174    /// Provide all blocks stored automatically
175    All,
176
177    /// Provide pinned blocks
178    Pinned,
179
180    /// Provide root blocks only (Currently NO-OP)
181    Roots,
182}
183
184impl Default for IpfsOptions {
185    fn default() -> Self {
186        Self {
187            ipfs_path: None,
188            #[cfg(target_arch = "wasm32")]
189            namespace: None,
190            bootstrap: Default::default(),
191            bitswap_config: Box::new(|config| config),
192            addr_config: Default::default(),
193            provider: Default::default(),
194            #[cfg(feature = "gateway")]
195            gateway: None,
196            #[cfg(feature = "routing")]
197            router: None,
198            listening_addrs: vec![],
199            span: None,
200            protocols: Default::default(),
201        }
202    }
203}
204
205impl fmt::Debug for IpfsOptions {
206    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
207        // needed since libp2p::identity::Keypair does not have a Debug impl, and the IpfsOptions
208        // is a struct with all public fields, don't enforce users to use this wrapper.
209        fmt.debug_struct("IpfsOptions")
210            .field("ipfs_path", &self.ipfs_path)
211            .field("bootstrap", &self.bootstrap)
212            .field("listening_addrs", &self.listening_addrs)
213            .field("span", &self.span)
214            .finish()
215    }
216}
217
218/// The facade for the Ipfs node.
219///
220/// The facade has most of the functionality either directly as a method or the functionality can
221/// be implemented using the provided methods. For more information, see examples or the HTTP
222/// endpoint implementations in `ipfs-http`.
223///
224/// The facade is created through [`UninitializedIpfs`] which is configured with [`IpfsOptions`].
225#[derive(Clone)]
226#[allow(clippy::type_complexity)]
227pub struct Ipfs {
228    span: Span,
229    repo: Repo<DefaultStorage>,
230    connexa: Connexa<IpfsEvent, DefaultKeystore>,
231    record_key_validator:
232        Arc<HashMap<String, Box<dyn Fn(&str) -> anyhow::Result<RecordKey> + Sync + Send>>>,
233    _gc_guard: AbortableJoinHandle<()>,
234    _discovery_guard: AbortableJoinHandle<()>,
235    #[cfg(feature = "gateway")]
236    gateways: Option<gateway::GatewayList>,
237    #[cfg(feature = "gateway")]
238    _gateway_guard: AbortableJoinHandle<()>,
239    #[cfg(feature = "routing")]
240    routers: Option<routing::RouterList>,
241    #[cfg(feature = "routing")]
242    _routing_guard: AbortableJoinHandle<()>,
243}
244
245impl std::fmt::Debug for Ipfs {
246    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247        f.debug_struct("Ipfs").finish()
248    }
249}
250
251type Channel<T> = OneshotSender<Result<T, Error>>;
252type ReceiverChannel<T> = oneshot::Receiver<Result<T, Error>>;
253/// Events used internally to communicate with the swarm, which is executed in the the background
254/// task.
255#[derive(Debug)]
256#[allow(clippy::type_complexity)]
257enum IpfsEvent {
258    /// Node supported protocol
259    Protocol(OneshotSender<Vec<String>>),
260    GetBitswapPeers(Channel<BoxFuture<'static, Vec<PeerId>>>),
261    BitswapStats(Channel<BoxFuture<'static, crate::p2p::bitswap::BitswapStats>>),
262    WantList(Option<PeerId>, Channel<BoxFuture<'static, Vec<Cid>>>),
263
264    FindPeerIdentity(PeerId, Channel<ReceiverChannel<identify::Info>>),
265    AddPeer(AddPeerOpt, Channel<()>),
266    Addresses(Channel<Vec<(PeerId, Vec<Multiaddr>)>>),
267    RemovePeer(PeerId, Option<Multiaddr>, Channel<bool>),
268    GetBootstrappers(OneshotSender<Vec<Multiaddr>>),
269    AddBootstrapper(Multiaddr, Channel<Multiaddr>),
270    RemoveBootstrapper(Multiaddr, Channel<Multiaddr>),
271    ClearBootstrappers(Channel<Vec<Multiaddr>>),
272    DefaultBootstrap(Channel<Vec<Multiaddr>>),
273}
274
275#[derive(Debug, Copy, Clone)]
276pub enum DhtMode {
277    Auto,
278    Client,
279    Server,
280}
281
282impl From<DhtMode> for Option<Mode> {
283    fn from(mode: DhtMode) -> Self {
284        match mode {
285            DhtMode::Auto => None,
286            DhtMode::Client => Some(Mode::Client),
287            DhtMode::Server => Some(Mode::Server),
288        }
289    }
290}
291
292#[derive(Debug, Clone, Eq, PartialEq)]
293pub enum PubsubEvent {
294    /// Subscription event to a given topic
295    Subscribe {
296        peer_id: PeerId,
297        topic: Option<String>,
298    },
299
300    /// Unsubscribing event to a given topic
301    Unsubscribe {
302        peer_id: PeerId,
303        topic: Option<String>,
304    },
305}
306
307type TSwarmEvent<C> = <TSwarm<C> as futures::Stream>::Item;
308type TSwarmEventFn<C> = Arc<dyn Fn(&mut TSwarm<C>, &TSwarmEvent<C>) + Sync + Send>;
309
310#[derive(Debug, Copy, Clone)]
311pub enum FDLimit {
312    Max,
313    Custom(u64),
314}
315
316#[derive(Debug, Clone)]
317pub enum PeerConnectionEvents {
318    IncomingConnection {
319        connection_id: ConnectionId,
320        addr: Multiaddr,
321    },
322    OutgoingConnection {
323        connection_id: ConnectionId,
324        addr: Multiaddr,
325    },
326    ClosedConnection {
327        connection_id: ConnectionId,
328    },
329}
330
331impl Ipfs {
332    /// Return an [`IpldDag`] for DAG operations
333    pub fn dag(&self) -> IpldDag {
334        IpldDag::new(self.clone())
335    }
336
337    /// Return an [`Repo`] to access the internal repo of the node
338    pub fn repo(&self) -> &Repo<DefaultStorage> {
339        &self.repo
340    }
341
342    /// Returns an [`IpfsUnixfs`] for files operations
343    pub fn unixfs(&self) -> IpfsUnixfs {
344        IpfsUnixfs::new(self.clone())
345    }
346
347    /// Returns the node's mutable filesystem ([`crate::mfs::Mfs`]) handle.
348    pub fn mfs(&self) -> crate::mfs::Mfs {
349        crate::mfs::Mfs::new(self.repo.clone())
350    }
351
352    /// Returns a [`Ipns`] for ipns operations
353    pub fn ipns(&self) -> Ipns {
354        Ipns::new(self.clone())
355    }
356
357    /// Returns a client for a remote pinning service at `endpoint` authenticated with `token`.
358    #[cfg(feature = "pinning")]
359    pub fn remote_pinning(
360        &self,
361        endpoint: impl Into<String>,
362        token: impl Into<String>,
363    ) -> crate::pinning::RemotePinningService {
364        crate::pinning::RemotePinningService::new(self.clone(), endpoint, token)
365    }
366
367    /// Adds a trustless gateway to the retrieval list.
368    #[cfg(feature = "gateway")]
369    pub fn add_gateway(&self, url: &str) -> bool {
370        self.gateways.as_ref().is_some_and(|g| g.add(url))
371    }
372
373    /// Removes a trustless gateway from the retrieval list.
374    #[cfg(feature = "gateway")]
375    pub fn remove_gateway(&self, url: &str) -> bool {
376        self.gateways.as_ref().is_some_and(|g| g.remove(url))
377    }
378
379    /// Lists the trustless gateways currently used for retrieval.
380    #[cfg(feature = "gateway")]
381    pub fn list_gateways(&self) -> Vec<String> {
382        self.gateways.as_ref().map(|g| g.list()).unwrap_or_default()
383    }
384
385    /// Adds a delegated routing endpoint used for provider discovery.
386    #[cfg(feature = "routing")]
387    pub fn add_router(&self, url: &str) -> bool {
388        self.routers.as_ref().is_some_and(|r| r.add(url))
389    }
390
391    /// Removes a delegated routing endpoint.
392    #[cfg(feature = "routing")]
393    pub fn remove_router(&self, url: &str) -> bool {
394        self.routers.as_ref().is_some_and(|r| r.remove(url))
395    }
396
397    /// Lists the delegated routing endpoints currently used for provider discovery.
398    #[cfg(feature = "routing")]
399    pub fn list_routers(&self) -> Vec<String> {
400        self.routers.as_ref().map(|r| r.list()).unwrap_or_default()
401    }
402
403    /// Puts a block into the ipfs repo.
404    pub fn put_block(&self, block: &Block) -> RepoPutBlock<DefaultStorage> {
405        self.repo.put_block(block).span(self.span.clone())
406    }
407
408    /// Retrieves a block from the local blockstore, or starts fetching from the network or join an
409    /// already started fetch.
410    pub fn get_block(&self, cid: impl Borrow<Cid>) -> RepoGetBlock<DefaultStorage> {
411        self.repo.get_block(cid).span(self.span.clone())
412    }
413
414    /// Remove block from the ipfs repo. A pinned block cannot be removed.
415    pub async fn remove_block(
416        &self,
417        cid: impl Borrow<Cid>,
418        recursive: bool,
419    ) -> Result<Vec<Cid>, Error> {
420        self.repo
421            .remove_block(cid, recursive)
422            .instrument(self.span.clone())
423            .await
424    }
425
426    /// Cleans up of all unpinned blocks
427    /// Note: This will prevent writing operations in [`Repo`] until it finish clearing unpinned
428    ///       blocks.
429    pub async fn gc(&self) -> Result<Vec<Cid>, Error> {
430        let _g = self.repo.inner.gclock.write().await;
431        self.repo.cleanup().instrument(self.span.clone()).await
432    }
433
434    /// Pins a given Cid recursively or directly (non-recursively).
435    ///
436    /// Pins on a block are additive in sense that a previously directly (non-recursively) pinned
437    /// can be made recursive, but removing the recursive pin on the block removes also the direct
438    /// pin as well.
439    ///
440    /// Pinning a Cid recursively (for supported dag-protobuf and dag-cbor) will walk its
441    /// references and pin the references indirectly. When a Cid is pinned indirectly it will keep
442    /// its previous direct or recursive pin and be indirect in addition.
443    ///
444    /// Recursively pinned Cids cannot be re-pinned non-recursively but non-recursively pinned Cids
445    /// can be "upgraded to" being recursively pinned.
446    ///
447    /// # Crash unsafety
448    ///
449    /// If a recursive `insert_pin` operation is interrupted because of a crash or the crash
450    /// prevents from synchronizing the data store to disk, this will leave the system in an inconsistent
451    /// state. The remedy is to re-pin recursive pins.
452    pub fn insert_pin(&self, cid: impl Borrow<Cid>) -> RepoInsertPin<DefaultStorage> {
453        self.repo().pin(cid).span(self.span.clone())
454    }
455
456    /// Unpins a given Cid recursively or only directly.
457    ///
458    /// Recursively unpinning a previously only directly pinned Cid will remove the direct pin.
459    ///
460    /// Unpinning an indirectly pinned Cid is not possible other than through its recursively
461    /// pinned tree roots.
462    pub fn remove_pin(&self, cid: impl Borrow<Cid>) -> RepoRemovePin<DefaultStorage> {
463        self.repo().remove_pin(cid).span(self.span.clone())
464    }
465
466    /// Checks whether a given block is pinned.
467    ///
468    /// Returns true if the block is pinned, false if not. See Crash unsafety notes for the false
469    /// response.
470    ///
471    /// # Crash unsafety
472    ///
473    /// Cannot currently detect partially written recursive pins. Those can happen if
474    /// [`Ipfs::insert_pin`] is interrupted by a crash for example.
475    ///
476    /// Works correctly only under no-crash situations. Workaround for hitting a crash is to re-pin
477    /// any existing recursive pins.
478    ///
479    pub async fn is_pinned(&self, cid: impl Borrow<Cid>) -> Result<bool, Error> {
480        let span = debug_span!(parent: &self.span, "is_pinned", cid = %cid.borrow());
481        self.repo.is_pinned(cid).instrument(span).await
482    }
483
484    /// Lists all pins, or the specific kind thereof.
485    ///
486    /// # Crash unsafety
487    ///
488    /// Does not currently recover from partial recursive pin insertions.
489    pub async fn list_pins(
490        &self,
491        filter: Option<PinMode>,
492    ) -> BoxStream<'static, Result<(Cid, PinMode), Error>> {
493        let span = debug_span!(parent: &self.span, "list_pins", ?filter);
494        self.repo.list_pins(filter).instrument(span).await
495    }
496
497    /// Read specific pins. When `requirement` is `Some`, all pins are required to be of the given
498    /// [`PinMode`].
499    ///
500    /// # Crash unsafety
501    ///
502    /// Does not currently recover from partial recursive pin insertions.
503    pub async fn query_pins(
504        &self,
505        cids: Vec<Cid>,
506        requirement: Option<PinMode>,
507    ) -> Result<Vec<(Cid, PinKind<Cid>)>, Error> {
508        let span = debug_span!(parent: &self.span, "query_pins", ids = cids.len(), ?requirement);
509        self.repo
510            .query_pins(cids, requirement)
511            .instrument(span)
512            .await
513    }
514
515    /// Puts an ipld node into the ipfs repo using `dag-cbor` codec and Sha2_256 hash.
516    ///
517    /// Returns Cid version 1 for the document
518    pub fn put_dag(&self, ipld: impl Serialize) -> DagPut {
519        self.dag().put_dag(ipld).span(self.span.clone())
520    }
521
522    /// Gets an ipld node from the ipfs, fetching the block if necessary.
523    ///
524    /// See [`IpldDag::get`] for more information.
525    pub fn get_dag(&self, path: impl Into<IpfsPath>) -> DagGet {
526        self.dag().get_dag(path).span(self.span.clone())
527    }
528
529    /// Creates a stream which will yield the bytes of an UnixFS file from the root Cid, with the
530    /// optional file byte range. If the range is specified and is outside of the file, the stream
531    /// will end without producing any bytes.
532    pub fn cat_unixfs(&self, starting_point: impl Into<unixfs::StartingPoint>) -> UnixfsCat {
533        self.unixfs().cat(starting_point).span(self.span.clone())
534    }
535
536    /// Add a file through a stream of data to the blockstore
537    pub fn add_unixfs(&self, opt: impl Into<AddOpt>) -> UnixfsAdd {
538        self.unixfs().add(opt).span(self.span.clone())
539    }
540
541    /// Retreive a file and saving it to a path.
542    pub fn get_unixfs(&self, path: impl Into<IpfsPath>, dest: impl AsRef<Path>) -> UnixfsGet {
543        self.unixfs().get(path, dest).span(self.span.clone())
544    }
545
546    /// List directory contents
547    pub fn ls_unixfs(&self, path: impl Into<IpfsPath>) -> UnixfsLs {
548        self.unixfs().ls(path).span(self.span.clone())
549    }
550
551    /// Resolves a ipns path to an ipld path; currently only supports dht and dnslink resolution.
552    pub async fn resolve_ipns(
553        &self,
554        path: impl Borrow<IpfsPath>,
555        recursive: bool,
556    ) -> Result<IpfsPath, Error> {
557        async move {
558            let ipns = self.ipns();
559            let mut resolved = ipns.resolve(path).await;
560
561            if recursive {
562                let mut seen = HashSet::with_capacity(1);
563                while let Ok(ref res) = resolved {
564                    if !seen.insert(res.clone()) {
565                        break;
566                    }
567                    resolved = ipns.resolve(res).await;
568                }
569            }
570            Ok(resolved?)
571        }
572        .instrument(self.span.clone())
573        .await
574    }
575
576    /// Publish ipns record to DHT
577    pub async fn publish_ipns(&self, path: impl Borrow<IpfsPath>) -> Result<IpfsPath, Error> {
578        async move {
579            let ipns = self.ipns();
580            ipns.publish(None, path, Default::default())
581                .await
582                .map_err(anyhow::Error::from)
583        }
584        .instrument(self.span.clone())
585        .await
586    }
587
588    /// Connects to the peer
589    pub async fn connect(&self, target: impl Into<DialOpts>) -> Result<ConnectionId, Error> {
590        self.connexa
591            .swarm()
592            .dial(target)
593            .await
594            .map_err(anyhow::Error::from)
595    }
596
597    /// Returns known peer addresses
598    pub async fn addrs(&self) -> Result<Vec<(PeerId, Vec<Multiaddr>)>, Error> {
599        let (tx, rx) = oneshot_channel();
600        self.connexa
601            .send_custom_event(IpfsEvent::Addresses(tx))
602            .await?;
603        rx.await?
604    }
605
606    /// Checks whether there is an established connection to a peer.
607    pub async fn is_connected(&self, peer_id: PeerId) -> Result<bool, Error> {
608        self.connexa
609            .swarm()
610            .is_connected(peer_id)
611            .await
612            .map_err(anyhow::Error::from)
613    }
614
615    /// Returns the connected peers
616    pub async fn connected(&self) -> Result<Vec<PeerId>, Error> {
617        self.connexa
618            .swarm()
619            .connected_peers()
620            .await
621            .map_err(anyhow::Error::from)
622    }
623
624    /// Disconnects a given peer.
625    pub async fn disconnect(&self, target: PeerId) -> Result<(), Error> {
626        self.connexa
627            .swarm()
628            .disconnect(target)
629            .await
630            .map_err(anyhow::Error::from)
631    }
632
633    /// Bans a peer.
634    pub async fn ban_peer(&self, target: PeerId) -> Result<(), Error> {
635        self.connexa
636            .blacklist()
637            .add(target)
638            .await
639            .map_err(anyhow::Error::from)
640    }
641
642    /// Unbans a peer.
643    pub async fn unban_peer(&self, target: PeerId) -> Result<(), Error> {
644        self.connexa
645            .blacklist()
646            .remove(target)
647            .await
648            .map_err(Into::into)
649    }
650
651    /// Returns the peer identity information. If no peer id is supplied the local node identity is used.
652    pub async fn identity(&self, peer_id: Option<PeerId>) -> Result<PeerInfo, Error> {
653        async move {
654            match peer_id {
655                Some(peer_id) => {
656                    let (tx, rx) = oneshot_channel();
657
658                    self.connexa
659                        .send_custom_event(IpfsEvent::FindPeerIdentity(peer_id, tx))
660                        .await?;
661
662                    rx.await??.await?.map(PeerInfo::from)
663                }
664                None => {
665                    let mut addresses = HashSet::new();
666
667                    let (local_result, external_result) =
668                        futures::join!(self.listening_addresses(), self.external_addresses());
669
670                    let external: HashSet<Multiaddr> =
671                        HashSet::from_iter(external_result.unwrap_or_default());
672                    let local: HashSet<Multiaddr> =
673                        HashSet::from_iter(local_result.unwrap_or_default());
674
675                    addresses.extend(external.iter().cloned());
676                    addresses.extend(local.iter().cloned());
677
678                    let mut addresses = Vec::from_iter(addresses);
679
680                    let (tx, rx) = oneshot_channel();
681                    self.connexa
682                        .send_custom_event(IpfsEvent::Protocol(tx))
683                        .await?;
684
685                    let protocols = rx
686                        .await?
687                        .iter()
688                        .filter_map(|s| StreamProtocol::try_from_owned(s.clone()).ok())
689                        .collect();
690
691                    let public_key = self.keypair().public();
692                    let peer_id = public_key.to_peer_id();
693
694                    for addr in &mut addresses {
695                        if !matches!(addr.iter().last(), Some(Protocol::P2p(_))) {
696                            addr.push(Protocol::P2p(peer_id))
697                        }
698                    }
699
700                    let info = PeerInfo {
701                        peer_id,
702                        public_key,
703                        protocol_version: String::new(), // TODO
704                        agent_version: String::new(),    // TODO
705                        listen_addrs: addresses,
706                        protocols,
707                        observed_addr: None,
708                    };
709
710                    Ok(info)
711                }
712            }
713        }
714        .instrument(self.span.clone())
715        .await
716    }
717
718    /// Subscribes to a given topic. Can unsubscribe by calling [`Ipfs::pubsub_unsubscribe`].
719    pub async fn pubsub_subscribe(&self, topic: impl IntoGossipsubTopic) -> Result<(), Error> {
720        self.connexa
721            .gossipsub()
722            .subscribe(topic)
723            .await
724            .map_err(anyhow::Error::from)
725    }
726
727    /// Creates a stream to listen on events of a given topic
728    pub async fn pubsub_listener(
729        &self,
730        topic: impl IntoGossipsubTopic,
731    ) -> Result<BoxStream<'static, connexa::prelude::GossipsubEvent>, Error> {
732        let st = self
733            .connexa
734            .gossipsub()
735            .listener(topic)
736            .await
737            .map_err(anyhow::Error::from)?;
738
739        Ok(st)
740    }
741
742    /// Publishes to the topic which may have been subscribed to earlier
743    pub async fn pubsub_publish(
744        &self,
745        topic: impl IntoGossipsubTopic,
746        data: impl Into<Bytes>,
747    ) -> Result<(), Error> {
748        self.connexa
749            .gossipsub()
750            .publish(topic, data)
751            .await
752            .map_err(Into::into)
753    }
754
755    /// Forcibly unsubscribes a previously made [`SubscriptionStream`], which could also be
756    /// unsubscribed by dropping the stream.
757    ///
758    /// Returns true if unsubscription was successful
759    pub async fn pubsub_unsubscribe(&self, topic: impl IntoGossipsubTopic) -> Result<(), Error> {
760        self.connexa
761            .gossipsub()
762            .unsubscribe(topic)
763            .await
764            .map_err(Into::into)
765    }
766
767    /// Returns all known pubsub peers within a given topic
768    pub async fn pubsub_peers(&self, topic: impl IntoGossipsubTopic) -> Result<Vec<PeerId>, Error> {
769        self.connexa
770            .gossipsub()
771            .peers(topic)
772            .await
773            .map_err(Into::into)
774    }
775
776    /// Returns all currently subscribed topics
777    pub async fn pubsub_subscribed(&self) -> Result<Vec<String>, Error> {
778        // self.connexa.gossipsub().
779        unimplemented!()
780    }
781
782    /// Subscribe to a stream of request. If a protocol is not supplied,
783    /// it will subscribe to the first or default protocol that was set in
784    /// [UninitializedIpfs::with_request_response]
785    pub async fn requests_subscribe(
786        &self,
787        protocol: impl Into<OptionalStreamProtocol>,
788    ) -> Result<BoxStream<'static, (PeerId, InboundRequestId, Bytes)>, Error> {
789        self.connexa
790            .request_response()
791            .listen_for_requests(protocol)
792            .await
793            .map_err(Into::into)
794    }
795
796    /// Sends a request to a specific peer.
797    /// If a protocol is not supplied, it will use the first/default protocol that was set in
798    /// [UninitializedIpfs::with_request_response].
799    pub async fn send_request(
800        &self,
801        peer_id: PeerId,
802        request: impl IntoRequest,
803    ) -> Result<Bytes, Error> {
804        self.connexa
805            .request_response()
806            .send_request(peer_id, request)
807            .await
808            .map_err(Into::into)
809    }
810
811    /// Sends a request to a list of peers.
812    /// If a protocol is not supplied, it will use the first/default protocol that was set in
813    /// [UninitializedIpfs::with_request_response]
814    pub async fn send_requests(
815        &self,
816        peers: impl IntoIterator<Item = PeerId>,
817        request: impl IntoRequest,
818    ) -> Result<BoxStream<'static, (PeerId, Result<Bytes, connexa::error::Error>)>, Error> {
819        self.connexa
820            .request_response()
821            .send_requests(peers, request)
822            .await
823            .map_err(Into::into)
824    }
825
826    /// Sends a request to a specific peer.
827    /// If a protocol is not supplied, it will use the first/default protocol that was set in
828    /// [UninitializedIpfs::with_request_response].
829    pub async fn send_response(
830        &self,
831        peer_id: PeerId,
832        id: InboundRequestId,
833        response: impl IntoRequest,
834    ) -> Result<(), Error> {
835        self.connexa
836            .request_response()
837            .send_response(peer_id, id, response)
838            .await
839            .map_err(Into::into)
840    }
841
842    /// Returns the known wantlist for the local node when the `peer` is `None` or the wantlist of the given `peer`
843    pub async fn bitswap_wantlist(
844        &self,
845        peer: impl Into<Option<PeerId>>,
846    ) -> Result<Vec<Cid>, Error> {
847        async move {
848            let peer = peer.into();
849            let (tx, rx) = oneshot_channel();
850
851            self.connexa
852                .send_custom_event(IpfsEvent::WantList(peer, tx))
853                .await?;
854
855            Ok(rx.await??.await)
856        }
857        .instrument(self.span.clone())
858        .await
859    }
860
861    #[cfg(feature = "stream")]
862    pub async fn stream_control(&self) -> Result<connexa::prelude::stream::Control, Error> {
863        self.connexa
864            .stream()
865            .control_handle()
866            .await
867            .map_err(Into::into)
868    }
869
870    #[cfg(feature = "stream")]
871    pub async fn new_stream(
872        &self,
873        protocol: impl IntoStreamProtocol,
874    ) -> Result<connexa::prelude::stream::IncomingStreams, Error> {
875        let protocol = protocol.into_protocol()?;
876        self.connexa
877            .stream()
878            .new_stream(protocol)
879            .await
880            .map_err(Into::into)
881    }
882
883    #[cfg(feature = "stream")]
884    pub async fn open_stream(
885        &self,
886        peer_id: PeerId,
887        protocol: impl IntoStreamProtocol,
888    ) -> Result<connexa::prelude::Stream, Error> {
889        self.connexa
890            .stream()
891            .open_stream(peer_id, protocol)
892            .await
893            .map_err(Into::into)
894    }
895
896    /// Returns a list of local blocks
897    pub async fn refs_local(&self) -> Vec<Cid> {
898        self.repo
899            .list_blocks()
900            .instrument(self.span.clone())
901            .await
902            .collect::<Vec<_>>()
903            .await
904    }
905
906    /// Returns local listening addresses
907    pub async fn listening_addresses(&self) -> Result<Vec<Multiaddr>, Error> {
908        self.connexa
909            .swarm()
910            .listening_addresses()
911            .await
912            .map_err(Into::into)
913    }
914
915    /// Returns external addresses
916    pub async fn external_addresses(&self) -> Result<Vec<Multiaddr>, Error> {
917        self.connexa
918            .swarm()
919            .external_addresses()
920            .await
921            .map_err(Into::into)
922    }
923
924    /// Add a given multiaddr as a listening address. Will fail if the address is unsupported, or
925    /// if it is already being listened on. Currently will invoke `Swarm::listen_on` internally,
926    /// returning the first `Multiaddr` that is being listened on.
927    pub async fn add_listening_address(&self, addr: Multiaddr) -> Result<ListenerId, Error> {
928        self.connexa
929            .swarm()
930            .listen_on(addr)
931            .await
932            .map_err(Into::into)
933    }
934
935    pub async fn get_listening_address(&self, id: ListenerId) -> Result<Vec<Multiaddr>, Error> {
936        self.connexa
937            .swarm()
938            .get_listening_addresses(id)
939            .await
940            .map_err(Into::into)
941    }
942
943    /// Stop listening on a previously added listening address. Fails if the address is not being
944    /// listened to.
945    ///
946    /// The removal of all listening addresses added through unspecified addresses is not supported.
947    pub async fn remove_listening_address(&self, id: ListenerId) -> Result<(), Error> {
948        self.connexa
949            .swarm()
950            .remove_listener(id)
951            .await
952            .map_err(Into::into)
953    }
954
955    /// Add a given multiaddr as a external address to indenticate how our node can be reached.
956    /// Note: We will not perform checks
957    pub async fn add_external_address(&self, addr: Multiaddr) -> Result<(), Error> {
958        self.connexa
959            .swarm()
960            .add_external_address(addr)
961            .await
962            .map_err(Into::into)
963    }
964
965    /// Removes a previously added external address.
966    pub async fn remove_external_address(&self, addr: Multiaddr) -> Result<(), Error> {
967        self.connexa
968            .swarm()
969            .remove_external_address(addr)
970            .await
971            .map_err(Into::into)
972    }
973
974    pub async fn swarm_events(&self) -> Result<BoxStream<'static, ConnexaSwarmEvent>, Error> {
975        self.connexa.swarm().listener().await.map_err(Into::into)
976    }
977
978    pub async fn peer_connection_events(
979        &self,
980        target: PeerId,
981    ) -> Result<BoxStream<'static, PeerConnectionEvents>, Error> {
982        let mut st = self.connexa.swarm().listener().await?;
983
984        let st = async_stream::stream! {
985            while let Some(event) = st.next().await {
986                yield match event {
987                    ConnexaSwarmEvent::ConnectionEstablished { peer_id, connection_id, endpoint, .. } if peer_id == target => {
988                        match endpoint {
989                            ConnectedPoint::Listener { send_back_addr, .. } => {
990                                PeerConnectionEvents::IncomingConnection { connection_id, addr: send_back_addr }
991                            }
992                            ConnectedPoint::Dialer { address, ..  } => {
993                                PeerConnectionEvents::OutgoingConnection { connection_id, addr: address }
994                            }
995                        }
996                    },
997                    ConnexaSwarmEvent::ConnectionClosed { peer_id, connection_id, .. } if peer_id == target => {
998                        PeerConnectionEvents::ClosedConnection { connection_id }
999                    }
1000                    _ => continue,
1001                }
1002            }
1003        };
1004
1005        Ok(st.boxed())
1006    }
1007
1008    /// Obtain the addresses associated with the given `PeerId`; they are first searched for locally
1009    /// and the DHT is used as a fallback: a `Kademlia::get_closest_peers(peer_id)` query is run and
1010    /// when it's finished, the newly added DHT records are checked for the existence of the desired
1011    /// `peer_id` and if it's there, the list of its known addresses is returned.
1012    pub async fn find_peer(&self, peer_id: PeerId) -> Result<Vec<Multiaddr>, Error> {
1013        self.connexa
1014            .dht()
1015            .find_peer(peer_id)
1016            .await
1017            .map_err(Into::into)
1018            .map(|list| list.into_iter().map(|info| info.addrs).flatten().collect())
1019    }
1020
1021    /// Performs a DHT lookup for providers of a value to the given key.
1022    ///
1023    /// Returns a list of peers found providing the Cid.
1024    pub async fn get_providers(
1025        &self,
1026        cid: Cid,
1027    ) -> Result<BoxStream<'static, Result<HashSet<PeerId>, connexa::error::Error>>, Error> {
1028        self.dht_get_providers(cid).await
1029    }
1030
1031    /// Performs a DHT lookup for providers of a value to the given key.
1032    pub async fn dht_get_providers(
1033        &self,
1034        key: impl ToRecordKey,
1035    ) -> Result<BoxStream<'static, Result<HashSet<PeerId>, connexa::error::Error>>, Error> {
1036        self.connexa
1037            .dht()
1038            .get_providers(key)
1039            .await
1040            .map_err(Into::into)
1041    }
1042
1043    /// Establishes the node as a provider of a block with the given Cid: it publishes a provider
1044    /// record with the given key (Cid) and the node's PeerId to the peers closest to the key. The
1045    /// publication of provider records is periodically repeated as per the interval specified in
1046    /// `libp2p`'s  `KademliaConfig`.
1047    pub async fn provide(&self, cid: Cid) -> Result<(), Error> {
1048        // don't provide things we don't actually have
1049        if !self.repo.contains(&cid).await? {
1050            return Err(anyhow!(
1051                "Error: block {} not found locally, cannot provide",
1052                cid
1053            ));
1054        }
1055
1056        self.dht_provide(cid.hash().to_bytes()).await
1057    }
1058
1059    /// Establishes the node as a provider of a given Key: it publishes a provider
1060    /// record with the given key and the node's PeerId to the peers closest to the key. The
1061    /// publication of provider records is periodically repeated as per the interval specified in
1062    /// `libp2p`'s  `KademliaConfig`.
1063    pub async fn dht_provide(&self, key: impl ToRecordKey) -> Result<(), Error> {
1064        self.connexa.dht().provide(key).await.map_err(Into::into)
1065    }
1066
1067    /// Fetches the block, and, if set, recursively walk the graph loading all the blocks to the blockstore.
1068    pub fn fetch(&self, cid: &Cid) -> RepoFetch<DefaultStorage> {
1069        self.repo.fetch(cid).span(self.span.clone())
1070    }
1071
1072    /// Returns a list of peers closest to the given `PeerId`, as suggested by the DHT. The
1073    /// node must have at least one known peer in its routing table in order for the query
1074    /// to return any values.
1075    pub async fn get_closest_peers(&self, peer_id: PeerId) -> Result<Vec<PeerId>, Error> {
1076        self.connexa
1077            .dht()
1078            .find_peer(peer_id)
1079            .await
1080            .map_err(Into::into)
1081            .map(|list| list.into_iter().map(|info| info.peer_id).collect())
1082    }
1083
1084    /// Change the DHT mode
1085    pub async fn dht_mode(&self, mode: DhtMode) -> Result<(), Error> {
1086        let mode = match mode {
1087            DhtMode::Client => Some(Mode::Client),
1088            DhtMode::Server => Some(Mode::Server),
1089            DhtMode::Auto => None,
1090        };
1091        self.connexa.dht().set_mode(mode).await.map_err(Into::into)
1092    }
1093
1094    /// Attempts to look a key up in the DHT and returns the values found in the records
1095    /// containing that key.
1096    pub async fn dht_get(
1097        &self,
1098        key: impl ToRecordKey,
1099    ) -> Result<BoxStream<'static, Record>, Error> {
1100        let st = self.connexa.dht().get(key).await?;
1101        let st = st
1102            .filter_map(|result| async move { result.ok() })
1103            .map(|record| record.record)
1104            .boxed();
1105
1106        Ok(st)
1107    }
1108
1109    /// Stores the given key + value record locally and replicates it in the DHT. It doesn't
1110    /// expire locally and is periodically replicated in the DHT, as per the `KademliaConfig`
1111    /// setup.
1112    pub async fn dht_put(
1113        &self,
1114        key: impl AsRef<[u8]>,
1115        value: impl Into<Bytes>,
1116        quorum: Quorum,
1117    ) -> Result<(), Error> {
1118        let key = key.as_ref();
1119
1120        let key_str = String::from_utf8_lossy(key);
1121
1122        let key = if let Ok((prefix, _)) = split_dht_key(&key_str) {
1123            if let Some(key_fn) = self.record_key_validator.get(prefix) {
1124                key_fn(&key_str)?
1125            } else {
1126                RecordKey::from(key.to_vec())
1127            }
1128        } else {
1129            RecordKey::from(key.to_vec())
1130        };
1131
1132        self.connexa
1133            .dht()
1134            .put(key, value, quorum)
1135            .await
1136            .map_err(Into::into)
1137    }
1138
1139    /// Add static relay peer
1140    pub async fn add_static_relay(&self, peer_id: PeerId, addr: Multiaddr) -> Result<bool, Error> {
1141        self.connexa
1142            .relay()
1143            .add_static_relay(peer_id, addr)
1144            .await
1145            .map_err(Into::into)
1146    }
1147
1148    /// Remove static relay peer
1149    pub async fn remove_static_relay(&self, peer_id: PeerId) -> Result<bool, Error> {
1150        self.connexa
1151            .relay()
1152            .remove_static_relay(peer_id)
1153            .await
1154            .map_err(Into::into)
1155    }
1156
1157    /// List all static relays.
1158    pub async fn list_static_relays(&self) -> Result<Vec<(PeerId, Vec<Multiaddr>)>, Error> {
1159        self.connexa
1160            .relay()
1161            .list_static_relays()
1162            .await
1163            .map_err(Into::into)
1164    }
1165
1166    /// Enables autorelay
1167    pub async fn enable_autorelay(&self) -> Result<(), Error> {
1168        self.connexa
1169            .relay()
1170            .enable_auto_relay()
1171            .await
1172            .map_err(Into::into)
1173    }
1174
1175    /// Disables autorelay
1176    pub async fn disable_autorelay(&self) -> Result<(), Error> {
1177        self.connexa
1178            .relay()
1179            .disable_auto_relay()
1180            .await
1181            .map_err(Into::into)
1182    }
1183
1184    pub async fn rendezvous_register_namespace(
1185        &self,
1186        namespace: impl IntoNamespace,
1187        ttl: impl Into<Option<u64>>,
1188        peer_id: PeerId,
1189    ) -> Result<(), Error> {
1190        self.connexa
1191            .rendezvous()
1192            .register(peer_id, namespace, ttl.into())
1193            .await
1194            .map_err(Into::into)
1195    }
1196
1197    pub async fn rendezvous_unregister_namespace(
1198        &self,
1199        namespace: impl IntoNamespace,
1200        peer_id: PeerId,
1201    ) -> Result<(), Error> {
1202        self.connexa
1203            .rendezvous()
1204            .unregister(peer_id, namespace)
1205            .await
1206            .map_err(Into::into)
1207    }
1208
1209    pub async fn rendezvous_namespace_discovery(
1210        &self,
1211        namespace: impl IntoNamespace,
1212        ttl: impl Into<Option<u64>>,
1213        peer_id: PeerId,
1214    ) -> Result<HashMap<PeerId, Vec<Multiaddr>>, Error> {
1215        self.connexa
1216            .rendezvous()
1217            .discovery(peer_id, namespace, ttl.into(), None)
1218            .await
1219            .map(|(_, list)| HashMap::from_iter(list))
1220            .map_err(anyhow::Error::from)
1221    }
1222
1223    /// Walk the given Iplds' links up to `max_depth` (or indefinitely for `None`). Will return
1224    /// any duplicate trees unless `unique` is `true`.
1225    ///
1226    /// More information and a `'static` lifetime version available at [`refs::iplds_refs`].
1227    pub fn refs<'a, Iter>(
1228        &'a self,
1229        iplds: Iter,
1230        max_depth: Option<u64>,
1231        unique: bool,
1232    ) -> impl futures::Stream<Item = Result<refs::Edge, anyhow::Error>> + Send + 'a
1233    where
1234        Iter: IntoIterator<Item = (Cid, Ipld)> + Send + 'a,
1235    {
1236        refs::iplds_refs(self.repo(), iplds, max_depth, unique)
1237    }
1238
1239    /// Obtain the list of addresses of bootstrapper nodes that are currently used.
1240    pub async fn get_bootstraps(&self) -> Result<Vec<Multiaddr>, Error> {
1241        async move {
1242            let (tx, rx) = oneshot_channel();
1243
1244            self.connexa
1245                .send_custom_event(IpfsEvent::GetBootstrappers(tx))
1246                .await?;
1247
1248            Ok(rx.await?)
1249        }
1250        .instrument(self.span.clone())
1251        .await
1252    }
1253
1254    /// Extend the list of used bootstrapper nodes with an additional address.
1255    /// Return value cannot be used to determine if the `addr` was a new bootstrapper, subject to
1256    /// change.
1257    pub async fn add_bootstrap(&self, addr: Multiaddr) -> Result<Multiaddr, Error> {
1258        async move {
1259            let (tx, rx) = oneshot_channel();
1260
1261            self.connexa
1262                .send_custom_event(IpfsEvent::AddBootstrapper(addr, tx))
1263                .await?;
1264
1265            rx.await?
1266        }
1267        .instrument(self.span.clone())
1268        .await
1269    }
1270
1271    /// Remove an address from the currently used list of bootstrapper nodes.
1272    /// Return value cannot be used to determine if the `addr` was an actual bootstrapper, subject to
1273    /// change.
1274    pub async fn remove_bootstrap(&self, addr: Multiaddr) -> Result<Multiaddr, Error> {
1275        async move {
1276            let (tx, rx) = oneshot_channel();
1277
1278            self.connexa
1279                .send_custom_event(IpfsEvent::RemoveBootstrapper(addr, tx))
1280                .await?;
1281
1282            rx.await?
1283        }
1284        .instrument(self.span.clone())
1285        .await
1286    }
1287
1288    /// Clear the currently used list of bootstrapper nodes, returning the removed addresses.
1289    pub async fn clear_bootstrap(&self) -> Result<Vec<Multiaddr>, Error> {
1290        async move {
1291            let (tx, rx) = oneshot_channel();
1292
1293            self.connexa
1294                .send_custom_event(IpfsEvent::ClearBootstrappers(tx))
1295                .await?;
1296
1297            rx.await?
1298        }
1299        .instrument(self.span.clone())
1300        .await
1301    }
1302
1303    /// Restore the originally configured bootstrapper node list by adding them to the list of the
1304    /// currently used bootstrapper node address list; returns the restored addresses.
1305    pub async fn default_bootstrap(&self) -> Result<Vec<Multiaddr>, Error> {
1306        async move {
1307            let (tx, rx) = oneshot_channel();
1308
1309            self.connexa
1310                .send_custom_event(IpfsEvent::DefaultBootstrap(tx))
1311                .await?;
1312
1313            rx.await?
1314        }
1315        .instrument(self.span.clone())
1316        .await
1317    }
1318
1319    /// Bootstraps the local node to join the DHT: it looks up the node's own ID in the
1320    /// DHT and introduces it to the other nodes in it; at least one other node must be
1321    /// known in order for the process to succeed. Subsequently, additional queries are
1322    /// ran with random keys so that the buckets farther from the closest neighbor also
1323    /// get refreshed.
1324    pub async fn bootstrap(&self) -> Result<(), Error> {
1325        self.connexa.dht().bootstrap().await.map_err(Into::into)
1326    }
1327
1328    /// Add address of a peer to the address book
1329    pub async fn add_peer(&self, opt: impl IntoAddPeerOpt) -> Result<(), Error> {
1330        let opt: AddPeerOpt = opt.into_opt()?;
1331        if opt.addresses().is_empty() {
1332            anyhow::bail!("no address supplied");
1333        }
1334
1335        let (tx, rx) = oneshot::channel();
1336
1337        self.connexa
1338            .send_custom_event(IpfsEvent::AddPeer(opt, tx))
1339            .await?;
1340
1341        rx.await??;
1342        Ok(())
1343    }
1344
1345    /// Remove peer from the address book
1346    pub async fn remove_peer(&self, peer_id: PeerId) -> Result<bool, Error> {
1347        let (tx, rx) = oneshot::channel();
1348
1349        self.connexa
1350            .send_custom_event(IpfsEvent::RemovePeer(peer_id, None, tx))
1351            .await?;
1352
1353        rx.await.map_err(anyhow::Error::from)?
1354    }
1355
1356    /// Remove peer address from the address book
1357    pub async fn remove_peer_address(
1358        &self,
1359        peer_id: PeerId,
1360        addr: Multiaddr,
1361    ) -> Result<bool, Error> {
1362        let (tx, rx) = oneshot::channel();
1363
1364        self.connexa
1365            .send_custom_event(IpfsEvent::RemovePeer(peer_id, Some(addr), tx))
1366            .await?;
1367
1368        rx.await.map_err(anyhow::Error::from)?
1369    }
1370
1371    /// Returns the Bitswap peers for the `Node`.
1372    pub async fn get_bitswap_peers(&self) -> Result<Vec<PeerId>, Error> {
1373        let (tx, rx) = oneshot_channel();
1374
1375        self.connexa
1376            .send_custom_event(IpfsEvent::GetBitswapPeers(tx))
1377            .await?;
1378
1379        Ok(rx.await??.await)
1380    }
1381
1382    pub async fn bitswap_stats(&self) -> Result<crate::p2p::bitswap::BitswapStats, Error> {
1383        let (tx, rx) = oneshot_channel();
1384
1385        self.connexa
1386            .send_custom_event(IpfsEvent::BitswapStats(tx))
1387            .await?;
1388
1389        Ok(rx.await??.await)
1390    }
1391
1392    /// Returns the keypair to the node
1393    pub fn keypair(&self) -> &Keypair {
1394        self.connexa.keypair()
1395    }
1396
1397    /// Returns the keychain
1398    pub fn keychain(&self) -> &Keychain<DefaultKeystore> {
1399        &self.connexa.keychain()
1400    }
1401
1402    /// Exit daemon.
1403    pub async fn exit_daemon(self) {
1404        // FIXME: this is a stopgap measure needed while repo is part of the struct Ipfs instead of
1405        // the background task or stream. After that this could be handled by dropping.
1406        self.repo.shutdown();
1407
1408        self.connexa.shutdown();
1409
1410        // terminate task that handles GC
1411        self._gc_guard.abort();
1412        // give back to the runtime to process anything in the background
1413        async_rt::task::yield_now().await;
1414    }
1415}
1416
1417#[derive(Debug)]
1418pub struct AddPeerOpt {
1419    peer_id: PeerId,
1420    addresses: Vec<Multiaddr>,
1421    condition: Option<PeerCondition>,
1422    dial: bool,
1423    keepalive: bool,
1424    reconnect: Option<(Duration, u8)>,
1425}
1426
1427impl AddPeerOpt {
1428    pub fn with_peer_id(peer_id: PeerId) -> Self {
1429        Self {
1430            peer_id,
1431            addresses: vec![],
1432            condition: None,
1433            dial: false,
1434            keepalive: false,
1435            reconnect: None,
1436        }
1437    }
1438
1439    pub fn add_address(mut self, mut addr: Multiaddr) -> Self {
1440        if addr.is_empty() {
1441            return self;
1442        }
1443
1444        match addr.iter().last() {
1445            // if the address contains a peerid, we should confirm it matches the initial peer
1446            Some(Protocol::P2p(peer_id)) if peer_id == self.peer_id => {
1447                addr.pop();
1448            }
1449            Some(Protocol::P2p(_)) => return self,
1450            _ => {}
1451        }
1452
1453        if !self.addresses.contains(&addr) {
1454            self.addresses.push(addr);
1455        }
1456
1457        self
1458    }
1459
1460    pub fn set_addresses(mut self, addrs: Vec<Multiaddr>) -> Self {
1461        for addr in addrs {
1462            self = self.add_address(addr);
1463        }
1464
1465        self
1466    }
1467
1468    pub fn set_peer_condition(mut self, condition: PeerCondition) -> Self {
1469        self.condition = Some(condition);
1470        self
1471    }
1472
1473    pub fn set_dial(mut self, dial: bool) -> Self {
1474        self.dial = dial;
1475        self
1476    }
1477
1478    pub fn set_reconnect(mut self, reconnect: impl Into<Option<(Duration, u8)>>) -> Self {
1479        self.reconnect = reconnect.into();
1480        self
1481    }
1482
1483    pub fn reconnect(mut self, duration: Duration, interval: u8) -> Self {
1484        self.reconnect = Some((duration, interval));
1485        self
1486    }
1487
1488    pub fn keepalive(mut self) -> Self {
1489        self.keepalive = true;
1490        self
1491    }
1492
1493    pub fn set_keepalive(mut self, keepalive: bool) -> Self {
1494        self.keepalive = keepalive;
1495        self
1496    }
1497}
1498
1499impl AddPeerOpt {
1500    pub fn peer_id(&self) -> &PeerId {
1501        &self.peer_id
1502    }
1503
1504    pub fn addresses(&self) -> &[Multiaddr] {
1505        &self.addresses
1506    }
1507
1508    pub fn can_keep_alive(&self) -> bool {
1509        self.keepalive
1510    }
1511
1512    pub fn reconnect_opt(&self) -> Option<(Duration, u8)> {
1513        self.reconnect
1514    }
1515
1516    pub fn to_dial_opts(&self) -> Option<DialOpts> {
1517        if !self.dial {
1518            return None;
1519        }
1520
1521        // We dial without addresses attached because it will they will be fetched within the address book
1522        // which will allow us not only to use those addresses but any addresses from other behaviours
1523        let opts = DialOpts::peer_id(self.peer_id)
1524            .condition(self.condition.unwrap_or_default())
1525            .build();
1526
1527        Some(opts)
1528    }
1529}
1530
1531pub trait IntoAddPeerOpt {
1532    fn into_opt(self) -> Result<AddPeerOpt, anyhow::Error>;
1533}
1534
1535impl IntoAddPeerOpt for AddPeerOpt {
1536    fn into_opt(self) -> Result<AddPeerOpt, anyhow::Error> {
1537        Ok(self)
1538    }
1539}
1540
1541impl IntoAddPeerOpt for (PeerId, Multiaddr) {
1542    fn into_opt(self) -> Result<AddPeerOpt, anyhow::Error> {
1543        let (peer_id, addr) = self;
1544        Ok(AddPeerOpt::with_peer_id(peer_id).add_address(addr))
1545    }
1546}
1547
1548impl IntoAddPeerOpt for (PeerId, Vec<Multiaddr>) {
1549    fn into_opt(self) -> Result<AddPeerOpt, anyhow::Error> {
1550        let (peer_id, addrs) = self;
1551        Ok(AddPeerOpt::with_peer_id(peer_id).set_addresses(addrs))
1552    }
1553}
1554
1555impl IntoAddPeerOpt for Multiaddr {
1556    fn into_opt(mut self) -> Result<AddPeerOpt, anyhow::Error> {
1557        let peer_id = self
1558            .extract_peer_id()
1559            .ok_or(anyhow::anyhow!("address does not contain peer id"))
1560            .map_err(std::io::Error::other)?;
1561        Ok(AddPeerOpt::with_peer_id(peer_id).add_address(self))
1562    }
1563}
1564
1565#[inline]
1566pub(crate) fn split_dht_key(key: &str) -> anyhow::Result<(&str, &str)> {
1567    anyhow::ensure!(!key.is_empty(), "Key cannot be empty");
1568
1569    let (key, val) = {
1570        let data = key
1571            .split('/')
1572            .filter(|s| !s.trim().is_empty())
1573            .collect::<Vec<_>>();
1574
1575        anyhow::ensure!(
1576            !data.is_empty() && data.len() == 2,
1577            "split dats cannot be empty"
1578        );
1579
1580        (data[0], data[1])
1581    };
1582
1583    Ok((key, val))
1584}
1585
1586#[inline]
1587pub(crate) fn ipns_to_dht_key<B: AsRef<str>>(key: B) -> anyhow::Result<RecordKey> {
1588    let default_ipns_prefix = b"/ipns/";
1589
1590    let mut key = key.as_ref().trim().to_string();
1591
1592    anyhow::ensure!(!key.is_empty(), "Key cannot be empty");
1593
1594    if key.starts_with('1') || key.starts_with('Q') {
1595        key.insert(0, 'z');
1596    }
1597
1598    let mut data = multibase::decode(key).map(|(_, data)| data)?;
1599
1600    if data[0] != 0x01 && data[1] != 0x72 {
1601        data = [vec![0x01, 0x72], data].concat();
1602    }
1603
1604    data = [default_ipns_prefix.to_vec(), data[2..].to_vec()].concat();
1605
1606    Ok(data.into())
1607}
1608
1609#[inline]
1610pub(crate) fn to_dht_key<B: AsRef<str>, F: Fn(&str) -> anyhow::Result<RecordKey>>(
1611    (prefix, func): (&str, F),
1612    key: B,
1613) -> anyhow::Result<RecordKey> {
1614    let key = key.as_ref().trim();
1615
1616    let (key, val) = split_dht_key(key)?;
1617
1618    anyhow::ensure!(!key.is_empty(), "Key cannot be empty");
1619    anyhow::ensure!(!val.is_empty(), "Value cannot be empty");
1620
1621    if key == prefix {
1622        return func(val);
1623    }
1624
1625    anyhow::bail!("Invalid prefix")
1626}
1627
1628use crate::p2p::AddressBookConfig;
1629use crate::repo::{DefaultKeystore, RepoGetBlock, RepoPutBlock};
1630#[cfg(all(feature = "full", not(target_arch = "wasm32")))]
1631#[doc(hidden)]
1632pub use node::Node;
1633
1634/// Node module provides an easy to use interface used in `tests/`.
1635#[cfg(all(feature = "full", not(target_arch = "wasm32")))]
1636mod node {
1637    use super::*;
1638    use crate::builder::DefaultIpfsBuilder;
1639
1640    /// Node encapsulates everything to setup a testing instance so that multi-node tests become
1641    /// easier.
1642    pub struct Node {
1643        /// The Ipfs facade.
1644        pub ipfs: Ipfs,
1645        /// The peer identifier on the network.
1646        pub id: PeerId,
1647        /// The listened to and externally visible addresses. The addresses are suffixed with the
1648        /// P2p protocol containing the node's PeerID.
1649        pub addrs: Vec<Multiaddr>,
1650    }
1651
1652    impl IntoAddPeerOpt for &Node {
1653        fn into_opt(self) -> Result<AddPeerOpt, anyhow::Error> {
1654            Ok(AddPeerOpt::with_peer_id(self.id).set_addresses(self.addrs.clone()))
1655        }
1656    }
1657
1658    impl Node {
1659        /// Initialises a new `Node` with an in-memory store backed configuration.
1660        ///
1661        /// This will use the testing defaults for the `IpfsOptions`. If `IpfsOptions` has been
1662        /// initialised manually, use `Node::with_options` instead.
1663        pub async fn new<T: AsRef<str>>(name: T) -> Self {
1664            Self::with_options(Some(trace_span!("ipfs", node = name.as_ref())), None).await
1665        }
1666
1667        /// Connects to a peer at the given address.
1668        pub async fn connect(&self, opt: impl Into<DialOpts>) -> Result<(), Error> {
1669            let opts = opt.into();
1670            if let Some(peer_id) = opts.get_peer_id() {
1671                if self.ipfs.is_connected(peer_id).await? {
1672                    return Ok(());
1673                }
1674            }
1675            self.ipfs.connect(opts).await.map(|_| ())
1676        }
1677
1678        /// Returns a new `Node` based on `IpfsOptions`.
1679        pub async fn with_options(span: Option<Span>, addr: Option<Vec<Multiaddr>>) -> Self {
1680            // for future: assume UninitializedIpfs handles instrumenting any futures with the
1681            // given span
1682            let mut uninit = DefaultIpfsBuilder::new()
1683                .with_default()
1684                .enable_tcp()
1685                .enable_memory_transport()
1686                .with_request_response(Default::default());
1687
1688            if let Some(span) = span {
1689                uninit = uninit.set_span(span);
1690            }
1691
1692            let list = addr.unwrap_or_else(|| vec![Multiaddr::empty().with(Protocol::Memory(0))]);
1693
1694            let ipfs = uninit.start().await.unwrap();
1695
1696            ipfs.dht_mode(DhtMode::Server).await.unwrap();
1697
1698            let id = ipfs.keypair().public().to_peer_id();
1699            for addr in list {
1700                ipfs.add_listening_address(addr).await.expect("To succeed");
1701            }
1702
1703            let mut addrs = ipfs.listening_addresses().await.unwrap();
1704
1705            for addr in &mut addrs {
1706                if let Some(proto) = addr.iter().last() {
1707                    if !matches!(proto, Protocol::P2p(_)) {
1708                        addr.push(Protocol::P2p(id));
1709                    }
1710                }
1711            }
1712
1713            Node { ipfs, id, addrs }
1714        }
1715
1716        /// Returns the subscriptions for a `Node`.
1717        #[allow(clippy::type_complexity)]
1718        pub fn get_subscriptions(
1719            &self,
1720        ) -> &parking_lot::Mutex<HashMap<Cid, HashMap<u64, oneshot::Sender<Result<Block, String>>>>>
1721        {
1722            &self.ipfs.repo.inner.subscriptions
1723        }
1724
1725        /// Bootstraps the local node to join the DHT: it looks up the node's own ID in the
1726        /// DHT and introduces it to the other nodes in it; at least one other node must be
1727        /// known in order for the process to succeed. Subsequently, additional queries are
1728        /// ran with random keys so that the buckets farther from the closest neighbor also
1729        /// get refreshed.
1730        pub async fn bootstrap(&self) -> Result<(), Error> {
1731            self.ipfs.bootstrap().await
1732        }
1733
1734        pub async fn add_node(&self, node: &Self) -> Result<(), Error> {
1735            for addr in &node.addrs {
1736                self.add_peer((node.id, addr.to_owned())).await?;
1737            }
1738
1739            Ok(())
1740        }
1741
1742        /// Shuts down the `Node`.
1743        pub async fn shutdown(self) {
1744            self.ipfs.exit_daemon().await;
1745        }
1746    }
1747
1748    impl std::ops::Deref for Node {
1749        type Target = Ipfs;
1750
1751        fn deref(&self) -> &Self::Target {
1752            &self.ipfs
1753        }
1754    }
1755
1756    impl std::ops::DerefMut for Node {
1757        fn deref_mut(&mut self) -> &mut Self::Target {
1758            &mut self.ipfs
1759        }
1760    }
1761}
1762
1763#[cfg(test)]
1764mod tests {
1765    use super::*;
1766
1767    use crate::block::BlockCodec;
1768    use ipld_core::ipld;
1769    use multihash_codetable::Code;
1770    use multihash_derive::MultihashDigest;
1771
1772    #[tokio::test]
1773    async fn test_put_and_get_block() {
1774        let ipfs = Node::new("test_node").await;
1775
1776        let data = b"hello block\n".to_vec();
1777        let cid = Cid::new_v1(BlockCodec::Raw.into(), Code::Sha2_256.digest(&data));
1778        let block = Block::new(cid, data).unwrap();
1779
1780        let cid: Cid = ipfs.put_block(&block).await.unwrap();
1781        let new_block = ipfs.get_block(cid).await.unwrap();
1782        assert_eq!(block, new_block);
1783    }
1784
1785    #[tokio::test]
1786    async fn test_put_and_get_dag() {
1787        let ipfs = Node::new("test_node").await;
1788
1789        let data = ipld!([-1, -2, -3]);
1790        let cid = ipfs.put_dag(data.clone()).await.unwrap();
1791        let new_data = ipfs.get_dag(cid).await.unwrap();
1792        assert_eq!(data, new_data);
1793    }
1794
1795    #[tokio::test]
1796    async fn test_pin_and_unpin() {
1797        let ipfs = Node::new("test_node").await;
1798
1799        let data = ipld!([-1, -2, -3]);
1800        let cid = ipfs.put_dag(data.clone()).pin(false).await.unwrap();
1801
1802        assert!(ipfs.is_pinned(cid).await.unwrap());
1803        ipfs.remove_pin(cid).await.unwrap();
1804        assert!(!ipfs.is_pinned(cid).await.unwrap());
1805    }
1806}