chainrpc_core/
method_safety.rs1use std::collections::HashSet;
4use std::sync::OnceLock;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum MethodSafety {
9 Safe,
11 Idempotent,
14 Unsafe,
17}
18
19pub fn classify_method(method: &str) -> MethodSafety {
21 if unsafe_methods().contains(method) {
22 MethodSafety::Unsafe
23 } else if idempotent_methods().contains(method) {
24 MethodSafety::Idempotent
25 } else {
26 MethodSafety::Safe
27 }
28}
29
30pub fn is_safe_to_retry(method: &str) -> bool {
32 classify_method(method) == MethodSafety::Safe
33}
34
35pub fn is_safe_to_dedup(method: &str) -> bool {
37 classify_method(method) == MethodSafety::Safe
38}
39
40pub fn is_cacheable(method: &str) -> bool {
42 classify_method(method) == MethodSafety::Safe
43}
44
45fn unsafe_methods() -> &'static HashSet<&'static str> {
46 static UNSAFE: OnceLock<HashSet<&'static str>> = OnceLock::new();
47 UNSAFE.get_or_init(|| {
48 [
49 "eth_sendTransaction",
50 "personal_sendTransaction",
51 "eth_sign",
52 "personal_sign",
53 "eth_signTransaction",
54 ]
55 .into_iter()
56 .collect()
57 })
58}
59
60fn idempotent_methods() -> &'static HashSet<&'static str> {
61 static IDEMPOTENT: OnceLock<HashSet<&'static str>> = OnceLock::new();
62 IDEMPOTENT.get_or_init(|| ["eth_sendRawTransaction"].into_iter().collect())
63}
64
65#[cfg(test)]
68mod tests {
69 use super::*;
70
71 #[test]
72 fn safe_read_methods() {
73 assert_eq!(classify_method("eth_blockNumber"), MethodSafety::Safe);
74 assert_eq!(classify_method("eth_getBalance"), MethodSafety::Safe);
75 assert_eq!(classify_method("eth_call"), MethodSafety::Safe);
76 assert_eq!(classify_method("eth_getLogs"), MethodSafety::Safe);
77 assert_eq!(
78 classify_method("eth_getTransactionReceipt"),
79 MethodSafety::Safe
80 );
81 assert_eq!(classify_method("eth_getBlockByNumber"), MethodSafety::Safe);
82 assert_eq!(classify_method("eth_chainId"), MethodSafety::Safe);
83 assert_eq!(classify_method("net_version"), MethodSafety::Safe);
84 }
85
86 #[test]
87 fn idempotent_methods_test() {
88 assert_eq!(
89 classify_method("eth_sendRawTransaction"),
90 MethodSafety::Idempotent
91 );
92 }
93
94 #[test]
95 fn unsafe_methods_test() {
96 assert_eq!(classify_method("eth_sendTransaction"), MethodSafety::Unsafe);
97 assert_eq!(
98 classify_method("personal_sendTransaction"),
99 MethodSafety::Unsafe
100 );
101 assert_eq!(classify_method("eth_sign"), MethodSafety::Unsafe);
102 }
103
104 #[test]
105 fn retry_safety() {
106 assert!(is_safe_to_retry("eth_blockNumber"));
107 assert!(!is_safe_to_retry("eth_sendRawTransaction"));
108 assert!(!is_safe_to_retry("eth_sendTransaction"));
109 }
110
111 #[test]
112 fn dedup_safety() {
113 assert!(is_safe_to_dedup("eth_getBalance"));
114 assert!(!is_safe_to_dedup("eth_sendRawTransaction"));
115 }
116
117 #[test]
118 fn unknown_methods_are_safe() {
119 assert_eq!(classify_method("custom_rpc_method"), MethodSafety::Safe);
120 }
121}