forest/rpc/methods/
net.rs

1// Copyright 2019-2025 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4mod types;
5use itertools::Itertools;
6pub use types::*;
7
8use std::any::Any;
9use std::str::FromStr;
10
11use crate::libp2p::{NetRPCMethods, NetworkMessage, PeerId};
12use crate::rpc::{ApiPaths, Ctx, Permission, RpcMethod, ServerError};
13use anyhow::{Context as _, Result};
14use cid::multibase;
15use enumflags2::BitFlags;
16use fvm_ipld_blockstore::Blockstore;
17
18pub enum NetAddrsListen {}
19impl RpcMethod<0> for NetAddrsListen {
20    const NAME: &'static str = "Filecoin.NetAddrsListen";
21    const PARAM_NAMES: [&'static str; 0] = [];
22    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
23    const PERMISSION: Permission = Permission::Read;
24    const DESCRIPTION: Option<&'static str> =
25        Some("Returns a list of listening addresses and the peer ID.");
26
27    type Params = ();
28    type Ok = AddrInfo;
29
30    async fn handle(ctx: Ctx<impl Blockstore>, (): Self::Params) -> Result<Self::Ok, ServerError> {
31        let (tx, rx) = flume::bounded(1);
32        let req = NetworkMessage::JSONRPCRequest {
33            method: NetRPCMethods::AddrsListen(tx),
34        };
35
36        ctx.network_send().send_async(req).await?;
37        let (id, addrs) = rx.recv_async().await?;
38
39        Ok(AddrInfo::new(id, addrs))
40    }
41}
42
43pub enum NetPeers {}
44impl RpcMethod<0> for NetPeers {
45    const NAME: &'static str = "Filecoin.NetPeers";
46    const PARAM_NAMES: [&'static str; 0] = [];
47    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
48    const PERMISSION: Permission = Permission::Read;
49    const DESCRIPTION: Option<&'static str> = Some("Returns a list of currently connected peers.");
50
51    type Params = ();
52    type Ok = Vec<AddrInfo>;
53
54    async fn handle(ctx: Ctx<impl Blockstore>, (): Self::Params) -> Result<Self::Ok, ServerError> {
55        let (tx, rx) = flume::bounded(1);
56        let req = NetworkMessage::JSONRPCRequest {
57            method: NetRPCMethods::Peers(tx),
58        };
59
60        ctx.network_send().send_async(req).await?;
61        let peer_addresses = rx.recv_async().await?;
62
63        let connections = peer_addresses
64            .into_iter()
65            .map(|(id, addrs)| AddrInfo::new(id, addrs))
66            .collect();
67
68        Ok(connections)
69    }
70}
71
72pub enum NetFindPeer {}
73impl RpcMethod<1> for NetFindPeer {
74    const NAME: &'static str = "Filecoin.NetFindPeer";
75    const PARAM_NAMES: [&'static str; 1] = ["peer_id"];
76    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
77    const PERMISSION: Permission = Permission::Read;
78
79    type Params = (String,);
80    type Ok = AddrInfo;
81
82    async fn handle(
83        ctx: Ctx<impl Blockstore>,
84        (peer_id,): Self::Params,
85    ) -> Result<Self::Ok, ServerError> {
86        let peer_id = PeerId::from_str(&peer_id)?;
87        let (tx, rx) = flume::bounded(1);
88        ctx.network_send()
89            .send_async(NetworkMessage::JSONRPCRequest {
90                method: NetRPCMethods::Peer(tx, peer_id),
91            })
92            .await?;
93        let addrs = rx
94            .recv_async()
95            .await?
96            .with_context(|| format!("peer {peer_id} not found"))?;
97        Ok(AddrInfo::new(peer_id, addrs))
98    }
99}
100
101pub enum NetListening {}
102impl RpcMethod<0> for NetListening {
103    const NAME: &'static str = "Filecoin.NetListening";
104    const PARAM_NAMES: [&'static str; 0] = [];
105    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
106    const PERMISSION: Permission = Permission::Read;
107    const NAME_ALIAS: Option<&'static str> = Some("net_listening");
108
109    type Params = ();
110    type Ok = bool;
111
112    async fn handle(_: Ctx<impl Any>, (): Self::Params) -> Result<Self::Ok, ServerError> {
113        Ok(true)
114    }
115}
116
117pub enum NetInfo {}
118impl RpcMethod<0> for NetInfo {
119    const NAME: &'static str = "Forest.NetInfo";
120    const PARAM_NAMES: [&'static str; 0] = [];
121    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
122    const PERMISSION: Permission = Permission::Read;
123
124    type Params = ();
125    type Ok = NetInfoResult;
126
127    async fn handle(ctx: Ctx<impl Blockstore>, (): Self::Params) -> Result<Self::Ok, ServerError> {
128        let (tx, rx) = flume::bounded(1);
129        let req = NetworkMessage::JSONRPCRequest {
130            method: NetRPCMethods::Info(tx),
131        };
132
133        ctx.network_send().send_async(req).await?;
134        Ok(rx.recv_async().await?)
135    }
136}
137
138pub enum NetConnect {}
139impl RpcMethod<1> for NetConnect {
140    const NAME: &'static str = "Filecoin.NetConnect";
141    const PARAM_NAMES: [&'static str; 1] = ["peerAddressInfo"];
142    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
143    const PERMISSION: Permission = Permission::Write;
144    const DESCRIPTION: Option<&'static str> = Some("Connects to a specified peer.");
145
146    type Params = (AddrInfo,);
147    type Ok = ();
148
149    async fn handle(
150        ctx: Ctx<impl Blockstore>,
151        (AddrInfo { id, addrs },): Self::Params,
152    ) -> Result<Self::Ok, ServerError> {
153        let (_, id) = multibase::decode(format!("{}{}", "z", id))?;
154        let peer_id = PeerId::from_bytes(&id)?;
155
156        let (tx, rx) = flume::bounded(1);
157        let req = NetworkMessage::JSONRPCRequest {
158            method: NetRPCMethods::Connect(tx, peer_id, addrs),
159        };
160
161        ctx.network_send().send_async(req).await?;
162        let success = rx.recv_async().await?;
163
164        if success {
165            Ok(())
166        } else {
167            Err(anyhow::anyhow!("Peer could not be dialed from any address provided").into())
168        }
169    }
170}
171
172pub enum NetDisconnect {}
173impl RpcMethod<1> for NetDisconnect {
174    const NAME: &'static str = "Filecoin.NetDisconnect";
175    const PARAM_NAMES: [&'static str; 1] = ["peerId"];
176    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
177    const PERMISSION: Permission = Permission::Write;
178    const DESCRIPTION: Option<&'static str> = Some("Disconnects from the specified peer.");
179
180    type Params = (String,);
181    type Ok = ();
182
183    async fn handle(
184        ctx: Ctx<impl Blockstore>,
185        (peer_id,): Self::Params,
186    ) -> Result<Self::Ok, ServerError> {
187        let peer_id = PeerId::from_str(&peer_id)?;
188
189        let (tx, rx) = flume::bounded(1);
190        let req = NetworkMessage::JSONRPCRequest {
191            method: NetRPCMethods::Disconnect(tx, peer_id),
192        };
193
194        ctx.network_send().send_async(req).await?;
195        rx.recv_async().await?;
196
197        Ok(())
198    }
199}
200
201pub enum NetAgentVersion {}
202impl RpcMethod<1> for NetAgentVersion {
203    const NAME: &'static str = "Filecoin.NetAgentVersion";
204    const PARAM_NAMES: [&'static str; 1] = ["peerId"];
205    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
206    const PERMISSION: Permission = Permission::Read;
207    const DESCRIPTION: Option<&'static str> = Some("Returns the agent version string.");
208
209    type Params = (String,);
210    type Ok = String;
211
212    async fn handle(
213        ctx: Ctx<impl Blockstore>,
214        (peer_id,): Self::Params,
215    ) -> Result<Self::Ok, ServerError> {
216        let peer_id = PeerId::from_str(&peer_id)?;
217        let (tx, rx) = flume::bounded(1);
218        ctx.network_send()
219            .send_async(NetworkMessage::JSONRPCRequest {
220                method: NetRPCMethods::AgentVersion(tx, peer_id),
221            })
222            .await?;
223        Ok(rx.recv_async().await?.context("item not found")?)
224    }
225}
226
227pub enum NetAutoNatStatus {}
228impl RpcMethod<0> for NetAutoNatStatus {
229    const NAME: &'static str = "Filecoin.NetAutoNatStatus";
230    const PARAM_NAMES: [&'static str; 0] = [];
231    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
232    const PERMISSION: Permission = Permission::Read;
233
234    type Params = ();
235    type Ok = NatStatusResult;
236
237    async fn handle(ctx: Ctx<impl Blockstore>, (): Self::Params) -> Result<Self::Ok, ServerError> {
238        let (tx, rx) = flume::bounded(1);
239        let req = NetworkMessage::JSONRPCRequest {
240            method: NetRPCMethods::AutoNATStatus(tx),
241        };
242        ctx.network_send().send_async(req).await?;
243        let nat_status = rx.recv_async().await?;
244        Ok(nat_status.into())
245    }
246}
247
248pub enum NetVersion {}
249impl RpcMethod<0> for NetVersion {
250    const NAME: &'static str = "Filecoin.NetVersion";
251    const PARAM_NAMES: [&'static str; 0] = [];
252    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all_with_v2();
253    const PERMISSION: Permission = Permission::Read;
254    const NAME_ALIAS: Option<&'static str> = Some("net_version");
255
256    type Params = ();
257    type Ok = String;
258
259    async fn handle(ctx: Ctx<impl Blockstore>, (): Self::Params) -> Result<Self::Ok, ServerError> {
260        Ok(ctx.chain_config().eth_chain_id.to_string())
261    }
262}
263
264pub enum NetProtectAdd {}
265impl RpcMethod<1> for NetProtectAdd {
266    const NAME: &'static str = "Filecoin.NetProtectAdd";
267    const PARAM_NAMES: [&'static str; 1] = ["peerIdList"];
268    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
269    const PERMISSION: Permission = Permission::Admin;
270    const DESCRIPTION: Option<&'static str> = Some(
271        "Protects a peer from having its connection(s) pruned in the event the libp2p host reaches its maximum number of peers.",
272    );
273
274    type Params = (Vec<String>,);
275    type Ok = ();
276
277    // This whitelists a peer in forest peer manager but has no impact on libp2p swarm
278    // due to the fact that `rust-libp2p` implementation is very different to that
279    // in go. However it would be nice to investigate connection limiting options in Rust.
280    // See: <https://github.com/ChainSafe/forest/issues/4355>.
281    async fn handle(
282        ctx: Ctx<impl Blockstore>,
283        (peer_ids,): Self::Params,
284    ) -> Result<Self::Ok, ServerError> {
285        let peer_ids = peer_ids
286            .iter()
287            .map(String::as_str)
288            .map(PeerId::from_str)
289            .try_collect()?;
290        let (tx, rx) = flume::bounded(1);
291        ctx.network_send()
292            .send_async(NetworkMessage::JSONRPCRequest {
293                method: NetRPCMethods::ProtectPeer(tx, peer_ids),
294            })
295            .await?;
296        rx.recv_async().await?;
297        Ok(())
298    }
299}
300
301pub enum NetProtectList {}
302impl RpcMethod<0> for NetProtectList {
303    const NAME: &'static str = "Filecoin.NetProtectList";
304    const PARAM_NAMES: [&'static str; 0] = [];
305    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
306    const PERMISSION: Permission = Permission::Read;
307    const DESCRIPTION: Option<&'static str> = Some("Returns the current list of protected peers.");
308
309    type Params = ();
310    type Ok = Vec<String>;
311    async fn handle(ctx: Ctx<impl Blockstore>, (): Self::Params) -> Result<Self::Ok, ServerError> {
312        let (tx, rx) = flume::bounded(1);
313        ctx.network_send()
314            .send_async(NetworkMessage::JSONRPCRequest {
315                method: NetRPCMethods::ListProtectedPeers(tx),
316            })
317            .await?;
318        let peers = rx.recv_async().await?;
319        Ok(peers.into_iter().map(|p| p.to_string()).collect())
320    }
321}
322
323pub enum NetProtectRemove {}
324impl RpcMethod<1> for NetProtectRemove {
325    const NAME: &'static str = "Filecoin.NetProtectRemove";
326    const PARAM_NAMES: [&'static str; 1] = ["peerIdList"];
327    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
328    const PERMISSION: Permission = Permission::Admin;
329    const DESCRIPTION: Option<&'static str> = Some("Remove a peer from the protected list.");
330
331    type Params = (Vec<String>,);
332    type Ok = ();
333
334    // Similar to NetProtectAdd
335    async fn handle(
336        ctx: Ctx<impl Blockstore>,
337        (peer_ids,): Self::Params,
338    ) -> Result<Self::Ok, ServerError> {
339        let peer_ids = peer_ids
340            .iter()
341            .map(String::as_str)
342            .map(PeerId::from_str)
343            .try_collect()?;
344        let (tx, rx) = flume::bounded(1);
345        ctx.network_send()
346            .send_async(NetworkMessage::JSONRPCRequest {
347                method: NetRPCMethods::UnprotectPeer(tx, peer_ids),
348            })
349            .await?;
350        rx.recv_async().await?;
351        Ok(())
352    }
353}