reth_rpc_server_types/
module.rs

1use std::{collections::HashSet, fmt, str::FromStr};
2
3use serde::{Deserialize, Serialize, Serializer};
4use strum::{AsRefStr, EnumIter, IntoStaticStr, ParseError, VariantArray, VariantNames};
5
6/// Describes the modules that should be installed.
7///
8/// # Example
9///
10/// Create a [`RpcModuleSelection`] from a selection.
11///
12/// ```
13/// use reth_rpc_server_types::{RethRpcModule, RpcModuleSelection};
14/// let config: RpcModuleSelection = vec![RethRpcModule::Eth].into();
15/// ```
16#[derive(Debug, Default, Clone, Eq, PartialEq)]
17pub enum RpcModuleSelection {
18    /// Use _all_ available modules.
19    All,
20    /// The default modules `eth`, `net`, `web3`
21    #[default]
22    Standard,
23    /// Only use the configured modules.
24    Selection(HashSet<RethRpcModule>),
25}
26
27// === impl RpcModuleSelection ===
28
29impl RpcModuleSelection {
30    /// The standard modules to instantiate by default `eth`, `net`, `web3`
31    pub const STANDARD_MODULES: [RethRpcModule; 3] =
32        [RethRpcModule::Eth, RethRpcModule::Net, RethRpcModule::Web3];
33
34    /// Returns a selection of [`RethRpcModule`] with all [`RethRpcModule::all_variants`].
35    pub fn all_modules() -> HashSet<RethRpcModule> {
36        RethRpcModule::modules().into_iter().collect()
37    }
38
39    /// Returns the [`RpcModuleSelection::STANDARD_MODULES`] as a selection.
40    pub fn standard_modules() -> HashSet<RethRpcModule> {
41        HashSet::from(Self::STANDARD_MODULES)
42    }
43
44    /// All modules that are available by default on IPC.
45    ///
46    /// By default all modules are available on IPC.
47    pub fn default_ipc_modules() -> HashSet<RethRpcModule> {
48        Self::all_modules()
49    }
50
51    /// Creates a new _unique_ [`RpcModuleSelection::Selection`] from the given items.
52    ///
53    /// # Note
54    ///
55    /// This will dedupe the selection and remove duplicates while preserving the order.
56    ///
57    /// # Example
58    ///
59    /// Create a selection from the [`RethRpcModule`] string identifiers
60    ///
61    /// ```
62    /// use reth_rpc_server_types::{RethRpcModule, RpcModuleSelection};
63    /// let selection = vec!["eth", "admin"];
64    /// let config = RpcModuleSelection::try_from_selection(selection).unwrap();
65    /// assert_eq!(config, RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]));
66    /// ```
67    ///
68    /// Create a unique selection from the [`RethRpcModule`] string identifiers
69    ///
70    /// ```
71    /// use reth_rpc_server_types::{RethRpcModule, RpcModuleSelection};
72    /// let selection = vec!["eth", "admin", "eth", "admin"];
73    /// let config = RpcModuleSelection::try_from_selection(selection).unwrap();
74    /// assert_eq!(config, RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]));
75    /// ```
76    pub fn try_from_selection<I, T>(selection: I) -> Result<Self, T::Error>
77    where
78        I: IntoIterator<Item = T>,
79        T: TryInto<RethRpcModule>,
80    {
81        selection.into_iter().map(TryInto::try_into).collect()
82    }
83
84    /// Returns the number of modules in the selection
85    pub fn len(&self) -> usize {
86        match self {
87            Self::All => RethRpcModule::variant_count(),
88            Self::Standard => Self::STANDARD_MODULES.len(),
89            Self::Selection(s) => s.len(),
90        }
91    }
92
93    /// Returns true if no selection is configured
94    pub fn is_empty(&self) -> bool {
95        match self {
96            Self::Selection(sel) => sel.is_empty(),
97            _ => false,
98        }
99    }
100
101    /// Returns an iterator over all configured [`RethRpcModule`]
102    pub fn iter_selection(&self) -> Box<dyn Iterator<Item = RethRpcModule> + '_> {
103        match self {
104            Self::All => Box::new(RethRpcModule::modules().into_iter()),
105            Self::Standard => Box::new(Self::STANDARD_MODULES.iter().copied()),
106            Self::Selection(s) => Box::new(s.iter().copied()),
107        }
108    }
109
110    /// Clones the set of configured [`RethRpcModule`].
111    pub fn to_selection(&self) -> HashSet<RethRpcModule> {
112        match self {
113            Self::All => Self::all_modules(),
114            Self::Standard => Self::standard_modules(),
115            Self::Selection(s) => s.clone(),
116        }
117    }
118
119    /// Converts the selection into a [`HashSet`].
120    pub fn into_selection(self) -> HashSet<RethRpcModule> {
121        match self {
122            Self::All => Self::all_modules(),
123            Self::Standard => Self::standard_modules(),
124            Self::Selection(s) => s,
125        }
126    }
127
128    /// Returns true if both selections are identical.
129    pub fn are_identical(http: Option<&Self>, ws: Option<&Self>) -> bool {
130        match (http, ws) {
131            // Shortcut for common case to avoid iterating later
132            (Some(Self::All), Some(other)) | (Some(other), Some(Self::All)) => {
133                other.len() == RethRpcModule::variant_count()
134            }
135
136            // If either side is disabled, then the other must be empty
137            (Some(some), None) | (None, Some(some)) => some.is_empty(),
138
139            (Some(http), Some(ws)) => http.to_selection() == ws.to_selection(),
140            (None, None) => true,
141        }
142    }
143
144    /// Returns true if the selection contains the given module.
145    pub fn contains(&self, module: &RethRpcModule) -> bool {
146        match self {
147            Self::All => true,
148            Self::Standard => Self::STANDARD_MODULES.contains(module),
149            Self::Selection(s) => s.contains(module),
150        }
151    }
152}
153
154impl From<&HashSet<RethRpcModule>> for RpcModuleSelection {
155    fn from(s: &HashSet<RethRpcModule>) -> Self {
156        Self::from(s.clone())
157    }
158}
159
160impl From<HashSet<RethRpcModule>> for RpcModuleSelection {
161    fn from(s: HashSet<RethRpcModule>) -> Self {
162        Self::Selection(s)
163    }
164}
165
166impl From<&[RethRpcModule]> for RpcModuleSelection {
167    fn from(s: &[RethRpcModule]) -> Self {
168        Self::Selection(s.iter().copied().collect())
169    }
170}
171
172impl From<Vec<RethRpcModule>> for RpcModuleSelection {
173    fn from(s: Vec<RethRpcModule>) -> Self {
174        Self::Selection(s.into_iter().collect())
175    }
176}
177
178impl<const N: usize> From<[RethRpcModule; N]> for RpcModuleSelection {
179    fn from(s: [RethRpcModule; N]) -> Self {
180        Self::Selection(s.iter().copied().collect())
181    }
182}
183
184impl<'a> FromIterator<&'a RethRpcModule> for RpcModuleSelection {
185    fn from_iter<I>(iter: I) -> Self
186    where
187        I: IntoIterator<Item = &'a RethRpcModule>,
188    {
189        iter.into_iter().copied().collect()
190    }
191}
192
193impl FromIterator<RethRpcModule> for RpcModuleSelection {
194    fn from_iter<I>(iter: I) -> Self
195    where
196        I: IntoIterator<Item = RethRpcModule>,
197    {
198        Self::Selection(iter.into_iter().collect())
199    }
200}
201
202impl FromStr for RpcModuleSelection {
203    type Err = ParseError;
204
205    fn from_str(s: &str) -> Result<Self, Self::Err> {
206        if s.is_empty() {
207            return Ok(Self::Selection(Default::default()))
208        }
209        let mut modules = s.split(',').map(str::trim).peekable();
210        let first = modules.peek().copied().ok_or(ParseError::VariantNotFound)?;
211        // We convert to lowercase to make the comparison case-insensitive
212        //
213        // This is a way to allow typing "all" and "ALL" and "All" and "aLl" etc.
214        match first.to_lowercase().as_str() {
215            "all" => Ok(Self::All),
216            "none" => Ok(Self::Selection(Default::default())),
217            _ => Self::try_from_selection(modules),
218        }
219    }
220}
221
222impl fmt::Display for RpcModuleSelection {
223    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224        write!(
225            f,
226            "[{}]",
227            self.iter_selection().map(|s| s.to_string()).collect::<Vec<_>>().join(", ")
228        )
229    }
230}
231
232/// Represents RPC modules that are supported by reth
233#[derive(
234    Debug,
235    Clone,
236    Copy,
237    Eq,
238    PartialEq,
239    Hash,
240    AsRefStr,
241    IntoStaticStr,
242    VariantNames,
243    VariantArray,
244    EnumIter,
245    Deserialize,
246)]
247#[serde(rename_all = "snake_case")]
248#[strum(serialize_all = "kebab-case")]
249pub enum RethRpcModule {
250    /// `admin_` module
251    Admin,
252    /// `debug_` module
253    Debug,
254    /// `eth_` module
255    Eth,
256    /// `net_` module
257    Net,
258    /// `trace_` module
259    Trace,
260    /// `txpool_` module
261    Txpool,
262    /// `web3_` module
263    Web3,
264    /// `rpc_` module
265    Rpc,
266    /// `reth_` module
267    Reth,
268    /// `ots_` module
269    Ots,
270    /// `flashbots_` module
271    Flashbots,
272    /// `miner_` module
273    Miner,
274    /// `mev_` module
275    Mev,
276}
277
278// === impl RethRpcModule ===
279
280impl RethRpcModule {
281    /// Returns the number of variants in the enum
282    pub const fn variant_count() -> usize {
283        <Self as VariantArray>::VARIANTS.len()
284    }
285
286    /// Returns all variant names of the enum
287    pub const fn all_variant_names() -> &'static [&'static str] {
288        <Self as VariantNames>::VARIANTS
289    }
290
291    /// Returns all variants of the enum
292    pub const fn all_variants() -> &'static [Self] {
293        <Self as VariantArray>::VARIANTS
294    }
295
296    /// Returns all variants of the enum
297    pub fn modules() -> impl IntoIterator<Item = Self> {
298        use strum::IntoEnumIterator;
299        Self::iter()
300    }
301
302    /// Returns the string representation of the module.
303    #[inline]
304    pub fn as_str(&self) -> &'static str {
305        self.into()
306    }
307}
308
309impl FromStr for RethRpcModule {
310    type Err = ParseError;
311
312    fn from_str(s: &str) -> Result<Self, Self::Err> {
313        Ok(match s {
314            "admin" => Self::Admin,
315            "debug" => Self::Debug,
316            "eth" => Self::Eth,
317            "net" => Self::Net,
318            "trace" => Self::Trace,
319            "txpool" => Self::Txpool,
320            "web3" => Self::Web3,
321            "rpc" => Self::Rpc,
322            "reth" => Self::Reth,
323            "ots" => Self::Ots,
324            "flashbots" => Self::Flashbots,
325            "miner" => Self::Miner,
326            "mev" => Self::Mev,
327            _ => return Err(ParseError::VariantNotFound),
328        })
329    }
330}
331
332impl TryFrom<&str> for RethRpcModule {
333    type Error = ParseError;
334    fn try_from(s: &str) -> Result<Self, <Self as TryFrom<&str>>::Error> {
335        FromStr::from_str(s)
336    }
337}
338
339impl fmt::Display for RethRpcModule {
340    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
341        f.pad(self.as_ref())
342    }
343}
344
345impl Serialize for RethRpcModule {
346    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
347    where
348        S: Serializer,
349    {
350        s.serialize_str(self.as_ref())
351    }
352}
353
354#[cfg(test)]
355mod test {
356    use super::*;
357
358    #[test]
359    fn test_all_modules() {
360        let all_modules = RpcModuleSelection::all_modules();
361        assert_eq!(all_modules.len(), RethRpcModule::variant_count());
362    }
363
364    #[test]
365    fn test_standard_modules() {
366        let standard_modules = RpcModuleSelection::standard_modules();
367        let expected_modules: HashSet<RethRpcModule> =
368            HashSet::from([RethRpcModule::Eth, RethRpcModule::Net, RethRpcModule::Web3]);
369        assert_eq!(standard_modules, expected_modules);
370    }
371
372    #[test]
373    fn test_default_ipc_modules() {
374        let default_ipc_modules = RpcModuleSelection::default_ipc_modules();
375        assert_eq!(default_ipc_modules, RpcModuleSelection::all_modules());
376    }
377
378    #[test]
379    fn test_try_from_selection_success() {
380        let selection = vec!["eth", "admin"];
381        let config = RpcModuleSelection::try_from_selection(selection).unwrap();
382        assert_eq!(config, RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]));
383    }
384
385    #[test]
386    fn test_rpc_module_selection_len() {
387        let all_modules = RpcModuleSelection::All;
388        let standard = RpcModuleSelection::Standard;
389        let selection = RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]);
390
391        assert_eq!(all_modules.len(), RethRpcModule::variant_count());
392        assert_eq!(standard.len(), 3);
393        assert_eq!(selection.len(), 2);
394    }
395
396    #[test]
397    fn test_rpc_module_selection_is_empty() {
398        let empty_selection = RpcModuleSelection::from(HashSet::new());
399        assert!(empty_selection.is_empty());
400
401        let non_empty_selection = RpcModuleSelection::from([RethRpcModule::Eth]);
402        assert!(!non_empty_selection.is_empty());
403    }
404
405    #[test]
406    fn test_rpc_module_selection_iter_selection() {
407        let all_modules = RpcModuleSelection::All;
408        let standard = RpcModuleSelection::Standard;
409        let selection = RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]);
410
411        assert_eq!(all_modules.iter_selection().count(), RethRpcModule::variant_count());
412        assert_eq!(standard.iter_selection().count(), 3);
413        assert_eq!(selection.iter_selection().count(), 2);
414    }
415
416    #[test]
417    fn test_rpc_module_selection_to_selection() {
418        let all_modules = RpcModuleSelection::All;
419        let standard = RpcModuleSelection::Standard;
420        let selection = RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]);
421
422        assert_eq!(all_modules.to_selection(), RpcModuleSelection::all_modules());
423        assert_eq!(standard.to_selection(), RpcModuleSelection::standard_modules());
424        assert_eq!(
425            selection.to_selection(),
426            HashSet::from([RethRpcModule::Eth, RethRpcModule::Admin])
427        );
428    }
429
430    #[test]
431    fn test_rpc_module_selection_are_identical() {
432        // Test scenario: both selections are `All`
433        //
434        // Since both selections include all possible RPC modules, they should be considered
435        // identical.
436        let all_modules = RpcModuleSelection::All;
437        assert!(RpcModuleSelection::are_identical(Some(&all_modules), Some(&all_modules)));
438
439        // Test scenario: both `http` and `ws` are `None`
440        //
441        // When both arguments are `None`, the function should return `true` because no modules are
442        // selected.
443        assert!(RpcModuleSelection::are_identical(None, None));
444
445        // Test scenario: both selections contain identical sets of specific modules
446        //
447        // In this case, both selections contain the same modules (`Eth` and `Admin`),
448        // so they should be considered identical.
449        let selection1 = RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]);
450        let selection2 = RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]);
451        assert!(RpcModuleSelection::are_identical(Some(&selection1), Some(&selection2)));
452
453        // Test scenario: one selection is `All`, the other is `Standard`
454        //
455        // `All` includes all possible modules, while `Standard` includes a specific set of modules.
456        // Since `Standard` does not cover all modules, these two selections should not be
457        // considered identical.
458        let standard = RpcModuleSelection::Standard;
459        assert!(!RpcModuleSelection::are_identical(Some(&all_modules), Some(&standard)));
460
461        // Test scenario: one is `None`, the other is an empty selection
462        //
463        // When one selection is `None` and the other is an empty selection (no modules),
464        // they should be considered identical because neither selects any modules.
465        let empty_selection = RpcModuleSelection::Selection(HashSet::new());
466        assert!(RpcModuleSelection::are_identical(None, Some(&empty_selection)));
467        assert!(RpcModuleSelection::are_identical(Some(&empty_selection), None));
468
469        // Test scenario: one is `None`, the other is a non-empty selection
470        //
471        // If one selection is `None` and the other contains modules, they should not be considered
472        // identical because `None` represents no selection, while the other explicitly
473        // selects modules.
474        let non_empty_selection = RpcModuleSelection::from([RethRpcModule::Eth]);
475        assert!(!RpcModuleSelection::are_identical(None, Some(&non_empty_selection)));
476        assert!(!RpcModuleSelection::are_identical(Some(&non_empty_selection), None));
477
478        // Test scenario: `All` vs. non-full selection
479        //
480        // If one selection is `All` (which includes all modules) and the other contains only a
481        // subset of modules, they should not be considered identical.
482        let partial_selection = RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Net]);
483        assert!(!RpcModuleSelection::are_identical(Some(&all_modules), Some(&partial_selection)));
484
485        // Test scenario: full selection vs `All`
486        //
487        // If the other selection explicitly selects all available modules, it should be identical
488        // to `All`.
489        let full_selection =
490            RpcModuleSelection::from(RethRpcModule::modules().into_iter().collect::<HashSet<_>>());
491        assert!(RpcModuleSelection::are_identical(Some(&all_modules), Some(&full_selection)));
492
493        // Test scenario: different non-empty selections
494        //
495        // If the two selections contain different sets of modules, they should not be considered
496        // identical.
497        let selection3 = RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Net]);
498        let selection4 = RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Web3]);
499        assert!(!RpcModuleSelection::are_identical(Some(&selection3), Some(&selection4)));
500
501        // Test scenario: `Standard` vs an equivalent selection
502        // The `Standard` selection includes a predefined set of modules. If we explicitly create
503        // a selection with the same set of modules, they should be considered identical.
504        let matching_standard =
505            RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Net, RethRpcModule::Web3]);
506        assert!(RpcModuleSelection::are_identical(Some(&standard), Some(&matching_standard)));
507
508        // Test scenario: `Standard` vs non-matching selection
509        //
510        // If the selection does not match the modules included in `Standard`, they should not be
511        // considered identical.
512        let non_matching_standard =
513            RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Net]);
514        assert!(!RpcModuleSelection::are_identical(Some(&standard), Some(&non_matching_standard)));
515    }
516
517    #[test]
518    fn test_rpc_module_selection_from_str() {
519        // Test empty string returns default selection
520        let result = RpcModuleSelection::from_str("");
521        assert!(result.is_ok());
522        assert_eq!(result.unwrap(), RpcModuleSelection::Selection(Default::default()));
523
524        // Test "all" (case insensitive) returns All variant
525        let result = RpcModuleSelection::from_str("all");
526        assert!(result.is_ok());
527        assert_eq!(result.unwrap(), RpcModuleSelection::All);
528
529        let result = RpcModuleSelection::from_str("All");
530        assert!(result.is_ok());
531        assert_eq!(result.unwrap(), RpcModuleSelection::All);
532
533        let result = RpcModuleSelection::from_str("ALL");
534        assert!(result.is_ok());
535        assert_eq!(result.unwrap(), RpcModuleSelection::All);
536
537        // Test "none" (case insensitive) returns empty selection
538        let result = RpcModuleSelection::from_str("none");
539        assert!(result.is_ok());
540        assert_eq!(result.unwrap(), RpcModuleSelection::Selection(Default::default()));
541
542        let result = RpcModuleSelection::from_str("None");
543        assert!(result.is_ok());
544        assert_eq!(result.unwrap(), RpcModuleSelection::Selection(Default::default()));
545
546        let result = RpcModuleSelection::from_str("NONE");
547        assert!(result.is_ok());
548        assert_eq!(result.unwrap(), RpcModuleSelection::Selection(Default::default()));
549
550        // Test valid selections: "eth,admin"
551        let result = RpcModuleSelection::from_str("eth,admin");
552        assert!(result.is_ok());
553        let expected_selection =
554            RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]);
555        assert_eq!(result.unwrap(), expected_selection);
556
557        // Test valid selection with extra spaces: " eth , admin "
558        let result = RpcModuleSelection::from_str(" eth , admin ");
559        assert!(result.is_ok());
560        assert_eq!(result.unwrap(), expected_selection);
561
562        // Test invalid selection should return error
563        let result = RpcModuleSelection::from_str("invalid,unknown");
564        assert!(result.is_err());
565        assert_eq!(result.unwrap_err(), ParseError::VariantNotFound);
566
567        // Test single valid selection: "eth"
568        let result = RpcModuleSelection::from_str("eth");
569        assert!(result.is_ok());
570        let expected_selection = RpcModuleSelection::from([RethRpcModule::Eth]);
571        assert_eq!(result.unwrap(), expected_selection);
572
573        // Test single invalid selection: "unknown"
574        let result = RpcModuleSelection::from_str("unknown");
575        assert!(result.is_err());
576        assert_eq!(result.unwrap_err(), ParseError::VariantNotFound);
577    }
578}