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        let mut rpc_call = self.client().request_noparams("admin_peerEvents_subscribe");
80        rpc_call.set_is_subscription();
81        GetSubscription::new(self.weak_client(), rpc_call)
82    }
83}
84
85#[cfg(test)]
86mod test {
87    use super::*;
88    use crate::{ext::test::async_ci_only, ProviderBuilder};
89    use alloy_node_bindings::{utils::run_with_tempdir, Geth};
90
91    #[tokio::test]
92    async fn node_info() {
93        async_ci_only(|| async move {
94            run_with_tempdir("geth-test-", |temp_dir| async move {
95                let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn();
96                let provider = ProviderBuilder::new().connect_http(geth.endpoint_url());
97                let node_info = provider.node_info().await.unwrap();
98                assert!(node_info.enode.starts_with("enode://"));
99            })
100            .await;
101        })
102        .await;
103    }
104
105    #[tokio::test]
106    async fn admin_peers() {
107        async_ci_only(|| async move {
108            run_with_tempdir("geth-test-1", |temp_dir_1| async move {
109                run_with_tempdir("geth-test-2", |temp_dir_2| async move {
110                    let geth1 =
111                        Geth::new().disable_discovery().keep_stderr().data_dir(&temp_dir_1).spawn();
112                    let mut geth2 = Geth::new()
113                        .disable_discovery()
114                        .keep_stderr()
115                        .port(0u16)
116                        .data_dir(&temp_dir_2)
117                        .spawn();
118
119                    let provider1 = ProviderBuilder::new().connect_http(geth1.endpoint_url());
120                    let provider2 = ProviderBuilder::new().connect_http(geth2.endpoint_url());
121                    let node1_info = provider1.node_info().await.unwrap();
122                    let node1_id = node1_info.id;
123                    let node1_enode = node1_info.enode;
124
125                    let added = provider2.add_peer(&node1_enode).await.unwrap();
126                    assert!(added);
127                    geth2.wait_to_add_peer(&node1_id).unwrap();
128                    let peers = provider2.peers().await.unwrap();
129                    assert_eq!(peers[0].enode, node1_enode);
130                })
131                .await;
132            })
133            .await;
134        })
135        .await;
136    }
137}