@@ -18,24 +18,32 @@ use reth_node_builder::{NodeBuilder, WithLaunchContext};
1818use reth_node_core:: { args:: LogArgs , version:: version_metadata} ;
1919use reth_node_ethereum:: { consensus:: EthBeaconConsensus , EthEvmConfig , EthereumNode } ;
2020use reth_node_metrics:: recorder:: install_prometheus_recorder;
21+ use reth_rpc_server_types:: { DefaultRpcModuleValidator , RpcModuleValidator } ;
2122use reth_tracing:: FileWorkerGuard ;
22- use std:: { ffi:: OsString , fmt, future:: Future , sync:: Arc } ;
23+ use std:: { ffi:: OsString , fmt, future:: Future , marker :: PhantomData , sync:: Arc } ;
2324use tracing:: info;
2425
2526/// The main reth cli interface.
2627///
2728/// This is the entrypoint to the executable.
2829#[ derive( Debug , Parser ) ]
2930#[ command( author, version =version_metadata( ) . short_version. as_ref( ) , long_version = version_metadata( ) . long_version. as_ref( ) , about = "Reth" , long_about = None ) ]
30- pub struct Cli < C : ChainSpecParser = EthereumChainSpecParser , Ext : clap:: Args + fmt:: Debug = NoArgs >
31- {
31+ pub struct Cli <
32+ C : ChainSpecParser = EthereumChainSpecParser ,
33+ Ext : clap:: Args + fmt:: Debug = NoArgs ,
34+ Rpc : RpcModuleValidator = DefaultRpcModuleValidator ,
35+ > {
3236 /// The command to run
3337 #[ command( subcommand) ]
3438 pub command : Commands < C , Ext > ,
3539
3640 /// The logging configuration for the CLI.
3741 #[ command( flatten) ]
3842 pub logs : LogArgs ,
43+
44+ /// Type marker for the RPC module validator
45+ #[ arg( skip) ]
46+ pub _phantom : PhantomData < Rpc > ,
3947}
4048
4149impl Cli {
@@ -54,7 +62,7 @@ impl Cli {
5462 }
5563}
5664
57- impl < C : ChainSpecParser , Ext : clap:: Args + fmt:: Debug > Cli < C , Ext > {
65+ impl < C : ChainSpecParser , Ext : clap:: Args + fmt:: Debug , Rpc : RpcModuleValidator > Cli < C , Ext , Rpc > {
5866 /// Execute the configured cli command.
5967 ///
6068 /// This accepts a closure that is used to launch the node via the
@@ -190,9 +198,20 @@ impl<C: ChainSpecParser, Ext: clap::Args + fmt::Debug> Cli<C, Ext> {
190198 let _ = install_prometheus_recorder ( ) ;
191199
192200 match self . command {
193- Commands :: Node ( command) => runner. run_command_until_exit ( |ctx| {
194- command. execute ( ctx, FnLauncher :: new :: < C , Ext > ( launcher) )
195- } ) ,
201+ Commands :: Node ( command) => {
202+ // Validate RPC modules using the configured validator
203+ if let Some ( http_api) = & command. rpc . http_api {
204+ Rpc :: validate_selection ( http_api, "http.api" )
205+ . map_err ( |e| eyre:: eyre!( "{e}" ) ) ?;
206+ }
207+ if let Some ( ws_api) = & command. rpc . ws_api {
208+ Rpc :: validate_selection ( ws_api, "ws.api" ) . map_err ( |e| eyre:: eyre!( "{e}" ) ) ?;
209+ }
210+
211+ runner. run_command_until_exit ( |ctx| {
212+ command. execute ( ctx, FnLauncher :: new :: < C , Ext > ( launcher) )
213+ } )
214+ }
196215 Commands :: Init ( command) => runner. run_blocking_until_ctrl_c ( command. execute :: < N > ( ) ) ,
197216 Commands :: InitState ( command) => {
198217 runner. run_blocking_until_ctrl_c ( command. execute :: < N > ( ) )
@@ -417,4 +436,72 @@ mod tests {
417436 . unwrap ( ) ;
418437 assert ! ( reth. run( async move |_, _| Ok ( ( ) ) ) . is_ok( ) ) ;
419438 }
439+
440+ #[ test]
441+ fn test_rpc_module_validation ( ) {
442+ use reth_rpc_server_types:: RethRpcModule ;
443+
444+ // Test that standard modules are accepted
445+ let cli =
446+ Cli :: try_parse_args_from ( [ "reth" , "node" , "--http.api" , "eth,admin,debug" ] ) . unwrap ( ) ;
447+
448+ if let Commands :: Node ( command) = & cli. command {
449+ if let Some ( http_api) = & command. rpc . http_api {
450+ // Should contain the expected modules
451+ let modules = http_api. to_selection ( ) ;
452+ assert ! ( modules. contains( & RethRpcModule :: Eth ) ) ;
453+ assert ! ( modules. contains( & RethRpcModule :: Admin ) ) ;
454+ assert ! ( modules. contains( & RethRpcModule :: Debug ) ) ;
455+ } else {
456+ panic ! ( "Expected http.api to be set" ) ;
457+ }
458+ } else {
459+ panic ! ( "Expected Node command" ) ;
460+ }
461+
462+ // Test that unknown modules are parsed as Other variant
463+ let cli =
464+ Cli :: try_parse_args_from ( [ "reth" , "node" , "--http.api" , "eth,customrpc" ] ) . unwrap ( ) ;
465+
466+ if let Commands :: Node ( command) = & cli. command {
467+ if let Some ( http_api) = & command. rpc . http_api {
468+ let modules = http_api. to_selection ( ) ;
469+ assert ! ( modules. contains( & RethRpcModule :: Eth ) ) ;
470+ assert ! ( modules. contains( & RethRpcModule :: Other ( "customrpc" . to_string( ) ) ) ) ;
471+ } else {
472+ panic ! ( "Expected http.api to be set" ) ;
473+ }
474+ } else {
475+ panic ! ( "Expected Node command" ) ;
476+ }
477+ }
478+
479+ #[ test]
480+ fn test_rpc_module_unknown_rejected ( ) {
481+ use reth_cli_runner:: CliRunner ;
482+
483+ // Test that unknown module names are rejected during validation
484+ let cli =
485+ Cli :: try_parse_args_from ( [ "reth" , "node" , "--http.api" , "unknownmodule" ] ) . unwrap ( ) ;
486+
487+ // When we try to run the CLI with validation, it should fail
488+ let runner = CliRunner :: try_default_runtime ( ) . unwrap ( ) ;
489+ let result = cli. with_runner ( runner, |_, _| async { Ok ( ( ) ) } ) ;
490+
491+ assert ! ( result. is_err( ) ) ;
492+ let err = result. unwrap_err ( ) ;
493+ let err_msg = err. to_string ( ) ;
494+
495+ // The error should mention it's an unknown module
496+ assert ! (
497+ err_msg. contains( "Unknown RPC module" ) ,
498+ "Error should mention unknown module: {}" ,
499+ err_msg
500+ ) ;
501+ assert ! (
502+ err_msg. contains( "'unknownmodule'" ) ,
503+ "Error should mention the module name: {}" ,
504+ err_msg
505+ ) ;
506+ }
420507}
0 commit comments