diff --git a/masq_lib/src/shared_schema.rs b/masq_lib/src/shared_schema.rs index 11dfb865f..9f853d450 100644 --- a/masq_lib/src/shared_schema.rs +++ b/masq_lib/src/shared_schema.rs @@ -7,7 +7,7 @@ use crate::constants::{ POLYGON_MAINNET_FULL_IDENTIFIER, }; use crate::crash_point::CrashPoint; -use clap::{App, Arg}; +use clap::{arg_enum, App, Arg}; use lazy_static::lazy_static; pub const BLOCKCHAIN_SERVICE_HELP: &str = @@ -77,6 +77,17 @@ pub const NEIGHBORS_HELP: &str = "One or more Node descriptors for running Nodes if you don't specify a neighbor, your Node will start without being connected to any MASQ \ Network, although other Nodes will be able to connect to yours if they know your Node's descriptor. \ --neighbors is meaningless in --neighborhood-mode zero-hop."; +pub const NEW_PUBLIC_KEY_HELP: &str = "Whenever you start it, the Node will try to use the same public key \ + it used last time. That's '--new-public-key off'. If you want it to select a new public key when it \ + starts, then specify '--new-public-key on', and you'll get a different one this time...which it will \ + reuse next time unless you specify '--new-public-key on' again.\n\n\ + You should be careful about restarting your Node with the same public key too quickly. If your new \ + Node tries to join the Network before the Network has forgotten your old Node, every Node you try \ + to connect to will ignore you.\n\n\ + There are some conditions under which the Node cannot use the same public key it used last time: \ + for example, if there was no last time, or if you don't specify a `--db-password`. Normally, in \ + these situations, the Node will select a new public key and store it for future use; but if you \ + explicitly demand the old public key with `--new-public-key off`, the Node will refuse to start."; // generated valid encoded keys for future needs // UJNoZW5p/PDVqEjpr3b+8jZ/93yPG8i5dOAgE1bhK+A @@ -222,6 +233,14 @@ lazy_static! { DEFAULT_GAS_PRICE); } +arg_enum! { + #[derive(Debug, PartialEq, Eq, Clone, Copy)] + pub enum OnOff { + On, + Off, + } +} + // These Args are needed in more than one clap schema. To avoid code duplication, they're defined here and referred // to from multiple places. pub fn chain_arg<'a>() -> Arg<'a, 'a> { @@ -463,13 +482,23 @@ pub fn shared_app(head: App<'static, 'static>) -> App<'static, 'static> { .min_values(0) .help(NEIGHBORS_HELP), ) + .arg( + Arg::with_name("new-public-key") + .long("new-public-key") + .value_name("NEW-PUBLIC-KEY") + .takes_value(true) + .possible_values(&OnOff::variants()) + .case_insensitive(true) + .help(NEW_PUBLIC_KEY_HELP), + ) .arg(real_user_arg()) .arg( Arg::with_name("scans") .long("scans") .value_name("SCANS") .takes_value(true) - .possible_values(&["on", "off"]) + .possible_values(&OnOff::variants()) + .case_insensitive(true) .help(SCANS_HELP), ) .arg(common_parameter_with_separate_u64_values( diff --git a/node/src/actor_system_factory.rs b/node/src/actor_system_factory.rs index 25e20aa7e..9c9f05a04 100644 --- a/node/src/actor_system_factory.rs +++ b/node/src/actor_system_factory.rs @@ -1140,6 +1140,7 @@ mod tests { data_directory: PathBuf::new(), node_descriptor: NodeDescriptor::default(), mapping_protocol_opt: None, + new_public_key_opt: None, real_user: RealUser::null(), neighborhood_config: NeighborhoodConfig { mode: NeighborhoodMode::Standard( @@ -1211,6 +1212,7 @@ mod tests { cryptde_pair: CRYPTDE_PAIR.clone(), mapping_protocol_opt: Some(Igdp), real_user: RealUser::null(), + new_public_key_opt: None, neighborhood_config: NeighborhoodConfig { mode: NeighborhoodMode::Standard( NodeAddr::new(&IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), &[1234, 2345]), @@ -1507,6 +1509,7 @@ mod tests { cryptde_pair: CRYPTDE_PAIR.clone(), mapping_protocol_opt: None, real_user: RealUser::null(), + new_public_key_opt: None, neighborhood_config: NeighborhoodConfig { mode: NeighborhoodMode::ConsumeOnly(vec![]), min_hops: MIN_HOPS_FOR_TEST, @@ -1690,6 +1693,7 @@ mod tests { cryptde_pair: CRYPTDE_PAIR.clone(), mapping_protocol_opt: None, real_user: RealUser::null(), + new_public_key_opt: None, neighborhood_config: NeighborhoodConfig { mode: NeighborhoodMode::Standard( NodeAddr::new(&IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)), &[]), diff --git a/node/src/bootstrapper.rs b/node/src/bootstrapper.rs index b70bc208c..351e61699 100644 --- a/node/src/bootstrapper.rs +++ b/node/src/bootstrapper.rs @@ -361,6 +361,7 @@ pub struct BootstrapperConfig { pub node_descriptor: NodeDescriptor, pub cryptde_pair: CryptDEPair, pub mapping_protocol_opt: Option, + pub new_public_key_opt: Option, pub real_user: RealUser, pub payment_thresholds_opt: Option, @@ -406,6 +407,7 @@ impl BootstrapperConfig { Box::new(CryptDEReal::disabled()), ), mapping_protocol_opt: None, + new_public_key_opt: None, real_user: RealUser::new(None, None, None), payment_thresholds_opt: Default::default(), diff --git a/node/src/daemon/setup_reporter.rs b/node/src/daemon/setup_reporter.rs index e12713cda..df12bbb28 100644 --- a/node/src/daemon/setup_reporter.rs +++ b/node/src/daemon/setup_reporter.rs @@ -1042,6 +1042,26 @@ impl ValueRetriever for Neighbors { } } +struct NewPublicKey {} +impl ValueRetriever for NewPublicKey { + fn value_name(&self) -> &'static str { + "new-public-key" + } + + fn computed_default( + &self, + _bootstrapper_config: &BootstrapperConfig, + _persistent_config: &dyn PersistentConfiguration, + _db_password_opt: &Option, + ) -> Option<(String, UiSetupResponseValueStatus)> { + Some(("".to_string(), Blank)) + } + + fn is_required(&self, _params: &SetupCluster) -> bool { + false + } +} + struct PaymentThresholds {} impl ValueRetriever for PaymentThresholds { fn value_name(&self) -> &'static str { @@ -1213,6 +1233,7 @@ fn value_retrievers(dirs_wrapper: &dyn DirsWrapper) -> Vec for NodeConfiguratorStandardUnprivileg )?; configure_database(&unprivileged_config, persistent_config.as_mut())?; let cryptde_pair = if multi_config.occurrences_of("fake-public-key") == 0 { + let new_public_key = value_m!(multi_config, "new-public-key", OnOff); + configure_cryptdes( + new_public_key, persistent_config.as_mut(), &unprivileged_config.db_password_opt, ) @@ -300,6 +303,15 @@ pub fn privileged_parse_args( None => vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)), 53)], }; + privileged_config.new_public_key_opt = match value_m!(multi_config, "new-public-key", String) { + Some(value) => match value.as_str() { + "on" => Some(true), + "off" => Some(false), + _ => panic!("Bad clap validation for new-public-key: {}", value), + }, + None => None, + }; + privileged_config.log_level = value_m!(multi_config, "log-level", LevelFilter).unwrap_or(LevelFilter::Warn); @@ -345,17 +357,30 @@ fn configure_database( } fn configure_cryptdes( + new_public_key: Option, persistent_config: &mut dyn PersistentConfiguration, db_password_opt: &Option, ) -> CryptDEPair { let cryptde_pair = if let Some(db_password) = db_password_opt { let chain = Chain::from(persistent_config.chain_name().as_str()); - let main_result = persistent_config.cryptde(db_password); + let main_result = match new_public_key { + None | Some(OnOff::Off) => persistent_config.cryptde(db_password), + Some(OnOff::On) => { + let main_cryptde: Box = Box::new(CryptDEReal::new(chain)); + persistent_config + .set_cryptde(main_cryptde.as_ref(), db_password) + .expect("Failed to set cryptde"); + Ok(Some(main_cryptde)) + } + }; match main_result { Ok(Some(last_main_cryptde)) => { CryptDEPair::new(last_main_cryptde, Box::new(CryptDEReal::new(chain))) } Ok(None) => { + if new_public_key == Some(OnOff::Off) { + panic!("--new-public-key off: Cannot reestablish old public key: no old public key available"); + } let main_cryptde: Box = Box::new(CryptDEReal::new(chain)); persistent_config .set_cryptde(main_cryptde.as_ref(), db_password) @@ -366,6 +391,9 @@ fn configure_cryptdes( Err(e) => panic!("Could not read last cryptde from database: {:?}", e), } } else { + if new_public_key == Some(OnOff::Off) { + panic!("--new-public-key off: Cannot reestablish old public key: no --db-password provided"); + } let chain = Chain::from(persistent_config.chain_name().as_str()); let main_cryptde: Box = Box::new(CryptDEReal::new(chain)); let alias_cryptde: Box = Box::new(CryptDEReal::new(chain)); @@ -530,6 +558,33 @@ mod tests { ); } + #[test] + #[should_panic( + expected = "--new-public-key off: Cannot reestablish old public key: no --db-password provided" + )] + fn node_configurator_standard_unprivileged_complains_if_no_password_and_new_public_key_off() { + let home_dir = ensure_node_home_directory_exists( + "node_configurator_standard", + "node_configurator_standard_unprivileged_complains_if_no_password_and_new_public_key_off", + ); + let multi_config = make_simplified_multi_config([ + "--chain", + "eth-mainnet", + "--new-public-key", + "off", + "--ip", + "1.2.3.4", + ]); + let mut privileged_config = BootstrapperConfig::default(); + privileged_config.data_directory = home_dir; + let subject = NodeConfiguratorStandardUnprivileged { + privileged_config, + logger: Logger::new("test"), + }; + + let _ = subject.configure(&multi_config).unwrap(); + } + #[test] fn node_configurator_standard_unprivileged_handles_fake_public_key() { let home_dir = ensure_node_home_directory_exists( @@ -640,7 +695,17 @@ mod tests { } #[test] - fn configure_cryptdes_handles_missing_password_with_uninitialized_cryptdes() { + #[should_panic( + expected = "--new-public-key off: Cannot reestablish old public key: no --db-password provided" + )] + fn configure_cryptdes_handles_missing_password_with_uninitialized_cryptdes_and_npk_off() { + let mut persistent_config = PersistentConfigurationMock::new(); + + configure_cryptdes(Some(OnOff::Off), &mut persistent_config, &None); + } + + #[test] + fn configure_cryptdes_handles_missing_password_with_uninitialized_cryptdes_and_npk_on() { let cryptde_params_arc = Arc::new(Mutex::new(vec![])); let set_cryptde_params_arc = Arc::new(Mutex::new(vec![])); let mut persistent_config = PersistentConfigurationMock::new() @@ -648,7 +713,7 @@ mod tests { .set_cryptde_params(&set_cryptde_params_arc) .chain_name_result(TEST_DEFAULT_CHAIN.to_string()); - let _result = configure_cryptdes(&mut persistent_config, &None); + let _result = configure_cryptdes(Some(OnOff::On), &mut persistent_config, &None); let cryptde_params = cryptde_params_arc.lock().unwrap(); assert_eq!(cryptde_params.len(), 0); @@ -657,7 +722,7 @@ mod tests { } #[test] - fn configure_cryptdes_handles_missing_last_cryptde() { + fn configure_cryptdes_handles_missing_last_cryptde_with_no_npk_param() { let cryptde_params_arc = Arc::new(Mutex::new(vec![])); let set_cryptde_params_arc = Arc::new(Mutex::new(vec![])); let mut persistent_config = PersistentConfigurationMock::new() @@ -667,7 +732,11 @@ mod tests { .set_cryptde_params(&set_cryptde_params_arc) .set_cryptde_result(Ok(())); - let result = configure_cryptdes(&mut persistent_config, &Some("db_password".to_string())); + let result = configure_cryptdes( + None, + &mut persistent_config, + &Some("db_password".to_string()), + ); let cryptde_params = cryptde_params_arc.lock().unwrap(); assert_eq!(*cryptde_params, vec!["db_password".to_string()]); @@ -677,6 +746,44 @@ mod tests { assert_eq!(call.1, "db_password".to_string()); } + #[test] + #[should_panic( + expected = "--new-public-key off: Cannot reestablish old public key: no old public key available" + )] + fn configure_cryptdes_handles_missing_last_cryptde_with_npk_off() { + let cryptde_params_arc = Arc::new(Mutex::new(vec![])); + let mut persistent_config = PersistentConfigurationMock::new() + .cryptde_params(&cryptde_params_arc) + .chain_name_result(TEST_DEFAULT_CHAIN.to_string()) + .cryptde_result(Ok(None)); + + configure_cryptdes( + Some(OnOff::Off), + &mut persistent_config, + &Some("db_password".to_string()), + ); + } + + #[test] + fn configure_cryptdes_handles_missing_last_cryptde_with_npk_on() { + let set_cryptde_params_arc = Arc::new(Mutex::new(vec![])); + let mut persistent_config = PersistentConfigurationMock::new() + .chain_name_result(TEST_DEFAULT_CHAIN.to_string()) + .set_cryptde_params(&set_cryptde_params_arc) + .set_cryptde_result(Ok(())); + + let result = configure_cryptdes( + Some(OnOff::On), + &mut persistent_config, + &Some("db_password".to_string()), + ); + + let set_cryptde_params = set_cryptde_params_arc.lock().unwrap(); + let call = &set_cryptde_params[0]; + assert_eq!(call.0.public_key(), result.main.public_key()); + assert_eq!(call.1, "db_password".to_string()); + } + #[test] #[should_panic(expected = "Could not read last cryptde from database: NotPresent")] fn configure_cryptdes_panics_if_database_throws_error() { @@ -685,11 +792,39 @@ mod tests { .chain_name_result(TEST_DEFAULT_CHAIN.to_string()) .cryptde_result(Err(PersistentConfigError::NotPresent)); - let _ = configure_cryptdes(&mut persistent_config, &Some("db_password".to_string())); + let _ = configure_cryptdes( + None, + &mut persistent_config, + &Some("db_password".to_string()), + ); + } + + #[test] + fn configure_cryptdes_handles_populated_database_with_no_npk_param() { + let _guard = EnvironmentGuard::new(); + let stored_main_cryptde_box = Box::new(CryptDEReal::new(TEST_DEFAULT_CHAIN)); + let cryptde_params_arc = Arc::new(Mutex::new(vec![])); + let mut persistent_config = PersistentConfigurationMock::new() + .cryptde_params(&cryptde_params_arc) + .chain_name_result(TEST_DEFAULT_CHAIN.to_string()) + .cryptde_result(Ok(Some(stored_main_cryptde_box.dup()))); + + let result = configure_cryptdes( + None, + &mut persistent_config, + &Some("db_password".to_string()), + ); + + assert_eq!( + result.main.public_key(), + stored_main_cryptde_box.public_key() + ); + let cryptde_params = cryptde_params_arc.lock().unwrap(); + assert_eq!(*cryptde_params, vec!["db_password".to_string()]); } #[test] - fn configure_cryptdes_handles_populated_database() { + fn configure_cryptdes_handles_populated_database_with_npk_off() { let _guard = EnvironmentGuard::new(); let stored_main_cryptde_box = Box::new(CryptDEReal::new(TEST_DEFAULT_CHAIN)); let cryptde_params_arc = Arc::new(Mutex::new(vec![])); @@ -698,7 +833,11 @@ mod tests { .chain_name_result(TEST_DEFAULT_CHAIN.to_string()) .cryptde_result(Ok(Some(stored_main_cryptde_box.dup()))); - let result = configure_cryptdes(&mut persistent_config, &Some("db_password".to_string())); + let result = configure_cryptdes( + Some(OnOff::Off), + &mut persistent_config, + &Some("db_password".to_string()), + ); assert_eq!( result.main.public_key(), @@ -708,6 +847,35 @@ mod tests { assert_eq!(*cryptde_params, vec!["db_password".to_string()]); } + #[test] + fn configure_cryptdes_handles_populated_database_with_npk_on() { + let _guard = EnvironmentGuard::new(); + let stored_main_cryptde_box = Box::new(CryptDEReal::new(TEST_DEFAULT_CHAIN)); + let set_cryptde_params_arc = Arc::new(Mutex::new(vec![])); + let mut persistent_config = PersistentConfigurationMock::new() + .chain_name_result(TEST_DEFAULT_CHAIN.to_string()) + .set_cryptde_params(&set_cryptde_params_arc) + .set_cryptde_result(Ok(())); + + let result = configure_cryptdes( + Some(OnOff::On), + &mut persistent_config, + &Some("db_password".to_string()), + ); + + assert_ne!( + result.main.public_key(), + stored_main_cryptde_box.public_key() + ); + let set_cryptde_params = set_cryptde_params_arc.lock().unwrap(); + assert_eq!( + set_cryptde_params[0].0.public_key(), + result.main.public_key() + ); + assert_eq!(set_cryptde_params[0].1, "db_password".to_string()); + assert_eq!(set_cryptde_params.len(), 1); + } + fn make_default_cli_params() -> ArgsBuilder { ArgsBuilder::new().param("--ip", "1.2.3.4") } @@ -845,6 +1013,7 @@ mod tests { "--consuming-private-key", "ABCDEF01ABCDEF01ABCDEF01ABCDEF01ABCDEF01ABCDEF01ABCDEF01ABCDEF01", ) + .param("--new-public-key", "on") .param("--real-user", "999:999:/home/booga") .param("--chain", "polygon-amoy"); let mut config = BootstrapperConfig::new(); @@ -878,12 +1047,28 @@ mod tests { None, ); assert_eq!(config.data_directory, home_dir); + assert_eq!(config.new_public_key_opt, Some(true)); assert_eq!( config.real_user, RealUser::new(Some(999), Some(999), Some(PathBuf::from("/home/booga"))) ); } + #[test] + fn privileged_parse_args_works_with_on_off_parameters() { + let _guard = EnvironmentGuard::new(); + running_test(); + let args = ArgsBuilder::new().param("--new-public-key", "on"); + let mut config = BootstrapperConfig::new(); + let vcls: Vec> = + vec![Box::new(CommandLineVcl::new(args.into()))]; + let multi_config = make_new_multi_config(&app_node(), vcls).unwrap(); + + privileged_parse_args(&DirsWrapperReal::default(), &multi_config, &mut config).unwrap(); + + assert_eq!(config.new_public_key_opt, Some(true)); + } + #[test] fn privileged_parse_args_creates_configuration_with_defaults() { let _guard = EnvironmentGuard::new(); @@ -903,6 +1088,7 @@ mod tests { ); assert_eq!(config.crash_point, CrashPoint::None); assert_eq!(config.ui_gateway_config.ui_port, DEFAULT_UI_PORT); + assert_eq!(config.new_public_key_opt, None); assert_eq!( config.real_user, RealUser::new(None, None, None).populate(&DirsWrapperReal::default()) @@ -958,6 +1144,7 @@ mod tests { ); assert_eq!(config.crash_point, CrashPoint::None); assert_eq!(config.ui_gateway_config.ui_port, DEFAULT_UI_PORT); + assert_eq!(config.new_public_key_opt, None); assert_eq!( config.real_user, RealUser::new(None, None, None).populate(&DirsWrapperReal::default()) @@ -1018,6 +1205,9 @@ mod tests { config_file .write_all(b"neighborhood-mode = \"zero-hop\"\n") .unwrap(); + config_file + .write_all(b"new-public-key = \"off\"\n") + .unwrap(); config_file .write_all(b"payment-thresholds = \"3333|55|33|646|999|999\"\n") .unwrap(); diff --git a/node/src/node_configurator/unprivileged_parse_args_configuration.rs b/node/src/node_configurator/unprivileged_parse_args_configuration.rs index 41cd9a424..499d1ea04 100644 --- a/node/src/node_configurator/unprivileged_parse_args_configuration.rs +++ b/node/src/node_configurator/unprivileged_parse_args_configuration.rs @@ -19,7 +19,7 @@ use masq_lib::blockchains::chains::Chain; use masq_lib::constants::{DEFAULT_CHAIN, MASQ_URL_PREFIX}; use masq_lib::logger::Logger; use masq_lib::multi_config::MultiConfig; -use masq_lib::shared_schema::{ConfiguratorError, ParamError}; +use masq_lib::shared_schema::{ConfiguratorError, OnOff, ParamError}; use masq_lib::utils::{to_string, AutomapProtocol, ExpectValue}; use rustc_hex::FromHex; use std::net::{IpAddr, Ipv4Addr}; @@ -504,7 +504,7 @@ fn configure_accountant_config( |pc: &mut dyn PersistentConfiguration, intervals| pc.set_scan_intervals(intervals), )?; let suppress_initial_scans = - value_m!(multi_config, "scans", String).unwrap_or_else(|| "on".to_string()) == *"off"; + value_m!(multi_config, "scans", OnOff).unwrap_or(OnOff::On) == OnOff::Off; config.payment_thresholds_opt = Some(payment_thresholds); config.scan_intervals_opt = Some(scan_intervals); diff --git a/node/src/server_initializer.rs b/node/src/server_initializer.rs index 8326e0157..0dcdecda4 100644 --- a/node/src/server_initializer.rs +++ b/node/src/server_initializer.rs @@ -821,7 +821,11 @@ pub mod tests { ]), ); - assert!(result.is_ok()); + assert!( + result.is_ok(), + "Result should have been Ok(()), but was: {:?}", + result + ); let real_user = RealUser::new(Some(123), Some(456), Some("/home/alice".into())); let chown_params = chown_params_arc.lock().unwrap(); assert_eq!(