Skip to main content

anvil_server/
lib.rs

1//! Bootstrap [axum] RPC servers.
2
3#![cfg_attr(not(test), warn(unused_crate_dependencies))]
4#![cfg_attr(docsrs, feature(doc_cfg))]
5
6#[macro_use]
7extern crate tracing;
8
9use anvil_rpc::{
10    error::RpcError,
11    request::RpcMethodCall,
12    response::{ResponseResult, RpcResponse},
13};
14use axum::{
15    Router,
16    extract::DefaultBodyLimit,
17    http::{HeaderValue, Method, header},
18    routing::{MethodRouter, post},
19};
20use serde::de::DeserializeOwned;
21use std::fmt;
22use tower_http::{cors::CorsLayer, trace::TraceLayer};
23
24mod config;
25pub use config::ServerConfig;
26
27mod error;
28mod handler;
29
30mod pubsub;
31pub use pubsub::{PubSubContext, PubSubRpcHandler};
32
33mod ws;
34
35#[cfg(feature = "ipc")]
36pub mod ipc;
37
38/// Helper trait that is used to execute ethereum rpc calls
39pub trait RpcHandler: Clone + Send + Sync + 'static {
40    /// The request type to expect
41    type Request: DeserializeOwned + Send + Sync + fmt::Debug;
42
43    /// Invoked when the request was received
44    fn on_request(&self, request: Self::Request) -> impl Future<Output = ResponseResult> + Send;
45
46    /// Invoked for every incoming [`RpcMethodCall`]. Notifications are adapted to method calls
47    /// with an [`anvil_rpc::request::Id::Null`] identifier, and their responses are discarded.
48    ///
49    /// This will attempt to deserialize a `{ "method" : "<name>", "params": "<params>" }` message
50    /// into the `Request` type of this handler. If a `Request` instance was deserialized
51    /// successfully, [`Self::on_request`] will be invoked.
52    ///
53    /// **Note**: override this function if the expected `Request` deviates from `{ "method" :
54    /// "<name>", "params": "<params>" }`
55    fn on_call(&self, call: RpcMethodCall) -> impl Future<Output = RpcResponse> + Send {
56        async move {
57            trace!(target: "rpc",  id = ?call.id , method = ?call.method, params = ?call.params, "received method call");
58            let RpcMethodCall { method, params, id, .. } = call;
59
60            let params: serde_json::Value = params.into();
61            let call = serde_json::json!({
62                "method": &method,
63                "params": params
64            });
65
66            match serde_json::from_value::<Self::Request>(call) {
67                Ok(req) => {
68                    let result = self.on_request(req).await;
69                    RpcResponse::new(id, result)
70                }
71                Err(err) => {
72                    let err = err.to_string();
73                    let method_not_found = serde_json::from_value::<Self::Request>(
74                        serde_json::json!({ "method": &method }),
75                    )
76                    .is_err_and(|err| err.to_string().contains("unknown variant"));
77                    if method_not_found {
78                        error!(target: "rpc", ?method, "failed to deserialize method due to unknown variant");
79                        RpcResponse::new(id, RpcError::method_not_found())
80                    } else {
81                        error!(target: "rpc", ?method, ?err, "failed to deserialize method");
82                        RpcResponse::new(id, RpcError::invalid_params(err))
83                    }
84                }
85            }
86        }
87    }
88}
89
90/// Configures an [`axum::Router`] that handles JSON-RPC calls via both HTTP and WS.
91pub fn http_ws_router<Http, Ws>(config: ServerConfig, http: Http, ws: Ws) -> Router
92where
93    Http: RpcHandler,
94    Ws: PubSubRpcHandler,
95{
96    router_inner(config, post(handler::handle).get(ws::handle_ws), (http, ws))
97}
98
99/// Configures an [`axum::Router`] that handles JSON-RPC calls via HTTP.
100pub fn http_router<Http>(config: ServerConfig, http: Http) -> Router
101where
102    Http: RpcHandler,
103{
104    router_inner(config, post(handler::handle), (http, ()))
105}
106
107fn router_inner<S: Clone + Send + Sync + 'static>(
108    config: ServerConfig,
109    root_method_router: MethodRouter<S>,
110    state: S,
111) -> Router {
112    let ServerConfig { allow_origin, no_cors, no_request_size_limit } = config;
113
114    let mut router = Router::new()
115        .route("/", root_method_router)
116        .with_state(state)
117        .layer(TraceLayer::new_for_http());
118    if !no_cors {
119        // See [`tower_http::cors`](https://docs.rs/tower-http/latest/tower_http/cors/index.html)
120        // for more details.
121        router = router.layer(
122            CorsLayer::new()
123                .allow_origin(allow_origin.0)
124                .allow_headers([header::CONTENT_TYPE])
125                .allow_methods([Method::GET, Method::POST]),
126        );
127    }
128    if no_request_size_limit {
129        router = router.layer(DefaultBodyLimit::disable());
130    }
131    router
132}