alloy_provider/ext/
admin.rs

1//! This module extends the Ethereum JSON-RPC provider with the Admin namespace's RPC methods.
2#[cfg(feature = "pubsub")]
3use crate::GetSubscription;
4use crate::Provider;
5use alloy_network::Network;
6use alloy_rpc_types_admin::{NodeInfo, PeerInfo};
7use alloy_transport::TransportResult;
8
9/// Admin namespace rpc interface that gives access to several non-standard RPC methods.
10#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
11#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
12pub trait AdminApi<N>: Send + Sync {
13    /// Requests adding the given peer, returning a boolean representing
14    /// whether or not the peer was accepted for tracking.
15    async fn add_peer(&self, record: &str) -> TransportResult<bool>;
16
17    /// Requests adding the given peer as a trusted peer, which the node will
18    /// always connect to even when its peer slots are full.
19    async fn add_trusted_peer(&self, record: &str) -> TransportResult<bool>;
20
21    /// Requests to remove the given peer, returning true if the enode was successfully parsed and
22    /// the peer was removed.
23    async fn remove_peer(&self, record: &str) -> TransportResult<bool>;
24
25    /// Requests to remove the given peer, returning a boolean representing whether or not the
26    /// enode url passed was validated. A return value of `true` does not necessarily mean that the
27    /// peer was disconnected.
28    async fn remove_trusted_peer(&self, record: &str) -> TransportResult<bool>;
29
30    /// Returns the list of peers currently connected to the node.
31    async fn peers(&self) -> TransportResult<Vec<PeerInfo>>;
32
33    /// Returns general information about the node as well as information about the running p2p
34    /// protocols (e.g. `eth`, `snap`).
35    async fn node_info(&self) -> TransportResult<NodeInfo>;
36
37    /// Subscribe to events received by peers over the network.
38    #[cfg(feature = "pubsub")]
39    fn subscribe_peer_events(
40        &self,
41    ) -> GetSubscription<alloy_rpc_client::NoParams, alloy_rpc_types_admin::PeerEvent>;
42}
43
44#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
45#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
46impl<N, P> AdminApi<N> for P
47where
48    N: Network,
49    P: Provider<N>,
50{
51    async fn add_peer(&self, record: &str) -> TransportResult<bool> {
52        self.client().request("admin_addPeer", (record,)).await
53    }
54
55    async fn add_trusted_peer(&self, record: &str) -> TransportResult<bool> {
56        self.client().request("admin_addTrustedPeer", (record,)).await
57    }
58
59    async fn remove_peer(&self, record: &str) -> TransportResult<bool> {
60        self.client().request("admin_removePeer", (record,)).await
61    }
62
63    async fn remove_trusted_peer(&self, record: &str) -> TransportResult<bool> {
64        self.client().request("admin_removeTrustedPeer", (record,)).await
65    }
66
67    async fn peers(&self) -> TransportResult<Vec<PeerInfo>> {
68        self.client().request_noparams("admin_peers").await
69    }
70
71    async fn node_info(&self) -> TransportResult<NodeInfo> {
72        self.client().request_noparams("admin_nodeInfo").await
73    }
74
75    #[cfg(feature = "pubsub")]
76    fn subscribe_peer_events(
77        &self,
78    ) -> GetSubscription<alloy_rpc_client::NoParams, alloy_rpc_types_admin::PeerEvent> {
79        self.subscribe_to("admin_peerEvents")
80    }
81}
82
83#[cfg(test)]
84mod test {
85    use super::*;
86    use crate::{ext::test::async_ci_only, ProviderBuilder};
87    use alloy_node_bindings::{utils::run_with_tempdir, Geth};
88
89    #[tokio::test]
90    async fn node_info() {
91        async_ci_only(|| async move {
92            run_with_tempdir("geth-test-", |temp_dir| async move {
93                let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn();
94                let provider = ProviderBuilder::new().connect_http(geth.endpoint_url());
95                let node_info = provider.node_info().await.unwrap();
96                assert!(node_info.enode.starts_with("enode://"));
97            })
98            .await;
99        })
100        .await;
101    }
102
103    #[tokio::test]
104    async fn admin_peers() {
105        async_ci_only(|| async move {
106            run_with_tempdir("geth-test-1", |temp_dir_1| async move {
107                run_with_tempdir("geth-test-2", |temp_dir_2| async move {
108                    let geth1 =
109                        Geth::new().disable_discovery().keep_stderr().data_dir(&temp_dir_1).spawn();
110                    let mut geth2 = Geth::new()
111                        .disable_discovery()
112                        .keep_stderr()
113                        .port(0u16)
114                        .data_dir(&temp_dir_2)
115                        .spawn();
116
117                    let provider1 = ProviderBuilder::new().connect_http(geth1.endpoint_url());
118                    let provider2 = ProviderBuilder::new().connect_http(geth2.endpoint_url());
119                    let node1_info = provider1.node_info().await.unwrap();
120                    let node1_id = node1_info.id;
121                    let node1_enode = node1_info.enode;
122
123                    let added = provider2.add_peer(&node1_enode).await.unwrap();
124                    assert!(added);
125                    geth2.wait_to_add_peer(&node1_id).unwrap();
126                    let peers = provider2.peers().await.unwrap();
127                    assert_eq!(peers[0].enode, node1_enode);
128                })
129                .await;
130            })
131            .await;
132        })
133        .await;
134    }
135}