From 1782e76bf17ddbb2eb09da7050381202d49cd7b1 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Fri, 19 Jul 2024 20:48:43 +0800 Subject: [PATCH] feat(rpc): implement Filecoin.NetFindPeer (#4569) --- CHANGELOG.md | 3 + scripts/tests/api_compare/filter-list | 2 - scripts/tests/api_compare/filter-list-offline | 1 + src/libp2p/service.rs | 62 +++---- src/rpc/error.rs | 1 + src/rpc/methods/net.rs | 170 ++++++------------ src/rpc/methods/net/types.rs | 91 ++++++++++ src/rpc/mod.rs | 1 + src/tool/subcommands/api_cmd.rs | 19 +- src/utils/flume/mod.rs | 14 ++ src/utils/mod.rs | 2 + src/utils/p2p/mod.rs | 44 +++++ 12 files changed, 247 insertions(+), 163 deletions(-) create mode 100644 src/rpc/methods/net/types.rs create mode 100644 src/utils/flume/mod.rs create mode 100644 src/utils/p2p/mod.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index b625804ee7b..2e535fd5290 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,9 @@ - [#4474](https://github.com/ChainSafe/forest/pull/4474) Add new subcommand `forest-cli healthcheck ready`. +- [#4569](https://github.com/ChainSafe/forest/pull/4569) Add support for the + `Filecoin.NetFindPeer` RPC method. + - [#4565](https://github.com/ChainSafe/forest/pull/4565) Add support for the `Filecoin.StateGetRandomnessDigestFromBeacon` RPC method. diff --git a/scripts/tests/api_compare/filter-list b/scripts/tests/api_compare/filter-list index c2afaf9aaa5..22c0d94ad0a 100644 --- a/scripts/tests/api_compare/filter-list +++ b/scripts/tests/api_compare/filter-list @@ -7,7 +7,5 @@ !Filecoin.StateReplay # CustomCheckFailed in Forest: https://github.com/ChainSafe/forest/actions/runs/9593268587/job/26453560366 !Filecoin.StateCall -# Rejected("item not found") in Forest: https://github.com/ChainSafe/forest/actions/runs/9581179496/job/26417600610 -!Filecoin.NetAgentVersion # CustomCheckFailed in Forest: https://github.com/ChainSafe/forest/issues/4446 !Filecoin.StateCirculatingSupply diff --git a/scripts/tests/api_compare/filter-list-offline b/scripts/tests/api_compare/filter-list-offline index 11425202270..eb6a5f16304 100644 --- a/scripts/tests/api_compare/filter-list-offline +++ b/scripts/tests/api_compare/filter-list-offline @@ -8,6 +8,7 @@ !Filecoin.NetAgentVersion !Filecoin.NetAutoNatStatus !Filecoin.NetPeers +!Filecoin.NetFindPeer # CustomCheckFailed in Forest: https://github.com/ChainSafe/forest/actions/runs/9593268587/job/26453560366 !Filecoin.StateReplay # CustomCheckFailed in Forest: https://github.com/ChainSafe/forest/actions/runs/9593268587/job/26453560366 diff --git a/src/libp2p/service.rs b/src/libp2p/service.rs index 0f2dca11e44..4710acecc37 100644 --- a/src/libp2p/service.rs +++ b/src/libp2p/service.rs @@ -6,17 +6,20 @@ use std::{ time::{Duration, SystemTime, UNIX_EPOCH}, }; -use crate::libp2p_bitswap::{ - request_manager::{BitswapRequestManager, ValidatePeerCallback}, - BitswapStoreRead, BitswapStoreReadWrite, -}; use crate::message::SignedMessage; use crate::{blocks::GossipBlock, rpc::net::NetInfoResult}; use crate::{chain::ChainStore, utils::encoding::from_slice_with_fallback}; +use crate::{ + libp2p_bitswap::{ + request_manager::{BitswapRequestManager, ValidatePeerCallback}, + BitswapStoreRead, BitswapStoreReadWrite, + }, + utils::flume::FlumeSenderExt as _, +}; use ahash::{HashMap, HashSet}; use cid::Cid; use flume::Sender; -use futures::{channel::oneshot, select, stream::StreamExt as _}; +use futures::{select, stream::StreamExt as _}; use fvm_ipld_blockstore::Blockstore; pub use libp2p::gossipsub::{IdentTopic, Topic}; use libp2p::{ @@ -145,13 +148,14 @@ pub enum NetworkMessage { /// Network RPC API methods used to gather data from libp2p node. #[derive(Debug)] pub enum NetRPCMethods { - AddrsListen(oneshot::Sender<(PeerId, HashSet)>), - Peers(oneshot::Sender>>), - Info(oneshot::Sender), - Connect(oneshot::Sender, PeerId, HashSet), - Disconnect(oneshot::Sender<()>, PeerId), - AgentVersion(oneshot::Sender>, PeerId), - AutoNATStatus(oneshot::Sender), + AddrsListen(flume::Sender<(PeerId, HashSet)>), + Peer(flume::Sender>>, PeerId), + Peers(flume::Sender>>), + Info(flume::Sender), + Connect(flume::Sender, PeerId, HashSet), + Disconnect(flume::Sender<()>, PeerId), + AgentVersion(flume::Sender>, PeerId), + AutoNATStatus(flume::Sender), } /// The `Libp2pService` listens to events from the libp2p swarm. @@ -479,25 +483,21 @@ async fn handle_network_message( NetRPCMethods::AddrsListen(response_channel) => { let listeners = Swarm::listeners(swarm).cloned().collect(); let peer_id = Swarm::local_peer_id(swarm); - - if response_channel.send((*peer_id, listeners)).is_err() { - warn!("Failed to get Libp2p listeners"); - } + response_channel.send_or_warn((*peer_id, listeners)); + } + NetRPCMethods::Peer(response_channel, peer) => { + let addresses = swarm.behaviour().peer_addresses().get(&peer).cloned(); + response_channel.send_or_warn(addresses); } NetRPCMethods::Peers(response_channel) => { let peer_addresses = swarm.behaviour().peer_addresses(); - if response_channel.send(peer_addresses).is_err() { - warn!("Failed to get Libp2p peers"); - } + response_channel.send_or_warn(peer_addresses); } NetRPCMethods::Info(response_channel) => { - if response_channel.send(swarm.network_info().into()).is_err() { - warn!("Failed to get Libp2p peers"); - } + response_channel.send_or_warn(swarm.network_info().into()); } NetRPCMethods::Connect(response_channel, peer_id, addresses) => { let mut success = false; - for mut multiaddr in addresses { multiaddr.push(Protocol::P2p(peer_id)); @@ -525,15 +525,11 @@ async fn handle_network_message( }; } - if response_channel.send(success).is_err() { - warn!("Failed to connect to a peer"); - } + response_channel.send_or_warn(success); } NetRPCMethods::Disconnect(response_channel, peer_id) => { let _ = Swarm::disconnect_peer_id(swarm, peer_id); - if response_channel.send(()).is_err() { - warn!("Failed to disconnect from a peer"); - } + response_channel.send_or_warn(()); } NetRPCMethods::AgentVersion(response_channel, peer_id) => { let agent_version = swarm.behaviour().peer_info(&peer_id).and_then(|info| { @@ -541,15 +537,11 @@ async fn handle_network_message( .as_ref() .map(|id| id.agent_version.clone()) }); - if response_channel.send(agent_version).is_err() { - warn!("Failed to get agent version"); - } + response_channel.send_or_warn(agent_version); } NetRPCMethods::AutoNATStatus(response_channel) => { let nat_status = swarm.behaviour().discovery.nat_status(); - if response_channel.send(nat_status).is_err() { - warn!("Failed to get nat status"); - } + response_channel.send_or_warn(nat_status); } } } diff --git a/src/rpc/error.rs b/src/rpc/error.rs index 9e38d41b5dc..7a168599dea 100644 --- a/src/rpc/error.rs +++ b/src/rpc/error.rs @@ -98,6 +98,7 @@ from2internal! { std::time::SystemTimeError, tokio::task::JoinError, fil_actors_shared::fvm_ipld_hamt::Error, + flume::RecvError, } impl From for ClientError { diff --git a/src/rpc/methods/net.rs b/src/rpc/methods/net.rs index d3c8f1c83a1..a3253ea1761 100644 --- a/src/rpc/methods/net.rs +++ b/src/rpc/methods/net.rs @@ -1,20 +1,17 @@ // Copyright 2019-2024 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT +mod types; +pub use types::*; + use std::any::Any; use std::str::FromStr; use crate::libp2p::{NetRPCMethods, NetworkMessage, PeerId}; -use crate::lotus_json::lotus_json_with_self; -use crate::rpc::{ApiPaths, Permission, ServerError}; -use crate::rpc::{Ctx, RpcMethod}; -use anyhow::Result; +use crate::rpc::{ApiPaths, Ctx, Permission, RpcMethod, ServerError}; +use anyhow::{Context as _, Result}; use cid::multibase; -use futures::channel::oneshot; use fvm_ipld_blockstore::Blockstore; -use libp2p::Multiaddr; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; pub enum NetAddrsListen {} impl RpcMethod<0> for NetAddrsListen { @@ -27,18 +24,15 @@ impl RpcMethod<0> for NetAddrsListen { type Ok = AddrInfo; async fn handle(ctx: Ctx, (): Self::Params) -> Result { - let (tx, rx) = oneshot::channel(); + let (tx, rx) = flume::bounded(1); let req = NetworkMessage::JSONRPCRequest { method: NetRPCMethods::AddrsListen(tx), }; ctx.network_send.send_async(req).await?; - let (id, addrs) = rx.await?; + let (id, addrs) = rx.recv_async().await?; - Ok(AddrInfo { - id: id.to_string(), - addrs, - }) + Ok(AddrInfo::new(id, addrs)) } } @@ -53,26 +47,52 @@ impl RpcMethod<0> for NetPeers { type Ok = Vec; async fn handle(ctx: Ctx, (): Self::Params) -> Result { - let (tx, rx) = oneshot::channel(); + let (tx, rx) = flume::bounded(1); let req = NetworkMessage::JSONRPCRequest { method: NetRPCMethods::Peers(tx), }; ctx.network_send.send_async(req).await?; - let peer_addresses = rx.await?; + let peer_addresses = rx.recv_async().await?; let connections = peer_addresses .into_iter() - .map(|(id, addrs)| AddrInfo { - id: id.to_string(), - addrs, - }) + .map(|(id, addrs)| AddrInfo::new(id, addrs)) .collect(); Ok(connections) } } +pub enum NetFindPeer {} +impl RpcMethod<1> for NetFindPeer { + const NAME: &'static str = "Filecoin.NetFindPeer"; + const PARAM_NAMES: [&'static str; 1] = ["peer_id"]; + const API_PATHS: ApiPaths = ApiPaths::V0; + const PERMISSION: Permission = Permission::Read; + + type Params = (String,); + type Ok = AddrInfo; + + async fn handle( + ctx: Ctx, + (peer_id,): Self::Params, + ) -> Result { + let peer_id = PeerId::from_str(&peer_id)?; + let (tx, rx) = flume::bounded(1); + ctx.network_send + .send_async(NetworkMessage::JSONRPCRequest { + method: NetRPCMethods::Peer(tx, peer_id), + }) + .await?; + let addrs = rx + .recv_async() + .await? + .with_context(|| format!("peer {peer_id} not found"))?; + Ok(AddrInfo::new(peer_id, addrs)) + } +} + pub enum NetListening {} impl RpcMethod<0> for NetListening { const NAME: &'static str = "Filecoin.NetListening"; @@ -99,13 +119,13 @@ impl RpcMethod<0> for NetInfo { type Ok = NetInfoResult; async fn handle(ctx: Ctx, (): Self::Params) -> Result { - let (tx, rx) = oneshot::channel(); + let (tx, rx) = flume::bounded(1); let req = NetworkMessage::JSONRPCRequest { method: NetRPCMethods::Info(tx), }; ctx.network_send.send_async(req).await?; - Ok(rx.await?) + Ok(rx.recv_async().await?) } } @@ -126,13 +146,13 @@ impl RpcMethod<1> for NetConnect { let (_, id) = multibase::decode(format!("{}{}", "z", id))?; let peer_id = PeerId::from_bytes(&id)?; - let (tx, rx) = oneshot::channel(); + let (tx, rx) = flume::bounded(1); let req = NetworkMessage::JSONRPCRequest { method: NetRPCMethods::Connect(tx, peer_id, addrs), }; ctx.network_send.send_async(req).await?; - let success = rx.await?; + let success = rx.recv_async().await?; if success { Ok(()) @@ -158,13 +178,13 @@ impl RpcMethod<1> for NetDisconnect { ) -> Result { let peer_id = PeerId::from_str(&id)?; - let (tx, rx) = oneshot::channel(); + let (tx, rx) = flume::bounded(1); let req = NetworkMessage::JSONRPCRequest { method: NetRPCMethods::Disconnect(tx, peer_id), }; ctx.network_send.send_async(req).await?; - rx.await?; + rx.recv_async().await?; Ok(()) } @@ -185,18 +205,13 @@ impl RpcMethod<1> for NetAgentVersion { (id,): Self::Params, ) -> Result { let peer_id = PeerId::from_str(&id)?; - - let (tx, rx) = oneshot::channel(); - let req = NetworkMessage::JSONRPCRequest { - method: NetRPCMethods::AgentVersion(tx, peer_id), - }; - - ctx.network_send.send_async(req).await?; - if let Some(agent_version) = rx.await? { - Ok(agent_version) - } else { - Err(anyhow::anyhow!("item not found").into()) - } + let (tx, rx) = flume::bounded(1); + ctx.network_send + .send_async(NetworkMessage::JSONRPCRequest { + method: NetRPCMethods::AgentVersion(tx, peer_id), + }) + .await?; + Ok(rx.recv_async().await?.context("item not found")?) } } @@ -211,12 +226,12 @@ impl RpcMethod<0> for NetAutoNatStatus { type Ok = NatStatusResult; async fn handle(ctx: Ctx, (): Self::Params) -> Result { - let (tx, rx) = oneshot::channel(); + let (tx, rx) = flume::bounded(1); let req = NetworkMessage::JSONRPCRequest { method: NetRPCMethods::AutoNATStatus(tx), }; ctx.network_send.send_async(req).await?; - let nat_status = rx.await?; + let nat_status = rx.recv_async().await?; Ok(nat_status.into()) } } @@ -257,78 +272,3 @@ impl RpcMethod<1> for NetProtectAdd { Ok(()) } } - -// Net API -#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] -#[serde(rename_all = "PascalCase")] -pub struct AddrInfo { - #[serde(rename = "ID")] - pub id: String, - #[schemars(with = "ahash::HashSet")] - pub addrs: ahash::HashSet, -} - -lotus_json_with_self!(AddrInfo); - -#[derive(Debug, Default, Serialize, Deserialize, Clone, JsonSchema)] -pub struct NetInfoResult { - pub num_peers: usize, - pub num_connections: u32, - pub num_pending: u32, - pub num_pending_incoming: u32, - pub num_pending_outgoing: u32, - pub num_established: u32, -} -lotus_json_with_self!(NetInfoResult); - -impl From for NetInfoResult { - fn from(i: libp2p::swarm::NetworkInfo) -> Self { - let counters = i.connection_counters(); - Self { - num_peers: i.num_peers(), - num_connections: counters.num_connections(), - num_pending: counters.num_pending(), - num_pending_incoming: counters.num_pending_incoming(), - num_pending_outgoing: counters.num_pending_outgoing(), - num_established: counters.num_established(), - } - } -} - -#[derive(Debug, Default, Serialize, Deserialize, Clone, JsonSchema)] -#[serde(rename_all = "PascalCase")] -pub struct NatStatusResult { - pub reachability: i32, - pub public_addrs: Option>, -} -lotus_json_with_self!(NatStatusResult); - -impl NatStatusResult { - // See - pub fn reachability_as_str(&self) -> &'static str { - match self.reachability { - 0 => "Unknown", - 1 => "Public", - 2 => "Private", - _ => "(unrecognized)", - } - } -} - -impl From for NatStatusResult { - fn from(nat: libp2p::autonat::NatStatus) -> Self { - use libp2p::autonat::NatStatus; - - // See - let (reachability, public_addrs) = match &nat { - NatStatus::Unknown => (0, None), - NatStatus::Public(addr) => (1, Some(vec![addr.to_string()])), - NatStatus::Private => (2, None), - }; - - NatStatusResult { - reachability, - public_addrs, - } - } -} diff --git a/src/rpc/methods/net/types.rs b/src/rpc/methods/net/types.rs new file mode 100644 index 00000000000..1f1bd4ba50f --- /dev/null +++ b/src/rpc/methods/net/types.rs @@ -0,0 +1,91 @@ +// Copyright 2019-2024 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + +use crate::lotus_json::lotus_json_with_self; +use crate::utils::p2p::MultiaddrExt as _; +use libp2p::{Multiaddr, PeerId}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +// Net API +#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] +#[serde(rename_all = "PascalCase")] +pub struct AddrInfo { + #[serde(rename = "ID")] + pub id: String, + #[schemars(with = "ahash::HashSet")] + pub addrs: ahash::HashSet, +} +lotus_json_with_self!(AddrInfo); + +impl AddrInfo { + pub fn new(peer: PeerId, addrs: ahash::HashSet) -> Self { + Self { + id: peer.to_string(), + addrs: addrs.into_iter().map(|addr| addr.without_p2p()).collect(), + } + } +} + +#[derive(Debug, Default, Serialize, Deserialize, Clone, JsonSchema)] +pub struct NetInfoResult { + pub num_peers: usize, + pub num_connections: u32, + pub num_pending: u32, + pub num_pending_incoming: u32, + pub num_pending_outgoing: u32, + pub num_established: u32, +} +lotus_json_with_self!(NetInfoResult); + +impl From for NetInfoResult { + fn from(i: libp2p::swarm::NetworkInfo) -> Self { + let counters = i.connection_counters(); + Self { + num_peers: i.num_peers(), + num_connections: counters.num_connections(), + num_pending: counters.num_pending(), + num_pending_incoming: counters.num_pending_incoming(), + num_pending_outgoing: counters.num_pending_outgoing(), + num_established: counters.num_established(), + } + } +} + +#[derive(Debug, Default, Serialize, Deserialize, Clone, JsonSchema)] +#[serde(rename_all = "PascalCase")] +pub struct NatStatusResult { + pub reachability: i32, + pub public_addrs: Option>, +} +lotus_json_with_self!(NatStatusResult); + +impl NatStatusResult { + // See + pub fn reachability_as_str(&self) -> &'static str { + match self.reachability { + 0 => "Unknown", + 1 => "Public", + 2 => "Private", + _ => "(unrecognized)", + } + } +} + +impl From for NatStatusResult { + fn from(nat: libp2p::autonat::NatStatus) -> Self { + use libp2p::autonat::NatStatus; + + // See + let (reachability, public_addrs) = match &nat { + NatStatus::Unknown => (0, None), + NatStatus::Public(addr) => (1, Some(vec![addr.to_string()])), + NatStatus::Private => (2, None), + }; + + NatStatusResult { + reachability, + public_addrs, + } + } +} diff --git a/src/rpc/mod.rs b/src/rpc/mod.rs index 4a6aac115aa..15768a04d57 100644 --- a/src/rpc/mod.rs +++ b/src/rpc/mod.rs @@ -120,6 +120,7 @@ macro_rules! for_each_method { $callback!(crate::rpc::net::NetAutoNatStatus); $callback!(crate::rpc::net::NetVersion); $callback!(crate::rpc::net::NetProtectAdd); + $callback!(crate::rpc::net::NetFindPeer); // node vertical $callback!(crate::rpc::node::NodeStatus); diff --git a/src/tool/subcommands/api_cmd.rs b/src/tool/subcommands/api_cmd.rs index f19230377e3..0001d20766f 100644 --- a/src/tool/subcommands/api_cmd.rs +++ b/src/tool/subcommands/api_cmd.rs @@ -13,7 +13,7 @@ use crate::key_management::{KeyStore, KeyStoreConfig}; use crate::lotus_json::HasLotusJson; use crate::message::{Message as _, SignedMessage}; use crate::message_pool::{MessagePool, MpoolRpcProvider}; -use crate::networks::{parse_bootstrap_peers, ChainConfig, NetworkChain}; +use crate::networks::{ChainConfig, NetworkChain}; use crate::rpc::beacon::BeaconGetEntry; use crate::rpc::eth::types::{EthAddress, EthBytes}; use crate::rpc::gas::GasEstimateGasLimit; @@ -575,15 +575,8 @@ fn mpool_tests_with_tipset(tipset: &Tipset) -> Vec { } fn net_tests() -> Vec { - let bootstrap_peers = parse_bootstrap_peers(include_str!("../../../build/bootstrap/calibnet")); - let peer_id = bootstrap_peers - .last() - .expect("No bootstrap peers found - bootstrap file is empty or corrupted") - .to_string() - .rsplit_once('/') - .expect("No peer id found - address is not in the expected format") - .1 - .to_string(); + // Tests with a known peer id tend to be flaky, use a random peer id to test the unhappy path only + let random_peer_id = libp2p::PeerId::random().to_string(); // More net commands should be tested. Tracking issue: // https://github.com/ChainSafe/forest/issues/3639 @@ -591,7 +584,11 @@ fn net_tests() -> Vec { RpcTest::basic(NetAddrsListen::request(()).unwrap()), RpcTest::basic(NetPeers::request(()).unwrap()), RpcTest::identity(NetListening::request(()).unwrap()), - RpcTest::basic(NetAgentVersion::request((peer_id,)).unwrap()), + RpcTest::basic(NetAgentVersion::request((random_peer_id.clone(),)).unwrap()) + .policy_on_rejected(PolicyOnRejected::PassWithIdenticalError), + RpcTest::basic(NetFindPeer::request((random_peer_id,)).unwrap()) + .policy_on_rejected(PolicyOnRejected::Pass) + .ignore("It times out in lotus when peer not found"), RpcTest::basic(NetInfo::request(()).unwrap()) .ignore("Not implemented in Lotus. Why do we even have this method?"), RpcTest::basic(NetAutoNatStatus::request(()).unwrap()), diff --git a/src/utils/flume/mod.rs b/src/utils/flume/mod.rs new file mode 100644 index 00000000000..b6be336c71a --- /dev/null +++ b/src/utils/flume/mod.rs @@ -0,0 +1,14 @@ +// Copyright 2019-2024 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + +pub trait FlumeSenderExt { + fn send_or_warn(&self, msg: T); +} + +impl FlumeSenderExt for flume::Sender { + fn send_or_warn(&self, msg: T) { + if let Err(e) = self.send(msg) { + tracing::warn!("{e}"); + } + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index a496b7cc132..ba8c208e07a 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -4,10 +4,12 @@ pub mod cid; pub mod db; pub mod encoding; +pub mod flume; pub mod io; pub mod misc; pub mod monitoring; pub mod net; +pub mod p2p; pub mod proofs_api; pub mod reqwest_resume; pub mod stats; diff --git a/src/utils/p2p/mod.rs b/src/utils/p2p/mod.rs new file mode 100644 index 00000000000..b12f3284cc8 --- /dev/null +++ b/src/utils/p2p/mod.rs @@ -0,0 +1,44 @@ +// Copyright 2019-2024 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + +use libp2p::Multiaddr; + +pub trait MultiaddrExt: Sized { + fn without_p2p(self) -> Self; +} + +impl MultiaddrExt for Multiaddr { + fn without_p2p(mut self) -> Self { + if let Some(multiaddr::Protocol::P2p(_)) = self.iter().last() { + self.pop(); + self + } else { + self + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::str::FromStr as _; + + #[test] + fn test_without_p2p_positive() { + let ma = Multiaddr::from_str("/dns/bootstrap-calibnet-1.chainsafe-fil.io/tcp/34000/p2p/12D3KooWS3ZRhMYL67b4bD5XQ6fcpTyVQXnDe8H89LvwrDqaSbiT").unwrap(); + assert_eq!( + ma.without_p2p().to_string().as_str(), + "/dns/bootstrap-calibnet-1.chainsafe-fil.io/tcp/34000" + ); + } + + #[test] + fn test_without_p2p_negative() { + let ma = + Multiaddr::from_str("/dns/bootstrap-calibnet-1.chainsafe-fil.io/tcp/34000").unwrap(); + assert_eq!( + ma.without_p2p().to_string().as_str(), + "/dns/bootstrap-calibnet-1.chainsafe-fil.io/tcp/34000" + ); + } +}