1#[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
113struct IpfsOptions {
115 pub ipfs_path: Option<PathBuf>,
125
126 #[cfg(target_arch = "wasm32")]
128 pub namespace: Option<Option<String>>,
129
130 pub bootstrap: Vec<Multiaddr>,
132
133 pub listening_addrs: Vec<Multiaddr>,
135
136 pub bitswap_config: Box<dyn Fn(p2p::bitswap::Config) -> p2p::bitswap::Config>,
137
138 pub addr_config: AddressBookConfig,
140
141 pub provider: RepoProvider,
143
144 #[cfg(feature = "gateway")]
146 pub gateway: Option<Vec<String>>,
147
148 #[cfg(feature = "routing")]
150 pub router: Option<Vec<String>>,
151
152 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 #[default]
172 None,
173
174 All,
176
177 Pinned,
179
180 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 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#[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#[derive(Debug)]
256#[allow(clippy::type_complexity)]
257enum IpfsEvent {
258 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 Subscribe {
296 peer_id: PeerId,
297 topic: Option<String>,
298 },
299
300 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 pub fn dag(&self) -> IpldDag {
334 IpldDag::new(self.clone())
335 }
336
337 pub fn repo(&self) -> &Repo<DefaultStorage> {
339 &self.repo
340 }
341
342 pub fn unixfs(&self) -> IpfsUnixfs {
344 IpfsUnixfs::new(self.clone())
345 }
346
347 pub fn mfs(&self) -> crate::mfs::Mfs {
349 crate::mfs::Mfs::new(self.repo.clone())
350 }
351
352 pub fn ipns(&self) -> Ipns {
354 Ipns::new(self.clone())
355 }
356
357 #[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 #[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 #[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 #[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 #[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 #[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 #[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 pub fn put_block(&self, block: &Block) -> RepoPutBlock<DefaultStorage> {
405 self.repo.put_block(block).span(self.span.clone())
406 }
407
408 pub fn get_block(&self, cid: impl Borrow<Cid>) -> RepoGetBlock<DefaultStorage> {
411 self.repo.get_block(cid).span(self.span.clone())
412 }
413
414 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 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 pub fn insert_pin(&self, cid: impl Borrow<Cid>) -> RepoInsertPin<DefaultStorage> {
453 self.repo().pin(cid).span(self.span.clone())
454 }
455
456 pub fn remove_pin(&self, cid: impl Borrow<Cid>) -> RepoRemovePin<DefaultStorage> {
463 self.repo().remove_pin(cid).span(self.span.clone())
464 }
465
466 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 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 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 pub fn put_dag(&self, ipld: impl Serialize) -> DagPut {
519 self.dag().put_dag(ipld).span(self.span.clone())
520 }
521
522 pub fn get_dag(&self, path: impl Into<IpfsPath>) -> DagGet {
526 self.dag().get_dag(path).span(self.span.clone())
527 }
528
529 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 pub fn add_unixfs(&self, opt: impl Into<AddOpt>) -> UnixfsAdd {
538 self.unixfs().add(opt).span(self.span.clone())
539 }
540
541 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 pub fn ls_unixfs(&self, path: impl Into<IpfsPath>) -> UnixfsLs {
548 self.unixfs().ls(path).span(self.span.clone())
549 }
550
551 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 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 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 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 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 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 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 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 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 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(), agent_version: String::new(), 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 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 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 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 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 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 pub async fn pubsub_subscribed(&self) -> Result<Vec<String>, Error> {
778 unimplemented!()
780 }
781
782 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 pub async fn provide(&self, cid: Cid) -> Result<(), Error> {
1048 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 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 pub fn fetch(&self, cid: &Cid) -> RepoFetch<DefaultStorage> {
1069 self.repo.fetch(cid).span(self.span.clone())
1070 }
1071
1072 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 pub async fn bootstrap(&self) -> Result<(), Error> {
1325 self.connexa.dht().bootstrap().await.map_err(Into::into)
1326 }
1327
1328 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 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 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 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 pub fn keypair(&self) -> &Keypair {
1394 self.connexa.keypair()
1395 }
1396
1397 pub fn keychain(&self) -> &Keychain<DefaultKeystore> {
1399 &self.connexa.keychain()
1400 }
1401
1402 pub async fn exit_daemon(self) {
1404 self.repo.shutdown();
1407
1408 self.connexa.shutdown();
1409
1410 self._gc_guard.abort();
1412 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 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 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#[cfg(all(feature = "full", not(target_arch = "wasm32")))]
1636mod node {
1637 use super::*;
1638 use crate::builder::DefaultIpfsBuilder;
1639
1640 pub struct Node {
1643 pub ipfs: Ipfs,
1645 pub id: PeerId,
1647 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 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 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 pub async fn with_options(span: Option<Span>, addr: Option<Vec<Multiaddr>>) -> Self {
1680 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 #[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 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 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}