1#![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::{Id, 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
24fn redact_params(method: &str, params: &serde_json::Value) -> serde_json::Value {
30 match method {
31 "eth_sendRawTransaction"
33 | "eth_sendRawTransactionSync"
34 | "eth_sendRawTransactionConditional"
35 | "eth_signRawTransaction"
36 | "anvil_classifyTransaction"
37 | "trace_rawTransaction" => {
38 if let Some(array) = params.as_array() {
39 if array.is_empty() {
40 params.clone()
41 } else {
42 let mut redacted = vec![serde_json::Value::String("[SIGNED_TX]".to_string())];
44 redacted.extend(array.iter().skip(1).cloned());
47 serde_json::Value::Array(redacted)
48 }
49 } else {
50 params.clone()
51 }
52 }
53 _ => {
54 let mut redacted = params.clone();
55 redact_fork_credentials(method, &mut redacted);
56 redacted
57 }
58 }
59}
60
61fn redact_fork_credentials(method: &str, params: &mut serde_json::Value) {
64 match method {
65 "anvil_reset" | "hardhat_reset" => redact_fork_params(params),
66 "anvil_setRpcUrl" => {
67 if let Some(serde_json::Value::String(url)) = params.get_mut(0) {
68 *url = redact_url_credentials(url);
69 }
70 }
71 _ => {}
72 }
73}
74
75fn redact_fork_params(param: &mut serde_json::Value) {
77 match param {
78 serde_json::Value::Array(array) => {
79 if let Some(first) = array.first_mut() {
82 redact_fork_params(first);
83 }
84 }
85 serde_json::Value::Object(object) => {
86 for (key, value) in object {
87 if matches!(key.as_str(), "params" | "forking" | "jsonRpcUrl") {
88 redact_fork_params(value);
89 }
90 }
91 }
92 serde_json::Value::String(url) => *url = redact_url_credentials(url),
93 _ => {}
94 }
95}
96
97fn redact_url_credentials(url: &str) -> String {
99 let Ok(mut parsed) = url::Url::parse(url) else { return "[REDACTED_URL]".to_string() };
100 if parsed.host().is_none() {
102 return "[REDACTED_URL]".to_string();
103 }
104 let _ = parsed.set_username("");
106 let _ = parsed.set_password(None);
107 parsed.set_path("");
108 parsed.set_query(None);
109 parsed.set_fragment(None);
110 parsed.to_string()
111}
112
113mod config;
114pub use config::ServerConfig;
115
116mod error;
117mod handler;
118
119mod pubsub;
120pub use pubsub::{PubSubContext, PubSubRpcHandler};
121
122mod ws;
123
124#[cfg(feature = "ipc")]
125pub mod ipc;
126
127pub trait RpcHandler: Clone + Send + Sync + 'static {
129 type Request: DeserializeOwned + Send + Sync + fmt::Debug;
131
132 fn on_request(&self, request: Self::Request) -> impl Future<Output = ResponseResult> + Send;
134
135 fn on_call(&self, call: RpcMethodCall) -> impl Future<Output = RpcResponse> + Send {
145 async move {
146 let RpcMethodCall { method, params, id, .. } = call;
147
148 let mut params: serde_json::Value = params.into();
149
150 trace!(target: "rpc", id = ?id, method = ?method, params = ?redact_params(&method, ¶ms), "received method call");
152
153 let call = serde_json::json!({
154 "method": &method,
155 "params": params
156 });
157
158 match serde_json::from_value::<Self::Request>(call) {
159 Ok(req) => {
160 let result = self.on_request(req).await;
161 if let ResponseResult::Error(error) = &result {
162 redact_fork_credentials(&method, &mut params);
163 self.on_request_error(&method, &id, ¶ms, error);
164 }
165 RpcResponse::new(id, result)
166 }
167 Err(err) => {
168 let err = err.to_string();
169 let method_not_found = serde_json::from_value::<Self::Request>(
170 serde_json::json!({ "method": &method }),
171 )
172 .is_err_and(|err| err.to_string().contains("unknown variant"));
173 if method_not_found {
174 error!(target: "rpc", ?id, ?method, "failed to deserialize method due to unknown variant");
175 RpcResponse::new(id, RpcError::method_not_found())
176 } else {
177 error!(target: "rpc", ?id, ?method, ?err, "failed to deserialize method");
178 RpcResponse::new(id, RpcError::invalid_params(err))
179 }
180 }
181 }
182 }
183 }
184
185 fn on_request_error(
189 &self,
190 _method: &str,
191 _id: &Id,
192 _params: &serde_json::Value,
193 _error: &RpcError,
194 ) {
195 }
196}
197
198pub fn http_ws_router<Http, Ws>(config: ServerConfig, http: Http, ws: Ws) -> Router
200where
201 Http: RpcHandler,
202 Ws: PubSubRpcHandler,
203{
204 router_inner(config, post(handler::handle).get(ws::handle_ws), (http, ws))
205}
206
207pub fn http_router<Http>(config: ServerConfig, http: Http) -> Router
209where
210 Http: RpcHandler,
211{
212 router_inner(config, post(handler::handle), (http, ()))
213}
214
215fn router_inner<S: Clone + Send + Sync + 'static>(
216 config: ServerConfig,
217 root_method_router: MethodRouter<S>,
218 state: S,
219) -> Router {
220 let ServerConfig { allow_origin, no_cors, no_request_size_limit } = config;
221
222 let mut router = Router::new()
223 .route("/", root_method_router)
224 .with_state(state)
225 .layer(TraceLayer::new_for_http());
226 if !no_cors {
227 router = router.layer(
230 CorsLayer::new()
231 .allow_origin(allow_origin.0)
232 .allow_headers([header::CONTENT_TYPE])
233 .allow_methods([Method::GET, Method::POST]),
234 );
235 }
236 if no_request_size_limit {
237 router = router.layer(DefaultBodyLimit::disable());
238 }
239 router
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245 use serde_json::json;
246
247 #[test]
248 fn redacts_url_credentials() {
249 for (url, expected) in [
250 ("https://user:pass@example.invalid:8545/", "https://example.invalid:8545/"),
251 ("https://example.invalid:8545/key?token=x#secret", "https://example.invalid:8545/"),
252 (
253 "https://example.invalid:8545/abcdefghijklmnop?api_key=abcdefghijklmnop",
254 "https://example.invalid:8545/",
255 ),
256 ("invalid secret URL", "[REDACTED_URL]"),
257 ("https://user:pass@invalid host/secret", "[REDACTED_URL]"),
258 ("mykey:secret@example.invalid/rpc/key", "[REDACTED_URL]"),
259 ("mykey:/secret@example.invalid/rpc/key", "[REDACTED_URL]"),
260 ] {
261 assert_eq!(redact_url_credentials(url), expected, "{url}");
262 }
263 }
264
265 #[test]
266 fn redacts_fork_parameter_shapes_without_losing_context() {
267 let url = "https://user:pass@example.invalid:8545/key?token=x#secret";
268 let safe = "https://example.invalid:8545/";
269 for (params, expected) in [
270 (json!([{"forking": {"jsonRpcUrl": url}}]), json!([{"forking": {"jsonRpcUrl": safe}}])),
271 (json!({"forking": {"jsonRpcUrl": url}}), json!({"forking": {"jsonRpcUrl": safe}})),
272 (json!({"jsonRpcUrl": url}), json!({"jsonRpcUrl": safe})),
273 (
274 json!({"params": {"forking": {"jsonRpcUrl": url, "blockNumber": 42}}}),
275 json!({"params": {"forking": {"jsonRpcUrl": safe, "blockNumber": 42}}}),
276 ),
277 (json!([[url, "0x2a"]]), json!([[safe, "0x2a"]])),
278 (json!([{"forking": [url, 42]}]), json!([{"forking": [safe, 42]}])),
279 (json!({"params": [url, 42]}), json!({"params": [safe, 42]})),
280 (json!([[[url, 42]]]), json!([[[safe, 42]]])),
281 (json!({"params": [[url, 42]]}), json!({"params": [[safe, 42]]})),
282 (json!([[url]]), json!([[safe]])),
283 (json!([[null, "0x2a"]]), json!([[null, "0x2a"]])),
284 (
285 json!({"params": {"blockNumber": "0x2a", "extra": ["diagnostic"]}}),
286 json!({"params": {"blockNumber": "0x2a", "extra": ["diagnostic"]}}),
287 ),
288 (
289 json!({"params": {"forking": [null, 42], "extra": ["diagnostic"]}}),
290 json!({"params": {"forking": [null, 42], "extra": ["diagnostic"]}}),
291 ),
292 ] {
293 for method in ["anvil_reset", "hardhat_reset"] {
294 assert_eq!(redact_params(method, ¶ms), expected, "{method}: {params}");
295 let mut failed = params.clone();
296 redact_fork_credentials(method, &mut failed);
297 assert_eq!(failed, expected, "{method}: {params}");
298 }
299 }
300 for (mut params, expected) in [
301 (json!([url]), json!([safe])),
302 (json!(["invalid secret URL"]), json!(["[REDACTED_URL]"])),
303 ] {
304 assert_eq!(redact_params("anvil_setRpcUrl", ¶ms), expected);
305 redact_fork_credentials("anvil_setRpcUrl", &mut params);
306 assert_eq!(params, expected);
307 }
308 }
309
310 #[test]
311 fn preserves_failure_params_and_redacts_only_trace_transactions() {
312 for method in [
313 "eth_sendRawTransaction",
314 "eth_sendRawTransactionSync",
315 "eth_sendRawTransactionConditional",
316 "eth_signRawTransaction",
317 "anvil_classifyTransaction",
318 "trace_rawTransaction",
319 ] {
320 for (params, expected) in [
321 (json!(["0x12345678"]), json!(["[SIGNED_TX]"])),
322 (json!(["0x12345678", 5000]), json!(["[SIGNED_TX]", 5000])),
323 (
324 json!(["0x12345678", ["trace", "stateDiff"], "0x2a"]),
325 json!(["[SIGNED_TX]", ["trace", "stateDiff"], "0x2a"]),
326 ),
327 (json!([]), json!([])),
328 (json!(null), json!(null)),
329 ] {
330 assert_eq!(redact_params(method, ¶ms), expected, "{method}");
331 let mut failed = params.clone();
332 redact_fork_credentials(method, &mut failed);
333 assert_eq!(failed, params, "{method}");
334 }
335 }
336 let params = json!([{"to": "0x1111111111111111111111111111111111111111", "data": "0x12345678"}, "0x100000"]);
337 assert_eq!(redact_params("eth_call", ¶ms), params);
338 let mut failed = params.clone();
339 redact_fork_credentials("eth_call", &mut failed);
340 assert_eq!(failed, params);
341 }
342}