cfx_rpc_builder/eth/
mod.rs

1// Copyright 2023-2024 Paradigm.xyz
2// This file is part of reth.
3// Reth is a modular, contributor-friendly and blazing-fast implementation of
4// the Ethereum protocol
5
6// Permission is hereby granted, free of charge, to any
7// person obtaining a copy of this software and associated
8// documentation files (the "Software"), to deal in the
9// Software without restriction, including without
10// limitation the rights to use, copy, modify, merge,
11// publish, distribute, sublicense, and/or sell copies of
12// the Software, and to permit persons to whom the Software
13// is furnished to do so, subject to the following
14// conditions:
15
16// The above copyright notice and this permission notice
17// shall be included in all copies or substantial portions
18// of the Software.
19
20// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
21// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
22// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
23// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
24// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
25// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
27// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
28// DEALINGS IN THE SOFTWARE.
29#![allow(unused)]
30mod module;
31
32pub use crate::{
33    error::*, id_provider::SubscriptionIdProvider, RpcServerHandle,
34};
35use cfx_rpc_middlewares::{
36    load_throttling_manager, maybe_cors_layer, Logger, Metrics, Throttle,
37};
38pub use module::{EthRpcModule, RpcModuleSelection};
39
40use cfx_rpc_cfx_types::RpcImplConfiguration;
41use cfx_rpc_eth_api::*;
42use cfx_rpc_eth_impl::{helpers::ChainInfo, *};
43use cfx_tasks::TaskExecutor;
44use cfxcore::{
45    Notifications, SharedConsensusGraph, SharedSynchronizationService,
46    SharedTransactionPool,
47};
48pub use jsonrpsee::server::ServerBuilder;
49use jsonrpsee::{
50    core::RegisterMethodError,
51    server::{
52        middleware::rpc::RpcServiceBuilder, AlreadyStoppedError, IdProvider,
53        ServerConfigBuilder, ServerHandle,
54    },
55    Methods, RpcModule,
56};
57use std::{
58    collections::HashMap,
59    net::{Ipv4Addr, SocketAddr, SocketAddrV4},
60    sync::Arc,
61};
62
63pub const DEFAULT_HTTP_PORT: u16 = 8545;
64pub const DEFAULT_WS_PORT: u16 = 8546;
65
66/// A builder type to configure the RPC module: See [`RpcModule`]
67///
68/// This is the main entrypoint and the easiest way to configure an RPC server.
69#[derive(Clone)]
70pub struct RpcModuleBuilder {
71    config: RpcImplConfiguration,
72    consensus: SharedConsensusGraph,
73    sync: SharedSynchronizationService,
74    tx_pool: SharedTransactionPool,
75    executor: TaskExecutor,
76    notifications: Arc<Notifications>,
77}
78
79impl RpcModuleBuilder {
80    pub fn new(
81        config: RpcImplConfiguration, consensus: SharedConsensusGraph,
82        sync: SharedSynchronizationService, tx_pool: SharedTransactionPool,
83        executor: TaskExecutor, notifications: Arc<Notifications>,
84    ) -> Self {
85        Self {
86            config,
87            consensus,
88            sync,
89            tx_pool,
90            executor,
91            notifications,
92        }
93    }
94
95    /// Configures all [`RpcModule`]s specific to the given
96    /// [`TransportRpcModuleConfig`] which can be used to start the
97    /// transport server(s).
98    pub fn build(
99        self, module_config: TransportRpcModuleConfig,
100    ) -> TransportRpcModules<()> {
101        let mut modules = TransportRpcModules::default();
102
103        if !module_config.is_empty() {
104            let TransportRpcModuleConfig { http, ws } = module_config.clone();
105
106            let Self {
107                config,
108                consensus,
109                sync,
110                tx_pool,
111                executor,
112                notifications,
113            } = self;
114
115            let mut registry = RpcRegistryInner::new(
116                config,
117                consensus,
118                sync,
119                tx_pool,
120                executor,
121                notifications,
122            );
123
124            modules.config = module_config;
125            modules.http = registry.maybe_module(http.as_ref());
126            modules.ws = registry.maybe_module(ws.as_ref());
127        }
128
129        modules
130    }
131}
132
133/// A Helper type the holds instances of the configured modules.
134#[derive(Clone)]
135pub struct RpcRegistryInner {
136    consensus: SharedConsensusGraph,
137    config: RpcImplConfiguration,
138    sync: SharedSynchronizationService,
139    tx_pool: SharedTransactionPool,
140    modules: HashMap<EthRpcModule, Methods>,
141    executor: TaskExecutor,
142    notifications: Arc<Notifications>,
143}
144
145impl RpcRegistryInner {
146    pub fn new(
147        config: RpcImplConfiguration, consensus: SharedConsensusGraph,
148        sync: SharedSynchronizationService, tx_pool: SharedTransactionPool,
149        executor: TaskExecutor, notifications: Arc<Notifications>,
150    ) -> Self {
151        Self {
152            consensus,
153            config,
154            sync,
155            tx_pool,
156            modules: Default::default(),
157            executor,
158            notifications,
159        }
160    }
161
162    /// Returns all installed methods
163    pub fn methods(&self) -> Vec<Methods> {
164        self.modules.values().cloned().collect()
165    }
166
167    /// Returns a merged `RpcModule`
168    pub fn module(&self) -> RpcModule<()> {
169        let mut module = RpcModule::new(());
170        for methods in self.modules.values().cloned() {
171            module.merge(methods).expect("No conflicts");
172        }
173        module
174    }
175}
176
177impl RpcRegistryInner {
178    pub fn web3_api(&self) -> Web3Api { Web3Api }
179
180    pub fn register_web3(&mut self) -> &mut Self {
181        let web3api = self.web3_api();
182        self.modules
183            .insert(EthRpcModule::Web3, web3api.into_rpc().into());
184        self
185    }
186
187    pub fn trace_api(&self) -> TraceApi {
188        TraceApi::new(
189            self.consensus.clone(),
190            self.sync.network.get_network_type().clone(),
191            self.config.max_estimation_gas_limit,
192        )
193    }
194
195    pub fn debug_api(&self) -> DebugApi {
196        DebugApi::new(
197            self.consensus.clone(),
198            self.config.max_estimation_gas_limit,
199        )
200    }
201
202    pub fn net_api(&self) -> NetApi {
203        NetApi::new(Box::new(ChainInfo::new(self.consensus.clone())))
204    }
205
206    /// Helper function to create a [`RpcModule`] if it's not `None`
207    fn maybe_module(
208        &mut self, config: Option<&RpcModuleSelection>,
209    ) -> Option<RpcModule<()>> {
210        config.map(|config| self.module_for(config))
211    }
212
213    /// Populates a new [`RpcModule`] based on the selected [`EthRpcModule`]s in
214    /// the given [`RpcModuleSelection`]
215    pub fn module_for(&mut self, config: &RpcModuleSelection) -> RpcModule<()> {
216        let mut module = RpcModule::new(());
217        let all_methods = self.eth_methods(config.iter_selection());
218        for methods in all_methods {
219            module.merge(methods).expect("No conflicts");
220        }
221        module
222    }
223
224    pub fn eth_methods(
225        &mut self, namespaces: impl Iterator<Item = EthRpcModule>,
226    ) -> Vec<Methods> {
227        let namespaces: Vec<_> = namespaces.collect();
228        let module_version = namespaces
229            .iter()
230            .map(|module| (module.to_string(), "1.0".to_string()))
231            .collect::<HashMap<String, String>>();
232
233        let namespace_methods = |namespace| {
234            self.modules
235                .entry(namespace)
236                .or_insert_with(|| match namespace {
237                    EthRpcModule::Debug => DebugApi::new(
238                        self.consensus.clone(),
239                        self.config.max_estimation_gas_limit,
240                    )
241                    .into_rpc()
242                    .into(),
243                    EthRpcModule::Eth => {
244                        let mut module = EthApi::new(
245                            self.config.clone(),
246                            self.consensus.clone(),
247                            self.sync.clone(),
248                            self.tx_pool.clone(),
249                            self.executor.clone(),
250                        )
251                        .into_rpc();
252                        if self.config.poll_lifetime_in_seconds.is_some() {
253                            let filter_module = EthFilterApi::new(
254                                self.consensus.clone(),
255                                self.tx_pool.clone(),
256                                self.notifications.epochs_ordered.clone(),
257                                self.executor.clone(),
258                                self.config.poll_lifetime_in_seconds.unwrap(),
259                                self.config.get_logs_filter_max_limit,
260                            )
261                            .into_rpc();
262                            module.merge(filter_module).expect("No conflicts");
263                        }
264                        module.into()
265                    }
266                    EthRpcModule::Net => NetApi::new(Box::new(ChainInfo::new(
267                        self.consensus.clone(),
268                    )))
269                    .into_rpc()
270                    .into(),
271                    EthRpcModule::Trace => TraceApi::new(
272                        self.consensus.clone(),
273                        self.sync.network.get_network_type().clone(),
274                        self.config.max_estimation_gas_limit,
275                    )
276                    .into_rpc()
277                    .into(),
278                    EthRpcModule::Web3 => Web3Api.into_rpc().into(),
279                    EthRpcModule::Rpc => {
280                        RPCApi::new(module_version.clone()).into_rpc().into()
281                    }
282                    EthRpcModule::Parity => {
283                        let eth_api = EthApi::new(
284                            self.config.clone(),
285                            self.consensus.clone(),
286                            self.sync.clone(),
287                            self.tx_pool.clone(),
288                            self.executor.clone(),
289                        );
290                        ParityApi::new(eth_api).into_rpc().into()
291                    }
292                    EthRpcModule::Txpool => {
293                        TxPoolApi::new(self.tx_pool.clone()).into_rpc().into()
294                    }
295                    EthRpcModule::PubSub => PubSubApi::new(
296                        self.consensus.clone(),
297                        self.notifications.clone(),
298                        self.executor.clone(),
299                    )
300                    .into_rpc()
301                    .into(),
302                })
303                .clone()
304        };
305
306        namespaces
307            .iter()
308            .copied()
309            .map(namespace_methods)
310            .collect::<Vec<_>>()
311    }
312}
313
314/// A builder type for configuring and launching the servers that will handle
315/// RPC requests.
316///
317/// Supported server transports are:
318///    - http
319///    - ws
320///
321/// Http and WS share the same settings: [`ServerBuilder`].
322///
323/// Once the [`RpcModule`] is built via [`RpcModuleBuilder`] the servers can be
324/// started, See also [`ServerBuilder::build`] and
325/// [`Server::start`](jsonrpsee::server::Server::start).
326#[derive(Debug)]
327pub struct RpcServerConfig {
328    /// Configs for JSON-RPC Http.
329    http_server_config: Option<ServerConfigBuilder>,
330    /// Allowed CORS Domains for http
331    http_cors_domains: Option<String>,
332    /// Address where to bind the http server to
333    http_addr: Option<SocketAddr>,
334    /// Configs for WS server
335    ws_server_config: Option<ServerConfigBuilder>,
336    /// Allowed CORS Domains for ws.
337    ws_cors_domains: Option<String>,
338    /// Address where to bind the ws server to
339    ws_addr: Option<SocketAddr>,
340}
341
342impl Default for RpcServerConfig {
343    fn default() -> Self {
344        Self {
345            http_server_config: None,
346            http_cors_domains: None,
347            http_addr: None,
348            ws_server_config: None,
349            ws_cors_domains: None,
350            ws_addr: None,
351            // rpc_middleware: RpcServiceBuilder::new(),
352        }
353    }
354}
355
356impl RpcServerConfig {
357    /// Creates a new config with only http set
358    pub fn http(config: ServerConfigBuilder) -> Self {
359        Self::default().with_http(config)
360    }
361
362    /// Creates a new config with only ws set
363    pub fn ws(config: ServerConfigBuilder) -> Self {
364        Self::default().with_ws(config)
365    }
366
367    /// Configures the http server
368    ///
369    /// Note: this always configures an [`SubscriptionIdProvider`]
370    /// [`IdProvider`] for convenience. To set a custom [`IdProvider`],
371    /// please use [`Self::with_id_provider`].
372    pub fn with_http(mut self, config: ServerConfigBuilder) -> Self {
373        self.http_server_config =
374            Some(config.set_id_provider(SubscriptionIdProvider::default()));
375        self
376    }
377
378    /// Configures the ws server
379    ///
380    /// Note: this always configures an [`SubscriptionIdProvider`]
381    /// [`IdProvider`] for convenience. To set a custom [`IdProvider`],
382    /// please use [`Self::with_id_provider`].
383    pub fn with_ws(mut self, config: ServerConfigBuilder) -> Self {
384        self.ws_server_config =
385            Some(config.set_id_provider(SubscriptionIdProvider::default()));
386        self
387    }
388}
389
390impl RpcServerConfig {
391    /// Configure the cors domains for http _and_ ws
392    pub fn with_cors(self, cors_domain: Option<String>) -> Self {
393        self.with_http_cors(cors_domain.clone())
394            .with_ws_cors(cors_domain)
395    }
396
397    /// Configure the cors domains for WS
398    pub fn with_ws_cors(mut self, cors_domain: Option<String>) -> Self {
399        self.ws_cors_domains = cors_domain;
400        self
401    }
402
403    /// Configure the cors domains for HTTP
404    pub fn with_http_cors(mut self, cors_domain: Option<String>) -> Self {
405        self.http_cors_domains = cors_domain;
406        self
407    }
408
409    /// Configures the [`SocketAddr`] of the http server
410    ///
411    /// Default is [`Ipv4Addr::LOCALHOST`] and
412    pub const fn with_http_address(mut self, addr: SocketAddr) -> Self {
413        self.http_addr = Some(addr);
414        self
415    }
416
417    /// Configures the [`SocketAddr`] of the ws server
418    ///
419    /// Default is [`Ipv4Addr::LOCALHOST`] and
420    pub const fn with_ws_address(mut self, addr: SocketAddr) -> Self {
421        self.ws_addr = Some(addr);
422        self
423    }
424
425    /// Sets a custom [`IdProvider`] for all configured transports.
426    ///
427    /// By default all transports use [`EthSubscriptionIdProvider`]
428    pub fn with_id_provider<I>(mut self, id_provider: I) -> Self
429    where I: IdProvider + Clone + 'static {
430        if let Some(http) = self.http_server_config {
431            self.http_server_config =
432                Some(http.set_id_provider(id_provider.clone()));
433        }
434        if let Some(ws) = self.ws_server_config {
435            self.ws_server_config =
436                Some(ws.set_id_provider(id_provider.clone()));
437        }
438
439        self
440    }
441
442    /// Returns true if any server is configured.
443    ///
444    /// If no server is configured, no server will be launched on
445    /// [`RpcServerConfig::start`].
446    pub const fn has_server(&self) -> bool {
447        self.http_server_config.is_some() || self.ws_server_config.is_some()
448    }
449
450    /// Returns the [`SocketAddr`] of the http server
451    pub const fn http_address(&self) -> Option<SocketAddr> { self.http_addr }
452
453    /// Returns the [`SocketAddr`] of the ws server
454    pub const fn ws_address(&self) -> Option<SocketAddr> { self.ws_addr }
455
456    // Builds and starts the configured server(s): http, ws, ipc.
457    //
458    // If both http and ws are on the same port, they are combined into one
459    // server.
460    //
461    // Returns the [`RpcServerHandle`] with the handle to the started servers.
462    pub async fn start(
463        self, modules: &TransportRpcModules,
464        throttling_conf_file: Option<String>, enable_metrics: bool,
465    ) -> Result<RpcServerHandle, RpcError> {
466        // No server to build: skip loading (and maybe panicking on) the conf.
467        if !self.has_server() {
468            return Ok(RpcServerHandle {
469                http_local_addr: None,
470                ws_local_addr: None,
471                http: None,
472                ws: None,
473            });
474        }
475
476        let throttle_manager =
477            load_throttling_manager(throttling_conf_file.as_deref(), "rpc");
478        let rpc_middleware = RpcServiceBuilder::new()
479            .layer_fn(move |s| Throttle::new(throttle_manager.clone(), s))
480            .layer_fn(move |s| Metrics::new(s, enable_metrics))
481            .layer_fn(|s| Logger::new(s));
482
483        let http_socket_addr = self.http_addr.unwrap_or(SocketAddr::V4(
484            SocketAddrV4::new(Ipv4Addr::LOCALHOST, DEFAULT_HTTP_PORT),
485        ));
486
487        let ws_socket_addr = self.ws_addr.unwrap_or(SocketAddr::V4(
488            SocketAddrV4::new(Ipv4Addr::LOCALHOST, DEFAULT_WS_PORT),
489        ));
490
491        // If both are configured on the same port, we combine them into one
492        // server.
493        if self.http_addr == self.ws_addr
494            && self.http_server_config.is_some()
495            && self.ws_server_config.is_some()
496        {
497            // we merge this into one server using the http setup
498            modules.config.ensure_ws_http_identical()?;
499
500            let cors = match (
501                self.ws_cors_domains.as_ref(),
502                self.http_cors_domains.as_ref(),
503            ) {
504                (Some(ws_cors), Some(http_cors)) => {
505                    if ws_cors.trim() != http_cors.trim() {
506                        return Err(
507                            WsHttpSamePortError::ConflictingCorsDomains {
508                                http_cors_domains: Some(http_cors.clone()),
509                                ws_cors_domains: Some(ws_cors.clone()),
510                            }
511                            .into(),
512                        );
513                    }
514                    Some(ws_cors)
515                }
516                (a, b) => a.or(b),
517            }
518            .cloned();
519
520            if let Some(config) = self.http_server_config {
521                let server = ServerBuilder::new()
522                    .set_http_middleware(
523                        tower::ServiceBuilder::new()
524                            .option_layer(maybe_cors_layer(cors)?),
525                    )
526                    .set_rpc_middleware(rpc_middleware)
527                    .set_config(config.build())
528                    .build(http_socket_addr)
529                    .await
530                    .map_err(|err| {
531                        RpcError::server_error(
532                            err,
533                            ServerKind::WsHttp(http_socket_addr),
534                        )
535                    })?;
536                let addr = server.local_addr().map_err(|err| {
537                    RpcError::server_error(
538                        err,
539                        ServerKind::WsHttp(http_socket_addr),
540                    )
541                })?;
542                if let Some(module) =
543                    modules.http.as_ref().or(modules.ws.as_ref())
544                {
545                    let handle = server.start(module.clone());
546                    let http_handle = Some(handle.clone());
547                    let ws_handle = Some(handle);
548
549                    return Ok(RpcServerHandle {
550                        http_local_addr: Some(addr),
551                        ws_local_addr: Some(addr),
552                        http: http_handle,
553                        ws: ws_handle,
554                    });
555                }
556
557                return Err(RpcError::Custom(
558                    "No valid RpcModule found from modules".to_string(),
559                ));
560            }
561        }
562
563        let mut result = RpcServerHandle {
564            http_local_addr: None,
565            ws_local_addr: None,
566            http: None,
567            ws: None,
568        };
569        if let Some(config) = self.ws_server_config {
570            let server = ServerBuilder::new()
571                .set_config(config.ws_only().build())
572                .set_http_middleware(tower::ServiceBuilder::new().option_layer(
573                    maybe_cors_layer(self.ws_cors_domains.clone())?,
574                ))
575                .set_rpc_middleware(rpc_middleware.clone())
576                .build(ws_socket_addr)
577                .await
578                .map_err(|err| {
579                    RpcError::server_error(err, ServerKind::WS(ws_socket_addr))
580                })?;
581
582            let addr = server.local_addr().map_err(|err| {
583                RpcError::server_error(err, ServerKind::WS(ws_socket_addr))
584            })?;
585
586            let ws_local_addr = Some(addr);
587            let ws_server = Some(server);
588            let ws_handle = ws_server.map(|ws_server| {
589                ws_server.start(modules.ws.clone().expect("ws server error"))
590            });
591
592            result.ws = ws_handle;
593            result.ws_local_addr = ws_local_addr;
594        }
595
596        if let Some(config) = self.http_server_config {
597            let server = ServerBuilder::new()
598                .set_config(config.http_only().build())
599                .set_http_middleware(tower::ServiceBuilder::new().option_layer(
600                    maybe_cors_layer(self.http_cors_domains.clone())?,
601                ))
602                .set_rpc_middleware(rpc_middleware)
603                .build(http_socket_addr)
604                .await
605                .map_err(|err| {
606                    RpcError::server_error(
607                        err,
608                        ServerKind::Http(http_socket_addr),
609                    )
610                })?;
611            let local_addr = server.local_addr().map_err(|err| {
612                RpcError::server_error(err, ServerKind::Http(http_socket_addr))
613            })?;
614            let http_local_addr = Some(local_addr);
615            let http_server = Some(server);
616            let http_handle = http_server.map(|http_server| {
617                http_server
618                    .start(modules.http.clone().expect("http server error"))
619            });
620
621            result.http = http_handle;
622            result.http_local_addr = http_local_addr;
623        }
624
625        Ok(result)
626    }
627}
628
629/// Holds modules to be installed per transport type
630#[derive(Debug, Clone, Default, Eq, PartialEq)]
631pub struct TransportRpcModuleConfig {
632    /// http module configuration
633    http: Option<RpcModuleSelection>,
634    /// ws module configuration
635    ws: Option<RpcModuleSelection>,
636}
637
638impl TransportRpcModuleConfig {
639    /// Creates a new config with only http set
640    pub fn set_http(http: impl Into<RpcModuleSelection>) -> Self {
641        Self::default().with_http(http)
642    }
643
644    /// Creates a new config with only ws set
645    pub fn set_ws(ws: impl Into<RpcModuleSelection>) -> Self {
646        Self::default().with_ws(ws)
647    }
648
649    /// Sets the [`RpcModuleSelection`] for the http transport.
650    pub fn with_http(mut self, http: impl Into<RpcModuleSelection>) -> Self {
651        self.http = Some(http.into());
652        self
653    }
654
655    /// Sets the [`RpcModuleSelection`] for the ws transport.
656    pub fn with_ws(mut self, ws: impl Into<RpcModuleSelection>) -> Self {
657        self.ws = Some(ws.into());
658        self
659    }
660
661    /// Get a mutable reference to the
662    pub fn http_mut(&mut self) -> &mut Option<RpcModuleSelection> {
663        &mut self.http
664    }
665
666    /// Get a mutable reference to the
667    pub fn ws_mut(&mut self) -> &mut Option<RpcModuleSelection> { &mut self.ws }
668
669    /// Returns true if no transports are configured
670    pub const fn is_empty(&self) -> bool {
671        self.http.is_none() && self.ws.is_none()
672    }
673
674    /// Returns the [`RpcModuleSelection`] for the http transport
675    pub const fn http(&self) -> Option<&RpcModuleSelection> {
676        self.http.as_ref()
677    }
678
679    /// Returns the [`RpcModuleSelection`] for the ws transport
680    pub const fn ws(&self) -> Option<&RpcModuleSelection> { self.ws.as_ref() }
681
682    /// Ensures that both http and ws are configured and that they are
683    /// configured to use the same port.
684    fn ensure_ws_http_identical(&self) -> Result<(), WsHttpSamePortError> {
685        if RpcModuleSelection::are_identical(
686            self.http.as_ref(),
687            self.ws.as_ref(),
688        ) {
689            Ok(())
690        } else {
691            let http_modules = self
692                .http
693                .as_ref()
694                .map(RpcModuleSelection::to_selection)
695                .unwrap_or_default();
696            let ws_modules = self
697                .ws
698                .as_ref()
699                .map(RpcModuleSelection::to_selection)
700                .unwrap_or_default();
701
702            let http_not_ws =
703                http_modules.difference(&ws_modules).copied().collect();
704            let ws_not_http =
705                ws_modules.difference(&http_modules).copied().collect();
706            let overlap =
707                http_modules.intersection(&ws_modules).copied().collect();
708
709            Err(WsHttpSamePortError::ConflictingModules(Box::new(
710                ConflictingModules {
711                    overlap,
712                    http_not_ws,
713                    ws_not_http,
714                },
715            )))
716        }
717    }
718}
719
720/// Holds installed modules per transport type.
721#[derive(Debug, Clone, Default)]
722pub struct TransportRpcModules<Context = ()> {
723    /// The original config
724    config: TransportRpcModuleConfig,
725    /// rpcs module for http
726    http: Option<RpcModule<Context>>,
727    /// rpcs module for ws
728    ws: Option<RpcModule<Context>>,
729}
730
731// === impl TransportRpcModules ===
732
733impl TransportRpcModules {
734    /// Returns the [`TransportRpcModuleConfig`] used to configure this
735    /// instance.
736    pub const fn module_config(&self) -> &TransportRpcModuleConfig {
737        &self.config
738    }
739
740    /// Merge the given [Methods] in the configured http methods.
741    ///
742    /// Fails if any of the methods in other is present already.
743    ///
744    /// Returns [Ok(false)] if no http transport is configured.
745    pub fn merge_http(
746        &mut self, other: impl Into<Methods>,
747    ) -> Result<bool, RegisterMethodError> {
748        if let Some(ref mut http) = self.http {
749            return http.merge(other.into()).map(|_| true);
750        }
751        Ok(false)
752    }
753
754    /// Merge the given [Methods] in the configured ws methods.
755    ///
756    /// Fails if any of the methods in other is present already.
757    ///
758    /// Returns [Ok(false)] if no ws transport is configured.
759    pub fn merge_ws(
760        &mut self, other: impl Into<Methods>,
761    ) -> Result<bool, RegisterMethodError> {
762        if let Some(ref mut ws) = self.ws {
763            return ws.merge(other.into()).map(|_| true);
764        }
765        Ok(false)
766    }
767
768    /// Merge the given [Methods] in all configured methods.
769    ///
770    /// Fails if any of the methods in other is present already.
771    pub fn merge_configured(
772        &mut self, other: impl Into<Methods>,
773    ) -> Result<(), RegisterMethodError> {
774        let other = other.into();
775        self.merge_http(other.clone())?;
776        self.merge_ws(other.clone())?;
777        Ok(())
778    }
779
780    /// Removes the method with the given name from the configured http methods.
781    ///
782    /// Returns `true` if the method was found and removed, `false` otherwise.
783    ///
784    /// Be aware that a subscription consist of two methods, `subscribe` and
785    /// `unsubscribe` and it's the caller responsibility to remove both
786    /// `subscribe` and `unsubscribe` methods for subscriptions.
787    pub fn remove_http_method(&mut self, method_name: &'static str) -> bool {
788        if let Some(http_module) = &mut self.http {
789            http_module.remove_method(method_name).is_some()
790        } else {
791            false
792        }
793    }
794
795    /// Removes the method with the given name from the configured ws methods.
796    ///
797    /// Returns `true` if the method was found and removed, `false` otherwise.
798    ///
799    /// Be aware that a subscription consist of two methods, `subscribe` and
800    /// `unsubscribe` and it's the caller responsibility to remove both
801    /// `subscribe` and `unsubscribe` methods for subscriptions.
802    pub fn remove_ws_method(&mut self, method_name: &'static str) -> bool {
803        if let Some(ws_module) = &mut self.ws {
804            ws_module.remove_method(method_name).is_some()
805        } else {
806            false
807        }
808    }
809
810    /// Removes the method with the given name from all configured transports.
811    ///
812    /// Returns `true` if the method was found and removed, `false` otherwise.
813    pub fn remove_method_from_configured(
814        &mut self, method_name: &'static str,
815    ) -> bool {
816        let http_removed = self.remove_http_method(method_name);
817        let ws_removed = self.remove_ws_method(method_name);
818
819        http_removed || ws_removed
820    }
821}