diff --git a/USER-INTERFACE-INTERFACE.md b/USER-INTERFACE-INTERFACE.md index 5aeb31877..ca5b112f5 100644 --- a/USER-INTERFACE-INTERFACE.md +++ b/USER-INTERFACE-INTERFACE.md @@ -332,17 +332,36 @@ Another reason the secrets might be missing is that there are not yet any secret "blockchainServiceUrl": , "chainName": , "clandestinePort": , - "consumingWalletDerivationPathOpt": , "currentSchemaVersion": , "earningWalletAddressOpt": , "gasPrice": , "neighborhoodMode": , + "consumingWalletPrivateKeyOpt": , + "consumingWalletAddressOpt": , "startBlock": , - "mnemonicSeedOpt": , - "pastNeighbors": [ + "pastNeighbors":[ , , ... ], + "PaymentThresholds": { + "debtThresholdGwei": , + "maturityThresholdSec": , + "paymentGracePeriodSec": , + "permanentDebtAllowedGwei": , + "thresholdIntervalSec": + "unbanBelowGwei": + }, + "ratePack": { + "routingByteRate": , + "routingServiceRate": , + "exitByteRate": , + "exitServiceRate: " + }, + "scanIntervals": { + "pendingPayableSec": , + "payableSec": , + "receivableSec": + }, } ``` ##### Description: @@ -351,11 +370,12 @@ because it hasn't been configured yet, or it might be because it's secret and yo database password. If you want to know whether the password you have is the correct one, try the `checkPassword` message. -* `blockchainServiceUrl`: The url which will be used for obtaining a communication to chosen services to interact with the blockchain. - This parameter is read, if present, only if the same parameter wasn't specified at another place (UI, configuration file, environment variables). +* `blockchainServiceUrl`: The url which will be used for obtaining a communication to chosen services to interact with the + blockchain. This parameter is read, if present, only if the same parameter wasn't specified at another place (UI, + configuration file, environment variables). -* `chainName`: This value reveals the chain which the open database has been created for. It is always present and once initiated, - during creation of the database, it never changes. It's basically a read-only value. +* `chainName`: This value reveals the chain which the open database has been created for. It is always present and once + initiated, during creation of the database, it never changes. It's basically a read-only value. * `clandestinePort`: The port on which the Node is currently listening for connections from other Nodes. @@ -364,19 +384,19 @@ database password. If you want to know whether the password you have is the corr * `consumingWalletAddress`: This is the address of the consuming wallet, as a 40-digit hexadecimal number prefixed by "0x". -* `currentSchemaVersion`: This will be a version number for the database schema represented as an ordinal numeral. This will always - be the same for a given version of Node. If you upgrade your Node, and the new Node wants to see a later +* `currentSchemaVersion`: This will be a version number for the database schema represented as an ordinal numeral. This will + always be the same for a given version of Node. If you upgrade your Node, and the new Node wants to see a later schema version in the database, it will migrate your existing data to the new schema and update its schema version. If this attempt fails for some reason, this value can be used to diagnose the issue. * `earningWalletAddressOpt`: The wallet address for the earning wallet. This is not secret, so if you don't get this field, it's because it hasn't been set yet. -* `gasPrice`: The Node will not pay more than this number of wei for gas to complete a transaction. +* `gasPrice`: The Node will not pay more than this number of Gwei for gas to complete a transaction. * `neighborhoodMode`: The neighborhood mode being currently used, this parameter has nothing to do with descriptors which - may have been used in order to set the Node's nearest neighborhood. It is only informative, to know what mode is running at the moment. - This value is ever present since the creation of the database. + may have been used in order to set the Node's nearest neighborhood. It is only informative, to know what mode is running + at the moment. This value is ever present since the creation of the database. * `startBlock`: When the Node scans for incoming payments, it can't scan the whole blockchain: that would take much too long. So instead, it scans starting from wherever it left off last time. This block number is where @@ -386,6 +406,70 @@ database password. If you want to know whether the password you have is the corr try to connect to when it starts up next time. It's a secret, so if you don't supply the `dbPasswordOpt` in the request you won't see it. +* `PaymentThresholds`: These are parameters that define thresholds to determine when and how much to pay other nodes + for routing and exit services and the expectations the node should have for receiving payments from other nodes for + routing and exit services. The thresholds are also used to determine whether to offer services to other Nodes or + enact a ban since they have not paid mature debts. These are ever present values, no matter if the user's set any + value, as they have defaults. + +* `thresholdIntervalSec`: This interval -- in seconds -- begins after maturityThresholdSec for payables and after + maturityThresholdSec + paymentGracePeriodSec for receivables. During the interval, the amount of a payable that is + allowed to remain unpaid, or a pending receivable that won’t cause a ban, decreases linearly from the debtThresholdGwei + to permanentDebtAllowedGwei or unbanBelowGwei. + +* `debtThresholdGwei`: Payables higher than this -- in Gwei of MASQ -- will be suggested for payment immediately upon + passing the maturityThresholdSec age. Payables less than this can stay unpaid longer. Receivables higher than this + will be expected to be settled by other Nodes, but will never cause bans until they pass the maturityThresholdSec + + paymentGracePeriodSec age. Receivables less than this will survive longer without banning. + +* `maturityThresholdSec`: Large payables can get this old -- in seconds -- before the Accountant's scanner suggests + that it be paid. + +* `paymentGracePeriodSec`: A large receivable can get as old as maturityThresholdSec + paymentGracePeriodSec -- in seconds + -- before the Node that owes it will be banned. + +* `permanentDebtAllowedGwei`: Receivables this small and smaller -- in Gwei of MASQ -- will not cause bans no matter + how old they get. + +* `unbanBelowGwei`: When a delinquent Node has been banned due to non-payment, the receivables balance must be paid + below this level -- in Gwei of MASQ -- to cause them to be unbanned. In most cases, you'll want this to be set the + same as permanentDebtAllowedGwei. + +* `ratePack`: These four parameters specify your rates that your Node will use for charging other Nodes for your provided + services. They are currently denominated in Gwei of MASQ, but will be improved to allow denomination in Wei units. + These are ever present values, no matter if the user's set any value, they have defaults. + +* `exitByteRate`: This parameter indicates an amount of MASQ demanded to process 1 byte of routed payload while the Node + acts as the exit Node. + +* `exitServiceRate`: This parameter indicates an amount of MASQ demanded to provide services, unpacking and repacking + 1 CORES package, while the Node acts as the exit Node. + +* `routingByteRate`: This parameter indicates an amount of MASQ demanded to process 1 byte of routed payload while the + Node is a common relay Node. + +* `routingServiceRate`: This parameter indicates an amount of MASQ demanded to provide services, unpacking and repacking + 1 CORES package, while the Node is a common relay Node. + +* `scanIntervals`: These three intervals describe the length of three different scan cycles running automatically in the + background since the Node has connected to a qualified neighborhood that consists of neighbors enabling a complete + 3-hop route. Each parameter can be set independently, but by default are all the same which currently is most desirable + for the consistency of service payments to and from your Node. Technically, there doesn't have to be any lower limit + for the minimum of time you can set; two scans of the same sort would never run at the same time but the next one is + always scheduled not earlier than the end of the previous one. These are ever present values, no matter if the user's + set any value, because defaults are prepared. + +* `pendingPayableSec`: Amount of seconds between two sequential cycles of scanning for payments that are marked as currently + pending; the payments were sent to pay our debts, the payable. The purpose of this process is to confirm the status of + the pending payment; either the payment transaction was written on blockchain as successful or failed. + +* `payableSec`: Amount of seconds between two sequential cycles of scanning aimed to find payable accounts of that meet + the criteria set by the Payment Thresholds; these accounts are tracked on behalf of our creditors. If they meet the + Payment Threshold criteria, our Node will send a debt payment transaction to the creditor in question. + +* `receivableSec`: Amount of seconds between two sequential cycles of scanning for payments on the blockchain that have + been sent by our creditors to us, which are credited against receivables recorded for services provided. + #### `configurationChanged` ##### Direction: Broadcast ##### Correspondent: Node diff --git a/dns_utility/src/win_dns_modifier.rs b/dns_utility/src/win_dns_modifier.rs index ff6ae2857..82f9c86df 100644 --- a/dns_utility/src/win_dns_modifier.rs +++ b/dns_utility/src/win_dns_modifier.rs @@ -3,6 +3,7 @@ use crate::dns_modifier::DnsModifier; use crate::ipconfig_wrapper::{IpconfigWrapper, IpconfigWrapperReal}; use crate::netsh::{Netsh, NetshCommand, NetshError}; +use masq_lib::utils::plus; use std::collections::HashSet; use std::fmt::Debug; use std::io; @@ -396,13 +397,6 @@ impl WinDnsModifier { } } -pub fn plus(mut source: Vec, item: T) -> Vec { - let mut result = vec![]; - result.append(&mut source); - result.push(item); - result -} - pub trait RegKeyTrait: Debug { fn path(&self) -> &str; fn enum_keys(&self) -> Vec>; diff --git a/dns_utility/tests/inspect_and_status_test_windows.rs b/dns_utility/tests/inspect_and_status_test_windows.rs index 7c9ae9e39..da5c35dda 100644 --- a/dns_utility/tests/inspect_and_status_test_windows.rs +++ b/dns_utility/tests/inspect_and_status_test_windows.rs @@ -11,7 +11,11 @@ use utils::TestCommand; // Any integration tests that should be run without root should have names ending in '_user_integration' fn winreg_inspect_and_status_user_integration() { let modifier = WinDnsModifier::default(); - let interfaces = modifier.find_interfaces_to_inspect().unwrap(); + let interfaces = match modifier.find_interfaces_to_inspect() { + Ok(interfaces) => interfaces, + Err(e) if e.contains("active network interfaces configured with") => return, + Err(e) => panic!("Didn't expect to stop for this error: {:?}", e), + }; let dns_server_list_csv = modifier.find_dns_server_list(interfaces).unwrap(); let dns_server_list = dns_server_list_csv.split(","); let expected_inspect_output = dns_server_list diff --git a/masq/src/commands/configuration_command.rs b/masq/src/commands/configuration_command.rs index b8aae31ad..95fcd15b2 100644 --- a/masq/src/commands/configuration_command.rs +++ b/masq/src/commands/configuration_command.rs @@ -12,8 +12,11 @@ use masq_lib::messages::{UiConfigurationRequest, UiConfigurationResponse}; use masq_lib::short_writeln; #[cfg(test)] use std::any::Any; -use std::fmt::Debug; +use std::fmt::{Debug, Display}; use std::io::Write; +use std::iter::once; + +const COLUMN_WIDTH: usize = 33; #[derive(Debug, PartialEq)] pub struct ConfigurationCommand { @@ -76,7 +79,6 @@ impl ConfigurationCommand { }) } - //put non-secret parameters first with both sorts alphabetical ordered fn dump_configuration(stream: &mut dyn Write, configuration: UiConfigurationResponse) { Self::dump_configuration_line(stream, "NAME", "VALUE"); Self::dump_configuration_line( @@ -124,6 +126,41 @@ impl ConfigurationCommand { &configuration.start_block.to_string(), ); Self::dump_value_list(stream, "Past neighbors:", &configuration.past_neighbors); + let payment_thresholds = Self::preprocess_combined_parameters({ + let p_c = &configuration.payment_thresholds; + &[ + ("Debt threshold:", &p_c.debt_threshold_gwei, "Gwei"), + ("Maturity threshold:", &p_c.maturity_threshold_sec, "s"), + ("Payment grace period:", &p_c.payment_grace_period_sec, "s"), + ( + "Permanent debt allowed:", + &p_c.permanent_debt_allowed_gwei, + "Gwei", + ), + ("Threshold interval:", &p_c.threshold_interval_sec, "s"), + ("Unban below:", &p_c.unban_below_gwei, "Gwei"), + ] + }); + Self::dump_value_list(stream, "Payment thresholds:", &payment_thresholds); + let rate_pack = Self::preprocess_combined_parameters({ + let r_p = &configuration.rate_pack; + &[ + ("Routing byte rate:", &r_p.routing_byte_rate, "Gwei"), + ("Routing service rate:", &r_p.routing_service_rate, "Gwei"), + ("Exit byte rate:", &r_p.exit_byte_rate, "Gwei"), + ("Exit service rate:", &r_p.exit_service_rate, "Gwei"), + ] + }); + Self::dump_value_list(stream, "Rate pack:", &rate_pack); + let scan_intervals = Self::preprocess_combined_parameters({ + let s_i = &configuration.scan_intervals; + &[ + ("Pending payable:", &s_i.pending_payable_sec, "s"), + ("Payable:", &s_i.payable_sec, "s"), + ("Receivable:", &s_i.receivable_sec, "s"), + ] + }); + Self::dump_value_list(stream, "Scan intervals:", &scan_intervals); } fn dump_value_list(stream: &mut dyn Write, name: &str, values: &[String]) { @@ -143,7 +180,7 @@ impl ConfigurationCommand { } fn dump_configuration_line(stream: &mut dyn Write, name: &str, value: &str) { - short_writeln!(stream, "{:33} {}", name, value); + short_writeln!(stream, "{:width$} {}", name, value, width = COLUMN_WIDTH); } fn interpret_option(value_opt: &Option) -> String { @@ -152,6 +189,19 @@ impl ConfigurationCommand { Some(s) => s.clone(), } } + + fn preprocess_combined_parameters(parameters: &[(&str, &dyn Display, &str)]) -> Vec { + let iter_of_strings = parameters.iter().map(|(description, value, unit)| { + format!( + "{:width$} {} {}", + description, + value, + unit, + width = COLUMN_WIDTH + ) + }); + once(String::from("")).chain(iter_of_strings).collect() + } } #[cfg(test)] @@ -163,7 +213,9 @@ mod tests { use crate::commands::commands_common::CommandError::ConnectionProblem; use crate::test_utils::mocks::CommandContextMock; use masq_lib::constants::NODE_NOT_RUNNING_ERROR; - use masq_lib::messages::{ToMessageBody, UiConfigurationResponse}; + use masq_lib::messages::{ + ToMessageBody, UiConfigurationResponse, UiPaymentThresholds, UiRatePack, UiScanIntervals, + }; use masq_lib::utils::AutomapProtocol; use std::sync::{Arc, Mutex}; @@ -256,7 +308,26 @@ mod tests { earning_wallet_address_opt: Some("earning address".to_string()), port_mapping_protocol_opt: Some(AutomapProtocol::Pcp.to_string()), past_neighbors: vec!["neighbor 1".to_string(), "neighbor 2".to_string()], + payment_thresholds: UiPaymentThresholds { + threshold_interval_sec: 11111, + debt_threshold_gwei: 1212, + payment_grace_period_sec: 4578, + permanent_debt_allowed_gwei: 11222, + maturity_threshold_sec: 3333, + unban_below_gwei: 12000, + }, + rate_pack: UiRatePack { + routing_byte_rate: 8, + routing_service_rate: 9, + exit_byte_rate: 12, + exit_service_rate: 14, + }, start_block: 3456, + scan_intervals: UiScanIntervals { + pending_payable_sec: 150, + payable_sec: 155, + receivable_sec: 250, + }, }; let mut context = CommandContextMock::new() .transact_params(&transact_params_arc) @@ -283,7 +354,8 @@ mod tests { ); assert_eq!( stdout_arc.lock().unwrap().get_string(), - "\ + format!( + "\ |NAME VALUE\n\ |Blockchain service URL: https://infura.io/ID\n\ |Chain: ropsten\n\ @@ -297,9 +369,24 @@ mod tests { |Start block: 3456\n\ |Past neighbors: neighbor 1\n\ | neighbor 2\n\ -" +|Payment thresholds: \n\ +| Debt threshold: 1212 Gwei\n\ +| Maturity threshold: 3333 s\n\ +| Payment grace period: 4578 s\n\ +| Permanent debt allowed: 11222 Gwei\n\ +| Threshold interval: 11111 s\n\ +| Unban below: 12000 Gwei\n\ +|Rate pack: \n\ +| Routing byte rate: 8 Gwei\n\ +| Routing service rate: 9 Gwei\n\ +| Exit byte rate: 12 Gwei\n\ +| Exit service rate: 14 Gwei\n\ +|Scan intervals: \n\ +| Pending payable: 150 s\n\ +| Payable: 155 s\n\ +| Receivable: 250 s\n" + ) .replace('|', "") - .to_string() ); assert_eq!(stderr_arc.lock().unwrap().get_string(), ""); } @@ -319,7 +406,26 @@ mod tests { earning_wallet_address_opt: Some("earning wallet".to_string()), port_mapping_protocol_opt: Some(AutomapProtocol::Pcp.to_string()), past_neighbors: vec![], + payment_thresholds: UiPaymentThresholds { + threshold_interval_sec: 1000, + debt_threshold_gwei: 2500, + payment_grace_period_sec: 666, + permanent_debt_allowed_gwei: 1200, + maturity_threshold_sec: 500, + unban_below_gwei: 1400, + }, + rate_pack: UiRatePack { + routing_byte_rate: 15, + routing_service_rate: 17, + exit_byte_rate: 20, + exit_service_rate: 30, + }, start_block: 3456, + scan_intervals: UiScanIntervals { + pending_payable_sec: 1000, + payable_sec: 1000, + receivable_sec: 1000, + }, }; let mut context = CommandContextMock::new() .transact_params(&transact_params_arc) @@ -344,21 +450,38 @@ mod tests { ); assert_eq!( stdout_arc.lock().unwrap().get_string(), - "\ -NAME VALUE\n\ -Blockchain service URL: https://infura.io/ID\n\ -Chain: mumbai\n\ -Clandestine port: 1234\n\ -Consuming wallet private key: [?]\n\ -Current schema version: schema version\n\ -Earning wallet address: earning wallet\n\ -Gas price: 2345\n\ -Neighborhood mode: zero-hop\n\ -Port mapping protocol: PCP\n\ -Start block: 3456\n\ -Past neighbors: [?]\n\ -" - .to_string() + format!( + "\ +|NAME VALUE\n\ +|Blockchain service URL: https://infura.io/ID\n\ +|Chain: mumbai\n\ +|Clandestine port: 1234\n\ +|Consuming wallet private key: [?]\n\ +|Current schema version: schema version\n\ +|Earning wallet address: earning wallet\n\ +|Gas price: 2345\n\ +|Neighborhood mode: zero-hop\n\ +|Port mapping protocol: PCP\n\ +|Start block: 3456\n\ +|Past neighbors: [?]\n\ +|Payment thresholds: \n\ +| Debt threshold: 2500 Gwei\n\ +| Maturity threshold: 500 s\n\ +| Payment grace period: 666 s\n\ +| Permanent debt allowed: 1200 Gwei\n\ +| Threshold interval: 1000 s\n\ +| Unban below: 1400 Gwei\n\ +|Rate pack: \n\ +| Routing byte rate: 15 Gwei\n\ +| Routing service rate: 17 Gwei\n\ +| Exit byte rate: 20 Gwei\n\ +| Exit service rate: 30 Gwei\n\ +|Scan intervals: \n\ +| Pending payable: 1000 s\n\ +| Payable: 1000 s\n\ +| Receivable: 1000 s\n", + ) + .replace('|', "") ); assert_eq!(stderr_arc.lock().unwrap().get_string(), ""); } diff --git a/masq/src/commands/setup_command.rs b/masq/src/commands/setup_command.rs index bfe12587c..fa6838995 100644 --- a/masq/src/commands/setup_command.rs +++ b/masq/src/commands/setup_command.rs @@ -106,14 +106,11 @@ impl SetupCommand { .partial_cmp(&b.name) .expect("String comparison failed") }); - short_writeln!( - stdout, - "NAME VALUE STATUS" - ); + short_writeln!(stdout, "{:29} {:64} {}", "NAME", "VALUE", "STATUS"); inner.values.into_iter().for_each(|value| { short_writeln!( stdout, - "{:23}{:64} {:?}", + "{:29} {:64} {:?}", value.name, value.value, value.status @@ -123,7 +120,7 @@ impl SetupCommand { if !inner.errors.is_empty() { short_writeln!(stdout, "ERRORS:"); inner.errors.into_iter().for_each(|(parameter, reason)| { - short_writeln!(stdout, "{:23}{}", parameter, reason) + short_writeln!(stdout, "{:29} {}", parameter, reason) }); short_writeln!(stdout); } @@ -189,6 +186,7 @@ mod tests { "masq://eth-mainnet:95VjByq5tEUUpDcczA__zXWGE6-7YFEvzN4CDVoPbWw@13.23.13.23:4545", Set, ), + UiSetupResponseValue::new("scan-intervals","123|111|228",Set) ], errors: vec![], } @@ -204,6 +202,8 @@ mod tests { "--log-level".to_string(), "--chain".to_string(), "eth-ropsten".to_string(), + "--scan-intervals".to_string(), + "123|111|228".to_string(), ]) .unwrap(); @@ -222,6 +222,7 @@ mod tests { ), UiSetupRequestValue::clear("log-level"), UiSetupRequestValue::new("neighborhood-mode", "zero-hop"), + UiSetupRequestValue::new("scan-intervals", "123|111|228") ] } .tmb(0), @@ -229,10 +230,11 @@ mod tests { )] ); assert_eq! (stdout_arc.lock().unwrap().get_string(), -"NAME VALUE STATUS\n\ -chain eth-ropsten Configured\n\ -neighborhood-mode zero-hop Set\n\ -neighbors masq://eth-mainnet:95VjByq5tEUUpDcczA__zXWGE6-7YFEvzN4CDVoPbWw@13.23.13.23:4545 Set\n\ +"NAME VALUE STATUS\n\ +chain eth-ropsten Configured\n\ +neighborhood-mode zero-hop Set\n\ +neighbors masq://eth-mainnet:95VjByq5tEUUpDcczA__zXWGE6-7YFEvzN4CDVoPbWw@13.23.13.23:4545 Set\n\ +scan-intervals 123|111|228 Set\n\ \n"); assert_eq!(stderr_arc.lock().unwrap().get_string(), String::new()); } @@ -288,13 +290,13 @@ neighbors masq://eth-mainnet:95VjByq5tEUUpDcczA__zXWGE6-7YFEvzN4CDV )] ); assert_eq! (stdout_arc.lock().unwrap().get_string(), -"NAME VALUE STATUS\n\ -chain eth-ropsten Set\n\ -clandestine-port 8534 Default\n\ -neighborhood-mode zero-hop Configured\n\ +"NAME VALUE STATUS\n\ +chain eth-ropsten Set\n\ +clandestine-port 8534 Default\n\ +neighborhood-mode zero-hop Configured\n\ \n\ ERRORS: -ip Nosir, I don't like it.\n\ +ip Nosir, I don't like it.\n\ \n\ NOTE: no changes were made to the setup because the Node is currently running.\n\ \n"); @@ -322,13 +324,13 @@ NOTE: no changes were made to the setup because the Node is currently running.\n "\n\ Daemon setup has changed:\n\ \n\ -NAME VALUE STATUS\n\ -chain eth-ropsten Set\n\ -clandestine-port 8534 Default\n\ -neighborhood-mode zero-hop Configured\n\ +NAME VALUE STATUS\n\ +chain eth-ropsten Set\n\ +clandestine-port 8534 Default\n\ +neighborhood-mode zero-hop Configured\n\ \n\ ERRORS: -ip No sir, I don't like it.\n\ +ip No sir, I don't like it.\n\ \n"); } } diff --git a/masq/src/communications/broadcast_handler.rs b/masq/src/communications/broadcast_handler.rs index cd2a4064e..23f829d0a 100644 --- a/masq/src/communications/broadcast_handler.rs +++ b/masq/src/communications/broadcast_handler.rs @@ -367,11 +367,11 @@ mod tests { //(the message is composed out of those entries in the vector above) let broadcast_output = "Daemon setup has changed: -NAME VALUE STATUS -chain ropsten Configured -ip 4.4.4.4 Set -log-level error Set -neighborhood-mode standard Default +NAME VALUE STATUS +chain ropsten Configured +ip 4.4.4.4 Set +log-level error Set +neighborhood-mode standard Default "; assertion_for_handle_broadcast(SetupCommand::handle_broadcast, setup_body, broadcast_output) diff --git a/masq/tests/startup_shutdown_tests_integration.rs b/masq/tests/startup_shutdown_tests_integration.rs index 0c2e65d0d..658936471 100644 --- a/masq/tests/startup_shutdown_tests_integration.rs +++ b/masq/tests/startup_shutdown_tests_integration.rs @@ -84,7 +84,7 @@ fn masq_terminates_based_on_loss_of_connection_to_the_daemon_integration() { assert_eq!(exit_code, None); #[cfg(target_os = "windows")] assert_eq!(exit_code.unwrap(), 1); - assert!(stdout.contains("neighborhood-mode standard Default")); + assert!(stdout.contains("neighborhood-mode standard Default")); assert_eq!( stderr, "\nThe Daemon is no longer running; masq is terminating.\n\n" @@ -118,7 +118,7 @@ fn handles_startup_and_shutdown_integration() { assert_eq!(&stderr, "", "setup phase: {}", stderr); assert_eq!( - stdout.contains("neighborhood-mode zero-hop"), + stdout.contains("neighborhood-mode zero-hop"), true, "{}", stdout diff --git a/masq_lib/src/constants.rs b/masq_lib/src/constants.rs index 6c3d38c98..7fe6c7746 100644 --- a/masq_lib/src/constants.rs +++ b/masq_lib/src/constants.rs @@ -3,7 +3,7 @@ use crate::blockchains::chains::Chain; use const_format::concatcp; -pub const DEFAULT_CHAIN: Chain = Chain::EthMainnet; +pub const DEFAULT_CHAIN: Chain = Chain::PolyMainnet; pub const HIGHEST_RANDOM_CLANDESTINE_PORT: u16 = 9999; pub const HTTP_PORT: u16 = 80; @@ -14,7 +14,6 @@ pub const LOWEST_USABLE_INSECURE_PORT: u16 = 1025; pub const HIGHEST_USABLE_PORT: u16 = 65535; pub const DEFAULT_UI_PORT: u16 = 5333; pub const CURRENT_LOGFILE_NAME: &str = "MASQNode_rCURRENT.log"; - pub const MASQ_PROMPT: &str = "masq> "; pub const ETH_MAINNET_CONTRACT_CREATION_BLOCK: u64 = 11_170_708; @@ -24,6 +23,7 @@ pub const POLYGON_MAINNET_CONTRACT_CREATION_BLOCK: u64 = 14_863_650; pub const MUMBAI_TESTNET_CONTRACT_CREATION_BLOCK: u64 = 24_638_838; //error codes +//////////////////////////////////////////////////////////////////////////////////////////////////// //moved from configurator pub const CONFIGURATOR_PREFIX: u64 = 0x0001_0000_0000_0000; @@ -51,6 +51,10 @@ pub const UNMARSHAL_ERROR: u64 = UI_NODE_COMMUNICATION_PREFIX | 4; pub const SETUP_ERROR: u64 = UI_NODE_COMMUNICATION_PREFIX | 5; pub const TIMEOUT_ERROR: u64 = UI_NODE_COMMUNICATION_PREFIX | 6; +//////////////////////////////////////////////////////////////////////////////////////////////////// + +pub const COMBINED_PARAMETERS_DELIMITER: char = '|'; + //descriptor pub const CENTRAL_DELIMITER: char = '@'; pub const CHAIN_IDENTIFIER_DELIMITER: char = ':'; @@ -72,7 +76,7 @@ mod tests { #[test] fn constants_have_correct_values() { - assert_eq!(DEFAULT_CHAIN, Chain::EthMainnet); + assert_eq!(DEFAULT_CHAIN, Chain::PolyMainnet); assert_eq!(HIGHEST_RANDOM_CLANDESTINE_PORT, 9999); assert_eq!(HTTP_PORT, 80); assert_eq!(TLS_PORT, 443); diff --git a/masq_lib/src/messages.rs b/masq_lib/src/messages.rs index ab7fb5910..45579de95 100644 --- a/masq_lib/src/messages.rs +++ b/masq_lib/src/messages.rs @@ -472,7 +472,6 @@ pub struct UiConfigurationRequest { } conversation_message!(UiConfigurationRequest, "configuration"); -//put non-secret parameters first with both sorts alphabetical ordered #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct UiConfigurationResponse { #[serde(rename = "blockchainServiceUrlOpt")] @@ -501,9 +500,54 @@ pub struct UiConfigurationResponse { pub consuming_wallet_address_opt: Option, #[serde(rename = "pastNeighbors")] pub past_neighbors: Vec, + #[serde(rename = "paymentThresholds")] + pub payment_thresholds: UiPaymentThresholds, + #[serde(rename = "ratePack")] + pub rate_pack: UiRatePack, + #[serde(rename = "scanIntervals")] + pub scan_intervals: UiScanIntervals, } + conversation_message!(UiConfigurationResponse, "configuration"); +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct UiRatePack { + #[serde(rename = "routingByteRate")] + pub routing_byte_rate: u64, + #[serde(rename = "routingServiceRate")] + pub routing_service_rate: u64, + #[serde(rename = "exitByteRate")] + pub exit_byte_rate: u64, + #[serde(rename = "exitServiceRate")] + pub exit_service_rate: u64, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct UiScanIntervals { + #[serde(rename = "pendingPayableSec")] + pub pending_payable_sec: u64, + #[serde(rename = "payableSec")] + pub payable_sec: u64, + #[serde(rename = "receivableSec")] + pub receivable_sec: u64, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct UiPaymentThresholds { + #[serde(rename = "thresholdIntervalSec")] + pub threshold_interval_sec: i64, + #[serde(rename = "debtThresholdGwei")] + pub debt_threshold_gwei: i64, + #[serde(rename = "paymentGracePeriodSec")] + pub payment_grace_period_sec: i64, + #[serde(rename = "maturityThresholdSec")] + pub maturity_threshold_sec: i64, + #[serde(rename = "permanentDebtAllowedGwei")] + pub permanent_debt_allowed_gwei: i64, + #[serde(rename = "unbanBelowGwei")] + pub unban_below_gwei: i64, +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct UiDescriptorRequest {} conversation_message!(UiDescriptorRequest, "descriptor"); diff --git a/masq_lib/src/multi_config.rs b/masq_lib/src/multi_config.rs index 2490a6934..1d32ab9bb 100644 --- a/masq_lib/src/multi_config.rs +++ b/masq_lib/src/multi_config.rs @@ -11,7 +11,6 @@ use std::collections::HashSet; use std::fmt::{Debug, Display}; use std::fs::File; use std::io::{ErrorKind, Read}; -use std::ops::Deref; use std::path::{Path, PathBuf}; use toml::value::Table; use toml::Value; @@ -19,8 +18,7 @@ use toml::Value; #[macro_export] macro_rules! value_m { ($m:ident, $v:expr, $t:ty) => {{ - use std::ops::Deref; - let matches = $m.deref(); + let matches = make_arg_matches_accesible(&$m); match value_t!(matches, $v, $t) { Ok(v) => Some(v), Err(_) => None, @@ -31,9 +29,8 @@ macro_rules! value_m { #[macro_export] macro_rules! value_user_specified_m { ($m:ident, $v:expr, $t:ty) => {{ - use std::ops::Deref; - let matches = $m.deref(); - let user_specified = matches.occurrences_of($v) > 0; + let user_specified = $m.occurrences_of($v) > 0; + let matches = make_arg_matches_accesible(&$m); match value_t!(matches, $v, $t) { Ok(v) => (Some(v), user_specified), Err(_) => (None, user_specified), @@ -44,8 +41,7 @@ macro_rules! value_user_specified_m { #[macro_export] macro_rules! values_m { ($m:ident, $v:expr, $t:ty) => {{ - use std::ops::Deref; - let matches = $m.deref(); + let matches = make_arg_matches_accesible(&$m); match values_t!(matches, $v, $t) { Ok(vs) => vs, Err(_) => vec![], @@ -57,13 +53,6 @@ pub struct MultiConfig<'a> { arg_matches: ArgMatches<'a>, } -impl<'a> Deref for MultiConfig<'a> { - type Target = ArgMatches<'a>; - fn deref(&self) -> &Self::Target { - &self.arg_matches - } -} - impl<'a> MultiConfig<'a> { /// Create a new MultiConfig that can be passed into the value_m! and values_m! macros, containing /// several VirtualCommandLine objects in increasing priority order. That is, values found in @@ -124,6 +113,14 @@ impl<'a> MultiConfig<'a> { } ConfiguratorError::required("", &format!("Unfamiliar message: {}", e.message)) } + + pub fn occurrences_of(&self, parameter: &str) -> u64 { + self.arg_matches.occurrences_of(parameter) + } +} + +pub fn make_arg_matches_accesible<'a>(multi_confuig: &'a MultiConfig) -> &'a ArgMatches<'a> { + &multi_confuig.arg_matches } pub trait VclArg: Debug { @@ -846,7 +843,7 @@ pub mod tests { assert!(user_specified_numeric); assert_eq!(Some(88), missing_arg_result); assert!(!user_specified_missing); - assert!(subject.deref().is_present("missing-arg")); + assert!(subject.arg_matches.is_present("missing-arg")); } #[test] @@ -863,11 +860,10 @@ pub mod tests { "--nonvalued".to_string(), ])), ]; - let subject = MultiConfig::try_new(&schema, vcls).unwrap(); - let result = subject.deref(); + let result = MultiConfig::try_new(&schema, vcls).unwrap(); - assert!(result.is_present("nonvalued")); + assert!(result.arg_matches.is_present("nonvalued")); } #[test] diff --git a/masq_lib/src/shared_schema.rs b/masq_lib/src/shared_schema.rs index ff52fb278..c85d2a658 100644 --- a/masq_lib/src/shared_schema.rs +++ b/masq_lib/src/shared_schema.rs @@ -59,10 +59,10 @@ pub const LOG_LEVEL_HELP: &str = pub const NEIGHBORS_HELP: &str = "One or more Node descriptors for running Nodes in the MASQ \ One or more Node descriptors for active Nodes in the MASQ Network to which you'd like your Node to connect \ on startup. A Node descriptor looks similar to one of these:\n\n\ - masq://polygon-mainnet:d2U3Dv1BqtS5t_Zz3mt9_sCl7AgxUlnkB4jOMElylrU@172.50.48.6:9342\n\ - masq://eth-mainnet:gBviQbjOS3e5ReFQCvIhUM3i02d1zPleo1iXg_EN6zQ@86.75.30.9:5542\n\ - masq://polygon-mumbai:A6PGHT3rRjaeFpD_rFi3qGEXAVPq7bJDfEUZpZaIyq8@14.10.50.6:10504\n\ - masq://eth-ropsten:OHsC2CAm4rmfCkaFfiynwxflUgVTJRb2oY5mWxNCQkY@150.60.42.72:6642/4789/5254\n\n\ + masq://polygon-mainnet:d2U3Dv1BqtS5t_Zz3mt9_sCl7AgxUlnkB4jOMElylrU@172.50.48.6:9342\n\ + masq://eth-mainnet:gBviQbjOS3e5ReFQCvIhUM3i02d1zPleo1iXg_EN6zQ@86.75.30.9:5542\n\ + masq://polygon-mumbai:A6PGHT3rRjaeFpD_rFi3qGEXAVPq7bJDfEUZpZaIyq8@14.10.50.6:10504\n\ + masq://eth-ropsten:OHsC2CAm4rmfCkaFfiynwxflUgVTJRb2oY5mWxNCQkY@150.60.42.72:6642/4789/5254\n\n\ Notice each of the different chain identifiers in the masq protocol prefix - they determine a family of chains \ and also the network the descriptor belongs to (mainnet or a testnet). See also the last descriptor which shows \ a configuration with multiple clandestine ports.\n\n\ @@ -110,6 +110,61 @@ pub const REAL_USER_HELP: &str = run with root privilege after bootstrapping, you might want to use this if you start the Node as root, or if \ you start the Node using pkexec or some other method that doesn't populate the SUDO_xxx variables. Use a value \ like ::."; +pub const RATE_PACK_HELP: &str = "\ + These four parameters specify your rates that your Node will use for charging other Nodes for your provided \ + services. These are ever present values, defaulted if left unspecified. The parameters must be always supplied \ + all together, delimited by vertical bars and in the right order.\n\n\ + 1. Routing Byte Rate: This parameter indicates an amount of MASQ demanded to process 1 byte of routed payload \ + while the Node is a common relay Node.\n\n\ + 2. Routing Service Rate: This parameter indicates an amount of MASQ demanded to provide services, unpacking \ + and repacking 1 CORES package, while the Node is a common relay Node.\n\n\ + 3. Exit Byte Rate: This parameter indicates an amount of MASQ demanded to process 1 byte of routed payload \ + while the Node acts as the exit Node.\n\n\ + 4. Exit Service Rate: This parameter indicates an amount of MASQ demanded to provide services, unpacking and \ + repacking 1 CORES package, while the Node acts as the exit Node."; +pub const PAYMENT_THRESHOLDS_HELP: &str = "\ + These are parameters that define thresholds to determine when and how much to pay other Nodes for routing and \ + exit services and the expectations the Node should have for receiving payments from other Nodes for routing and \ + exit services. The thresholds are also used to determine whether to offer services to other Nodes or enact a ban \ + since they have not paid mature debts. These are ever present values, no matter if the user's set any value, as \ + they have defaults. The parameters must be always supplied all together, delimited by vertical bars and in the right \ + order.\n\n\ + 1. Debt Threshold Gwei: Payables higher than this -- in Gwei of MASQ -- will be suggested for payment immediately \ + upon passing the Maturity Threshold Sec age. Payables less than this can stay unpaid longer. Receivables higher than \ + this will be expected to be settled by other Nodes, but will never cause bans until they pass the Maturity Threshold Sec \ + + Payment Grace Period Sec age. Receivables less than this will survive longer without banning.\n\n\ + 2. Maturity Threshold Sec: Large payables can get this old -- in seconds -- before the Accountant's scanner suggests \ + that it be paid.\n\n\ + 3. Payment Grace Period Sec: A large receivable can get as old as Maturity Threshold Sec + Payment Grace Period Sec \ + -- in seconds -- before the Node that owes it will be banned.\n\n\ + 4. Permanent Debt Allowed Gwei: Receivables this small and smaller -- in Gwei of MASQ -- will not cause bans no \ + matter how old they get.\n\n\ + 5. Threshold Interval Sec: This interval -- in seconds -- begins after Maturity Threshold Sec for payables and after \ + Maturity Threshold Sec + Payment Grace Period Sec for receivables. During the interval, the amount of a payable that is \ + allowed to remain unpaid, or a pending receivable that won’t cause a ban, decreases linearly from the Debt Threshold Gwei \ + to Permanent Debt Allowed Gwei or Unban Below Gwei.\n\n\ + 6. Unban Below Gwei: When a delinquent Node has been banned due to non-payment, the receivables balance must be paid \ + below this level -- in Gwei of MASQ -- to cause them to be unbanned. In most cases, you'll want this to be set the same \ + as Permanent Debt Allowed Gwei."; +pub const SCAN_INTERVALS_HELP:&str = "\ + These three intervals describe the length of three different scan cycles running automatically in the background \ + since the Node has connected to a qualified neighborhood that consists of neighbors enabling a complete 3-hop \ + route. Each parameter can be set independently, but by default are all the same which currently is most desirable \ + for the consistency of service payments to and from your Node. Technically, there doesn't have to be any lower \ + limit for the minimum of time you can set; two scans of the same sort would never run at the same time but the \ + next one is always scheduled not earlier than the end of the previous one. These are ever present values, no matter \ + if the user's set any value, they have defaults. The parameters must be always supplied all together, delimited by vertical \ + bars and in the right order.\n\n\ + 1. Pending Payable Scan Interval: Amount of seconds between two sequential cycles of scanning for payments that are \ + marked as currently pending; the payments were sent to pay our debts, the payable. The purpose of this process is to \ + confirm the status of the pending payment; either the payment transaction was written on blockchain as successful or \ + failed.\n\n\ + 2. Payable Scan Interval: Amount of seconds between two sequential cycles of scanning aimed to find payable accounts \ + of that meet the criteria set by the Payment Thresholds; these accounts are tracked on behalf of our creditors. If \ + they meet the Payment Threshold criteria, our Node will send a debt payment transaction to the creditor in question.\n\n\ + 3. Receivable Scan Interval: Amount of seconds between two sequential cycles of scanning for payments on the \ + blockchain that have been sent by our creditors to us, which are credited against receivables recorded for services \ + provided."; lazy_static! { pub static ref DEFAULT_UI_PORT_VALUE: String = DEFAULT_UI_PORT.to_string(); @@ -235,11 +290,20 @@ pub fn ui_port_arg(help: &str) -> Arg { .help(help) } +fn common_parameter_with_separate_u64_values<'a>(name: &'a str, help: &'a str) -> Arg<'a, 'a> { + Arg::with_name(name) + .long(name) + .value_name(Box::leak(name.to_uppercase().into_boxed_str())) + .min_values(0) + .max_values(1) + .validator(common_validators::validate_separate_u64_values) + .help(help) +} + pub fn shared_app(head: App<'static, 'static>) -> App<'static, 'static> { head.arg( Arg::with_name("blockchain-service-url") .long("blockchain-service-url") - .empty_values(false) .value_name("URL") .min_values(0) .max_values(1) @@ -250,7 +314,6 @@ pub fn shared_app(head: App<'static, 'static>) -> App<'static, 'static> { Arg::with_name("clandestine-port") .long("clandestine-port") .value_name("CLANDESTINE-PORT") - .empty_values(false) .min_values(0) .validator(common_validators::validate_clandestine_port) .help(&CLANDESTINE_PORT_HELP), @@ -354,6 +417,18 @@ pub fn shared_app(head: App<'static, 'static>) -> App<'static, 'static> { .help(NEIGHBORS_HELP), ) .arg(real_user_arg()) + .arg(common_parameter_with_separate_u64_values( + "scan-intervals", + SCAN_INTERVALS_HELP, + )) + .arg(common_parameter_with_separate_u64_values( + "rate-pack", + RATE_PACK_HELP, + )) + .arg(common_parameter_with_separate_u64_values( + "payment-thresholds", + PAYMENT_THRESHOLDS_HELP, + )) } pub mod common_validators { @@ -472,6 +547,18 @@ pub mod common_validators { Err(_) => Err(port), } } + + pub fn validate_separate_u64_values(values_with_delimiters: String) -> Result<(), String> { + values_with_delimiters.split('|').try_for_each(|segment| { + segment + .parse::() + .map_err(|_| { + "Wrong format, supply positive numeric values separated by vertical bars like 111|222|333|..." + .to_string() + }) + .map(|_| ()) + }) + } } #[derive(Debug, PartialEq, Clone)] @@ -693,6 +780,66 @@ mod tests { DEFAULT_GAS_PRICE ) ); + assert_eq!( + RATE_PACK_HELP, + "These four parameters specify your rates that your Node will use for charging other Nodes for your provided \ + services. These are ever present values, defaulted if left unspecified. The parameters must be always supplied \ + all together, delimited by vertical bars and in the right order.\n\n\ + 1. Routing Byte Rate: This parameter indicates an amount of MASQ demanded to process 1 byte of routed payload \ + while the Node is a common relay Node.\n\n\ + 2. Routing Service Rate: This parameter indicates an amount of MASQ demanded to provide services, unpacking \ + and repacking 1 CORES package, while the Node is a common relay Node.\n\n\ + 3. Exit Byte Rate: This parameter indicates an amount of MASQ demanded to process 1 byte of routed payload \ + while the Node acts as the exit Node.\n\n\ + 4. Exit Service Rate: This parameter indicates an amount of MASQ demanded to provide services, unpacking and \ + repacking 1 CORES package, while the Node acts as the exit Node." + ); + assert_eq!( + PAYMENT_THRESHOLDS_HELP, + "These are parameters that define thresholds to determine when and how much to pay other Nodes for routing and \ + exit services and the expectations the Node should have for receiving payments from other Nodes for routing and \ + exit services. The thresholds are also used to determine whether to offer services to other Nodes or enact a ban \ + since they have not paid mature debts. These are ever present values, no matter if the user's set any value, as \ + they have defaults. The parameters must be always supplied all together, delimited by vertical bars and in the right order.\n\n\ + 1. Debt Threshold Gwei: Payables higher than this -- in Gwei of MASQ -- will be suggested for payment immediately \ + upon passing the Maturity Threshold Sec age. Payables less than this can stay unpaid longer. Receivables higher than \ + this will be expected to be settled by other Nodes, but will never cause bans until they pass the Maturity Threshold Sec \ + + Payment Grace Period Sec age. Receivables less than this will survive longer without banning.\n\n\ + 2. Maturity Threshold Sec: Large payables can get this old -- in seconds -- before the Accountant's scanner suggests \ + that it be paid.\n\n\ + 3. Payment Grace Period Sec: A large receivable can get as old as Maturity Threshold Sec + Payment Grace Period Sec \ + -- in seconds -- before the Node that owes it will be banned.\n\n\ + 4. Permanent Debt Allowed Gwei: Receivables this small and smaller -- in Gwei of MASQ -- will not cause bans no \ + matter how old they get.\n\n\ + 5. Threshold Interval Sec: This interval -- in seconds -- begins after Maturity Threshold Sec for payables and after \ + Maturity Threshold Sec + Payment Grace Period Sec for receivables. During the interval, the amount of a payable that is \ + allowed to remain unpaid, or a pending receivable that won’t cause a ban, decreases linearly from the Debt Threshold Gwei \ + to Permanent Debt Allowed Gwei or Unban Below Gwei.\n\n\ + 6. Unban Below Gwei: When a delinquent Node has been banned due to non-payment, the receivables balance must be paid \ + below this level -- in Gwei of MASQ -- to cause them to be unbanned. In most cases, you'll want this to be set the same \ + as Permanent Debt Allowed Gwei." + ); + assert_eq!( + SCAN_INTERVALS_HELP, + "These three intervals describe the length of three different scan cycles running automatically in the background \ + since the Node has connected to a qualified neighborhood that consists of neighbors enabling a complete 3-hop \ + route. Each parameter can be set independently, but by default are all the same which currently is most desirable \ + for the consistency of service payments to and from your Node. Technically, there doesn't have to be any lower \ + limit for the minimum of time you can set; two scans of the same sort would never run at the same time but the \ + next one is always scheduled not earlier than the end of the previous one. These are ever present values, no matter \ + if the user's set any value, they have defaults. The parameters must be always supplied all together, delimited by \ + vertical bars and in the right order.\n\n\ + 1. Pending Payable Scan Interval: Amount of seconds between two sequential cycles of scanning for payments that are \ + marked as currently pending; the payments were sent to pay our debts, the payable. The purpose of this process is to \ + confirm the status of the pending payment; either the payment transaction was written on blockchain as successful or \ + failed.\n\n\ + 2. Payable Scan Interval: Amount of seconds between two sequential cycles of scanning aimed to find payable accounts \ + of that meet the criteria set by the Payment Thresholds; these accounts are tracked on behalf of our creditors. If \ + they meet the Payment Threshold criteria, our Node will send a debt payment transaction to the creditor in question.\n\n\ + 3. Receivable Scan Interval: Amount of seconds between two sequential cycles of scanning for payments on the \ + blockchain that have been sent by our creditors to us, which are credited against receivables recorded for services \ + provided." + ) } #[test] @@ -785,39 +932,35 @@ mod tests { fn validate_clandestine_port_rejects_port_number_too_high() { let result = common_validators::validate_clandestine_port(String::from("65536")); - assert_eq!(Err(String::from("65536")), result); + assert_eq!(result, Err(String::from("65536"))); } #[test] fn validate_clandestine_port_accepts_port_if_provided() { let result = common_validators::validate_clandestine_port(String::from("4567")); - assert!(result.is_ok()); - assert_eq!(Ok(()), result); + assert_eq!(result, Ok(())); } #[test] fn validate_gas_price_zero() { let result = common_validators::validate_gas_price("0".to_string()); - assert!(result.is_err()); - assert_eq!(Err(String::from("0")), result); + assert_eq!(result, Err(String::from("0"))); } #[test] fn validate_gas_price_normal_ropsten() { let result = common_validators::validate_gas_price("2".to_string()); - assert!(result.is_ok()); - assert_eq!(Ok(()), result); + assert_eq!(result, Ok(())); } #[test] fn validate_gas_price_normal_mainnet() { let result = common_validators::validate_gas_price("20".to_string()); - assert!(result.is_ok()); - assert_eq!(Ok(()), result); + assert_eq!(result, Ok(())); } #[test] @@ -831,15 +974,58 @@ mod tests { #[test] fn validate_gas_price_not_digits_fails() { let result = common_validators::validate_gas_price("not".to_string()); - assert!(result.is_err()); - assert_eq!(Err(String::from("not")), result); + + assert_eq!(result, Err(String::from("not"))); } #[test] fn validate_gas_price_hex_fails() { let result = common_validators::validate_gas_price("0x0".to_string()); - assert!(result.is_err()); - assert_eq!(Err(String::from("0x0")), result); + + assert_eq!(result, Err(String::from("0x0"))); + } + + #[test] + fn validate_separate_u64_values_happy_path() { + let result = common_validators::validate_separate_u64_values("4567|1111|444".to_string()); + + assert_eq!(result, Ok(())) + } + + #[test] + fn validate_separate_u64_values_sad_path_with_non_numeric_values() { + let result = common_validators::validate_separate_u64_values("4567|foooo|444".to_string()); + + assert_eq!( + result, + Err(String::from( + "Wrong format, supply positive numeric values separated by vertical bars like 111|222|333|..." + )) + ) + } + + #[test] + fn validate_separate_u64_values_sad_path_bad_delimiters_generally() { + let result = common_validators::validate_separate_u64_values("4567,555,444".to_string()); + + assert_eq!( + result, + Err(String::from( + "Wrong format, supply positive numeric values separated by vertical bars like 111|222|333|..." + )) + ) + } + + #[test] + fn validate_separate_u64_values_sad_path_bad_delimiters_at_the_end() { + let result = common_validators::validate_separate_u64_values("|4567|5555|444".to_string()); + + assert_eq!( + result, + Err(String::from( + "Wrong format, supply positive numeric values separated by vertical bars like 111|222|333|..." + )) + ) } #[test] diff --git a/multinode_integration_tests/src/masq_real_node.rs b/multinode_integration_tests/src/masq_real_node.rs index ca7758e6e..729e34744 100644 --- a/multinode_integration_tests/src/masq_real_node.rs +++ b/multinode_integration_tests/src/masq_real_node.rs @@ -13,12 +13,12 @@ use masq_lib::test_utils::utils::TEST_DEFAULT_MULTINODE_CHAIN; use masq_lib::utils::localhost; use masq_lib::utils::{DEFAULT_CONSUMING_DERIVATION_PATH, DEFAULT_EARNING_DERIVATION_PATH}; use node_lib::blockchain::bip32::Bip32ECKeyProvider; -use node_lib::sub_lib::accountant::DEFAULT_EARNING_WALLET; +use node_lib::sub_lib::accountant::{ + PaymentThresholds, DEFAULT_EARNING_WALLET, DEFAULT_PAYMENT_THRESHOLDS, +}; use node_lib::sub_lib::cryptde::{CryptDE, PublicKey}; use node_lib::sub_lib::cryptde_null::CryptDENull; -use node_lib::sub_lib::neighborhood::RatePack; -use node_lib::sub_lib::neighborhood::DEFAULT_RATE_PACK; -use node_lib::sub_lib::neighborhood::ZERO_RATE_PACK; +use node_lib::sub_lib::neighborhood::{RatePack, DEFAULT_RATE_PACK, ZERO_RATE_PACK}; use node_lib::sub_lib::node_addr::NodeAddr; use node_lib::sub_lib::wallet::Wallet; use regex::Regex; @@ -119,6 +119,7 @@ pub struct NodeStartupConfig { pub earning_wallet_info: EarningWalletInfo, pub consuming_wallet_info: ConsumingWalletInfo, pub rate_pack: RatePack, + pub payment_thresholds: PaymentThresholds, pub firewall_opt: Option, pub memory_opt: Option, pub fake_public_key_opt: Option, @@ -146,6 +147,7 @@ impl NodeStartupConfig { earning_wallet_info: EarningWalletInfo::None, consuming_wallet_info: ConsumingWalletInfo::None, rate_pack: DEFAULT_RATE_PACK, + payment_thresholds: *DEFAULT_PAYMENT_THRESHOLDS, firewall_opt: None, memory_opt: None, fake_public_key_opt: None, @@ -184,6 +186,10 @@ impl NodeStartupConfig { args.push("trace".to_string()); args.push("--data-directory".to_string()); args.push(DATA_DIRECTORY.to_string()); + args.push("--rate-pack".to_string()); + args.push(format!("\"{}\"", self.rate_pack)); + args.push("--payment-thresholds".to_string()); + args.push(format!("\"{}\"", self.payment_thresholds)); if let EarningWalletInfo::Address(ref address) = self.earning_wallet_info { args.push("--earning-wallet".to_string()); args.push(address.to_string()); @@ -370,6 +376,7 @@ pub struct NodeStartupConfigBuilder { earning_wallet_info: EarningWalletInfo, consuming_wallet_info: ConsumingWalletInfo, rate_pack: RatePack, + payment_thresholds: PaymentThresholds, firewall: Option, memory: Option, fake_public_key: Option, @@ -390,7 +397,8 @@ impl NodeStartupConfigBuilder { dns_port: 53, earning_wallet_info: EarningWalletInfo::None, consuming_wallet_info: ConsumingWalletInfo::None, - rate_pack: ZERO_RATE_PACK.clone(), + rate_pack: ZERO_RATE_PACK, + payment_thresholds: *DEFAULT_PAYMENT_THRESHOLDS, firewall: None, memory: None, fake_public_key: None, @@ -415,7 +423,8 @@ impl NodeStartupConfigBuilder { consuming_wallet_info: ConsumingWalletInfo::PrivateKey( "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC".to_string(), ), - rate_pack: ZERO_RATE_PACK.clone(), + rate_pack: ZERO_RATE_PACK, + payment_thresholds: *DEFAULT_PAYMENT_THRESHOLDS, firewall: None, memory: None, fake_public_key: None, @@ -440,7 +449,8 @@ impl NodeStartupConfigBuilder { consuming_wallet_info: ConsumingWalletInfo::PrivateKey( "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC".to_string(), ), - rate_pack: DEFAULT_RATE_PACK.clone(), + rate_pack: DEFAULT_RATE_PACK, + payment_thresholds: *DEFAULT_PAYMENT_THRESHOLDS, firewall: None, memory: None, fake_public_key: None, @@ -461,7 +471,8 @@ impl NodeStartupConfigBuilder { dns_port: 53, earning_wallet_info: EarningWalletInfo::None, consuming_wallet_info: ConsumingWalletInfo::None, - rate_pack: DEFAULT_RATE_PACK.clone(), + rate_pack: DEFAULT_RATE_PACK, + payment_thresholds: *DEFAULT_PAYMENT_THRESHOLDS, firewall: None, memory: None, fake_public_key: None, @@ -482,7 +493,8 @@ impl NodeStartupConfigBuilder { dns_port: config.dns_port, earning_wallet_info: config.earning_wallet_info.clone(), consuming_wallet_info: config.consuming_wallet_info.clone(), - rate_pack: config.rate_pack.clone(), + rate_pack: config.rate_pack, + payment_thresholds: config.payment_thresholds, firewall: config.firewall_opt.clone(), memory: config.memory_opt.clone(), fake_public_key: config.fake_public_key_opt.clone(), @@ -563,6 +575,11 @@ impl NodeStartupConfigBuilder { self } + pub fn payment_thresholds(mut self, value: PaymentThresholds) -> Self { + self.payment_thresholds = value; + self + } + pub fn open_firewall_port(mut self, port: u16) -> Self { if self.firewall.is_none() { self.firewall = Some(Firewall { @@ -609,6 +626,7 @@ impl NodeStartupConfigBuilder { earning_wallet_info: self.earning_wallet_info, consuming_wallet_info: self.consuming_wallet_info, rate_pack: self.rate_pack, + payment_thresholds: self.payment_thresholds, firewall_opt: self.firewall, memory_opt: self.memory, fake_public_key_opt: self.fake_public_key, @@ -801,7 +819,7 @@ impl MASQRealNode { ), // placeholder earning_wallet: real_startup_config.get_earning_wallet(), consuming_wallet_opt: real_startup_config.get_consuming_wallet(), - rate_pack: DEFAULT_RATE_PACK.clone(), // replace with this when rate packs are configurable: startup_config.rate_pack.clone() + rate_pack: real_startup_config.rate_pack, root_dir, cryptde_null_pair_opt: match cryptde_null_opt { None => None, @@ -1298,6 +1316,14 @@ mod tests { exit_byte_rate: 30, exit_service_rate: 40, }, + payment_thresholds: PaymentThresholds { + debt_threshold_gwei: 20, + maturity_threshold_sec: 40, + payment_grace_period_sec: 30, + permanent_debt_allowed_gwei: 50, + threshold_interval_sec: 10, + unban_below_gwei: 60, + }, firewall_opt: Some(Firewall { ports_to_open: vec![HTTP_PORT, TLS_PORT], }), @@ -1364,7 +1390,18 @@ mod tests { result.fake_public_key_opt, Some(PublicKey::new(&[1, 2, 3, 4])) ); - assert_eq!(result.db_password_opt, Some("booga".to_string())) + assert_eq!(result.db_password_opt, Some("booga".to_string())); + assert_eq!( + result.payment_thresholds, + PaymentThresholds { + debt_threshold_gwei: 20, + maturity_threshold_sec: 40, + threshold_interval_sec: 10, + payment_grace_period_sec: 30, + permanent_debt_allowed_gwei: 50, + unban_below_gwei: 60 + } + ) } #[test] @@ -1381,12 +1418,28 @@ mod tests { vec![3456, 4567], TEST_DEFAULT_MULTINODE_CHAIN, ); + let rate_pack = RatePack { + routing_byte_rate: 1, + routing_service_rate: 90, + exit_byte_rate: 3, + exit_service_rate: 250, + }; + let payment_thresholds = PaymentThresholds { + debt_threshold_gwei: 10000000000, + maturity_threshold_sec: 1200, + permanent_debt_allowed_gwei: 490000000, + payment_grace_period_sec: 1200, + threshold_interval_sec: 2592000, + unban_below_gwei: 490000000, + }; let subject = NodeStartupConfigBuilder::standard() .neighborhood_mode("consume-only") .ip(IpAddr::from_str("1.3.5.7").unwrap()) .neighbor(one_neighbor.clone()) .neighbor(another_neighbor.clone()) + .rate_pack(rate_pack) + .payment_thresholds(payment_thresholds) .consuming_wallet_info(default_consuming_wallet_info()) .build(); @@ -1405,6 +1458,10 @@ mod tests { "trace", "--data-directory", DATA_DIRECTORY, + "--rate-pack", + "\"1|90|3|250\"", + "--payment-thresholds", + "\"10000000000|1200|1200|490000000|2592000|490000000\"", "--consuming-private-key", "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC", "--chain", diff --git a/multinode_integration_tests/tests/verify_bill_payment.rs b/multinode_integration_tests/tests/verify_bill_payment.rs index 1bd9cbaab..9fce7de48 100644 --- a/multinode_integration_tests/tests/verify_bill_payment.rs +++ b/multinode_integration_tests/tests/verify_bill_payment.rs @@ -19,6 +19,7 @@ use node_lib::blockchain::blockchain_interface::{ }; use node_lib::database::db_initializer::{DbInitializer, DbInitializerReal}; use node_lib::database::db_migrations::{ExternalData, MigratorConfig}; +use node_lib::sub_lib::accountant::PaymentThresholds; use node_lib::sub_lib::wallet::Wallet; use node_lib::test_utils; use rustc_hex::{FromHex, ToHex}; @@ -37,7 +38,6 @@ fn verify_bill_payment() { Ok(cluster) => cluster, Err(e) => panic!("{}", e), }; - let blockchain_server = BlockchainServer { name: "ganache-cli", }; @@ -52,7 +52,6 @@ fn verify_bill_payment() { let deriv_path = derivation_path(0, 0); let seed = make_seed(); let (contract_owner_wallet, _) = make_node_wallet(&seed, &deriv_path); - let contract_addr = deploy_smart_contract(&contract_owner_wallet, &web3, cluster.chain); assert_eq!( contract_addr, @@ -68,17 +67,37 @@ fn verify_bill_payment() { "99998043204000000000", "472000000000000000000000000", ); - let (consuming_config, _) = build_config(&blockchain_server, &seed, deriv_path); - - let (serving_node_1_config, serving_node_1_wallet) = - build_config(&blockchain_server, &seed, derivation_path(0, 1)); - let (serving_node_2_config, serving_node_2_wallet) = - build_config(&blockchain_server, &seed, derivation_path(0, 2)); - let (serving_node_3_config, serving_node_3_wallet) = - build_config(&blockchain_server, &seed, derivation_path(0, 3)); + let payment_thresholds = PaymentThresholds { + threshold_interval_sec: 2_592_000, + debt_threshold_gwei: 1_000_000_000, + payment_grace_period_sec: 86_400, + maturity_threshold_sec: 86_400, + permanent_debt_allowed_gwei: 10_000_000, + unban_below_gwei: 10_000_000, + }; + let (consuming_config, _) = + build_config(&blockchain_server, &seed, payment_thresholds, deriv_path); + + let (serving_node_1_config, serving_node_1_wallet) = build_config( + &blockchain_server, + &seed, + payment_thresholds, + derivation_path(0, 1), + ); + let (serving_node_2_config, serving_node_2_wallet) = build_config( + &blockchain_server, + &seed, + payment_thresholds, + derivation_path(0, 2), + ); + let (serving_node_3_config, serving_node_3_wallet) = build_config( + &blockchain_server, + &seed, + payment_thresholds, + derivation_path(0, 3), + ); - let amount = 10u64 - * u64::try_from(node_lib::accountant::PAYMENT_CURVES.permanent_debt_allowed_gwub).unwrap(); + let amount = 10u64 * u64::try_from(payment_thresholds.permanent_debt_allowed_gwei).unwrap(); let project_root = MASQNodeUtils::find_project_root(); let (consuming_node_name, consuming_node_index) = cluster.prepare_real_node(&consuming_config); @@ -385,6 +404,7 @@ fn make_seed() -> Seed { fn build_config( server: &BlockchainServer, seed: &Seed, + payment_thresholds: PaymentThresholds, wallet_derivation_path: String, ) -> (NodeStartupConfig, Wallet) { let (serving_node_wallet, serving_node_secret) = @@ -392,6 +412,7 @@ fn build_config( let config = NodeStartupConfigBuilder::standard() .blockchain_service_url(server.service_url()) .chain(Chain::Dev) + .payment_thresholds(payment_thresholds) .consuming_wallet_info(ConsumingWalletInfo::PrivateKey(serving_node_secret)) .earning_wallet_info(EarningWalletInfo::Address(format!( "{}", diff --git a/node/Cargo.lock b/node/Cargo.lock index ab1c8b4ce..c3df3836c 100644 --- a/node/Cargo.lock +++ b/node/Cargo.lock @@ -437,7 +437,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef196d5d972878a48da7decb7686eded338b4858fbabeed513d63a7c98b2b82d" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.7", + "quote 1.0.14", "unicode-xid 0.2.1", ] @@ -695,7 +695,7 @@ checksum = "8d2d6daefd5f1d4b74a891a5d2ab7dccba028d423107c074232a0c5dc0d40a9e" dependencies = [ "data-encoding", "proc-macro-hack", - "syn 1.0.84", + "syn 1.0.85", ] [[package]] @@ -706,9 +706,9 @@ checksum = "40eebddd2156ce1bb37b20bbe5151340a31828b1f2d22ba4141f3531710e38df" dependencies = [ "convert_case", "proc-macro2 1.0.36", - "quote 1.0.7", + "quote 1.0.14", "rustc_version 0.3.3", - "syn 1.0.84", + "syn 1.0.85", ] [[package]] @@ -897,8 +897,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.7", - "syn 1.0.84", + "quote 1.0.14", + "syn 1.0.85", "synstructure", ] @@ -2144,6 +2144,7 @@ dependencies = [ "native-tls", "nix 0.23.1", "openssl", + "paste", "pretty-hex 0.2.1", "primitive-types 0.5.1", "rand 0.8.4", @@ -2387,6 +2388,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "paste" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" + [[package]] name = "pbkdf2" version = "0.3.0" @@ -2482,8 +2489,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b95af56fee93df76d721d356ac1ca41fccf168bc448eb14049234df764ba3e76" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.7", - "syn 1.0.84", + "quote 1.0.14", + "syn 1.0.85", ] [[package]] @@ -2595,9 +2602,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.7" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d" dependencies = [ "proc-macro2 1.0.36", ] @@ -3213,8 +3220,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecc0db5cb2556c0e558887d9bbdcf6ac4471e83ff66cf696e5419024d1606276" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.7", - "syn 1.0.84", + "quote 1.0.14", + "syn 1.0.85", ] [[package]] @@ -3258,8 +3265,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2acd6defeddb41eb60bb468f8825d0cfd0c2a76bc03bfd235b6a1dc4f6a1ad5" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.7", - "syn 1.0.84", + "quote 1.0.14", + "syn 1.0.85", ] [[package]] @@ -3471,12 +3478,12 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.84" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecb2e6da8ee5eb9a61068762a32fa9619cc591ceb055b3687f4cd4051ec2e06b" +checksum = "a684ac3dcd8913827e18cd09a68384ee66c1de24157e3c556c9ab16d85695fb7" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.7", + "quote 1.0.14", "unicode-xid 0.2.1", ] @@ -3487,8 +3494,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.7", - "syn 1.0.84", + "quote 1.0.14", + "syn 1.0.85", "unicode-xid 0.2.1", ] @@ -3580,8 +3587,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.7", - "syn 1.0.84", + "quote 1.0.14", + "syn 1.0.85", ] [[package]] @@ -4324,8 +4331,8 @@ dependencies = [ "lazy_static", "log 0.4.14", "proc-macro2 1.0.36", - "quote 1.0.7", - "syn 1.0.84", + "quote 1.0.14", + "syn 1.0.85", "wasm-bindgen-shared", ] @@ -4347,7 +4354,7 @@ version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" dependencies = [ - "quote 1.0.7", + "quote 1.0.14", "wasm-bindgen-macro-support", ] @@ -4358,8 +4365,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.7", - "syn 1.0.84", + "quote 1.0.14", + "syn 1.0.85", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4582,7 +4589,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65f1a51723ec88c66d5d1fe80c841f17f63587d6691901d66be9bec6c3b51f73" dependencies = [ "proc-macro2 1.0.36", - "quote 1.0.7", - "syn 1.0.84", + "quote 1.0.14", + "syn 1.0.85", "synstructure", ] diff --git a/node/Cargo.toml b/node/Cargo.toml index a69954047..ff9df7634 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -63,6 +63,7 @@ unindent = "0.1.7" web3 = {version = "0.11.0", default-features = false, features = ["http", "tls"]} websocket = {version = "0.26.2", default-features = false, features = ["async", "sync"]} secp256k1secrets = {package = "secp256k1", version = "0.17.2"} +paste = "1.0.6" [target.'cfg(target_os = "macos")'.dependencies] system-configuration = "0.4.0" diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index 384238eec..190a1b040 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -13,7 +13,7 @@ use crate::accountant::pending_payable_dao::{PendingPayableDao, PendingPayableDa use crate::accountant::receivable_dao::{ ReceivableAccount, ReceivableDaoError, ReceivableDaoFactory, }; -use crate::accountant::tools::accountant_tools::{Scanners, TransactionConfirmationTools}; +use crate::accountant::tools::accountant_tools::{Scanner, Scanners, TransactionConfirmationTools}; use crate::banned_dao::{BannedDao, BannedDaoFactory}; use crate::blockchain::blockchain_bridge::{PendingPayableFingerprint, RetrieveTransactions}; use crate::blockchain::blockchain_interface::{BlockchainError, Transaction}; @@ -24,12 +24,12 @@ use crate::db_config::config_dao::ConfigDaoFactory; use crate::db_config::persistent_configuration::{ PersistentConfiguration, PersistentConfigurationReal, }; -use crate::sub_lib::accountant::AccountantConfig; use crate::sub_lib::accountant::AccountantSubs; use crate::sub_lib::accountant::ReportExitServiceConsumedMessage; use crate::sub_lib::accountant::ReportExitServiceProvidedMessage; use crate::sub_lib::accountant::ReportRoutingServiceConsumedMessage; use crate::sub_lib::accountant::ReportRoutingServiceProvidedMessage; +use crate::sub_lib::accountant::{AccountantConfig, PaymentThresholds}; use crate::sub_lib::blockchain_bridge::ReportAccountsPayable; use crate::sub_lib::peer_actors::{BindMessage, StartMessage}; use crate::sub_lib::utils::{handle_ui_crash_request, NODE_MAILBOX_CAPACITY}; @@ -42,7 +42,6 @@ use actix::Handler; use actix::Message; use actix::Recipient; use itertools::Itertools; -use lazy_static::lazy_static; use masq_lib::crash_point::CrashPoint; use masq_lib::logger::Logger; use masq_lib::messages::{FromMessageBody, ToMessageBody, UiFinancialsRequest}; @@ -59,45 +58,9 @@ use std::time::{Duration, SystemTime}; use web3::types::{TransactionReceipt, H256}; pub const CRASH_KEY: &str = "ACCOUNTANT"; -pub const DEFAULT_PENDING_TRANSACTION_SCAN_INTERVAL: u64 = 3600; -pub const DEFAULT_PAYABLES_SCAN_INTERVAL: u64 = 3600; -pub const DEFAULT_RECEIVABLES_SCAN_INTERVAL: u64 = 3600; - -const SECONDS_PER_DAY: i64 = 86_400; - -lazy_static! { - pub static ref PAYMENT_CURVES: PaymentCurves = PaymentCurves { - payment_suggested_after_sec: SECONDS_PER_DAY, - payment_grace_before_ban_sec: SECONDS_PER_DAY, - permanent_debt_allowed_gwub: 10_000_000, - balance_to_decrease_from_gwub: 1_000_000_000, - balance_decreases_for_sec: 30 * SECONDS_PER_DAY, - unban_when_balance_below_gwub: 10_000_000, - }; -} pub const DEFAULT_PENDING_TOO_LONG_SEC: u64 = 21_600; //6 hours -#[derive(PartialEq, Debug, Clone, Copy)] -pub struct PaymentCurves { - pub payment_suggested_after_sec: i64, - pub payment_grace_before_ban_sec: i64, - pub permanent_debt_allowed_gwub: i64, - pub balance_to_decrease_from_gwub: i64, - pub balance_decreases_for_sec: i64, - pub unban_when_balance_below_gwub: i64, -} - -impl PaymentCurves { - pub fn sugg_and_grace(&self, now: i64) -> i64 { - now - self.payment_suggested_after_sec - self.payment_grace_before_ban_sec - } - - pub fn sugg_thru_decreasing(&self, now: i64) -> i64 { - self.sugg_and_grace(now) - self.balance_decreases_for_sec - } -} - pub struct Accountant { config: AccountantConfig, consuming_wallet: Option, @@ -115,6 +78,7 @@ pub struct Accountant { report_new_payments_sub: Option>, report_sent_payments_sub: Option>, ui_message_sub: Option>, + payable_threshold_tools: Box, logger: Logger, } @@ -160,30 +124,11 @@ impl Handler for Accountant { } } -macro_rules! notify_later_assertable { - ($self: expr, $ctx: expr, $message_type: ident, $notify_later_handle_field: ident,$scan_interval_field: ident) => { - let closure = - Box::new(|msg: $message_type, interval: Duration| $ctx.notify_later(msg, interval)); - let _ = $self.tools.$notify_later_handle_field.notify_later( - $message_type {}, - $self.config.$scan_interval_field, - closure, - ); - }; -} - impl Handler for Accountant { type Result = (); fn handle(&mut self, _msg: ScanForPayables, ctx: &mut Self::Context) -> Self::Result { - self.scanners.payables.scan(self); - notify_later_assertable!( - self, - ctx, - ScanForPayables, - notify_later_handle_scan_for_payable, - payables_scan_interval - ); + self.handle_scan_message(self.scanners.payables.as_ref(), ctx) } } @@ -191,14 +136,7 @@ impl Handler for Accountant { type Result = (); fn handle(&mut self, _msg: ScanForPendingPayable, ctx: &mut Self::Context) -> Self::Result { - self.scanners.pending_payable.scan(self); - notify_later_assertable!( - self, - ctx, - ScanForPendingPayable, - notify_later_handle_scan_for_pending_payable, - pending_payable_scan_interval - ); + self.handle_scan_message(self.scanners.pending_payables.as_ref(), ctx) } } @@ -206,14 +144,7 @@ impl Handler for Accountant { type Result = (); fn handle(&mut self, _msg: ScanForReceivables, ctx: &mut Self::Context) -> Self::Result { - self.scanners.receivables.scan(self); - notify_later_assertable!( - self, - ctx, - ScanForReceivables, - notify_later_handle_scan_for_receivable, - receivables_scan_interval - ); + self.handle_scan_message(self.scanners.receivables.as_ref(), ctx) } } @@ -365,7 +296,10 @@ impl Accountant { config_dao_factory: Box, ) -> Accountant { Accountant { - config: config.accountant_config.clone(), + config: *config + .accountant_config_opt + .as_ref() + .expectv("Accountant config"), consuming_wallet: config.consuming_wallet_opt.clone(), earning_wallet: config.earning_wallet.clone(), payable_dao: payable_dao_factory.make(), @@ -383,6 +317,7 @@ impl Accountant { report_new_payments_sub: None, report_sent_payments_sub: None, ui_message_sub: None, + payable_threshold_tools: Box::new(PayableExceedThresholdToolsReal {}), logger: Logger::new("Accountant"), } } @@ -407,6 +342,11 @@ impl Accountant { DaoFactoryReal::new(data_directory, false, MigratorConfig::panic_on_migration()) } + fn handle_scan_message(&self, scanner: &dyn Scanner, ctx: &mut Context) { + scanner.scan(self); + scanner.notify_later_assertable(self, ctx) + } + fn scan_for_payables(&self) { debug!(self.logger, "Scanning for payables"); @@ -418,7 +358,7 @@ impl Accountant { ); let qualified_payables = all_non_pending_payables .into_iter() - .filter(Accountant::should_pay) + .filter(|account| self.should_pay(account)) .collect::>(); info!( self.logger, @@ -428,7 +368,7 @@ impl Accountant { debug!( self.logger, "{}", - Self::payables_debug_summary(&qualified_payables) + self.payables_debug_summary(&qualified_payables) ); if !qualified_payables.is_empty() { self.report_accounts_payable_sub @@ -445,7 +385,7 @@ impl Accountant { debug!(self.logger, "Scanning for delinquencies"); let now = SystemTime::now(); self.receivable_dao - .new_delinquencies(now, &PAYMENT_CURVES) + .new_delinquencies(now, &self.config.payment_thresholds) .into_iter() .for_each(|account| { self.banned_dao.ban(&account.wallet); @@ -459,7 +399,7 @@ impl Accountant { ) }); self.receivable_dao - .paid_delinquencies(&PAYMENT_CURVES) + .paid_delinquencies(&self.config.payment_thresholds) .into_iter() .for_each(|account| { self.banned_dao.unban(&account.wallet); @@ -530,26 +470,34 @@ impl Accountant { (balance, age) } - fn should_pay(payable: &PayableAccount) -> bool { - Self::payable_exceeded_threshold(payable).is_some() + fn should_pay(&self, payable: &PayableAccount) -> bool { + self.payable_exceeded_threshold(payable).is_some() } - fn payable_exceeded_threshold(payable: &PayableAccount) -> Option { + fn payable_exceeded_threshold(&self, payable: &PayableAccount) -> Option { // TODO: This calculation should be done in the database, if possible let time_since_last_paid = SystemTime::now() .duration_since(payable.last_paid_timestamp) .expect("Internal error") .as_secs(); - if time_since_last_paid <= PAYMENT_CURVES.payment_suggested_after_sec as u64 { + if self.payable_threshold_tools.is_innocent_age( + time_since_last_paid, + self.config.payment_thresholds.maturity_threshold_sec as u64, + ) { return None; } - if payable.balance <= PAYMENT_CURVES.permanent_debt_allowed_gwub { + if self.payable_threshold_tools.is_innocent_balance( + payable.balance, + self.config.payment_thresholds.permanent_debt_allowed_gwei, + ) { return None; } - let threshold = Accountant::calculate_payout_threshold(time_since_last_paid); + let threshold = self + .payable_threshold_tools + .calculate_payout_threshold(self.config.payment_thresholds, time_since_last_paid); if payable.balance as f64 > threshold { Some(threshold as u64) } else { @@ -557,16 +505,6 @@ impl Accountant { } } - fn calculate_payout_threshold(x: u64) -> f64 { - let m = -((PAYMENT_CURVES.balance_to_decrease_from_gwub as f64 - - PAYMENT_CURVES.permanent_debt_allowed_gwub as f64) - / (PAYMENT_CURVES.balance_decreases_for_sec as f64 - - PAYMENT_CURVES.payment_suggested_after_sec as f64)); - let b = PAYMENT_CURVES.balance_to_decrease_from_gwub as f64 - - m * PAYMENT_CURVES.payment_suggested_after_sec as f64; - m * x as f64 + b - } - fn record_service_provided( &self, service_rate: u64, @@ -665,18 +603,22 @@ impl Accountant { .duration_since(p.last_paid_timestamp) .expect("Payable time is corrupt"); { - //seek for a test for this if you don't understand the purpose - let check_age_significance_across = + //look at a test if not understandable + let check_age_parameter_if_the_first_is_the_same = || -> bool { p.balance == biggest.balance && p_age > biggest.age }; - if p.balance > biggest.balance || check_age_significance_across() { + + if p.balance > biggest.balance || check_age_parameter_if_the_first_is_the_same() + { biggest = PayableInfo { balance: p.balance, age: p_age, } } - let check_balance_significance_across = + + let check_balance_parameter_if_the_first_is_the_same = || -> bool { p_age == oldest.age && p.balance > oldest.balance }; - if p_age > oldest.age || check_balance_significance_across() { + + if p_age > oldest.age || check_balance_parameter_if_the_first_is_the_same() { oldest = PayableInfo { balance: p.balance, age: p_age, @@ -691,7 +633,7 @@ impl Accountant { } } - fn payables_debug_summary(qualified_payables: &[PayableAccount]) -> String { + fn payables_debug_summary(&self, qualified_payables: &[PayableAccount]) -> String { let now = SystemTime::now(); let list = qualified_payables .iter() @@ -699,8 +641,9 @@ impl Accountant { let p_age = now .duration_since(payable.last_paid_timestamp) .expect("Payable time is corrupt"); - let threshold = - Self::payable_exceeded_threshold(payable).expect("Threshold suddenly changed!"); + let threshold = self + .payable_exceeded_threshold(payable) + .expect("Threshold suddenly changed!"); format!( "{} owed for {}sec exceeds threshold: {}; creditor: {}", payable.balance, @@ -1010,7 +953,7 @@ impl Accountant { ) -> PendingTransactionStatus { fn handle_none_status( fingerprint: PendingPayableFingerprint, - pending_interval: u64, + max_pending_interval: u64, logger: &Logger, ) -> PendingTransactionStatus { info!(logger,"Pending transaction '{}' couldn't be confirmed at attempt {} at {}ms after its sending",fingerprint.hash, fingerprint.attempt_opt.expectv("initialized attempt"), elapsed_in_ms(fingerprint.timestamp)); @@ -1022,9 +965,9 @@ impl Accountant { hash: fingerprint.hash, rowid: fingerprint.rowid_opt.expectv("initialized rowid"), }; - if pending_interval <= elapsed.as_secs() { + if max_pending_interval <= elapsed.as_secs() { error!(logger,"Pending transaction '{}' has exceeded the maximum pending time ({}sec) and the confirmation process is going to be aborted now at the final attempt {}; \ - manual resolution is required from the user to complete the transaction.",fingerprint.hash,pending_interval,fingerprint.attempt_opt.expectv("initialized attempt")); + manual resolution is required from the user to complete the transaction.",fingerprint.hash,max_pending_interval,fingerprint.attempt_opt.expectv("initialized attempt")); PendingTransactionStatus::Failure(transaction_id) } else { PendingTransactionStatus::StillPending(transaction_id) @@ -1096,7 +1039,7 @@ impl Accountant { fn cancel_failed_transaction(&self, transaction_id: PendingPayableId, ctx: &mut Context) { let closure = |msg: CancelFailedPendingTransaction| ctx.notify(msg); - self.tools.notify_handle_cancel_failed_transaction.notify( + self.tools.notify_cancel_failed_transaction.notify( CancelFailedPendingTransaction { id: transaction_id }, Box::new(closure), ) @@ -1108,7 +1051,7 @@ impl Accountant { ctx: &mut Context, ) { let closure = |msg: ConfirmPendingTransaction| ctx.notify(msg); - self.tools.notify_handle_confirm_transaction.notify( + self.tools.notify_confirm_transaction.notify( ConfirmPendingTransaction { pending_payable_fingerprint, }, @@ -1168,6 +1111,35 @@ impl From<&PendingPayableFingerprint> for PendingPayableId { } } +//TODO the data types should change with GH-497 (including signed => unsigned) +trait PayableExceedThresholdTools { + fn is_innocent_age(&self, age: u64, limit: u64) -> bool; + fn is_innocent_balance(&self, balance: i64, limit: i64) -> bool; + fn calculate_payout_threshold(&self, payment_thresholds: PaymentThresholds, x: u64) -> f64; +} + +struct PayableExceedThresholdToolsReal {} + +impl PayableExceedThresholdTools for PayableExceedThresholdToolsReal { + fn is_innocent_age(&self, age: u64, limit: u64) -> bool { + age <= limit + } + + fn is_innocent_balance(&self, balance: i64, limit: i64) -> bool { + balance <= limit + } + + fn calculate_payout_threshold(&self, payment_thresholds: PaymentThresholds, x: u64) -> f64 { + let m = -((payment_thresholds.debt_threshold_gwei as f64 + - payment_thresholds.permanent_debt_allowed_gwei as f64) + / (payment_thresholds.threshold_interval_sec as f64 + - payment_thresholds.maturity_threshold_sec as f64)); + let b = payment_thresholds.debt_threshold_gwei as f64 + - m * payment_thresholds.maturity_threshold_sec as f64; + m * x as f64 + b + } +} + #[cfg(test)] mod tests { use super::*; @@ -1191,16 +1163,19 @@ mod tests { use crate::database::dao_utils::to_time_t; use crate::db_config::mocks::ConfigDaoMock; use crate::db_config::persistent_configuration::PersistentConfigError; - use crate::sub_lib::accountant::ReportRoutingServiceConsumedMessage; + use crate::sub_lib::accountant::{ + ReportRoutingServiceConsumedMessage, ScanIntervals, DEFAULT_PAYMENT_THRESHOLDS, + }; use crate::sub_lib::blockchain_bridge::ReportAccountsPayable; use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMock; - use crate::test_utils::pure_test_utils::{ - prove_that_crash_request_handler_is_hooked_up, CleanUpMessage, DummyActor, - NotifyHandleMock, NotifyLaterHandleMock, - }; use crate::test_utils::recorder::make_recorder; use crate::test_utils::recorder::peer_actors_builder; use crate::test_utils::recorder::Recorder; + use crate::test_utils::unshared_test_utils::{ + make_accountant_config_null, make_populated_accountant_config_with_defaults, + prove_that_crash_request_handler_is_hooked_up, CleanUpMessage, DummyActor, + NotifyHandleMock, NotifyLaterHandleMock, + }; use crate::test_utils::{make_paying_wallet, make_wallet}; use actix::{Arbiter, System}; use ethereum_types::{BigEndianHash, U64}; @@ -1219,29 +1194,91 @@ mod tests { use web3::types::U256; use web3::types::{TransactionReceipt, H256}; + #[derive(Default)] + struct PayableThresholdToolsMock { + is_innocent_age_params: Arc>>, + is_innocent_age_results: RefCell>, + is_innocent_balance_params: Arc>>, + is_innocent_balance_results: RefCell>, + calculate_payout_threshold_params: Arc>>, + calculate_payout_threshold_results: RefCell>, + } + + impl PayableExceedThresholdTools for PayableThresholdToolsMock { + fn is_innocent_age(&self, age: u64, limit: u64) -> bool { + self.is_innocent_age_params + .lock() + .unwrap() + .push((age, limit)); + self.is_innocent_age_results.borrow_mut().remove(0) + } + + fn is_innocent_balance(&self, balance: i64, limit: i64) -> bool { + self.is_innocent_balance_params + .lock() + .unwrap() + .push((balance, limit)); + self.is_innocent_balance_results.borrow_mut().remove(0) + } + + fn calculate_payout_threshold(&self, payment_thresholds: PaymentThresholds, x: u64) -> f64 { + self.calculate_payout_threshold_params + .lock() + .unwrap() + .push((payment_thresholds, x)); + self.calculate_payout_threshold_results + .borrow_mut() + .remove(0) + } + } + + impl PayableThresholdToolsMock { + fn is_innocent_age_params(mut self, params: &Arc>>) -> Self { + self.is_innocent_age_params = params.clone(); + self + } + + fn is_innocent_age_result(self, result: bool) -> Self { + self.is_innocent_age_results.borrow_mut().push(result); + self + } + + fn is_innocent_balance_params(mut self, params: &Arc>>) -> Self { + self.is_innocent_balance_params = params.clone(); + self + } + + fn is_innocent_balance_result(self, result: bool) -> Self { + self.is_innocent_balance_results.borrow_mut().push(result); + self + } + + fn calculate_payout_threshold_params( + mut self, + params: &Arc>>, + ) -> Self { + self.calculate_payout_threshold_params = params.clone(); + self + } + + fn calculate_payout_threshold_result(self, result: f64) -> Self { + self.calculate_payout_threshold_results + .borrow_mut() + .push(result); + self + } + } + #[test] fn constants_have_correct_values() { - let payment_curves_expected: PaymentCurves = PaymentCurves { - payment_suggested_after_sec: SECONDS_PER_DAY, - payment_grace_before_ban_sec: SECONDS_PER_DAY, - permanent_debt_allowed_gwub: 10_000_000, - balance_to_decrease_from_gwub: 1_000_000_000, - balance_decreases_for_sec: 30 * SECONDS_PER_DAY, - unban_when_balance_below_gwub: 10_000_000, - }; - assert_eq!(CRASH_KEY, "ACCOUNTANT"); - assert_eq!(DEFAULT_PENDING_TRANSACTION_SCAN_INTERVAL, 3600); - assert_eq!(DEFAULT_PAYABLES_SCAN_INTERVAL, 3600); - assert_eq!(DEFAULT_RECEIVABLES_SCAN_INTERVAL, 3600); - assert_eq!(SECONDS_PER_DAY, 86_400); assert_eq!(DEFAULT_PENDING_TOO_LONG_SEC, 21_600); - assert_eq!(*PAYMENT_CURVES, payment_curves_expected); } #[test] fn new_calls_factories_properly() { - let config = BootstrapperConfig::new(); + let mut config = BootstrapperConfig::new(); + config.accountant_config_opt = Some(make_accountant_config_null()); let payable_dao_factory_called = Rc::new(RefCell::new(false)); let payable_dao = PayableDaoMock::new(); let payable_dao_factory = @@ -1324,12 +1361,7 @@ mod tests { let system = System::new("test"); let subject = AccountantBuilder::default() .bootstrapper_config(bc_from_ac_plus_earning_wallet( - AccountantConfig { - payables_scan_interval: Duration::from_millis(10_000), - receivables_scan_interval: Duration::from_millis(10_000), - pending_payable_scan_interval: Duration::from_millis(10_000), - when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, - }, + make_populated_accountant_config_with_defaults(), make_wallet("some_wallet_address"), )) .receivable_dao(receivable_dao) @@ -1421,12 +1453,7 @@ mod tests { let system = System::new("accountant_calls_payable_dao_to_mark_pending_payable"); let accountant = AccountantBuilder::default() .bootstrapper_config(bc_from_ac_plus_earning_wallet( - AccountantConfig { - payables_scan_interval: Duration::from_millis(10_000), - receivables_scan_interval: Duration::from_millis(10_000), - pending_payable_scan_interval: Duration::from_millis(10_000), - when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, - }, + make_populated_accountant_config_with_defaults(), make_wallet("some_wallet_address"), )) .payable_dao(payable_dao) @@ -1570,17 +1597,21 @@ mod tests { let accounts = vec![ PayableAccount { wallet: make_wallet("blah"), - balance: PAYMENT_CURVES.balance_to_decrease_from_gwub + 55, + balance: DEFAULT_PAYMENT_THRESHOLDS.debt_threshold_gwei + 55, last_paid_timestamp: from_time_t( - to_time_t(SystemTime::now()) - PAYMENT_CURVES.payment_suggested_after_sec - 5, + to_time_t(SystemTime::now()) + - DEFAULT_PAYMENT_THRESHOLDS.maturity_threshold_sec + - 5, ), pending_payable_opt: None, }, PayableAccount { wallet: make_wallet("foo"), - balance: PAYMENT_CURVES.balance_to_decrease_from_gwub + 66, + balance: DEFAULT_PAYMENT_THRESHOLDS.debt_threshold_gwei + 66, last_paid_timestamp: from_time_t( - to_time_t(SystemTime::now()) - PAYMENT_CURVES.payment_suggested_after_sec - 500, + to_time_t(SystemTime::now()) + - DEFAULT_PAYMENT_THRESHOLDS.maturity_threshold_sec + - 500, ), pending_payable_opt: None, }, @@ -1589,17 +1620,12 @@ mod tests { let system = System::new("report_accounts_payable forwarded to blockchain_bridge"); let mut subject = AccountantBuilder::default() .bootstrapper_config(bc_from_ac_plus_earning_wallet( - AccountantConfig { - payables_scan_interval: Duration::from_secs(100_000), - receivables_scan_interval: Duration::from_secs(100_000), - pending_payable_scan_interval: Duration::from_secs(100_000), - when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, - }, + make_populated_accountant_config_with_defaults(), make_wallet("some_wallet_address"), )) .payable_dao(payable_dao) .build(); - subject.scanners.pending_payable = Box::new(NullScanner); + subject.scanners.pending_payables = Box::new(NullScanner); subject.scanners.receivables = Box::new(NullScanner); let accountant_addr = subject.start(); let accountant_subs = Accountant::make_subs_from(&accountant_addr); @@ -1642,19 +1668,14 @@ mod tests { PersistentConfigurationMock::default().start_block_result(Ok(1_000_000)); let mut subject = AccountantBuilder::default() .bootstrapper_config(bc_from_ac_plus_earning_wallet( - AccountantConfig { - payables_scan_interval: Duration::from_secs(100_000), - receivables_scan_interval: Duration::from_secs(100_000), - pending_payable_scan_interval: Duration::from_secs(100_000), - when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, - }, + make_populated_accountant_config_with_defaults(), earning_wallet.clone(), )) .payable_dao(payable_dao) .receivable_dao(receivable_dao) .persistent_config(persistent_config) .build(); - subject.scanners.pending_payable = Box::new(NullScanner); + subject.scanners.pending_payables = Box::new(NullScanner); subject.scanners.payables = Box::new(NullScanner); let accountant_addr = subject.start(); let accountant_subs = Accountant::make_subs_from(&accountant_addr); @@ -1699,12 +1720,7 @@ mod tests { .more_money_received_result(Ok(())); let accountant = AccountantBuilder::default() .bootstrapper_config(bc_from_ac_plus_earning_wallet( - AccountantConfig { - payables_scan_interval: Duration::from_secs(10_000), - receivables_scan_interval: Duration::from_secs(10_000), - pending_payable_scan_interval: Duration::from_secs(10_000), - when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, - }, + make_populated_accountant_config_with_defaults(), earning_wallet.clone(), )) .payable_dao(PayableDaoMock::new().non_pending_payables_result(vec![])) @@ -1740,9 +1756,12 @@ mod tests { let system = System::new("accountant_scans_after_startup"); let config = bc_from_ac_plus_wallets( AccountantConfig { - payables_scan_interval: Duration::from_secs(100), //making sure we cannot enter the first repeated scanning - receivables_scan_interval: Duration::from_secs(100), - pending_payable_scan_interval: Duration::from_millis(100), //except here, where we use it to stop the system + scan_intervals: ScanIntervals { + payable_scan_interval: Duration::from_secs(100), //making sure we cannot enter the first repeated scanning + receivable_scan_interval: Duration::from_secs(100), + pending_payable_scan_interval: Duration::from_millis(100), //except here, where we use it to stop the system + }, + payment_thresholds: *DEFAULT_PAYMENT_THRESHOLDS, when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, }, make_wallet("buy"), @@ -1807,10 +1826,10 @@ mod tests { captured_timestamp < SystemTime::now() && captured_timestamp >= from_time_t(to_time_t(SystemTime::now()) - 5) ); - assert_eq!(captured_curves, *PAYMENT_CURVES); + assert_eq!(captured_curves, *DEFAULT_PAYMENT_THRESHOLDS); let paid_delinquencies_params = paid_delinquencies_params_arc.lock().unwrap(); assert_eq!(paid_delinquencies_params.len(), 1); - assert_eq!(paid_delinquencies_params[0], *PAYMENT_CURVES); + assert_eq!(paid_delinquencies_params[0], *DEFAULT_PAYMENT_THRESHOLDS); } #[test] @@ -1824,10 +1843,13 @@ mod tests { let (blockchain_bridge, _, blockchain_bridge_recording) = make_recorder(); let config = bc_from_ac_plus_earning_wallet( AccountantConfig { - payables_scan_interval: Duration::from_secs(100), - receivables_scan_interval: Duration::from_millis(99), - pending_payable_scan_interval: Duration::from_secs(100), + scan_intervals: ScanIntervals { + payable_scan_interval: Duration::from_secs(100), + receivable_scan_interval: Duration::from_millis(99), + pending_payable_scan_interval: Duration::from_secs(100), + }, when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, + payment_thresholds: *DEFAULT_PAYMENT_THRESHOLDS, }, earning_wallet.clone(), ); @@ -1858,9 +1880,9 @@ mod tests { .banned_dao(banned_dao) .persistent_config(persistent_config) .build(); - subject.scanners.pending_payable = Box::new(NullScanner); + subject.scanners.pending_payables = Box::new(NullScanner); subject.scanners.payables = Box::new(NullScanner); - subject.tools.notify_later_handle_scan_for_receivable = Box::new( + subject.tools.notify_later_scan_for_receivable = Box::new( NotifyLaterHandleMock::default() .notify_later_params(¬ify_later_receivable_params_arc), ); @@ -1898,7 +1920,7 @@ mod tests { ] ); //sadly I cannot effectively assert on the exact params - //they are a) real timestamp of now, b) constant payment_curves + //they are a) real timestamp of now, b) constant payment_thresholds //the Rust type system gives me enough support to be okay with counting occurrences let new_delinquencies_params = new_delinquencies_params_arc.lock().unwrap(); assert_eq!(new_delinquencies_params.len(), 3); //the third one is the signal to shut the system down @@ -1926,9 +1948,12 @@ mod tests { System::new("accountant_payable_scan_timer_triggers_scanning_for_pending_payable"); let config = bc_from_ac_plus_earning_wallet( AccountantConfig { - payables_scan_interval: Duration::from_secs(100), - receivables_scan_interval: Duration::from_secs(100), - pending_payable_scan_interval: Duration::from_millis(98), + scan_intervals: ScanIntervals { + payable_scan_interval: Duration::from_secs(100), + receivable_scan_interval: Duration::from_secs(100), + pending_payable_scan_interval: Duration::from_millis(98), + }, + payment_thresholds: *DEFAULT_PAYMENT_THRESHOLDS, when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, }, make_wallet("hi"), @@ -1959,7 +1984,7 @@ mod tests { .build(); subject.scanners.receivables = Box::new(NullScanner); //skipping subject.scanners.payables = Box::new(NullScanner); //skipping - subject.tools.notify_later_handle_scan_for_pending_payable = Box::new( + subject.tools.notify_later_scan_for_pending_payable = Box::new( NotifyLaterHandleMock::default() .notify_later_params(¬ify_later_pending_payable_params_arc), ); @@ -2008,10 +2033,13 @@ mod tests { let system = System::new("accountant_payable_scan_timer_triggers_scanning_for_payables"); let config = bc_from_ac_plus_earning_wallet( AccountantConfig { - payables_scan_interval: Duration::from_millis(97), - receivables_scan_interval: Duration::from_secs(100), - pending_payable_scan_interval: Duration::from_secs(100), + scan_intervals: ScanIntervals { + payable_scan_interval: Duration::from_millis(97), + receivable_scan_interval: Duration::from_secs(100), + pending_payable_scan_interval: Duration::from_secs(100), + }, when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, + payment_thresholds: *DEFAULT_PAYMENT_THRESHOLDS, }, make_wallet("hi"), ); @@ -2019,8 +2047,10 @@ mod tests { // slightly above minimum balance, to the right of the curve (time intersection) let account = PayableAccount { wallet: make_wallet("wallet"), - balance: PAYMENT_CURVES.balance_to_decrease_from_gwub + 5, - last_paid_timestamp: from_time_t(now - PAYMENT_CURVES.balance_decreases_for_sec - 10), + balance: DEFAULT_PAYMENT_THRESHOLDS.debt_threshold_gwei + 5, + last_paid_timestamp: from_time_t( + now - DEFAULT_PAYMENT_THRESHOLDS.threshold_interval_sec - 10, + ), pending_payable_opt: None, }; let mut payable_dao = PayableDaoMock::new() @@ -2038,9 +2068,9 @@ mod tests { .payable_dao(payable_dao) .persistent_config(persistent_config) .build(); - subject.scanners.pending_payable = Box::new(NullScanner); //skipping + subject.scanners.pending_payables = Box::new(NullScanner); //skipping subject.scanners.receivables = Box::new(NullScanner); //skipping - subject.tools.notify_later_handle_scan_for_payable = Box::new( + subject.tools.notify_later_scan_for_payable = Box::new( NotifyLaterHandleMock::default().notify_later_params(¬ify_later_payables_params_arc), ); let subject_addr = subject.start(); @@ -2077,41 +2107,42 @@ mod tests { #[test] fn scan_for_payable_message_does_not_trigger_payment_for_balances_below_the_curve() { init_test_logging(); - let config = bc_from_ac_plus_earning_wallet( - AccountantConfig { - payables_scan_interval: Duration::from_secs(100), - receivables_scan_interval: Duration::from_secs(1000), - pending_payable_scan_interval: Duration::from_secs(100), - when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, - }, - make_wallet("mine"), - ); + let accountant_config = make_populated_accountant_config_with_defaults(); + let config = bc_from_ac_plus_earning_wallet(accountant_config, make_wallet("mine")); let now = to_time_t(SystemTime::now()); + let payment_thresholds = PaymentThresholds { + threshold_interval_sec: 2_592_000, + debt_threshold_gwei: 1_000_000_000, + payment_grace_period_sec: 86_400, + maturity_threshold_sec: 86_400, + permanent_debt_allowed_gwei: 10_000_000, + unban_below_gwei: 10_000_000, + }; let accounts = vec![ // below minimum balance, to the right of time intersection (inside buffer zone) PayableAccount { wallet: make_wallet("wallet0"), - balance: PAYMENT_CURVES.permanent_debt_allowed_gwub - 1, + balance: payment_thresholds.permanent_debt_allowed_gwei - 1, last_paid_timestamp: from_time_t( - now - PAYMENT_CURVES.balance_decreases_for_sec - 10, + now - payment_thresholds.threshold_interval_sec - 10, ), pending_payable_opt: None, }, // above balance intersection, to the left of minimum time (inside buffer zone) PayableAccount { wallet: make_wallet("wallet1"), - balance: PAYMENT_CURVES.balance_to_decrease_from_gwub + 1, + balance: payment_thresholds.debt_threshold_gwei + 1, last_paid_timestamp: from_time_t( - now - PAYMENT_CURVES.payment_suggested_after_sec + 10, + now - payment_thresholds.maturity_threshold_sec + 10, ), pending_payable_opt: None, }, // above minimum balance, to the right of minimum time (not in buffer zone, below the curve) PayableAccount { wallet: make_wallet("wallet2"), - balance: PAYMENT_CURVES.balance_to_decrease_from_gwub - 1000, + balance: payment_thresholds.debt_threshold_gwei - 1000, last_paid_timestamp: from_time_t( - now - PAYMENT_CURVES.payment_suggested_after_sec - 1, + now - payment_thresholds.maturity_threshold_sec - 1, ), pending_payable_opt: None, }, @@ -2131,6 +2162,7 @@ mod tests { .payable_dao(payable_dao) .build(); subject.report_accounts_payable_sub = Some(report_accounts_payable_sub); + subject.config.payment_thresholds = payment_thresholds; subject.scan_for_payables(); @@ -2145,9 +2177,12 @@ mod tests { init_test_logging(); let config = bc_from_ac_plus_earning_wallet( AccountantConfig { - payables_scan_interval: Duration::from_millis(100), - receivables_scan_interval: Duration::from_secs(100), - pending_payable_scan_interval: Duration::from_secs(100), + scan_intervals: ScanIntervals { + pending_payable_scan_interval: Duration::from_secs(50_000), + payable_scan_interval: Duration::from_millis(100), + receivable_scan_interval: Duration::from_secs(50_000), + }, + payment_thresholds: DEFAULT_PAYMENT_THRESHOLDS.clone(), when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, }, make_wallet("mine"), @@ -2157,18 +2192,18 @@ mod tests { // slightly above minimum balance, to the right of the curve (time intersection) PayableAccount { wallet: make_wallet("wallet0"), - balance: PAYMENT_CURVES.permanent_debt_allowed_gwub + 1, + balance: DEFAULT_PAYMENT_THRESHOLDS.permanent_debt_allowed_gwei + 1, last_paid_timestamp: from_time_t( - now - PAYMENT_CURVES.balance_decreases_for_sec - 10, + now - DEFAULT_PAYMENT_THRESHOLDS.threshold_interval_sec - 10, ), pending_payable_opt: None, }, // slightly above the curve (balance intersection), to the right of minimum time PayableAccount { wallet: make_wallet("wallet1"), - balance: PAYMENT_CURVES.balance_to_decrease_from_gwub + 1, + balance: DEFAULT_PAYMENT_THRESHOLDS.debt_threshold_gwei + 1, last_paid_timestamp: from_time_t( - now - PAYMENT_CURVES.payment_suggested_after_sec - 10, + now - DEFAULT_PAYMENT_THRESHOLDS.maturity_threshold_sec - 10, ), pending_payable_opt: None, }, @@ -2187,7 +2222,7 @@ mod tests { .bootstrapper_config(config) .payable_dao(payable_dao) .build(); - subject.scanners.pending_payable = Box::new(NullScanner); + subject.scanners.pending_payables = Box::new(NullScanner); subject.scanners.receivables = Box::new(NullScanner); let subject_addr = subject.start(); let accountant_subs = Accountant::make_subs_from(&subject_addr); @@ -2221,15 +2256,8 @@ mod tests { #[test] fn scan_for_delinquencies_triggers_bans_and_unbans() { init_test_logging(); - let config = bc_from_ac_plus_earning_wallet( - AccountantConfig { - payables_scan_interval: Duration::from_secs(100), - receivables_scan_interval: Duration::from_secs(1000), - pending_payable_scan_interval: Duration::from_secs(100), - when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, - }, - make_wallet("mine"), - ); + let accountant_config = make_populated_accountant_config_with_defaults(); + let config = bc_from_ac_plus_earning_wallet(accountant_config, make_wallet("mine")); let newly_banned_1 = make_receivable_account(1234, true); let newly_banned_2 = make_receivable_account(2345, true); let newly_unbanned_1 = make_receivable_account(3456, false); @@ -2257,12 +2285,18 @@ mod tests { subject.scan_for_delinquencies(); - let new_delinquencies_parameters: MutexGuard> = + let new_delinquencies_parameters: MutexGuard> = new_delinquencies_parameters_arc.lock().unwrap(); - assert_eq!(PAYMENT_CURVES.clone(), new_delinquencies_parameters[0].1); - let paid_delinquencies_parameters: MutexGuard> = + assert_eq!( + DEFAULT_PAYMENT_THRESHOLDS.clone(), + new_delinquencies_parameters[0].1 + ); + let paid_delinquencies_parameters: MutexGuard> = paid_delinquencies_parameters_arc.lock().unwrap(); - assert_eq!(PAYMENT_CURVES.clone(), paid_delinquencies_parameters[0]); + assert_eq!( + DEFAULT_PAYMENT_THRESHOLDS.clone(), + paid_delinquencies_parameters[0] + ); let ban_parameters = ban_parameters_arc.lock().unwrap(); assert!(ban_parameters.contains(&newly_banned_1.wallet)); assert!(ban_parameters.contains(&newly_banned_2.wallet)); @@ -2323,12 +2357,7 @@ mod tests { payable_fingerprint_2.clone(), ]); let config = bc_from_ac_plus_earning_wallet( - AccountantConfig { - payables_scan_interval: Duration::from_secs(100), - receivables_scan_interval: Duration::from_secs(100), - pending_payable_scan_interval: Duration::from_secs(100), - when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, - }, + make_populated_accountant_config_with_defaults(), make_wallet("mine"), ); let system = System::new("pending payable scan"); @@ -2365,22 +2394,16 @@ mod tests { #[test] fn report_routing_service_provided_message_is_received() { init_test_logging(); - let config = bc_from_ac_plus_earning_wallet( - AccountantConfig { - payables_scan_interval: Duration::from_secs(100), - receivables_scan_interval: Duration::from_secs(100), - pending_payable_scan_interval: Duration::from_secs(100), - when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, - }, - make_wallet("hi"), - ); + let mut bootstrapper_config = BootstrapperConfig::default(); + bootstrapper_config.accountant_config_opt = Some(make_accountant_config_null()); + bootstrapper_config.earning_wallet = make_wallet("hi"); let more_money_receivable_parameters_arc = Arc::new(Mutex::new(vec![])); let payable_dao_mock = PayableDaoMock::new().non_pending_payables_result(vec![]); let receivable_dao_mock = ReceivableDaoMock::new() .more_money_receivable_parameters(&more_money_receivable_parameters_arc) .more_money_receivable_result(Ok(())); let subject = AccountantBuilder::default() - .bootstrapper_config(config) + .bootstrapper_config(bootstrapper_config) .payable_dao(payable_dao_mock) .receivable_dao(receivable_dao_mock) .build(); @@ -2420,12 +2443,7 @@ mod tests { init_test_logging(); let consuming_wallet = make_wallet("our consuming wallet"); let config = bc_from_ac_plus_wallets( - AccountantConfig { - payables_scan_interval: Duration::from_secs(100), - receivables_scan_interval: Duration::from_secs(100), - pending_payable_scan_interval: Duration::from_secs(100), - when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, - }, + make_populated_accountant_config_with_defaults(), consuming_wallet.clone(), make_wallet("our earning wallet"), ); @@ -2473,12 +2491,7 @@ mod tests { init_test_logging(); let earning_wallet = make_wallet("our earning wallet"); let config = bc_from_ac_plus_earning_wallet( - AccountantConfig { - payables_scan_interval: Duration::from_secs(100), - receivables_scan_interval: Duration::from_secs(100), - pending_payable_scan_interval: Duration::from_secs(100), - when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, - }, + make_populated_accountant_config_with_defaults(), earning_wallet.clone(), ); let more_money_receivable_parameters_arc = Arc::new(Mutex::new(vec![])); @@ -2524,12 +2537,7 @@ mod tests { fn report_routing_service_consumed_message_is_received() { init_test_logging(); let config = bc_from_ac_plus_earning_wallet( - AccountantConfig { - payables_scan_interval: Duration::from_secs(100), - receivables_scan_interval: Duration::from_secs(100), - pending_payable_scan_interval: Duration::from_secs(100), - when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, - }, + make_populated_accountant_config_with_defaults(), make_wallet("hi"), ); let more_money_payable_parameters_arc = Arc::new(Mutex::new(vec![])); @@ -2576,12 +2584,7 @@ mod tests { init_test_logging(); let consuming_wallet = make_wallet("the consuming wallet"); let config = bc_from_ac_plus_wallets( - AccountantConfig { - payables_scan_interval: Duration::from_secs(100), - receivables_scan_interval: Duration::from_secs(100), - pending_payable_scan_interval: Duration::from_secs(100), - when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, - }, + make_populated_accountant_config_with_defaults(), consuming_wallet.clone(), make_wallet("the earning wallet"), ); @@ -2625,12 +2628,7 @@ mod tests { init_test_logging(); let earning_wallet = make_wallet("the earning wallet"); let config = bc_from_ac_plus_earning_wallet( - AccountantConfig { - payables_scan_interval: Duration::from_secs(100), - receivables_scan_interval: Duration::from_secs(100), - pending_payable_scan_interval: Duration::from_secs(100), - when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, - }, + make_populated_accountant_config_with_defaults(), earning_wallet.clone(), ); let more_money_payable_parameters_arc = Arc::new(Mutex::new(vec![])); @@ -2671,12 +2669,7 @@ mod tests { fn report_exit_service_provided_message_is_received() { init_test_logging(); let config = bc_from_ac_plus_earning_wallet( - AccountantConfig { - payables_scan_interval: Duration::from_secs(100), - receivables_scan_interval: Duration::from_secs(100), - pending_payable_scan_interval: Duration::from_secs(100), - when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, - }, + make_populated_accountant_config_with_defaults(), make_wallet("hi"), ); let more_money_receivable_parameters_arc = Arc::new(Mutex::new(vec![])); @@ -2725,12 +2718,7 @@ mod tests { init_test_logging(); let consuming_wallet = make_wallet("my consuming wallet"); let config = bc_from_ac_plus_wallets( - AccountantConfig { - payables_scan_interval: Duration::from_secs(100), - receivables_scan_interval: Duration::from_secs(100), - pending_payable_scan_interval: Duration::from_secs(100), - when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, - }, + make_accountant_config_null(), consuming_wallet.clone(), make_wallet("my earning wallet"), ); @@ -2777,15 +2765,8 @@ mod tests { fn report_exit_service_provided_message_is_received_from_our_earning_wallet() { init_test_logging(); let earning_wallet = make_wallet("my earning wallet"); - let config = bc_from_ac_plus_earning_wallet( - AccountantConfig { - payables_scan_interval: Duration::from_secs(100), - receivables_scan_interval: Duration::from_secs(100), - pending_payable_scan_interval: Duration::from_secs(100), - when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, - }, - earning_wallet.clone(), - ); + let config = + bc_from_ac_plus_earning_wallet(make_accountant_config_null(), earning_wallet.clone()); let more_money_receivable_parameters_arc = Arc::new(Mutex::new(vec![])); let payable_dao_mock = PayableDaoMock::new().non_pending_payables_result(vec![]); let receivable_dao_mock = ReceivableDaoMock::new() @@ -2839,15 +2820,8 @@ mod tests { #[test] fn report_exit_service_consumed_message_is_received() { init_test_logging(); - let config = bc_from_ac_plus_earning_wallet( - AccountantConfig { - payables_scan_interval: Duration::from_secs(100), - receivables_scan_interval: Duration::from_secs(100), - pending_payable_scan_interval: Duration::from_secs(100), - when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, - }, - make_wallet("hi"), - ); + let config = + bc_from_ac_plus_earning_wallet(make_accountant_config_null(), make_wallet("hi")); let more_money_payable_parameters_arc = Arc::new(Mutex::new(vec![])); let payable_dao_mock = PayableDaoMock::new() .non_pending_payables_result(vec![]) @@ -2893,12 +2867,7 @@ mod tests { init_test_logging(); let consuming_wallet = make_wallet("own consuming wallet"); let config = bc_from_ac_plus_wallets( - AccountantConfig { - payables_scan_interval: Duration::from_secs(100), - receivables_scan_interval: Duration::from_secs(100), - pending_payable_scan_interval: Duration::from_secs(100), - when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, - }, + make_accountant_config_null(), consuming_wallet.clone(), make_wallet("own earning wallet"), ); @@ -2941,15 +2910,8 @@ mod tests { fn report_exit_service_consumed_message_is_received_for_our_earning_wallet() { init_test_logging(); let earning_wallet = make_wallet("own earning wallet"); - let config = bc_from_ac_plus_earning_wallet( - AccountantConfig { - payables_scan_interval: Duration::from_secs(100), - receivables_scan_interval: Duration::from_secs(100), - pending_payable_scan_interval: Duration::from_secs(100), - when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, - }, - earning_wallet.clone(), - ); + let config = + bc_from_ac_plus_earning_wallet(make_accountant_config_null(), earning_wallet.clone()); let more_money_payable_parameters_arc = Arc::new(Mutex::new(vec![])); let payable_dao_mock = PayableDaoMock::new() .non_pending_payables_result(vec![]) @@ -3323,6 +3285,7 @@ mod tests { fn accountant_can_be_crashed_properly_but_not_improperly() { let mut config = BootstrapperConfig::default(); config.crash_point = CrashPoint::Message; + config.accountant_config_opt = Some(make_accountant_config_null()); let accountant = AccountantBuilder::default() .bootstrapper_config(config) .build(); @@ -3372,26 +3335,40 @@ mod tests { #[test] fn payables_debug_summary_prints_pretty_summary() { let now = to_time_t(SystemTime::now()); + let payment_thresholds = PaymentThresholds { + threshold_interval_sec: 2_592_000, + debt_threshold_gwei: 1_000_000_000, + payment_grace_period_sec: 86_400, + maturity_threshold_sec: 86_400, + permanent_debt_allowed_gwei: 10_000_000, + unban_below_gwei: 10_000_000, + }; let qualified_payables = &[ PayableAccount { wallet: make_wallet("wallet0"), - balance: PAYMENT_CURVES.permanent_debt_allowed_gwub + 1000, + balance: payment_thresholds.permanent_debt_allowed_gwei + 1000, last_paid_timestamp: from_time_t( - now - PAYMENT_CURVES.balance_decreases_for_sec - 1234, + now - payment_thresholds.threshold_interval_sec - 1234, ), pending_payable_opt: None, }, PayableAccount { wallet: make_wallet("wallet1"), - balance: PAYMENT_CURVES.permanent_debt_allowed_gwub + 1, + balance: payment_thresholds.permanent_debt_allowed_gwei + 1, last_paid_timestamp: from_time_t( - now - PAYMENT_CURVES.balance_decreases_for_sec - 1, + now - payment_thresholds.threshold_interval_sec - 1, ), pending_payable_opt: None, }, ]; + let mut config = BootstrapperConfig::default(); + config.accountant_config_opt = Some(make_populated_accountant_config_with_defaults()); + let mut subject = AccountantBuilder::default() + .bootstrapper_config(config) + .build(); + subject.config.payment_thresholds = payment_thresholds; - let result = Accountant::payables_debug_summary(qualified_payables); + let result = subject.payables_debug_summary(qualified_payables); assert_eq!(result, "Paying qualified debts:\n\ @@ -3400,6 +3377,86 @@ mod tests { ) } + #[test] + fn threshold_calculation_depends_on_user_defined_payment_thresholds() { + let safe_age_params_arc = Arc::new(Mutex::new(vec![])); + let safe_balance_params_arc = Arc::new(Mutex::new(vec![])); + let calculate_payable_threshold_params_arc = Arc::new(Mutex::new(vec![])); + let balance = 5555; + let how_far_in_past = Duration::from_secs(1111 + 1); + let last_paid_timestamp = SystemTime::now().sub(how_far_in_past); + let payable_account = PayableAccount { + wallet: make_wallet("hi"), + balance, + last_paid_timestamp, + pending_payable_opt: None, + }; + let custom_payment_thresholds = PaymentThresholds { + maturity_threshold_sec: 1111, + payment_grace_period_sec: 2222, + permanent_debt_allowed_gwei: 3333, + debt_threshold_gwei: 4444, + threshold_interval_sec: 5555, + unban_below_gwei: 3333, + }; + let mut bootstrapper_config = BootstrapperConfig::default(); + bootstrapper_config.accountant_config_opt = Some(AccountantConfig { + scan_intervals: Default::default(), + payment_thresholds: custom_payment_thresholds, + when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, + }); + let payable_thresholds_tools = PayableThresholdToolsMock::default() + .is_innocent_age_params(&safe_age_params_arc) + .is_innocent_age_result( + how_far_in_past.as_secs() + <= custom_payment_thresholds.maturity_threshold_sec as u64, + ) + .is_innocent_balance_params(&safe_balance_params_arc) + .is_innocent_balance_result( + balance <= custom_payment_thresholds.permanent_debt_allowed_gwei, + ) + .calculate_payout_threshold_params(&calculate_payable_threshold_params_arc) + .calculate_payout_threshold_result(4567.0); //made up value + let mut subject = AccountantBuilder::default() + .bootstrapper_config(bootstrapper_config) + .build(); + subject.payable_threshold_tools = Box::new(payable_thresholds_tools); + + let result = subject.payable_exceeded_threshold(&payable_account); + + assert_eq!(result, Some(4567)); + let mut safe_age_params = safe_age_params_arc.lock().unwrap(); + let safe_age_single_params = safe_age_params.remove(0); + assert_eq!(*safe_age_params, vec![]); + let (time_elapsed, curve_derived_time) = safe_age_single_params; + assert!( + (how_far_in_past.as_secs() - 3) < time_elapsed + && time_elapsed < (how_far_in_past.as_secs() + 3) + ); + assert_eq!( + curve_derived_time, + custom_payment_thresholds.maturity_threshold_sec as u64 + ); + let safe_balance_params = safe_balance_params_arc.lock().unwrap(); + assert_eq!( + *safe_balance_params, + vec![( + payable_account.balance, + custom_payment_thresholds.permanent_debt_allowed_gwei + )] + ); + let mut calculate_payable_curves_params = + calculate_payable_threshold_params_arc.lock().unwrap(); + let calculate_payable_curves_single_params = calculate_payable_curves_params.remove(0); + assert_eq!(*calculate_payable_curves_params, vec![]); + let (payment_thresholds, time_elapsed) = calculate_payable_curves_single_params; + assert!( + (how_far_in_past.as_secs() - 3) < time_elapsed + && time_elapsed < (how_far_in_past.as_secs() + 3) + ); + assert_eq!(payment_thresholds, custom_payment_thresholds) + } + #[test] fn pending_transaction_is_registered_and_monitored_until_it_gets_confirmed_or_canceled() { init_test_logging(); @@ -3425,14 +3482,17 @@ mod tests { let pending_tx_hash_2 = H256::from_uint(&U256::from(567)); let rowid_for_account_1 = 3; let rowid_for_account_2 = 5; - let payable_timestamp_1 = SystemTime::now().sub(Duration::from_secs( - (PAYMENT_CURVES.payment_suggested_after_sec + 555) as u64, + let now = SystemTime::now(); + let past_payable_timestamp_1 = now.sub(Duration::from_secs( + (DEFAULT_PAYMENT_THRESHOLDS.maturity_threshold_sec + 555) as u64, )); - let payable_timestamp_2 = SystemTime::now().sub(Duration::from_secs( - (PAYMENT_CURVES.payment_suggested_after_sec + 50) as u64, + let past_payable_timestamp_2 = now.sub(Duration::from_secs( + (DEFAULT_PAYMENT_THRESHOLDS.maturity_threshold_sec + 50) as u64, )); - let payable_account_balance_1 = PAYMENT_CURVES.balance_to_decrease_from_gwub + 10; - let payable_account_balance_2 = PAYMENT_CURVES.balance_to_decrease_from_gwub + 666; + let this_payable_timestamp_1 = now; + let this_payable_timestamp_2 = now.add(Duration::from_millis(50)); + let payable_account_balance_1 = DEFAULT_PAYMENT_THRESHOLDS.debt_threshold_gwei + 10; + let payable_account_balance_2 = DEFAULT_PAYMENT_THRESHOLDS.debt_threshold_gwei + 666; let transaction_receipt_tx_2_first_round = TransactionReceipt::default(); let transaction_receipt_tx_1_second_round = TransactionReceipt::default(); let transaction_receipt_tx_2_second_round = TransactionReceipt::default(); @@ -3449,8 +3509,8 @@ mod tests { //a fingerprint of that payable in the DB - this happens inside send_raw_transaction() .send_transaction_tools_result(Box::new(SendTransactionToolsWrapperNull)) .send_transaction_tools_result(Box::new(SendTransactionToolsWrapperNull)) - .send_transaction_result(Ok((pending_tx_hash_1, payable_timestamp_1))) - .send_transaction_result(Ok((pending_tx_hash_2, payable_timestamp_2))) + .send_transaction_result(Ok((pending_tx_hash_1, past_payable_timestamp_1))) + .send_transaction_result(Ok((pending_tx_hash_2, past_payable_timestamp_2))) .get_transaction_receipt_params(&get_transaction_receipt_params_arc) .get_transaction_receipt_result(Ok(None)) .get_transaction_receipt_result(Ok(Some(transaction_receipt_tx_2_first_round))) @@ -3472,14 +3532,14 @@ mod tests { let account_1 = PayableAccount { wallet: wallet_account_1.clone(), balance: payable_account_balance_1, - last_paid_timestamp: payable_timestamp_1, + last_paid_timestamp: past_payable_timestamp_1, pending_payable_opt: None, }; let wallet_account_2 = make_wallet("creditor2"); let account_2 = PayableAccount { wallet: wallet_account_2.clone(), balance: payable_account_balance_2, - last_paid_timestamp: payable_timestamp_2, + last_paid_timestamp: past_payable_timestamp_2, pending_payable_opt: None, }; let pending_payable_scan_interval = 200; //should be slightly less than 1/5 of the time until shutting the system @@ -3493,17 +3553,21 @@ mod tests { .transaction_confirmed_result(Ok(())); let bootstrapper_config = bc_from_ac_plus_earning_wallet( AccountantConfig { - payables_scan_interval: Duration::from_secs(1_000_000), //we don't care about this scan - receivables_scan_interval: Duration::from_secs(1_000_000), //we don't care about this scan - pending_payable_scan_interval: Duration::from_millis(pending_payable_scan_interval), - when_pending_too_long_sec: (PAYMENT_CURVES.payment_suggested_after_sec + 1000) - as u64, + scan_intervals: ScanIntervals { + payable_scan_interval: Duration::from_secs(1_000_000), //we don't care about this scan + receivable_scan_interval: Duration::from_secs(1_000_000), //we don't care about this scan + pending_payable_scan_interval: Duration::from_millis( + pending_payable_scan_interval, + ), + }, + payment_thresholds: *DEFAULT_PAYMENT_THRESHOLDS, + when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, }, make_wallet("some_wallet_address"), ); let fingerprint_1_first_round = PendingPayableFingerprint { rowid_opt: Some(rowid_for_account_1), - timestamp: payable_timestamp_1, + timestamp: this_payable_timestamp_1, hash: pending_tx_hash_1, attempt_opt: Some(1), amount: payable_account_balance_1 as u64, @@ -3511,7 +3575,7 @@ mod tests { }; let fingerprint_2_first_round = PendingPayableFingerprint { rowid_opt: Some(rowid_for_account_2), - timestamp: payable_timestamp_2, + timestamp: this_payable_timestamp_2, hash: pending_tx_hash_2, attempt_opt: Some(1), amount: payable_account_balance_2 as u64, @@ -3537,7 +3601,7 @@ mod tests { attempt_opt: Some(4), ..fingerprint_2_first_round.clone() }; - let pending_payable_dao = PendingPayableDaoMock::default() + let mut pending_payable_dao = PendingPayableDaoMock::default() .return_all_fingerprints_params(&return_all_fingerprints_params_arc) .return_all_fingerprints_result(vec![]) .return_all_fingerprints_result(vec![ @@ -3553,8 +3617,6 @@ mod tests { fingerprint_2_third_round, ]) .return_all_fingerprints_result(vec![fingerprint_2_fourth_round.clone()]) - //extra one, for a case when we are too fast at some machine - .return_all_fingerprints_result(vec![]) .insert_fingerprint_params(&insert_fingerprint_params_arc) .insert_fingerprint_result(Ok(())) .insert_fingerprint_result(Ok(())) @@ -3569,9 +3631,11 @@ mod tests { .mark_failure_params(&mark_failure_params_arc) //we don't have a better solution yet, so we mark this down .mark_failure_result(Ok(())) + .mark_failure_result(Ok(())) .delete_fingerprint_params(&delete_record_params_arc) //this is used during confirmation of the successful one .delete_fingerprint_result(Ok(())); + pending_payable_dao.have_return_all_fingerprints_shut_down_the_system = true; let accountant_addr = Arbiter::builder() .stop_system_on_panic(true) .start(move |_| { @@ -3583,16 +3647,16 @@ mod tests { subject.scanners.receivables = Box::new(NullScanner); let notify_later_half_mock = NotifyLaterHandleMock::default() .notify_later_params(¬ify_later_scan_for_pending_payable_arc_cloned); - subject.tools.notify_later_handle_scan_for_pending_payable = + subject.tools.notify_later_scan_for_pending_payable = Box::new(notify_later_half_mock); let mut notify_half_mock = NotifyHandleMock::default() .notify_params(¬ify_cancel_failed_transaction_params_arc_cloned); notify_half_mock.do_you_want_to_proceed_after = true; - subject.tools.notify_handle_cancel_failed_transaction = Box::new(notify_half_mock); + subject.tools.notify_cancel_failed_transaction = Box::new(notify_half_mock); let mut notify_half_mock = NotifyHandleMock::default() .notify_params(¬ify_confirm_transaction_params_arc_cloned); notify_half_mock.do_you_want_to_proceed_after = true; - subject.tools.notify_handle_confirm_transaction = Box::new(notify_half_mock); + subject.tools.notify_confirm_transaction = Box::new(notify_half_mock); subject }); let mut peer_actors = peer_actors_builder().build(); @@ -3601,18 +3665,11 @@ mod tests { let blockchain_bridge_addr = blockchain_bridge.start(); let blockchain_bridge_subs = BlockchainBridge::make_subs_from(&blockchain_bridge_addr); peer_actors.blockchain_bridge = blockchain_bridge_subs.clone(); - let dummy_actor = DummyActor::new(None); - let dummy_actor_addr = Arbiter::builder() - .stop_system_on_panic(true) - .start(move |_| dummy_actor); send_bind_message!(accountant_subs, peer_actors); send_bind_message!(blockchain_bridge_subs, peer_actors); send_start_message!(accountant_subs); - dummy_actor_addr - .try_send(CleanUpMessage { sleep_ms: 1090 }) - .unwrap(); assert_eq!(system.run(), 0); let mut mark_pending_payable_params = mark_pending_payable_params_arc.lock().unwrap(); let first_payable = mark_pending_payable_params.remove(0); @@ -3627,7 +3684,7 @@ mod tests { assert_eq!(second_payable.0, wallet_account_2); assert_eq!(second_payable.1, rowid_for_account_2); let return_all_fingerprints_params = return_all_fingerprints_params_arc.lock().unwrap(); - //it varies with machines and sometimes we manage more cycles than necessary, + //it varies with machines and sometimes we manage more cycles than necessary assert!(return_all_fingerprints_params.len() >= 5); let non_pending_payables_params = non_pending_payables_params_arc.lock().unwrap(); assert_eq!(*non_pending_payables_params, vec![()]); //because we disabled further scanning for payables @@ -3672,6 +3729,7 @@ mod tests { notify_later_scan_for_pending_payable_params_arc .lock() .unwrap(); + //it varies with machines and sometimes we manage more cycles than necessary let vector_of_first_five_cycles = notify_later_check_for_confirmation .drain(0..=4) .collect_vec(); @@ -3737,7 +3795,7 @@ mod tests { fn accountant_receives_reported_transaction_receipts_and_processes_them_all() { let notify_handle_params_arc = Arc::new(Mutex::new(vec![])); let mut subject = AccountantBuilder::default().build(); - subject.tools.notify_handle_confirm_transaction = + subject.tools.notify_confirm_transaction = Box::new(NotifyHandleMock::default().notify_params(¬ify_handle_params_arc)); let subject_addr = subject.start(); let transaction_hash_1 = H256::from_uint(&U256::from(4545)); @@ -4045,21 +4103,21 @@ mod tests { } #[test] - fn jackass_unsigned_to_signed_handles_zero() { + fn unsigned_to_signed_handles_zero() { let result = unsigned_to_signed(0u64); assert_eq!(result, Ok(0i64)); } #[test] - fn jackass_unsigned_to_signed_handles_max_allowable() { + fn unsigned_to_signed_handles_max_allowable() { let result = unsigned_to_signed(i64::MAX as u64); assert_eq!(result, Ok(i64::MAX)); } #[test] - fn jackass_unsigned_to_signed_handles_max_plus_one() { + fn unsigned_to_signed_handles_max_plus_one() { let attempt = (i64::MAX as u64) + 1; let result = unsigned_to_signed((i64::MAX as u64) + 1); diff --git a/node/src/accountant/receivable_dao.rs b/node/src/accountant/receivable_dao.rs index 1263d0c19..167d8b91e 100644 --- a/node/src/accountant/receivable_dao.rs +++ b/node/src/accountant/receivable_dao.rs @@ -1,11 +1,12 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::{unsigned_to_signed, PaymentCurves}; +use crate::accountant::unsigned_to_signed; use crate::blockchain::blockchain_interface::Transaction; use crate::database::connection_wrapper::ConnectionWrapper; use crate::database::dao_utils; use crate::database::dao_utils::{to_time_t, DaoFactoryReal}; use crate::db_config::config_dao::{ConfigDaoWrite, ConfigDaoWriteableReal}; use crate::db_config::persistent_configuration::PersistentConfigError; +use crate::sub_lib::accountant::PaymentThresholds; use crate::sub_lib::wallet::Wallet; use indoc::indoc; use masq_lib::logger::Logger; @@ -47,10 +48,10 @@ pub trait ReceivableDao: Send { fn new_delinquencies( &self, now: SystemTime, - payment_curves: &PaymentCurves, + payment_thresholds: &PaymentThresholds, ) -> Vec; - fn paid_delinquencies(&self, payment_curves: &PaymentCurves) -> Vec; + fn paid_delinquencies(&self, payment_thresholds: &PaymentThresholds) -> Vec; fn top_records(&self, minimum_amount: u64, maximum_age: u64) -> Vec; @@ -156,12 +157,12 @@ impl ReceivableDao for ReceivableDaoReal { fn new_delinquencies( &self, system_now: SystemTime, - payment_curves: &PaymentCurves, + payment_thresholds: &PaymentThresholds, ) -> Vec { let now = to_time_t(system_now); - let slope = (payment_curves.permanent_debt_allowed_gwub as f64 - - payment_curves.balance_to_decrease_from_gwub as f64) - / (payment_curves.balance_decreases_for_sec as f64); + let slope = (payment_thresholds.permanent_debt_allowed_gwei as f64 + - payment_thresholds.debt_threshold_gwei as f64) + / (payment_thresholds.threshold_interval_sec as f64); let sql = indoc!( r" select r.wallet_address, r.balance, r.last_received_timestamp @@ -177,9 +178,9 @@ impl ReceivableDao for ReceivableDaoReal { stmt.query_map( named_params! { ":slope": slope, - ":sugg_and_grace": payment_curves.sugg_and_grace(now), - ":balance_to_decrease_from": payment_curves.balance_to_decrease_from_gwub, - ":permanent_debt": payment_curves.permanent_debt_allowed_gwub, + ":sugg_and_grace": payment_thresholds.sugg_and_grace(now), + ":balance_to_decrease_from": payment_thresholds.debt_threshold_gwei, + ":permanent_debt": payment_thresholds.permanent_debt_allowed_gwei, }, Self::row_to_account, ) @@ -188,7 +189,7 @@ impl ReceivableDao for ReceivableDaoReal { .collect() } - fn paid_delinquencies(&self, payment_curves: &PaymentCurves) -> Vec { + fn paid_delinquencies(&self, payment_thresholds: &PaymentThresholds) -> Vec { let sql = indoc!( r" select r.wallet_address, r.balance, r.last_received_timestamp @@ -200,7 +201,7 @@ impl ReceivableDao for ReceivableDaoReal { let mut stmt = self.conn.prepare(sql).expect("Couldn't prepare statement"); stmt.query_map( named_params! { - ":unban_balance": payment_curves.unban_when_balance_below_gwub, + ":unban_balance": payment_thresholds.unban_below_gwei, }, Self::row_to_account, ) @@ -765,37 +766,37 @@ mod tests { #[test] fn new_delinquencies_unit_slope() { - let pcs = PaymentCurves { - payment_suggested_after_sec: 25, - payment_grace_before_ban_sec: 50, - permanent_debt_allowed_gwub: 100, - balance_to_decrease_from_gwub: 200, - balance_decreases_for_sec: 100, - unban_when_balance_below_gwub: 0, // doesn't matter for this test + let pcs = PaymentThresholds { + maturity_threshold_sec: 25, + payment_grace_period_sec: 50, + permanent_debt_allowed_gwei: 100, + debt_threshold_gwei: 200, + threshold_interval_sec: 100, + unban_below_gwei: 0, // doesn't matter for this test }; let now = now_time_t(); let mut not_delinquent_inside_grace_period = make_receivable_account(1234, false); - not_delinquent_inside_grace_period.balance = pcs.balance_to_decrease_from_gwub + 1; + not_delinquent_inside_grace_period.balance = pcs.debt_threshold_gwei + 1; not_delinquent_inside_grace_period.last_received_timestamp = from_time_t(pcs.sugg_and_grace(now) + 2); let mut not_delinquent_after_grace_below_slope = make_receivable_account(2345, false); - not_delinquent_after_grace_below_slope.balance = pcs.balance_to_decrease_from_gwub - 2; + not_delinquent_after_grace_below_slope.balance = pcs.debt_threshold_gwei - 2; not_delinquent_after_grace_below_slope.last_received_timestamp = from_time_t(pcs.sugg_and_grace(now) - 1); let mut delinquent_above_slope_after_grace = make_receivable_account(3456, true); - delinquent_above_slope_after_grace.balance = pcs.balance_to_decrease_from_gwub - 1; + delinquent_above_slope_after_grace.balance = pcs.debt_threshold_gwei - 1; delinquent_above_slope_after_grace.last_received_timestamp = from_time_t(pcs.sugg_and_grace(now) - 2); let mut not_delinquent_below_slope_before_stop = make_receivable_account(4567, false); - not_delinquent_below_slope_before_stop.balance = pcs.permanent_debt_allowed_gwub + 1; + not_delinquent_below_slope_before_stop.balance = pcs.permanent_debt_allowed_gwei + 1; not_delinquent_below_slope_before_stop.last_received_timestamp = from_time_t(pcs.sugg_thru_decreasing(now) + 2); let mut delinquent_above_slope_before_stop = make_receivable_account(5678, true); - delinquent_above_slope_before_stop.balance = pcs.permanent_debt_allowed_gwub + 2; + delinquent_above_slope_before_stop.balance = pcs.permanent_debt_allowed_gwei + 2; delinquent_above_slope_before_stop.last_received_timestamp = from_time_t(pcs.sugg_thru_decreasing(now) + 1); let mut not_delinquent_above_slope_after_stop = make_receivable_account(6789, false); - not_delinquent_above_slope_after_stop.balance = pcs.permanent_debt_allowed_gwub - 1; + not_delinquent_above_slope_after_stop.balance = pcs.permanent_debt_allowed_gwei - 1; not_delinquent_above_slope_after_stop.last_received_timestamp = from_time_t(pcs.sugg_thru_decreasing(now) - 2); let home_dir = ensure_node_home_directory_exists("accountant", "new_delinquencies"); @@ -820,13 +821,13 @@ mod tests { #[test] fn new_delinquencies_shallow_slope() { - let pcs = PaymentCurves { - payment_suggested_after_sec: 100, - payment_grace_before_ban_sec: 100, - permanent_debt_allowed_gwub: 100, - balance_to_decrease_from_gwub: 110, - balance_decreases_for_sec: 100, - unban_when_balance_below_gwub: 0, // doesn't matter for this test + let pcs = PaymentThresholds { + maturity_threshold_sec: 100, + payment_grace_period_sec: 100, + permanent_debt_allowed_gwei: 100, + debt_threshold_gwei: 110, + threshold_interval_sec: 100, + unban_below_gwei: 0, // doesn't matter for this test }; let now = now_time_t(); let mut not_delinquent = make_receivable_account(1234, false); @@ -853,13 +854,13 @@ mod tests { #[test] fn new_delinquencies_steep_slope() { - let pcs = PaymentCurves { - payment_suggested_after_sec: 100, - payment_grace_before_ban_sec: 100, - permanent_debt_allowed_gwub: 100, - balance_to_decrease_from_gwub: 1100, - balance_decreases_for_sec: 100, - unban_when_balance_below_gwub: 0, // doesn't matter for this test + let pcs = PaymentThresholds { + maturity_threshold_sec: 100, + payment_grace_period_sec: 100, + permanent_debt_allowed_gwei: 100, + debt_threshold_gwei: 1100, + threshold_interval_sec: 100, + unban_below_gwei: 0, // doesn't matter for this test }; let now = now_time_t(); let mut not_delinquent = make_receivable_account(1234, false); @@ -886,13 +887,13 @@ mod tests { #[test] fn new_delinquencies_does_not_find_existing_delinquencies() { - let pcs = PaymentCurves { - payment_suggested_after_sec: 25, - payment_grace_before_ban_sec: 50, - permanent_debt_allowed_gwub: 100, - balance_to_decrease_from_gwub: 200, - balance_decreases_for_sec: 100, - unban_when_balance_below_gwub: 0, // doesn't matter for this test + let pcs = PaymentThresholds { + maturity_threshold_sec: 25, + payment_grace_period_sec: 50, + permanent_debt_allowed_gwei: 100, + debt_threshold_gwei: 200, + threshold_interval_sec: 100, + unban_below_gwei: 0, // doesn't matter for this test }; let now = now_time_t(); let mut existing_delinquency = make_receivable_account(1234, true); @@ -923,13 +924,13 @@ mod tests { #[test] fn paid_delinquencies() { - let pcs = PaymentCurves { - payment_suggested_after_sec: 0, // doesn't matter for this test - payment_grace_before_ban_sec: 0, // doesn't matter for this test - permanent_debt_allowed_gwub: 0, // doesn't matter for this test - balance_to_decrease_from_gwub: 0, // doesn't matter for this test - balance_decreases_for_sec: 0, // doesn't matter for this test - unban_when_balance_below_gwub: 50, + let pcs = PaymentThresholds { + maturity_threshold_sec: 0, // doesn't matter for this test + payment_grace_period_sec: 0, // doesn't matter for this test + permanent_debt_allowed_gwei: 0, // doesn't matter for this test + debt_threshold_gwei: 0, // doesn't matter for this test + threshold_interval_sec: 0, // doesn't matter for this test + unban_below_gwei: 50, }; let mut paid_delinquent = make_receivable_account(1234, true); paid_delinquent.balance = 50; @@ -954,13 +955,13 @@ mod tests { #[test] fn paid_delinquencies_does_not_find_existing_nondelinquencies() { - let pcs = PaymentCurves { - payment_suggested_after_sec: 0, // doesn't matter for this test - payment_grace_before_ban_sec: 0, // doesn't matter for this test - permanent_debt_allowed_gwub: 0, // doesn't matter for this test - balance_to_decrease_from_gwub: 0, // doesn't matter for this test - balance_decreases_for_sec: 0, // doesn't matter for this test - unban_when_balance_below_gwub: 50, + let pcs = PaymentThresholds { + maturity_threshold_sec: 0, // doesn't matter for this test + payment_grace_period_sec: 0, // doesn't matter for this test + permanent_debt_allowed_gwei: 0, // doesn't matter for this test + debt_threshold_gwei: 0, // doesn't matter for this test + threshold_interval_sec: 0, // doesn't matter for this test + unban_below_gwei: 50, }; let mut newly_non_delinquent = make_receivable_account(1234, false); newly_non_delinquent.balance = 25; diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs index 1ee7e50e3..796b0eca1 100644 --- a/node/src/accountant/test_utils.rs +++ b/node/src/accountant/test_utils.rs @@ -11,7 +11,7 @@ use crate::accountant::pending_payable_dao::{ use crate::accountant::receivable_dao::{ ReceivableAccount, ReceivableDao, ReceivableDaoError, ReceivableDaoFactory, }; -use crate::accountant::{Accountant, PaymentCurves, PendingPayableId}; +use crate::accountant::{Accountant, PendingPayableId}; use crate::banned_dao::{BannedDao, BannedDaoFactory}; use crate::blockchain::blockchain_bridge::PendingPayableFingerprint; use crate::blockchain::blockchain_interface::Transaction; @@ -20,10 +20,11 @@ use crate::database::dao_utils; use crate::database::dao_utils::{from_time_t, to_time_t}; use crate::db_config::config_dao::{ConfigDao, ConfigDaoFactory}; use crate::db_config::mocks::ConfigDaoMock; -use crate::sub_lib::accountant::AccountantConfig; +use crate::sub_lib::accountant::{AccountantConfig, PaymentThresholds}; use crate::sub_lib::wallet::Wallet; use crate::test_utils::make_wallet; use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMock; +use crate::test_utils::unshared_test_utils::make_populated_accountant_config_with_defaults; use actix::System; use ethereum_types::{BigEndianHash, H256, U256}; use rusqlite::{Connection, Error, OptionalExtension}; @@ -131,7 +132,11 @@ impl AccountantBuilder { } pub fn build(self) -> Accountant { - let config = self.config.unwrap_or(Default::default()); + let config = self.config.unwrap_or({ + let mut config = BootstrapperConfig::default(); + config.accountant_config_opt = Some(make_populated_accountant_config_with_defaults()); + config + }); let payable_dao_factory = self .payable_dao_factory .unwrap_or(Box::new(PayableDaoFactoryMock::new(PayableDaoMock::new()))); @@ -441,9 +446,9 @@ pub struct ReceivableDaoMock { more_money_received_parameters: Arc>>>, more_money_received_results: RefCell>>, receivables_results: RefCell>>, - new_delinquencies_parameters: Arc>>, + new_delinquencies_parameters: Arc>>, new_delinquencies_results: RefCell>>, - paid_delinquencies_parameters: Arc>>, + paid_delinquencies_parameters: Arc>>, paid_delinquencies_results: RefCell>>, top_records_parameters: Arc>>, top_records_results: RefCell>>, @@ -487,12 +492,12 @@ impl ReceivableDao for ReceivableDaoMock { fn new_delinquencies( &self, now: SystemTime, - payment_curves: &PaymentCurves, + payment_thresholds: &PaymentThresholds, ) -> Vec { self.new_delinquencies_parameters .lock() .unwrap() - .push((now, payment_curves.clone())); + .push((now, payment_thresholds.clone())); if self.new_delinquencies_results.borrow().is_empty() && self.have_new_delinquencies_shutdown_the_system { @@ -502,11 +507,11 @@ impl ReceivableDao for ReceivableDaoMock { self.new_delinquencies_results.borrow_mut().remove(0) } - fn paid_delinquencies(&self, payment_curves: &PaymentCurves) -> Vec { + fn paid_delinquencies(&self, payment_thresholds: &PaymentThresholds) -> Vec { self.paid_delinquencies_parameters .lock() .unwrap() - .push(payment_curves.clone()); + .push(payment_thresholds.clone()); self.paid_delinquencies_results.borrow_mut().remove(0) } @@ -556,7 +561,7 @@ impl ReceivableDaoMock { pub fn new_delinquencies_parameters( mut self, - parameters: &Arc>>, + parameters: &Arc>>, ) -> Self { self.new_delinquencies_parameters = parameters.clone(); self @@ -569,7 +574,7 @@ impl ReceivableDaoMock { pub fn paid_delinquencies_parameters( mut self, - parameters: &Arc>>, + parameters: &Arc>>, ) -> Self { self.paid_delinquencies_parameters = parameters.clone(); self @@ -650,7 +655,7 @@ pub fn bc_from_ac_plus_earning_wallet( earning_wallet: Wallet, ) -> BootstrapperConfig { let mut bc = BootstrapperConfig::new(); - bc.accountant_config = ac; + bc.accountant_config_opt = Some(ac); bc.earning_wallet = earning_wallet; bc } @@ -661,7 +666,7 @@ pub fn bc_from_ac_plus_wallets( earning_wallet: Wallet, ) -> BootstrapperConfig { let mut bc = BootstrapperConfig::new(); - bc.accountant_config = ac; + bc.accountant_config_opt = Some(ac); bc.consuming_wallet_opt = Some(consuming_wallet); bc.earning_wallet = earning_wallet; bc diff --git a/node/src/accountant/tools.rs b/node/src/accountant/tools.rs index 27a65750c..293fe6c72 100644 --- a/node/src/accountant/tools.rs +++ b/node/src/accountant/tools.rs @@ -6,12 +6,25 @@ pub(in crate::accountant) mod accountant_tools { RequestTransactionReceipts, ScanForPayables, ScanForPendingPayable, ScanForReceivables, }; use crate::sub_lib::utils::{NotifyHandle, NotifyLaterHandle}; - use actix::Recipient; + use actix::{AsyncContext, Context, Recipient}; #[cfg(test)] use std::any::Any; + use std::time::Duration; + + macro_rules! notify_later_assertable { + ($accountant: expr, $ctx: expr, $message_type: ident, $notify_later_handle_field: ident,$scan_interval_field: ident) => { + let closure = + Box::new(|msg: $message_type, interval: Duration| $ctx.notify_later(msg, interval)); + let _ = $accountant.tools.$notify_later_handle_field.notify_later( + $message_type {}, + $accountant.config.scan_intervals.$scan_interval_field, + closure, + ); + }; + } pub struct Scanners { - pub pending_payable: Box, + pub pending_payables: Box, pub payables: Box, pub receivables: Box, } @@ -19,7 +32,7 @@ pub(in crate::accountant) mod accountant_tools { impl Default for Scanners { fn default() -> Self { Scanners { - pending_payable: Box::new(PendingPaymentsScanner), + pending_payables: Box::new(PendingPaymentsScanner), payables: Box::new(PayablesScanner), receivables: Box::new(ReceivablesScanner), } @@ -28,6 +41,7 @@ pub(in crate::accountant) mod accountant_tools { pub trait Scanner { fn scan(&self, accountant: &Accountant); + fn notify_later_assertable(&self, accountant: &Accountant, ctx: &mut Context); as_any_dcl!(); } @@ -38,6 +52,15 @@ pub(in crate::accountant) mod accountant_tools { fn scan(&self, accountant: &Accountant) { accountant.scan_for_pending_payable() } + fn notify_later_assertable(&self, accountant: &Accountant, ctx: &mut Context) { + notify_later_assertable!( + accountant, + ctx, + ScanForPendingPayable, + notify_later_scan_for_pending_payable, + pending_payable_scan_interval + ); + } as_any_impl!(); } @@ -48,6 +71,17 @@ pub(in crate::accountant) mod accountant_tools { fn scan(&self, accountant: &Accountant) { accountant.scan_for_payables() } + + fn notify_later_assertable(&self, accountant: &Accountant, ctx: &mut Context) { + notify_later_assertable!( + accountant, + ctx, + ScanForPayables, + notify_later_scan_for_payable, + payable_scan_interval + ); + } + as_any_impl!(); } @@ -59,28 +93,44 @@ pub(in crate::accountant) mod accountant_tools { accountant.scan_for_received_payments(); accountant.scan_for_delinquencies() } + + fn notify_later_assertable(&self, accountant: &Accountant, ctx: &mut Context) { + notify_later_assertable!( + accountant, + ctx, + ScanForReceivables, + notify_later_scan_for_receivable, + receivable_scan_interval + ); + } + as_any_impl!(); } - //this is for when you want to turn off the certain scanner in your testing, giving you space for testing just a constrained area + //this is for turning off a certain scanner in testing to prevent it make "noise" #[derive(Debug, PartialEq)] pub struct NullScanner; impl Scanner for NullScanner { fn scan(&self, _accountant: &Accountant) {} + fn notify_later_assertable( + &self, + _accountant: &Accountant, + _ctx: &mut Context, + ) { + } as_any_impl!(); } #[derive(Default)] pub struct TransactionConfirmationTools { - pub notify_later_handle_scan_for_pending_payable: + pub notify_later_scan_for_pending_payable: Box>, - pub notify_later_handle_scan_for_payable: Box>, - pub notify_later_handle_scan_for_receivable: Box>, + pub notify_later_scan_for_payable: Box>, + pub notify_later_scan_for_receivable: Box>, + pub notify_confirm_transaction: Box>, + pub notify_cancel_failed_transaction: Box>, pub request_transaction_receipts_subs_opt: Option>, - pub notify_handle_confirm_transaction: Box>, - pub notify_handle_cancel_failed_transaction: - Box>, } } @@ -95,7 +145,7 @@ mod tests { let subject = Scanners::default(); assert_eq!( - subject.pending_payable.as_any().downcast_ref(), + subject.pending_payables.as_any().downcast_ref(), Some(&PendingPaymentsScanner) ); assert_eq!( diff --git a/node/src/actor_system_factory.rs b/node/src/actor_system_factory.rs index e432c51cb..06c64ded8 100644 --- a/node/src/actor_system_factory.rs +++ b/node/src/actor_system_factory.rs @@ -50,11 +50,12 @@ pub trait ActorSystemFactory: Send { config: BootstrapperConfig, actor_factory: Box, persist_config: &dyn PersistentConfiguration, - actor_system_factory_tools: &dyn ActorSystemFactoryTools, ) -> StreamHandlerPoolSubs; } -pub struct ActorSystemFactoryReal; +pub struct ActorSystemFactoryReal { + t: Box, +} impl ActorSystemFactory for ActorSystemFactoryReal { fn make_and_start_actors( @@ -62,23 +63,22 @@ impl ActorSystemFactory for ActorSystemFactoryReal { config: BootstrapperConfig, actor_factory: Box, persist_config: &dyn PersistentConfiguration, - tools: &dyn ActorSystemFactoryTools, ) -> StreamHandlerPoolSubs { - let main_cryptde = tools.main_cryptde_ref(); - let alias_cryptde = tools.alias_cryptde_ref(); - tools.database_chain_assertion(config.blockchain_bridge_config.chain, persist_config); - - tools.prepare_initial_messages(main_cryptde, alias_cryptde, config, actor_factory) + self.t + .validate_database_chain(persist_config, config.blockchain_bridge_config.chain); + let (main_cryptde, alias_cryptde) = self.t.cryptdes(); + self.t + .prepare_initial_messages(main_cryptde, alias_cryptde, config, actor_factory) } } impl ActorSystemFactoryReal { - pub fn new() -> Self { - Self {} + pub fn new(tools: Box) -> Self { + Self { t: tools } } } -pub trait ActorSystemFactoryTools { +pub trait ActorSystemFactoryTools: Send { fn prepare_initial_messages( &self, main_cryptde: &'static dyn CryptDE, @@ -86,12 +86,11 @@ pub trait ActorSystemFactoryTools { config: BootstrapperConfig, actor_factory: Box, ) -> StreamHandlerPoolSubs; - fn main_cryptde_ref(&self) -> &'static dyn CryptDE; - fn alias_cryptde_ref(&self) -> &'static dyn CryptDE; - fn database_chain_assertion( + fn cryptdes(&self) -> (&'static dyn CryptDE, &'static dyn CryptDE); + fn validate_database_chain( &self, - chain: Chain, persistent_config: &dyn PersistentConfiguration, + chain: Chain, ); } @@ -121,7 +120,6 @@ impl ActorSystemFactoryTools for ActorSystemFactoryToolsReal { .neighborhood_config .mode .rate_pack() - .clone() .exit_service_rate, exit_byte_rate: config.neighborhood_config.mode.rate_pack().exit_byte_rate, crashable: is_crashable(&config), @@ -137,13 +135,11 @@ impl ActorSystemFactoryTools for ActorSystemFactoryToolsReal { .neighborhood_config .mode .rate_pack() - .clone() .routing_service_rate, per_routing_byte: config .neighborhood_config .mode .rate_pack() - .clone() .routing_byte_rate, is_decentralized: config.neighborhood_config.mode.is_decentralized(), crashable: is_crashable(&config), @@ -215,25 +211,24 @@ impl ActorSystemFactoryTools for ActorSystemFactoryToolsReal { stream_handler_pool_subs } - fn main_cryptde_ref(&self) -> &'static dyn CryptDE { - bootstrapper::main_cryptde_ref() - } - - fn alias_cryptde_ref(&self) -> &'static dyn CryptDE { - bootstrapper::alias_cryptde_ref() + fn cryptdes(&self) -> (&'static dyn CryptDE, &'static dyn CryptDE) { + ( + bootstrapper::main_cryptde_ref(), + bootstrapper::alias_cryptde_ref(), + ) } - fn database_chain_assertion( + fn validate_database_chain( &self, - chain: Chain, persistent_config: &dyn PersistentConfiguration, + chain: Chain, ) { - let requested_chain = chain.rec().literal_identifier.to_string(); - let chain_in_db = persistent_config.chain_name(); - if requested_chain != chain_in_db { + let from_db = persistent_config.chain_name(); + let demanded = chain.rec().literal_identifier.to_string(); + if demanded != from_db { panic!( "Database with the wrong chain name detected; expected: {}, was: {}", - requested_chain, chain_in_db + demanded, from_db ) } } @@ -553,22 +548,19 @@ impl AutomapControlFactory for AutomapControlFactoryNull { #[cfg(test)] mod tests { use super::*; - use crate::accountant::DEFAULT_PENDING_TOO_LONG_SEC; use crate::bootstrapper::{Bootstrapper, RealUser}; use crate::database::connection_wrapper::ConnectionWrapper; use crate::node_test_utils::{ make_stream_handler_pool_subs_from, make_stream_handler_pool_subs_from_recorder, start_recorder_refcell_opt, }; - use crate::sub_lib::accountant::AccountantConfig; use crate::sub_lib::blockchain_bridge::BlockchainBridgeConfig; use crate::sub_lib::cryptde::{PlainData, PublicKey}; use crate::sub_lib::cryptde_null::CryptDENull; use crate::sub_lib::dispatcher::{InboundClientData, StreamShutdownMsg}; - use crate::sub_lib::neighborhood::NeighborhoodConfig; use crate::sub_lib::neighborhood::NeighborhoodMode; use crate::sub_lib::neighborhood::NodeDescriptor; - use crate::sub_lib::neighborhood::DEFAULT_RATE_PACK; + use crate::sub_lib::neighborhood::{NeighborhoodConfig, DEFAULT_RATE_PACK}; use crate::sub_lib::node_addr::NodeAddr; use crate::sub_lib::peer_actors::StartMessage; use crate::sub_lib::stream_handler_pool::TransmitDataMsg; @@ -577,7 +569,6 @@ mod tests { use crate::test_utils::main_cryptde; use crate::test_utils::make_wallet; use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMock; - use crate::test_utils::pure_test_utils::{CleanUpMessage, DummyActor}; use crate::test_utils::recorder::{ make_accountant_subs_from_recorder, make_blockchain_bridge_subs_from, make_configurator_subs_from, make_hopper_subs_from, make_neighborhood_subs_from, @@ -585,6 +576,9 @@ mod tests { make_ui_gateway_subs_from_recorder, Recording, }; use crate::test_utils::recorder::{make_recorder, Recorder}; + use crate::test_utils::unshared_test_utils::{ + make_populated_accountant_config_with_defaults, CleanUpMessage, DummyActor, + }; use crate::test_utils::{alias_cryptde, rate_pack}; use crate::{hopper, proxy_client, proxy_server, stream_handler_pool, ui_gateway}; use actix::{Actor, Arbiter, System}; @@ -622,10 +616,8 @@ mod tests { >, >, prepare_initial_messages_results: RefCell>, - main_cryptde_ref_results: RefCell>, - alias_cryptde_ref_results: RefCell>, - database_chain_assertion_params: Arc>>, - compare_persistent_config_to_pointer: RefCell>, + cryptdes_results: RefCell>, //first main, second alias + validate_database_chain_params: Arc>>, } impl ActorSystemFactoryTools for ActorSystemFactoryToolsMock { @@ -645,52 +637,33 @@ mod tests { self.prepare_initial_messages_results.borrow_mut().remove(0) } - fn main_cryptde_ref(&self) -> &'static dyn CryptDE { - self.main_cryptde_ref_results.borrow_mut().remove(0) + fn cryptdes(&self) -> (&'static dyn CryptDE, &'static dyn CryptDE) { + self.cryptdes_results.borrow_mut().remove(0) } - fn alias_cryptde_ref(&self) -> &'static dyn CryptDE { - self.alias_cryptde_ref_results.borrow_mut().remove(0) - } - - fn database_chain_assertion( + fn validate_database_chain( &self, - chain: Chain, persistent_config: &dyn PersistentConfiguration, + chain: Chain, ) { - self.database_chain_assertion_params + self.validate_database_chain_params .lock() .unwrap() .push(chain); - assert_eq!( - self.compare_persistent_config_to_pointer - .borrow_mut() - .remove(0), - addr_of!(*persistent_config) - ) + //nonstandard... + //for an outer assertion, proving that we're using the expected, pasted-in object + persistent_config.chain_name(); } } impl ActorSystemFactoryToolsMock { - pub fn main_cryptde_ref_result(self, result: &'static dyn CryptDE) -> Self { - self.main_cryptde_ref_results.borrow_mut().push(result); - self - } - - pub fn alias_cryptde_ref_result(self, result: &'static dyn CryptDE) -> Self { - self.alias_cryptde_ref_results.borrow_mut().push(result); + pub fn cryptdes_result(self, result: (&'static dyn CryptDE, &'static dyn CryptDE)) -> Self { + self.cryptdes_results.borrow_mut().push(result); self } - pub fn database_chain_assertion_params( - mut self, - real_param: &Arc>>, - to_compare_for_in_place_param: *const dyn PersistentConfiguration, - ) -> Self { - self.database_chain_assertion_params = real_param.clone(); - self.compare_persistent_config_to_pointer - .borrow_mut() - .push(to_compare_for_in_place_param); + pub fn validate_database_chain_params(mut self, params: &Arc>>) -> Self { + self.validate_database_chain_params = params.clone(); self } @@ -982,12 +955,7 @@ mod tests { log_level: LevelFilter::Off, crash_point: CrashPoint::None, dns_servers: vec![], - accountant_config: AccountantConfig { - payables_scan_interval: Duration::from_secs(100), - receivables_scan_interval: Duration::from_secs(100), - pending_payable_scan_interval: Duration::from_secs(100), - when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, - }, + accountant_config_opt: Some(make_populated_accountant_config_with_defaults()), clandestine_discriminator_factories: Vec::new(), ui_gateway_config: UiGatewayConfig { ui_port: 5335 }, blockchain_bridge_config: BlockchainBridgeConfig { @@ -1020,7 +988,6 @@ mod tests { &Some(main_cryptde()), &Some(alias_cryptde()), ); - let subject = ActorSystemFactoryReal::new(); let mut tools = ActorSystemFactoryToolsReal::new(); tools.automap_control_factory = Box::new( AutomapControlFactoryMock::new().make_result( @@ -1029,9 +996,10 @@ mod tests { .add_mapping_result(Ok(())), ), ); + let subject = ActorSystemFactoryReal::new(Box::new(tools)); let system = System::new("test"); - subject.make_and_start_actors(config, Box::new(actor_factory), &persistent_config, &tools); + subject.make_and_start_actors(config, Box::new(actor_factory), &persistent_config); System::current().stop(); system.run(); @@ -1058,12 +1026,7 @@ mod tests { log_level: LevelFilter::Off, crash_point: CrashPoint::None, dns_servers: vec![], - accountant_config: AccountantConfig { - payables_scan_interval: Duration::from_secs(100), - receivables_scan_interval: Duration::from_secs(100), - pending_payable_scan_interval: Duration::from_secs(100), - when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, - }, + accountant_config_opt: None, clandestine_discriminator_factories: Vec::new(), ui_gateway_config: UiGatewayConfig { ui_port: 5335 }, blockchain_bridge_config: BlockchainBridgeConfig { @@ -1243,12 +1206,7 @@ mod tests { log_level: LevelFilter::Off, crash_point: CrashPoint::None, dns_servers: vec![], - accountant_config: AccountantConfig { - payables_scan_interval: Duration::from_secs(100), - receivables_scan_interval: Duration::from_secs(100), - pending_payable_scan_interval: Duration::from_secs(100), - when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, - }, + accountant_config_opt: None, clandestine_discriminator_factories: Vec::new(), ui_gateway_config: UiGatewayConfig { ui_port: 5335 }, blockchain_bridge_config: BlockchainBridgeConfig { @@ -1411,12 +1369,7 @@ mod tests { log_level: LevelFilter::Off, crash_point: CrashPoint::None, dns_servers: vec![], - accountant_config: AccountantConfig { - payables_scan_interval: Duration::from_secs(100), - receivables_scan_interval: Duration::from_secs(100), - pending_payable_scan_interval: Duration::from_secs(100), - when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, - }, + accountant_config_opt: None, clandestine_discriminator_factories: Vec::new(), ui_gateway_config: UiGatewayConfig { ui_port: 5335 }, blockchain_bridge_config: BlockchainBridgeConfig { @@ -1630,20 +1583,20 @@ mod tests { } #[test] - fn database_chain_validity_happy_path() { + fn validate_database_chain_happy_path() { let chain = DEFAULT_CHAIN; - let persistent_config = - PersistentConfigurationMock::default().chain_name_result("eth-mainnet".to_string()); + let persistent_config = PersistentConfigurationMock::default() + .chain_name_result(DEFAULT_CHAIN.rec().literal_identifier.to_string()); let _ = - ActorSystemFactoryToolsReal::new().database_chain_assertion(chain, &persistent_config); + ActorSystemFactoryToolsReal::new().validate_database_chain(&persistent_config, chain); } #[test] #[should_panic( expected = "Database with the wrong chain name detected; expected: eth-ropsten, was: eth-mainnet" )] - fn make_and_start_actors_will_not_tolerate_differences_in_setup_chain_and_database_chain() { + fn make_and_start_actors_does_not_tolerate_differences_in_setup_chain_and_database_chain() { let mut bootstrapper_config = BootstrapperConfig::new(); bootstrapper_config.blockchain_bridge_config.chain = TEST_DEFAULT_CHAIN; let persistent_config = @@ -1652,13 +1605,12 @@ mod tests { &Some(main_cryptde().clone()), &Some(alias_cryptde().clone()), ); - let subject = ActorSystemFactoryReal::new(); + let subject = ActorSystemFactoryReal::new(Box::new(ActorSystemFactoryToolsReal::new())); let _ = subject.make_and_start_actors( bootstrapper_config, Box::new(ActorFactoryReal {}), &persistent_config, - &ActorSystemFactoryToolsReal::new(), ); } @@ -1666,6 +1618,7 @@ mod tests { fn make_and_start_actors_happy_path() { let database_chain_assertion_params_arc = Arc::new(Mutex::new(vec![])); let prepare_initial_messages_params_arc = Arc::new(Mutex::new(vec![])); + let chain_name_params_arc = Arc::new(Mutex::new(vec![])); let (recorder, _, recording_arc) = make_recorder(); let stream_holder_pool_subs = make_stream_handler_pool_subs_from(Some(recorder)); let mut bootstrapper_config = BootstrapperConfig::new(); @@ -1679,24 +1632,22 @@ mod tests { let alias_cryptde_public_key_before = public_key_for_dyn_cryptde_being_null(alias_cryptde); let actor_factory = Box::new(ActorFactoryReal {}) as Box; let actor_factory_raw_address = addr_of!(*actor_factory); - let persistent_config = PersistentConfigurationMock::default(); - let persistent_config_raw_address = addr_of!(persistent_config); + let persistent_config = PersistentConfigurationMock::default() + .chain_name_params(&chain_name_params_arc) + .chain_name_result( + "believe or not, supplied for nothing but prevention of panicking".to_string(), + ); let tools = ActorSystemFactoryToolsMock::default() - .main_cryptde_ref_result(main_cryptde) - .alias_cryptde_ref_result(alias_cryptde) - .database_chain_assertion_params( - &database_chain_assertion_params_arc, - persistent_config_raw_address, - ) + .cryptdes_result((main_cryptde, alias_cryptde)) + .validate_database_chain_params(&database_chain_assertion_params_arc) .prepare_initial_messages_params(&prepare_initial_messages_params_arc) .prepare_initial_messages_result(stream_holder_pool_subs); - let subject = ActorSystemFactoryReal::new(); + let subject = ActorSystemFactoryReal::new(Box::new(tools)); let result = subject.make_and_start_actors( bootstrapper_config, Box::new(ActorFactoryReal {}), &persistent_config, - &tools, ); let database_chain_assertion_params = database_chain_assertion_params_arc.lock().unwrap(); @@ -1744,7 +1695,9 @@ mod tests { system.run(); let recording = recording_arc.lock().unwrap(); let msg = recording.get_record::(0); - assert_eq!(msg, &msg_of_irrelevant_choice) + assert_eq!(msg, &msg_of_irrelevant_choice); + let chain_name_params = chain_name_params_arc.lock().unwrap(); + assert_eq!(*chain_name_params, vec![()]) } fn public_key_for_dyn_cryptde_being_null(cryptde: &dyn CryptDE) -> &PublicKey { diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index 458893eef..2d8fc4a66 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -379,10 +379,10 @@ mod tests { use crate::database::db_initializer::test_utils::DbInitializerMock; use crate::db_config::persistent_configuration::PersistentConfigError; use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMock; - use crate::test_utils::pure_test_utils::{ - make_default_persistent_configuration, prove_that_crash_request_handler_is_hooked_up, - }; use crate::test_utils::recorder::{make_recorder, peer_actors_builder}; + use crate::test_utils::unshared_test_utils::{ + configure_default_persistent_config, prove_that_crash_request_handler_is_hooked_up, ZERO, + }; use crate::test_utils::{make_paying_wallet, make_wallet}; use actix::System; use ethereum_types::{BigEndianHash, U64}; @@ -413,7 +413,7 @@ mod tests { let consuming_wallet = Wallet::from(Bip32ECKeyProvider::from_raw_secret(&secret).unwrap()); let subject = BlockchainBridge::new( stub_bi(), - Box::new(make_default_persistent_configuration()), + Box::new(configure_default_persistent_config(ZERO)), false, Some(consuming_wallet.clone()), ); diff --git a/node/src/blockchain/blockchain_interface.rs b/node/src/blockchain/blockchain_interface.rs index 99503714c..0fae92304 100644 --- a/node/src/blockchain/blockchain_interface.rs +++ b/node/src/blockchain/blockchain_interface.rs @@ -545,8 +545,8 @@ mod tests { SendTransactionToolsWrapperMock, TestTransport, }; use crate::sub_lib::wallet::Wallet; - use crate::test_utils::pure_test_utils::decode_hex; use crate::test_utils::recorder::make_recorder; + use crate::test_utils::unshared_test_utils::decode_hex; use crate::test_utils::{await_value, make_paying_wallet}; use crate::test_utils::{make_wallet, TestRawTransaction}; use actix::{Actor, System}; diff --git a/node/src/bootstrapper.rs b/node/src/bootstrapper.rs index e41283a1d..6ddab3bb0 100644 --- a/node/src/bootstrapper.rs +++ b/node/src/bootstrapper.rs @@ -1,8 +1,4 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::{ - DEFAULT_PAYABLES_SCAN_INTERVAL, DEFAULT_PENDING_TOO_LONG_SEC, - DEFAULT_PENDING_TRANSACTION_SCAN_INTERVAL, DEFAULT_RECEIVABLES_SCAN_INTERVAL, -}; use crate::actor_system_factory::ActorSystemFactory; use crate::actor_system_factory::ActorSystemFactoryReal; use crate::actor_system_factory::{ActorFactoryReal, ActorSystemFactoryToolsReal}; @@ -55,7 +51,6 @@ use std::fmt::{Debug, Display, Error, Formatter}; use std::net::{Ipv4Addr, SocketAddr}; use std::path::PathBuf; use std::str::FromStr; -use std::time::Duration; use std::vec::Vec; use tokio::prelude::stream::futures_unordered::FuturesUnordered; use tokio::prelude::Async; @@ -306,7 +301,7 @@ pub struct BootstrapperConfig { // These fields can be set while privileged without penalty pub log_level: LevelFilter, pub dns_servers: Vec, - pub accountant_config: AccountantConfig, + pub accountant_config_opt: Option, pub crash_point: CrashPoint, pub clandestine_discriminator_factories: Vec>, pub ui_gateway_config: UiGatewayConfig, @@ -339,14 +334,7 @@ impl BootstrapperConfig { // These fields can be set while privileged without penalty log_level: LevelFilter::Off, dns_servers: vec![], - accountant_config: AccountantConfig { - payables_scan_interval: Duration::from_secs(DEFAULT_PAYABLES_SCAN_INTERVAL), - receivables_scan_interval: Duration::from_secs(DEFAULT_RECEIVABLES_SCAN_INTERVAL), - pending_payable_scan_interval: Duration::from_secs( - DEFAULT_PENDING_TRANSACTION_SCAN_INTERVAL, - ), - when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, - }, + accountant_config_opt: Default::default(), crash_point: CrashPoint::None, clandestine_discriminator_factories: vec![], ui_gateway_config: UiGatewayConfig { @@ -386,6 +374,7 @@ impl BootstrapperConfig { self.earning_wallet = unprivileged.earning_wallet; self.consuming_wallet_opt = unprivileged.consuming_wallet_opt; self.db_password_opt = unprivileged.db_password_opt; + self.accountant_config_opt = unprivileged.accountant_config_opt; } pub fn exit_service_rate(&self) -> u64 { @@ -422,7 +411,6 @@ impl Future for Bootstrapper { fn poll(&mut self) -> Result::Item>, ::Error> { try_ready!(CrashTestDummy::new(self.config.crash_point, BootstrapperConfig::new()).poll()); - try_ready!(self.listener_handlers.poll()); Ok(Async::Ready(())) } @@ -497,7 +485,6 @@ impl ConfiguredByPrivilege for Bootstrapper { MigratorConfig::panic_on_migration(), ) .as_ref(), - &ActorSystemFactoryToolsReal::new(), ); self.listener_handlers @@ -513,7 +500,9 @@ impl Bootstrapper { listener_handler_factory: Box::new(ListenerHandlerFactoryReal::new()), listener_handlers: FuturesUnordered::>>::new(), - actor_system_factory: Box::new(ActorSystemFactoryReal::new()), + actor_system_factory: Box::new(ActorSystemFactoryReal::new(Box::new( + ActorSystemFactoryToolsReal::new(), + ))), logger_initializer, config: BootstrapperConfig::new(), } @@ -536,21 +525,34 @@ impl Bootstrapper { alias_cryptde_null_opt: &Option<&dyn CryptDE>, chain: Chain, ) -> (&'a dyn CryptDE, &'b dyn CryptDE) { - match main_cryptde_null_opt { - Some(cryptde) => unsafe { - MAIN_CRYPTDE_BOX_OPT = Some(Box::new(<&CryptDENull>::from(*cryptde).clone())) - }, - None => unsafe { MAIN_CRYPTDE_BOX_OPT = Some(Box::new(CryptDEReal::new(chain))) }, - } - match alias_cryptde_null_opt { - Some(cryptde) => unsafe { - ALIAS_CRYPTDE_BOX_OPT = Some(Box::new(<&CryptDENull>::from(*cryptde).clone())) - }, - None => unsafe { ALIAS_CRYPTDE_BOX_OPT = Some(Box::new(CryptDEReal::new(chain))) }, + unsafe { + Self::initialize_single_cryptde(main_cryptde_null_opt, &mut MAIN_CRYPTDE_BOX_OPT, chain) + }; + unsafe { + Self::initialize_single_cryptde( + alias_cryptde_null_opt, + &mut ALIAS_CRYPTDE_BOX_OPT, + chain, + ) } (main_cryptde_ref(), alias_cryptde_ref()) } + fn initialize_single_cryptde( + cryptde_null_opt: &Option<&dyn CryptDE>, + boxed_cryptde: &mut Option>, + chain: Chain, + ) { + match cryptde_null_opt { + Some(cryptde) => { + let _ = boxed_cryptde.replace(Box::new(<&CryptDENull>::from(*cryptde).clone())); + } + None => { + let _ = boxed_cryptde.replace(Box::new(CryptDEReal::new(chain))); + } + } + } + fn make_local_descriptor( cryptde: &dyn CryptDE, node_addr_opt: Option, @@ -613,7 +615,7 @@ impl Bootstrapper { mode: NeighborhoodMode::Standard( NodeAddr::new(&node_addr.ip_addr(), &[clandestine_port]), neighbor_configs.clone(), - rate_pack.clone(), + *rate_pack, ), }; Some(clandestine_port) @@ -664,37 +666,7 @@ impl Bootstrapper { #[cfg(test)] mod tests { - use std::cell::RefCell; - use std::io; - use std::io::ErrorKind; - use std::marker::Sync; - use std::net::{IpAddr, SocketAddr}; - use std::ops::DerefMut; - use std::path::PathBuf; - use std::str::FromStr; - use std::sync::{Arc, Mutex}; - use std::thread; - use std::time::Duration; - - use actix::Recipient; - use actix::System; - use crossbeam_channel::unbounded; - use futures::Future; - use lazy_static::lazy_static; - use log::LevelFilter; - use tokio; - use tokio::prelude::stream::FuturesUnordered; - use tokio::prelude::Async; - - use masq_lib::test_utils::environment_guard::ClapGuard; - use masq_lib::test_utils::fake_stream_holder::FakeStreamHolder; - use masq_lib::test_utils::utils::{ensure_node_home_directory_exists, TEST_DEFAULT_CHAIN}; - - use crate::accountant::{ - DEFAULT_PAYABLES_SCAN_INTERVAL, DEFAULT_PENDING_TRANSACTION_SCAN_INTERVAL, - DEFAULT_RECEIVABLES_SCAN_INTERVAL, - }; - use crate::actor_system_factory::{ActorFactory, ActorSystemFactory, ActorSystemFactoryTools}; + use crate::actor_system_factory::{ActorFactory, ActorSystemFactory}; use crate::bootstrapper::{ main_cryptde_ref, Bootstrapper, BootstrapperConfig, EnvironmentWrapper, PortConfiguration, RealUser, @@ -715,7 +687,6 @@ mod tests { use crate::server_initializer::LoggerInitializerWrapper; use crate::stream_handler_pool::StreamHandlerPoolSubs; use crate::stream_messages::AddStreamMsg; - use crate::sub_lib::blockchain_bridge::BlockchainBridgeConfig; use crate::sub_lib::cryptde::PublicKey; use crate::sub_lib::cryptde::{CryptDE, PlainData}; use crate::sub_lib::cryptde_null::CryptDENull; @@ -723,20 +694,45 @@ mod tests { use crate::sub_lib::node_addr::NodeAddr; use crate::sub_lib::socket_server::ConfiguredByPrivilege; use crate::sub_lib::stream_connector::ConnectionInfo; - use crate::test_utils::main_cryptde; use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMock; - use crate::test_utils::pure_test_utils::make_simplified_multi_config; use crate::test_utils::recorder::make_recorder; use crate::test_utils::recorder::RecordAwaiter; use crate::test_utils::recorder::Recording; use crate::test_utils::tokio_wrapper_mocks::ReadHalfWrapperMock; use crate::test_utils::tokio_wrapper_mocks::WriteHalfWrapperMock; + use crate::test_utils::unshared_test_utils::{ + make_populated_accountant_config_with_defaults, make_simplified_multi_config, + }; use crate::test_utils::{assert_contains, rate_pack}; + use crate::test_utils::{main_cryptde, make_wallet}; + use actix::Recipient; + use actix::System; + use crossbeam_channel::unbounded; + use futures::Future; + use lazy_static::lazy_static; + use log::LevelFilter; + use log::LevelFilter::Off; use masq_lib::blockchains::chains::Chain; - use masq_lib::constants::DEFAULT_GAS_PRICE; use masq_lib::logger::Logger; + use masq_lib::test_utils::environment_guard::ClapGuard; + use masq_lib::test_utils::fake_stream_holder::FakeStreamHolder; use masq_lib::test_utils::logging::{init_test_logging, TestLog, TestLogHandler}; + use masq_lib::test_utils::utils::{ensure_node_home_directory_exists, TEST_DEFAULT_CHAIN}; use masq_lib::utils::find_free_port; + use std::cell::RefCell; + use std::collections::HashMap; + use std::io; + use std::io::ErrorKind; + use std::marker::Sync; + use std::net::{IpAddr, SocketAddr}; + use std::ops::DerefMut; + use std::path::PathBuf; + use std::str::FromStr; + use std::sync::{Arc, Mutex}; + use std::thread; + use tokio; + use tokio::prelude::stream::FuturesUnordered; + use tokio::prelude::Async; lazy_static! { pub static ref INITIALIZATION: Mutex = Mutex::new(false); @@ -1045,7 +1041,6 @@ mod tests { subject .initialize_as_privileged(&make_simplified_multi_config([ - "MASQNode", "--neighborhood-mode", "zero-hop", ])) @@ -1081,7 +1076,6 @@ mod tests { subject .initialize_as_privileged(&make_simplified_multi_config([ - "MASQNode", "--data-directory", data_dir.to_str().unwrap(), "--ip", @@ -1124,7 +1118,6 @@ mod tests { subject .initialize_as_unprivileged( &make_simplified_multi_config([ - "MASQNode", "--ip", "1.2.3.4", "--clandestine-port", @@ -1150,45 +1143,74 @@ mod tests { } #[test] - fn blockchain_service_url_from_the_unprivileged_config_is_merged_into_the_final_config() { - let _lock = INITIALIZATION.lock(); - init_test_logging(); - let data_dir = ensure_node_home_directory_exists( - "bootstrapper", - "blockchain_service_url_from_the_unprivileged_config_is_merged_into_the_final_config", + fn merging_unprivileged_config_picks_correct_items() { + let mut privileged_config = BootstrapperConfig::new(); + let mut port_configuration = HashMap::new(); + port_configuration.insert( + 555, + PortConfiguration { + discriminator_factories: vec![], + is_clandestine: true, + }, ); - let mut config = BootstrapperConfig::new(); - config.clandestine_port_opt = Some(1234); - config.data_directory = data_dir.clone(); - let mut subject = BootstrapperBuilder::new() - .add_listener_handler(Box::new( - ListenerHandlerNull::new(vec![]).bind_port_result(Ok(())), - )) - .config(config) - .build(); - - subject - .initialize_as_unprivileged( - &make_simplified_multi_config([ - "MASQNode", - "--ip", - "1.2.3.4", - "--blockchain-service-url", - "http://infura.io/ID", - ]), - &mut FakeStreamHolder::new().streams(), - ) - .unwrap(); - - let config = subject.config; + privileged_config.port_configurations = port_configuration; + privileged_config.log_level = Off; + privileged_config.dns_servers = + vec![SocketAddr::new(IpAddr::from_str("1.2.3.4").unwrap(), 1111)]; + let mut unprivileged_config = BootstrapperConfig::new(); + //values from unprivileged config + let gas_price = 123; + let blockchain_url_opt = Some("some.service@earth.abc".to_string()); + let clandestine_port_opt = Some(44444); + let neighborhood_config = NeighborhoodConfig { + mode: NeighborhoodMode::OriginateOnly(vec![], rate_pack(9)), + }; + let earning_wallet = make_wallet("earning wallet"); + let consuming_wallet_opt = Some(make_wallet("consuming wallet")); + let db_password_opt = Some("password".to_string()); + unprivileged_config.blockchain_bridge_config.gas_price = gas_price; + unprivileged_config + .blockchain_bridge_config + .blockchain_service_url_opt = blockchain_url_opt.clone(); + unprivileged_config.clandestine_port_opt = clandestine_port_opt; + unprivileged_config.neighborhood_config = neighborhood_config.clone(); + unprivileged_config.earning_wallet = earning_wallet.clone(); + unprivileged_config.consuming_wallet_opt = consuming_wallet_opt.clone(); + unprivileged_config.db_password_opt = db_password_opt.clone(); + unprivileged_config.accountant_config_opt = + Some(make_populated_accountant_config_with_defaults()); + + privileged_config.merge_unprivileged(unprivileged_config); + + //merged arguments assert_eq!( - config.blockchain_bridge_config, - BlockchainBridgeConfig { - blockchain_service_url_opt: Some("http://infura.io/ID".to_string()), - chain: TEST_DEFAULT_CHAIN, - gas_price: DEFAULT_GAS_PRICE - } + privileged_config.blockchain_bridge_config.gas_price, + gas_price + ); + assert_eq!( + privileged_config + .blockchain_bridge_config + .blockchain_service_url_opt, + blockchain_url_opt + ); + assert_eq!(privileged_config.clandestine_port_opt, clandestine_port_opt); + assert_eq!(privileged_config.neighborhood_config, neighborhood_config); + assert_eq!(privileged_config.earning_wallet, earning_wallet); + assert_eq!(privileged_config.consuming_wallet_opt, consuming_wallet_opt); + assert_eq!(privileged_config.db_password_opt, db_password_opt); + assert_eq!( + privileged_config.accountant_config_opt, + Some(make_populated_accountant_config_with_defaults()) + ); + //some values from the privileged config + assert_eq!(privileged_config.log_level, Off); + assert_eq!( + privileged_config.dns_servers, + vec![SocketAddr::new(IpAddr::from_str("1.2.3.4").unwrap(), 1111)] ); + let port_config = privileged_config.port_configurations.get(&555).unwrap(); + assert!(port_config.discriminator_factories.is_empty()); + assert_eq!(port_config.is_clandestine, true) } #[test] @@ -1210,13 +1232,7 @@ mod tests { subject .initialize_as_unprivileged( - &make_simplified_multi_config([ - "MASQNode", - "--ip", - "1.2.3.4", - "--clandestine-port", - "5123", - ]), + &make_simplified_multi_config(["--ip", "1.2.3.4", "--clandestine-port", "5123"]), &mut FakeStreamHolder::new().streams(), ) .unwrap(); @@ -1256,7 +1272,6 @@ mod tests { subject .initialize_as_unprivileged( &make_simplified_multi_config([ - "MASQNode", "--data-directory", data_dir.to_str().unwrap(), "--clandestine-port", @@ -1295,7 +1310,7 @@ mod tests { subject .initialize_as_unprivileged( - &make_simplified_multi_config(["MASQNode", "--ip", "1.2.3.4", "--gas-price", "11"]), + &make_simplified_multi_config(["--ip", "1.2.3.4", "--gas-price", "11"]), &mut FakeStreamHolder::new().streams(), ) .unwrap(); @@ -1321,7 +1336,6 @@ mod tests { .add_listener_handler(third_handler) .build(); let args = [ - "MASQNode", "--neighborhood-mode", "zero-hop", "--clandestine-port", @@ -1330,12 +1344,11 @@ mod tests { data_dir.to_str().unwrap(), ]; let mut holder = FakeStreamHolder::new(); + let multi_config = make_simplified_multi_config(args); + subject.initialize_as_privileged(&multi_config).unwrap(); subject - .initialize_as_privileged(&make_simplified_multi_config(args)) - .unwrap(); - subject - .initialize_as_unprivileged(&make_simplified_multi_config(args), &mut holder.streams()) + .initialize_as_unprivileged(&multi_config, &mut holder.streams()) .unwrap(); let config = subject.config; @@ -1353,7 +1366,6 @@ mod tests { "init_as_privileged_stores_dns_servers_and_passes_them_to_actor_system_factory_for_proxy_client_in_init_as_unprivileged", ); let args = [ - "MASQNode", "--dns-servers", "1.2.3.4,2.3.4.5", "--ip", @@ -1378,12 +1390,11 @@ mod tests { ListenerHandlerNull::new(vec![]).bind_port_result(Ok(())), )) .build(); + let multi_config = make_simplified_multi_config(args); + subject.initialize_as_privileged(&multi_config).unwrap(); subject - .initialize_as_privileged(&make_simplified_multi_config(args)) - .unwrap(); - subject - .initialize_as_unprivileged(&make_simplified_multi_config(args), &mut holder.streams()) + .initialize_as_unprivileged(&multi_config, &mut holder.streams()) .unwrap(); let dns_servers_guard = dns_servers_arc.lock().unwrap(); @@ -1411,11 +1422,7 @@ mod tests { .build(); subject - .initialize_as_privileged(&make_simplified_multi_config([ - "MASQNode", - "--ip", - "111.111.111.111", - ])) + .initialize_as_privileged(&make_simplified_multi_config(["--ip", "111.111.111.111"])) .unwrap(); } @@ -1557,7 +1564,6 @@ mod tests { let mut holder = FakeStreamHolder::new(); subject .initialize_as_privileged(&make_simplified_multi_config([ - "MASQNode", "--data-directory", data_dir.to_str().unwrap(), ])) @@ -1566,7 +1572,6 @@ mod tests { subject .initialize_as_unprivileged( &make_simplified_multi_config([ - "MASQNode", "--clandestine-port", "1234", "--ip", @@ -1594,7 +1599,6 @@ mod tests { let data_dir = ensure_node_home_directory_exists("bootstrapper", "initialize_as_unprivileged_moves_streams_from_listener_handlers_to_stream_handler_pool"); init_test_logging(); let args = [ - "MASQNode", "--ip", "111.111.111.111", "--data-directory", @@ -1615,12 +1619,11 @@ mod tests { .add_listener_handler(Box::new(yet_another_listener_handler)) .config(config) .build(); - subject - .initialize_as_privileged(&make_simplified_multi_config(args)) - .unwrap(); + let multi_config = &make_simplified_multi_config(args); + subject.initialize_as_privileged(&multi_config).unwrap(); subject - .initialize_as_unprivileged(&make_simplified_multi_config(args), &mut holder.streams()) + .initialize_as_unprivileged(&multi_config, &mut holder.streams()) .unwrap(); // Checking log message cause I don't know how to get at add_stream_sub @@ -1693,18 +1696,16 @@ mod tests { .add_listener_handler(Box::new(another_listener_handler)) .build(); let args = [ - "MASQNode", "--neighborhood-mode", "zero-hop", "--data-directory", data_dir.to_str().unwrap(), ]; + let multi_config = make_simplified_multi_config(args); + subject.initialize_as_privileged(&multi_config).unwrap(); subject - .initialize_as_privileged(&make_simplified_multi_config(args)) - .unwrap(); - subject - .initialize_as_unprivileged(&make_simplified_multi_config(args), &mut holder.streams()) + .initialize_as_unprivileged(&multi_config, &mut holder.streams()) .unwrap(); thread::spawn(|| { @@ -2057,24 +2058,6 @@ mod tests { ); } - #[test] - fn default_bootstrapper_config_has_scan_intervals_in_proper_units() { - let result = BootstrapperConfig::new(); - - assert_eq!( - result.accountant_config.pending_payable_scan_interval, - Duration::from_secs(DEFAULT_PENDING_TRANSACTION_SCAN_INTERVAL) - ); - assert_eq!( - result.accountant_config.payables_scan_interval, - Duration::from_secs(DEFAULT_PAYABLES_SCAN_INTERVAL) - ); - assert_eq!( - result.accountant_config.receivables_scan_interval, - Duration::from_secs(DEFAULT_RECEIVABLES_SCAN_INTERVAL) - ) - } - struct StreamHandlerPoolCluster { recording: Option>>, awaiter: Option, @@ -2092,7 +2075,6 @@ mod tests { config: BootstrapperConfig, _actor_factory: Box, _persist_config: &dyn PersistentConfiguration, - _tools: &dyn ActorSystemFactoryTools, ) -> StreamHandlerPoolSubs { let mut parameter_guard = self.dnss.lock().unwrap(); let parameter_ref = parameter_guard.deref_mut(); diff --git a/node/src/daemon/daemon_initializer.rs b/node/src/daemon/daemon_initializer.rs index 7c445d9e4..5867ee3c7 100644 --- a/node/src/daemon/daemon_initializer.rs +++ b/node/src/daemon/daemon_initializer.rs @@ -7,7 +7,7 @@ use crate::daemon::{ }; use crate::node_configurator::node_configurator_initialization::InitializationConfig; use crate::node_configurator::port_is_busy; -use crate::run_modes_factories::{ClusteredParams, DaemonInitializer, RunModeResult}; +use crate::run_modes_factories::{DIClusteredParams, DaemonInitializer, RunModeResult}; use crate::sub_lib::main_tools::main_with_args; use crate::sub_lib::ui_gateway::UiGatewayConfig; use crate::ui_gateway::UiGateway; @@ -111,7 +111,10 @@ impl RerunnerReal { } impl DaemonInitializerReal { - pub fn new(config: InitializationConfig, mut params: ClusteredParams) -> DaemonInitializerReal { + pub fn new( + config: InitializationConfig, + mut params: DIClusteredParams, + ) -> DaemonInitializerReal { let real_user = RealUser::new(None, None, None).populate(params.dirs_wrapper.as_ref()); let dirs_home_dir_opt = params.dirs_wrapper.home_dir(); let dirs_home_dir = dirs_home_dir_opt @@ -178,44 +181,30 @@ impl DaemonInitializerReal { } } -#[cfg(test)] -impl DaemonInitializerReal { - pub fn access_to_the_fields_test( - &self, - ) -> ( - &InitializationConfig, - &Box, - &Box, - &Box, - ) { - ( - &self.config, - &self.channel_factory, - &self.recipients_factory, - &self.rerunner, - ) - } -} - #[cfg(test)] mod tests { use super::*; use crate::daemon::Recipients; - use crate::node_configurator::node_configurator_initialization::InitializationConfig; + use crate::node_configurator::node_configurator_initialization::{ + InitializationConfig, NodeConfiguratorInitializationReal, + }; use crate::node_test_utils::DirsWrapperMock; + use crate::run_modes_factories::mocks::test_clustered_params; + use crate::run_modes_factories::{DaemonInitializerFactory, DaemonInitializerFactoryReal}; use crate::server_initializer::test_utils::LoggerInitializerWrapperMock; - use crate::test_utils::pure_test_utils::ChannelFactoryMock; use crate::test_utils::recorder::{make_recorder, Recorder}; + use crate::test_utils::unshared_test_utils::ChannelFactoryMock; use actix::System; use crossbeam_channel::unbounded; use masq_lib::test_utils::environment_guard::EnvironmentGuard; use masq_lib::test_utils::fake_stream_holder::FakeStreamHolder; use masq_lib::test_utils::utils::ensure_node_home_directory_exists; - use masq_lib::utils::{find_free_port, localhost}; + use masq_lib::utils::{array_of_borrows_to_vec, find_free_port, localhost}; use std::cell::RefCell; use std::iter::FromIterator; use std::net::{SocketAddr, TcpListener}; use std::path::PathBuf; + use std::ptr::addr_of; use std::str::FromStr; use std::sync::{Arc, Mutex}; @@ -311,7 +300,7 @@ mod tests { .join(PathBuf::from_str(relative_data_dir).unwrap()), )); let config = InitializationConfig::default(); - let mut clustered_params = ClusteredParams::default(); + let mut clustered_params = DIClusteredParams::default(); let init_params_arc = Arc::new(Mutex::new(vec![])); let logger_initializer_wrapper = LoggerInitializerWrapperMock::new().init_parameters(&init_params_arc); @@ -345,7 +334,7 @@ mod tests { let channel_factory = ChannelFactoryMock::new(); let addr_factory = RecipientsFactoryMock::new().make_result(recipients); let rerunner = RerunnerMock::new(); - let clustered_params = ClusteredParams { + let clustered_params = DIClusteredParams { dirs_wrapper: Box::new(dirs_wrapper), logger_initializer_wrapper: Box::new(logger_initializer_wrapper), channel_factory: Box::new(channel_factory), @@ -385,7 +374,7 @@ mod tests { let addr_factory = RecipientsFactoryMock::new(); let rerun_parameters_arc = Arc::new(Mutex::new(vec![])); let rerunner = RerunnerMock::new().rerun_parameters(&rerun_parameters_arc); - let clustered_params = ClusteredParams { + let clustered_params = DIClusteredParams { dirs_wrapper: Box::new(dirs_wrapper), logger_initializer_wrapper: Box::new(logger_initializer_wrapper), channel_factory: Box::new(channel_factory), @@ -427,7 +416,7 @@ mod tests { let logger_initializer_wrapper = LoggerInitializerWrapperMock::new(); let port = find_free_port(); let _listener = TcpListener::bind(SocketAddr::new(localhost(), port)).unwrap(); - let clustered_params = ClusteredParams { + let clustered_params = DIClusteredParams { dirs_wrapper: Box::new(dirs_wrapper), logger_initializer_wrapper: Box::new(logger_initializer_wrapper), channel_factory: Box::new(ChannelFactoryMock::new()), @@ -459,4 +448,43 @@ mod tests { bind_message_subs: vec![daemon_addr.recipient(), ui_gateway_addr.recipient()], } } + + #[test] + fn make_for_daemon_initializer_factory_labours_hard_and_produces_a_proper_object() { + let daemon_clustered_params = test_clustered_params(); + let init_pointer_of_recipients_factory = + addr_of!(*daemon_clustered_params.recipients_factory); + let init_pointer_of_channel_factory = addr_of!(*daemon_clustered_params.channel_factory); + let init_pointer_of_rerunner = addr_of!(*daemon_clustered_params.rerunner); + let subject = DaemonInitializerFactoryReal::new( + Box::new(NodeConfiguratorInitializationReal), + daemon_clustered_params, + ); + let args = &array_of_borrows_to_vec(&[ + "program", + "--initialization", + "--ui-port", + 1234.to_string().as_str(), + ]); + + let result = subject.make(&args).unwrap(); + + let factory_product = result + .as_any() + .downcast_ref::() + .unwrap(); + assert_eq!(factory_product.config.ui_port, 1234); + let final_pointer_of_recipients_factory = addr_of!(*factory_product.recipients_factory); + assert_eq!( + init_pointer_of_recipients_factory, + final_pointer_of_recipients_factory + ); + let final_pointer_of_channel_factory = addr_of!(*factory_product.channel_factory); + assert_eq!( + init_pointer_of_channel_factory, + final_pointer_of_channel_factory + ); + let final_pointer_of_rerunner = addr_of!(*factory_product.rerunner); + assert_eq!(init_pointer_of_rerunner, final_pointer_of_rerunner); + } } diff --git a/node/src/daemon/setup_reporter.rs b/node/src/daemon/setup_reporter.rs index 04add3d4d..a6eb106c1 100644 --- a/node/src/daemon/setup_reporter.rs +++ b/node/src/daemon/setup_reporter.rs @@ -11,14 +11,17 @@ use crate::db_config::config_dao_null::ConfigDaoNull; use crate::db_config::persistent_configuration::{ PersistentConfiguration, PersistentConfigurationReal, }; -use crate::node_configurator::node_configurator_standard::{ - privileged_parse_args, unprivileged_parse_args, +use crate::node_configurator::node_configurator_standard::privileged_parse_args; +use crate::node_configurator::unprivileged_parse_args_configuration::{ + UnprivilegedParseArgsConfiguration, UnprivilegedParseArgsConfigurationDaoNull, + UnprivilegedParseArgsConfigurationDaoReal, }; use crate::node_configurator::{ data_directory_from_context, determine_config_file_path, DirsWrapper, DirsWrapperReal, }; -use crate::sub_lib::neighborhood::NeighborhoodMode as NeighborhoodModeEnum; +use crate::sub_lib::accountant::{DEFAULT_PAYMENT_THRESHOLDS, DEFAULT_SCAN_INTERVALS}; use crate::sub_lib::neighborhood::NodeDescriptor; +use crate::sub_lib::neighborhood::{NeighborhoodMode as NeighborhoodModeEnum, DEFAULT_RATE_PACK}; use crate::sub_lib::utils::make_new_multi_config; use crate::test_utils::main_cryptde; use clap::value_t; @@ -28,11 +31,14 @@ use masq_lib::constants::DEFAULT_CHAIN; use masq_lib::logger::Logger; use masq_lib::messages::UiSetupResponseValueStatus::{Blank, Configured, Default, Required, Set}; use masq_lib::messages::{UiSetupRequestValue, UiSetupResponseValue, UiSetupResponseValueStatus}; +use masq_lib::multi_config::make_arg_matches_accesible; use masq_lib::multi_config::{ CommandLineVcl, ConfigFileVcl, EnvironmentVcl, MultiConfig, VirtualCommandLine, }; use masq_lib::shared_schema::{shared_app, ConfiguratorError}; +use masq_lib::utils::ExpectValue; use std::collections::HashMap; +use std::fmt::Display; use std::net::{IpAddr, Ipv4Addr}; use std::path::{Path, PathBuf}; use std::str::FromStr; @@ -78,10 +84,9 @@ impl SetupReporter for SetupReporterReal { blanked_out_former_values.insert(v.name.clone(), former_value); }; }); - //we had troubles at an attempt to blank out this parameter on an error resulting in a diverging chain from the data_dir - if blanked_out_former_values.get("chain").is_some() { - let _ = blanked_out_former_values.remove("chain"); - } + //TODO investigate this, not sure if the right way to solve the issue + //answers an attempt to blank out 'chain' behind an error resulting in chain different from data_dir + let _ = blanked_out_former_values.remove("chain"); let mut incoming_setup = incoming_setup .into_iter() .filter(|v| v.value.is_some()) @@ -199,10 +204,7 @@ impl SetupReporterReal { let name = opt.b.name; match opt.v.default_val { Some(os_str) => { - let value = match os_str.to_str() { - Some(v) => v, - None => unimplemented!(), - }; + let value = os_str.to_str().expect("expected valid UTF-8"); Some(( name.to_string(), UiSetupResponseValue::new(name, value, Default), @@ -288,7 +290,7 @@ impl SetupReporterReal { Ok(mc) => mc, Err(ce) => return (HashMap::new(), Some(ce)), }; - let ((bootstrapper_config, persistent_config_opt), error_opt) = + let ((bootstrapper_config, persistent_config), error_opt) = self.run_configuration(&multi_config, data_directory); if let Some(error) = error_opt { error_so_far.extend(error); @@ -298,7 +300,7 @@ impl SetupReporterReal { .map(|r| { let computed_default = r.computed_default_value( &bootstrapper_config, - &persistent_config_opt, + persistent_config.as_ref(), &db_password_opt, ); let configured = match value_m!(multi_config, r.value_name(), String) { @@ -396,7 +398,7 @@ impl SetupReporterReal { multi_config: &MultiConfig, data_directory: &Path, ) -> ( - (BootstrapperConfig, Option>), + (BootstrapperConfig, Box), Option, ) { let mut error_so_far = ConfiguratorError::new(vec![]); @@ -419,43 +421,44 @@ impl SetupReporterReal { MigratorConfig::migration_suppressed_with_error(), ) { Ok(conn) => { + let parse_args_configuration = UnprivilegedParseArgsConfigurationDaoReal {}; let mut persistent_config = PersistentConfigurationReal::from(conn); - match unprivileged_parse_args( + match parse_args_configuration.unprivileged_parse_args( multi_config, &mut bootstrapper_config, &mut persistent_config, &self.logger, ) { - Ok(_) => ( - (bootstrapper_config, Some(Box::new(persistent_config))), - None, - ), + Ok(_) => ((bootstrapper_config, Box::new(persistent_config)), None), Err(ce) => { error_so_far.extend(ce); ( - (bootstrapper_config, Some(Box::new(persistent_config))), + (bootstrapper_config, Box::new(persistent_config)), Some(error_so_far), ) } } } - Err( - InitializationError::Nonexistent | InitializationError::SuppressedMigrationError, - ) => { + Err(InitializationError::Nonexistent | InitializationError::SuppressedMigration) => { // When the Daemon runs for the first time, the database will not yet have been // created. If the database is old, it should not be used by the Daemon. + let parse_args_configuration = UnprivilegedParseArgsConfigurationDaoNull {}; let mut persistent_config = PersistentConfigurationReal::new(Box::new(ConfigDaoNull::default())); - match unprivileged_parse_args( + match parse_args_configuration.unprivileged_parse_args( multi_config, &mut bootstrapper_config, &mut persistent_config, &self.logger, ) { - Ok(_) => ((bootstrapper_config, None), None), + Ok(_) => ((bootstrapper_config, Box::new(persistent_config)), None), Err(ce) => { error_so_far.extend(ce); - ((bootstrapper_config, None), Some(error_so_far)) + + ( + (bootstrapper_config, Box::new(persistent_config)), + Some(error_so_far), + ) } } } @@ -470,7 +473,7 @@ trait ValueRetriever { fn computed_default( &self, _bootstrapper_config: &BootstrapperConfig, - _persistent_config_opt: &Option>, + _persistent_config: &dyn PersistentConfiguration, _db_password_opt: &Option, ) -> Option<(String, UiSetupResponseValueStatus)> { None @@ -479,10 +482,10 @@ trait ValueRetriever { fn computed_default_value( &self, bootstrapper_config: &BootstrapperConfig, - persistent_config_opt: &Option>, + persistent_config: &dyn PersistentConfiguration, db_password_opt: &Option, ) -> UiSetupResponseValue { - match self.computed_default(bootstrapper_config, persistent_config_opt, db_password_opt) { + match self.computed_default(bootstrapper_config, persistent_config, db_password_opt) { Some((value, status)) => UiSetupResponseValue::new(self.value_name(), &value, status), None => UiSetupResponseValue::new(self.value_name(), "", Blank), } @@ -521,7 +524,7 @@ impl ValueRetriever for Chain { fn computed_default( &self, _bootstrapper_config: &BootstrapperConfig, - _persistent_config_opt: &Option>, + _persistent_config: &dyn PersistentConfiguration, _db_password_opt: &Option, ) -> Option<(String, UiSetupResponseValueStatus)> { Some((DEFAULT_CHAIN.rec().literal_identifier.to_string(), Default)) @@ -541,16 +544,12 @@ impl ValueRetriever for ClandestinePort { fn computed_default( &self, _bootstrapper_config: &BootstrapperConfig, - persistent_config_opt: &Option>, + persistent_config: &dyn PersistentConfiguration, _db_password_opt: &Option, ) -> Option<(String, UiSetupResponseValueStatus)> { - if let Some(persistent_config) = persistent_config_opt { - match persistent_config.clandestine_port() { - Ok(clandestine_port) => Some((clandestine_port.to_string(), Default)), - Err(_) => None, - } - } else { - None + match persistent_config.clandestine_port() { + Ok(clandestine_port) => Some((clandestine_port.to_string(), Configured)), + Err(_) => None, } } @@ -591,7 +590,7 @@ impl ValueRetriever for DataDirectory { fn computed_default( &self, bootstrapper_config: &BootstrapperConfig, - _persistent_config_opt: &Option>, + _persistent_config: &dyn PersistentConfiguration, _db_password_opt: &Option, ) -> Option<(String, UiSetupResponseValueStatus)> { let real_user = &bootstrapper_config.real_user; @@ -658,7 +657,7 @@ impl ValueRetriever for DnsServers { fn computed_default( &self, _bootstrapper_config: &BootstrapperConfig, - _persistent_config_opt: &Option>, + _persistent_config: &dyn PersistentConfiguration, _db_password_opt: &Option, ) -> Option<(String, UiSetupResponseValueStatus)> { let inspector = self.factory.make()?; @@ -704,7 +703,7 @@ impl ValueRetriever for GasPrice { fn computed_default( &self, bootstrapper_config: &BootstrapperConfig, - _persistent_config_opt: &Option>, + _persistent_config: &dyn PersistentConfiguration, _db_password_opt: &Option, ) -> Option<(String, UiSetupResponseValueStatus)> { Some(( @@ -730,7 +729,7 @@ impl ValueRetriever for Ip { fn computed_default( &self, bootstrapper_config: &BootstrapperConfig, - _persistent_config_opt: &Option>, + _persistent_config: &dyn PersistentConfiguration, _db_password_opt: &Option, ) -> Option<(String, UiSetupResponseValueStatus)> { let neighborhood_mode = &bootstrapper_config.neighborhood_config.mode; @@ -762,7 +761,7 @@ impl ValueRetriever for LogLevel { fn computed_default( &self, _bootstrapper_config: &BootstrapperConfig, - _persistent_config_opt: &Option>, + _persistent_config: &dyn PersistentConfiguration, _db_password_opt: &Option, ) -> Option<(String, UiSetupResponseValueStatus)> { Some(("warn".to_string(), Default)) @@ -781,31 +780,16 @@ impl ValueRetriever for MappingProtocol { fn computed_default( &self, - bootstrapper_config: &BootstrapperConfig, - persistent_config_opt: &Option>, + _bootstrapper_config: &BootstrapperConfig, + persistent_config: &dyn PersistentConfiguration, _db_password_opt: &Option, ) -> Option<(String, UiSetupResponseValueStatus)> { - let persistent_mapping_protocol_opt = match persistent_config_opt { - Some(pc) => match pc.mapping_protocol() { - Ok(protocol_opt) => protocol_opt, - Err(_) => None, - }, - None => None, + let persistent_config_value_opt = match persistent_config.mapping_protocol() { + Ok(protocol_opt) => protocol_opt, + Err(_) => None, }; - let from_bootstrapper_opt = bootstrapper_config.mapping_protocol_opt; - match (persistent_mapping_protocol_opt, from_bootstrapper_opt) { - (Some(persistent), None) => Some((persistent.to_string().to_lowercase(), Configured)), - (None, Some(from_bootstrapper)) => { - Some((from_bootstrapper.to_string().to_lowercase(), Configured)) - } - (Some(persistent), Some(from_bootstrapper)) if persistent != from_bootstrapper => { - Some((from_bootstrapper.to_string().to_lowercase(), Configured)) - } - (Some(persistent), Some(_)) => { - Some((persistent.to_string().to_lowercase(), Configured)) - } - _ => None, - } + persistent_config_value_opt + .map(|protocol| (protocol.to_string().to_lowercase(), Configured)) } } @@ -818,7 +802,7 @@ impl ValueRetriever for NeighborhoodMode { fn computed_default( &self, _bootstrapper_config: &BootstrapperConfig, - _persistent_config_opt: &Option>, + _persistent_config: &dyn PersistentConfiguration, _db_password_opt: &Option, ) -> Option<(String, UiSetupResponseValueStatus)> { Some(("standard".to_string(), Default)) @@ -846,15 +830,15 @@ impl ValueRetriever for Neighbors { fn computed_default( &self, _bootstrapper_config: &BootstrapperConfig, - persistent_config_opt: &Option>, + persistent_config: &dyn PersistentConfiguration, db_password_opt: &Option, ) -> Option<(String, UiSetupResponseValueStatus)> { - match (persistent_config_opt, db_password_opt) { - (Some(pc), Some(pw)) => match pc.past_neighbors(pw) { + match db_password_opt { + Some(pw) => match persistent_config.past_neighbors(pw) { Ok(Some(pns)) => Some((node_descriptors_to_neighbors(pns), Configured)), _ => None, }, - _ => None, + None => None, } } @@ -867,6 +851,92 @@ impl ValueRetriever for Neighbors { } } +struct PaymentThresholds {} +impl ValueRetriever for PaymentThresholds { + fn value_name(&self) -> &'static str { + "payment-thresholds" + } + + fn computed_default( + &self, + _bootstrapper_config: &BootstrapperConfig, + pc: &dyn PersistentConfiguration, + _db_password_opt: &Option, + ) -> Option<(String, UiSetupResponseValueStatus)> { + let pc_value = pc.payment_thresholds().expectv("payment-thresholds"); + payment_thresholds_rate_pack_and_scan_intervals(pc_value, *DEFAULT_PAYMENT_THRESHOLDS) + } + + fn is_required(&self, _params: &SetupCluster) -> bool { + true + } +} + +struct RatePack {} +impl ValueRetriever for RatePack { + fn value_name(&self) -> &'static str { + "rate-pack" + } + + fn computed_default( + &self, + bootstrapper_config: &BootstrapperConfig, + pc: &dyn PersistentConfiguration, + _db_password_opt: &Option, + ) -> Option<(String, UiSetupResponseValueStatus)> { + match &bootstrapper_config.neighborhood_config.mode { + NeighborhoodModeEnum::Standard(_, _, _) | NeighborhoodModeEnum::OriginateOnly(_, _) => { + } + _ => return None, + } + let pc_value = pc.rate_pack().expectv("rate-pack"); + payment_thresholds_rate_pack_and_scan_intervals(pc_value, DEFAULT_RATE_PACK) + } + + fn is_required(&self, params: &SetupCluster) -> bool { + match params.get("neighborhood-mode") { + Some(nhm) if &nhm.value == "standard" => true, + Some(nhm) if &nhm.value == "originate-only" => true, + _ => false, + } + } +} + +struct ScanIntervals {} +impl ValueRetriever for ScanIntervals { + fn value_name(&self) -> &'static str { + "scan-intervals" + } + + fn computed_default( + &self, + _bootstrapper_config: &BootstrapperConfig, + pc: &dyn PersistentConfiguration, + _db_password_opt: &Option, + ) -> Option<(String, UiSetupResponseValueStatus)> { + let pc_value = pc.scan_intervals().expectv("scan-intervals"); + payment_thresholds_rate_pack_and_scan_intervals(pc_value, *DEFAULT_SCAN_INTERVALS) + } + + fn is_required(&self, _params: &SetupCluster) -> bool { + true + } +} + +fn payment_thresholds_rate_pack_and_scan_intervals( + persistent_config_value: T, + default: T, +) -> Option<(String, UiSetupResponseValueStatus)> +where + T: PartialEq + Display + Copy, +{ + if persistent_config_value == default { + Some((default.to_string(), Default)) + } else { + Some((persistent_config_value.to_string(), Configured)) + } +} + struct RealUser { #[allow(dead_code)] dirs_wrapper: Box, @@ -879,7 +949,7 @@ impl ValueRetriever for RealUser { fn computed_default( &self, _bootstrapper_config: &BootstrapperConfig, - _persistent_config_opt: &Option>, + _persistent_config: &dyn PersistentConfiguration, _db_password_opt: &Option, ) -> Option<(String, UiSetupResponseValueStatus)> { #[cfg(target_os = "windows")] @@ -928,6 +998,9 @@ fn value_retrievers(dirs_wrapper: &dyn DirsWrapper) -> Vec (dss, Default), - None => ("".to_string(), Required), - }; + let (dns_servers_str, dns_servers_status) = match DnsServers::new().computed_default( + &BootstrapperConfig::new(), + &make_persistent_config_real_with_config_dao_null(), + &None, + ) { + Some((dss, _)) => (dss, Default), + None => ("".to_string(), Required), + }; let expected_result = vec![ - ("blockchain-service-url", "", Required), + ( + "blockchain-service-url", + "https://well-known-provider.com", + Set, + ), ("chain", DEFAULT_CHAIN.rec().literal_identifier, Default), - ("clandestine-port", "1234", Default), + ("clandestine-port", "1234", Configured), ("config-file", "config.toml", Default), ("consuming-private-key", "", Blank), ("crash-point", "", Blank), @@ -1132,6 +1216,12 @@ mod tests { "masq://eth-mainnet:QUJDRA@1.2.3.4:1234,masq://eth-mainnet:RUZHSA@5.6.7.8:5678", Configured, ), + ( + "payment-thresholds", + &DEFAULT_PAYMENT_THRESHOLDS.to_string(), + Default, + ), + ("rate-pack", &DEFAULT_RATE_PACK.to_string(), Default), #[cfg(not(target_os = "windows"))] ( "real-user", @@ -1140,6 +1230,11 @@ mod tests { .to_string(), Default, ), + ( + "scan-intervals", + &DEFAULT_SCAN_INTERVALS.to_string(), + Default, + ), ] .into_iter() .map(|(name, value, status)| { @@ -1167,6 +1262,7 @@ mod tests { ("blockchain-service-url", "https://example.com", Set), ("chain", TEST_DEFAULT_CHAIN.rec().literal_identifier, Set), ("clandestine-port", "1234", Set), + ("config-file", "config.toml", Default), ("consuming-private-key", "0011223344556677001122334455667700112233445566770011223344556677", Set), ("crash-point", "Message", Set), ("data-directory", home_dir.to_str().unwrap(), Set), @@ -1179,8 +1275,11 @@ mod tests { ("mapping-protocol", "pmp", Set), ("neighborhood-mode", "originate-only", Set), ("neighbors", "masq://eth-ropsten:MTIzNDU2Nzg5MTEyMzQ1Njc4OTIxMjM0NTY3ODkzMTI@1.2.3.4:1234,masq://eth-ropsten:MTIzNDU2Nzg5MTEyMzQ1Njc4OTIxMjM0NTY3ODkzMTI@5.6.7.8:5678", Set), + ("payment-thresholds","1234|50000|1000|1000|20000|20000",Set), + ("rate-pack","1|3|3|8",Set), #[cfg(not(target_os = "windows"))] ("real-user", "9999:9999:booga", Set), + ("scan-intervals","150|150|150",Set) ]); let dirs_wrapper = Box::new(DirsWrapperReal); let subject = SetupReporterReal::new(dirs_wrapper); @@ -1204,8 +1303,11 @@ mod tests { ("mapping-protocol", "pmp", Set), ("neighborhood-mode", "originate-only", Set), ("neighbors", "masq://eth-ropsten:MTIzNDU2Nzg5MTEyMzQ1Njc4OTIxMjM0NTY3ODkzMTI@1.2.3.4:1234,masq://eth-ropsten:MTIzNDU2Nzg5MTEyMzQ1Njc4OTIxMjM0NTY3ODkzMTI@5.6.7.8:5678", Set), + ("payment-thresholds","1234|50000|1000|1000|20000|20000",Set), + ("rate-pack","1|3|3|8",Set), #[cfg(not(target_os = "windows"))] ("real-user", "9999:9999:booga", Set), + ("scan-intervals","150|150|150",Set) ].into_iter() .map (|(name, value, status)| (name.to_string(), UiSetupResponseValue::new(name, value, status))) .collect_vec(); @@ -1239,8 +1341,11 @@ mod tests { ("mapping-protocol", "igdp"), ("neighborhood-mode", "originate-only"), ("neighbors", "masq://eth-ropsten:MTIzNDU2Nzg5MTEyMzQ1Njc4OTIxMjM0NTY3ODkzMTI@1.2.3.4:1234,masq://eth-ropsten:MTIzNDU2Nzg5MTEyMzQ1Njc4OTIxMjM0NTY3ODkzMTI@5.6.7.8:5678"), + ("payment-thresholds","1234|50000|1000|1000|15000|15000"), + ("rate-pack","1|3|3|8"), #[cfg(not(target_os = "windows"))] ("real-user", "9999:9999:booga"), + ("scan-intervals","140|130|150") ].into_iter() .map (|(name, value)| UiSetupRequestValue::new(name, value)) .collect_vec(); @@ -1268,8 +1373,11 @@ mod tests { ("mapping-protocol", "igdp", Set), ("neighborhood-mode", "originate-only", Set), ("neighbors", "masq://eth-ropsten:MTIzNDU2Nzg5MTEyMzQ1Njc4OTIxMjM0NTY3ODkzMTI@1.2.3.4:1234,masq://eth-ropsten:MTIzNDU2Nzg5MTEyMzQ1Njc4OTIxMjM0NTY3ODkzMTI@5.6.7.8:5678", Set), + ("payment-thresholds","1234|50000|1000|1000|15000|15000",Set), + ("rate-pack","1|3|3|8",Set), #[cfg(not(target_os = "windows"))] ("real-user", "9999:9999:booga", Set), + ("scan-intervals","140|130|150",Set) ].into_iter() .map (|(name, value, status)| (name.to_string(), UiSetupResponseValue::new(name, value, status))) .collect_vec(); @@ -1304,8 +1412,11 @@ mod tests { ("MASQ_MAPPING_PROTOCOL", "pmp"), ("MASQ_NEIGHBORHOOD_MODE", "originate-only"), ("MASQ_NEIGHBORS", "masq://eth-ropsten:MTIzNDU2Nzg5MTEyMzQ1Njc4OTIxMjM0NTY3ODkzMTI@1.2.3.4:1234,masq://eth-ropsten:MTIzNDU2Nzg5MTEyMzQ1Njc4OTIxMjM0NTY3ODkzMTI@5.6.7.8:5678"), + ("MASQ_PAYMENT_THRESHOLDS","1234|50000|1000|1234|19000|20000"), + ("MASQ_RATE_PACK","1|3|3|8"), #[cfg(not(target_os = "windows"))] ("MASQ_REAL_USER", "9999:9999:booga"), + ("MASQ_SCAN_INTERVALS","133|133|111") ].into_iter() .for_each (|(name, value)| std::env::set_var (name, value)); let dirs_wrapper = Box::new(DirsWrapperReal); @@ -1331,8 +1442,11 @@ mod tests { ("mapping-protocol", "pmp", Configured), ("neighborhood-mode", "originate-only", Configured), ("neighbors", "masq://eth-ropsten:MTIzNDU2Nzg5MTEyMzQ1Njc4OTIxMjM0NTY3ODkzMTI@1.2.3.4:1234,masq://eth-ropsten:MTIzNDU2Nzg5MTEyMzQ1Njc4OTIxMjM0NTY3ODkzMTI@5.6.7.8:5678", Configured), + ("payment-thresholds","1234|50000|1000|1234|19000|20000",Configured), + ("rate-pack","1|3|3|8",Configured), #[cfg(not(target_os = "windows"))] ("real-user", "9999:9999:booga", Configured), + ("scan-intervals","133|133|111",Configured) ].into_iter() .map (|(name, value, status)| (name.to_string(), UiSetupResponseValue::new(name, value, status))) .collect_vec(); @@ -1383,6 +1497,13 @@ mod tests { config_file .write_all(b"neighborhood-mode = \"zero-hop\"\n") .unwrap(); + config_file.write_all(b"rate-pack = \"2|2|2|2\"\n").unwrap(); + config_file + .write_all(b"payment-thresholds = \"33|55|33|646|999|999\"\n") + .unwrap(); + config_file + .write_all(b"scan-intervals = \"111|100|99\"\n") + .unwrap() } let ropsten_dir = data_root .join("MASQ") @@ -1399,6 +1520,9 @@ mod tests { // NOTE: You can't really change consuming-private-key without starting a new database config_file.write_all(b"consuming-private-key = \"FFEEDDCCBBAA99887766554433221100FFEEDDCCBBAA99887766554433221100\"\n").unwrap(); config_file.write_all(b"crash-point = \"None\"\n").unwrap(); + config_file + .write_all(b"db-password = \"ropstenPassword\"\n") + .unwrap(); config_file .write_all(b"dns-servers = \"8.7.6.5\"\n") .unwrap(); @@ -1414,13 +1538,21 @@ mod tests { config_file .write_all(b"neighborhood-mode = \"zero-hop\"\n") .unwrap(); + config_file + .write_all(b"rate-pack = \"55|50|60|61\"\n") + .unwrap(); + config_file + .write_all(b"payment-thresholds = \"1000|1000|3000|3333|10000|20000\"\n") + .unwrap(); + config_file + .write_all(b"scan-intervals = \"555|555|555\"\n") + .unwrap() } let subject = SetupReporterReal::new(Box::new( DirsWrapperMock::new() .home_dir_result(Some(home_dir.clone())) .data_dir_result(Some(data_root.clone())), )); - let params = vec![UiSetupRequestValue::new( "chain", DEFAULT_CHAIN.rec().literal_identifier, @@ -1453,7 +1585,7 @@ mod tests { &ropsten_dir.to_string_lossy().to_string(), Default, ), - ("db-password", "", Blank), + ("db-password", "ropstenPassword", Configured), ("dns-servers", "8.7.6.5", Configured), ( "earning-wallet", @@ -1466,6 +1598,12 @@ mod tests { ("mapping-protocol", "pmp", Configured), ("neighborhood-mode", "zero-hop", Configured), ("neighbors", "", Blank), + ( + "payment-thresholds", + "1000|1000|3000|3333|10000|20000", + Configured, + ), + ("rate-pack", "55|50|60|61", Configured), #[cfg(not(target_os = "windows"))] ( "real-user", @@ -1474,6 +1612,7 @@ mod tests { .to_string(), Default, ), + ("scan-intervals", "555|555|555", Configured), ] .into_iter() .map(|(name, value, status)| { @@ -1498,23 +1637,23 @@ mod tests { "get_modified_setup_database_nonexistent_all_but_requireds_cleared", ); vec![ - ("MASQ_BLOCKCHAIN_SERVICE_URL", "https://example.com"), ("MASQ_CHAIN", TEST_DEFAULT_CHAIN.rec().literal_identifier), ("MASQ_CLANDESTINE_PORT", "1234"), ("MASQ_CONSUMING_PRIVATE_KEY", "0011223344556677001122334455667700112233445566770011223344556677"), ("MASQ_CRASH_POINT", "Panic"), ("MASQ_DATA_DIRECTORY", home_dir.to_str().unwrap()), - ("MASQ_DB_PASSWORD", "password"), ("MASQ_DNS_SERVERS", "8.8.8.8"), ("MASQ_EARNING_WALLET", "0x0123456789012345678901234567890123456789"), ("MASQ_GAS_PRICE", "50"), - ("MASQ_IP", "4.3.2.1"), ("MASQ_LOG_LEVEL", "error"), ("MASQ_MAPPING_PROTOCOL", "pcp"), ("MASQ_NEIGHBORHOOD_MODE", "originate-only"), ("MASQ_NEIGHBORS", "masq://eth-ropsten:MTIzNDU2Nzg5MTEyMzQ1Njc4OTIxMjM0NTY3ODkzMTI@1.2.3.4:1234,masq://eth-ropsten:MTIzNDU2Nzg5MTEyMzQ1Njc4OTIxMjM0NTY3ODkzMTI@5.6.7.8:5678"), + ("MASQ_PAYMENT_THRESHOLDS","1234|50000|1000|1000|20000|20000"), + ("MASQ_RATE_PACK","1|3|3|8"), #[cfg(not(target_os = "windows"))] ("MASQ_REAL_USER", "9999:9999:booga"), + ("MASQ_SCAN_INTERVALS","150|150|155"), ].into_iter() .for_each (|(name, value)| std::env::set_var (name, value)); let params = vec![ @@ -1533,13 +1672,17 @@ mod tests { "mapping-protocol", "neighborhood-mode", "neighbors", + "payment-thresholds", + "rate-pack", #[cfg(not(target_os = "windows"))] "real-user", + "scan-intervals", ] .into_iter() .map(|name| UiSetupRequestValue::clear(name)) .collect_vec(); - let existing_setup = setup_cluster_from(vec![ + let existing_setup = + setup_cluster_from(vec![ ("blockchain-service-url", "https://booga.com", Set), ("clandestine-port", "4321", Set), ( @@ -1566,8 +1709,11 @@ mod tests { "masq://eth-ropsten:MTIzNDU2Nzg5MTEyMzQ1Njc4OTIxMjM0NTY3ODkzMTI@9.10.11.12:9101", Set, ), + ("payment-thresholds", "4321|66666|777|987|123456|124444", Set), + ("rate-pack", "10|30|13|28", Set), #[cfg(not(target_os = "windows"))] ("real-user", "6666:6666:agoob", Set), + ("scan-intervals", "111|111|111", Set), ]); let dirs_wrapper = Box::new(DirsWrapperReal); let subject = SetupReporterReal::new(dirs_wrapper); @@ -1575,14 +1721,14 @@ mod tests { let result = subject.get_modified_setup(existing_setup, params).unwrap(); let expected_result = vec![ - ("blockchain-service-url", "https://example.com", Configured), + ("blockchain-service-url", "", Required), ("chain", TEST_DEFAULT_CHAIN.rec().literal_identifier, Configured), ("clandestine-port", "1234", Configured), ("config-file", "config.toml", Default), ("consuming-private-key", "0011223344556677001122334455667700112233445566770011223344556677", Configured), ("crash-point", "Panic", Configured), ("data-directory", home_dir.to_str().unwrap(), Configured), - ("db-password", "password", Configured), + ("db-password", "",Required), ("dns-servers", "8.8.8.8", Configured), ( "earning-wallet", @@ -1590,13 +1736,16 @@ mod tests { Configured, ), ("gas-price", "50", Configured), - ("ip", "4.3.2.1", Configured), + ("ip","", Blank), ("log-level", "error", Configured), ("mapping-protocol", "pcp", Configured), ("neighborhood-mode", "originate-only", Configured), ("neighbors", "masq://eth-ropsten:MTIzNDU2Nzg5MTEyMzQ1Njc4OTIxMjM0NTY3ODkzMTI@1.2.3.4:1234,masq://eth-ropsten:MTIzNDU2Nzg5MTEyMzQ1Njc4OTIxMjM0NTY3ODkzMTI@5.6.7.8:5678", Configured), + ("payment-thresholds","1234|50000|1000|1000|20000|20000",Configured), + ("rate-pack","1|3|3|8",Configured), #[cfg(not(target_os = "windows"))] ("real-user", "9999:9999:booga", Configured), + ("scan-intervals","150|150|155",Configured), ] .into_iter() .map(|(name, value, status)| { @@ -1729,12 +1878,11 @@ mod tests { } #[test] - fn get_modified_setup_data_directory_on_error_with_input_trying_to_blank_chain_out() { - //by blanking the original chain the default values is set to its place + fn get_modified_setup_data_directory_trying_to_blank_chain_out_on_error() { let _guard = EnvironmentGuard::new(); let base_dir = ensure_node_home_directory_exists( "setup_reporter", - "get_modified_setup_data_directory_depends_on_new_chain_on_error", + "get_modified_setup_data_directory_trying_to_blank_chain_out_on_error", ); let current_data_dir = base_dir .join("MASQ") @@ -1875,35 +2023,84 @@ mod tests { } #[test] - fn run_configuration_suppresses_db_migration_which_is_why_it_refuses_to_initiate_persistent_config( - ) { + fn run_configuration_without_existing_database_implies_config_dao_null_to_use() { + let _guard = EnvironmentGuard::new(); + let home_dir = ensure_node_home_directory_exists( + "setup_reporter", + "run_configuration_without_existing_database_implies_config_dao_null_to_use", + ); + let current_default_gas_price = DEFAULT_GAS_PRICE; + let gas_price_for_set_attempt = current_default_gas_price + 78; + let multi_config = + make_simplified_multi_config(["--data-directory", home_dir.to_str().unwrap()]); + let dirs_wrapper = make_pre_populated_mocked_directory_wrapper(); + let subject = SetupReporterReal::new(Box::new(dirs_wrapper)); + + let ((bootstrapper_config, mut persistent_config), _) = + subject.run_configuration(&multi_config, &home_dir); + + let error = DbInitializerReal::default() + .initialize(&home_dir, false, MigratorConfig::test_default()) + .unwrap_err(); + assert_eq!(error, InitializationError::Nonexistent); + assert_eq!( + bootstrapper_config.blockchain_bridge_config.gas_price, + current_default_gas_price + ); + persistent_config + .set_gas_price(gas_price_for_set_attempt) + .unwrap(); + //if this had contained ConfigDaoReal the setting would've worked + let gas_price = persistent_config.gas_price().unwrap(); + //asserting negation + assert_ne!(gas_price, gas_price_for_set_attempt); + } + + #[test] + fn run_configuration_suppresses_db_migration_which_implies_just_use_of_config_dao_null() { let data_dir = ensure_node_home_directory_exists( "setup_reporter", - "run_configuration_suppresses_db_migration_which_is_why_it_refuses_to_initiate_persistent_config", + "run_configuration_suppresses_db_migration_which_implies_just_use_of_config_dao_null", ); + let current_default_gas_price = DEFAULT_GAS_PRICE; + let gas_price_to_be_in_the_real_db = current_default_gas_price + 55; + let gas_price_for_set_attempt = current_default_gas_price + 66; let conn = bring_db_0_back_to_life_and_return_connection(&data_dir.join(DATABASE_FILE)); - conn.execute("update config set value = 55 where name = 'gas_price'", []) - .unwrap(); + conn.execute( + "update config set value = ? where name = 'gas_price'", + [&gas_price_to_be_in_the_real_db], + ) + .unwrap(); let dao = ConfigDaoReal::new(Box::new(ConnectionWrapperReal::new(conn))); let updated_gas_price = dao.get("gas_price").unwrap().value_opt.unwrap(); - assert_eq!(updated_gas_price, "55"); + assert_eq!( + updated_gas_price, + gas_price_to_be_in_the_real_db.to_string() + ); let schema_version_before = dao.get("schema_version").unwrap().value_opt.unwrap(); assert_eq!(schema_version_before, "0"); - let multi_config = make_simplified_multi_config([ - "MASQNode", - "--data-directory", - data_dir.to_str().unwrap(), - ]); + let multi_config = + make_simplified_multi_config(["--data-directory", data_dir.to_str().unwrap()]); let dirs_wrapper = make_pre_populated_mocked_directory_wrapper(); let subject = SetupReporterReal::new(Box::new(dirs_wrapper)); - let ((bootstrapper_config, persistent_config), _) = + let ((bootstrapper_config, mut persistent_config), _) = subject.run_configuration(&multi_config, &data_dir); - assert_ne!(bootstrapper_config.blockchain_bridge_config.gas_price, 55); //asserting negation - assert!(persistent_config.is_none()); let schema_version_after = dao.get("schema_version").unwrap().value_opt.unwrap(); - assert_eq!(schema_version_before, schema_version_after) + assert_eq!(schema_version_before, schema_version_after); + //asserting negation + assert_ne!( + bootstrapper_config.blockchain_bridge_config.gas_price, + gas_price_to_be_in_the_real_db + ); + persistent_config + .set_gas_price(gas_price_for_set_attempt) + .unwrap(); + //if this had contained ConfigDaoReal the setting would've worked + let gas_price = persistent_config.gas_price().unwrap(); + //asserting negation + assert_ne!(gas_price, gas_price_for_set_attempt); } #[test] @@ -2135,7 +2332,11 @@ mod tests { assert_eq!( result.get("gas-price").unwrap().value, GasPrice {} - .computed_default(&BootstrapperConfig::new(), &None, &None) + .computed_default( + &BootstrapperConfig::new(), + &make_persistent_config_real_with_config_dao_null(), + &None + ) .unwrap() .0 ); @@ -2312,7 +2513,11 @@ mod tests { fn chain_computed_default() { let subject = Chain {}; - let result = subject.computed_default(&BootstrapperConfig::new(), &None, &None); + let result = subject.computed_default( + &BootstrapperConfig::new(), + &make_persistent_config_real_with_config_dao_null(), + &None, + ); assert_eq!( result, @@ -2326,22 +2531,10 @@ mod tests { PersistentConfigurationMock::new().clandestine_port_result(Ok(1234)); let subject = ClandestinePort {}; - let result = subject.computed_default( - &BootstrapperConfig::new(), - &Some(Box::new(persistent_config)), - &None, - ); + let result = + subject.computed_default(&BootstrapperConfig::new(), &persistent_config, &None); - assert_eq!(result, Some(("1234".to_string(), Default))) - } - - #[test] - fn clandestine_port_computed_default_absent() { - let subject = ClandestinePort {}; - - let result = subject.computed_default(&BootstrapperConfig::new(), &None, &None); - - assert_eq!(result, None) + assert_eq!(result, Some(("1234".to_string(), Configured))) } #[test] @@ -2350,11 +2543,8 @@ mod tests { let persistent_config = PersistentConfigurationMock::new() .clandestine_port_result(Err(PersistentConfigError::NotPresent)); - let result = subject.computed_default( - &BootstrapperConfig::new(), - &Some(Box::new(persistent_config)), - &None, - ); + let result = + subject.computed_default(&BootstrapperConfig::new(), &persistent_config, &None); assert_eq!(result, None) } @@ -2376,7 +2566,11 @@ mod tests { let subject = DataDirectory::default(); - let result = subject.computed_default(&config, &None, &None); + let result = subject.computed_default( + &config, + &make_persistent_config_real_with_config_dao_null(), + &None, + ); assert_eq!(result, Some((expected, Default))) } @@ -2387,7 +2581,11 @@ mod tests { let mut subject = DnsServers::new(); subject.factory = Box::new(factory); - let result = subject.computed_default(&BootstrapperConfig::new(), &None, &None); + let result = subject.computed_default( + &BootstrapperConfig::new(), + &make_persistent_config_real_with_config_dao_null(), + &None, + ); assert_eq!(result, None) } @@ -2400,7 +2598,11 @@ mod tests { let mut subject = DnsServers::new(); subject.factory = Box::new(factory); - let result = subject.computed_default(&BootstrapperConfig::new(), &None, &None); + let result = subject.computed_default( + &BootstrapperConfig::new(), + &make_persistent_config_real_with_config_dao_null(), + &None, + ); assert_eq!(result, None) } @@ -2414,7 +2616,11 @@ mod tests { let mut subject = DnsServers::new(); subject.factory = Box::new(factory); - let result = subject.computed_default(&BootstrapperConfig::new(), &None, &None); + let result = subject.computed_default( + &BootstrapperConfig::new(), + &make_persistent_config_real_with_config_dao_null(), + &None, + ); assert_eq!(result, None); TestLogHandler::new().exists_log_containing("WARN: DnsServers: Error inspecting DNS settings: This system does not appear to be connected to a network"); @@ -2427,7 +2633,11 @@ mod tests { let mut subject = DnsServers::new(); subject.factory = Box::new(factory); - let result = subject.computed_default(&BootstrapperConfig::new(), &None, &None); + let result = subject.computed_default( + &BootstrapperConfig::new(), + &make_persistent_config_real_with_config_dao_null(), + &None, + ); assert_eq!(result, None) } @@ -2442,7 +2652,11 @@ mod tests { let mut subject = DnsServers::new(); subject.factory = Box::new(factory); - let result = subject.computed_default(&BootstrapperConfig::new(), &None, &None); + let result = subject.computed_default( + &BootstrapperConfig::new(), + &make_persistent_config_real_with_config_dao_null(), + &None, + ); assert_eq!(result, Some(("192.168.0.1,8.8.8.8".to_string(), Default))) } @@ -2451,13 +2665,11 @@ mod tests { fn earning_wallet_computed_default_with_everything_configured_is_still_none() { let mut config = BootstrapperConfig::new(); config.earning_wallet = Wallet::new("command-line address"); - let persistent_config_opt: Option> = Some(Box::new( - PersistentConfigurationMock::new() - .earning_wallet_address_result(Ok(Some("persistent address".to_string()))), - )); + let persistent_config = PersistentConfigurationMock::new() + .earning_wallet_address_result(Ok(Some("persistent address".to_string()))); let subject = EarningWallet {}; - let result = subject.computed_default(&config, &persistent_config_opt, &None); + let result = subject.computed_default(&config, &persistent_config, &None); assert_eq!(result, None) } @@ -2467,7 +2679,11 @@ mod tests { let config = BootstrapperConfig::new(); let subject = EarningWallet {}; - let result = subject.computed_default(&config, &None, &None); + let result = subject.computed_default( + &config, + &make_persistent_config_real_with_config_dao_null(), + &None, + ); assert_eq!(result, None) } @@ -2478,7 +2694,11 @@ mod tests { bootstrapper_config.blockchain_bridge_config.gas_price = 57; let subject = GasPrice {}; - let result = subject.computed_default(&bootstrapper_config, &None, &None); + let result = subject.computed_default( + &bootstrapper_config, + &make_persistent_config_real_with_config_dao_null(), + &None, + ); assert_eq!(result, Some(("57".to_string(), Default))) } @@ -2487,7 +2707,11 @@ mod tests { fn gas_price_computed_default_absent() { let subject = GasPrice {}; - let result = subject.computed_default(&BootstrapperConfig::new(), &None, &None); + let result = subject.computed_default( + &BootstrapperConfig::new(), + &make_persistent_config_real_with_config_dao_null(), + &None, + ); assert_eq!(result, Some(("1".to_string(), Default))) } @@ -2498,7 +2722,11 @@ mod tests { let mut config = BootstrapperConfig::new(); config.neighborhood_config.mode = crate::sub_lib::neighborhood::NeighborhoodMode::ZeroHop; - let result = subject.computed_default(&config, &None, &None); + let result = subject.computed_default( + &config, + &make_persistent_config_real_with_config_dao_null(), + &None, + ); assert_eq!(result, Some(("".to_string(), Blank))); } @@ -2513,7 +2741,11 @@ mod tests { DEFAULT_RATE_PACK, ); - let result = subject.computed_default(&config, &None, &None); + let result = subject.computed_default( + &config, + &make_persistent_config_real_with_config_dao_null(), + &None, + ); assert_eq!(result, Some(("5.6.7.8".to_string(), Set))); } @@ -2524,7 +2756,11 @@ mod tests { let mut config = BootstrapperConfig::new(); config.neighborhood_config.mode = crate::sub_lib::neighborhood::NeighborhoodMode::ZeroHop; - let result = subject.computed_default(&config, &None, &None); + let result = subject.computed_default( + &config, + &make_persistent_config_real_with_config_dao_null(), + &None, + ); assert_eq!(result, Some(("".to_string(), Blank))); } @@ -2533,70 +2769,54 @@ mod tests { fn log_level_computed_default() { let subject = LogLevel {}; - let result = subject.computed_default(&BootstrapperConfig::new(), &None, &None); + let result = subject.computed_default( + &BootstrapperConfig::new(), + &make_persistent_config_real_with_config_dao_null(), + &None, + ); assert_eq!(result, Some(("warn".to_string(), Default))) } #[test] - fn mapping_protocol_is_just_blank_if_no_data_in_database_and_unspecified_on_command_line() { + fn mapping_protocol_is_just_blank_if_no_data_in_database() { let subject = MappingProtocol {}; let persistent_config = PersistentConfigurationMock::default().mapping_protocol_result(Ok(None)); - let result = subject.computed_default( - &BootstrapperConfig::new(), - &Some(Box::new(persistent_config)), - &None, - ); + let result = + subject.computed_default(&BootstrapperConfig::new(), &persistent_config, &None); assert_eq!(result, None) } #[test] - fn mapping_protocol_is_configured_if_data_in_database_and_no_command_line() { + fn mapping_protocol_is_configured_if_data_in_database() { let subject = MappingProtocol {}; let persistent_config = PersistentConfigurationMock::default() .mapping_protocol_result(Ok(Some(AutomapProtocol::Pmp))); let bootstrapper_config = BootstrapperConfig::new(); - let result = subject.computed_default( - &bootstrapper_config, - &Some(Box::new(persistent_config)), - &None, - ); + let result = subject.computed_default(&bootstrapper_config, &persistent_config, &None); assert_eq!(result, Some(("pmp".to_string(), Configured))) } #[test] - fn mapping_protocol_is_configured_if_no_database_but_bootstrapper_config_contains_some_value() { - let subject = MappingProtocol {}; - let persistent_config = - PersistentConfigurationMock::default().mapping_protocol_result(Ok(None)); - let mut bootstrapper_config = BootstrapperConfig::new(); - bootstrapper_config.mapping_protocol_opt = Some(AutomapProtocol::Pcp); + fn neighborhood_mode_computed_default() { + let subject = NeighborhoodMode {}; let result = subject.computed_default( - &bootstrapper_config, - &Some(Box::new(persistent_config)), + &BootstrapperConfig::new(), + &make_persistent_config_real_with_config_dao_null(), &None, ); - assert_eq!(result, Some(("pcp".to_string(), Configured))) - } - - #[test] - fn neighborhood_mode_computed_default() { - let subject = NeighborhoodMode {}; - - let result = subject.computed_default(&BootstrapperConfig::new(), &None, &None); - assert_eq!(result, Some(("standard".to_string(), Default))) } #[test] - fn neighbors_computed_default_present_present_present_ok() { + fn neighbors_computed_default_persistent_config_present_password_present_values_present() { let past_neighbors_params_arc = Arc::new(Mutex::new(vec![])); let persistent_config = PersistentConfigurationMock::new() .past_neighbors_params(&past_neighbors_params_arc) @@ -2616,7 +2836,7 @@ mod tests { let result = subject.computed_default( &BootstrapperConfig::new(), - &Some(Box::new(persistent_config)), + &persistent_config, &Some("password".to_string()), ); @@ -2626,16 +2846,16 @@ mod tests { } #[test] - fn neighbors_computed_default_present_present_err() { + fn neighbors_computed_default_persistent_config_present_password_present_values_absent() { let past_neighbors_params_arc = Arc::new(Mutex::new(vec![])); let persistent_config = PersistentConfigurationMock::new() .past_neighbors_params(&past_neighbors_params_arc) - .past_neighbors_result(Err(PersistentConfigError::PasswordError)); + .past_neighbors_result(Ok(None)); let subject = Neighbors {}; let result = subject.computed_default( &BootstrapperConfig::new(), - &Some(Box::new(persistent_config)), + &persistent_config, &Some("password".to_string()), ); @@ -2645,17 +2865,33 @@ mod tests { } #[test] - fn neighbors_computed_default_present_absent() { - // absence of configured result will cause panic if past_neighbors is called - let persistent_config = PersistentConfigurationMock::new(); + fn neighbors_computed_default_persistent_config_present_password_present_but_with_err() { + let past_neighbors_params_arc = Arc::new(Mutex::new(vec![])); + let persistent_config = PersistentConfigurationMock::new() + .past_neighbors_params(&past_neighbors_params_arc) + .past_neighbors_result(Err(PersistentConfigError::PasswordError)); let subject = Neighbors {}; let result = subject.computed_default( &BootstrapperConfig::new(), - &Some(Box::new(persistent_config)), - &None, + &persistent_config, + &Some("password".to_string()), ); + assert_eq!(result, None); + let past_neighbors_params = past_neighbors_params_arc.lock().unwrap(); + assert_eq!(*past_neighbors_params, vec!["password".to_string()]) + } + + #[test] + fn neighbors_computed_default_persistent_config_present_password_absent() { + // absence of configured result will cause panic if past_neighbors is called + let persistent_config = PersistentConfigurationMock::new(); + let subject = Neighbors {}; + + let result = + subject.computed_default(&BootstrapperConfig::new(), &persistent_config, &None); + assert_eq!(result, None); } @@ -2663,7 +2899,11 @@ mod tests { fn neighbors_computed_default_absent() { let subject = Neighbors {}; - let result = subject.computed_default(&BootstrapperConfig::new(), &None, &None); + let result = subject.computed_default( + &BootstrapperConfig::new(), + &make_persistent_config_real_with_config_dao_null(), + &None, + ); assert_eq!(result, None); } @@ -2673,7 +2913,11 @@ mod tests { fn real_user_computed_default() { let subject = crate::daemon::setup_reporter::RealUser::default(); - let result = subject.computed_default(&BootstrapperConfig::new(), &None, &None); + let result = subject.computed_default( + &BootstrapperConfig::new(), + &make_persistent_config_real_with_config_dao_null(), + &None, + ); assert_eq!( result, @@ -2691,11 +2935,189 @@ mod tests { fn real_user_computed_default() { let subject = crate::daemon::setup_reporter::RealUser::default(); - let result = subject.computed_default(&BootstrapperConfig::new(), &None, &None); + let result = subject.computed_default( + &BootstrapperConfig::new(), + &make_persistent_config_real_with_config_dao_null(), + &None, + ); assert_eq!(result, None); } + fn assert_rate_pack_computed_default_advanced_evaluation_regarding_specific_neighborhood( + neighborhood_mode: fn(rate_pack: neighborhood::RatePack) -> NeighborhoodModeEnum, + ) { + let subject = RatePack {}; + let mut bootstrapper_config = BootstrapperConfig::new(); + bootstrapper_config.neighborhood_config.mode = neighborhood_mode(DEFAULT_RATE_PACK); + let persistent_config = + PersistentConfigurationReal::new(Box::new(ConfigDaoNull::default())); + + let result = subject.computed_default(&bootstrapper_config, &persistent_config, &None); + + assert_eq!(result, Some((DEFAULT_RATE_PACK.to_string(), Default))) + } + + #[test] + fn rate_pack_computed_default_when_persistent_config_like_default() { + assert_computed_default_when_persistent_config_like_default( + &RatePack {}, + DEFAULT_RATE_PACK.to_string(), + ) + } + + #[test] + fn rate_pack_computed_default_persistent_config_unequal_to_default() { + let mut rate_pack = DEFAULT_RATE_PACK; + rate_pack.routing_byte_rate += 5; + rate_pack.exit_service_rate += 6; + + assert_computed_default_when_persistent_config_unequal_to_default( + &RatePack {}, + rate_pack, + &|p_c: PersistentConfigurationMock, value: neighborhood::RatePack| { + p_c.rate_pack_result(Ok(value)) + }, + ) + } + + #[test] + fn rate_pack_computed_default_neighborhood_mode_diff_from_standard_or_originate_only_returns_none( + ) { + let subject = &RatePack {}; + let mut bootstrapper_config = BootstrapperConfig::new(); + let consume_only = NeighborhoodModeEnum::ConsumeOnly(vec![]); + bootstrapper_config.neighborhood_config.mode = consume_only; + let persistent_config = + PersistentConfigurationReal::new(Box::new(ConfigDaoNull::default())); + + let result = subject.computed_default(&bootstrapper_config, &persistent_config, &None); + + assert_eq!(result, None); + let zero_hop = NeighborhoodModeEnum::ZeroHop; + bootstrapper_config.neighborhood_config.mode = zero_hop; + let persistent_config = + PersistentConfigurationReal::new(Box::new(ConfigDaoNull::default())); + + let result = subject.computed_default(&bootstrapper_config, &persistent_config, &None); + + assert_eq!(result, None); + } + + #[test] + fn rate_pack_standard_mode_goes_on_with_further_evaluation() { + assert_rate_pack_computed_default_advanced_evaluation_regarding_specific_neighborhood( + |rate_pack: neighborhood::RatePack| { + NeighborhoodModeEnum::Standard( + NodeAddr::new(&IpAddr::from_str("4.5.6.7").unwrap(), &[44444]), + vec![], + rate_pack, + ) + }, + ); + } + + #[test] + fn rate_pack_originate_only_mode_goes_on_with_further_evaluation() { + assert_rate_pack_computed_default_advanced_evaluation_regarding_specific_neighborhood( + |rate_pack: neighborhood::RatePack| { + NeighborhoodModeEnum::OriginateOnly(vec![], rate_pack) + }, + ); + } + + #[test] + fn scan_intervals_computed_default_when_persistent_config_like_default() { + assert_computed_default_when_persistent_config_like_default( + &ScanIntervals {}, + *DEFAULT_SCAN_INTERVALS, + ) + } + + #[test] + fn scan_intervals_computed_default_persistent_config_unequal_to_default() { + let mut scan_intervals = *DEFAULT_SCAN_INTERVALS; + scan_intervals.pending_payable_scan_interval = scan_intervals + .pending_payable_scan_interval + .add(Duration::from_secs(15)); + scan_intervals.pending_payable_scan_interval = scan_intervals + .receivable_scan_interval + .sub(Duration::from_secs(33)); + + assert_computed_default_when_persistent_config_unequal_to_default( + &ScanIntervals {}, + scan_intervals, + &|p_c: PersistentConfigurationMock, value: accountant::ScanIntervals| { + p_c.scan_intervals_result(Ok(value)) + }, + ) + } + + #[test] + fn payment_thresholds_computed_default_when_persistent_config_like_default() { + assert_computed_default_when_persistent_config_like_default( + &PaymentThresholds {}, + DEFAULT_PAYMENT_THRESHOLDS.to_string(), + ) + } + + #[test] + fn payment_thresholds_computed_default_persistent_config_unequal_to_default() { + let mut payment_thresholds = *DEFAULT_PAYMENT_THRESHOLDS; + payment_thresholds.maturity_threshold_sec += 12; + payment_thresholds.unban_below_gwei -= 11; + payment_thresholds.debt_threshold_gwei += 1111; + + assert_computed_default_when_persistent_config_unequal_to_default( + &PaymentThresholds {}, + payment_thresholds, + &|p_c: PersistentConfigurationMock, value: accountant::PaymentThresholds| { + p_c.payment_thresholds_result(Ok(value)) + }, + ) + } + + fn assert_computed_default_when_persistent_config_like_default( + subject: &dyn ValueRetriever, + default: T, + ) where + T: Display + PartialEq, + { + let mut bootstrapper_config = BootstrapperConfig::new(); + //the rate_pack within the mode setting does not determine the result, so I just set a nonsense + bootstrapper_config.neighborhood_config.mode = + NeighborhoodModeEnum::OriginateOnly(vec![], rate_pack(0)); + let persistent_config = + PersistentConfigurationReal::new(Box::new(ConfigDaoNull::default())); + + let result = subject.computed_default(&bootstrapper_config, &persistent_config, &None); + + assert_eq!(result, Some((default.to_string(), Default))) + } + + fn assert_computed_default_when_persistent_config_unequal_to_default( + subject: &dyn ValueRetriever, + persistent_config_value: T, + pc_method_result_setter: &C, + ) where + C: Fn(PersistentConfigurationMock, T) -> PersistentConfigurationMock, + T: Display + PartialEq + Copy, + { + let mut bootstrapper_config = BootstrapperConfig::new(); + //the rate_pack within the mode setting does not determine the result, so I just set a nonsense + bootstrapper_config.neighborhood_config.mode = + NeighborhoodModeEnum::OriginateOnly(vec![], rate_pack(0)); + let persistent_config = + pc_method_result_setter(PersistentConfigurationMock::new(), persistent_config_value); + + let result = subject.computed_default(&bootstrapper_config, &persistent_config, &None); + + assert_eq!( + result, + Some((persistent_config_value.to_string(), Configured)) + ) + } + fn verify_requirements( subject: &dyn ValueRetriever, param_name: &str, @@ -2779,6 +3201,20 @@ mod tests { verify_needed_for_blockchain(&GasPrice {}); } + #[test] + fn routing_byte_rate_requirements() { + verify_requirements( + &setup_reporter::RatePack {}, + "neighborhood-mode", + vec![ + ("standard", true), + ("zero-hop", false), + ("originate-only", true), + ("consume-only", false), + ], + ); + } + #[test] fn dumb_requirements() { let params = HashMap::new(); @@ -2797,6 +3233,11 @@ mod tests { assert_eq!(MappingProtocol {}.is_required(¶ms), false); assert_eq!(NeighborhoodMode {}.is_required(¶ms), true); assert_eq!(Neighbors {}.is_required(¶ms), true); + assert_eq!( + setup_reporter::PaymentThresholds {}.is_required(¶ms), + true + ); + assert_eq!(ScanIntervals {}.is_required(¶ms), true); assert_eq!( crate::daemon::setup_reporter::RealUser::default().is_required(¶ms), false @@ -2823,6 +3264,12 @@ mod tests { assert_eq!(MappingProtocol {}.value_name(), "mapping-protocol"); assert_eq!(NeighborhoodMode {}.value_name(), "neighborhood-mode"); assert_eq!(Neighbors {}.value_name(), "neighbors"); + assert_eq!( + setup_reporter::PaymentThresholds {}.value_name(), + "payment-thresholds" + ); + assert_eq!(setup_reporter::RatePack {}.value_name(), "rate-pack"); + assert_eq!(ScanIntervals {}.value_name(), "scan-intervals"); assert_eq!( crate::daemon::setup_reporter::RealUser::default().value_name(), "real-user" diff --git a/node/src/database/config_dumper.rs b/node/src/database/config_dumper.rs index f54b7d2dc..abcc02761 100644 --- a/node/src/database/config_dumper.rs +++ b/node/src/database/config_dumper.rs @@ -23,6 +23,7 @@ use clap::value_t; use heck::MixedCase; use masq_lib::blockchains::chains::Chain; use masq_lib::command::StdStreams; +use masq_lib::multi_config::make_arg_matches_accesible; use masq_lib::multi_config::{CommandLineVcl, EnvironmentVcl, VirtualCommandLine}; use masq_lib::shared_schema::ConfiguratorError; use rustc_hex::ToHex; @@ -40,7 +41,7 @@ impl DumpConfigRunner for DumpConfigRunnerReal { distill_args(&DirsWrapperReal {}, args)?; let cryptde = CryptDEReal::new(chain); PrivilegeDropperReal::new().drop_privileges(&real_user); - let config_dao = make_config_dao(&data_directory, MigratorConfig::migration_suppressed()); //dump config never migrates db + let config_dao = make_config_dao(&data_directory, MigratorConfig::migration_suppressed()); //dump config is not supposed to migrate db let configuration = config_dao.get_all().expect("Couldn't fetch configuration"); let json = configuration_to_json(configuration, password_opt, &cryptde); write_string(streams, json); @@ -159,8 +160,9 @@ mod tests { PersistentConfiguration, PersistentConfigurationReal, }; use crate::db_config::typed_config_layer::encode_bytes; + use crate::sub_lib::accountant::{DEFAULT_PAYMENT_THRESHOLDS, DEFAULT_SCAN_INTERVALS}; use crate::sub_lib::cryptde::PlainData; - use crate::sub_lib::neighborhood::NodeDescriptor; + use crate::sub_lib::neighborhood::{NodeDescriptor, DEFAULT_RATE_PACK}; use crate::test_utils::database_utils::bring_db_0_back_to_life_and_return_connection; use crate::test_utils::{main_cryptde, ArgsBuilder}; use masq_lib::test_utils::environment_guard::ClapGuard; @@ -332,7 +334,13 @@ mod tests { &dao.get("example_encrypted").unwrap().value_opt.unwrap(), &map, ); - + assert_value( + "paymentThresholds", + &DEFAULT_PAYMENT_THRESHOLDS.to_string(), + &map, + ); + assert_value("ratePack", &DEFAULT_RATE_PACK.to_string(), &map); + assert_value("scanIntervals", &DEFAULT_SCAN_INTERVALS.to_string(), &map); assert!(output.ends_with("\n}\n")) //asserting that there is a blank line at the end } @@ -436,6 +444,13 @@ mod tests { let expected_ee_decrypted = Bip39::decrypt_bytes(&expected_ee_entry, "password").unwrap(); let expected_ee_string = encode_bytes(Some(expected_ee_decrypted)).unwrap().unwrap(); assert_value("exampleEncrypted", &expected_ee_string, &map); + assert_value( + "paymentThresholds", + &DEFAULT_PAYMENT_THRESHOLDS.to_string(), + &map, + ); + assert_value("ratePack", &DEFAULT_RATE_PACK.to_string(), &map); + assert_value("scanIntervals", &DEFAULT_SCAN_INTERVALS.to_string(), &map); } #[test] @@ -548,6 +563,13 @@ mod tests { &dao.get("example_encrypted").unwrap().value_opt.unwrap(), &map, ); + assert_value( + "paymentThresholds", + &DEFAULT_PAYMENT_THRESHOLDS.to_string(), + &map, + ); + assert_value("ratePack", &DEFAULT_RATE_PACK.to_string(), &map); + assert_value("scanIntervals", &DEFAULT_SCAN_INTERVALS.to_string(), &map); } #[test] diff --git a/node/src/database/db_initializer.rs b/node/src/database/db_initializer.rs index 942299443..230dd12f1 100644 --- a/node/src/database/db_initializer.rs +++ b/node/src/database/db_initializer.rs @@ -4,6 +4,8 @@ use crate::database::db_migrations::{ DbMigrator, DbMigratorReal, ExternalData, MigratorConfig, Suppression, }; use crate::db_config::secure_config_layer::EXAMPLE_ENCRYPTED; +use crate::sub_lib::accountant::{DEFAULT_PAYMENT_THRESHOLDS, DEFAULT_SCAN_INTERVALS}; +use crate::sub_lib::neighborhood::DEFAULT_RATE_PACK; use masq_lib::constants::{ DEFAULT_GAS_PRICE, HIGHEST_RANDOM_CLANDESTINE_PORT, LOWEST_USABLE_INSECURE_PORT, }; @@ -20,7 +22,7 @@ use std::path::Path; use tokio::net::TcpListener; pub const DATABASE_FILE: &str = "node-data.db"; -pub const CURRENT_SCHEMA_VERSION: usize = 5; +pub const CURRENT_SCHEMA_VERSION: usize = 6; #[derive(Debug, PartialEq)] pub enum InitializationError { @@ -28,7 +30,7 @@ pub enum InitializationError { UndetectableVersion(String), SqliteError(rusqlite::Error), MigrationError(String), - SuppressedMigrationError, + SuppressedMigration, } pub trait DbInitializer { @@ -103,7 +105,7 @@ impl DbInitializer for DbInitializerReal { } (Some(_), &Suppression::Yes) => Ok(Box::new(ConnectionWrapperReal::new(conn))), (Some(_), &Suppression::WithErr) => { - Err(InitializationError::SuppressedMigrationError) + Err(InitializationError::SuppressedMigration) } } } @@ -254,6 +256,27 @@ impl DbInitializerReal { false, "last successful protocol for port mapping on the router", ); + Self::set_config_value( + conn, + "payment_thresholds", + Some(&DEFAULT_PAYMENT_THRESHOLDS.to_string()), + false, + "payment thresholds", + ); + Self::set_config_value( + conn, + "rate_pack", + Some(&DEFAULT_RATE_PACK.to_string()), + false, + "rate pack", + ); + Self::set_config_value( + conn, + "scan_intervals", + Some(&DEFAULT_SCAN_INTERVALS.to_string()), + false, + "scan intervals", + ); } fn create_pending_payable_table(&self, conn: &Connection) { @@ -612,7 +635,7 @@ mod tests { #[test] fn constants_have_correct_values() { assert_eq!(DATABASE_FILE, "node-data.db"); - assert_eq!(CURRENT_SCHEMA_VERSION, 5); + assert_eq!(CURRENT_SCHEMA_VERSION, 6); } #[test] @@ -922,7 +945,25 @@ mod tests { false, ); verify(&mut config_vec, "past_neighbors", None, true); - verify(&mut config_vec, "preexisting", Some("yes"), false); // makes sure we just created this database + verify( + &mut config_vec, + "payment_thresholds", + Some(&DEFAULT_PAYMENT_THRESHOLDS.to_string()), + false, + ); + verify(&mut config_vec, "preexisting", Some("yes"), false); // making sure we opened the preexisting database + verify( + &mut config_vec, + "rate_pack", + Some(&DEFAULT_RATE_PACK.to_string()), + false, + ); + verify( + &mut config_vec, + "scan_intervals", + Some(&DEFAULT_SCAN_INTERVALS.to_string()), + false, + ); verify( &mut config_vec, "schema_version", @@ -1184,7 +1225,7 @@ mod tests { Ok(_) => panic!("expected an Err, got Ok"), Err(e) => e, }; - assert_eq!(err, InitializationError::SuppressedMigrationError); + assert_eq!(err, InitializationError::SuppressedMigration); let schema_version_after = dao.get("schema_version").unwrap().value_opt.unwrap(); assert_eq!(schema_version_after, schema_version_before) } diff --git a/node/src/database/db_migrations.rs b/node/src/database/db_migrations.rs index 771a7b0ab..c4386c1d1 100644 --- a/node/src/database/db_migrations.rs +++ b/node/src/database/db_migrations.rs @@ -5,7 +5,9 @@ use crate::database::connection_wrapper::ConnectionWrapper; use crate::database::db_initializer::CURRENT_SCHEMA_VERSION; use crate::db_config::db_encryption_layer::DbEncryptionLayer; use crate::db_config::typed_config_layer::decode_bytes; +use crate::sub_lib::accountant::{DEFAULT_PAYMENT_THRESHOLDS, DEFAULT_SCAN_INTERVALS}; use crate::sub_lib::cryptde::PlainData; +use crate::sub_lib::neighborhood::DEFAULT_RATE_PACK; use itertools::Itertools; use masq_lib::blockchains::chains::Chain; use masq_lib::logger::Logger; @@ -345,7 +347,7 @@ impl DatabaseMigration for Migrate_3_to_4 { }; utils.execute_upon_transaction(&[ format! ("insert into config (name, value, encrypted) values ('consuming_wallet_private_key', {}, 1)", - private_key_column).as_str(), + private_key_column).as_str(), "delete from config where name in ('seed', 'consuming_wallet_derivation_path', 'consuming_wallet_public_key')", ]) } @@ -428,6 +430,46 @@ impl DatabaseMigration for Migrate_4_to_5 { } } +#[derive(Debug)] +#[allow(non_camel_case_types)] +struct Migrate_5_to_6; + +impl DatabaseMigration for Migrate_5_to_6 { + fn migrate<'a>( + &self, + declaration_utils: Box, + ) -> rusqlite::Result<()> { + let statement_1 = Self::make_initialization_statement( + "payment_thresholds", + &DEFAULT_PAYMENT_THRESHOLDS.to_string(), + ); + let statement_2 = + Self::make_initialization_statement("rate_pack", &DEFAULT_RATE_PACK.to_string()); + let statement_3 = Self::make_initialization_statement( + "scan_intervals", + &DEFAULT_SCAN_INTERVALS.to_string(), + ); + declaration_utils.execute_upon_transaction(&[ + statement_1.as_str(), + statement_2.as_str(), + statement_3.as_str(), + ]) + } + + fn old_version(&self) -> usize { + 5 + } +} + +impl Migrate_5_to_6 { + fn make_initialization_statement(name: &str, value: &str) -> String { + format!( + "INSERT INTO config (name, value, encrypted) VALUES ('{}', '{}', 0)", + name, value + ) + } +} + //////////////////////////////////////////////////////////////////////////////////////////////////// impl DbMigratorReal { @@ -445,6 +487,7 @@ impl DbMigratorReal { &Migrate_2_to_3, &Migrate_3_to_4, &Migrate_4_to_5, + &Migrate_5_to_6, ] } @@ -673,7 +716,9 @@ mod tests { use crate::database::db_migrations::{DBMigratorInnerConfiguration, DbMigratorReal}; use crate::db_config::db_encryption_layer::DbEncryptionLayer; use crate::db_config::typed_config_layer::encode_bytes; + use crate::sub_lib::accountant::{DEFAULT_PAYMENT_THRESHOLDS, DEFAULT_SCAN_INTERVALS}; use crate::sub_lib::cryptde::PlainData; + use crate::sub_lib::neighborhood::DEFAULT_RATE_PACK; use crate::sub_lib::wallet::Wallet; use crate::test_utils::database_utils::retrieve_config_row; use crate::test_utils::database_utils::{ @@ -1484,7 +1529,10 @@ mod tests { #[test] fn migration_from_0_to_1_is_properly_set() { - let dir_path = ensure_node_home_directory_exists("db_migrations", "0_to_1"); + let dir_path = ensure_node_home_directory_exists( + "db_migrations", + "migration_from_0_to_1_is_properly_set", + ); create_dir_all(&dir_path).unwrap(); let db_path = dir_path.join(DATABASE_FILE); let _ = bring_db_0_back_to_life_and_return_connection(&db_path); @@ -1509,7 +1557,10 @@ mod tests { fn migration_from_1_to_2_is_properly_set() { init_test_logging(); let start_at = 1; - let dir_path = ensure_node_home_directory_exists("db_migrations", "1_to_2"); + let dir_path = ensure_node_home_directory_exists( + "db_migrations", + "migration_from_1_to_2_is_properly_set", + ); let db_path = dir_path.join(DATABASE_FILE); let _ = bring_db_0_back_to_life_and_return_connection(&db_path); let subject = DbInitializerReal::default(); @@ -1546,7 +1597,10 @@ mod tests { #[test] fn migration_from_2_to_3_is_properly_set() { let start_at = 2; - let dir_path = ensure_node_home_directory_exists("db_migrations", "2_to_3"); + let dir_path = ensure_node_home_directory_exists( + "db_migrations", + "migration_from_2_to_3_is_properly_set", + ); let db_path = dir_path.join(DATABASE_FILE); let _ = bring_db_0_back_to_life_and_return_connection(&db_path); let subject = DbInitializerReal::default(); @@ -1931,4 +1985,50 @@ mod tests { vec_of_values.sort_by_key(|(name, _)| name.clone()); vec_of_values } + + #[test] + fn migration_from_5_to_6_works() { + let dir_path = + ensure_node_home_directory_exists("db_migrations", "migration_from_5_to_6_works"); + let db_path = dir_path.join(DATABASE_FILE); + let _ = bring_db_0_back_to_life_and_return_connection(&db_path); + let subject = DbInitializerReal::default(); + { + subject + .initialize_to_version( + &dir_path, + 6, + true, + MigratorConfig::create_or_migrate(make_external_migration_parameters()), + ) + .unwrap(); + } + + let result = subject.initialize_to_version( + &dir_path, + 6, + true, + MigratorConfig::create_or_migrate(ExternalData::new( + DEFAULT_CHAIN, + NeighborhoodModeLight::ConsumeOnly, + None, + )), + ); + + let connection = result.unwrap(); + let (payment_thresholds, encrypted) = + retrieve_config_row(connection.as_ref(), "payment_thresholds"); + assert_eq!( + payment_thresholds, + Some(DEFAULT_PAYMENT_THRESHOLDS.to_string()) + ); + assert_eq!(encrypted, false); + let (rate_pack, encrypted) = retrieve_config_row(connection.as_ref(), "rate_pack"); + assert_eq!(rate_pack, Some(DEFAULT_RATE_PACK.to_string())); + assert_eq!(encrypted, false); + let (scan_intervals, encrypted) = + retrieve_config_row(connection.as_ref(), "scan_intervals"); + assert_eq!(scan_intervals, Some(DEFAULT_SCAN_INTERVALS.to_string())); + assert_eq!(encrypted, false); + } } diff --git a/node/src/db_config/config_dao_null.rs b/node/src/db_config/config_dao_null.rs index 1084906cc..978716c36 100644 --- a/node/src/db_config/config_dao_null.rs +++ b/node/src/db_config/config_dao_null.rs @@ -4,12 +4,45 @@ use crate::database::db_initializer::{DbInitializerReal, CURRENT_SCHEMA_VERSION} use crate::db_config::config_dao::{ ConfigDao, ConfigDaoError, ConfigDaoRead, ConfigDaoReadWrite, ConfigDaoRecord, ConfigDaoWrite, }; +use crate::sub_lib::accountant::{DEFAULT_PAYMENT_THRESHOLDS, DEFAULT_SCAN_INTERVALS}; +use crate::sub_lib::neighborhood::DEFAULT_RATE_PACK; use itertools::Itertools; use masq_lib::blockchains::chains::Chain; -use masq_lib::constants::ETH_MAINNET_CONTRACT_CREATION_BLOCK; +use masq_lib::constants::DEFAULT_GAS_PRICE; use rusqlite::Transaction; use std::collections::HashMap; +/* + +This class exists because the Daemon uses the same configuration code that the Node uses, and +that configuration code requires access to the database...except that the Daemon isn't allowed +access to the database, so it's given this configuration DAO instead. This DAO provides plain-vanilla +default values when read, and claims to have successfully written values (which are actually +thrown away) when updated. + +Theoretically, the Daemon could be given access to the real database, but there are a few problems +that would need to be overcome first. + +1. The database must be created by a normal user, not by root--or at least once it's finished it +must _look_ as though it were created by a normal user. The Daemon must always run as root, and +may not give up its privilege. This is not an insurmountable problem, but it is a problem. + +2. The database can't be located until the chain is known, because the chain is part of the +directory to the database. Every setup command has the potential to need access to the database, +but there's no easy way to ensure that the first setup command establishes the chain. + +3. If the database needs to be migrated from its schema version to the Daemon's schema version, +and the migration involves secret fields, then the migration will need the database password. +Again, the password will be needed the moment the database is first connected, which will probably +be when the first setup command is given, and there's no easy way to ensure that the first setup +command establishes the password. + +4. If two different processes have simultaneous write access to the same database, one process may +make changes that the other process doesn't know about. This is another problem that is not +insurmountable, but it would need to be considered and coded around. + + */ + pub struct ConfigDaoNull { data: HashMap, bool)>, } @@ -78,10 +111,16 @@ impl Default for ConfigDaoNull { false, ), ); - data.insert("gas_price".to_string(), (Some("1".to_string()), false)); + data.insert( + "gas_price".to_string(), + (Some(DEFAULT_GAS_PRICE.to_string()), false), + ); data.insert( "start_block".to_string(), - (Some(ETH_MAINNET_CONTRACT_CREATION_BLOCK.to_string()), false), + ( + Some(Chain::default().rec().contract_creation_block.to_string()), + false, + ), ); data.insert("consuming_wallet_private_key".to_string(), (None, true)); data.insert("example_encrypted".to_string(), (None, true)); @@ -97,6 +136,18 @@ impl Default for ConfigDaoNull { "schema_version".to_string(), (Some(format!("{}", CURRENT_SCHEMA_VERSION)), false), ); + data.insert( + "payment_thresholds".to_string(), + (Some(DEFAULT_PAYMENT_THRESHOLDS.to_string()), false), + ); + data.insert( + "rate_pack".to_string(), + (Some(DEFAULT_RATE_PACK.to_string()), false), + ); + data.insert( + "scan_intervals".to_string(), + (Some(DEFAULT_SCAN_INTERVALS.to_string()), false), + ); Self { data } } } @@ -108,6 +159,7 @@ mod tests { use crate::database::db_migrations::MigratorConfig; use crate::db_config::config_dao::ConfigDaoReal; use masq_lib::blockchains::chains::Chain; + use masq_lib::constants::{DEFAULT_CHAIN, ETH_MAINNET_CONTRACT_CREATION_BLOCK}; use masq_lib::test_utils::utils::ensure_node_home_directory_exists; use std::collections::HashSet; @@ -139,7 +191,7 @@ mod tests { subject.get("start_block").unwrap(), ConfigDaoRecord::new( "start_block", - Some(Ð_MAINNET_CONTRACT_CREATION_BLOCK.to_string()), + Some(&DEFAULT_CHAIN.rec().contract_creation_block.to_string()), false ) ); @@ -148,6 +200,26 @@ mod tests { subject.get("consuming_wallet_private_key").unwrap(), ConfigDaoRecord::new("consuming_wallet_private_key", None, true) ); + assert_eq!( + subject.get("payment_thresholds").unwrap(), + ConfigDaoRecord::new( + "payment_thresholds", + Some(&DEFAULT_PAYMENT_THRESHOLDS.to_string()), + false + ) + ); + assert_eq!( + subject.get("rate_pack").unwrap(), + ConfigDaoRecord::new("rate_pack", Some(&DEFAULT_RATE_PACK.to_string()), false) + ); + assert_eq!( + subject.get("scan_intervals").unwrap(), + ConfigDaoRecord::new( + "scan_intervals", + Some(&DEFAULT_SCAN_INTERVALS.to_string()), + false + ) + ); } #[test] @@ -162,21 +234,19 @@ mod tests { .unwrap(); let real_config_dao = ConfigDaoReal::new(conn); let subject = ConfigDaoNull::default(); - let real_pairs = real_config_dao - .get_all() - .unwrap() - .into_iter() - .map(|r| (r.name, r.encrypted)) - .collect::>(); + let real_pairs = return_parameter_pairs(&real_config_dao); - let null_pairs = subject - .get_all() + let null_pairs = return_parameter_pairs(&subject); + + assert_eq!(null_pairs, real_pairs); + } + + fn return_parameter_pairs(dao: &dyn ConfigDao) -> HashSet<(String, bool)> { + dao.get_all() .unwrap() .into_iter() .map(|r| (r.name, r.encrypted)) - .collect::>(); - - assert_eq!(null_pairs, real_pairs); + .collect() } #[test] diff --git a/node/src/db_config/persistent_configuration.rs b/node/src/db_config/persistent_configuration.rs index a9ebd021a..b37f0ff53 100644 --- a/node/src/db_config/persistent_configuration.rs +++ b/node/src/db_config/persistent_configuration.rs @@ -1,20 +1,24 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + use crate::blockchain::bip32::Bip32ECKeyProvider; use crate::blockchain::bip39::{Bip39, Bip39Error}; use crate::database::connection_wrapper::ConnectionWrapper; use crate::db_config::config_dao::{ConfigDao, ConfigDaoError, ConfigDaoReal, ConfigDaoRecord}; use crate::db_config::secure_config_layer::{SecureConfigLayer, SecureConfigLayerError}; use crate::db_config::typed_config_layer::{ - decode_bytes, decode_u64, encode_bytes, encode_u64, TypedConfigLayerError, + decode_bytes, decode_combined_params, decode_u64, encode_bytes, encode_u64, + TypedConfigLayerError, }; +use crate::sub_lib::accountant::{PaymentThresholds, ScanIntervals}; use crate::sub_lib::cryptde::PlainData; -use crate::sub_lib::neighborhood::NodeDescriptor; +use crate::sub_lib::neighborhood::{NodeDescriptor, RatePack}; use crate::sub_lib::wallet::Wallet; use masq_lib::constants::{HIGHEST_USABLE_PORT, LOWEST_USABLE_INSECURE_PORT}; use masq_lib::shared_schema::{ConfiguratorError, ParamError}; use masq_lib::utils::AutomapProtocol; use masq_lib::utils::NeighborhoodModeLight; use rustc_hex::{FromHex, ToHex}; +use std::fmt::Display; use std::net::{Ipv4Addr, SocketAddrV4, TcpListener}; use std::str::FromStr; use websocket::url::Url; @@ -28,6 +32,7 @@ pub enum PersistentConfigError { BadPortNumber(String), BadNumberFormat(String), BadHexFormat(String), + BadCoupledParamsFormat(String), BadMnemonicSeed(PlainData), BadDerivationPathFormat(String), BadAddressFormat(String), @@ -43,6 +48,9 @@ impl From for PersistentConfigError { TypedConfigLayerError::BadNumberFormat(msg) => { PersistentConfigError::BadNumberFormat(msg) } + TypedConfigLayerError::BadCombinedParamsFormat(msg) => { + PersistentConfigError::BadCoupledParamsFormat(msg) + } } } } @@ -86,15 +94,6 @@ pub trait PersistentConfiguration { old_password_opt: Option, new_password: &str, ) -> Result<(), PersistentConfigError>; - fn clandestine_port(&self) -> Result; - fn set_clandestine_port(&mut self, port: u16) -> Result<(), PersistentConfigError>; - fn gas_price(&self) -> Result; - fn set_gas_price(&mut self, gas_price: u64) -> Result<(), PersistentConfigError>; - fn mapping_protocol(&self) -> Result, PersistentConfigError>; - fn set_mapping_protocol( - &mut self, - value: Option, - ) -> Result<(), PersistentConfigError>; // WARNING: Actors should get consuming-wallet information from their startup config, not from here fn consuming_wallet(&self, db_password: &str) -> Result, PersistentConfigError>; // WARNING: Actors should get consuming-wallet information from their startup config, not from here @@ -102,15 +101,23 @@ pub trait PersistentConfiguration { &self, db_password: &str, ) -> Result, PersistentConfigError>; + fn clandestine_port(&self) -> Result; + fn set_clandestine_port(&mut self, port: u16) -> Result<(), PersistentConfigError>; // WARNING: Actors should get earning-wallet information from their startup config, not from here fn earning_wallet(&self) -> Result, PersistentConfigError>; // WARNING: Actors should get earning-wallet information from their startup config, not from here fn earning_wallet_address(&self) -> Result, PersistentConfigError>; - fn set_wallet_info( + fn gas_price(&self) -> Result; + fn set_gas_price(&mut self, gas_price: u64) -> Result<(), PersistentConfigError>; + fn mapping_protocol(&self) -> Result, PersistentConfigError>; + fn set_mapping_protocol( &mut self, - consuming_wallet_private_key: &str, - earning_wallet_address: &str, - db_password: &str, + value: Option, + ) -> Result<(), PersistentConfigError>; + fn neighborhood_mode(&self) -> Result; + fn set_neighborhood_mode( + &mut self, + value: NeighborhoodModeLight, ) -> Result<(), PersistentConfigError>; fn past_neighbors( &self, @@ -123,11 +130,18 @@ pub trait PersistentConfiguration { ) -> Result<(), PersistentConfigError>; fn start_block(&self) -> Result; fn set_start_block(&mut self, value: u64) -> Result<(), PersistentConfigError>; - fn neighborhood_mode(&self) -> Result; - fn set_neighborhood_mode( + fn set_wallet_info( &mut self, - value: NeighborhoodModeLight, + consuming_wallet_private_key: &str, + earning_wallet_address: &str, + db_password: &str, ) -> Result<(), PersistentConfigError>; + fn payment_thresholds(&self) -> Result; + fn set_payment_thresholds(&mut self, curves: String) -> Result<(), PersistentConfigError>; + fn rate_pack(&self) -> Result; + fn set_rate_pack(&mut self, rate_pack: String) -> Result<(), PersistentConfigError>; + fn scan_intervals(&self) -> Result; + fn set_scan_intervals(&mut self, intervals: String) -> Result<(), PersistentConfigError>; } pub struct PersistentConfigurationReal { @@ -191,9 +205,47 @@ impl PersistentConfiguration for PersistentConfigurationReal { Ok(writer.commit()?) } + fn consuming_wallet(&self, db_password: &str) -> Result, PersistentConfigError> { + self.consuming_wallet_private_key(db_password) + .map(|key_opt| { + key_opt.map(|key| match key.from_hex::>() { + Err(e) => panic!( + "Database corruption {:?}: consuming private key is not hex, but '{}'", + e, key + ), + Ok(bytes) => match Bip32ECKeyProvider::from_raw_secret(bytes.as_slice()) { + Err(e) => panic!( + "Database corruption {:?}: consuming private key is invalid", + e + ), + Ok(pair) => Wallet::from(pair), + }, + }) + }) + } + + fn consuming_wallet_private_key( + &self, + db_password: &str, + ) -> Result, PersistentConfigError> { + let encrypted_value_opt = self.get_record("consuming_wallet_private_key")?.value_opt; + if let Some(encrypted_value) = encrypted_value_opt { + match Bip39::decrypt_bytes(&encrypted_value, db_password) { + Ok(decrypted_bytes) => Ok(Some(decrypted_bytes.as_slice().to_hex())), + Err(Bip39Error::DecryptionFailure(_)) => Err(PersistentConfigError::PasswordError), + Err(e) => panic!( + "Database corruption {:?}: consuming private key can't be decrypted", + e + ), + } + } else { + Ok(None) + } + } + fn clandestine_port(&self) -> Result { let unchecked_port = match decode_u64(self.get("clandestine_port")?)? { - None => panic!("ever-supplied clandestine_port value missing; database is corrupt!"), + None => Self::missing_value_panic("clandestine_port"), Some(port) => port, }; if (unchecked_port < u64::from(LOWEST_USABLE_INSECURE_PORT)) @@ -227,6 +279,23 @@ impl PersistentConfiguration for PersistentConfigurationReal { Ok(writer.commit()?) } + fn earning_wallet(&self) -> Result, PersistentConfigError> { + match self.earning_wallet_address()? { + None => Ok(None), + Some(address) => match Wallet::from_str(&address) { + Ok(w) => Ok(Some(w)), + Err(error) => panic!( + "Database corrupt: invalid earning wallet address '{}': {:?}", + address, error + ), + }, + } + } + + fn earning_wallet_address(&self) -> Result, PersistentConfigError> { + Ok(self.get("earning_wallet_address")?) + } + fn gas_price(&self) -> Result { match decode_u64(self.get("gas_price")?) { Ok(val) => { @@ -237,64 +306,90 @@ impl PersistentConfiguration for PersistentConfigurationReal { } fn set_gas_price(&mut self, gas_price: u64) -> Result<(), PersistentConfigError> { + self.simple_set_method("gas_price", gas_price) + } + + fn mapping_protocol(&self) -> Result, PersistentConfigError> { + let result = self + .get("mapping_protocol")? + .map(|val| AutomapProtocol::from_str(&val)); + match result { + None => Ok(None), + Some(Ok(protocol)) => Ok(Some(protocol)), + Some(Err(msg)) => Err(PersistentConfigError::DatabaseError(msg)), + } + } + + fn set_mapping_protocol( + &mut self, + value: Option, + ) -> Result<(), PersistentConfigError> { let mut writer = self.dao.start_transaction()?; - writer.set("gas_price", encode_u64(Some(gas_price))?)?; + writer.set("mapping_protocol", value.map(|v| v.to_string()))?; Ok(writer.commit()?) } - fn consuming_wallet(&self, db_password: &str) -> Result, PersistentConfigError> { - self.consuming_wallet_private_key(db_password) - .map(|key_opt| { - key_opt.map(|key| match key.from_hex::>() { - Err(e) => panic!( - "Database corruption {:?}: consuming private key is not hex, but '{}'", - e, key - ), - Ok(bytes) => match Bip32ECKeyProvider::from_raw_secret(bytes.as_slice()) { - Err(e) => panic!( - "Database corruption {:?}: consuming private key is invalid", - e - ), - Ok(pair) => Wallet::from(pair), - }, - }) - }) + fn neighborhood_mode(&self) -> Result { + NeighborhoodModeLight::from_str( + self.get("neighborhood_mode")? + .expect("ever-supplied value is missing: neighborhood-mode; database is corrupt!") + .as_str(), + ) + .map_err(PersistentConfigError::UninterpretableValue) } - fn consuming_wallet_private_key( + fn set_neighborhood_mode( + &mut self, + value: NeighborhoodModeLight, + ) -> Result<(), PersistentConfigError> { + self.simple_set_method("neighborhood_mode", value) + } + + fn past_neighbors( &self, db_password: &str, - ) -> Result, PersistentConfigError> { - let encrypted_value_opt = self.get_record("consuming_wallet_private_key")?.value_opt; - if let Some(encrypted_value) = encrypted_value_opt { - match Bip39::decrypt_bytes(&encrypted_value, db_password) { - Ok(decrypted_bytes) => Ok(Some(decrypted_bytes.as_slice().to_hex())), - Err(Bip39Error::DecryptionFailure(_)) => Err(PersistentConfigError::PasswordError), - Err(e) => panic!( - "Database corruption {:?}: consuming private key can't be decrypted", - e - ), - } - } else { - Ok(None) + ) -> Result>, PersistentConfigError> { + let bytes_opt = decode_bytes(self.scl.decrypt( + self.get_record("past_neighbors")?, + Some(db_password.to_string()), + &self.dao, + )?)?; + match bytes_opt { + None => Ok (None), + Some (bytes) => Ok(Some(serde_cbor::de::from_slice::>(bytes.as_slice()) + .expect ("Can't continue; past neighbors configuration is corrupt and cannot be deserialized."))), } } - fn earning_wallet(&self) -> Result, PersistentConfigError> { - match self.earning_wallet_address()? { - None => Ok(None), - Some(address) => match Wallet::from_str(&address) { - Ok(w) => Ok(Some(w)), - Err(error) => panic!( - "Database corrupt: invalid earning wallet address '{}': {:?}", - address, error - ), - }, - } + fn set_past_neighbors( + &mut self, + node_descriptors_opt: Option>, + db_password: &str, + ) -> Result<(), PersistentConfigError> { + let plain_data_opt = node_descriptors_opt.map(|node_descriptors| { + PlainData::new( + &serde_cbor::ser::to_vec(&node_descriptors).expect("Serialization failed"), + ) + }); + let mut writer = self.dao.start_transaction()?; + writer.set( + "past_neighbors", + self.scl.encrypt( + "past_neighbors", + encode_bytes(plain_data_opt)?, + Some(db_password.to_string()), + &writer, + )?, + )?; + Ok(writer.commit()?) } - fn earning_wallet_address(&self) -> Result, PersistentConfigError> { - Ok(self.get("earning_wallet_address")?) + fn start_block(&self) -> Result { + self.simple_get_method(decode_u64, "start_block") + } + + fn set_start_block(&mut self, value: u64) -> Result<(), PersistentConfigError> { + self.simple_set_method("start_block", value) } fn set_wallet_info( @@ -347,96 +442,31 @@ impl PersistentConfiguration for PersistentConfigurationReal { Ok(writer.commit()?) } - fn past_neighbors( - &self, - db_password: &str, - ) -> Result>, PersistentConfigError> { - let bytes_opt = decode_bytes(self.scl.decrypt( - self.get_record("past_neighbors")?, - Some(db_password.to_string()), - &self.dao, - )?)?; - match bytes_opt { - None => Ok (None), - Some (bytes) => Ok(Some(serde_cbor::de::from_slice::>(bytes.as_slice()) - .expect ("Can't continue; past neighbors configuration is corrupt and cannot be deserialized."))), - } - } - - fn set_past_neighbors( - &mut self, - node_descriptors_opt: Option>, - db_password: &str, - ) -> Result<(), PersistentConfigError> { - let plain_data_opt = node_descriptors_opt.map(|node_descriptors| { - PlainData::new( - &serde_cbor::ser::to_vec(&node_descriptors).expect("Serialization failed"), - ) - }); - let mut writer = self.dao.start_transaction()?; - writer.set( - "past_neighbors", - self.scl.encrypt( - "past_neighbors", - encode_bytes(plain_data_opt)?, - Some(db_password.to_string()), - &writer, - )?, - )?; - Ok(writer.commit()?) - } - - fn start_block(&self) -> Result { - match decode_u64(self.get("start_block")?) { - Ok(val) => { - Ok(val.expect("ever-supplied start_block value missing; database is corrupt!")) - } - Err(e) => Err(PersistentConfigError::from(e)), - } + fn payment_thresholds(&self) -> Result { + self.combined_params_get_method( + |str: &str| PaymentThresholds::try_from(str), + "payment_thresholds", + ) } - fn set_start_block(&mut self, value: u64) -> Result<(), PersistentConfigError> { - let mut writer = self.dao.start_transaction()?; - writer.set("start_block", encode_u64(Some(value))?)?; - Ok(writer.commit()?) + fn set_payment_thresholds(&mut self, curves: String) -> Result<(), PersistentConfigError> { + self.simple_set_method("payment_thresholds", curves) } - fn mapping_protocol(&self) -> Result, PersistentConfigError> { - let result = self - .get("mapping_protocol")? - .map(|val| AutomapProtocol::from_str(&val)); - match result { - None => Ok(None), - Some(Ok(protocol)) => Ok(Some(protocol)), - Some(Err(msg)) => Err(PersistentConfigError::DatabaseError(msg)), - } + fn rate_pack(&self) -> Result { + self.combined_params_get_method(|str: &str| RatePack::try_from(str), "rate_pack") } - fn set_mapping_protocol( - &mut self, - value: Option, - ) -> Result<(), PersistentConfigError> { - let mut writer = self.dao.start_transaction()?; - writer.set("mapping_protocol", value.map(|v| v.to_string()))?; - Ok(writer.commit()?) + fn set_rate_pack(&mut self, rate_pack: String) -> Result<(), PersistentConfigError> { + self.simple_set_method("rate_pack", rate_pack) } - fn neighborhood_mode(&self) -> Result { - NeighborhoodModeLight::from_str( - self.get("neighborhood_mode")? - .expect("ever-supplied value neighborhood_mode is missing; database is corrupt!") - .as_str(), - ) - .map_err(PersistentConfigError::UninterpretableValue) + fn scan_intervals(&self) -> Result { + self.combined_params_get_method(|str: &str| ScanIntervals::try_from(str), "scan_intervals") } - fn set_neighborhood_mode( - &mut self, - value: NeighborhoodModeLight, - ) -> Result<(), PersistentConfigError> { - let mut writer = self.dao.start_transaction()?; - writer.set("neighborhood_mode", Some(value.to_string()))?; - Ok(writer.commit()?) + fn set_scan_intervals(&mut self, intervals: String) -> Result<(), PersistentConfigError> { + self.simple_set_method("scan_intervals", intervals) } } @@ -490,22 +520,75 @@ impl PersistentConfigurationReal { record.name, name) }) } + + fn simple_set_method( + &mut self, + parameter_name: &str, + value: T, + ) -> Result<(), PersistentConfigError> { + let mut writer = self.dao.start_transaction()?; + writer.set(parameter_name, Some(value.to_string()))?; + Ok(writer.commit()?) + } + + fn simple_get_method( + &self, + decoder: fn(Option) -> Result, TypedConfigLayerError>, + parameter: &str, + ) -> Result { + match decoder(self.get(parameter)?)? { + None => Self::missing_value_panic(parameter), + Some(rate) => Ok(rate), + } + } + + fn combined_params_get_method<'a, T, C>( + &'a self, + values_parser: C, + parameter: &'a str, + ) -> Result + where + C: Fn(&str) -> Result, + { + match decode_combined_params(values_parser, self.get(parameter)?)? { + None => Self::missing_value_panic(parameter), + Some(rate) => Ok(rate), + } + } + + fn missing_value_panic(parameter_name: &str) -> ! { + panic!( + "ever-supplied value missing: {}; database is corrupt!", + parameter_name + ) + } } #[cfg(test)] mod tests { use super::*; use crate::blockchain::bip39::Bip39; + use crate::database::db_initializer::{DbInitializer, DbInitializerReal}; + use crate::database::db_migrations::MigratorConfig; use crate::db_config::config_dao::ConfigDaoRecord; use crate::db_config::mocks::{ConfigDaoMock, ConfigDaoWriteableMock}; use crate::db_config::secure_config_layer::EXAMPLE_ENCRYPTED; use crate::test_utils::main_cryptde; use bip39::{Language, MnemonicType}; + use lazy_static::lazy_static; + use masq_lib::test_utils::utils::ensure_node_home_directory_exists; use masq_lib::utils::{derivation_path, find_free_port}; + use paste::paste; + use std::convert::TryFrom; use std::net::SocketAddr; use std::sync::{Arc, Mutex}; + use std::time::Duration; use tiny_hderive::bip32::ExtendedPrivKey; + lazy_static! { + static ref CONFIG_TABLE_PARAMETERS: Vec = list_of_config_parameters(); + } + #[test] fn from_config_dao_error() { vec![ @@ -561,6 +644,10 @@ mod tests { TypedConfigLayerError::BadNumberFormat("booga".to_string()), PersistentConfigError::BadNumberFormat("booga".to_string()), ), + ( + TypedConfigLayerError::BadCombinedParamsFormat("booga".to_string()), + PersistentConfigError::BadCoupledParamsFormat("booga".to_string()), + ), ] .into_iter() .for_each(|(tcle, pce)| assert_eq!(PersistentConfigError::from(tcle), pce)) @@ -679,7 +766,9 @@ mod tests { } #[test] - #[should_panic(expected = "ever-supplied clandestine_port value missing; database is corrupt!")] + #[should_panic( + expected = "ever-supplied value missing: clandestine_port; database is corrupt!" + )] fn clandestine_port_panics_if_none_got_from_database() { let config_dao = ConfigDaoMock::new().get_result(Ok(ConfigDaoRecord::new( "clandestine_port", @@ -1402,7 +1491,7 @@ mod tests { } #[test] - #[should_panic(expected = "ever-supplied start_block value missing; database is corrupt!")] + #[should_panic(expected = "ever-supplied value missing: start_block; database is corrupt!")] fn start_block_does_not_tolerate_optional_output() { let config_dao = Box::new(ConfigDaoMock::new().get_result(Ok(ConfigDaoRecord::new( "start_block", @@ -1658,25 +1747,6 @@ mod tests { assert_eq!(*get_params, vec!["neighborhood_mode".to_string()]); } - #[test] - fn neighborhood_mode_detects_specific_error() { - let config_dao = ConfigDaoMock::new().get_result(Ok(ConfigDaoRecord::new( - "neighborhood_mode", - Some("blah"), - false, - ))); - let subject = PersistentConfigurationReal::new(Box::new(config_dao)); - - let result = subject.neighborhood_mode(); - - assert_eq!( - result, - Err(PersistentConfigError::UninterpretableValue( - "Invalid value read for neighborhood mode: blah".to_string() - )) - ); - } - #[test] fn set_neighborhood_mode_works() { let set_params_arc = Arc::new(Mutex::new(vec![])); @@ -1700,4 +1770,181 @@ mod tests { )] ); } + + macro_rules! persistent_config_plain_data_assertions_for_simple_get_method { + ($parameter_name: literal,$expected_in_database: literal, $expected_result: expr) => { + paste! { + let get_params_arc = Arc::new(Mutex::new(vec![])); + let config_dao = ConfigDaoMock::new() + .get_params(&get_params_arc) + .get_result(Ok(ConfigDaoRecord::new( + $parameter_name, + Some($expected_in_database), + false, + ))); + let subject = PersistentConfigurationReal::new(Box::new(config_dao)); + + let result = subject.[<$parameter_name>]().unwrap(); + + assert_eq!(result, $expected_result); + let get_params = get_params_arc.lock().unwrap(); + assert_eq!(*get_params, vec![$parameter_name.to_string()]); + } + assert_eq!( + CONFIG_TABLE_PARAMETERS + .iter() + .filter(|parameter_name| parameter_name.as_str() == $parameter_name) + .count(), + 1, + "this parameter '{}' is not in the config table", + $parameter_name + ) + }; + } + + macro_rules! persistent_config_plain_data_assertions_for_simple_set_method { + ($parameter_name: literal,$set_value: expr) => { + paste! { + let set_params_arc = Arc::new(Mutex::new(vec![])); + let config_dao = ConfigDaoWriteableMock::new() + .set_params(&set_params_arc) + .set_result(Ok(())) + .commit_result(Ok(())); + let mut subject = PersistentConfigurationReal::new(Box::new( + ConfigDaoMock::new().start_transaction_result(Ok(Box::new(config_dao))), + )); + + let result = subject.[]($set_value); + + assert!(result.is_ok()); + let set_params = set_params_arc.lock().unwrap(); + assert_eq!( + *set_params, + vec![( + $parameter_name.to_string(), + Some($set_value.to_string()) + )] + ); + } + }; + } + + macro_rules! getter_method_plain_data_does_not_tolerate_none_value { + ($parameter_name: literal) => { + paste! { + let config_dao = ConfigDaoMock::new() + .get_result(Ok(ConfigDaoRecord::new( + $parameter_name, + None, + false, + ))); + let subject = PersistentConfigurationReal::new(Box::new(config_dao)); + + let _ = subject.[<$parameter_name>](); + } + }; + } + + #[test] + fn rate_pack_get_method_works() { + persistent_config_plain_data_assertions_for_simple_get_method!( + "rate_pack", + "7|11|15|20", + RatePack { + routing_byte_rate: 7, + routing_service_rate: 11, + exit_byte_rate: 15, + exit_service_rate: 20, + } + ); + } + + #[test] + fn rate_pack_set_method_works() { + persistent_config_plain_data_assertions_for_simple_set_method!( + "rate_pack", + "7|11|15|20".to_string() + ); + } + + #[test] + #[should_panic(expected = "ever-supplied value missing: rate_pack; database is corrupt!")] + fn rate_pack_panics_at_none_value() { + getter_method_plain_data_does_not_tolerate_none_value!("rate_pack"); + } + + #[test] + fn scan_intervals_get_method_works() { + persistent_config_plain_data_assertions_for_simple_get_method!( + "scan_intervals", + "40|60|50", + ScanIntervals { + pending_payable_scan_interval: Duration::from_secs(40), + payable_scan_interval: Duration::from_secs(60), + receivable_scan_interval: Duration::from_secs(50), + } + ); + } + + #[test] + fn scan_interval_set_method_works() { + persistent_config_plain_data_assertions_for_simple_set_method!( + "scan_intervals", + "111|123|110".to_string() + ); + } + + #[test] + #[should_panic(expected = "ever-supplied value missing: scan_intervals; database is corrupt!")] + fn scan_intervals_panics_at_none_value() { + getter_method_plain_data_does_not_tolerate_none_value!("scan_intervals"); + } + + #[test] + fn payment_thresholds_get_method_works() { + persistent_config_plain_data_assertions_for_simple_get_method!( + "payment_thresholds", + "100000|1000|1000|20000|1000|20000", + PaymentThresholds { + threshold_interval_sec: 1000, + debt_threshold_gwei: 100000, + payment_grace_period_sec: 1000, + maturity_threshold_sec: 1000, + permanent_debt_allowed_gwei: 20000, + unban_below_gwei: 20000, + } + ); + } + + #[test] + fn payment_thresholds_set_method_works() { + persistent_config_plain_data_assertions_for_simple_set_method!( + "payment_thresholds", + "1050|100050|1050|1050|20040|20040".to_string() + ); + } + + #[test] + #[should_panic( + expected = "ever-supplied value missing: payment_thresholds; database is corrupt!" + )] + fn payment_thresholds_panics_at_none_value() { + getter_method_plain_data_does_not_tolerate_none_value!("payment_thresholds"); + } + + fn list_of_config_parameters() -> Vec { + let home_dir = ensure_node_home_directory_exists( + "persistent_configuration", + "current_config_table_schema", + ); + let db_conn = DbInitializerReal::default() + .initialize(&home_dir, true, MigratorConfig::test_default()) + .unwrap(); + let mut statement = db_conn.prepare("select name from config").unwrap(); + statement + .query_map([], |row| Ok(row.get(0).unwrap())) + .unwrap() + .flatten() + .collect() + } } diff --git a/node/src/db_config/typed_config_layer.rs b/node/src/db_config/typed_config_layer.rs index d5b150952..5071e3128 100644 --- a/node/src/db_config/typed_config_layer.rs +++ b/node/src/db_config/typed_config_layer.rs @@ -7,6 +7,7 @@ use rustc_hex::{FromHex, ToHex}; pub enum TypedConfigLayerError { BadNumberFormat(String), BadHexFormat(String), + BadCombinedParamsFormat(String), } pub fn decode_u64(string_opt: Option) -> Result, TypedConfigLayerError> { @@ -31,6 +32,21 @@ pub fn decode_bytes( } } +pub fn decode_combined_params( + values_parser: C, + string_opt: Option, +) -> Result, TypedConfigLayerError> +where + C: Fn(&str) -> Result, +{ + match string_opt { + None => Ok(None), + Some(string_params) => values_parser(string_params.as_str()) + .map(|val| Some(val)) + .map_err(TypedConfigLayerError::BadCombinedParamsFormat), + } +} + pub fn encode_u64(value_opt: Option) -> Result, TypedConfigLayerError> { match value_opt { None => Ok(None), diff --git a/node/src/dispatcher.rs b/node/src/dispatcher.rs index 184d8ad1c..4f14d20ab 100644 --- a/node/src/dispatcher.rs +++ b/node/src/dispatcher.rs @@ -218,9 +218,9 @@ mod tests { use crate::sub_lib::neighborhood::NodeDescriptor; use crate::sub_lib::node_addr::NodeAddr; use crate::test_utils::main_cryptde; - use crate::test_utils::pure_test_utils::prove_that_crash_request_handler_is_hooked_up; use crate::test_utils::recorder::Recorder; use crate::test_utils::recorder::{make_recorder, peer_actors_builder}; + use crate::test_utils::unshared_test_utils::prove_that_crash_request_handler_is_hooked_up; use actix::System; use lazy_static::lazy_static; use masq_lib::blockchains::chains::Chain; diff --git a/node/src/entry_dns/dns_socket_server.rs b/node/src/entry_dns/dns_socket_server.rs index 862b85e19..eabc6fae6 100644 --- a/node/src/entry_dns/dns_socket_server.rs +++ b/node/src/entry_dns/dns_socket_server.rs @@ -93,7 +93,7 @@ mod tests { use super::super::packet_facade::PacketFacade; use super::*; use crate::sub_lib::udp_socket_wrapper::UdpSocketWrapperTrait; - use crate::test_utils::pure_test_utils::make_simplified_multi_config; + use crate::test_utils::unshared_test_utils::make_simplified_multi_config; use masq_lib::test_utils::fake_stream_holder::FakeStreamHolder; use masq_lib::test_utils::logging::init_test_logging; use masq_lib::test_utils::logging::TestLogHandler; diff --git a/node/src/hopper/mod.rs b/node/src/hopper/mod.rs index be38e6ce8..3b7f2fc45 100644 --- a/node/src/hopper/mod.rs +++ b/node/src/hopper/mod.rs @@ -152,7 +152,7 @@ mod tests { use crate::sub_lib::hopper::IncipientCoresPackage; use crate::sub_lib::route::Route; use crate::sub_lib::route::RouteSegment; - use crate::test_utils::pure_test_utils::prove_that_crash_request_handler_is_hooked_up; + use crate::test_utils::unshared_test_utils::prove_that_crash_request_handler_is_hooked_up; use crate::test_utils::{ alias_cryptde, main_cryptde, make_meaningless_message_type, make_paying_wallet, route_to_proxy_client, diff --git a/node/src/neighborhood/mod.rs b/node/src/neighborhood/mod.rs index 39f569172..1dc937a05 100644 --- a/node/src/neighborhood/mod.rs +++ b/node/src/neighborhood/mod.rs @@ -150,7 +150,7 @@ impl Handler for Neighborhood { NodeQueryResponseMetadata::new( node_record_ref.public_key().clone(), node_record_ref.node_addr_opt(), - node_record_ref.rate_pack().clone(), + *node_record_ref.rate_pack(), ) })) } @@ -173,7 +173,7 @@ impl Handler for Neighborhood { NodeQueryResponseMetadata::new( node_record_ref.public_key().clone(), node_record_ref.node_addr_opt(), - node_record_ref.rate_pack().clone(), + *node_record_ref.rate_pack(), ) }); @@ -254,6 +254,10 @@ impl Handler for Neighborhood { match msg { NodeRecordMetadataMessage::Desirable(public_key, desirable) => { if let Some(node_record) = self.neighborhood_database.node_by_key_mut(&public_key) { + debug!( + self.logger, + "About to set desirable '{}' for '{:?}'", desirable, public_key + ); node_record.set_desirable(desirable); }; } @@ -967,13 +971,13 @@ impl Neighborhood { Ok(ExpectedService::Exit( route_segment_key.clone(), node.earning_wallet(), - node.rate_pack().clone(), + *node.rate_pack(), )) } (Some(_), Some(_)) => Ok(ExpectedService::Routing( route_segment_key.clone(), node.earning_wallet(), - node.rate_pack().clone(), + *node.rate_pack(), )), _ => Err( "cannot calculate expected service, no keys provided in route segment" @@ -1293,12 +1297,12 @@ mod tests { neighborhood_from_nodes, }; use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMock; - use crate::test_utils::pure_test_utils::prove_that_crash_request_handler_is_hooked_up; use crate::test_utils::rate_pack; use crate::test_utils::recorder::make_recorder; use crate::test_utils::recorder::peer_actors_builder; use crate::test_utils::recorder::Recorder; use crate::test_utils::recorder::Recording; + use crate::test_utils::unshared_test_utils::prove_that_crash_request_handler_is_hooked_up; use crate::test_utils::vec_to_set; use crate::test_utils::{main_cryptde, make_paying_wallet}; diff --git a/node/src/neighborhood/neighborhood_database.rs b/node/src/neighborhood/neighborhood_database.rs index d90303d61..fe47b2823 100644 --- a/node/src/neighborhood/neighborhood_database.rs +++ b/node/src/neighborhood/neighborhood_database.rs @@ -53,7 +53,7 @@ impl NeighborhoodDatabase { let mut node_record = NodeRecord::new( public_key, earning_wallet, - neighborhood_mode.rate_pack().clone(), + *neighborhood_mode.rate_pack(), neighborhood_mode.accepts_connections(), neighborhood_mode.routes_data(), 0, diff --git a/node/src/neighborhood/node_record.rs b/node/src/neighborhood/node_record.rs index 37f4adf51..5074b30f9 100644 --- a/node/src/neighborhood/node_record.rs +++ b/node/src/neighborhood/node_record.rs @@ -4,8 +4,7 @@ use crate::neighborhood::gossip::GossipNodeRecord; use crate::neighborhood::neighborhood_database::{NeighborhoodDatabase, NeighborhoodDatabaseError}; use crate::neighborhood::{regenerate_signed_gossip, AccessibleGossipRecord}; use crate::sub_lib::cryptde::{CryptDE, CryptData, PlainData, PublicKey}; -use crate::sub_lib::neighborhood::NodeDescriptor; -use crate::sub_lib::neighborhood::RatePack; +use crate::sub_lib::neighborhood::{NodeDescriptor, RatePack}; use crate::sub_lib::node_addr::NodeAddr; use crate::sub_lib::utils::time_t_timestamp; use crate::sub_lib::wallet::Wallet; @@ -951,8 +950,8 @@ mod tests { let result = subject.update(agr); assert_eq!( - Err("Updating a NodeRecord must not change its rate pack: 1236+1235b route 1238+1237b exit -> 0+0b route 0+0b exit".to_string()), - result + result, + Err("Updating a NodeRecord must not change its rate pack: 1235|1236|1237|1238 -> 0|0|0|0".to_string()), ) } diff --git a/node/src/node_configurator/configurator.rs b/node/src/node_configurator/configurator.rs index 22273ce4d..a8f0a9176 100644 --- a/node/src/node_configurator/configurator.rs +++ b/node/src/node_configurator/configurator.rs @@ -8,9 +8,9 @@ use masq_lib::messages::{ FromMessageBody, ToMessageBody, UiChangePasswordRequest, UiChangePasswordResponse, UiCheckPasswordRequest, UiCheckPasswordResponse, UiConfigurationRequest, UiConfigurationResponse, UiGenerateSeedSpec, UiGenerateWalletsRequest, - UiGenerateWalletsResponse, UiNewPasswordBroadcast, UiRecoverWalletsRequest, - UiRecoverWalletsResponse, UiSetConfigurationRequest, UiSetConfigurationResponse, - UiWalletAddressesRequest, UiWalletAddressesResponse, + UiGenerateWalletsResponse, UiNewPasswordBroadcast, UiPaymentThresholds, UiRatePack, + UiRecoverWalletsRequest, UiRecoverWalletsResponse, UiScanIntervals, UiSetConfigurationRequest, + UiSetConfigurationResponse, UiWalletAddressesRequest, UiWalletAddressesResponse, }; use masq_lib::ui_gateway::MessageTarget::ClientId; use masq_lib::ui_gateway::{ @@ -589,6 +589,24 @@ impl Configurator { } None => (None, None, vec![]), }; + let rate_pack = Self::value_required(persistent_config.rate_pack(), "ratePack")?; + let scan_intervals = + Self::value_required(persistent_config.scan_intervals(), "scanIntervals")?; + let payment_thresholds = + Self::value_required(persistent_config.payment_thresholds(), "paymentThresholds")?; + let routing_byte_rate = rate_pack.routing_byte_rate; + let routing_service_rate = rate_pack.routing_service_rate; + let exit_byte_rate = rate_pack.exit_byte_rate; + let exit_service_rate = rate_pack.exit_service_rate; + let pending_payable_sec = scan_intervals.pending_payable_scan_interval.as_secs(); + let payable_sec = scan_intervals.payable_scan_interval.as_secs(); + let receivable_sec = scan_intervals.receivable_scan_interval.as_secs(); + let threshold_interval_sec = payment_thresholds.threshold_interval_sec; + let debt_threshold_gwei = payment_thresholds.debt_threshold_gwei; + let payment_grace_period_sec = payment_thresholds.payment_grace_period_sec; + let maturity_threshold_sec = payment_thresholds.maturity_threshold_sec; + let permanent_debt_allowed_gwei = payment_thresholds.permanent_debt_allowed_gwei; + let unban_below_gwei = payment_thresholds.unban_below_gwei; let response = UiConfigurationResponse { blockchain_service_url_opt, current_schema_version, @@ -601,7 +619,26 @@ impl Configurator { earning_wallet_address_opt, port_mapping_protocol_opt, past_neighbors, + payment_thresholds: UiPaymentThresholds { + threshold_interval_sec, + debt_threshold_gwei, + maturity_threshold_sec, + payment_grace_period_sec, + permanent_debt_allowed_gwei, + unban_below_gwei, + }, + rate_pack: UiRatePack { + routing_byte_rate, + routing_service_rate, + exit_byte_rate, + exit_service_rate, + }, start_block, + scan_intervals: UiScanIntervals { + pending_payable_sec, + payable_sec, + receivable_sec, + }, }; Ok(response.tmb(context_id)) } @@ -769,12 +806,14 @@ mod tests { use actix::System; use masq_lib::messages::{ ToMessageBody, UiChangePasswordResponse, UiCheckPasswordRequest, UiCheckPasswordResponse, - UiGenerateSeedSpec, UiGenerateWalletsResponse, UiNewPasswordBroadcast, UiRecoverSeedSpec, - UiStartOrder, UiWalletAddressesRequest, UiWalletAddressesResponse, + UiGenerateSeedSpec, UiGenerateWalletsResponse, UiNewPasswordBroadcast, UiPaymentThresholds, + UiRatePack, UiRecoverSeedSpec, UiScanIntervals, UiStartOrder, UiWalletAddressesRequest, + UiWalletAddressesResponse, }; use masq_lib::ui_gateway::{MessagePath, MessageTarget}; use std::str::FromStr; use std::sync::{Arc, Mutex}; + use std::time::Duration; use crate::db_config::persistent_configuration::{ PersistentConfigError, PersistentConfigurationReal, @@ -788,13 +827,14 @@ mod tests { use crate::blockchain::bip39::Bip39; use crate::blockchain::test_utils::make_meaningless_phrase_words; use crate::database::db_initializer::{DbInitializer, DbInitializerReal}; + use crate::sub_lib::accountant::{PaymentThresholds, ScanIntervals}; use crate::sub_lib::cryptde::PublicKey as PK; use crate::sub_lib::cryptde::{CryptDE, PlainData}; - use crate::sub_lib::neighborhood::NodeDescriptor; + use crate::sub_lib::neighborhood::{NodeDescriptor, RatePack}; use crate::sub_lib::node_addr::NodeAddr; use crate::sub_lib::wallet::Wallet; - use crate::test_utils::pure_test_utils::{ - make_default_persistent_configuration, prove_that_crash_request_handler_is_hooked_up, + use crate::test_utils::unshared_test_utils::{ + configure_default_persistent_config, prove_that_crash_request_handler_is_hooked_up, ZERO, }; use bip39::{Language, Mnemonic}; use masq_lib::blockchains::chains::Chain; @@ -1760,7 +1800,7 @@ mod tests { }; let set_wallet_info_params_arc = Arc::new(Mutex::new(vec![])); let mut persistent_config: Box = Box::new( - make_default_persistent_configuration() + configure_default_persistent_config(ZERO) .check_password_result(Ok(true)) .set_wallet_info_params(&set_wallet_info_params_arc) .set_wallet_info_result(Ok(())), @@ -1787,7 +1827,7 @@ mod tests { earning_address_opt: Some("0x0123456789012345678901234567890123456789".to_string()), }; let mut persistent_config: Box = - Box::new(make_default_persistent_configuration().check_password_result(Ok(true))); + Box::new(configure_default_persistent_config(ZERO).check_password_result(Ok(true))); let result = Configurator::unfriendly_handle_recover_wallets(msg, 1234, &mut persistent_config); @@ -1812,7 +1852,7 @@ mod tests { earning_address_opt: Some(earning_address), }; let mut persistent_config: Box = - Box::new(make_default_persistent_configuration().check_password_result(Ok(true))); + Box::new(configure_default_persistent_config(ZERO).check_password_result(Ok(true))); let result = Configurator::unfriendly_handle_recover_wallets(msg, 1234, &mut persistent_config); @@ -1838,7 +1878,7 @@ mod tests { earning_address_opt: None, }; let mut persistent_config: Box = - Box::new(make_default_persistent_configuration().check_password_result(Ok(true))); + Box::new(configure_default_persistent_config(ZERO).check_password_result(Ok(true))); let result = Configurator::unfriendly_handle_recover_wallets(msg, 1234, &mut persistent_config); @@ -1862,7 +1902,7 @@ mod tests { earning_address_opt: Some("0x0123456789012345678901234567890123456789".to_string()), }; let mut persistent_config: Box = Box::new( - make_default_persistent_configuration() + configure_default_persistent_config(ZERO) .check_password_result(Ok(true)) .set_wallet_info_result(Ok(())), ); @@ -1903,7 +1943,6 @@ mod tests { let response = ui_gateway_recording.get_record::(0); let (_, context_id) = UiSetConfigurationResponse::fmb(response.body.clone()).unwrap(); assert_eq!(context_id, 4444); - let check_start_block_params = set_start_block_params_arc.lock().unwrap(); assert_eq!(*check_start_block_params, vec![166666]); } @@ -2148,7 +2187,7 @@ mod tests { .blockchain_service_url_result(Ok(None)) .check_password_result(Ok(true)) .chain_name_result("ropsten".to_string()) - .current_schema_version_result("1.2.3") + .current_schema_version_result("3") .clandestine_port_result(Ok(1234)) .gas_price_result(Ok(2345)) .consuming_wallet_private_key_result(Ok(Some(consuming_wallet_private_key))) @@ -2157,6 +2196,7 @@ mod tests { .past_neighbors_result(Ok(Some(vec![node_descriptor.clone()]))) .earning_wallet_address_result(Ok(Some(earning_wallet_address.clone()))) .start_block_result(Ok(3456)); + let persistent_config = payment_thresholds_scan_intervals_rate_pack(persistent_config); let mut subject = make_subject(Some(persistent_config)); let (configuration, context_id) = @@ -2173,7 +2213,7 @@ mod tests { configuration, UiConfigurationResponse { blockchain_service_url_opt: None, - current_schema_version: "1.2.3".to_string(), + current_schema_version: "3".to_string(), clandestine_port: 1234, chain_name: "ropsten".to_string(), gas_price: 2345, @@ -2183,11 +2223,55 @@ mod tests { earning_wallet_address_opt: Some(earning_wallet_address), port_mapping_protocol_opt: Some("IGDP".to_string()), past_neighbors: vec![], - start_block: 3456 + payment_thresholds: UiPaymentThresholds { + threshold_interval_sec: 10_000, + debt_threshold_gwei: 5_000_000, + maturity_threshold_sec: 1200, + payment_grace_period_sec: 1000, + permanent_debt_allowed_gwei: 20_000, + unban_below_gwei: 20_000 + }, + rate_pack: UiRatePack { + routing_byte_rate: 6, + routing_service_rate: 8, + exit_byte_rate: 10, + exit_service_rate: 13 + }, + start_block: 3456, + scan_intervals: UiScanIntervals { + pending_payable_sec: 122, + payable_sec: 125, + receivable_sec: 128 + } } ); } + fn payment_thresholds_scan_intervals_rate_pack( + persistent_config: PersistentConfigurationMock, + ) -> PersistentConfigurationMock { + persistent_config + .rate_pack_result(Ok(RatePack { + routing_byte_rate: 6, + routing_service_rate: 8, + exit_byte_rate: 10, + exit_service_rate: 13, + })) + .scan_intervals_result(Ok(ScanIntervals { + pending_payable_scan_interval: Duration::from_secs(122), + payable_scan_interval: Duration::from_secs(125), + receivable_scan_interval: Duration::from_secs(128), + })) + .payment_thresholds_result(Ok(PaymentThresholds { + threshold_interval_sec: 10000, + debt_threshold_gwei: 5000000, + payment_grace_period_sec: 1000, + maturity_threshold_sec: 1200, + permanent_debt_allowed_gwei: 20000, + unban_below_gwei: 20000, + })) + } + #[test] #[should_panic( expected = "panic message (processed with: node_lib::sub_lib::utils::crash_request_analyzer)" @@ -2230,7 +2314,7 @@ mod tests { .blockchain_service_url_result(Ok(None)) .check_password_result(Ok(true)) .chain_name_result("ropsten".to_string()) - .current_schema_version_result("1.2.3") + .current_schema_version_result("3") .clandestine_port_result(Ok(1234)) .gas_price_result(Ok(2345)) .consuming_wallet_private_key_params(&consuming_wallet_private_key_params_arc) @@ -2240,7 +2324,9 @@ mod tests { .past_neighbors_params(&past_neighbors_params_arc) .past_neighbors_result(Ok(Some(vec![node_descriptor.clone()]))) .earning_wallet_address_result(Ok(Some(earning_wallet_address.clone()))) + .start_block_result(Ok(3456)) .start_block_result(Ok(3456)); + let persistent_config = payment_thresholds_scan_intervals_rate_pack(persistent_config); let mut subject = make_subject(Some(persistent_config)); let (configuration, context_id) = @@ -2257,7 +2343,7 @@ mod tests { configuration, UiConfigurationResponse { blockchain_service_url_opt: None, - current_schema_version: "1.2.3".to_string(), + current_schema_version: "3".to_string(), clandestine_port: 1234, chain_name: "ropsten".to_string(), gas_price: 2345, @@ -2267,7 +2353,26 @@ mod tests { earning_wallet_address_opt: Some(earning_wallet_address), port_mapping_protocol_opt: Some(AutomapProtocol::Igdp.to_string()), past_neighbors: vec![node_descriptor.to_string(main_cryptde())], - start_block: 3456 + payment_thresholds: UiPaymentThresholds { + threshold_interval_sec: 10_000, + debt_threshold_gwei: 5_000_000, + maturity_threshold_sec: 1200, + payment_grace_period_sec: 1000, + permanent_debt_allowed_gwei: 20_000, + unban_below_gwei: 20_000 + }, + rate_pack: UiRatePack { + routing_byte_rate: 6, + routing_service_rate: 8, + exit_byte_rate: 10, + exit_service_rate: 13 + }, + start_block: 3456, + scan_intervals: UiScanIntervals { + pending_payable_sec: 122, + payable_sec: 125, + receivable_sec: 128 + } } ); let consuming_wallet_private_key_params = diff --git a/node/src/node_configurator/mod.rs b/node/src/node_configurator/mod.rs index 9e2e8e1c1..a5f00cb7c 100644 --- a/node/src/node_configurator/mod.rs +++ b/node/src/node_configurator/mod.rs @@ -3,6 +3,8 @@ pub mod configurator; pub mod node_configurator_initialization; pub mod node_configurator_standard; +pub mod unprivileged_parse_args_configuration; + use crate::bootstrapper::RealUser; use crate::database::db_initializer::{DbInitializer, DbInitializerReal, DATABASE_FILE}; use crate::database::db_migrations::MigratorConfig; @@ -14,6 +16,7 @@ use clap::{value_t, App}; use dirs::{data_local_dir, home_dir}; use masq_lib::blockchains::chains::Chain; use masq_lib::constants::DEFAULT_CHAIN; +use masq_lib::multi_config::make_arg_matches_accesible; use masq_lib::multi_config::{merge, CommandLineVcl, EnvironmentVcl, MultiConfig, VclArg}; use masq_lib::shared_schema::{ chain_arg, config_file_arg, data_directory_arg, real_user_arg, ConfiguratorError, diff --git a/node/src/node_configurator/node_configurator_initialization.rs b/node/src/node_configurator/node_configurator_initialization.rs index 138205b59..bb4008cfd 100644 --- a/node/src/node_configurator/node_configurator_initialization.rs +++ b/node/src/node_configurator/node_configurator_initialization.rs @@ -39,6 +39,7 @@ mod initialization { use super::*; use clap::value_t; use masq_lib::constants::DEFAULT_UI_PORT; + use masq_lib::multi_config::make_arg_matches_accesible; use masq_lib::multi_config::MultiConfig; pub fn parse_args(multi_config: &MultiConfig, config: &mut InitializationConfig) { diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index 644bf1ae4..67e3c51f8 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -7,7 +7,6 @@ use masq_lib::crash_point::CrashPoint; use masq_lib::logger::Logger; use masq_lib::multi_config::MultiConfig; use masq_lib::shared_schema::ConfiguratorError; -use masq_lib::utils::AutomapProtocol; use masq_lib::utils::{ExpectValue, NeighborhoodModeLight}; use std::net::SocketAddr; use std::net::{IpAddr, Ipv4Addr}; @@ -16,37 +15,26 @@ use clap::value_t; use log::LevelFilter; use crate::apps::app_node; -use crate::blockchain::bip32::Bip32ECKeyProvider; use crate::bootstrapper::PortConfiguration; use crate::database::db_migrations::{ExternalData, MigratorConfig}; -use crate::db_config::persistent_configuration::{PersistentConfigError, PersistentConfiguration}; +use crate::db_config::persistent_configuration::PersistentConfiguration; use crate::http_request_start_finder::HttpRequestDiscriminatorFactory; +use crate::node_configurator::unprivileged_parse_args_configuration::{ + UnprivilegedParseArgsConfiguration, UnprivilegedParseArgsConfigurationDaoReal, +}; use crate::node_configurator::{ data_directory_from_context, determine_config_file_path, real_user_data_directory_opt_and_chain, real_user_from_multi_config_or_populate, }; use crate::server_initializer::GatheredParams; -use crate::sub_lib::accountant::DEFAULT_EARNING_WALLET; -use crate::sub_lib::cryptde::{CryptDE, PublicKey}; +use crate::sub_lib::cryptde::PublicKey; use crate::sub_lib::cryptde_null::CryptDENull; -use crate::sub_lib::cryptde_real::CryptDEReal; -use crate::sub_lib::neighborhood::{ - NeighborhoodConfig, NeighborhoodMode, NodeDescriptor, DEFAULT_RATE_PACK, -}; -use crate::sub_lib::node_addr::NodeAddr; use crate::sub_lib::utils::make_new_multi_config; -use crate::sub_lib::wallet::Wallet; use crate::tls_discriminator_factory::TlsDiscriminatorFactory; -use itertools::Itertools; -use masq_lib::blockchains::chains::Chain; -use masq_lib::constants::{DEFAULT_CHAIN, DEFAULT_UI_PORT, HTTP_PORT, MASQ_URL_PREFIX, TLS_PORT}; +use masq_lib::constants::{DEFAULT_UI_PORT, HTTP_PORT, TLS_PORT}; +use masq_lib::multi_config::make_arg_matches_accesible; use masq_lib::multi_config::{CommandLineVcl, ConfigFileVcl, EnvironmentVcl}; -use masq_lib::shared_schema::ParamError; -use masq_lib::test_utils::utils::TEST_DEFAULT_CHAIN; use masq_lib::utils::WrapResult; -use rustc_hex::FromHex; -use std::convert::TryFrom; -use std::ops::Deref; use std::str::FromStr; pub struct NodeConfiguratorStandardPrivileged { @@ -96,10 +84,11 @@ impl NodeConfigurator for NodeConfiguratorStandardUnprivileg let mut persistent_config = initialize_database( &self.privileged_config.data_directory, true, - MigratorConfig::create_or_migrate(self.wrap_up_external_params_for_db(multi_config)), + MigratorConfig::create_or_migrate(self.wrap_up_db_externals(multi_config)), ); let mut unprivileged_config = BootstrapperConfig::new(); - unprivileged_parse_args( + let parse_args_configurator = UnprivilegedParseArgsConfigurationDaoReal {}; + parse_args_configurator.unprivileged_parse_args( multi_config, &mut unprivileged_config, persistent_config.as_mut(), @@ -118,16 +107,27 @@ impl NodeConfiguratorStandardUnprivileged { } } - fn wrap_up_external_params_for_db(&self, multi_config: &MultiConfig) -> ExternalData { + fn wrap_up_db_externals(&self, multi_config: &MultiConfig) -> ExternalData { + let (neighborhood_mode, db_password_opt) = + collect_externals_from_multi_config(multi_config); ExternalData::new( self.privileged_config.blockchain_bridge_config.chain, - value_m!(multi_config, "neighborhood-mode", NeighborhoodModeLight) - .unwrap_or(NeighborhoodModeLight::Standard), - value_m!(multi_config, "db-password", String), + neighborhood_mode, + db_password_opt, ) } } +fn collect_externals_from_multi_config( + multi_config: &MultiConfig, +) -> (NeighborhoodModeLight, Option) { + ( + value_m!(multi_config, "neighborhood-mode", NeighborhoodModeLight) + .unwrap_or(NeighborhoodModeLight::Standard), + value_m!(multi_config, "db-password", String), + ) +} + pub fn server_initializer_collected_params<'a>( dirs_wrapper: &dyn DirsWrapper, args: &[String], @@ -235,50 +235,7 @@ pub fn privileged_parse_args( Ok(()) } -// Only initialization that cannot be done with privilege should happen here. -pub fn unprivileged_parse_args( - multi_config: &MultiConfig, - unprivileged_config: &mut BootstrapperConfig, - persistent_config: &mut dyn PersistentConfiguration, - logger: &Logger, -) -> Result<(), ConfiguratorError> { - unprivileged_config - .blockchain_bridge_config - .blockchain_service_url_opt = if is_user_specified(multi_config, "blockchain-service-url") { - value_m!(multi_config, "blockchain-service-url", String) - } else { - match persistent_config.blockchain_service_url() { - Ok(Some(price)) => Some(price), - Ok(None) => None, - Err(pce) => return Err(pce.into_configurator_error("gas-price")), - } - }; - unprivileged_config.clandestine_port_opt = value_m!(multi_config, "clandestine-port", u16); - unprivileged_config.blockchain_bridge_config.gas_price = - if is_user_specified(multi_config, "gas-price") { - value_m!(multi_config, "gas-price", u64).expectv("gas price") - } else { - match persistent_config.gas_price() { - Ok(price) => price, - Err(pce) => return Err(pce.into_configurator_error("gas-price")), - } - }; - unprivileged_config.db_password_opt = value_m!(multi_config, "db-password", String); - unprivileged_config.mapping_protocol_opt = - compute_mapping_protocol_opt(multi_config, persistent_config, logger); - let mnc_result = { - get_wallets(multi_config, persistent_config, unprivileged_config)?; - make_neighborhood_config(multi_config, persistent_config, unprivileged_config) - }; - - mnc_result.map(|config| unprivileged_config.neighborhood_config = config) -} - -fn is_user_specified(multi_config: &MultiConfig, parameter: &str) -> bool { - multi_config.deref().occurrences_of(parameter) > 0 -} - -pub fn configure_database( +fn configure_database( config: &BootstrapperConfig, persistent_config: &mut dyn PersistentConfiguration, ) -> Result<(), ConfiguratorError> { @@ -294,9 +251,9 @@ pub fn configure_database( if let Some(url) = config .blockchain_bridge_config .blockchain_service_url_opt - .clone() + .as_ref() { - if let Err(pce) = persistent_config.set_blockchain_service_url(url.as_str()) { + if let Err(pce) = persistent_config.set_blockchain_service_url(url) { return Err(pce.into_configurator_error("blockchain-service-url")); } } @@ -306,442 +263,92 @@ pub fn configure_database( Ok(()) } -fn zero_hop_neighbors_configuration( - password_opt: Option, - descriptors: Vec, - persistent_config: &mut dyn PersistentConfiguration, -) -> Result<(), ConfiguratorError> { - match password_opt { - Some(password) => { - if let Err(e) = persistent_config.set_past_neighbors(Some(descriptors), &password) { - return Err(e.into_configurator_error("neighbors")); - } - } - None => { - return Err(ConfiguratorError::required( - "neighbors", - "Cannot proceed without a password", - )); - } - } - Ok(()) -} - -pub fn get_wallets( - multi_config: &MultiConfig, - persistent_config: &mut dyn PersistentConfiguration, - config: &mut BootstrapperConfig, -) -> Result<(), ConfiguratorError> { - let mc_consuming_opt = value_m!(multi_config, "consuming-private-key", String); - let mc_earning_opt = value_m!(multi_config, "earning-wallet", String); - let pc_consuming_opt = if let Some(db_password) = &config.db_password_opt { - match persistent_config.consuming_wallet_private_key(db_password.as_str()) { - Ok(pco) => pco, - Err(PersistentConfigError::PasswordError) => None, - Err(e) => return Err(e.into_configurator_error("consuming-private-key")), - } - } else { - None - }; - let pc_earning_opt = match persistent_config.earning_wallet_address() { - Ok(peo) => peo, - Err(e) => return Err(e.into_configurator_error("earning-wallet")), - }; - let consuming_opt = match (&mc_consuming_opt, &pc_consuming_opt) { - (None, _) => pc_consuming_opt, - (Some(_), None) => mc_consuming_opt, - (Some(m), Some(c)) if wallet_parms_are_equal(m, c) => pc_consuming_opt, - _ => { - return Err(ConfiguratorError::required( - "consuming-private-key", - "Cannot change to a private key different from that previously set", - )) - } - }; - let earning_opt = match (&mc_earning_opt, &pc_earning_opt) { - (None, _) => pc_earning_opt, - (Some(_), None) => mc_earning_opt, - (Some(m), Some(c)) if wallet_parms_are_equal(m, c) => pc_earning_opt, - (Some(m), Some(c)) => { - return Err(ConfiguratorError::required( - "earning-wallet", - &format!( - "Cannot change to an address ({}) different from that previously set ({})", - m, c - ), - )) - } - }; - let consuming_wallet_opt = consuming_opt.map(|consuming_private_key| { - let key_bytes = consuming_private_key - .from_hex::>() - .unwrap_or_else(|_| { - panic!( - "Wallet corruption: bad hex value for consuming wallet private key: {}", - consuming_private_key - ) - }); - let key_pair = - Bip32ECKeyProvider::from_raw_secret(key_bytes.as_slice()).unwrap_or_else(|_| { - panic!( - "Wallet corruption: consuming wallet private key in invalid format: {:?}", - key_bytes - ) - }); - Wallet::from(key_pair) - }); - let earning_wallet_opt = earning_opt.map(|earning_address| { - Wallet::from_str(&earning_address).unwrap_or_else(|_| { - panic!( - "Wallet corruption: bad value for earning wallet address: {}", - earning_address - ) - }) - }); - config.consuming_wallet_opt = consuming_wallet_opt; - config.earning_wallet = earning_wallet_opt.unwrap_or_else(|| DEFAULT_EARNING_WALLET.clone()); - Ok(()) -} - -fn wallet_parms_are_equal(a: &str, b: &str) -> bool { - a.to_uppercase() == b.to_uppercase() -} - -pub fn make_neighborhood_config( - multi_config: &MultiConfig, - persistent_config: &mut dyn PersistentConfiguration, - unprivileged_config: &mut BootstrapperConfig, -) -> Result { - let neighbor_configs: Vec = { - match convert_ci_configs(multi_config)? { - Some(configs) => configs, - None => get_past_neighbors(persistent_config, unprivileged_config)?, - } - }; - match make_neighborhood_mode(multi_config, neighbor_configs, persistent_config) { - Ok(mode) => Ok(NeighborhoodConfig { mode }), - Err(e) => Err(e), - } -} - -pub fn convert_ci_configs( - multi_config: &MultiConfig, -) -> Result>, ConfiguratorError> { - type DescriptorParsingResult = Result; - match value_m!(multi_config, "neighbors", String) { - None => Ok(None), - Some(joined_configs) => { - let separate_configs: Vec = joined_configs - .split(',') - .map(|s| s.to_string()) - .collect_vec(); - if separate_configs.is_empty() { - Ok(None) - } else { - let dummy_cryptde: Box = { - if value_m!(multi_config, "fake-public-key", String).is_none() { - Box::new(CryptDEReal::new(TEST_DEFAULT_CHAIN)) - } else { - Box::new(CryptDENull::new(TEST_DEFAULT_CHAIN)) - } - }; - let desired_chain = Chain::from( - value_m!(multi_config, "chain", String) - .unwrap_or_else(|| DEFAULT_CHAIN.rec().literal_identifier.to_string()) - .as_str(), - ); - let results = - validate_descriptors_from_user(separate_configs, dummy_cryptde, desired_chain); - let (ok, err): (Vec, Vec) = - results.into_iter().partition(|result| result.is_ok()); - let ok = ok - .into_iter() - .map(|ok| ok.expect("NodeDescriptor")) - .collect_vec(); - let err = err - .into_iter() - .map(|err| err.expect_err("ParamError")) - .collect_vec(); - if err.is_empty() { - Ok(Some(ok)) - } else { - Err(ConfiguratorError::new(err)) - } - } - } - } -} - -fn validate_descriptors_from_user( - descriptors: Vec, - dummy_cryptde: Box, - desired_chain: Chain, -) -> Vec> { - fn validate( - descriptor: NodeDescriptor, - desired_chain: Chain, - str_descriptor_from_usr: &str, - ) -> Result { - let nd_chain = descriptor.blockchain; - if desired_chain == nd_chain { - validate_mandatory_node_addr(str_descriptor_from_usr, descriptor) - } else { - let name_of_desired_chain = desired_chain.rec().literal_identifier; - Err(ParamError::new( - "neighbors", - &format!("Mismatched chains. You are requiring access to '{}' ({}{}:@) with descriptor belonging to '{}'", - name_of_desired_chain, - MASQ_URL_PREFIX, - name_of_desired_chain, - nd_chain.rec().literal_identifier) - )) - } - } - descriptors - .into_iter() - .map(|node_desc_from_ci| { - let node_desc_trimmed = node_desc_from_ci.trim(); - match NodeDescriptor::try_from((dummy_cryptde.as_ref(), node_desc_trimmed)) { - Ok(descriptor) => validate(descriptor, desired_chain, node_desc_trimmed), - Err(e) => Err(ParamError::new("neighbors", &e)), - } - }) - .collect() -} - -fn validate_mandatory_node_addr( - supplied_descriptor: &str, - descriptor: NodeDescriptor, -) -> Result { - if descriptor.node_addr_opt.is_some() { - Ok(descriptor) - } else { - Err(ParamError::new( - "neighbors", - &format!( - "Neighbors supplied without ip addresses and ports are not valid: '{}:", - if supplied_descriptor.ends_with("@:") { - supplied_descriptor.strip_suffix(':').expect("logic failed") - } else { - supplied_descriptor - } - ), - )) - } -} - -pub fn get_past_neighbors( - persistent_config: &mut dyn PersistentConfiguration, - unprivileged_config: &mut BootstrapperConfig, -) -> Result, ConfiguratorError> { - Ok( - match &get_db_password(unprivileged_config, persistent_config)? { - Some(db_password) => match persistent_config.past_neighbors(db_password) { - Ok(Some(past_neighbors)) => past_neighbors, - Ok(None) => vec![], - Err(PersistentConfigError::PasswordError) => { - return Err(ConfiguratorError::new(vec![ParamError::new( - "db-password", - "PasswordError", - )])) - } - Err(e) => { - return Err(ConfiguratorError::new(vec![ParamError::new( - "[past neighbors]", - &format!("{:?}", e), - )])) - } - }, - None => vec![], - }, - ) -} - -fn compute_mapping_protocol_opt( - multi_config: &MultiConfig, - persistent_config: &mut dyn PersistentConfiguration, - logger: &Logger, -) -> Option { - let persistent_mapping_protocol_opt = match persistent_config.mapping_protocol() { - Ok(mp_opt) => mp_opt, - Err(e) => { - warning!( - logger, - "Could not read mapping protocol from database: {:?}", - e - ); - None - } - }; - let mapping_protocol_specified = multi_config.occurrences_of("mapping-protocol") > 0; - let computed_mapping_protocol_opt = match ( - value_m!(multi_config, "mapping-protocol", AutomapProtocol), - persistent_mapping_protocol_opt, - mapping_protocol_specified, - ) { - (None, Some(persisted_mapping_protocol), false) => Some(persisted_mapping_protocol), - (None, _, true) => None, - (cmd_line_mapping_protocol_opt, _, _) => cmd_line_mapping_protocol_opt, - }; - if computed_mapping_protocol_opt != persistent_mapping_protocol_opt { - if computed_mapping_protocol_opt.is_none() { - debug!(logger, "Blanking mapping protocol out of the database") - } - match persistent_config.set_mapping_protocol(computed_mapping_protocol_opt) { - Ok(_) => (), - Err(e) => { - warning!( - logger, - "Could not save mapping protocol to database: {:?}", - e - ); - } - } - } - computed_mapping_protocol_opt -} - -fn make_neighborhood_mode( - multi_config: &MultiConfig, - neighbor_configs: Vec, - persistent_config: &mut dyn PersistentConfiguration, -) -> Result { - let neighborhood_mode_opt = value_m!(multi_config, "neighborhood-mode", String); - match neighborhood_mode_opt { - Some(ref s) if s == "standard" => { - neighborhood_mode_standard(multi_config, neighbor_configs) - } - Some(ref s) if s == "originate-only" => { - if neighbor_configs.is_empty() { - Err(ConfiguratorError::required("neighborhood-mode", "Node cannot run as --neighborhood-mode originate-only without --neighbors specified")) - } else { - Ok(NeighborhoodMode::OriginateOnly( - neighbor_configs, - DEFAULT_RATE_PACK, - )) - } - } - Some(ref s) if s == "consume-only" => { - let mut errors = ConfiguratorError::new(vec![]); - if neighbor_configs.is_empty() { - errors = errors.another_required("neighborhood-mode", "Node cannot run as --neighborhood-mode consume-only without --neighbors specified"); - } - if value_m!(multi_config, "dns-servers", String).is_some() { - errors = errors.another_required("neighborhood-mode", "Node cannot run as --neighborhood-mode consume-only if --dns-servers is specified"); - } - if !errors.is_empty() { - Err(errors) - } else { - Ok(NeighborhoodMode::ConsumeOnly(neighbor_configs)) - } - } - Some(ref s) if s == "zero-hop" => { - if value_m!(multi_config, "ip", IpAddr).is_some() { - Err(ConfiguratorError::required( - "neighborhood-mode", - "Node cannot run as --neighborhood-mode zero-hop if --ip is specified", - )) - } else { - if !neighbor_configs.is_empty() { - let password_opt = value_m!(multi_config, "db-password", String); - zero_hop_neighbors_configuration( - password_opt, - neighbor_configs, - persistent_config, - )? - } - Ok(NeighborhoodMode::ZeroHop) - } - } - // These two cases are untestable - Some(ref s) => panic!( - "--neighborhood-mode {} has not been properly provided for in the code", - s - ), - None => neighborhood_mode_standard(multi_config, neighbor_configs), - } -} - -fn neighborhood_mode_standard( - multi_config: &MultiConfig, - neighbor_configs: Vec, -) -> Result { - let ip = get_public_ip(multi_config)?; - Ok(NeighborhoodMode::Standard( - NodeAddr::new(&ip, &[]), - neighbor_configs, - DEFAULT_RATE_PACK, - )) -} - -pub fn get_public_ip(multi_config: &MultiConfig) -> Result { - match value_m!(multi_config, "ip", String) { - Some(ip_str) => match IpAddr::from_str(&ip_str) { - Ok(ip_addr) => Ok(ip_addr), - Err(_) => todo!("Drive in a better error message"), //Err(ConfiguratorError::required("ip", &format! ("blockety blip: '{}'", ip_str), - }, - None => Ok(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))), // sentinel: means "Try Automap" - } -} - -pub fn get_db_password( - config: &mut BootstrapperConfig, - persistent_config: &mut dyn PersistentConfiguration, -) -> Result, ConfiguratorError> { - if let Some(db_password) = &config.db_password_opt { - set_db_password_at_first_mention(db_password, persistent_config)?; - return Ok(Some(db_password.clone())); - } - Ok(None) -} - -fn set_db_password_at_first_mention( - db_password: &str, - persistent_config: &mut dyn PersistentConfiguration, -) -> Result { - match persistent_config.check_password(None) { - Ok(true) => match persistent_config.change_password(None, db_password) { - Ok(_) => Ok(true), - Err(e) => Err(e.into_configurator_error("db-password")), - }, - Ok(false) => Ok(false), - Err(e) => Err(e.into_configurator_error("db-password")), - } -} - #[cfg(test)] mod tests { use super::*; + use crate::blockchain::bip32::Bip32ECKeyProvider; use crate::bootstrapper::{BootstrapperConfig, RealUser}; use crate::database::db_initializer::{DbInitializer, DbInitializerReal}; - use crate::db_config::config_dao::{ConfigDao, ConfigDaoReal}; + use crate::db_config::config_dao::ConfigDaoReal; use crate::db_config::persistent_configuration::PersistentConfigError; - use crate::db_config::persistent_configuration::PersistentConfigError::NotPresent; use crate::db_config::persistent_configuration::PersistentConfigurationReal; + use crate::node_configurator::unprivileged_parse_args_configuration::UnprivilegedParseArgsConfigurationDaoNull; use crate::node_test_utils::DirsWrapperMock; - use crate::sub_lib::cryptde::PlainData; + use crate::sub_lib::cryptde::CryptDE; use crate::sub_lib::neighborhood::NeighborhoodMode::ZeroHop; + use crate::sub_lib::neighborhood::{NeighborhoodConfig, NeighborhoodMode, NodeDescriptor}; use crate::sub_lib::utils::make_new_test_multi_config; + use crate::sub_lib::wallet::Wallet; use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMock; - use crate::test_utils::pure_test_utils; - use crate::test_utils::pure_test_utils::{ - make_default_persistent_configuration, make_pre_populated_mocked_directory_wrapper, - make_simplified_multi_config, + use crate::test_utils::unshared_test_utils::{ + make_pre_populated_mocked_directory_wrapper, make_simplified_multi_config, }; use crate::test_utils::{assert_string_contains, main_cryptde, ArgsBuilder}; - use masq_lib::constants::DEFAULT_GAS_PRICE; - use masq_lib::multi_config::{NameValueVclArg, VclArg, VirtualCommandLine}; + use masq_lib::blockchains::chains::Chain; + use masq_lib::constants::DEFAULT_CHAIN; + use masq_lib::multi_config::VirtualCommandLine; use masq_lib::test_utils::environment_guard::{ClapGuard, EnvironmentGuard}; - use masq_lib::test_utils::fake_stream_holder::ByteArrayWriter; - use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; use masq_lib::test_utils::utils::{ensure_node_home_directory_exists, TEST_DEFAULT_CHAIN}; use masq_lib::utils::{array_of_borrows_to_vec, running_test}; + use rustc_hex::FromHex; + use std::convert::TryFrom; use std::fs::File; use std::io::Write; use std::path::PathBuf; use std::sync::{Arc, Mutex}; use std::vec; + #[test] + fn node_configurator_standard_unprivileged_uses_parse_args_configurator_dao_real() { + let home_dir = ensure_node_home_directory_exists( + "node_configurator_standard", + "node_configurator_standard_unprivileged_uses_parse_args_configurator_dao_real", + ); + let neighbor = vec![NodeDescriptor::try_from(( + main_cryptde(), + "masq://eth-mainnet:MTEyMjMzNDQ1NTY2Nzc4ODExMjIzMzQ0NTU2Njc3ODg@1.2.3.4:1234", + )) + .unwrap()]; + { + let conn = DbInitializerReal::default() + .initialize(home_dir.as_path(), true, MigratorConfig::test_default()) + .unwrap(); + let mut persistent_config = PersistentConfigurationReal::from(conn); + persistent_config.change_password(None, "password").unwrap(); + persistent_config + .set_past_neighbors(Some(neighbor.clone()), "password") + .unwrap(); + } + let multi_config = make_simplified_multi_config([ + "--chain", + "eth-mainnet", + "--db-password", + "password", + "--neighborhood-mode", + "originate-only", + ]); + let mut privileged_config = BootstrapperConfig::default(); + privileged_config.data_directory = home_dir; + let subject = NodeConfiguratorStandardUnprivileged { + privileged_config, + logger: Logger::new("test"), + }; + + let result = subject.configure(&multi_config).unwrap(); + + let set_neighbors = if let NeighborhoodMode::OriginateOnly(neighbors, _) = + result.neighborhood_config.mode + { + neighbors + } else { + panic!( + "we expected originate only mode but got: {:?}", + result.neighborhood_config.mode + ) + }; + assert_eq!(set_neighbors, neighbor) + } + #[test] fn configure_database_handles_error_during_setting_clandestine_port() { let mut config = BootstrapperConfig::new(); @@ -807,765 +414,104 @@ mod tests { ) } - #[test] - fn convert_ci_configs_handles_blockchain_mismatch() { - let multi_config = make_simplified_multi_config([ - "MASQNode", - "--neighbors", - "masq://eth-ropsten:abJ5XvhVbmVyGejkYUkmftF09pmGZGKg_PzRNnWQxFw@12.23.34.45:5678", - "--chain", - DEFAULT_CHAIN.rec().literal_identifier, - ]); - - let result = convert_ci_configs(&multi_config).err().unwrap(); - - assert_eq!( - result, - ConfiguratorError::required( - "neighbors", - "Mismatched chains. You are requiring access to 'eth-mainnet' (masq://eth-mainnet:@) with descriptor belonging to 'eth-ropsten'" - ) - ) - } - - #[test] - fn set_db_password_at_first_mention_handles_existing_password() { - let check_password_params_arc = Arc::new(Mutex::new(vec![])); - let mut persistent_config = make_default_persistent_configuration() - .check_password_params(&check_password_params_arc) - .check_password_result(Ok(false)); - - let result = set_db_password_at_first_mention("password", &mut persistent_config); - - assert_eq!(result, Ok(false)); - let check_password_params = check_password_params_arc.lock().unwrap(); - assert_eq!(*check_password_params, vec![None]) + fn make_default_cli_params() -> ArgsBuilder { + ArgsBuilder::new().param("--ip", "1.2.3.4") } #[test] - fn set_db_password_at_first_mention_sets_password_correctly() { - let change_password_params_arc = Arc::new(Mutex::new(vec![])); - let mut persistent_config = make_default_persistent_configuration() - .check_password_result(Ok(true)) - .change_password_params(&change_password_params_arc) - .change_password_result(Ok(())); - - let result = set_db_password_at_first_mention("password", &mut persistent_config); + fn server_initializer_collected_params_can_read_parameters_from_config_file() { + running_test(); + let _guard = EnvironmentGuard::new(); + let home_dir = ensure_node_home_directory_exists( + "node_configurator", + "server_initializer_collected_params_can_read_parameters_from_config_file", + ); + { + let mut config_file = File::create(home_dir.join("config.toml")).unwrap(); + config_file + .write_all(b"dns-servers = \"111.111.111.111,222.222.222.222\"\n") + .unwrap(); + } + let directory_wrapper = make_pre_populated_mocked_directory_wrapper(); - assert_eq!(result, Ok(true)); - let change_password_params = change_password_params_arc.lock().unwrap(); - assert_eq!( - *change_password_params, - vec![(None, "password".to_string())] + let gathered_params = server_initializer_collected_params( + &directory_wrapper, + &array_of_borrows_to_vec(&["", "--data-directory", home_dir.to_str().unwrap()]), ) - } - - #[test] - fn set_db_password_at_first_mention_handles_password_check_error() { - let check_password_params_arc = Arc::new(Mutex::new(vec![])); - let mut persistent_config = make_default_persistent_configuration() - .check_password_params(&check_password_params_arc) - .check_password_result(Err(PersistentConfigError::NotPresent)); - - let result = set_db_password_at_first_mention("password", &mut persistent_config); + .unwrap(); + let multi_config = gathered_params.multi_config; assert_eq!( - result, - Err(PersistentConfigError::NotPresent.into_configurator_error("db-password")) + value_m!(multi_config, "dns-servers", String).unwrap(), + "111.111.111.111,222.222.222.222".to_string() ); - let check_password_params = check_password_params_arc.lock().unwrap(); - assert_eq!(*check_password_params, vec![None]) } #[test] - fn set_db_password_at_first_mention_handles_password_set_error() { - let change_password_params_arc = Arc::new(Mutex::new(vec![])); - let mut persistent_config = make_default_persistent_configuration() - .check_password_result(Ok(true)) - .change_password_params(&change_password_params_arc) - .change_password_result(Err(PersistentConfigError::NotPresent)); - - let result = set_db_password_at_first_mention("password", &mut persistent_config); - - assert_eq!( - result, - Err(NotPresent.into_configurator_error("db-password")) + fn can_read_dns_servers_and_consuming_private_key_from_config_file() { + running_test(); + let home_dir = ensure_node_home_directory_exists( + "node_configurator", + "can_read_wallet_parameters_from_config_file", ); - let change_password_params = change_password_params_arc.lock().unwrap(); - assert_eq!( - *change_password_params, - vec![(None, "password".to_string())] - ) - } - - #[test] - fn compute_mapping_protocol_returns_saved_value_if_nothing_supplied() { + let mut persistent_config = PersistentConfigurationReal::new(Box::new(ConfigDaoReal::new( + DbInitializerReal::default() + .initialize(&home_dir.clone(), true, MigratorConfig::test_default()) + .unwrap(), + ))); + let consuming_private_key = + "89ABCDEF89ABCDEF89ABCDEF89ABCDEF89ABCDEF89ABCDEF89ABCDEF89ABCDEF"; + let config_file_path = home_dir.join("config.toml"); + { + let mut config_file = File::create(&config_file_path).unwrap(); + short_writeln!( + config_file, + "consuming-private-key = \"{}\"", + consuming_private_key + ); + } + let args = ArgsBuilder::new() + .param("--data-directory", home_dir.to_str().unwrap()) + .param("--ip", "1.2.3.4"); + let mut bootstrapper_config = BootstrapperConfig::new(); let multi_config = make_new_test_multi_config( &app_node(), - vec![Box::new(CommandLineVcl::new(ArgsBuilder::new().into()))], + vec![ + Box::new(CommandLineVcl::new(args.into())), + Box::new(ConfigFileVcl::new(&config_file_path, false).unwrap()), + ], ) .unwrap(); - let logger = Logger::new("test"); - let mut persistent_config = PersistentConfigurationMock::new() - .mapping_protocol_result(Ok(Some(AutomapProtocol::Pmp))); - let result = compute_mapping_protocol_opt(&multi_config, &mut persistent_config, &logger); + privileged_parse_args(&DirsWrapperReal {}, &multi_config, &mut bootstrapper_config) + .unwrap(); + let node_parse_args_configurator = UnprivilegedParseArgsConfigurationDaoNull {}; + node_parse_args_configurator + .unprivileged_parse_args( + &multi_config, + &mut bootstrapper_config, + &mut persistent_config, + &Logger::new("test logger"), + ) + .unwrap(); - assert_eq!(result, Some(AutomapProtocol::Pmp)); - // No result provided for .set_mapping_protocol; if it's called, the panic will fail this test - } - - #[test] - fn compute_mapping_protocol_saves_computed_value_if_different() { - let multi_config = make_new_test_multi_config( - &app_node(), - vec![Box::new(CommandLineVcl::new( - ArgsBuilder::new() - .param("--mapping-protocol", "IGDP") - .into(), - ))], - ) - .unwrap(); - let logger = Logger::new("test"); - let set_mapping_protocol_params_arc = Arc::new(Mutex::new(vec![])); - let mut persistent_config = make_default_persistent_configuration() - .mapping_protocol_result(Ok(Some(AutomapProtocol::Pmp))) - .set_mapping_protocol_params(&set_mapping_protocol_params_arc) - .set_mapping_protocol_result(Ok(())); - - let result = compute_mapping_protocol_opt(&multi_config, &mut persistent_config, &logger); - - assert_eq!(result, Some(AutomapProtocol::Igdp)); - let set_mapping_protocol_params = set_mapping_protocol_params_arc.lock().unwrap(); - assert_eq!( - *set_mapping_protocol_params, - vec![Some(AutomapProtocol::Igdp)] - ); - } - - #[test] - fn compute_mapping_protocol_blanks_database_if_command_line_with_missing_value() { - let multi_config = make_simplified_multi_config(["MASQNode", "--mapping-protocol"]); - let logger = Logger::new("test"); - let set_mapping_protocol_params_arc = Arc::new(Mutex::new(vec![])); - let mut persistent_config = PersistentConfigurationMock::new() - .mapping_protocol_result(Ok(Some(AutomapProtocol::Pmp))) - .set_mapping_protocol_params(&set_mapping_protocol_params_arc) - .set_mapping_protocol_result(Ok(())); - - let result = compute_mapping_protocol_opt(&multi_config, &mut persistent_config, &logger); - - assert_eq!(result, None); - let set_mapping_protocol_params = set_mapping_protocol_params_arc.lock().unwrap(); - assert_eq!(*set_mapping_protocol_params, vec![None]); - } - - #[test] - fn compute_mapping_protocol_does_not_resave_entry_if_no_change() { - let multi_config = make_simplified_multi_config(["MASQNode", "--mapping-protocol", "igdp"]); - let logger = Logger::new("test"); - let mut persistent_config = PersistentConfigurationMock::new() - .mapping_protocol_result(Ok(Some(AutomapProtocol::Igdp))); - - let result = compute_mapping_protocol_opt(&multi_config, &mut persistent_config, &logger); - - assert_eq!(result, Some(AutomapProtocol::Igdp)); - // No result provided for .set_mapping_protocol; if it's called, the panic will fail this test - } - - #[test] - fn compute_mapping_protocol_logs_and_uses_none_if_saved_mapping_protocol_cannot_be_read() { - init_test_logging(); - let multi_config = make_simplified_multi_config(["MASQNode"]); - let logger = Logger::new("BAD_MP_READ"); - let mut persistent_config = PersistentConfigurationMock::new() - .mapping_protocol_result(Err(PersistentConfigError::NotPresent)); - - let result = compute_mapping_protocol_opt(&multi_config, &mut persistent_config, &logger); - - assert_eq!(result, None); - // No result provided for .set_mapping_protocol; if it's called, the panic will fail this test - TestLogHandler::new().exists_log_containing( - "WARN: BAD_MP_READ: Could not read mapping protocol from database: NotPresent", - ); - } - - #[test] - fn compute_mapping_protocol_logs_and_moves_on_if_mapping_protocol_cannot_be_saved() { - init_test_logging(); - let multi_config = make_simplified_multi_config(["MASQNode", "--mapping-protocol", "IGDP"]); - let logger = Logger::new("BAD_MP_WRITE"); - let mut persistent_config = PersistentConfigurationMock::new() - .mapping_protocol_result(Ok(Some(AutomapProtocol::Pcp))) - .set_mapping_protocol_result(Err(PersistentConfigError::NotPresent)); - - let result = compute_mapping_protocol_opt(&multi_config, &mut persistent_config, &logger); - - assert_eq!(result, Some(AutomapProtocol::Igdp)); - TestLogHandler::new().exists_log_containing( - "WARN: BAD_MP_WRITE: Could not save mapping protocol to database: NotPresent", - ); - } - - fn make_default_cli_params() -> ArgsBuilder { - ArgsBuilder::new().param("--ip", "1.2.3.4") - } - - #[test] - fn make_neighborhood_config_standard_happy_path() { - running_test(); - let multi_config = make_new_test_multi_config( - &app_node(), - vec![Box::new(CommandLineVcl::new( - ArgsBuilder::new() - .param("--neighborhood-mode", "standard") - .param("--ip", "1.2.3.4") - .param( - "--neighbors", - "masq://eth-mainnet:mhtjjdMt7Gyoebtb1yiK0hdaUx6j84noHdaAHeDR1S4@1.2.3.4:1234/2345,masq://eth-mainnet:Si06R3ulkOjJOLw1r2R9GOsY87yuinHU_IHK2FJyGnk@2.3.4.5:3456/4567", - ) - .into(), - ))] - ).unwrap(); - - let result = make_neighborhood_config( - &multi_config, - &mut make_default_persistent_configuration(), - &mut BootstrapperConfig::new(), - ); - - let dummy_cryptde = CryptDEReal::new(TEST_DEFAULT_CHAIN); - assert_eq!( - result, - Ok(NeighborhoodConfig { - mode: NeighborhoodMode::Standard( - NodeAddr::new(&IpAddr::from_str("1.2.3.4").unwrap(), &[]), - vec![ - NodeDescriptor::try_from(( - &dummy_cryptde as &dyn CryptDE, - "masq://eth-mainnet:mhtjjdMt7Gyoebtb1yiK0hdaUx6j84noHdaAHeDR1S4@1.2.3.4:1234/2345" - )) - .unwrap(), - NodeDescriptor::try_from(( - &dummy_cryptde as &dyn CryptDE, - "masq://eth-mainnet:Si06R3ulkOjJOLw1r2R9GOsY87yuinHU_IHK2FJyGnk@2.3.4.5:3456/4567" - )) - .unwrap() - ], - DEFAULT_RATE_PACK - ) - }) - ); - } - - #[test] - fn make_neighborhood_config_standard_missing_ip() { - running_test(); - let multi_config = make_new_test_multi_config( - &app_node(), - vec![Box::new(CommandLineVcl::new( - ArgsBuilder::new() - .param("--neighborhood-mode", "standard") - .param( - "--neighbors", - "masq://eth-mainnet:QmlsbA@1.2.3.4:1234/2345,masq://eth-mainnet:VGVk@2.3.4.5:3456/4567", - ) - .param("--fake-public-key", "booga") - .into(), - ))], - ) - .unwrap(); - - let result = make_neighborhood_config( - &multi_config, - &mut make_default_persistent_configuration(), - &mut BootstrapperConfig::new(), - ); - - let node_addr = match result { - Ok(NeighborhoodConfig { - mode: NeighborhoodMode::Standard(node_addr, _, _), - }) => node_addr, - x => panic!("Wasn't expecting {:?}", x), - }; - assert_eq!(node_addr.ip_addr(), IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))); - } - - #[test] - fn make_neighborhood_config_originate_only_doesnt_need_ip() { - running_test(); - let multi_config = make_new_test_multi_config( - &app_node(), - vec![Box::new(CommandLineVcl::new( - ArgsBuilder::new() - .param("--neighborhood-mode", "originate-only") - .param( - "--neighbors", - "masq://eth-mainnet:QmlsbA@1.2.3.4:1234/2345,masq://eth-mainnet:VGVk@2.3.4.5:3456/4567", - ) - .param("--fake-public-key", "booga") - .into(), - ))], - ) - .unwrap(); - - let result = make_neighborhood_config( - &multi_config, - &mut make_default_persistent_configuration(), - &mut BootstrapperConfig::new(), - ); - - assert_eq!( - result, - Ok(NeighborhoodConfig { - mode: NeighborhoodMode::OriginateOnly( - vec![ - NodeDescriptor::try_from(( - main_cryptde(), - "masq://eth-mainnet:QmlsbA@1.2.3.4:1234/2345" - )) - .unwrap(), - NodeDescriptor::try_from(( - main_cryptde(), - "masq://eth-mainnet:VGVk@2.3.4.5:3456/4567" - )) - .unwrap() - ], - DEFAULT_RATE_PACK - ) - }) - ); - } - - #[test] - fn make_neighborhood_config_originate_only_does_need_at_least_one_neighbor() { - running_test(); - let multi_config = make_new_test_multi_config( - &app_node(), - vec![Box::new(CommandLineVcl::new( - ArgsBuilder::new() - .param("--neighborhood-mode", "originate-only") - .into(), - ))], - ) - .unwrap(); - - let result = make_neighborhood_config( - &multi_config, - &mut make_default_persistent_configuration().check_password_result(Ok(false)), - &mut BootstrapperConfig::new(), - ); - - assert_eq! (result, Err(ConfiguratorError::required("neighborhood-mode", "Node cannot run as --neighborhood-mode originate-only without --neighbors specified"))) - } - - #[test] - fn make_neighborhood_config_consume_only_doesnt_need_ip() { - running_test(); - let multi_config = make_new_test_multi_config( - &app_node(), - vec![Box::new(CommandLineVcl::new( - ArgsBuilder::new() - .param("--neighborhood-mode", "consume-only") - .param( - "--neighbors", - "masq://eth-mainnet:QmlsbA@1.2.3.4:1234/2345,masq://eth-mainnet:VGVk@2.3.4.5:3456/4567", - ) - .param("--fake-public-key", "booga") - .into(), - ))], - ) - .unwrap(); - - let result = make_neighborhood_config( - &multi_config, - &mut make_default_persistent_configuration(), - &mut BootstrapperConfig::new(), - ); - - assert_eq!( - result, - Ok(NeighborhoodConfig { - mode: NeighborhoodMode::ConsumeOnly(vec![ - NodeDescriptor::try_from(( - main_cryptde(), - "masq://eth-mainnet:QmlsbA@1.2.3.4:1234/2345" - )) - .unwrap(), - NodeDescriptor::try_from(( - main_cryptde(), - "masq://eth-mainnet:VGVk@2.3.4.5:3456/4567" - )) - .unwrap() - ],) - }) - ); - } - - #[test] - fn make_neighborhood_config_consume_only_rejects_dns_servers_and_needs_at_least_one_neighbor() { - running_test(); - let multi_config = make_new_test_multi_config( - &app_node(), - vec![Box::new(CommandLineVcl::new( - ArgsBuilder::new() - .param("--neighborhood-mode", "consume-only") - .param("--dns-servers", "1.1.1.1") - .param("--fake-public-key", "booga") - .into(), - ))], - ) - .unwrap(); - - let result = make_neighborhood_config( - &multi_config, - &mut make_default_persistent_configuration(), - &mut BootstrapperConfig::new(), - ); - - assert_eq!( - result, - Err(ConfiguratorError::required( - "neighborhood-mode", - "Node cannot run as --neighborhood-mode consume-only without --neighbors specified" - ) - .another_required( - "neighborhood-mode", - "Node cannot run as --neighborhood-mode consume-only if --dns-servers is specified" - )) - ) - } - - #[test] - fn make_neighborhood_config_zero_hop_doesnt_need_ip_or_neighbors() { - running_test(); - let multi_config = make_new_test_multi_config( - &app_node(), - vec![Box::new(CommandLineVcl::new( - ArgsBuilder::new() - .param("--neighborhood-mode", "zero-hop") - .into(), - ))], - ) - .unwrap(); - - let result = make_neighborhood_config( - &multi_config, - &mut make_default_persistent_configuration().check_password_result(Ok(false)), - &mut BootstrapperConfig::new(), - ); - - assert_eq!( - result, - Ok(NeighborhoodConfig { - mode: NeighborhoodMode::ZeroHop - }) - ); - } - - #[test] - fn make_neighborhood_config_zero_hop_cant_tolerate_ip() { - running_test(); - let multi_config = make_new_test_multi_config( - &app_node(), - vec![Box::new(CommandLineVcl::new( - ArgsBuilder::new() - .param("--neighborhood-mode", "zero-hop") - .param("--ip", "1.2.3.4") - .into(), - ))], - ) - .unwrap(); - - let result = make_neighborhood_config( - &multi_config, - &mut make_default_persistent_configuration().check_password_result(Ok(false)), - &mut BootstrapperConfig::new(), - ); - - assert_eq!( - result, - Err(ConfiguratorError::required( - "neighborhood-mode", - "Node cannot run as --neighborhood-mode zero-hop if --ip is specified" - )) - ) - } - - #[test] - fn making_sure_that_neighbors_are_validated_despite_zero_hop_mode() { - //we need this to be able to pre-configure the database - running_test(); - let multi_config = make_new_test_multi_config( - &app_node(), - vec![Box::new(CommandLineVcl::new( - ArgsBuilder::new() - .param("--neighborhood-mode", "zero-hop") - .param("--neighbors", "masq://eth-spacenet:QmlsbA@1.2.3.4:1234") - .param("--fake-public-key", "booga") - .into(), - ))], - ) - .unwrap(); - - let result = make_neighborhood_config( - &multi_config, - &mut make_default_persistent_configuration(), - &mut BootstrapperConfig::new(), - ); - - assert_eq!( - result, - Err(ConfiguratorError { - param_errors: vec![ParamError { - parameter: "neighbors".to_string(), - reason: "Chain identifier 'eth-spacenet' is not valid; possible values are \ - 'polygon-mainnet', 'eth-mainnet', 'polygon-mumbai', 'eth-ropsten' while \ - formatted as 'masq://:@'" - .to_string() - }] - }) - ) - } - - #[test] - fn get_public_ip_returns_sentinel_if_multiconfig_provides_none() { - let multi_config = make_new_test_multi_config(&app_node(), vec![]).unwrap(); - - let result = get_public_ip(&multi_config); - - assert_eq!(result, Ok(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)))); - } - - #[test] - fn get_public_ip_uses_multi_config() { - let args = ArgsBuilder::new().param("--ip", "4.3.2.1"); - let vcl = Box::new(CommandLineVcl::new(args.into())); - let multi_config = make_new_test_multi_config(&app_node(), vec![vcl]).unwrap(); - - let result = get_public_ip(&multi_config); - - assert_eq!(result, Ok(IpAddr::from_str("4.3.2.1").unwrap())); - } - - #[test] - fn get_past_neighbors_handles_good_password_but_no_past_neighbors() { - running_test(); - let mut persistent_config = make_default_persistent_configuration() - .check_password_result(Ok(false)) - .past_neighbors_result(Ok(None)); - let mut unprivileged_config = BootstrapperConfig::new(); - unprivileged_config.db_password_opt = Some("password".to_string()); - - let result = get_past_neighbors(&mut persistent_config, &mut unprivileged_config).unwrap(); - - assert!(result.is_empty()); - } - - #[test] - fn get_past_neighbors_handles_non_password_error() { - running_test(); - let mut persistent_config = PersistentConfigurationMock::new() - .check_password_result(Ok(false)) - .past_neighbors_result(Err(PersistentConfigError::NotPresent)); - let mut unprivileged_config = BootstrapperConfig::new(); - unprivileged_config.db_password_opt = Some("password".to_string()); - - let result = get_past_neighbors(&mut persistent_config, &mut unprivileged_config); - - assert_eq!( - result, - Err(ConfiguratorError::new(vec![ParamError::new( - "[past neighbors]", - "NotPresent" - )])) - ); - } - - #[test] - fn get_past_neighbors_handles_unavailable_password() { - //sets the password in the database - we'll have to resolve if the use case is appropriate - running_test(); - let mut persistent_config = make_default_persistent_configuration() - .check_password_result(Ok(true)) - .change_password_result(Ok(())); - let mut unprivileged_config = BootstrapperConfig::new(); - unprivileged_config.db_password_opt = Some("password".to_string()); - - let result = get_past_neighbors(&mut persistent_config, &mut unprivileged_config).unwrap(); - - assert!(result.is_empty()); - } - - #[test] - fn convert_ci_configs_handles_whitespaces_between_descriptors_and_commas() { - let multi_config = make_simplified_multi_config([ - "program", - "--chain", - "eth-ropsten", - "--fake-public-key", - "ABCDE", - "--neighbors", - "masq://eth-ropsten:abJ5XvhVbmVyGejkYUkmftF09pmGZGKg_PzRNnWQxFw@1.2.3.4:5555, masq://eth-ropsten:gBviQbjOS3e5ReFQCvIhUM3i02d1zPleo1iXg_EN6zQ@86.75.30.9:5542 , masq://eth-ropsten:A6PGHT3rRjaeFpD_rFi3qGEXAVPq7bJDfEUZpZaIyq8@14.10.50.6:10504", - ]); - let public_key = PublicKey::new(b"ABCDE"); - let cryptde = CryptDENull::from(&public_key, Chain::EthRopsten); - let cryptde_traitified = &cryptde as &dyn CryptDE; - - let result = convert_ci_configs(&multi_config); - - assert_eq!(result, Ok(Some( - vec![ - NodeDescriptor::try_from((cryptde_traitified, "masq://eth-ropsten:abJ5XvhVbmVyGejkYUkmftF09pmGZGKg_PzRNnWQxFw@1.2.3.4:5555")).unwrap(), - NodeDescriptor::try_from((cryptde_traitified, "masq://eth-ropsten:gBviQbjOS3e5ReFQCvIhUM3i02d1zPleo1iXg_EN6zQ@86.75.30.9:5542")).unwrap(), - NodeDescriptor::try_from((cryptde_traitified, "masq://eth-ropsten:A6PGHT3rRjaeFpD_rFi3qGEXAVPq7bJDfEUZpZaIyq8@14.10.50.6:10504")).unwrap()]) - ) - ) - } - - #[test] - fn convert_ci_configs_does_not_like_neighbors_with_bad_syntax() { - running_test(); - let multi_config = make_simplified_multi_config(["program", "--neighbors", "ooga,booga"]); - - let result = convert_ci_configs(&multi_config).err(); - - assert_eq!( - result, - Some(ConfiguratorError::new(vec![ - ParamError::new( - "neighbors", - "Prefix or more missing. Should be 'masq://:@', not 'ooga'" - ), - ParamError::new( - "neighbors", - "Prefix or more missing. Should be 'masq://:@', not 'booga'" - ), - ])) - ); - } - - #[test] - fn convert_ci_configs_complains_about_descriptor_without_node_address_when_mainnet_required() { - let descriptor = format!( - "masq://{}:abJ5XvhVbmVyGejkYUkmftF09pmGZGKg_PzRNnWQxFw@:", - DEFAULT_CHAIN.rec().literal_identifier - ); - let multi_config = make_simplified_multi_config(["program", "--neighbors", &descriptor]); - - let result = convert_ci_configs(&multi_config); - - assert_eq!(result,Err(ConfiguratorError::new(vec![ParamError::new("neighbors", &format!("Neighbors supplied without ip addresses and ports are not valid: '{}:",&descriptor[..descriptor.len()-1]))]))); - } - - #[test] - fn convert_ci_configs_complains_about_descriptor_without_node_address_when_test_chain_required() - { - let multi_config = make_simplified_multi_config([ - "program", - "--chain", - "eth-ropsten", - "--neighbors", - "masq://eth-ropsten:abJ5XvhVbmVyGejkYUkmftF09pmGZGKg_PzRNnWQxFw@:", - ]); - - let result = convert_ci_configs(&multi_config); - - assert_eq!(result,Err(ConfiguratorError::new(vec![ParamError::new("neighbors", "Neighbors supplied without ip addresses and ports are not valid: 'masq://eth-ropsten:abJ5XvhVbmVyGejkYUkmftF09pmGZGKg_PzRNnWQxFw@:")]))) - } - - #[test] - fn server_initializer_collected_params_can_read_parameters_from_config_file() { - running_test(); - let _guard = EnvironmentGuard::new(); - let home_dir = ensure_node_home_directory_exists( - "node_configurator", - "server_initializer_collected_params_can_read_parameters_from_config_file", - ); - { - let mut config_file = File::create(home_dir.join("config.toml")).unwrap(); - config_file - .write_all(b"dns-servers = \"111.111.111.111,222.222.222.222\"\n") - .unwrap(); - } - let directory_wrapper = make_pre_populated_mocked_directory_wrapper(); - - let gathered_params = server_initializer_collected_params( - &directory_wrapper, - &array_of_borrows_to_vec(&["", "--data-directory", home_dir.to_str().unwrap()]), - ) - .unwrap(); - - let multi_config = gathered_params.multi_config; - assert_eq!( - value_m!(multi_config, "dns-servers", String).unwrap(), - "111.111.111.111,222.222.222.222".to_string() - ); - } - - #[test] - fn can_read_dns_servers_and_consuming_private_key_from_config_file() { - running_test(); - let home_dir = ensure_node_home_directory_exists( - "node_configurator", - "can_read_wallet_parameters_from_config_file", - ); - let mut persistent_config = PersistentConfigurationReal::new(Box::new(ConfigDaoReal::new( - DbInitializerReal::default() - .initialize(&home_dir.clone(), true, MigratorConfig::test_default()) - .unwrap(), - ))); - let consuming_private_key = - "89ABCDEF89ABCDEF89ABCDEF89ABCDEF89ABCDEF89ABCDEF89ABCDEF89ABCDEF"; - let config_file_path = home_dir.join("config.toml"); - { - let mut config_file = File::create(&config_file_path).unwrap(); - short_writeln!( - config_file, - "consuming-private-key = \"{}\"", - consuming_private_key - ); - } - let args = ArgsBuilder::new() - .param("--data-directory", home_dir.to_str().unwrap()) - .param("--ip", "1.2.3.4"); - let mut bootstrapper_config = BootstrapperConfig::new(); - let multi_config = make_new_test_multi_config( - &app_node(), - vec![ - Box::new(CommandLineVcl::new(args.into())), - Box::new(ConfigFileVcl::new(&config_file_path, false).unwrap()), - ], - ) - .unwrap(); - - privileged_parse_args(&DirsWrapperReal {}, &multi_config, &mut bootstrapper_config) - .unwrap(); - unprivileged_parse_args( - &multi_config, - &mut bootstrapper_config, - &mut persistent_config, - &Logger::new("test logger"), - ) - .unwrap(); - let consuming_private_key_bytes: Vec = consuming_private_key.from_hex().unwrap(); - let consuming_keypair = - Bip32ECKeyProvider::from_raw_secret(consuming_private_key_bytes.as_ref()).unwrap(); - assert_eq!( - bootstrapper_config.consuming_wallet_opt, - Some(Wallet::from(consuming_keypair)), - ); - - let public_key = PublicKey::new(&[1, 2, 3]); - let payer = bootstrapper_config - .consuming_wallet_opt - .unwrap() - .as_payer(&public_key, &TEST_DEFAULT_CHAIN.rec().contract); - let cryptdenull = CryptDENull::from(&public_key, TEST_DEFAULT_CHAIN); - assert!( - payer.owns_secret_key(&cryptdenull.digest()), - "Neighborhood config should have a WalletKind::KeyPair wallet" - ); + let consuming_private_key_bytes: Vec = consuming_private_key.from_hex().unwrap(); + let consuming_keypair = + Bip32ECKeyProvider::from_raw_secret(consuming_private_key_bytes.as_ref()).unwrap(); + assert_eq!( + bootstrapper_config.consuming_wallet_opt, + Some(Wallet::from(consuming_keypair)), + ); + let public_key = PublicKey::new(&[1, 2, 3]); + let payer = bootstrapper_config + .consuming_wallet_opt + .unwrap() + .as_payer(&public_key, &TEST_DEFAULT_CHAIN.rec().contract); + let cryptdenull = CryptDENull::from(&public_key, TEST_DEFAULT_CHAIN); + assert!( + payer.owns_secret_key(&cryptdenull.digest()), + "Neighborhood config should have a WalletKind::KeyPair wallet" + ); } #[test] @@ -1616,320 +562,33 @@ mod tests { SocketAddr::from_str("12.34.56.78:53").unwrap(), SocketAddr::from_str("23.45.67.89:53").unwrap() ), - ); - assert_eq!(config.ui_gateway_config.ui_port, 5335); - assert_eq!( - config.neighborhood_config, - NeighborhoodConfig { - mode: NeighborhoodMode::ZeroHop // not populated on the privileged side - } - ); - assert_eq!( - config.blockchain_bridge_config.blockchain_service_url_opt, - None, - ); - assert_eq!(config.data_directory, home_dir); - assert_eq!( - config.main_cryptde_null_opt.unwrap().public_key(), - &PublicKey::new(&[1, 2, 3, 4]), - ); - assert_eq!( - config.real_user, - RealUser::new(Some(999), Some(999), Some(PathBuf::from("/home/booga"))) - ); - } - - #[test] - fn privileged_parse_args_creates_configuration_with_defaults() { - running_test(); - let args = ArgsBuilder::new().param("--ip", "1.2.3.4"); - let mut config = BootstrapperConfig::new(); - let vcls: Vec> = - vec![Box::new(CommandLineVcl::new(args.into()))]; - let multi_config = make_new_test_multi_config(&app_node(), vcls).unwrap(); - - privileged_parse_args(&DirsWrapperReal {}, &multi_config, &mut config).unwrap(); - - assert_eq!( - Some(PathBuf::from("config.toml")), - value_m!(multi_config, "config-file", PathBuf) - ); - assert_eq!( - config.dns_servers, - vec!(SocketAddr::from_str("1.1.1.1:53").unwrap()) - ); - assert_eq!(config.crash_point, CrashPoint::None); - assert_eq!(config.ui_gateway_config.ui_port, DEFAULT_UI_PORT); - assert!(config.main_cryptde_null_opt.is_none()); - assert_eq!( - config.real_user, - RealUser::new(None, None, None).populate(&DirsWrapperReal {}) - ); - } - - #[test] - #[cfg(not(target_os = "windows"))] - fn privileged_parse_args_with_real_user_defaults_data_directory_properly() { - running_test(); - let args = ArgsBuilder::new() - .param("--ip", "1.2.3.4") - .param("--real-user", "::/home/booga"); - let mut config = BootstrapperConfig::new(); - let vcls: Vec> = - vec![Box::new(CommandLineVcl::new(args.into()))]; - let multi_config = make_new_test_multi_config(&app_node(), vcls).unwrap(); - - privileged_parse_args(&DirsWrapperReal {}, &multi_config, &mut config).unwrap(); - - #[cfg(target_os = "linux")] - assert_eq!( - config.data_directory, - PathBuf::from("/home/booga/.local/share/MASQ") - .join(DEFAULT_CHAIN.rec().literal_identifier) - ); - - #[cfg(target_os = "macos")] - assert_eq!( - config.data_directory, - PathBuf::from("/home/booga/Library/Application Support/MASQ") - .join(DEFAULT_CHAIN.rec().literal_identifier) - ); - } - - /////////////////////// - /////////////////////// - /////////////////////// - - #[test] - fn unprivileged_parse_args_creates_configurations() { - running_test(); - let home_dir = ensure_node_home_directory_exists( - "node_configurator", - "unprivileged_parse_args_creates_configurations", - ); - let config_dao: Box = Box::new(ConfigDaoReal::new( - DbInitializerReal::default() - .initialize(&home_dir.clone(), true, MigratorConfig::test_default()) - .unwrap(), - )); - let consuming_private_key_text = - "ABCDEF01ABCDEF01ABCDEF01ABCDEF01ABCDEF01ABCDEF01ABCDEF01ABCDEF01"; - let consuming_private_key = PlainData::from_str(consuming_private_key_text).unwrap(); - let mut persistent_config = PersistentConfigurationReal::new(config_dao); - let password = "secret-db-password"; - let args = ArgsBuilder::new() - .param("--config-file", "specified_config.toml") - .param("--dns-servers", "12.34.56.78,23.45.67.89") - .param( - "--neighbors", - "masq://eth-mainnet:QmlsbA@1.2.3.4:1234/2345,masq://eth-mainnet:VGVk@2.3.4.5:3456/4567", - ) - .param("--ip", "34.56.78.90") - .param("--clandestine-port", "1234") - .param("--ui-port", "5335") - .param("--data-directory", home_dir.to_str().unwrap()) - .param("--blockchain-service-url", "http://127.0.0.1:8545") - .param("--log-level", "trace") - .param("--fake-public-key", "AQIDBA") - .param("--db-password", password) - .param("--consuming-private-key", consuming_private_key_text) - .param( - "--earning-wallet", - "0x0123456789012345678901234567890123456789", - ) - .param("--mapping-protocol", "pcp") - .param("--real-user", "999:999:/home/booga"); - let mut config = BootstrapperConfig::new(); - let vcls: Vec> = - vec![Box::new(CommandLineVcl::new(args.into()))]; - let multi_config = make_new_test_multi_config(&app_node(), vcls).unwrap(); - - unprivileged_parse_args( - &multi_config, - &mut config, - &mut persistent_config, - &Logger::new("test logger"), - ) - .unwrap(); - - assert_eq!( - value_m!(multi_config, "config-file", PathBuf), - Some(PathBuf::from("specified_config.toml")), - ); - assert_eq!( - config.blockchain_bridge_config.blockchain_service_url_opt, - Some("http://127.0.0.1:8545".to_string()) - ); - assert_eq!( - config.earning_wallet, - Wallet::from_str("0x0123456789012345678901234567890123456789").unwrap() - ); - assert_eq!(Some(1234u16), config.clandestine_port_opt); - assert_eq!( - config.earning_wallet, - Wallet::from_str("0x0123456789012345678901234567890123456789").unwrap() - ); - assert_eq!( - config.consuming_wallet_opt, - Some(Wallet::from( - Bip32ECKeyProvider::from_raw_secret(consuming_private_key.as_slice()).unwrap() - )), - ); - assert_eq!( - config.neighborhood_config, - NeighborhoodConfig { - mode: NeighborhoodMode::Standard( - NodeAddr::new(&IpAddr::from_str("34.56.78.90").unwrap(), &[]), - vec![ - NodeDescriptor::try_from(( - main_cryptde(), - "masq://eth-mainnet:QmlsbA@1.2.3.4:1234/2345" - )) - .unwrap(), - NodeDescriptor::try_from(( - main_cryptde(), - "masq://eth-mainnet:VGVk@2.3.4.5:3456/4567" - )) - .unwrap(), - ], - DEFAULT_RATE_PACK.clone() - ) - } - ); - assert_eq!(config.db_password_opt, Some(password.to_string())); - assert_eq!(config.mapping_protocol_opt, Some(AutomapProtocol::Pcp)); - } - - #[test] - fn unprivileged_parse_args_creates_configuration_with_defaults() { - running_test(); - let args = ArgsBuilder::new(); - let mut config = BootstrapperConfig::new(); - let vcls: Vec> = - vec![Box::new(CommandLineVcl::new(args.into()))]; - let multi_config = make_new_test_multi_config(&app_node(), vcls).unwrap(); - let mut persistent_config = make_default_persistent_configuration() - .mapping_protocol_result(Ok(None)) - .check_password_result(Ok(false)); - - unprivileged_parse_args( - &multi_config, - &mut config, - &mut persistent_config, - &Logger::new("test logger"), - ) - .unwrap(); - - assert_eq!( - Some(PathBuf::from("config.toml")), - value_m!(multi_config, "config-file", PathBuf) - ); - assert_eq!(None, config.clandestine_port_opt); - assert!(config - .neighborhood_config - .mode - .neighbor_configs() - .is_empty()); - assert_eq!( - config - .neighborhood_config - .mode - .node_addr_opt() - .unwrap() - .ip_addr(), - IpAddr::from_str("0.0.0.0").unwrap(), - ); - assert_eq!(config.earning_wallet, DEFAULT_EARNING_WALLET.clone(),); - assert_eq!(config.consuming_wallet_opt, None); - assert_eq!(config.mapping_protocol_opt, None); - } - - #[test] - fn unprivileged_parse_args_with_neighbor_and_mapping_protocol_in_database_but_not_command_line() - { - running_test(); - let args = ArgsBuilder::new() - .param("--ip", "1.2.3.4") - .param("--fake-public-key", "BORSCHT") - .param("--db-password", "password"); - let mut config = BootstrapperConfig::new(); - config.db_password_opt = Some("password".to_string()); - let vcls: Vec> = - vec![Box::new(CommandLineVcl::new(args.into()))]; - let multi_config = make_new_test_multi_config(&app_node(), vcls).unwrap(); - let set_mapping_protocol_params_arc = Arc::new(Mutex::new(vec![])); - let past_neighbors_params_arc = Arc::new(Mutex::new(vec![])); - let mut persistent_configuration = make_persistent_config( - Some("password"), - None, - None, - None, - Some("masq://eth-ropsten:AQIDBA@1.2.3.4:1234,masq://eth-ropsten:AgMEBQ@2.3.4.5:2345"), - ) - .check_password_result(Ok(false)) - .set_mapping_protocol_params(&set_mapping_protocol_params_arc) - .past_neighbors_params(&past_neighbors_params_arc) - .blockchain_service_url_result(Ok(None)); - - unprivileged_parse_args( - &multi_config, - &mut config, - &mut persistent_configuration, - &Logger::new("test logger"), - ) - .unwrap(); - + ); + assert_eq!(config.ui_gateway_config.ui_port, 5335); assert_eq!( - config.neighborhood_config.mode.neighbor_configs(), - &[ - NodeDescriptor::try_from(( - main_cryptde(), - "masq://eth-ropsten:AQIDBA@1.2.3.4:1234" - )) - .unwrap(), - NodeDescriptor::try_from(( - main_cryptde(), - "masq://eth-ropsten:AgMEBQ@2.3.4.5:2345" - )) - .unwrap(), - ] + config.neighborhood_config, + NeighborhoodConfig { + mode: NeighborhoodMode::ZeroHop // not populated on the privileged side + } ); - let past_neighbors_params = past_neighbors_params_arc.lock().unwrap(); - assert_eq!(past_neighbors_params[0], "password".to_string()); - assert_eq!(config.mapping_protocol_opt, Some(AutomapProtocol::Pcp)); - let set_mapping_protocol_params = set_mapping_protocol_params_arc.lock().unwrap(); - assert_eq!(*set_mapping_protocol_params, vec![]); - } - - #[test] - fn unprivileged_parse_args_with_blockchain_service_in_database_but_not_command_line() { - running_test(); - let args = ArgsBuilder::new().param("--neighborhood-mode", "zero-hop"); - let mut config = BootstrapperConfig::new(); - let vcls: Vec> = - vec![Box::new(CommandLineVcl::new(args.into()))]; - let multi_config = make_new_test_multi_config(&app_node(), vcls).unwrap(); - let mut persistent_configuration = make_persistent_config(None, None, None, None, None) - .blockchain_service_url_result(Ok(Some("https://infura.io/ID".to_string()))); - - unprivileged_parse_args( - &multi_config, - &mut config, - &mut persistent_configuration, - &Logger::new("test"), - ) - .unwrap(); - assert_eq!( config.blockchain_bridge_config.blockchain_service_url_opt, - Some("https://infura.io/ID".to_string()) + None, + ); + assert_eq!(config.data_directory, home_dir); + assert_eq!( + config.main_cryptde_null_opt.unwrap().public_key(), + &PublicKey::new(&[1, 2, 3, 4]), + ); + assert_eq!( + config.real_user, + RealUser::new(Some(999), Some(999), Some(PathBuf::from("/home/booga"))) ); } #[test] - fn privileged_parse_args_with_no_command_line_params() { + fn privileged_parse_args_creates_configuration_with_defaults() { running_test(); - let args = ArgsBuilder::new(); + let args = ArgsBuilder::new().param("--ip", "1.2.3.4"); let mut config = BootstrapperConfig::new(); let vcls: Vec> = vec![Box::new(CommandLineVcl::new(args.into()))]; @@ -1955,324 +614,59 @@ mod tests { } #[test] - fn unprivileged_parse_args_with_mapping_protocol_both_on_command_line_and_in_database() { + #[cfg(not(target_os = "windows"))] + fn privileged_parse_args_with_real_user_defaults_data_directory_properly() { running_test(); - let args = ArgsBuilder::new().param("--mapping-protocol", "pmp"); + let args = ArgsBuilder::new() + .param("--ip", "1.2.3.4") + .param("--real-user", "::/home/booga"); let mut config = BootstrapperConfig::new(); let vcls: Vec> = vec![Box::new(CommandLineVcl::new(args.into()))]; let multi_config = make_new_test_multi_config(&app_node(), vcls).unwrap(); - let set_mapping_protocol_params_arc = Arc::new(Mutex::new(vec![])); - let mut persistent_config = make_default_persistent_configuration() - .mapping_protocol_result(Ok(Some(AutomapProtocol::Pcp))) - .set_mapping_protocol_params(&set_mapping_protocol_params_arc) - .set_mapping_protocol_result(Ok(())); - - unprivileged_parse_args( - &multi_config, - &mut config, - &mut persistent_config, - &Logger::new("test logger"), - ) - .unwrap(); - - assert_eq!(config.mapping_protocol_opt, Some(AutomapProtocol::Pmp)); - let set_mapping_protocol_params = set_mapping_protocol_params_arc.lock().unwrap(); - assert_eq!( - *set_mapping_protocol_params, - vec![Some(AutomapProtocol::Pmp)] - ); - } - - fn make_persistent_config( - db_password_opt: Option<&str>, - consuming_wallet_private_key_opt: Option<&str>, - earning_wallet_address_opt: Option<&str>, - gas_price_opt: Option, - past_neighbors_opt: Option<&str>, - ) -> PersistentConfigurationMock { - let consuming_wallet_private_key_opt = - consuming_wallet_private_key_opt.map(|x| x.to_string()); - let earning_wallet_opt = match earning_wallet_address_opt { - None => None, - Some(address) => Some(Wallet::from_str(address).unwrap()), - }; - let gas_price = gas_price_opt.unwrap_or(DEFAULT_GAS_PRICE); - let past_neighbors_result = match (past_neighbors_opt, db_password_opt) { - (Some(past_neighbors), Some(_)) => Ok(Some( - past_neighbors - .split(",") - .map(|s| NodeDescriptor::try_from((main_cryptde(), s)).unwrap()) - .collect::>(), - )), - _ => Ok(None), - }; - PersistentConfigurationMock::new() - .consuming_wallet_private_key_result(Ok(consuming_wallet_private_key_opt)) - .earning_wallet_address_result( - Ok(earning_wallet_address_opt.map(|ewa| ewa.to_string())), - ) - .earning_wallet_result(Ok(earning_wallet_opt)) - .gas_price_result(Ok(gas_price)) - .past_neighbors_result(past_neighbors_result) - .mapping_protocol_result(Ok(Some(AutomapProtocol::Pcp))) - } - - #[test] - fn get_wallets_with_brand_new_database_establishes_default_earning_wallet_without_requiring_password( - ) { - running_test(); - let args = ["program"]; - let multi_config = pure_test_utils::make_simplified_multi_config(args); - let mut persistent_config = make_persistent_config(None, None, None, None, None); - let mut config = BootstrapperConfig::new(); - - get_wallets(&multi_config, &mut persistent_config, &mut config).unwrap(); - - assert_eq!(config.consuming_wallet_opt, None); - assert_eq!(config.earning_wallet, DEFAULT_EARNING_WALLET.clone()); - } - - #[test] - fn get_wallets_handles_failure_of_consuming_wallet_private_key() { - let args = ["program"]; - let multi_config = pure_test_utils::make_simplified_multi_config(args); - let mut persistent_config = PersistentConfigurationMock::new() - .earning_wallet_address_result(Ok(None)) - .consuming_wallet_private_key_result(Err(PersistentConfigError::NotPresent)); - let mut config = BootstrapperConfig::new(); - config.db_password_opt = Some("password".to_string()); - - let result = get_wallets(&multi_config, &mut persistent_config, &mut config); - - assert_eq!( - result, - Err(PersistentConfigError::NotPresent.into_configurator_error("consuming-private-key")) - ); - } - - #[test] - fn earning_wallet_address_different_from_database() { - running_test(); - let args = [ - "program", - "--earning-wallet", - "0x0123456789012345678901234567890123456789", - ]; - let multi_config = pure_test_utils::make_simplified_multi_config(args); - let mut persistent_config = make_persistent_config( - None, - None, - Some("0x9876543210987654321098765432109876543210"), - None, - None, - ); - let mut config = BootstrapperConfig::new(); - - let result = get_wallets(&multi_config, &mut persistent_config, &mut config).err(); - - assert_eq! (result, Some (ConfiguratorError::new (vec![ - ParamError::new ("earning-wallet", "Cannot change to an address (0x0123456789012345678901234567890123456789) different from that previously set (0x9876543210987654321098765432109876543210)") - ]))); - } - #[test] - fn earning_wallet_address_matches_database() { - running_test(); - let args = [ - "program", - "--earning-wallet", - "0xB00FA567890123456789012345678901234b00fa", - ]; - let multi_config = pure_test_utils::make_simplified_multi_config(args); - let mut persistent_config = make_persistent_config( - None, - None, - Some("0xb00fa567890123456789012345678901234B00FA"), - None, - None, - ); - let mut config = BootstrapperConfig::new(); - - get_wallets(&multi_config, &mut persistent_config, &mut config).unwrap(); + privileged_parse_args(&DirsWrapperReal {}, &multi_config, &mut config).unwrap(); + #[cfg(target_os = "linux")] assert_eq!( - config.earning_wallet, - Wallet::new("0xB00FA567890123456789012345678901234B00FA") - ); - } - - #[test] - fn consuming_wallet_private_key_different_from_database() { - running_test(); - let consuming_private_key_hex = - "ABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD"; - let args = [ - "program", - "--consuming-private-key", - consuming_private_key_hex, - ]; - let multi_config = pure_test_utils::make_simplified_multi_config(args); - let mut persistent_config = make_persistent_config( - Some("password"), - Some("DCBADCBADCBADCBADCBADCBADCBADCBADCBADCBADCBADCBADCBADCBADCBADCBA"), - Some("0x0123456789012345678901234567890123456789"), - None, - None, + config.data_directory, + PathBuf::from("/home/booga/.local/share/MASQ") + .join(DEFAULT_CHAIN.rec().literal_identifier) ); - let mut config = BootstrapperConfig::new(); - config.db_password_opt = Some("password".to_string()); - - let result = get_wallets(&multi_config, &mut persistent_config, &mut config).err(); + #[cfg(target_os = "macos")] assert_eq!( - result, - Some(ConfiguratorError::new(vec![ParamError::new( - "consuming-private-key", - "Cannot change to a private key different from that previously set" - )])) + config.data_directory, + PathBuf::from("/home/booga/Library/Application Support/MASQ") + .join(DEFAULT_CHAIN.rec().literal_identifier) ); } #[test] - fn consuming_wallet_private_key_with_no_db_password_parameter() { + fn privileged_parse_args_with_no_command_line_params() { running_test(); - let args = ["program"]; - let multi_config = pure_test_utils::make_simplified_multi_config(args); - let mut persistent_config = make_persistent_config( - None, - Some("0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"), - Some("0xcafedeadbeefbabefacecafedeadbeefbabeface"), - None, - None, - ) - .check_password_result(Ok(false)); + let args = ArgsBuilder::new(); let mut config = BootstrapperConfig::new(); + let vcls: Vec> = + vec![Box::new(CommandLineVcl::new(args.into()))]; + let multi_config = make_new_test_multi_config(&app_node(), vcls).unwrap(); - get_wallets(&multi_config, &mut persistent_config, &mut config).unwrap(); - - assert_eq!(config.consuming_wallet_opt, None); - assert_eq!( - config.earning_wallet, - Wallet::from_str("0xcafedeadbeefbabefacecafedeadbeefbabeface").unwrap() - ); - } - - #[test] - fn unprivileged_parse_args_with_invalid_consuming_wallet_private_key_reacts_correctly() { - running_test(); - let home_directory = ensure_node_home_directory_exists( - "node_configurator", - "parse_args_with_invalid_consuming_wallet_private_key_panics_correctly", - ); - - let args = ArgsBuilder::new().param("--data-directory", home_directory.to_str().unwrap()); - let vcl_args: Vec> = vec![Box::new(NameValueVclArg::new( - &"--consuming-private-key", - &"not valid hex", - ))]; - - let faux_environment = CommandLineVcl::from(vcl_args); - - let vcls: Vec> = vec![ - Box::new(faux_environment), - Box::new(CommandLineVcl::new(args.into())), - ]; - - let result = make_new_test_multi_config(&app_node(), vcls).err().unwrap(); + privileged_parse_args(&DirsWrapperReal {}, &multi_config, &mut config).unwrap(); assert_eq!( - result, - ConfiguratorError::required("consuming-private-key", "Invalid value: not valid hex") - ) - } - - #[test] - fn unprivileged_parse_args_consuming_private_key_happy_path() { - running_test(); - let home_directory = ensure_node_home_directory_exists( - "node_configurator", - "parse_args_consuming_private_key_happy_path", + Some(PathBuf::from("config.toml")), + value_m!(multi_config, "config-file", PathBuf) ); - - let args = ArgsBuilder::new() - .param("--ip", "1.2.3.4") - .param("--data-directory", home_directory.to_str().unwrap()) - .opt("--db-password"); - let vcl_args: Vec> = vec![Box::new(NameValueVclArg::new( - &"--consuming-private-key", - &"cc46befe8d169b89db447bd725fc2368b12542113555302598430cb5d5c74ea9", - ))]; - - let faux_environment = CommandLineVcl::from(vcl_args); - - let mut config = BootstrapperConfig::new(); - config.db_password_opt = Some("password".to_string()); - let vcls: Vec> = vec![ - Box::new(faux_environment), - Box::new(CommandLineVcl::new(args.into())), - ]; - let multi_config = make_new_test_multi_config(&app_node(), vcls).unwrap(); - let stdout_writer = &mut ByteArrayWriter::new(); - - unprivileged_parse_args( - &multi_config, - &mut config, - &mut make_default_persistent_configuration() - .mapping_protocol_result(Ok(Some(AutomapProtocol::Pcp))), - &Logger::new("test logger"), - ) - .unwrap(); - - let captured_output = stdout_writer.get_string(); - let expected_output = ""; - assert!(config.consuming_wallet_opt.is_some()); assert_eq!( - format!("{}", config.consuming_wallet_opt.unwrap()), - "0x8e4d2317e56c8fd1fc9f13ba2aa62df1c5a542a7".to_string() + config.dns_servers, + vec!(SocketAddr::from_str("1.1.1.1:53").unwrap()) ); - assert_eq!(captured_output, expected_output); - } - - #[test] - fn get_db_password_shortcuts_if_its_already_gotten() { - running_test(); - let mut config = BootstrapperConfig::new(); - let mut persistent_config = - make_default_persistent_configuration().check_password_result(Ok(false)); - config.db_password_opt = Some("password".to_string()); - - let result = get_db_password(&mut config, &mut persistent_config); - - assert_eq!(result, Ok(Some("password".to_string()))); - } - - #[test] - fn get_db_password_doesnt_bother_if_database_has_no_password_yet() { - running_test(); - let mut config = BootstrapperConfig::new(); - let mut persistent_config = - make_default_persistent_configuration().check_password_result(Ok(true)); - - let result = get_db_password(&mut config, &mut persistent_config); - - assert_eq!(result, Ok(None)); - } - - #[test] - fn get_db_password_handles_database_write_error() { - running_test(); - let mut config = BootstrapperConfig::new(); - config.db_password_opt = Some("password".to_string()); - let mut persistent_config = make_default_persistent_configuration() - .check_password_result(Ok(true)) - .change_password_result(Err(PersistentConfigError::NotPresent)); - - let result = get_db_password(&mut config, &mut persistent_config); - + assert_eq!(config.crash_point, CrashPoint::None); + assert_eq!(config.ui_gateway_config.ui_port, DEFAULT_UI_PORT); + assert!(config.main_cryptde_null_opt.is_none()); assert_eq!( - result, - Err(PersistentConfigError::NotPresent.into_configurator_error("db-password")) + config.real_user, + RealUser::new(None, None, None).populate(&DirsWrapperReal {}) ); } @@ -2334,7 +728,7 @@ mod tests { running_test(); let _clap_guard = ClapGuard::new(); let subject = NodeConfiguratorStandardPrivileged::new(); - let args = ["program", "--ip", "1.2.3.4", "--chain", "dev"]; + let args = ["--ip", "1.2.3.4", "--chain", "dev"]; let config = subject .configure(&make_simplified_multi_config(args)) @@ -2348,7 +742,6 @@ mod tests { running_test(); let subject = NodeConfiguratorStandardPrivileged::new(); let args = [ - "program", "--ip", "1.2.3.4", "--chain", @@ -2367,7 +760,7 @@ mod tests { running_test(); let _clap_guard = ClapGuard::new(); let subject = NodeConfiguratorStandardPrivileged::new(); - let args = ["program", "--ip", "1.2.3.4"]; + let args = ["--ip", "1.2.3.4"]; let config = subject .configure(&make_simplified_multi_config(args)) @@ -2388,7 +781,6 @@ mod tests { running_test(); let subject = NodeConfiguratorStandardPrivileged::new(); let args = [ - "program", "--ip", "1.2.3.4", "--chain", @@ -2415,7 +807,7 @@ mod tests { let mut subject = NodeConfiguratorStandardUnprivileged::new(&BootstrapperConfig::new()); subject.privileged_config = BootstrapperConfig::new(); subject.privileged_config.data_directory = data_dir; - let args = ["program", "--ip", "1.2.3.4", "--gas-price", "57"]; + let args = ["--ip", "1.2.3.4", "--gas-price", "57"]; let config = subject .configure(&make_simplified_multi_config(args)) @@ -2435,7 +827,7 @@ mod tests { let mut subject = NodeConfiguratorStandardUnprivileged::new(&BootstrapperConfig::new()); subject.privileged_config = BootstrapperConfig::new(); subject.privileged_config.data_directory = data_dir; - let args = ["program", "--ip", "1.2.3.4"]; + let args = ["--ip", "1.2.3.4"]; let config = subject .configure(&make_simplified_multi_config(args)) @@ -2514,139 +906,6 @@ mod tests { assert_eq!(*set_clandestine_port_params, vec![1234]); } - #[test] - fn configure_zero_hop_with_neighbors_supplied() { - running_test(); - let set_past_neighbors_params_arc = Arc::new(Mutex::new(vec![])); - let mut config = BootstrapperConfig::new(); - let mut persistent_config = make_default_persistent_configuration() - .set_past_neighbors_params(&set_past_neighbors_params_arc) - .set_past_neighbors_result(Ok(())); - let multi_config = make_simplified_multi_config([ - "MASQNode", - "--chain", - "eth-ropsten", - "--neighbors", - "masq://eth-ropsten:UJNoZW5p-PDVqEjpr3b_8jZ_93yPG8i5dOAgE1bhK_A@2.3.4.5:2345", - "--db-password", - "password", - "--neighborhood-mode", - "zero-hop", - "--fake-public-key", - "booga", - ]); - - let _ = unprivileged_parse_args( - &multi_config, - &mut config, - &mut persistent_config, - &Logger::new("test"), - ) - .unwrap(); - - assert_eq!( - config.neighborhood_config, - NeighborhoodConfig { - mode: NeighborhoodMode::ZeroHop - } - ); - let set_past_neighbors_params = set_past_neighbors_params_arc.lock().unwrap(); - assert_eq!( - *set_past_neighbors_params, - vec![( - Some(vec![NodeDescriptor::try_from(( - main_cryptde(), - "masq://eth-ropsten:UJNoZW5p-PDVqEjpr3b_8jZ_93yPG8i5dOAgE1bhK_A@2.3.4.5:2345" - )) - .unwrap()]), - "password".to_string() - )] - ) - } - - #[test] - fn setting_zero_hop_neighbors_is_ignored_if_no_neighbors_supplied() { - running_test(); - let set_past_neighbors_params_arc = Arc::new(Mutex::new(vec![])); - let mut config = BootstrapperConfig::new(); - let mut persistent_config = make_default_persistent_configuration() - .set_past_neighbors_params(&set_past_neighbors_params_arc); - let multi_config = make_simplified_multi_config([ - "MASQNode", - "--chain", - "eth-ropsten", - "--neighborhood-mode", - "zero-hop", - ]); - - let _ = unprivileged_parse_args( - &multi_config, - &mut config, - &mut persistent_config, - &Logger::new("test"), - ) - .unwrap(); - - assert_eq!( - config.neighborhood_config, - NeighborhoodConfig { - mode: NeighborhoodMode::ZeroHop - } - ); - let set_past_neighbors_params = set_past_neighbors_params_arc.lock().unwrap(); - assert!(set_past_neighbors_params.is_empty()) - } - - #[test] - fn configure_zero_hop_with_neighbors_but_no_password() { - running_test(); - let mut persistent_config = PersistentConfigurationMock::new(); - //no results prepared for set_past_neighbors() and no panic so it was not called - let descriptor_list = vec![NodeDescriptor::try_from(( - main_cryptde(), - "masq://eth-ropsten:UJNoZW5p-PDVqEjpr3b_8jZ_93yPG8i5dOAgE1bhK_A@2.3.4.5:2345", - )) - .unwrap()]; - - let result = - zero_hop_neighbors_configuration(None, descriptor_list, &mut persistent_config); - - assert_eq!( - result, - Err(ConfiguratorError::required( - "neighbors", - "Cannot proceed without a password" - )) - ); - } - - #[test] - fn configure_zero_hop_with_neighbors_but_setting_values_failed() { - running_test(); - let mut persistent_config = PersistentConfigurationMock::new().set_past_neighbors_result( - Err(PersistentConfigError::DatabaseError("Oh yeah".to_string())), - ); - let descriptor_list = vec![NodeDescriptor::try_from(( - main_cryptde(), - "masq://eth-ropsten:UJNoZW5p-PDVqEjpr3b_8jZ_93yPG8i5dOAgE1bhK_A@2.3.4.5:2345", - )) - .unwrap()]; - - let result = zero_hop_neighbors_configuration( - Some("password".to_string()), - descriptor_list, - &mut persistent_config, - ); - - assert_eq!( - result, - Err(ConfiguratorError::required( - "neighbors", - "DatabaseError(\"Oh yeah\")" - )) - ); - } - #[test] fn configure_database_with_no_data_specified() { running_test(); @@ -2678,18 +937,17 @@ mod tests { } #[test] - fn wrap_up_external_params_for_db_is_properly_set_when_password_is_provided() { + fn wrap_up_db_externals_is_properly_set_when_password_is_provided() { let mut subject = NodeConfiguratorStandardUnprivileged::new(&BootstrapperConfig::new()); subject.privileged_config.blockchain_bridge_config.chain = DEFAULT_CHAIN; let multi_config = make_simplified_multi_config([ - "MASQNode", "--neighborhood-mode", "zero-hop", "--db-password", "password", ]); - let result = subject.wrap_up_external_params_for_db(&multi_config); + let result = subject.wrap_up_db_externals(&multi_config); let expected = ExternalData::new( DEFAULT_CHAIN, @@ -2700,13 +958,12 @@ mod tests { } #[test] - fn wrap_up_external_params_for_db_is_properly_set_when_no_password_is_provided() { + fn wrap_up_db_externals_is_properly_set_when_no_password_is_provided() { let mut subject = NodeConfiguratorStandardUnprivileged::new(&BootstrapperConfig::new()); subject.privileged_config.blockchain_bridge_config.chain = DEFAULT_CHAIN; - let multi_config = - make_simplified_multi_config(["MASQNode", "--neighborhood-mode", "zero-hop"]); + let multi_config = make_simplified_multi_config(["--neighborhood-mode", "zero-hop"]); - let result = subject.wrap_up_external_params_for_db(&multi_config); + let result = subject.wrap_up_db_externals(&multi_config); let expected = ExternalData::new(DEFAULT_CHAIN, NeighborhoodModeLight::ZeroHop, None); assert_eq!(result, expected) diff --git a/node/src/node_configurator/unprivileged_parse_args_configuration.rs b/node/src/node_configurator/unprivileged_parse_args_configuration.rs new file mode 100644 index 000000000..6cd26a0d6 --- /dev/null +++ b/node/src/node_configurator/unprivileged_parse_args_configuration.rs @@ -0,0 +1,2371 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::accountant::DEFAULT_PENDING_TOO_LONG_SEC; +use crate::blockchain::bip32::Bip32ECKeyProvider; +use crate::bootstrapper::BootstrapperConfig; +use crate::db_config::persistent_configuration::{PersistentConfigError, PersistentConfiguration}; +use crate::sub_lib::accountant::{ + AccountantConfig, PaymentThresholds, ScanIntervals, DEFAULT_EARNING_WALLET, +}; +use crate::sub_lib::cryptde::CryptDE; +use crate::sub_lib::cryptde_null::CryptDENull; +use crate::sub_lib::cryptde_real::CryptDEReal; +use crate::sub_lib::neighborhood::{ + NeighborhoodConfig, NeighborhoodMode, NodeDescriptor, RatePack, +}; +use crate::sub_lib::node_addr::NodeAddr; +use crate::sub_lib::wallet::Wallet; +use clap::value_t; +use itertools::Itertools; +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::make_arg_matches_accesible; +use masq_lib::multi_config::MultiConfig; +use masq_lib::shared_schema::{ConfiguratorError, ParamError}; +use masq_lib::test_utils::utils::TEST_DEFAULT_CHAIN; +use masq_lib::utils::{AutomapProtocol, ExpectValue, WrapResult}; +use rustc_hex::FromHex; +use std::net::{IpAddr, Ipv4Addr}; +use std::str::FromStr; + +pub trait UnprivilegedParseArgsConfiguration { + // Only initialization that cannot be done with privilege should happen here. + fn unprivileged_parse_args( + &self, + multi_config: &MultiConfig, + unprivileged_config: &mut BootstrapperConfig, + persistent_config: &mut dyn PersistentConfiguration, + logger: &Logger, + ) -> Result<(), ConfiguratorError> { + unprivileged_config + .blockchain_bridge_config + .blockchain_service_url_opt = + if is_user_specified(multi_config, "blockchain-service-url") { + value_m!(multi_config, "blockchain-service-url", String) + } else { + match persistent_config.blockchain_service_url() { + Ok(Some(price)) => Some(price), + Ok(None) => None, + Err(pce) => return Err(pce.into_configurator_error("gas-price")), + } + }; + unprivileged_config.clandestine_port_opt = value_m!(multi_config, "clandestine-port", u16); + unprivileged_config.blockchain_bridge_config.gas_price = + if is_user_specified(multi_config, "gas-price") { + value_m!(multi_config, "gas-price", u64).expectv("gas price") + } else { + match persistent_config.gas_price() { + Ok(price) => price, + Err(pce) => return Err(pce.into_configurator_error("gas-price")), + } + }; + unprivileged_config.db_password_opt = value_m!(multi_config, "db-password", String); + configure_accountant_config(multi_config, unprivileged_config, persistent_config)?; + unprivileged_config.mapping_protocol_opt = + compute_mapping_protocol_opt(multi_config, persistent_config, logger); + let mnc_result = { + get_wallets(multi_config, persistent_config, unprivileged_config)?; + make_neighborhood_config(self, multi_config, persistent_config, unprivileged_config) + }; + + mnc_result.map(|config| unprivileged_config.neighborhood_config = config) + } + + fn get_past_neighbors( + &self, + persistent_config: &mut dyn PersistentConfiguration, + unprivileged_config: &mut BootstrapperConfig, + ) -> Result, ConfiguratorError>; +} + +pub struct UnprivilegedParseArgsConfigurationDaoReal {} + +impl UnprivilegedParseArgsConfiguration for UnprivilegedParseArgsConfigurationDaoReal { + fn get_past_neighbors( + &self, + persistent_config: &mut dyn PersistentConfiguration, + unprivileged_config: &mut BootstrapperConfig, + ) -> Result, ConfiguratorError> { + Ok( + match &get_db_password(unprivileged_config, persistent_config)? { + Some(db_password) => match persistent_config.past_neighbors(db_password) { + Ok(Some(past_neighbors)) => past_neighbors, + Ok(None) => vec![], + Err(PersistentConfigError::PasswordError) => { + return Err(ConfiguratorError::new(vec![ParamError::new( + "db-password", + "PasswordError", + )])) + } + Err(e) => { + return Err(ConfiguratorError::new(vec![ParamError::new( + "[past neighbors]", + &format!("{:?}", e), + )])) + } + }, + None => vec![], + }, + ) + } +} + +pub struct UnprivilegedParseArgsConfigurationDaoNull {} + +impl UnprivilegedParseArgsConfiguration for UnprivilegedParseArgsConfigurationDaoNull { + fn get_past_neighbors( + &self, + _persistent_config: &mut dyn PersistentConfiguration, + _unprivileged_config: &mut BootstrapperConfig, + ) -> Result, ConfiguratorError> { + Ok(vec![]) + } +} + +pub fn get_wallets( + multi_config: &MultiConfig, + persistent_config: &mut dyn PersistentConfiguration, + config: &mut BootstrapperConfig, +) -> Result<(), ConfiguratorError> { + let mc_consuming_opt = value_m!(multi_config, "consuming-private-key", String); + let mc_earning_opt = value_m!(multi_config, "earning-wallet", String); + let pc_consuming_opt = if let Some(db_password) = &config.db_password_opt { + match persistent_config.consuming_wallet_private_key(db_password.as_str()) { + Ok(pco) => pco, + Err(PersistentConfigError::PasswordError) => None, + Err(e) => return Err(e.into_configurator_error("consuming-private-key")), + } + } else { + None + }; + let pc_earning_opt = match persistent_config.earning_wallet_address() { + Ok(peo) => peo, + Err(e) => return Err(e.into_configurator_error("earning-wallet")), + }; + let consuming_opt = match (&mc_consuming_opt, &pc_consuming_opt) { + (None, _) => pc_consuming_opt, + (Some(_), None) => mc_consuming_opt, + (Some(m), Some(c)) if wallet_params_are_equal(m, c) => pc_consuming_opt, + _ => { + return Err(ConfiguratorError::required( + "consuming-private-key", + "Cannot change to a private key different from that previously set", + )) + } + }; + let earning_opt = match (&mc_earning_opt, &pc_earning_opt) { + (None, _) => pc_earning_opt, + (Some(_), None) => mc_earning_opt, + (Some(m), Some(c)) if wallet_params_are_equal(m, c) => pc_earning_opt, + (Some(m), Some(c)) => { + return Err(ConfiguratorError::required( + "earning-wallet", + &format!( + "Cannot change to an address ({}) different from that previously set ({})", + m, c + ), + )) + } + }; + let consuming_wallet_opt = consuming_opt.map(|consuming_private_key| { + let key_bytes = consuming_private_key + .from_hex::>() + .unwrap_or_else(|_| { + panic!( + "Wallet corruption: bad hex value for consuming wallet private key: {}", + consuming_private_key + ) + }); + let key_pair = + Bip32ECKeyProvider::from_raw_secret(key_bytes.as_slice()).unwrap_or_else(|_| { + panic!( + "Wallet corruption: consuming wallet private key in invalid format: {:?}", + key_bytes + ) + }); + Wallet::from(key_pair) + }); + let earning_wallet_opt = earning_opt.map(|earning_address| { + Wallet::from_str(&earning_address).unwrap_or_else(|_| { + panic!( + "Wallet corruption: bad value for earning wallet address: {}", + earning_address + ) + }) + }); + config.consuming_wallet_opt = consuming_wallet_opt; + config.earning_wallet = earning_wallet_opt.unwrap_or_else(|| DEFAULT_EARNING_WALLET.clone()); + Ok(()) +} + +fn wallet_params_are_equal(a: &str, b: &str) -> bool { + a.to_uppercase() == b.to_uppercase() +} + +pub fn make_neighborhood_config( + parse_args_configurator: &T, + multi_config: &MultiConfig, + persistent_config: &mut dyn PersistentConfiguration, + unprivileged_config: &mut BootstrapperConfig, +) -> Result { + let neighbor_configs: Vec = { + match convert_ci_configs(multi_config)? { + Some(configs) => configs, + None => parse_args_configurator + .get_past_neighbors(persistent_config, unprivileged_config)?, + } + }; + match make_neighborhood_mode(multi_config, neighbor_configs, persistent_config) { + Ok(mode) => Ok(NeighborhoodConfig { mode }), + Err(e) => Err(e), + } +} + +fn make_neighborhood_mode( + multi_config: &MultiConfig, + neighbor_configs: Vec, + persistent_config: &mut dyn PersistentConfiguration, +) -> Result { + let neighborhood_mode_opt = value_m!(multi_config, "neighborhood-mode", String); + match neighborhood_mode_opt { + Some(ref s) if s == "standard" || s == "originate-only" => { + let rate_pack = configure_rate_pack(multi_config, persistent_config)?; + match s.as_str() { + "standard" => neighborhood_mode_standard(multi_config, neighbor_configs, rate_pack), + "originate-only" => { + if neighbor_configs.is_empty() { + Err(ConfiguratorError::required("neighborhood-mode", "Node cannot run as --neighborhood-mode originate-only without --neighbors specified")) + } else { + Ok(NeighborhoodMode::OriginateOnly(neighbor_configs, rate_pack)) + } + } + _ => unreachable!(), + } + } + Some(ref s) if s == "consume-only" => { + let mut errors = ConfiguratorError::new(vec![]); + if neighbor_configs.is_empty() { + errors = errors.another_required("neighborhood-mode", "Node cannot run as --neighborhood-mode consume-only without --neighbors specified"); + } + if value_m!(multi_config, "dns-servers", String).is_some() { + errors = errors.another_required("neighborhood-mode", "Node cannot run as --neighborhood-mode consume-only if --dns-servers is specified"); + } + if !errors.is_empty() { + Err(errors) + } else { + Ok(NeighborhoodMode::ConsumeOnly(neighbor_configs)) + } + } + Some(ref s) if s == "zero-hop" => { + if value_m!(multi_config, "ip", IpAddr).is_some() { + Err(ConfiguratorError::required( + "neighborhood-mode", + "Node cannot run as --neighborhood-mode zero-hop if --ip is specified", + )) + } else { + if !neighbor_configs.is_empty() { + let password_opt = value_m!(multi_config, "db-password", String); + zero_hop_neighbors_configuration( + password_opt, + neighbor_configs, + persistent_config, + )? + } + Ok(NeighborhoodMode::ZeroHop) + } + } + // These two cases are untestable + Some(ref s) => panic!( + "--neighborhood-mode {} has not been properly provided for in the code", + s + ), + None => { + let rate_pack = configure_rate_pack(multi_config, persistent_config)?; + neighborhood_mode_standard(multi_config, neighbor_configs, rate_pack) + } + } +} + +fn zero_hop_neighbors_configuration( + password_opt: Option, + descriptors: Vec, + persistent_config: &mut dyn PersistentConfiguration, +) -> Result<(), ConfiguratorError> { + match password_opt { + Some(password) => { + if let Err(e) = persistent_config.set_past_neighbors(Some(descriptors), &password) { + return Err(e.into_configurator_error("neighbors")); + } + } + None => { + return Err(ConfiguratorError::required( + "neighbors", + "Cannot proceed without a password", + )); + } + } + Ok(()) +} + +fn neighborhood_mode_standard( + multi_config: &MultiConfig, + neighbor_configs: Vec, + rate_pack: RatePack, +) -> Result { + let ip = get_public_ip(multi_config)?; + Ok(NeighborhoodMode::Standard( + NodeAddr::new(&ip, &[]), + neighbor_configs, + rate_pack, + )) +} + +fn get_public_ip(multi_config: &MultiConfig) -> Result { + match value_m!(multi_config, "ip", String) { + Some(ip_str) => match IpAddr::from_str(&ip_str) { + Ok(ip_addr) => Ok(ip_addr), + Err(_) => todo!("Drive in a better error message"), //Err(ConfiguratorError::required("ip", &format! ("blockety blip: '{}'", ip_str), + }, + None => Ok(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))), // sentinel: means "Try Automap" + } +} + +fn convert_ci_configs( + multi_config: &MultiConfig, +) -> Result>, ConfiguratorError> { + type DescriptorParsingResult = Result; + match value_m!(multi_config, "neighbors", String) { + None => Ok(None), + Some(joined_configs) => { + let separate_configs: Vec = joined_configs + .split(',') + .map(|s| s.to_string()) + .collect_vec(); + if separate_configs.is_empty() { + Ok(None) + } else { + let dummy_cryptde: Box = { + if value_m!(multi_config, "fake-public-key", String).is_none() { + Box::new(CryptDEReal::new(TEST_DEFAULT_CHAIN)) + } else { + Box::new(CryptDENull::new(TEST_DEFAULT_CHAIN)) + } + }; + let desired_chain = Chain::from( + value_m!(multi_config, "chain", String) + .unwrap_or_else(|| DEFAULT_CHAIN.rec().literal_identifier.to_string()) + .as_str(), + ); + let results = + validate_descriptors_from_user(separate_configs, dummy_cryptde, desired_chain); + let (ok, err): (Vec, Vec) = + results.into_iter().partition(|result| result.is_ok()); + let ok = ok + .into_iter() + .map(|ok| ok.expect("NodeDescriptor")) + .collect_vec(); + let err = err + .into_iter() + .map(|err| err.expect_err("ParamError")) + .collect_vec(); + if err.is_empty() { + Ok(Some(ok)) + } else { + Err(ConfiguratorError::new(err)) + } + } + } + } +} + +fn validate_descriptors_from_user( + descriptors: Vec, + dummy_cryptde: Box, + desired_native_chain: Chain, +) -> Vec> { + descriptors.into_iter().map(|node_desc_from_ci| { + let node_desc_trimmed = node_desc_from_ci.trim(); + match NodeDescriptor::try_from((dummy_cryptde.as_ref(), node_desc_trimmed)) { + Ok(descriptor) => { + let competence_from_descriptor = descriptor.blockchain; + if desired_native_chain == competence_from_descriptor { + validate_mandatory_node_addr(node_desc_trimmed, descriptor) + } else { + let desired_chain = desired_native_chain.rec().literal_identifier; + Err(ParamError::new( + "neighbors", &format!( + "Mismatched chains. You are requiring access to '{}' ({}{}:@) with descriptor belonging to '{}'", + desired_chain, MASQ_URL_PREFIX, + desired_chain, + competence_from_descriptor.rec().literal_identifier + ) + )) + } + } + Err(e) => ParamError::new("neighbors", &e).wrap_to_err() + } + }) + .collect_vec() +} + +fn validate_mandatory_node_addr( + supplied_descriptor: &str, + descriptor: NodeDescriptor, +) -> Result { + if descriptor.node_addr_opt.is_some() { + Ok(descriptor) + } else { + Err(ParamError::new( + "neighbors", + &format!( + "Neighbors supplied without ip addresses and ports are not valid: '{}:", + if supplied_descriptor.ends_with("@:") { + supplied_descriptor.strip_suffix(':').expect("logic failed") + } else { + supplied_descriptor + } + ), + )) + } +} + +fn compute_mapping_protocol_opt( + multi_config: &MultiConfig, + persistent_config: &mut dyn PersistentConfiguration, + logger: &Logger, +) -> Option { + let persistent_mapping_protocol_opt = match persistent_config.mapping_protocol() { + Ok(mp_opt) => mp_opt, + Err(e) => { + warning!( + logger, + "Could not read mapping protocol from database: {:?}", + e + ); + None + } + }; + let mapping_protocol_specified = multi_config.occurrences_of("mapping-protocol") > 0; + let computed_mapping_protocol_opt = match ( + value_m!(multi_config, "mapping-protocol", AutomapProtocol), + persistent_mapping_protocol_opt, + mapping_protocol_specified, + ) { + (None, Some(persisted_mapping_protocol), false) => Some(persisted_mapping_protocol), + (None, _, true) => None, + (cmd_line_mapping_protocol_opt, _, _) => cmd_line_mapping_protocol_opt, + }; + if computed_mapping_protocol_opt != persistent_mapping_protocol_opt { + if computed_mapping_protocol_opt.is_none() { + debug!(logger, "Blanking mapping protocol out of the database") + } + match persistent_config.set_mapping_protocol(computed_mapping_protocol_opt) { + Ok(_) => (), + Err(e) => { + warning!( + logger, + "Could not save mapping protocol to database: {:?}", + e + ); + } + } + } + computed_mapping_protocol_opt +} + +fn configure_accountant_config( + multi_config: &MultiConfig, + config: &mut BootstrapperConfig, + persist_config: &mut dyn PersistentConfiguration, +) -> Result<(), ConfiguratorError> { + let accountant_config = AccountantConfig { + scan_intervals: process_combined_params( + "scan-intervals", + multi_config, + persist_config, + |str: &str| ScanIntervals::try_from(str), + |pc: &dyn PersistentConfiguration| pc.scan_intervals(), + |pc: &mut dyn PersistentConfiguration, intervals| pc.set_scan_intervals(intervals), + )?, + payment_thresholds: process_combined_params( + "payment-thresholds", + multi_config, + persist_config, + |str: &str| PaymentThresholds::try_from(str), + |pc: &dyn PersistentConfiguration| pc.payment_thresholds(), + |pc: &mut dyn PersistentConfiguration, curves| pc.set_payment_thresholds(curves), + )?, + when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, + }; + config.accountant_config_opt = Some(accountant_config); + Ok(()) +} + +fn configure_rate_pack( + multi_config: &MultiConfig, + persist_config: &mut dyn PersistentConfiguration, +) -> Result { + process_combined_params( + "rate-pack", + multi_config, + persist_config, + |str: &str| RatePack::try_from(str), + |pc: &dyn PersistentConfiguration| pc.rate_pack(), + |pc: &mut dyn PersistentConfiguration, rate_pack| pc.set_rate_pack(rate_pack), + ) +} + +fn process_combined_params<'a, T: PartialEq, C1, C2>( + parameter_name: &'a str, + multi_config: &MultiConfig, + persist_config: &'a mut dyn PersistentConfiguration, + parser: fn(&str) -> Result, + persistent_config_getter: C1, + persistent_config_setter: C2, +) -> Result +where + C1: Fn(&dyn PersistentConfiguration) -> Result, + C2: Fn(&mut dyn PersistentConfiguration, String) -> Result<(), PersistentConfigError>, +{ + Ok( + match ( + value_m!(multi_config, parameter_name, String), + persistent_config_getter(persist_config), + ) { + (Some(cli_string_values), pc_result) => { + let cli_values: T = parser(&cli_string_values) + .map_err(|e| ConfiguratorError::required(parameter_name, &e))?; + let pc_values: T = pc_result.unwrap_or_else(|e| { + panic!("{}: database query failed due to {:?}", parameter_name, e) + }); + if cli_values != pc_values { + persistent_config_setter(persist_config, cli_string_values).unwrap_or_else( + |e| { + panic!( + "{}: writing database failed due to: {:?}", + parameter_name, e + ) + }, + ) + } + cli_values + } + (_, pc_result) => pc_result.unwrap_or_else(|e| { + panic!("{}: database query failed due to {:?}", parameter_name, e) + }), + }, + ) +} + +fn get_db_password( + config: &mut BootstrapperConfig, + persistent_config: &mut dyn PersistentConfiguration, +) -> Result, ConfiguratorError> { + if let Some(db_password) = &config.db_password_opt { + set_db_password_at_first_mention(db_password, persistent_config)?; + return Ok(Some(db_password.clone())); + } + Ok(None) +} + +fn set_db_password_at_first_mention( + db_password: &str, + persistent_config: &mut dyn PersistentConfiguration, +) -> Result { + match persistent_config.check_password(None) { + Ok(true) => match persistent_config.change_password(None, db_password) { + Ok(_) => Ok(true), + Err(e) => Err(e.into_configurator_error("db-password")), + }, + Ok(false) => Ok(false), + Err(e) => Err(e.into_configurator_error("db-password")), + } +} + +fn is_user_specified(multi_config: &MultiConfig, parameter: &str) -> bool { + multi_config.occurrences_of(parameter) > 0 +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::apps::app_node; + use crate::blockchain::bip32::Bip32ECKeyProvider; + use crate::database::db_initializer::{DbInitializer, DbInitializerReal}; + use crate::database::db_migrations::MigratorConfig; + use crate::db_config::config_dao::{ConfigDao, ConfigDaoReal}; + use crate::db_config::persistent_configuration::PersistentConfigError::NotPresent; + use crate::db_config::persistent_configuration::PersistentConfigurationReal; + use crate::sub_lib::cryptde::{PlainData, PublicKey}; + use crate::sub_lib::neighborhood::DEFAULT_RATE_PACK; + use crate::sub_lib::utils::make_new_test_multi_config; + use crate::sub_lib::wallet::Wallet; + use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMock; + use crate::test_utils::unshared_test_utils::{ + configure_default_persistent_config, default_persistent_config_just_accountant_config, + make_persistent_config_real_with_config_dao_null, make_simplified_multi_config, + ACCOUNTANT_CONFIG_PARAMS, MAPPING_PROTOCOL, RATE_PACK, ZERO, + }; + use crate::test_utils::{main_cryptde, ArgsBuilder}; + use masq_lib::constants::DEFAULT_GAS_PRICE; + use masq_lib::multi_config::{CommandLineVcl, NameValueVclArg, VclArg, VirtualCommandLine}; + use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; + use masq_lib::test_utils::utils::ensure_node_home_directory_exists; + use masq_lib::utils::running_test; + use std::path::PathBuf; + use std::str::FromStr; + use std::sync::{Arc, Mutex}; + use std::time::Duration; + + #[test] + fn convert_ci_configs_handles_blockchain_mismatch() { + let multi_config = make_simplified_multi_config([ + "--neighbors", + "masq://eth-ropsten:abJ5XvhVbmVyGejkYUkmftF09pmGZGKg_PzRNnWQxFw@12.23.34.45:5678", + "--chain", + DEFAULT_CHAIN.rec().literal_identifier, + ]); + + let result = convert_ci_configs(&multi_config).err().unwrap(); + + assert_eq!( + result, + ConfiguratorError::required( + "neighbors", + &format!("Mismatched chains. You are requiring access to '{identifier}' (masq://{identifier}:@) with descriptor belonging to 'eth-ropsten'",identifier = DEFAULT_CHAIN.rec().literal_identifier) + ) + ) + } + + #[test] + fn make_neighborhood_config_standard_happy_path() { + running_test(); + let multi_config = make_new_test_multi_config( + &app_node(), + vec![Box::new(CommandLineVcl::new( + ArgsBuilder::new() + .param("--neighborhood-mode", "standard") + .param("--ip", "1.2.3.4") + .param( + "--neighbors", + &format!("masq://{identifier}:mhtjjdMt7Gyoebtb1yiK0hdaUx6j84noHdaAHeDR1S4@1.2.3.4:1234/2345,masq://{identifier}:Si06R3ulkOjJOLw1r2R9GOsY87yuinHU_IHK2FJyGnk@2.3.4.5:3456/4567",identifier = DEFAULT_CHAIN.rec().literal_identifier), + ) + .into(), + ))] + ).unwrap(); + + let result = make_neighborhood_config( + &UnprivilegedParseArgsConfigurationDaoReal {}, + &multi_config, + &mut configure_default_persistent_config(RATE_PACK), + &mut BootstrapperConfig::new(), + ); + + let dummy_cryptde = CryptDEReal::new(TEST_DEFAULT_CHAIN); + assert_eq!( + result, + Ok(NeighborhoodConfig { + mode: NeighborhoodMode::Standard( + NodeAddr::new(&IpAddr::from_str("1.2.3.4").unwrap(), &[]), + vec![ + NodeDescriptor::try_from(( + &dummy_cryptde as &dyn CryptDE, + format!("masq://{}:mhtjjdMt7Gyoebtb1yiK0hdaUx6j84noHdaAHeDR1S4@1.2.3.4:1234/2345",DEFAULT_CHAIN.rec().literal_identifier).as_str() + )) + .unwrap(), + NodeDescriptor::try_from(( + &dummy_cryptde as &dyn CryptDE, + format!("masq://{}:Si06R3ulkOjJOLw1r2R9GOsY87yuinHU_IHK2FJyGnk@2.3.4.5:3456/4567",DEFAULT_CHAIN.rec().literal_identifier).as_str() + )) + .unwrap() + ], + DEFAULT_RATE_PACK + ) + }) + ); + } + + #[test] + fn make_neighborhood_config_standard_missing_ip() { + running_test(); + let multi_config = make_new_test_multi_config( + &app_node(), + vec![Box::new(CommandLineVcl::new( + ArgsBuilder::new() + .param("--neighborhood-mode", "standard") + .param( + "--neighbors", + &format!("masq://{identifier}:QmlsbA@1.2.3.4:1234/2345,masq://{identifier}:VGVk@2.3.4.5:3456/4567",identifier = DEFAULT_CHAIN.rec().literal_identifier), + ) + .param("--fake-public-key", "booga") + .into(), + ))], + ) + .unwrap(); + + let result = make_neighborhood_config( + &UnprivilegedParseArgsConfigurationDaoReal {}, + &multi_config, + &mut configure_default_persistent_config(RATE_PACK), + &mut BootstrapperConfig::new(), + ); + + let node_addr = match result { + Ok(NeighborhoodConfig { + mode: NeighborhoodMode::Standard(node_addr, _, _), + }) => node_addr, + x => panic!("Wasn't expecting {:?}", x), + }; + assert_eq!(node_addr.ip_addr(), IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))); + } + + #[test] + fn make_neighborhood_config_originate_only_doesnt_need_ip() { + running_test(); + let multi_config = make_new_test_multi_config( + &app_node(), + vec![Box::new(CommandLineVcl::new( + ArgsBuilder::new() + .param("--neighborhood-mode", "originate-only") + .param( + "--neighbors", + &format!("masq://{identifier}:QmlsbA@1.2.3.4:1234/2345,masq://{identifier}:VGVk@2.3.4.5:3456/4567",identifier = DEFAULT_CHAIN.rec().literal_identifier), + ) + .param("--fake-public-key", "booga") + .into(), + ))], + ) + .unwrap(); + + let result = make_neighborhood_config( + &UnprivilegedParseArgsConfigurationDaoReal {}, + &multi_config, + &mut configure_default_persistent_config(RATE_PACK), + &mut BootstrapperConfig::new(), + ); + + assert_eq!( + result, + Ok(NeighborhoodConfig { + mode: NeighborhoodMode::OriginateOnly( + vec![ + NodeDescriptor::try_from(( + main_cryptde(), + format!( + "masq://{}:QmlsbA@1.2.3.4:1234/2345", + DEFAULT_CHAIN.rec().literal_identifier + ) + .as_str() + )) + .unwrap(), + NodeDescriptor::try_from(( + main_cryptde(), + format!( + "masq://{}:VGVk@2.3.4.5:3456/4567", + DEFAULT_CHAIN.rec().literal_identifier + ) + .as_str() + )) + .unwrap() + ], + DEFAULT_RATE_PACK + ) + }) + ); + } + + #[test] + fn make_neighborhood_config_originate_only_does_need_at_least_one_neighbor() { + running_test(); + let multi_config = make_new_test_multi_config( + &app_node(), + vec![Box::new(CommandLineVcl::new( + ArgsBuilder::new() + .param("--neighborhood-mode", "originate-only") + .into(), + ))], + ) + .unwrap(); + + let result = make_neighborhood_config( + &UnprivilegedParseArgsConfigurationDaoReal {}, + &multi_config, + &mut configure_default_persistent_config(RATE_PACK).check_password_result(Ok(false)), + &mut BootstrapperConfig::new(), + ); + + assert_eq! (result, Err(ConfiguratorError::required("neighborhood-mode", "Node cannot run as --neighborhood-mode originate-only without --neighbors specified"))) + } + + #[test] + fn make_neighborhood_config_consume_only_doesnt_need_ip() { + running_test(); + let multi_config = make_new_test_multi_config( + &app_node(), + vec![Box::new(CommandLineVcl::new( + ArgsBuilder::new() + .param("--neighborhood-mode", "consume-only") + .param( + "--neighbors", + &format!("masq://{identifier}:QmlsbA@1.2.3.4:1234/2345,masq://{identifier}:VGVk@2.3.4.5:3456/4567",identifier = DEFAULT_CHAIN.rec().literal_identifier), + ) + .param("--fake-public-key", "booga") + .into(), + ))], + ) + .unwrap(); + + let result = make_neighborhood_config( + &UnprivilegedParseArgsConfigurationDaoReal {}, + &multi_config, + &mut configure_default_persistent_config(ZERO), + &mut BootstrapperConfig::new(), + ); + + assert_eq!( + result, + Ok(NeighborhoodConfig { + mode: NeighborhoodMode::ConsumeOnly(vec![ + NodeDescriptor::try_from(( + main_cryptde(), + format!( + "masq://{}:QmlsbA@1.2.3.4:1234/2345", + DEFAULT_CHAIN.rec().literal_identifier + ) + .as_str() + )) + .unwrap(), + NodeDescriptor::try_from(( + main_cryptde(), + format!( + "masq://{}:VGVk@2.3.4.5:3456/4567", + DEFAULT_CHAIN.rec().literal_identifier + ) + .as_str() + )) + .unwrap() + ],) + }) + ); + } + + #[test] + fn make_neighborhood_config_consume_only_rejects_dns_servers_and_needs_at_least_one_neighbor() { + running_test(); + let multi_config = make_new_test_multi_config( + &app_node(), + vec![Box::new(CommandLineVcl::new( + ArgsBuilder::new() + .param("--neighborhood-mode", "consume-only") + .param("--dns-servers", "1.1.1.1") + .param("--fake-public-key", "booga") + .into(), + ))], + ) + .unwrap(); + + let result = make_neighborhood_config( + &UnprivilegedParseArgsConfigurationDaoReal {}, + &multi_config, + &mut configure_default_persistent_config(ZERO), + &mut BootstrapperConfig::new(), + ); + + assert_eq!( + result, + Err(ConfiguratorError::required( + "neighborhood-mode", + "Node cannot run as --neighborhood-mode consume-only without --neighbors specified" + ) + .another_required( + "neighborhood-mode", + "Node cannot run as --neighborhood-mode consume-only if --dns-servers is specified" + )) + ) + } + + #[test] + fn make_neighborhood_config_zero_hop_doesnt_need_ip_or_neighbors() { + running_test(); + let multi_config = make_new_test_multi_config( + &app_node(), + vec![Box::new(CommandLineVcl::new( + ArgsBuilder::new() + .param("--neighborhood-mode", "zero-hop") + .into(), + ))], + ) + .unwrap(); + + let result = make_neighborhood_config( + &UnprivilegedParseArgsConfigurationDaoReal {}, + &multi_config, + &mut configure_default_persistent_config(ZERO).check_password_result(Ok(false)), + &mut BootstrapperConfig::new(), + ); + + assert_eq!( + result, + Ok(NeighborhoodConfig { + mode: NeighborhoodMode::ZeroHop + }) + ); + } + + #[test] + fn make_neighborhood_config_zero_hop_cant_tolerate_ip() { + running_test(); + let multi_config = make_new_test_multi_config( + &app_node(), + vec![Box::new(CommandLineVcl::new( + ArgsBuilder::new() + .param("--neighborhood-mode", "zero-hop") + .param("--ip", "1.2.3.4") + .into(), + ))], + ) + .unwrap(); + + let result = make_neighborhood_config( + &UnprivilegedParseArgsConfigurationDaoReal {}, + &multi_config, + &mut configure_default_persistent_config(ZERO).check_password_result(Ok(false)), + &mut BootstrapperConfig::new(), + ); + + assert_eq!( + result, + Err(ConfiguratorError::required( + "neighborhood-mode", + "Node cannot run as --neighborhood-mode zero-hop if --ip is specified" + )) + ) + } + + #[test] + fn get_past_neighbors_handles_good_password_but_no_past_neighbors_parse_args_configuration_dao_real( + ) { + running_test(); + let mut persistent_config = + configure_default_persistent_config(ZERO).check_password_result(Ok(false)); + let mut unprivileged_config = BootstrapperConfig::new(); + unprivileged_config.db_password_opt = Some("password".to_string()); + let subject = UnprivilegedParseArgsConfigurationDaoReal {}; + + let result = subject + .get_past_neighbors(&mut persistent_config, &mut unprivileged_config) + .unwrap(); + + assert!(result.is_empty()); + } + + #[test] + fn get_past_neighbors_handles_non_password_error_for_parse_args_configuration_dao_real() { + running_test(); + let mut persistent_config = PersistentConfigurationMock::new() + .check_password_result(Ok(false)) + .past_neighbors_result(Err(PersistentConfigError::NotPresent)); + let mut unprivileged_config = BootstrapperConfig::new(); + unprivileged_config.db_password_opt = Some("password".to_string()); + let subject = UnprivilegedParseArgsConfigurationDaoReal {}; + + let result = subject.get_past_neighbors(&mut persistent_config, &mut unprivileged_config); + + assert_eq!( + result, + Err(ConfiguratorError::new(vec![ParamError::new( + "[past neighbors]", + "NotPresent" + )])) + ); + } + + #[test] + fn get_past_neighbors_handles_unavailable_password_for_parse_args_configuration_dao_real() { + //sets the password in the database - we'll have to resolve if the use case is appropriate + running_test(); + let mut persistent_config = configure_default_persistent_config(ZERO) + .check_password_result(Ok(true)) + .change_password_result(Ok(())); + let mut unprivileged_config = BootstrapperConfig::new(); + unprivileged_config.db_password_opt = Some("password".to_string()); + let subject = UnprivilegedParseArgsConfigurationDaoReal {}; + + let result = subject + .get_past_neighbors(&mut persistent_config, &mut unprivileged_config) + .unwrap(); + + assert!(result.is_empty()); + } + + #[test] + fn get_past_neighbors_does_nothing_for_parse_args_configuration_dao_null() { + //slightly adapted aside reality; we would've been using PersistentConfigurationReal + //with ConfigDaoNull but it wouldn't have necessarily panicked if its method called so we use PersistentConfigMock + //which can provide us with a reaction like so + running_test(); + let mut persistent_config = PersistentConfigurationMock::new(); + let mut unprivileged_config = BootstrapperConfig::new(); + let subject = UnprivilegedParseArgsConfigurationDaoNull {}; + + let result = subject.get_past_neighbors(&mut persistent_config, &mut unprivileged_config); + + assert_eq!(result, Ok(vec![])); + //Nothing panicked so we could not call real persistent config's methods. + } + + #[test] + fn set_db_password_at_first_mention_handles_existing_password() { + let check_password_params_arc = Arc::new(Mutex::new(vec![])); + let mut persistent_config = configure_default_persistent_config(ZERO) + .check_password_params(&check_password_params_arc) + .check_password_result(Ok(false)); + + let result = set_db_password_at_first_mention("password", &mut persistent_config); + + assert_eq!(result, Ok(false)); + let check_password_params = check_password_params_arc.lock().unwrap(); + assert_eq!(*check_password_params, vec![None]) + } + + #[test] + fn set_db_password_at_first_mention_sets_password_correctly() { + let change_password_params_arc = Arc::new(Mutex::new(vec![])); + let mut persistent_config = configure_default_persistent_config(ZERO) + .check_password_result(Ok(true)) + .change_password_params(&change_password_params_arc) + .change_password_result(Ok(())); + + let result = set_db_password_at_first_mention("password", &mut persistent_config); + + assert_eq!(result, Ok(true)); + let change_password_params = change_password_params_arc.lock().unwrap(); + assert_eq!( + *change_password_params, + vec![(None, "password".to_string())] + ) + } + + #[test] + fn set_db_password_at_first_mention_handles_password_check_error() { + let check_password_params_arc = Arc::new(Mutex::new(vec![])); + let mut persistent_config = configure_default_persistent_config(ZERO) + .check_password_params(&check_password_params_arc) + .check_password_result(Err(PersistentConfigError::NotPresent)); + + let result = set_db_password_at_first_mention("password", &mut persistent_config); + + assert_eq!( + result, + Err(PersistentConfigError::NotPresent.into_configurator_error("db-password")) + ); + let check_password_params = check_password_params_arc.lock().unwrap(); + assert_eq!(*check_password_params, vec![None]) + } + + #[test] + fn set_db_password_at_first_mention_handles_password_set_error() { + let change_password_params_arc = Arc::new(Mutex::new(vec![])); + let mut persistent_config = configure_default_persistent_config(ZERO) + .check_password_result(Ok(true)) + .change_password_params(&change_password_params_arc) + .change_password_result(Err(PersistentConfigError::NotPresent)); + + let result = set_db_password_at_first_mention("password", &mut persistent_config); + + assert_eq!( + result, + Err(NotPresent.into_configurator_error("db-password")) + ); + let change_password_params = change_password_params_arc.lock().unwrap(); + assert_eq!( + *change_password_params, + vec![(None, "password".to_string())] + ) + } + + #[test] + fn get_db_password_if_supplied() { + running_test(); + let mut config = BootstrapperConfig::new(); + let mut persistent_config = + configure_default_persistent_config(ZERO).check_password_result(Ok(false)); + config.db_password_opt = Some("password".to_string()); + + let result = get_db_password(&mut config, &mut persistent_config); + + assert_eq!(result, Ok(Some("password".to_string()))); + } + + #[test] + fn get_db_password_doesnt_bother_if_database_has_no_password_yet() { + running_test(); + let mut config = BootstrapperConfig::new(); + let mut persistent_config = + configure_default_persistent_config(ZERO).check_password_result(Ok(true)); + + let result = get_db_password(&mut config, &mut persistent_config); + + assert_eq!(result, Ok(None)); + } + + #[test] + fn get_db_password_handles_database_write_error() { + running_test(); + let mut config = BootstrapperConfig::new(); + config.db_password_opt = Some("password".to_string()); + let mut persistent_config = configure_default_persistent_config(ZERO) + .check_password_result(Ok(true)) + .change_password_result(Err(PersistentConfigError::NotPresent)); + + let result = get_db_password(&mut config, &mut persistent_config); + + assert_eq!( + result, + Err(PersistentConfigError::NotPresent.into_configurator_error("db-password")) + ); + } + + #[test] + fn convert_ci_configs_handles_leftover_whitespaces_between_descriptors_and_commas() { + let multi_config = make_simplified_multi_config([ + "--chain", + "eth-ropsten", + "--fake-public-key", + "ABCDE", + "--neighbors", + "masq://eth-ropsten:abJ5XvhVbmVyGejkYUkmftF09pmGZGKg_PzRNnWQxFw@1.2.3.4:5555, masq://eth-ropsten:gBviQbjOS3e5ReFQCvIhUM3i02d1zPleo1iXg_EN6zQ@86.75.30.9:5542 , masq://eth-ropsten:A6PGHT3rRjaeFpD_rFi3qGEXAVPq7bJDfEUZpZaIyq8@14.10.50.6:10504", + ]); + let public_key = PublicKey::new(b"ABCDE"); + let cryptde = CryptDENull::from(&public_key, Chain::EthRopsten); + let cryptde_traitified = &cryptde as &dyn CryptDE; + + let result = convert_ci_configs(&multi_config); + + assert_eq!(result, Ok(Some( + vec![ + NodeDescriptor::try_from((cryptde_traitified, "masq://eth-ropsten:abJ5XvhVbmVyGejkYUkmftF09pmGZGKg_PzRNnWQxFw@1.2.3.4:5555")).unwrap(), + NodeDescriptor::try_from((cryptde_traitified, "masq://eth-ropsten:gBviQbjOS3e5ReFQCvIhUM3i02d1zPleo1iXg_EN6zQ@86.75.30.9:5542")).unwrap(), + NodeDescriptor::try_from((cryptde_traitified, "masq://eth-ropsten:A6PGHT3rRjaeFpD_rFi3qGEXAVPq7bJDfEUZpZaIyq8@14.10.50.6:10504")).unwrap()]) + ) + ) + } + + #[test] + fn convert_ci_configs_does_not_like_neighbors_with_bad_syntax() { + running_test(); + let multi_config = make_simplified_multi_config(["--neighbors", "ooga,booga"]); + + let result = convert_ci_configs(&multi_config).err(); + + assert_eq!( + result, + Some(ConfiguratorError::new(vec![ + ParamError::new( + "neighbors", + "Prefix or more missing. Should be 'masq://:@', not 'ooga'" + ), + ParamError::new( + "neighbors", + "Prefix or more missing. Should be 'masq://:@', not 'booga'" + ), + ])) + ); + } + + #[test] + fn convert_ci_configs_complains_about_descriptor_without_node_address_when_mainnet_required() { + let descriptor = format!( + "masq://{}:abJ5XvhVbmVyGejkYUkmftF09pmGZGKg_PzRNnWQxFw@:", + DEFAULT_CHAIN.rec().literal_identifier + ); + let multi_config = make_simplified_multi_config(["--neighbors", &descriptor]); + + let result = convert_ci_configs(&multi_config); + + assert_eq!(result,Err(ConfiguratorError::new(vec![ParamError::new("neighbors", &format!("Neighbors supplied without ip addresses and ports are not valid: '{}:",&descriptor[..descriptor.len()-1]))]))); + } + + #[test] + fn convert_ci_configs_complains_about_descriptor_without_node_address_when_test_chain_required() + { + let multi_config = make_simplified_multi_config([ + "--chain", + "eth-ropsten", + "--neighbors", + "masq://eth-ropsten:abJ5XvhVbmVyGejkYUkmftF09pmGZGKg_PzRNnWQxFw@:", + ]); + + let result = convert_ci_configs(&multi_config); + + assert_eq!(result,Err(ConfiguratorError::new(vec![ParamError::new("neighbors", "Neighbors supplied without ip addresses and ports are not valid: 'masq://eth-ropsten:abJ5XvhVbmVyGejkYUkmftF09pmGZGKg_PzRNnWQxFw@:")]))) + } + + #[test] + fn configure_zero_hop_with_neighbors_supplied() { + running_test(); + let set_past_neighbors_params_arc = Arc::new(Mutex::new(vec![])); + let mut config = BootstrapperConfig::new(); + let mut persistent_config = configure_default_persistent_config( + RATE_PACK | ACCOUNTANT_CONFIG_PARAMS | MAPPING_PROTOCOL, + ) + .set_past_neighbors_params(&set_past_neighbors_params_arc) + .set_past_neighbors_result(Ok(())); + let multi_config = make_simplified_multi_config([ + "--chain", + "eth-ropsten", + "--neighbors", + "masq://eth-ropsten:UJNoZW5p-PDVqEjpr3b_8jZ_93yPG8i5dOAgE1bhK_A@2.3.4.5:2345", + "--db-password", + "password", + "--neighborhood-mode", + "zero-hop", + "--fake-public-key", + "booga", + ]); + let subject = UnprivilegedParseArgsConfigurationDaoReal {}; + + let _ = subject + .unprivileged_parse_args( + &multi_config, + &mut config, + &mut persistent_config, + &Logger::new("test"), + ) + .unwrap(); + + assert_eq!( + config.neighborhood_config, + NeighborhoodConfig { + mode: NeighborhoodMode::ZeroHop + } + ); + let set_past_neighbors_params = set_past_neighbors_params_arc.lock().unwrap(); + assert_eq!( + *set_past_neighbors_params, + vec![( + Some(vec![NodeDescriptor::try_from(( + main_cryptde(), + "masq://eth-ropsten:UJNoZW5p-PDVqEjpr3b_8jZ_93yPG8i5dOAgE1bhK_A@2.3.4.5:2345" + )) + .unwrap()]), + "password".to_string() + )] + ) + } + + #[test] + fn setting_zero_hop_neighbors_is_ignored_if_no_neighbors_supplied() { + running_test(); + let set_past_neighbors_params_arc = Arc::new(Mutex::new(vec![])); + let mut config = BootstrapperConfig::new(); + let mut persistent_config = configure_default_persistent_config( + RATE_PACK | ACCOUNTANT_CONFIG_PARAMS | MAPPING_PROTOCOL, + ) + .set_past_neighbors_params(&set_past_neighbors_params_arc); + let multi_config = make_simplified_multi_config([ + "--chain", + "eth-ropsten", + "--neighborhood-mode", + "zero-hop", + ]); + let subject = UnprivilegedParseArgsConfigurationDaoReal {}; + + let _ = subject + .unprivileged_parse_args( + &multi_config, + &mut config, + &mut persistent_config, + &Logger::new("test"), + ) + .unwrap(); + + assert_eq!( + config.neighborhood_config, + NeighborhoodConfig { + mode: NeighborhoodMode::ZeroHop + } + ); + let set_past_neighbors_params = set_past_neighbors_params_arc.lock().unwrap(); + assert!(set_past_neighbors_params.is_empty()) + } + + #[test] + fn configure_zero_hop_with_neighbors_but_no_password() { + running_test(); + let mut persistent_config = PersistentConfigurationMock::new(); + //no results prepared for set_past_neighbors() and no panic so it was not called + let descriptor_list = vec![NodeDescriptor::try_from(( + main_cryptde(), + "masq://eth-ropsten:UJNoZW5p-PDVqEjpr3b_8jZ_93yPG8i5dOAgE1bhK_A@2.3.4.5:2345", + )) + .unwrap()]; + + let result = + zero_hop_neighbors_configuration(None, descriptor_list, &mut persistent_config); + + assert_eq!( + result, + Err(ConfiguratorError::required( + "neighbors", + "Cannot proceed without a password" + )) + ); + } + + #[test] + fn configure_zero_hop_with_neighbors_but_setting_values_failed() { + running_test(); + let mut persistent_config = PersistentConfigurationMock::new().set_past_neighbors_result( + Err(PersistentConfigError::DatabaseError("Oh yeah".to_string())), + ); + let descriptor_list = vec![NodeDescriptor::try_from(( + main_cryptde(), + "masq://eth-ropsten:UJNoZW5p-PDVqEjpr3b_8jZ_93yPG8i5dOAgE1bhK_A@2.3.4.5:2345", + )) + .unwrap()]; + + let result = zero_hop_neighbors_configuration( + Some("password".to_string()), + descriptor_list, + &mut persistent_config, + ); + + assert_eq!( + result, + Err(ConfiguratorError::required( + "neighbors", + "DatabaseError(\"Oh yeah\")" + )) + ); + } + + #[test] + fn unprivileged_parse_args_dao_real_creates_configurations() { + let home_dir = ensure_node_home_directory_exists( + "unprivileged_parse_args_configuration", + "unprivileged_parse_args_dao_real_creates_configurations", + ); + assert_unprivileged_parse_args_creates_configurations( + home_dir, + &UnprivilegedParseArgsConfigurationDaoReal {}, + ) + } + + #[test] + fn unprivileged_parse_args_dao_null_creates_configurations() { + let home_dir = ensure_node_home_directory_exists( + "unprivileged_parse_args_configuration", + "unprivileged_parse_args_dao_null_creates_configurations", + ); + assert_unprivileged_parse_args_creates_configurations( + home_dir, + &UnprivilegedParseArgsConfigurationDaoNull {}, + ) + } + + fn assert_unprivileged_parse_args_creates_configurations( + home_dir: PathBuf, + subject: &dyn UnprivilegedParseArgsConfiguration, + ) { + running_test(); + let config_dao: Box = Box::new(ConfigDaoReal::new( + DbInitializerReal::default() + .initialize(&home_dir.clone(), true, MigratorConfig::test_default()) + .unwrap(), + )); + let consuming_private_key_text = + "ABCDEF01ABCDEF01ABCDEF01ABCDEF01ABCDEF01ABCDEF01ABCDEF01ABCDEF01"; + let consuming_private_key = PlainData::from_str(consuming_private_key_text).unwrap(); + let mut persistent_config = PersistentConfigurationReal::new(config_dao); + let password = "secret-db-password"; + let args = ArgsBuilder::new() + .param("--config-file", "specified_config.toml") + .param("--dns-servers", "12.34.56.78,23.45.67.89") + .param( + "--neighbors", + &format!("masq://{identifier}:QmlsbA@1.2.3.4:1234/2345,masq://{identifier}:VGVk@2.3.4.5:3456/4567",identifier = DEFAULT_CHAIN.rec().literal_identifier), + ) + .param("--ip", "34.56.78.90") + .param("--clandestine-port", "1234") + .param("--ui-port", "5335") + .param("--data-directory", home_dir.to_str().unwrap()) + .param("--blockchain-service-url", "http://127.0.0.1:8545") + .param("--log-level", "trace") + .param("--fake-public-key", "AQIDBA") + .param("--db-password", password) + .param( + "--earning-wallet", + "0x0123456789012345678901234567890123456789", + ) + .param("--consuming-private-key", consuming_private_key_text) + .param("--mapping-protocol", "pcp") + .param("--real-user", "999:999:/home/booga"); + let mut config = BootstrapperConfig::new(); + let vcls: Vec> = + vec![Box::new(CommandLineVcl::new(args.into()))]; + let multi_config = make_new_test_multi_config(&app_node(), vcls).unwrap(); + + subject + .unprivileged_parse_args( + &multi_config, + &mut config, + &mut persistent_config, + &Logger::new("test logger"), + ) + .unwrap(); + + assert_eq!( + value_m!(multi_config, "config-file", PathBuf), + Some(PathBuf::from("specified_config.toml")), + ); + assert_eq!( + config.blockchain_bridge_config.blockchain_service_url_opt, + Some("http://127.0.0.1:8545".to_string()) + ); + assert_eq!( + config.earning_wallet, + Wallet::from_str("0x0123456789012345678901234567890123456789").unwrap() + ); + assert_eq!(Some(1234u16), config.clandestine_port_opt); + assert_eq!( + config.earning_wallet, + Wallet::from_str("0x0123456789012345678901234567890123456789").unwrap() + ); + assert_eq!( + config.consuming_wallet_opt, + Some(Wallet::from( + Bip32ECKeyProvider::from_raw_secret(consuming_private_key.as_slice()).unwrap() + )), + ); + assert_eq!( + config.neighborhood_config, + NeighborhoodConfig { + mode: NeighborhoodMode::Standard( + NodeAddr::new(&IpAddr::from_str("34.56.78.90").unwrap(), &[]), + vec![ + NodeDescriptor::try_from(( + main_cryptde(), + format!( + "masq://{}:QmlsbA@1.2.3.4:1234/2345", + DEFAULT_CHAIN.rec().literal_identifier + ) + .as_str() + )) + .unwrap(), + NodeDescriptor::try_from(( + main_cryptde(), + format!( + "masq://{}:VGVk@2.3.4.5:3456/4567", + DEFAULT_CHAIN.rec().literal_identifier + ) + .as_str() + )) + .unwrap(), + ], + DEFAULT_RATE_PACK.clone() + ) + } + ); + assert_eq!(config.db_password_opt, Some(password.to_string())); + assert_eq!(config.mapping_protocol_opt, Some(AutomapProtocol::Pcp)); + } + + #[test] + fn unprivileged_parse_args_creates_configuration_with_defaults() { + running_test(); + let args = ArgsBuilder::new(); + let mut config = BootstrapperConfig::new(); + let vcls: Vec> = + vec![Box::new(CommandLineVcl::new(args.into()))]; + let multi_config = make_new_test_multi_config(&app_node(), vcls).unwrap(); + let mut persistent_config = configure_default_persistent_config( + RATE_PACK | ACCOUNTANT_CONFIG_PARAMS | MAPPING_PROTOCOL, + ) + .check_password_result(Ok(false)); + let subject = UnprivilegedParseArgsConfigurationDaoReal {}; + + subject + .unprivileged_parse_args( + &multi_config, + &mut config, + &mut persistent_config, + &Logger::new("test logger"), + ) + .unwrap(); + + assert_eq!(None, config.clandestine_port_opt); + assert!(config + .neighborhood_config + .mode + .neighbor_configs() + .is_empty()); + assert_eq!( + config + .neighborhood_config + .mode + .node_addr_opt() + .unwrap() + .ip_addr(), + IpAddr::from_str("0.0.0.0").unwrap(), + ); + assert_eq!(config.earning_wallet, DEFAULT_EARNING_WALLET.clone(),); + assert_eq!(config.consuming_wallet_opt, None); + assert_eq!(config.mapping_protocol_opt, None); + } + + #[test] + fn unprivileged_parse_args_with_neighbor_and_mapping_protocol_in_database_but_not_command_line() + { + running_test(); + let args = ArgsBuilder::new() + .param("--ip", "1.2.3.4") + .param("--fake-public-key", "BORSCHT") + .param("--db-password", "password"); + let mut config = BootstrapperConfig::new(); + config.db_password_opt = Some("password".to_string()); + let vcls: Vec> = + vec![Box::new(CommandLineVcl::new(args.into()))]; + let multi_config = make_new_test_multi_config(&app_node(), vcls).unwrap(); + let set_mapping_protocol_params_arc = Arc::new(Mutex::new(vec![])); + let past_neighbors_params_arc = Arc::new(Mutex::new(vec![])); + let mut persistent_configuration = { + let config = make_persistent_config( + Some("password"), + None, + None, + None, + Some( + "masq://eth-ropsten:AQIDBA@1.2.3.4:1234,masq://eth-ropsten:AgMEBQ@2.3.4.5:2345", + ), + None, + ) + .check_password_result(Ok(false)) + .set_mapping_protocol_params(&set_mapping_protocol_params_arc) + .past_neighbors_params(&past_neighbors_params_arc) + .blockchain_service_url_result(Ok(None)); + default_persistent_config_just_accountant_config(config) + }; + let subject = UnprivilegedParseArgsConfigurationDaoReal {}; + + subject + .unprivileged_parse_args( + &multi_config, + &mut config, + &mut persistent_configuration, + &Logger::new("test logger"), + ) + .unwrap(); + + assert_eq!( + config.neighborhood_config.mode.neighbor_configs(), + &[ + NodeDescriptor::try_from(( + main_cryptde(), + "masq://eth-ropsten:AQIDBA@1.2.3.4:1234" + )) + .unwrap(), + NodeDescriptor::try_from(( + main_cryptde(), + "masq://eth-ropsten:AgMEBQ@2.3.4.5:2345" + )) + .unwrap(), + ] + ); + let past_neighbors_params = past_neighbors_params_arc.lock().unwrap(); + assert_eq!(past_neighbors_params[0], "password".to_string()); + assert_eq!(config.mapping_protocol_opt, Some(AutomapProtocol::Pcp)); + let set_mapping_protocol_params = set_mapping_protocol_params_arc.lock().unwrap(); + assert_eq!(*set_mapping_protocol_params, vec![]); + } + + #[test] + fn unprivileged_parse_args_with_blockchain_service_in_database_but_not_command_line() { + running_test(); + let args = ArgsBuilder::new().param("--neighborhood-mode", "zero-hop"); + let mut config = BootstrapperConfig::new(); + let vcls: Vec> = + vec![Box::new(CommandLineVcl::new(args.into()))]; + let multi_config = make_new_test_multi_config(&app_node(), vcls).unwrap(); + let mut persistent_configuration = { + let config = make_persistent_config(None, None, None, None, None, None) + .blockchain_service_url_result(Ok(Some("https://infura.io/ID".to_string()))); + default_persistent_config_just_accountant_config(config) + }; + let subject = UnprivilegedParseArgsConfigurationDaoReal {}; + + subject + .unprivileged_parse_args( + &multi_config, + &mut config, + &mut persistent_configuration, + &Logger::new("test"), + ) + .unwrap(); + + assert_eq!( + config.blockchain_bridge_config.blockchain_service_url_opt, + Some("https://infura.io/ID".to_string()) + ); + } + + #[test] + fn unprivileged_parse_args_with_mapping_protocol_both_on_command_line_and_in_database() { + running_test(); + let args = ArgsBuilder::new().param("--mapping-protocol", "pmp"); + let mut config = BootstrapperConfig::new(); + let vcls: Vec> = + vec![Box::new(CommandLineVcl::new(args.into()))]; + let multi_config = make_new_test_multi_config(&app_node(), vcls).unwrap(); + let set_mapping_protocol_params_arc = Arc::new(Mutex::new(vec![])); + let mut persistent_config = configure_default_persistent_config(0b0000_1101) + .mapping_protocol_result(Ok(Some(AutomapProtocol::Pcp))) + .set_mapping_protocol_params(&set_mapping_protocol_params_arc) + .set_mapping_protocol_result(Ok(())); + let subject = UnprivilegedParseArgsConfigurationDaoReal {}; + + subject + .unprivileged_parse_args( + &multi_config, + &mut config, + &mut persistent_config, + &Logger::new("test logger"), + ) + .unwrap(); + + assert_eq!(config.mapping_protocol_opt, Some(AutomapProtocol::Pmp)); + let set_mapping_protocol_params = set_mapping_protocol_params_arc.lock().unwrap(); + assert_eq!( + *set_mapping_protocol_params, + vec![Some(AutomapProtocol::Pmp)] + ); + } + + #[test] + fn unprivileged_parse_args_consuming_private_key_happy_path() { + running_test(); + let home_directory = ensure_node_home_directory_exists( + "unprivileged_parse_args_configuration", + "parse_args_consuming_private_key_happy_path", + ); + + let args = ArgsBuilder::new() + .param("--ip", "1.2.3.4") + .param("--data-directory", home_directory.to_str().unwrap()) + .opt("--db-password"); + let vcl_args: Vec> = vec![Box::new(NameValueVclArg::new( + &"--consuming-private-key", + &"cc46befe8d169b89db447bd725fc2368b12542113555302598430cb5d5c74ea9", + ))]; + + let faux_environment = CommandLineVcl::from(vcl_args); + + let mut config = BootstrapperConfig::new(); + config.db_password_opt = Some("password".to_string()); + let vcls: Vec> = vec![ + Box::new(faux_environment), + Box::new(CommandLineVcl::new(args.into())), + ]; + let multi_config = make_new_test_multi_config(&app_node(), vcls).unwrap(); + let subject = UnprivilegedParseArgsConfigurationDaoReal {}; + + subject + .unprivileged_parse_args( + &multi_config, + &mut config, + &mut configure_default_persistent_config( + RATE_PACK | MAPPING_PROTOCOL | ACCOUNTANT_CONFIG_PARAMS, + ), + &Logger::new("test logger"), + ) + .unwrap(); + + assert!(config.consuming_wallet_opt.is_some()); + assert_eq!( + format!("{}", config.consuming_wallet_opt.unwrap()), + "0x8e4d2317e56c8fd1fc9f13ba2aa62df1c5a542a7".to_string() + ); + } + + #[test] + fn unprivileged_parse_args_accountant_config_aggregated_params_command_line_values_different_from_database( + ) { + running_test(); + let set_scan_intervals_params_arc = Arc::new(Mutex::new(vec![])); + let set_payment_thresholds_params_arc = Arc::new(Mutex::new(vec![])); + let args = [ + "--ip", + "1.2.3.4", + "--scan-intervals", + "180|150|130", + "--payment-thresholds", + "10000|10000|1000|20000|1000|20000", + ]; + let mut config = BootstrapperConfig::new(); + let multi_config = make_simplified_multi_config(args); + let mut persistent_configuration = + configure_default_persistent_config(RATE_PACK | MAPPING_PROTOCOL) + .scan_intervals_result(Ok(ScanIntervals { + pending_payable_scan_interval: Duration::from_secs(100), + payable_scan_interval: Duration::from_secs(101), + receivable_scan_interval: Duration::from_secs(102), + })) + .payment_thresholds_result(Ok(PaymentThresholds { + threshold_interval_sec: 3000, + debt_threshold_gwei: 30000, + payment_grace_period_sec: 3000, + maturity_threshold_sec: 30000, + permanent_debt_allowed_gwei: 30000, + unban_below_gwei: 30000, + })) + .set_scan_intervals_params(&set_scan_intervals_params_arc) + .set_scan_intervals_result(Ok(())) + .set_payment_thresholds_params(&set_payment_thresholds_params_arc) + .set_payment_thresholds_result(Ok(())); + let subject = UnprivilegedParseArgsConfigurationDaoReal {}; + + subject + .unprivileged_parse_args( + &multi_config, + &mut config, + &mut persistent_configuration, + &Logger::new("test"), + ) + .unwrap(); + + let actual_accountant_config = config.accountant_config_opt.unwrap(); + let expected_accountant_config = AccountantConfig { + scan_intervals: ScanIntervals { + pending_payable_scan_interval: Duration::from_secs(180), + payable_scan_interval: Duration::from_secs(150), + receivable_scan_interval: Duration::from_secs(130), + }, + payment_thresholds: PaymentThresholds { + threshold_interval_sec: 1000, + debt_threshold_gwei: 10000, + payment_grace_period_sec: 1000, + maturity_threshold_sec: 10000, + permanent_debt_allowed_gwei: 20000, + unban_below_gwei: 20000, + }, + when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, + }; + assert_eq!(actual_accountant_config, expected_accountant_config); + let set_scan_intervals_params = set_scan_intervals_params_arc.lock().unwrap(); + assert_eq!(*set_scan_intervals_params, vec!["180|150|130".to_string()]); + let set_payment_thresholds_params = set_payment_thresholds_params_arc.lock().unwrap(); + assert_eq!( + *set_payment_thresholds_params, + vec!["10000|10000|1000|20000|1000|20000".to_string()] + ) + } + + #[test] + fn unprivileged_parse_args_configures_accountant_config_with_values_from_command_line_which_are_equal_to_those_in_database( + ) { + running_test(); + let args = [ + "--ip", + "1.2.3.4", + "--scan-intervals", + "180|150|130", + "--payment-thresholds", + "100000|1000|1000|20000|1000|20000", + ]; + let mut config = BootstrapperConfig::new(); + let multi_config = make_simplified_multi_config(args); + let mut persistent_configuration = + configure_default_persistent_config(RATE_PACK | MAPPING_PROTOCOL) + .scan_intervals_result(Ok(ScanIntervals { + pending_payable_scan_interval: Duration::from_secs(180), + payable_scan_interval: Duration::from_secs(150), + receivable_scan_interval: Duration::from_secs(130), + })) + .payment_thresholds_result(Ok(PaymentThresholds { + threshold_interval_sec: 1000, + debt_threshold_gwei: 100000, + payment_grace_period_sec: 1000, + maturity_threshold_sec: 1000, + permanent_debt_allowed_gwei: 20000, + unban_below_gwei: 20000, + })); + let subject = UnprivilegedParseArgsConfigurationDaoReal {}; + + subject + .unprivileged_parse_args( + &multi_config, + &mut config, + &mut persistent_configuration, + &Logger::new("test"), + ) + .unwrap(); + + let actual_accountant_config = config.accountant_config_opt.unwrap(); + let expected_accountant_config = AccountantConfig { + scan_intervals: ScanIntervals { + pending_payable_scan_interval: Duration::from_secs(180), + payable_scan_interval: Duration::from_secs(150), + receivable_scan_interval: Duration::from_secs(130), + }, + payment_thresholds: PaymentThresholds { + threshold_interval_sec: 1000, + debt_threshold_gwei: 100000, + payment_grace_period_sec: 1000, + maturity_threshold_sec: 1000, + permanent_debt_allowed_gwei: 20000, + unban_below_gwei: 20000, + }, + when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, + }; + assert_eq!(actual_accountant_config, expected_accountant_config); + //no prepared results for the setter methods, that is they were uncalled + } + + #[test] + fn unprivileged_parse_args_rate_pack_values_from_cli_different_from_database_standard_mode() { + running_test(); + let set_rate_pack_params_arc = Arc::new(Mutex::new(vec![])); + let args = [ + "--ip", + "1.2.3.4", + "--neighborhood-mode", + "standard", + "--rate-pack", + "2|3|4|5", + ]; + let mut config = BootstrapperConfig::new(); + let multi_config = make_simplified_multi_config(args); + let mut persistent_configuration = + configure_default_persistent_config(MAPPING_PROTOCOL | ACCOUNTANT_CONFIG_PARAMS) + .rate_pack_result(Ok(RatePack { + routing_byte_rate: 3, + routing_service_rate: 5, + exit_byte_rate: 4, + exit_service_rate: 7, + })) + .set_rate_pack_result(Ok(())) + .set_rate_pack_params(&set_rate_pack_params_arc); + let subject = UnprivilegedParseArgsConfigurationDaoReal {}; + + subject + .unprivileged_parse_args( + &multi_config, + &mut config, + &mut persistent_configuration, + &Logger::new("test"), + ) + .unwrap(); + + let actual_rate_pack = *config.neighborhood_config.mode.rate_pack(); + let expected_rate_pack = RatePack { + routing_byte_rate: 2, + routing_service_rate: 3, + exit_byte_rate: 4, + exit_service_rate: 5, + }; + assert_eq!(actual_rate_pack, expected_rate_pack); + let set_rate_pack_params = set_rate_pack_params_arc.lock().unwrap(); + assert_eq!(*set_rate_pack_params, vec!["2|3|4|5".to_string()]) + } + + #[test] + fn unprivileged_parse_args_rate_pack_with_values_from_cli_equal_to_database_standard_mode() { + running_test(); + let args = [ + "--ip", + "1.2.3.4", + "--neighborhood-mode", + "standard", + "--rate-pack", + "6|7|8|9", + ]; + let mut config = BootstrapperConfig::new(); + let multi_config = make_simplified_multi_config(args); + let mut persistent_configuration = + configure_default_persistent_config(ACCOUNTANT_CONFIG_PARAMS | MAPPING_PROTOCOL) + .rate_pack_result(Ok(RatePack { + routing_byte_rate: 6, + routing_service_rate: 7, + exit_byte_rate: 8, + exit_service_rate: 9, + })); + let subject = UnprivilegedParseArgsConfigurationDaoReal {}; + + subject + .unprivileged_parse_args( + &multi_config, + &mut config, + &mut persistent_configuration, + &Logger::new("test"), + ) + .unwrap(); + + assert_eq!(config.neighborhood_config.mode.is_standard(), true); + let actual_rate_pack = *config.neighborhood_config.mode.rate_pack(); + let expected_rate_pack = RatePack { + routing_byte_rate: 6, + routing_service_rate: 7, + exit_byte_rate: 8, + exit_service_rate: 9, + }; + assert_eq!(actual_rate_pack, expected_rate_pack); + //no prepared results for the setter methods, that is they were uncalled + } + + #[test] + fn unprivileged_parse_args_rate_pack_with_values_from_cli_equal_to_database_originate_only_mode( + ) { + running_test(); + let args = [ + "--ip", + "1.2.3.4", + "--chain", + "polygon-mainnet", + "--neighborhood-mode", + "originate-only", + "--rate-pack", + "2|3|4|5", + "--neighbors", + "masq://polygon-mainnet:d2U3Dv1BqtS5t_Zz3mt9_sCl7AgxUlnkB4jOMElylrU@172.50.48.6:9342", + ]; + let mut config = BootstrapperConfig::new(); + let multi_config = make_simplified_multi_config(args); + let mut persistent_configuration = + configure_default_persistent_config(ACCOUNTANT_CONFIG_PARAMS | MAPPING_PROTOCOL) + .rate_pack_result(Ok(RatePack { + routing_byte_rate: 2, + routing_service_rate: 3, + exit_byte_rate: 4, + exit_service_rate: 5, + })); + let subject = UnprivilegedParseArgsConfigurationDaoReal {}; + + subject + .unprivileged_parse_args( + &multi_config, + &mut config, + &mut persistent_configuration, + &Logger::new("test"), + ) + .unwrap(); + + assert_eq!(config.neighborhood_config.mode.is_originate_only(), true); + let actual_rate_pack = *config.neighborhood_config.mode.rate_pack(); + let expected_rate_pack = RatePack { + routing_byte_rate: 2, + routing_service_rate: 3, + exit_byte_rate: 4, + exit_service_rate: 5, + }; + assert_eq!(actual_rate_pack, expected_rate_pack); + //no prepared results for the setter methods, that is they're uncalled + } + + #[test] + fn unprivileged_parse_args_with_invalid_consuming_wallet_private_key_reacts_correctly() { + running_test(); + let home_directory = ensure_node_home_directory_exists( + "unprivileged_parse_args_configuration", + "parse_args_with_invalid_consuming_wallet_private_key_panics_correctly", + ); + let args = ArgsBuilder::new().param("--data-directory", home_directory.to_str().unwrap()); + let vcl_args: Vec> = vec![Box::new(NameValueVclArg::new( + &"--consuming-private-key", + &"not valid hex", + ))]; + let faux_environment = CommandLineVcl::from(vcl_args); + let vcls: Vec> = vec![ + Box::new(faux_environment), + Box::new(CommandLineVcl::new(args.into())), + ]; + + let result = make_new_test_multi_config(&app_node(), vcls).err().unwrap(); + + assert_eq!( + result, + ConfiguratorError::required("consuming-private-key", "Invalid value: not valid hex") + ) + } + + fn execute_process_combined_params_for_rate_pack( + multi_config: &MultiConfig, + persist_config: &mut dyn PersistentConfiguration, + ) -> Result { + process_combined_params( + "rate-pack", + multi_config, + persist_config, + |str: &str| RatePack::try_from(str), + |pc: &dyn PersistentConfiguration| pc.rate_pack(), + |pc: &mut dyn PersistentConfiguration, rate_pack| pc.set_rate_pack(rate_pack), + ) + } + + #[test] + fn process_combined_params_handles_parse_error() { + let multi_config = make_simplified_multi_config(["--rate-pack", "8|9"]); + let mut persist_config = + PersistentConfigurationMock::default().rate_pack_result(Ok(DEFAULT_RATE_PACK)); + + let result = + execute_process_combined_params_for_rate_pack(&multi_config, &mut persist_config); + + assert_eq!( + result, + Err(ConfiguratorError::required( + "rate-pack", + "Wrong number of values: expected 4 but 2 supplied" + )) + ) + } + + #[test] + #[should_panic(expected = "rate-pack: database query failed due to NotPresent")] + fn process_combined_params_panics_on_persistent_config_getter_method_with_cli_present() { + let multi_config = make_simplified_multi_config(["--rate-pack", "4|5|6|7"]); + let mut persist_config = PersistentConfigurationMock::default() + .rate_pack_result(Err(PersistentConfigError::NotPresent)); + + let _ = execute_process_combined_params_for_rate_pack(&multi_config, &mut persist_config); + } + + #[test] + #[should_panic(expected = "rate-pack: writing database failed due to: TransactionError")] + fn process_combined_params_panics_on_persistent_config_setter_method_with_cli_present() { + let multi_config = make_simplified_multi_config(["--rate-pack", "4|5|6|7"]); + let mut persist_config = PersistentConfigurationMock::default() + .rate_pack_result(Ok(RatePack::try_from("1|1|2|2").unwrap())) + .set_rate_pack_result(Err(PersistentConfigError::TransactionError)); + + let _ = execute_process_combined_params_for_rate_pack(&multi_config, &mut persist_config); + } + + #[test] + #[should_panic(expected = "rate-pack: database query failed due to NotPresent")] + fn process_combined_params_panics_on_persistent_config_getter_method_with_cli_absent() { + let multi_config = make_simplified_multi_config([]); + let mut persist_config = PersistentConfigurationMock::default() + .rate_pack_result(Err(PersistentConfigError::NotPresent)); + + let _ = execute_process_combined_params_for_rate_pack(&multi_config, &mut persist_config); + } + + #[test] + fn get_wallets_with_brand_new_database_establishes_default_earning_wallet_without_requiring_password( + ) { + running_test(); + let multi_config = make_simplified_multi_config([]); + let mut persistent_config = make_persistent_config(None, None, None, None, None, None); + let mut config = BootstrapperConfig::new(); + + get_wallets(&multi_config, &mut persistent_config, &mut config).unwrap(); + + assert_eq!(config.consuming_wallet_opt, None); + assert_eq!(config.earning_wallet, DEFAULT_EARNING_WALLET.clone()); + } + + #[test] + fn get_wallets_handles_failure_of_consuming_wallet_private_key() { + let multi_config = make_simplified_multi_config([]); + let mut persistent_config = PersistentConfigurationMock::new() + .earning_wallet_address_result(Ok(None)) + .consuming_wallet_private_key_result(Err(PersistentConfigError::NotPresent)); + let mut config = BootstrapperConfig::new(); + config.db_password_opt = Some("password".to_string()); + + let result = get_wallets(&multi_config, &mut persistent_config, &mut config); + + assert_eq!( + result, + Err(PersistentConfigError::NotPresent.into_configurator_error("consuming-private-key")) + ); + } + + #[test] + fn earning_wallet_address_different_from_database() { + running_test(); + let args = [ + "--earning-wallet", + "0x0123456789012345678901234567890123456789", + ]; + let multi_config = make_simplified_multi_config(args); + let mut persistent_config = make_persistent_config( + None, + None, + Some("0x9876543210987654321098765432109876543210"), + None, + None, + None, + ); + let mut config = BootstrapperConfig::new(); + + let result = get_wallets(&multi_config, &mut persistent_config, &mut config).err(); + + assert_eq! (result, Some (ConfiguratorError::new (vec![ + ParamError::new ("earning-wallet", "Cannot change to an address (0x0123456789012345678901234567890123456789) different from that previously set (0x9876543210987654321098765432109876543210)") + ]))); + } + + #[test] + fn earning_wallet_address_matches_database() { + running_test(); + let args = [ + "--earning-wallet", + "0xb00fa567890123456789012345678901234B00FA", + ]; + let multi_config = make_simplified_multi_config(args); + let mut persistent_config = make_persistent_config( + None, + None, + Some("0xB00FA567890123456789012345678901234b00fa"), + None, + None, + None, + ); + let mut config = BootstrapperConfig::new(); + + get_wallets(&multi_config, &mut persistent_config, &mut config).unwrap(); + + assert_eq!( + config.earning_wallet, + Wallet::new("0xb00fa567890123456789012345678901234b00fa") + ); + } + + #[test] + fn consuming_wallet_private_key_different_from_database() { + running_test(); + let consuming_private_key_hex = + "ABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD"; + let args = ["--consuming-private-key", consuming_private_key_hex]; + let multi_config = make_simplified_multi_config(args); + let mut persistent_config = make_persistent_config( + Some("password"), + Some("DCBADCBADCBADCBADCBADCBADCBADCBADCBADCBADCBADCBADCBADCBADCBADCBA"), + Some("0x0123456789012345678901234567890123456789"), + None, + None, + None, + ); + let mut config = BootstrapperConfig::new(); + config.db_password_opt = Some("password".to_string()); + + let result = get_wallets(&multi_config, &mut persistent_config, &mut config).err(); + + assert_eq!( + result, + Some(ConfiguratorError::new(vec![ParamError::new( + "consuming-private-key", + "Cannot change to a private key different from that previously set" + )])) + ) + } + + #[test] + fn consuming_wallet_private_key_with_no_db_password_parameter() { + running_test(); + let multi_config = make_simplified_multi_config([]); + let mut persistent_config = make_persistent_config( + None, + Some("0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"), + Some("0xcafedeadbeefbabefacecafedeadbeefbabeface"), + None, + None, + None, + ) + .check_password_result(Ok(false)); + let mut config = BootstrapperConfig::new(); + + get_wallets(&multi_config, &mut persistent_config, &mut config).unwrap(); + + assert_eq!(config.consuming_wallet_opt, None); + assert_eq!( + config.earning_wallet, + Wallet::from_str("0xcafedeadbeefbabefacecafedeadbeefbabeface").unwrap() + ); + } + + #[test] + fn configure_rate_pack_command_line_absent_config_dao_null_so_all_defaults() { + running_test(); + let multi_config = make_simplified_multi_config([]); + let mut persistent_config = make_persistent_config_real_with_config_dao_null(); + + let result = configure_rate_pack(&multi_config, &mut persistent_config).unwrap(); + + let expected_rate_pack = RatePack { + routing_byte_rate: DEFAULT_RATE_PACK.routing_byte_rate, + routing_service_rate: DEFAULT_RATE_PACK.routing_service_rate, + exit_byte_rate: DEFAULT_RATE_PACK.exit_byte_rate, + exit_service_rate: DEFAULT_RATE_PACK.exit_service_rate, + }; + assert_eq!(result, expected_rate_pack) + } + + #[test] + fn compute_mapping_protocol_returns_saved_value_if_nothing_supplied() { + let multi_config = make_new_test_multi_config( + &app_node(), + vec![Box::new(CommandLineVcl::new(ArgsBuilder::new().into()))], + ) + .unwrap(); + let logger = Logger::new("test"); + let mut persistent_config = configure_default_persistent_config(ZERO) + .mapping_protocol_result(Ok(Some(AutomapProtocol::Pmp))); + + let result = compute_mapping_protocol_opt(&multi_config, &mut persistent_config, &logger); + + assert_eq!(result, Some(AutomapProtocol::Pmp)); + // No result provided for .set_mapping_protocol; if it's called, the panic will fail this test + } + + #[test] + fn compute_mapping_protocol_saves_computed_value_if_different() { + let multi_config = make_new_test_multi_config( + &app_node(), + vec![Box::new(CommandLineVcl::new( + ArgsBuilder::new() + .param("--mapping-protocol", "IGDP") + .into(), + ))], + ) + .unwrap(); + let logger = Logger::new("test"); + let set_mapping_protocol_params_arc = Arc::new(Mutex::new(vec![])); + let mut persistent_config = configure_default_persistent_config(ZERO) + .mapping_protocol_result(Ok(Some(AutomapProtocol::Pmp))) + .set_mapping_protocol_params(&set_mapping_protocol_params_arc) + .set_mapping_protocol_result(Ok(())); + + let result = compute_mapping_protocol_opt(&multi_config, &mut persistent_config, &logger); + + assert_eq!(result, Some(AutomapProtocol::Igdp)); + let set_mapping_protocol_params = set_mapping_protocol_params_arc.lock().unwrap(); + assert_eq!( + *set_mapping_protocol_params, + vec![Some(AutomapProtocol::Igdp)] + ); + } + + #[test] + fn compute_mapping_protocol_blanks_database_if_command_line_with_missing_value() { + let multi_config = make_simplified_multi_config(["--mapping-protocol"]); + let logger = Logger::new("test"); + let set_mapping_protocol_params_arc = Arc::new(Mutex::new(vec![])); + let mut persistent_config = configure_default_persistent_config(ZERO) + .mapping_protocol_result(Ok(Some(AutomapProtocol::Pmp))) + .set_mapping_protocol_params(&set_mapping_protocol_params_arc) + .set_mapping_protocol_result(Ok(())); + + let result = compute_mapping_protocol_opt(&multi_config, &mut persistent_config, &logger); + + assert_eq!(result, None); + let set_mapping_protocol_params = set_mapping_protocol_params_arc.lock().unwrap(); + assert_eq!(*set_mapping_protocol_params, vec![None]); + } + + #[test] + fn compute_mapping_protocol_does_not_resave_entry_if_no_change() { + let multi_config = make_simplified_multi_config(["--mapping-protocol", "igdp"]); + let logger = Logger::new("test"); + let mut persistent_config = configure_default_persistent_config(ZERO) + .mapping_protocol_result(Ok(Some(AutomapProtocol::Igdp))); + + let result = compute_mapping_protocol_opt(&multi_config, &mut persistent_config, &logger); + + assert_eq!(result, Some(AutomapProtocol::Igdp)); + // No result provided for .set_mapping_protocol; if it's called, the panic will fail this test + } + + #[test] + fn compute_mapping_protocol_logs_and_uses_none_if_saved_mapping_protocol_cannot_be_read() { + init_test_logging(); + let multi_config = make_simplified_multi_config([]); + let logger = Logger::new("BAD_MP_READ"); + let mut persistent_config = configure_default_persistent_config(ZERO) + .mapping_protocol_result(Err(PersistentConfigError::NotPresent)); + + let result = compute_mapping_protocol_opt(&multi_config, &mut persistent_config, &logger); + + assert_eq!(result, None); + // No result provided for .set_mapping_protocol; if it's called, the panic will fail this test + TestLogHandler::new().exists_log_containing( + "WARN: BAD_MP_READ: Could not read mapping protocol from database: NotPresent", + ); + } + + #[test] + fn compute_mapping_protocol_logs_and_moves_on_if_mapping_protocol_cannot_be_saved() { + init_test_logging(); + let multi_config = make_simplified_multi_config(["--mapping-protocol", "IGDP"]); + let logger = Logger::new("BAD_MP_WRITE"); + let mut persistent_config = configure_default_persistent_config(ZERO) + .mapping_protocol_result(Ok(Some(AutomapProtocol::Pcp))) + .set_mapping_protocol_result(Err(PersistentConfigError::NotPresent)); + + let result = compute_mapping_protocol_opt(&multi_config, &mut persistent_config, &logger); + + assert_eq!(result, Some(AutomapProtocol::Igdp)); + TestLogHandler::new().exists_log_containing( + "WARN: BAD_MP_WRITE: Could not save mapping protocol to database: NotPresent", + ); + } + + #[test] + fn get_public_ip_returns_sentinel_if_multiconfig_provides_none() { + let multi_config = make_new_test_multi_config(&app_node(), vec![]).unwrap(); + + let result = get_public_ip(&multi_config); + + assert_eq!(result, Ok(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)))); + } + + #[test] + fn get_public_ip_uses_multi_config() { + let args = ArgsBuilder::new().param("--ip", "4.3.2.1"); + let vcl = Box::new(CommandLineVcl::new(args.into())); + let multi_config = make_new_test_multi_config(&app_node(), vec![vcl]).unwrap(); + + let result = get_public_ip(&multi_config); + + assert_eq!(result, Ok(IpAddr::from_str("4.3.2.1").unwrap())); + } + + fn make_persistent_config( + db_password_opt: Option<&str>, + consuming_wallet_private_key_opt: Option<&str>, + earning_wallet_address_opt: Option<&str>, + gas_price_opt: Option, + past_neighbors_opt: Option<&str>, + rate_pack_opt: Option, + ) -> PersistentConfigurationMock { + let consuming_wallet_private_key_opt = + consuming_wallet_private_key_opt.map(|x| x.to_string()); + let earning_wallet_opt = match earning_wallet_address_opt { + None => None, + Some(address) => Some(Wallet::from_str(address).unwrap()), + }; + let gas_price = gas_price_opt.unwrap_or(DEFAULT_GAS_PRICE); + let past_neighbors_result = match (past_neighbors_opt, db_password_opt) { + (Some(past_neighbors), Some(_)) => Ok(Some( + past_neighbors + .split(",") + .map(|s| NodeDescriptor::try_from((main_cryptde(), s)).unwrap()) + .collect::>(), + )), + _ => Ok(None), + }; + let rate_pack = rate_pack_opt.unwrap_or(DEFAULT_RATE_PACK); + PersistentConfigurationMock::new() + .consuming_wallet_private_key_result(Ok(consuming_wallet_private_key_opt)) + .earning_wallet_address_result( + Ok(earning_wallet_address_opt.map(|ewa| ewa.to_string())), + ) + .earning_wallet_result(Ok(earning_wallet_opt)) + .gas_price_result(Ok(gas_price)) + .past_neighbors_result(past_neighbors_result) + .mapping_protocol_result(Ok(Some(AutomapProtocol::Pcp))) + .rate_pack_result(Ok(rate_pack)) + } +} diff --git a/node/src/proxy_client/mod.rs b/node/src/proxy_client/mod.rs index 400b8fc89..9e2a542fe 100644 --- a/node/src/proxy_client/mod.rs +++ b/node/src/proxy_client/mod.rs @@ -349,10 +349,10 @@ mod tests { use crate::sub_lib::versioned_data::VersionedData; use crate::sub_lib::wallet::Wallet; use crate::test_utils::make_wallet; - use crate::test_utils::pure_test_utils::prove_that_crash_request_handler_is_hooked_up; use crate::test_utils::recorder::make_recorder; use crate::test_utils::recorder::peer_actors_builder; use crate::test_utils::recorder::Recorder; + use crate::test_utils::unshared_test_utils::prove_that_crash_request_handler_is_hooked_up; use crate::test_utils::*; use actix::System; use masq_lib::blockchains::chains::Chain; diff --git a/node/src/proxy_server/mod.rs b/node/src/proxy_server/mod.rs index e8e8a72ea..6313ab218 100644 --- a/node/src/proxy_server/mod.rs +++ b/node/src/proxy_server/mod.rs @@ -20,11 +20,10 @@ use crate::sub_lib::cryptde::PublicKey; use crate::sub_lib::dispatcher::InboundClientData; use crate::sub_lib::dispatcher::{Endpoint, StreamShutdownMsg}; use crate::sub_lib::hopper::{ExpiredCoresPackage, IncipientCoresPackage}; -use crate::sub_lib::neighborhood::RatePack; use crate::sub_lib::neighborhood::RouteQueryMessage; use crate::sub_lib::neighborhood::RouteQueryResponse; use crate::sub_lib::neighborhood::{ExpectedService, NodeRecordMetadataMessage}; -use crate::sub_lib::neighborhood::{ExpectedServices, DEFAULT_RATE_PACK}; +use crate::sub_lib::neighborhood::{ExpectedServices, RatePack, DEFAULT_RATE_PACK}; use crate::sub_lib::peer_actors::BindMessage; use crate::sub_lib::proxy_client::{ClientResponsePayload_0v1, DnsResolveFailure_0v1}; use crate::sub_lib::proxy_server::ClientRequestPayload_0v1; @@ -731,12 +730,12 @@ impl ProxyServer { } earning_wallets_and_rates .into_iter() - .for_each(|(earning_wallet, _rate_pack)| { + .for_each(|(earning_wallet, rate_pack)| { let report_routing_service_consumed = ReportRoutingServiceConsumedMessage { earning_wallet: earning_wallet.clone(), payload_size, - service_rate: DEFAULT_RATE_PACK.routing_service_rate, - byte_rate: DEFAULT_RATE_PACK.routing_byte_rate, + service_rate: rate_pack.routing_service_rate, + byte_rate: rate_pack.routing_byte_rate, }; accountant_routing_sub .try_send(report_routing_service_consumed) @@ -758,13 +757,13 @@ impl ProxyServer { } _ => None, }) { - Some((earning_wallet, _rate_pack)) => { + Some((earning_wallet, rate_pack)) => { let payload_size = payload.sequenced_packet.data.len(); let report_exit_service_consumed_message = ReportExitServiceConsumedMessage { earning_wallet: earning_wallet.clone(), payload_size, - service_rate: DEFAULT_RATE_PACK.exit_service_rate, - byte_rate: DEFAULT_RATE_PACK.exit_byte_rate, + service_rate: rate_pack.exit_service_rate, + byte_rate: rate_pack.exit_byte_rate, }; accountant_exit_sub .try_send(report_exit_service_consumed_message) @@ -912,7 +911,7 @@ impl ProxyServer { .iter() .for_each(|service| match service { ExpectedService::Nothing => (), - ExpectedService::Exit(_, wallet, _rate_pack) => self + ExpectedService::Exit(_, wallet, rate_pack) => self .subs .as_ref() .expect("ProxyServer unbound") @@ -920,8 +919,8 @@ impl ProxyServer { .try_send(ReportExitServiceConsumedMessage { earning_wallet: wallet.clone(), payload_size: exit_size, - service_rate: DEFAULT_RATE_PACK.exit_service_rate, - byte_rate: DEFAULT_RATE_PACK.exit_byte_rate, + service_rate: rate_pack.exit_service_rate, + byte_rate: rate_pack.exit_byte_rate, }) .expect("Accountant is dead"), ExpectedService::Routing(_, wallet, _rate_pack) => self @@ -967,8 +966,8 @@ mod tests { use crate::sub_lib::dispatcher::Component; use crate::sub_lib::hop::LiveHop; use crate::sub_lib::hopper::MessageType; + use crate::sub_lib::neighborhood::ExpectedService; use crate::sub_lib::neighborhood::ExpectedServices; - use crate::sub_lib::neighborhood::{ExpectedService, DEFAULT_RATE_PACK}; use crate::sub_lib::proxy_client::{ClientResponsePayload_0v1, DnsResolveFailure_0v1}; use crate::sub_lib::proxy_server::ClientRequestPayload_0v1; use crate::sub_lib::proxy_server::ProxyProtocol; @@ -981,11 +980,11 @@ mod tests { use crate::test_utils::main_cryptde; use crate::test_utils::make_meaningless_stream_key; use crate::test_utils::make_wallet; - use crate::test_utils::pure_test_utils::prove_that_crash_request_handler_is_hooked_up; use crate::test_utils::recorder::make_recorder; use crate::test_utils::recorder::peer_actors_builder; use crate::test_utils::recorder::Recorder; use crate::test_utils::recorder::Recording; + use crate::test_utils::unshared_test_utils::prove_that_crash_request_handler_is_hooked_up; use crate::test_utils::zero_hop_route_response; use crate::test_utils::{alias_cryptde, rate_pack}; use crate::test_utils::{make_meaningless_route, make_paying_wallet}; @@ -1097,14 +1096,15 @@ mod tests { idx: usize, wallet: &Wallet, payload_size: usize, + rate_pack: RatePack, ) { assert_eq!( accountant_recording.get_record::(idx), &ReportExitServiceConsumedMessage { earning_wallet: wallet.clone(), payload_size, - service_rate: DEFAULT_RATE_PACK.exit_service_rate, - byte_rate: DEFAULT_RATE_PACK.exit_byte_rate, + service_rate: rate_pack.exit_service_rate, + byte_rate: rate_pack.exit_byte_rate, } ); } @@ -2237,6 +2237,8 @@ mod tests { let (accountant_mock, _, accountant_recording_arc) = make_recorder(); let (hopper_mock, _, hopper_recording_arc) = make_recorder(); let (proxy_server_mock, _, proxy_server_recording_arc) = make_recorder(); + let routing_node_1_rate_pack = rate_pack(101); + let routing_node_2_rate_pack = rate_pack(102); let route_query_response = RouteQueryResponse { route: make_meaningless_route(), expected_services: ExpectedServices::RoundTrip( @@ -2245,12 +2247,12 @@ mod tests { ExpectedService::Routing( PublicKey::new(&[1]), route_1_earning_wallet.clone(), - rate_pack(101), + routing_node_1_rate_pack, ), ExpectedService::Routing( PublicKey::new(&[2]), route_2_earning_wallet.clone(), - rate_pack(102), + routing_node_2_rate_pack, ), ExpectedService::Exit( PublicKey::new(&[3]), @@ -2325,8 +2327,8 @@ mod tests { &ReportRoutingServiceConsumedMessage { earning_wallet: route_1_earning_wallet, payload_size: payload_enc.len(), - service_rate: DEFAULT_RATE_PACK.routing_service_rate, - byte_rate: DEFAULT_RATE_PACK.routing_byte_rate, + service_rate: routing_node_1_rate_pack.routing_service_rate, + byte_rate: routing_node_1_rate_pack.routing_byte_rate, } ); let record = recording.get_record::(2); @@ -2335,8 +2337,8 @@ mod tests { &ReportRoutingServiceConsumedMessage { earning_wallet: route_2_earning_wallet, payload_size: payload_enc.len(), - service_rate: DEFAULT_RATE_PACK.routing_service_rate, - byte_rate: DEFAULT_RATE_PACK.routing_byte_rate, + service_rate: routing_node_2_rate_pack.routing_service_rate, + byte_rate: routing_node_2_rate_pack.routing_byte_rate, } ); let recording = proxy_server_recording_arc.lock().unwrap(); @@ -2473,6 +2475,7 @@ mod tests { let http_request = b"GET /index.html HTTP/1.1\r\nHost: nowhere.com\r\n\r\n"; let (accountant_mock, accountant_awaiter, accountant_log_arc) = make_recorder(); let (neighborhood_mock, _, _) = make_recorder(); + let exit_node_rate_pack = rate_pack(101); let neighborhood_mock = neighborhood_mock.route_query_response(Some(RouteQueryResponse { route: make_meaningless_route(), expected_services: ExpectedServices::RoundTrip( @@ -2481,7 +2484,7 @@ mod tests { ExpectedService::Exit( PublicKey::new(&[3]), earning_wallet.clone(), - rate_pack(101), + exit_node_rate_pack, ), ], vec![ @@ -2537,8 +2540,8 @@ mod tests { &ReportExitServiceConsumedMessage { earning_wallet, payload_size: expected_data.len(), - service_rate: DEFAULT_RATE_PACK.exit_service_rate, - byte_rate: DEFAULT_RATE_PACK.exit_byte_rate, + service_rate: exit_node_rate_pack.exit_service_rate, + byte_rate: exit_node_rate_pack.exit_byte_rate, } ); } @@ -3379,6 +3382,7 @@ mod tests { 0, &incoming_route_d_wallet, first_exit_size, + rate_pack(101), ); check_routing_report( &accountant_recording, @@ -3398,6 +3402,7 @@ mod tests { 3, &incoming_route_g_wallet, second_exit_size, + rate_pack(104), ); check_routing_report( &accountant_recording, @@ -3563,7 +3568,13 @@ mod tests { system.run(); let accountant_recording = accountant_recording_arc.lock().unwrap(); - check_exit_report(&accountant_recording, 0, &incoming_route_d_wallet, 0); + check_exit_report( + &accountant_recording, + 0, + &incoming_route_d_wallet, + 0, + rate_pack(101), + ); check_routing_report( &accountant_recording, 1, diff --git a/node/src/run_modes_factories.rs b/node/src/run_modes_factories.rs index fb744a4a1..b0daab7e8 100644 --- a/node/src/run_modes_factories.rs +++ b/node/src/run_modes_factories.rs @@ -25,22 +25,22 @@ pub struct DumpConfigRunnerFactoryReal; pub struct ServerInitializerFactoryReal; pub struct DaemonInitializerFactoryReal { configurator: RefCell>>>, - inner: RefCell>, + inner: RefCell>, } impl Default for DaemonInitializerFactoryReal { fn default() -> Self { DaemonInitializerFactoryReal::new( Box::new(NodeConfiguratorInitializationReal), - ClusteredParams::default(), + DIClusteredParams::default(), ) } } impl DaemonInitializerFactoryReal { - fn new( + pub fn new( configurator: Box>, - clustered_params: ClusteredParams, + clustered_params: DIClusteredParams, ) -> Self { Self { configurator: RefCell::new(Some(configurator)), @@ -48,8 +48,11 @@ impl DaemonInitializerFactoryReal { } } - fn expect(mut value_opt: Option) -> T { - value_opt.take().expectv(std::any::type_name::()) + fn expect(value_ref_opt: &RefCell>) -> T { + value_ref_opt + .take() + .take() + .expectv(std::any::type_name::()) } } @@ -92,20 +95,20 @@ impl ServerInitializerFactory for ServerInitializerFactoryReal { impl DaemonInitializerFactory for DaemonInitializerFactoryReal { fn make(&self, args: &[String]) -> Result, ConfiguratorError> { - let configurator = Self::expect(self.configurator.take()); let multi_config = NodeConfiguratorInitializationReal::make_multi_config_daemon_specific(args)?; + let configurator = Self::expect(&self.configurator); let initialization_config = configurator.configure(&multi_config)?; - let initializer_clustered_params = Self::expect(self.inner.take()); + let clustered_params = Self::expect(&self.inner); let daemon_initializer = Box::new(DaemonInitializerReal::new( initialization_config, - initializer_clustered_params, + clustered_params, )); Ok(daemon_initializer) } } -impl Default for ClusteredParams { +impl Default for DIClusteredParams { fn default() -> Self { Self { dirs_wrapper: Box::new(DirsWrapperReal), @@ -117,7 +120,7 @@ impl Default for ClusteredParams { } } -pub struct ClusteredParams { +pub struct DIClusteredParams { pub dirs_wrapper: Box, pub logger_initializer_wrapper: Box, pub channel_factory: Box, @@ -127,36 +130,22 @@ pub struct ClusteredParams { #[cfg(test)] mod tests { - use crate::daemon::daemon_initializer::{ - DaemonInitializerReal, RecipientsFactoryReal, RerunnerReal, - }; use crate::database::config_dumper::DumpConfigRunnerReal; use crate::node_configurator::node_configurator_initialization::NodeConfiguratorInitializationReal; - use crate::run_modes_factories::mocks::NodeConfiguratorInitializationMock; + use crate::run_modes_factories::mocks::{ + test_clustered_params, NodeConfiguratorInitializationMock, + }; use crate::run_modes_factories::{ - ClusteredParams, DaemonInitializerFactory, DaemonInitializerFactoryReal, + DIClusteredParams, DaemonInitializerFactory, DaemonInitializerFactoryReal, DumpConfigRunnerFactory, DumpConfigRunnerFactoryReal, ServerInitializerFactory, ServerInitializerFactoryReal, }; - use crate::server_initializer::test_utils::LoggerInitializerWrapperMock; use crate::server_initializer::ServerInitializerReal; - use crate::test_utils::pure_test_utils::{ - make_pre_populated_mocked_directory_wrapper, ChannelFactoryMock, - }; use masq_lib::shared_schema::ConfiguratorError; use masq_lib::utils::array_of_borrows_to_vec; + use std::cell::RefCell; use std::sync::{Arc, Mutex}; - fn test_clustered_params() -> ClusteredParams { - ClusteredParams { - dirs_wrapper: Box::new(make_pre_populated_mocked_directory_wrapper()), - logger_initializer_wrapper: Box::new(LoggerInitializerWrapperMock::new()), - channel_factory: Box::new(ChannelFactoryMock::new()), - recipients_factory: Box::new(RecipientsFactoryReal::new()), - rerunner: Box::new(RerunnerReal::new()), - } - } - #[test] fn make_for_dump_config_runner_factory_produces_a_proper_object() { let subject = DumpConfigRunnerFactoryReal; @@ -179,55 +168,15 @@ mod tests { .unwrap(); } - #[test] - fn make_for_daemon_initializer_factory_labours_hard_and_produces_a_proper_object() { - use std::ptr::addr_of; - let daemon_clustered_params = test_clustered_params(); - let init_pointer_of_recipients_factory = - addr_of!(*daemon_clustered_params.recipients_factory); - let init_pointer_of_channel_factory = addr_of!(*daemon_clustered_params.channel_factory); - let init_pointer_of_rerunner = addr_of!(*daemon_clustered_params.rerunner); - let subject = DaemonInitializerFactoryReal::new( - Box::new(NodeConfiguratorInitializationReal), - daemon_clustered_params, - ); - let args = &array_of_borrows_to_vec(&[ - "program", - "--initialization", - "--ui-port", - 1234.to_string().as_str(), - ]); - - let result = subject.make(&args).unwrap(); - - let factory_product = result - .as_any() - .downcast_ref::() - .unwrap(); - let (config, channel_factory, recipients_factory, rerunner) = - factory_product.access_to_the_fields_test(); - assert_eq!(config.ui_port, 1234); - let final_pointer_of_recipients_factory = addr_of!(**recipients_factory); - assert_eq!( - init_pointer_of_recipients_factory, - final_pointer_of_recipients_factory - ); - let final_pointer_of_channel_factory = addr_of!(**channel_factory); - assert_eq!( - init_pointer_of_channel_factory, - final_pointer_of_channel_factory - ); - let final_pointer_of_rerunner = addr_of!(**rerunner); - assert_eq!(init_pointer_of_rerunner, final_pointer_of_rerunner); - } + //test for make() of DaemonInitializerReal moved to daemon_initializer.rs #[test] #[should_panic( - expected = "value for 'node_lib::run_modes_factories::ClusteredParams' badly prepared" + expected = "value for 'node_lib::run_modes_factories::DIClusteredParams' badly prepared" )] fn incorrect_value_in_expect_is_reasonably_displayed() { - let cluster_params_opt: Option = None; - let _ = DaemonInitializerFactoryReal::expect(cluster_params_opt); + let cluster_params_ref_opt: RefCell> = RefCell::new(None); + let _ = DaemonInitializerFactoryReal::expect(&cluster_params_ref_opt); } #[test] @@ -286,15 +235,20 @@ mod tests { #[cfg(test)] pub mod mocks { + use crate::daemon::daemon_initializer::{RecipientsFactoryReal, RerunnerReal}; use crate::node_configurator::node_configurator_initialization::InitializationConfig; use crate::node_configurator::NodeConfigurator; use crate::run_modes_factories::{ - DaemonInitializer, DaemonInitializerFactory, DumpConfigRunner, DumpConfigRunnerFactory, - RunModeResult, ServerInitializer, ServerInitializerFactory, + DIClusteredParams, DaemonInitializer, DaemonInitializerFactory, DumpConfigRunner, + DumpConfigRunnerFactory, RunModeResult, ServerInitializer, ServerInitializerFactory, }; + use crate::server_initializer::test_utils::LoggerInitializerWrapperMock; use crate::server_initializer::tests::{ ingest_values_from_multi_config, MultiConfigExtractedValues, }; + use crate::test_utils::unshared_test_utils::{ + make_pre_populated_mocked_directory_wrapper, ChannelFactoryMock, + }; use futures::{Async, Future}; use masq_lib::command::StdStreams; use masq_lib::multi_config::MultiConfig; @@ -302,6 +256,16 @@ pub mod mocks { use std::cell::RefCell; use std::sync::{Arc, Mutex}; + pub fn test_clustered_params() -> DIClusteredParams { + DIClusteredParams { + dirs_wrapper: Box::new(make_pre_populated_mocked_directory_wrapper()), + logger_initializer_wrapper: Box::new(LoggerInitializerWrapperMock::new()), + channel_factory: Box::new(ChannelFactoryMock::new()), + recipients_factory: Box::new(RecipientsFactoryReal::new()), + rerunner: Box::new(RerunnerReal::new()), + } + } + #[derive(Default)] pub struct DumpConfigRunnerFactoryMock { make_results: RefCell>>, diff --git a/node/src/server_initializer.rs b/node/src/server_initializer.rs index 446fdc554..638948cb2 100644 --- a/node/src/server_initializer.rs +++ b/node/src/server_initializer.rs @@ -392,9 +392,10 @@ pub mod tests { use crate::node_test_utils::DirsWrapperMock; use crate::server_initializer::test_utils::PrivilegeDropperMock; use crate::test_utils::logfile_name_guard::LogfileNameGuard; - use crate::test_utils::pure_test_utils::make_pre_populated_mocked_directory_wrapper; + use crate::test_utils::unshared_test_utils::make_pre_populated_mocked_directory_wrapper; + use masq_lib::constants::DEFAULT_CHAIN; use masq_lib::crash_point::CrashPoint; - use masq_lib::multi_config::MultiConfig; + use masq_lib::multi_config::{make_arg_matches_accesible, MultiConfig}; use masq_lib::shared_schema::{ConfiguratorError, ParamError}; use masq_lib::test_utils::fake_stream_holder::{ ByteArrayReader, ByteArrayWriter, FakeStreamHolder, @@ -540,7 +541,12 @@ pub mod tests { ) -> Self { self.arg_matches_requested_entries = required .iter() - .map(|key| multi_config.value_of(key).unwrap().to_string()) + .map(|key| { + make_arg_matches_accesible(multi_config) + .value_of(key) + .unwrap() + .to_string() + }) .collect(); self } @@ -823,7 +829,10 @@ pub mod tests { assert_eq!( *chown_params, vec![( - PathBuf::from("/home/alice/mock_directory/MASQ/eth-mainnet"), + PathBuf::from(format!( + "/home/alice/mock_directory/MASQ/{}", + DEFAULT_CHAIN.rec().literal_identifier + )), real_user.clone() )] ); diff --git a/node/src/stream_handler_pool.rs b/node/src/stream_handler_pool.rs index 3a68de340..5c97c7946 100644 --- a/node/src/stream_handler_pool.rs +++ b/node/src/stream_handler_pool.rs @@ -15,11 +15,10 @@ use crate::sub_lib::cryptde::PublicKey; use crate::sub_lib::dispatcher; use crate::sub_lib::dispatcher::Endpoint; use crate::sub_lib::dispatcher::{DispatcherSubs, StreamShutdownMsg}; -use crate::sub_lib::neighborhood::DispatcherNodeQueryMessage; use crate::sub_lib::neighborhood::NodeQueryMessage; use crate::sub_lib::neighborhood::NodeQueryResponseMetadata; use crate::sub_lib::neighborhood::RemoveNeighborMessage; -use crate::sub_lib::neighborhood::ZERO_RATE_PACK; +use crate::sub_lib::neighborhood::{DispatcherNodeQueryMessage, ZERO_RATE_PACK}; use crate::sub_lib::node_addr::NodeAddr; use crate::sub_lib::sequence_buffer::SequencedPacket; use crate::sub_lib::stream_connector::StreamConnector; @@ -310,7 +309,7 @@ impl StreamHandlerPool { result: Some(NodeQueryResponseMetadata::new( PublicKey::new(&[]), Some(NodeAddr::from(&socket_addr)), - ZERO_RATE_PACK.clone(), + ZERO_RATE_PACK, )), context: msg, }) @@ -575,7 +574,6 @@ mod tests { use crate::test_utils::await_messages; use crate::test_utils::channel_wrapper_mocks::SenderWrapperMock; use crate::test_utils::main_cryptde; - use crate::test_utils::pure_test_utils::prove_that_crash_request_handler_is_hooked_up; use crate::test_utils::rate_pack; use crate::test_utils::recorder::make_recorder; use crate::test_utils::recorder::peer_actors_builder; @@ -584,6 +582,7 @@ mod tests { use crate::test_utils::stream_connector_mock::StreamConnectorMock; use crate::test_utils::tokio_wrapper_mocks::ReadHalfWrapperMock; use crate::test_utils::tokio_wrapper_mocks::WriteHalfWrapperMock; + use crate::test_utils::unshared_test_utils::prove_that_crash_request_handler_is_hooked_up; use actix::Actor; use actix::Addr; use actix::System; diff --git a/node/src/sub_lib/accountant.rs b/node/src/sub_lib/accountant.rs index cd089552e..735d8dbc3 100644 --- a/node/src/sub_lib/accountant.rs +++ b/node/src/sub_lib/accountant.rs @@ -19,11 +19,58 @@ lazy_static! { pub static ref TEMPORARY_CONSUMING_WALLET: Wallet = Wallet::from_str("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF").expect("Internal error"); } -#[derive(Clone, PartialEq, Debug)] -pub struct AccountantConfig { - pub payables_scan_interval: Duration, - pub receivables_scan_interval: Duration, +lazy_static! { + pub static ref DEFAULT_PAYMENT_THRESHOLDS: PaymentThresholds = PaymentThresholds { + debt_threshold_gwei: 1_000_000_000, + maturity_threshold_sec: 1200, + payment_grace_period_sec: 1200, + permanent_debt_allowed_gwei: 500_000_000, + threshold_interval_sec: 21600, + unban_below_gwei: 500_000_000, + }; +} + +lazy_static! { + pub static ref DEFAULT_SCAN_INTERVALS: ScanIntervals = ScanIntervals { + pending_payable_scan_interval: Duration::from_secs(600), + payable_scan_interval: Duration::from_secs(600), + receivable_scan_interval: Duration::from_secs(600) + }; +} + +//please, alphabetical order +#[derive(PartialEq, Debug, Clone, Copy, Default)] +pub struct PaymentThresholds { + pub debt_threshold_gwei: i64, + pub maturity_threshold_sec: i64, + pub payment_grace_period_sec: i64, + pub permanent_debt_allowed_gwei: i64, + pub threshold_interval_sec: i64, + pub unban_below_gwei: i64, +} + +//this code is used in tests in Accountant +impl PaymentThresholds { + pub fn sugg_and_grace(&self, now: i64) -> i64 { + now - self.maturity_threshold_sec - self.payment_grace_period_sec + } + + pub fn sugg_thru_decreasing(&self, now: i64) -> i64 { + self.sugg_and_grace(now) - self.threshold_interval_sec + } +} + +#[derive(PartialEq, Debug, Clone, Copy, Default)] +pub struct ScanIntervals { pub pending_payable_scan_interval: Duration, + pub payable_scan_interval: Duration, + pub receivable_scan_interval: Duration, +} + +#[derive(Clone, Copy, PartialEq, Debug)] +pub struct AccountantConfig { + pub scan_intervals: ScanIntervals, + pub payment_thresholds: PaymentThresholds, pub when_pending_too_long_sec: u64, } @@ -94,11 +141,15 @@ pub struct FinancialStatisticsMessage { #[cfg(test)] mod tests { - use crate::sub_lib::accountant::{DEFAULT_EARNING_WALLET, TEMPORARY_CONSUMING_WALLET}; + use crate::sub_lib::accountant::{ + PaymentThresholds, ScanIntervals, DEFAULT_EARNING_WALLET, DEFAULT_PAYMENT_THRESHOLDS, + DEFAULT_SCAN_INTERVALS, TEMPORARY_CONSUMING_WALLET, + }; use crate::sub_lib::wallet::Wallet; use crate::test_utils::recorder::{make_accountant_subs_from_recorder, Recorder}; use actix::Actor; use std::str::FromStr; + use std::time::Duration; #[test] fn constants_have_correct_values() { @@ -106,7 +157,21 @@ mod tests { Wallet::from_str("0x27d9A2AC83b493f88ce9B4532EDcf74e95B9788d").expect("Internal error"); let temporary_consuming_wallet_expected: Wallet = Wallet::from_str("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF").expect("Internal error"); - + let payment_thresholds_expected = PaymentThresholds { + debt_threshold_gwei: 1_000_000_000, + maturity_threshold_sec: 1200, + payment_grace_period_sec: 1200, + permanent_debt_allowed_gwei: 500_000_000, + threshold_interval_sec: 21600, + unban_below_gwei: 500_000_000, + }; + let scan_intervals_expected = ScanIntervals { + pending_payable_scan_interval: Duration::from_secs(600), + payable_scan_interval: Duration::from_secs(600), + receivable_scan_interval: Duration::from_secs(600), + }; + assert_eq!(*DEFAULT_SCAN_INTERVALS, scan_intervals_expected); + assert_eq!(*DEFAULT_PAYMENT_THRESHOLDS, payment_thresholds_expected); assert_eq!(*DEFAULT_EARNING_WALLET, default_earning_wallet_expected); assert_eq!( *TEMPORARY_CONSUMING_WALLET, diff --git a/node/src/sub_lib/combined_parameters.rs b/node/src/sub_lib/combined_parameters.rs new file mode 100644 index 000000000..355370595 --- /dev/null +++ b/node/src/sub_lib/combined_parameters.rs @@ -0,0 +1,598 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::sub_lib::accountant::{PaymentThresholds, ScanIntervals}; +use crate::sub_lib::combined_parameters::CombinedParamsDataTypes::{I64, U64}; +use crate::sub_lib::neighborhood::RatePack; +use masq_lib::constants::COMBINED_PARAMETERS_DELIMITER; +use masq_lib::utils::ExpectValue; +use paste::paste; +use std::any::Any; +use std::collections::HashMap; +use std::fmt; +use std::fmt::Display; +use std::time::Duration; + +macro_rules! initiate_struct{ + ($struct_type: ident, $hash_map: expr, $($field:literal),+) =>{ + paste!{ + $struct_type{ + $([<$field>]: CombinedParamsValueRetriever::get_value( + $hash_map, + $field + )),+ + } + } + }; + ($struct_type: ident, $hash_map: expr, $value_convertor: expr, $($field:literal),+) =>{ + paste!{ + $struct_type{ + $([<$field>]: $value_convertor(CombinedParamsValueRetriever::get_value( + $hash_map, + $field + ))),+ + } + } + } +} + +#[derive(PartialEq, Debug)] +pub enum CombinedParamsDataTypes { + U64, + I64, + U128, +} + +#[derive(PartialEq, Debug)] +pub enum CombinedParamsValueRetriever { + U64(u64), + I64(i64), + U128(u128), +} + +impl CombinedParamsValueRetriever { + fn parse(str_value: &str, data_type: &CombinedParamsDataTypes) -> Result { + fn parse(str_value: &str) -> Result + where + T: std::str::FromStr, + ::Err: ToString, + { + str_value.parse::().map_err(|e| e.to_string()) + } + match data_type { + CombinedParamsDataTypes::U64 => { + Ok(CombinedParamsValueRetriever::U64(parse(str_value)?)) + } + CombinedParamsDataTypes::I64 => { + Ok(CombinedParamsValueRetriever::I64(parse(str_value)?)) + } + CombinedParamsDataTypes::U128 => { + Ok(CombinedParamsValueRetriever::U128(parse(str_value)?)) + } + } + } + + pub fn get_value( + map: &HashMap, + parameter_name: &str, + ) -> T { + let dynamic: &dyn Any = match map.get(parameter_name).expectv(parameter_name) { + CombinedParamsValueRetriever::U64(num) => num, + CombinedParamsValueRetriever::I64(num) => num, + CombinedParamsValueRetriever::U128(num) => num, + }; + *dynamic + .downcast_ref::() + .unwrap_or_else(|| panic!("expected Some() of {}", std::any::type_name::())) + } +} + +#[derive(Debug)] +enum CombinedParams { + RatePack(Option), + PaymentThresholds(Option), + ScanIntervals(Option), +} + +impl CombinedParams { + pub fn parse(&self, parameters_str: &str) -> Result { + let parsed_values = Self::parse_combined_params( + parameters_str, + COMBINED_PARAMETERS_DELIMITER, + self.into(), + )?; + Ok(self.initiate_objects(parsed_values)) + } + + fn parse_combined_params( + input: &str, + delimiter: char, + expected_collection: &[(&str, CombinedParamsDataTypes)], + ) -> Result, String> { + let check = |count: usize| { + if count != expected_collection.len() { + return Err(format!( + "Wrong number of values: expected {} but {} supplied{}", + expected_collection.len(), + count, + if count == 1 { + format!(". Did you use the correct delimiter '{}'?", delimiter) + } else { + "".to_string() + } + )); + } + Ok(()) + }; + let pieces: Vec<&str> = input.split(delimiter).collect(); + check(pieces.len())?; + let zipped = pieces.into_iter().zip(expected_collection.iter()); + Ok(zipped + .map(|(piece, (param_name, data_type))| { + ( + param_name.to_string(), + CombinedParamsValueRetriever::parse(piece, data_type).expectv("numeric value"), + ) + }) + .collect()) + } + + fn initiate_objects( + &self, + parsed_values: HashMap, + ) -> Self { + match self { + Self::RatePack(None) => Self::RatePack(Some(initiate_struct!( + RatePack, + &parsed_values, + "routing_byte_rate", + "routing_service_rate", + "exit_byte_rate", + "exit_service_rate" + ))), + Self::PaymentThresholds(None) => Self::PaymentThresholds(Some(initiate_struct!( + PaymentThresholds, + &parsed_values, + "maturity_threshold_sec", + "payment_grace_period_sec", + "permanent_debt_allowed_gwei", + "debt_threshold_gwei", + "threshold_interval_sec", + "unban_below_gwei" + ))), + Self::ScanIntervals(None) => Self::ScanIntervals(Some(initiate_struct!( + ScanIntervals, + &parsed_values, + Duration::from_secs, + "pending_payable_scan_interval", + "payable_scan_interval", + "receivable_scan_interval" + ))), + _ => panic!( + "should be called only on uninitialized object, not: {:?}", + self + ), + } + } +} + +impl From<&CombinedParams> for &[(&str, CombinedParamsDataTypes)] { + fn from(params: &CombinedParams) -> &'static [(&'static str, CombinedParamsDataTypes)] { + match params { + CombinedParams::RatePack(None) => &[ + ("routing_byte_rate", U64), + ("routing_service_rate", U64), + ("exit_byte_rate", U64), + ("exit_service_rate", U64), + ], + CombinedParams::PaymentThresholds(None) => &[ + ("debt_threshold_gwei", I64), + ("maturity_threshold_sec", I64), + ("payment_grace_period_sec", I64), + ("permanent_debt_allowed_gwei", I64), + ("threshold_interval_sec", I64), + ("unban_below_gwei", I64), + ], + CombinedParams::ScanIntervals(None) => &[ + ("pending_payable_scan_interval", U64), + ("payable_scan_interval", U64), + ("receivable_scan_interval", U64), + ], + _ => panic!( + "should be called only on uninitialized object, not: {:?}", + params + ), + } + } +} + +impl Display for ScanIntervals { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}|{}|{}", + self.pending_payable_scan_interval.as_secs(), + self.payable_scan_interval.as_secs(), + self.receivable_scan_interval.as_secs() + ) + } +} + +impl TryFrom<&str> for ScanIntervals { + type Error = String; + + fn try_from(parameters: &str) -> Result { + match CombinedParams::ScanIntervals(None).parse(parameters) { + Ok(CombinedParams::ScanIntervals(Some(scan_intervals))) => Ok(scan_intervals), + Err(e) => Err(e), + _ => unreachable(), + } + } +} + +impl Display for PaymentThresholds { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}|{}|{}|{}|{}|{}", + self.debt_threshold_gwei, + self.maturity_threshold_sec, + self.payment_grace_period_sec, + self.permanent_debt_allowed_gwei, + self.threshold_interval_sec, + self.unban_below_gwei + ) + } +} + +impl TryFrom<&str> for PaymentThresholds { + type Error = String; + + fn try_from(parameters: &str) -> Result { + match CombinedParams::PaymentThresholds(None).parse(parameters) { + Ok(CombinedParams::PaymentThresholds(Some(payment_thresholds))) => { + Ok(payment_thresholds) + } + Err(e) => Err(e), + _ => unreachable(), + } + } +} + +impl Display for RatePack { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}|{}|{}|{}", + self.routing_byte_rate, + self.routing_service_rate, + self.exit_byte_rate, + self.exit_service_rate + ) + } +} + +impl TryFrom<&str> for RatePack { + type Error = String; + + fn try_from(parameters: &str) -> Result { + match CombinedParams::RatePack(None).parse(parameters) { + Ok(CombinedParams::RatePack(Some(rate_pack))) => Ok(rate_pack), + Err(e) => Err(e), + _ => unreachable(), + } + } +} + +fn unreachable() -> ! { + unreachable!("technically shouldn't be possible") +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::sub_lib::accountant::{DEFAULT_PAYMENT_THRESHOLDS, DEFAULT_SCAN_INTERVALS}; + use crate::sub_lib::combined_parameters::CombinedParamsDataTypes::U128; + use crate::sub_lib::neighborhood::DEFAULT_RATE_PACK; + use std::panic::catch_unwind; + + #[test] + fn parse_combined_params_with_delimiters_happy_path() { + let input = "555|123|8989"; + + let result = CombinedParams::parse_combined_params( + input, + '|', + &[ + ("first_parameter", U64), + ("second_parameter", U128), + ("third_parameter", U64), + ], + ) + .unwrap(); + + assert_eq!( + CombinedParamsValueRetriever::get_value::(&result, "first_parameter"), + 555 + ); + assert_eq!( + CombinedParamsValueRetriever::get_value::(&result, "second_parameter"), + 123 + ); + assert_eq!( + CombinedParamsValueRetriever::get_value::(&result, "third_parameter"), + 8989 + ); + } + + #[test] + fn parse_combined_params_with_delimiters_wrong_number_of_parameters() { + let input = "555|123|8989|11|557"; + + let result: Result, String> = + CombinedParams::parse_combined_params( + input, + '|', + &[ + ("first_parameter", U64), + ("second_parameter", U64), + ("third_parameter", U64), + ("fourth_parameter", U64), + ], + ); + + assert_eq!( + result, + Err("Wrong number of values: expected 4 but 5 supplied".to_string()) + ) + } + + #[test] + fn parse_combined_params_with_delimiters_not_separable() { + let input = "555|123|8989|11|557"; + + let result: Result, String> = + CombinedParams::parse_combined_params( + input, + '@', + &[ + ("first_parameter", U64), + ("second_parameter", U64), + ("third_parameter", U64), + ("fourth_parameter", U64), + ], + ); + + assert_eq!( + result, + Err("Wrong number of values: expected 4 but 1 supplied. Did you use the correct delimiter '@'?".to_string()) + ) + } + + #[test] + fn combined_params_can_be_converted_to_collection_of_typed_parametres() { + let rate_pack: &[(&str, CombinedParamsDataTypes)] = + (&CombinedParams::RatePack(None)).into(); + assert_eq!( + rate_pack, + &[ + ("routing_byte_rate", U64), + ("routing_service_rate", U64), + ("exit_byte_rate", U64), + ("exit_service_rate", U64), + ] + ); + let scan_interval: &[(&str, CombinedParamsDataTypes)] = + (&CombinedParams::ScanIntervals(None)).into(); + assert_eq!( + scan_interval, + &[ + ("pending_payable_scan_interval", U64), + ("payable_scan_interval", U64), + ("receivable_scan_interval", U64), + ] + ); + let payment_thresholds: &[(&str, CombinedParamsDataTypes)] = + (&CombinedParams::PaymentThresholds(None)).into(); + assert_eq!( + payment_thresholds, + &[ + ("debt_threshold_gwei", I64), + ("maturity_threshold_sec", I64), + ("payment_grace_period_sec", I64), + ("permanent_debt_allowed_gwei", I64), + ("threshold_interval_sec", I64), + ("unban_below_gwei", I64) + ] + ); + } + + #[test] + fn array_type_conversion_should_use_uninitialized_instances_only() { + let panic_1 = catch_unwind(|| { + let _: &[(&str, CombinedParamsDataTypes)] = + (&CombinedParams::RatePack(Some(DEFAULT_RATE_PACK))).into(); + }) + .unwrap_err(); + let panic_1_msg = panic_1.downcast_ref::().unwrap(); + + assert_eq!( + panic_1_msg, + &format!( + "should be called only on uninitialized object, not: RatePack(Some({:?}))", + DEFAULT_RATE_PACK + ) + ); + + let panic_2 = catch_unwind(|| { + let _: &[(&str, CombinedParamsDataTypes)] = + (&CombinedParams::PaymentThresholds(Some(*DEFAULT_PAYMENT_THRESHOLDS))).into(); + }) + .unwrap_err(); + let panic_2_msg = panic_2.downcast_ref::().unwrap(); + + assert_eq!( + panic_2_msg, + &format!( + "should be called only on uninitialized object, not: PaymentThresholds(Some({:?}))", + *DEFAULT_PAYMENT_THRESHOLDS + ) + ); + + let panic_3 = catch_unwind(|| { + let _: &[(&str, CombinedParamsDataTypes)] = + (&CombinedParams::ScanIntervals(Some(*DEFAULT_SCAN_INTERVALS))).into(); + }) + .unwrap_err(); + let panic_3_msg = panic_3.downcast_ref::().unwrap(); + + assert_eq!( + panic_3_msg, + &format!( + "should be called only on uninitialized object, not: ScanIntervals(Some({:?}))", + *DEFAULT_SCAN_INTERVALS + ) + ); + } + + #[test] + fn initiate_objects_should_use_uninitialized_instances_only() { + let panic_1 = catch_unwind(|| { + (&CombinedParams::RatePack(Some(DEFAULT_RATE_PACK))).initiate_objects(HashMap::new()); + }) + .unwrap_err(); + let panic_1_msg = panic_1.downcast_ref::().unwrap(); + + assert_eq!( + panic_1_msg, + &format!( + "should be called only on uninitialized object, not: RatePack(Some({:?}))", + DEFAULT_RATE_PACK + ) + ); + + let panic_2 = catch_unwind(|| { + (&CombinedParams::PaymentThresholds(Some(*DEFAULT_PAYMENT_THRESHOLDS))) + .initiate_objects(HashMap::new()); + }) + .unwrap_err(); + let panic_2_msg = panic_2.downcast_ref::().unwrap(); + + assert_eq!( + panic_2_msg, + &format!( + "should be called only on uninitialized object, not: PaymentThresholds(Some({:?}))", + *DEFAULT_PAYMENT_THRESHOLDS + ) + ); + + let panic_3 = catch_unwind(|| { + (&CombinedParams::ScanIntervals(Some(*DEFAULT_SCAN_INTERVALS))) + .initiate_objects(HashMap::new()); + }) + .unwrap_err(); + let panic_3_msg = panic_3.downcast_ref::().unwrap(); + + assert_eq!( + panic_3_msg, + &format!( + "should be called only on uninitialized object, not: ScanIntervals(Some({:?}))", + *DEFAULT_SCAN_INTERVALS + ) + ); + } + + #[test] + fn rate_pack_from_combined_params() { + let rate_pack_str = "8|9|11|13"; + + let result = RatePack::try_from(rate_pack_str).unwrap(); + + assert_eq!( + result, + RatePack { + routing_byte_rate: 8, + routing_service_rate: 9, + exit_byte_rate: 11, + exit_service_rate: 13 + } + ) + } + + #[test] + fn rate_pack_to_combined_params() { + let scan_intervals = RatePack { + routing_byte_rate: 18, + routing_service_rate: 19, + exit_byte_rate: 21, + exit_service_rate: 22, + }; + + let result = scan_intervals.to_string(); + + assert_eq!(result, "18|19|21|22".to_string()); + } + + #[test] + fn scan_intervals_from_combined_params() { + let scan_intervals_str = "110|115|113"; + + let result = ScanIntervals::try_from(scan_intervals_str).unwrap(); + + assert_eq!( + result, + ScanIntervals { + pending_payable_scan_interval: Duration::from_secs(110), + payable_scan_interval: Duration::from_secs(115), + receivable_scan_interval: Duration::from_secs(113) + } + ) + } + + #[test] + fn scan_intervals_to_combined_params() { + let scan_intervals = ScanIntervals { + pending_payable_scan_interval: Duration::from_secs(60), + payable_scan_interval: Duration::from_secs(70), + receivable_scan_interval: Duration::from_secs(100), + }; + + let result = scan_intervals.to_string(); + + assert_eq!(result, "60|70|100".to_string()); + } + + #[test] + fn payment_thresholds_from_combined_params() { + let payment_thresholds_str = "5000010|120|100|20000|10020|18000"; + + let result = PaymentThresholds::try_from(payment_thresholds_str).unwrap(); + + assert_eq!( + result, + PaymentThresholds { + debt_threshold_gwei: 5000010, + maturity_threshold_sec: 120, + payment_grace_period_sec: 100, + permanent_debt_allowed_gwei: 20000, + threshold_interval_sec: 10020, + unban_below_gwei: 18000 + } + ) + } + + #[test] + fn payment_thresholds_to_combined_params() { + let payment_thresholds = PaymentThresholds { + threshold_interval_sec: 30020, + debt_threshold_gwei: 5000010, + payment_grace_period_sec: 123, + maturity_threshold_sec: 120, + permanent_debt_allowed_gwei: 20000, + unban_below_gwei: 111, + }; + + let result = payment_thresholds.to_string(); + + assert_eq!(result, "5000010|120|123|20000|30020|111".to_string()); + } +} diff --git a/node/src/sub_lib/mod.rs b/node/src/sub_lib/mod.rs index f69ad092d..2bbc553ab 100644 --- a/node/src/sub_lib/mod.rs +++ b/node/src/sub_lib/mod.rs @@ -10,6 +10,7 @@ pub mod bidi_hashmap; pub mod binary_traverser; pub mod blockchain_bridge; pub mod channel_wrappers; +pub mod combined_parameters; pub mod configurator; pub mod cryptde; pub mod cryptde_null; diff --git a/node/src/sub_lib/neighborhood.rs b/node/src/sub_lib/neighborhood.rs index e3537e077..54d1908c6 100644 --- a/node/src/sub_lib/neighborhood.rs +++ b/node/src/sub_lib/neighborhood.rs @@ -31,10 +31,10 @@ use std::net::IpAddr; use std::str::FromStr; pub const DEFAULT_RATE_PACK: RatePack = RatePack { - routing_byte_rate: 100, - routing_service_rate: 10000, - exit_byte_rate: 101, - exit_service_rate: 10001, + routing_byte_rate: 1, + routing_service_rate: 10, + exit_byte_rate: 2, + exit_service_rate: 20, }; pub const ZERO_RATE_PACK: RatePack = RatePack { @@ -44,6 +44,14 @@ pub const ZERO_RATE_PACK: RatePack = RatePack { exit_service_rate: 0, }; +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub struct RatePack { + pub routing_byte_rate: u64, + pub routing_service_rate: u64, + pub exit_byte_rate: u64, + pub exit_service_rate: u64, +} + #[derive(Clone, Debug, PartialEq)] pub enum NeighborhoodMode { Standard(NodeAddr, Vec, RatePack), @@ -149,8 +157,7 @@ impl Default for NodeDescriptor { } } -//confusing but seems like the public key in the args plays a role just in tests, -//making the public key part of the descriptor persistent and reliable for testing +//the public key's role as a separate arg is to enable the produced descriptor to be constant and reliable in tests impl From<(&PublicKey, &NodeAddr, Chain, &dyn CryptDE)> for NodeDescriptor { fn from(tuple: (&PublicKey, &NodeAddr, Chain, &dyn CryptDE)) -> Self { let (public_key, node_addr, blockchain, cryptde) = tuple; @@ -318,14 +325,20 @@ enum DescriptorParsingError<'a> { impl Display for DescriptorParsingError<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self{ - Self::CentralDelimiterProbablyMissing(descriptor) => write!(f, "Delimiter '@' probably missing. Should be 'masq://:@', not '{}'", descriptor), - Self::CentralDelimOrNodeAddr(descriptor,tail) => write!(f, "Either '@' delimiter position or format of node address is wrong. Should be 'masq://:@', not '{}'\nNodeAddr should be expressed as '://...', probably not as '{}'", descriptor,tail), - Self::CentralDelimOrIdentifier(descriptor) => write!(f, "Either '@' delimiter position or format of chain identifier is wrong. Should be 'masq://:@', not '{}'", descriptor), - Self::ChainIdentifierDelimiter(descriptor) => write!(f, "Chain identifier delimiter mismatch. Should be 'masq://:@', not '{}'", descriptor), - Self::PrefixMissing(descriptor) => write!(f,"Prefix or more missing. Should be 'masq://:@', not '{}'",descriptor), - Self::WrongChainIdentifier(identifier) => write!(f, "Chain identifier '{}' is not valid; possible values are '{}' while formatted as 'masq://:@'", - identifier, - CHAINS.iter().map(|record|record.literal_identifier).filter(|identifier|*identifier != "dev").join("', '") + Self::CentralDelimiterProbablyMissing(descriptor) => + write!(f, "Delimiter '@' probably missing. Should be 'masq://:@', not '{}'", descriptor), + Self::CentralDelimOrNodeAddr(descriptor,tail) => + write!(f, "Either '@' delimiter position or format of node address is wrong. Should be 'masq://:@', not '{}'\nNodeAddr should be expressed as '://...', probably not as '{}'", descriptor,tail), + Self::CentralDelimOrIdentifier(descriptor) => + write!(f, "Either '@' delimiter position or format of chain identifier is wrong. Should be 'masq://:@', not '{}'", descriptor), + Self::ChainIdentifierDelimiter(descriptor) => + write!(f, "Chain identifier delimiter mismatch. Should be 'masq://:@', not '{}'", descriptor), + Self::PrefixMissing(descriptor) => + write!(f,"Prefix or more missing. Should be 'masq://:@', not '{}'",descriptor), + Self::WrongChainIdentifier(identifier) => + write!(f, "Chain identifier '{}' is not valid; possible values are '{}' while formatted as 'masq://:@'", + identifier, + CHAINS.iter().map(|record|record.literal_identifier).filter(|identifier|*identifier != "dev").join("', '") ) } } @@ -463,27 +476,6 @@ pub enum NodeRecordMetadataMessage { Desirable(PublicKey, bool), } -#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] -pub struct RatePack { - pub routing_byte_rate: u64, - pub routing_service_rate: u64, - pub exit_byte_rate: u64, - pub exit_service_rate: u64, -} - -impl fmt::Display for RatePack { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!( - f, - "{}+{}b route {}+{}b exit", - self.routing_service_rate, - self.routing_byte_rate, - self.exit_service_rate, - self.exit_byte_rate - ) - } -} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[allow(non_camel_case_types)] pub enum GossipFailure_0v1 { @@ -524,10 +516,10 @@ mod tests { assert_eq!( DEFAULT_RATE_PACK, RatePack { - routing_byte_rate: 100, - routing_service_rate: 10000, - exit_byte_rate: 101, - exit_service_rate: 10001, + routing_byte_rate: 1, + routing_service_rate: 10, + exit_byte_rate: 2, + exit_service_rate: 20, } ); assert_eq!( diff --git a/node/src/sub_lib/socket_server.rs b/node/src/sub_lib/socket_server.rs index 43c674ed3..47254e92b 100644 --- a/node/src/sub_lib/socket_server.rs +++ b/node/src/sub_lib/socket_server.rs @@ -2,10 +2,9 @@ use masq_lib::command::StdStreams; use masq_lib::multi_config::MultiConfig; use masq_lib::shared_schema::ConfiguratorError; -use std::marker::Send; use tokio::prelude::Future; -pub trait ConfiguredByPrivilege: Send + Future { +pub trait ConfiguredByPrivilege: Future { fn initialize_as_privileged( &mut self, multi_config: &MultiConfig, diff --git a/node/src/sub_lib/utils.rs b/node/src/sub_lib/utils.rs index 8982dd546..fc6625205 100644 --- a/node/src/sub_lib/utils.rs +++ b/node/src/sub_lib/utils.rs @@ -101,10 +101,10 @@ pub fn handle_ui_crash_request( crashable: bool, crash_key: &str, ) { - let crasher = crash_request_analyzer; - if let Some(cr) = crasher(msg, logger, crashable, crash_key) { - let requester = type_name_of(crasher); - panic!("{} (processed with: {})", cr.panic_message, requester) + let crash_analyzer = crash_request_analyzer; + if let Some(cr) = crash_analyzer(msg, logger, crashable, crash_key) { + let processed_with = type_name_of(crash_analyzer); + panic!("{} (processed with: {})", cr.panic_message, processed_with) } } @@ -118,7 +118,12 @@ fn crash_request_analyzer( if logger.debug_enabled() { match UiCrashRequest::fmb(msg.body) { Ok((msg, _)) if msg.actor == crash_key => { - debug!(logger,"Received a crash request intended for this actor '{}' but not set up to be crashable",crash_key) + debug!( + logger, + "Received a crash request intended for this actor \ + '{}' but not set up to be crashable", + crash_key + ) } _ => (), } @@ -195,7 +200,7 @@ pub fn make_new_test_multi_config<'a>( } #[cfg(test)] -pub mod tests { +mod tests { use super::*; use crate::apps::app_node; use log::Level; diff --git a/node/src/test_utils/database_utils.rs b/node/src/test_utils/database_utils.rs index 7ac249d6d..7d72f2562 100644 --- a/node/src/test_utils/database_utils.rs +++ b/node/src/test_utils/database_utils.rs @@ -91,7 +91,12 @@ pub fn retrieve_config_row(conn: &dyn ConnectionWrapper, name: &str) -> (Option< }; Ok((value_opt, encrypted_flag)) }) - .unwrap_or_else(|e| panic!("panicked at {} for statement: {}", e, sql)) + .unwrap_or_else(|e| { + panic!( + "panicked at {} for statement: {} on parameter '{}'", + e, sql, name + ) + }) } pub fn query_specific_schema_information( diff --git a/node/src/test_utils/mod.rs b/node/src/test_utils/mod.rs index 96590b375..cde71c74d 100644 --- a/node/src/test_utils/mod.rs +++ b/node/src/test_utils/mod.rs @@ -24,10 +24,9 @@ use crate::sub_lib::cryptde::PublicKey; use crate::sub_lib::cryptde_null::CryptDENull; use crate::sub_lib::dispatcher::Component; use crate::sub_lib::hopper::MessageType; -use crate::sub_lib::neighborhood::ExpectedService; use crate::sub_lib::neighborhood::ExpectedServices; -use crate::sub_lib::neighborhood::RatePack; use crate::sub_lib::neighborhood::RouteQueryResponse; +use crate::sub_lib::neighborhood::{ExpectedService, RatePack}; use crate::sub_lib::proxy_client::{ClientResponsePayload_0v1, DnsResolveFailure_0v1}; use crate::sub_lib::proxy_server::{ClientRequestPayload_0v1, ProxyProtocol}; use crate::sub_lib::route::Route; @@ -35,7 +34,6 @@ use crate::sub_lib::route::RouteSegment; use crate::sub_lib::sequence_buffer::SequencedPacket; use crate::sub_lib::stream_key::StreamKey; use crate::sub_lib::wallet::Wallet; -use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMock; use crossbeam_channel::{unbounded, Receiver, Sender}; use ethsign_crypto::Keccak256; use lazy_static::lazy_static; @@ -205,17 +203,6 @@ pub fn make_meaningless_wallet_private_key() -> PlainData { ) } -pub fn make_default_persistent_configuration() -> PersistentConfigurationMock { - PersistentConfigurationMock::new() - .earning_wallet_address_result(Ok(None)) - .earning_wallet_result(Ok(None)) - .consuming_wallet_private_key_result(Ok(None)) - .consuming_wallet_result(Ok(None)) - .past_neighbors_result(Ok(None)) - .gas_price_result(Ok(1)) - .mapping_protocol_result(Ok(None)) -} - pub fn route_to_proxy_client(key: &PublicKey, cryptde: &dyn CryptDE) -> Route { shift_one_hop(zero_hop_route_response(key, cryptde).route, cryptde) } @@ -516,10 +503,17 @@ pub struct TestRawTransaction { } #[cfg(test)] -pub mod pure_test_utils { +pub mod unshared_test_utils { + use crate::accountant::DEFAULT_PENDING_TOO_LONG_SEC; use crate::apps::app_node; use crate::daemon::ChannelFactory; + use crate::db_config::config_dao_null::ConfigDaoNull; + use crate::db_config::persistent_configuration::PersistentConfigurationReal; use crate::node_test_utils::DirsWrapperMock; + use crate::sub_lib::accountant::{ + AccountantConfig, DEFAULT_PAYMENT_THRESHOLDS, DEFAULT_SCAN_INTERVALS, + }; + use crate::sub_lib::neighborhood::DEFAULT_RATE_PACK; use crate::sub_lib::utils::{NotifyHandle, NotifyLaterHandle}; use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMock; use actix::{Actor, Addr, Context, Handler, System}; @@ -538,22 +532,78 @@ pub mod pure_test_utils { use std::time::Duration; pub fn make_simplified_multi_config<'a, const T: usize>(args: [&str; T]) -> MultiConfig<'a> { - let owned_args = array_of_borrows_to_vec(&args); - let arg_matches = app_node().get_matches_from_safe(owned_args).unwrap(); + let mut app_args = vec!["MASQNode".to_string()]; + app_args.append(&mut array_of_borrows_to_vec(&args)); + let arg_matches = app_node().get_matches_from_safe(app_args).unwrap(); MultiConfig::new_test_only(arg_matches) } - pub fn make_default_persistent_configuration() -> PersistentConfigurationMock { - PersistentConfigurationMock::new() + pub const ZERO: u32 = 0b0; + pub const MAPPING_PROTOCOL: u32 = 0b000010; + pub const ACCOUNTANT_CONFIG_PARAMS: u32 = 0b000100; + pub const RATE_PACK: u32 = 0b001000; + + pub fn configure_default_persistent_config(bit_flag: u32) -> PersistentConfigurationMock { + let config = default_persistent_config_just_base(PersistentConfigurationMock::new()); + let config = if (bit_flag & MAPPING_PROTOCOL) == MAPPING_PROTOCOL { + config.mapping_protocol_result(Ok(None)) + } else { + config + }; + let config = if (bit_flag & ACCOUNTANT_CONFIG_PARAMS) == ACCOUNTANT_CONFIG_PARAMS { + default_persistent_config_just_accountant_config(config) + } else { + config + }; + let config = if (bit_flag & RATE_PACK) == RATE_PACK { + config.rate_pack_result(Ok(DEFAULT_RATE_PACK)) + } else { + config + }; + config + } + + pub fn default_persistent_config_just_base( + persistent_config_mock: PersistentConfigurationMock, + ) -> PersistentConfigurationMock { + persistent_config_mock .earning_wallet_address_result(Ok(None)) .earning_wallet_result(Ok(None)) .consuming_wallet_private_key_result(Ok(None)) + .consuming_wallet_result(Ok(None)) .past_neighbors_result(Ok(None)) .gas_price_result(Ok(1)) - .mapping_protocol_result(Ok(None)) .blockchain_service_url_result(Ok(None)) } + pub fn default_persistent_config_just_accountant_config( + persistent_config_mock: PersistentConfigurationMock, + ) -> PersistentConfigurationMock { + persistent_config_mock + .payment_thresholds_result(Ok(*DEFAULT_PAYMENT_THRESHOLDS)) + .scan_intervals_result(Ok(*DEFAULT_SCAN_INTERVALS)) + } + + pub fn make_persistent_config_real_with_config_dao_null() -> PersistentConfigurationReal { + PersistentConfigurationReal::new(Box::new(ConfigDaoNull::default())) + } + + pub fn make_populated_accountant_config_with_defaults() -> AccountantConfig { + AccountantConfig { + scan_intervals: *DEFAULT_SCAN_INTERVALS, + payment_thresholds: *DEFAULT_PAYMENT_THRESHOLDS, + when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, + } + } + + pub fn make_accountant_config_null() -> AccountantConfig { + AccountantConfig { + scan_intervals: Default::default(), + payment_thresholds: Default::default(), + when_pending_too_long_sec: Default::default(), + } + } + pub struct ChannelFactoryMock { make_results: RefCell< Vec<( diff --git a/node/src/test_utils/neighborhood_test_utils.rs b/node/src/test_utils/neighborhood_test_utils.rs index 5b2172044..0f1cdb761 100644 --- a/node/src/test_utils/neighborhood_test_utils.rs +++ b/node/src/test_utils/neighborhood_test_utils.rs @@ -93,7 +93,7 @@ pub fn neighborhood_from_nodes( mode: NeighborhoodMode::Standard( root.node_addr_opt().unwrap(), vec![NodeDescriptor::from((neighbor, Chain::EthRopsten, cryptde))], - root.rate_pack().clone(), + *root.rate_pack(), ), }, None => NeighborhoodConfig { @@ -115,9 +115,9 @@ impl From<&NodeRecord> for NeighborhoodMode { node.routes_data(), ) { (Some(node_addr), true, true) => { - NeighborhoodMode::Standard(node_addr, vec![], node.rate_pack().clone()) + NeighborhoodMode::Standard(node_addr, vec![], *node.rate_pack()) } - (_, false, true) => NeighborhoodMode::OriginateOnly(vec![], node.rate_pack().clone()), + (_, false, true) => NeighborhoodMode::OriginateOnly(vec![], *node.rate_pack()), (_, false, false) => NeighborhoodMode::ConsumeOnly(vec![]), (node_addr_opt, accepts_connections, routes_data) => panic!( "Cannot determine NeighborhoodMode from triple: ({:?}, {}, {})", diff --git a/node/src/test_utils/persistent_configuration_mock.rs b/node/src/test_utils/persistent_configuration_mock.rs index 55a864a75..f589bd1e5 100644 --- a/node/src/test_utils/persistent_configuration_mock.rs +++ b/node/src/test_utils/persistent_configuration_mock.rs @@ -1,7 +1,10 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. +#![cfg(test)] + use crate::db_config::persistent_configuration::{PersistentConfigError, PersistentConfiguration}; -use crate::sub_lib::neighborhood::NodeDescriptor; +use crate::sub_lib::accountant::{PaymentThresholds, ScanIntervals}; +use crate::sub_lib::neighborhood::{NodeDescriptor, RatePack}; use crate::sub_lib::wallet::Wallet; use masq_lib::utils::AutomapProtocol; use masq_lib::utils::NeighborhoodModeLight; @@ -15,6 +18,7 @@ pub struct PersistentConfigurationMock { set_blockchain_service_url_params: Arc>>, set_blockchain_service_url_results: RefCell>>, current_schema_version_results: RefCell>, + chain_name_params: Arc>>, chain_name_results: RefCell>, check_password_params: Arc>>>, check_password_results: RefCell>>, @@ -50,6 +54,15 @@ pub struct PersistentConfigurationMock { start_block_results: RefCell>>, set_start_block_params: Arc>>, set_start_block_results: RefCell>>, + payment_thresholds_results: RefCell>>, + set_payment_thresholds_params: Arc>>, + set_payment_thresholds_results: RefCell>>, + rate_pack_results: RefCell>>, + set_rate_pack_params: Arc>>, + set_rate_pack_results: RefCell>>, + scan_intervals_results: RefCell>>, + set_scan_intervals_params: Arc>>, + set_scan_intervals_results: RefCell>>, } impl PersistentConfiguration for PersistentConfigurationMock { @@ -72,6 +85,7 @@ impl PersistentConfiguration for PersistentConfigurationMock { } fn chain_name(&self) -> String { + self.chain_name_params.lock().unwrap().push(()); self.chain_name_results.borrow_mut().remove(0) } @@ -98,6 +112,25 @@ impl PersistentConfiguration for PersistentConfigurationMock { self.change_password_results.borrow_mut().remove(0) } + fn consuming_wallet(&self, db_password: &str) -> Result, PersistentConfigError> { + self.consuming_wallet_params + .lock() + .unwrap() + .push(db_password.to_string()); + Self::result_from(&self.consuming_wallet_results) + } + + fn consuming_wallet_private_key( + &self, + db_password: &str, + ) -> Result, PersistentConfigError> { + self.consuming_wallet_private_key_params + .lock() + .unwrap() + .push(db_password.to_string()); + Self::result_from(&self.consuming_wallet_private_key_results) + } + fn clandestine_port(&self) -> Result { Self::result_from(&self.clandestine_port_results) } @@ -107,6 +140,14 @@ impl PersistentConfiguration for PersistentConfigurationMock { self.set_clandestine_port_results.borrow_mut().remove(0) } + fn earning_wallet(&self) -> Result, PersistentConfigError> { + Self::result_from(&self.earning_wallet_results) + } + + fn earning_wallet_address(&self) -> Result, PersistentConfigError> { + Self::result_from(&self.earning_wallet_address_results) + } + fn gas_price(&self) -> Result { Self::result_from(&self.gas_price_results) } @@ -128,45 +169,19 @@ impl PersistentConfiguration for PersistentConfigurationMock { self.set_mapping_protocol_results.borrow_mut().remove(0) } - fn consuming_wallet(&self, db_password: &str) -> Result, PersistentConfigError> { - self.consuming_wallet_params - .lock() - .unwrap() - .push(db_password.to_string()); - Self::result_from(&self.consuming_wallet_results) - } - - fn consuming_wallet_private_key( - &self, - db_password: &str, - ) -> Result, PersistentConfigError> { - self.consuming_wallet_private_key_params - .lock() - .unwrap() - .push(db_password.to_string()); - Self::result_from(&self.consuming_wallet_private_key_results) - } - - fn earning_wallet(&self) -> Result, PersistentConfigError> { - Self::result_from(&self.earning_wallet_results) - } - - fn earning_wallet_address(&self) -> Result, PersistentConfigError> { - Self::result_from(&self.earning_wallet_address_results) + fn neighborhood_mode(&self) -> Result { + self.neighborhood_mode_results.borrow_mut().remove(0) } - fn set_wallet_info( + fn set_neighborhood_mode( &mut self, - consuming_wallet_private_key: &str, - earning_wallet_address: &str, - db_password: &str, + value: NeighborhoodModeLight, ) -> Result<(), PersistentConfigError> { - self.set_wallet_info_params.lock().unwrap().push(( - consuming_wallet_private_key.to_string(), - earning_wallet_address.to_string(), - db_password.to_string(), - )); - self.set_wallet_info_results.borrow_mut().remove(0) + self.set_neighborhood_mode_params + .lock() + .unwrap() + .push(value); + self.set_neighborhood_mode_results.borrow_mut().remove(0) } fn past_neighbors( @@ -202,19 +217,51 @@ impl PersistentConfiguration for PersistentConfigurationMock { Self::result_from(&self.set_start_block_results) } - fn neighborhood_mode(&self) -> Result { - self.neighborhood_mode_results.borrow_mut().remove(0) - } - - fn set_neighborhood_mode( + fn set_wallet_info( &mut self, - value: NeighborhoodModeLight, + consuming_wallet_private_key: &str, + earning_wallet_address: &str, + db_password: &str, ) -> Result<(), PersistentConfigError> { - self.set_neighborhood_mode_params + self.set_wallet_info_params.lock().unwrap().push(( + consuming_wallet_private_key.to_string(), + earning_wallet_address.to_string(), + db_password.to_string(), + )); + self.set_wallet_info_results.borrow_mut().remove(0) + } + + fn payment_thresholds(&self) -> Result { + self.payment_thresholds_results.borrow_mut().remove(0) + } + + fn set_payment_thresholds(&mut self, curves: String) -> Result<(), PersistentConfigError> { + self.set_payment_thresholds_params .lock() .unwrap() - .push(value); - self.set_neighborhood_mode_results.borrow_mut().remove(0) + .push(curves); + self.set_payment_thresholds_results.borrow_mut().remove(0) + } + + fn rate_pack(&self) -> Result { + self.rate_pack_results.borrow_mut().remove(0) + } + + fn set_rate_pack(&mut self, rate_pack: String) -> Result<(), PersistentConfigError> { + self.set_rate_pack_params.lock().unwrap().push(rate_pack); + self.set_rate_pack_results.borrow_mut().remove(0) + } + + fn scan_intervals(&self) -> Result { + self.scan_intervals_results.borrow_mut().remove(0) + } + + fn set_scan_intervals(&mut self, intervals: String) -> Result<(), PersistentConfigError> { + self.set_scan_intervals_params + .lock() + .unwrap() + .push(intervals); + self.set_scan_intervals_results.borrow_mut().remove(0) } } @@ -255,6 +302,11 @@ impl PersistentConfigurationMock { self } + pub fn chain_name_params(mut self, params: &Arc>>) -> Self { + self.chain_name_params = params.clone(); + self + } + pub fn chain_name_result(self, result: String) -> Self { self.chain_name_results.borrow_mut().push(result); self @@ -481,6 +533,59 @@ impl PersistentConfigurationMock { self } + pub fn payment_thresholds_result( + self, + result: Result, + ) -> Self { + self.payment_thresholds_results.borrow_mut().push(result); + self + } + + pub fn set_payment_thresholds_params(mut self, params: &Arc>>) -> Self { + self.set_payment_thresholds_params = params.clone(); + self + } + + pub fn set_payment_thresholds_result(self, result: Result<(), PersistentConfigError>) -> Self { + self.set_payment_thresholds_results + .borrow_mut() + .push(result); + self + } + + pub fn rate_pack_result(self, result: Result) -> Self { + self.rate_pack_results.borrow_mut().push(result); + self + } + + pub fn set_rate_pack_params(mut self, params: &Arc>>) -> Self { + self.set_rate_pack_params = params.clone(); + self + } + + pub fn set_rate_pack_result(self, result: Result<(), PersistentConfigError>) -> Self { + self.set_rate_pack_results.borrow_mut().push(result); + self + } + + pub fn scan_intervals_result( + self, + result: Result, + ) -> Self { + self.scan_intervals_results.borrow_mut().push(result); + self + } + + pub fn set_scan_intervals_params(mut self, params: &Arc>>) -> Self { + self.set_scan_intervals_params = params.clone(); + self + } + + pub fn set_scan_intervals_result(self, result: Result<(), PersistentConfigError>) -> Self { + self.set_scan_intervals_results.borrow_mut().push(result); + self + } + pub fn mapping_protocol_result( self, result: Result, PersistentConfigError>,