From 304e8885e65e689a48dcfd903f3b203c8249d0ca Mon Sep 17 00:00:00 2001 From: Bert <65427484+bertllll@users.noreply.github.com> Date: Sun, 27 Feb 2022 19:32:11 +0100 Subject: [PATCH 01/10] GH-236: 'Node' and 'masq' implementation, finalized --- USER-INTERFACE-INTERFACE.md | 89 ++---- masq/src/command_factory.rs | 2 + masq/src/commands/commands_common.rs | 12 + masq/src/commands/configuration_command.rs | 34 +-- masq/src/commands/descriptor_command.rs | 5 +- masq/src/commands/financials_command.rs | 163 +++++++++++ masq/src/commands/mod.rs | 1 + masq/src/communications/connection_manager.rs | 34 +-- masq/src/schema.rs | 2 + masq_lib/src/messages.rs | 136 +++------ node/src/accountant/mod.rs | 267 ++++++++---------- node/src/accountant/payable_dao.rs | 8 +- node/src/accountant/receivable_dao.rs | 34 +-- node/src/accountant/test_utils.rs | 20 +- node/src/blockchain/blockchain_bridge.rs | 6 +- node/src/blockchain/blockchain_interface.rs | 18 +- node/src/blockchain/test_utils.rs | 8 +- node/src/daemon/mod.rs | 8 +- node/src/sub_lib/accountant.rs | 15 +- node/src/test_utils/recorder.rs | 3 +- node/tests/initialization_test.rs | 7 +- node/tests/ui_gateway_test.rs | 12 +- 22 files changed, 456 insertions(+), 428 deletions(-) create mode 100644 masq/src/commands/financials_command.rs diff --git a/USER-INTERFACE-INTERFACE.md b/USER-INTERFACE-INTERFACE.md index 5aeb31877..d2fcc40d7 100644 --- a/USER-INTERFACE-INTERFACE.md +++ b/USER-INTERFACE-INTERFACE.md @@ -480,80 +480,47 @@ field will be null or absent. ##### Correspondent: Node ##### Layout: ``` -"payload": { - "payableMinimumAmount" = , - "payableMaximumAge" = , - "receivableMinimumAmount" = , - "receivableMaximumAge" = -} +"payload": {} ``` ##### Description: -Requests a financial report from the Node. - -In most cases, there will be many records in the database, most of them irrelevant because of amount or age. -Therefore, when the UI requests a financial report, it should specify minimum amounts and maximum ages. Records -with amounts smaller than the minimums, or older than the maximums, won't be included in the results, although -their values will be included in the totals. - -This request will result in a cluster of queries to the database, which are quick but not instantaneous, -especially on old databases that contain lots of records. A UI that makes this request too many times per -second will perceptibly degrade the performance of the Node. +Requests financial statistics from the Node. -Amounts are specified in gwei (billions of wei); ages are specified in seconds. Values less than zero or -greater than 64 bits long will cause undefined behavior. +This will report back information about Node's performance, recorded services over time; mostly put as +some money value. #### `financials` ##### Direction: Response ##### Correspondent: Node ##### Layout: ``` + "payload": { - "payables": [ - { - "wallet": , - "age": , - "amount": , - "pendingPayableHashOpt": - }, - < ... > - ], - "totalPayable": , - "receivables": [ - { - "wallet": , - "age": , - "amount": - }, - < ... > - ], - "totalReceivable": + "totalUnpaidPayable": + "totalPaidPayable": + "totalUnpaidReceivable": + "totalPaidReceivable": } ``` ##### Description: -Contains a financial report from the Node. - -In most cases, there will be accounts in the database that are too old, or whose balances are too low, to -show up in this report. The `totalPayable` and `totalReceivable` fields will be accurate, but they will -probably be larger than the sums of the `payables` and `receivables` `amount` fields. The UI may choose to -ignore this discrepancy, or it may generate an "Other" account in each case to make up the difference. - -The `wallet` fields will consist of 40 hexadecimal digits, prefixed by "0x". - -The `age` fields contain the age in seconds, at the time the request was received, of the most recent transaction -on the associated account. The value will not be less than zero or longer than 64 bits. - -The `amount` fields contain the total amount in gwei owed to or due from the associated account at the time the -request was received. The value will not be less than zero or longer than 64 bits. - -The `pendingPayableHashOpt` fields, if present, indicate that an obligation has been paid, but the payment is not -yet confirmed on the blockchain. If they appear, they will be standard 64-digit hexadecimal transaction numbers, -prefixed by "0x". If no `pendingPayableHashOpt` is given, then there were no pending payments on that account -at the time the request was received. - -The `payables` and `receivables` arrays are not in any particular order. - -For security reasons, the Node does not keep track of individual blockchain transactions, with the exception -of payments that have not yet been confirmed. Only cumulative account balances are retained. +Contains a financial statistics report from the Node. + +`totalUnpaidPayable` is a cumulative amount of Gwei from all accounts that were established on behalf of the Node's +creditors in the DB; referred as payable, that is a basis of our payments to make to other Nodes. The fact there is +an account in the DB's affected table may mean two situations, either the debt is still being build up to reach a level +when it begins to be desirable to pay, or it also may mean that there is already a pending transaction with a goal to +settle a qualified debt. However, an existence of the record clearly means the debt's settlement has not completed yet, +either way, and thus the blockchain definitely hasn't been modified accordingly. + +`totalPaidPayable`, unlike the previous, this is a sum of every paid (or settled) token amount that our Node has sent to +our creditors, and as well, the transactions were confirmed eventually. Tracked since the startup, figures in Gwei. + +`totalUnpaidReceivable`, this is similar to `totalUnpaidReceivables`, the values making a sum here come from the DB +table belonging to receivable; this table contains records of accounts of different Nodes that have drawn the Node's +services in the past. We take a track of the amount of "work", put in Gwei, for each and here we request the cumulative +active debt from all our debtors. + +`totalPaidReceivable`: this number of Gwei means a total of transactions we detected as already paid on our account +from our debtors since the Node's startup. #### `generateWallets` ##### Direction: Request diff --git a/masq/src/command_factory.rs b/masq/src/command_factory.rs index 5200f5fad..8bc697171 100644 --- a/masq/src/command_factory.rs +++ b/masq/src/command_factory.rs @@ -7,6 +7,7 @@ use crate::commands::commands_common::Command; use crate::commands::configuration_command::ConfigurationCommand; use crate::commands::crash_command::CrashCommand; use crate::commands::descriptor_command::DescriptorCommand; +use crate::commands::financials_command::FinancialsCommand; use crate::commands::generate_wallets_command::GenerateWalletsCommand; use crate::commands::recover_wallets_command::RecoverWalletsCommand; use crate::commands::set_configuration_command::SetConfigurationCommand; @@ -48,6 +49,7 @@ impl CommandFactory for CommandFactoryReal { Err(msg) => return Err(CommandSyntax(msg)), }, "descriptor" => Box::new(DescriptorCommand::new()), + "financials" => Box::new(FinancialsCommand::new()), "generate-wallets" => match GenerateWalletsCommand::new(pieces) { Ok(command) => Box::new(command), Err(msg) => return Err(CommandSyntax(msg)), diff --git a/masq/src/commands/commands_common.rs b/masq/src/commands/commands_common.rs index 953a5b70a..f1f2d7568 100644 --- a/masq/src/commands/commands_common.rs +++ b/masq/src/commands/commands_common.rs @@ -11,8 +11,10 @@ use masq_lib::ui_gateway::MessageBody; use std::any::Any; use std::fmt::Debug; use std::fmt::Display; +use std::io::Write; pub const STANDARD_COMMAND_TIMEOUT_MILLIS: u64 = 1000; +pub const STANDARD_COLUMN_WIDTH: usize = 33; #[derive(Debug, PartialEq)] pub enum CommandError { @@ -95,6 +97,16 @@ impl From for CommandError { } } +pub(in crate::commands) fn dump_parameter_line(stream: &mut dyn Write, name: &str, value: &str) { + short_writeln!( + stream, + "{:width$} {}", + name, + value, + width = STANDARD_COLUMN_WIDTH + ); +} + #[cfg(test)] mod tests { use super::*; diff --git a/masq/src/commands/configuration_command.rs b/masq/src/commands/configuration_command.rs index 182e3f3ec..864063f4c 100644 --- a/masq/src/commands/configuration_command.rs +++ b/masq/src/commands/configuration_command.rs @@ -3,7 +3,7 @@ use crate::command_context::CommandContext; use crate::commands::commands_common::CommandError::Payload; use crate::commands::commands_common::{ - transaction, Command, CommandError, STANDARD_COMMAND_TIMEOUT_MILLIS, + dump_parameter_line, transaction, Command, CommandError, STANDARD_COMMAND_TIMEOUT_MILLIS, }; use clap::{App, Arg, SubCommand}; use masq_lib::as_any_impl; @@ -74,47 +74,47 @@ 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( + dump_parameter_line(stream, "NAME", "VALUE"); + dump_parameter_line( stream, "Blockchain service URL:", &configuration .blockchain_service_url_opt .unwrap_or_else(|| "[?]".to_string()), ); - Self::dump_configuration_line(stream, "Chain:", &configuration.chain_name); - Self::dump_configuration_line( + dump_parameter_line(stream, "Chain:", &configuration.chain_name); + dump_parameter_line( stream, "Clandestine port:", &configuration.clandestine_port.to_string(), ); - Self::dump_configuration_line( + dump_parameter_line( stream, "Consuming wallet private key:", &Self::interpret_option(&configuration.consuming_wallet_private_key_opt), ); - Self::dump_configuration_line( + dump_parameter_line( stream, "Current schema version:", &configuration.current_schema_version, ); - Self::dump_configuration_line( + dump_parameter_line( stream, "Earning wallet address:", &Self::interpret_option(&configuration.earning_wallet_address_opt), ); - Self::dump_configuration_line(stream, "Gas price:", &configuration.gas_price.to_string()); - Self::dump_configuration_line( + dump_parameter_line(stream, "Gas price:", &configuration.gas_price.to_string()); + dump_parameter_line( stream, "Neighborhood mode:", &configuration.neighborhood_mode, ); - Self::dump_configuration_line( + dump_parameter_line( stream, "Port mapping protocol:", &Self::interpret_option(&configuration.port_mapping_protocol_opt), ); - Self::dump_configuration_line( + dump_parameter_line( stream, "Start block:", &configuration.start_block.to_string(), @@ -124,24 +124,20 @@ impl ConfigurationCommand { fn dump_value_list(stream: &mut dyn Write, name: &str, values: &[String]) { if values.is_empty() { - Self::dump_configuration_line(stream, name, "[?]"); + dump_parameter_line(stream, name, "[?]"); return; } let mut name_row = true; values.iter().for_each(|value| { if name_row { - Self::dump_configuration_line(stream, name, value); + dump_parameter_line(stream, name, value); name_row = false; } else { - Self::dump_configuration_line(stream, "", value); + dump_parameter_line(stream, "", value); } }) } - fn dump_configuration_line(stream: &mut dyn Write, name: &str, value: &str) { - short_writeln!(stream, "{:33} {}", name, value); - } - fn interpret_option(value_opt: &Option) -> String { match value_opt { None => "[?]".to_string(), diff --git a/masq/src/commands/descriptor_command.rs b/masq/src/commands/descriptor_command.rs index 74e388213..6e9e2addf 100644 --- a/masq/src/commands/descriptor_command.rs +++ b/masq/src/commands/descriptor_command.rs @@ -73,7 +73,6 @@ mod tests { use crate::commands::commands_common::CommandError::ConnectionProblem; use crate::test_utils::mocks::CommandContextMock; use masq_lib::messages::{ToMessageBody, UiDescriptorRequest, UiDescriptorResponse}; - use masq_lib::utils::find_free_port; use std::sync::{Arc, Mutex}; #[test] @@ -177,11 +176,9 @@ mod tests { #[test] fn descriptor_command_sad_path() { let transact_params_arc = Arc::new(Mutex::new(vec![])); - let port = find_free_port(); let mut context = CommandContextMock::new() .transact_params(&transact_params_arc) - .transact_result(Err(ConnectionDropped("Booga".to_string()))) - .active_port_result(Some(port)); + .transact_result(Err(ConnectionDropped("Booga".to_string()))); let stdout_arc = context.stdout_arc(); let stderr_arc = context.stderr_arc(); let subject = DescriptorCommand::new(); diff --git a/masq/src/commands/financials_command.rs b/masq/src/commands/financials_command.rs new file mode 100644 index 000000000..e0826ccc9 --- /dev/null +++ b/masq/src/commands/financials_command.rs @@ -0,0 +1,163 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::command_context::CommandContext; +use crate::commands::commands_common::{ + dump_parameter_line, transaction, Command, CommandError, STANDARD_COMMAND_TIMEOUT_MILLIS, +}; +use clap::{App, SubCommand}; +use masq_lib::messages::{UiFinancialsRequest, UiFinancialsResponse}; +use masq_lib::short_writeln; +use std::fmt::Debug; + +#[derive(Debug)] +pub struct FinancialsCommand {} + +pub fn financials_subcommand() -> App<'static, 'static> { + SubCommand::with_name("financials").about( + "Displays financial data of the given MASQNode. Only valid if Node is already running.", + ) +} + +impl Command for FinancialsCommand { + fn execute(&self, context: &mut dyn CommandContext) -> Result<(), CommandError> { + let input = UiFinancialsRequest {}; + let output: Result = + transaction(input, context, STANDARD_COMMAND_TIMEOUT_MILLIS); + match output { + Ok(response) => { + let stdout = context.stdout(); + dump_parameter_line( + stdout, + "Total unpaid payable:", + &response.total_unpaid_payable.to_string(), + ); + dump_parameter_line( + stdout, + "Total paid payable:", + &response.total_paid_payable.to_string(), + ); + dump_parameter_line( + stdout, + "Total unpaid receivable:", + &response.total_unpaid_receivable.to_string(), + ); + dump_parameter_line( + stdout, + "Total paid receivable:", + &response.total_paid_receivable.to_string(), + ); + Ok(()) + } + Err(e) => { + short_writeln!(context.stderr(), "Financials retrieval failed: {:?}", e); + Err(e) + } + } + } +} + +impl Default for FinancialsCommand { + fn default() -> Self { + Self::new() + } +} + +impl FinancialsCommand { + pub fn new() -> Self { + Self {} + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::command_context::ContextError::ConnectionDropped; + use crate::command_factory::{CommandFactory, CommandFactoryReal}; + use crate::commands::commands_common::CommandError::ConnectionProblem; + use crate::test_utils::mocks::CommandContextMock; + use masq_lib::messages::{ToMessageBody, UiFinancialsResponse}; + use std::sync::{Arc, Mutex}; + + #[test] + fn testing_command_factory_here() { + let factory = CommandFactoryReal::new(); + let mut context = CommandContextMock::new().transact_result(Ok(UiFinancialsResponse { + total_unpaid_payable: 0, + total_paid_payable: 1111, + total_unpaid_receivable: 2222, + total_paid_receivable: 3333, + } + .tmb(0))); + let subject = factory.make(&["financials".to_string()]).unwrap(); + + let result = subject.execute(&mut context); + + assert_eq!(result, Ok(())); + } + + #[test] + fn financials_command_happy_path() { + let transact_params_arc = Arc::new(Mutex::new(vec![])); + let expected_response = UiFinancialsResponse { + total_unpaid_payable: 116688, + total_paid_payable: 55555, + total_unpaid_receivable: 221144, + total_paid_receivable: 66555, + }; + let mut context = CommandContextMock::new() + .transact_params(&transact_params_arc) + .transact_result(Ok(expected_response.tmb(31))); + let stdout_arc = context.stdout_arc(); + let stderr_arc = context.stderr_arc(); + let subject = FinancialsCommand::new(); + + let result = subject.execute(&mut context); + + assert_eq!(result, Ok(())); + let transact_params = transact_params_arc.lock().unwrap(); + assert_eq!( + *transact_params, + vec![( + UiFinancialsRequest {}.tmb(0), + STANDARD_COMMAND_TIMEOUT_MILLIS + )] + ); + assert_eq!( + stdout_arc.lock().unwrap().get_string(), + "\ + Total unpaid payable: 116688\n\ + Total paid payable: 55555\n\ + Total unpaid receivable: 221144\n\ + Total paid receivable: 66555\n" + ); + assert_eq!(stderr_arc.lock().unwrap().get_string(), String::new()); + } + + #[test] + fn financials_command_sad_path() { + let transact_params_arc = Arc::new(Mutex::new(vec![])); + let mut context = CommandContextMock::new() + .transact_params(&transact_params_arc) + .transact_result(Err(ConnectionDropped("Booga".to_string()))); + let stdout_arc = context.stdout_arc(); + let stderr_arc = context.stderr_arc(); + let subject = FinancialsCommand::new(); + + let result = subject.execute(&mut context); + + assert_eq!(result, Err(ConnectionProblem("Booga".to_string()))); + let transact_params = transact_params_arc.lock().unwrap(); + assert_eq!( + *transact_params, + vec![( + UiFinancialsRequest {}.tmb(0), + STANDARD_COMMAND_TIMEOUT_MILLIS + )] + ); + assert_eq!(stdout_arc.lock().unwrap().get_string(), String::new()); + assert_eq!( + stderr_arc.lock().unwrap().get_string(), + "Financials retrieval failed: ConnectionProblem(\"Booga\")\n" + ); + } +} diff --git a/masq/src/commands/mod.rs b/masq/src/commands/mod.rs index efa7c6825..c7531f25b 100644 --- a/masq/src/commands/mod.rs +++ b/masq/src/commands/mod.rs @@ -6,6 +6,7 @@ pub mod commands_common; pub mod configuration_command; pub mod crash_command; pub mod descriptor_command; +pub mod financials_command; pub mod generate_wallets_command; pub mod recover_wallets_command; pub mod set_configuration_command; diff --git a/masq/src/communications/connection_manager.rs b/masq/src/communications/connection_manager.rs index fae2a66ab..2c9d70679 100644 --- a/masq/src/communications/connection_manager.rs +++ b/masq/src/communications/connection_manager.rs @@ -1163,10 +1163,10 @@ mod tests { let node_port = find_free_port(); let node_server = MockWebSocketsServer::new(node_port).queue_response( UiFinancialsResponse { - payables: vec![], - total_payable: 21, - receivables: vec![], - total_receivable: 32, + total_unpaid_payable: 10, + total_paid_payable: 22, + total_unpaid_receivable: 29, + total_paid_receivable: 32, } .tmb(1), ); @@ -1180,13 +1180,7 @@ mod tests { payload: r#"{"payableMinimumAmount":12,"payableMaximumAge":23,"receivableMinimumAmount":34,"receivableMaximumAge":45}"#.to_string() }.tmb(0)); let daemon_stop_handle = daemon_server.start(); - let request = UiFinancialsRequest { - payable_minimum_amount: 12, - payable_maximum_age: 23, - receivable_minimum_amount: 34, - receivable_maximum_age: 45, - } - .tmb(1); + let request = UiFinancialsRequest {}.tmb(1); let send_params_arc = Arc::new(Mutex::new(vec![])); let broadcast_handler = BroadcastHandleMock::new().send_params(&send_params_arc); let mut subject = ConnectionManager::new(); @@ -1198,23 +1192,15 @@ mod tests { let result = conversation.transact(request, 1000).unwrap(); let request_body = node_stop_handle.stop()[0].clone().unwrap(); - assert_eq!( - UiFinancialsRequest::fmb(request_body).unwrap().0, - UiFinancialsRequest { - payable_minimum_amount: 12, - payable_maximum_age: 23, - receivable_minimum_amount: 34, - receivable_maximum_age: 45, - } - ); + UiFinancialsRequest::fmb(request_body).unwrap(); let (response, context_id) = UiFinancialsResponse::fmb(result).unwrap(); assert_eq!( response, UiFinancialsResponse { - payables: vec![], - total_payable: 21, - receivables: vec![], - total_receivable: 32 + total_unpaid_payable: 10, + total_paid_payable: 22, + total_unpaid_receivable: 29, + total_paid_receivable: 32 } ); assert_eq!(context_id, 1); diff --git a/masq/src/schema.rs b/masq/src/schema.rs index e2e8e4d6f..6b988e21f 100644 --- a/masq/src/schema.rs +++ b/masq/src/schema.rs @@ -7,6 +7,7 @@ use crate::commands::check_password_command::check_password_subcommand; use crate::commands::configuration_command::configuration_subcommand; use crate::commands::crash_command::crash_subcommand; use crate::commands::descriptor_command::descriptor_subcommand; +use crate::commands::financials_command::financials_subcommand; use crate::commands::generate_wallets_command::generate_wallets_subcommand; use crate::commands::recover_wallets_command::recover_wallets_subcommand; use crate::commands::set_configuration_command::set_configuration_subcommand; @@ -57,6 +58,7 @@ pub fn app() -> App<'static, 'static> { .subcommand(crash_subcommand()) .subcommand(configuration_subcommand()) .subcommand(descriptor_subcommand()) + .subcommand(financials_subcommand()) .subcommand(generate_wallets_subcommand()) .subcommand(recover_wallets_subcommand()) .subcommand(set_configuration_subcommand()) diff --git a/masq_lib/src/messages.rs b/masq_lib/src/messages.rs index 7c34e0e1c..a30513948 100644 --- a/masq_lib/src/messages.rs +++ b/masq_lib/src/messages.rs @@ -532,26 +532,19 @@ pub struct UiReceivableAccount { } #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -pub struct UiFinancialsRequest { - #[serde(rename = "payableMinimumAmount")] - pub payable_minimum_amount: u64, - #[serde(rename = "payableMaximumAge")] - pub payable_maximum_age: u64, - #[serde(rename = "receivableMinimumAmount")] - pub receivable_minimum_amount: u64, - #[serde(rename = "receivableMaximumAge")] - pub receivable_maximum_age: u64, -} +pub struct UiFinancialsRequest {} conversation_message!(UiFinancialsRequest, "financials"); #[derive(Serialize, Deserialize, Debug, PartialEq)] pub struct UiFinancialsResponse { - pub payables: Vec, - #[serde(rename = "totalPayable")] - pub total_payable: u64, - pub receivables: Vec, - #[serde(rename = "totalReceivable")] - pub total_receivable: u64, + #[serde(rename = "totalUnpaidPayable")] + pub total_unpaid_payable: i64, + #[serde(rename = "totalPaidPayable")] + pub total_paid_payable: u64, + #[serde(rename = "totalUnpaidReceivable")] + pub total_unpaid_receivable: i64, + #[serde(rename = "totalPaidReceivable")] + pub total_paid_receivable: u64, } conversation_message!(UiFinancialsResponse, "financials"); @@ -718,43 +711,28 @@ mod tests { } #[test] - fn ui_financials_methods_were_correctly_generated() { - let subject = UiFinancialsResponse { - payables: vec![], - total_payable: 0, - receivables: vec![], - total_receivable: 0, + fn ui_descriptor_methods_were_correctly_generated() { + let subject = UiDescriptorResponse { + node_descriptor_opt: Some("descriptor".to_string()), }; - assert_eq!(subject.opcode(), "financials"); + assert_eq!(subject.opcode(), "descriptor"); assert_eq!(subject.is_conversational(), true); } #[test] - fn can_serialize_ui_financials_response() { - let subject = UiFinancialsResponse { - payables: vec![UiPayableAccount { - wallet: "wallet".to_string(), - age: 3456, - amount: 4567, - pending_payable_hash_opt: Some("Oxab45c6e2c3d1f6c231".to_string()), - }], - total_payable: 1234, - receivables: vec![UiReceivableAccount { - wallet: "tellaw".to_string(), - age: 6789, - amount: 7890, - }], - total_receivable: 2345, + fn can_serialize_ui_descriptor_response() { + let subject = UiDescriptorResponse { + node_descriptor_opt: None, }; let subject_json = serde_json::to_string(&subject).unwrap(); - let result: MessageBody = UiFinancialsResponse::tmb(subject, 1357); + let result: MessageBody = UiDescriptorResponse::tmb(subject, 1357); assert_eq!( result, MessageBody { - opcode: "financials".to_string(), + opcode: "descriptor".to_string(), path: Conversation(1357), payload: Ok(subject_json) } @@ -762,13 +740,10 @@ mod tests { } #[test] - fn can_deserialize_ui_financials_response_with_bad_opcode() { + fn can_deserialize_ui_descriptor_response_with_bad_opcode() { let json = r#" { - "payables": [], - "totalPayable": 1234, - "receivables": [], - "totalReceivable": 2345 + "nodeDescriptorOpt": "descriptor" } "# .to_string(); @@ -778,60 +753,57 @@ mod tests { payload: Ok(json), }; - let result: Result<(UiFinancialsResponse, u64), UiMessageError> = - UiFinancialsResponse::fmb(message_body.clone()); + let result: Result<(UiDescriptorResponse, u64), UiMessageError> = + UiDescriptorResponse::fmb(message_body.clone()); assert_eq!(result, Err(UnexpectedMessage(message_body))) } #[test] - fn can_deserialize_ui_financials_response_with_bad_path() { + fn can_deserialize_ui_descriptor_response_with_bad_path() { let json = r#" { - "payables": [], - "totalPayable": 1234, - "receivables": [], - "totalReceivable": 2345 + "nodeDescriptorOpt": "descriptor" } "# .to_string(); let message_body = MessageBody { - opcode: "financials".to_string(), + opcode: "descriptor".to_string(), path: FireAndForget, payload: Ok(json), }; - let result: Result<(UiFinancialsResponse, u64), UiMessageError> = - UiFinancialsResponse::fmb(message_body.clone()); + let result: Result<(UiDescriptorResponse, u64), UiMessageError> = + UiDescriptorResponse::fmb(message_body.clone()); assert_eq!(result, Err(UnexpectedMessage(message_body))) } #[test] - fn can_deserialize_ui_financials_response_with_bad_payload() { + fn can_deserialize_ui_descriptor_response_with_bad_payload() { let message_body = MessageBody { - opcode: "financials".to_string(), + opcode: "descriptor".to_string(), path: Conversation(1234), payload: Err((100, "error".to_string())), }; - let result: Result<(UiFinancialsResponse, u64), UiMessageError> = - UiFinancialsResponse::fmb(message_body.clone()); + let result: Result<(UiDescriptorResponse, u64), UiMessageError> = + UiDescriptorResponse::fmb(message_body.clone()); assert_eq!(result, Err(PayloadError(message_body))) } #[test] - fn can_deserialize_unparseable_ui_financials_response() { + fn can_deserialize_unparseable_ui_descriptor_response() { let json = "} - unparseable - {".to_string(); let message_body = MessageBody { - opcode: "financials".to_string(), + opcode: "descriptor".to_string(), path: Conversation(1234), payload: Ok(json), }; - let result: Result<(UiFinancialsResponse, u64), UiMessageError> = - UiFinancialsResponse::fmb(message_body.clone()); + let result: Result<(UiDescriptorResponse, u64), UiMessageError> = + UiDescriptorResponse::fmb(message_body.clone()); assert_eq!( result, @@ -843,51 +815,27 @@ mod tests { } #[test] - fn can_deserialize_ui_financials_response() { + fn can_deserialize_ui_descriptor_response() { let json = r#" { - "payables": [{ - "wallet": "wallet", - "age": 3456, - "amount": 4567, - "pendingPayableHashOpt": "Oxab45c6e2c3d1f6c231" - }], - "totalPayable": 1234, - "receivables": [{ - "wallet": "tellaw", - "age": 6789, - "amount": 7890 - }], - "totalReceivable": 2345 + "nodeDescriptorOpt": "descriptor" } "# .to_string(); let message_body = MessageBody { - opcode: "financials".to_string(), + opcode: "descriptor".to_string(), path: Conversation(4321), payload: Ok(json), }; - let result: Result<(UiFinancialsResponse, u64), UiMessageError> = - UiFinancialsResponse::fmb(message_body); + let result: Result<(UiDescriptorResponse, u64), UiMessageError> = + UiDescriptorResponse::fmb(message_body); assert_eq!( result, Ok(( - UiFinancialsResponse { - payables: vec![UiPayableAccount { - wallet: "wallet".to_string(), - age: 3456, - amount: 4567, - pending_payable_hash_opt: Some("Oxab45c6e2c3d1f6c231".to_string()) - }], - total_payable: 1234, - receivables: vec![UiReceivableAccount { - wallet: "tellaw".to_string(), - age: 6789, - amount: 7890 - }], - total_receivable: 2345 + UiDescriptorResponse { + node_descriptor_opt: Some("descriptor".to_string()) }, 4321 )) diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index c717d3adf..4725c599d 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -16,7 +16,7 @@ use crate::accountant::receivable_dao::{ use crate::accountant::tools::accountant_tools::{Scanners, TransactionConfirmationTools}; use crate::banned_dao::{BannedDao, BannedDaoFactory}; use crate::blockchain::blockchain_bridge::{PendingPayableFingerprint, RetrieveTransactions}; -use crate::blockchain::blockchain_interface::{BlockchainError, Transaction}; +use crate::blockchain::blockchain_interface::{BlockchainError, PaidReceivable}; use crate::bootstrapper::BootstrapperConfig; use crate::database::dao_utils::DaoFactoryReal; use crate::database::db_migrations::MigratorConfig; @@ -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, FinancialStatistics}; 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}; @@ -45,8 +45,8 @@ use itertools::Itertools; use lazy_static::lazy_static; use masq_lib::crash_point::CrashPoint; use masq_lib::logger::Logger; +use masq_lib::messages::UiFinancialsResponse; use masq_lib::messages::{FromMessageBody, ToMessageBody, UiFinancialsRequest}; -use masq_lib::messages::{UiFinancialsResponse, UiPayableAccount, UiReceivableAccount}; use masq_lib::ui_gateway::MessageTarget::ClientId; use masq_lib::ui_gateway::{NodeFromUiMessage, NodeToUiMessage}; use masq_lib::utils::{plus, ExpectValue}; @@ -109,6 +109,7 @@ pub struct Accountant { crashable: bool, scanners: Scanners, tools: TransactionConfirmationTools, + financial_statistics: FinancialStatistics, persistent_configuration: Box, report_accounts_payable_sub: Option>, retrieve_transactions_sub: Option>, @@ -124,7 +125,7 @@ impl Actor for Accountant { #[derive(Debug, Eq, Message, PartialEq)] pub struct ReceivedPayments { - pub payments: Vec, + pub payments: Vec, } #[derive(Debug, Message, PartialEq)] @@ -347,8 +348,8 @@ impl Handler for Accountant { fn handle(&mut self, msg: NodeFromUiMessage, _ctx: &mut Self::Context) -> Self::Result { let client_id = msg.client_id; - if let Ok((body, context_id)) = UiFinancialsRequest::fmb(msg.clone().body) { - self.handle_financials(client_id, context_id, body); + if let Ok((_, context_id)) = UiFinancialsRequest::fmb(msg.clone().body) { + self.handle_financials(client_id, context_id); } else { handle_ui_crash_request(msg, &self.logger, self.crashable, CRASH_KEY) } @@ -375,6 +376,7 @@ impl Accountant { crashable: config.crash_point == CrashPoint::Message, scanners: Scanners::default(), tools: TransactionConfirmationTools::default(), + financial_statistics: FinancialStatistics::default(), persistent_configuration: Box::new(PersistentConfigurationReal::new( config_dao_factory.make(), )), @@ -733,9 +735,14 @@ impl Accountant { if msg.payments.is_empty() { warning!(self.logger, "Handling received payments we got zero payments but expected some, skipping database operations") } else { + let total_newly_paid_receivable = msg + .payments + .iter() + .fold(0, |so_far, now| so_far + now.gwei_amount); self.receivable_dao .as_mut() .more_money_received(msg.payments); + self.financial_statistics.total_paid_receivable += total_newly_paid_receivable; } } @@ -841,47 +848,16 @@ impl Accountant { ); } - fn handle_financials(&mut self, client_id: u64, context_id: u64, request: UiFinancialsRequest) { - let payables = self - .payable_dao - .top_records(request.payable_minimum_amount, request.payable_maximum_age) - .iter() - .map(|account| UiPayableAccount { - wallet: account.wallet.to_string(), - age: SystemTime::now() - .duration_since(account.last_paid_timestamp) - .expect("Bad interval") - .as_secs(), - amount: account.balance as u64, - pending_payable_hash_opt: account - .pending_payable_opt - .as_ref() - .map(|PendingPayableId { hash, .. }| format!("{:?}", hash)), - }) - .collect_vec(); - let total_payable = self.payable_dao.total(); - let receivables = self - .receivable_dao - .top_records( - request.receivable_minimum_amount, - request.receivable_maximum_age, - ) - .iter() - .map(|account| UiReceivableAccount { - wallet: account.wallet.to_string(), - age: SystemTime::now() - .duration_since(account.last_received_timestamp) - .expect("Bad interval") - .as_secs(), - amount: account.balance as u64, - }) - .collect_vec(); - let total_receivable = self.receivable_dao.total(); + fn handle_financials(&mut self, client_id: u64, context_id: u64) { + let total_unpaid_payable = self.payable_dao.total(); + let total_paid_payable = self.financial_statistics.total_paid_payable; + let total_unpaid_receivable = self.receivable_dao.total(); + let total_paid_receivable = self.financial_statistics.total_paid_receivable; let body = UiFinancialsResponse { - payables, - total_payable, - receivables, - total_receivable, + total_unpaid_payable, + total_paid_payable, + total_unpaid_receivable, + total_paid_receivable, } .tmb(context_id); self.ui_message_sub @@ -906,7 +882,7 @@ impl Accountant { } } - fn handle_confirm_pending_transaction(&self, msg: ConfirmPendingTransaction) { + fn handle_confirm_pending_transaction(&mut self, msg: ConfirmPendingTransaction) { if let Err(e) = self .payable_dao .transaction_confirmed(&msg.pending_payable_fingerprint) @@ -916,6 +892,7 @@ impl Accountant { msg.pending_payable_fingerprint.hash, e ) } else { + self.financial_statistics.total_paid_payable += msg.pending_payable_fingerprint.amount; debug!( self.logger, "Confirmation of transaction {}; record for payable was modified", @@ -1087,14 +1064,18 @@ impl Accountant { if let PendingTransactionStatus::StillPending(transaction_id) = status { self.update_payable_fingerprint(transaction_id) } else if let PendingTransactionStatus::Failure(transaction_id) = status { - self.cancel_failed_transaction(transaction_id, ctx) + self.order_cancel_failed_transaction(transaction_id, ctx) } else if let PendingTransactionStatus::Confirmed(fingerprint) = status { - self.confirm_transaction(fingerprint, ctx) + self.order_confirm_transaction(fingerprint, ctx) } }); } - fn cancel_failed_transaction(&self, transaction_id: PendingPayableId, ctx: &mut Context) { + fn order_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( CancelFailedPendingTransaction { id: transaction_id }, @@ -1102,7 +1083,7 @@ impl Accountant { ) } - fn confirm_transaction( + fn order_confirm_transaction( &self, pending_payable_fingerprint: PendingPayableFingerprint, ctx: &mut Context, @@ -1184,7 +1165,7 @@ mod tests { use crate::accountant::tools::accountant_tools::NullScanner; use crate::blockchain::blockchain_bridge::BlockchainBridge; use crate::blockchain::blockchain_interface::BlockchainError; - use crate::blockchain::blockchain_interface::Transaction; + use crate::blockchain::blockchain_interface::PaidReceivable; use crate::blockchain::test_utils::BlockchainInterfaceMock; use crate::blockchain::tool_wrappers::SendTransactionToolsWrapperNull; use crate::database::dao_utils::from_time_t; @@ -1264,45 +1245,10 @@ mod tests { #[test] fn financials_request_produces_financials_response() { - let payable_top_records_parameters_arc = Arc::new(Mutex::new(vec![])); - let payable_dao = PayableDaoMock::new() - .top_records_parameters(&payable_top_records_parameters_arc) - .top_records_result(vec![ - PayableAccount { - wallet: make_wallet("earning 1"), - balance: 12345678, - last_paid_timestamp: SystemTime::now().sub(Duration::from_secs(10000)), - pending_payable_opt: Some(PendingPayableId { - rowid: 789, - hash: H256::from_uint(&U256::from(3333333)), - }), - }, - PayableAccount { - wallet: make_wallet("earning 2"), - balance: 12345679, - last_paid_timestamp: SystemTime::now().sub(Duration::from_secs(10001)), - pending_payable_opt: None, - }, - ]) - .total_result(23456789); - let receivable_top_records_parameters_arc = Arc::new(Mutex::new(vec![])); - let receivable_dao = ReceivableDaoMock::new() - .top_records_parameters(&receivable_top_records_parameters_arc) - .top_records_result(vec![ - ReceivableAccount { - wallet: make_wallet("consuming 1"), - balance: 87654321, - last_received_timestamp: SystemTime::now().sub(Duration::from_secs(20000)), - }, - ReceivableAccount { - wallet: make_wallet("consuming 2"), - balance: 87654322, - last_received_timestamp: SystemTime::now().sub(Duration::from_secs(20001)), - }, - ]) - .total_result(98765432); + let payable_dao = PayableDaoMock::new().total_result(23456789); + let receivable_dao = ReceivableDaoMock::new().total_result(98765432); let system = System::new("test"); - let subject = AccountantBuilder::default() + let mut subject = AccountantBuilder::default() .bootstrapper_config(bc_from_ac_plus_earning_wallet( AccountantConfig { payables_scan_interval: Duration::from_millis(10_000), @@ -1315,6 +1261,8 @@ mod tests { .receivable_dao(receivable_dao) .payable_dao(payable_dao) .build(); + subject.financial_statistics.total_paid_payable = 123456; + subject.financial_statistics.total_paid_receivable = 334455; let (ui_gateway, _, ui_gateway_recording_arc) = make_recorder(); let subject_addr = subject.start(); let peer_actors = peer_actors_builder().ui_gateway(ui_gateway).build(); @@ -1324,19 +1272,14 @@ mod tests { body: MessageBody { opcode: "financials".to_string(), path: Conversation(2222), - payload: Ok(r#"{"payableMinimumAmount": 50001, "payableMaximumAge": 50002, "receivableMinimumAmount": 50003, "receivableMaximumAge": 50004}"#.to_string()), - } + payload: Ok("{}".to_string()), + }, }; subject_addr.try_send(ui_message).unwrap(); System::current().stop(); system.run(); - let payable_top_records_parameters = payable_top_records_parameters_arc.lock().unwrap(); - assert_eq!(*payable_top_records_parameters, vec![(50001, 50002)]); - let receivable_top_records_parameters = - receivable_top_records_parameters_arc.lock().unwrap(); - assert_eq!(*receivable_top_records_parameters, vec![(50003, 50004)]); let ui_gateway_recording = ui_gateway_recording_arc.lock().unwrap(); let response = ui_gateway_recording.get_record::(0); assert_eq!(response.target, MessageTarget::ClientId(1234)); @@ -1348,41 +1291,87 @@ mod tests { assert_eq!( parsed_payload, UiFinancialsResponse { - payables: vec![ - UiPayableAccount { - wallet: "0x00000000000000000000006561726e696e672031".to_string(), - age: 10000, - amount: 12345678, - pending_payable_hash_opt: Some( - "0x000000000000000000000000000000000000000000000000000000000032dcd5" - .to_string() - ) - }, - UiPayableAccount { - wallet: "0x00000000000000000000006561726e696e672032".to_string(), - age: 10001, - amount: 12345679, - pending_payable_hash_opt: None, - } - ], - total_payable: 23456789, - receivables: vec![ - UiReceivableAccount { - wallet: "0x000000000000000000636f6e73756d696e672031".to_string(), - age: 20000, - amount: 87654321, - }, - UiReceivableAccount { - wallet: "0x000000000000000000636f6e73756d696e672032".to_string(), - age: 20001, - amount: 87654322, - } - ], - total_receivable: 98765432 + total_unpaid_payable: 23456789, + total_paid_payable: 123456, + total_unpaid_receivable: 98765432, + total_paid_receivable: 334455 } ); } + #[test] + fn total_paid_payable_for_financial_statistics_starts_at_zero_and_is_repeatedly_updated() { + let transaction_confirmed_params_arc = Arc::new(Mutex::new(vec![])); + let fingerprint = PendingPayableFingerprint { + rowid_opt: Some(5), + timestamp: from_time_t(189_999_888), + hash: H256::from_uint(&U256::from(56789)), + attempt_opt: Some(1), + amount: 5478, + process_error: None, + }; + let mut pending_payable_dao = + PendingPayableDaoMock::default().delete_fingerprint_result(Ok(())); + let payable_dao = PayableDaoMock::default() + .transaction_confirmed_params(&transaction_confirmed_params_arc) + .transaction_confirmed_result(Ok(())) + .transaction_confirmed_result(Ok(())); + pending_payable_dao.have_return_all_fingerprints_shut_down_the_system = true; + let mut subject = AccountantBuilder::default() + .pending_payable_dao(pending_payable_dao) + .payable_dao(payable_dao) + .build(); + assert_eq!(subject.financial_statistics.total_paid_payable, 0); + //to demonstrate that we will perform addition and not overwrite the value + subject.financial_statistics.total_paid_payable += 1111; + let msg = ConfirmPendingTransaction { + pending_payable_fingerprint: fingerprint.clone(), + }; + + subject.handle_confirm_pending_transaction(msg); + + assert_eq!(subject.financial_statistics.total_paid_payable, 1111 + 5478); + let transaction_confirmed_params = transaction_confirmed_params_arc.lock().unwrap(); + assert_eq!(*transaction_confirmed_params, vec![fingerprint]) + } + + #[test] + fn total_paid_receivable_for_financial_statistics_starts_at_zero_and_is_repeatedly_updated() { + let more_money_received_params_arc = Arc::new(Mutex::new(vec![])); + let receivable_dao = ReceivableDaoMock::new() + .more_money_received_parameters(&more_money_received_params_arc) + .more_money_receivable_result(Ok(())); + let mut subject = AccountantBuilder::default() + .receivable_dao(receivable_dao) + .build(); + assert_eq!(subject.financial_statistics.total_paid_receivable, 0); + //to demonstrate that we will perform addition and not overwrite the value + subject.financial_statistics.total_paid_receivable += 2222; + let receivables = vec![ + PaidReceivable { + block_number: 4578910, + from: make_wallet("wallet_1"), + gwei_amount: 45780, + }, + PaidReceivable { + block_number: 4569898, + from: make_wallet("wallet_2"), + gwei_amount: 33345, + }, + ]; + + subject.handle_received_payments(ReceivedPayments { + payments: receivables.clone(), + }); + + assert_eq!( + subject.financial_statistics.total_paid_receivable, + 2222 + 45780 + 33345 + ); + let more_money_received_params = more_money_received_params_arc.lock().unwrap(); + assert_eq!(*more_money_received_params, vec![receivables]); + } + #[test] fn accountant_calls_payable_dao_to_mark_pending_payable() { let fingerprint_rowid_params_arc = Arc::new(Mutex::new(vec![])); @@ -1663,12 +1652,12 @@ mod tests { #[test] fn accountant_receives_new_payments_to_the_receivables_dao() { let earning_wallet = make_wallet("earner3000"); - let expected_receivable_1 = Transaction { + let expected_receivable_1 = PaidReceivable { block_number: 7, from: make_wallet("wallet0"), gwei_amount: 456, }; - let expected_receivable_2 = Transaction { + let expected_receivable_2 = PaidReceivable { block_number: 13, from: make_wallet("wallet1"), gwei_amount: 10000, @@ -3158,7 +3147,7 @@ mod tests { let pending_payable_dao = PendingPayableDaoMock::default() .delete_fingerprint_params(&delete_pending_payable_fingerprint_params_arc) .delete_fingerprint_result(Ok(())); - let subject = AccountantBuilder::default() + let mut subject = AccountantBuilder::default() .payable_dao(payable_dao) .pending_payable_dao(pending_payable_dao) .build(); @@ -3205,7 +3194,7 @@ mod tests { let payable_dao = PayableDaoMock::new().transaction_confirmed_result(Err( PayableDaoError::RusqliteError("record change not successful".to_string()), )); - let subject = AccountantBuilder::default() + let mut subject = AccountantBuilder::default() .payable_dao(payable_dao) .build(); let mut payment = make_pending_payable_fingerprint(); @@ -3232,7 +3221,7 @@ mod tests { "the database is fooling around with us".to_string(), ), )); - let subject = AccountantBuilder::default() + let mut subject = AccountantBuilder::default() .payable_dao(payable_dao) .pending_payable_dao(pending_payable_dao) .build(); @@ -3517,7 +3506,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![ @@ -3533,8 +3522,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(())) @@ -3552,6 +3539,7 @@ mod tests { .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 |_| { @@ -3581,18 +3569,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); diff --git a/node/src/accountant/payable_dao.rs b/node/src/accountant/payable_dao.rs index ad8d8f01b..a1a26faad 100644 --- a/node/src/accountant/payable_dao.rs +++ b/node/src/accountant/payable_dao.rs @@ -67,7 +67,7 @@ pub trait PayableDao: Debug + Send { fn top_records(&self, minimum_amount: u64, maximum_age: u64) -> Vec; - fn total(&self) -> u64; + fn total(&self) -> i64; } pub trait PayableDaoFactory { @@ -228,7 +228,7 @@ impl PayableDao for PayableDaoReal { .collect() } - fn total(&self) -> u64 { + fn total(&self) -> i64 { let mut stmt = self .conn .prepare("select sum(balance) from payable") @@ -236,7 +236,7 @@ impl PayableDao for PayableDaoReal { match stmt.query_row([], |row| { let total_balance_result: Result = row.get(0); match total_balance_result { - Ok(total_balance) => Ok(total_balance as u64), + Ok(total_balance) => Ok(total_balance), Err(e) if e == rusqlite::Error::InvalidColumnType( 0, @@ -244,7 +244,7 @@ impl PayableDao for PayableDaoReal { Type::Null, ) => { - Ok(0u64) + Ok(0) } Err(e) => panic!( "Database is corrupt: PAYABLE table columns and/or types: {:?}", diff --git a/node/src/accountant/receivable_dao.rs b/node/src/accountant/receivable_dao.rs index 1263d0c19..beec71a4c 100644 --- a/node/src/accountant/receivable_dao.rs +++ b/node/src/accountant/receivable_dao.rs @@ -1,6 +1,6 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::accountant::{unsigned_to_signed, PaymentCurves}; -use crate::blockchain::blockchain_interface::Transaction; +use crate::blockchain::blockchain_interface::PaidReceivable; use crate::database::connection_wrapper::ConnectionWrapper; use crate::database::dao_utils; use crate::database::dao_utils::{to_time_t, DaoFactoryReal}; @@ -38,7 +38,7 @@ pub trait ReceivableDao: Send { fn more_money_receivable(&self, wallet: &Wallet, amount: u64) -> Result<(), ReceivableDaoError>; - fn more_money_received(&mut self, transactions: Vec); + fn more_money_received(&mut self, transactions: Vec); fn account_status(&self, wallet: &Wallet) -> Option; @@ -54,7 +54,7 @@ pub trait ReceivableDao: Send { fn top_records(&self, minimum_amount: u64, maximum_age: u64) -> Vec; - fn total(&self) -> u64; + fn total(&self) -> i64; } pub trait ReceivableDaoFactory { @@ -94,7 +94,7 @@ impl ReceivableDao for ReceivableDaoReal { } } - fn more_money_received(&mut self, payments: Vec) { + fn more_money_received(&mut self, payments: Vec) { self.try_multi_insert_payment(&payments) .unwrap_or_else(|e| { let mut report_lines = @@ -251,7 +251,7 @@ impl ReceivableDao for ReceivableDaoReal { .collect() } - fn total(&self) -> u64 { + fn total(&self) -> i64 { let mut stmt = self .conn .prepare("select sum(balance) from receivable") @@ -259,7 +259,7 @@ impl ReceivableDao for ReceivableDaoReal { match stmt.query_row([], |row| { let total_balance_result: Result = row.get(0); match total_balance_result { - Ok(total_balance) => Ok(total_balance as u64), + Ok(total_balance) => Ok(total_balance), Err(e) if e == rusqlite::Error::InvalidColumnType( 0, @@ -267,7 +267,7 @@ impl ReceivableDao for ReceivableDaoReal { Type::Null, ) => { - Ok(0u64) + Ok(0) } Err(e) => panic!( "Database is corrupt: RECEIVABLE table columns and/or types: {:?}", @@ -314,7 +314,7 @@ impl ReceivableDaoReal { fn try_multi_insert_payment( &mut self, - payments: &[Transaction], + payments: &[PaidReceivable], ) -> Result<(), ReceivableDaoError> { let tx = match self.conn.transaction() { Ok(t) => t, @@ -415,7 +415,7 @@ mod tests { .initialize(&home_dir, true, MigratorConfig::test_default()) .unwrap(), ); - let payments = vec![Transaction { + let payments = vec![PaidReceivable { block_number: 42u64, from: make_wallet("some_address"), gwei_amount: 18446744073709551615, @@ -444,7 +444,7 @@ mod tests { } let mut subject = ReceivableDaoReal::new(conn); - let payments = vec![Transaction { + let payments = vec![PaidReceivable { block_number: 42u64, from: make_wallet("some_address"), gwei_amount: 18446744073709551615, @@ -476,7 +476,7 @@ mod tests { } let mut subject = ReceivableDaoReal::new(conn); - let payments = vec![Transaction { + let payments = vec![PaidReceivable { block_number: 42u64, from: make_wallet("some_address"), gwei_amount: 18446744073709551615, @@ -600,12 +600,12 @@ mod tests { let (status1, status2) = { let transactions = vec![ - Transaction { + PaidReceivable { from: debtor1.clone(), gwei_amount: 1200u64, block_number: 35u64, }, - Transaction { + PaidReceivable { from: debtor2.clone(), gwei_amount: 2300u64, block_number: 57u64, @@ -654,7 +654,7 @@ mod tests { ); let status = { - let transactions = vec![Transaction { + let transactions = vec![PaidReceivable { from: debtor.clone(), gwei_amount: 2300u64, block_number: 33u64, @@ -673,17 +673,17 @@ mod tests { ConnectionWrapperMock::default().transaction_result(Err(Error::InvalidQuery)); let mut receivable_dao = ReceivableDaoReal::new(Box::new(conn_mock)); let payments = vec![ - Transaction { + PaidReceivable { block_number: 1234567890, from: Wallet::new("0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"), gwei_amount: 123456789123456789, }, - Transaction { + PaidReceivable { block_number: 2345678901, from: Wallet::new("0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"), gwei_amount: 234567891234567891, }, - Transaction { + PaidReceivable { block_number: 3456789012, from: Wallet::new("0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"), gwei_amount: 345678912345678912, diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs index 1ee7e50e3..29fa9a3f9 100644 --- a/node/src/accountant/test_utils.rs +++ b/node/src/accountant/test_utils.rs @@ -14,7 +14,7 @@ use crate::accountant::receivable_dao::{ use crate::accountant::{Accountant, PaymentCurves, PendingPayableId}; use crate::banned_dao::{BannedDao, BannedDaoFactory}; use crate::blockchain::blockchain_bridge::PendingPayableFingerprint; -use crate::blockchain::blockchain_interface::Transaction; +use crate::blockchain::blockchain_interface::PaidReceivable; use crate::bootstrapper::BootstrapperConfig; use crate::database::dao_utils; use crate::database::dao_utils::{from_time_t, to_time_t}; @@ -285,7 +285,7 @@ pub struct PayableDaoMock { transaction_canceled_results: RefCell>>, top_records_parameters: Arc>>, top_records_results: RefCell>>, - total_results: RefCell>, + total_results: RefCell>, pub have_non_pending_payables_shut_down_the_system: bool, } @@ -342,7 +342,7 @@ impl PayableDao for PayableDaoMock { self.top_records_results.borrow_mut().remove(0) } - fn total(&self) -> u64 { + fn total(&self) -> i64 { self.total_results.borrow_mut().remove(0) } } @@ -426,7 +426,7 @@ impl PayableDaoMock { self } - pub fn total_result(self, result: u64) -> Self { + pub fn total_result(self, result: i64) -> Self { self.total_results.borrow_mut().push(result); self } @@ -438,7 +438,7 @@ pub struct ReceivableDaoMock { account_status_results: RefCell>>, more_money_receivable_parameters: Arc>>, more_money_receivable_results: RefCell>>, - more_money_received_parameters: Arc>>>, + more_money_received_parameters: Arc>>>, more_money_received_results: RefCell>>, receivables_results: RefCell>>, new_delinquencies_parameters: Arc>>, @@ -447,7 +447,7 @@ pub struct ReceivableDaoMock { paid_delinquencies_results: RefCell>>, top_records_parameters: Arc>>, top_records_results: RefCell>>, - total_results: RefCell>, + total_results: RefCell>, pub have_new_delinquencies_shutdown_the_system: bool, } @@ -464,7 +464,7 @@ impl ReceivableDao for ReceivableDaoMock { self.more_money_receivable_results.borrow_mut().remove(0) } - fn more_money_received(&mut self, transactions: Vec) { + fn more_money_received(&mut self, transactions: Vec) { self.more_money_received_parameters .lock() .unwrap() @@ -518,7 +518,7 @@ impl ReceivableDao for ReceivableDaoMock { self.top_records_results.borrow_mut().remove(0) } - fn total(&self) -> u64 { + fn total(&self) -> i64 { self.total_results.borrow_mut().remove(0) } } @@ -543,7 +543,7 @@ impl ReceivableDaoMock { pub fn more_money_received_parameters( mut self, - parameters: &Arc>>>, + parameters: &Arc>>>, ) -> Self { self.more_money_received_parameters = parameters.clone(); self @@ -590,7 +590,7 @@ impl ReceivableDaoMock { self } - pub fn total_result(self, result: u64) -> Self { + pub fn total_result(self, result: i64) -> Self { self.total_results.borrow_mut().push(result); self } diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index 0df24738d..29065bbb3 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -371,7 +371,7 @@ mod tests { use crate::blockchain::bip32::Bip32ECKeyProvider; use crate::blockchain::blockchain_bridge::Payable; use crate::blockchain::blockchain_interface::{ - BlockchainError, BlockchainTransactionError, Transaction, + BlockchainError, BlockchainTransactionError, PaidReceivable, }; use crate::blockchain::test_utils::BlockchainInterfaceMock; use crate::blockchain::tool_wrappers::SendTransactionToolsWrapperNull; @@ -903,12 +903,12 @@ mod tests { let amount = 42; let amount2 = 55; let expected_transactions = vec![ - Transaction { + PaidReceivable { block_number: 7, from: earning_wallet.clone(), gwei_amount: amount, }, - Transaction { + PaidReceivable { block_number: 9, from: earning_wallet.clone(), gwei_amount: amount2, diff --git a/node/src/blockchain/blockchain_interface.rs b/node/src/blockchain/blockchain_interface.rs index 1a706008f..194778f6f 100644 --- a/node/src/blockchain/blockchain_interface.rs +++ b/node/src/blockchain/blockchain_interface.rs @@ -38,13 +38,13 @@ const TRANSACTION_LITERAL: H256 = H256 { const TRANSFER_METHOD_ID: [u8; 4] = [0xa9, 0x05, 0x9c, 0xbb]; #[derive(Clone, Debug, Eq, Message, PartialEq)] -pub struct Transaction { +pub struct PaidReceivable { pub block_number: u64, pub from: Wallet, pub gwei_amount: u64, } -impl fmt::Display for Transaction { +impl fmt::Display for PaidReceivable { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { write!( f, @@ -80,13 +80,13 @@ impl Display for BlockchainError { pub type BlockchainResult = Result; pub type Balance = BlockchainResult; pub type Nonce = BlockchainResult; -pub type Transactions = BlockchainResult>; +pub type PaidReceivables = BlockchainResult>; pub type Receipt = BlockchainResult>; pub trait BlockchainInterface { fn contract_address(&self) -> Address; - fn retrieve_transactions(&self, start_block: u64, recipient: &Wallet) -> Transactions; + fn retrieve_transactions(&self, start_block: u64, recipient: &Wallet) -> PaidReceivables; fn send_transaction( &self, @@ -140,7 +140,7 @@ impl BlockchainInterface for BlockchainInterfaceClandestine { self.chain.rec().contract } - fn retrieve_transactions(&self, _start_block: u64, _recipient: &Wallet) -> Transactions { + fn retrieve_transactions(&self, _start_block: u64, _recipient: &Wallet) -> PaidReceivables { let msg = "Can't retrieve transactions clandestinely yet".to_string(); error!(self.logger, "{}", &msg); Err(BlockchainError::QueryFailed(msg)) @@ -219,7 +219,7 @@ where self.chain.rec().contract } - fn retrieve_transactions(&self, start_block: u64, recipient: &Wallet) -> Transactions { + fn retrieve_transactions(&self, start_block: u64, recipient: &Wallet) -> PaidReceivables { debug!( self.logger, "Retrieving transactions from start block: {} for: {} chain_id: {} contract: {:#x}", @@ -244,7 +244,7 @@ where let logger = self.logger.clone(); log_request .then(|logs| { - future::result::, BlockchainError>(match logs { + future::result::, BlockchainError>(match logs { Ok(logs) => { if logs .iter() @@ -258,7 +258,7 @@ where Some(block_number) => { let amount: U256 = U256::from(log.data.0.as_slice()); let gwei_amount = to_gwei(amount); - gwei_amount.map(|gwei_amount| Transaction { + gwei_amount.map(|gwei_amount| PaidReceivable { block_number: u64::try_from(block_number) .expect("Internal Error"), // TODO: back to testing for overflow from: Wallet::from(log.topics[1]), @@ -673,7 +673,7 @@ mod tests { ); assert_eq!( result, - vec![Transaction { + vec![PaidReceivable { block_number: 4_974_179u64, from: Wallet::from_str("0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc").unwrap(), gwei_amount: 4_503_599u64, diff --git a/node/src/blockchain/test_utils.rs b/node/src/blockchain/test_utils.rs index 39aba7991..bed242ffa 100644 --- a/node/src/blockchain/test_utils.rs +++ b/node/src/blockchain/test_utils.rs @@ -5,7 +5,7 @@ use crate::blockchain::blockchain_bridge::PendingPayableFingerprint; use crate::blockchain::blockchain_interface::{ Balance, BlockchainError, BlockchainInterface, BlockchainResult, BlockchainTransactionError, - Nonce, Receipt, SendTransactionInputs, Transaction, Transactions, REQUESTS_IN_PARALLEL, + Nonce, PaidReceivable, PaidReceivables, Receipt, SendTransactionInputs, REQUESTS_IN_PARALLEL, }; use crate::blockchain::tool_wrappers::SendTransactionToolsWrapper; use crate::sub_lib::wallet::Wallet; @@ -51,7 +51,7 @@ pub fn make_meaningless_seed() -> Seed { #[derive(Default)] pub struct BlockchainInterfaceMock { retrieve_transactions_parameters: Arc>>, - retrieve_transactions_results: RefCell>>>, + retrieve_transactions_results: RefCell>>>, send_transaction_parameters: Arc>>, send_transaction_results: RefCell>>, get_transaction_receipt_params: Arc>>, @@ -70,7 +70,7 @@ impl BlockchainInterfaceMock { pub fn retrieve_transactions_result( self, - result: Result, BlockchainError>, + result: Result, BlockchainError>, ) -> Self { self.retrieve_transactions_results.borrow_mut().push(result); self @@ -135,7 +135,7 @@ impl BlockchainInterface for BlockchainInterfaceMock { self.contract_address_results.borrow_mut().remove(0) } - fn retrieve_transactions(&self, start_block: u64, recipient: &Wallet) -> Transactions { + fn retrieve_transactions(&self, start_block: u64, recipient: &Wallet) -> PaidReceivables { self.retrieve_transactions_parameters .lock() .unwrap() diff --git a/node/src/daemon/mod.rs b/node/src/daemon/mod.rs index 706ec85b8..89d2f32ae 100644 --- a/node/src/daemon/mod.rs +++ b/node/src/daemon/mod.rs @@ -1541,13 +1541,7 @@ mod tests { subject_addr .try_send(make_bind_message(ui_gateway)) .unwrap(); - let body: MessageBody = UiFinancialsRequest { - payable_minimum_amount: 0, - payable_maximum_age: 0, - receivable_minimum_amount: 0, - receivable_maximum_age: 0, - } - .tmb(4321); + let body: MessageBody = UiFinancialsRequest {}.tmb(4321); subject_addr .try_send(NodeFromUiMessage { diff --git a/node/src/sub_lib/accountant.rs b/node/src/sub_lib/accountant.rs index bae2d2f27..3e8978a41 100644 --- a/node/src/sub_lib/accountant.rs +++ b/node/src/sub_lib/accountant.rs @@ -7,7 +7,6 @@ use actix::Message; use actix::Recipient; use lazy_static::lazy_static; use masq_lib::ui_gateway::NodeFromUiMessage; -use serde_derive::{Deserialize, Serialize}; use std::fmt::{Debug, Formatter}; use std::str::FromStr; use std::time::Duration; @@ -80,16 +79,10 @@ pub struct ReportExitServiceConsumedMessage { pub byte_rate: u64, } -#[derive(Clone, PartialEq, Debug, Message)] -pub struct GetFinancialStatisticsMessage { - pub client_id: u64, -} - -#[derive(Clone, PartialEq, Debug, Message, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct FinancialStatisticsMessage { - pub pending_credit: i64, - pub pending_debt: i64, +#[derive(Clone, PartialEq, Debug, Default)] +pub struct FinancialStatistics { + pub total_paid_payable: u64, + pub total_paid_receivable: u64, } #[cfg(test)] diff --git a/node/src/test_utils/recorder.rs b/node/src/test_utils/recorder.rs index 48068dc09..f43687306 100644 --- a/node/src/test_utils/recorder.rs +++ b/node/src/test_utils/recorder.rs @@ -9,11 +9,11 @@ use crate::daemon::crash_notification::CrashNotification; use crate::daemon::DaemonBindMessage; use crate::neighborhood::gossip::Gossip_0v1; use crate::stream_messages::{AddStreamMsg, PoolBindMessage, RemoveStreamMsg}; +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::{AccountantSubs, GetFinancialStatisticsMessage}; use crate::sub_lib::blockchain_bridge::{BlockchainBridgeSubs, SetDbPasswordMsg}; use crate::sub_lib::blockchain_bridge::{ReportAccountsPayable, SetGasPriceMsg}; use crate::sub_lib::configurator::{ConfiguratorSubs, NewPasswordMessage}; @@ -103,7 +103,6 @@ recorder_message_handler!(ExpiredCoresPackage); recorder_message_handler!(ExpiredCoresPackage); recorder_message_handler!(ExpiredCoresPackage); recorder_message_handler!(ExpiredCoresPackage); -recorder_message_handler!(GetFinancialStatisticsMessage); recorder_message_handler!(InboundClientData); recorder_message_handler!(InboundServerData); recorder_message_handler!(IncipientCoresPackage); diff --git a/node/tests/initialization_test.rs b/node/tests/initialization_test.rs index f332e9c3f..81758d156 100644 --- a/node/tests/initialization_test.rs +++ b/node/tests/initialization_test.rs @@ -78,12 +78,7 @@ fn initialization_sequence_integration() { ("data-directory", Some(&data_directory.to_str().unwrap())), ])) .unwrap(); - let financials_request = UiFinancialsRequest { - payable_minimum_amount: 0, - payable_maximum_age: 0, - receivable_minimum_amount: 0, - receivable_maximum_age: 0, - }; + let financials_request = UiFinancialsRequest {}; let context_id = 1234; // diff --git a/node/tests/ui_gateway_test.rs b/node/tests/ui_gateway_test.rs index 3a6028838..e655ced84 100644 --- a/node/tests/ui_gateway_test.rs +++ b/node/tests/ui_gateway_test.rs @@ -48,19 +48,11 @@ fn request_financial_information_integration() { true, ); node.wait_for_log("UIGateway bound", Some(5000)); - let financials_request = UiFinancialsRequest { - payable_minimum_amount: 0, - payable_maximum_age: 1_000_000_000_000, - receivable_minimum_amount: 0, - receivable_maximum_age: 1_000_000_000_000, - }; + let financials_request = UiFinancialsRequest {}; let mut client = UiConnection::new(port, NODE_UI_PROTOCOL); client.send(financials_request); - let financials_response: UiFinancialsResponse = client.receive().unwrap(); - - assert_eq!(financials_response.payables.len(), 0); - assert_eq!(financials_response.receivables.len(), 0); + let _: UiFinancialsResponse = client.receive().unwrap(); client.send(UiShutdownRequest {}); node.wait_for_exit(); } From d5f6744579e737fdae7676039c7e17c99e351b38 Mon Sep 17 00:00:00 2001 From: Bert Date: Tue, 15 Mar 2022 11:25:33 +0100 Subject: [PATCH 02/10] GH-236: adjustment in displayed output and command description --- masq/src/commands/financials_command.rs | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/masq/src/commands/financials_command.rs b/masq/src/commands/financials_command.rs index e0826ccc9..01d7211bc 100644 --- a/masq/src/commands/financials_command.rs +++ b/masq/src/commands/financials_command.rs @@ -9,13 +9,14 @@ use masq_lib::messages::{UiFinancialsRequest, UiFinancialsResponse}; use masq_lib::short_writeln; use std::fmt::Debug; +const FINANCIALS_SUBCOMMAND_ABOUT: &str = + "Displays financial statistics of this Node. Only valid if Node is already running."; + #[derive(Debug)] pub struct FinancialsCommand {} pub fn financials_subcommand() -> App<'static, 'static> { - SubCommand::with_name("financials").about( - "Displays financial data of the given MASQNode. Only valid if Node is already running.", - ) + SubCommand::with_name("financials").about(FINANCIALS_SUBCOMMAND_ABOUT) } impl Command for FinancialsCommand { @@ -28,7 +29,7 @@ impl Command for FinancialsCommand { let stdout = context.stdout(); dump_parameter_line( stdout, - "Total unpaid payable:", + "Total unpaid and pending payable:", &response.total_unpaid_payable.to_string(), ); dump_parameter_line( @@ -78,6 +79,14 @@ mod tests { use masq_lib::messages::{ToMessageBody, UiFinancialsResponse}; use std::sync::{Arc, Mutex}; + #[test] + fn constants_have_correct_values() { + assert_eq!( + FINANCIALS_SUBCOMMAND_ABOUT, + "Displays financial statistics of this Node. Only valid if Node is already running." + ); + } + #[test] fn testing_command_factory_here() { let factory = CommandFactoryReal::new(); @@ -125,10 +134,10 @@ mod tests { assert_eq!( stdout_arc.lock().unwrap().get_string(), "\ - Total unpaid payable: 116688\n\ - Total paid payable: 55555\n\ - Total unpaid receivable: 221144\n\ - Total paid receivable: 66555\n" + Total unpaid and pending payable: 116688\n\ + Total paid payable: 55555\n\ + Total unpaid receivable: 221144\n\ + Total paid receivable: 66555\n" ); assert_eq!(stderr_arc.lock().unwrap().get_string(), String::new()); } From 1d37d1b8d4e6ebeb4cc87cd69d8b0569c03df57c Mon Sep 17 00:00:00 2001 From: Bert Date: Tue, 15 Mar 2022 14:36:51 +0100 Subject: [PATCH 03/10] GH-236: eliminating Action's issue on Win - dns_utility --- dns_utility/tests/inspect_and_status_test_windows.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 From f8af1c6d222d3065e87b7df058566e4dc80dbaf4 Mon Sep 17 00:00:00 2001 From: Bert Date: Wed, 16 Mar 2022 18:58:16 +0100 Subject: [PATCH 04/10] GH-236: reformulation of description in the documentation and struct's component renaming --- USER-INTERFACE-INTERFACE.md | 52 +++++++++++-------- masq/src/commands/commands_common.rs | 1 + masq/src/commands/financials_command.rs | 6 +-- masq/src/communications/connection_manager.rs | 4 +- masq_lib/src/messages.rs | 4 +- node/src/accountant/mod.rs | 6 +-- 6 files changed, 40 insertions(+), 33 deletions(-) diff --git a/USER-INTERFACE-INTERFACE.md b/USER-INTERFACE-INTERFACE.md index d2fcc40d7..2625790ba 100644 --- a/USER-INTERFACE-INTERFACE.md +++ b/USER-INTERFACE-INTERFACE.md @@ -485,8 +485,8 @@ field will be null or absent. ##### Description: Requests financial statistics from the Node. -This will report back information about Node's performance, recorded services over time; mostly put as -some money value. +This will report back information about Node's performance, that is records of services having been run over time +(provided and also consumed); put as some kind of money value. #### `financials` ##### Direction: Response @@ -495,32 +495,38 @@ some money value. ``` "payload": { - "totalUnpaidPayable": - "totalPaidPayable": + "totalUnpaidAndPendingPayable": + "totalPaidPayable": "totalUnpaidReceivable": - "totalPaidReceivable": + "totalPaidReceivable": } ``` ##### Description: -Contains a financial statistics report from the Node. - -`totalUnpaidPayable` is a cumulative amount of Gwei from all accounts that were established on behalf of the Node's -creditors in the DB; referred as payable, that is a basis of our payments to make to other Nodes. The fact there is -an account in the DB's affected table may mean two situations, either the debt is still being build up to reach a level -when it begins to be desirable to pay, or it also may mean that there is already a pending transaction with a goal to -settle a qualified debt. However, an existence of the record clearly means the debt's settlement has not completed yet, -either way, and thus the blockchain definitely hasn't been modified accordingly. +Brings requested values of financial statistics of the running Node. + +`totalUnpaidAndPendingPayable` is a cumulative amount of Gwei from all accounts that were established in the DB on +behalf of the Node's creditors; referred as payable, that is the base of our payments to other Nodes. The fact there +is an account in the DB may mean two situations, either the debt is still bulking up to reach the level when it is +supposed to be settled as a qualified debt (with enough significance), or it also may mean a record with an attached +pending transaction with the goal to finally settle a qualified debt. That's why this values technically composed of +two money amounts from two different states of the debts. An existence of a record in this database table clearly +means the process of debt settlement has not completed yet either way and thus the blockchain certainly hasn't been +modified up to this time. -`totalPaidPayable`, unlike the previous, this is a sum of every paid (or settled) token amount that our Node has sent to -our creditors, and as well, the transactions were confirmed eventually. Tracked since the startup, figures in Gwei. - -`totalUnpaidReceivable`, this is similar to `totalUnpaidReceivables`, the values making a sum here come from the DB -table belonging to receivable; this table contains records of accounts of different Nodes that have drawn the Node's -services in the past. We take a track of the amount of "work", put in Gwei, for each and here we request the cumulative -active debt from all our debtors. - -`totalPaidReceivable`: this number of Gwei means a total of transactions we detected as already paid on our account -from our debtors since the Node's startup. +`totalPaidPayable`, unlike the previous, this is a sum of each paid (or settled) amount of the token that our +Node has sent to our creditors, and as well, implying that the transactions have been confirmed by now. Values in Gwei +tracked since the startup (should be changed later to really give all time values regardless Node's restarts). + +`totalUnpaidReceivable`, this is shallowly similar to `totalUnpaidAndPendingPayable`, the values making the sum here +come from the DB table put up for receivable; this table contains accounts of different Nodes that have drawn the Node's +services but so far without paying for that. We take track of the amount of work we gave, put in money, Gwei; +here we sum them up to get cumulative active debt from all our debtors. When a Node pays us to redeem its debt +the respective account takes a subtracting operation of the owed balance by the paid amount, this also leads to a decrease +in this total value to be displayed. + +`totalPaidReceivable`: this number of Gwei means a total of all transactions made on our account and that we detected +as confirmed on the blockchain. Currently, we compute the value since the startup only, but we plan to enhance it to work +even over multiple starts and shutdowns as an all-time total. #### `generateWallets` ##### Direction: Request diff --git a/masq/src/commands/commands_common.rs b/masq/src/commands/commands_common.rs index 797d89647..ceed89b44 100644 --- a/masq/src/commands/commands_common.rs +++ b/masq/src/commands/commands_common.rs @@ -122,6 +122,7 @@ mod tests { #[test] fn constants_have_correct_values() { assert_eq!(STANDARD_COMMAND_TIMEOUT_MILLIS, 1000); + assert_eq!(STANDARD_COLUMN_WIDTH, 33) } #[test] diff --git a/masq/src/commands/financials_command.rs b/masq/src/commands/financials_command.rs index 01d7211bc..b0f7b7908 100644 --- a/masq/src/commands/financials_command.rs +++ b/masq/src/commands/financials_command.rs @@ -30,7 +30,7 @@ impl Command for FinancialsCommand { dump_parameter_line( stdout, "Total unpaid and pending payable:", - &response.total_unpaid_payable.to_string(), + &response.total_unpaid_and_pending_payable.to_string(), ); dump_parameter_line( stdout, @@ -91,7 +91,7 @@ mod tests { fn testing_command_factory_here() { let factory = CommandFactoryReal::new(); let mut context = CommandContextMock::new().transact_result(Ok(UiFinancialsResponse { - total_unpaid_payable: 0, + total_unpaid_and_pending_payable: 0, total_paid_payable: 1111, total_unpaid_receivable: 2222, total_paid_receivable: 3333, @@ -108,7 +108,7 @@ mod tests { fn financials_command_happy_path() { let transact_params_arc = Arc::new(Mutex::new(vec![])); let expected_response = UiFinancialsResponse { - total_unpaid_payable: 116688, + total_unpaid_and_pending_payable: 116688, total_paid_payable: 55555, total_unpaid_receivable: 221144, total_paid_receivable: 66555, diff --git a/masq/src/communications/connection_manager.rs b/masq/src/communications/connection_manager.rs index 56316095a..f7421391a 100644 --- a/masq/src/communications/connection_manager.rs +++ b/masq/src/communications/connection_manager.rs @@ -1170,7 +1170,7 @@ mod tests { let node_port = find_free_port(); let node_server = MockWebSocketsServer::new(node_port).queue_response( UiFinancialsResponse { - total_unpaid_payable: 10, + total_unpaid_and_pending_payable: 10, total_paid_payable: 22, total_unpaid_receivable: 29, total_paid_receivable: 32, @@ -1204,7 +1204,7 @@ mod tests { assert_eq!( response, UiFinancialsResponse { - total_unpaid_payable: 10, + total_unpaid_and_pending_payable: 10, total_paid_payable: 22, total_unpaid_receivable: 29, total_paid_receivable: 32 diff --git a/masq_lib/src/messages.rs b/masq_lib/src/messages.rs index b2ffa1005..bb6fdc2c2 100644 --- a/masq_lib/src/messages.rs +++ b/masq_lib/src/messages.rs @@ -537,8 +537,8 @@ conversation_message!(UiFinancialsRequest, "financials"); #[derive(Serialize, Deserialize, Debug, PartialEq)] pub struct UiFinancialsResponse { - #[serde(rename = "totalUnpaidPayable")] - pub total_unpaid_payable: i64, + #[serde(rename = "totalUnpaidAndPendingPayable")] + pub total_unpaid_and_pending_payable: i64, #[serde(rename = "totalPaidPayable")] pub total_paid_payable: u64, #[serde(rename = "totalUnpaidReceivable")] diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index 75c13df52..c937c6056 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -849,12 +849,12 @@ impl Accountant { } fn handle_financials(&mut self, client_id: u64, context_id: u64) { - let total_unpaid_payable = self.payable_dao.total(); + let total_unpaid_and_pending_payable = self.payable_dao.total(); let total_paid_payable = self.financial_statistics.total_paid_payable; let total_unpaid_receivable = self.receivable_dao.total(); let total_paid_receivable = self.financial_statistics.total_paid_receivable; let body = UiFinancialsResponse { - total_unpaid_payable, + total_unpaid_and_pending_payable, total_paid_payable, total_unpaid_receivable, total_paid_receivable, @@ -1311,7 +1311,7 @@ mod tests { assert_eq!( parsed_payload, UiFinancialsResponse { - total_unpaid_payable: 23456789, + total_unpaid_and_pending_payable: 23456789, total_paid_payable: 123456, total_unpaid_receivable: 98765432, total_paid_receivable: 334455 From f109cd96af3ef870ebbd4858d481c77738f26e14 Mon Sep 17 00:00:00 2001 From: Bert Date: Mon, 21 Mar 2022 22:33:52 +0100 Subject: [PATCH 05/10] GH-236: merging master in --- USER-INTERFACE-INTERFACE.md | 108 +- dns_utility/src/win_dns_modifier.rs | 8 +- masq/src/commands/configuration_command.rs | 165 +- masq/src/commands/setup_command.rs | 42 +- masq/src/communications/broadcast_handler.rs | 10 +- .../startup_shutdown_tests_integration.rs | 4 +- masq_lib/src/constants.rs | 10 +- masq_lib/src/messages.rs | 46 +- masq_lib/src/multi_config.rs | 34 +- masq_lib/src/shared_schema.rs | 224 +- .../src/masq_real_node.rs | 79 +- .../tests/verify_bill_payment.rs | 45 +- node/Cargo.lock | 63 +- node/Cargo.toml | 1 + node/src/accountant/mod.rs | 754 +++--- node/src/accountant/receivable_dao.rs | 121 +- node/src/accountant/test_utils.rs | 31 +- node/src/accountant/tools.rs | 72 +- node/src/actor_system_factory.rs | 181 +- node/src/blockchain/blockchain_bridge.rs | 8 +- node/src/blockchain/blockchain_interface.rs | 2 +- node/src/bootstrapper.rs | 298 +-- node/src/daemon/daemon_initializer.rs | 84 +- node/src/daemon/setup_reporter.rs | 831 ++++-- node/src/database/config_dumper.rs | 28 +- node/src/database/db_initializer.rs | 53 +- node/src/database/db_migrations.rs | 108 +- node/src/db_config/config_dao_null.rs | 100 +- .../src/db_config/persistent_configuration.rs | 577 ++-- node/src/db_config/typed_config_layer.rs | 16 + node/src/dispatcher.rs | 2 +- node/src/entry_dns/dns_socket_server.rs | 2 +- node/src/hopper/mod.rs | 2 +- node/src/neighborhood/mod.rs | 14 +- .../src/neighborhood/neighborhood_database.rs | 2 +- node/src/neighborhood/node_record.rs | 7 +- node/src/node_configurator/configurator.rs | 145 +- node/src/node_configurator/mod.rs | 3 + .../node_configurator_initialization.rs | 1 + .../node_configurator_standard.rs | 2199 ++------------- .../unprivileged_parse_args_configuration.rs | 2371 +++++++++++++++++ node/src/proxy_client/mod.rs | 2 +- node/src/proxy_server/mod.rs | 61 +- node/src/run_modes_factories.rs | 116 +- node/src/server_initializer.rs | 17 +- node/src/stream_handler_pool.rs | 7 +- node/src/sub_lib/accountant.rs | 77 +- node/src/sub_lib/combined_parameters.rs | 598 +++++ node/src/sub_lib/mod.rs | 1 + node/src/sub_lib/neighborhood.rs | 70 +- node/src/sub_lib/socket_server.rs | 3 +- node/src/sub_lib/utils.rs | 17 +- node/src/test_utils/database_utils.rs | 7 +- node/src/test_utils/mod.rs | 90 +- .../src/test_utils/neighborhood_test_utils.rs | 6 +- .../persistent_configuration_mock.rs | 195 +- 56 files changed, 6604 insertions(+), 3514 deletions(-) create mode 100644 node/src/node_configurator/unprivileged_parse_args_configuration.rs create mode 100644 node/src/sub_lib/combined_parameters.rs diff --git a/USER-INTERFACE-INTERFACE.md b/USER-INTERFACE-INTERFACE.md index 2625790ba..6ff595d64 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/masq/src/commands/configuration_command.rs b/masq/src/commands/configuration_command.rs index 7b6e60123..55b532f8f 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) { dump_parameter_line(stream, "NAME", "VALUE"); dump_parameter_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]) { @@ -148,6 +185,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)] @@ -159,7 +209,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}; @@ -252,7 +304,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) @@ -279,7 +350,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\ @@ -293,9 +365,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(), ""); } @@ -315,7 +402,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) @@ -340,21 +446,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 bb6fdc2c2..eeb2d779f 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 c937c6056..c745229f4 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, PaidReceivable}; @@ -29,7 +29,7 @@ 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, FinancialStatistics}; +use crate::sub_lib::accountant::{AccountantConfig, FinancialStatistics, 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::UiFinancialsResponse; @@ -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, @@ -116,6 +79,7 @@ pub struct Accountant { report_new_payments_sub: Option>, report_sent_payments_sub: Option>, ui_message_sub: Option>, + payable_threshold_tools: Box, logger: Logger, } @@ -161,30 +125,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) } } @@ -192,14 +137,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) } } @@ -207,14 +145,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) } } @@ -366,7 +297,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(), @@ -385,6 +319,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"), } } @@ -409,6 +344,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"); @@ -420,7 +360,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, @@ -430,7 +370,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 @@ -447,7 +387,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); @@ -461,7 +401,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); @@ -532,26 +472,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 { @@ -559,16 +507,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, @@ -667,18 +605,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, @@ -693,7 +635,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() @@ -701,8 +643,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, @@ -987,7 +930,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)); @@ -999,9 +942,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) @@ -1077,7 +1020,7 @@ impl Accountant { 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), ) @@ -1089,7 +1032,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, }, @@ -1149,6 +1092,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::*; @@ -1172,16 +1144,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}; @@ -1200,29 +1175,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 = @@ -1270,12 +1307,7 @@ mod tests { let system = System::new("test"); let mut 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) @@ -1410,12 +1442,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) @@ -1559,17 +1586,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, }, @@ -1578,17 +1609,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); @@ -1631,19 +1657,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); @@ -1688,12 +1709,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![])) @@ -1729,9 +1745,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"), @@ -1796,10 +1815,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] @@ -1813,10 +1832,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(), ); @@ -1847,9 +1869,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), ); @@ -1887,7 +1909,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 @@ -1915,9 +1937,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"), @@ -1948,7 +1973,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), ); @@ -1997,10 +2022,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"), ); @@ -2008,8 +2036,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() @@ -2027,9 +2057,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(); @@ -2066,41 +2096,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, }, @@ -2120,6 +2151,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(); @@ -2134,9 +2166,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"), @@ -2146,18 +2181,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, }, @@ -2176,7 +2211,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); @@ -2210,15 +2245,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); @@ -2246,12 +2274,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)); @@ -2312,12 +2346,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"); @@ -2354,22 +2383,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(); @@ -2409,12 +2432,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"), ); @@ -2462,12 +2480,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![])); @@ -2513,12 +2526,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![])); @@ -2565,12 +2573,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"), ); @@ -2614,12 +2617,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![])); @@ -2660,12 +2658,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![])); @@ -2714,12 +2707,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"), ); @@ -2766,15 +2754,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() @@ -2828,15 +2809,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![]) @@ -2882,12 +2856,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"), ); @@ -2930,15 +2899,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![]) @@ -3312,6 +3274,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(); @@ -3361,26 +3324,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\ @@ -3389,6 +3366,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(); @@ -3414,14 +3471,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(); @@ -3438,8 +3498,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))) @@ -3461,14 +3521,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 @@ -3482,17 +3542,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, @@ -3500,7 +3564,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, @@ -3556,6 +3620,7 @@ 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(())); @@ -3571,16 +3636,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(); @@ -3608,7 +3673,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 @@ -3653,6 +3718,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(); @@ -3718,7 +3784,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)); @@ -4026,21 +4092,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 beec71a4c..04edac4a0 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::PaidReceivable; 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 29fa9a3f9..effd1dc74 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::PaidReceivable; @@ -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 0b1228e17..ec77e2e86 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 2a30cbfff..b132912de 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 026d1cffb..ff68b8970 100644 --- a/node/src/sub_lib/accountant.rs +++ b/node/src/sub_lib/accountant.rs @@ -18,11 +18,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, } @@ -87,11 +134,15 @@ pub struct FinancialStatistics { #[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() { @@ -99,7 +150,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>, From d2b4c6332a37eb72d6566142775c29260963de22 Mon Sep 17 00:00:00 2001 From: Bert Date: Tue, 29 Mar 2022 17:21:14 +0200 Subject: [PATCH 06/10] GH-236: addressing issues in the first review (not ended fully) --- USER-INTERFACE-INTERFACE.md | 59 +++++++++++++------------ masq/src/commands/financials_command.rs | 19 ++++---- node/src/accountant/mod.rs | 6 +-- 3 files changed, 43 insertions(+), 41 deletions(-) diff --git a/USER-INTERFACE-INTERFACE.md b/USER-INTERFACE-INTERFACE.md index 6ff595d64..1d6d27bc8 100644 --- a/USER-INTERFACE-INTERFACE.md +++ b/USER-INTERFACE-INTERFACE.md @@ -569,8 +569,9 @@ field will be null or absent. ##### Description: Requests financial statistics from the Node. -This will report back information about Node's performance, that is records of services having been run over time -(provided and also consumed); put as some kind of money value. +This request will report back information about a Node's historical financial operations. This will include both +services ordered from other Nodes and services provided for other Nodes, represented as monetary values owed and +paid to and from this Node's wallets. #### `financials` ##### Direction: Response @@ -580,37 +581,37 @@ This will report back information about Node's performance, that is records of s "payload": { "totalUnpaidAndPendingPayable": - "totalPaidPayable": + "totalPaidPayable": "totalUnpaidReceivable": - "totalPaidReceivable": + "totalPaidReceivable": } ``` ##### Description: -Brings requested values of financial statistics of the running Node. - -`totalUnpaidAndPendingPayable` is a cumulative amount of Gwei from all accounts that were established in the DB on -behalf of the Node's creditors; referred as payable, that is the base of our payments to other Nodes. The fact there -is an account in the DB may mean two situations, either the debt is still bulking up to reach the level when it is -supposed to be settled as a qualified debt (with enough significance), or it also may mean a record with an attached -pending transaction with the goal to finally settle a qualified debt. That's why this values technically composed of -two money amounts from two different states of the debts. An existence of a record in this database table clearly -means the process of debt settlement has not completed yet either way and thus the blockchain certainly hasn't been -modified up to this time. - -`totalPaidPayable`, unlike the previous, this is a sum of each paid (or settled) amount of the token that our -Node has sent to our creditors, and as well, implying that the transactions have been confirmed by now. Values in Gwei -tracked since the startup (should be changed later to really give all time values regardless Node's restarts). - -`totalUnpaidReceivable`, this is shallowly similar to `totalUnpaidAndPendingPayable`, the values making the sum here -come from the DB table put up for receivable; this table contains accounts of different Nodes that have drawn the Node's -services but so far without paying for that. We take track of the amount of work we gave, put in money, Gwei; -here we sum them up to get cumulative active debt from all our debtors. When a Node pays us to redeem its debt -the respective account takes a subtracting operation of the owed balance by the paid amount, this also leads to a decrease -in this total value to be displayed. - -`totalPaidReceivable`: this number of Gwei means a total of all transactions made on our account and that we detected -as confirmed on the blockchain. Currently, we compute the value since the startup only, but we plan to enhance it to work -even over multiple starts and shutdowns as an all-time total. +Contains requested values of financial statistics of the running Node. + +`totalUnpaidAndPendingPayable` is a cumulative amount of Gwei referring to the `payable` and `pending_payable` tables +in the database, owed money over possibly several accounts set up for the Node's creditors, either with a payment being +underway or waiting until it full qualify and comes to be paid. This is why these values technically compose of +two money amounts derived from two different books of debts. Because the sub amount representing pending payable can +still potentially end up as a failure we cannot include it into an amount of paid payable yet, not until the transaction +is added to the blockchain, and we can confirm that on our end. While we pay our debts, this total decreases in +its value but raises other time. + +`totalPaidPayable`, unlike the previous, this is a sum of paid amounts of the token that our Node has sent to our +creditors and the transactions must have been confirmed by now. This parameter displays as a value in Gwei +tracked since the startup (we plan a change to keep track of all time values regardless the Node's nonoperational +periods). + +`totalUnpaidReceivable`, this is shallowly similar to `totalUnpaidAndPendingPayable`, the values making the sum +come from the `receivable` table in the database. This table collects accounts for Nodes that have drawn our Node's +services but haven't paid for them yet. The value is represented in Gwei. When a Node pays us to redeem its debt +its respective account faces a subtracting operation of the payment out of the cumulative owed balance, this also leads +to a decrease in this total to be displayed. + +`totalPaidReceivable`: this number of Gwei means a total of all transactions made on our account to other Nodes +and that we've already detected as confirmed on the blockchain. Currently, we compute the value since the startup only +and it gets zeroed with every new startup (we plan a change to keep track of all time values regardless the Node's +nonoperational periods). #### `generateWallets` ##### Direction: Request diff --git a/masq/src/commands/financials_command.rs b/masq/src/commands/financials_command.rs index b0f7b7908..10c6a1221 100644 --- a/masq/src/commands/financials_command.rs +++ b/masq/src/commands/financials_command.rs @@ -27,24 +27,25 @@ impl Command for FinancialsCommand { match output { Ok(response) => { let stdout = context.stdout(); + short_writeln!(stdout, "Financial status totals in Gwei\n"); dump_parameter_line( stdout, - "Total unpaid and pending payable:", + "Unpaid and pending payable:", &response.total_unpaid_and_pending_payable.to_string(), ); dump_parameter_line( stdout, - "Total paid payable:", + "Paid payable:", &response.total_paid_payable.to_string(), ); dump_parameter_line( stdout, - "Total unpaid receivable:", + "Unpaid receivable:", &response.total_unpaid_receivable.to_string(), ); dump_parameter_line( stdout, - "Total paid receivable:", + "Paid receivable:", &response.total_paid_receivable.to_string(), ); Ok(()) @@ -134,10 +135,12 @@ mod tests { assert_eq!( stdout_arc.lock().unwrap().get_string(), "\ - Total unpaid and pending payable: 116688\n\ - Total paid payable: 55555\n\ - Total unpaid receivable: 221144\n\ - Total paid receivable: 66555\n" + Financial status totals in Gwei\n\ + \n\ + Unpaid and pending payable: 116688\n\ + Paid payable: 55555\n\ + Unpaid receivable: 221144\n\ + Paid receivable: 66555\n" ); assert_eq!(stderr_arc.lock().unwrap().get_string(), String::new()); } diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index c745229f4..244b88f0c 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -1352,7 +1352,7 @@ mod tests { } #[test] - fn total_paid_payable_for_financial_statistics_starts_at_zero_and_is_repeatedly_updated() { + fn total_paid_payable_starts_at_zero_and_gets_increasingly_bigger() { let transaction_confirmed_params_arc = Arc::new(Mutex::new(vec![])); let fingerprint = PendingPayableFingerprint { rowid_opt: Some(5), @@ -1374,7 +1374,6 @@ mod tests { .payable_dao(payable_dao) .build(); assert_eq!(subject.financial_statistics.total_paid_payable, 0); - //to demonstrate that we will perform addition and not overwrite the value subject.financial_statistics.total_paid_payable += 1111; let msg = ConfirmPendingTransaction { pending_payable_fingerprint: fingerprint.clone(), @@ -1388,7 +1387,7 @@ mod tests { } #[test] - fn total_paid_receivable_for_financial_statistics_starts_at_zero_and_is_repeatedly_updated() { + fn total_paid_receivable_starts_at_zero_and_gets_increasingly_bigger() { let more_money_received_params_arc = Arc::new(Mutex::new(vec![])); let receivable_dao = ReceivableDaoMock::new() .more_money_received_parameters(&more_money_received_params_arc) @@ -1397,7 +1396,6 @@ mod tests { .receivable_dao(receivable_dao) .build(); assert_eq!(subject.financial_statistics.total_paid_receivable, 0); - //to demonstrate that we will perform addition and not overwrite the value subject.financial_statistics.total_paid_receivable += 2222; let receivables = vec![ PaidReceivable { From 2fa9188feb7e900d9c4dbf55126cc9f0261c6b75 Mon Sep 17 00:00:00 2001 From: Bert Date: Thu, 31 Mar 2022 12:14:34 +0200 Subject: [PATCH 07/10] GH-236: finishing review one --- masq_lib/src/messages.rs | 2 +- node/src/accountant/mod.rs | 308 ++++++++++++++++++----------- node/src/accountant/payable_dao.rs | 6 +- node/src/accountant/test_utils.rs | 6 +- node/src/sub_lib/utils.rs | 10 +- node/tests/ui_gateway_test.rs | 74 ++++--- 6 files changed, 247 insertions(+), 159 deletions(-) diff --git a/masq_lib/src/messages.rs b/masq_lib/src/messages.rs index eeb2d779f..93ab79a2b 100644 --- a/masq_lib/src/messages.rs +++ b/masq_lib/src/messages.rs @@ -582,7 +582,7 @@ conversation_message!(UiFinancialsRequest, "financials"); #[derive(Serialize, Deserialize, Debug, PartialEq)] pub struct UiFinancialsResponse { #[serde(rename = "totalUnpaidAndPendingPayable")] - pub total_unpaid_and_pending_payable: i64, + pub total_unpaid_and_pending_payable: u64, #[serde(rename = "totalPaidPayable")] pub total_paid_payable: u64, #[serde(rename = "totalUnpaidReceivable")] diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index 244b88f0c..d44124bc6 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -51,6 +51,8 @@ use masq_lib::ui_gateway::{NodeFromUiMessage, NodeToUiMessage}; use masq_lib::utils::{plus, ExpectValue}; use payable_dao::PayableDao; use receivable_dao::ReceivableDao; +#[cfg(test)] +use std::any::Any; use std::default::Default; use std::ops::Add; use std::path::Path; @@ -319,7 +321,7 @@ impl Accountant { report_new_payments_sub: None, report_sent_payments_sub: None, ui_message_sub: None, - payable_threshold_tools: Box::new(PayableExceedThresholdToolsReal {}), + payable_threshold_tools: Box::new(PayableExceedThresholdToolsReal::default()), logger: Logger::new("Accountant"), } } @@ -1097,8 +1099,10 @@ 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; + as_any_dcl!(); } +#[derive(Default, PartialEq, Debug)] struct PayableExceedThresholdToolsReal {} impl PayableExceedThresholdTools for PayableExceedThresholdToolsReal { @@ -1119,6 +1123,7 @@ impl PayableExceedThresholdTools for PayableExceedThresholdToolsReal { - m * payment_thresholds.maturity_threshold_sec as f64; m * x as f64 + b } + as_any_impl!(); } #[cfg(test)] @@ -1134,7 +1139,7 @@ mod tests { ReceivableDaoFactoryMock, ReceivableDaoMock, }; use crate::accountant::test_utils::{AccountantBuilder, BannedDaoMock}; - use crate::accountant::tools::accountant_tools::NullScanner; + use crate::accountant::tools::accountant_tools::{NullScanner, ReceivablesScanner}; use crate::blockchain::blockchain_bridge::BlockchainBridge; use crate::blockchain::blockchain_interface::BlockchainError; use crate::blockchain::blockchain_interface::PaidReceivable; @@ -1148,6 +1153,7 @@ mod tests { ReportRoutingServiceConsumedMessage, ScanIntervals, DEFAULT_PAYMENT_THRESHOLDS, }; use crate::sub_lib::blockchain_bridge::ReportAccountsPayable; + use crate::sub_lib::utils::{NotifyHandleReal, NotifyLaterHandleReal}; use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMock; use crate::test_utils::recorder::make_recorder; use crate::test_utils::recorder::peer_actors_builder; @@ -1301,125 +1307,69 @@ mod tests { } #[test] - fn financials_request_produces_financials_response() { - let payable_dao = PayableDaoMock::new().total_result(23456789); - let receivable_dao = ReceivableDaoMock::new().total_result(98765432); - let system = System::new("test"); - let mut subject = AccountantBuilder::default() - .bootstrapper_config(bc_from_ac_plus_earning_wallet( - make_populated_accountant_config_with_defaults(), - make_wallet("some_wallet_address"), - )) - .receivable_dao(receivable_dao) - .payable_dao(payable_dao) - .build(); - subject.financial_statistics.total_paid_payable = 123456; - subject.financial_statistics.total_paid_receivable = 334455; - let (ui_gateway, _, ui_gateway_recording_arc) = make_recorder(); - let subject_addr = subject.start(); - let peer_actors = peer_actors_builder().ui_gateway(ui_gateway).build(); - subject_addr.try_send(BindMessage { peer_actors }).unwrap(); - let ui_message = NodeFromUiMessage { - client_id: 1234, - body: MessageBody { - opcode: "financials".to_string(), - path: Conversation(2222), - payload: Ok("{}".to_string()), - }, - }; - - subject_addr.try_send(ui_message).unwrap(); - - System::current().stop(); - system.run(); - let ui_gateway_recording = ui_gateway_recording_arc.lock().unwrap(); - let response = ui_gateway_recording.get_record::(0); - assert_eq!(response.target, MessageTarget::ClientId(1234)); - assert_eq!(response.body.opcode, "financials".to_string()); - assert_eq!(response.body.path, Conversation(2222)); - let parsed_payload = - serde_json::from_str::(&response.body.payload.as_ref().unwrap()) - .unwrap(); - assert_eq!( - parsed_payload, - UiFinancialsResponse { - total_unpaid_and_pending_payable: 23456789, - total_paid_payable: 123456, - total_unpaid_receivable: 98765432, - total_paid_receivable: 334455 - } + fn accountant_have_proper_defaulted_values() { + let mut bootstrapper_config = BootstrapperConfig::new(); + bootstrapper_config.accountant_config_opt = + Some(make_populated_accountant_config_with_defaults()); + let payable_dao_factory = Box::new(PayableDaoFactoryMock::new(PayableDaoMock::new())); + let receivable_dao_factory = + Box::new(ReceivableDaoFactoryMock::new(ReceivableDaoMock::new())); + let pending_payable_dao_factory = Box::new(PendingPayableDaoFactoryMock::new( + PendingPayableDaoMock::default(), + )); + let banned_dao_factory = Box::new(BannedDaoFactoryMock::new(BannedDaoMock::new())); + let config_dao_factory = Box::new(ConfigDaoFactoryMock::new(ConfigDaoMock::new())); + + let result = Accountant::new( + &bootstrapper_config, + payable_dao_factory, + receivable_dao_factory, + pending_payable_dao_factory, + banned_dao_factory, + config_dao_factory, ); - } - #[test] - fn total_paid_payable_starts_at_zero_and_gets_increasingly_bigger() { - let transaction_confirmed_params_arc = Arc::new(Mutex::new(vec![])); - let fingerprint = PendingPayableFingerprint { - rowid_opt: Some(5), - timestamp: from_time_t(189_999_888), - hash: H256::from_uint(&U256::from(56789)), - attempt_opt: Some(1), - amount: 5478, - process_error: None, - }; - let mut pending_payable_dao = - PendingPayableDaoMock::default().delete_fingerprint_result(Ok(())); - let payable_dao = PayableDaoMock::default() - .transaction_confirmed_params(&transaction_confirmed_params_arc) - .transaction_confirmed_result(Ok(())) - .transaction_confirmed_result(Ok(())); - pending_payable_dao.have_return_all_fingerprints_shut_down_the_system = true; - let mut subject = AccountantBuilder::default() - .pending_payable_dao(pending_payable_dao) - .payable_dao(payable_dao) - .build(); - assert_eq!(subject.financial_statistics.total_paid_payable, 0); - subject.financial_statistics.total_paid_payable += 1111; - let msg = ConfirmPendingTransaction { - pending_payable_fingerprint: fingerprint.clone(), - }; - - subject.handle_confirm_pending_transaction(msg); - - assert_eq!(subject.financial_statistics.total_paid_payable, 1111 + 5478); - let transaction_confirmed_params = transaction_confirmed_params_arc.lock().unwrap(); - assert_eq!(*transaction_confirmed_params, vec![fingerprint]) - } - - #[test] - fn total_paid_receivable_starts_at_zero_and_gets_increasingly_bigger() { - let more_money_received_params_arc = Arc::new(Mutex::new(vec![])); - let receivable_dao = ReceivableDaoMock::new() - .more_money_received_parameters(&more_money_received_params_arc) - .more_money_receivable_result(Ok(())); - let mut subject = AccountantBuilder::default() - .receivable_dao(receivable_dao) - .build(); - assert_eq!(subject.financial_statistics.total_paid_receivable, 0); - subject.financial_statistics.total_paid_receivable += 2222; - let receivables = vec![ - PaidReceivable { - block_number: 4578910, - from: make_wallet("wallet_1"), - gwei_amount: 45780, - }, - PaidReceivable { - block_number: 4569898, - from: make_wallet("wallet_2"), - gwei_amount: 33345, - }, - ]; - - subject.handle_received_payments(ReceivedPayments { - payments: receivables.clone(), - }); - - assert_eq!( - subject.financial_statistics.total_paid_receivable, - 2222 + 45780 + 33345 - ); - let more_money_received_params = more_money_received_params_arc.lock().unwrap(); - assert_eq!(*more_money_received_params, vec![receivables]); + let transaction_confirmation_tools = result.tools; + transaction_confirmation_tools + .notify_confirm_transaction + .as_any() + .downcast_ref::>() + .unwrap(); + transaction_confirmation_tools + .notify_cancel_failed_transaction + .as_any() + .downcast_ref::>() + .unwrap(); + transaction_confirmation_tools + .notify_later_scan_for_pending_payable + .as_any() + .downcast_ref::>() + .unwrap(); + transaction_confirmation_tools + .notify_later_scan_for_payable + .as_any() + .downcast_ref::>() + .unwrap(); + transaction_confirmation_tools + .notify_later_scan_for_receivable + .as_any() + .downcast_ref::>() + .unwrap(); + //testing presence of real scanners, there is a different test covering them all + result + .scanners + .receivables + .as_any() + .downcast_ref::() + .unwrap(); + result + .payable_threshold_tools + .as_any() + .downcast_ref::() + .unwrap(); + assert_eq!(result.crashable, false); + assert_eq!(result.financial_statistics.total_paid_receivable, 0); + assert_eq!(result.financial_statistics.total_paid_payable, 0); } #[test] @@ -4089,6 +4039,126 @@ mod tests { let _ = subject.update_payable_fingerprint(transaction_id); } + #[test] + fn financials_request_produces_financials_response() { + let payable_dao = PayableDaoMock::new().total_result(23456789); + let receivable_dao = ReceivableDaoMock::new().total_result(98765432); + let system = System::new("test"); + let mut subject = AccountantBuilder::default() + .bootstrapper_config(bc_from_ac_plus_earning_wallet( + make_populated_accountant_config_with_defaults(), + make_wallet("some_wallet_address"), + )) + .receivable_dao(receivable_dao) + .payable_dao(payable_dao) + .build(); + subject.financial_statistics.total_paid_payable = 123456; + subject.financial_statistics.total_paid_receivable = 334455; + let (ui_gateway, _, ui_gateway_recording_arc) = make_recorder(); + let subject_addr = subject.start(); + let peer_actors = peer_actors_builder().ui_gateway(ui_gateway).build(); + subject_addr.try_send(BindMessage { peer_actors }).unwrap(); + let ui_message = NodeFromUiMessage { + client_id: 1234, + body: MessageBody { + opcode: "financials".to_string(), + path: Conversation(2222), + payload: Ok("{}".to_string()), + }, + }; + + subject_addr.try_send(ui_message).unwrap(); + + System::current().stop(); + system.run(); + let ui_gateway_recording = ui_gateway_recording_arc.lock().unwrap(); + let response = ui_gateway_recording.get_record::(0); + assert_eq!(response.target, MessageTarget::ClientId(1234)); + assert_eq!(response.body.opcode, "financials".to_string()); + assert_eq!(response.body.path, Conversation(2222)); + let parsed_payload = + serde_json::from_str::(&response.body.payload.as_ref().unwrap()) + .unwrap(); + assert_eq!( + parsed_payload, + UiFinancialsResponse { + total_unpaid_and_pending_payable: 23456789, + total_paid_payable: 123456, + total_unpaid_receivable: 98765432, + total_paid_receivable: 334455 + } + ); + } + + #[test] + fn total_paid_payable_starts_at_zero_and_gets_increasingly_bigger() { + let transaction_confirmed_params_arc = Arc::new(Mutex::new(vec![])); + let fingerprint = PendingPayableFingerprint { + rowid_opt: Some(5), + timestamp: from_time_t(189_999_888), + hash: H256::from_uint(&U256::from(56789)), + attempt_opt: Some(1), + amount: 5478, + process_error: None, + }; + let mut pending_payable_dao = + PendingPayableDaoMock::default().delete_fingerprint_result(Ok(())); + let payable_dao = PayableDaoMock::default() + .transaction_confirmed_params(&transaction_confirmed_params_arc) + .transaction_confirmed_result(Ok(())) + .transaction_confirmed_result(Ok(())); + pending_payable_dao.have_return_all_fingerprints_shut_down_the_system = true; + let mut subject = AccountantBuilder::default() + .pending_payable_dao(pending_payable_dao) + .payable_dao(payable_dao) + .build(); + subject.financial_statistics.total_paid_payable += 1111; + let msg = ConfirmPendingTransaction { + pending_payable_fingerprint: fingerprint.clone(), + }; + + subject.handle_confirm_pending_transaction(msg); + + assert_eq!(subject.financial_statistics.total_paid_payable, 1111 + 5478); + let transaction_confirmed_params = transaction_confirmed_params_arc.lock().unwrap(); + assert_eq!(*transaction_confirmed_params, vec![fingerprint]) + } + + #[test] + fn total_paid_receivable_starts_at_zero_and_gets_increasingly_bigger() { + let more_money_received_params_arc = Arc::new(Mutex::new(vec![])); + let receivable_dao = ReceivableDaoMock::new() + .more_money_received_parameters(&more_money_received_params_arc) + .more_money_receivable_result(Ok(())); + let mut subject = AccountantBuilder::default() + .receivable_dao(receivable_dao) + .build(); + subject.financial_statistics.total_paid_receivable += 2222; + let receivables = vec![ + PaidReceivable { + block_number: 4578910, + from: make_wallet("wallet_1"), + gwei_amount: 45780, + }, + PaidReceivable { + block_number: 4569898, + from: make_wallet("wallet_2"), + gwei_amount: 33345, + }, + ]; + + subject.handle_received_payments(ReceivedPayments { + payments: receivables.clone(), + }); + + assert_eq!( + subject.financial_statistics.total_paid_receivable, + 2222 + 45780 + 33345 + ); + let more_money_received_params = more_money_received_params_arc.lock().unwrap(); + assert_eq!(*more_money_received_params, vec![receivables]); + } + #[test] fn unsigned_to_signed_handles_zero() { let result = unsigned_to_signed(0u64); diff --git a/node/src/accountant/payable_dao.rs b/node/src/accountant/payable_dao.rs index a1a26faad..81ee0b9e3 100644 --- a/node/src/accountant/payable_dao.rs +++ b/node/src/accountant/payable_dao.rs @@ -67,7 +67,7 @@ pub trait PayableDao: Debug + Send { fn top_records(&self, minimum_amount: u64, maximum_age: u64) -> Vec; - fn total(&self) -> i64; + fn total(&self) -> u64; } pub trait PayableDaoFactory { @@ -228,13 +228,13 @@ impl PayableDao for PayableDaoReal { .collect() } - fn total(&self) -> i64 { + fn total(&self) -> u64 { let mut stmt = self .conn .prepare("select sum(balance) from payable") .expect("Internal error"); match stmt.query_row([], |row| { - let total_balance_result: Result = row.get(0); + let total_balance_result: Result = row.get(0); match total_balance_result { Ok(total_balance) => Ok(total_balance), Err(e) diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs index effd1dc74..e9aefae55 100644 --- a/node/src/accountant/test_utils.rs +++ b/node/src/accountant/test_utils.rs @@ -290,7 +290,7 @@ pub struct PayableDaoMock { transaction_canceled_results: RefCell>>, top_records_parameters: Arc>>, top_records_results: RefCell>>, - total_results: RefCell>, + total_results: RefCell>, pub have_non_pending_payables_shut_down_the_system: bool, } @@ -347,7 +347,7 @@ impl PayableDao for PayableDaoMock { self.top_records_results.borrow_mut().remove(0) } - fn total(&self) -> i64 { + fn total(&self) -> u64 { self.total_results.borrow_mut().remove(0) } } @@ -431,7 +431,7 @@ impl PayableDaoMock { self } - pub fn total_result(self, result: i64) -> Self { + pub fn total_result(self, result: u64) -> Self { self.total_results.borrow_mut().push(result); self } diff --git a/node/src/sub_lib/utils.rs b/node/src/sub_lib/utils.rs index fc6625205..3de4920d8 100644 --- a/node/src/sub_lib/utils.rs +++ b/node/src/sub_lib/utils.rs @@ -8,6 +8,8 @@ use masq_lib::multi_config::{MultiConfig, VirtualCommandLine}; use masq_lib::shared_schema::ConfiguratorError; use masq_lib::ui_gateway::NodeFromUiMessage; use masq_lib::utils::type_name_of; +#[cfg(test)] +use std::any::Any; use std::io::ErrorKind; use std::marker::PhantomData; use std::time::{Duration, SystemTime, UNIX_EPOCH}; @@ -144,6 +146,7 @@ pub trait NotifyLaterHandle { interval: Duration, closure: Box SpawnHandle + 'a>, ) -> SpawnHandle; + as_any_dcl!(); } pub struct NotifyLaterHandleReal { @@ -158,7 +161,7 @@ impl Default for Box> { } } -impl NotifyLaterHandle for NotifyLaterHandleReal { +impl NotifyLaterHandle for NotifyLaterHandleReal { fn notify_later<'a>( &'a self, msg: T, @@ -167,10 +170,12 @@ impl NotifyLaterHandle for NotifyLaterHandleReal { ) -> SpawnHandle { closure(msg, interval) } + as_any_impl!(); } pub trait NotifyHandle { fn notify<'a>(&'a self, msg: T, closure: Box); + as_any_dcl!(); } impl Default for Box> { @@ -185,10 +190,11 @@ pub struct NotifyHandleReal { phantom: PhantomData, } -impl NotifyHandle for NotifyHandleReal { +impl NotifyHandle for NotifyHandleReal { fn notify<'a>(&'a self, msg: T, mut closure: Box) { closure(msg) } + as_any_impl!(); } #[cfg(test)] diff --git a/node/tests/ui_gateway_test.rs b/node/tests/ui_gateway_test.rs index e655ced84..aacb7729f 100644 --- a/node/tests/ui_gateway_test.rs +++ b/node/tests/ui_gateway_test.rs @@ -3,46 +3,48 @@ pub mod utils; use masq_lib::messages::{ - UiDescriptorRequest, UiDescriptorResponse, UiFinancialsRequest, UiFinancialsResponse, - UiShutdownRequest, NODE_UI_PROTOCOL, + UiFinancialsRequest, UiFinancialsResponse, UiShutdownRequest, NODE_UI_PROTOCOL, }; use masq_lib::test_utils::ui_connection::UiConnection; +use masq_lib::test_utils::utils::ensure_node_home_directory_exists; use masq_lib::utils::find_free_port; +use node_lib::accountant::payable_dao::{PayableDao, PayableDaoReal}; +use node_lib::accountant::receivable_dao::{ReceivableDao, ReceivableDaoReal}; +use node_lib::database::db_initializer::{DbInitializer, DbInitializerReal}; +use node_lib::database::db_migrations::MigratorConfig; +use node_lib::test_utils::make_wallet; use utils::CommandConfig; #[test] -fn dispatcher_message_integration() { +fn ui_requests_something_and_gets_corresponding_response() { fdlimit::raise_fd_limit(); let port = find_free_port(); - let mut node = utils::MASQNode::start_standard( - "dispatcher_message_integration", - Some(CommandConfig::new().pair("--ui-port", &port.to_string())), - true, - true, - false, - true, + let home_dir = ensure_node_home_directory_exists( + "ui_gateway_test", + "ui_requests_something_and_gets_corresponding_response", ); - node.wait_for_log("UIGateway bound", Some(5000)); - let descriptor_req = UiDescriptorRequest {}; - let mut descriptor_client = UiConnection::new(port, NODE_UI_PROTOCOL); - let shutdown_req = UiShutdownRequest {}; - let mut shutdown_client = UiConnection::new(port, NODE_UI_PROTOCOL); - - descriptor_client.send(descriptor_req); - let _: UiDescriptorResponse = descriptor_client.receive().unwrap(); - shutdown_client.send(shutdown_req); - - node.wait_for_exit(); -} - -#[test] -fn request_financial_information_integration() { - fdlimit::raise_fd_limit(); - let port = find_free_port(); + let make_conn = || { + DbInitializerReal::default() + .initialize(&home_dir, true, MigratorConfig::panic_on_migration()) + .unwrap() + }; + PayableDaoReal::new(make_conn()) + .more_money_payable(&make_wallet("abc"), 45678) + .unwrap(); + ReceivableDaoReal::new(make_conn()) + .more_money_receivable(&make_wallet("xyz"), 65432) + .unwrap(); let mut node = utils::MASQNode::start_standard( - "request_financial_information_integration", - Some(CommandConfig::new().pair("--ui-port", &port.to_string())), - true, + "ui_requests_something_and_gets_corresponding_response", + Some( + CommandConfig::new() + .pair("--ui-port", &port.to_string()) + .pair( + "--data-directory", + home_dir.into_os_string().to_str().unwrap(), + ), + ), + false, true, false, true, @@ -52,7 +54,17 @@ fn request_financial_information_integration() { let mut client = UiConnection::new(port, NODE_UI_PROTOCOL); client.send(financials_request); - let _: UiFinancialsResponse = client.receive().unwrap(); + + let response: UiFinancialsResponse = client.receive().unwrap(); + assert_eq!( + response, + UiFinancialsResponse { + total_unpaid_and_pending_payable: 45678, + total_paid_payable: 0, + total_unpaid_receivable: 65432, + total_paid_receivable: 0 + } + ); client.send(UiShutdownRequest {}); node.wait_for_exit(); } From 8babb1ddf348ce37fca401db4cbf5d78e4c0d6e5 Mon Sep 17 00:00:00 2001 From: Bert Date: Thu, 31 Mar 2022 15:28:37 +0200 Subject: [PATCH 08/10] GH-236: post second review --- USER-INTERFACE-INTERFACE.md | 43 +++++++++++++++---------------------- node/src/accountant/mod.rs | 24 +++++++-------------- 2 files changed, 25 insertions(+), 42 deletions(-) diff --git a/USER-INTERFACE-INTERFACE.md b/USER-INTERFACE-INTERFACE.md index 1d6d27bc8..ef55e7130 100644 --- a/USER-INTERFACE-INTERFACE.md +++ b/USER-INTERFACE-INTERFACE.md @@ -578,7 +578,6 @@ paid to and from this Node's wallets. ##### Correspondent: Node ##### Layout: ``` - "payload": { "totalUnpaidAndPendingPayable": "totalPaidPayable": @@ -587,31 +586,23 @@ paid to and from this Node's wallets. } ``` ##### Description: -Contains requested values of financial statistics of the running Node. - -`totalUnpaidAndPendingPayable` is a cumulative amount of Gwei referring to the `payable` and `pending_payable` tables -in the database, owed money over possibly several accounts set up for the Node's creditors, either with a payment being -underway or waiting until it full qualify and comes to be paid. This is why these values technically compose of -two money amounts derived from two different books of debts. Because the sub amount representing pending payable can -still potentially end up as a failure we cannot include it into an amount of paid payable yet, not until the transaction -is added to the blockchain, and we can confirm that on our end. While we pay our debts, this total decreases in -its value but raises other time. - -`totalPaidPayable`, unlike the previous, this is a sum of paid amounts of the token that our Node has sent to our -creditors and the transactions must have been confirmed by now. This parameter displays as a value in Gwei -tracked since the startup (we plan a change to keep track of all time values regardless the Node's nonoperational -periods). - -`totalUnpaidReceivable`, this is shallowly similar to `totalUnpaidAndPendingPayable`, the values making the sum -come from the `receivable` table in the database. This table collects accounts for Nodes that have drawn our Node's -services but haven't paid for them yet. The value is represented in Gwei. When a Node pays us to redeem its debt -its respective account faces a subtracting operation of the payment out of the cumulative owed balance, this also leads -to a decrease in this total to be displayed. - -`totalPaidReceivable`: this number of Gwei means a total of all transactions made on our account to other Nodes -and that we've already detected as confirmed on the blockchain. Currently, we compute the value since the startup only -and it gets zeroed with every new startup (we plan a change to keep track of all time values regardless the Node's -nonoperational periods). +Contains the requested financial statistics. + +`totalUnpaidAndPendingPayable` is the number of Gwei we believe we owe to other Nodes and that those other Nodes have +not yet received, as far as we know. This includes both bills we haven't yet paid and bills we have paid, but whose +transactions we have not yet seen confirmed on the blockchain. + +`totalPaidPayable` is the number of Gwei we have successfully paid to our creditors and seen confirmed during the time +the current instance of the Node has been running. In the future, this number may become cumulative over more time than +just the current Node run. + +`totalUnpaidReceivable` is the number of Gwei we believe other Nodes owe to us, but have not yet been included in +payments we have seen confirmed on the blockchain. This includes both payments that have never been made and also +payments that have been made but not yet confirmed. + +`totalPaidReceivable` is the number of Gwei we have successfully received in confirmed payments from our debtors during +the time the current instance of the Node has been running. In the future, this number may become cumulative over more +time than just the current Node run. #### `generateWallets` ##### Direction: Request diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index d44124bc6..3e86f9668 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -1102,7 +1102,7 @@ trait PayableExceedThresholdTools { as_any_dcl!(); } -#[derive(Default, PartialEq, Debug)] +#[derive(Default)] struct PayableExceedThresholdToolsReal {} impl PayableExceedThresholdTools for PayableExceedThresholdToolsReal { @@ -1169,8 +1169,7 @@ mod tests { use ethsign_crypto::Keccak256; use masq_lib::test_utils::logging::init_test_logging; use masq_lib::test_utils::logging::TestLogHandler; - use masq_lib::ui_gateway::MessagePath::Conversation; - use masq_lib::ui_gateway::{MessageBody, MessageTarget, NodeFromUiMessage, NodeToUiMessage}; + use masq_lib::ui_gateway::{MessageTarget, NodeFromUiMessage, NodeToUiMessage}; use std::cell::RefCell; use std::ops::Sub; use std::rc::Rc; @@ -4060,11 +4059,7 @@ mod tests { subject_addr.try_send(BindMessage { peer_actors }).unwrap(); let ui_message = NodeFromUiMessage { client_id: 1234, - body: MessageBody { - opcode: "financials".to_string(), - path: Conversation(2222), - payload: Ok("{}".to_string()), - }, + body: UiFinancialsRequest {}.tmb(2222), }; subject_addr.try_send(ui_message).unwrap(); @@ -4074,13 +4069,10 @@ mod tests { let ui_gateway_recording = ui_gateway_recording_arc.lock().unwrap(); let response = ui_gateway_recording.get_record::(0); assert_eq!(response.target, MessageTarget::ClientId(1234)); - assert_eq!(response.body.opcode, "financials".to_string()); - assert_eq!(response.body.path, Conversation(2222)); - let parsed_payload = - serde_json::from_str::(&response.body.payload.as_ref().unwrap()) - .unwrap(); + let (body, context_id) = UiFinancialsResponse::fmb(response.body.clone()).unwrap(); + assert_eq!(context_id, 2222); assert_eq!( - parsed_payload, + body, UiFinancialsResponse { total_unpaid_and_pending_payable: 23456789, total_paid_payable: 123456, @@ -4091,7 +4083,7 @@ mod tests { } #[test] - fn total_paid_payable_starts_at_zero_and_gets_increasingly_bigger() { + fn total_paid_payable_raises_with_each_bill_paid() { let transaction_confirmed_params_arc = Arc::new(Mutex::new(vec![])); let fingerprint = PendingPayableFingerprint { rowid_opt: Some(5), @@ -4125,7 +4117,7 @@ mod tests { } #[test] - fn total_paid_receivable_starts_at_zero_and_gets_increasingly_bigger() { + fn total_paid_receivable_raises_with_each_bill_paid() { let more_money_received_params_arc = Arc::new(Mutex::new(vec![])); let receivable_dao = ReceivableDaoMock::new() .more_money_received_parameters(&more_money_received_params_arc) From a38a329626cc5702ff04f5f45ac781687c6059f5 Mon Sep 17 00:00:00 2001 From: Bert Date: Wed, 6 Apr 2022 13:08:20 +0200 Subject: [PATCH 09/10] GH-236: spelling correction --- node/src/accountant/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index 3e86f9668..6753e896f 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -4083,7 +4083,7 @@ mod tests { } #[test] - fn total_paid_payable_raises_with_each_bill_paid() { + fn total_paid_payable_rises_with_each_bill_paid() { let transaction_confirmed_params_arc = Arc::new(Mutex::new(vec![])); let fingerprint = PendingPayableFingerprint { rowid_opt: Some(5), @@ -4117,7 +4117,7 @@ mod tests { } #[test] - fn total_paid_receivable_raises_with_each_bill_paid() { + fn total_paid_receivable_rises_with_each_bill_paid() { let more_money_received_params_arc = Arc::new(Mutex::new(vec![])); let receivable_dao = ReceivableDaoMock::new() .more_money_received_parameters(&more_money_received_params_arc) From b859035ce3f38dd548cf6fd049126c34b96020ec Mon Sep 17 00:00:00 2001 From: Bert Date: Sat, 16 Apr 2022 16:03:16 +0200 Subject: [PATCH 10/10] GH-236: eliminating a panic due to a timed out connection from noninteractive mode --- node/src/ui_gateway/websocket_supervisor.rs | 83 +++++++++++---------- 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/node/src/ui_gateway/websocket_supervisor.rs b/node/src/ui_gateway/websocket_supervisor.rs index 7b0f808bc..a6989acd9 100644 --- a/node/src/ui_gateway/websocket_supervisor.rs +++ b/node/src/ui_gateway/websocket_supervisor.rs @@ -8,7 +8,6 @@ use futures::stream::SplitSink; use futures::Future; use futures::Sink; use futures::Stream; -use itertools::Itertools; use masq_lib::constants::UNMARSHAL_ERROR; use masq_lib::logger::Logger; use masq_lib::messages::{ToMessageBody, UiUnmarshalError, NODE_UI_PROTOCOL}; @@ -119,19 +118,37 @@ impl WebSocketSupervisorReal { Ok(Box::new(WebSocketSupervisorReal { inner })) } + fn filter_clients<'a, P>( + locked_inner: &'a mut MutexGuard, + predicate: P, + ) -> Vec<(u64, &'a mut dyn ClientWrapper)> + where + P: FnMut(&(&u64, &mut Box)) -> bool, + { + locked_inner + .client_by_id + .iter_mut() + .filter(predicate) + .map(|(id, item)| (*id, item.as_mut())) + .collect() + } + fn send_msg(locked_inner: &mut MutexGuard, msg: NodeToUiMessage) { - let client_ids = match msg.target { - MessageTarget::ClientId(n) => vec![n], - MessageTarget::AllExcept(n) => locked_inner - .client_by_id - .keys() - .filter(|k| k != &&n) - .copied() - .collect_vec(), - MessageTarget::AllClients => locked_inner.client_by_id.keys().copied().collect_vec(), + let clients = match msg.target { + MessageTarget::ClientId(n) => { + let clients = Self::filter_clients(locked_inner, |(id, _)| **id == n); + if !clients.is_empty() { + clients + } else { + Self::log_absent_client(n); + return; + } + } + MessageTarget::AllExcept(n) => Self::filter_clients(locked_inner, |(id, _)| **id != n), + MessageTarget::AllClients => Self::filter_clients(locked_inner, |_| true), }; let json = UiTrafficConverter::new_marshal(msg.body); - Self::send_to_clients(locked_inner, client_ids, json); + Self::send_to_clients(clients, json); } fn remove_failures( @@ -371,16 +388,8 @@ impl WebSocketSupervisorReal { ok::<(), ()>(()) } - fn send_to_clients( - locked_inner: &mut MutexGuard, - client_ids: Vec, - json: String, - ) { - client_ids.into_iter().for_each(|client_id| { - let client = locked_inner - .client_by_id - .get_mut(&client_id) - .unwrap_or_else(|| panic!("Tried to send to a nonexistent client {}", client_id)); + fn send_to_clients(clients: Vec<(u64, &mut dyn ClientWrapper)>, json: String) { + clients.into_iter().for_each(|(client_id, client)| { match client.send(OwnedMessage::Text(json.clone())) { Ok(_) => match client.flush() { Ok(_) => (), @@ -441,6 +450,14 @@ impl WebSocketSupervisorReal { } } } + + fn log_absent_client(client_id: u64) { + warning!( + Logger::new("WebsocketSupervisor"), + "WebsocketSupervisor: WARN: Tried to send to an absent client {}", + client_id + ) + } } pub trait WebSocketSupervisorFactory: Send { @@ -1013,26 +1030,11 @@ mod tests { #[test] fn can_handle_flush_failure_after_send() { init_test_logging(); - let (ui_gateway, _, _) = make_recorder(); - let from_ui_message_sub = subs(ui_gateway); - let client = ClientWrapperMock::new() + let mut client = ClientWrapperMock::new() .send_result(Ok(())) .flush_result(Err(WebSocketError::NoDataAvailable)); - let mut client_by_id: HashMap> = HashMap::new(); - client_by_id.insert(1234, Box::new(client)); - let inner_arc = Arc::new(Mutex::new(WebSocketSupervisorInner { - port: 0, - next_client_id: 0, - from_ui_message_sub, - client_id_by_socket_addr: Default::default(), - client_by_id, - })); - WebSocketSupervisorReal::send_to_clients( - &mut inner_arc.lock().unwrap(), - vec![1234], - "json".to_string(), - ); + WebSocketSupervisorReal::send_to_clients(vec![(1234, &mut client)], "json".to_string()); TestLogHandler::new().exists_log_containing ("WARN: WebSocketSupervisor: Client 1234 dropped its connection before it could be flushed"); } @@ -1304,8 +1306,8 @@ mod tests { } #[test] - #[should_panic(expected = "Tried to send to a nonexistent client")] fn send_msg_fails_to_look_up_client_to_send_to() { + init_test_logging(); let port = find_free_port(); let (ui_gateway, _, _) = make_recorder(); let ui_message_sub = subs(ui_gateway); @@ -1326,5 +1328,8 @@ mod tests { actix::spawn(lazy_future); System::current().stop(); system.run(); + TestLogHandler::new().exists_log_containing( + "WebsocketSupervisor: WARN: Tried to send to an absent client 7", + ); } }