diff --git a/automap/src/comm_layer/pcp_pmp_common/macos_specific.rs b/automap/src/comm_layer/pcp_pmp_common/macos_specific.rs index b3cc6f116..d82ad7a1a 100644 --- a/automap/src/comm_layer/pcp_pmp_common/macos_specific.rs +++ b/automap/src/comm_layer/pcp_pmp_common/macos_specific.rs @@ -1,8 +1,10 @@ // Copyright (c) 2019-2021, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + #![cfg(target_os = "macos")] use crate::comm_layer::pcp_pmp_common::FindRoutersCommand; use crate::comm_layer::AutomapError; +use masq_lib::utils::to_string; use std::net::IpAddr; use std::str::FromStr; @@ -13,13 +15,9 @@ pub fn macos_find_routers(command: &dyn FindRoutersCommand) -> Result>() - }) + .map(|line| line.split(": ").map(to_string).collect::>()) .filter(|pieces| pieces.len() > 1) .map(|pieces| IpAddr::from_str(&pieces[1]).expect("Bad syntax from route -n get default")) .collect::>(); diff --git a/masq/src/command_processor.rs b/masq/src/command_processor.rs index d82f91628..4438e2b0e 100644 --- a/masq/src/command_processor.rs +++ b/masq/src/command_processor.rs @@ -85,7 +85,7 @@ mod tests { use masq_lib::messages::UiShutdownRequest; use masq_lib::messages::{ToMessageBody, UiCheckPasswordResponse, UiUndeliveredFireAndForget}; use masq_lib::test_utils::mock_websockets_server::MockWebSocketsServer; - use masq_lib::utils::{find_free_port, running_test}; + use masq_lib::utils::{find_free_port, running_test, to_string}; use std::thread; use std::time::Duration; @@ -210,7 +210,7 @@ mod tests { fn whole_message() -> String { TameCommand::MESSAGE_IN_PIECES .iter() - .map(|str| str.to_string()) + .map(to_string) .collect() } } diff --git a/masq/src/commands/change_password_command.rs b/masq/src/commands/change_password_command.rs index ef2fb020c..9dcc71075 100644 --- a/masq/src/commands/change_password_command.rs +++ b/masq/src/commands/change_password_command.rs @@ -9,9 +9,7 @@ use clap::{App, Arg, SubCommand}; use masq_lib::messages::{ UiChangePasswordRequest, UiChangePasswordResponse, UiNewPasswordBroadcast, }; -use masq_lib::{implement_as_any, short_writeln}; -#[cfg(test)] -use std::any::Any; +use masq_lib::{as_any_in_trait_impl, short_writeln}; use std::io::Write; #[derive(Debug, PartialEq, Eq)] @@ -81,7 +79,7 @@ impl Command for ChangePasswordCommand { Ok(()) } - implement_as_any!(); + as_any_in_trait_impl!(); } pub fn change_password_subcommand() -> App<'static, 'static> { diff --git a/masq/src/commands/check_password_command.rs b/masq/src/commands/check_password_command.rs index 3dcc5abb2..ee7081e7d 100644 --- a/masq/src/commands/check_password_command.rs +++ b/masq/src/commands/check_password_command.rs @@ -5,11 +5,10 @@ use crate::commands::commands_common::{ transaction, Command, CommandError, STANDARD_COMMAND_TIMEOUT_MILLIS, }; use clap::{App, Arg, SubCommand}; -use masq_lib::implement_as_any; +use masq_lib::as_any_in_trait_impl; use masq_lib::messages::{UiCheckPasswordRequest, UiCheckPasswordResponse}; use masq_lib::short_writeln; -#[cfg(test)] -use std::any::Any; +use masq_lib::utils::to_string; #[derive(Debug, PartialEq, Eq)] pub struct CheckPasswordCommand { @@ -52,7 +51,7 @@ impl Command for CheckPasswordCommand { Ok(()) } - implement_as_any!(); + as_any_in_trait_impl!(); } impl CheckPasswordCommand { @@ -62,7 +61,7 @@ impl CheckPasswordCommand { Err(e) => return Err(format!("{}", e)), }; Ok(Self { - db_password_opt: matches.value_of("db-password").map(|r| r.to_string()), + db_password_opt: matches.value_of("db-password").map(to_string), }) } } diff --git a/masq/src/commands/configuration_command.rs b/masq/src/commands/configuration_command.rs index ab7906cc3..766eedc6d 100644 --- a/masq/src/commands/configuration_command.rs +++ b/masq/src/commands/configuration_command.rs @@ -6,12 +6,11 @@ use crate::commands::commands_common::{ dump_parameter_line, transaction, Command, CommandError, STANDARD_COMMAND_TIMEOUT_MILLIS, }; use clap::{App, Arg, SubCommand}; +use masq_lib::as_any_in_trait_impl; use masq_lib::constants::NODE_NOT_RUNNING_ERROR; -use masq_lib::implement_as_any; use masq_lib::messages::{UiConfigurationRequest, UiConfigurationResponse}; use masq_lib::short_writeln; -#[cfg(test)] -use std::any::Any; +use masq_lib::utils::to_string; use std::fmt::{Debug, Display}; use std::io::Write; use std::iter::once; @@ -65,7 +64,7 @@ impl Command for ConfigurationCommand { } } - implement_as_any!(); + as_any_in_trait_impl!(); } impl ConfigurationCommand { @@ -76,7 +75,7 @@ impl ConfigurationCommand { }; Ok(ConfigurationCommand { - db_password: matches.value_of("db-password").map(|s| s.to_string()), + db_password: matches.value_of("db-password").map(to_string), }) } diff --git a/masq/src/commands/connection_status_command.rs b/masq/src/commands/connection_status_command.rs index bc3ece105..fc96c6716 100644 --- a/masq/src/commands/connection_status_command.rs +++ b/masq/src/commands/connection_status_command.rs @@ -6,14 +6,12 @@ use crate::commands::commands_common::{ transaction, Command, CommandError, STANDARD_COMMAND_TIMEOUT_MILLIS, }; use clap::{App, SubCommand}; +use masq_lib::as_any_in_trait_impl; use masq_lib::constants::NODE_NOT_RUNNING_ERROR; -use masq_lib::implement_as_any; use masq_lib::messages::{ UiConnectionStage, UiConnectionStatusRequest, UiConnectionStatusResponse, }; use masq_lib::short_writeln; -#[cfg(test)] -use std::any::Any; use std::fmt::Debug; #[derive(Debug, PartialEq, Eq)] @@ -64,7 +62,7 @@ impl Command for ConnectionStatusCommand { } } - implement_as_any!(); + as_any_in_trait_impl!(); } impl ConnectionStatusCommand { diff --git a/masq/src/commands/financials_command/pretty_print_utils.rs b/masq/src/commands/financials_command/pretty_print_utils.rs index 16088b7d5..2adbfbad4 100644 --- a/masq/src/commands/financials_command/pretty_print_utils.rs +++ b/masq/src/commands/financials_command/pretty_print_utils.rs @@ -11,6 +11,7 @@ pub(in crate::commands::financials_command) mod restricted { use masq_lib::constants::WALLET_ADDRESS_LENGTH; use masq_lib::messages::{UiPayableAccount, UiReceivableAccount}; use masq_lib::short_writeln; + use masq_lib::utils::to_string; use std::fmt::{Debug, Display}; use std::io::Write; use thousands::Separable; @@ -163,7 +164,7 @@ pub(in crate::commands::financials_command) mod restricted { fn prepare_headings_of_records(is_gwei: bool) -> (HeadingsHolder, HeadingsHolder) { fn to_owned_strings(words: Vec<&str>) -> Vec { - words.iter().map(|str| str.to_string()).collect() + words.iter().map(to_string).collect() } let balance = gwei_or_masq_balance(is_gwei); ( diff --git a/masq/src/commands/generate_wallets_command.rs b/masq/src/commands/generate_wallets_command.rs index 7cc388393..6bfd8b359 100644 --- a/masq/src/commands/generate_wallets_command.rs +++ b/masq/src/commands/generate_wallets_command.rs @@ -1,19 +1,16 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -#[cfg(test)] -use std::any::Any; - use crate::command_context::CommandContext; use crate::commands::commands_common::{ transaction, Command, CommandError, STANDARD_COMMAND_TIMEOUT_MILLIS, }; use clap::{App, Arg, SubCommand}; use lazy_static::lazy_static; -use masq_lib::implement_as_any; +use masq_lib::as_any_in_trait_impl; use masq_lib::messages::{UiGenerateSeedSpec, UiGenerateWalletsRequest, UiGenerateWalletsResponse}; use masq_lib::short_writeln; -use masq_lib::utils::DEFAULT_CONSUMING_DERIVATION_PATH; use masq_lib::utils::DEFAULT_EARNING_DERIVATION_PATH; +use masq_lib::utils::{to_string, DEFAULT_CONSUMING_DERIVATION_PATH}; lazy_static! { static ref CONSUMING_PATH_HELP: String = format!( @@ -85,8 +82,8 @@ impl GenerateWalletsCommand { Err(e) => return Err(format!("{}", e)), }; - let consuming_path_opt = matches.value_of("consuming-path").map(|p| p.to_string()); - let earning_path_opt = matches.value_of("earning-path").map(|p| p.to_string()); + let consuming_path_opt = matches.value_of("consuming-path").map(to_string); + let earning_path_opt = matches.value_of("earning-path").map(to_string); let seed_spec_opt = if consuming_path_opt.is_some() || earning_path_opt.is_some() { Some(SeedSpec { word_count: matches @@ -99,7 +96,7 @@ impl GenerateWalletsCommand { .value_of("language") .expect("language not properly defaulted") .to_string(), - passphrase_opt: matches.value_of("passphrase").map(|s| s.to_string()), + passphrase_opt: matches.value_of("passphrase").map(to_string), }) } else { None @@ -168,7 +165,7 @@ impl Command for GenerateWalletsCommand { Ok(()) } - implement_as_any!(); + as_any_in_trait_impl!(); } pub fn generate_wallets_subcommand() -> App<'static, 'static> { diff --git a/masq/src/commands/recover_wallets_command.rs b/masq/src/commands/recover_wallets_command.rs index 34ea03179..b7f3e6bc1 100644 --- a/masq/src/commands/recover_wallets_command.rs +++ b/masq/src/commands/recover_wallets_command.rs @@ -6,11 +6,10 @@ use crate::commands::commands_common::{ }; use clap::{App, Arg, ArgGroup, SubCommand}; use itertools::{Either, Itertools}; -use masq_lib::implement_as_any; +use masq_lib::as_any_in_trait_impl; use masq_lib::messages::{UiRecoverSeedSpec, UiRecoverWalletsRequest, UiRecoverWalletsResponse}; use masq_lib::short_writeln; -#[cfg(test)] -use std::any::Any; +use masq_lib::utils::to_string; #[derive(Debug, PartialEq, Eq)] pub struct SeedSpec { @@ -36,12 +35,12 @@ impl RecoverWalletsCommand { let mnemonic_phrase_opt = matches .value_of("mnemonic-phrase") - .map(|mpv| mpv.split(' ').map(|x| x.to_string()).collect_vec()); + .map(|mpv| mpv.split(' ').map(to_string).collect_vec()); let language = matches .value_of("language") .expect("language is not properly defaulted by clap") .to_string(); - let passphrase_opt = matches.value_of("passphrase").map(|mp| mp.to_string()); + let passphrase_opt = matches.value_of("passphrase").map(to_string); let seed_spec_opt = mnemonic_phrase_opt.map(|mnemonic_phrase| SeedSpec { mnemonic_phrase, language, @@ -121,7 +120,7 @@ impl Command for RecoverWalletsCommand { Ok(()) } - implement_as_any!(); + as_any_in_trait_impl!(); } const RECOVER_WALLETS_ABOUT: &str = @@ -340,7 +339,7 @@ mod tests { db_password: "password".to_string(), seed_spec_opt: Some (SeedSpec { mnemonic_phrase: "river message view churn potato cabbage craft luggage tape month observe obvious" - .split(" ").into_iter().map(|x| x.to_string()).collect(), + .split(" ").into_iter().map(to_string).collect(), passphrase_opt: Some("booga".to_string()), language: "English".to_string(), }), diff --git a/masq/src/commands/set_configuration_command.rs b/masq/src/commands/set_configuration_command.rs index 0dd7e1e92..72fabc498 100644 --- a/masq/src/commands/set_configuration_command.rs +++ b/masq/src/commands/set_configuration_command.rs @@ -1,14 +1,12 @@ use crate::command_context::CommandContext; use crate::commands::commands_common::{transaction, Command, CommandError}; use clap::{App, Arg, ArgGroup, SubCommand}; -use masq_lib::implement_as_any; +use masq_lib::as_any_in_trait_impl; use masq_lib::messages::{UiSetConfigurationRequest, UiSetConfigurationResponse}; use masq_lib::shared_schema::gas_price_arg; use masq_lib::shared_schema::min_hops_arg; use masq_lib::short_writeln; use masq_lib::utils::ExpectValue; -#[cfg(test)] -use std::any::Any; #[derive(Debug, PartialEq, Eq)] pub struct SetConfigurationCommand { @@ -55,7 +53,7 @@ impl Command for SetConfigurationCommand { Ok(()) } - implement_as_any!(); + as_any_in_trait_impl!(); } const SET_CONFIGURATION_ABOUT: &str = diff --git a/masq/src/commands/setup_command.rs b/masq/src/commands/setup_command.rs index 7c4e7514f..baec35e1b 100644 --- a/masq/src/commands/setup_command.rs +++ b/masq/src/commands/setup_command.rs @@ -4,8 +4,8 @@ use crate::command_context::CommandContext; use crate::commands::commands_common::{transaction, Command, CommandError}; use crate::terminal::terminal_interface::TerminalWrapper; use clap::{value_t, App, SubCommand}; +use masq_lib::as_any_in_trait_impl; use masq_lib::constants::SETUP_ERROR; -use masq_lib::implement_as_any; use masq_lib::messages::{ UiSetupBroadcast, UiSetupInner, UiSetupRequest, UiSetupRequestValue, UiSetupResponse, UiSetupResponseValue, UiSetupResponseValueStatus, @@ -13,8 +13,6 @@ use masq_lib::messages::{ use masq_lib::shared_schema::{data_directory_arg, shared_app}; use masq_lib::short_writeln; use masq_lib::utils::{index_of_from, DATA_DIRECTORY_DAEMON_HELP}; -#[cfg(test)] -use std::any::Any; use std::fmt::Debug; use std::io::Write; use std::iter::Iterator; @@ -53,7 +51,7 @@ impl Command for SetupCommand { Err(e) => Err(e), } } - implement_as_any!(); + as_any_in_trait_impl!(); } impl SetupCommand { diff --git a/masq/src/commands/wallet_addresses_command.rs b/masq/src/commands/wallet_addresses_command.rs index e1f90e4fc..edc2e2a0f 100644 --- a/masq/src/commands/wallet_addresses_command.rs +++ b/masq/src/commands/wallet_addresses_command.rs @@ -6,9 +6,7 @@ use crate::commands::commands_common::{ }; use clap::{App, Arg, SubCommand}; use masq_lib::messages::{UiWalletAddressesRequest, UiWalletAddressesResponse}; -use masq_lib::{implement_as_any, short_writeln}; -#[cfg(test)] -use std::any::Any; +use masq_lib::{as_any_in_trait_impl, short_writeln}; #[derive(Debug, PartialEq, Eq)] pub struct WalletAddressesCommand { @@ -68,7 +66,7 @@ impl Command for WalletAddressesCommand { ); Ok(()) } - implement_as_any!(); + as_any_in_trait_impl!(); } #[cfg(test)] diff --git a/masq/src/communications/broadcast_handler.rs b/masq/src/communications/broadcast_handler.rs index 54ad2af8f..e4903ef17 100644 --- a/masq/src/communications/broadcast_handler.rs +++ b/masq/src/communications/broadcast_handler.rs @@ -11,18 +11,16 @@ use masq_lib::messages::{ }; use masq_lib::ui_gateway::MessageBody; use masq_lib::utils::ExpectValue; -use masq_lib::{declare_as_any, implement_as_any, short_writeln}; +use masq_lib::{as_any_in_trait, as_any_in_trait_impl, short_writeln}; use std::fmt::Debug; use std::io::Write; use std::thread; use crate::notifications::connection_change_notification::ConnectionChangeNotification; -#[cfg(test)] -use std::any::Any; pub trait BroadcastHandle: Send { fn send(&self, message_body: MessageBody); - declare_as_any!(); + as_any_in_trait!(); } pub struct BroadcastHandleInactive; @@ -30,7 +28,7 @@ pub struct BroadcastHandleInactive; impl BroadcastHandle for BroadcastHandleInactive { //simply dropped (unless we find a better use for such a message) fn send(&self, _message_body: MessageBody) {} - implement_as_any!(); + as_any_in_trait_impl!(); } pub struct BroadcastHandleGeneric { diff --git a/masq/src/non_interactive_clap.rs b/masq/src/non_interactive_clap.rs index 14b5fa7db..46f60957a 100644 --- a/masq/src/non_interactive_clap.rs +++ b/masq/src/non_interactive_clap.rs @@ -40,13 +40,14 @@ fn handle_help_or_version_if_required<'a>(args: &[String]) -> ArgMatches<'a> { mod tests { use super::*; use masq_lib::constants::DEFAULT_UI_PORT; + use masq_lib::utils::to_string; #[test] fn non_interactive_clap_real_produces_default_value_for_ui_port() { let result = NonInteractiveClapReal.non_interactive_initial_clap_operations( &vec!["masq", "setup", "--chain"] .iter() - .map(|str| str.to_string()) + .map(to_string) .collect::>(), ); @@ -58,7 +59,7 @@ mod tests { let result = NonInteractiveClapReal.non_interactive_initial_clap_operations( &vec!["masq", "--ui-port", "10000", "setup", "--log-level", "off"] .iter() - .map(|str| str.to_string()) + .map(to_string) .collect::>(), ); diff --git a/masq/src/non_interactive_mode.rs b/masq/src/non_interactive_mode.rs index 290e57cc1..164b9c293 100644 --- a/masq/src/non_interactive_mode.rs +++ b/masq/src/non_interactive_mode.rs @@ -176,6 +176,7 @@ mod tests { use masq_lib::intentionally_blank; use masq_lib::messages::{ToMessageBody, UiNewPasswordBroadcast, UiShutdownRequest}; use masq_lib::test_utils::fake_stream_holder::FakeStreamHolder; + use masq_lib::utils::to_string; use std::any::Any; use std::sync::{Arc, Mutex}; @@ -217,7 +218,7 @@ mod tests { "--param3", ] .iter() - .map(|str| str.to_string()) + .map(to_string) .collect::>(), ); @@ -228,7 +229,7 @@ mod tests { vec![ vec!["subcommand", "--param1", "value1", "--param2", "--param3"] .iter() - .map(|str| str.to_string()) + .map(to_string) .collect::>(), ] ); @@ -478,7 +479,7 @@ mod tests { fn extract_subcommands_can_process_normal_non_interactive_request() { let args = vec!["masq", "setup", "--log-level", "off"] .iter() - .map(|str| str.to_string()) + .map(to_string) .collect::>(); let result = Main::extract_subcommand(&args); @@ -497,7 +498,7 @@ mod tests { fn extract_subcommands_can_process_non_interactive_request_including_special_port() { let args = vec!["masq", "--ui-port", "10000", "setup", "--log-level", "off"] .iter() - .map(|str| str.to_string()) + .map(to_string) .collect::>(); let result = Main::extract_subcommand(&args); diff --git a/masq_lib/src/blockchains/blockchain_records.rs b/masq_lib/src/blockchains/blockchain_records.rs index 1bd13e622..f9b4bcab0 100644 --- a/masq_lib/src/blockchains/blockchain_records.rs +++ b/masq_lib/src/blockchains/blockchain_records.rs @@ -1,6 +1,6 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::blockchains::chains::{Chain, ChainFamily}; +use crate::blockchains::chains::Chain; use crate::constants::{ DEV_CHAIN_FULL_IDENTIFIER, ETH_MAINNET_CONTRACT_CREATION_BLOCK, ETH_MAINNET_FULL_IDENTIFIER, ETH_ROPSTEN_FULL_IDENTIFIER, MULTINODE_TESTNET_CONTRACT_CREATION_BLOCK, @@ -15,7 +15,6 @@ pub const CHAINS: [BlockchainRecord; 5] = [ BlockchainRecord { self_id: Chain::PolyMainnet, num_chain_id: 137, - chain_family: ChainFamily::Polygon, literal_identifier: POLYGON_MAINNET_FULL_IDENTIFIER, contract: POLYGON_MAINNET_CONTRACT_ADDRESS, contract_creation_block: POLYGON_MAINNET_CONTRACT_CREATION_BLOCK, @@ -23,7 +22,6 @@ pub const CHAINS: [BlockchainRecord; 5] = [ BlockchainRecord { self_id: Chain::EthMainnet, num_chain_id: 1, - chain_family: ChainFamily::Eth, literal_identifier: ETH_MAINNET_FULL_IDENTIFIER, contract: ETH_MAINNET_CONTRACT_ADDRESS, contract_creation_block: ETH_MAINNET_CONTRACT_CREATION_BLOCK, @@ -31,7 +29,6 @@ pub const CHAINS: [BlockchainRecord; 5] = [ BlockchainRecord { self_id: Chain::PolyMumbai, num_chain_id: 80001, - chain_family: ChainFamily::Polygon, literal_identifier: POLYGON_MUMBAI_FULL_IDENTIFIER, contract: MUMBAI_TESTNET_CONTRACT_ADDRESS, contract_creation_block: MUMBAI_TESTNET_CONTRACT_CREATION_BLOCK, @@ -39,7 +36,6 @@ pub const CHAINS: [BlockchainRecord; 5] = [ BlockchainRecord { self_id: Chain::EthRopsten, num_chain_id: 3, - chain_family: ChainFamily::Eth, literal_identifier: ETH_ROPSTEN_FULL_IDENTIFIER, contract: ROPSTEN_TESTNET_CONTRACT_ADDRESS, contract_creation_block: ROPSTEN_TESTNET_CONTRACT_CREATION_BLOCK, @@ -47,7 +43,6 @@ pub const CHAINS: [BlockchainRecord; 5] = [ BlockchainRecord { self_id: Chain::Dev, num_chain_id: 2, - chain_family: ChainFamily::Dev, literal_identifier: DEV_CHAIN_FULL_IDENTIFIER, contract: MULTINODE_TESTNET_CONTRACT_ADDRESS, contract_creation_block: MULTINODE_TESTNET_CONTRACT_CREATION_BLOCK, @@ -58,7 +53,6 @@ pub const CHAINS: [BlockchainRecord; 5] = [ pub struct BlockchainRecord { pub self_id: Chain, pub num_chain_id: u64, - pub chain_family: ChainFamily, pub literal_identifier: &'static str, pub contract: Address, pub contract_creation_block: u64, @@ -172,26 +166,8 @@ mod tests { literal_identifier: "eth-mainnet", contract: ETH_MAINNET_CONTRACT_ADDRESS, contract_creation_block: ETH_MAINNET_CONTRACT_CREATION_BLOCK, - chain_family: ChainFamily::Eth } - ) - } - - #[test] - fn multinode_testnet_chain_record_is_properly_declared() { - let examined_chain = Chain::Dev; - let chain_record = return_examined(examined_chain); - assert_eq!( - chain_record, - &BlockchainRecord { - num_chain_id: 2, - self_id: examined_chain, - literal_identifier: "dev", - contract: MULTINODE_TESTNET_CONTRACT_ADDRESS, - contract_creation_block: 0, - chain_family: ChainFamily::Dev - } - ) + ); } #[test] @@ -206,9 +182,8 @@ mod tests { literal_identifier: "eth-ropsten", contract: ROPSTEN_TESTNET_CONTRACT_ADDRESS, contract_creation_block: ROPSTEN_TESTNET_CONTRACT_CREATION_BLOCK, - chain_family: ChainFamily::Eth } - ) + ); } #[test] @@ -223,9 +198,8 @@ mod tests { literal_identifier: "polygon-mainnet", contract: POLYGON_MAINNET_CONTRACT_ADDRESS, contract_creation_block: POLYGON_MAINNET_CONTRACT_CREATION_BLOCK, - chain_family: ChainFamily::Polygon } - ) + ); } #[test] @@ -240,9 +214,24 @@ mod tests { literal_identifier: "polygon-mumbai", contract: MUMBAI_TESTNET_CONTRACT_ADDRESS, contract_creation_block: MUMBAI_TESTNET_CONTRACT_CREATION_BLOCK, - chain_family: ChainFamily::Polygon } - ) + ); + } + + #[test] + fn multinode_testnet_chain_record_is_properly_declared() { + let examined_chain = Chain::Dev; + let chain_record = return_examined(examined_chain); + assert_eq!( + chain_record, + &BlockchainRecord { + num_chain_id: 2, + self_id: examined_chain, + literal_identifier: "dev", + contract: MULTINODE_TESTNET_CONTRACT_ADDRESS, + contract_creation_block: 0, + } + ); } fn return_examined<'a>(chain: Chain) -> &'a BlockchainRecord { diff --git a/masq_lib/src/blockchains/chains.rs b/masq_lib/src/blockchains/chains.rs index be0d9f79f..ac3fbbac0 100644 --- a/masq_lib/src/blockchains/chains.rs +++ b/masq_lib/src/blockchains/chains.rs @@ -16,13 +16,6 @@ pub enum Chain { Dev, } -#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)] -pub enum ChainFamily { - Eth, - Polygon, - Dev, -} - impl Default for Chain { fn default() -> Self { DEFAULT_CHAIN @@ -143,7 +136,6 @@ mod tests { literal_identifier: "", contract: Default::default(), contract_creation_block: 0, - chain_family: ChainFamily::Polygon, } } diff --git a/masq_lib/src/lib.rs b/masq_lib/src/lib.rs index db70ff4cf..e5232b221 100644 --- a/masq_lib/src/lib.rs +++ b/masq_lib/src/lib.rs @@ -22,5 +22,6 @@ pub mod crash_point; pub mod data_version; pub mod shared_schema; pub mod test_utils; +pub mod type_obfuscation; pub mod ui_gateway; pub mod ui_traffic_converter; diff --git a/masq_lib/src/messages.rs b/masq_lib/src/messages.rs index e80cf0241..59522171e 100644 --- a/masq_lib/src/messages.rs +++ b/masq_lib/src/messages.rs @@ -4,6 +4,7 @@ use crate::messages::UiMessageError::{DeserializationError, PayloadError, Unexpe use crate::shared_schema::ConfiguratorError; use crate::ui_gateway::MessageBody; use crate::ui_gateway::MessagePath::{Conversation, FireAndForget}; +use crate::utils::to_string; use itertools::Itertools; use serde::de::DeserializeOwned; use serde_derive::{Deserialize, Serialize}; @@ -243,7 +244,7 @@ impl UiSetupRequest { .into_iter() .map(|(name, value)| UiSetupRequestValue { name: name.to_string(), - value: value.map(|v| v.to_string()), + value: value.map(to_string), }) .collect(), } @@ -777,7 +778,7 @@ conversation_message!(UiRecoverWalletsRequest, "recoverWallets"); pub struct UiRecoverWalletsResponse {} conversation_message!(UiRecoverWalletsResponse, "recoverWallets"); -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy, Hash)] pub enum ScanType { Payables, Receivables, diff --git a/masq_lib/src/multi_config.rs b/masq_lib/src/multi_config.rs index 8cc6574f5..ffc9cc434 100644 --- a/masq_lib/src/multi_config.rs +++ b/masq_lib/src/multi_config.rs @@ -501,6 +501,7 @@ pub mod tests { use super::*; use crate::test_utils::environment_guard::EnvironmentGuard; use crate::test_utils::utils::ensure_node_home_directory_exists; + use crate::utils::to_string; use clap::Arg; use std::fs::File; use std::io::Write; @@ -929,7 +930,7 @@ pub mod tests { "--other_takes_no_value", ] .into_iter() - .map(|s| s.to_string()) + .map(to_string) .collect(); let subject = CommandLineVcl::new(command_line.clone()); @@ -952,10 +953,7 @@ pub mod tests { #[test] #[should_panic(expected = "Expected option beginning with '--', not value")] fn command_line_vcl_panics_when_given_value_without_name() { - let command_line: Vec = vec!["", "value"] - .into_iter() - .map(|s| s.to_string()) - .collect(); + let command_line: Vec = vec!["", "value"].into_iter().map(to_string).collect(); CommandLineVcl::new(command_line.clone()); } diff --git a/masq_lib/src/shared_schema.rs b/masq_lib/src/shared_schema.rs index e3ef6b2bf..00cee6b7c 100644 --- a/masq_lib/src/shared_schema.rs +++ b/masq_lib/src/shared_schema.rs @@ -1,3 +1,5 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + use crate::constants::{ DEFAULT_GAS_PRICE, DEFAULT_UI_PORT, DEV_CHAIN_FULL_IDENTIFIER, ETH_MAINNET_FULL_IDENTIFIER, ETH_ROPSTEN_FULL_IDENTIFIER, HIGHEST_USABLE_PORT, LOWEST_USABLE_INSECURE_PORT, diff --git a/masq_lib/src/type_obfuscation.rs b/masq_lib/src/type_obfuscation.rs new file mode 100644 index 000000000..1f3c79258 --- /dev/null +++ b/masq_lib/src/type_obfuscation.rs @@ -0,0 +1,83 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use std::any::TypeId; +use std::mem::transmute; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Obfuscated { + type_id: TypeId, + bytes: Vec, +} + +impl Obfuscated { + // Although we're asking the compiler for a cast between two types + // where one is generic and both could possibly be of a different + // size, which almost applies to an unsupported kind of operation, + // the compiler stays calm here. The use of vectors at the input as + // well as output lets us avoid the above depicted situation. + // + // If you wish to write an implementation allowing more arbitrary + // types on your own, instead of helping yourself by a library like + // 'bytemuck', consider these functions from the std library, + // 'mem::transmute_copy' or 'mem::forget()', which will renew + // the compiler's trust for you. However, the true adventure will + // begin when you are supposed to write code to realign the plain + // bytes backwards to your desired type... + + pub fn obfuscate_vector(data: Vec) -> Obfuscated { + let bytes = unsafe { transmute::, Vec>(data) }; + + Obfuscated { + type_id: TypeId::of::(), + bytes, + } + } + + pub fn expose_vector(self) -> Vec { + if self.type_id != TypeId::of::() { + panic!("Forbidden! You're trying to interpret obfuscated data as the wrong type.") + } + + unsafe { transmute::, Vec>(self.bytes) } + } + + // Proper casting from a non vec structure into a vector of bytes + // is difficult and ideally requires an involvement of a library + // like bytemuck. + // If you think we do need such cast, place other methods in here + // and don't remove the ones above because: + // a) bytemuck will force you to implement its 'Pod' trait which + // might imply an (at minimum) ugly implementation for a std + // type like a Vec because both the trait and the type have + // their definitions situated externally to our project, + // therefore you might need to solve it by introducing + // a super-trait from our code + // b) using our simple 'obfuscate_vector' function will always + // be fairly more efficient than if done with help of + // the other library +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn obfuscation_works() { + let data = vec!["I'm fearing of losing my entire identity".to_string()]; + + let obfuscated_data = Obfuscated::obfuscate_vector(data.clone()); + let fenix_like_data: Vec = obfuscated_data.expose_vector(); + + assert_eq!(data, fenix_like_data) + } + + #[test] + #[should_panic( + expected = "Forbidden! You're trying to interpret obfuscated data as the wrong type." + )] + fn obfuscation_attempt_to_reinterpret_to_wrong_type() { + let data = vec![0_u64]; + let obfuscated_data = Obfuscated::obfuscate_vector(data.clone()); + let _: Vec = obfuscated_data.expose_vector(); + } +} diff --git a/masq_lib/src/utils.rs b/masq_lib/src/utils.rs index 672d3e5da..24dc6d320 100644 --- a/masq_lib/src/utils.rs +++ b/masq_lib/src/utils.rs @@ -321,10 +321,7 @@ pub fn exit_process_with_sigterm(message: &str) { } pub fn slice_of_strs_to_vec_of_strings(slice: &[&str]) -> Vec { - slice - .iter() - .map(|item| item.to_string()) - .collect::>() + slice.iter().map(to_string).collect::>() } pub trait ExpectValue { @@ -389,6 +386,14 @@ where fn helper_access(&mut self) -> &mut Option; } +// A handy function for closures +pub fn to_string(displayable: D) -> String +where + D: Display, +{ + displayable.to_string() +} + #[macro_export] macro_rules! short_writeln { ($dst:expr) => ( @@ -407,10 +412,10 @@ macro_rules! intentionally_blank { } #[macro_export] -macro_rules! declare_as_any { +macro_rules! as_any_in_trait { () => { #[cfg(test)] - fn as_any(&self) -> &dyn Any { + fn as_any(&self) -> &dyn std::any::Any { use masq_lib::intentionally_blank; intentionally_blank!() } @@ -418,15 +423,25 @@ macro_rules! declare_as_any { } #[macro_export] -macro_rules! implement_as_any { +macro_rules! as_any_in_trait_impl { () => { #[cfg(test)] - fn as_any(&self) -> &dyn Any { + fn as_any(&self) -> &dyn std::any::Any { self } }; } +#[macro_export] +macro_rules! test_only_use { + ($($use_clause: item),+) => { + $( + #[cfg(test)] + $use_clause + )+ + } +} + #[macro_export(local_inner_macros)] macro_rules! hashmap { () => { diff --git a/multinode_integration_tests/src/masq_node.rs b/multinode_integration_tests/src/masq_node.rs index 02e6d7cea..5f293828c 100644 --- a/multinode_integration_tests/src/masq_node.rs +++ b/multinode_integration_tests/src/masq_node.rs @@ -6,6 +6,7 @@ use masq_lib::constants::{ CENTRAL_DELIMITER, CHAIN_IDENTIFIER_DELIMITER, CURRENT_LOGFILE_NAME, HIGHEST_USABLE_PORT, MASQ_URL_PREFIX, }; +use masq_lib::utils::to_string; use node_lib::sub_lib::cryptde::{CryptDE, PublicKey}; use node_lib::sub_lib::cryptde_null::CryptDENull; use node_lib::sub_lib::neighborhood::{NodeDescriptor, RatePack}; @@ -81,7 +82,7 @@ impl fmt::Display for NodeReference { Some(node_addr) => node_addr .ports() .iter() - .map(|port| port.to_string()) + .map(to_string) .collect::>() .join(NodeAddr::PORTS_SEPARATOR), None => String::new(), diff --git a/multinode_integration_tests/src/masq_real_node.rs b/multinode_integration_tests/src/masq_real_node.rs index e458f0d64..77b82054c 100644 --- a/multinode_integration_tests/src/masq_real_node.rs +++ b/multinode_integration_tests/src/masq_real_node.rs @@ -12,7 +12,7 @@ use log::Level; use masq_lib::blockchains::chains::Chain; use masq_lib::constants::{CURRENT_LOGFILE_NAME, DEFAULT_UI_PORT}; use masq_lib::test_utils::utils::TEST_DEFAULT_MULTINODE_CHAIN; -use masq_lib::utils::localhost; +use masq_lib::utils::{localhost, to_string}; use masq_lib::utils::{DEFAULT_CONSUMING_DERIVATION_PATH, DEFAULT_EARNING_DERIVATION_PATH}; use node_lib::blockchain::bip32::Bip32EncryptionKeyProvider; use node_lib::neighborhood::DEFAULT_MIN_HOPS; @@ -260,7 +260,7 @@ impl NodeStartupConfig { } fn slices_to_strings(strs: Vec<&str>) -> Vec { - strs.into_iter().map(|x| x.to_string()).collect() + strs.into_iter().map(to_string).collect() } fn make_establish_wallet_args(&self) -> Option> { @@ -642,7 +642,7 @@ impl NodeStartupConfigBuilder { } pub fn db_password(mut self, value: Option<&str>) -> Self { - self.db_password = value.map(|str| str.to_string()); + self.db_password = value.map(to_string); self } diff --git a/multinode_integration_tests/tests/blockchain_interaction_test.rs b/multinode_integration_tests/tests/blockchain_interaction_test.rs index f3b632880..1eb6a3ca7 100644 --- a/multinode_integration_tests/tests/blockchain_interaction_test.rs +++ b/multinode_integration_tests/tests/blockchain_interaction_test.rs @@ -22,7 +22,7 @@ use multinode_integration_tests_lib::utils::{ config_dao, node_chain_specific_data_directory, open_all_file_permissions, receivable_dao, UrlHolder, }; -use node_lib::accountant::db_access_objects::dao_utils::CustomQuery; +use node_lib::accountant::db_access_objects::utils::CustomQuery; use node_lib::sub_lib::wallet::Wallet; #[test] diff --git a/multinode_integration_tests/tests/bookkeeping_test.rs b/multinode_integration_tests/tests/bookkeeping_test.rs index cd6bfe6ba..41642a724 100644 --- a/multinode_integration_tests/tests/bookkeeping_test.rs +++ b/multinode_integration_tests/tests/bookkeeping_test.rs @@ -6,9 +6,9 @@ use multinode_integration_tests_lib::masq_real_node::{ STANDARD_CLIENT_TIMEOUT_MILLIS, }; use multinode_integration_tests_lib::utils::{payable_dao, receivable_dao}; -use node_lib::accountant::db_access_objects::dao_utils::CustomQuery; use node_lib::accountant::db_access_objects::payable_dao::PayableAccount; use node_lib::accountant::db_access_objects::receivable_dao::ReceivableAccount; +use node_lib::accountant::db_access_objects::utils::CustomQuery; use node_lib::sub_lib::wallet::Wallet; use std::collections::HashMap; use std::thread; diff --git a/multinode_integration_tests/tests/verify_bill_payment.rs b/multinode_integration_tests/tests/verify_bill_payment.rs index 9689c0691..5e9b50347 100644 --- a/multinode_integration_tests/tests/verify_bill_payment.rs +++ b/multinode_integration_tests/tests/verify_bill_payment.rs @@ -16,9 +16,10 @@ use multinode_integration_tests_lib::utils::{ use node_lib::accountant::db_access_objects::payable_dao::{PayableDao, PayableDaoReal}; use node_lib::accountant::db_access_objects::receivable_dao::{ReceivableDao, ReceivableDaoReal}; use node_lib::blockchain::bip32::Bip32EncryptionKeyProvider; -use node_lib::blockchain::blockchain_interface::{ - BlockchainInterface, BlockchainInterfaceWeb3, REQUESTS_IN_PARALLEL, +use node_lib::blockchain::blockchain_interface::blockchain_interface_web3::{ + BlockchainInterfaceWeb3, REQUESTS_IN_PARALLEL, }; +use node_lib::blockchain::blockchain_interface::BlockchainInterface; use node_lib::database::db_initializer::{ DbInitializationConfig, DbInitializer, DbInitializerReal, ExternalData, }; @@ -47,7 +48,7 @@ fn verify_bill_payment() { blockchain_server.start(); blockchain_server.wait_until_ready(); let url = blockchain_server.url().to_string(); - let (_event_loop_handle, http) = Http::with_max_parallel(&url, REQUESTS_IN_PARALLEL).unwrap(); + let (event_loop_handle, http) = Http::with_max_parallel(&url, REQUESTS_IN_PARALLEL).unwrap(); let web3 = Web3::new(http.clone()); let deriv_path = derivation_path(0, 0); let seed = make_seed(); @@ -59,8 +60,7 @@ fn verify_bill_payment() { "Ganache is not as predictable as we thought: Update blockchain_interface::MULTINODE_CONTRACT_ADDRESS with {:?}", contract_addr ); - let blockchain_interface = - BlockchainInterfaceWeb3::new(http, _event_loop_handle, cluster.chain); + let blockchain_interface = BlockchainInterfaceWeb3::new(http, event_loop_handle, cluster.chain); assert_balances( &contract_owner_wallet, &blockchain_interface, @@ -324,6 +324,7 @@ fn assert_balances( expected_token_balance: &str, ) { let eth_balance = blockchain_interface + .lower_interface() .get_transaction_fee_balance(&wallet) .unwrap_or_else(|_| panic!("Failed to retrieve gas balance for {}", wallet)); assert_eq!( @@ -334,8 +335,9 @@ fn assert_balances( expected_eth_balance ); let token_balance = blockchain_interface - .get_token_balance(&wallet) - .unwrap_or_else(|_| panic!("Failed to retrieve token balance for {}", wallet)); + .lower_interface() + .get_service_fee_balance(&wallet) + .unwrap_or_else(|_| panic!("Failed to retrieve masq balance for {}", wallet)); assert_eq!( token_balance, web3::types::U256::from_dec_str(expected_token_balance).unwrap(), diff --git a/node/src/accountant/db_access_objects/banned_dao.rs b/node/src/accountant/db_access_objects/banned_dao.rs index 852bb3675..24b3ced81 100644 --- a/node/src/accountant/db_access_objects/banned_dao.rs +++ b/node/src/accountant/db_access_objects/banned_dao.rs @@ -1,5 +1,6 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::db_access_objects::dao_utils::{DaoFactoryReal, VigilantRusqliteFlatten}; + +use crate::accountant::db_access_objects::utils::{DaoFactoryReal, VigilantRusqliteFlatten}; use crate::database::connection_wrapper::ConnectionWrapper; use crate::sub_lib::wallet::Wallet; use lazy_static::lazy_static; diff --git a/node/src/accountant/db_access_objects/mod.rs b/node/src/accountant/db_access_objects/mod.rs index 7c4fc5ffb..a350148ab 100644 --- a/node/src/accountant/db_access_objects/mod.rs +++ b/node/src/accountant/db_access_objects/mod.rs @@ -1,7 +1,7 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. pub mod banned_dao; -pub mod dao_utils; pub mod payable_dao; pub mod pending_payable_dao; pub mod receivable_dao; +pub mod utils; diff --git a/node/src/accountant/db_access_objects/payable_dao.rs b/node/src/accountant/db_access_objects/payable_dao.rs index b3a3cbb19..16f72d5d4 100644 --- a/node/src/accountant/db_access_objects/payable_dao.rs +++ b/node/src/accountant/db_access_objects/payable_dao.rs @@ -10,8 +10,8 @@ use crate::accountant::db_big_integer::big_int_db_processor::{ BigIntDbProcessor, BigIntSqlConfig, Param, SQLParamsBuilder, TableNameDAO, }; use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; -use crate::accountant::db_access_objects::dao_utils; -use crate::accountant::db_access_objects::dao_utils::{ +use crate::accountant::db_access_objects::utils; +use crate::accountant::db_access_objects::utils::{ sum_i128_values_from_table, to_time_t, AssemblerFeeder, CustomQuery, DaoFactoryReal, RangeStmConfig, TopStmConfig, VigilantRusqliteFlatten, }; @@ -48,21 +48,6 @@ pub struct PayableAccount { pub pending_payable_opt: Option, } -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct PendingPayable { - pub recipient_wallet: Wallet, - pub hash: H256, -} - -impl PendingPayable { - pub fn new(recipient_wallet: Wallet, hash: H256) -> Self { - Self { - recipient_wallet, - hash, - } - } -} - pub trait PayableDao: Debug + Send { fn more_money_payable( &self, @@ -207,7 +192,7 @@ impl PayableDao for PayableDaoReal { balance_wei: checked_conversion::(BigIntDivider::reconstitute( high_b, low_b, )), - last_paid_timestamp: dao_utils::from_time_t(last_paid_timestamp), + last_paid_timestamp: utils::from_time_t(last_paid_timestamp), pending_payable_opt: None, }) } @@ -293,7 +278,7 @@ impl PayableDao for PayableDaoReal { balance_wei: checked_conversion::(BigIntDivider::reconstitute( high_bytes, low_bytes, )), - last_paid_timestamp: dao_utils::from_time_t(last_paid_timestamp), + last_paid_timestamp: utils::from_time_t(last_paid_timestamp), pending_payable_opt: match rowid { Some(rowid) => Some(PendingPayableId::new( u64::try_from(rowid).unwrap(), @@ -349,7 +334,7 @@ impl PayableDaoReal { balance_wei: checked_conversion::(BigIntDivider::reconstitute( high_bytes, low_bytes, )), - last_paid_timestamp: dao_utils::from_time_t(last_paid_timestamp), + last_paid_timestamp: utils::from_time_t(last_paid_timestamp), pending_payable_opt: rowid_opt.map(|rowid| { let hash_str = hash_opt.expect("database corrupt; missing hash but existing rowid"); @@ -402,10 +387,10 @@ impl TableNameDAO for PayableDaoReal { mod mark_pending_payable_associated_functions { use crate::accountant::comma_joined_stringifiable; - use crate::accountant::db_access_objects::dao_utils::{ + use crate::accountant::db_access_objects::payable_dao::PayableDaoError; + use crate::accountant::db_access_objects::utils::{ update_rows_and_return_valid_count, VigilantRusqliteFlatten, }; - use crate::accountant::db_access_objects::payable_dao::PayableDaoError; use crate::database::connection_wrapper::ConnectionWrapper; use crate::sub_lib::wallet::Wallet; use itertools::Itertools; @@ -552,7 +537,7 @@ mod mark_pending_payable_associated_functions { #[cfg(test)] mod tests { use super::*; - use crate::accountant::db_access_objects::dao_utils::{from_time_t, now_time_t, to_time_t}; + use crate::accountant::db_access_objects::utils::{from_time_t, now_time_t, to_time_t}; use crate::accountant::gwei_to_wei; use crate::accountant::db_access_objects::payable_dao::mark_pending_payable_associated_functions::explanatory_extension; use crate::accountant::test_utils::{ @@ -1585,7 +1570,7 @@ mod tests { let conn = DbInitializerReal::default() .initialize(&home_dir, DbInitializationConfig::test_default()) .unwrap(); - let timestamp = dao_utils::now_time_t(); + let timestamp = utils::now_time_t(); insert_payable_record_fn( &*conn, "0x1111111111111111111111111111111111111111", diff --git a/node/src/accountant/db_access_objects/pending_payable_dao.rs b/node/src/accountant/db_access_objects/pending_payable_dao.rs index 86a102452..a918c7175 100644 --- a/node/src/accountant/db_access_objects/pending_payable_dao.rs +++ b/node/src/accountant/db_access_objects/pending_payable_dao.rs @@ -1,12 +1,13 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::db_access_objects::dao_utils::{ +use crate::accountant::db_access_objects::utils::{ from_time_t, to_time_t, DaoFactoryReal, VigilantRusqliteFlatten, }; use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; use crate::accountant::{checked_conversion, comma_joined_stringifiable}; use crate::blockchain::blockchain_bridge::PendingPayableFingerprint; use crate::database::connection_wrapper::ConnectionWrapper; +use crate::sub_lib::wallet::Wallet; use itertools::Itertools; use masq_lib::utils::ExpectValue; use rusqlite::Row; @@ -209,13 +210,18 @@ impl PendingPayableDao for PendingPayableDaoReal<'_> { } } -pub trait PendingPayableDaoFactory { - fn make(&self) -> Box; +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PendingPayable { + pub recipient_wallet: Wallet, + pub hash: H256, } -impl PendingPayableDaoFactory for DaoFactoryReal { - fn make(&self) -> Box { - Box::new(PendingPayableDaoReal::new(self.make_connection())) +impl PendingPayable { + pub fn new(recipient_wallet: Wallet, hash: H256) -> Self { + Self { + recipient_wallet, + hash, + } } } @@ -238,13 +244,23 @@ impl<'a> PendingPayableDaoReal<'a> { } } +pub trait PendingPayableDaoFactory { + fn make(&self) -> Box; +} + +impl PendingPayableDaoFactory for DaoFactoryReal { + fn make(&self) -> Box { + Box::new(PendingPayableDaoReal::new(self.make_connection())) + } +} + #[cfg(test)] mod tests { use crate::accountant::checked_conversion; - use crate::accountant::db_access_objects::dao_utils::from_time_t; use crate::accountant::db_access_objects::pending_payable_dao::{ PendingPayableDao, PendingPayableDaoError, PendingPayableDaoReal, }; + use crate::accountant::db_access_objects::utils::from_time_t; use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; use crate::blockchain::blockchain_bridge::PendingPayableFingerprint; use crate::blockchain::test_utils::make_tx_hash; @@ -418,18 +434,16 @@ mod tests { let hash_2 = make_tx_hash(22229); let hash_3 = make_tx_hash(33339); let hash_4 = make_tx_hash(44449); - { - // For more illustrative results with the hash_3 linking to rowid 2 instead of the ambiguous 1 - subject - .insert_new_fingerprints(&[(hash_2, 8901234)], SystemTime::now()) - .unwrap(); - { - subject - .insert_new_fingerprints(&[(hash_3, 1234567)], SystemTime::now()) - .unwrap() - } - subject.delete_fingerprints(&[1]).unwrap() - } + // For more illustrative results, I use the official tooling but also generate one extra record before the chief one for + // this test, and in the end, I delete the first one. It leaves a single record still in but with the rowid 2 instead of + // just an ambiguous 1 + subject + .insert_new_fingerprints(&[(hash_2, 8901234)], SystemTime::now()) + .unwrap(); + subject + .insert_new_fingerprints(&[(hash_3, 1234567)], SystemTime::now()) + .unwrap(); + subject.delete_fingerprints(&[1]).unwrap(); let result = subject.fingerprints_rowids(&[hash_1, hash_2, hash_3, hash_4]); diff --git a/node/src/accountant/db_access_objects/receivable_dao.rs b/node/src/accountant/db_access_objects/receivable_dao.rs index 122362e88..14fe4b4e9 100644 --- a/node/src/accountant/db_access_objects/receivable_dao.rs +++ b/node/src/accountant/db_access_objects/receivable_dao.rs @@ -1,12 +1,12 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::accountant::checked_conversion; -use crate::accountant::db_access_objects::dao_utils; -use crate::accountant::db_access_objects::dao_utils::{ +use crate::accountant::db_access_objects::receivable_dao::ReceivableDaoError::RusqliteError; +use crate::accountant::db_access_objects::utils; +use crate::accountant::db_access_objects::utils::{ sum_i128_values_from_table, to_time_t, AssemblerFeeder, CustomQuery, DaoFactoryReal, RangeStmConfig, ThresholdUtils, TopStmConfig, VigilantRusqliteFlatten, }; -use crate::accountant::db_access_objects::receivable_dao::ReceivableDaoError::RusqliteError; use crate::accountant::db_big_integer::big_int_db_processor::KnownKeyVariants::WalletAddress; use crate::accountant::db_big_integer::big_int_db_processor::WeiChange::{Addition, Subtraction}; use crate::accountant::db_big_integer::big_int_db_processor::{ @@ -14,7 +14,7 @@ use crate::accountant::db_big_integer::big_int_db_processor::{ }; use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; use crate::accountant::gwei_to_wei; -use crate::blockchain::blockchain_interface::BlockchainTransaction; +use crate::blockchain::blockchain_interface::data_structures::BlockchainTransaction; use crate::database::connection_wrapper::ConnectionWrapper; use crate::database::db_initializer::{connection_or_panic, DbInitializerReal}; use crate::db_config::persistent_configuration::PersistentConfigError; @@ -29,8 +29,6 @@ use masq_lib::utils::{plus, ExpectValue}; use rusqlite::OptionalExtension; use rusqlite::Row; use rusqlite::{named_params, Error}; -#[cfg(test)] -use std::any::Any; use std::time::SystemTime; #[derive(Debug, PartialEq, Eq)] @@ -84,7 +82,7 @@ pub trait ReceivableDao: Send { //test-only-like method but because of share with multi-node tests #[cfg(test)] is disallowed fn account_status(&self, wallet: &Wallet) -> Option; - declare_as_any!(); + as_any_in_trait!(); } pub trait ReceivableDaoFactory { @@ -267,7 +265,7 @@ impl ReceivableDao for ReceivableDaoReal { } } - implement_as_any!(); + as_any_in_trait_impl!(); } impl ReceivableDaoReal { @@ -333,7 +331,7 @@ impl ReceivableDaoReal { Ok(ReceivableAccount { wallet, balance_wei: BigIntDivider::reconstitute(high_bytes, low_bytes), - last_received_timestamp: dao_utils::from_time_t(last_received_timestamp), + last_received_timestamp: utils::from_time_t(last_received_timestamp), }) } e => panic!( @@ -411,14 +409,17 @@ impl TableNameDAO for ReceivableDaoReal { #[cfg(test)] mod tests { use super::*; - use crate::accountant::db_access_objects::dao_utils::{from_time_t, now_time_t, to_time_t}; + use crate::accountant::db_access_objects::utils::{ + from_time_t, now_time_t, to_time_t, CustomQuery, + }; use crate::accountant::gwei_to_wei; use crate::accountant::test_utils::{ assert_account_creation_fn_fails_on_finding_wrong_columns_and_value_types, make_receivable_account, }; - use crate::database::db_initializer::{DbInitializationConfig, DbInitializer}; - use crate::database::db_initializer::{DbInitializerReal, ExternalData}; + use crate::database::db_initializer::{ + DbInitializationConfig, DbInitializer, DbInitializerReal, ExternalData, + }; use crate::db_config::persistent_configuration::PersistentConfigError; use crate::test_utils::assert_contains; use crate::test_utils::make_wallet; @@ -1434,7 +1435,7 @@ mod tests { .unwrap(); let insert = insert_account_by_separate_values; - let timestamp = dao_utils::now_time_t(); + let timestamp = utils::now_time_t(); insert( &*conn, "0x1111111111111111111111111111111111111111", diff --git a/node/src/accountant/db_access_objects/dao_utils.rs b/node/src/accountant/db_access_objects/utils.rs similarity index 99% rename from node/src/accountant/db_access_objects/dao_utils.rs rename to node/src/accountant/db_access_objects/utils.rs index 18c1a6eb4..85e5adade 100644 --- a/node/src/accountant/db_access_objects/dao_utils.rs +++ b/node/src/accountant/db_access_objects/utils.rs @@ -288,11 +288,11 @@ pub fn remap_receivable_accounts(accounts: Vec) -> Vec, + consuming_wallet_opt: Option, earning_wallet: Rc, payable_dao: Box, receivable_dao: Box, pending_payable_dao: Box, crashable: bool, scanners: Scanners, - scan_timings: ScanTimings, + scan_schedulers: ScanSchedulers, financial_statistics: Rc>, - report_accounts_payable_sub_opt: Option>, - request_balances_to_pay_payables_sub_opt: Option>, + outbound_payments_instructions_sub_opt: Option>, + qualified_payables_sub_opt: Option>, retrieve_transactions_sub_opt: Option>, request_transaction_receipts_subs_opt: Option>, report_inbound_payments_sub_opt: Option>, @@ -131,13 +132,6 @@ pub struct SentPayables { pub response_skeleton_opt: Option, } -#[derive(Debug, Message, PartialEq, Eq)] -pub struct ConsumingWalletBalancesAndQualifiedPayables { - pub qualified_payables: Vec, - pub consuming_wallet_balances: ConsumingWalletBalances, - pub response_skeleton_opt: Option, -} - #[derive(Debug, Message, Default, PartialEq, Eq, Clone, Copy)] pub struct ScanForPayables { pub response_skeleton_opt: Option, @@ -210,23 +204,15 @@ impl Handler for Accountant { } } -impl Handler for Accountant { +impl Handler for Accountant { type Result = (); fn handle( &mut self, - msg: ConsumingWalletBalancesAndQualifiedPayables, + msg: BlockchainAgentWithContextMessage, _ctx: &mut Self::Context, ) -> Self::Result { - //TODO GH-672 with PaymentAdjuster hasn't been implemented yet - self.report_accounts_payable_sub_opt - .as_ref() - .expect("BlockchainBridge is unbound") - .try_send(ReportAccountsPayable { - accounts: msg.qualified_payables, - response_skeleton_opt: msg.response_skeleton_opt, - }) - .expect("BlockchainBridge is dead") + self.handle_payable_payment_setup(msg) } } @@ -249,9 +235,7 @@ impl Handler for Accountant { fn handle(&mut self, msg: ScanForPayables, ctx: &mut Self::Context) -> Self::Result { self.handle_request_of_scan_for_payable(msg.response_skeleton_opt); - self.scan_timings - .payable - .schedule_another_periodic_scan(ctx); + self.schedule_next_scan(ScanType::Payables, ctx); } } @@ -260,9 +244,7 @@ impl Handler for Accountant { fn handle(&mut self, msg: ScanForPendingPayables, ctx: &mut Self::Context) -> Self::Result { self.handle_request_of_scan_for_pending_payable(msg.response_skeleton_opt); - self.scan_timings - .pending_payable - .schedule_another_periodic_scan(ctx); + self.schedule_next_scan(ScanType::PendingPayables, ctx); } } @@ -271,9 +253,7 @@ impl Handler for Accountant { fn handle(&mut self, msg: ScanForReceivables, ctx: &mut Self::Context) -> Self::Result { self.handle_request_of_scan_for_receivable(msg.response_skeleton_opt); - self.scan_timings - .receivable - .schedule_another_periodic_scan(ctx); + self.schedule_next_scan(ScanType::Receivables, ctx); } } @@ -442,17 +422,17 @@ impl Accountant { Accountant { suppress_initial_scans: config.suppress_initial_scans, - consuming_wallet: config.consuming_wallet_opt.clone(), + consuming_wallet_opt: config.consuming_wallet_opt.clone(), earning_wallet: Rc::clone(&earning_wallet), payable_dao, receivable_dao, pending_payable_dao, scanners, crashable: config.crash_point == CrashPoint::Message, - scan_timings: ScanTimings::new(scan_intervals), + scan_schedulers: ScanSchedulers::new(scan_intervals), financial_statistics: Rc::clone(&financial_statistics), - report_accounts_payable_sub_opt: None, - request_balances_to_pay_payables_sub_opt: None, + outbound_payments_instructions_sub_opt: None, + qualified_payables_sub_opt: None, report_sent_payables_sub_opt: None, retrieve_transactions_sub_opt: None, report_inbound_payments_sub_opt: None, @@ -470,10 +450,7 @@ impl Accountant { report_routing_service_provided: recipient!(addr, ReportRoutingServiceProvidedMessage), report_exit_service_provided: recipient!(addr, ReportExitServiceProvidedMessage), report_services_consumed: recipient!(addr, ReportServicesConsumedMessage), - report_consuming_wallet_balances_and_qualified_payables: recipient!( - addr, - ConsumingWalletBalancesAndQualifiedPayables - ), + report_payable_payments_setup: recipient!(addr, BlockchainAgentWithContextMessage), report_inbound_payments: recipient!(addr, ReceivedPayments), init_pending_payable_fingerprints: recipient!(addr, PendingPayableFingerprintSeeds), report_transaction_receipts: recipient!(addr, ReportTransactionReceipts), @@ -557,24 +534,24 @@ impl Accountant { } fn our_wallet(&self, wallet: &Wallet) -> bool { - match &self.consuming_wallet { + match &self.consuming_wallet_opt { Some(ref consuming) if consuming.address() == wallet.address() => true, _ => wallet.address() == self.earning_wallet.address(), } } fn handle_bind_message(&mut self, msg: BindMessage) { - self.report_accounts_payable_sub_opt = - Some(msg.peer_actors.blockchain_bridge.report_accounts_payable); + self.outbound_payments_instructions_sub_opt = Some( + msg.peer_actors + .blockchain_bridge + .outbound_payments_instructions, + ); self.retrieve_transactions_sub_opt = Some(msg.peer_actors.blockchain_bridge.retrieve_transactions); self.report_inbound_payments_sub_opt = Some(msg.peer_actors.accountant.report_inbound_payments); - self.request_balances_to_pay_payables_sub_opt = Some( - msg.peer_actors - .blockchain_bridge - .request_balances_to_pay_payables, - ); + self.qualified_payables_sub_opt = + Some(msg.peer_actors.blockchain_bridge.qualified_payables); self.report_sent_payables_sub_opt = Some(msg.peer_actors.accountant.report_sent_payments); self.ui_message_sub_opt = Some(msg.peer_actors.ui_gateway.node_to_ui_message_sub); self.request_transaction_receipts_subs_opt = Some( @@ -585,6 +562,14 @@ impl Accountant { info!(self.logger, "Accountant bound"); } + fn schedule_next_scan(&self, scan_type: ScanType, ctx: &mut Context) { + self.scan_schedulers + .schedulers + .get(&scan_type) + .unwrap_or_else(|| panic!("Scan Scheduler {:?} not properly prepared", scan_type)) + .schedule(ctx) + } + fn handle_report_routing_service_provided_message( &mut self, msg: ReportRoutingServiceProvidedMessage, @@ -665,6 +650,29 @@ impl Accountant { }) } + fn handle_payable_payment_setup(&mut self, msg: BlockchainAgentWithContextMessage) { + let blockchain_bridge_instructions = match self + .scanners + .payable + .try_skipping_payment_adjustment(msg, &self.logger) + { + Ok(Either::Left(finalized_msg)) => finalized_msg, + Ok(Either::Right(unaccepted_msg)) => { + //TODO we will eventually query info from Neighborhood before the adjustment, according to GH-699 + self.scanners + .payable + .perform_payment_adjustment(unaccepted_msg, &self.logger) + } + Err(_e) => todo!("be completed by GH-711"), + }; + self.outbound_payments_instructions_sub_opt + .as_ref() + .expect("BlockchainBridge is unbound") + .try_send(blockchain_bridge_instructions) + .expect("BlockchainBridge is dead") + //TODO implement send point for ScanError; be completed by GH-711 + } + fn handle_financials(&self, msg: &UiFinancialsRequest, client_id: u64, context_id: u64) { let body: MessageBody = self.compute_financials(msg, context_id); self.ui_message_sub_opt @@ -803,7 +811,7 @@ impl Accountant { &self.logger, ) { Ok(scan_message) => { - self.request_balances_to_pay_payables_sub_opt + self.qualified_payables_sub_opt .as_ref() .expect("BlockchainBridge is unbound") .try_send(scan_message) @@ -973,68 +981,46 @@ pub fn wei_to_gwei, S: Display + Copy + Div + From(wei.div(S::from(WEIS_IN_GWEI as u32))) } -#[cfg(test)] -pub mod check_sqlite_fns { - use super::*; - use crate::sub_lib::accountant::DEFAULT_PAYMENT_THRESHOLDS; - use actix::System; - - #[derive(Message)] - pub struct TestUserDefinedSqliteFnsForNewDelinquencies {} - - impl Handler for Accountant { - type Result = (); - - fn handle( - &mut self, - _msg: TestUserDefinedSqliteFnsForNewDelinquencies, - _ctx: &mut Self::Context, - ) -> Self::Result { - //will crash a test if our user-defined SQLite fns have been unregistered - self.receivable_dao - .new_delinquencies(SystemTime::now(), &DEFAULT_PAYMENT_THRESHOLDS); - System::current().stop(); - } - } -} - #[cfg(test)] mod tests { use super::*; - use crate::accountant::db_access_objects::dao_utils::from_time_t; - use crate::accountant::db_access_objects::dao_utils::{to_time_t, CustomQuery}; use crate::accountant::db_access_objects::payable_dao::{ - PayableAccount, PayableDaoError, PayableDaoFactory, PendingPayable, + PayableAccount, PayableDaoError, PayableDaoFactory, + }; + use crate::accountant::db_access_objects::pending_payable_dao::{ + PendingPayable, PendingPayableDaoError, }; - use crate::accountant::db_access_objects::pending_payable_dao::PendingPayableDaoError; use crate::accountant::db_access_objects::receivable_dao::ReceivableAccount; - use crate::accountant::scanners::{BeginScanError, NullScanner, ScannerMock}; + use crate::accountant::db_access_objects::utils::{from_time_t, to_time_t, CustomQuery}; + use crate::accountant::payment_adjuster::Adjustment; + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::test_utils::BlockchainAgentMock; + use crate::accountant::scanners::test_utils::protect_payables_in_test; + use crate::accountant::scanners::BeginScanError; use crate::accountant::test_utils::DaoWithDestination::{ ForAccountantBody, ForPayableScanner, ForPendingPayableScanner, ForReceivableScanner, }; use crate::accountant::test_utils::{ bc_from_earning_wallet, bc_from_wallets, make_payable_account, make_payables, - BannedDaoFactoryMock, MessageIdGeneratorMock, PayableDaoFactoryMock, PayableDaoMock, - PendingPayableDaoFactoryMock, PendingPayableDaoMock, ReceivableDaoFactoryMock, - ReceivableDaoMock, + BannedDaoFactoryMock, MessageIdGeneratorMock, NullScanner, PayableDaoFactoryMock, + PayableDaoMock, PayableScannerBuilder, PaymentAdjusterMock, PendingPayableDaoFactoryMock, + PendingPayableDaoMock, ReceivableDaoFactoryMock, ReceivableDaoMock, ScannerMock, }; use crate::accountant::test_utils::{AccountantBuilder, BannedDaoMock}; use crate::accountant::Accountant; use crate::blockchain::blockchain_bridge::BlockchainBridge; - use crate::blockchain::blockchain_interface::BlockchainTransaction; - use crate::blockchain::blockchain_interface::ProcessedPayableFallible::Correct; use crate::blockchain::test_utils::{make_tx_hash, BlockchainInterfaceMock}; use crate::match_every_type_id; use crate::sub_lib::accountant::{ ExitServiceConsumed, PaymentThresholds, RoutingServiceConsumed, ScanIntervals, - DEFAULT_PAYMENT_THRESHOLDS, + DEFAULT_EARNING_WALLET, DEFAULT_PAYMENT_THRESHOLDS, }; - use crate::sub_lib::utils::NotifyLaterHandleReal; + use crate::sub_lib::blockchain_bridge::OutboundPaymentsInstructions; use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMock; 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_stop_conditions::{StopCondition, StopConditions}; + use crate::test_utils::unshared_test_utils::arbitrary_id_stamp::ArbitraryIdStamp; use crate::test_utils::unshared_test_utils::notify_handlers::NotifyLaterHandleMock; use crate::test_utils::unshared_test_utils::system_killer_actor::SystemKillerActor; use crate::test_utils::unshared_test_utils::{ @@ -1067,7 +1053,7 @@ mod tests { use std::sync::Mutex; use std::time::Duration; use std::vec; - use web3::types::{TransactionReceipt, U256}; + use web3::types::TransactionReceipt; impl Handler> for Accountant { type Result = (); @@ -1171,24 +1157,33 @@ mod tests { ); let financial_statistics = result.financial_statistics().clone(); - let scan_timings = result.scan_timings; - scan_timings - .pending_payable - .handle - .as_any() - .downcast_ref::>() - .unwrap(); - scan_timings - .payable - .handle - .as_any() - .downcast_ref::>() - .unwrap(); - scan_timings - .receivable - .handle - .as_any() - .downcast_ref::>(); + let assert_scan_scheduler = |scan_type: ScanType, expected_scan_interval: Duration| { + assert_eq!( + result + .scan_schedulers + .schedulers + .get(&scan_type) + .unwrap() + .interval(), + expected_scan_interval + ) + }; + let default_scan_intervals = ScanIntervals::default(); + assert_scan_scheduler( + ScanType::Payables, + default_scan_intervals.payable_scan_interval, + ); + assert_scan_scheduler( + ScanType::PendingPayables, + default_scan_intervals.pending_payable_scan_interval, + ); + assert_scan_scheduler( + ScanType::Receivables, + default_scan_intervals.receivable_scan_interval, + ); + assert_eq!(result.consuming_wallet_opt, None); + assert_eq!(*result.earning_wallet, *DEFAULT_EARNING_WALLET); + assert_eq!(result.suppress_initial_scans, false); result .message_id_generator .as_any() @@ -1324,13 +1319,13 @@ mod tests { system.run(); let blockchain_bridge_recording = blockchain_bridge_recording_arc.lock().unwrap(); assert_eq!( - blockchain_bridge_recording.get_record::(0), - &RequestBalancesToPayPayables { - accounts: vec![payable_account], + blockchain_bridge_recording.get_record::(0), + &QualifiedPayablesMessage { + protected_qualified_payables: protect_payables_in_test(vec![payable_account]), response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, context_id: 4321, - }), + }) } ); } @@ -1352,7 +1347,7 @@ mod tests { let peer_actors = peer_actors_builder().ui_gateway(ui_gateway).build(); subject_addr.try_send(BindMessage { peer_actors }).unwrap(); let sent_payable = SentPayables { - payment_procedure_result: Ok(vec![Correct(PendingPayable { + payment_procedure_result: Ok(vec![Ok(PendingPayable { recipient_wallet: make_wallet("blah"), hash: make_tx_hash(123), })]), @@ -1377,49 +1372,209 @@ mod tests { } #[test] - fn received_balances_and_qualified_payables_considered_feasible_payments_thus_all_forwarded_to_blockchain_bridge( + fn received_balances_and_qualified_payables_under_our_money_limit_thus_all_forwarded_to_blockchain_bridge( ) { + // the numbers for balances don't do real math, they need not to match either the condition for + // the payment adjustment or the actual values that come from the payable size reducing algorithm; + // all that is mocked in this test + init_test_logging(); + let test_name = "received_balances_and_qualified_payables_under_our_money_limit_thus_all_forwarded_to_blockchain_bridge"; + let is_adjustment_required_params_arc = Arc::new(Mutex::new(vec![])); + let (blockchain_bridge, _, blockchain_bridge_recording_arc) = make_recorder(); + let instructions_recipient = blockchain_bridge + .system_stop_conditions(match_every_type_id!(OutboundPaymentsInstructions)) + .start() + .recipient(); let mut subject = AccountantBuilder::default().build(); + let payment_adjuster = PaymentAdjusterMock::default() + .is_adjustment_required_params(&is_adjustment_required_params_arc) + .is_adjustment_required_result(Ok(None)); + let payable_scanner = PayableScannerBuilder::new() + .payment_adjuster(payment_adjuster) + .build(); + subject.scanners.payable = Box::new(payable_scanner); + subject.outbound_payments_instructions_sub_opt = Some(instructions_recipient); + subject.logger = Logger::new(test_name); + let subject_addr = subject.start(); + let account_1 = make_payable_account(44_444); + let account_2 = make_payable_account(333_333); + let system = System::new("test"); + let agent_id_stamp = ArbitraryIdStamp::new(); + let agent = BlockchainAgentMock::default().set_arbitrary_id_stamp(agent_id_stamp); + let accounts = vec![account_1, account_2]; + let msg = BlockchainAgentWithContextMessage { + protected_qualified_payables: protect_payables_in_test(accounts.clone()), + agent: Box::new(agent), + response_skeleton_opt: Some(ResponseSkeleton { + client_id: 1234, + context_id: 4321, + }), + }; + + subject_addr.try_send(msg).unwrap(); + + system.run(); + let mut is_adjustment_required_params = is_adjustment_required_params_arc.lock().unwrap(); + let (blockchain_agent_with_context_msg_actual, logger_clone) = + is_adjustment_required_params.remove(0); + assert_eq!( + blockchain_agent_with_context_msg_actual.protected_qualified_payables, + protect_payables_in_test(accounts.clone()) + ); + assert_eq!( + blockchain_agent_with_context_msg_actual.response_skeleton_opt, + Some(ResponseSkeleton { + client_id: 1234, + context_id: 4321, + }) + ); + assert_eq!( + blockchain_agent_with_context_msg_actual + .agent + .arbitrary_id_stamp(), + agent_id_stamp + ); + assert!(is_adjustment_required_params.is_empty()); + let blockchain_bridge_recording = blockchain_bridge_recording_arc.lock().unwrap(); + let payments_instructions = + blockchain_bridge_recording.get_record::(0); + assert_eq!(payments_instructions.affordable_accounts, accounts); + assert_eq!( + payments_instructions.response_skeleton_opt, + Some(ResponseSkeleton { + client_id: 1234, + context_id: 4321, + }) + ); + assert_eq!( + payments_instructions.agent.arbitrary_id_stamp(), + agent_id_stamp + ); + assert_eq!(blockchain_bridge_recording.len(), 1); + test_use_of_the_same_logger(&logger_clone, test_name) + // adjust_payments() did not need a prepared result which means it wasn't reached + // because otherwise this test would've panicked + } + + fn test_use_of_the_same_logger(logger_clone: &Logger, test_name: &str) { + let experiment_msg = format!("DEBUG: {test_name}: hello world"); + let log_handler = TestLogHandler::default(); + log_handler.exists_no_log_containing(&experiment_msg); + debug!(logger_clone, "hello world"); + log_handler.exists_log_containing(&experiment_msg); + } + + #[test] + fn received_qualified_payables_exceeding_our_masq_balance_are_adjusted_before_forwarded_to_blockchain_bridge( + ) { + // the numbers for balances don't do real math, they need not to match either the condition for + // the payment adjustment or the actual values that come from the payable size reducing algorithm; + // all that is mocked in this test + init_test_logging(); + let test_name = "received_qualified_payables_exceeding_our_masq_balance_are_adjusted_before_forwarded_to_blockchain_bridge"; + let adjust_payments_params_arc = Arc::new(Mutex::new(vec![])); let (blockchain_bridge, _, blockchain_bridge_recording_arc) = make_recorder(); let report_recipient = blockchain_bridge - .system_stop_conditions(match_every_type_id!(ReportAccountsPayable)) + .system_stop_conditions(match_every_type_id!(OutboundPaymentsInstructions)) .start() .recipient(); - subject.report_accounts_payable_sub_opt = Some(report_recipient); + let mut subject = AccountantBuilder::default().build(); + let unadjusted_account_1 = make_payable_account(111_111); + let unadjusted_account_2 = make_payable_account(222_222); + let adjusted_account_1 = PayableAccount { + balance_wei: gwei_to_wei(55_550_u64), + ..unadjusted_account_1.clone() + }; + let adjusted_account_2 = PayableAccount { + balance_wei: gwei_to_wei(100_000_u64), + ..unadjusted_account_2.clone() + }; + let response_skeleton = ResponseSkeleton { + client_id: 12, + context_id: 55, + }; + let agent_id_stamp_first_phase = ArbitraryIdStamp::new(); + let agent = + BlockchainAgentMock::default().set_arbitrary_id_stamp(agent_id_stamp_first_phase); + let payable_payments_setup_msg = BlockchainAgentWithContextMessage { + protected_qualified_payables: protect_payables_in_test(vec![ + unadjusted_account_1.clone(), + unadjusted_account_2.clone(), + ]), + agent: Box::new(agent), + response_skeleton_opt: Some(response_skeleton), + }; + // In the real world the agents are identical, here they bear different ids + // so that we can watch their journey better + let agent_id_stamp_second_phase = ArbitraryIdStamp::new(); + let agent = + BlockchainAgentMock::default().set_arbitrary_id_stamp(agent_id_stamp_second_phase); + let affordable_accounts = vec![adjusted_account_1.clone(), adjusted_account_2.clone()]; + let payments_instructions = OutboundPaymentsInstructions { + affordable_accounts: affordable_accounts.clone(), + agent: Box::new(agent), + response_skeleton_opt: Some(response_skeleton), + }; + let payment_adjuster = PaymentAdjusterMock::default() + .is_adjustment_required_result(Ok(Some(Adjustment::MasqToken))) + .adjust_payments_params(&adjust_payments_params_arc) + .adjust_payments_result(payments_instructions); + let payable_scanner = PayableScannerBuilder::new() + .payment_adjuster(payment_adjuster) + .build(); + subject.scanners.payable = Box::new(payable_scanner); + subject.outbound_payments_instructions_sub_opt = Some(report_recipient); + subject.logger = Logger::new(test_name); let subject_addr = subject.start(); - let half_of_u32_max_in_wei = u32::MAX as u64 / (2 * WEIS_IN_GWEI as u64); - let account_1 = make_payable_account(half_of_u32_max_in_wei); - let account_2 = account_1.clone(); let system = System::new("test"); - let consuming_balances_and_qualified_payments = - ConsumingWalletBalancesAndQualifiedPayables { - qualified_payables: vec![account_1.clone(), account_2.clone()], - consuming_wallet_balances: ConsumingWalletBalances { - gas_currency: U256::from(u32::MAX), - masq_tokens: U256::from(u32::MAX), - }, - response_skeleton_opt: Some(ResponseSkeleton { - client_id: 1234, - context_id: 4321, - }), - }; - subject_addr - .try_send(consuming_balances_and_qualified_payments) - .unwrap(); + subject_addr.try_send(payable_payments_setup_msg).unwrap(); - system.run(); + let before = SystemTime::now(); + assert_eq!(system.run(), 0); + let after = SystemTime::now(); + let mut adjust_payments_params = adjust_payments_params_arc.lock().unwrap(); + let (actual_prepared_adjustment, captured_now, logger_clone) = + adjust_payments_params.remove(0); + assert_eq!(actual_prepared_adjustment.adjustment, Adjustment::MasqToken); + assert_eq!( + actual_prepared_adjustment + .original_setup_msg + .protected_qualified_payables, + protect_payables_in_test(affordable_accounts.clone()) + ); + assert_eq!( + actual_prepared_adjustment + .original_setup_msg + .agent + .arbitrary_id_stamp(), + agent_id_stamp_first_phase + ); + assert!( + before <= captured_now && captured_now <= after, + "captured timestamp should have been between {:?} and {:?} but was {:?}", + before, + after, + captured_now + ); + assert!(adjust_payments_params.is_empty()); let blockchain_bridge_recording = blockchain_bridge_recording_arc.lock().unwrap(); + let payments_instructions = + blockchain_bridge_recording.get_record::(0); assert_eq!( - blockchain_bridge_recording.get_record::(0), - &ReportAccountsPayable { - accounts: vec![account_1, account_2], - response_skeleton_opt: Some(ResponseSkeleton { - client_id: 1234, - context_id: 4321, - }) - } + payments_instructions.affordable_accounts, + affordable_accounts + ); + assert_eq!( + payments_instructions.response_skeleton_opt, + Some(response_skeleton) + ); + assert_eq!( + payments_instructions.agent.arbitrary_id_stamp(), + agent_id_stamp_second_phase ); + assert_eq!(blockchain_bridge_recording.len(), 1); + test_use_of_the_same_logger(&logger_clone, test_name) } #[test] @@ -1591,7 +1746,7 @@ mod tests { .build(); let expected_payable = PendingPayable::new(expected_wallet.clone(), expected_hash.clone()); let sent_payable = SentPayables { - payment_procedure_result: Ok(vec![Correct(expected_payable.clone())]), + payment_procedure_result: Ok(vec![Ok(expected_payable.clone())]), response_skeleton_opt: None, }; let subject = accountant.start(); @@ -1613,8 +1768,7 @@ mod tests { } #[test] - fn accountant_sends_asks_blockchain_bridge_about_consuming_wallet_balances_when_qualified_payable_found( - ) { + fn accountant_sends_initial_payable_payments_msg_when_qualified_payable_found() { let (blockchain_bridge, _, blockchain_bridge_recording_arc) = make_recorder(); let now = SystemTime::now(); let payment_thresholds = PaymentThresholds::default(); @@ -1622,7 +1776,9 @@ mod tests { make_payables(now, &payment_thresholds); let payable_dao = PayableDaoMock::new().non_pending_payables_result(all_non_pending_payables); - let system = System::new("request for balances forwarded to blockchain_bridge"); + let system = System::new( + "accountant_sends_initial_payable_payments_msg_when_qualified_payable_found", + ); let mut subject = AccountantBuilder::default() .bootstrapper_config(bc_from_earning_wallet(make_wallet("some_wallet_address"))) .payable_daos(vec![ForPayableScanner(payable_dao)]) @@ -1642,24 +1798,23 @@ mod tests { system.run(); let blockchain_bridge_recorder = blockchain_bridge_recording_arc.lock().unwrap(); assert_eq!(blockchain_bridge_recorder.len(), 1); - let message = blockchain_bridge_recorder.get_record::(0); + let message = blockchain_bridge_recorder.get_record::(0); assert_eq!( message, - &RequestBalancesToPayPayables { - accounts: qualified_payables, + &QualifiedPayablesMessage { + protected_qualified_payables: protect_payables_in_test(qualified_payables), response_skeleton_opt: None, } ); } #[test] - fn accountant_sends_request_to_blockchain_bridge_to_scan_for_received_payments() { + fn accountant_requests_blockchain_bridge_to_scan_for_received_payments() { init_test_logging(); let (blockchain_bridge, _, blockchain_bridge_recording_arc) = make_recorder(); let earning_wallet = make_wallet("someearningwallet"); - let system = System::new( - "accountant_sends_request_to_blockchain_bridge_to_scan_for_received_payments", - ); + let system = + System::new("accountant_requests_blockchain_bridge_to_scan_for_received_payments"); let receivable_dao = ReceivableDaoMock::new() .new_delinquencies_result(vec![]) .paid_delinquencies_result(vec![]); @@ -1694,7 +1849,7 @@ mod tests { } #[test] - fn accountant_processes_msg_with_received_payments_using_receivables_dao() { + fn accountant_uses_receivables_dao_to_process_received_payments() { let now = SystemTime::now(); let earning_wallet = make_wallet("earner3000"); let expected_receivable_1 = BlockchainTransaction { @@ -1715,8 +1870,7 @@ mod tests { .bootstrapper_config(bc_from_earning_wallet(earning_wallet.clone())) .receivable_daos(vec![ForReceivableScanner(receivable_dao)]) .build(); - let system = - System::new("accountant_processes_msg_with_received_payments_using_receivables_dao"); + let system = System::new("accountant_uses_receivables_dao_to_process_received_payments"); let subject = accountant.start(); subject @@ -1816,7 +1970,7 @@ mod tests { recipient: make_wallet("some_recipient"), response_skeleton_opt: None, })) - .stop_the_system(); + .stop_the_system_after_last_msg(); let mut config = make_bc_with_defaults(); config.scan_intervals_opt = Some(ScanIntervals { payable_scan_interval: Duration::from_secs(100), @@ -1830,10 +1984,14 @@ mod tests { subject.scanners.payable = Box::new(NullScanner::new()); // Skipping subject.scanners.pending_payable = Box::new(NullScanner::new()); // Skipping subject.scanners.receivable = Box::new(receivable_scanner); - subject.scan_timings.receivable.handle = Box::new( - NotifyLaterHandleMock::default() - .notify_later_params(¬ify_later_receivable_params_arc) - .permit_to_send_out(), + subject.scan_schedulers.update_scheduler( + ScanType::Receivables, + Some(Box::new( + NotifyLaterHandleMock::default() + .notify_later_params(¬ify_later_receivable_params_arc) + .capture_msg_and_let_it_fly_on(), + )), + None, ); let subject_addr = subject.start(); let subject_subs = Accountant::make_subs_from(&subject_addr); @@ -1883,7 +2041,7 @@ mod tests { pending_payable: vec![], response_skeleton_opt: None, })) - .stop_the_system(); + .stop_the_system_after_last_msg(); let mut config = make_bc_with_defaults(); config.scan_intervals_opt = Some(ScanIntervals { payable_scan_interval: Duration::from_secs(100), @@ -1897,10 +2055,14 @@ mod tests { subject.scanners.payable = Box::new(NullScanner::new()); //skipping subject.scanners.pending_payable = Box::new(pending_payable_scanner); subject.scanners.receivable = Box::new(NullScanner::new()); //skipping - subject.scan_timings.pending_payable.handle = Box::new( - NotifyLaterHandleMock::default() - .notify_later_params(¬ify_later_pending_payable_params_arc) - .permit_to_send_out(), + subject.scan_schedulers.update_scheduler( + ScanType::PendingPayables, + Some(Box::new( + NotifyLaterHandleMock::default() + .notify_later_params(¬ify_later_pending_payable_params_arc) + .capture_msg_and_let_it_fly_on(), + )), + None, ); let subject_addr: Addr = subject.start(); let subject_subs = Accountant::make_subs_from(&subject_addr); @@ -1947,11 +2109,13 @@ mod tests { let payable_scanner = ScannerMock::new() .begin_scan_params(&begin_scan_params_arc) .begin_scan_result(Err(BeginScanError::NothingToProcess)) - .begin_scan_result(Ok(RequestBalancesToPayPayables { - accounts: vec![], + .begin_scan_result(Ok(QualifiedPayablesMessage { + protected_qualified_payables: protect_payables_in_test(vec![make_payable_account( + 123, + )]), response_skeleton_opt: None, })) - .stop_the_system(); + .stop_the_system_after_last_msg(); let mut config = bc_from_earning_wallet(make_wallet("hi")); config.scan_intervals_opt = Some(ScanIntervals { payable_scan_interval: Duration::from_millis(97), @@ -1965,10 +2129,14 @@ mod tests { subject.scanners.payable = Box::new(payable_scanner); subject.scanners.pending_payable = Box::new(NullScanner::new()); //skipping subject.scanners.receivable = Box::new(NullScanner::new()); //skipping - subject.scan_timings.payable.handle = Box::new( - NotifyLaterHandleMock::default() - .notify_later_params(¬ify_later_payables_params_arc) - .permit_to_send_out(), + subject.scan_schedulers.update_scheduler( + ScanType::Payables, + Some(Box::new( + NotifyLaterHandleMock::default() + .notify_later_params(¬ify_later_payables_params_arc) + .capture_msg_and_let_it_fly_on(), + )), + None, ); let subject_addr = subject.start(); let subject_subs = Accountant::make_subs_from(&subject_addr); @@ -2091,13 +2259,13 @@ mod tests { "scan_for_payable_message_does_not_trigger_payment_for_balances_below_the_curve", ); let blockchain_bridge_addr: Addr = blockchain_bridge.start(); - let report_accounts_payable_sub = - blockchain_bridge_addr.recipient::(); + let outbound_payments_instructions_sub = + blockchain_bridge_addr.recipient::(); let mut subject = AccountantBuilder::default() .bootstrapper_config(config) .payable_daos(vec![ForPayableScanner(payable_dao)]) .build(); - subject.report_accounts_payable_sub_opt = Some(report_accounts_payable_sub); + subject.outbound_payments_instructions_sub_opt = Some(outbound_payments_instructions_sub); let _result = subject .scanners @@ -2120,7 +2288,7 @@ mod tests { receivable_scan_interval: Duration::from_secs(50_000), }); let now = to_time_t(SystemTime::now()); - let accounts = vec![ + let qualified_payables = vec![ // slightly above minimum balance, to the right of the curve (time intersection) PayableAccount { wallet: make_wallet("wallet0"), @@ -2148,10 +2316,11 @@ mod tests { pending_payable_opt: None, }, ]; - let payable_dao = PayableDaoMock::default().non_pending_payables_result(accounts.clone()); + let payable_dao = + PayableDaoMock::default().non_pending_payables_result(qualified_payables.clone()); let (blockchain_bridge, _, blockchain_bridge_recordings_arc) = make_recorder(); let blockchain_bridge = blockchain_bridge - .system_stop_conditions(match_every_type_id!(RequestBalancesToPayPayables)); + .system_stop_conditions(match_every_type_id!(QualifiedPayablesMessage)); let system = System::new("scan_for_payable_message_triggers_payment_for_balances_over_the_curve"); let peer_actors = peer_actors_builder() @@ -2171,11 +2340,11 @@ mod tests { system.run(); let blockchain_bridge_recordings = blockchain_bridge_recordings_arc.lock().unwrap(); - let message = blockchain_bridge_recordings.get_record::(0); + let message = blockchain_bridge_recordings.get_record::(0); assert_eq!( message, - &RequestBalancesToPayPayables { - accounts, + &QualifiedPayablesMessage { + protected_qualified_payables: protect_payables_in_test(qualified_payables), response_skeleton_opt: None, } ); @@ -2189,11 +2358,11 @@ mod tests { let (blockchain_bridge, _, blockchain_bridge_recording) = make_recorder(); let blockchain_bridge_addr = blockchain_bridge .system_stop_conditions(match_every_type_id!( - RequestBalancesToPayPayables, - RequestBalancesToPayPayables + QualifiedPayablesMessage, + QualifiedPayablesMessage )) .start(); - let request_balances_to_pay_payables_sub = blockchain_bridge_addr.clone().recipient(); + let pps_for_blockchain_bridge_sub = blockchain_bridge_addr.clone().recipient(); let last_paid_timestamp = to_time_t(SystemTime::now()) - DEFAULT_PAYMENT_THRESHOLDS.maturity_threshold_sec as i64 - 1; @@ -2225,8 +2394,7 @@ mod tests { context_id: 444, }), }; - subject.request_balances_to_pay_payables_sub_opt = - Some(request_balances_to_pay_payables_sub); + subject.qualified_payables_sub_opt = Some(pps_for_blockchain_bridge_sub); let addr = subject.start(); addr.try_send(message_before.clone()).unwrap(); @@ -2252,12 +2420,12 @@ mod tests { let recording = blockchain_bridge_recording.lock().unwrap(); let messages_received = recording.len(); assert_eq!(messages_received, 2); - let first_message: &RequestBalancesToPayPayables = recording.get_record(0); + let first_message: &QualifiedPayablesMessage = recording.get_record(0); assert_eq!( first_message.response_skeleton_opt, message_before.response_skeleton_opt ); - let second_message: &RequestBalancesToPayPayables = recording.get_record(1); + let second_message: &QualifiedPayablesMessage = recording.get_record(1); assert_eq!( second_message.response_skeleton_opt, message_after.response_skeleton_opt @@ -2958,6 +3126,7 @@ mod tests { #[test] fn pending_transaction_is_registered_and_monitored_until_it_gets_confirmed_or_canceled() { init_test_logging(); + let build_blockchain_agent_params = Arc::new(Mutex::new(vec![])); let mark_pending_payable_params_arc = Arc::new(Mutex::new(vec![])); let transactions_confirmed_params_arc = Arc::new(Mutex::new(vec![])); let get_transaction_receipt_params_arc = Arc::new(Mutex::new(vec![])); @@ -2968,7 +3137,7 @@ mod tests { let delete_record_params_arc = Arc::new(Mutex::new(vec![])); let notify_later_scan_for_pending_payable_params_arc = Arc::new(Mutex::new(vec![])); let notify_later_scan_for_pending_payable_arc_cloned = - notify_later_scan_for_pending_payable_params_arc.clone(); //because it moves into a closure + notify_later_scan_for_pending_payable_params_arc.clone(); // because it moves into a closure let pending_tx_hash_1 = make_tx_hash(0x7b); let pending_tx_hash_2 = make_tx_hash(0x237); let rowid_for_account_1 = 3; @@ -2992,24 +3161,23 @@ mod tests { let transaction_receipt_tx_1_second_round = TransactionReceipt::default(); let transaction_receipt_tx_2_second_round = TransactionReceipt::default(); let mut transaction_receipt_tx_1_third_round = TransactionReceipt::default(); - transaction_receipt_tx_1_third_round.status = Some(U64::from(0)); //failure + transaction_receipt_tx_1_third_round.status = Some(U64::from(0)); // failure let transaction_receipt_tx_2_third_round = TransactionReceipt::default(); let mut transaction_receipt_tx_2_fourth_round = TransactionReceipt::default(); transaction_receipt_tx_2_fourth_round.status = Some(U64::from(1)); // confirmed + let agent = BlockchainAgentMock::default(); let blockchain_interface = BlockchainInterfaceMock::default() - .get_transaction_fee_balance_result(Ok(U256::from(u128::MAX))) - .get_token_balance_result(Ok(U256::from(u128::MAX))) - .get_transaction_count_result(Ok(web3::types::U256::from(1))) - .get_transaction_count_result(Ok(web3::types::U256::from(2))) - //because we cannot have both, resolution on the high level and also of what's inside blockchain interface, - //there is one component missing in this wholesome test - the part where we send a request for - //a fingerprint of that payable in the DB - this happens inside send_raw_transaction() - .send_payables_within_batch_result(Ok(vec![ - Correct(PendingPayable { + .build_blockchain_agent_params(&build_blockchain_agent_params) + .build_blockchain_agent_result(Ok(Box::new(agent))) + // because we cannot have both, resolution on the high level and also of what's inside blockchain interface, + // there is one component missing in this wholesome test - the part where we send a request for + // a fingerprint of that payable in the DB - this happens inside send_raw_transaction() + .send_batch_of_payables_result(Ok(vec![ + Ok(PendingPayable { recipient_wallet: wallet_account_1.clone(), hash: pending_tx_hash_1, }), - Correct(PendingPayable { + Ok(PendingPayable { recipient_wallet: wallet_account_2.clone(), hash: pending_tx_hash_2, }), @@ -3024,12 +3192,14 @@ mod tests { .get_transaction_receipt_result(Ok(Some(transaction_receipt_tx_2_fourth_round))); let consuming_wallet = make_paying_wallet(b"wallet"); let system = System::new("pending_transaction"); - let persistent_config = PersistentConfigurationMock::default().gas_price_result(Ok(130)); + let persistent_config_id_stamp = ArbitraryIdStamp::new(); + let persistent_config = PersistentConfigurationMock::default() + .set_arbitrary_id_stamp(persistent_config_id_stamp); let blockchain_bridge = BlockchainBridge::new( Box::new(blockchain_interface), Box::new(persistent_config), false, - Some(consuming_wallet), + Some(consuming_wallet.clone()), ); let account_1 = PayableAccount { wallet: wallet_account_1.clone(), @@ -3043,7 +3213,7 @@ mod tests { 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 + let pending_payable_scan_interval = 200; // should be slightly less than 1/5 of the time until shutting the system let payable_dao_for_payable_scanner = PayableDaoMock::new() .non_pending_payables_params(&non_pending_payables_params_arc) .non_pending_payables_result(vec![account_1, account_2]) @@ -3054,8 +3224,8 @@ mod tests { .transactions_confirmed_result(Ok(())); let mut bootstrapper_config = bc_from_earning_wallet(make_wallet("some_wallet_address")); bootstrapper_config.scan_intervals_opt = Some(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 + 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), }); let fingerprint_1_first_round = PendingPayableFingerprint { @@ -3124,10 +3294,10 @@ mod tests { .increment_scan_attempts_result(Ok(())) .increment_scan_attempts_result(Ok(())) .mark_failures_params(&mark_failure_params_arc) - //we don't have a better solution yet, so we mark this down + // we don't have a better solution yet, so we mark this down .mark_failures_result(Ok(())) .delete_fingerprints_params(&delete_record_params_arc) - //this is used during confirmation of the successful one + // this is used during confirmation of the successful one .delete_fingerprints_result(Ok(())); pending_payable_dao_for_pending_payable_scanner .have_return_all_errorless_fingerprints_shut_down_the_system = true; @@ -3148,8 +3318,12 @@ mod tests { subject.scanners.receivable = Box::new(NullScanner::new()); let notify_later_half_mock = NotifyLaterHandleMock::default() .notify_later_params(¬ify_later_scan_for_pending_payable_arc_cloned) - .permit_to_send_out(); - subject.scan_timings.pending_payable.handle = Box::new(notify_later_half_mock); + .capture_msg_and_let_it_fly_on(); + subject.scan_schedulers.update_scheduler( + ScanType::PendingPayables, + Some(Box::new(notify_later_half_mock)), + None, + ); subject }); let mut peer_actors = peer_actors_builder().build(); @@ -3180,10 +3354,10 @@ mod tests { assert_eq!(second_payable.1, rowid_for_account_2); let return_all_errorless_fingerprints_params = return_all_errorless_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_errorless_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 + assert_eq!(*non_pending_payables_params, vec![()]); // because we disabled further scanning for payables let get_transaction_receipt_params = get_transaction_receipt_params_arc.lock().unwrap(); assert_eq!( *get_transaction_receipt_params, @@ -3197,6 +3371,11 @@ mod tests { pending_tx_hash_2, ] ); + let build_blockchain_agent_params = build_blockchain_agent_params.lock().unwrap(); + assert_eq!( + *build_blockchain_agent_params, + vec![(consuming_wallet, persistent_config_id_stamp)] + ); let update_fingerprints_params = update_fingerprint_params_arc.lock().unwrap(); assert_eq!( *update_fingerprints_params, @@ -3225,7 +3404,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 + // 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(); @@ -3241,10 +3420,17 @@ mod tests { ); let log_handler = TestLogHandler::new(); log_handler.exists_log_containing( - "WARN: Accountant: Broken transactions 0x000000000000000000000000000000000000000000000000000000000000007b marked as an error. \ - You should take over the care of those to make sure your debts are going to be settled properly. At the moment, there is no automated process fixing that without your assistance"); - log_handler.exists_log_matching("INFO: Accountant: Transaction 0x0000000000000000000000000000000000000000000000000000000000000237 has been added to the blockchain; detected locally at attempt 4 at \\d{2,}ms after its sending"); - log_handler.exists_log_containing("INFO: Accountant: Transactions 0x0000000000000000000000000000000000000000000000000000000000000237 completed their confirmation process succeeding"); + "WARN: Accountant: Broken transactions 0x00000000000000000000000000000000000000000000000\ + 0000000000000007b marked as an error. You should take over the care of those to make sure \ + your debts are going to be settled properly. At the moment, there is no automated process \ + fixing that without your assistance"); + log_handler.exists_log_matching("INFO: Accountant: Transaction 0x000000000000000000000000000\ + 0000000000000000000000000000000000237 has been added to the blockchain; detected locally at \ + attempt 4 at \\d{2,}ms after its sending"); + log_handler.exists_log_containing( + "INFO: Accountant: Transactions 0x000000000000000000000000\ + 0000000000000000000000000000000000000237 completed their confirmation process succeeding", + ); } #[test] @@ -4299,3 +4485,199 @@ mod tests { assert_on_initialization_with_panic_on_migration(&data_dir, &act); } } + +#[cfg(test)] +pub mod exportable_test_parts { + use super::*; + use crate::accountant::test_utils::bc_from_earning_wallet; + use crate::actor_system_factory::SubsFactory; + use crate::database::db_initializer::{DbInitializer, DbInitializerReal}; + use crate::sub_lib::accountant::DEFAULT_PAYMENT_THRESHOLDS; + use crate::test_utils::actor_system_factory::BannedCacheLoaderMock; + use crate::test_utils::make_wallet; + use crate::test_utils::recorder::make_accountant_subs_from_recorder; + use crate::test_utils::unshared_test_utils::{AssertionsMessage, SubsFactoryTestAddrLeaker}; + use actix::System; + use crossbeam_channel::bounded; + use masq_lib::test_utils::utils::ShouldWeRunTheTest::{GoAhead, Skip}; + use masq_lib::test_utils::utils::{ + check_if_source_code_is_attached, ensure_node_home_directory_exists, ShouldWeRunTheTest, + }; + use regex::Regex; + use std::env::current_dir; + use std::fs::File; + use std::io::{BufRead, BufReader}; + use std::path::PathBuf; + + impl SubsFactory for SubsFactoryTestAddrLeaker { + fn make(&self, addr: &Addr) -> AccountantSubs { + self.send_leaker_msg_and_return_meaningless_subs( + addr, + make_accountant_subs_from_recorder, + ) + } + } + + fn verify_presence_of_user_defined_sqlite_fns_in_new_delinquencies_for_receivable_dao( + ) -> ShouldWeRunTheTest { + fn skip_down_to_first_line_saying_new_delinquencies( + previous: impl Iterator, + ) -> impl Iterator { + previous + .skip_while(|line| { + let adjusted_line: String = line + .chars() + .skip_while(|char| char.is_whitespace()) + .collect(); + !adjusted_line.starts_with("fn new_delinquencies(") + }) + .skip(1) + } + fn assert_is_not_trait_definition(body_lines: impl Iterator) -> String { + fn yield_if_contains_semicolon(line: &str) -> Option { + line.contains(';').then(|| line.to_string()) + } + let mut semicolon_line_opt = None; + let line_undivided_fn_body = body_lines + .map(|line| { + if semicolon_line_opt.is_none() { + if let Some(result) = yield_if_contains_semicolon(&line) { + semicolon_line_opt = Some(result) + } + } + line + }) + .collect::(); + if let Some(line) = semicolon_line_opt { + let regex = Regex::new(r"Vec<\w+>;").unwrap(); + if regex.is_match(&line) { + // The important part of the regex is the semicolon at the end. Trait + // implementations don't use it. They go on with an opening bracket of + // the function body. Its presence therefore signifies we have to do + // with a trait definition + panic!( + "The second parsed chunk of code is a trait definition \ + and the implementation lies before it. Conventions say the opposite. Simply \ + change the placement order in the production code." + ) + } + } + line_undivided_fn_body + } + fn scope_fn_new_delinquency_alone(reader: BufReader) -> String { + let all_lines_in_the_file = reader.lines().flatten(); + let lines_with_cut_fn_trait_definition = + skip_down_to_first_line_saying_new_delinquencies(all_lines_in_the_file); + let assumed_implemented_function_body = + skip_down_to_first_line_saying_new_delinquencies( + lines_with_cut_fn_trait_definition, + ) + .take_while(|line| { + let adjusted_line: String = line + .chars() + .skip_while(|char| char.is_whitespace()) + .collect(); + !adjusted_line.starts_with("fn") + }); + assert_is_not_trait_definition(assumed_implemented_function_body) + } + fn user_defined_functions_detected(line_undivided_fn_body: &str) -> bool { + line_undivided_fn_body.contains(" slope_drop_high_bytes(") + && line_undivided_fn_body.contains(" slope_drop_low_bytes(") + } + + let current_dir = current_dir().unwrap(); + let file_path = current_dir.join(PathBuf::from_iter([ + "src", + "accountant", + "db_access_objects", + "receivable_dao.rs", + ])); + let file = match File::open(file_path) { + Ok(file) => file, + Err(_) => match check_if_source_code_is_attached(¤t_dir) { + Skip => return Skip, + _ => panic!( + "if panics, the file receivable_dao.rs probably doesn't exist or \ + has moved to an unexpected location" + ), + }, + }; + let reader = BufReader::new(file); + let function_body_ready_for_final_check = scope_fn_new_delinquency_alone(reader); + if user_defined_functions_detected(&function_body_ready_for_final_check) { + GoAhead + } else { + panic!( + "was about to test user-defined SQLite functions (slope_drop_high_bytes and + slope_drop_low_bytes) in new_delinquencies() but found out those are absent at the + expected place and would leave falsely positive results" + ) + } + } + + pub fn test_accountant_is_constructed_with_upgraded_db_connection_recognizing_our_extra_sqlite_functions< + A, + >( + test_module: &str, + test_name: &str, + act: A, + ) where + A: FnOnce( + BootstrapperConfig, + DbInitializerReal, + BannedCacheLoaderMock, + SubsFactoryTestAddrLeaker, + ) -> AccountantSubs, + { + // precondition: .new_delinquencies() still encompasses the considered functions, otherwise + // the test is false-positive + if let Skip = + verify_presence_of_user_defined_sqlite_fns_in_new_delinquencies_for_receivable_dao() + { + eprintln!( + "skipping test {test_name} due to having been unable to find receivable_dao.rs" + ); + return; + }; + let data_dir = ensure_node_home_directory_exists(test_module, test_name); + let _ = DbInitializerReal::default() + .initialize(data_dir.as_ref(), DbInitializationConfig::test_default()) + .unwrap(); + let mut bootstrapper_config = bc_from_earning_wallet(make_wallet("mine")); + bootstrapper_config.data_directory = data_dir; + let db_initializer = DbInitializerReal::default(); + let banned_cache_loader = BannedCacheLoaderMock::default(); + let (tx, accountant_addr_rv) = bounded(1); + let address_leaker = SubsFactoryTestAddrLeaker { address_leaker: tx }; + let system = System::new(test_name); + + act( + bootstrapper_config, + db_initializer, + banned_cache_loader, + address_leaker, + ); + + let accountant_addr = accountant_addr_rv.try_recv().unwrap(); + let assertion_msg = AssertionsMessage { + assertions: Box::new(|accountant: &mut Accountant| { + // Will crash a test if our user-defined SQLite fns have been unreachable; + // We cannot rely on failures in the DAO tests, because Account's database connection + // has to be set up specially first (we teach it about the extra functions) as we're + // creating the actor + + accountant + .receivable_dao + .new_delinquencies(SystemTime::now(), &DEFAULT_PAYMENT_THRESHOLDS); + // Don't move this to the main test, it could produce a deceiving result. + // It wouldn't actually process this message. I don't know why exactly + System::current().stop(); + }), + }; + accountant_addr.try_send(assertion_msg).unwrap(); + assert_eq!(system.run(), 0); + // We didn't blow up, it recognized the functions. + // This is an example of the error: "no such function: slope_drop_high_bytes" + } +} diff --git a/node/src/accountant/payment_adjuster.rs b/node/src/accountant/payment_adjuster.rs new file mode 100644 index 000000000..8d230484a --- /dev/null +++ b/node/src/accountant/payment_adjuster.rs @@ -0,0 +1,105 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::msgs::BlockchainAgentWithContextMessage; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::PreparedAdjustment; +use crate::sub_lib::blockchain_bridge::OutboundPaymentsInstructions; +use masq_lib::logger::Logger; +use std::time::SystemTime; + +pub trait PaymentAdjuster { + fn search_for_indispensable_adjustment( + &self, + msg: &BlockchainAgentWithContextMessage, + logger: &Logger, + ) -> Result, AnalysisError>; + + fn adjust_payments( + &self, + setup: PreparedAdjustment, + now: SystemTime, + logger: &Logger, + ) -> OutboundPaymentsInstructions; + + as_any_in_trait!(); +} + +pub struct PaymentAdjusterReal {} + +impl PaymentAdjuster for PaymentAdjusterReal { + fn search_for_indispensable_adjustment( + &self, + _msg: &BlockchainAgentWithContextMessage, + _logger: &Logger, + ) -> Result, AnalysisError> { + Ok(None) + } + + fn adjust_payments( + &self, + _setup: PreparedAdjustment, + _now: SystemTime, + _logger: &Logger, + ) -> OutboundPaymentsInstructions { + todo!("this function is dead until the card GH-711 is played") + } + + as_any_in_trait_impl!(); +} + +impl PaymentAdjusterReal { + pub fn new() -> Self { + Self {} + } +} + +impl Default for PaymentAdjusterReal { + fn default() -> Self { + Self::new() + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum Adjustment { + MasqToken, + TransactionFeeCurrency { limiting_count: u16 }, + Both, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum AnalysisError {} + +#[cfg(test)] +mod tests { + use crate::accountant::payment_adjuster::{PaymentAdjuster, PaymentAdjusterReal}; + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::msgs::BlockchainAgentWithContextMessage; + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::test_utils::BlockchainAgentMock; + use crate::accountant::scanners::test_utils::protect_payables_in_test; + use crate::accountant::test_utils::make_payable_account; + use masq_lib::logger::Logger; + use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; + + #[test] + fn search_for_indispensable_adjustment_always_returns_none() { + init_test_logging(); + let test_name = "is_adjustment_required_always_returns_none"; + let mut payable = make_payable_account(111); + payable.balance_wei = 100_000_000; + let agent = BlockchainAgentMock::default(); + let setup_msg = BlockchainAgentWithContextMessage { + protected_qualified_payables: protect_payables_in_test(vec![payable]), + agent: Box::new(agent), + response_skeleton_opt: None, + }; + let logger = Logger::new(test_name); + let subject = PaymentAdjusterReal::new(); + + let result = subject.search_for_indispensable_adjustment(&setup_msg, &logger); + + assert_eq!(result, Ok(None)); + TestLogHandler::default().exists_no_log_containing(test_name); + // Nobody in this test asked about the wallet balances and the transaction fee + // requirement, yet we got through with the final None. + // How do we know? The mock agent didn't blow up while missing these + // results + } +} diff --git a/node/src/accountant/scanners/mid_scan_msg_handling/mod.rs b/node/src/accountant/scanners/mid_scan_msg_handling/mod.rs new file mode 100644 index 000000000..16331e4bf --- /dev/null +++ b/node/src/accountant/scanners/mid_scan_msg_handling/mod.rs @@ -0,0 +1,3 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +pub mod payable_scanner; diff --git a/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/agent_null.rs b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/agent_null.rs new file mode 100644 index 000000000..57615ba30 --- /dev/null +++ b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/agent_null.rs @@ -0,0 +1,192 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; + +use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; +use crate::sub_lib::wallet::Wallet; +use ethereum_types::U256; +use masq_lib::logger::Logger; + +#[derive(Clone)] +pub struct BlockchainAgentNull { + wallet: Wallet, + logger: Logger, +} + +impl BlockchainAgent for BlockchainAgentNull { + fn estimated_transaction_fee_total(&self, _number_of_transactions: usize) -> u128 { + self.log_function_call("estimated_transaction_fee_total()"); + 0 + } + + fn consuming_wallet_balances(&self) -> ConsumingWalletBalances { + self.log_function_call("consuming_wallet_balances()"); + ConsumingWalletBalances { + transaction_fee_balance_in_minor_units: U256::zero(), + masq_token_balance_in_minor_units: U256::zero(), + } + } + + fn agreed_fee_per_computation_unit(&self) -> u64 { + self.log_function_call("agreed_fee_per_computation_unit()"); + 0 + } + + fn consuming_wallet(&self) -> &Wallet { + self.log_function_call("consuming_wallet()"); + &self.wallet + } + + fn pending_transaction_id(&self) -> U256 { + self.log_function_call("pending_transaction_id()"); + U256::zero() + } + + #[cfg(test)] + fn dup(&self) -> Box { + intentionally_blank!() + } + + #[cfg(test)] + as_any_in_trait_impl!(); +} + +impl BlockchainAgentNull { + pub fn new() -> Self { + Self { + wallet: Wallet::null(), + logger: Logger::new("BlockchainAgentNull"), + } + } + + fn log_function_call(&self, function_call: &str) { + error!( + self.logger, + "calling null version of {function_call} for BlockchainAgentNull will be without effect", + ); + } +} + +impl Default for BlockchainAgentNull { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::agent_null::BlockchainAgentNull; + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; + + use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; + use crate::sub_lib::wallet::Wallet; + + use masq_lib::logger::Logger; + use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; + use web3::types::U256; + + fn blockchain_agent_null_constructor_works(constructor: C) + where + C: Fn() -> BlockchainAgentNull, + { + init_test_logging(); + + let result = constructor(); + + assert_eq!(result.wallet, Wallet::null()); + warning!(result.logger, "blockchain_agent_null_constructor_works"); + TestLogHandler::default().exists_log_containing( + "WARN: BlockchainAgentNull: \ + blockchain_agent_null_constructor_works", + ); + } + + #[test] + fn blockchain_agent_null_constructor_works_for_new() { + blockchain_agent_null_constructor_works(BlockchainAgentNull::new) + } + + #[test] + fn blockchain_agent_null_constructor_works_for_default() { + blockchain_agent_null_constructor_works(BlockchainAgentNull::default) + } + + fn assert_error_log(test_name: &str, expected_operation: &str) { + TestLogHandler::default().exists_log_containing(&format!( + "ERROR: {test_name}: calling \ + null version of {expected_operation}() for BlockchainAgentNull \ + will be without effect" + )); + } + + #[test] + fn null_agent_estimated_transaction_fee_total() { + init_test_logging(); + let test_name = "null_agent_estimated_transaction_fee_total"; + let mut subject = BlockchainAgentNull::new(); + subject.logger = Logger::new(test_name); + + let result = subject.estimated_transaction_fee_total(4); + + assert_eq!(result, 0); + assert_error_log(test_name, "estimated_transaction_fee_total"); + } + + #[test] + fn null_agent_consuming_wallet_balances() { + init_test_logging(); + let test_name = "null_agent_consuming_wallet_balances"; + let mut subject = BlockchainAgentNull::new(); + subject.logger = Logger::new(test_name); + + let result = subject.consuming_wallet_balances(); + + assert_eq!( + result, + ConsumingWalletBalances { + transaction_fee_balance_in_minor_units: U256::zero(), + masq_token_balance_in_minor_units: U256::zero() + } + ); + assert_error_log(test_name, "consuming_wallet_balances") + } + + #[test] + fn null_agent_agreed_fee_per_computation_unit() { + init_test_logging(); + let test_name = "null_agent_agreed_fee_per_computation_unit"; + let mut subject = BlockchainAgentNull::new(); + subject.logger = Logger::new(test_name); + + let result = subject.agreed_fee_per_computation_unit(); + + assert_eq!(result, 0); + assert_error_log(test_name, "agreed_fee_per_computation_unit") + } + + #[test] + fn null_agent_consuming_wallet() { + init_test_logging(); + let test_name = "null_agent_consuming_wallet"; + let mut subject = BlockchainAgentNull::new(); + subject.logger = Logger::new(test_name); + + let result = subject.consuming_wallet(); + + assert_eq!(result, &Wallet::null()); + assert_error_log(test_name, "consuming_wallet") + } + + #[test] + fn null_agent_pending_transaction_id() { + init_test_logging(); + let test_name = "null_agent_pending_transaction_id"; + let mut subject = BlockchainAgentNull::new(); + subject.logger = Logger::new(test_name); + + let result = subject.pending_transaction_id(); + + assert_eq!(result, U256::zero()); + assert_error_log(test_name, "pending_transaction_id"); + } +} diff --git a/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/agent_web3.rs b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/agent_web3.rs new file mode 100644 index 000000000..d54224352 --- /dev/null +++ b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/agent_web3.rs @@ -0,0 +1,140 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; + +use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; +use crate::sub_lib::wallet::Wallet; + +use web3::types::U256; + +#[derive(Debug, Clone)] +pub struct BlockchainAgentWeb3 { + gas_price_gwei: u64, + gas_limit_const_part: u64, + maximum_added_gas_margin: u64, + consuming_wallet: Wallet, + consuming_wallet_balances: ConsumingWalletBalances, + pending_transaction_id: U256, +} + +impl BlockchainAgent for BlockchainAgentWeb3 { + fn estimated_transaction_fee_total(&self, number_of_transactions: usize) -> u128 { + let gas_price = self.gas_price_gwei as u128; + let max_gas_limit = (self.maximum_added_gas_margin + self.gas_limit_const_part) as u128; + number_of_transactions as u128 * gas_price * max_gas_limit + } + + fn consuming_wallet_balances(&self) -> ConsumingWalletBalances { + self.consuming_wallet_balances + } + + fn agreed_fee_per_computation_unit(&self) -> u64 { + self.gas_price_gwei + } + + fn consuming_wallet(&self) -> &Wallet { + &self.consuming_wallet + } + + fn pending_transaction_id(&self) -> U256 { + self.pending_transaction_id + } +} + +// 64 * (64 - 12) ... std transaction has data of 64 bytes and 12 bytes are never used with us; +// each non-zero byte costs 64 units of gas +pub const WEB3_MAXIMAL_GAS_LIMIT_MARGIN: u64 = 3328; + +impl BlockchainAgentWeb3 { + pub fn new( + gas_price_gwei: u64, + gas_limit_const_part: u64, + consuming_wallet: Wallet, + consuming_wallet_balances: ConsumingWalletBalances, + pending_transaction_id: U256, + ) -> Self { + Self { + gas_price_gwei, + gas_limit_const_part, + consuming_wallet, + maximum_added_gas_margin: WEB3_MAXIMAL_GAS_LIMIT_MARGIN, + consuming_wallet_balances, + pending_transaction_id, + } + } +} + +#[cfg(test)] +mod tests { + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::agent_web3::{ + BlockchainAgentWeb3, WEB3_MAXIMAL_GAS_LIMIT_MARGIN, + }; + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; + + use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; + use crate::test_utils::make_wallet; + + use web3::types::U256; + + #[test] + fn constants_are_correct() { + assert_eq!(WEB3_MAXIMAL_GAS_LIMIT_MARGIN, 3328) + } + + #[test] + fn blockchain_agent_can_return_non_computed_input_values() { + let gas_price_gwei = 123; + let gas_limit_const_part = 44_000; + let consuming_wallet = make_wallet("abcde"); + let consuming_wallet_balances = ConsumingWalletBalances { + transaction_fee_balance_in_minor_units: U256::from(456_789), + masq_token_balance_in_minor_units: U256::from(123_000_000), + }; + let pending_transaction_id = U256::from(777); + + let subject = BlockchainAgentWeb3::new( + gas_price_gwei, + gas_limit_const_part, + consuming_wallet.clone(), + consuming_wallet_balances, + pending_transaction_id, + ); + + assert_eq!(subject.agreed_fee_per_computation_unit(), gas_price_gwei); + assert_eq!(subject.consuming_wallet(), &consuming_wallet); + assert_eq!( + subject.consuming_wallet_balances(), + consuming_wallet_balances + ); + assert_eq!(subject.pending_transaction_id(), pending_transaction_id) + } + + #[test] + fn estimated_transaction_fee_works() { + let consuming_wallet = make_wallet("efg"); + let consuming_wallet_balances = ConsumingWalletBalances { + transaction_fee_balance_in_minor_units: Default::default(), + masq_token_balance_in_minor_units: Default::default(), + }; + let nonce = U256::from(55); + let agent = BlockchainAgentWeb3::new( + 444, + 77_777, + consuming_wallet, + consuming_wallet_balances, + nonce, + ); + + let result = agent.estimated_transaction_fee_total(3); + + assert_eq!(agent.gas_limit_const_part, 77_777); + assert_eq!( + agent.maximum_added_gas_margin, + WEB3_MAXIMAL_GAS_LIMIT_MARGIN + ); + assert_eq!( + result, + (3 * (77_777 + WEB3_MAXIMAL_GAS_LIMIT_MARGIN)) as u128 * 444 + ); + } +} diff --git a/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/blockchain_agent.rs b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/blockchain_agent.rs new file mode 100644 index 000000000..704704457 --- /dev/null +++ b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/blockchain_agent.rs @@ -0,0 +1,37 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::arbitrary_id_stamp_in_trait; +use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; +use crate::sub_lib::wallet::Wallet; +use web3::types::U256; + +// Table of chains by +// +// a) adoption of the fee market (variations on "gas price") +// b) customizable limit of allowed computation ("gas limit") +// +// CHAINS a) | b) +//-------------------------------+------ +// Ethereum yes | yes +// Polygon yes | yes +// Qtum yes | yes +// NEO yes | no* +// Cardano no | yes +// Bitcoin yes | no + +//* defaulted limit + +pub trait BlockchainAgent: Send { + fn estimated_transaction_fee_total(&self, number_of_transactions: usize) -> u128; + fn consuming_wallet_balances(&self) -> ConsumingWalletBalances; + fn agreed_fee_per_computation_unit(&self) -> u64; + fn consuming_wallet(&self) -> &Wallet; + fn pending_transaction_id(&self) -> U256; + + #[cfg(test)] + fn dup(&self) -> Box { + intentionally_blank!() + } + as_any_in_trait!(); + arbitrary_id_stamp_in_trait!(); +} diff --git a/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/mod.rs b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/mod.rs new file mode 100644 index 000000000..257c88fde --- /dev/null +++ b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/mod.rs @@ -0,0 +1,68 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +pub mod agent_null; +pub mod agent_web3; +pub mod blockchain_agent; +pub mod msgs; +pub mod test_utils; + +use crate::accountant::payment_adjuster::Adjustment; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::msgs::BlockchainAgentWithContextMessage; +use crate::accountant::scanners::Scanner; +use crate::sub_lib::blockchain_bridge::OutboundPaymentsInstructions; +use actix::Message; +use itertools::Either; +use masq_lib::logger::Logger; + +pub trait MultistagePayableScanner: + Scanner + SolvencySensitivePaymentInstructor +where + BeginMessage: Message, + EndMessage: Message, +{ +} + +pub trait SolvencySensitivePaymentInstructor { + fn try_skipping_payment_adjustment( + &self, + msg: BlockchainAgentWithContextMessage, + logger: &Logger, + ) -> Result, String>; + + fn perform_payment_adjustment( + &self, + setup: PreparedAdjustment, + logger: &Logger, + ) -> OutboundPaymentsInstructions; +} + +pub struct PreparedAdjustment { + pub original_setup_msg: BlockchainAgentWithContextMessage, + pub adjustment: Adjustment, +} + +impl PreparedAdjustment { + pub fn new( + original_setup_msg: BlockchainAgentWithContextMessage, + adjustment: Adjustment, + ) -> Self { + Self { + original_setup_msg, + adjustment, + } + } +} + +#[cfg(test)] +mod tests { + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::PreparedAdjustment; + + impl Clone for PreparedAdjustment { + fn clone(&self) -> Self { + Self { + original_setup_msg: self.original_setup_msg.clone(), + adjustment: self.adjustment.clone(), + } + } + } +} diff --git a/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/msgs.rs b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/msgs.rs new file mode 100644 index 000000000..d4cd40be4 --- /dev/null +++ b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/msgs.rs @@ -0,0 +1,72 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; +use crate::accountant::{ResponseSkeleton, SkeletonOptHolder}; +use actix::Message; +use masq_lib::type_obfuscation::Obfuscated; +use std::fmt::Debug; + +#[derive(Debug, Message, PartialEq, Eq, Clone)] +pub struct QualifiedPayablesMessage { + pub protected_qualified_payables: Obfuscated, + pub response_skeleton_opt: Option, +} + +impl QualifiedPayablesMessage { + pub(in crate::accountant) fn new( + protected_qualified_payables: Obfuscated, + response_skeleton_opt: Option, + ) -> Self { + Self { + protected_qualified_payables, + response_skeleton_opt, + } + } +} + +impl SkeletonOptHolder for QualifiedPayablesMessage { + fn skeleton_opt(&self) -> Option { + self.response_skeleton_opt + } +} + +#[derive(Message)] +pub struct BlockchainAgentWithContextMessage { + pub protected_qualified_payables: Obfuscated, + pub agent: Box, + pub response_skeleton_opt: Option, +} + +impl BlockchainAgentWithContextMessage { + pub fn new( + qualified_payables: Obfuscated, + blockchain_agent: Box, + response_skeleton_opt: Option, + ) -> Self { + Self { + protected_qualified_payables: qualified_payables, + agent: blockchain_agent, + response_skeleton_opt, + } + } +} + +#[cfg(test)] +mod tests { + + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::msgs::BlockchainAgentWithContextMessage; + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::test_utils::BlockchainAgentMock; + + impl Clone for BlockchainAgentWithContextMessage { + fn clone(&self) -> Self { + let original_agent_id = self.agent.arbitrary_id_stamp(); + let cloned_agent = + BlockchainAgentMock::default().set_arbitrary_id_stamp(original_agent_id); + Self { + protected_qualified_payables: self.protected_qualified_payables.clone(), + agent: Box::new(cloned_agent), + response_skeleton_opt: self.response_skeleton_opt, + } + } + } +} diff --git a/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/test_utils.rs b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/test_utils.rs new file mode 100644 index 000000000..5de2a6b06 --- /dev/null +++ b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/test_utils.rs @@ -0,0 +1,80 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +#![cfg(test)] + +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; +use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; +use crate::sub_lib::wallet::Wallet; +use crate::test_utils::unshared_test_utils::arbitrary_id_stamp::ArbitraryIdStamp; +use crate::{arbitrary_id_stamp_in_trait_impl, set_arbitrary_id_stamp_in_mock_impl}; +use ethereum_types::U256; +use std::cell::RefCell; + +#[derive(Default)] +pub struct BlockchainAgentMock { + consuming_wallet_balances_results: RefCell>, + agreed_fee_per_computation_unit_results: RefCell>, + consuming_wallet_result_opt: Option, + pending_transaction_id_results: RefCell>, + arbitrary_id_stamp_opt: Option, +} + +impl BlockchainAgent for BlockchainAgentMock { + fn estimated_transaction_fee_total(&self, _number_of_transactions: usize) -> u128 { + todo!("to be implemented by GH-711") + } + + fn consuming_wallet_balances(&self) -> ConsumingWalletBalances { + todo!("to be implemented by GH-711") + } + + fn agreed_fee_per_computation_unit(&self) -> u64 { + self.agreed_fee_per_computation_unit_results + .borrow_mut() + .remove(0) + } + + fn consuming_wallet(&self) -> &Wallet { + self.consuming_wallet_result_opt.as_ref().unwrap() + } + + fn pending_transaction_id(&self) -> U256 { + self.pending_transaction_id_results.borrow_mut().remove(0) + } + + fn dup(&self) -> Box { + intentionally_blank!() + } + + arbitrary_id_stamp_in_trait_impl!(); +} + +impl BlockchainAgentMock { + pub fn consuming_wallet_balances_result(self, result: ConsumingWalletBalances) -> Self { + self.consuming_wallet_balances_results + .borrow_mut() + .push(result); + self + } + + pub fn agreed_fee_per_computation_unit_result(self, result: u64) -> Self { + self.agreed_fee_per_computation_unit_results + .borrow_mut() + .push(result); + self + } + + pub fn consuming_wallet_result(mut self, consuming_wallet_result: Wallet) -> Self { + self.consuming_wallet_result_opt = Some(consuming_wallet_result); + self + } + + pub fn pending_transaction_id_result(self, result: U256) -> Self { + self.pending_transaction_id_results + .borrow_mut() + .push(result); + self + } + + set_arbitrary_id_stamp_in_mock_impl!(); +} diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index e12238a6d..0e4174e83 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -1,10 +1,13 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. +pub mod mid_scan_msg_handling; pub mod scanners_utils; +pub mod test_utils; -use crate::accountant::db_access_objects::payable_dao::{PayableAccount, PayableDao, PendingPayable}; -use crate::accountant::db_access_objects::pending_payable_dao::PendingPayableDao; +use crate::accountant::db_access_objects::payable_dao::{PayableAccount, PayableDao}; +use crate::accountant::db_access_objects::pending_payable_dao::{PendingPayable, PendingPayableDao}; use crate::accountant::db_access_objects::receivable_dao::ReceivableDao; +use crate::accountant::payment_adjuster::{PaymentAdjuster, PaymentAdjusterReal}; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableTransactingErrorEnum::{ LocallyCausedError, RemotelyCausedErrors, }; @@ -12,7 +15,7 @@ use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{ debugging_summary_after_error_separation, err_msg_if_failed_without_existing_fingerprints, investigate_debt_extremes, mark_pending_payable_fatal_error, payables_debug_summary, separate_errors, separate_rowids_and_hashes, PayableThresholdsGauge, - PayableThresholdsGaugeReal, PayableTransactingErrorEnum, PendingPayableTriple, + PayableThresholdsGaugeReal, PayableTransactingErrorEnum, PendingPayableMetadata, VecOfRowidOptAndHash, }; use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{ @@ -28,15 +31,16 @@ use crate::accountant::{ }; use crate::accountant::db_access_objects::banned_dao::BannedDao; use crate::blockchain::blockchain_bridge::{PendingPayableFingerprint, RetrieveTransactions}; -use crate::blockchain::blockchain_interface::PayableTransactionError; use crate::sub_lib::accountant::{ DaoFactories, FinancialStatistics, PaymentThresholds, ScanIntervals, }; -use crate::sub_lib::blockchain_bridge::RequestBalancesToPayPayables; +use crate::sub_lib::blockchain_bridge::{ + OutboundPaymentsInstructions, +}; use crate::sub_lib::utils::{NotifyLaterHandle, NotifyLaterHandleReal}; use crate::sub_lib::wallet::Wallet; -use actix::{Context, Message, System}; -use itertools::Itertools; +use actix::{Context, Message}; +use itertools::{Either, Itertools}; use masq_lib::logger::Logger; use masq_lib::logger::TIME_FORMATTING_STRING; use masq_lib::messages::{ScanType, ToMessageBody, UiScanResponse}; @@ -45,15 +49,19 @@ use masq_lib::utils::ExpectValue; #[cfg(test)] use std::any::Any; use std::cell::RefCell; +use std::collections::HashMap; use std::rc::Rc; -use std::sync::{Arc, Mutex}; use std::time::{Duration, SystemTime}; use time::format_description::parse; use time::OffsetDateTime; use web3::types::{TransactionReceipt, H256}; +use masq_lib::type_obfuscation::Obfuscated; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::{PreparedAdjustment, MultistagePayableScanner, SolvencySensitivePaymentInstructor}; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::msgs::{BlockchainAgentWithContextMessage, QualifiedPayablesMessage}; +use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; pub struct Scanners { - pub payable: Box>, + pub payable: Box>, pub pending_payable: Box>, pub receivable: Box>, } @@ -71,6 +79,7 @@ impl Scanners { dao_factories.payable_dao_factory.make(), dao_factories.pending_payable_dao_factory.make(), Rc::clone(&payment_thresholds), + Box::new(PaymentAdjusterReal::new()), )), pending_payable: Box::new(PendingPayableScanner::new( dao_factories.payable_dao_factory.make(), @@ -105,7 +114,8 @@ where fn scan_started_at(&self) -> Option; fn mark_as_started(&mut self, timestamp: SystemTime); fn mark_as_ended(&mut self, logger: &Logger); - declare_as_any!(); + + as_any_in_trait!(); } pub struct ScannerCommon { @@ -169,15 +179,16 @@ pub struct PayableScanner { pub payable_dao: Box, pub pending_payable_dao: Box, pub payable_threshold_gauge: Box, + pub payment_adjuster: Box, } -impl Scanner for PayableScanner { +impl Scanner for PayableScanner { fn begin_scan( &mut self, timestamp: SystemTime, response_skeleton_opt: Option, logger: &Logger, - ) -> Result { + ) -> Result { if let Some(timestamp) = self.scan_started_at() { return Err(BeginScanError::ScanAlreadyRunning(timestamp)); } @@ -191,10 +202,10 @@ impl Scanner for PayableScanner { investigate_debt_extremes(timestamp, &all_non_pending_payables) ); - let qualified_payable = + let qualified_payables = self.sniff_out_alarming_payables_and_maybe_log_them(all_non_pending_payables, logger); - match qualified_payable.is_empty() { + match qualified_payables.is_empty() { true => { self.mark_as_ended(logger); Err(BeginScanError::NothingToProcess) @@ -203,12 +214,12 @@ impl Scanner for PayableScanner { info!( logger, "Chose {} qualified debts to pay", - qualified_payable.len() + qualified_payables.len() ); - Ok(RequestBalancesToPayPayables { - accounts: qualified_payable, - response_skeleton_opt, - }) + let protected_payables = self.protect_payables(qualified_payables); + let outgoing_msg = + QualifiedPayablesMessage::new(protected_payables, response_skeleton_opt); + Ok(outgoing_msg) } } } @@ -238,20 +249,58 @@ impl Scanner for PayableScanner { time_marking_methods!(Payables); - implement_as_any!(); + as_any_in_trait_impl!(); } +impl SolvencySensitivePaymentInstructor for PayableScanner { + fn try_skipping_payment_adjustment( + &self, + msg: BlockchainAgentWithContextMessage, + logger: &Logger, + ) -> Result, String> { + match self + .payment_adjuster + .search_for_indispensable_adjustment(&msg, logger) + { + Ok(None) => { + let protected = msg.protected_qualified_payables; + let unprotected = self.expose_payables(protected); + Ok(Either::Left(OutboundPaymentsInstructions::new( + unprotected, + msg.agent, + msg.response_skeleton_opt, + ))) + } + Ok(Some(adjustment)) => Ok(Either::Right(PreparedAdjustment::new(msg, adjustment))), + Err(_e) => todo!("be implemented with GH-711"), + } + } + + fn perform_payment_adjustment( + &self, + setup: PreparedAdjustment, + logger: &Logger, + ) -> OutboundPaymentsInstructions { + let now = SystemTime::now(); + self.payment_adjuster.adjust_payments(setup, now, logger) + } +} + +impl MultistagePayableScanner for PayableScanner {} + impl PayableScanner { pub fn new( payable_dao: Box, pending_payable_dao: Box, payment_thresholds: Rc, + payment_adjuster: Box, ) -> Self { Self { common: ScannerCommon::new(payment_thresholds), payable_dao, pending_payable_dao, payable_threshold_gauge: Box::new(PayableThresholdsGaugeReal::default()), + payment_adjuster, } } @@ -317,14 +366,14 @@ impl PayableScanner { } } - fn separate_id_triples_by_existent_and_nonexistent_fingerprints<'a>( + fn separate_existent_and_nonexistent_fingerprints<'a>( &'a self, sent_payables: &'a [&'a PendingPayable], - ) -> (Vec, Vec) { - fn make_triples( + ) -> (Vec, Vec) { + fn sew_metadata( ((rowid_opt, hash), pending_payable): ((Option, H256), &PendingPayable), - ) -> PendingPayableTriple { - PendingPayableTriple::new(&pending_payable.recipient_wallet, hash, rowid_opt) + ) -> PendingPayableMetadata { + PendingPayableMetadata::new(&pending_payable.recipient_wallet, hash, rowid_opt) } let hashes = sent_payables @@ -345,43 +394,43 @@ impl PayableScanner { .sorted_by(|(_, hash_a), (_, hash_b)| Ord::cmp(&hash_a, &hash_b)) .collect::, H256)>>(); - Self::symmetry_check( + if !Self::is_symmetrical( &sent_payables_sorted_by_hashes, &rowid_pairs_sorted_by_hashes, - ); + ) { + panic!( + "Inconsistency in two maps, they cannot be matched by hashes. Data set directly \ + sent from BlockchainBridge: {:?}, set derived from the DB: {:?}", + sent_payables_sorted_by_hashes, rowid_pairs_sorted_by_hashes + ) + } rowid_pairs_sorted_by_hashes .into_iter() .zip(sent_payables_sorted_by_hashes.into_iter()) - .map(make_triples) + .map(sew_metadata) .partition(|pp_triple| pp_triple.rowid_opt.is_some()) } - fn symmetry_check( + fn is_symmetrical( sent_payables_sorted_by_hashes: &[&PendingPayable], rowid_pairs_sorted_by_hashes: &[(Option, H256)], - ) { - let set_a = sent_payables_sorted_by_hashes + ) -> bool { + let map_a = sent_payables_sorted_by_hashes .iter() .map(|pp| pp.hash) .sorted() .collect::>(); - let set_b = rowid_pairs_sorted_by_hashes + let map_b = rowid_pairs_sorted_by_hashes .iter() .map(|(_, hash)| *hash) .sorted() .collect::>(); - if set_a != set_b { - panic!( - "Inconsistency in two data sets, they cannot be matched by hashes. Set A: {:?}, \ - set B: {:?}", - sent_payables_sorted_by_hashes, rowid_pairs_sorted_by_hashes - ) - } + map_a == map_b } fn mark_pending_payable(&self, sent_payments: &[&PendingPayable], logger: &Logger) { - fn missing_fingerprints_msg(nonexistent: &[PendingPayableTriple]) -> String { + fn missing_fingerprints_msg(nonexistent: &[PendingPayableMetadata]) -> String { format!( "Expected pending payable fingerprints for {} were not found; system unreliable", comma_joined_stringifiable(nonexistent, |pp_triple| format!( @@ -391,7 +440,7 @@ impl PayableScanner { ) } fn ready_data_for_supply<'a>( - existent: &'a [PendingPayableTriple], + existent: &'a [PendingPayableMetadata], ) -> Vec<(&'a Wallet, u64)> { existent .iter() @@ -400,7 +449,7 @@ impl PayableScanner { } let (existent, nonexistent) = - self.separate_id_triples_by_existent_and_nonexistent_fingerprints(sent_payments); + self.separate_existent_and_nonexistent_fingerprints(sent_payments); let mark_pp_input_data = ready_data_for_supply(&existent); if !mark_pp_input_data.is_empty() { if let Err(e) = self @@ -490,6 +539,14 @@ impl PayableScanner { panic!("{}", msg) }; } + + fn protect_payables(&self, payables: Vec) -> Obfuscated { + Obfuscated::obfuscate_vector(payables) + } + + fn expose_payables(&self, obfuscated: Obfuscated) -> Vec { + obfuscated.expose_vector() + } } pub struct PendingPayableScanner { @@ -561,7 +618,7 @@ impl Scanner for PendingP time_marking_methods!(PendingPayables); - implement_as_any!(); + as_any_in_trait_impl!(); } impl PendingPayableScanner { @@ -821,7 +878,7 @@ impl Scanner for ReceivableScanner { time_marking_methods!(Receivables); - implement_as_any!(); + as_any_in_trait_impl!(); } impl ReceivableScanner { @@ -932,193 +989,88 @@ impl BeginScanError { } } -pub struct NullScanner {} - -impl Scanner for NullScanner -where - BeginMessage: Message, - EndMessage: Message, -{ - fn begin_scan( - &mut self, - _timestamp: SystemTime, - _response_skeleton_opt: Option, - _logger: &Logger, - ) -> Result { - Err(BeginScanError::CalledFromNullScanner) - } - - fn finish_scan(&mut self, _message: EndMessage, _logger: &Logger) -> Option { - panic!("Called finish_scan() from NullScanner"); - } - - fn scan_started_at(&self) -> Option { - panic!("Called scan_started_at() from NullScanner"); - } - - fn mark_as_started(&mut self, _timestamp: SystemTime) { - panic!("Called mark_as_started() from NullScanner"); - } - - fn mark_as_ended(&mut self, _logger: &Logger) { - panic!("Called mark_as_ended() from NullScanner"); - } - - implement_as_any!(); +pub struct ScanSchedulers { + pub schedulers: HashMap>, } -impl Default for NullScanner { - fn default() -> Self { - Self::new() - } -} - -impl NullScanner { - pub fn new() -> Self { - Self {} +impl ScanSchedulers { + pub fn new(scan_intervals: ScanIntervals) -> Self { + let schedulers = HashMap::from_iter([ + ( + ScanType::Payables, + Box::new(PeriodicalScanScheduler:: { + handle: Box::new(NotifyLaterHandleReal::default()), + interval: scan_intervals.payable_scan_interval, + }) as Box, + ), + ( + ScanType::PendingPayables, + Box::new(PeriodicalScanScheduler:: { + handle: Box::new(NotifyLaterHandleReal::default()), + interval: scan_intervals.pending_payable_scan_interval, + }), + ), + ( + ScanType::Receivables, + Box::new(PeriodicalScanScheduler:: { + handle: Box::new(NotifyLaterHandleReal::default()), + interval: scan_intervals.receivable_scan_interval, + }), + ), + ]); + ScanSchedulers { schedulers } } } -pub struct ScannerMock { - begin_scan_params: Arc>>, - begin_scan_results: RefCell>>, - end_scan_params: Arc>>, - end_scan_results: RefCell>>, - stop_system_after_last_message: RefCell, +pub struct PeriodicalScanScheduler { + pub handle: Box>, + pub interval: Duration, } -impl Scanner - for ScannerMock -where - BeginMessage: Message, - EndMessage: Message, -{ - fn begin_scan( - &mut self, - _timestamp: SystemTime, - _response_skeleton_opt: Option, - _logger: &Logger, - ) -> Result { - self.begin_scan_params.lock().unwrap().push(()); - if self.is_allowed_to_stop_the_system() && self.is_last_message() { - System::current().stop(); - } - self.begin_scan_results.borrow_mut().remove(0) - } - - fn finish_scan(&mut self, message: EndMessage, _logger: &Logger) -> Option { - self.end_scan_params.lock().unwrap().push(message); - if self.is_allowed_to_stop_the_system() && self.is_last_message() { - System::current().stop(); - } - self.end_scan_results.borrow_mut().remove(0) - } - - fn scan_started_at(&self) -> Option { +pub trait ScanScheduler { + fn schedule(&self, ctx: &mut Context); + fn interval(&self) -> Duration { intentionally_blank!() } - fn mark_as_started(&mut self, _timestamp: SystemTime) { - intentionally_blank!() - } + as_any_in_trait!(); - fn mark_as_ended(&mut self, _logger: &Logger) { - intentionally_blank!() - } + #[cfg(test)] + fn as_any_mut(&mut self) -> &mut dyn Any; } -impl Default for ScannerMock { - fn default() -> Self { - Self::new() - } -} - -impl ScannerMock { - pub fn new() -> Self { - Self { - begin_scan_params: Arc::new(Mutex::new(vec![])), - begin_scan_results: RefCell::new(vec![]), - end_scan_params: Arc::new(Mutex::new(vec![])), - end_scan_results: RefCell::new(vec![]), - stop_system_after_last_message: RefCell::new(false), - } +impl ScanScheduler for PeriodicalScanScheduler { + fn schedule(&self, ctx: &mut Context) { + // the default of the message implies response_skeleton_opt to be None + // because scheduled scans don't respond + let _ = self.handle.notify_later(T::default(), self.interval, ctx); } - - pub fn begin_scan_params(mut self, params: &Arc>>) -> Self { - self.begin_scan_params = params.clone(); - self + fn interval(&self) -> Duration { + self.interval } - pub fn begin_scan_result(self, result: Result) -> Self { - self.begin_scan_results.borrow_mut().push(result); - self - } + as_any_in_trait_impl!(); - pub fn stop_the_system(self) -> Self { - self.stop_system_after_last_message.replace(true); + #[cfg(test)] + fn as_any_mut(&mut self) -> &mut dyn Any { self } - - pub fn is_allowed_to_stop_the_system(&self) -> bool { - *self.stop_system_after_last_message.borrow() - } - - pub fn is_last_message(&self) -> bool { - self.is_last_message_from_begin_scan() || self.is_last_message_from_end_scan() - } - - pub fn is_last_message_from_begin_scan(&self) -> bool { - self.begin_scan_results.borrow().len() == 1 && self.end_scan_results.borrow().is_empty() - } - - pub fn is_last_message_from_end_scan(&self) -> bool { - self.end_scan_results.borrow().len() == 1 && self.begin_scan_results.borrow().is_empty() - } -} - -pub struct ScanTimings { - pub pending_payable: PeriodicalScanConfig, - pub payable: PeriodicalScanConfig, - pub receivable: PeriodicalScanConfig, -} - -impl ScanTimings { - pub fn new(scan_intervals: ScanIntervals) -> Self { - ScanTimings { - pending_payable: PeriodicalScanConfig { - handle: Box::new(NotifyLaterHandleReal::default()), - interval: scan_intervals.pending_payable_scan_interval, - }, - payable: PeriodicalScanConfig { - handle: Box::new(NotifyLaterHandleReal::default()), - interval: scan_intervals.payable_scan_interval, - }, - receivable: PeriodicalScanConfig { - handle: Box::new(NotifyLaterHandleReal::default()), - interval: scan_intervals.receivable_scan_interval, - }, - } - } -} - -pub struct PeriodicalScanConfig { - pub handle: Box>, - pub interval: Duration, -} - -impl PeriodicalScanConfig { - pub fn schedule_another_periodic_scan(&self, ctx: &mut Context) { - // the default of the message implies response_skeleton_opt to be None - // because scheduled scans don't respond - let _ = self.handle.notify_later(T::default(), self.interval, ctx); - } } #[cfg(test)] mod tests { + use crate::accountant::db_access_objects::payable_dao::{PayableAccount, PayableDaoError}; + use crate::accountant::db_access_objects::pending_payable_dao::{ + PendingPayable, PendingPayableDaoError, + }; + use crate::accountant::db_access_objects::utils::{from_time_t, to_time_t}; + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::msgs::QualifiedPayablesMessage; + use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PendingPayableMetadata; + use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::PendingPayableScanReport; + use crate::accountant::scanners::test_utils::protect_payables_in_test; use crate::accountant::scanners::{ - BeginScanError, PayableScanner, PendingPayableScanner, ReceivableScanner, Scanner, - ScannerCommon, Scanners, + BeginScanError, PayableScanner, PendingPayableScanner, ReceivableScanner, ScanSchedulers, + Scanner, ScannerCommon, Scanners, }; use crate::accountant::test_utils::{ make_custom_payment_thresholds, make_payable_account, make_payables, @@ -1133,28 +1085,15 @@ mod tests { RequestTransactionReceipts, SentPayables, DEFAULT_PENDING_TOO_LONG_SEC, }; use crate::blockchain::blockchain_bridge::{PendingPayableFingerprint, RetrieveTransactions}; - use std::cell::RefCell; - use std::ops::Sub; - use std::panic::{catch_unwind, AssertUnwindSafe}; - - use crate::accountant::db_access_objects::dao_utils::{from_time_t, to_time_t}; - use crate::accountant::db_access_objects::payable_dao::{ - PayableAccount, PayableDaoError, PendingPayable, - }; - use crate::accountant::db_access_objects::pending_payable_dao::PendingPayableDaoError; - use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{ - PayableThresholdsGaugeReal, PendingPayableTriple, - }; - use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::PendingPayableScanReport; - use crate::blockchain::blockchain_interface::ProcessedPayableFallible::{Correct, Failed}; - use crate::blockchain::blockchain_interface::{ - BlockchainTransaction, PayableTransactionError, RpcPayableFailure, + use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; + use crate::blockchain::blockchain_interface::data_structures::{ + BlockchainTransaction, RpcPayablesFailure, }; use crate::blockchain::test_utils::make_tx_hash; use crate::sub_lib::accountant::{ - DaoFactories, FinancialStatistics, PaymentThresholds, DEFAULT_PAYMENT_THRESHOLDS, + DaoFactories, FinancialStatistics, PaymentThresholds, ScanIntervals, + DEFAULT_PAYMENT_THRESHOLDS, }; - use crate::sub_lib::blockchain_bridge::RequestBalancesToPayPayables; use crate::test_utils::make_wallet; use actix::{Message, System}; use ethereum_types::U64; @@ -1162,6 +1101,9 @@ mod tests { use masq_lib::messages::ScanType; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; use regex::Regex; + use std::cell::RefCell; + use std::ops::Sub; + use std::panic::{catch_unwind, AssertUnwindSafe}; use std::rc::Rc; use std::sync::{Arc, Mutex}; use std::time::{Duration, SystemTime}; @@ -1222,11 +1164,6 @@ mod tests { &payment_thresholds ); assert_eq!(payable_scanner.common.initiated_at_opt.is_some(), false); - payable_scanner - .payable_threshold_gauge - .as_any() - .downcast_ref::() - .unwrap(); assert_eq!( pending_payable_scanner.when_pending_too_long_sec, when_pending_too_long_sec @@ -1262,6 +1199,18 @@ mod tests { ); } + #[test] + fn protected_payables_can_be_cast_from_and_back_to_vec_of_payable_accounts_by_payable_scanner() + { + let initial_unprotected = vec![make_payable_account(123), make_payable_account(456)]; + let subject = PayableScannerBuilder::new().build(); + + let protected = subject.protect_payables(initial_unprotected.clone()); + let again_unprotected: Vec = subject.expose_payables(protected); + + assert_eq!(initial_unprotected, again_unprotected) + } + #[test] fn payable_scanner_can_initiate_a_scan() { init_test_logging(); @@ -1281,8 +1230,10 @@ mod tests { assert_eq!(timestamp, Some(now)); assert_eq!( result, - Ok(RequestBalancesToPayPayables { - accounts: qualified_payable_accounts.clone(), + Ok(QualifiedPayablesMessage { + protected_qualified_payables: protect_payables_in_test( + qualified_payable_accounts.clone() + ), response_skeleton_opt: None, }) ); @@ -1349,7 +1300,7 @@ mod tests { let failure_payable_hash_2 = make_tx_hash(0xde); let failure_payable_rowid_2 = 126; let failure_payable_wallet_2 = make_wallet("hihihi"); - let failure_payable_2 = RpcPayableFailure { + let failure_payable_2 = RpcPayablesFailure { rpc_error: Error::InvalidResponse( "Learn how to write before you send your garbage!".to_string(), ), @@ -1384,9 +1335,9 @@ mod tests { let logger = Logger::new(test_name); let sent_payable = SentPayables { payment_procedure_result: Ok(vec![ - Correct(correct_pending_payable_1), - Failed(failure_payable_2), - Correct(correct_pending_payable_3), + Ok(correct_pending_payable_1), + Err(failure_payable_2), + Ok(correct_pending_payable_3), ]), response_skeleton_opt: None, }; @@ -1441,7 +1392,7 @@ mod tests { } #[test] - fn keeping_entries_consistent_and_aligned_is_so_important() { + fn entries_must_be_kept_consistent_and_aligned() { let wallet_1 = make_wallet("abc"); let hash_1 = make_tx_hash(123); let wallet_2 = make_wallet("def"); @@ -1469,79 +1420,122 @@ mod tests { .pending_payable_dao(pending_payable_dao) .build(); - let (existent, nonexistent) = subject - .separate_id_triples_by_existent_and_nonexistent_fingerprints(&pending_payables_ref); + let (existent, nonexistent) = + subject.separate_existent_and_nonexistent_fingerprints(&pending_payables_ref); assert_eq!( existent, vec![ - PendingPayableTriple::new(&wallet_1, hash_1, Some(1)), - PendingPayableTriple::new(&wallet_2, hash_2, Some(2)), - PendingPayableTriple::new(&wallet_3, hash_3, Some(3)), - PendingPayableTriple::new(&wallet_4, hash_4, Some(4)) + PendingPayableMetadata::new(&wallet_1, hash_1, Some(1)), + PendingPayableMetadata::new(&wallet_2, hash_2, Some(2)), + PendingPayableMetadata::new(&wallet_3, hash_3, Some(3)), + PendingPayableMetadata::new(&wallet_4, hash_4, Some(4)) ] ); assert!(nonexistent.is_empty()) } - #[test] - fn symmetry_check_happy_path() { + struct TestingMismatchedDataAboutPendingPayables { + pending_payables: Vec, + common_hash_1: H256, + common_hash_3: H256, + intruder_for_hash_2: H256, + } + + fn prepare_values_for_mismatched_setting() -> TestingMismatchedDataAboutPendingPayables { let hash_1 = make_tx_hash(123); let hash_2 = make_tx_hash(456); let hash_3 = make_tx_hash(789); - let blockchain_bridge_returned_pending_payables = vec![ + let intruder = make_tx_hash(567); + let pending_payables = vec![ PendingPayable::new(make_wallet("abc"), hash_1), PendingPayable::new(make_wallet("def"), hash_2), PendingPayable::new(make_wallet("ghi"), hash_3), ]; - let bb_returned_p_payables_ref = blockchain_bridge_returned_pending_payables - .iter() - .collect::>(); - let rowids_and_hashes_from_fingerprints = - vec![(Some(3), hash_1), (Some(5), hash_2), (Some(6), hash_3)]; - - PayableScanner::symmetry_check( - &bb_returned_p_payables_ref, - &rowids_and_hashes_from_fingerprints, - ) - // No panic, test passed + TestingMismatchedDataAboutPendingPayables { + pending_payables, + common_hash_1: hash_1, + common_hash_3: hash_3, + intruder_for_hash_2: intruder, + } } #[test] #[should_panic( - expected = "Inconsistency in two data sets, they cannot be matched by hashes. \ - Set A: \ + expected = "Inconsistency in two maps, they cannot be matched by hashes. \ + Data set directly sent from BlockchainBridge: \ [PendingPayable { recipient_wallet: Wallet { kind: Address(0x0000000000000000000000000000000000616263) }, \ hash: 0x000000000000000000000000000000000000000000000000000000000000007b }, \ PendingPayable { recipient_wallet: Wallet { kind: Address(0x0000000000000000000000000000000000646566) }, \ hash: 0x00000000000000000000000000000000000000000000000000000000000001c8 }, \ PendingPayable { recipient_wallet: Wallet { kind: Address(0x0000000000000000000000000000000000676869) }, \ hash: 0x0000000000000000000000000000000000000000000000000000000000000315 }], \ - set B: \ - [(Some(3), 0x000000000000000000000000000000000000000000000000000000000000007b), \ - (Some(5), 0x0000000000000000000000000000000000000000000000000000000000000237), \ - (Some(6), 0x0000000000000000000000000000000000000000000000000000000000000315)]" + set derived from the DB: \ + [(Some(4), 0x000000000000000000000000000000000000000000000000000000000000007b), \ + (Some(1), 0x0000000000000000000000000000000000000000000000000000000000000237), \ + (Some(3), 0x0000000000000000000000000000000000000000000000000000000000000315)]" )] - fn symmetry_check_sad_path_for_intruder() { + fn two_sourced_information_of_new_pending_payables_and_their_fingerprints_is_not_symmetrical() { + let vals = prepare_values_for_mismatched_setting(); + let pending_payables_ref = vals + .pending_payables + .iter() + .collect::>(); + let pending_payable_dao = PendingPayableDaoMock::new().fingerprints_rowids_result(vec![ + (Some(4), vals.common_hash_1), + (Some(1), vals.intruder_for_hash_2), + (Some(3), vals.common_hash_3), + ]); + let subject = PayableScannerBuilder::new() + .pending_payable_dao(pending_payable_dao) + .build(); + + subject.separate_existent_and_nonexistent_fingerprints(&pending_payables_ref); + } + + #[test] + fn symmetry_check_happy_path() { let hash_1 = make_tx_hash(123); let hash_2 = make_tx_hash(456); let hash_3 = make_tx_hash(789); - let intruder = make_tx_hash(567); - let blockchain_bridge_returned_pending_payables = vec![ + let pending_payables_sent_from_blockchain_bridge = vec![ PendingPayable::new(make_wallet("abc"), hash_1), PendingPayable::new(make_wallet("def"), hash_2), PendingPayable::new(make_wallet("ghi"), hash_3), ]; - let bb_returned_p_payables_ref = blockchain_bridge_returned_pending_payables + let pending_payables_ref = pending_payables_sent_from_blockchain_bridge .iter() .collect::>(); let rowids_and_hashes_from_fingerprints = - vec![(Some(3), hash_1), (Some(5), intruder), (Some(6), hash_3)]; + vec![(Some(3), hash_1), (Some(5), hash_2), (Some(6), hash_3)]; - PayableScanner::symmetry_check( - &bb_returned_p_payables_ref, + let result = PayableScanner::is_symmetrical( + &pending_payables_ref, &rowids_and_hashes_from_fingerprints, - ) + ); + + assert_eq!(result, true) + } + + #[test] + fn symmetry_check_sad_path_for_intruder() { + let vals = prepare_values_for_mismatched_setting(); + let pending_payables_ref_from_blockchain_bridge = vals + .pending_payables + .iter() + .collect::>(); + let rowids_and_hashes_from_fingerprints = vec![ + (Some(3), vals.common_hash_1), + (Some(5), vals.intruder_for_hash_2), + (Some(6), vals.common_hash_3), + ]; + + let result = PayableScanner::is_symmetrical( + &pending_payables_ref_from_blockchain_bridge, + &rowids_and_hashes_from_fingerprints, + ); + + assert_eq!(result, false) } #[test] @@ -1549,23 +1543,24 @@ mod tests { let hash_1 = make_tx_hash(123); let hash_2 = make_tx_hash(456); let hash_3 = make_tx_hash(789); - let blockchain_bridge_returned_pending_payables = vec![ + let pending_payables_sent_from_blockchain_bridge = vec![ PendingPayable::new(make_wallet("abc"), hash_1), PendingPayable::new(make_wallet("def"), hash_2), PendingPayable::new(make_wallet("ghi"), hash_3), ]; - let bb_returned_p_payables_ref = blockchain_bridge_returned_pending_payables + let bb_returned_p_payables_ref = pending_payables_sent_from_blockchain_bridge .iter() .collect::>(); // Not in an ascending order let rowids_and_hashes_from_fingerprints = vec![(Some(3), hash_1), (Some(5), hash_3), (Some(6), hash_2)]; - PayableScanner::symmetry_check( + let result = PayableScanner::is_symmetrical( &bb_returned_p_payables_ref, &rowids_and_hashes_from_fingerprints, - ) - // No panic, test passed + ); + + assert_eq!(result, true) } #[test] @@ -1587,7 +1582,7 @@ mod tests { .pending_payable_dao(pending_payable_dao) .build(); let sent_payable = SentPayables { - payment_procedure_result: Ok(vec![Correct(payment_1), Correct(payment_2)]), + payment_procedure_result: Ok(vec![Ok(payment_1), Ok(payment_2)]), response_skeleton_opt: None, }; @@ -1610,7 +1605,7 @@ mod tests { .pending_payable_dao(pending_payable_dao) .build(); let sent_payables = SentPayables { - payment_procedure_result: Ok(vec![Correct(payable_1), Correct(payable_2)]), + payment_procedure_result: Ok(vec![Ok(payable_1), Ok(payable_2)]), response_skeleton_opt: None, }; @@ -1860,12 +1855,12 @@ mod tests { let mut subject = PayableScannerBuilder::new() .pending_payable_dao(pending_payable_dao) .build(); - let failed_payment_1 = Failed(RpcPayableFailure { + let failed_payment_1 = Err(RpcPayablesFailure { rpc_error: Error::Unreachable, recipient_wallet: make_wallet("abc"), hash: existent_record_hash, }); - let failed_payment_2 = Failed(RpcPayableFailure { + let failed_payment_2 = Err(RpcPayablesFailure { rpc_error: Error::Internal, recipient_wallet: make_wallet("def"), hash: nonexistent_record_hash, @@ -3092,7 +3087,7 @@ mod tests { let logger = Logger::new(test_name); let log_handler = TestLogHandler::new(); - assert_elapsed_time_in_mark_as_ended::( + assert_elapsed_time_in_mark_as_ended::( &mut PayableScannerBuilder::new().build(), "Payables", test_name, @@ -3114,4 +3109,40 @@ mod tests { &log_handler, ); } + + #[test] + fn scan_schedulers_can_be_properly_initialized() { + let scan_intervals = ScanIntervals { + payable_scan_interval: Duration::from_secs(240), + pending_payable_scan_interval: Duration::from_secs(300), + receivable_scan_interval: Duration::from_secs(360), + }; + + let result = ScanSchedulers::new(scan_intervals); + + assert_eq!( + result + .schedulers + .get(&ScanType::Payables) + .unwrap() + .interval(), + scan_intervals.payable_scan_interval + ); + assert_eq!( + result + .schedulers + .get(&ScanType::PendingPayables) + .unwrap() + .interval(), + scan_intervals.pending_payable_scan_interval + ); + assert_eq!( + result + .schedulers + .get(&ScanType::Receivables) + .unwrap() + .interval(), + scan_intervals.receivable_scan_interval + ); + } } diff --git a/node/src/accountant/scanners/scanners_utils.rs b/node/src/accountant/scanners/scanners_utils.rs index 158be4215..a68718a36 100644 --- a/node/src/accountant/scanners/scanners_utils.rs +++ b/node/src/accountant/scanners/scanners_utils.rs @@ -1,28 +1,25 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. pub mod payable_scanner_utils { - use crate::accountant::db_access_objects::dao_utils::ThresholdUtils; - use crate::accountant::db_access_objects::payable_dao::{PayableAccount, PayableDaoError, PendingPayable}; + use crate::accountant::db_access_objects::utils::ThresholdUtils; + use crate::accountant::db_access_objects::payable_dao::{PayableAccount, PayableDaoError}; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableTransactingErrorEnum::{ LocallyCausedError, RemotelyCausedErrors, }; - use crate::accountant::{comma_joined_stringifiable, SentPayables}; - use crate::blockchain::blockchain_interface::ProcessedPayableFallible::{Correct, Failed}; - use crate::blockchain::blockchain_interface::{ - PayableTransactionError, ProcessedPayableFallible, RpcPayableFailure, - }; + use crate::accountant::{comma_joined_stringifiable, ProcessedPayableFallible, SentPayables}; use crate::masq_lib::utils::ExpectValue; use crate::sub_lib::accountant::PaymentThresholds; use crate::sub_lib::wallet::Wallet; use itertools::Itertools; use masq_lib::logger::Logger; - #[cfg(test)] - use std::any::Any; use std::cmp::Ordering; use std::ops::Not; use std::time::SystemTime; use thousands::Separable; use web3::types::H256; + use crate::accountant::db_access_objects::pending_payable_dao::PendingPayable; + use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; + use crate::blockchain::blockchain_interface::data_structures::{RpcPayablesFailure}; pub type VecOfRowidOptAndHash = Vec<(Option, H256)>; @@ -157,8 +154,8 @@ pub mod payable_scanner_utils { logger: &'b Logger, ) -> SeparateTxsByResult<'a> { match rpc_result { - Correct(pending_payable) => add_pending_payable(acc, pending_payable), - Failed(RpcPayableFailure { + Ok(pending_payable) => add_pending_payable(acc, pending_payable), + Err(RpcPayablesFailure { rpc_error, recipient_wallet, hash, @@ -224,19 +221,19 @@ pub mod payable_scanner_utils { } #[derive(Debug, PartialEq, Eq)] - pub struct PendingPayableTriple<'a> { + pub struct PendingPayableMetadata<'a> { pub recipient: &'a Wallet, pub hash: H256, pub rowid_opt: Option, } - impl<'a> PendingPayableTriple<'a> { + impl<'a> PendingPayableMetadata<'a> { pub fn new( recipient: &'a Wallet, hash: H256, rowid_opt: Option, - ) -> PendingPayableTriple<'a> { - PendingPayableTriple { + ) -> PendingPayableMetadata<'a> { + PendingPayableMetadata { recipient, hash, rowid_opt, @@ -246,9 +243,9 @@ pub mod payable_scanner_utils { pub fn mark_pending_payable_fatal_error( sent_payments: &[&PendingPayable], - nonexistent: &[PendingPayableTriple], + nonexistent: &[PendingPayableMetadata], error: PayableDaoError, - missing_fingerprints_msg_maker: fn(&[PendingPayableTriple]) -> String, + missing_fingerprints_msg_maker: fn(&[PendingPayableMetadata]) -> String, logger: &Logger, ) { if !nonexistent.is_empty() { @@ -294,7 +291,7 @@ pub mod payable_scanner_utils { payment_thresholds: &PaymentThresholds, x: u64, ) -> u128; - declare_as_any!(); + as_any_in_trait!(); } #[derive(Default)] @@ -316,7 +313,7 @@ pub mod payable_scanner_utils { ) -> u128 { ThresholdUtils::calculate_finite_debt_limit_by_age(payment_thresholds, debt_age) } - implement_as_any!(); + as_any_in_trait_impl!(); } } @@ -430,8 +427,8 @@ pub mod receivable_scanner_utils { #[cfg(test)] mod tests { - use crate::accountant::db_access_objects::dao_utils::{from_time_t, to_time_t}; - use crate::accountant::db_access_objects::payable_dao::{PayableAccount, PendingPayable}; + use crate::accountant::db_access_objects::utils::{from_time_t, to_time_t}; + use crate::accountant::db_access_objects::payable_dao::{PayableAccount}; use crate::accountant::db_access_objects::receivable_dao::ReceivableAccount; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableTransactingErrorEnum::{ LocallyCausedError, RemotelyCausedErrors, @@ -443,10 +440,6 @@ mod tests { }; use crate::accountant::scanners::scanners_utils::receivable_scanner_utils::balance_and_age; use crate::accountant::{checked_conversion, gwei_to_wei, SentPayables}; - use crate::blockchain::blockchain_interface::ProcessedPayableFallible::{Correct, Failed}; - use crate::blockchain::blockchain_interface::{ - BlockchainError, PayableTransactionError, RpcPayableFailure, - }; use crate::blockchain::test_utils::make_tx_hash; use crate::sub_lib::accountant::PaymentThresholds; use crate::test_utils::make_wallet; @@ -454,6 +447,9 @@ mod tests { use masq_lib::logger::Logger; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; use std::time::SystemTime; + use crate::accountant::db_access_objects::pending_payable_dao::PendingPayable; + use crate::blockchain::blockchain_interface::data_structures::errors::{BlockchainError, PayableTransactionError}; + use crate::blockchain::blockchain_interface::data_structures::{RpcPayablesFailure}; #[test] fn investigate_debt_extremes_picks_the_most_relevant_records() { @@ -523,8 +519,8 @@ mod tests { }; let sent_payable = SentPayables { payment_procedure_result: Ok(vec![ - Correct(correct_payment_1.clone()), - Correct(correct_payment_2.clone()), + Ok(correct_payment_1.clone()), + Ok(correct_payment_2.clone()), ]), response_skeleton_opt: None, }; @@ -565,16 +561,13 @@ mod tests { recipient_wallet: make_wallet("blah"), hash: make_tx_hash(123), }; - let bad_rpc_call = RpcPayableFailure { - rpc_error: web3::Error::InvalidResponse("that donkey screwed it up".to_string()), + let bad_rpc_call = RpcPayablesFailure { + rpc_error: web3::Error::InvalidResponse("That jackass screwed it up".to_string()), recipient_wallet: make_wallet("whooa"), hash: make_tx_hash(0x315), }; let sent_payable = SentPayables { - payment_procedure_result: Ok(vec![ - Correct(payable_ok.clone()), - Failed(bad_rpc_call.clone()), - ]), + payment_procedure_result: Ok(vec![Ok(payable_ok.clone()), Err(bad_rpc_call.clone())]), response_skeleton_opt: None, }; @@ -582,9 +575,10 @@ mod tests { assert_eq!(oks, vec![&payable_ok]); assert_eq!(errs, Some(RemotelyCausedErrors(vec![make_tx_hash(0x315)]))); - TestLogHandler::new().exists_log_containing("WARN: test_logger: Remote transaction failure: 'Got invalid response: \ - that donkey screwed it up' for payment to 0x00000000000000000000000000000077686f6f61 and transaction hash \ - 0x0000000000000000000000000000000000000000000000000000000000000315. Please check your blockchain service URL configuration."); + TestLogHandler::new().exists_log_containing("WARN: test_logger: Remote transaction failure: \ + 'Got invalid response: That jackass screwed it up' for payment to 0x000000000000000000000000\ + 00000077686f6f61 and transaction hash 0x0000000000000000000000000000000000000000000000000000\ + 000000000315. Please check your blockchain service URL configuration."); } #[test] @@ -770,7 +764,7 @@ mod tests { #[test] fn count_total_errors_says_unknown_number_for_early_local_errors() { let early_local_errors = [ - PayableTransactionError::TransactionCount(BlockchainError::QueryFailed( + PayableTransactionError::TransactionID(BlockchainError::QueryFailed( "blah".to_string(), )), PayableTransactionError::MissingConsumingWallet, diff --git a/node/src/accountant/scanners/test_utils.rs b/node/src/accountant/scanners/test_utils.rs new file mode 100644 index 000000000..c43d6f71b --- /dev/null +++ b/node/src/accountant/scanners/test_utils.rs @@ -0,0 +1,10 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +#![cfg(test)] + +use crate::accountant::db_access_objects::payable_dao::PayableAccount; +use masq_lib::type_obfuscation::Obfuscated; + +pub fn protect_payables_in_test(payables: Vec) -> Obfuscated { + Obfuscated::obfuscate_vector(payables) +} diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs index 2bd51ccdd..f63370578 100644 --- a/node/src/accountant/test_utils.rs +++ b/node/src/accountant/test_utils.rs @@ -3,7 +3,6 @@ #![cfg(test)] use crate::accountant::db_access_objects::banned_dao::{BannedDao, BannedDaoFactory}; -use crate::accountant::db_access_objects::dao_utils::{from_time_t, to_time_t, CustomQuery}; use crate::accountant::db_access_objects::payable_dao::{ PayableAccount, PayableDao, PayableDaoError, PayableDaoFactory, }; @@ -13,31 +12,48 @@ use crate::accountant::db_access_objects::pending_payable_dao::{ use crate::accountant::db_access_objects::receivable_dao::{ ReceivableAccount, ReceivableDao, ReceivableDaoError, ReceivableDaoFactory, }; +use crate::accountant::db_access_objects::utils::{from_time_t, to_time_t, CustomQuery}; +use crate::accountant::payment_adjuster::{Adjustment, AnalysisError, PaymentAdjuster}; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::msgs::{ + BlockchainAgentWithContextMessage, QualifiedPayablesMessage, +}; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::{ + MultistagePayableScanner, PreparedAdjustment, SolvencySensitivePaymentInstructor, +}; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableThresholdsGauge; -use crate::accountant::scanners::{PayableScanner, PendingPayableScanner, ReceivableScanner}; -use crate::accountant::{gwei_to_wei, Accountant, DEFAULT_PENDING_TOO_LONG_SEC}; +use crate::accountant::scanners::{ + BeginScanError, PayableScanner, PendingPayableScanner, PeriodicalScanScheduler, + ReceivableScanner, ScanSchedulers, Scanner, +}; +use crate::accountant::{ + gwei_to_wei, Accountant, ResponseSkeleton, SentPayables, DEFAULT_PENDING_TOO_LONG_SEC, +}; use crate::blockchain::blockchain_bridge::PendingPayableFingerprint; -use crate::blockchain::blockchain_interface::BlockchainTransaction; +use crate::blockchain::blockchain_interface::data_structures::BlockchainTransaction; use crate::blockchain::test_utils::make_tx_hash; use crate::bootstrapper::BootstrapperConfig; use crate::db_config::config_dao::{ConfigDao, ConfigDaoFactory}; use crate::db_config::mocks::ConfigDaoMock; use crate::sub_lib::accountant::{DaoFactories, FinancialStatistics}; use crate::sub_lib::accountant::{MessageIdGenerator, PaymentThresholds}; +use crate::sub_lib::blockchain_bridge::OutboundPaymentsInstructions; +use crate::sub_lib::utils::NotifyLaterHandle; use crate::sub_lib::wallet::Wallet; use crate::test_utils::make_wallet; use crate::test_utils::unshared_test_utils::make_bc_with_defaults; -use actix::System; +use actix::{Message, System}; use ethereum_types::H256; +use itertools::Either; use masq_lib::logger::Logger; -use masq_lib::utils::plus; +use masq_lib::messages::ScanType; +use masq_lib::ui_gateway::NodeToUiMessage; use rusqlite::{Connection, Row}; use std::any::type_name; use std::cell::RefCell; use std::fmt::Debug; use std::rc::Rc; use std::sync::{Arc, Mutex}; -use std::time::SystemTime; +use std::time::{Duration, SystemTime}; pub fn make_receivable_account(n: u64, expected_delinquent: bool) -> ReceivableAccount { let now = to_time_t(SystemTime::now()); @@ -140,7 +156,7 @@ impl DaoWithDestination { fn guts_for_dao_factory_queue_initialization( customized_supplied_daos: &mut Vec>, - acc: Vec>, + mut acc: Vec>, used_input: usize, position: DestinationMarker, ) -> (Vec>, usize) { @@ -151,9 +167,13 @@ fn guts_for_dao_factory_queue_initialization( Some(idx) => { let customized_dao = customized_supplied_daos.remove(idx).into_inner(); let used_input_updated = used_input + 1; - (plus(acc, Box::new(customized_dao)), used_input_updated) + acc.push(Box::new(customized_dao)); + (acc, used_input_updated) + } + None => { + acc.push(Box::new(Default::default())); + (acc, used_input) } - None => (plus(acc, Box::new(Default::default())), used_input), } } @@ -1040,6 +1060,7 @@ pub struct PayableScannerBuilder { payable_dao: PayableDaoMock, pending_payable_dao: PendingPayableDaoMock, payment_thresholds: PaymentThresholds, + payment_adjuster: PaymentAdjusterMock, } impl PayableScannerBuilder { @@ -1048,6 +1069,7 @@ impl PayableScannerBuilder { payable_dao: PayableDaoMock::new(), pending_payable_dao: PendingPayableDaoMock::new(), payment_thresholds: PaymentThresholds::default(), + payment_adjuster: PaymentAdjusterMock::default(), } } @@ -1056,6 +1078,14 @@ impl PayableScannerBuilder { self } + pub fn payment_adjuster( + mut self, + payment_adjuster: PaymentAdjusterMock, + ) -> PayableScannerBuilder { + self.payment_adjuster = payment_adjuster; + self + } + pub fn payment_thresholds(mut self, payment_thresholds: PaymentThresholds) -> Self { self.payment_thresholds = payment_thresholds; self @@ -1074,6 +1104,7 @@ impl PayableScannerBuilder { Box::new(self.payable_dao), Box::new(self.pending_payable_dao), Rc::new(self.payment_thresholds), + Box::new(self.payment_adjuster), ) } } @@ -1361,3 +1392,270 @@ impl PayableThresholdsGaugeMock { self } } + +#[derive(Default)] +pub struct PaymentAdjusterMock { + search_for_indispensable_adjustment_params: + Arc>>, + search_for_indispensable_adjustment_results: + RefCell, AnalysisError>>>, + adjust_payments_params: Arc>>, + adjust_payments_results: RefCell>, +} + +impl PaymentAdjuster for PaymentAdjusterMock { + fn search_for_indispensable_adjustment( + &self, + msg: &BlockchainAgentWithContextMessage, + logger: &Logger, + ) -> Result, AnalysisError> { + self.search_for_indispensable_adjustment_params + .lock() + .unwrap() + .push((msg.clone(), logger.clone())); + self.search_for_indispensable_adjustment_results + .borrow_mut() + .remove(0) + } + + fn adjust_payments( + &self, + setup: PreparedAdjustment, + now: SystemTime, + logger: &Logger, + ) -> OutboundPaymentsInstructions { + self.adjust_payments_params + .lock() + .unwrap() + .push((setup.clone(), now, logger.clone())); + self.adjust_payments_results.borrow_mut().remove(0) + } +} + +impl PaymentAdjusterMock { + pub fn is_adjustment_required_params( + mut self, + params: &Arc>>, + ) -> Self { + self.search_for_indispensable_adjustment_params = params.clone(); + self + } + + pub fn is_adjustment_required_result( + self, + result: Result, AnalysisError>, + ) -> Self { + self.search_for_indispensable_adjustment_results + .borrow_mut() + .push(result); + self + } + + pub fn adjust_payments_params( + mut self, + params: &Arc>>, + ) -> Self { + self.adjust_payments_params = params.clone(); + self + } + + pub fn adjust_payments_result(self, result: OutboundPaymentsInstructions) -> Self { + self.adjust_payments_results.borrow_mut().push(result); + self + } +} + +macro_rules! formal_traits_for_payable_mid_scan_msg_handling { + ($scanner:ty) => { + impl MultistagePayableScanner for $scanner {} + + impl SolvencySensitivePaymentInstructor for $scanner { + fn try_skipping_payment_adjustment( + &self, + _msg: BlockchainAgentWithContextMessage, + _logger: &Logger, + ) -> Result, String> { + intentionally_blank!() + } + + fn perform_payment_adjustment( + &self, + _setup: PreparedAdjustment, + _logger: &Logger, + ) -> OutboundPaymentsInstructions { + intentionally_blank!() + } + } + }; +} + +pub struct NullScanner {} + +impl Scanner for NullScanner +where + BeginMessage: Message, + EndMessage: Message, +{ + fn begin_scan( + &mut self, + _timestamp: SystemTime, + _response_skeleton_opt: Option, + _logger: &Logger, + ) -> Result { + Err(BeginScanError::CalledFromNullScanner) + } + + fn finish_scan(&mut self, _message: EndMessage, _logger: &Logger) -> Option { + panic!("Called finish_scan() from NullScanner"); + } + + fn scan_started_at(&self) -> Option { + panic!("Called scan_started_at() from NullScanner"); + } + + fn mark_as_started(&mut self, _timestamp: SystemTime) { + panic!("Called mark_as_started() from NullScanner"); + } + + fn mark_as_ended(&mut self, _logger: &Logger) { + panic!("Called mark_as_ended() from NullScanner"); + } + + as_any_in_trait_impl!(); +} + +formal_traits_for_payable_mid_scan_msg_handling!(NullScanner); + +impl Default for NullScanner { + fn default() -> Self { + Self::new() + } +} + +impl NullScanner { + pub fn new() -> Self { + Self {} + } +} + +pub struct ScannerMock { + begin_scan_params: Arc>>, + begin_scan_results: RefCell>>, + end_scan_params: Arc>>, + end_scan_results: RefCell>>, + stop_system_after_last_message: RefCell, +} + +impl Scanner + for ScannerMock +where + BeginMessage: Message, + EndMessage: Message, +{ + fn begin_scan( + &mut self, + _timestamp: SystemTime, + _response_skeleton_opt: Option, + _logger: &Logger, + ) -> Result { + self.begin_scan_params.lock().unwrap().push(()); + if self.is_allowed_to_stop_the_system() && self.is_last_message() { + System::current().stop(); + } + self.begin_scan_results.borrow_mut().remove(0) + } + + fn finish_scan(&mut self, message: EndMessage, _logger: &Logger) -> Option { + self.end_scan_params.lock().unwrap().push(message); + if self.is_allowed_to_stop_the_system() && self.is_last_message() { + System::current().stop(); + } + self.end_scan_results.borrow_mut().remove(0) + } + + fn scan_started_at(&self) -> Option { + intentionally_blank!() + } + + fn mark_as_started(&mut self, _timestamp: SystemTime) { + intentionally_blank!() + } + + fn mark_as_ended(&mut self, _logger: &Logger) { + intentionally_blank!() + } +} + +impl Default for ScannerMock { + fn default() -> Self { + Self::new() + } +} + +impl ScannerMock { + pub fn new() -> Self { + Self { + begin_scan_params: Arc::new(Mutex::new(vec![])), + begin_scan_results: RefCell::new(vec![]), + end_scan_params: Arc::new(Mutex::new(vec![])), + end_scan_results: RefCell::new(vec![]), + stop_system_after_last_message: RefCell::new(false), + } + } + + pub fn begin_scan_params(mut self, params: &Arc>>) -> Self { + self.begin_scan_params = params.clone(); + self + } + + pub fn begin_scan_result(self, result: Result) -> Self { + self.begin_scan_results.borrow_mut().push(result); + self + } + + pub fn stop_the_system_after_last_msg(self) -> Self { + self.stop_system_after_last_message.replace(true); + self + } + + pub fn is_allowed_to_stop_the_system(&self) -> bool { + *self.stop_system_after_last_message.borrow() + } + + pub fn is_last_message(&self) -> bool { + self.is_last_message_from_begin_scan() || self.is_last_message_from_end_scan() + } + + pub fn is_last_message_from_begin_scan(&self) -> bool { + self.begin_scan_results.borrow().len() == 1 && self.end_scan_results.borrow().is_empty() + } + + pub fn is_last_message_from_end_scan(&self) -> bool { + self.end_scan_results.borrow().len() == 1 && self.begin_scan_results.borrow().is_empty() + } +} + +formal_traits_for_payable_mid_scan_msg_handling!(ScannerMock); + +impl ScanSchedulers { + pub fn update_scheduler( + &mut self, + scan_type: ScanType, + handle_opt: Option>>, + interval_opt: Option, + ) { + let scheduler = self + .schedulers + .get_mut(&scan_type) + .unwrap() + .as_any_mut() + .downcast_mut::>() + .unwrap(); + if let Some(new_handle) = handle_opt { + scheduler.handle = new_handle + } + if let Some(new_interval) = interval_opt { + scheduler.interval = new_interval + } + } +} diff --git a/node/src/actor_system_factory.rs b/node/src/actor_system_factory.rs index c1252b31e..870460dd1 100644 --- a/node/src/actor_system_factory.rs +++ b/node/src/actor_system_factory.rs @@ -11,15 +11,13 @@ use super::stream_handler_pool::StreamHandlerPoolSubs; use super::stream_messages::PoolBindMessage; use super::ui_gateway::UiGateway; use crate::accountant::db_access_objects::banned_dao::{BannedCacheLoader, BannedCacheLoaderReal}; -use crate::blockchain::blockchain_bridge::BlockchainBridge; +use crate::blockchain::blockchain_bridge::{BlockchainBridge, BlockchainBridgeSubsFactoryReal}; use crate::bootstrapper::CryptDEPair; use crate::database::db_initializer::DbInitializationConfig; use crate::database::db_initializer::{connection_or_panic, DbInitializer, DbInitializerReal}; use crate::db_config::persistent_configuration::PersistentConfiguration; use crate::node_configurator::configurator::Configurator; -use crate::sub_lib::accountant::{ - AccountantSubs, AccountantSubsFactory, AccountantSubsFactoryReal, DaoFactories, -}; +use crate::sub_lib::accountant::{AccountantSubs, AccountantSubsFactoryReal, DaoFactories}; use crate::sub_lib::blockchain_bridge::BlockchainBridgeSubs; use crate::sub_lib::configurator::ConfiguratorSubs; use crate::sub_lib::cryptde::CryptDE; @@ -69,18 +67,13 @@ impl ActorSystemFactory for ActorSystemFactoryReal { &self, config: BootstrapperConfig, actor_factory: Box, - persist_config: Box, + persistent_config: Box, ) -> StreamHandlerPoolSubs { - self.tools.validate_database_chain( - persist_config.as_ref(), - config.blockchain_bridge_config.chain, - ); - self.tools.prepare_initial_messages( - self.tools.cryptdes(), - config, - persist_config, - actor_factory, - ) + self.tools + .validate_database_chain(&*persistent_config, config.blockchain_bridge_config.chain); + let cryptdes = self.tools.cryptdes(); + self.tools + .prepare_initial_messages(cryptdes, config, persistent_config, actor_factory) } } @@ -155,7 +148,8 @@ impl ActorSystemFactoryTools for ActorSystemFactoryToolsReal { is_decentralized: config.neighborhood_config.mode.is_decentralized(), crashable: is_crashable(&config), }); - let blockchain_bridge_subs = actor_factory.make_and_start_blockchain_bridge(&config); + let blockchain_bridge_subs = actor_factory + .make_and_start_blockchain_bridge(&config, &BlockchainBridgeSubsFactoryReal {}); let neighborhood_subs = actor_factory.make_and_start_neighborhood(cryptdes.main, &config); let accountant_subs = actor_factory.make_and_start_accountant( config.clone(), @@ -368,7 +362,7 @@ pub trait ActorFactory { config: BootstrapperConfig, db_initializer: &dyn DbInitializer, banned_cache_loader: &dyn BannedCacheLoader, - accountant_subs_factory: &dyn AccountantSubsFactory, + subs_factory: &dyn SubsFactory, ) -> AccountantSubs; fn make_and_start_ui_gateway(&self, config: &BootstrapperConfig) -> UiGatewaySubs; fn make_and_start_stream_handler_pool( @@ -376,8 +370,11 @@ pub trait ActorFactory { config: &BootstrapperConfig, ) -> StreamHandlerPoolSubs; fn make_and_start_proxy_client(&self, config: ProxyClientConfig) -> ProxyClientSubs; - fn make_and_start_blockchain_bridge(&self, config: &BootstrapperConfig) - -> BlockchainBridgeSubs; + fn make_and_start_blockchain_bridge( + &self, + config: &BootstrapperConfig, + subs_factory: &dyn SubsFactory, + ) -> BlockchainBridgeSubs; fn make_and_start_configurator(&self, config: &BootstrapperConfig) -> ConfiguratorSubs; } @@ -447,7 +444,7 @@ impl ActorFactory for ActorFactoryReal { config: BootstrapperConfig, db_initializer: &dyn DbInitializer, banned_cache_loader: &dyn BannedCacheLoader, - accountant_subs_factory: &dyn AccountantSubsFactory, + subs_factory: &dyn SubsFactory, ) -> AccountantSubs { let data_directory = config.data_directory.as_path(); let payable_dao_factory = Box::new(Accountant::dao_factory(data_directory)); @@ -467,7 +464,7 @@ impl ActorFactory for ActorFactoryReal { }, ) }); - accountant_subs_factory.make(&addr) + subs_factory.make(&addr) } fn make_and_start_ui_gateway(&self, config: &BootstrapperConfig) -> UiGatewaySubs { @@ -500,6 +497,7 @@ impl ActorFactory for ActorFactoryReal { fn make_and_start_blockchain_bridge( &self, config: &BootstrapperConfig, + subs_factory: &dyn SubsFactory, ) -> BlockchainBridgeSubs { let blockchain_service_url_opt = config .blockchain_bridge_config @@ -508,14 +506,15 @@ impl ActorFactory for ActorFactoryReal { let crashable = is_crashable(config); let wallet_opt = config.consuming_wallet_opt.clone(); let data_directory = config.data_directory.clone(); - let chain_id = config.blockchain_bridge_config.chain; + let chain = config.blockchain_bridge_config.chain; let arbiter = Arbiter::builder().stop_system_on_panic(true); let addr: Addr = arbiter.start(move |_| { - let (blockchain_interface, persistent_config) = BlockchainBridge::make_connections( + let blockchain_interface = BlockchainBridge::initialize_blockchain_interface( blockchain_service_url_opt, - data_directory, - chain_id, + chain, ); + let persistent_config = + BlockchainBridge::initialize_persistent_configuration(&data_directory); BlockchainBridge::new( blockchain_interface, persistent_config, @@ -523,7 +522,7 @@ impl ActorFactory for ActorFactoryReal { wallet_opt, ) }); - BlockchainBridge::make_subs_from(&addr) + subs_factory.make(&addr) } fn make_and_start_configurator(&self, config: &BootstrapperConfig) -> ConfiguratorSubs { @@ -613,18 +612,26 @@ impl LogRecipientSetter for LogRecipientSetterReal { } } +// Test writing easing stuff. If further examination of the actor +// starting methods in ActorFactory is desirable. +// This allows to get the started actor's address and then messages +// can be sent to it, possibly the AssertionMessage. +pub trait SubsFactory +where + Actor: actix::Actor, +{ + fn make(&self, addr: &Addr) -> ActorSubs; +} + #[cfg(test)] mod tests { use super::*; - use crate::accountant::check_sqlite_fns::TestUserDefinedSqliteFnsForNewDelinquencies; - use crate::accountant::test_utils::bc_from_earning_wallet; + use crate::accountant::exportable_test_parts::test_accountant_is_constructed_with_upgraded_db_connection_recognizing_our_extra_sqlite_functions; use crate::accountant::DEFAULT_PENDING_TOO_LONG_SEC; - use crate::actor_system_factory::tests::ShouldWeRunTheTest::{GoAhead, Skip}; + use crate::blockchain::blockchain_bridge::exportable_test_parts::test_blockchain_bridge_is_constructed_with_correctly_functioning_connections; 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, + make_stream_handler_pool_subs_from_recorder, start_recorder_refcell_opt, }; use crate::sub_lib::accountant::{PaymentThresholds, ScanIntervals}; use crate::sub_lib::blockchain_bridge::BlockchainBridgeConfig; @@ -638,20 +645,23 @@ mod tests { use crate::sub_lib::peer_actors::StartMessage; use crate::sub_lib::stream_handler_pool::TransmitDataMsg; use crate::sub_lib::ui_gateway::UiGatewayConfig; + use crate::test_utils::actor_system_factory::BannedCacheLoaderMock; use crate::test_utils::automap_mocks::{AutomapControlFactoryMock, AutomapControlMock}; use crate::test_utils::make_wallet; use crate::test_utils::neighborhood_test_utils::MIN_HOPS_FOR_TEST; use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMock; 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, - make_proxy_client_subs_from, make_proxy_server_subs_from, - make_ui_gateway_subs_from_recorder, Recording, + make_accountant_subs_from_recorder, make_blockchain_bridge_subs_from_recorder, + make_configurator_subs_from_recorder, make_hopper_subs_from_recorder, + make_neighborhood_subs_from_recorder, make_proxy_client_subs_from_recorder, + make_proxy_server_subs_from_recorder, make_ui_gateway_subs_from_recorder, Recording, }; use crate::test_utils::recorder::{make_recorder, Recorder}; use crate::test_utils::unshared_test_utils::arbitrary_id_stamp::ArbitraryIdStamp; - use crate::test_utils::unshared_test_utils::assert_on_initialization_with_panic_on_migration; use crate::test_utils::unshared_test_utils::system_killer_actor::SystemKillerActor; + use crate::test_utils::unshared_test_utils::{ + assert_on_initialization_with_panic_on_migration, SubsFactoryTestAddrLeaker, + }; use crate::test_utils::{alias_cryptde, rate_pack}; use crate::test_utils::{main_cryptde, make_cryptde_pair}; use crate::{hopper, proxy_client, proxy_server, stream_handler_pool, ui_gateway}; @@ -661,27 +671,20 @@ mod tests { use automap_lib::mocks::{ parameterizable_automap_control, TransactorMock, PUBLIC_IP, ROUTER_IP, }; - use crossbeam_channel::{bounded, unbounded, Sender}; + use crossbeam_channel::unbounded; use log::LevelFilter; use masq_lib::constants::DEFAULT_CHAIN; use masq_lib::crash_point::CrashPoint; #[cfg(feature = "log_recipient_test")] use masq_lib::logger::INITIALIZATION_COUNTER; use masq_lib::messages::{ToMessageBody, UiCrashRequest, UiDescriptorRequest}; - use masq_lib::test_utils::utils::{ - check_if_source_code_is_attached, ensure_node_home_directory_exists, ShouldWeRunTheTest, - TEST_DEFAULT_CHAIN, - }; + use masq_lib::test_utils::utils::{ensure_node_home_directory_exists, TEST_DEFAULT_CHAIN}; use masq_lib::ui_gateway::NodeFromUiMessage; use masq_lib::utils::running_test; use masq_lib::utils::AutomapProtocol::Igdp; - use regex::Regex; use std::cell::RefCell; use std::collections::HashMap; use std::convert::TryFrom; - use std::env::current_dir; - use std::fs::File; - use std::io::{BufRead, BufReader}; use std::net::Ipv4Addr; use std::net::{IpAddr, SocketAddr, SocketAddrV4}; use std::path::PathBuf; @@ -795,17 +798,6 @@ mod tests { } } - #[derive(Default)] - struct BannedCacheLoaderMock { - pub load_params: Arc>>>, - } - - impl BannedCacheLoader for BannedCacheLoaderMock { - fn load(&self, conn: Box) { - self.load_params.lock().unwrap().push(conn); - } - } - struct ActorFactoryMock<'a> { dispatcher: RefCell>, proxy_client: RefCell>, @@ -854,7 +846,7 @@ mod tests { .unwrap() .get_or_insert((cryptdes, config.clone())); let addr: Addr = ActorFactoryMock::start_recorder(&self.proxy_server); - make_proxy_server_subs_from(&addr) + make_proxy_server_subs_from_recorder(&addr) } fn make_and_start_hopper(&self, config: HopperConfig) -> HopperSubs { @@ -864,7 +856,7 @@ mod tests { .unwrap() .get_or_insert(config); let addr: Addr = start_recorder_refcell_opt(&self.hopper); - make_hopper_subs_from(&addr) + make_hopper_subs_from_recorder(&addr) } fn make_and_start_neighborhood( @@ -878,7 +870,7 @@ mod tests { .unwrap() .get_or_insert((cryptde, config.clone())); let addr: Addr = start_recorder_refcell_opt(&self.neighborhood); - make_neighborhood_subs_from(&addr) + make_neighborhood_subs_from_recorder(&addr) } fn make_and_start_accountant( @@ -886,7 +878,7 @@ mod tests { config: BootstrapperConfig, _db_initializer: &dyn DbInitializer, _banned_cache_loader: &dyn BannedCacheLoader, - _accountant_subs_factory: &dyn AccountantSubsFactory, + _accountant_subs_factory: &dyn SubsFactory, ) -> AccountantSubs { self.parameters .accountant_params @@ -922,12 +914,13 @@ mod tests { .unwrap() .get_or_insert(config); let addr: Addr = start_recorder_refcell_opt(&self.proxy_client); - make_proxy_client_subs_from(&addr) + make_proxy_client_subs_from_recorder(&addr) } fn make_and_start_blockchain_bridge( &self, config: &BootstrapperConfig, + _subs_factory: &dyn SubsFactory, ) -> BlockchainBridgeSubs { self.parameters .blockchain_bridge_params @@ -935,7 +928,7 @@ mod tests { .unwrap() .get_or_insert(config.clone()); let addr: Addr = start_recorder_refcell_opt(&self.blockchain_bridge); - make_blockchain_bridge_subs_from(&addr) + make_blockchain_bridge_subs_from_recorder(&addr) } fn make_and_start_configurator(&self, config: &BootstrapperConfig) -> ConfiguratorSubs { @@ -945,7 +938,7 @@ mod tests { .unwrap() .get_or_insert(config.clone()); let addr: Addr = start_recorder_refcell_opt(&self.configurator); - make_configurator_subs_from(&addr) + make_configurator_subs_from_recorder(&addr) } } @@ -1048,6 +1041,107 @@ mod tests { } } + #[test] + fn make_and_start_actors_happy_path() { + let validate_database_chain_params_arc = Arc::new(Mutex::new(vec![])); + let prepare_initial_messages_params_arc = Arc::new(Mutex::new(vec![])); + let (stream_handler_pool, _, stream_handler_pool_recording_arc) = make_recorder(); + let main_cryptde = main_cryptde(); + let alias_cryptde = alias_cryptde(); + let cryptde_pair = CryptDEPair { + main: main_cryptde, + alias: alias_cryptde, + }; + let main_cryptde_public_key_expected = pk_from_cryptde_null(main_cryptde); + let alias_cryptde_public_key_expected = pk_from_cryptde_null(alias_cryptde); + let actor_factory = Box::new(ActorFactoryReal {}); + let actor_factory_raw_address_expected = addr_of!(*actor_factory); + let persistent_config_expected_arbitrary_id = ArbitraryIdStamp::new(); + let persistent_config = Box::new( + PersistentConfigurationMock::default() + .set_arbitrary_id_stamp(persistent_config_expected_arbitrary_id), + ); + let stream_holder_pool_subs = + make_stream_handler_pool_subs_from_recorder(&stream_handler_pool.start()); + let actor_system_factor_tools = ActorSystemFactoryToolsMock::default() + .validate_database_chain_params(&validate_database_chain_params_arc) + .cryptdes_result(cryptde_pair) + .prepare_initial_messages_params(&prepare_initial_messages_params_arc) + .prepare_initial_messages_result(stream_holder_pool_subs); + let data_dir = PathBuf::new().join("parent_directory/child_directory"); + let subject = ActorSystemFactoryReal::new(Box::new(actor_system_factor_tools)); + let mut bootstrapper_config = BootstrapperConfig::new(); + bootstrapper_config.blockchain_bridge_config.chain = Chain::PolyMainnet; + bootstrapper_config.data_directory = data_dir.clone(); + bootstrapper_config.db_password_opt = Some("password".to_string()); + + let result = + subject.make_and_start_actors(bootstrapper_config, actor_factory, persistent_config); + + let mut validate_database_chain_params = validate_database_chain_params_arc.lock().unwrap(); + let (persistent_config_actual_arbitrary_id, actual_chain) = + validate_database_chain_params.remove(0); + assert_eq!( + persistent_config_actual_arbitrary_id, + persistent_config_expected_arbitrary_id + ); + assert_eq!(actual_chain, Chain::PolyMainnet); + assert!(validate_database_chain_params.is_empty()); + let mut prepare_initial_messages_params = + prepare_initial_messages_params_arc.lock().unwrap(); + let ( + main_cryptde_actual, + alias_cryptde_actual, + bootstrapper_config_actual, + actor_factory_actual, + persistent_config_actual, + ) = prepare_initial_messages_params.remove(0); + let main_cryptde_public_key_actual = pk_from_cryptde_null(main_cryptde_actual.as_ref()); + assert_eq!( + main_cryptde_public_key_actual, + main_cryptde_public_key_expected + ); + let alias_cryptde_public_key_actual = pk_from_cryptde_null(alias_cryptde_actual.as_ref()); + assert_eq!( + alias_cryptde_public_key_actual, + alias_cryptde_public_key_expected + ); + assert_eq!(bootstrapper_config_actual.data_directory, data_dir); + assert_eq!( + bootstrapper_config_actual.db_password_opt, + Some("password".to_string()) + ); + assert_eq!( + addr_of!(*actor_factory_actual), + actor_factory_raw_address_expected + ); + assert_eq!( + persistent_config_actual.arbitrary_id_stamp(), + persistent_config_expected_arbitrary_id + ); + assert!(prepare_initial_messages_params.is_empty()); + verify_recipient(&result.node_from_ui_sub, &stream_handler_pool_recording_arc) + } + + fn verify_recipient( + recipient: &Recipient, + recording_arc: &Arc>, + ) { + let system = System::new("verifying_recipient_returned_in_test"); + let expected_msg = NodeFromUiMessage { + client_id: 5, + body: UiDescriptorRequest {}.tmb(1), + }; + + recipient.try_send(expected_msg.clone()).unwrap(); + + System::current().stop_with_code(0); + system.run(); + let recording = recording_arc.lock().unwrap(); + let actual_msg = recording.get_record::(0); + assert_eq!(actual_msg, &expected_msg); + } + #[test] fn make_and_start_actors_sends_bind_messages() { let actor_factory = ActorFactoryMock::new(); @@ -1863,242 +1957,41 @@ mod tests { ); } - struct AccountantSubsFactoryTestOnly { - address_leaker: Sender>, - } - - impl AccountantSubsFactory for AccountantSubsFactoryTestOnly { - fn make(&self, addr: &Addr) -> AccountantSubs { - self.address_leaker.try_send(addr.clone()).unwrap(); - let nonsensical_addr = Recorder::new().start(); - make_accountant_subs_from_recorder(&nonsensical_addr) - } - } - - fn check_ongoing_usage_of_user_defined_fns_within_new_delinquencies_for_receivable_dao( - ) -> ShouldWeRunTheTest { - fn skip_down_to_first_line_saying_new_delinquencies( - previous: impl Iterator, - ) -> impl Iterator { - previous - .skip_while(|line| { - let adjusted_line: String = line - .chars() - .skip_while(|char| char.is_whitespace()) - .collect(); - !adjusted_line.starts_with("fn new_delinquencies(") - }) - .skip(1) - } - fn user_defined_functions_detected(line_undivided_fn_body: &str) -> bool { - line_undivided_fn_body.contains(" slope_drop_high_bytes(") - && line_undivided_fn_body.contains(" slope_drop_low_bytes(") - } - fn assert_is_not_trait_definition(body_lines: impl Iterator) -> String { - fn yield_if_contains_semicolon(line: &str) -> Option { - line.contains(';').then(|| line.to_string()) - } - let mut semicolon_line_opt = None; - let line_undivided_fn_body = body_lines - .map(|line| { - if semicolon_line_opt.is_none() { - if let Some(result) = yield_if_contains_semicolon(&line) { - semicolon_line_opt = Some(result) - } - } - line - }) - .collect::(); - if let Some(line) = semicolon_line_opt { - let regex = Regex::new(r"Vec<\w+>;").unwrap(); - if regex.is_match(&line) { - // The important part of the regex is the ending semicolon. Trait implementations don't use it; - // they just go on with an opening bracket of the function body. Its presence therefore signifies - // we have to do with a trait definition - panic!("the second parsed chunk of code is a trait definition and the implementation lies first") - } - } else { - () //means is a clean function body without semicolon - } - line_undivided_fn_body - } - - let current_dir = current_dir().unwrap(); - let file_path = current_dir.join(PathBuf::from_iter([ - "src", - "accountant", - "db_access_objects", - "receivable_dao.rs", - ])); - let file = match File::open(file_path) { - Ok(file) => file, - Err(_) => match check_if_source_code_is_attached(¤t_dir) { - Skip => return Skip, - _ => panic!( - "if panics, the file receivable_dao.rs probably doesn't exist or \ - has been moved to an unexpected location" - ), - }, - }; - let reader = BufReader::new(file); - let lines_without_fn_trait_definition = - skip_down_to_first_line_saying_new_delinquencies(reader.lines().flatten()); - let function_body_ready_for_final_check = { - let assumed_implemented_function_body = - skip_down_to_first_line_saying_new_delinquencies(lines_without_fn_trait_definition) - .take_while(|line| { - let adjusted_line: String = line - .chars() - .skip_while(|char| char.is_whitespace()) - .collect(); - !adjusted_line.starts_with("fn") - }); - assert_is_not_trait_definition(assumed_implemented_function_body) - }; - if user_defined_functions_detected(&function_body_ready_for_final_check) { - GoAhead - } else { - panic!("was about to test user-defined SQLite functions (slope_drop_high_bytes and slope_drop_low_bytes) - in new_delinquencies() but found out those are absent at the expected place and would leave falsely positive results") - } - } - #[test] - fn our_big_int_sqlite_functions_are_linked_to_receivable_dao_within_accountant() { - //condition: .new_delinquencies() still encompasses our user defined functions (that's why a formal check opens this test) - if let Skip = - check_ongoing_usage_of_user_defined_fns_within_new_delinquencies_for_receivable_dao() - { - eprintln!("skipping test our_big_int_sqlite_functions_are_linked_to_receivable_dao_within_accountant; - was unable to find receivable_dao.rs"); - return; + fn accountant_is_constructed_with_upgraded_db_connection_recognizing_our_extra_sqlite_functions( + ) { + let act = |bootstrapper_config: BootstrapperConfig, + db_initializer: DbInitializerReal, + banned_cache_loader: BannedCacheLoaderMock, + address_leaker: SubsFactoryTestAddrLeaker| { + ActorFactoryReal {}.make_and_start_accountant( + bootstrapper_config, + &db_initializer, + &banned_cache_loader, + &address_leaker, + ) }; - let data_dir = ensure_node_home_directory_exists( - "actor_system_factory", - "our_big_int_sqlite_functions_are_linked_to_receivable_dao_within_accountant", - ); - let _ = DbInitializerReal::default() - .initialize(data_dir.as_ref(), DbInitializationConfig::test_default()) - .unwrap(); - let mut b_config = bc_from_earning_wallet(make_wallet("mine")); - b_config.data_directory = data_dir; - let system = System::new( - "our_big_int_sqlite_functions_are_linked_to_receivable_dao_within_accountant", - ); - let (addr_tx, addr_rv) = bounded(1); - let subject = ActorFactoryReal {}; - - subject.make_and_start_accountant( - b_config, - &DbInitializerReal::default(), - &BannedCacheLoaderMock::default(), - &AccountantSubsFactoryTestOnly { - address_leaker: addr_tx, - }, - ); - let accountant_addr = addr_rv.try_recv().unwrap(); - //this message also stops the system after the check - accountant_addr - .try_send(TestUserDefinedSqliteFnsForNewDelinquencies {}) - .unwrap(); - assert_eq!(system.run(), 0); - //we didn't blow up, it recognized the functions - //this is an example of the error: "no such function: slope_drop_high_bytes" + test_accountant_is_constructed_with_upgraded_db_connection_recognizing_our_extra_sqlite_functions( + "actor_system_factory", + "accountant_is_constructed_with_upgraded_db_connection_recognizing_our_extra_sqlite_functions", + act, + ) } #[test] - fn make_and_start_actors_happy_path() { - let validate_database_chain_params_arc = Arc::new(Mutex::new(vec![])); - let prepare_initial_messages_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(); - let irrelevant_data_dir = PathBuf::new().join("big_directory/small_directory"); - bootstrapper_config.blockchain_bridge_config.chain = Chain::PolyMainnet; - bootstrapper_config.data_directory = irrelevant_data_dir.clone(); - bootstrapper_config.db_password_opt = Some("chameleon".to_string()); - let main_cryptde = main_cryptde(); - let main_cryptde_public_key_before = public_key_for_dyn_cryptde_being_null(main_cryptde); - let alias_cryptde = alias_cryptde(); - 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_before_raw_address = addr_of!(*actor_factory); - let persistent_config_id = ArbitraryIdStamp::new(); - let persistent_config = Box::new( - PersistentConfigurationMock::default().set_arbitrary_id_stamp(persistent_config_id), - ); - let persistent_config_before_raw = addr_of!(*persistent_config); - let tools = ActorSystemFactoryToolsMock::default() - .cryptdes_result(CryptDEPair { - main: main_cryptde, - alias: alias_cryptde, - }) - .validate_database_chain_params(&validate_database_chain_params_arc) - .prepare_initial_messages_params(&prepare_initial_messages_params_arc) - .prepare_initial_messages_result(stream_holder_pool_subs); - let subject = ActorSystemFactoryReal::new(Box::new(tools)); - - let result = - subject.make_and_start_actors(bootstrapper_config, actor_factory, persistent_config); - - let mut validate_database_chain_params = validate_database_chain_params_arc.lock().unwrap(); - let (persistent_config_id_captured, chain) = validate_database_chain_params.remove(0); - assert!(validate_database_chain_params.is_empty()); - assert_eq!(persistent_config_id_captured, persistent_config_id); - assert_eq!(chain, Chain::PolyMainnet); - let mut prepare_initial_messages_params = - prepare_initial_messages_params_arc.lock().unwrap(); - let ( - main_cryptde_after, - alias_cryptde_after, - bootstrapper_config_after, - actor_factory_after, - persistent_config_after, - ) = prepare_initial_messages_params.remove(0); - assert!(prepare_initial_messages_params.is_empty()); - let main_cryptde_public_key_after = - public_key_for_dyn_cryptde_being_null(main_cryptde_after.as_ref()); - assert_eq!( - main_cryptde_public_key_after, - main_cryptde_public_key_before - ); - let alias_cryptde_public_key_after = - public_key_for_dyn_cryptde_being_null(alias_cryptde_after.as_ref()); - assert_eq!( - alias_cryptde_public_key_after, - alias_cryptde_public_key_before - ); - assert_eq!( - bootstrapper_config_after.data_directory, - irrelevant_data_dir - ); - assert_eq!( - bootstrapper_config_after.db_password_opt, - Some("chameleon".to_string()) - ); - assert_eq!( - addr_of!(*actor_factory_after), - actor_factory_before_raw_address - ); - assert_eq!( - addr_of!(*persistent_config_after), - persistent_config_before_raw - ); - let system = System::new("make_and_start_actors_happy_path"); - let msg_of_irrelevant_choice = NodeFromUiMessage { - client_id: 5, - body: UiDescriptorRequest {}.tmb(1), + fn blockchain_bridge_is_constructed_with_correctly_functioning_connections() { + let act = |bootstrapper_config: BootstrapperConfig, + address_leaker: SubsFactoryTestAddrLeaker| { + ActorFactoryReal {} + .make_and_start_blockchain_bridge(&bootstrapper_config, &address_leaker) }; - result - .node_from_ui_sub - .try_send(msg_of_irrelevant_choice.clone()) - .unwrap(); - System::current().stop_with_code(0); - system.run(); - let recording = recording_arc.lock().unwrap(); - let msg = recording.get_record::(0); - assert_eq!(msg, &msg_of_irrelevant_choice); + + test_blockchain_bridge_is_constructed_with_correctly_functioning_connections( + "actor_system_factory", + "blockchain_bridge_is_constructed_with_correctly_functioning_connections", + act, + ) } #[test] @@ -2119,7 +2012,7 @@ mod tests { assert_on_initialization_with_panic_on_migration(&data_dir, &act); } - fn public_key_for_dyn_cryptde_being_null(cryptde: &dyn CryptDE) -> &PublicKey { + fn pk_from_cryptde_null(cryptde: &dyn CryptDE) -> &PublicKey { let null_cryptde = <&CryptDENull>::from(cryptde); null_cryptde.public_key() } diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index da7374b58..9b170709a 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -1,23 +1,28 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. +use crate::accountant::db_access_objects::payable_dao::PayableAccount; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::msgs::{ + BlockchainAgentWithContextMessage, QualifiedPayablesMessage, +}; use crate::accountant::{ - ConsumingWalletBalancesAndQualifiedPayables, ReceivedPayments, ResponseSkeleton, ScanError, - SentPayables, SkeletonOptHolder, + ReceivedPayments, ResponseSkeleton, ScanError, SentPayables, SkeletonOptHolder, }; use crate::accountant::{ReportTransactionReceipts, RequestTransactionReceipts}; -use crate::blockchain::blockchain_interface::{ - BlockchainError, BlockchainInterface, BlockchainInterfaceNull, BlockchainInterfaceWeb3, - PayableTransactionError, ProcessedPayableFallible, +use crate::actor_system_factory::SubsFactory; +use crate::blockchain::blockchain_interface::blockchain_interface_null::BlockchainInterfaceNull; +use crate::blockchain::blockchain_interface::data_structures::errors::{ + BlockchainError, PayableTransactionError, }; +use crate::blockchain::blockchain_interface::data_structures::ProcessedPayableFallible; +use crate::blockchain::blockchain_interface::BlockchainInterface; +use crate::blockchain::blockchain_interface_initializer::BlockchainInterfaceInitializer; use crate::database::db_initializer::{DbInitializationConfig, DbInitializer, DbInitializerReal}; use crate::db_config::config_dao::ConfigDaoReal; use crate::db_config::persistent_configuration::{ PersistentConfiguration, PersistentConfigurationReal, }; -use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; -use crate::sub_lib::blockchain_bridge::{ - BlockchainBridgeSubs, ReportAccountsPayable, RequestBalancesToPayPayables, -}; +use crate::sub_lib::blockchain_bridge::{BlockchainBridgeSubs, OutboundPaymentsInstructions}; use crate::sub_lib::peer_actors::BindMessage; use crate::sub_lib::utils::{db_connection_launch_panic, handle_ui_crash_request}; use crate::sub_lib::wallet::Wallet; @@ -31,10 +36,10 @@ use masq_lib::blockchains::chains::Chain; use masq_lib::logger::Logger; use masq_lib::messages::ScanType; use masq_lib::ui_gateway::NodeFromUiMessage; +use masq_lib::utils::to_string; use regex::Regex; -use std::path::PathBuf; +use std::path::Path; use std::time::SystemTime; -use web3::transports::Http; use web3::types::{BlockNumber, TransactionReceipt, H256}; pub const CRASH_KEY: &str = "BLOCKCHAINBRIDGE"; @@ -45,7 +50,7 @@ pub struct BlockchainBridge { logger: Logger, persistent_config: Box, sent_payable_subs_opt: Option>, - balances_and_payables_sub_opt: Option>, + payable_payments_setup_subs_opt: Option>, received_payments_subs_opt: Option>, scan_error_subs_opt: Option>, crashable: bool, @@ -71,11 +76,8 @@ impl Handler for BlockchainBridge { self.pending_payable_confirmation .report_transaction_receipts_sub_opt = Some(msg.peer_actors.accountant.report_transaction_receipts); - self.balances_and_payables_sub_opt = Some( - msg.peer_actors - .accountant - .report_consuming_wallet_balances_and_qualified_payables, - ); + self.payable_payments_setup_subs_opt = + Some(msg.peer_actors.accountant.report_payable_payments_setup); self.sent_payable_subs_opt = Some(msg.peer_actors.accountant.report_sent_payments); self.received_payments_subs_opt = Some(msg.peer_actors.accountant.report_inbound_payments); self.scan_error_subs_opt = Some(msg.peer_actors.accountant.scan_errors); @@ -132,24 +134,20 @@ impl Handler for BlockchainBridge { } } -impl Handler for BlockchainBridge { +impl Handler for BlockchainBridge { type Result = (); - fn handle(&mut self, msg: RequestBalancesToPayPayables, _ctx: &mut Self::Context) { - self.handle_scan( - Self::handle_request_balances_to_pay_payables, - ScanType::Payables, - msg, - ); + fn handle(&mut self, msg: QualifiedPayablesMessage, _ctx: &mut Self::Context) { + self.handle_scan(Self::handle_qualified_payable_msg, ScanType::Payables, msg); } } -impl Handler for BlockchainBridge { +impl Handler for BlockchainBridge { type Result = (); - fn handle(&mut self, msg: ReportAccountsPayable, _ctx: &mut Self::Context) { + fn handle(&mut self, msg: OutboundPaymentsInstructions, _ctx: &mut Self::Context) { self.handle_scan( - Self::handle_report_accounts_payable, + Self::handle_outbound_payments_instructions, ScanType::Payables, msg, ) @@ -164,11 +162,11 @@ pub struct PendingPayableFingerprintSeeds { #[derive(Debug, PartialEq, Eq, Clone)] pub struct PendingPayableFingerprint { - //Sqlite begins counting from 1 + // Sqlite begins counting from 1 pub rowid: u64, pub timestamp: SystemTime, pub hash: H256, - //Sqlite begins counting from 1 + // We have Sqlite begin counting from 1 pub attempt: u16, pub amount: u128, pub process_error: Option, @@ -194,7 +192,7 @@ impl BlockchainBridge { blockchain_interface, persistent_config, sent_payable_subs_opt: None, - balances_and_payables_sub_opt: None, + payable_payments_setup_subs_opt: None, received_payments_subs_opt: None, scan_error_subs_opt: None, crashable, @@ -206,114 +204,85 @@ impl BlockchainBridge { } } - pub fn make_connections( - blockchain_service_url: Option, - data_directory: PathBuf, - chain: Chain, - ) -> ( - Box, - Box, - ) { - let blockchain_interface: Box = - { - match blockchain_service_url { - Some(url) => match Http::new(&url) { - Ok((event_loop_handle, transport)) => Box::new( - BlockchainInterfaceWeb3::new(transport, event_loop_handle, chain), - ), - Err(e) => panic!("Invalid blockchain node URL: {:?}", e), - }, - None => Box::new(BlockchainInterfaceNull::default()), - } - }; + pub fn initialize_persistent_configuration( + data_directory: &Path, + ) -> Box { let config_dao = Box::new(ConfigDaoReal::new( DbInitializerReal::default() - .initialize( - &data_directory, - DbInitializationConfig::panic_on_migration(), - ) - .unwrap_or_else(|err| db_connection_launch_panic(err, &data_directory)), + .initialize(data_directory, DbInitializationConfig::panic_on_migration()) + .unwrap_or_else(|err| db_connection_launch_panic(err, data_directory)), )); - ( - blockchain_interface, - Box::new(PersistentConfigurationReal::new(config_dao)), - ) + Box::new(PersistentConfigurationReal::new(config_dao)) + } + + pub fn initialize_blockchain_interface( + blockchain_service_url_opt: Option, + chain: Chain, + ) -> Box { + match blockchain_service_url_opt { + Some(url) => { + // TODO if we decided to have interchangeably runtime switchable or simultaneously usable interfaces we will + // probably want to make BlockchainInterfaceInitializer a collaborator that's a part of the actor + BlockchainInterfaceInitializer {}.initialize_interface(&url, chain) + } + None => Box::new(BlockchainInterfaceNull::default()), + } } pub fn make_subs_from(addr: &Addr) -> BlockchainBridgeSubs { BlockchainBridgeSubs { bind: recipient!(addr, BindMessage), - report_accounts_payable: recipient!(addr, ReportAccountsPayable), - request_balances_to_pay_payables: recipient!(addr, RequestBalancesToPayPayables), + outbound_payments_instructions: recipient!(addr, OutboundPaymentsInstructions), + qualified_payables: recipient!(addr, QualifiedPayablesMessage), retrieve_transactions: recipient!(addr, RetrieveTransactions), ui_sub: recipient!(addr, NodeFromUiMessage), request_transaction_receipts: recipient!(addr, RequestTransactionReceipts), } } - fn handle_request_balances_to_pay_payables( + fn handle_qualified_payable_msg( &mut self, - msg: RequestBalancesToPayPayables, + incoming_message: QualifiedPayablesMessage, ) -> Result<(), String> { - let consuming_wallet = match self.consuming_wallet_opt.as_ref() { - Some(wallet) => wallet, - None => { - return Err( - "Cannot inspect available balances for payables while consuming wallet \ - is missing" - .to_string(), - ) - } - }; - //TODO rewrite this into a batch call as soon as GH-629 gets into master - let gas_balance = match self - .blockchain_interface - .get_transaction_fee_balance(consuming_wallet) - { - Ok(gas_balance) => gas_balance, - Err(e) => { - return Err(format!( - "Did not find out gas balance of the consuming wallet: {:?}", - e - )) - } + let consuming_wallet = if let Some(wallet) = self.consuming_wallet_opt.as_ref() { + wallet + } else { + return Err( + "Cannot inspect available balances for payables while consuming wallet is missing" + .to_string(), + ); }; - let token_balance = match self + + let agent = self .blockchain_interface - .get_token_balance(consuming_wallet) - { - Ok(token_balance) => token_balance, - Err(e) => { - return Err(format!( - "Did not find out token balance of the consuming wallet: {:?}", - e - )) - } - }; - let consuming_wallet_balances = { - ConsumingWalletBalances { - gas_currency: gas_balance, - masq_tokens: token_balance, - } - }; - self.balances_and_payables_sub_opt + .build_blockchain_agent(consuming_wallet, &*self.persistent_config) + .map_err(to_string)?; + + let outgoing_message = BlockchainAgentWithContextMessage::new( + incoming_message.protected_qualified_payables, + agent, + incoming_message.response_skeleton_opt, + ); + + self.payable_payments_setup_subs_opt .as_ref() .expect("Accountant is unbound") - .try_send(ConsumingWalletBalancesAndQualifiedPayables { - qualified_payables: msg.accounts, - consuming_wallet_balances, - response_skeleton_opt: msg.response_skeleton_opt, - }) + .try_send(outgoing_message) .expect("Accountant is dead"); Ok(()) } - fn handle_report_accounts_payable(&mut self, msg: ReportAccountsPayable) -> Result<(), String> { + fn handle_outbound_payments_instructions( + &mut self, + msg: OutboundPaymentsInstructions, + ) -> Result<(), String> { let skeleton_opt = msg.response_skeleton_opt; - let result = self.process_payments(&msg); + let agent = msg.agent; + let checked_accounts = msg.affordable_accounts; + let result = self.process_payments(agent, checked_accounts); - let local_processing_result = match &result { + let locally_produced_result = match &result { Err(e) => Err(format!("ReportAccountsPayable: {}", e)), Ok(_) => Ok(()), }; @@ -327,7 +296,7 @@ impl BlockchainBridge { }) .expect("Accountant is dead"); - local_processing_result + locally_produced_result } fn handle_retrieve_transactions(&mut self, msg: RetrieveTransactions) -> Result<(), String> { @@ -339,7 +308,11 @@ impl BlockchainBridge { Ok(Some(mbc)) => mbc, _ => u64::MAX, }; - let end_block = match self.blockchain_interface.get_block_number() { + let end_block = match self + .blockchain_interface + .lower_interface() + .get_block_number() + { Ok(eb) => { if u64::MAX == max_block_count { BlockNumber::Number(eb) @@ -491,38 +464,19 @@ impl BlockchainBridge { fn process_payments( &self, - msg: &ReportAccountsPayable, + agent: Box, + affordable_accounts: Vec, ) -> Result, PayableTransactionError> { - let (consuming_wallet, gas_price) = match self.consuming_wallet_opt.as_ref() { - Some(consuming_wallet) => match self.persistent_config.gas_price() { - Ok(gas_price) => (consuming_wallet, gas_price), - Err(e) => { - return Err(PayableTransactionError::GasPriceQueryFailed(format!( - "{:?}", - e - ))) - } - }, - None => return Err(PayableTransactionError::MissingConsumingWallet), - }; - - let pending_nonce = self - .blockchain_interface - .get_transaction_count(consuming_wallet) - .map_err(PayableTransactionError::TransactionCount)?; + let new_fingerprints_recipient = self.new_fingerprints_recipient(); - let new_fingerprints_recipient = self.get_new_fingerprints_recipient(); - - self.blockchain_interface.send_payables_within_batch( - consuming_wallet, - gas_price, - pending_nonce, + self.blockchain_interface.send_batch_of_payables( + agent, new_fingerprints_recipient, - &msg.accounts, + &affordable_accounts, ) } - fn get_new_fingerprints_recipient(&self) -> &Recipient { + fn new_fingerprints_recipient(&self) -> &Recipient { self.pending_payable_confirmation .new_pp_fingerprints_sub_opt .as_ref() @@ -559,46 +513,72 @@ struct PendingTxInfo { when_sent: SystemTime, } +pub struct BlockchainBridgeSubsFactoryReal {} + +impl SubsFactory for BlockchainBridgeSubsFactoryReal { + fn make(&self, addr: &Addr) -> BlockchainBridgeSubs { + BlockchainBridge::make_subs_from(addr) + } +} + #[cfg(test)] mod tests { use super::*; - use crate::accountant::db_access_objects::dao_utils::from_time_t; - use crate::accountant::db_access_objects::payable_dao::{PayableAccount, PendingPayable}; + use crate::accountant::db_access_objects::payable_dao::PayableAccount; + use crate::accountant::db_access_objects::pending_payable_dao::PendingPayable; + use crate::accountant::db_access_objects::utils::from_time_t; + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::test_utils::BlockchainAgentMock; + use crate::accountant::scanners::test_utils::protect_payables_in_test; use crate::accountant::test_utils::make_pending_payable_fingerprint; - use crate::accountant::ConsumingWalletBalancesAndQualifiedPayables; use crate::blockchain::bip32::Bip32EncryptionKeyProvider; - use crate::blockchain::blockchain_interface::ProcessedPayableFallible::Correct; - use crate::blockchain::blockchain_interface::{ - BlockchainError, BlockchainTransaction, LatestBlockNumber, RetrievedBlockchainTransactions, + use crate::blockchain::blockchain_interface::blockchain_interface_null::BlockchainInterfaceNull; + use crate::blockchain::blockchain_interface::data_structures::errors::{ + BlockchainAgentBuildError, PayableTransactionError, + }; + use crate::blockchain::blockchain_interface::data_structures::{ + BlockchainTransaction, RetrievedBlockchainTransactions, }; + use crate::blockchain::blockchain_interface::lower_level_interface::LatestBlockNumber; + use crate::blockchain::blockchain_interface::test_utils::LowBlockchainIntMock; use crate::blockchain::test_utils::{make_tx_hash, BlockchainInterfaceMock}; use crate::db_config::persistent_configuration::PersistentConfigError; use crate::match_every_type_id; use crate::node_test_utils::check_timestamp; - use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMock; use crate::test_utils::recorder::{make_recorder, peer_actors_builder}; use crate::test_utils::recorder_stop_conditions::StopCondition; use crate::test_utils::recorder_stop_conditions::StopConditions; + use crate::test_utils::unshared_test_utils::arbitrary_id_stamp::ArbitraryIdStamp; use crate::test_utils::unshared_test_utils::{ assert_on_initialization_with_panic_on_migration, configure_default_persistent_config, - prove_that_crash_request_handler_is_hooked_up, ZERO, + prove_that_crash_request_handler_is_hooked_up, AssertionsMessage, ZERO, }; use crate::test_utils::{make_paying_wallet, make_wallet}; use actix::System; use ethereum_types::U64; use ethsign_crypto::Keccak256; - use masq_lib::constants::DEFAULT_CHAIN; use masq_lib::messages::ScanType; use masq_lib::test_utils::logging::init_test_logging; use masq_lib::test_utils::logging::TestLogHandler; - use masq_lib::test_utils::utils::ensure_node_home_directory_exists; + use masq_lib::test_utils::utils::{ensure_node_home_directory_exists, TEST_DEFAULT_CHAIN}; use rustc_hex::FromHex; use std::any::TypeId; use std::path::Path; use std::sync::{Arc, Mutex}; use std::time::{Duration, SystemTime}; - use web3::types::{TransactionReceipt, H160, H256, U256}; + use web3::types::{TransactionReceipt, H160, H256}; + + impl Handler> for BlockchainBridge { + type Result = (); + + fn handle( + &mut self, + msg: AssertionsMessage, + _ctx: &mut Self::Context, + ) -> Self::Result { + (msg.assertions)(self) + } + } #[test] fn constants_have_correct_values() { @@ -639,6 +619,16 @@ mod tests { )); } + #[test] + fn blockchain_interface_null_as_result_of_missing_blockchain_service_url() { + let result = BlockchainBridge::initialize_blockchain_interface(None, TEST_DEFAULT_CHAIN); + + result + .as_any() + .downcast_ref::() + .unwrap(); + } + #[test] fn blockchain_bridge_receives_bind_message_without_consuming_private_key() { init_test_logging(); @@ -664,90 +654,42 @@ mod tests { } #[test] - #[should_panic(expected = "Invalid blockchain node URL")] - fn invalid_blockchain_url_produces_panic() { - let data_directory = PathBuf::new(); //never reached - let blockchain_service_url = Some("http://λ:8545".to_string()); - let _ = BlockchainBridge::make_connections( - blockchain_service_url, - data_directory, - DEFAULT_CHAIN, - ); - } - - #[test] - fn report_accounts_payable_returns_error_when_there_is_no_consuming_wallet_configured() { - let blockchain_interface_mock = BlockchainInterfaceMock::default(); - let persistent_configuration_mock = PersistentConfigurationMock::default(); - let (accountant, _, accountant_recording_arc) = make_recorder(); - let recipient = accountant.start().recipient(); - let mut subject = BlockchainBridge::new( - Box::new(blockchain_interface_mock), - Box::new(persistent_configuration_mock), - false, - None, - ); - subject.sent_payable_subs_opt = Some(recipient); - let request = ReportAccountsPayable { - accounts: vec![PayableAccount { - wallet: make_wallet("blah"), - balance_wei: 42, - last_paid_timestamp: SystemTime::now(), - pending_payable_opt: None, - }], - response_skeleton_opt: None, - }; - let system = System::new("test"); - - let result = subject.handle_report_accounts_payable(request); - - System::current().stop(); - assert_eq!(system.run(), 0); - assert_eq!( - result, - Err("ReportAccountsPayable: Missing consuming wallet to pay payable from".to_string()) - ); - let accountant_recording = accountant_recording_arc.lock().unwrap(); - let sent_payables_msg = accountant_recording.get_record::(0); - assert_eq!( - sent_payables_msg, - &SentPayables { - payment_procedure_result: Err(PayableTransactionError::MissingConsumingWallet), - response_skeleton_opt: None - } - ); - assert_eq!(accountant_recording.len(), 1) - } - - #[test] - fn handle_request_balances_to_pay_payables_reports_balances_and_payables_back_to_accountant() { + fn qualified_payables_msg_is_handled_and_new_msg_with_an_added_blockchain_agent_returns_to_accountant( + ) { let system = System::new( - "handle_request_balances_to_pay_payables_reports_balances_and_payables_back_to_accountant", + "qualified_payables_msg_is_handled_and_new_msg_with_an_added_blockchain_agent_returns_to_accountant", ); - let get_transaction_fee_balance_params_arc = Arc::new(Mutex::new(vec![])); - let get_token_balance_params_arc = Arc::new(Mutex::new(vec![])); + let build_blockchain_agent_params_arc = Arc::new(Mutex::new(vec![])); let (accountant, _, accountant_recording_arc) = make_recorder(); - let gas_balance = U256::from(4455); - let token_balance = U256::from(112233); - let wallet_balances_found = ConsumingWalletBalances { - gas_currency: gas_balance, - masq_tokens: token_balance, - }; + let agent_id_stamp = ArbitraryIdStamp::new(); + let agent = BlockchainAgentMock::default().set_arbitrary_id_stamp(agent_id_stamp); let blockchain_interface = BlockchainInterfaceMock::default() - .get_transaction_fee_balance_params(&get_transaction_fee_balance_params_arc) - .get_transaction_fee_balance_result(Ok(gas_balance)) - .get_token_balance_params(&get_token_balance_params_arc) - .get_token_balance_result(Ok(token_balance)); + .build_blockchain_agent_params(&build_blockchain_agent_params_arc) + .build_blockchain_agent_result(Ok(Box::new(agent))); let consuming_wallet = make_paying_wallet(b"somewallet"); - let persistent_configuration = PersistentConfigurationMock::default(); - let qualified_accounts = vec![PayableAccount { - wallet: make_wallet("booga"), - balance_wei: 78_654_321, - last_paid_timestamp: SystemTime::now() - .checked_sub(Duration::from_secs(1000)) - .unwrap(), - pending_payable_opt: None, - }]; + let persistent_config_id_stamp = ArbitraryIdStamp::new(); + let persistent_configuration = PersistentConfigurationMock::default() + .set_arbitrary_id_stamp(persistent_config_id_stamp); + let wallet_1 = make_wallet("booga"); + let wallet_2 = make_wallet("gulp"); + let qualified_payables = vec![ + PayableAccount { + wallet: wallet_1.clone(), + balance_wei: 78_654_321_124, + last_paid_timestamp: SystemTime::now() + .checked_sub(Duration::from_secs(1000)) + .unwrap(), + pending_payable_opt: None, + }, + PayableAccount { + wallet: wallet_2.clone(), + balance_wei: 60_457_111_003, + last_paid_timestamp: SystemTime::now() + .checked_sub(Duration::from_secs(500)) + .unwrap(), + pending_payable_opt: None, + }, + ]; let subject = BlockchainBridge::new( Box::new(blockchain_interface), Box::new(persistent_configuration), @@ -757,50 +699,47 @@ mod tests { let addr = subject.start(); let subject_subs = BlockchainBridge::make_subs_from(&addr); let peer_actors = peer_actors_builder().accountant(accountant).build(); - send_bind_message!(subject_subs, peer_actors); - - addr.try_send(RequestBalancesToPayPayables { - accounts: qualified_accounts.clone(), + let qualified_payables = protect_payables_in_test(qualified_payables.clone()); + let qualified_payables_msg = QualifiedPayablesMessage { + protected_qualified_payables: qualified_payables.clone(), response_skeleton_opt: Some(ResponseSkeleton { client_id: 11122, context_id: 444, }), - }) - .unwrap(); + }; + send_bind_message!(subject_subs, peer_actors); + + addr.try_send(qualified_payables_msg).unwrap(); System::current().stop(); system.run(); - let get_transaction_fee_balance_params = - get_transaction_fee_balance_params_arc.lock().unwrap(); + + let build_blockchain_agent_params = build_blockchain_agent_params_arc.lock().unwrap(); assert_eq!( - *get_transaction_fee_balance_params, - vec![consuming_wallet.clone()] + *build_blockchain_agent_params, + vec![(consuming_wallet.clone(), persistent_config_id_stamp)] ); - let get_token_balance_params = get_token_balance_params_arc.lock().unwrap(); - assert_eq!(*get_token_balance_params, vec![consuming_wallet]); let accountant_received_payment = accountant_recording_arc.lock().unwrap(); - assert_eq!(accountant_received_payment.len(), 1); - let reported_balances_and_qualified_accounts: &ConsumingWalletBalancesAndQualifiedPayables = + let blockchain_agent_with_context_msg_actual: &BlockchainAgentWithContextMessage = accountant_received_payment.get_record(0); assert_eq!( - reported_balances_and_qualified_accounts, - &ConsumingWalletBalancesAndQualifiedPayables { - qualified_payables: qualified_accounts, - consuming_wallet_balances: wallet_balances_found, - response_skeleton_opt: Some(ResponseSkeleton { - client_id: 11122, - context_id: 444 - }) - } + blockchain_agent_with_context_msg_actual.protected_qualified_payables, + qualified_payables + ); + assert_eq!( + blockchain_agent_with_context_msg_actual + .agent + .arbitrary_id_stamp(), + agent_id_stamp ); + assert_eq!(accountant_received_payment.len(), 1); } - fn assert_failure_during_balance_inspection( - test_name: &str, - blockchain_interface: BlockchainInterfaceMock, - error_msg: &str, - ) { + #[test] + fn build_of_blockchain_agent_throws_err_out_and_ends_handling_qualified_payables_message() { init_test_logging(); + let test_name = + "build_of_blockchain_agent_throws_err_out_and_ends_handling_qualified_payables_message"; let (accountant, _, accountant_recording_arc) = make_recorder(); let scan_error_recipient: Recipient = accountant .system_stop_conditions(match_every_type_id!(ScanError)) @@ -808,6 +747,10 @@ mod tests { .recipient(); let persistent_configuration = PersistentConfigurationMock::default(); let consuming_wallet = make_wallet(test_name); + let blockchain_interface = BlockchainInterfaceMock::default() + .build_blockchain_agent_result(Err(BlockchainAgentBuildError::GasPrice( + PersistentConfigError::NotPresent, + ))); let mut subject = BlockchainBridge::new( Box::new(blockchain_interface), Box::new(persistent_configuration), @@ -816,13 +759,13 @@ mod tests { ); subject.logger = Logger::new(test_name); subject.scan_error_subs_opt = Some(scan_error_recipient); - let request = RequestBalancesToPayPayables { - accounts: vec![PayableAccount { + let request = QualifiedPayablesMessage { + protected_qualified_payables: protect_payables_in_test(vec![PayableAccount { wallet: make_wallet("blah"), balance_wei: 42, last_paid_timestamp: SystemTime::now(), pending_payable_opt: None, - }], + }]), response_skeleton_opt: Some(ResponseSkeleton { client_id: 11, context_id: 2323, @@ -839,6 +782,8 @@ mod tests { let recording = accountant_recording_arc.lock().unwrap(); let message = recording.get_record::(0); assert_eq!(recording.len(), 1); + let expected_error_msg = "Blockchain agent construction failed at fetching gas \ + price from the database: NotPresent"; assert_eq!( message, &ScanError { @@ -847,43 +792,15 @@ mod tests { client_id: 11, context_id: 2323 }), - msg: error_msg.to_string() + msg: expected_error_msg.to_string() } ); - TestLogHandler::new().exists_log_containing(&format!("WARN: {}: {}", test_name, error_msg)); - } - - #[test] - fn handle_request_balances_to_pay_payables_fails_on_inspection_of_gas_balance() { - let test_name = - "handle_request_balances_to_pay_payables_fails_on_inspection_of_gas_balance"; - let blockchain_interface = BlockchainInterfaceMock::default() - .get_transaction_fee_balance_result(Err(BlockchainError::QueryFailed( - "Lazy and yet you're asking for balances?".to_string(), - ))); - let error_msg = "Did not find out gas balance of the consuming wallet: \ - QueryFailed(\"Lazy and yet you're asking for balances?\")"; - - assert_failure_during_balance_inspection(test_name, blockchain_interface, error_msg) - } - - #[test] - fn handle_request_balances_to_pay_payables_fails_on_inspection_of_token_balance() { - let test_name = - "handle_request_balances_to_pay_payables_fails_on_inspection_of_token_balance"; - let blockchain_interface = BlockchainInterfaceMock::default() - .get_transaction_fee_balance_result(Ok(U256::from(45678))) - .get_token_balance_result(Err(BlockchainError::QueryFailed( - "Go get you a job. This balance must be deserved".to_string(), - ))); - let error_msg = "Did not find out token balance of the consuming wallet: QueryFailed(\ - \"Go get you a job. This balance must be deserved\")"; - - assert_failure_during_balance_inspection(test_name, blockchain_interface, error_msg) + TestLogHandler::new() + .exists_log_containing(&format!("WARN: {test_name}: {expected_error_msg}")); } #[test] - fn handle_request_balances_to_pay_payables_fails_at_missing_consuming_wallet() { + fn handle_qualified_payable_msg_fails_at_missing_consuming_wallet() { let blockchain_interface = BlockchainInterfaceMock::default(); let persistent_configuration = PersistentConfigurationMock::default(); let mut subject = BlockchainBridge::new( @@ -892,17 +809,17 @@ mod tests { false, None, ); - let request = RequestBalancesToPayPayables { - accounts: vec![PayableAccount { + let request = QualifiedPayablesMessage { + protected_qualified_payables: protect_payables_in_test(vec![PayableAccount { wallet: make_wallet("blah"), balance_wei: 4254, last_paid_timestamp: SystemTime::now(), pending_payable_opt: None, - }], + }]), response_skeleton_opt: None, }; - let result = subject.handle_request_balances_to_pay_payables(request); + let result = subject.handle_qualified_payable_msg(request); assert_eq!( result, @@ -914,37 +831,34 @@ mod tests { } #[test] - fn handle_report_accounts_payable_transacts_and_sends_finished_payments_back_to_accountant() { + fn handle_outbound_payments_instructions_sees_payments_happen_and_sends_payment_results_back_to_accountant( + ) { let system = - System::new("handle_report_accounts_payable_transacts_and_sends_finished_payments_back_to_accountant"); - let get_transaction_count_params_arc = Arc::new(Mutex::new(vec![])); - let send_payables_within_batch_params_arc = Arc::new(Mutex::new(vec![])); + System::new("handle_outbound_payments_instructions_sees_payments_happen_and_sends_payment_results_back_to_accountant"); + let send_batch_of_payables_params_arc = Arc::new(Mutex::new(vec![])); let (accountant, _, accountant_recording_arc) = make_recorder(); let accountant = accountant.system_stop_conditions(match_every_type_id!(PendingPayableFingerprintSeeds)); let wallet_account_1 = make_wallet("blah"); let wallet_account_2 = make_wallet("foo"); + let blockchain_interface_id_stamp = ArbitraryIdStamp::new(); let blockchain_interface_mock = BlockchainInterfaceMock::default() - .get_transaction_count_params(&get_transaction_count_params_arc) - .get_transaction_count_result(Ok(U256::from(1u64))) - .send_payables_within_batch_params(&send_payables_within_batch_params_arc) - .send_payables_within_batch_result(Ok(vec![ - Correct(PendingPayable { + .set_arbitrary_id_stamp(blockchain_interface_id_stamp) + .send_batch_of_payables_params(&send_batch_of_payables_params_arc) + .send_batch_of_payables_result(Ok(vec![ + Ok(PendingPayable { recipient_wallet: wallet_account_1.clone(), hash: H256::from("sometransactionhash".keccak256()), }), - Correct(PendingPayable { + Ok(PendingPayable { recipient_wallet: wallet_account_2.clone(), hash: H256::from("someothertransactionhash".keccak256()), }), ])); - let expected_gas_price = 145u64; - let persistent_configuration_mock = - PersistentConfigurationMock::default().gas_price_result(Ok(expected_gas_price)); let consuming_wallet = make_paying_wallet(b"somewallet"); let subject = BlockchainBridge::new( Box::new(blockchain_interface_mock), - Box::new(persistent_configuration_mock), + Box::new(PersistentConfigurationMock::default()), false, Some(consuming_wallet.clone()), ); @@ -965,11 +879,14 @@ mod tests { pending_payable_opt: None, }, ]; + let agent_id_stamp = ArbitraryIdStamp::new(); + let agent = BlockchainAgentMock::default().set_arbitrary_id_stamp(agent_id_stamp); send_bind_message!(subject_subs, peer_actors); let _ = addr - .try_send(ReportAccountsPayable { - accounts: accounts.clone(), + .try_send(OutboundPaymentsInstructions { + affordable_accounts: accounts.clone(), + agent: Box::new(agent), response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, context_id: 4321, @@ -979,34 +896,24 @@ mod tests { System::current().stop(); system.run(); - let mut send_payables_within_batch_params = - send_payables_within_batch_params_arc.lock().unwrap(); + let mut send_batch_of_payables_params = send_batch_of_payables_params_arc.lock().unwrap(); //cannot assert on the captured recipient as its actor is gone after the System stops spinning - let ( - consuming_wallet_actual, - gas_price_actual, - nonce_actual, - _recipient_actual, - accounts_actual, - ) = send_payables_within_batch_params.remove(0); - assert!(send_payables_within_batch_params.is_empty()); - assert_eq!(consuming_wallet_actual, consuming_wallet.clone()); - assert_eq!(gas_price_actual, expected_gas_price); - assert_eq!(nonce_actual, U256::from(1u64)); + let (actual_agent_id_stamp, _recipient_actual, accounts_actual) = + send_batch_of_payables_params.remove(0); + assert!(send_batch_of_payables_params.is_empty()); + assert_eq!(actual_agent_id_stamp, agent_id_stamp); assert_eq!(accounts_actual, accounts); - let get_transaction_count_params = get_transaction_count_params_arc.lock().unwrap(); - assert_eq!(*get_transaction_count_params, vec![consuming_wallet]); let accountant_recording = accountant_recording_arc.lock().unwrap(); let sent_payments_msg = accountant_recording.get_record::(0); assert_eq!( *sent_payments_msg, SentPayables { payment_procedure_result: Ok(vec![ - Correct(PendingPayable { + Ok(PendingPayable { recipient_wallet: wallet_account_1, hash: H256::from("sometransactionhash".keccak256()) }), - Correct(PendingPayable { + Ok(PendingPayable { recipient_wallet: wallet_account_2, hash: H256::from("someothertransactionhash".keccak256()) }) @@ -1021,9 +928,9 @@ mod tests { } #[test] - fn handle_report_accounts_payable_transmits_eleventh_hour_error_back_to_accountant() { + fn handle_outbound_payments_instructions_sends_eleventh_hour_error_back_to_accountant() { let system = System::new( - "handle_report_accounts_payable_transmits_eleventh_hour_error_back_to_accountant", + "handle_outbound_payments_instructions_sends_eleventh_hour_error_back_to_accountant", ); let (accountant, _, accountant_recording_arc) = make_recorder(); let hash = make_tx_hash(0xde); @@ -1035,10 +942,8 @@ mod tests { hashes: vec![hash], }); let blockchain_interface_mock = BlockchainInterfaceMock::default() - .get_transaction_count_result(Ok(U256::from(1u64))) - .send_payables_within_batch_result(expected_error.clone()); - let persistent_configuration_mock = - PersistentConfigurationMock::default().gas_price_result(Ok(123)); + .send_batch_of_payables_result(expected_error.clone()); + let persistent_configuration_mock = PersistentConfigurationMock::default(); let consuming_wallet = make_paying_wallet(b"somewallet"); let subject = BlockchainBridge::new( Box::new(blockchain_interface_mock), @@ -1055,11 +960,13 @@ mod tests { last_paid_timestamp: from_time_t(150_000_000), pending_payable_opt: None, }]; + let agent = BlockchainAgentMock::default(); send_bind_message!(subject_subs, peer_actors); let _ = addr - .try_send(ReportAccountsPayable { - accounts, + .try_send(OutboundPaymentsInstructions { + affordable_accounts: accounts, + agent: Box::new(agent), response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, context_id: 4321, @@ -1100,149 +1007,46 @@ mod tests { assert_eq!(accountant_recording.len(), 2) } - #[test] - fn report_accounts_payable_returns_error_fetching_pending_nonce() { - let blockchain_interface_mock = BlockchainInterfaceMock::default() - .get_transaction_count_result(Err(BlockchainError::QueryFailed( - "What the hack...??".to_string(), - ))); - let consuming_wallet = make_wallet("somewallet"); - let persistent_configuration_mock = - PersistentConfigurationMock::new().gas_price_result(Ok(3u64)); - let subject = BlockchainBridge::new( - Box::new(blockchain_interface_mock), - Box::new(persistent_configuration_mock), - false, - Some(consuming_wallet), - ); - let request = ReportAccountsPayable { - accounts: vec![PayableAccount { - wallet: make_wallet("blah"), - balance_wei: 123_456, - last_paid_timestamp: SystemTime::now(), - pending_payable_opt: None, - }], - response_skeleton_opt: None, - }; - - let result = subject.process_payments(&request); - - assert_eq!( - result, - Err(PayableTransactionError::TransactionCount( - BlockchainError::QueryFailed("What the hack...??".to_string()) - )) - ); - } - #[test] fn process_payments_returns_error_from_sending_batch() { let transaction_hash = make_tx_hash(789); let blockchain_interface_mock = BlockchainInterfaceMock::default() - .get_transaction_count_result(Ok(web3::types::U256::from(1))) - .send_payables_within_batch_result(Err(PayableTransactionError::Sending { - msg: "failure from exhaustion".to_string(), + .send_batch_of_payables_result(Err(PayableTransactionError::Sending { + msg: "failure from chronic exhaustion".to_string(), hashes: vec![transaction_hash], })); let consuming_wallet = make_wallet("somewallet"); - let persistent_configuration_mock = - PersistentConfigurationMock::new().gas_price_result(Ok(3u64)); + let persistent_configuration_mock = PersistentConfigurationMock::new(); let mut subject = BlockchainBridge::new( Box::new(blockchain_interface_mock), Box::new(persistent_configuration_mock), false, Some(consuming_wallet.clone()), ); - let request = ReportAccountsPayable { - accounts: vec![PayableAccount { - wallet: make_wallet("blah"), - balance_wei: 424_454, - last_paid_timestamp: SystemTime::now(), - pending_payable_opt: None, - }], - response_skeleton_opt: None, - }; + let checked_accounts = vec![PayableAccount { + wallet: make_wallet("blah"), + balance_wei: 424_454, + last_paid_timestamp: SystemTime::now(), + pending_payable_opt: None, + }]; + let agent = Box::new(BlockchainAgentMock::default()); let (accountant, _, _) = make_recorder(); let fingerprint_recipient = accountant.start().recipient(); subject .pending_payable_confirmation .new_pp_fingerprints_sub_opt = Some(fingerprint_recipient); - let result = subject.process_payments(&request); + let result = subject.process_payments(agent, checked_accounts); assert_eq!( result, Err(PayableTransactionError::Sending { - msg: "failure from exhaustion".to_string(), + msg: "failure from chronic exhaustion".to_string(), hashes: vec![transaction_hash] }) ); } - #[test] - fn handle_report_accounts_payable_manages_gas_price_error() { - init_test_logging(); - let (accountant, _, accountant_recording_arc) = make_recorder(); - let accountant_addr = accountant - .system_stop_conditions(match_every_type_id!(ScanError)) - .start(); - let sent_payables_recipient = accountant_addr.clone().recipient(); - let scan_error_recipient = accountant_addr.recipient(); - let blockchain_interface_mock = BlockchainInterfaceMock::default() - .get_transaction_count_result(Ok(web3::types::U256::from(1))); - let persistent_configuration_mock = PersistentConfigurationMock::new() - .gas_price_result(Err(PersistentConfigError::TransactionError)); - let consuming_wallet = make_wallet("somewallet"); - let mut subject = BlockchainBridge::new( - Box::new(blockchain_interface_mock), - Box::new(persistent_configuration_mock), - false, - Some(consuming_wallet), - ); - subject.sent_payable_subs_opt = Some(sent_payables_recipient); - subject.scan_error_subs_opt = Some(scan_error_recipient); - let request = ReportAccountsPayable { - accounts: vec![PayableAccount { - wallet: make_wallet("blah"), - balance_wei: 42, - last_paid_timestamp: SystemTime::now(), - pending_payable_opt: None, - }], - response_skeleton_opt: None, - }; - let subject_addr = subject.start(); - let system = System::new("test"); - - subject_addr.try_send(request).unwrap(); - - system.run(); - let recording = accountant_recording_arc.lock().unwrap(); - let actual_sent_payable_msg = recording.get_record::(0); - assert_eq!( - actual_sent_payable_msg, - &SentPayables { - payment_procedure_result: Err(PayableTransactionError::GasPriceQueryFailed( - "TransactionError".to_string() - )), - response_skeleton_opt: None - } - ); - let actual_scan_err_msg = recording.get_record::(1); - assert_eq!( - actual_scan_err_msg, - &ScanError { - scan_type: ScanType::Payables, - response_skeleton_opt: None, - msg: "ReportAccountsPayable: Unsuccessful gas price query: \"TransactionError\"" - .to_string() - } - ); - assert_eq!(recording.len(), 2); - TestLogHandler::new().exists_log_containing( - "WARN: BlockchainBridge: ReportAccountsPayable: Unsuccessful gas price query: \"TransactionError\"", - ); - } - #[test] fn blockchain_bridge_processes_requests_for_transaction_receipts_when_all_were_ok() { let get_transaction_receipt_params_arc = Arc::new(Mutex::new(vec![])); @@ -1319,11 +1123,13 @@ mod tests { .system_stop_conditions(match_every_type_id!(ScanError)) .start() .recipient(); + let lower_interface = LowBlockchainIntMock::default() + .get_block_number_result(LatestBlockNumber::Ok(U64::from(1234u64))); let blockchain_interface = BlockchainInterfaceMock::default() .retrieve_transactions_result(Err(BlockchainError::QueryFailed( "we have no luck".to_string(), ))) - .get_block_number_result(LatestBlockNumber::Ok(U64::from(1234u64))); + .lower_interface_results(Box::new(lower_interface)); let persistent_config = PersistentConfigurationMock::new() .max_block_count_result(Ok(Some(100_000))) .start_block_result(Ok(5)); // no set_start_block_result: set_start_block() must not be called @@ -1478,7 +1284,7 @@ mod tests { let (accountant, _, accountant_recording) = make_recorder(); let recipient = accountant.start().recipient(); let mut subject = BlockchainBridge::new( - Box::new(BlockchainInterfaceNull::default()), + Box::new(BlockchainInterfaceMock::default()), Box::new(PersistentConfigurationMock::default()), false, Some(Wallet::new("mine")), @@ -1612,19 +1418,20 @@ mod tests { }, ], }; + let lower_interface = + LowBlockchainIntMock::default().get_block_number_result(LatestBlockNumber::Err( + BlockchainError::QueryFailed("Failed to read the latest block number".to_string()), + )); let blockchain_interface_mock = BlockchainInterfaceMock::default() .retrieve_transactions_params(&retrieve_transactions_params_arc) .retrieve_transactions_result(Ok(expected_transactions.clone())) - .get_block_number_result(LatestBlockNumber::Err(BlockchainError::QueryFailed( - "Failed to read the latest block number".to_string(), - ))); + .lower_interface_results(Box::new(lower_interface)); let set_start_block_params_arc = Arc::new(Mutex::new(vec![])); let persistent_config = PersistentConfigurationMock::new() .max_block_count_result(Ok(Some(10000u64))) .start_block_result(Ok(6)) .set_start_block_params(&set_start_block_params_arc) .set_start_block_result(Ok(())); - let subject = BlockchainBridge::new( Box::new(blockchain_interface_mock), Box::new(persistent_config), @@ -1705,10 +1512,12 @@ mod tests { ], }; let latest_block_number = LatestBlockNumber::Ok(1024u64.into()); + let lower_interface = + LowBlockchainIntMock::default().get_block_number_result(latest_block_number); let blockchain_interface_mock = BlockchainInterfaceMock::default() .retrieve_transactions_params(&retrieve_transactions_params_arc) .retrieve_transactions_result(Ok(expected_transactions.clone())) - .get_block_number_result(latest_block_number); + .lower_interface_results(Box::new(lower_interface)); let set_start_block_params_arc = Arc::new(Mutex::new(vec![])); let persistent_config = PersistentConfigurationMock::new() .max_block_count_result(Ok(Some(10000u64))) @@ -1770,12 +1579,14 @@ mod tests { #[test] fn processing_of_received_payments_continues_even_if_no_payments_are_detected() { init_test_logging(); + let lower_interface = + LowBlockchainIntMock::default().get_block_number_result(Ok(0u64.into())); let blockchain_interface_mock = BlockchainInterfaceMock::default() .retrieve_transactions_result(Ok(RetrievedBlockchainTransactions { new_start_block: 7, transactions: vec![], })) - .get_block_number_result(Ok(0u64.into())); + .lower_interface_results(Box::new(lower_interface)); let set_start_block_params_arc = Arc::new(Mutex::new(vec![])); let persistent_config = PersistentConfigurationMock::new() .max_block_count_result(Ok(Some(10000u64))) @@ -1835,8 +1646,10 @@ mod tests { expected = "Cannot retrieve start block from database; payments to you may not be processed: TransactionError" )] fn handle_retrieve_transactions_panics_if_start_block_cannot_be_read() { + let lower_interface = + LowBlockchainIntMock::default().get_block_number_result(Ok(0u64.into())); let blockchain_interface = - BlockchainInterfaceMock::default().get_block_number_result(Ok(0u64.into())); + BlockchainInterfaceMock::default().lower_interface_results(Box::new(lower_interface)); let persistent_config = PersistentConfigurationMock::new() .start_block_result(Err(PersistentConfigError::TransactionError)); let mut subject = BlockchainBridge::new( @@ -1860,10 +1673,11 @@ mod tests { fn handle_retrieve_transactions_panics_if_start_block_cannot_be_written() { let persistent_config = PersistentConfigurationMock::new() .start_block_result(Ok(1234)) - .set_start_block_result(Err(PersistentConfigError::TransactionError)) - .max_block_count_result(Ok(Some(10000u64))); + .max_block_count_result(Ok(Some(10000u64))) + .set_start_block_result(Err(PersistentConfigError::TransactionError)); + let lower_interface = + LowBlockchainIntMock::default().get_block_number_result(Ok(0u64.into())); let blockchain_interface = BlockchainInterfaceMock::default() - .get_block_number_result(Ok(0u64.into())) .retrieve_transactions_result(Ok(RetrievedBlockchainTransactions { new_start_block: 1234, transactions: vec![BlockchainTransaction { @@ -1871,7 +1685,8 @@ mod tests { from: make_wallet("somewallet"), wei_amount: 2345, }], - })); + })) + .lower_interface_results(Box::new(lower_interface)); let mut subject = BlockchainBridge::new( Box::new(blockchain_interface), Box::new(persistent_config), @@ -2158,13 +1973,150 @@ mod tests { ); let act = |data_dir: &Path| { - BlockchainBridge::make_connections( - Some("http://127.0.0.1".to_string()), - data_dir.to_path_buf(), - Chain::PolyMumbai, - ); + BlockchainBridge::initialize_persistent_configuration(data_dir); }; assert_on_initialization_with_panic_on_migration(&data_dir, &act); } } + +#[cfg(test)] +pub mod exportable_test_parts { + use super::*; + use crate::bootstrapper::BootstrapperConfig; + use crate::test_utils::http_test_server::TestServer; + use crate::test_utils::make_wallet; + use crate::test_utils::recorder::make_blockchain_bridge_subs_from_recorder; + use crate::test_utils::unshared_test_utils::{AssertionsMessage, SubsFactoryTestAddrLeaker}; + use actix::System; + use crossbeam_channel::{bounded, Receiver}; + use masq_lib::test_utils::utils::{ensure_node_home_directory_exists, TEST_DEFAULT_CHAIN}; + use masq_lib::utils::find_free_port; + use serde_json::Value::Object; + use serde_json::{Map, Value}; + use std::net::Ipv4Addr; + + impl SubsFactory + for SubsFactoryTestAddrLeaker + { + fn make(&self, addr: &Addr) -> BlockchainBridgeSubs { + self.send_leaker_msg_and_return_meaningless_subs( + addr, + make_blockchain_bridge_subs_from_recorder, + ) + } + } + + pub fn test_blockchain_bridge_is_constructed_with_correctly_functioning_connections( + test_module: &str, + test_name: &str, + act: A, + ) where + A: FnOnce( + BootstrapperConfig, + SubsFactoryTestAddrLeaker, + ) -> BlockchainBridgeSubs, + { + fn prepare_db_with_unique_value(data_dir: &Path, gas_price: u64) { + let mut persistent_config = { + let conn = DbInitializerReal::default() + .initialize(data_dir, DbInitializationConfig::test_default()) + .unwrap(); + PersistentConfigurationReal::from(conn) + }; + persistent_config.set_gas_price(gas_price).unwrap() + } + fn launch_prepared_test_server() -> (TestServer, String) { + let port = find_free_port(); + let server_url = format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port); + ( + TestServer::start( + port, + vec![br#"{"jsonrpc":"2.0","id":0,"result":someGarbage}"#.to_vec()], + ), + server_url, + ) + } + fn send_rpc_request_to_assert_on_later_and_assert_db_connection( + actor_addr_rx: Receiver>, + wallet: Wallet, + expected_gas_price: u64, + ) { + let blockchain_bridge_addr = actor_addr_rx.try_recv().unwrap(); + let msg = AssertionsMessage { + assertions: Box::new(move |bb: &mut BlockchainBridge| { + // We will assert on soundness of the connection by checking the receipt of + // this request + let _result = bb + .blockchain_interface + .lower_interface() + .get_service_fee_balance(&wallet); + + // Asserting that we can look into the expected db from here, meaning the + // PersistentConfiguration was set up correctly + assert_eq!( + bb.persistent_config.gas_price().unwrap(), + expected_gas_price + ); + // I don't know why exactly but the standard position + // of this call doesn't work + System::current().stop(); + }), + }; + blockchain_bridge_addr.try_send(msg).unwrap(); + } + fn assert_blockchain_interface_connection(test_server: &TestServer, wallet: Wallet) { + let requests = test_server.requests_so_far(); + let bodies: Vec = requests + .into_iter() + .map(|request| serde_json::from_slice(&request.body()).unwrap()) + .collect(); + let params = &bodies[0]["params"]; + let expected_params = { + let mut map = Map::new(); + let hashed_data = format!( + "0x70a08231000000000000000000000000{}", + &wallet.to_string()[2..] + ); + map.insert("data".to_string(), Value::String(hashed_data)); + map.insert( + "to".to_string(), + Value::String(format!("{:?}", TEST_DEFAULT_CHAIN.rec().contract)), + ); + map + }; + assert_eq!( + params, + &Value::Array(vec![ + Object(expected_params), + Value::String("latest".to_string()) + ]) + ); + } + + let data_dir = ensure_node_home_directory_exists(test_module, test_name); + let gas_price = 444; + prepare_db_with_unique_value(&data_dir, gas_price); + let (test_server, server_url) = launch_prepared_test_server(); + let wallet = make_wallet("abc"); + let mut bootstrapper_config = BootstrapperConfig::new(); + bootstrapper_config + .blockchain_bridge_config + .blockchain_service_url_opt = Some(server_url); + bootstrapper_config.blockchain_bridge_config.chain = TEST_DEFAULT_CHAIN; + bootstrapper_config.data_directory = data_dir; + let (tx, blockchain_bridge_addr_rx) = bounded(1); + let address_leaker = SubsFactoryTestAddrLeaker { address_leaker: tx }; + let system = System::new(test_name); + + act(bootstrapper_config, address_leaker); + + send_rpc_request_to_assert_on_later_and_assert_db_connection( + blockchain_bridge_addr_rx, + wallet.clone(), + gas_price, + ); + assert_eq!(system.run(), 0); + assert_blockchain_interface_connection(&test_server, wallet) + } +} diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_null/lower_level_interface_null.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_null/lower_level_interface_null.rs new file mode 100644 index 000000000..bde60a6bc --- /dev/null +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_null/lower_level_interface_null.rs @@ -0,0 +1,112 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainError; +use crate::blockchain::blockchain_interface::lower_level_interface::{ + LatestBlockNumber, LowBlockchainInt, ResultForBalance, ResultForNonce, +}; +use crate::sub_lib::wallet::Wallet; +use masq_lib::logger::Logger; + +pub struct LowBlockChainIntNull { + logger: Logger, +} + +impl LowBlockchainInt for LowBlockChainIntNull { + fn get_transaction_fee_balance(&self, _wallet: &Wallet) -> ResultForBalance { + Err(self.handle_null_call("transaction fee balance")) + } + + fn get_service_fee_balance(&self, _wallet: &Wallet) -> ResultForBalance { + Err(self.handle_null_call("masq balance")) + } + + fn get_block_number(&self) -> LatestBlockNumber { + Err(self.handle_null_call("block number")) + } + + fn get_transaction_id(&self, _wallet: &Wallet) -> ResultForNonce { + Err(self.handle_null_call("transaction id")) + } +} + +impl LowBlockChainIntNull { + pub fn new(logger: &Logger) -> Self { + Self { + logger: logger.clone(), + } + } + + fn handle_null_call(&self, operation: &str) -> BlockchainError { + error!(self.logger, "Null version can't fetch {operation}"); + BlockchainError::UninitializedBlockchainInterface + } +} + +#[cfg(test)] +mod tests { + use crate::blockchain::blockchain_interface::BlockchainError; + use crate::blockchain::blockchain_interface::blockchain_interface_null::lower_level_interface_null::LowBlockChainIntNull; + use crate::blockchain::blockchain_interface::lower_level_interface::LowBlockchainInt; + use crate::sub_lib::wallet::Wallet; + use crate::test_utils::make_wallet; + use masq_lib::logger::Logger; + use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; + use std::fmt::Debug; + + #[test] + fn low_bci_null_gets_no_transaction_fee_balance() { + let test_name = "low_bci_null_gets_no_transaction_fee_balance"; + let act = |subject: &LowBlockChainIntNull, wallet: &Wallet| { + subject.get_transaction_fee_balance(wallet) + }; + + test_null_method(test_name, act, "transaction fee balance"); + } + + #[test] + fn low_bci_null_gets_no_masq_balance() { + let test_name = "low_bci_null_gets_no_masq_balance"; + let act = |subject: &LowBlockChainIntNull, wallet: &Wallet| { + subject.get_service_fee_balance(wallet) + }; + + test_null_method(test_name, act, "masq balance"); + } + + #[test] + fn low_bci_null_gets_no_block_number() { + let test_name = "low_bci_null_gets_no_block_number"; + let act = |subject: &LowBlockChainIntNull, _wallet: &Wallet| subject.get_block_number(); + + test_null_method(test_name, act, "block number"); + } + + #[test] + fn low_bci_null_gets_no_transaction_id() { + let test_name = "low_bci_null_gets_no_transaction_id"; + let act = + |subject: &LowBlockChainIntNull, wallet: &Wallet| subject.get_transaction_id(wallet); + + test_null_method(test_name, act, "transaction id"); + } + + fn test_null_method( + test_name: &str, + act: fn(&LowBlockChainIntNull, &Wallet) -> Result, + expected_method_name: &str, + ) { + init_test_logging(); + let wallet = make_wallet("blah"); + let subject = LowBlockChainIntNull::new(&Logger::new(test_name)); + + let result = act(&subject, &wallet); + + assert_eq!( + result, + Err(BlockchainError::UninitializedBlockchainInterface) + ); + let _expected_log_msg = TestLogHandler::new().exists_log_containing(&format!( + "ERROR: {test_name}: Null version can't fetch {expected_method_name}" + )); + } +} diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_null/mod.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_null/mod.rs new file mode 100644 index 000000000..e33ff06ab --- /dev/null +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_null/mod.rs @@ -0,0 +1,291 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +pub mod lower_level_interface_null; + +use crate::accountant::db_access_objects::payable_dao::PayableAccount; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; +use crate::blockchain::blockchain_bridge::PendingPayableFingerprintSeeds; +use crate::blockchain::blockchain_interface::blockchain_interface_null::lower_level_interface_null::LowBlockChainIntNull; +use crate::blockchain::blockchain_interface::lower_level_interface::LowBlockchainInt; +use crate::db_config::persistent_configuration::PersistentConfiguration; +use crate::sub_lib::wallet::Wallet; +use actix::Recipient; +use masq_lib::logger::Logger; +use web3::types::{Address, BlockNumber, H160, H256}; +use crate::blockchain::blockchain_interface::BlockchainInterface; +use crate::blockchain::blockchain_interface::data_structures::errors::{BlockchainAgentBuildError, BlockchainError, PayableTransactionError, ResultForReceipt}; +use crate::blockchain::blockchain_interface::data_structures::{ProcessedPayableFallible, RetrievedBlockchainTransactions}; + +pub struct BlockchainInterfaceNull { + logger: Logger, + lower_level_interface: Box, +} + +impl BlockchainInterface for BlockchainInterfaceNull { + fn contract_address(&self) -> Address { + self.log_uninitialized_for_operation("get contract address"); + H160::zero() + } + + fn retrieve_transactions( + &self, + _start_block: BlockNumber, + _end_block: BlockNumber, + _wallet: &Wallet, + ) -> Result { + self.handle_uninitialized_interface("retrieve transactions") + } + + fn build_blockchain_agent( + &self, + _consuming_wallet: &Wallet, + _persistent_config: &dyn PersistentConfiguration, + ) -> Result, BlockchainAgentBuildError> { + self.handle_uninitialized_interface("build blockchain agent") + } + + fn send_batch_of_payables( + &self, + _agent: Box, + _new_fingerprints_recipient: &Recipient, + _accounts: &[PayableAccount], + ) -> Result, PayableTransactionError> { + self.handle_uninitialized_interface("pay for payables") + } + + fn get_transaction_receipt(&self, _hash: H256) -> ResultForReceipt { + self.handle_uninitialized_interface("get transaction receipt") + } + + fn lower_interface(&self) -> &dyn LowBlockchainInt { + error!( + self.logger, + "Provides the null version of lower blockchain interface only" + ); + &*self.lower_level_interface + } + + as_any_in_trait_impl!(); +} + +impl Default for BlockchainInterfaceNull { + fn default() -> Self { + Self::new() + } +} + +trait BlockchainInterfaceUninitializedError { + fn error() -> Self; +} + +macro_rules! impl_bci_uninitialized { + ($($error_type: ty),+) => { + $( + impl BlockchainInterfaceUninitializedError for $error_type { + fn error() -> Self { + Self::UninitializedBlockchainInterface + } + } + )+ + } +} + +impl_bci_uninitialized!( + PayableTransactionError, + BlockchainError, + BlockchainAgentBuildError +); + +impl BlockchainInterfaceNull { + pub fn new() -> Self { + let logger = Logger::new("BlockchainInterface"); + let lower_level_interface = Box::new(LowBlockChainIntNull::new(&logger)); + BlockchainInterfaceNull { + logger, + lower_level_interface, + } + } + + fn handle_uninitialized_interface( + &self, + operation: &str, + ) -> Result + where + E: BlockchainInterfaceUninitializedError, + { + self.log_uninitialized_for_operation(operation); + let err = E::error(); + Err(err) + } + + fn log_uninitialized_for_operation(&self, operation: &str) { + error!( + self.logger, + "Failed to {} with uninitialized blockchain \ + interface. Parameter blockchain-service-url is missing.", + operation + ) + } +} + +#[cfg(test)] +mod tests { + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::agent_null::BlockchainAgentNull; + use crate::accountant::test_utils::make_payable_account; + use crate::blockchain::blockchain_interface::blockchain_interface_null::lower_level_interface_null::LowBlockChainIntNull; + use crate::blockchain::blockchain_interface::blockchain_interface_null::{ + BlockchainInterfaceNull, BlockchainInterfaceUninitializedError, + }; + use crate::blockchain::test_utils::make_tx_hash; + use crate::test_utils::make_wallet; + use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMock; + use crate::test_utils::recorder::make_recorder; + use actix::Actor; + use ethereum_types::U64; + use masq_lib::logger::Logger; + use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; + use web3::types::{BlockNumber, H160}; + use crate::blockchain::blockchain_interface::BlockchainInterface; + use crate::blockchain::blockchain_interface::data_structures::errors::{BlockchainAgentBuildError, BlockchainError, PayableTransactionError}; + + fn make_subject(test_name: &str) -> BlockchainInterfaceNull { + let logger = Logger::new(test_name); + let lower_level_interface = Box::new(LowBlockChainIntNull::new(&logger)); + BlockchainInterfaceNull { + logger, + lower_level_interface, + } + } + + #[test] + fn blockchain_interface_null_returns_contract_address() { + let result = make_subject("irrelevant").contract_address(); + + assert_eq!(result, H160::zero()) + } + + #[test] + fn blockchain_interface_null_retrieves_no_transactions() { + init_test_logging(); + let test_name = "blockchain_interface_null_retrieves_no_transactions"; + let wallet = make_wallet("blah"); + + let result = make_subject(test_name).retrieve_transactions( + BlockNumber::Number(U64::zero()), + BlockNumber::Latest, + &wallet, + ); + + assert_eq!( + result, + Err(BlockchainError::UninitializedBlockchainInterface) + ); + let expected_msg = "Failed to retrieve transactions with uninitialized blockchain \ + interface. Parameter blockchain-service-url is missing."; + let expected_log_msg = format!("ERROR: {test_name}: {}", expected_msg); + TestLogHandler::new().exists_log_containing(expected_log_msg.as_str()); + } + + #[test] + fn blockchain_interface_null_builds_null_agent() { + init_test_logging(); + let test_name = "blockchain_interface_null_builds_null_agent"; + let wallet = make_wallet("blah"); + let persistent_config = PersistentConfigurationMock::new(); + let subject = make_subject(test_name); + + let result = subject.build_blockchain_agent(&wallet, &persistent_config); + + let err = match result { + Ok(_) => panic!("we expected an error but got ok"), + Err(e) => e, + }; + assert_eq!( + err, + BlockchainAgentBuildError::UninitializedBlockchainInterface + ); + let expected_log_msg = format!( + "ERROR: {test_name}: Failed to build blockchain agent with uninitialized blockchain \ + interface. Parameter blockchain-service-url is missing." + ); + TestLogHandler::new().exists_log_containing(expected_log_msg.as_str()); + } + + #[test] + fn blockchain_interface_null_cannot_send_batch_of_payables() { + init_test_logging(); + let test_name = "blockchain_interface_null_cannot_send_batch_of_payables"; + let (recorder, _, _) = make_recorder(); + let recipient = recorder.start().recipient(); + let accounts = vec![make_payable_account(111)]; + let agent = Box::new(BlockchainAgentNull::new()); + + let result = make_subject(test_name).send_batch_of_payables(agent, &recipient, &accounts); + + assert_eq!( + result, + Err(PayableTransactionError::UninitializedBlockchainInterface) + ); + let expected_log_msg = format!( + "ERROR: {test_name}: Failed to pay for payables with uninitialized blockchain \ + interface. Parameter blockchain-service-url is missing." + ); + TestLogHandler::new().exists_log_containing(expected_log_msg.as_str()); + } + + #[test] + fn blockchain_interface_null_gets_no_transaction_receipt() { + init_test_logging(); + let test_name = "blockchain_interface_null_gets_no_transaction_receipt"; + let tx_hash = make_tx_hash(123); + + let result = make_subject(test_name).get_transaction_receipt(tx_hash); + + assert_eq!( + result, + Err(BlockchainError::UninitializedBlockchainInterface) + ); + let expected_log_msg = format!( + "ERROR: {test_name}: Failed to get transaction receipt with uninitialized \ + blockchain interface. Parameter blockchain-service-url is missing." + ); + TestLogHandler::new().exists_log_containing(expected_log_msg.as_str()); + } + + #[test] + fn blockchain_interface_null_gives_null_lower_interface() { + init_test_logging(); + let test_name = "blockchain_interface_null_gives_null_lower_interface"; + let wallet = make_wallet("abc"); + + let _ = make_subject(test_name) + .lower_interface() + .get_transaction_id(&wallet); + + let expected_log_msg_from_low_level_interface_call = format!( + "ERROR: {test_name}: Provides the null version of lower blockchain interface only" + ); + let expected_log_msg_from_rcp_call = + format!("ERROR: {test_name}: Null version can't fetch transaction id"); + TestLogHandler::new().assert_logs_contain_in_order(vec![ + expected_log_msg_from_low_level_interface_call.as_str(), + expected_log_msg_from_rcp_call.as_str(), + ]); + } + + #[test] + fn blockchain_interface_null_error_is_implemented_for_blockchain_error() { + assert_eq!( + BlockchainError::error(), + BlockchainError::UninitializedBlockchainInterface + ) + } + + #[test] + fn blockchain_interface_null_error_is_implemented_for_payable_transaction_error() { + assert_eq!( + PayableTransactionError::error(), + PayableTransactionError::UninitializedBlockchainInterface + ) + } +} diff --git a/node/src/blockchain/batch_payable_tools.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/batch_payable_tools.rs similarity index 96% rename from node/src/blockchain/batch_payable_tools.rs rename to node/src/blockchain/blockchain_interface/blockchain_interface_web3/batch_payable_tools.rs index f57a3f12c..ed0e21d15 100644 --- a/node/src/blockchain/batch_payable_tools.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/batch_payable_tools.rs @@ -92,8 +92,10 @@ impl BatchPayableTools for BatchPayableToolsReal +where + T: BatchTransport, +{ + web3: Rc>, + // TODO waiting for GH-707 (note: consider to query the balances together with the id) + _batch_web3: Rc>>, + contract: Contract, +} + +impl LowBlockchainInt for LowBlockchainIntWeb3 +where + T: BatchTransport, +{ + fn get_transaction_fee_balance(&self, wallet: &Wallet) -> ResultForBalance { + self.web3 + .eth() + .balance(wallet.address(), None) + .map_err(|e| BlockchainError::QueryFailed(format!("{} for wallet {}", e, wallet))) + .wait() + } + + fn get_service_fee_balance(&self, wallet: &Wallet) -> ResultForBalance { + self.contract + .query( + "balanceOf", + wallet.address(), + None, + Options::default(), + None, + ) + .map_err(|e| BlockchainError::QueryFailed(format!("{} for wallet {}", e, wallet))) + .wait() + } + + fn get_block_number(&self) -> LatestBlockNumber { + self.web3 + .eth() + .block_number() + .map_err(|e| BlockchainError::QueryFailed(e.to_string())) + .wait() + } + + fn get_transaction_id(&self, wallet: &Wallet) -> ResultForNonce { + self.web3 + .eth() + .transaction_count(wallet.address(), Some(BlockNumber::Pending)) + .map_err(|e| BlockchainError::QueryFailed(format!("{} for wallet {}", e, wallet))) + .wait() + } +} + +impl LowBlockchainIntWeb3 +where + T: BatchTransport, +{ + pub fn new(web3: Rc>, batch_web3: Rc>>, contract: Contract) -> Self { + Self { + web3, + _batch_web3: batch_web3, + contract, + } + } +} + +#[cfg(test)] +mod tests { + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::LowBlockchainIntWeb3; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::{ + CONTRACT_ABI, REQUESTS_IN_PARALLEL, + }; + use crate::blockchain::blockchain_interface::lower_level_interface::{LowBlockchainInt, ResultForBalance}; + use crate::blockchain::blockchain_interface::BlockchainError; + use crate::sub_lib::wallet::Wallet; + use crate::test_utils::http_test_server::TestServer; + use crate::test_utils::make_paying_wallet; + use ethereum_types::U64; + use masq_lib::blockchains::chains::Chain; + use masq_lib::test_utils::utils::TEST_DEFAULT_CHAIN; + use masq_lib::utils::find_free_port; + use serde_json::{json, Value}; + use std::net::Ipv4Addr; + use std::rc::Rc; + use std::str::FromStr; + use std::sync::{Arc, Mutex}; + use web3::contract::Contract; + use web3::transports::{Batch, Http}; + use web3::types::U256; + use web3::{BatchTransport, Web3}; + use crate::blockchain::test_utils::TestTransport; + + #[test] + fn low_interface_web3_transaction_fee_balance_works() { + let port = find_free_port(); + let test_server = TestServer::start( + port, + vec![br#"{"jsonrpc":"2.0","id":0,"result":"0xDEADBEEF"}"#.to_vec()], + ); + let (_event_loop_handle, transport) = Http::with_max_parallel( + &format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port), + REQUESTS_IN_PARALLEL, + ) + .unwrap(); + let chain = TEST_DEFAULT_CHAIN; + let subject = make_subject(transport, chain); + + let result = subject + .get_transaction_fee_balance( + &Wallet::from_str("0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc").unwrap(), + ) + .unwrap(); + + assert_eq!(result, U256::from(0xDEADBEEF_u64)); + let requests = test_server.requests_so_far(); + let bodies: Vec = requests + .into_iter() + .map(|request| serde_json::from_slice(&request.body()).unwrap()) + .collect(); + assert_eq!(bodies[0]["method"].to_string(), "\"eth_getBalance\"",); + assert_eq!( + bodies[0]["params"][0].to_string(), + "\"0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc\"", + ); + assert_eq!(bodies.len(), 1) + } + + #[test] + #[should_panic(expected = "No address for an uninitialized wallet!")] + fn low_interface_web3_get_transaction_fee_balance_returns_err_for_an_invalid_wallet() { + let port = 8545; + let (_event_loop_handle, transport) = Http::with_max_parallel( + &format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port), + REQUESTS_IN_PARALLEL, + ) + .unwrap(); + let chain = TEST_DEFAULT_CHAIN; + let subject = make_subject(transport, chain); + + let result = subject.get_transaction_fee_balance(&Wallet::new("0x_invalid_wallet_address")); + + assert_eq!(result, Err(BlockchainError::InvalidAddress)); + } + + #[test] + fn low_interface_web3_get_transaction_fee_balance_returns_err_for_unintelligible_response() { + let act = |subject: &LowBlockchainIntWeb3, wallet: &Wallet| { + subject.get_transaction_fee_balance(wallet) + }; + + assert_error_from_unintelligible_response(act, "invalid hex character"); + } + + #[test] + fn low_interface_web3_get_masq_balance_works() { + let port = find_free_port(); + let test_server = TestServer::start (port, vec![ + br#"{"jsonrpc":"2.0","id":0,"result":"0x00000000000000000000000000000000000000000000000000000000DEADBEEF"}"#.to_vec() + ]); + let (_event_loop_handle, transport) = Http::with_max_parallel( + &format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port), + REQUESTS_IN_PARALLEL, + ) + .unwrap(); + let chain = TEST_DEFAULT_CHAIN; + let subject = make_subject(transport, chain); + + let result = subject + .get_service_fee_balance( + &Wallet::from_str("0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc").unwrap(), + ) + .unwrap(); + + assert_eq!(result, U256::from(0xDEADBEEF_u64)); + let requests = test_server.requests_so_far(); + let bodies: Vec = requests + .into_iter() + .map(|request| serde_json::from_slice(&request.body()).unwrap()) + .collect(); + assert_eq!(bodies[0]["method"].to_string(), "\"eth_call\"",); + let contract_address = chain.rec().contract; + assert_eq!( + bodies[0]["params"][0]["to"].to_string(), + format!("\"{:?}\"", contract_address), + ); + assert_eq!(bodies.len(), 1) + } + + #[test] + #[should_panic(expected = "No address for an uninitialized wallet!")] + fn low_interface_web3_get_masq_balance_returns_err_for_an_invalid_wallet() { + let port = 8545; + let (_event_loop_handle, transport) = Http::with_max_parallel( + &format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port), + REQUESTS_IN_PARALLEL, + ) + .unwrap(); + let chain = TEST_DEFAULT_CHAIN; + let subject = make_subject(transport, chain); + + let result = subject.get_service_fee_balance(&Wallet::new("0x_invalid_wallet_address")); + + assert_eq!(result, Err(BlockchainError::InvalidAddress)); + } + + #[test] + fn low_interface_web3_get_masq_balance_returns_err_for_unintelligible_response() { + let act = |subject: &LowBlockchainIntWeb3, wallet: &Wallet| { + subject.get_service_fee_balance(wallet) + }; + + assert_error_from_unintelligible_response(act, "Invalid hex"); + } + + #[test] + fn low_interface_web3_can_fetch_latest_block_number_successfully() { + let prepare_params_arc = Arc::new(Mutex::new(vec![])); + let transport = TestTransport::default() + .prepare_params(&prepare_params_arc) + .send_result(json!("0x1e37066")); + let subject = make_subject(transport, TEST_DEFAULT_CHAIN); + + let latest_block_number = subject.get_block_number().unwrap(); + + assert_eq!(latest_block_number, U64::from(0x1e37066u64)); + let mut prepare_params = prepare_params_arc.lock().unwrap(); + let (method_name, actual_arguments) = prepare_params.remove(0); + assert!(prepare_params.is_empty()); + assert_eq!(method_name, "eth_blockNumber".to_string()); + let expected_arguments: Vec = vec![]; + assert_eq!(actual_arguments, expected_arguments); + } + + #[test] + fn low_interface_web3_handles_latest_null_block_number_error() { + let prepare_params_arc = Arc::new(Mutex::new(vec![])); + let transport = TestTransport::default() + .prepare_params(&prepare_params_arc) + .send_result(Value::Null); + let subject = make_subject(transport, TEST_DEFAULT_CHAIN); + + let expected_error = subject.get_block_number().unwrap_err(); + + assert_eq!( + expected_error, + BlockchainError::QueryFailed( + "Decoder error: Error(\"invalid type: null, expected \ + a 0x-prefixed hex string with length between (0; 16]\", line: 0, column: 0)" + .to_string() + ) + ); + let mut prepare_params = prepare_params_arc.lock().unwrap(); + let (method_name, actual_arguments) = prepare_params.remove(0); + assert!(prepare_params.is_empty()); + assert_eq!(method_name, "eth_blockNumber".to_string()); + let expected_arguments: Vec = vec![]; + assert_eq!(actual_arguments, expected_arguments); + } + + #[test] + fn low_interface_web3_can_handle_latest_string_block_number_error() { + let prepare_params_arc: Arc)>>> = + Arc::new(Mutex::new(vec![])); + let transport = TestTransport::default() + .prepare_params(&prepare_params_arc) + .send_result(Value::String("this is an invalid block number".to_string())); + let subject = make_subject(transport, TEST_DEFAULT_CHAIN); + + let expected_error = subject.get_block_number().unwrap_err(); + + assert_eq!( + expected_error, + BlockchainError::QueryFailed( + "Decoder error: Error(\"0x prefix is missing\", line: 0, column: 0)".to_string() + ) + ); + let mut prepare_params = prepare_params_arc.lock().unwrap(); + let (method_name, actual_arguments) = prepare_params.remove(0); + assert!(prepare_params.is_empty()); + assert_eq!(method_name, "eth_blockNumber".to_string()); + let expected_arguments: Vec = vec![]; + assert_eq!(actual_arguments, expected_arguments); + } + + #[test] + fn low_interface_web3_get_transaction_id_works() { + let prepare_params_arc = Arc::new(Mutex::new(vec![])); + let send_params_arc = Arc::new(Mutex::new(vec![])); + let transport = TestTransport::default() + .prepare_params(&prepare_params_arc) + .send_params(&send_params_arc) + .send_result(json!( + "0x0000000000000000000000000000000000000000000000000000000000000001" + )); + let chain = TEST_DEFAULT_CHAIN; + let subject = make_subject(transport, chain); + + let result = subject.get_transaction_id(&make_paying_wallet(b"gdasgsa")); + + assert_eq!(result, Ok(U256::from(1))); + let mut prepare_params = prepare_params_arc.lock().unwrap(); + let (method_name, actual_arguments) = prepare_params.remove(0); + assert!(prepare_params.is_empty()); + let actual_arguments: Vec = actual_arguments + .into_iter() + .map(|arg| serde_json::to_string(&arg).unwrap()) + .collect(); + assert_eq!(method_name, "eth_getTransactionCount".to_string()); + assert_eq!( + actual_arguments, + vec![ + String::from(r#""0x5c361ba8d82fcf0e5538b2a823e9d457a2296725""#), + String::from(r#""pending""#), + ] + ); + let send_params = send_params_arc.lock().unwrap(); + let rpc_call_params = vec![ + Value::String(String::from("0x5c361ba8d82fcf0e5538b2a823e9d457a2296725")), + Value::String(String::from("pending")), + ]; + let expected_request = + web3::helpers::build_request(1, "eth_getTransactionCount", rpc_call_params); + assert_eq!(*send_params, vec![(1, expected_request)]) + } + + #[test] + fn low_interface_web3_get_transaction_id_handles_err() { + let act = |subject: &LowBlockchainIntWeb3, wallet: &Wallet| { + subject.get_transaction_id(wallet) + }; + + assert_error_from_unintelligible_response(act, "invalid hex character") + } + + fn assert_error_from_unintelligible_response(act: F, expected_err_fragment: &str) + where + F: FnOnce(&LowBlockchainIntWeb3, &Wallet) -> ResultForBalance, + { + let port = find_free_port(); + let _test_server = TestServer::start (port, vec![ + br#"{"jsonrpc":"2.0","id":0,"result":"0x000000000000000000000000000000000000000000000000000000000000FFFQ"}"#.to_vec() + ]); + let (_event_loop_handle, transport) = Http::with_max_parallel( + &format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port), + REQUESTS_IN_PARALLEL, + ) + .unwrap(); + let chain = TEST_DEFAULT_CHAIN; + let wallet = Wallet::from_str("0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc").unwrap(); + let subject = make_subject(transport, chain); + + let result = act(&subject, &wallet); + + let err_msg = match result { + Err(BlockchainError::QueryFailed(msg)) => msg, + x => panic!("Expected BlockchainError::QueryFailed, but got {:?}", x), + }; + assert!( + err_msg.contains(expected_err_fragment), + "Expected this fragment \"{}\" in this err msg: {}", + expected_err_fragment, + err_msg + ); + assert!( + err_msg.contains("for wallet 0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc"), + "Expected the wallet to be cited in the err msg like \" for wallet {}\" but wasn't", + wallet + ) + } + + fn make_subject(transport: T, chain: Chain) -> LowBlockchainIntWeb3 + where + T: BatchTransport, + { + let web3 = Web3::new(transport.clone()); + let web3_batch = Web3::new(Batch::new(transport.clone())); + let contract = + Contract::from_json(web3.eth(), chain.rec().contract, CONTRACT_ABI.as_bytes()) + .expect("Unable to initialize contract."); + LowBlockchainIntWeb3::new(Rc::new(web3), Rc::new(web3_batch), contract) + } +} diff --git a/node/src/blockchain/blockchain_interface.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs similarity index 68% rename from node/src/blockchain/blockchain_interface.rs rename to node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs index 5616a70a6..083f75822 100644 --- a/node/src/blockchain/blockchain_interface.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs @@ -1,40 +1,45 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::comma_joined_stringifiable; -use crate::accountant::db_access_objects::payable_dao::{PayableAccount, PendingPayable}; -use crate::blockchain::batch_payable_tools::{BatchPayableTools, BatchPayableToolsReal}; +mod batch_payable_tools; +pub mod lower_level_interface_web3; +mod test_utils; + +use crate::accountant::db_access_objects::payable_dao::{PayableAccount}; +use crate::accountant::{gwei_to_wei}; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::agent_web3::BlockchainAgentWeb3; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; use crate::blockchain::blockchain_bridge::PendingPayableFingerprintSeeds; -use crate::blockchain::blockchain_interface::BlockchainError::{ - InvalidAddress, InvalidResponse, InvalidUrl, QueryFailed, UninitializedBlockchainInterface, +use crate::blockchain::blockchain_interface::blockchain_interface_web3::batch_payable_tools::{ + BatchPayableTools, BatchPayableToolsReal, }; +use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::LowBlockchainIntWeb3; +use crate::blockchain::blockchain_interface::lower_level_interface::LowBlockchainInt; +use crate::blockchain::blockchain_interface::{BlockchainAgentBuildError, BlockchainError, BlockchainInterface, PayableTransactionError, ResultForReceipt, RetrievedBlockchainTransactions}; +use crate::db_config::persistent_configuration::PersistentConfiguration; +use crate::masq_lib::utils::ExpectValue; +use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; use crate::sub_lib::wallet::Wallet; -use actix::{Message, Recipient}; -use ethereum_types::U64; +use actix::Recipient; use futures::Future; use indoc::indoc; -use itertools::fold; -use itertools::Either::{Left, Right}; -use masq_lib::blockchains::chains::{Chain, ChainFamily}; +use masq_lib::blockchains::chains::Chain; use masq_lib::logger::Logger; -use masq_lib::utils::ExpectValue; use serde_json::Value; -use std::convert::{From, TryFrom, TryInto}; -use std::fmt; -use std::fmt::{Debug, Display, Formatter}; +use std::fmt::Debug; use std::iter::once; +use std::rc::Rc; use thousands::Separable; -use variant_count::VariantCount; -use web3::contract::{Contract, Options}; +use web3::contract::Contract; use web3::transports::{Batch, EventLoopHandle}; use web3::types::{ Address, BlockNumber, Bytes, FilterBuilder, Log, SignedTransaction, TransactionParameters, - TransactionReceipt, H160, H256, U256, + H160, H256, U256, }; use web3::{BatchTransport, Error, Web3}; +use crate::accountant::db_access_objects::pending_payable_dao::PendingPayable; +use crate::blockchain::blockchain_interface::data_structures::{BlockchainTransaction, ProcessedPayableFallible, RpcPayablesFailure}; -pub const REQUESTS_IN_PARALLEL: usize = 1; - -pub const CONTRACT_ABI: &str = indoc!( +const CONTRACT_ABI: &str = indoc!( r#"[{ "constant":true, "inputs":[{"name":"owner","type":"address"}], @@ -61,232 +66,7 @@ const TRANSACTION_LITERAL: H256 = H256([ const TRANSFER_METHOD_ID: [u8; 4] = [0xa9, 0x05, 0x9c, 0xbb]; -const BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED: &str = "To avoid being delinquency-banned, you should \ -restart the Node with a value for blockchain-service-url"; - -#[derive(Clone, Debug, Eq, Message, PartialEq)] -pub struct BlockchainTransaction { - pub block_number: u64, - pub from: Wallet, - pub wei_amount: u128, -} - -impl Display for BlockchainTransaction { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { - write!( - f, - "{}wei from {} ({:?})", - self.wei_amount, self.from, self.block_number - ) - } -} - -#[derive(Clone, Debug, PartialEq, Eq, VariantCount)] -pub enum BlockchainError { - InvalidUrl, - InvalidAddress, - InvalidResponse, - QueryFailed(String), - UninitializedBlockchainInterface, -} - -impl Display for BlockchainError { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let description = match self { - InvalidUrl => Left("Invalid url"), - InvalidAddress => Left("Invalid address"), - InvalidResponse => Left("Invalid response"), - QueryFailed(msg) => Right(format!("Query failed: {}", msg)), - UninitializedBlockchainInterface => Left(BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED), - }; - write!(f, "Blockchain error: {}", description) - } -} - -impl BlockchainInterfaceUninitializedError for BlockchainError { - fn error() -> Self { - Self::UninitializedBlockchainInterface - } -} - -pub type BlockchainResult = Result; -pub type ResultForBalance = BlockchainResult; -pub type ResultForBothBalances = BlockchainResult<(web3::types::U256, web3::types::U256)>; -pub type ResultForNonce = BlockchainResult; -pub type ResultForReceipt = BlockchainResult>; -pub type LatestBlockNumber = BlockchainResult; - -#[derive(Clone, Debug, PartialEq, Eq, VariantCount)] -pub enum PayableTransactionError { - MissingConsumingWallet, - GasPriceQueryFailed(String), - TransactionCount(BlockchainError), - UnusableWallet(String), - Signing(String), - Sending { msg: String, hashes: Vec }, - UninitializedBlockchainInterface, -} - -impl Display for PayableTransactionError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let description = match self { - Self::MissingConsumingWallet => Left("Missing consuming wallet to pay payable from"), - Self::GasPriceQueryFailed(msg) => { - Right(format!("Unsuccessful gas price query: \"{}\"", msg)) - } - Self::TransactionCount(blockchain_err) => Right(format!( - "Transaction count fetching failed for: {}", - blockchain_err - )), - Self::UnusableWallet(msg) => Right(format!( - "Unusable wallet for signing payable transactions: \"{}\"", - msg - )), - Self::Signing(msg) => Right(format!("Signing phase: \"{}\"", msg)), - Self::Sending { msg, hashes } => Right(format!( - "Sending phase: \"{}\". Signed and hashed transactions: {}", - msg, - comma_joined_stringifiable(hashes, |hash| format!("{:?}", hash)) - )), - Self::UninitializedBlockchainInterface => Left(BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED), - }; - write!(f, "{}", description) - } -} - -impl BlockchainInterfaceUninitializedError for PayableTransactionError { - fn error() -> Self { - Self::UninitializedBlockchainInterface - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct RetrievedBlockchainTransactions { - pub new_start_block: u64, - pub transactions: Vec, -} - -pub trait BlockchainInterface { - fn contract_address(&self) -> Address; - - fn retrieve_transactions( - &self, - start_block: BlockNumber, - end_block: BlockNumber, - recipient: &Wallet, - ) -> Result; - - fn send_payables_within_batch( - &self, - consuming_wallet: &Wallet, - gas_price: u64, - pending_nonce: U256, - new_fingerprints_recipient: &Recipient, - accounts: &[PayableAccount], - ) -> Result, PayableTransactionError>; - - fn get_block_number(&self) -> LatestBlockNumber; - - fn get_transaction_fee_balance(&self, address: &Wallet) -> ResultForBalance; - - fn get_token_balance(&self, address: &Wallet) -> ResultForBalance; - - fn get_transaction_count(&self, address: &Wallet) -> ResultForNonce; - - fn get_transaction_receipt(&self, hash: H256) -> ResultForReceipt; -} - -pub struct BlockchainInterfaceNull { - logger: Logger, -} - -impl Default for BlockchainInterfaceNull { - fn default() -> Self { - Self::new() - } -} - -impl BlockchainInterface for BlockchainInterfaceNull { - fn contract_address(&self) -> Address { - self.log_uninitialized_for_operation("get contract address"); - H160::zero() - } - - fn retrieve_transactions( - &self, - _start_block: BlockNumber, - _end_block: BlockNumber, - _recipient: &Wallet, - ) -> Result { - self.handle_uninitialized_interface("retrieve transactions") - } - - fn send_payables_within_batch( - &self, - _consuming_wallet: &Wallet, - _gas_price: u64, - _last_nonce: U256, - _new_fingerprints_recipient: &Recipient, - _accounts: &[PayableAccount], - ) -> Result, PayableTransactionError> { - self.handle_uninitialized_interface("pay payables") - } - - fn get_block_number(&self) -> LatestBlockNumber { - let msg = "Can't get latest block number clandestinely yet"; - error!(self.logger, "{}", msg); - Err(BlockchainError::QueryFailed(msg.to_string())) - } - - fn get_transaction_fee_balance(&self, _address: &Wallet) -> ResultForBalance { - self.handle_uninitialized_interface("get transaction fee balance") - } - - fn get_token_balance(&self, _address: &Wallet) -> ResultForBalance { - self.handle_uninitialized_interface("get token balance") - } - - fn get_transaction_count(&self, _address: &Wallet) -> ResultForNonce { - self.handle_uninitialized_interface("get transaction count") - } - - fn get_transaction_receipt(&self, _hash: H256) -> ResultForReceipt { - self.handle_uninitialized_interface("get transaction receipt") - } -} - -trait BlockchainInterfaceUninitializedError { - fn error() -> Self; -} - -impl BlockchainInterfaceNull { - pub fn new() -> Self { - BlockchainInterfaceNull { - logger: Logger::new("BlockchainInterfaceNull"), - } - } - - fn handle_uninitialized_interface( - &self, - operation: &str, - ) -> Result - where - E: BlockchainInterfaceUninitializedError, - { - self.log_uninitialized_for_operation(operation); - let err = E::error(); - Err(err) - } - - fn log_uninitialized_for_operation(&self, operation: &str) { - error!( - self.logger, - "Failed to {} with uninitialized blockchain \ - interface. Parameter blockchain-service-url is missing.", - operation - ) - } -} +pub const REQUESTS_IN_PARALLEL: usize = 1; pub struct BlockchainInterfaceWeb3 where @@ -294,19 +74,13 @@ where { logger: Logger, chain: Chain, + gas_limit_const_part: u64, // This must not be dropped for Web3 requests to be completed _event_loop_handle: EventLoopHandle, - web3: Web3, - web3_batch: Web3>, + web3: Rc>, + web3_batch: Rc>>, batch_payable_tools: Box>, - contract: Contract, -} - -const GWEI: U256 = U256([1_000_000_000u64, 0, 0, 0]); - -pub fn to_wei(gwei: u64) -> U256 { - let result = U256::from(gwei); - result.full_mul(GWEI).try_into().expect("Internal Error") + lower_interface: Box, } impl BlockchainInterface for BlockchainInterfaceWeb3 @@ -428,14 +202,78 @@ where } } - fn send_payables_within_batch( + fn build_blockchain_agent( &self, consuming_wallet: &Wallet, - gas_price: u64, - pending_nonce: U256, + persistent_config: &dyn PersistentConfiguration, + ) -> Result, BlockchainAgentBuildError> { + let gas_price_gwei = match persistent_config.gas_price() { + Ok(price) => price, + Err(e) => return Err(BlockchainAgentBuildError::GasPrice(e)), + }; + + let transaction_fee_balance = match self + .lower_interface + .get_transaction_fee_balance(consuming_wallet) + { + Ok(balance) => balance, + Err(e) => { + return Err(BlockchainAgentBuildError::TransactionFeeBalance( + consuming_wallet.clone(), + e, + )) + } + }; + + let masq_token_balance = match self + .lower_interface + .get_service_fee_balance(consuming_wallet) + { + Ok(balance) => balance, + Err(e) => { + return Err(BlockchainAgentBuildError::ServiceFeeBalance( + consuming_wallet.clone(), + e, + )) + } + }; + + let pending_transaction_id = match self.lower_interface.get_transaction_id(consuming_wallet) + { + Ok(id) => id, + Err(e) => { + return Err(BlockchainAgentBuildError::TransactionID( + consuming_wallet.clone(), + e, + )) + } + }; + + let consuming_wallet_balances = ConsumingWalletBalances { + transaction_fee_balance_in_minor_units: transaction_fee_balance, + masq_token_balance_in_minor_units: masq_token_balance, + }; + let consuming_wallet = consuming_wallet.clone(); + + Ok(Box::new(BlockchainAgentWeb3::new( + gas_price_gwei, + self.gas_limit_const_part, + consuming_wallet, + consuming_wallet_balances, + pending_transaction_id, + ))) + } + + fn send_batch_of_payables( + &self, + agent: Box, new_fingerprints_recipient: &Recipient, accounts: &[PayableAccount], ) -> Result, PayableTransactionError> { + let consuming_wallet = agent.consuming_wallet(); + let gas_price = agent.agreed_fee_per_computation_unit(); + let pending_nonce = agent.pending_transaction_id(); + debug!( self.logger, "Common attributes of payables to be transacted: sender wallet: {}, contract: {:?}, chain_id: {}, gas_price: {}", @@ -475,43 +313,6 @@ where } } - fn get_block_number(&self) -> LatestBlockNumber { - self.web3 - .eth() - .block_number() - .map_err(|e| BlockchainError::QueryFailed(e.to_string())) - .wait() - } - - fn get_transaction_fee_balance(&self, wallet: &Wallet) -> ResultForBalance { - self.web3 - .eth() - .balance(wallet.address(), None) - .map_err(|e| BlockchainError::QueryFailed(e.to_string())) - .wait() - } - - fn get_token_balance(&self, wallet: &Wallet) -> ResultForBalance { - self.contract - .query( - "balanceOf", - wallet.address(), - None, - Options::default(), - None, - ) - .map_err(|e| BlockchainError::QueryFailed(e.to_string())) - .wait() - } - - fn get_transaction_count(&self, wallet: &Wallet) -> ResultForNonce { - self.web3 - .eth() - .transaction_count(wallet.address(), Some(BlockNumber::Pending)) - .map_err(|e| BlockchainError::QueryFailed(e.to_string())) - .wait() - } - fn get_transaction_receipt(&self, hash: H256) -> ResultForReceipt { self.web3 .eth() @@ -519,43 +320,39 @@ where .map_err(|e| BlockchainError::QueryFailed(e.to_string())) .wait() } -} - -#[derive(Debug, PartialEq, Clone)] -pub enum ProcessedPayableFallible { - Correct(PendingPayable), - Failed(RpcPayableFailure), -} -#[derive(Debug, PartialEq, Clone)] -pub struct RpcPayableFailure { - pub rpc_error: Error, - pub recipient_wallet: Wallet, - pub hash: H256, + fn lower_interface(&self) -> &dyn LowBlockchainInt { + &*self.lower_interface + } } -type HashAndAmountResult = Result, PayableTransactionError>; - impl BlockchainInterfaceWeb3 where T: 'static + BatchTransport + Debug, { pub fn new(transport: T, event_loop_handle: EventLoopHandle, chain: Chain) -> Self { - let web3 = Web3::new(transport.clone()); - let web3_batch = Web3::new(Batch::new(transport)); + let web3 = Rc::new(Web3::new(transport.clone())); + let web3_batch = Rc::new(Web3::new(Batch::new(transport))); let batch_payable_tools = Box::new(BatchPayableToolsReal::::default()); let contract = Contract::from_json(web3.eth(), chain.rec().contract, CONTRACT_ABI.as_bytes()) .expect("Unable to initialize contract."); + let lower_level_blockchain_interface = Box::new(LowBlockchainIntWeb3::new( + Rc::clone(&web3), + Rc::clone(&web3_batch), + contract, + )); + let gas_limit_const_part = Self::web3_gas_limit_const_part(chain); Self { logger: Logger::new("BlockchainInterface"), chain, + gas_limit_const_part, _event_loop_handle: event_loop_handle, web3, web3_batch, + lower_interface: lower_level_blockchain_interface, batch_payable_tools, - contract, } } @@ -659,11 +456,11 @@ where .zip(accounts.iter()); iterator_with_all_data .map(|((rpc_result, (hash, _)), account)| match rpc_result { - Ok(_) => ProcessedPayableFallible::Correct(PendingPayable { + Ok(_) => Ok(PendingPayable { recipient_wallet: account.wallet.clone(), hash, }), - Err(rpc_error) => ProcessedPayableFallible::Failed(RpcPayableFailure { + Err(rpc_error) => Err(RpcPayablesFailure { rpc_error, recipient_wallet: account.wallet.clone(), hash, @@ -709,29 +506,11 @@ where nonce: U256, gas_price: u64, ) -> Result { - let mut data = [0u8; 4 + 32 + 32]; - data[0..4].copy_from_slice(&TRANSFER_METHOD_ID); - data[16..36].copy_from_slice(&recipient.address().0[..]); - U256::try_from(amount) - .expect("shouldn't overflow") - .to_big_endian(&mut data[36..68]); - let base_gas_limit = Self::base_gas_limit(self.chain); - let gas_limit = - ethereum_types::U256::try_from(data.iter().fold(base_gas_limit, |acc, v| { - acc + if v == &0u8 { 4 } else { 68 } - })) - .expect("Internal error"); - let converted_nonce = serde_json::from_value::( - serde_json::to_value(nonce).expect("Internal error"), - ) - .expect("Internal error"); - let gas_price = serde_json::from_value::( - serde_json::to_value(to_wei(gas_price)).expect("Internal error"), - ) - .expect("Internal error"); - + let data = Self::transaction_data(recipient, amount); + let gas_limit = self.compute_gas_limit(data.as_slice()); + let gas_price = gwei_to_wei::(gas_price); let transaction_parameters = TransactionParameters { - nonce: Some(converted_nonce), + nonce: Some(nonce), to: Some(H160(self.contract_address().0)), gas: gas_limit, gas_price: Some(gas_price), @@ -780,11 +559,27 @@ where introduction.chain(body).collect() } - fn base_gas_limit(chain: Chain) -> u64 { - match chain.rec().chain_family { - ChainFamily::Polygon => 70_000, - ChainFamily::Eth => 55_000, - ChainFamily::Dev => 55_000, + fn transaction_data(recipient: &Wallet, amount: u128) -> [u8; 68] { + let mut data = [0u8; 4 + 32 + 32]; + data[0..4].copy_from_slice(&TRANSFER_METHOD_ID); + data[16..36].copy_from_slice(&recipient.address().0[..]); + U256::try_from(amount) + .expect("shouldn't overflow") + .to_big_endian(&mut data[36..68]); + data + } + + fn compute_gas_limit(&self, data: &[u8]) -> U256 { + ethereum_types::U256::try_from(data.iter().fold(self.gas_limit_const_part, |acc, v| { + acc + if v == &0u8 { 4 } else { 68 } + })) + .expect("Internal error") + } + + fn web3_gas_limit_const_part(chain: Chain) -> u64 { + match chain { + Chain::EthMainnet | Chain::EthRopsten | Chain::Dev => 55_000, + Chain::PolyMainnet | Chain::PolyMumbai => 70_000, } } @@ -807,67 +602,92 @@ where fn find_largest_transaction_block_number( &self, response_block_number: u64, - transactions: &Vec, + transactions: &[BlockchainTransaction], ) -> u64 { if transactions.is_empty() { response_block_number } else { - fold(transactions, response_block_number, |a, b| { - a.max(b.block_number) - }) + transactions + .iter() + .fold(response_block_number, |a, b| a.max(b.block_number)) } } - - #[cfg(test)] - fn web3(&self) -> &Web3 { - &self.web3 - } } +type HashAndAmountResult = Result, PayableTransactionError>; + #[cfg(test)] mod tests { - use super::*; - use crate::accountant::db_access_objects::dao_utils::from_time_t; + use crate::accountant::db_access_objects::utils::from_time_t; use crate::accountant::gwei_to_wei; + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::agent_web3::WEB3_MAXIMAL_GAS_LIMIT_MARGIN; + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; use crate::accountant::test_utils::{ make_payable_account, make_payable_account_with_wallet_and_balance_and_timestamp_opt, }; use crate::blockchain::bip32::Bip32EncryptionKeyProvider; - use crate::blockchain::blockchain_interface::ProcessedPayableFallible::{Correct, Failed}; + use crate::blockchain::blockchain_bridge::PendingPayableFingerprintSeeds; + + use crate::blockchain::blockchain_interface::blockchain_interface_web3::{ + BlockchainInterfaceWeb3, CONTRACT_ABI, REQUESTS_IN_PARALLEL, TRANSACTION_LITERAL, + TRANSFER_METHOD_ID, + }; + use crate::blockchain::blockchain_interface::test_utils::{ + test_blockchain_interface_is_connected_and_functioning, LowBlockchainIntMock, + }; + use crate::blockchain::blockchain_interface::{ + BlockchainAgentBuildError, BlockchainError, BlockchainInterface, PayableTransactionError, + RetrievedBlockchainTransactions, + }; use crate::blockchain::test_utils::{ - make_default_signed_transaction, make_fake_event_loop_handle, make_tx_hash, - BatchPayableToolsMock, TestTransport, + all_chains, make_fake_event_loop_handle, make_tx_hash, TestTransport, }; + use crate::db_config::persistent_configuration::PersistentConfigError; + use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; use crate::sub_lib::wallet::Wallet; + use crate::test_utils::assert_string_contains; + use crate::test_utils::http_test_server::TestServer; + use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMock; use crate::test_utils::recorder::{make_recorder, Recorder}; use crate::test_utils::unshared_test_utils::decode_hex; - use crate::test_utils::{assert_string_contains, make_paying_wallet}; - use crate::test_utils::{make_wallet, TestRawTransaction}; + use crate::test_utils::{make_paying_wallet, make_wallet, TestRawTransaction}; use actix::{Actor, System}; - use crossbeam_channel::{unbounded, Receiver}; use ethereum_types::U64; use ethsign_crypto::Keccak256; + use futures::Future; use jsonrpc_core::Version::V2; - use jsonrpc_core::{Call, Error, ErrorCode, Id, MethodCall, Params}; + use jsonrpc_core::{Call, Error as RPCError, ErrorCode, Id, MethodCall, Params}; + use masq_lib::blockchains::chains::Chain; + use masq_lib::logger::Logger; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; use masq_lib::test_utils::utils::TEST_DEFAULT_CHAIN; - use masq_lib::utils::{find_free_port, slice_of_strs_to_vec_of_strings}; + use masq_lib::utils::find_free_port; use serde_derive::Deserialize; use serde_json::{json, Value}; - use simple_server::{Request, Server}; - use std::io::Write; - use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream}; - use std::ops::Add; + use std::net::Ipv4Addr; + + use crate::accountant::db_access_objects::payable_dao::PayableAccount; + use crate::accountant::db_access_objects::pending_payable_dao::PendingPayable; + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::test_utils::BlockchainAgentMock; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::test_utils::{ + make_default_signed_transaction, BatchPayableToolsMock, + }; + use crate::blockchain::blockchain_interface::data_structures::{ + BlockchainTransaction, RpcPayablesFailure, + }; + use indoc::indoc; use std::str::FromStr; use std::sync::{Arc, Mutex}; - use std::thread; - use std::time::{Duration, Instant, SystemTime}; - use web3::transports::Http; - use web3::types::H2048; + use std::time::SystemTime; + use web3::transports::{Batch, Http}; + use web3::types::{ + Address, BlockNumber, Bytes, TransactionParameters, TransactionReceipt, H2048, H256, U256, + }; use web3::Error as Web3Error; + use web3::Web3; #[test] - fn constants_have_correct_values() { + fn constants_are_correct() { let contract_abi_expected: &str = indoc!( r#"[{ "constant":true, @@ -894,129 +714,42 @@ mod tests { 0xf5, 0x23, 0xb3, 0xef, ], }; - assert_eq!(REQUESTS_IN_PARALLEL, 1); assert_eq!(CONTRACT_ABI, contract_abi_expected); assert_eq!(TRANSACTION_LITERAL, transaction_literal_expected); assert_eq!(TRANSFER_METHOD_ID, [0xa9, 0x05, 0x9c, 0xbb]); - assert_eq!(GWEI, U256([1_000_000_000u64, 0, 0, 0])); - assert_eq!( - BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED, - "To avoid being delinquency-banned, you \ - should restart the Node with a value for blockchain-service-url" - ) - } - - struct TestServer { - port: u16, - rx: Receiver>>, - } - - impl Drop for TestServer { - fn drop(&mut self) { - self.stop(); - } + assert_eq!(REQUESTS_IN_PARALLEL, 1); } - impl TestServer { - fn start(port: u16, bodies: Vec>) -> Self { - std::env::set_var("SIMPLESERVER_THREADS", "1"); - let (tx, rx) = unbounded(); - let _ = thread::spawn(move || { - let bodies_arc = Arc::new(Mutex::new(bodies)); - Server::new(move |req, mut rsp| { - if req.headers().get("X-Quit").is_some() { - panic!("Server stop requested"); - } - tx.send(req).unwrap(); - let body = bodies_arc.lock().unwrap().remove(0); - Ok(rsp.body(body)?) - }) - .listen(&Ipv4Addr::LOCALHOST.to_string(), &format!("{}", port)); - }); - let deadline = Instant::now().add(Duration::from_secs(5)); - loop { - thread::sleep(Duration::from_millis(10)); - match TcpStream::connect(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port)) { - Ok(_) => break, - Err(e) => eprintln!("No: {:?}", e), - } - if Instant::now().gt(&deadline) { - panic!("TestServer still not started after 5sec"); - } - } - TestServer { port, rx } - } - - fn requests_so_far(&self) -> Vec>> { - let mut requests = vec![]; - while let Ok(request) = self.rx.try_recv() { - requests.push(request); - } - return requests; - } + #[test] + fn blockchain_interface_web3_can_return_contract() { + all_chains().iter().for_each(|chain| { + let subject = BlockchainInterfaceWeb3::new( + TestTransport::default(), + make_fake_event_loop_handle(), + *chain, + ); - fn stop(&mut self) { - let mut stream = match TcpStream::connect(SocketAddr::new( - IpAddr::V4(Ipv4Addr::LOCALHOST), - self.port, - )) { - Ok(s) => s, - Err(_) => return, - }; - stream - .write(b"DELETE /irrelevant.htm HTTP/1.1\r\nX-Quit: Yes") - .unwrap(); - } + assert_eq!(subject.contract_address(), chain.rec().contract) + }) } #[test] - fn blockchain_interface_web3_handles_no_retrieved_transactions() { - let to = "0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc"; - let port = find_free_port(); - let test_server = TestServer::start( - port, - vec![br#"[{"jsonrpc":"2.0","id":2,"result":"0x400"},{"jsonrpc":"2.0","id":3,"result":[]}]"#.to_vec()], - ); - let (event_loop_handle, transport) = Http::with_max_parallel( - &format!("http://{}:{}", &Ipv4Addr::LOCALHOST, port), - REQUESTS_IN_PARALLEL, - ) - .unwrap(); - let subject = - BlockchainInterfaceWeb3::new(transport, event_loop_handle, TEST_DEFAULT_CHAIN); - - let end_block_nbr = 1024u64; - let result = subject - .retrieve_transactions( - BlockNumber::Number(42u64.into()), - BlockNumber::Number(end_block_nbr.into()), - &Wallet::from_str(&to).unwrap(), + fn blockchain_interface_web3_provides_plain_rp_calls_correctly() { + let subject_factory = |port: u16, _chain: Chain| { + let chain = Chain::PolyMainnet; + let (event_loop_handle, transport) = Http::with_max_parallel( + &format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port), + REQUESTS_IN_PARALLEL, ) .unwrap(); + Box::new(BlockchainInterfaceWeb3::new( + transport, + event_loop_handle, + chain, + )) as Box + }; - let requests = test_server.requests_so_far(); - - let bodies: Vec = requests - .into_iter() - .map(|request| serde_json::from_slice(&request.body()).unwrap()) - .map(|b: Value| serde_json::to_string(&b).unwrap()) - .collect(); - let expected_body_prefix = r#"[{"id":0,"jsonrpc":"2.0","method":"eth_blockNumber","params":[]},{"id":1,"jsonrpc":"2.0","method":"eth_getLogs","params":[{"address":"0x384dec25e03f94931767ce4c3556168468ba24c3","fromBlock":"0x2a","toBlock":"0x400","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",null,"0x000000000000000000000000"#; - let expected_body_suffix = r#""]}]}]"#; - let expected_body = format!( - "{}{}{}", - expected_body_prefix, - &to[2..], - expected_body_suffix - ); - assert_eq!(bodies, vec!(expected_body)); - assert_eq!( - result, - RetrievedBlockchainTransactions { - new_start_block: 1 + end_block_nbr, - transactions: vec![] - } - ); + test_blockchain_interface_is_connected_and_functioning(subject_factory) } #[test] @@ -1062,16 +795,15 @@ mod tests { ] }]"#.to_vec(), ]); - let (event_loop_handle, transport) = Http::with_max_parallel( &format!("http://{}:{}", &Ipv4Addr::LOCALHOST, port), REQUESTS_IN_PARALLEL, ) .unwrap(); - let subject = - BlockchainInterfaceWeb3::new(transport, event_loop_handle, TEST_DEFAULT_CHAIN); - + let chain = TEST_DEFAULT_CHAIN; + let subject = BlockchainInterfaceWeb3::new(transport, event_loop_handle, chain); let end_block_nbr = 1024u64; + let result = subject .retrieve_transactions( BlockNumber::Number(42u64.into()), @@ -1117,18 +849,67 @@ mod tests { ) } + #[test] + fn blockchain_interface_web3_handles_no_retrieved_transactions() { + let to = "0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc"; + let port = find_free_port(); + let test_server = TestServer::start( + port, + vec![br#"[{"jsonrpc":"2.0","id":2,"result":"0x400"},{"jsonrpc":"2.0","id":3,"result":[]}]"#.to_vec()], + ); + let (event_loop_handle, transport) = Http::with_max_parallel( + &format!("http://{}:{}", &Ipv4Addr::LOCALHOST, port), + REQUESTS_IN_PARALLEL, + ) + .unwrap(); + let subject = + BlockchainInterfaceWeb3::new(transport, event_loop_handle, TEST_DEFAULT_CHAIN); + let end_block_nbr = 1024u64; + + let result = subject + .retrieve_transactions( + BlockNumber::Number(42u64.into()), + BlockNumber::Number(end_block_nbr.into()), + &Wallet::from_str(&to).unwrap(), + ) + .unwrap(); + + let requests = test_server.requests_so_far(); + let bodies: Vec = requests + .into_iter() + .map(|request| serde_json::from_slice(&request.body()).unwrap()) + .map(|b: Value| serde_json::to_string(&b).unwrap()) + .collect(); + let expected_body_prefix = r#"[{"id":0,"jsonrpc":"2.0","method":"eth_blockNumber","params":[]},{"id":1,"jsonrpc":"2.0","method":"eth_getLogs","params":[{"address":"0x384dec25e03f94931767ce4c3556168468ba24c3","fromBlock":"0x2a","toBlock":"0x400","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",null,"0x000000000000000000000000"#; + let expected_body_suffix = r#""]}]}]"#; + let expected_body = format!( + "{}{}{}", + expected_body_prefix, + &to[2..], + expected_body_suffix + ); + assert_eq!(bodies, vec!(expected_body)); + assert_eq!( + result, + RetrievedBlockchainTransactions { + new_start_block: 1 + end_block_nbr, + transactions: vec![] + } + ); + } + #[test] #[should_panic(expected = "No address for an uninitialized wallet!")] fn blockchain_interface_web3_retrieve_transactions_returns_an_error_if_the_to_address_is_invalid( ) { - let port = 8545; + let port = find_free_port(); let (event_loop_handle, transport) = Http::with_max_parallel( &format!("http://{}:{}", &Ipv4Addr::LOCALHOST, port), REQUESTS_IN_PARALLEL, ) .unwrap(); - let subject = - BlockchainInterfaceWeb3::new(transport, event_loop_handle, TEST_DEFAULT_CHAIN); + let chain = TEST_DEFAULT_CHAIN; + let subject = BlockchainInterfaceWeb3::new(transport, event_loop_handle, chain); let result = subject.retrieve_transactions( BlockNumber::Number(42u64.into()), @@ -1154,8 +935,8 @@ mod tests { REQUESTS_IN_PARALLEL, ) .unwrap(); - let subject = - BlockchainInterfaceWeb3::new(transport, event_loop_handle, TEST_DEFAULT_CHAIN); + let chain = TEST_DEFAULT_CHAIN; + let subject = BlockchainInterfaceWeb3::new(transport, event_loop_handle, chain); let result = subject.retrieve_transactions( BlockNumber::Number(42u64.into()), @@ -1176,15 +957,13 @@ mod tests { let _test_server = TestServer::start(port, vec![ br#"[{"jsonrpc":"2.0","id":2,"result":"0x400"},{"jsonrpc":"2.0","id":3,"result":[{"address":"0xcd6c588e005032dd882cd43bf53a32129be81302","blockHash":"0x1a24b9169cbaec3f6effa1f600b70c7ab9e8e86db44062b49132a4415d26732a","blockNumber":"0x4be663","data":"0x0000000000000000000000000000000000000000000000056bc75e2d6310000001","logIndex":"0x0","removed":false,"topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000003f69f9efd4f2592fd70be8c32ecd9dce71c472fc","0x000000000000000000000000adc1853c7859369639eb414b6342b36288fe6092"],"transactionHash":"0x955cec6ac4f832911ab894ce16aa22c3003f46deff3f7165b32700d2f5ff0681","transactionIndex":"0x0"}]}]"#.to_vec() ]); - let (event_loop_handle, transport) = Http::with_max_parallel( &format!("http://{}:{}", &Ipv4Addr::LOCALHOST, port), REQUESTS_IN_PARALLEL, ) .unwrap(); - - let subject = - BlockchainInterfaceWeb3::new(transport, event_loop_handle, TEST_DEFAULT_CHAIN); + let chain = TEST_DEFAULT_CHAIN; + let subject = BlockchainInterfaceWeb3::new(transport, event_loop_handle, chain); let result = subject.retrieve_transactions( BlockNumber::Number(42u64.into()), @@ -1239,15 +1018,13 @@ mod tests { let _test_server = TestServer::start (port, vec![ br#"[{"jsonrpc":"2.0","id":1,"result":"error"},{"jsonrpc":"2.0","id":2,"result":[{"address":"0xcd6c588e005032dd882cd43bf53a32129be81302","blockHash":"0x1a24b9169cbaec3f6effa1f600b70c7ab9e8e86db44062b49132a4415d26732a","data":"0x0000000000000000000000000000000000000000000000000010000000000000","logIndex":"0x0","removed":false,"topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000003f69f9efd4f2592fd70be8c32ecd9dce71c472fc","0x000000000000000000000000adc1853c7859369639eb414b6342b36288fe6092"],"transactionHash":"0x955cec6ac4f832911ab894ce16aa22c3003f46deff3f7165b32700d2f5ff0681","transactionIndex":"0x0"}]}]"#.to_vec() ]); - let (event_loop_handle, transport) = Http::with_max_parallel( &format!("http://{}:{}", &Ipv4Addr::LOCALHOST, port), REQUESTS_IN_PARALLEL, ) .unwrap(); - - let subject = - BlockchainInterfaceWeb3::new(transport, event_loop_handle, TEST_DEFAULT_CHAIN); + let chain = TEST_DEFAULT_CHAIN; + let subject = BlockchainInterfaceWeb3::new(transport, event_loop_handle, chain); let start_block = BlockNumber::Number(42u64.into()); let result = subject.retrieve_transactions( @@ -1273,169 +1050,166 @@ mod tests { } #[test] - fn blockchain_interface_web3_can_retrieve_eth_balance_of_a_wallet() { - let port = find_free_port(); - let _test_server = TestServer::start( - port, - vec![br#"{"jsonrpc":"2.0","id":0,"result":"0xFFFF"}"#.to_vec()], + fn blockchain_interface_web3_can_build_blockchain_agent() { + let get_transaction_fee_balance_params_arc = Arc::new(Mutex::new(vec![])); + let get_masq_balance_params_arc = Arc::new(Mutex::new(vec![])); + let get_transactions_id_params_arc = Arc::new(Mutex::new(vec![])); + let chain = Chain::PolyMainnet; + let wallet = make_wallet("abc"); + let persistent_config = PersistentConfigurationMock::new().gas_price_result(Ok(50)); + let mut subject = BlockchainInterfaceWeb3::new( + TestTransport::default(), + make_fake_event_loop_handle(), + chain, ); - - let (event_loop_handle, transport) = Http::with_max_parallel( - &format!("http://{}:{}", &Ipv4Addr::LOCALHOST, port), - REQUESTS_IN_PARALLEL, - ) - .unwrap(); - - let subject = - BlockchainInterfaceWeb3::new(transport, event_loop_handle, TEST_DEFAULT_CHAIN); + let transaction_fee_balance = U256::from(123_456_789); + let masq_balance = U256::from(444_444_444); + let transaction_id = U256::from(23); + let lower_blockchain_interface = LowBlockchainIntMock::default() + .get_transaction_fee_balance_params(&get_transaction_fee_balance_params_arc) + .get_transaction_fee_balance_result(Ok(transaction_fee_balance)) + .get_masq_balance_params(&get_masq_balance_params_arc) + .get_masq_balance_result(Ok(masq_balance)) + .get_transaction_id_params(&get_transactions_id_params_arc) + .get_transaction_id_result(Ok(transaction_id)); + subject.lower_interface = Box::new(lower_blockchain_interface); let result = subject - .get_transaction_fee_balance( - &Wallet::from_str("0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc").unwrap(), - ) + .build_blockchain_agent(&wallet, &persistent_config) .unwrap(); - assert_eq!(result, U256::from(65_535)); - } - - #[test] - #[should_panic(expected = "No address for an uninitialized wallet!")] - fn blockchain_interface_web3_returns_an_error_when_requesting_eth_balance_of_an_invalid_wallet() - { - let port = 8545; - let (event_loop_handle, transport) = Http::with_max_parallel( - &format!("http://{}:{}", &Ipv4Addr::LOCALHOST, port), - REQUESTS_IN_PARALLEL, - ) - .unwrap(); - let subject = - BlockchainInterfaceWeb3::new(transport, event_loop_handle, TEST_DEFAULT_CHAIN); - - let result = subject.get_transaction_fee_balance(&Wallet::new( - "0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fQ", - )); - - assert_eq!(result, Err(BlockchainError::InvalidAddress)); - } - - #[test] - fn blockchain_interface_web3_returns_an_error_for_unintelligible_response_to_requesting_eth_balance( - ) { - let port = find_free_port(); - let _test_server = TestServer::start( - port, - vec![br#"{"jsonrpc":"2.0","id":0,"result":"0xFFFQ"}"#.to_vec()], - ); - - let (event_loop_handle, transport) = - Http::new(&format!("http://{}:{}", &Ipv4Addr::LOCALHOST, port)).unwrap(); - let subject = - BlockchainInterfaceWeb3::new(transport, event_loop_handle, TEST_DEFAULT_CHAIN); - - let result = subject.get_transaction_fee_balance( - &Wallet::from_str("0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc").unwrap(), - ); - - match result { - Err(BlockchainError::QueryFailed(msg)) if msg.contains("invalid hex character: Q") => { - () + let get_transaction_fee_balance_params = + get_transaction_fee_balance_params_arc.lock().unwrap(); + assert_eq!(*get_transaction_fee_balance_params, vec![wallet.clone()]); + let get_masq_balance_params = get_masq_balance_params_arc.lock().unwrap(); + assert_eq!(*get_masq_balance_params, vec![wallet.clone()]); + let get_transaction_id_params = get_transactions_id_params_arc.lock().unwrap(); + assert_eq!(*get_transaction_id_params, vec![wallet.clone()]); + assert_eq!(result.consuming_wallet(), &wallet); + assert_eq!(result.pending_transaction_id(), transaction_id); + assert_eq!( + result.consuming_wallet_balances(), + ConsumingWalletBalances { + transaction_fee_balance_in_minor_units: transaction_fee_balance, + masq_token_balance_in_minor_units: masq_balance } - x => panic!("Expected complaint about hex character, but got {:?}", x), - }; - } - - #[test] - fn blockchain_interface_web3_returns_error_for_unintelligible_response_to_gas_balance() { - let act = |subject: &BlockchainInterfaceWeb3, wallet: &Wallet| { - subject.get_transaction_fee_balance(wallet) - }; - - assert_error_during_requesting_balance(act, "invalid hex character"); + ); + assert_eq!(result.agreed_fee_per_computation_unit(), 50); + let expected_fee_estimation = (3 + * (BlockchainInterfaceWeb3::::web3_gas_limit_const_part(chain) + + WEB3_MAXIMAL_GAS_LIMIT_MARGIN) + * 50) as u128; + assert_eq!( + result.estimated_transaction_fee_total(3), + expected_fee_estimation + ) } #[test] - fn blockchain_interface_web3_can_retrieve_token_balance_of_a_wallet() { - let port = find_free_port(); - let _test_server = TestServer::start (port, vec![ - br#"{"jsonrpc":"2.0","id":0,"result":"0x000000000000000000000000000000000000000000000000000000000000FFFF"}"#.to_vec() - ]); - - let (event_loop_handle, transport) = Http::with_max_parallel( - &format!("http://{}:{}", &Ipv4Addr::LOCALHOST, port), - REQUESTS_IN_PARALLEL, - ) - .unwrap(); - let subject = - BlockchainInterfaceWeb3::new(transport, event_loop_handle, TEST_DEFAULT_CHAIN); + fn build_of_the_blockchain_agent_fails_on_fetching_gas_price() { + let chain = Chain::PolyMumbai; + let wallet = make_wallet("abc"); + let persistent_config = PersistentConfigurationMock::new().gas_price_result(Err( + PersistentConfigError::UninterpretableValue("booga".to_string()), + )); + let subject = BlockchainInterfaceWeb3::new( + TestTransport::default(), + make_fake_event_loop_handle(), + chain, + ); - let result = subject - .get_token_balance( - &Wallet::from_str("0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc").unwrap(), - ) - .unwrap(); + let result = subject.build_blockchain_agent(&wallet, &persistent_config); - assert_eq!(result, U256::from(65_535)); + let err = match result { + Err(e) => e, + _ => panic!("we expected Err() but got Ok()"), + }; + let expected_err = BlockchainAgentBuildError::GasPrice( + PersistentConfigError::UninterpretableValue("booga".to_string()), + ); + assert_eq!(err, expected_err) } - #[test] - #[should_panic(expected = "No address for an uninitialized wallet!")] - fn blockchain_interface_web3_returns_an_error_when_requesting_token_balance_of_an_invalid_wallet( - ) { - let port = 8545; - let (event_loop_handle, transport) = Http::with_max_parallel( - &format!("http://{}:{}", &Ipv4Addr::LOCALHOST, port), - REQUESTS_IN_PARALLEL, - ) - .unwrap(); - let subject = - BlockchainInterfaceWeb3::new(transport, event_loop_handle, TEST_DEFAULT_CHAIN); + fn build_of_the_blockchain_agent_fails_on_blockchain_interface_error( + lower_blockchain_interface: LowBlockchainIntMock, + expected_err_factory: F, + ) where + F: FnOnce(&Wallet) -> BlockchainAgentBuildError, + { + let chain = Chain::EthMainnet; + let wallet = make_wallet("bcd"); + let persistent_config = PersistentConfigurationMock::new().gas_price_result(Ok(30)); + let mut subject = BlockchainInterfaceWeb3::new( + TestTransport::default(), + make_fake_event_loop_handle(), + chain, + ); + subject.lower_interface = Box::new(lower_blockchain_interface); - let result = - subject.get_token_balance(&Wallet::new("0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fQ")); + let result = subject.build_blockchain_agent(&wallet, &persistent_config); - assert_eq!(result, Err(BlockchainError::InvalidAddress)); + let err = match result { + Err(e) => e, + _ => panic!("we expected Err() but got Ok()"), + }; + let expected_err = expected_err_factory(&wallet); + assert_eq!(err, expected_err) } #[test] - fn blockchain_interface_web3_returns_error_for_unintelligible_response_to_token_balance() { - let act = |subject: &BlockchainInterfaceWeb3, wallet: &Wallet| { - subject.get_token_balance(wallet) + fn build_of_the_blockchain_agent_fails_on_transaction_fee_balance() { + let lower_interface = LowBlockchainIntMock::default() + .get_transaction_fee_balance_result(Err(BlockchainError::InvalidAddress)); + let expected_err_factory = |wallet: &Wallet| { + BlockchainAgentBuildError::TransactionFeeBalance( + wallet.clone(), + BlockchainError::InvalidAddress, + ) }; - assert_error_during_requesting_balance(act, "Invalid hex"); + build_of_the_blockchain_agent_fails_on_blockchain_interface_error( + lower_interface, + expected_err_factory, + ) } - fn assert_error_during_requesting_balance(act: F, expected_err_msg_fragment: &str) - where - F: FnOnce(&BlockchainInterfaceWeb3, &Wallet) -> ResultForBalance, - { - let port = find_free_port(); - let _test_server = TestServer::start (port, vec![ - br#"{"jsonrpc":"2.0","id":0,"result":"0x000000000000000000000000000000000000000000000000000000000000FFFQ"}"#.to_vec() - ]); + #[test] + fn build_of_the_blockchain_agent_fails_on_masq_balance() { + let transaction_fee_balance = U256::from(123_456_789); + let lower_interface = LowBlockchainIntMock::default() + .get_transaction_fee_balance_result(Ok(transaction_fee_balance)) + .get_masq_balance_result(Err(BlockchainError::InvalidResponse)); + let expected_err_factory = |wallet: &Wallet| { + BlockchainAgentBuildError::ServiceFeeBalance( + wallet.clone(), + BlockchainError::InvalidResponse, + ) + }; - let (event_loop_handle, transport) = Http::with_max_parallel( - &format!("http://{}:{}", &Ipv4Addr::LOCALHOST, port), - REQUESTS_IN_PARALLEL, + build_of_the_blockchain_agent_fails_on_blockchain_interface_error( + lower_interface, + expected_err_factory, ) - .unwrap(); - let subject = - BlockchainInterfaceWeb3::new(transport, event_loop_handle, TEST_DEFAULT_CHAIN); - - let result = act( - &subject, - &Wallet::from_str("0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc").unwrap(), - ); + } - let err_msg = match result { - Err(BlockchainError::QueryFailed(msg)) => msg, - x => panic!("Expected BlockchainError::QueryFailed, but got {:?}", x), + #[test] + fn build_of_the_blockchain_agent_fails_on_transaction_id() { + let transaction_fee_balance = U256::from(123_456_789); + let masq_balance = U256::from(500_000_000); + let lower_interface = LowBlockchainIntMock::default() + .get_transaction_fee_balance_result(Ok(transaction_fee_balance)) + .get_masq_balance_result(Ok(masq_balance)) + .get_transaction_id_result(Err(BlockchainError::InvalidResponse)); + let expected_err_factory = |wallet: &Wallet| { + BlockchainAgentBuildError::TransactionID( + wallet.clone(), + BlockchainError::InvalidResponse, + ) }; - assert!( - err_msg.contains(expected_err_msg_fragment), - "Expected this fragment {} in this err msg: {}", - expected_err_msg_fragment, - err_msg + + build_of_the_blockchain_agent_fails_on_blockchain_interface_error( + lower_interface, + expected_err_factory, ); } @@ -1449,7 +1223,7 @@ mod tests { //Any eventual rpc errors brought back are processed as well... let expected_batch_responses = vec![ Ok(json!("...unnecessarily important hash...")), - Err(web3::Error::Rpc(Error { + Err(web3::Error::Rpc(RPCError { code: ErrorCode::ServerError(114), message: "server being busy".to_string(), data: None, @@ -1463,13 +1237,10 @@ mod tests { let actor_addr = accountant.start(); let fingerprint_recipient = recipient!(actor_addr, PendingPayableFingerprintSeeds); let logger = Logger::new("sending_batch_payments"); - let mut subject = BlockchainInterfaceWeb3::new( - transport.clone(), - make_fake_event_loop_handle(), - TEST_DEFAULT_CHAIN, - ); + let chain = TEST_DEFAULT_CHAIN; + let mut subject = + BlockchainInterfaceWeb3::new(transport.clone(), make_fake_event_loop_handle(), chain); subject.logger = logger; - let gas_price = 120; let amount_1 = gwei_to_wei(900_000_000_u64); let account_1 = make_payable_account_with_wallet_and_balance_and_timestamp_opt( make_wallet("w123"), @@ -1488,19 +1259,13 @@ mod tests { amount_3, None, ); - let pending_nonce = U256::from(6); let accounts_to_process = vec![account_1, account_2, account_3]; let consuming_wallet = make_paying_wallet(b"gdasgsa"); + let agent = make_initialized_agent(120, consuming_wallet, U256::from(6)); let test_timestamp_before = SystemTime::now(); let result = subject - .send_payables_within_batch( - &consuming_wallet, - gas_price, - pending_nonce, - &fingerprint_recipient, - &accounts_to_process, - ) + .send_batch_of_payables(agent, &fingerprint_recipient, &accounts_to_process) .unwrap(); let test_timestamp_after = SystemTime::now(); @@ -1548,10 +1313,10 @@ mod tests { ); let check_expected_successful_request = |expected_hash: H256, idx: usize| { let pending_payable = match &result[idx]{ - Correct(pp) => pp, - Failed(RpcPayableFailure{ rpc_error, recipient_wallet: recipient, hash }) => panic!( - "we expected correct pending payable but got one with rpc_error: {:?} and hash: {} for recipient: {}", - rpc_error, hash, recipient + Ok(pp) => pp, + Err(RpcPayablesFailure { rpc_error, recipient_wallet: recipient, hash }) => panic!( + "we expected correct pending payable but got one with rpc_error: {:?} and hash: {} for recipient: {}", + rpc_error, hash, recipient ), }; let hash = pending_payable.hash; @@ -1565,11 +1330,11 @@ mod tests { //failing request let pending_payable_fallible_2 = &result[1]; let (rpc_error, recipient_2, hash_2) = match pending_payable_fallible_2 { - Correct(pp) => panic!( + Ok(pp) => panic!( "we expected failing pending payable but got a good one: {:?}", pp ), - Failed(RpcPayableFailure { + Err(RpcPayablesFailure { rpc_error, recipient_wallet: recipient, hash, @@ -1577,7 +1342,7 @@ mod tests { }; assert_eq!( rpc_error, - &web3::Error::Rpc(Error { + &web3::Error::Rpc(RPCError { code: ErrorCode::ServerError(114), message: "server being busy".to_string(), data: None @@ -1644,7 +1409,7 @@ mod tests { } #[test] - fn web3_interface_send_payables_within_batch_components_are_used_together_properly() { + fn send_payables_within_batch_components_are_used_together_properly() { let sign_transaction_params_arc = Arc::new(Mutex::new(vec![])); let append_transaction_to_batch_params_arc = Arc::new(Mutex::new(vec![])); let new_payable_fingerprint_params_arc = Arc::new(Mutex::new(vec![])); @@ -1661,7 +1426,7 @@ mod tests { let chain = Chain::EthMainnet; let mut subject = BlockchainInterfaceWeb3::new(transport, make_fake_event_loop_handle(), chain); - let first_tx_parameters = TransactionParameters { + let first_transaction_params_expected = TransactionParameters { nonce: Some(U256::from(4)), to: Some(subject.contract_address()), gas: U256::from(56_552), @@ -1677,10 +1442,10 @@ mod tests { let first_signed_transaction = subject .web3 .accounts() - .sign_transaction(first_tx_parameters.clone(), &secret_key) + .sign_transaction(first_transaction_params_expected.clone(), &secret_key) .wait() .unwrap(); - let second_tx_parameters = TransactionParameters { + let second_transaction_params_expected = TransactionParameters { nonce: Some(U256::from(5)), to: Some(subject.contract_address()), gas: U256::from(56_552), @@ -1696,12 +1461,11 @@ mod tests { let second_signed_transaction = subject .web3 .accounts() - .sign_transaction(second_tx_parameters.clone(), &secret_key) + .sign_transaction(second_transaction_params_expected.clone(), &secret_key) .wait() .unwrap(); let first_hash = first_signed_transaction.transaction_hash; let second_hash = second_signed_transaction.transaction_hash; - let pending_nonce = U256::from(4); //technically, the JSON values in the correct responses don't matter, we only check for errors if any came back let rpc_responses = vec![ Ok(Value::String((&first_hash.to_string()[2..]).to_string())), @@ -1718,7 +1482,6 @@ mod tests { .submit_batch_result(Ok(rpc_responses)); subject.batch_payable_tools = Box::new(batch_payables_tools); let consuming_wallet = make_paying_wallet(consuming_wallet_secret); - let gas_price = 123; let first_payment_amount = 333_222_111_000; let first_creditor_wallet = make_wallet("creditor321"); let first_account = make_payable_account_with_wallet_and_balance_and_timestamp_opt( @@ -1733,11 +1496,10 @@ mod tests { second_payment_amount, None, ); + let agent = make_initialized_agent(123, consuming_wallet, U256::from(4)); - let result = subject.send_payables_within_batch( - &consuming_wallet, - gas_price, - pending_nonce, + let result = subject.send_batch_of_payables( + agent, &initiate_fingerprints_recipient, &vec![first_account, second_account], ); @@ -1753,13 +1515,16 @@ mod tests { assert_eq!( result, Ok(vec![ - Correct(first_resulting_pending_payable), - Correct(second_resulting_pending_payable) + Ok(first_resulting_pending_payable), + Ok(second_resulting_pending_payable) ]) ); let mut sign_transaction_params = sign_transaction_params_arc.lock().unwrap(); - let (first_transaction_params, web3, secret) = sign_transaction_params.remove(0); - assert_eq!(first_transaction_params, first_tx_parameters); + let (first_transaction_params_actual, web3, secret) = sign_transaction_params.remove(0); + assert_eq!( + first_transaction_params_actual, + first_transaction_params_expected + ); let check_web3_origin = |web3: &Web3>| { let ref_count_before_clone = Arc::strong_count(&reference_counter_arc); let _new_ref = web3.clone(); @@ -1773,9 +1538,12 @@ mod tests { .unwrap()) .into() ); - let (second_transaction_params, web3_from_st_call, secret) = + let (second_transaction_params_actual, web3_from_st_call, secret) = sign_transaction_params.remove(0); - assert_eq!(second_transaction_params, second_tx_parameters); + assert_eq!( + second_transaction_params_actual, + second_transaction_params_expected + ); check_web3_origin(&web3_from_st_call); assert_eq!( secret, @@ -1817,9 +1585,8 @@ mod tests { assert_eq!(submit_batch_params.len(), 1); check_web3_origin(&web3_from_sb_call); assert!(accountant_recording_arc.lock().unwrap().is_empty()); - let system = System::new( - "web3_interface_send_payables_in_batch_components_are_used_together_properly", - ); + let system = + System::new("send_payables_within_batch_components_are_used_together_properly"); let probe_message = PendingPayableFingerprintSeeds { batch_wide_timestamp: SystemTime::now(), hashes_and_balances: vec![], @@ -1832,70 +1599,47 @@ mod tests { } #[test] - fn web3_interface_base_gas_limit_is_properly_set() { - assert_eq!( - BlockchainInterfaceWeb3::::base_gas_limit(Chain::PolyMainnet), - 70_000 - ); - assert_eq!( - BlockchainInterfaceWeb3::::base_gas_limit(Chain::PolyMumbai), - 70_000 - ); + fn web3_gas_limit_const_part_returns_reasonable_values() { + type Subject = BlockchainInterfaceWeb3; assert_eq!( - BlockchainInterfaceWeb3::::base_gas_limit(Chain::EthMainnet), + Subject::web3_gas_limit_const_part(Chain::EthMainnet), 55_000 ); assert_eq!( - BlockchainInterfaceWeb3::::base_gas_limit(Chain::EthRopsten), + Subject::web3_gas_limit_const_part(Chain::EthRopsten), 55_000 ); assert_eq!( - BlockchainInterfaceWeb3::::base_gas_limit(Chain::Dev), - 55_000 + Subject::web3_gas_limit_const_part(Chain::PolyMainnet), + 70_000 ); - } - - #[test] - fn web3_interface_gas_limit_for_polygon_mainnet_starts_on_70000_as_the_base() { - let transport = TestTransport::default(); - let subject = BlockchainInterfaceWeb3::new( - transport, - make_fake_event_loop_handle(), - Chain::PolyMainnet, + assert_eq!( + Subject::web3_gas_limit_const_part(Chain::PolyMumbai), + 70_000 ); - - assert_gas_limit_is_between(subject, 70000, u64::MAX) + assert_eq!(Subject::web3_gas_limit_const_part(Chain::Dev), 55_000); } #[test] - fn web3_interface_gas_limit_for_dev_lies_within_limits() { - let transport = TestTransport::default(); - let subject = - BlockchainInterfaceWeb3::new(transport, make_fake_event_loop_handle(), Chain::Dev); - - assert_gas_limit_is_between(subject, 55000, 65000) + fn gas_limit_for_polygon_mainnet_lies_within_limits_for_raw_transaction() { + test_gas_limit_is_between_limits(Chain::PolyMainnet); } #[test] - fn web3_interface_gas_limit_for_eth_mainnet_lies_within_limits() { - let transport = TestTransport::default(); - let subject = BlockchainInterfaceWeb3::new( - transport, - make_fake_event_loop_handle(), - Chain::EthMainnet, - ); - - assert_gas_limit_is_between(subject, 55000, 65000) + fn gas_limit_for_eth_mainnet_lies_within_limits_for_raw_transaction() { + test_gas_limit_is_between_limits(Chain::EthMainnet) } - fn assert_gas_limit_is_between( - mut subject: BlockchainInterfaceWeb3, - not_under_this_value: u64, - not_above_this_value: u64, - ) { + fn test_gas_limit_is_between_limits(chain: Chain) { let sign_transaction_params_arc = Arc::new(Mutex::new(vec![])); + let transport = TestTransport::default(); + let mut subject = + BlockchainInterfaceWeb3::new(transport, make_fake_event_loop_handle(), chain); + let not_under_this_value = + BlockchainInterfaceWeb3::::web3_gas_limit_const_part(chain); + let not_above_this_value = not_under_this_value + WEB3_MAXIMAL_GAS_LIMIT_MARGIN; let consuming_wallet_secret_raw_bytes = b"my-wallet"; - let batch_payable_tools = BatchPayableToolsMock::::default() + let batch_payable_tools = BatchPayableToolsMock::::default() .sign_transaction_params(&sign_transaction_params_arc) .sign_transaction_result(Ok(make_default_signed_transaction())); subject.batch_payable_tools = Box::new(batch_payable_tools); @@ -1914,8 +1658,18 @@ mod tests { let mut sign_transaction_params = sign_transaction_params_arc.lock().unwrap(); let (transaction_params, _, secret) = sign_transaction_params.remove(0); assert!(sign_transaction_params.is_empty()); - assert!(transaction_params.gas >= U256::from(not_under_this_value)); - assert!(transaction_params.gas <= U256::from(not_above_this_value)); + assert!( + transaction_params.gas >= U256::from(not_under_this_value), + "actual gas limit {} isn't above or equal {}", + transaction_params.gas, + not_under_this_value + ); + assert!( + transaction_params.gas <= U256::from(not_above_this_value), + "actual gas limit {} isn't below or equal {}", + transaction_params.gas, + not_above_this_value + ); assert_eq!( secret, (&Bip32EncryptionKeyProvider::from_raw_secret( @@ -1927,33 +1681,24 @@ mod tests { } #[test] - fn signing_error_ends_iteration_over_accounts_after_detecting_first_error_which_is_then_propagated_all_way_up_and_out( - ) { + fn signing_error_terminates_iteration_over_accounts_and_propagates_it_all_way_up_and_out() { let transport = TestTransport::default(); + let chain = Chain::PolyMumbai; let batch_payable_tools = BatchPayableToolsMock::::default() .sign_transaction_result(Err(Web3Error::Signing( secp256k1secrets::Error::InvalidSecretKey, ))) //we return after meeting the first result .sign_transaction_result(Err(Web3Error::Internal)); - let mut subject = BlockchainInterfaceWeb3::new( - transport, - make_fake_event_loop_handle(), - Chain::PolyMumbai, - ); + let mut subject = + BlockchainInterfaceWeb3::new(transport, make_fake_event_loop_handle(), chain); subject.batch_payable_tools = Box::new(batch_payable_tools); let recipient = Recorder::new().start().recipient(); let consuming_wallet = make_paying_wallet(&b"consume, you greedy fool!"[..]); - let nonce = U256::from(123); let accounts = vec![make_payable_account(5555), make_payable_account(6666)]; + let agent = make_initialized_agent(123, consuming_wallet, U256::from(4)); - let result = subject.send_payables_within_batch( - &consuming_wallet, - 111, - nonce, - &recipient, - &accounts, - ); + let result = subject.send_batch_of_payables(agent, &recipient, &accounts); assert_eq!( result, @@ -1966,16 +1711,12 @@ mod tests { } #[test] - fn send_payables_within_batch_fails_on_badly_prepared_consuming_wallet_without_secret() { + fn send_batch_of_payables_fails_on_badly_prepared_consuming_wallet_without_secret() { let transport = TestTransport::default(); let incomplete_consuming_wallet = Wallet::from_str("0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc").unwrap(); - let subject = BlockchainInterfaceWeb3::new( - transport, - make_fake_event_loop_handle(), - TEST_DEFAULT_CHAIN, - ); - + let chain = TEST_DEFAULT_CHAIN; + let subject = BlockchainInterfaceWeb3::new(transport, make_fake_event_loop_handle(), chain); let system = System::new("test"); let (accountant, _, accountant_recording_arc) = make_recorder(); let recipient = accountant.start().recipient(); @@ -1984,16 +1725,9 @@ mod tests { 9000, None, ); - let gas_price = 123; - let nonce = U256::from(1); + let agent = make_initialized_agent(123, incomplete_consuming_wallet, U256::from(1)); - let result = subject.send_payables_within_batch( - &incomplete_consuming_wallet, - gas_price, - nonce, - &recipient, - &vec![account], - ); + let result = subject.send_batch_of_payables(agent, &recipient, &vec![account]); System::current().stop(); system.run(); @@ -2005,7 +1739,7 @@ mod tests { } #[test] - fn send_payables_within_batch_fails_on_sending() { + fn send_batch_of_payables_fails_on_sending() { let transport = TestTransport::default(); let hash = make_tx_hash(123); let mut signed_transaction = make_default_signed_transaction(); @@ -2015,11 +1749,9 @@ mod tests { .batch_wide_timestamp_result(SystemTime::now()) .submit_batch_result(Err(Web3Error::Transport("Transaction crashed".to_string()))); let consuming_wallet_secret_raw_bytes = b"okay-wallet"; - let mut subject = BlockchainInterfaceWeb3::new( - transport, - make_fake_event_loop_handle(), - Chain::PolyMumbai, - ); + let chain = Chain::PolyMumbai; + let mut subject = + BlockchainInterfaceWeb3::new(transport, make_fake_event_loop_handle(), chain); subject.batch_payable_tools = Box::new(batch_payable_tools); let unimportant_recipient = Recorder::new().start().recipient(); let account = make_payable_account_with_wallet_and_balance_and_timestamp_opt( @@ -2028,16 +1760,9 @@ mod tests { None, ); let consuming_wallet = make_paying_wallet(consuming_wallet_secret_raw_bytes); - let gas_price = 123; - let nonce = U256::from(1); + let agent = make_initialized_agent(120, consuming_wallet, U256::from(6)); - let result = subject.send_payables_within_batch( - &consuming_wallet, - gas_price, - nonce, - &unimportant_recipient, - &vec![account], - ); + let result = subject.send_batch_of_payables(agent, &unimportant_recipient, &vec![account]); assert_eq!( result, @@ -2056,11 +1781,9 @@ mod tests { secp256k1secrets::Error::InvalidSecretKey, ))); let consuming_wallet_secret_raw_bytes = b"okay-wallet"; - let mut subject = BlockchainInterfaceWeb3::new( - transport, - make_fake_event_loop_handle(), - Chain::PolyMumbai, - ); + let chain = Chain::PolyMumbai; + let mut subject = + BlockchainInterfaceWeb3::new(transport, make_fake_event_loop_handle(), chain); subject.batch_payable_tools = Box::new(batch_payable_tools); let recipient = make_wallet("unlucky man"); let consuming_wallet = make_paying_wallet(consuming_wallet_secret_raw_bytes); @@ -2078,6 +1801,10 @@ mod tests { ); } + const TEST_PAYMENT_AMOUNT: u128 = 1_000_000_000_000; + const TEST_GAS_PRICE_ETH: u64 = 110; + const TEST_GAS_PRICE_POLYGON: u64 = 50; + fn test_consuming_wallet_with_secret() -> Wallet { let key_pair = Bip32EncryptionKeyProvider::from_raw_secret( &decode_hex("97923d8fd8de4a00f912bfb77ef483141dec551bd73ea59343ef5c4aac965d04") @@ -2094,10 +1821,6 @@ mod tests { Wallet::from(address) } - const TEST_PAYMENT_AMOUNT: u128 = 1_000_000_000_000; - const TEST_GAS_PRICE_ETH: u64 = 110; - const TEST_GAS_PRICE_POLYGON: u64 = 50; - fn assert_that_signed_transactions_agrees_with_template( chain: Chain, nonce: u64, @@ -2108,10 +1831,9 @@ mod tests { let consuming_wallet = test_consuming_wallet_with_secret(); let recipient_wallet = test_recipient_wallet(); let nonce_correct_type = U256::from(nonce); - let gas_price = match chain.rec().chain_family { - ChainFamily::Eth => TEST_GAS_PRICE_ETH, - ChainFamily::Polygon => TEST_GAS_PRICE_POLYGON, - _ => panic!("isn't our interest in this test"), + let gas_price = match chain { + Chain::EthMainnet | Chain::EthRopsten | Chain::Dev => TEST_GAS_PRICE_ETH, + Chain::PolyMainnet | Chain::PolyMumbai => TEST_GAS_PRICE_POLYGON, }; let payable_account = make_payable_account_with_wallet_and_balance_and_timestamp_opt( recipient_wallet, @@ -2202,7 +1924,7 @@ mod tests { //an adapted test from old times when we had our own signing method //I don't have data for the new chains so I omit them in this kind of tests #[test] - fn signs_various_transaction_for_eth_mainnet() { + fn signs_various_transactions_for_eth_mainnet() { let signatures = &[ &[ 248, 108, 9, 133, 4, 168, 23, 200, 0, 130, 82, 8, 148, 53, 53, 53, 53, 53, 53, 53, @@ -2330,7 +2052,7 @@ mod tests { .unwrap(); let tx_params = from_raw_transaction_to_transaction_parameters(tx, chain); let sign = subject - .web3() + .web3 .accounts() .sign_transaction(tx_params, &secret) .wait() @@ -2356,50 +2078,6 @@ mod tests { } } - #[test] - fn blockchain_interface_web3_can_fetch_nonce() { - let prepare_params_arc = Arc::new(Mutex::new(vec![])); - let send_params_arc = Arc::new(Mutex::new(vec![])); - let transport = TestTransport::default() - .prepare_params(&prepare_params_arc) - .send_params(&send_params_arc) - .send_result(json!( - "0x0000000000000000000000000000000000000000000000000000000000000001" - )); - let subject = BlockchainInterfaceWeb3::new( - transport.clone(), - make_fake_event_loop_handle(), - TEST_DEFAULT_CHAIN, - ); - - let result = subject.get_transaction_count(&make_paying_wallet(b"gdasgsa")); - - assert_eq!(result, Ok(U256::from(1))); - let mut prepare_params = prepare_params_arc.lock().unwrap(); - let (method_name, actual_arguments) = prepare_params.remove(0); - assert!(prepare_params.is_empty()); - let actual_arguments: Vec = actual_arguments - .into_iter() - .map(|arg| serde_json::to_string(&arg).unwrap()) - .collect(); - assert_eq!(method_name, "eth_getTransactionCount".to_string()); - assert_eq!( - actual_arguments, - vec![ - String::from(r#""0x5c361ba8d82fcf0e5538b2a823e9d457a2296725""#), - String::from(r#""pending""#), - ] - ); - let send_params = send_params_arc.lock().unwrap(); - let rpc_call_params = vec![ - Value::String(String::from("0x5c361ba8d82fcf0e5538b2a823e9d457a2296725")), - Value::String(String::from("pending")), - ]; - let expected_request = - web3::helpers::build_request(1, "eth_getTransactionCount", rpc_call_params); - assert_eq!(*send_params, vec![(1, expected_request)]) - } - #[test] fn blockchain_interface_web3_can_fetch_transaction_receipt() { let port = find_free_port(); @@ -2412,8 +2090,8 @@ mod tests { REQUESTS_IN_PARALLEL, ) .unwrap(); - let subject = - BlockchainInterfaceWeb3::new(transport, event_loop_handle, TEST_DEFAULT_CHAIN); + let chain = TEST_DEFAULT_CHAIN; + let subject = BlockchainInterfaceWeb3::new(transport, event_loop_handle, chain); let tx_hash = H256::from_str("a128f9ca1e705cc20a936a24a7fa1df73bad6e0aaf58e8e6ffcc154a7cff6e0e") .unwrap(); @@ -2444,8 +2122,8 @@ mod tests { REQUESTS_IN_PARALLEL, ) .unwrap(); - let subject = - BlockchainInterfaceWeb3::new(transport, event_loop_handle, TEST_DEFAULT_CHAIN); + let chain = TEST_DEFAULT_CHAIN; + let subject = BlockchainInterfaceWeb3::new(transport, event_loop_handle, chain); let tx_hash = make_tx_hash(4564546); let actual_error = subject.get_transaction_receipt(tx_hash).unwrap_err(); @@ -2464,119 +2142,6 @@ mod tests { ); } - #[test] - fn to_wei_converts_units_properly_for_max_value() { - let converted_wei = to_wei(u64::MAX); - - assert_eq!( - converted_wei, - U256::from_dec_str(format!("{}000000000", u64::MAX).as_str()).unwrap() - ); - } - - #[test] - fn to_wei_converts_units_properly_for_one() { - let converted_wei = to_wei(1); - - assert_eq!(converted_wei, U256::from_dec_str("1000000000").unwrap()); - } - - #[test] - fn constant_gwei_matches_calculated_value() { - let gwei = U256([1_000_000_000u64, 0, 0, 0]); - assert_eq!(gwei, GWEI); - } - - #[test] - fn hash_the_smartcontract_transfer_function_signature() { - assert_eq!( - TRANSFER_METHOD_ID, - "transfer(address,uint256)".keccak256()[0..4] - ); - } - - #[test] - fn blockchain_error_implements_display() { - let original_errors = [ - BlockchainError::InvalidUrl, - BlockchainError::InvalidAddress, - BlockchainError::InvalidResponse, - BlockchainError::QueryFailed( - "Don't query so often, it gives me a headache".to_string(), - ), - BlockchainError::UninitializedBlockchainInterface, - ]; - - let actual_error_msgs = original_errors - .iter() - .map(|err| err.to_string()) - .collect::>(); - - assert_eq!( - original_errors.len(), - BlockchainError::VARIANT_COUNT, - "you forgot to add all variants in this test" - ); - assert_eq!( - actual_error_msgs, - slice_of_strs_to_vec_of_strings(&[ - "Blockchain error: Invalid url", - "Blockchain error: Invalid address", - "Blockchain error: Invalid response", - "Blockchain error: Query failed: Don't query so often, it gives me a headache", - &format!("Blockchain error: {}", BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED) - ]) - ); - } - - #[test] - fn payable_payment_error_implements_display() { - let original_errors = [ - PayableTransactionError::MissingConsumingWallet, - PayableTransactionError::GasPriceQueryFailed( - "Gas halves shut, no drop left".to_string(), - ), - PayableTransactionError::TransactionCount(BlockchainError::InvalidResponse), - PayableTransactionError::UnusableWallet( - "This is a LEATHER wallet, not LEDGER wallet, stupid.".to_string(), - ), - PayableTransactionError::Signing( - "You cannot sign with just three crosses here, clever boy".to_string(), - ), - PayableTransactionError::Sending { - msg: "Sending to cosmos belongs elsewhere".to_string(), - hashes: vec![make_tx_hash(0x6f), make_tx_hash(0xde)], - }, - PayableTransactionError::UninitializedBlockchainInterface, - ]; - - let actual_error_msgs = original_errors - .iter() - .map(|err| err.to_string()) - .collect::>(); - - assert_eq!( - original_errors.len(), - PayableTransactionError::VARIANT_COUNT, - "you forgot to add all variants in this test" - ); - assert_eq!( - actual_error_msgs, - slice_of_strs_to_vec_of_strings(&[ - "Missing consuming wallet to pay payable from", - "Unsuccessful gas price query: \"Gas halves shut, no drop left\"", - "Transaction count fetching failed for: Blockchain error: Invalid response", - "Unusable wallet for signing payable transactions: \"This is a LEATHER wallet, not \ - LEDGER wallet, stupid.\"", - "Signing phase: \"You cannot sign with just three crosses here, clever boy\"", - "Sending phase: \"Sending to cosmos belongs elsewhere\". Signed and hashed \ - transactions: 0x000000000000000000000000000000000000000000000000000000000000006f, \ - 0x00000000000000000000000000000000000000000000000000000000000000de", - BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED - ]) - ) - } - #[test] fn advance_used_nonce() { let initial_nonce = U256::from(55); @@ -2608,7 +2173,7 @@ mod tests { ]; let responses = vec![ Ok(Value::String(String::from("blah"))), - Err(web3::Error::Rpc(Error { + Err(web3::Error::Rpc(RPCError { code: ErrorCode::ParseError, message: "I guess we've got a problem".to_string(), data: None, @@ -2624,12 +2189,12 @@ mod tests { assert_eq!( result, vec![ - Correct(PendingPayable { + Ok(PendingPayable { recipient_wallet: make_wallet("4567"), hash: make_tx_hash(444) }), - Failed(RpcPayableFailure { - rpc_error: web3::Error::Rpc(Error { + Err(RpcPayablesFailure { + rpc_error: web3::Error::Rpc(RPCError { code: ErrorCode::ParseError, message: "I guess we've got a problem".to_string(), data: None, @@ -2641,100 +2206,24 @@ mod tests { ) } - #[test] - fn blockchain_interface_null_error_is_implemented_for_blockchain_error() { - assert_eq!( - BlockchainError::error(), - BlockchainError::UninitializedBlockchainInterface - ) - } - - #[test] - fn blockchain_interface_null_error_is_implemented_for_payable_transaction_error() { - assert_eq!( - PayableTransactionError::error(), - PayableTransactionError::UninitializedBlockchainInterface + fn make_initialized_agent( + gas_price_gwei: u64, + consuming_wallet: Wallet, + nonce: U256, + ) -> Box { + Box::new( + BlockchainAgentMock::default() + .consuming_wallet_result(consuming_wallet) + .agreed_fee_per_computation_unit_result(gas_price_gwei) + .pending_transaction_id_result(nonce), ) } #[test] - fn non_clandestine_can_fetch_latest_block_number_successfully() { - let prepare_params_arc = Arc::new(Mutex::new(vec![])); - let transport = TestTransport::default() - .prepare_params(&prepare_params_arc) - .send_result(json!("0x1e37066")); - let subject = BlockchainInterfaceWeb3::new( - transport, - make_fake_event_loop_handle(), - TEST_DEFAULT_CHAIN, - ); - - let latest_block_number = subject.get_block_number().unwrap(); - - assert_eq!(latest_block_number, U64::from(0x1e37066u64)); - - let mut prepare_params = prepare_params_arc.lock().unwrap(); - let (method_name, actual_arguments) = prepare_params.remove(0); - assert!(prepare_params.is_empty()); - assert_eq!(method_name, "eth_blockNumber".to_string()); - let expected_arguments: Vec = vec![]; - assert_eq!(actual_arguments, expected_arguments); - } - - #[test] - fn non_clandestine_can_handle_latest_null_block_number_error() { - let prepare_params_arc = Arc::new(Mutex::new(vec![])); - let transport = TestTransport::default() - .prepare_params(&prepare_params_arc) - .send_result(Value::Null); - let subject = BlockchainInterfaceWeb3::new( - transport, - make_fake_event_loop_handle(), - TEST_DEFAULT_CHAIN, - ); - - let expected_error = subject.get_block_number().unwrap_err(); - assert_eq!( - expected_error, - BlockchainError::QueryFailed("Decoder error: Error(\"invalid type: null, expected a 0x-prefixed hex string with length between (0; 16]\", line: 0, column: 0)".to_string()) - ); - - let mut prepare_params = prepare_params_arc.lock().unwrap(); - let (method_name, actual_arguments) = prepare_params.remove(0); - assert!(prepare_params.is_empty()); - assert_eq!(method_name, "eth_blockNumber".to_string()); - let expected_arguments: Vec = vec![]; - assert_eq!(actual_arguments, expected_arguments); - } - - #[test] - fn non_clandestine_can_handle_latest_string_block_number_error() { - let prepare_params_arc: Arc)>>> = - Arc::new(Mutex::new(vec![])); - let transport = TestTransport::default() - .prepare_params(&prepare_params_arc) - .send_result(Value::String("this is an invalid block number".to_string())); - - let subject = BlockchainInterfaceWeb3::new( - transport.clone(), - make_fake_event_loop_handle(), - TEST_DEFAULT_CHAIN, - ); - - let expected_error = subject.get_block_number().unwrap_err(); - + fn hash_the_smart_contract_transfer_function_signature() { assert_eq!( - expected_error, - BlockchainError::QueryFailed( - "Decoder error: Error(\"0x prefix is missing\", line: 0, column: 0)".to_string() - ) + "transfer(address,uint256)".keccak256()[0..4], + TRANSFER_METHOD_ID, ); - - let mut prepare_params = prepare_params_arc.lock().unwrap(); - let (method_name, actual_arguments) = prepare_params.remove(0); - assert!(prepare_params.is_empty()); - assert_eq!(method_name, "eth_blockNumber".to_string()); - let expected_arguments: Vec = vec![]; - assert_eq!(actual_arguments, expected_arguments); } } diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/test_utils.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/test_utils.rs new file mode 100644 index 000000000..55f9fbce7 --- /dev/null +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/test_utils.rs @@ -0,0 +1,172 @@ +// Copyright (c) 2023, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +#![cfg(test)] + +use crate::blockchain::blockchain_bridge::PendingPayableFingerprintSeeds; +use crate::blockchain::blockchain_interface::blockchain_interface_web3::batch_payable_tools::BatchPayableTools; +use actix::Recipient; +use jsonrpc_core as rpc; +use std::cell::RefCell; +use std::sync::{Arc, Mutex}; +use std::time::SystemTime; +use web3::transports::Batch; +use web3::types::{Bytes, SignedTransaction, TransactionParameters, H256}; +use web3::{BatchTransport, Error as Web3Error, Web3}; + +#[derive(Default)] +pub struct BatchPayableToolsMock { + sign_transaction_params: Arc< + Mutex< + Vec<( + TransactionParameters, + Web3>, + secp256k1secrets::key::SecretKey, + )>, + >, + >, + sign_transaction_results: RefCell>>, + append_transaction_to_batch_params: Arc>)>>>, + //append_transaction_to_batch returns just the unit type + //batch_wide_timestamp doesn't have params + batch_wide_timestamp_results: RefCell>, + send_new_payable_fingerprints_seeds_params: Arc< + Mutex< + Vec<( + SystemTime, + Recipient, + Vec<(H256, u128)>, + )>, + >, + >, + //new_payable_fingerprints returns just the unit type + submit_batch_params: Arc>>>>, + submit_batch_results: + RefCell>, Web3Error>>>, +} + +impl BatchPayableTools for BatchPayableToolsMock { + fn sign_transaction( + &self, + transaction_params: TransactionParameters, + web3: &Web3>, + key: &secp256k1secrets::key::SecretKey, + ) -> Result { + self.sign_transaction_params.lock().unwrap().push(( + transaction_params.clone(), + web3.clone(), + key.clone(), + )); + self.sign_transaction_results.borrow_mut().remove(0) + } + + fn append_transaction_to_batch(&self, signed_transaction: Bytes, web3: &Web3>) { + self.append_transaction_to_batch_params + .lock() + .unwrap() + .push((signed_transaction, web3.clone())); + } + + fn batch_wide_timestamp(&self) -> SystemTime { + self.batch_wide_timestamp_results.borrow_mut().remove(0) + } + + fn send_new_payable_fingerprints_seeds( + &self, + batch_wide_timestamp: SystemTime, + pp_fingerprint_sub: &Recipient, + hashes_and_balances: &[(H256, u128)], + ) { + self.send_new_payable_fingerprints_seeds_params + .lock() + .unwrap() + .push(( + batch_wide_timestamp, + (*pp_fingerprint_sub).clone(), + hashes_and_balances.to_vec(), + )); + } + + fn submit_batch( + &self, + web3: &Web3>, + ) -> Result>, Web3Error> { + self.submit_batch_params.lock().unwrap().push(web3.clone()); + self.submit_batch_results.borrow_mut().remove(0) + } +} + +impl BatchPayableToolsMock { + pub fn sign_transaction_params( + mut self, + params: &Arc< + Mutex< + Vec<( + TransactionParameters, + Web3>, + secp256k1secrets::key::SecretKey, + )>, + >, + >, + ) -> Self { + self.sign_transaction_params = params.clone(); + self + } + + pub fn sign_transaction_result(self, result: Result) -> Self { + self.sign_transaction_results.borrow_mut().push(result); + self + } + + pub fn batch_wide_timestamp_result(self, result: SystemTime) -> Self { + self.batch_wide_timestamp_results.borrow_mut().push(result); + self + } + + pub fn send_new_payable_fingerprint_credentials_params( + mut self, + params: &Arc< + Mutex< + Vec<( + SystemTime, + Recipient, + Vec<(H256, u128)>, + )>, + >, + >, + ) -> Self { + self.send_new_payable_fingerprints_seeds_params = params.clone(); + self + } + + pub fn append_transaction_to_batch_params( + mut self, + params: &Arc>)>>>, + ) -> Self { + self.append_transaction_to_batch_params = params.clone(); + self + } + + pub fn submit_batch_params(mut self, params: &Arc>>>>) -> Self { + self.submit_batch_params = params.clone(); + self + } + + pub fn submit_batch_result( + self, + result: Result>, Web3Error>, + ) -> Self { + self.submit_batch_results.borrow_mut().push(result); + self + } +} + +pub fn make_default_signed_transaction() -> SignedTransaction { + SignedTransaction { + message_hash: Default::default(), + v: 0, + r: Default::default(), + s: Default::default(), + raw_transaction: Default::default(), + transaction_hash: Default::default(), + } +} diff --git a/node/src/blockchain/blockchain_interface/data_structures/errors.rs b/node/src/blockchain/blockchain_interface/data_structures/errors.rs new file mode 100644 index 000000000..e4649c744 --- /dev/null +++ b/node/src/blockchain/blockchain_interface/data_structures/errors.rs @@ -0,0 +1,262 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::accountant::comma_joined_stringifiable; +use crate::db_config::persistent_configuration::PersistentConfigError; +use crate::sub_lib::wallet::Wallet; +use itertools::Either; +use std::fmt; +use std::fmt::{Display, Formatter}; +use variant_count::VariantCount; +use web3::types::{TransactionReceipt, H256}; + +const BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED: &str = "Uninitialized blockchain interface. To avoid \ +being delinquency-banned, you should restart the Node with a value for blockchain-service-url"; + +#[derive(Clone, Debug, PartialEq, Eq, VariantCount)] +pub enum BlockchainError { + InvalidUrl, + InvalidAddress, + InvalidResponse, + QueryFailed(String), + UninitializedBlockchainInterface, +} + +impl Display for BlockchainError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let err_spec = match self { + Self::InvalidUrl => Either::Left("Invalid url"), + Self::InvalidAddress => Either::Left("Invalid address"), + Self::InvalidResponse => Either::Left("Invalid response"), + Self::QueryFailed(msg) => Either::Right(format!("Query failed: {}", msg)), + Self::UninitializedBlockchainInterface => { + Either::Left(BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED) + } + }; + write!(f, "Blockchain error: {}", err_spec) + } +} + +pub type BlockchainResult = Result; +pub type ResultForReceipt = BlockchainResult>; + +#[derive(Clone, Debug, PartialEq, Eq, VariantCount)] +pub enum PayableTransactionError { + MissingConsumingWallet, + GasPriceQueryFailed(String), + TransactionID(BlockchainError), + UnusableWallet(String), + Signing(String), + Sending { msg: String, hashes: Vec }, + UninitializedBlockchainInterface, +} + +impl Display for PayableTransactionError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::MissingConsumingWallet => { + write!(f, "Missing consuming wallet to pay payable from") + } + Self::GasPriceQueryFailed(msg) => { + write!(f, "Unsuccessful gas price query: \"{}\"", msg) + } + Self::TransactionID(blockchain_err) => { + write!(f, "Transaction id fetching failed: {}", blockchain_err) + } + Self::UnusableWallet(msg) => write!( + f, + "Unusable wallet for signing payable transactions: \"{}\"", + msg + ), + Self::Signing(msg) => write!(f, "Signing phase: \"{}\"", msg), + Self::Sending { msg, hashes } => write!( + f, + "Sending phase: \"{}\". Signed and hashed transactions: {}", + msg, + comma_joined_stringifiable(hashes, |hash| format!("{:?}", hash)) + ), + Self::UninitializedBlockchainInterface => { + write!(f, "{}", BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED) + } + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, VariantCount)] +pub enum BlockchainAgentBuildError { + GasPrice(PersistentConfigError), + TransactionFeeBalance(Wallet, BlockchainError), + ServiceFeeBalance(Wallet, BlockchainError), + TransactionID(Wallet, BlockchainError), + UninitializedBlockchainInterface, +} + +impl Display for BlockchainAgentBuildError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let preformatted_or_complete = match self { + Self::GasPrice(persistent_config_e) => Either::Left(format!( + "gas price from the database: {:?}", + persistent_config_e + )), + Self::TransactionFeeBalance(wallet, blockchain_e) => Either::Left(format!( + "transaction fee balance for our earning wallet {} due to: {}", + wallet, blockchain_e + )), + Self::ServiceFeeBalance(wallet, blockchain_e) => Either::Left(format!( + "masq balance for our earning wallet {} due to {}", + wallet, blockchain_e + )), + Self::TransactionID(wallet, blockchain_e) => Either::Left(format!( + "transaction id for our earning wallet {} due to {}", + wallet, blockchain_e + )), + Self::UninitializedBlockchainInterface => { + Either::Right(BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED.to_string()) + } + }; + + match preformatted_or_complete { + Either::Left(ending) => write!( + f, + "Blockchain agent construction failed at fetching {}", + ending + ), + Either::Right(msg) => write!(f, "{}", msg), + } + } +} + +#[cfg(test)] +mod tests { + use crate::blockchain::blockchain_interface::data_structures::errors::BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED; + use crate::blockchain::blockchain_interface::{ + BlockchainAgentBuildError, BlockchainError, PayableTransactionError, + }; + use crate::blockchain::test_utils::make_tx_hash; + use crate::db_config::persistent_configuration::PersistentConfigError; + use crate::test_utils::make_wallet; + use masq_lib::utils::{slice_of_strs_to_vec_of_strings, to_string}; + + #[test] + fn constants_have_correct_values() { + assert_eq!( + BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED, + "Uninitialized blockchain interface. To avoid being delinquency-banned, you should \ + restart the Node with a value for blockchain-service-url" + ) + } + + #[test] + fn blockchain_error_implements_display() { + let original_errors = [ + BlockchainError::InvalidUrl, + BlockchainError::InvalidAddress, + BlockchainError::InvalidResponse, + BlockchainError::QueryFailed( + "Don't query so often, it gives me a headache".to_string(), + ), + BlockchainError::UninitializedBlockchainInterface, + ]; + + let actual_error_msgs = original_errors.iter().map(to_string).collect::>(); + + assert_eq!( + original_errors.len(), + BlockchainError::VARIANT_COUNT, + "you forgot to add all variants in this test" + ); + assert_eq!( + actual_error_msgs, + slice_of_strs_to_vec_of_strings(&[ + "Blockchain error: Invalid url", + "Blockchain error: Invalid address", + "Blockchain error: Invalid response", + "Blockchain error: Query failed: Don't query so often, it gives me a headache", + &format!("Blockchain error: {}", BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED) + ]) + ); + } + + #[test] + fn payable_payment_error_implements_display() { + let original_errors = [ + PayableTransactionError::MissingConsumingWallet, + PayableTransactionError::GasPriceQueryFailed( + "Gas halves shut, no drop left".to_string(), + ), + PayableTransactionError::TransactionID(BlockchainError::InvalidResponse), + PayableTransactionError::UnusableWallet( + "This is a LEATHER wallet, not LEDGER wallet, stupid.".to_string(), + ), + PayableTransactionError::Signing( + "You cannot sign with just three crosses here, clever boy".to_string(), + ), + PayableTransactionError::Sending { + msg: "Sending to cosmos belongs elsewhere".to_string(), + hashes: vec![make_tx_hash(0x6f), make_tx_hash(0xde)], + }, + PayableTransactionError::UninitializedBlockchainInterface, + ]; + + let actual_error_msgs = original_errors.iter().map(to_string).collect::>(); + + assert_eq!( + original_errors.len(), + PayableTransactionError::VARIANT_COUNT, + "you forgot to add all variants in this test" + ); + assert_eq!( + actual_error_msgs, + slice_of_strs_to_vec_of_strings(&[ + "Missing consuming wallet to pay payable from", + "Unsuccessful gas price query: \"Gas halves shut, no drop left\"", + "Transaction id fetching failed: Blockchain error: Invalid response", + "Unusable wallet for signing payable transactions: \"This is a LEATHER wallet, not \ + LEDGER wallet, stupid.\"", + "Signing phase: \"You cannot sign with just three crosses here, clever boy\"", + "Sending phase: \"Sending to cosmos belongs elsewhere\". Signed and hashed \ + transactions: 0x000000000000000000000000000000000000000000000000000000000000006f, \ + 0x00000000000000000000000000000000000000000000000000000000000000de", + BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED + ]) + ) + } + + #[test] + fn blockchain_agent_build_error_implements_display() { + let wallet = make_wallet("abc"); + let original_errors = [ + BlockchainAgentBuildError::GasPrice(PersistentConfigError::NotPresent), + BlockchainAgentBuildError::TransactionFeeBalance( + wallet.clone(), + BlockchainError::InvalidResponse, + ), + BlockchainAgentBuildError::ServiceFeeBalance( + wallet.clone(), + BlockchainError::InvalidAddress, + ), + BlockchainAgentBuildError::TransactionID(wallet.clone(), BlockchainError::InvalidUrl), + BlockchainAgentBuildError::UninitializedBlockchainInterface, + ]; + + let actual_error_msgs = original_errors.iter().map(to_string).collect::>(); + + assert_eq!( + original_errors.len(), + BlockchainAgentBuildError::VARIANT_COUNT, + "you forgot to add all variants in this test" + ); + assert_eq!( + actual_error_msgs, + slice_of_strs_to_vec_of_strings(&[ + "Blockchain agent construction failed at fetching gas price from the database: NotPresent", + "Blockchain agent construction failed at fetching transaction fee balance for our earning \ + wallet 0x0000000000000000000000000000000000616263 due to: Blockchain error: Invalid response", + "Blockchain agent construction failed at fetching masq balance for our earning wallet \ + 0x0000000000000000000000000000000000616263 due to Blockchain error: Invalid address", + "Blockchain agent construction failed at fetching transaction id for our earning wallet \ + 0x0000000000000000000000000000000000616263 due to Blockchain error: Invalid url", + BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED + ]) + ) + } +} diff --git a/node/src/blockchain/blockchain_interface/data_structures/mod.rs b/node/src/blockchain/blockchain_interface/data_structures/mod.rs new file mode 100644 index 000000000..d1d785aae --- /dev/null +++ b/node/src/blockchain/blockchain_interface/data_structures/mod.rs @@ -0,0 +1,29 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +pub mod errors; +use crate::accountant::db_access_objects::pending_payable_dao::PendingPayable; +use crate::sub_lib::wallet::Wallet; +use web3::types::H256; +use web3::Error; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct BlockchainTransaction { + pub block_number: u64, + pub from: Wallet, + pub wei_amount: u128, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RetrievedBlockchainTransactions { + pub new_start_block: u64, + pub transactions: Vec, +} + +pub type ProcessedPayableFallible = Result; + +#[derive(Debug, PartialEq, Clone)] +pub struct RpcPayablesFailure { + pub rpc_error: Error, + pub recipient_wallet: Wallet, + pub hash: H256, +} diff --git a/node/src/blockchain/blockchain_interface/lower_level_interface.rs b/node/src/blockchain/blockchain_interface/lower_level_interface.rs new file mode 100644 index 000000000..7300a7ef8 --- /dev/null +++ b/node/src/blockchain/blockchain_interface/lower_level_interface.rs @@ -0,0 +1,21 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainResult; +use crate::sub_lib::wallet::Wallet; +use ethereum_types::U64; +use web3::types::U256; + +pub trait LowBlockchainInt { + fn get_transaction_fee_balance(&self, wallet: &Wallet) -> ResultForBalance; + + fn get_service_fee_balance(&self, wallet: &Wallet) -> ResultForBalance; + + fn get_block_number(&self) -> LatestBlockNumber; + + fn get_transaction_id(&self, wallet: &Wallet) -> ResultForNonce; +} + +pub type ResultForBalance = BlockchainResult; +pub type ResultForBothBalances = BlockchainResult<(web3::types::U256, web3::types::U256)>; +pub type ResultForNonce = BlockchainResult; +pub type LatestBlockNumber = BlockchainResult; diff --git a/node/src/blockchain/blockchain_interface/mod.rs b/node/src/blockchain/blockchain_interface/mod.rs new file mode 100644 index 000000000..250f0d2f8 --- /dev/null +++ b/node/src/blockchain/blockchain_interface/mod.rs @@ -0,0 +1,52 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +pub mod blockchain_interface_null; +pub mod blockchain_interface_web3; +pub mod data_structures; +pub mod lower_level_interface; +pub mod test_utils; + +use crate::accountant::db_access_objects::payable_dao::PayableAccount; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; +use crate::blockchain::blockchain_bridge::PendingPayableFingerprintSeeds; +use crate::blockchain::blockchain_interface::data_structures::errors::{ + BlockchainAgentBuildError, BlockchainError, PayableTransactionError, ResultForReceipt, +}; +use crate::blockchain::blockchain_interface::data_structures::{ + ProcessedPayableFallible, RetrievedBlockchainTransactions, +}; +use crate::blockchain::blockchain_interface::lower_level_interface::LowBlockchainInt; +use crate::db_config::persistent_configuration::PersistentConfiguration; +use crate::sub_lib::wallet::Wallet; +use actix::Recipient; +use web3::types::{Address, BlockNumber, H256}; + +pub trait BlockchainInterface { + fn contract_address(&self) -> Address; + + fn retrieve_transactions( + &self, + start_block: BlockNumber, + end_block: BlockNumber, + recipient: &Wallet, + ) -> Result; + + fn build_blockchain_agent( + &self, + consuming_wallet: &Wallet, + persistent_config: &dyn PersistentConfiguration, + ) -> Result, BlockchainAgentBuildError>; + + fn send_batch_of_payables( + &self, + agent: Box, + new_fingerprints_recipient: &Recipient, + accounts: &[PayableAccount], + ) -> Result, PayableTransactionError>; + + fn get_transaction_receipt(&self, hash: H256) -> ResultForReceipt; + + fn lower_interface(&self) -> &dyn LowBlockchainInt; + + as_any_in_trait!(); +} diff --git a/node/src/blockchain/blockchain_interface/test_utils.rs b/node/src/blockchain/blockchain_interface/test_utils.rs new file mode 100644 index 000000000..6817e74ab --- /dev/null +++ b/node/src/blockchain/blockchain_interface/test_utils.rs @@ -0,0 +1,131 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +#![cfg(test)] + +use crate::blockchain::blockchain_interface::lower_level_interface::{ + LatestBlockNumber, LowBlockchainInt, ResultForBalance, ResultForNonce, +}; + +use crate::blockchain::blockchain_interface::BlockchainInterface; +use crate::sub_lib::wallet::Wallet; +use crate::test_utils::http_test_server::TestServer; +use crate::test_utils::make_wallet; +use masq_lib::blockchains::chains::Chain; +use masq_lib::utils::find_free_port; +use serde_json::Value; +use std::cell::RefCell; +use std::sync::{Arc, Mutex}; + +#[derive(Default)] +pub struct LowBlockchainIntMock { + get_transaction_fee_balance_params: Arc>>, + get_transaction_fee_balance_results: RefCell>, + get_masq_balance_params: Arc>>, + get_masq_balance_results: RefCell>, + get_block_number_results: RefCell>, + get_transaction_id_params: Arc>>, + get_transaction_id_results: RefCell>, +} + +impl LowBlockchainInt for LowBlockchainIntMock { + fn get_transaction_fee_balance(&self, address: &Wallet) -> ResultForBalance { + self.get_transaction_fee_balance_params + .lock() + .unwrap() + .push(address.clone()); + self.get_transaction_fee_balance_results + .borrow_mut() + .remove(0) + } + + fn get_service_fee_balance(&self, address: &Wallet) -> ResultForBalance { + self.get_masq_balance_params + .lock() + .unwrap() + .push(address.clone()); + self.get_masq_balance_results.borrow_mut().remove(0) + } + + fn get_block_number(&self) -> LatestBlockNumber { + self.get_block_number_results.borrow_mut().remove(0) + } + + fn get_transaction_id(&self, address: &Wallet) -> ResultForNonce { + self.get_transaction_id_params + .lock() + .unwrap() + .push(address.clone()); + self.get_transaction_id_results.borrow_mut().remove(0) + } +} + +impl LowBlockchainIntMock { + pub fn get_transaction_fee_balance_params(mut self, params: &Arc>>) -> Self { + self.get_transaction_fee_balance_params = params.clone(); + self + } + + pub fn get_transaction_fee_balance_result(self, result: ResultForBalance) -> Self { + self.get_transaction_fee_balance_results + .borrow_mut() + .push(result); + self + } + + pub fn get_masq_balance_params(mut self, params: &Arc>>) -> Self { + self.get_masq_balance_params = params.clone(); + self + } + + pub fn get_masq_balance_result(self, result: ResultForBalance) -> Self { + self.get_masq_balance_results.borrow_mut().push(result); + self + } + + pub fn get_block_number_result(self, result: LatestBlockNumber) -> Self { + self.get_block_number_results.borrow_mut().push(result); + self + } + + pub fn get_transaction_id_params(mut self, params: &Arc>>) -> Self { + self.get_transaction_id_params = params.clone(); + self + } + + pub fn get_transaction_id_result(self, result: ResultForNonce) -> Self { + self.get_transaction_id_results.borrow_mut().push(result); + self + } +} + +pub fn test_blockchain_interface_is_connected_and_functioning(subject_factory: F) +where + F: Fn(u16, Chain) -> Box, +{ + let port = find_free_port(); + let test_server = TestServer::start( + port, + vec![br#"{"jsonrpc":"2.0","id":0,"result":someGarbage}"#.to_vec()], + ); + let wallet = make_wallet("123"); + let chain = Chain::PolyMainnet; + let subject = subject_factory(port, chain); + + // no assertion for the result, we anticipate an error from a badly formatted response from the server; + // yet enough to prove we have a proper connection + let _ = subject.lower_interface().get_service_fee_balance(&wallet); + + let requests = test_server.requests_so_far(); + let bodies: Vec = requests + .into_iter() + .map(|request| serde_json::from_slice(&request.body()).unwrap()) + .collect(); + assert_eq!( + bodies[0]["params"][0]["data"].to_string()[35..75], + wallet.to_string()[2..] + ); + assert_eq!( + bodies[0]["params"][0]["to"], + format!("{:?}", chain.rec().contract) + ); +} diff --git a/node/src/blockchain/blockchain_interface_initializer.rs b/node/src/blockchain/blockchain_interface_initializer.rs new file mode 100644 index 000000000..7448420a3 --- /dev/null +++ b/node/src/blockchain/blockchain_interface_initializer.rs @@ -0,0 +1,75 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::blockchain::blockchain_interface::blockchain_interface_web3::{ + BlockchainInterfaceWeb3, REQUESTS_IN_PARALLEL, +}; +use crate::blockchain::blockchain_interface::BlockchainInterface; +use masq_lib::blockchains::chains::Chain; +use web3::transports::Http; + +pub(in crate::blockchain) struct BlockchainInterfaceInitializer {} + +impl BlockchainInterfaceInitializer { + // TODO when we have multiple chains of fundamentally different architectures and are able to switch them, + // this should probably be replaced by a HashMap of distinct interfaces for each chain + pub fn initialize_interface( + &self, + blockchain_service_url: &str, + chain: Chain, + ) -> Box { + self.initialize_web3_interface(blockchain_service_url, chain) + } + + fn initialize_web3_interface( + &self, + blockchain_service_url: &str, + chain: Chain, + ) -> Box { + match Http::with_max_parallel(blockchain_service_url, REQUESTS_IN_PARALLEL) { + Ok((event_loop_handle, transport)) => Box::new(BlockchainInterfaceWeb3::new( + transport, + event_loop_handle, + chain, + )), + Err(e) => panic!( + "Invalid blockchain service URL \"{}\". Error: {:?}. Chain: {}", + blockchain_service_url, + e, + chain.rec().literal_identifier + ), + } + } +} + +#[cfg(test)] +mod tests { + use crate::blockchain::blockchain_interface_initializer::BlockchainInterfaceInitializer; + use masq_lib::blockchains::chains::Chain; + + use std::net::Ipv4Addr; + + use crate::blockchain::blockchain_interface::test_utils::test_blockchain_interface_is_connected_and_functioning; + + use masq_lib::constants::DEFAULT_CHAIN; + + #[test] + fn initialize_web3_interface_works() { + let subject_factory = |port: u16, chain: Chain| { + let subject = BlockchainInterfaceInitializer {}; + let server_url = &format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port); + subject.initialize_web3_interface(server_url, chain) + }; + + test_blockchain_interface_is_connected_and_functioning(subject_factory) + } + + #[test] + #[should_panic(expected = "Invalid blockchain service URL \"http://λ:8545\". \ + Error: Transport(\"InvalidUri(InvalidUriChar)\"). Chain: polygon-mainnet")] + fn invalid_blockchain_url_for_produces_panic_for_web3_interface() { + let blockchain_service_url = "http://λ:8545"; + let subject = BlockchainInterfaceInitializer {}; + + subject.initialize_web3_interface(blockchain_service_url, DEFAULT_CHAIN); + } +} diff --git a/node/src/blockchain/mod.rs b/node/src/blockchain/mod.rs index 7375b60e9..937380fd1 100644 --- a/node/src/blockchain/mod.rs +++ b/node/src/blockchain/mod.rs @@ -1,9 +1,10 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -pub mod batch_payable_tools; + pub mod bip32; pub mod bip39; pub mod blockchain_bridge; pub mod blockchain_interface; +pub mod blockchain_interface_initializer; pub mod payer; pub mod signature; diff --git a/node/src/blockchain/test_utils.rs b/node/src/blockchain/test_utils.rs index 9778cb686..b1a6eae30 100644 --- a/node/src/blockchain/test_utils.rs +++ b/node/src/blockchain/test_utils.rs @@ -2,33 +2,39 @@ #![cfg(test)] +use crate::accountant::db_access_objects::payable_dao::PayableAccount; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; use crate::blockchain::blockchain_bridge::PendingPayableFingerprintSeeds; -use crate::blockchain::blockchain_interface::{ - BlockchainError, BlockchainInterface, BlockchainResult, LatestBlockNumber, - PayableTransactionError, ProcessedPayableFallible, ResultForBalance, ResultForNonce, - ResultForReceipt, REQUESTS_IN_PARALLEL, +use crate::blockchain::blockchain_interface::blockchain_interface_web3::REQUESTS_IN_PARALLEL; +use crate::blockchain::blockchain_interface::data_structures::errors::{ + BlockchainAgentBuildError, BlockchainError, PayableTransactionError, ResultForReceipt, +}; +use crate::blockchain::blockchain_interface::data_structures::{ + ProcessedPayableFallible, RetrievedBlockchainTransactions, }; +use crate::blockchain::blockchain_interface::lower_level_interface::LowBlockchainInt; +use crate::blockchain::blockchain_interface::test_utils::LowBlockchainIntMock; +use crate::blockchain::blockchain_interface::BlockchainInterface; +use crate::db_config::persistent_configuration::PersistentConfiguration; +use crate::set_arbitrary_id_stamp_in_mock_impl; use crate::sub_lib::wallet::Wallet; +use crate::test_utils::unshared_test_utils::arbitrary_id_stamp::ArbitraryIdStamp; use actix::Recipient; use bip39::{Language, Mnemonic, Seed}; use ethereum_types::{BigEndianHash, H256}; use jsonrpc_core as rpc; use lazy_static::lazy_static; +use masq_lib::blockchains::chains::Chain; +use masq_lib::utils::to_string; use std::cell::RefCell; use std::collections::VecDeque; use std::fmt::Debug; use std::sync::{Arc, Mutex}; -use std::time::SystemTime; - -use crate::accountant::db_access_objects::payable_dao::PayableAccount; -use crate::blockchain::batch_payable_tools::BatchPayableTools; -use web3::transports::{Batch, EventLoopHandle, Http}; -use web3::types::{Address, BlockNumber, Bytes, SignedTransaction, TransactionParameters, U256}; -use web3::{BatchTransport, Error as Web3Error, Web3}; +use web3::transports::{EventLoopHandle, Http}; +use web3::types::{Address, BlockNumber, U256}; +use web3::{BatchTransport, Error as Web3Error}; use web3::{RequestId, Transport}; -use crate::blockchain::blockchain_interface::RetrievedBlockchainTransactions; - lazy_static! { static ref BIG_MEANINGLESS_PHRASE: Vec<&'static str> = vec![ "parent", "prevent", "vehicle", "tooth", "crazy", "cruel", "update", "mango", "female", @@ -38,10 +44,7 @@ lazy_static! { } pub fn make_meaningless_phrase_words() -> Vec { - BIG_MEANINGLESS_PHRASE - .iter() - .map(|word| word.to_string()) - .collect() + BIG_MEANINGLESS_PHRASE.iter().map(to_string).collect() } pub fn make_meaningless_phrase() -> String { @@ -58,34 +61,29 @@ pub struct BlockchainInterfaceMock { retrieve_transactions_parameters: Arc>>, retrieve_transactions_results: RefCell>>, - send_payables_within_batch_params: Arc< + build_blockchain_agent_params: Arc>>, + build_blockchain_agent_results: + RefCell, BlockchainAgentBuildError>>>, + send_batch_of_payables_params: Arc< Mutex< Vec<( - Wallet, - u64, - U256, + ArbitraryIdStamp, Recipient, Vec, )>, >, >, - send_payables_within_batch_results: + send_batch_of_payables_results: RefCell, PayableTransactionError>>>, - get_transaction_fee_balance_params: Arc>>, - get_transaction_fee_balance_results: RefCell>, - get_token_balance_params: Arc>>, - get_token_balance_results: RefCell>, get_transaction_receipt_params: Arc>>, get_transaction_receipt_results: RefCell>, - contract_address_results: RefCell>, - get_transaction_count_parameters: Arc>>, - get_transaction_count_results: RefCell>>, - get_block_number_results: RefCell>, + arbitrary_id_stamp_opt: Option, + lower_interface_result: Option>, } impl BlockchainInterface for BlockchainInterfaceMock { fn contract_address(&self) -> Address { - self.contract_address_results.borrow_mut().remove(0) + unimplemented!("not needed so far") } fn retrieve_transactions( @@ -102,57 +100,30 @@ impl BlockchainInterface for BlockchainInterfaceMock { self.retrieve_transactions_results.borrow_mut().remove(0) } - fn send_payables_within_batch( + fn build_blockchain_agent( &self, consuming_wallet: &Wallet, - gas_price: u64, - last_nonce: U256, + persistent_config: &dyn PersistentConfiguration, + ) -> Result, BlockchainAgentBuildError> { + self.build_blockchain_agent_params.lock().unwrap().push(( + consuming_wallet.clone(), + persistent_config.arbitrary_id_stamp(), + )); + self.build_blockchain_agent_results.borrow_mut().remove(0) + } + + fn send_batch_of_payables( + &self, + agent: Box, new_fingerprints_recipient: &Recipient, accounts: &[PayableAccount], ) -> Result, PayableTransactionError> { - self.send_payables_within_batch_params - .lock() - .unwrap() - .push(( - consuming_wallet.clone(), - gas_price, - last_nonce, - new_fingerprints_recipient.clone(), - accounts.to_vec(), - )); - self.send_payables_within_batch_results - .borrow_mut() - .remove(0) - } - - fn get_block_number(&self) -> LatestBlockNumber { - self.get_block_number_results.borrow_mut().remove(0) - } - - fn get_transaction_fee_balance(&self, address: &Wallet) -> ResultForBalance { - self.get_transaction_fee_balance_params - .lock() - .unwrap() - .push(address.clone()); - self.get_transaction_fee_balance_results - .borrow_mut() - .remove(0) - } - - fn get_token_balance(&self, address: &Wallet) -> ResultForBalance { - self.get_token_balance_params - .lock() - .unwrap() - .push(address.clone()); - self.get_token_balance_results.borrow_mut().remove(0) - } - - fn get_transaction_count(&self, wallet: &Wallet) -> ResultForNonce { - self.get_transaction_count_parameters - .lock() - .unwrap() - .push(wallet.clone()); - self.get_transaction_count_results.borrow_mut().remove(0) + self.send_batch_of_payables_params.lock().unwrap().push(( + agent.arbitrary_id_stamp(), + new_fingerprints_recipient.clone(), + accounts.to_vec(), + )); + self.send_batch_of_payables_results.borrow_mut().remove(0) } fn get_transaction_receipt(&self, hash: H256) -> ResultForReceipt { @@ -162,6 +133,10 @@ impl BlockchainInterface for BlockchainInterfaceMock { .push(hash); self.get_transaction_receipt_results.borrow_mut().remove(0) } + + fn lower_interface(&self) -> &dyn LowBlockchainInt { + self.lower_interface_result.as_ref().unwrap().as_ref() + } } impl BlockchainInterfaceMock { @@ -181,76 +156,50 @@ impl BlockchainInterfaceMock { self } - pub fn send_payables_within_batch_params( + pub fn build_blockchain_agent_params( + mut self, + params: &Arc>>, + ) -> Self { + self.build_blockchain_agent_params = params.clone(); + self + } + + pub fn build_blockchain_agent_result( + self, + result: Result, BlockchainAgentBuildError>, + ) -> Self { + self.build_blockchain_agent_results + .borrow_mut() + .push(result); + self + } + + pub fn send_batch_of_payables_params( mut self, params: &Arc< Mutex< Vec<( - Wallet, - u64, - U256, + ArbitraryIdStamp, Recipient, Vec, )>, >, >, ) -> Self { - self.send_payables_within_batch_params = params.clone(); + self.send_batch_of_payables_params = params.clone(); self } - pub fn send_payables_within_batch_result( + pub fn send_batch_of_payables_result( self, result: Result, PayableTransactionError>, ) -> Self { - self.send_payables_within_batch_results - .borrow_mut() - .push(result); - self - } - - pub fn get_block_number_result(self, result: LatestBlockNumber) -> Self { - self.get_block_number_results.borrow_mut().push(result); - self - } - - pub fn get_transaction_fee_balance_params(mut self, params: &Arc>>) -> Self { - self.get_transaction_fee_balance_params = params.clone(); - self - } - - pub fn get_transaction_fee_balance_result(self, result: ResultForBalance) -> Self { - self.get_transaction_fee_balance_results + self.send_batch_of_payables_results .borrow_mut() .push(result); self } - pub fn get_token_balance_params(mut self, params: &Arc>>) -> Self { - self.get_token_balance_params = params.clone(); - self - } - - pub fn get_token_balance_result(self, result: ResultForBalance) -> Self { - self.get_token_balance_results.borrow_mut().push(result); - self - } - - pub fn contract_address_result(self, address: Address) -> Self { - self.contract_address_results.borrow_mut().push(address); - self - } - - pub fn get_transaction_count_params(mut self, params: &Arc>>) -> Self { - self.get_transaction_count_parameters = params.clone(); - self - } - - pub fn get_transaction_count_result(self, result: BlockchainResult) -> Self { - self.get_transaction_count_results.borrow_mut().push(result); - self - } - pub fn get_transaction_receipt_params(mut self, params: &Arc>>) -> Self { self.get_transaction_receipt_params = params.clone(); self @@ -262,6 +211,16 @@ impl BlockchainInterfaceMock { .push(result); self } + + pub fn lower_interface_results( + mut self, + aggregated_results: Box, + ) -> Self { + self.lower_interface_result = Some(aggregated_results); + self + } + + set_arbitrary_id_stamp_in_mock_impl!(); } #[derive(Debug, Default, Clone)] @@ -364,174 +323,15 @@ pub fn make_fake_event_loop_handle() -> EventLoopHandle { .0 } -#[derive(Default)] -pub struct BatchPayableToolsFactoryMock { - make_results: RefCell>>>, -} - -impl BatchPayableToolsFactoryMock { - pub fn make_result(self, result: Box>) -> Self { - self.make_results.borrow_mut().push(result); - self - } -} - -#[derive(Default)] -pub struct BatchPayableToolsMock { - sign_transaction_params: Arc< - Mutex< - Vec<( - TransactionParameters, - Web3>, - secp256k1secrets::key::SecretKey, - )>, - >, - >, - sign_transaction_results: RefCell>>, - append_transaction_to_batch_params: Arc>)>>>, - //append_transaction_to_batch returns just the unit type - //batch_wide_timestamp doesn't have params - batch_wide_timestamp_results: RefCell>, - send_new_payable_fingerprints_seeds_params: Arc< - Mutex< - Vec<( - SystemTime, - Recipient, - Vec<(H256, u128)>, - )>, - >, - >, - //new_payable_fingerprints returns just the unit type - submit_batch_params: Arc>>>>, - submit_batch_results: - RefCell>, Web3Error>>>, -} - -impl BatchPayableTools for BatchPayableToolsMock { - fn sign_transaction( - &self, - transaction_params: TransactionParameters, - web3: &Web3>, - key: &secp256k1secrets::key::SecretKey, - ) -> Result { - self.sign_transaction_params.lock().unwrap().push(( - transaction_params.clone(), - web3.clone(), - key.clone(), - )); - self.sign_transaction_results.borrow_mut().remove(0) - } - - fn append_transaction_to_batch(&self, signed_transaction: Bytes, web3: &Web3>) { - self.append_transaction_to_batch_params - .lock() - .unwrap() - .push((signed_transaction, web3.clone())); - } - - fn batch_wide_timestamp(&self) -> SystemTime { - self.batch_wide_timestamp_results.borrow_mut().remove(0) - } - - fn send_new_payable_fingerprints_seeds( - &self, - batch_wide_timestamp: SystemTime, - pp_fingerprint_sub: &Recipient, - hashes_and_balances: &[(H256, u128)], - ) { - self.send_new_payable_fingerprints_seeds_params - .lock() - .unwrap() - .push(( - batch_wide_timestamp, - (*pp_fingerprint_sub).clone(), - hashes_and_balances.to_vec(), - )); - } - - fn submit_batch( - &self, - web3: &Web3>, - ) -> Result>, Web3Error> { - self.submit_batch_params.lock().unwrap().push(web3.clone()); - self.submit_batch_results.borrow_mut().remove(0) - } -} - -impl BatchPayableToolsMock { - pub fn sign_transaction_params( - mut self, - params: &Arc< - Mutex< - Vec<( - TransactionParameters, - Web3>, - secp256k1secrets::key::SecretKey, - )>, - >, - >, - ) -> Self { - self.sign_transaction_params = params.clone(); - self - } - pub fn sign_transaction_result(self, result: Result) -> Self { - self.sign_transaction_results.borrow_mut().push(result); - self - } - - pub fn batch_wide_timestamp_result(self, result: SystemTime) -> Self { - self.batch_wide_timestamp_results.borrow_mut().push(result); - self - } - - pub fn send_new_payable_fingerprint_credentials_params( - mut self, - params: &Arc< - Mutex< - Vec<( - SystemTime, - Recipient, - Vec<(H256, u128)>, - )>, - >, - >, - ) -> Self { - self.send_new_payable_fingerprints_seeds_params = params.clone(); - self - } - - pub fn append_transaction_to_batch_params( - mut self, - params: &Arc>)>>>, - ) -> Self { - self.append_transaction_to_batch_params = params.clone(); - self - } - - pub fn submit_batch_params(mut self, params: &Arc>>>>) -> Self { - self.submit_batch_params = params.clone(); - self - } - pub fn submit_batch_result( - self, - result: Result>, Web3Error>, - ) -> Self { - self.submit_batch_results.borrow_mut().push(result); - self - } -} - -pub fn make_default_signed_transaction() -> SignedTransaction { - SignedTransaction { - message_hash: Default::default(), - v: 0, - r: Default::default(), - s: Default::default(), - raw_transaction: Default::default(), - transaction_hash: Default::default(), - } -} - pub fn make_tx_hash(base: u32) -> H256 { H256::from_uint(&U256::from(base)) } + +pub fn all_chains() -> [Chain; 4] { + [ + Chain::EthMainnet, + Chain::PolyMainnet, + Chain::PolyMumbai, + Chain::Dev, + ] +} diff --git a/node/src/bootstrapper.rs b/node/src/bootstrapper.rs index d92a2e40a..5d40c2379 100644 --- a/node/src/bootstrapper.rs +++ b/node/src/bootstrapper.rs @@ -724,9 +724,8 @@ mod tests { use crate::discriminator::Discriminator; use crate::discriminator::UnmaskedChunk; use crate::listener_handler::{ListenerHandler, ListenerHandlerFactory}; - use crate::node_test_utils::make_stream_handler_pool_subs_from; - use crate::node_test_utils::TestLogOwner; use crate::node_test_utils::{extract_log, DirsWrapperMock, IdWrapperMock}; + use crate::node_test_utils::{make_stream_handler_pool_subs_from_recorder, TestLogOwner}; use crate::server_initializer::test_utils::LoggerInitializerWrapperMock; use crate::server_initializer::LoggerInitializerWrapper; use crate::stream_handler_pool::StreamHandlerPoolSubs; @@ -753,8 +752,8 @@ mod tests { }; use crate::test_utils::{assert_contains, rate_pack}; use crate::test_utils::{main_cryptde, make_wallet}; - use actix::Recipient; use actix::System; + use actix::{Actor, Recipient}; use crossbeam_channel::unbounded; use futures::Future; use lazy_static::lazy_static; @@ -767,7 +766,7 @@ mod tests { 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 masq_lib::utils::{find_free_port, to_string}; use std::cell::RefCell; use std::collections::HashMap; use std::io; @@ -934,9 +933,9 @@ mod tests { sudo_user: Option<&str>, ) -> EnvironmentWrapperMock { EnvironmentWrapperMock { - sudo_uid: sudo_uid.map(|s| s.to_string()), - sudo_gid: sudo_gid.map(|s| s.to_string()), - sudo_user: sudo_user.map(|s| s.to_string()), + sudo_uid: sudo_uid.map(to_string), + sudo_gid: sudo_gid.map(to_string), + sudo_user: sudo_user.map(to_string), } } } @@ -2229,7 +2228,9 @@ mod tests { StreamHandlerPoolCluster { recording: Some(recording), awaiter: Some(awaiter), - subs: make_stream_handler_pool_subs_from(Some(stream_handler_pool)), + subs: make_stream_handler_pool_subs_from_recorder( + &stream_handler_pool.start(), + ), } }; diff --git a/node/src/daemon/daemon_initializer.rs b/node/src/daemon/daemon_initializer.rs index beda18164..61519c047 100644 --- a/node/src/daemon/daemon_initializer.rs +++ b/node/src/daemon/daemon_initializer.rs @@ -20,8 +20,6 @@ use masq_lib::shared_schema::ConfiguratorError; use std::collections::HashMap; use masq_lib::utils::ExpectValue; -#[cfg(test)] -use std::any::Any; use std::path::PathBuf; use std::str::FromStr; @@ -85,7 +83,7 @@ impl DaemonInitializer for DaemonInitializerReal { self.split(system, receiver); Ok(()) } - implement_as_any!(); + as_any_in_trait_impl!(); } pub trait Rerunner { diff --git a/node/src/daemon/setup_reporter.rs b/node/src/daemon/setup_reporter.rs index f336864e9..6ad031cc6 100644 --- a/node/src/daemon/setup_reporter.rs +++ b/node/src/daemon/setup_reporter.rs @@ -37,7 +37,7 @@ use masq_lib::multi_config::{ CommandLineVcl, ConfigFileVcl, EnvironmentVcl, MultiConfig, VirtualCommandLine, }; use masq_lib::shared_schema::{shared_app, ConfiguratorError}; -use masq_lib::utils::{add_chain_specific_directory, ExpectValue}; +use masq_lib::utils::{add_chain_specific_directory, to_string, ExpectValue}; use std::collections::HashMap; use std::fmt::Display; use std::net::{IpAddr, Ipv4Addr}; @@ -783,10 +783,7 @@ impl ValueRetriever for DnsServers { if ip_addrs.iter().any(|ip_addr| ip_addr.is_loopback()) { return None; } - let dns_servers = ip_addrs - .into_iter() - .map(|ip_addr| ip_addr.to_string()) - .join(","); + let dns_servers = ip_addrs.into_iter().map(to_string).join(","); Some((dns_servers, Default)) } Err(e) => { diff --git a/node/src/database/config_dumper.rs b/node/src/database/config_dumper.rs index fb4715a94..3b2ed50c3 100644 --- a/node/src/database/config_dumper.rs +++ b/node/src/database/config_dumper.rs @@ -27,8 +27,6 @@ use masq_lib::shared_schema::ConfiguratorError; use rustc_hex::ToHex; use serde_json::json; use serde_json::{Map, Value}; -#[cfg(test)] -use std::any::Any; use std::path::{Path, PathBuf}; pub struct DumpConfigRunnerReal { @@ -52,7 +50,7 @@ impl DumpConfigRunner for DumpConfigRunnerReal { Ok(()) } - implement_as_any!(); + as_any_in_trait_impl!(); } fn write_string(streams: &mut StdStreams, json: String) { diff --git a/node/src/database/db_migrations/migrations/migration_3_to_4.rs b/node/src/database/db_migrations/migrations/migration_3_to_4.rs index 2853bb94d..e06bd50d0 100644 --- a/node/src/database/db_migrations/migrations/migration_3_to_4.rs +++ b/node/src/database/db_migrations/migrations/migration_3_to_4.rs @@ -125,7 +125,7 @@ mod tests { }; use bip39::{Language, Mnemonic, MnemonicType, Seed}; use masq_lib::test_utils::utils::ensure_node_home_directory_exists; - use masq_lib::utils::derivation_path; + use masq_lib::utils::{derivation_path, to_string}; use rand::Rng; use rusqlite::ToSql; use std::panic::{catch_unwind, AssertUnwindSafe}; @@ -294,9 +294,9 @@ mod tests { seed_encrypted_opt: Option<&str>, ) -> String { let mig_declarator = &DBMigDeclaratorMock::default(); - let consuming_path_opt = consuming_path_opt.map(|str| str.to_string()); - let example_encrypted_opt = example_encrypted_opt.map(|str| str.to_string()); - let seed_encrypted_opt = seed_encrypted_opt.map(|str| str.to_string()); + let consuming_path_opt = consuming_path_opt.map(to_string); + let example_encrypted_opt = example_encrypted_opt.map(to_string); + let seed_encrypted_opt = seed_encrypted_opt.map(to_string); let panic = catch_unwind(AssertUnwindSafe(|| { Migrate_3_to_4::maybe_exchange_seed_for_private_key( consuming_path_opt, diff --git a/node/src/database/db_migrations/migrations/migration_4_to_5.rs b/node/src/database/db_migrations/migrations/migration_4_to_5.rs index 016a49196..7b3af68bd 100644 --- a/node/src/database/db_migrations/migrations/migration_4_to_5.rs +++ b/node/src/database/db_migrations/migrations/migration_4_to_5.rs @@ -1,6 +1,6 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::db_access_objects::dao_utils::VigilantRusqliteFlatten; +use crate::accountant::db_access_objects::utils::VigilantRusqliteFlatten; use crate::database::db_migrations::db_migrator::DatabaseMigration; use crate::database::db_migrations::migrator_utils::DBMigDeclarator; @@ -76,7 +76,7 @@ impl DatabaseMigration for Migrate_4_to_5 { #[cfg(test)] mod tests { - use crate::accountant::db_access_objects::dao_utils::{from_time_t, to_time_t}; + use crate::accountant::db_access_objects::utils::{from_time_t, to_time_t}; use crate::database::connection_wrapper::{ConnectionWrapper, ConnectionWrapperReal}; use crate::database::db_initializer::{ DbInitializationConfig, DbInitializer, DbInitializerReal, ExternalData, DATABASE_FILE, diff --git a/node/src/database/db_migrations/migrations/migration_6_to_7.rs b/node/src/database/db_migrations/migrations/migration_6_to_7.rs index 08a9a4544..12f14ecfc 100644 --- a/node/src/database/db_migrations/migrations/migration_6_to_7.rs +++ b/node/src/database/db_migrations/migrations/migration_6_to_7.rs @@ -1,6 +1,6 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::db_access_objects::dao_utils::VigilantRusqliteFlatten; +use crate::accountant::db_access_objects::utils::VigilantRusqliteFlatten; use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; use crate::accountant::gwei_to_wei; use crate::database::db_migrations::db_migrator::DatabaseMigration; diff --git a/node/src/database/db_migrations/migrator_utils.rs b/node/src/database/db_migrations/migrator_utils.rs index e9bab401c..422273b06 100644 --- a/node/src/database/db_migrations/migrator_utils.rs +++ b/node/src/database/db_migrations/migrator_utils.rs @@ -5,7 +5,7 @@ use crate::database::db_initializer::ExternalData; use crate::database::db_migrations::db_migrator::{DatabaseMigration, DbMigratorReal}; use masq_lib::constants::CURRENT_SCHEMA_VERSION; use masq_lib::logger::Logger; -use masq_lib::utils::ExpectValue; +use masq_lib::utils::{to_string, ExpectValue}; use rusqlite::{params_from_iter, Error, ToSql, Transaction}; use std::fmt::{Display, Formatter}; @@ -71,7 +71,7 @@ impl<'a> DBMigrationUtilities for DBMigrationUtilitiesReal<'a> { .take() .expectv("owned root transaction") .commit() - .map_err(|e| e.to_string()) + .map_err(to_string) } fn make_mig_declarator<'b>( diff --git a/node/src/database/db_migrations/test_utils.rs b/node/src/database/db_migrations/test_utils.rs index d85fbc41b..11c68c5a7 100644 --- a/node/src/database/db_migrations/test_utils.rs +++ b/node/src/database/db_migrations/test_utils.rs @@ -4,6 +4,7 @@ use crate::database::db_initializer::ExternalData; use crate::database::db_migrations::migrator_utils::{DBMigDeclarator, StatementObject}; use masq_lib::logger::Logger; +use masq_lib::utils::to_string; use rusqlite::Transaction; use std::cell::RefCell; use std::sync::{Arc, Mutex}; @@ -53,7 +54,7 @@ impl DBMigDeclarator for DBMigDeclaratorMock { self.execute_upon_transaction_params.lock().unwrap().push( sql_statements .iter() - .map(|stm_obj| stm_obj.to_string()) + .map(to_string) .collect::>(), ); self.execute_upon_transaction_results.borrow_mut().remove(0) diff --git a/node/src/db_config/config_dao.rs b/node/src/db_config/config_dao.rs index 2e96d350d..7d386f151 100644 --- a/node/src/db_config/config_dao.rs +++ b/node/src/db_config/config_dao.rs @@ -1,6 +1,7 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::db_access_objects::dao_utils::DaoFactoryReal; +use crate::accountant::db_access_objects::utils::DaoFactoryReal; use crate::database::connection_wrapper::ConnectionWrapper; +use masq_lib::utils::to_string; use rusqlite::types::ToSql; use rusqlite::{Row, Rows, Statement}; @@ -22,7 +23,7 @@ impl ConfigDaoRecord { pub fn new(name: &str, value: Option<&str>, encrypted: bool) -> Self { Self { name: name.to_string(), - value_opt: value.map(|x| x.to_string()), + value_opt: value.map(to_string), encrypted, } } diff --git a/node/src/db_config/mocks.rs b/node/src/db_config/mocks.rs index 60b092c7d..a3083807c 100644 --- a/node/src/db_config/mocks.rs +++ b/node/src/db_config/mocks.rs @@ -1,6 +1,7 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::db_config::config_dao::{ConfigDao, ConfigDaoError, ConfigDaoRecord}; +use masq_lib::utils::to_string; use std::cell::RefCell; use std::sync::{Arc, Mutex}; @@ -27,7 +28,7 @@ impl ConfigDao for ConfigDaoMock { self.set_params .lock() .unwrap() - .push((name.to_string(), value.map(|x| x.to_string()))); + .push((name.to_string(), value.map(to_string))); self.set_results.borrow_mut().remove(0) } } diff --git a/node/src/db_config/persistent_configuration.rs b/node/src/db_config/persistent_configuration.rs index b16e280c4..8842b896a 100644 --- a/node/src/db_config/persistent_configuration.rs +++ b/node/src/db_config/persistent_configuration.rs @@ -1,6 +1,5 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -#[cfg(test)] use crate::arbitrary_id_stamp_in_trait; use crate::blockchain::bip32::Bip32EncryptionKeyProvider; use crate::blockchain::bip39::{Bip39, Bip39Error}; @@ -15,12 +14,10 @@ use crate::sub_lib::accountant::{PaymentThresholds, ScanIntervals}; use crate::sub_lib::cryptde::PlainData; use crate::sub_lib::neighborhood::{Hops, NodeDescriptor, RatePack}; use crate::sub_lib::wallet::Wallet; -#[cfg(test)] -use crate::test_utils::unshared_test_utils::arbitrary_id_stamp::ArbitraryIdStamp; 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 masq_lib::utils::{to_string, AutomapProtocol}; use rustc_hex::{FromHex, ToHex}; use std::fmt::Display; use std::net::{Ipv4Addr, SocketAddrV4, TcpListener}; @@ -151,7 +148,6 @@ pub trait PersistentConfiguration { fn scan_intervals(&self) -> Result; fn set_scan_intervals(&mut self, intervals: String) -> Result<(), PersistentConfigError>; - #[cfg(test)] arbitrary_id_stamp_in_trait!(); } @@ -336,9 +332,7 @@ impl PersistentConfiguration for PersistentConfigurationReal { &mut self, value: Option, ) -> Result<(), PersistentConfigError> { - Ok(self - .dao - .set("mapping_protocol", value.map(|v| v.to_string()))?) + Ok(self.dao.set("mapping_protocol", value.map(to_string))?) } fn min_hops(&self) -> Result { diff --git a/node/src/dispatcher.rs b/node/src/dispatcher.rs index 583677035..415f12612 100644 --- a/node/src/dispatcher.rs +++ b/node/src/dispatcher.rs @@ -211,7 +211,7 @@ mod tests { use super::*; use crate::actor_system_factory::{ActorFactory, ActorFactoryReal}; use crate::bootstrapper::BootstrapperConfig; - use crate::node_test_utils::make_stream_handler_pool_subs_from; + use crate::node_test_utils::make_stream_handler_pool_subs_from_recorder; use crate::stream_messages::NonClandestineAttributes; use crate::sub_lib::cryptde::CryptDE; use crate::sub_lib::dispatcher::Endpoint; @@ -426,7 +426,7 @@ mod tests { let mut peer_actors = peer_actors_builder().build(); peer_actors.dispatcher = Dispatcher::make_subs_from(&subject_addr); let stream_handler_pool_subs = - make_stream_handler_pool_subs_from(Some(stream_handler_pool)); + make_stream_handler_pool_subs_from_recorder(&stream_handler_pool.start()); subject_addr .try_send(PoolBindMessage { dispatcher_subs: peer_actors.dispatcher.clone(), @@ -440,14 +440,11 @@ mod tests { System::current().stop_with_code(0); system.run(); - awaiter.await_message_count(1); let recording = recording_arc.lock().unwrap(); - let message = recording.get_record::(0); let actual_endpoint = message.endpoint.clone(); let actual_data = message.data.clone(); - assert_eq!(actual_endpoint, Endpoint::Socket(socket_addr)); assert_eq!(actual_data, data); assert_eq!(recording.len(), 1); diff --git a/node/src/neighborhood/mod.rs b/node/src/neighborhood/mod.rs index 5f06c90df..da08ddedf 100644 --- a/node/src/neighborhood/mod.rs +++ b/node/src/neighborhood/mod.rs @@ -45,11 +45,11 @@ use crate::sub_lib::cryptde::{CryptDE, CryptData, PlainData}; use crate::sub_lib::dispatcher::{Component, StreamShutdownMsg}; use crate::sub_lib::hopper::{ExpiredCoresPackage, NoLookupIncipientCoresPackage}; use crate::sub_lib::hopper::{IncipientCoresPackage, MessageType}; -use crate::sub_lib::neighborhood::RouteQueryMessage; use crate::sub_lib::neighborhood::RouteQueryResponse; +use crate::sub_lib::neighborhood::UpdateNodeRecordMetadataMessage; use crate::sub_lib::neighborhood::{AskAboutDebutGossipMessage, NodeDescriptor}; use crate::sub_lib::neighborhood::{ConfigurationChange, RemoveNeighborMessage}; -use crate::sub_lib::neighborhood::{ConfigurationChangeMessage, NodeRecordMetadataMessage}; +use crate::sub_lib::neighborhood::{ConfigurationChangeMessage, RouteQueryMessage}; use crate::sub_lib::neighborhood::{ConnectionProgressEvent, ExpectedServices}; use crate::sub_lib::neighborhood::{ConnectionProgressMessage, ExpectedService}; use crate::sub_lib::neighborhood::{DispatcherNodeQueryMessage, GossipFailure_0v1}; @@ -338,10 +338,14 @@ impl Handler for Neighborhood { } } -impl Handler for Neighborhood { +impl Handler for Neighborhood { type Result = (); - fn handle(&mut self, msg: NodeRecordMetadataMessage, _ctx: &mut Self::Context) -> Self::Result { + fn handle( + &mut self, + msg: UpdateNodeRecordMetadataMessage, + _ctx: &mut Self::Context, + ) -> Self::Result { match msg.metadata_change { NRMetadataChange::AddUnreachableHost { hostname } => { let public_key = msg.public_key; @@ -491,7 +495,9 @@ impl Neighborhood { start: addr.clone().recipient::(), new_public_ip: addr.clone().recipient::(), route_query: addr.clone().recipient::(), - update_node_record_metadata: addr.clone().recipient::(), + update_node_record_metadata: addr + .clone() + .recipient::(), from_hopper: addr.clone().recipient::>(), gossip_failure: addr .clone() @@ -5552,7 +5558,7 @@ mod tests { let addr = subject.start(); let system = System::new("test"); - let _ = addr.try_send(NodeRecordMetadataMessage { + let _ = addr.try_send(UpdateNodeRecordMetadataMessage { public_key: public_key.clone(), metadata_change: NRMetadataChange::AddUnreachableHost { hostname: unreachable_host.clone(), diff --git a/node/src/node_configurator/configurator.rs b/node/src/node_configurator/configurator.rs index afa596967..80bbad03e 100644 --- a/node/src/node_configurator/configurator.rs +++ b/node/src/node_configurator/configurator.rs @@ -40,7 +40,7 @@ use masq_lib::constants::{ UNKNOWN_ERROR, UNRECOGNIZED_MNEMONIC_LANGUAGE_ERROR, UNRECOGNIZED_PARAMETER, }; use masq_lib::logger::Logger; -use masq_lib::utils::derivation_path; +use masq_lib::utils::{derivation_path, to_string}; use rustc_hex::{FromHex, ToHex}; use tiny_hderive::bip32::ExtendedPrivKey; @@ -441,11 +441,7 @@ impl Configurator { let mnemonic = Bip39::mnemonic(mnemonic_type, language); let mnemonic_passphrase = Self::make_passphrase(passphrase_opt); let seed = Bip39::seed(&mnemonic, &mnemonic_passphrase); - let phrase_words: Vec = mnemonic - .into_phrase() - .split(' ') - .map(|w| w.to_string()) - .collect(); + let phrase_words: Vec = mnemonic.into_phrase().split(' ').map(to_string).collect(); Ok((seed, phrase_words)) } @@ -569,7 +565,7 @@ impl Configurator { .to_string(); let port_mapping_protocol_opt = Self::value_not_required(persistent_config.mapping_protocol(), "portMappingProtocol")? - .map(|p| p.to_string()); + .map(to_string); let (consuming_wallet_private_key_opt, consuming_wallet_address_opt, past_neighbors) = match good_password_opt { Some(password) => { diff --git a/node/src/node_configurator/unprivileged_parse_args_configuration.rs b/node/src/node_configurator/unprivileged_parse_args_configuration.rs index d522ff782..4238bd8d5 100644 --- a/node/src/node_configurator/unprivileged_parse_args_configuration.rs +++ b/node/src/node_configurator/unprivileged_parse_args_configuration.rs @@ -20,7 +20,7 @@ use masq_lib::constants::{DEFAULT_CHAIN, MASQ_URL_PREFIX}; use masq_lib::logger::Logger; use masq_lib::multi_config::MultiConfig; use masq_lib::shared_schema::{ConfiguratorError, ParamError}; -use masq_lib::utils::{AutomapProtocol, ExpectValue}; +use masq_lib::utils::{to_string, AutomapProtocol, ExpectValue}; use rustc_hex::FromHex; use std::net::{IpAddr, Ipv4Addr}; use std::str::FromStr; @@ -343,10 +343,8 @@ fn convert_ci_configs( 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(); + let separate_configs: Vec = + joined_configs.split(',').map(to_string).collect_vec(); if separate_configs.is_empty() { Ok(None) } else { @@ -623,7 +621,7 @@ fn is_user_specified(multi_config: &MultiConfig, parameter: &str) -> bool { #[cfg(test)] mod tests { use super::*; - use crate::accountant::db_access_objects::dao_utils::ThresholdUtils; + use crate::accountant::db_access_objects::utils::ThresholdUtils; use crate::apps::app_node; use crate::blockchain::bip32::Bip32EncryptionKeyProvider; use crate::database::db_initializer::DbInitializationConfig; @@ -2634,8 +2632,7 @@ mod tests { rate_pack_opt: Option, min_hops_opt: Option, ) -> PersistentConfigurationMock { - let consuming_wallet_private_key_opt = - consuming_wallet_private_key_opt.map(|x| x.to_string()); + let consuming_wallet_private_key_opt = consuming_wallet_private_key_opt.map(to_string); let earning_wallet_opt = match earning_wallet_address_opt { None => None, Some(address) => Some(Wallet::from_str(address).unwrap()), @@ -2654,9 +2651,7 @@ mod tests { let min_hops = min_hops_opt.unwrap_or(MIN_HOPS_FOR_TEST); 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_address_result(Ok(earning_wallet_address_opt.map(to_string))) .earning_wallet_result(Ok(earning_wallet_opt)) .gas_price_result(Ok(gas_price)) .past_neighbors_result(past_neighbors_result) diff --git a/node/src/node_test_utils.rs b/node/src/node_test_utils.rs index d5fbf01b0..f4c1a8d29 100644 --- a/node/src/node_test_utils.rs +++ b/node/src/node_test_utils.rs @@ -289,17 +289,6 @@ pub fn start_recorder_refcell_opt(recorder: &RefCell>) -> Addr< recorder.borrow_mut().take().unwrap().start() } -pub fn make_stream_handler_pool_subs_from( - stream_handler_pool_opt: Option, -) -> StreamHandlerPoolSubs { - let recorder = match stream_handler_pool_opt { - Some(recorder) => recorder, - None => Recorder::new(), - }; - let addr = recorder.start(); - make_stream_handler_pool_subs_from_recorder(&addr) -} - pub fn make_stream_handler_pool_subs_from_recorder(addr: &Addr) -> StreamHandlerPoolSubs { StreamHandlerPoolSubs { add_sub: recipient!(addr, AddStreamMsg), diff --git a/node/src/proxy_server/mod.rs b/node/src/proxy_server/mod.rs index 209548f6d..2eebd12ca 100644 --- a/node/src/proxy_server/mod.rs +++ b/node/src/proxy_server/mod.rs @@ -24,7 +24,7 @@ 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::RouteQueryResponse; -use crate::sub_lib::neighborhood::{ExpectedService, NodeRecordMetadataMessage}; +use crate::sub_lib::neighborhood::{ExpectedService, UpdateNodeRecordMetadataMessage}; use crate::sub_lib::neighborhood::{ExpectedServices, RatePack}; use crate::sub_lib::neighborhood::{NRMetadataChange, RouteQueryMessage}; use crate::sub_lib::peer_actors::BindMessage; @@ -60,7 +60,7 @@ struct ProxyServerOutSubs { hopper: Recipient, accountant: Recipient, route_source: Recipient, - update_node_record_metadata: Recipient, + update_node_record_metadata: Recipient, add_return_route: Recipient, add_route: Recipient, stream_shutdown_sub: Recipient, @@ -267,7 +267,7 @@ impl ProxyServer { .as_ref() .expect("Neighborhood unbound in ProxyServer") .update_node_record_metadata - .try_send(NodeRecordMetadataMessage { + .try_send(UpdateNodeRecordMetadataMessage { public_key: exit_public_key.clone(), metadata_change: NRMetadataChange::AddUnreachableHost { hostname: server_name, @@ -1072,7 +1072,7 @@ mod tests { hopper: recipient!(addr, IncipientCoresPackage), accountant: recipient!(addr, ReportServicesConsumedMessage), route_source: recipient!(addr, RouteQueryMessage), - update_node_record_metadata: recipient!(addr, NodeRecordMetadataMessage), + update_node_record_metadata: recipient!(addr, UpdateNodeRecordMetadataMessage), add_return_route: recipient!(addr, AddReturnRouteMessage), add_route: recipient!(addr, AddRouteMessage), stream_shutdown_sub: recipient!(addr, StreamShutdownMsg), @@ -3787,10 +3787,10 @@ mod tests { System::current().stop(); system.run(); let neighborhood_recording = neighborhood_log_arc.lock().unwrap(); - let record = neighborhood_recording.get_record::(0); + let record = neighborhood_recording.get_record::(0); assert_eq!( record, - &NodeRecordMetadataMessage { + &UpdateNodeRecordMetadataMessage { public_key: exit_public_key, metadata_change: NRMetadataChange::AddUnreachableHost { hostname: "server.com".to_string() @@ -3851,7 +3851,8 @@ mod tests { System::current().stop(); system.run(); let neighborhood_recording = neighborhood_recording_arc.lock().unwrap(); - let record_opt = neighborhood_recording.get_record_opt::(0); + let record_opt = + neighborhood_recording.get_record_opt::(0); assert_eq!(record_opt, None); } diff --git a/node/src/run_modes_factories.rs b/node/src/run_modes_factories.rs index c168b195d..b3f3d3f08 100644 --- a/node/src/run_modes_factories.rs +++ b/node/src/run_modes_factories.rs @@ -15,8 +15,6 @@ use crate::server_initializer::{ use masq_lib::command::StdStreams; use masq_lib::shared_schema::ConfiguratorError; use masq_lib::utils::ExpectValue; -#[cfg(test)] -use std::any::Any; use std::cell::RefCell; pub type RunModeResult = Result<(), ConfiguratorError>; @@ -68,17 +66,17 @@ pub trait DaemonInitializerFactory { pub trait DumpConfigRunner { fn go(&self, streams: &mut StdStreams, args: &[String]) -> RunModeResult; - declare_as_any!(); + as_any_in_trait!(); } pub trait ServerInitializer: futures::Future { fn go(&mut self, streams: &mut StdStreams, args: &[String]) -> RunModeResult; - declare_as_any!(); + as_any_in_trait!(); } pub trait DaemonInitializer { fn go(&mut self, streams: &mut StdStreams, args: &[String]) -> RunModeResult; - declare_as_any!(); + as_any_in_trait!(); } impl DumpConfigRunnerFactory for DumpConfigRunnerFactoryReal { diff --git a/node/src/server_initializer.rs b/node/src/server_initializer.rs index 76a4c97d5..95ec30799 100644 --- a/node/src/server_initializer.rs +++ b/node/src/server_initializer.rs @@ -67,7 +67,7 @@ impl ServerInitializer for ServerInitializerReal { .initialize_as_unprivileged(¶ms.multi_config, streams), ) } - implement_as_any!(); + as_any_in_trait_impl!(); } impl Future for ServerInitializerReal { diff --git a/node/src/stream_messages.rs b/node/src/stream_messages.rs index 4b838ecdc..34dccbf92 100644 --- a/node/src/stream_messages.rs +++ b/node/src/stream_messages.rs @@ -77,27 +77,20 @@ impl Debug for PoolBindMessage { #[cfg(test)] mod tests { use super::*; - use crate::node_test_utils::make_stream_handler_pool_subs_from; - use crate::test_utils::recorder::peer_actors_builder; - use actix::System; - - impl PartialEq for AddStreamMsg { - fn eq(&self, _other: &Self) -> bool { - // We need to implement PartialEq so that AddStreamMsg can be received by the Recorder; - // but AddStreamMsg breaks the rules for an actor message by containing references to - // outside resources (namely, an I/O stream) and therefore cannot have a real implementation - // of PartialEq. So here we break the rules again to patch up the problems created by - // the first breach of the rules. Don't move this into the production tree; it only needs - // to be here for the Recorder, and the Recorder is only in the test tree. - intentionally_blank!() - } - } + use crate::node_test_utils::make_stream_handler_pool_subs_from_recorder; + use crate::test_utils::recorder::{ + make_dispatcher_subs_from_recorder, make_recorder, peer_actors_builder, + }; + use actix::{Actor, System}; #[test] fn pool_bind_message_is_debug() { let _system = System::new("test"); - let dispatcher_subs = peer_actors_builder().build().dispatcher; - let stream_handler_pool_subs = make_stream_handler_pool_subs_from(None); + let (dispatcher, _, _) = make_recorder(); + let dispatcher_subs = make_dispatcher_subs_from_recorder(&dispatcher.start()); + let (stream_handler_pool, _, _) = make_recorder(); + let stream_handler_pool_subs = + make_stream_handler_pool_subs_from_recorder(&stream_handler_pool.start()); let neighborhood_subs = peer_actors_builder().build().neighborhood; let subject = PoolBindMessage { dispatcher_subs, diff --git a/node/src/stream_reader.rs b/node/src/stream_reader.rs index 6f297f806..7b42aad17 100644 --- a/node/src/stream_reader.rs +++ b/node/src/stream_reader.rs @@ -213,11 +213,11 @@ mod tests { use crate::json_discriminator_factory::JsonDiscriminatorFactory; use crate::json_masquerader::JsonMasquerader; use crate::masquerader::Masquerader; - use crate::node_test_utils::{check_timestamp, make_stream_handler_pool_subs_from}; + use crate::node_test_utils::{check_timestamp, make_stream_handler_pool_subs_from_recorder}; use crate::stream_handler_pool::StreamHandlerPoolSubs; use crate::stream_messages::RemovedStreamType::NonClandestine; use crate::sub_lib::dispatcher::DispatcherSubs; - use crate::test_utils::recorder::make_dispatcher_subs_from; + use crate::test_utils::recorder::make_dispatcher_subs_from_recorder; use crate::test_utils::recorder::make_recorder; use crate::test_utils::recorder::Recorder; use crate::test_utils::recorder::Recording; @@ -238,13 +238,16 @@ mod tests { fn stream_handler_pool_stuff() -> (Arc>, StreamHandlerPoolSubs) { let (shp, _, recording) = make_recorder(); - (recording, make_stream_handler_pool_subs_from(Some(shp))) + ( + recording, + make_stream_handler_pool_subs_from_recorder(&shp.start()), + ) } fn dispatcher_stuff() -> (Arc>, DispatcherSubs) { let (dispatcher, _, recording) = make_recorder(); let addr: Addr = dispatcher.start(); - (recording, make_dispatcher_subs_from(&addr)) + (recording, make_dispatcher_subs_from_recorder(&addr)) } #[test] diff --git a/node/src/sub_lib/accountant.rs b/node/src/sub_lib/accountant.rs index 1ca2c3319..15ceb0613 100644 --- a/node/src/sub_lib/accountant.rs +++ b/node/src/sub_lib/accountant.rs @@ -3,10 +3,12 @@ use crate::accountant::db_access_objects::banned_dao::BannedDaoFactory; use crate::accountant::db_access_objects::payable_dao::PayableDaoFactory; use crate::accountant::db_access_objects::pending_payable_dao::PendingPayableDaoFactory; use crate::accountant::db_access_objects::receivable_dao::ReceivableDaoFactory; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::msgs::BlockchainAgentWithContextMessage; use crate::accountant::{ - checked_conversion, Accountant, ConsumingWalletBalancesAndQualifiedPayables, ReceivedPayments, - ReportTransactionReceipts, ScanError, SentPayables, + checked_conversion, Accountant, ReceivedPayments, ReportTransactionReceipts, ScanError, + SentPayables, }; +use crate::actor_system_factory::SubsFactory; use crate::blockchain::blockchain_bridge::PendingPayableFingerprintSeeds; use crate::sub_lib::peer_actors::{BindMessage, StartMessage}; use crate::sub_lib::wallet::Wallet; @@ -14,8 +16,6 @@ use actix::Recipient; use actix::{Addr, Message}; use lazy_static::lazy_static; use masq_lib::ui_gateway::NodeFromUiMessage; -#[cfg(test)] -use std::any::Any; use std::fmt::{Debug, Formatter}; use std::str::FromStr; use std::sync::atomic::{AtomicU32, Ordering}; @@ -76,8 +76,8 @@ pub struct DaoFactories { #[derive(PartialEq, Eq, Debug, Clone, Copy)] pub struct ScanIntervals { - pub pending_payable_scan_interval: Duration, pub payable_scan_interval: Duration, + pub pending_payable_scan_interval: Duration, pub receivable_scan_interval: Duration, } @@ -94,8 +94,7 @@ pub struct AccountantSubs { pub report_routing_service_provided: Recipient, pub report_exit_service_provided: Recipient, pub report_services_consumed: Recipient, - pub report_consuming_wallet_balances_and_qualified_payables: - Recipient, + pub report_payable_payments_setup: Recipient, pub report_inbound_payments: Recipient, pub init_pending_payable_fingerprints: Recipient, pub report_transaction_receipts: Recipient, @@ -110,13 +109,9 @@ impl Debug for AccountantSubs { } } -pub trait AccountantSubsFactory { - fn make(&self, addr: &Addr) -> AccountantSubs; -} - pub struct AccountantSubsFactoryReal {} -impl AccountantSubsFactory for AccountantSubsFactoryReal { +impl SubsFactory for AccountantSubsFactoryReal { fn make(&self, addr: &Addr) -> AccountantSubs { Accountant::make_subs_from(addr) } @@ -179,7 +174,7 @@ pub enum SignConversionError { pub trait MessageIdGenerator { fn id(&self) -> u32; - declare_as_any!(); + as_any_in_trait!(); } #[derive(Default)] @@ -189,7 +184,7 @@ impl MessageIdGenerator for MessageIdGeneratorReal { fn id(&self) -> u32 { MSG_ID_INCREMENTER.fetch_add(1, Ordering::Relaxed) } - implement_as_any!(); + as_any_in_trait_impl!(); } #[cfg(test)] @@ -197,10 +192,9 @@ mod tests { use crate::accountant::test_utils::AccountantBuilder; use crate::accountant::{checked_conversion, Accountant}; use crate::sub_lib::accountant::{ - AccountantSubsFactory, AccountantSubsFactoryReal, MessageIdGenerator, - MessageIdGeneratorReal, PaymentThresholds, ScanIntervals, DEFAULT_EARNING_WALLET, - DEFAULT_PAYMENT_THRESHOLDS, DEFAULT_SCAN_INTERVALS, MSG_ID_INCREMENTER, - TEMPORARY_CONSUMING_WALLET, + AccountantSubsFactoryReal, MessageIdGenerator, MessageIdGeneratorReal, PaymentThresholds, + ScanIntervals, SubsFactory, DEFAULT_EARNING_WALLET, DEFAULT_PAYMENT_THRESHOLDS, + DEFAULT_SCAN_INTERVALS, MSG_ID_INCREMENTER, TEMPORARY_CONSUMING_WALLET, }; use crate::sub_lib::wallet::Wallet; use crate::test_utils::recorder::{make_accountant_subs_from_recorder, Recorder}; diff --git a/node/src/sub_lib/blockchain_bridge.rs b/node/src/sub_lib/blockchain_bridge.rs index be55eb7de..ff006fdaa 100644 --- a/node/src/sub_lib/blockchain_bridge.rs +++ b/node/src/sub_lib/blockchain_bridge.rs @@ -1,6 +1,8 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::accountant::db_access_objects::payable_dao::PayableAccount; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::msgs::QualifiedPayablesMessage; use crate::accountant::{RequestTransactionReceipts, ResponseSkeleton, SkeletonOptHolder}; use crate::blockchain::blockchain_bridge::RetrieveTransactions; use crate::sub_lib::peer_actors::BindMessage; @@ -16,14 +18,16 @@ use web3::types::U256; pub struct BlockchainBridgeConfig { pub blockchain_service_url_opt: Option, pub chain: Chain, + // TODO: totally ignored during the setup of the BlockchainBridge actor! + // Use it in the body or delete this field pub gas_price: u64, } #[derive(Clone, PartialEq, Eq)] pub struct BlockchainBridgeSubs { pub bind: Recipient, - pub report_accounts_payable: Recipient, - pub request_balances_to_pay_payables: Recipient, + pub outbound_payments_instructions: Recipient, + pub qualified_payables: Recipient, pub retrieve_transactions: Recipient, pub ui_sub: Recipient, pub request_transaction_receipts: Recipient, @@ -35,47 +39,81 @@ impl Debug for BlockchainBridgeSubs { } } -#[derive(Clone, PartialEq, Eq, Debug, Message)] -pub struct RequestBalancesToPayPayables { - pub accounts: Vec, +#[derive(Message)] +pub struct OutboundPaymentsInstructions { + pub affordable_accounts: Vec, + pub agent: Box, pub response_skeleton_opt: Option, } -impl SkeletonOptHolder for RequestBalancesToPayPayables { - fn skeleton_opt(&self) -> Option { - self.response_skeleton_opt +impl OutboundPaymentsInstructions { + pub fn new( + affordable_accounts: Vec, + agent: Box, + response_skeleton_opt: Option, + ) -> Self { + Self { + affordable_accounts, + agent, + response_skeleton_opt, + } } } -#[derive(Clone, PartialEq, Eq, Debug, Message)] -pub struct ReportAccountsPayable { - pub accounts: Vec, - pub response_skeleton_opt: Option, -} - -impl SkeletonOptHolder for ReportAccountsPayable { +impl SkeletonOptHolder for OutboundPaymentsInstructions { fn skeleton_opt(&self) -> Option { self.response_skeleton_opt } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ConsumingWalletBalances { - pub gas_currency: U256, - pub masq_tokens: U256, + pub transaction_fee_balance_in_minor_units: U256, + pub masq_token_balance_in_minor_units: U256, +} + +impl ConsumingWalletBalances { + pub fn new(transaction_fee: U256, masq_token: U256) -> Self { + Self { + transaction_fee_balance_in_minor_units: transaction_fee, + masq_token_balance_in_minor_units: masq_token, + } + } } #[cfg(test)] mod tests { - use crate::test_utils::recorder::{make_blockchain_bridge_subs_from, Recorder}; + use crate::actor_system_factory::SubsFactory; + use crate::blockchain::blockchain_bridge::{BlockchainBridge, BlockchainBridgeSubsFactoryReal}; + use crate::blockchain::test_utils::BlockchainInterfaceMock; + use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMock; + use crate::test_utils::recorder::{make_blockchain_bridge_subs_from_recorder, Recorder}; use actix::Actor; #[test] fn blockchain_bridge_subs_debug() { let recorder = Recorder::new().start(); - let subject = make_blockchain_bridge_subs_from(&recorder); + let subject = make_blockchain_bridge_subs_from_recorder(&recorder); assert_eq!(format!("{:?}", subject), "BlockchainBridgeSubs"); } + + #[test] + fn blockchain_bridge_subs_factory_produces_proper_subs() { + let subject = BlockchainBridgeSubsFactoryReal {}; + let blockchain_interface = BlockchainInterfaceMock::default(); + let persistent_config = PersistentConfigurationMock::new(); + let accountant = BlockchainBridge::new( + Box::new(blockchain_interface), + Box::new(persistent_config), + false, + None, + ); + let addr = accountant.start(); + + let subs = subject.make(&addr); + + assert_eq!(subs, BlockchainBridge::make_subs_from(&addr)) + } } diff --git a/node/src/sub_lib/combined_parameters.rs b/node/src/sub_lib/combined_parameters.rs index dfe188a81..f70f60f0f 100644 --- a/node/src/sub_lib/combined_parameters.rs +++ b/node/src/sub_lib/combined_parameters.rs @@ -5,7 +5,7 @@ use crate::sub_lib::combined_parameters::CombinedParamsDataTypes::U64; use crate::sub_lib::combined_parameters::InitializationState::{Initialized, Uninitialized}; use crate::sub_lib::neighborhood::RatePack; use masq_lib::constants::COMBINED_PARAMETERS_DELIMITER; -use masq_lib::utils::ExpectValue; +use masq_lib::utils::{to_string, ExpectValue}; use paste::paste; use std::any::Any; use std::collections::HashMap; @@ -55,9 +55,9 @@ impl CombinedParamsValueRetriever { fn parse(str_value: &str) -> Result where T: std::str::FromStr, - ::Err: ToString, + ::Err: Display, { - str_value.parse::().map_err(|e| e.to_string()) + str_value.parse::().map_err(to_string) } match data_type { CombinedParamsDataTypes::U64 => { diff --git a/node/src/sub_lib/neighborhood.rs b/node/src/sub_lib/neighborhood.rs index 7a6ffbd65..984671d91 100644 --- a/node/src/sub_lib/neighborhood.rs +++ b/node/src/sub_lib/neighborhood.rs @@ -416,7 +416,7 @@ pub struct NeighborhoodSubs { pub start: Recipient, pub new_public_ip: Recipient, pub route_query: Recipient, - pub update_node_record_metadata: Recipient, + pub update_node_record_metadata: Recipient, pub from_hopper: Recipient>, pub gossip_failure: Recipient>, pub dispatcher_node_query: Recipient, @@ -542,7 +542,7 @@ pub struct AskAboutDebutGossipMessage { } #[derive(Clone, Debug, Message, PartialEq, Eq)] -pub struct NodeRecordMetadataMessage { +pub struct UpdateNodeRecordMetadataMessage { pub public_key: PublicKey, pub metadata_change: NRMetadataChange, } @@ -663,7 +663,7 @@ mod tests { start: recipient!(recorder, StartMessage), new_public_ip: recipient!(recorder, NewPublicIp), route_query: recipient!(recorder, RouteQueryMessage), - update_node_record_metadata: recipient!(recorder, NodeRecordMetadataMessage), + update_node_record_metadata: recipient!(recorder, UpdateNodeRecordMetadataMessage), from_hopper: recipient!(recorder, ExpiredCoresPackage), gossip_failure: recipient!(recorder, ExpiredCoresPackage), dispatcher_node_query: recipient!(recorder, DispatcherNodeQueryMessage), diff --git a/node/src/sub_lib/utils.rs b/node/src/sub_lib/utils.rs index cad371e09..4b493cb5d 100644 --- a/node/src/sub_lib/utils.rs +++ b/node/src/sub_lib/utils.rs @@ -9,8 +9,6 @@ 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::path::Path; @@ -151,7 +149,7 @@ where interval: Duration, ctx: &mut Context, ) -> Box; - declare_as_any!(); + as_any_in_trait!(); } #[derive(Default)] @@ -181,7 +179,7 @@ where let handle = ctx.notify_later(msg, interval); Box::new(NLSpawnHandleHolderReal::new(handle)) } - implement_as_any!(); + as_any_in_trait_impl!(); } pub trait NotifyHandle @@ -189,7 +187,7 @@ where A: Actor>, { fn notify<'a>(&'a self, msg: M, ctx: &'a mut Context); - declare_as_any!(); + as_any_in_trait!(); } impl Default for Box> @@ -236,7 +234,7 @@ where fn notify<'a>(&'a self, msg: M, ctx: &'a mut Context) { ctx.notify(msg) } - implement_as_any!(); + as_any_in_trait_impl!(); } pub fn db_connection_launch_panic(err: InitializationError, data_directory: &Path) -> ! { diff --git a/node/src/sub_lib/wallet.rs b/node/src/sub_lib/wallet.rs index bf925cc72..feb0667ec 100644 --- a/node/src/sub_lib/wallet.rs +++ b/node/src/sub_lib/wallet.rs @@ -132,6 +132,12 @@ impl Wallet { } } + pub fn null() -> Self { + Wallet { + kind: WalletKind::Uninitialized, + } + } + pub fn string_address_from_keypair(&self) -> String { format!("{:#x}", self.address()) } @@ -505,6 +511,18 @@ mod tests { assert_eq!(result, "0x28330c4b886fc83bd6e3409a9eae776c19403c2e") } + #[test] + fn null_wallet() { + let result = Wallet::null(); + + assert_eq!( + result, + Wallet { + kind: WalletKind::Uninitialized + } + ) + } + #[test] fn serialization_roundtrips_wallet_by_address_with_cbor_successfully() { let expected_wallet = make_wallet("A valid eth address!"); diff --git a/node/src/test_utils/actor_system_factory.rs b/node/src/test_utils/actor_system_factory.rs new file mode 100644 index 000000000..ba0baeccc --- /dev/null +++ b/node/src/test_utils/actor_system_factory.rs @@ -0,0 +1,16 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::accountant::db_access_objects::banned_dao::BannedCacheLoader; +use crate::database::connection_wrapper::ConnectionWrapper; +use std::sync::{Arc, Mutex}; + +#[derive(Default)] +pub struct BannedCacheLoaderMock { + pub load_params: Arc>>>, +} + +impl BannedCacheLoader for BannedCacheLoaderMock { + fn load(&self, conn: Box) { + self.load_params.lock().unwrap().push(conn); + } +} diff --git a/node/src/test_utils/database_utils.rs b/node/src/test_utils/database_utils.rs index 1fe8b94f3..ac64b0681 100644 --- a/node/src/test_utils/database_utils.rs +++ b/node/src/test_utils/database_utils.rs @@ -2,14 +2,14 @@ #![cfg(test)] -use crate::accountant::db_access_objects::dao_utils::VigilantRusqliteFlatten; +use crate::accountant::db_access_objects::utils::VigilantRusqliteFlatten; use crate::database::connection_wrapper::ConnectionWrapper; use crate::database::db_initializer::ExternalData; use crate::database::db_migrations::db_migrator::DbMigrator; use masq_lib::logger::Logger; use masq_lib::test_utils::utils::TEST_DEFAULT_CHAIN; -use masq_lib::utils::NeighborhoodModeLight; +use masq_lib::utils::{to_string, NeighborhoodModeLight}; use rusqlite::{Connection, Error}; use std::cell::RefCell; use std::env::current_dir; @@ -207,7 +207,7 @@ fn prepare_expected_vectors_of_words_including_sorting( .map(|slice_of_strs| { let mut one_line = slice_of_strs .into_iter() - .map(|word| word.to_string()) + .map(to_string) .collect::>(); one_line.sort(); one_line @@ -233,7 +233,7 @@ fn parse_sql_to_pieces(sql: &str) -> SQLLinesChoppedIntoWords { let mut vec_of_words = sql_line .split(|char: char| char.is_whitespace()) .filter(|chunk| !chunk.is_empty()) - .map(|chunk| chunk.to_string()) + .map(to_string) .collect::>(); vec_of_words.sort(); vec_of_words diff --git a/node/src/test_utils/http_test_server.rs b/node/src/test_utils/http_test_server.rs new file mode 100644 index 000000000..56e0daddf --- /dev/null +++ b/node/src/test_utils/http_test_server.rs @@ -0,0 +1,73 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +#![cfg(test)] + +use crossbeam_channel::{unbounded, Receiver}; +use simple_server::{Request, Server}; +use std::io::Write; +use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream}; +use std::ops::Add; +use std::sync::{Arc, Mutex}; +use std::thread; +use std::time::{Duration, Instant}; + +pub struct TestServer { + port: u16, + rx: Receiver>>, +} + +impl Drop for TestServer { + fn drop(&mut self) { + self.stop(); + } +} + +impl TestServer { + pub fn start(port: u16, bodies: Vec>) -> Self { + std::env::set_var("SIMPLESERVER_THREADS", "1"); + let (tx, rx) = unbounded(); + let _ = thread::spawn(move || { + let bodies_arc = Arc::new(Mutex::new(bodies)); + Server::new(move |req, mut rsp| { + if req.headers().get("X-Quit").is_some() { + panic!("Server stop requested"); + } + tx.send(req).unwrap(); + let body = bodies_arc.lock().unwrap().remove(0); + Ok(rsp.body(body)?) + }) + .listen(&Ipv4Addr::LOCALHOST.to_string(), &format!("{}", port)); + }); + let deadline = Instant::now().add(Duration::from_secs(5)); + loop { + thread::sleep(Duration::from_millis(10)); + match TcpStream::connect(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port)) { + Ok(_) => break, + Err(e) => eprintln!("No: {:?}", e), + } + if Instant::now().gt(&deadline) { + panic!("TestServer still not started after 5sec"); + } + } + TestServer { port, rx } + } + + pub fn requests_so_far(&self) -> Vec>> { + let mut requests = vec![]; + while let Ok(request) = self.rx.try_recv() { + requests.push(request); + } + return requests; + } + + fn stop(&mut self) { + let mut stream = + match TcpStream::connect(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), self.port)) { + Ok(s) => s, + Err(_) => return, + }; + stream + .write(b"DELETE /irrelevant.htm HTTP/1.1\r\nX-Quit: Yes") + .unwrap(); + } +} diff --git a/node/src/test_utils/mod.rs b/node/src/test_utils/mod.rs index 340c9b5e7..f5cad813d 100644 --- a/node/src/test_utils/mod.rs +++ b/node/src/test_utils/mod.rs @@ -2,10 +2,12 @@ #[macro_use] pub mod channel_wrapper_mocks; +pub mod actor_system_factory; pub mod automap_mocks; pub mod data_hunk; pub mod data_hunk_framer; pub mod database_utils; +pub mod http_test_server; pub mod little_tcp_server; pub mod logfile_name_guard; pub mod neighborhood_test_utils; @@ -15,6 +17,7 @@ pub mod recorder_stop_conditions; pub mod stream_connector_mock; pub mod tcp_wrapper_mocks; pub mod tokio_wrapper_mocks; + use crate::blockchain::bip32::Bip32EncryptionKeyProvider; use crate::blockchain::payer::Payer; use crate::bootstrapper::CryptDEPair; @@ -540,6 +543,14 @@ pub struct TestRawTransaction { pub data: Vec, } +#[macro_export] +macro_rules! arbitrary_id_stamp_in_trait { + () => { + #[cfg(test)] + $crate::arbitrary_id_stamp_in_trait_internal___!(); + }; +} + #[cfg(test)] pub mod unshared_test_utils { use crate::accountant::DEFAULT_PENDING_TOO_LONG_SEC; @@ -889,7 +900,7 @@ pub mod unshared_test_utils { self } - pub fn permit_to_send_out(mut self) -> Self { + pub fn capture_msg_and_let_it_fly_on(mut self) -> Self { self.send_message_out = true; self } @@ -969,6 +980,7 @@ pub mod unshared_test_utils { pub mod arbitrary_id_stamp { use super::*; + use crate::arbitrary_id_stamp_in_trait; //The issues we are to solve might look as follows: @@ -1000,27 +1012,31 @@ pub mod unshared_test_utils { #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct ArbitraryIdStamp { - id: usize, + id_opt: Option, } impl ArbitraryIdStamp { pub fn new() -> Self { + let mut access = ARBITRARY_ID_STAMP_SEQUENCER.lock().unwrap(); + access.0 += 1; ArbitraryIdStamp { - id: { - let mut access = ARBITRARY_ID_STAMP_SEQUENCER.lock().unwrap(); - access.0 += 1; - access.0 - }, + id_opt: Some(access.0), } } + + pub fn null() -> Self { + ArbitraryIdStamp { id_opt: None } + } } // To be added together with other methods in your trait + // DO NOT USE ME DIRECTLY, USE arbitrary_id_stamp_in_trait INSTEAD! #[macro_export] - macro_rules! arbitrary_id_stamp_in_trait { + macro_rules! arbitrary_id_stamp_in_trait_internal___ { () => { - #[cfg(test)] - fn arbitrary_id_stamp(&self) -> ArbitraryIdStamp { + fn arbitrary_id_stamp( + &self, + ) -> crate::test_utils::unshared_test_utils::arbitrary_id_stamp::ArbitraryIdStamp { // No necessity to implement this method for all impls, // basically you want to do that just for the mock version @@ -1043,7 +1059,23 @@ pub mod unshared_test_utils { macro_rules! arbitrary_id_stamp_in_trait_impl { () => { fn arbitrary_id_stamp(&self) -> ArbitraryIdStamp { - *self.arbitrary_id_stamp_opt.as_ref().unwrap() + match self.arbitrary_id_stamp_opt { + Some(id) => id, + // In some implementations of mocks that have methods demanding args, the best we can do in order to + // capture and examine these args in assertions is to receive the ArbitraryIdStamp of the given + // argument. + // If such strategy is once decided for, transfers of this id will have to happen in all the tests + // relying on this mock, while also calling the intended method. So even in cases where we certainly + // are not really interested in checking that id, if we ignored that, the call of this method would + // blow up because the field that stores it is likely optional, with the value defaulted to None. + // + // As prevention of confusion from putting a requirement on devs to set the id stamp even though + // they're not planning to use it, we have a null type of that stamp to be there at most cases. + // As a result, we don't risk a direct punishment (for the None value being the problem) but also + // we'll set the assertion on fire if it doesn't match the expected id in tests where we suddenly + // do care + None => ArbitraryIdStamp::null(), + } } }; } @@ -1177,6 +1209,28 @@ pub mod unshared_test_utils { } } } + + pub struct SubsFactoryTestAddrLeaker + where + A: actix::Actor, + { + pub address_leaker: Sender>, + } + + impl SubsFactoryTestAddrLeaker + where + A: actix::Actor, + { + pub fn send_leaker_msg_and_return_meaningless_subs( + &self, + addr: &Addr, + make_subs_from_recorder_fn: fn(&Addr) -> S, + ) -> S { + self.address_leaker.try_send(addr.clone()).unwrap(); + let meaningless_addr = Recorder::new().start(); + make_subs_from_recorder_fn(&meaningless_addr) + } + } } #[cfg(test)] diff --git a/node/src/test_utils/recorder.rs b/node/src/test_utils/recorder.rs index 7ec349aca..bd656db03 100644 --- a/node/src/test_utils/recorder.rs +++ b/node/src/test_utils/recorder.rs @@ -1,6 +1,9 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. #![cfg(test)] -use crate::accountant::{ConsumingWalletBalancesAndQualifiedPayables, ReportTransactionReceipts}; + +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::msgs::BlockchainAgentWithContextMessage; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::msgs::QualifiedPayablesMessage; +use crate::accountant::ReportTransactionReceipts; use crate::accountant::{ ReceivedPayments, RequestTransactionReceipts, ScanError, ScanForPayables, ScanForPendingPayables, ScanForReceivables, SentPayables, @@ -15,8 +18,8 @@ use crate::sub_lib::accountant::AccountantSubs; use crate::sub_lib::accountant::ReportExitServiceProvidedMessage; use crate::sub_lib::accountant::ReportRoutingServiceProvidedMessage; use crate::sub_lib::accountant::ReportServicesConsumedMessage; -use crate::sub_lib::blockchain_bridge::ReportAccountsPayable; -use crate::sub_lib::blockchain_bridge::{BlockchainBridgeSubs, RequestBalancesToPayPayables}; +use crate::sub_lib::blockchain_bridge::BlockchainBridgeSubs; +use crate::sub_lib::blockchain_bridge::OutboundPaymentsInstructions; use crate::sub_lib::dispatcher::InboundClientData; use crate::sub_lib::dispatcher::{DispatcherSubs, StreamShutdownMsg}; use crate::sub_lib::hopper::IncipientCoresPackage; @@ -27,10 +30,10 @@ use crate::sub_lib::neighborhood::{ConfigurationChangeMessage, ConnectionProgres use crate::sub_lib::configurator::ConfiguratorSubs; use crate::sub_lib::neighborhood::NodeQueryResponseMetadata; -use crate::sub_lib::neighborhood::NodeRecordMetadataMessage; use crate::sub_lib::neighborhood::RemoveNeighborMessage; use crate::sub_lib::neighborhood::RouteQueryMessage; use crate::sub_lib::neighborhood::RouteQueryResponse; +use crate::sub_lib::neighborhood::UpdateNodeRecordMetadataMessage; use crate::sub_lib::neighborhood::{DispatcherNodeQueryMessage, GossipFailure_0v1}; use crate::sub_lib::peer_actors::PeerActors; use crate::sub_lib::peer_actors::{BindMessage, NewPublicIp, StartMessage}; @@ -44,7 +47,9 @@ use crate::sub_lib::stream_handler_pool::DispatcherNodeQueryResponse; use crate::sub_lib::stream_handler_pool::TransmitDataMsg; use crate::sub_lib::ui_gateway::UiGatewaySubs; use crate::sub_lib::utils::MessageScheduler; -use crate::test_utils::recorder_stop_conditions::StopConditions; +use crate::test_utils::recorder_stop_conditions::{ + ForcedMatchable, PretendedMatchableWrapper, StopConditions, +}; use crate::test_utils::to_millis; use crate::test_utils::unshared_test_utils::system_killer_actor::SystemKillerActor; use actix::Addr; @@ -54,7 +59,7 @@ use actix::MessageResult; use actix::System; use actix::{Actor, Message}; use masq_lib::ui_gateway::{NodeFromUiMessage, NodeToUiMessage}; -use std::any::Any; +use std::any::{Any, TypeId}; use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; @@ -81,65 +86,90 @@ impl Actor for Recorder { type Context = Context; } -macro_rules! recorder_message_handler { - ($message_type: ty) => { +macro_rules! message_handler_common { + ($message_type: ty, $handling_fn: ident) => { impl Handler<$message_type> for Recorder { type Result = (); fn handle(&mut self, msg: $message_type, _ctx: &mut Self::Context) { - self.handle_msg(msg) + self.$handling_fn(msg) + } + } + }; +} + +macro_rules! matchable { + ($message_type: ty) => { + impl ForcedMatchable<$message_type> for $message_type { + fn correct_msg_type_id(&self) -> TypeId { + TypeId::of::<$message_type>() } } }; } -recorder_message_handler!(AddReturnRouteMessage); -recorder_message_handler!(AddRouteMessage); -recorder_message_handler!(AddStreamMsg); -recorder_message_handler!(BindMessage); -recorder_message_handler!(ConfigurationChangeMessage); -recorder_message_handler!(CrashNotification); -recorder_message_handler!(DaemonBindMessage); -recorder_message_handler!(DispatcherNodeQueryMessage); -recorder_message_handler!(DispatcherNodeQueryResponse); -recorder_message_handler!(DnsResolveFailure_0v1); -recorder_message_handler!(ExpiredCoresPackage); -recorder_message_handler!(ExpiredCoresPackage); -recorder_message_handler!(ExpiredCoresPackage); -recorder_message_handler!(ExpiredCoresPackage); -recorder_message_handler!(ExpiredCoresPackage); -recorder_message_handler!(ExpiredCoresPackage); -recorder_message_handler!(InboundClientData); -recorder_message_handler!(InboundServerData); -recorder_message_handler!(IncipientCoresPackage); -recorder_message_handler!(NewPublicIp); -recorder_message_handler!(NodeFromUiMessage); -recorder_message_handler!(NodeToUiMessage); -recorder_message_handler!(NodeRecordMetadataMessage); -recorder_message_handler!(NoLookupIncipientCoresPackage); -recorder_message_handler!(PoolBindMessage); -recorder_message_handler!(ReceivedPayments); -recorder_message_handler!(RemoveNeighborMessage); -recorder_message_handler!(RemoveStreamMsg); -recorder_message_handler!(ReportServicesConsumedMessage); -recorder_message_handler!(ReportExitServiceProvidedMessage); -recorder_message_handler!(ReportRoutingServiceProvidedMessage); -recorder_message_handler!(ScanError); -recorder_message_handler!(ConsumingWalletBalancesAndQualifiedPayables); -recorder_message_handler!(SentPayables); -recorder_message_handler!(RequestBalancesToPayPayables); -recorder_message_handler!(StartMessage); -recorder_message_handler!(StreamShutdownMsg); -recorder_message_handler!(TransmitDataMsg); -recorder_message_handler!(PendingPayableFingerprintSeeds); -recorder_message_handler!(RetrieveTransactions); -recorder_message_handler!(RequestTransactionReceipts); -recorder_message_handler!(ReportTransactionReceipts); -recorder_message_handler!(ReportAccountsPayable); -recorder_message_handler!(ScanForReceivables); -recorder_message_handler!(ScanForPayables); -recorder_message_handler!(ConnectionProgressMessage); -recorder_message_handler!(ScanForPendingPayables); +// t, m, p (type, match, predicate) represents a list of the possible system stop conditions + +macro_rules! recorder_message_handler_t_m_p { + ($message_type: ty) => { + message_handler_common!($message_type, handle_msg_t_m_p); + matchable!($message_type); + }; +} + +macro_rules! recorder_message_handler_t_p { + ($message_type: ty) => { + message_handler_common!($message_type, handle_msg_t_p); + }; +} + +recorder_message_handler_t_m_p!(AddReturnRouteMessage); +recorder_message_handler_t_m_p!(AddRouteMessage); +recorder_message_handler_t_p!(AddStreamMsg); +recorder_message_handler_t_m_p!(BindMessage); +recorder_message_handler_t_p!(BlockchainAgentWithContextMessage); +recorder_message_handler_t_m_p!(ConfigurationChangeMessage); +recorder_message_handler_t_m_p!(ConnectionProgressMessage); +recorder_message_handler_t_m_p!(CrashNotification); +recorder_message_handler_t_m_p!(DaemonBindMessage); +recorder_message_handler_t_m_p!(DispatcherNodeQueryMessage); +recorder_message_handler_t_m_p!(DispatcherNodeQueryResponse); +recorder_message_handler_t_m_p!(DnsResolveFailure_0v1); +recorder_message_handler_t_m_p!(ExpiredCoresPackage); +recorder_message_handler_t_m_p!(ExpiredCoresPackage); +recorder_message_handler_t_m_p!(ExpiredCoresPackage); +recorder_message_handler_t_m_p!(ExpiredCoresPackage); +recorder_message_handler_t_m_p!(ExpiredCoresPackage); +recorder_message_handler_t_m_p!(ExpiredCoresPackage); +recorder_message_handler_t_m_p!(InboundClientData); +recorder_message_handler_t_m_p!(InboundServerData); +recorder_message_handler_t_m_p!(IncipientCoresPackage); +recorder_message_handler_t_m_p!(NewPublicIp); +recorder_message_handler_t_m_p!(NodeFromUiMessage); +recorder_message_handler_t_m_p!(NodeToUiMessage); +recorder_message_handler_t_m_p!(NoLookupIncipientCoresPackage); +recorder_message_handler_t_p!(OutboundPaymentsInstructions); +recorder_message_handler_t_m_p!(PendingPayableFingerprintSeeds); +recorder_message_handler_t_m_p!(PoolBindMessage); +recorder_message_handler_t_m_p!(QualifiedPayablesMessage); +recorder_message_handler_t_m_p!(ReceivedPayments); +recorder_message_handler_t_m_p!(RemoveNeighborMessage); +recorder_message_handler_t_m_p!(RemoveStreamMsg); +recorder_message_handler_t_m_p!(ReportExitServiceProvidedMessage); +recorder_message_handler_t_m_p!(ReportRoutingServiceProvidedMessage); +recorder_message_handler_t_m_p!(ReportServicesConsumedMessage); +recorder_message_handler_t_m_p!(ReportTransactionReceipts); +recorder_message_handler_t_m_p!(RequestTransactionReceipts); +recorder_message_handler_t_m_p!(RetrieveTransactions); +recorder_message_handler_t_m_p!(ScanError); +recorder_message_handler_t_m_p!(ScanForPayables); +recorder_message_handler_t_m_p!(ScanForPendingPayables); +recorder_message_handler_t_m_p!(ScanForReceivables); +recorder_message_handler_t_m_p!(SentPayables); +recorder_message_handler_t_m_p!(StartMessage); +recorder_message_handler_t_m_p!(StreamShutdownMsg); +recorder_message_handler_t_m_p!(TransmitDataMsg); +recorder_message_handler_t_m_p!(UpdateNodeRecordMetadataMessage); impl Handler> for Recorder where @@ -148,7 +178,17 @@ where type Result = (); fn handle(&mut self, msg: MessageScheduler, _ctx: &mut Self::Context) { - self.handle_msg(msg) + self.handle_msg_t_m_p(msg) + } +} + +impl ForcedMatchable for MessageScheduler +where + OuterM: PartialEq + 'static, + InnerM: PartialEq + Send + Message, +{ + fn correct_msg_type_id(&self) -> TypeId { + TypeId::of::() } } @@ -229,9 +269,12 @@ impl Recorder { system_killer.start(); } - fn handle_msg(&mut self, msg: T) { + fn handle_msg_t_m_p(&mut self, msg: M) + where + M: 'static + ForcedMatchable + Send, + { let kill_system = if let Some(stop_conditions) = &mut self.stop_conditions_opt { - stop_conditions.resolve_stop_conditions::(&msg) + stop_conditions.resolve_stop_conditions::(&msg) } else { false }; @@ -242,6 +285,14 @@ impl Recorder { System::current().stop() } } + + //for messages that cannot implement PartialEq + fn handle_msg_t_p(&mut self, msg: M) + where + M: 'static + Send, + { + self.handle_msg_t_m_p(PretendedMatchableWrapper(msg)) + } } impl Recording { @@ -277,7 +328,7 @@ impl Recording { self.get_record_inner_body(index).ok() } - fn get_record_inner_body(&self, index: usize) -> Result<&T, String> { + fn get_record_inner_body(&self, index: usize) -> Result<&T, String> { let item_box = match self.messages.get(index) { Some(item_box) => item_box, None => { @@ -288,14 +339,20 @@ impl Recording { )) } }; - let item_opt = item_box.downcast_ref::(); - - match item_opt { + match item_box.downcast_ref::() { Some(item) => Ok(item), - None => Err(format!( - "Message {:?} could not be downcast to the expected type", - item_box - )), + None => { + // double-checking for an uncommon, yet possible other type of an actor message, which doesn't implement PartialEq + let item_opt = item_box.downcast_ref::>(); + + match item_opt { + Some(item) => Ok(&item.0), + None => Err(format!( + "Message {:?} could not be downcast to the expected type", + item_box + )), + } + } } } } @@ -333,7 +390,7 @@ pub fn make_recorder() -> (Recorder, RecordAwaiter, Arc>) { (recorder, awaiter, recording) } -pub fn make_proxy_server_subs_from(addr: &Addr) -> ProxyServerSubs { +pub fn make_proxy_server_subs_from_recorder(addr: &Addr) -> ProxyServerSubs { ProxyServerSubs { bind: recipient!(addr, BindMessage), from_dispatcher: recipient!(addr, InboundClientData), @@ -346,7 +403,7 @@ pub fn make_proxy_server_subs_from(addr: &Addr) -> ProxyServerSubs { } } -pub fn make_dispatcher_subs_from(addr: &Addr) -> DispatcherSubs { +pub fn make_dispatcher_subs_from_recorder(addr: &Addr) -> DispatcherSubs { DispatcherSubs { ibcd_sub: recipient!(addr, InboundClientData), bind: recipient!(addr, BindMessage), @@ -357,7 +414,7 @@ pub fn make_dispatcher_subs_from(addr: &Addr) -> DispatcherSubs { } } -pub fn make_hopper_subs_from(addr: &Addr) -> HopperSubs { +pub fn make_hopper_subs_from_recorder(addr: &Addr) -> HopperSubs { HopperSubs { bind: recipient!(addr, BindMessage), from_hopper_client: recipient!(addr, IncipientCoresPackage), @@ -367,7 +424,7 @@ pub fn make_hopper_subs_from(addr: &Addr) -> HopperSubs { } } -pub fn make_proxy_client_subs_from(addr: &Addr) -> ProxyClientSubs { +pub fn make_proxy_client_subs_from_recorder(addr: &Addr) -> ProxyClientSubs { ProxyClientSubs { bind: recipient!(addr, BindMessage), from_hopper: recipient!(addr, ExpiredCoresPackage), @@ -377,13 +434,13 @@ pub fn make_proxy_client_subs_from(addr: &Addr) -> ProxyClientSubs { } } -pub fn make_neighborhood_subs_from(addr: &Addr) -> NeighborhoodSubs { +pub fn make_neighborhood_subs_from_recorder(addr: &Addr) -> NeighborhoodSubs { NeighborhoodSubs { bind: recipient!(addr, BindMessage), start: recipient!(addr, StartMessage), new_public_ip: recipient!(addr, NewPublicIp), route_query: recipient!(addr, RouteQueryMessage), - update_node_record_metadata: recipient!(addr, NodeRecordMetadataMessage), + update_node_record_metadata: recipient!(addr, UpdateNodeRecordMetadataMessage), from_hopper: recipient!(addr, ExpiredCoresPackage), gossip_failure: recipient!(addr, ExpiredCoresPackage), dispatcher_node_query: recipient!(addr, DispatcherNodeQueryMessage), @@ -402,10 +459,7 @@ pub fn make_accountant_subs_from_recorder(addr: &Addr) -> AccountantSu report_routing_service_provided: recipient!(addr, ReportRoutingServiceProvidedMessage), report_exit_service_provided: recipient!(addr, ReportExitServiceProvidedMessage), report_services_consumed: recipient!(addr, ReportServicesConsumedMessage), - report_consuming_wallet_balances_and_qualified_payables: recipient!( - addr, - ConsumingWalletBalancesAndQualifiedPayables - ), + report_payable_payments_setup: recipient!(addr, BlockchainAgentWithContextMessage), report_inbound_payments: recipient!(addr, ReceivedPayments), init_pending_payable_fingerprints: recipient!(addr, PendingPayableFingerprintSeeds), report_transaction_receipts: recipient!(addr, ReportTransactionReceipts), @@ -423,18 +477,18 @@ pub fn make_ui_gateway_subs_from_recorder(addr: &Addr) -> UiGatewaySub } } -pub fn make_blockchain_bridge_subs_from(addr: &Addr) -> BlockchainBridgeSubs { +pub fn make_blockchain_bridge_subs_from_recorder(addr: &Addr) -> BlockchainBridgeSubs { BlockchainBridgeSubs { bind: recipient!(addr, BindMessage), - report_accounts_payable: recipient!(addr, ReportAccountsPayable), - request_balances_to_pay_payables: recipient!(addr, RequestBalancesToPayPayables), + outbound_payments_instructions: recipient!(addr, OutboundPaymentsInstructions), + qualified_payables: recipient!(addr, QualifiedPayablesMessage), retrieve_transactions: recipient!(addr, RetrieveTransactions), ui_sub: recipient!(addr, NodeFromUiMessage), request_transaction_receipts: recipient!(addr, RequestTransactionReceipts), } } -pub fn make_configurator_subs_from(addr: &Addr) -> ConfiguratorSubs { +pub fn make_configurator_subs_from_recorder(addr: &Addr) -> ConfiguratorSubs { ConfiguratorSubs { bind: recipient!(addr, BindMessage), node_from_ui_sub: recipient!(addr, NodeFromUiMessage), @@ -531,15 +585,15 @@ impl PeerActorsBuilder { let configurator_addr = self.configurator.start(); PeerActors { - proxy_server: make_proxy_server_subs_from(&proxy_server_addr), - dispatcher: make_dispatcher_subs_from(&dispatcher_addr), - hopper: make_hopper_subs_from(&hopper_addr), - proxy_client_opt: Some(make_proxy_client_subs_from(&proxy_client_addr)), - neighborhood: make_neighborhood_subs_from(&neighborhood_addr), + proxy_server: make_proxy_server_subs_from_recorder(&proxy_server_addr), + dispatcher: make_dispatcher_subs_from_recorder(&dispatcher_addr), + hopper: make_hopper_subs_from_recorder(&hopper_addr), + proxy_client_opt: Some(make_proxy_client_subs_from_recorder(&proxy_client_addr)), + neighborhood: make_neighborhood_subs_from_recorder(&neighborhood_addr), accountant: make_accountant_subs_from_recorder(&accountant_addr), ui_gateway: make_ui_gateway_subs_from_recorder(&ui_gateway_addr), - blockchain_bridge: make_blockchain_bridge_subs_from(&blockchain_bridge_addr), - configurator: make_configurator_subs_from(&configurator_addr), + blockchain_bridge: make_blockchain_bridge_subs_from_recorder(&blockchain_bridge_addr), + configurator: make_configurator_subs_from_recorder(&configurator_addr), } } } @@ -549,13 +603,14 @@ mod tests { use super::*; use actix::Message; use actix::System; + use std::any::TypeId; #[derive(Debug, PartialEq, Eq, Message)] struct FirstMessageType { string: String, } - recorder_message_handler!(FirstMessageType); + recorder_message_handler_t_m_p!(FirstMessageType); #[derive(Debug, PartialEq, Eq, Message)] struct SecondMessageType { @@ -563,7 +618,7 @@ mod tests { flag: bool, } - recorder_message_handler!(SecondMessageType); + recorder_message_handler_t_m_p!(SecondMessageType); #[test] fn recorder_records_different_messages() { @@ -604,4 +659,20 @@ mod tests { ); assert_eq!(recording.len(), 2); } + + struct ExampleMsgA; + + struct ExampleMsgB; + + #[test] + fn different_messages_in_pretending_matchable_have_different_type_ids() { + assert_eq!( + TypeId::of::>(), + TypeId::of::>() + ); + assert_ne!( + TypeId::of::>(), + TypeId::of::>() + ) + } } diff --git a/node/src/test_utils/recorder_stop_conditions.rs b/node/src/test_utils/recorder_stop_conditions.rs index c9a18cbcb..b3dca287d 100644 --- a/node/src/test_utils/recorder_stop_conditions.rs +++ b/node/src/test_utils/recorder_stop_conditions.rs @@ -24,14 +24,17 @@ pub type BoxedMsgExpected = Box; pub type RefMsgExpected<'a> = &'a (dyn Any + Send); impl StopConditions { - pub fn resolve_stop_conditions(&mut self, msg: &T) -> bool { + pub fn resolve_stop_conditions + Send + 'static>( + &mut self, + msg: &T, + ) -> bool { match self { StopConditions::Any(conditions) => Self::resolve_any::(conditions, msg), StopConditions::All(conditions) => Self::resolve_all::(conditions, msg), } } - fn resolve_any( + fn resolve_any + Send + 'static>( conditions: &Vec, msg: &T, ) -> bool { @@ -40,7 +43,7 @@ impl StopConditions { .any(|condition| condition.resolve_condition::(msg)) } - fn resolve_all( + fn resolve_all + Send + 'static>( conditions: &mut Vec, msg: &T, ) -> bool { @@ -49,7 +52,7 @@ impl StopConditions { conditions.is_empty() } - fn indexes_of_matched_conditions( + fn indexes_of_matched_conditions + Send + 'static>( conditions: &[StopCondition], msg: &T, ) -> Vec { @@ -82,9 +85,9 @@ impl StopConditions { } impl StopCondition { - fn resolve_condition(&self, msg: &T) -> bool { + fn resolve_condition + Send + 'static>(&self, msg: &T) -> bool { match self { - StopCondition::StopOnType(type_id) => Self::matches_stop_on_type::(*type_id), + StopCondition::StopOnType(type_id) => Self::matches_stop_on_type::(msg, *type_id), StopCondition::StopOnMatch { exemplar } => { Self::matches_stop_on_match::(exemplar, msg) } @@ -94,12 +97,12 @@ impl StopCondition { } } - fn matches_stop_on_type(expected_type_id: TypeId) -> bool { - let msg_type_id = TypeId::of::(); - msg_type_id == expected_type_id + fn matches_stop_on_type>(msg: &T, expected_type_id: TypeId) -> bool { + let correct_msg_type_id = msg.correct_msg_type_id(); + correct_msg_type_id == expected_type_id } - fn matches_stop_on_match( + fn matches_stop_on_match + 'static + Send>( exemplar: &BoxedMsgExpected, msg: &T, ) -> bool { @@ -117,6 +120,33 @@ impl StopCondition { } } +pub trait ForcedMatchable: PartialEq + Send { + fn correct_msg_type_id(&self) -> TypeId; +} + +pub struct PretendedMatchableWrapper(pub M); + +impl ForcedMatchable for PretendedMatchableWrapper +where + OuterM: PartialEq, + InnerM: Send, +{ + fn correct_msg_type_id(&self) -> TypeId { + TypeId::of::() + } +} + +impl PartialEq for PretendedMatchableWrapper { + fn eq(&self, _other: &Self) -> bool { + panic!( + r#"You requested StopCondition::StopOnMatch for message + that does not implement PartialEq. Consider two other + options: matching the type simply by its TypeId or using + a predicate."# + ) + } +} + #[macro_export] macro_rules! match_every_type_id{ ($($single_message: ident),+) => { diff --git a/node/tests/financials_test.rs b/node/tests/financials_test.rs index 49ae24186..0874cb39b 100644 --- a/node/tests/financials_test.rs +++ b/node/tests/financials_test.rs @@ -10,9 +10,9 @@ use masq_lib::messages::{ 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::db_access_objects::dao_utils::{from_time_t, to_time_t}; use node_lib::accountant::db_access_objects::payable_dao::{PayableDao, PayableDaoReal}; use node_lib::accountant::db_access_objects::receivable_dao::{ReceivableDao, ReceivableDaoReal}; +use node_lib::accountant::db_access_objects::utils::{from_time_t, to_time_t}; use node_lib::accountant::gwei_to_wei; use node_lib::test_utils::make_wallet; use std::time::SystemTime;