Skip to main content

anvil/server/
rpc_handlers.rs

1//! Contains RPC handlers
2use crate::{
3    EthApi,
4    eth::error::to_rpc_result,
5    pubsub::{EthSubscription, LogsSubscription},
6};
7use alloy_rpc_types::{
8    FilteredParams,
9    pubsub::{Params, SubscriptionKind},
10};
11use anvil_core::eth::{EthPubSub, EthRequest, EthRpcCall, subscription::SubscriptionId};
12use anvil_rpc::{error::RpcError, response::ResponseResult};
13use anvil_server::{PubSubContext, PubSubRpcHandler, RpcHandler};
14use foundry_primitives::FoundryNetwork;
15
16/// A `RpcHandler` that expects `EthRequest` rpc calls via http
17#[derive(Clone)]
18pub struct HttpEthRpcHandler {
19    /// Access to the node
20    api: EthApi<FoundryNetwork>,
21}
22
23impl HttpEthRpcHandler {
24    /// Creates a new instance of the handler using the given `EthApi`
25    pub const fn new(api: EthApi<FoundryNetwork>) -> Self {
26        Self { api }
27    }
28}
29
30impl RpcHandler for HttpEthRpcHandler {
31    type Request = EthRequest;
32
33    fn on_request(&self, request: Self::Request) -> impl Future<Output = ResponseResult> + Send {
34        self.api.execute(request)
35    }
36}
37
38/// A `RpcHandler` that expects `EthRequest` rpc calls and `EthPubSub` via pubsub connection
39#[derive(Clone)]
40pub struct PubSubEthRpcHandler {
41    /// Access to the node
42    api: EthApi<FoundryNetwork>,
43}
44
45impl PubSubEthRpcHandler {
46    /// Creates a new instance of the handler using the given `EthApi`
47    pub const fn new(api: EthApi<FoundryNetwork>) -> Self {
48        Self { api }
49    }
50
51    /// Invoked for an ethereum pubsub rpc call
52    async fn on_pub_sub(&self, pubsub: EthPubSub, cx: PubSubContext<Self>) -> ResponseResult {
53        let id = SubscriptionId::random_hex();
54        trace!(target: "rpc::ws", "received pubsub request {:?}", pubsub);
55        match pubsub {
56            EthPubSub::EthUnSubscribe(id) => {
57                trace!(target: "rpc::ws", "canceling subscription {:?}", id);
58                let canceled = cx.remove_subscription(&id).is_some();
59                ResponseResult::Success(canceled.into())
60            }
61            EthPubSub::EthSubscribe(kind, raw_params) => {
62                let filter = match &*raw_params {
63                    Params::None => None,
64                    Params::Logs(filter) => Some(filter.clone()),
65                    Params::Bool(_) => None,
66                    Params::TransactionReceipts(_) => None,
67                };
68                let params = FilteredParams::new(filter.map(|b| *b));
69
70                let subscription = match kind {
71                    SubscriptionKind::Logs => {
72                        if raw_params.is_bool() {
73                            return ResponseResult::Error(RpcError::invalid_params(
74                                "Expected params for logs subscription",
75                            ));
76                        }
77
78                        trace!(target: "rpc::ws", "received logs subscription {:?}", params);
79                        let blocks = self.api.new_block_notifications();
80                        let storage = self.api.storage_info();
81                        EthSubscription::Logs(Box::new(LogsSubscription {
82                            blocks,
83                            storage,
84                            filter: params,
85                            queued: Default::default(),
86                            id: id.clone(),
87                        }))
88                    }
89                    SubscriptionKind::NewHeads => {
90                        trace!(target: "rpc::ws", "received header subscription");
91                        let blocks = self.api.new_block_notifications();
92                        let storage = self.api.storage_info();
93                        EthSubscription::Header(blocks, storage, id.clone())
94                    }
95                    SubscriptionKind::NewPendingTransactions => {
96                        trace!(target: "rpc::ws", "received pending transactions subscription");
97                        match *raw_params {
98                            Params::Bool(true) => EthSubscription::FullPendingTransactions(
99                                self.api.full_pending_transactions(),
100                                id.clone(),
101                            ),
102                            Params::Bool(false) | Params::None => {
103                                EthSubscription::PendingTransactions(
104                                    self.api.new_ready_transactions(),
105                                    id.clone(),
106                                )
107                            }
108                            _ => {
109                                return ResponseResult::Error(RpcError::invalid_params(
110                                    "Expected boolean parameter for newPendingTransactions",
111                                ));
112                            }
113                        }
114                    }
115                    SubscriptionKind::TransactionReceipts => {
116                        trace!(target: "rpc::ws", "received transaction receipts subscription");
117                        match *raw_params {
118                            Params::None => EthSubscription::TransactionReceipts(
119                                self.api.transaction_receipts_subscription(Default::default()),
120                                id.clone(),
121                            ),
122                            Params::TransactionReceipts(filter) => {
123                                EthSubscription::TransactionReceipts(
124                                    self.api.transaction_receipts_subscription(filter),
125                                    id.clone(),
126                                )
127                            }
128                            _ => {
129                                return ResponseResult::Error(RpcError::invalid_params(
130                                    "Expected transactionReceipts params",
131                                ));
132                            }
133                        }
134                    }
135                    SubscriptionKind::Syncing => {
136                        trace!(target: "rpc::ws", "received syncing subscription");
137                        EthSubscription::Syncing(Some(id.clone()))
138                    }
139                };
140
141                cx.add_subscription(id.clone(), subscription);
142
143                trace!(target: "rpc::ws", "created new subscription: {:?}", id);
144                to_rpc_result(id)
145            }
146        }
147    }
148}
149
150impl PubSubRpcHandler for PubSubEthRpcHandler {
151    type Request = EthRpcCall;
152    type SubscriptionId = SubscriptionId;
153    type Subscription = EthSubscription<FoundryNetwork>;
154
155    async fn on_request(&self, request: Self::Request, cx: PubSubContext<Self>) -> ResponseResult {
156        trace!(target: "rpc", "received pubsub request {:?}", request);
157        match request {
158            EthRpcCall::Request(request) => self.api.execute(*request).await,
159            EthRpcCall::PubSub(pubsub) => self.on_pub_sub(pubsub, cx).await,
160        }
161    }
162}