alloy_provider/ext/
admin.rs

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