diff --git a/USER-INTERFACE-INTERFACE.md b/USER-INTERFACE-INTERFACE.md index ca5b112f5..ef55e7130 100644 --- a/USER-INTERFACE-INTERFACE.md +++ b/USER-INTERFACE-INTERFACE.md @@ -564,27 +564,14 @@ 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. +Requests financial statistics from the Node. -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. - -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 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 @@ -592,52 +579,30 @@ greater than 64 bits long will cause undefined behavior. ##### Layout: ``` "payload": { - "payables": [ - { - "wallet": , - "age": , - "amount": , - "pendingPayableHashOpt": - }, - < ... > - ], - "totalPayable": , - "receivables": [ - { - "wallet": , - "age": , - "amount": - }, - < ... > - ], - "totalReceivable": + "totalUnpaidAndPendingPayable": + "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. +Contains the requested financial statistics. -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. +`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. -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. +`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. -The `payables` and `receivables` arrays are not in any particular order. +`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. -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. +`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/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 62628ab08..ceed89b44 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::*; @@ -110,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/configuration_command.rs b/masq/src/commands/configuration_command.rs index 95fcd15b2..55b532f8f 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; @@ -80,47 +80,47 @@ impl ConfigurationCommand { } 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(), @@ -165,24 +165,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, "{:width$} {}", name, value, width = COLUMN_WIDTH); - } - 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 d2d3bc87c..c7c4dd511 100644 --- a/masq/src/commands/descriptor_command.rs +++ b/masq/src/commands/descriptor_command.rs @@ -75,7 +75,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] @@ -187,11 +186,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..10c6a1221 --- /dev/null +++ b/masq/src/commands/financials_command.rs @@ -0,0 +1,175 @@ +// 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; + +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(FINANCIALS_SUBCOMMAND_ABOUT) +} + +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(); + short_writeln!(stdout, "Financial status totals in Gwei\n"); + dump_parameter_line( + stdout, + "Unpaid and pending payable:", + &response.total_unpaid_and_pending_payable.to_string(), + ); + dump_parameter_line( + stdout, + "Paid payable:", + &response.total_paid_payable.to_string(), + ); + dump_parameter_line( + stdout, + "Unpaid receivable:", + &response.total_unpaid_receivable.to_string(), + ); + dump_parameter_line( + stdout, + "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 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(); + let mut context = CommandContextMock::new().transact_result(Ok(UiFinancialsResponse { + total_unpaid_and_pending_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_and_pending_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(), + "\ + 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()); + } + + #[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 bf9269650..f7421391a 100644 --- a/masq/src/communications/connection_manager.rs +++ b/masq/src/communications/connection_manager.rs @@ -1170,10 +1170,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_and_pending_payable: 10, + total_paid_payable: 22, + total_unpaid_receivable: 29, + total_paid_receivable: 32, } .tmb(1), ); @@ -1187,13 +1187,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(); @@ -1205,23 +1199,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_and_pending_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 5303470b8..529e01b47 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; @@ -63,6 +64,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 45579de95..93ab79a2b 100644 --- a/masq_lib/src/messages.rs +++ b/masq_lib/src/messages.rs @@ -576,26 +576,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 = "totalUnpaidAndPendingPayable")] + pub total_unpaid_and_pending_payable: u64, + #[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"); @@ -767,43 +760,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) } @@ -811,13 +789,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(); @@ -827,60 +802,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, @@ -892,51 +864,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 190a1b040..6753e896f 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::{Scanner, 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; @@ -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, PaymentThresholds}; +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}; @@ -44,13 +44,15 @@ use actix::Recipient; use itertools::Itertools; 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}; 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; @@ -72,6 +74,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>, @@ -88,7 +91,7 @@ impl Actor for Accountant { #[derive(Debug, Eq, Message, PartialEq)] pub struct ReceivedPayments { - pub payments: Vec, + pub payments: Vec, } #[derive(Debug, Message, PartialEq)] @@ -278,8 +281,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) } @@ -309,6 +312,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(), )), @@ -317,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"), } } @@ -676,9 +680,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; } } @@ -784,47 +793,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_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 { - payables, - total_payable, - receivables, - total_receivable, + total_unpaid_and_pending_payable, + total_paid_payable, + total_unpaid_receivable, + total_paid_receivable, } .tmb(context_id); self.ui_message_sub @@ -849,7 +827,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) @@ -859,6 +837,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", @@ -1030,14 +1009,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_cancel_failed_transaction.notify( CancelFailedPendingTransaction { id: transaction_id }, @@ -1045,7 +1028,7 @@ impl Accountant { ) } - fn confirm_transaction( + fn order_confirm_transaction( &self, pending_payable_fingerprint: PendingPayableFingerprint, ctx: &mut Context, @@ -1116,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)] struct PayableExceedThresholdToolsReal {} impl PayableExceedThresholdTools for PayableExceedThresholdToolsReal { @@ -1138,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)] @@ -1153,10 +1139,10 @@ 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::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; @@ -1167,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; @@ -1182,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; @@ -1320,119 +1306,69 @@ 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 system = System::new("test"); - let 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(); - 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(r#"{"payableMinimumAmount": 50001, "payableMaximumAge": 50002, "receivableMinimumAmount": 50003, "receivableMaximumAge": 50004}"#.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)); - 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 { - 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 - } + 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, ); + + 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] @@ -1704,12 +1640,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, @@ -3140,7 +3076,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(); @@ -3187,7 +3123,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(); @@ -3214,7 +3150,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(); @@ -4102,6 +4038,119 @@ 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: UiFinancialsRequest {}.tmb(2222), + }; + + 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)); + let (body, context_id) = UiFinancialsResponse::fmb(response.body.clone()).unwrap(); + assert_eq!(context_id, 2222); + assert_eq!( + body, + UiFinancialsResponse { + total_unpaid_and_pending_payable: 23456789, + total_paid_payable: 123456, + total_unpaid_receivable: 98765432, + total_paid_receivable: 334455 + } + ); + } + + #[test] + 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), + 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_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) + .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 ad8d8f01b..81ee0b9e3 100644 --- a/node/src/accountant/payable_dao.rs +++ b/node/src/accountant/payable_dao.rs @@ -234,9 +234,9 @@ impl PayableDao for PayableDaoReal { .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 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 167d8b91e..04edac4a0 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; -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}; @@ -39,7 +39,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; @@ -55,7 +55,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 { @@ -95,7 +95,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 = @@ -252,7 +252,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") @@ -260,7 +260,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, @@ -268,7 +268,7 @@ impl ReceivableDao for ReceivableDaoReal { Type::Null, ) => { - Ok(0u64) + Ok(0) } Err(e) => panic!( "Database is corrupt: RECEIVABLE table columns and/or types: {:?}", @@ -315,7 +315,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, @@ -416,7 +416,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, @@ -445,7 +445,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, @@ -477,7 +477,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, @@ -601,12 +601,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, @@ -655,7 +655,7 @@ mod tests { ); let status = { - let transactions = vec![Transaction { + let transactions = vec![PaidReceivable { from: debtor.clone(), gwei_amount: 2300u64, block_number: 33u64, @@ -674,17 +674,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 796b0eca1..e9aefae55 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, 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}; @@ -443,7 +443,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>>, @@ -452,7 +452,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, } @@ -469,7 +469,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() @@ -523,7 +523,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) } } @@ -548,7 +548,7 @@ impl ReceivableDaoMock { pub fn more_money_received_parameters( mut self, - parameters: &Arc>>>, + parameters: &Arc>>>, ) -> Self { self.more_money_received_parameters = parameters.clone(); self @@ -595,7 +595,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 2d8fc4a66..ec77e2e86 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; @@ -908,12 +908,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 0fae92304..b132912de 100644 --- a/node/src/blockchain/blockchain_interface.rs +++ b/node/src/blockchain/blockchain_interface.rs @@ -35,13 +35,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, @@ -77,13 +77,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, @@ -137,7 +137,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)) @@ -216,7 +216,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}", @@ -241,7 +241,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() @@ -255,7 +255,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]), @@ -685,7 +685,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 1f4e40610..912c3eab1 100644 --- a/node/src/daemon/mod.rs +++ b/node/src/daemon/mod.rs @@ -1555,13 +1555,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 735d8dbc3..ff68b8970 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; @@ -127,16 +126,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/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/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/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", + ); } } 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..aacb7729f 100644 --- a/node/tests/ui_gateway_test.rs +++ b/node/tests/ui_gateway_test.rs @@ -3,64 +3,68 @@ 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, ); 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 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(); }