diff --git a/USER-INTERFACE-INTERFACE.md b/USER-INTERFACE-INTERFACE.md index 443abc65a..3a5cacffa 100644 --- a/USER-INTERFACE-INTERFACE.md +++ b/USER-INTERFACE-INTERFACE.md @@ -358,8 +358,8 @@ Another reason the secrets might be missing is that there are not yet any secret "exitServiceRate: " }, "scanIntervals": { - "pendingPayableSec": , "payableSec": , + "pendingPayableSec": , "receivableSec": }, } @@ -453,20 +453,21 @@ database password. If you want to know whether the password you have is the corr * `scanIntervals`: These three intervals describe the length of three different scan cycles running automatically in the background since the Node has connected to a qualified neighborhood that consists of neighbors enabling a complete - 3-hop route. Each parameter can be set independently, but by default are all the same which currently is most desirable - for the consistency of service payments to and from your Node. Technically, there doesn't have to be any lower limit - for the minimum of time you can set; two scans of the same sort would never run at the same time but the next one is + 3-hop route. Each parameter can be set independently. Technically, there doesn't have to be any lower limit for +* the minimum of time you can set; two scans of the same sort would never run at the same time but the next one is always scheduled not earlier than the end of the previous one. These are ever present values, no matter if the user's set any value, because defaults are prepared. -* `pendingPayableSec`: Amount of seconds between two sequential cycles of scanning for payments that are marked as currently - pending; the payments were sent to pay our debts, the payable. The purpose of this process is to confirm the status of - the pending payment; either the payment transaction was written on blockchain as successful or failed. - -* `payableSec`: Amount of seconds between two sequential cycles of scanning aimed to find payable accounts of that meet +* `payableSec`: Amount of seconds between two sequential cycles of scanning aimed to find payable accounts that meet the criteria set by the Payment Thresholds; these accounts are tracked on behalf of our creditors. If they meet the Payment Threshold criteria, our Node will send a debt payment transaction to the creditor in question. +* `pendingPayableSec`: The time elapsed since the last payable transaction was processed. This scan operates + on an irregular schedule and is triggered after new transactions are sent or when failed transactions need + to be replaced. The scanner monitors pending transactions and verifies their blockchain status, determining whether + each payment was successfully recorded or failed. Any failed transaction is automatically resubmitted as soon + as the failure is detected. + * `receivableSec`: Amount of seconds between two sequential cycles of scanning for payments on the blockchain that have been sent by our creditors to us, which are credited against receivables recorded for services provided. diff --git a/masq_lib/src/blockchains/blockchain_records.rs b/masq_lib/src/blockchains/blockchain_records.rs index 00ac1bb66..821b0d7f1 100644 --- a/masq_lib/src/blockchains/blockchain_records.rs +++ b/masq_lib/src/blockchains/blockchain_records.rs @@ -4,7 +4,9 @@ use crate::blockchains::chains::Chain; use crate::constants::{ BASE_GAS_PRICE_CEILING_WEI, BASE_MAINNET_CHAIN_ID, BASE_MAINNET_CONTRACT_CREATION_BLOCK, BASE_MAINNET_FULL_IDENTIFIER, BASE_SEPOLIA_CHAIN_ID, BASE_SEPOLIA_CONTRACT_CREATION_BLOCK, - BASE_SEPOLIA_FULL_IDENTIFIER, DEV_CHAIN_FULL_IDENTIFIER, DEV_CHAIN_ID, + BASE_SEPOLIA_FULL_IDENTIFIER, DEFAULT_PENDING_PAYABLE_INTERVAL_BASE_SEC, + DEFAULT_PENDING_PAYABLE_INTERVAL_DEV_SEC, DEFAULT_PENDING_PAYABLE_INTERVAL_ETH_SEC, + DEFAULT_PENDING_PAYABLE_INTERVAL_POLYGON_SEC, DEV_CHAIN_FULL_IDENTIFIER, DEV_CHAIN_ID, DEV_GAS_PRICE_CEILING_WEI, ETH_GAS_PRICE_CEILING_WEI, ETH_MAINNET_CHAIN_ID, ETH_MAINNET_CONTRACT_CREATION_BLOCK, ETH_MAINNET_FULL_IDENTIFIER, ETH_ROPSTEN_CHAIN_ID, ETH_ROPSTEN_CONTRACT_CREATION_BLOCK, ETH_ROPSTEN_FULL_IDENTIFIER, @@ -15,14 +17,13 @@ use crate::constants::{ }; use ethereum_types::{Address, H160}; -// TODO these should probably be a static (it's a shame that we construct the data every time anew -// when we ask for the chain specs), and dynamic initialization should be allowed as well -pub const CHAINS: [BlockchainRecord; 7] = [ +pub static CHAINS: [BlockchainRecord; 7] = [ BlockchainRecord { self_id: Chain::PolyMainnet, num_chain_id: POLYGON_MAINNET_CHAIN_ID, literal_identifier: POLYGON_MAINNET_FULL_IDENTIFIER, gas_price_safe_ceiling_minor: POLYGON_GAS_PRICE_CEILING_WEI, + default_pending_payable_interval_sec: DEFAULT_PENDING_PAYABLE_INTERVAL_POLYGON_SEC, contract: POLYGON_MAINNET_CONTRACT_ADDRESS, contract_creation_block: POLYGON_MAINNET_CONTRACT_CREATION_BLOCK, }, @@ -31,6 +32,7 @@ pub const CHAINS: [BlockchainRecord; 7] = [ num_chain_id: ETH_MAINNET_CHAIN_ID, literal_identifier: ETH_MAINNET_FULL_IDENTIFIER, gas_price_safe_ceiling_minor: ETH_GAS_PRICE_CEILING_WEI, + default_pending_payable_interval_sec: DEFAULT_PENDING_PAYABLE_INTERVAL_ETH_SEC, contract: ETH_MAINNET_CONTRACT_ADDRESS, contract_creation_block: ETH_MAINNET_CONTRACT_CREATION_BLOCK, }, @@ -39,6 +41,7 @@ pub const CHAINS: [BlockchainRecord; 7] = [ num_chain_id: BASE_MAINNET_CHAIN_ID, literal_identifier: BASE_MAINNET_FULL_IDENTIFIER, gas_price_safe_ceiling_minor: BASE_GAS_PRICE_CEILING_WEI, + default_pending_payable_interval_sec: DEFAULT_PENDING_PAYABLE_INTERVAL_BASE_SEC, contract: BASE_MAINNET_CONTRACT_ADDRESS, contract_creation_block: BASE_MAINNET_CONTRACT_CREATION_BLOCK, }, @@ -47,6 +50,7 @@ pub const CHAINS: [BlockchainRecord; 7] = [ num_chain_id: BASE_SEPOLIA_CHAIN_ID, literal_identifier: BASE_SEPOLIA_FULL_IDENTIFIER, gas_price_safe_ceiling_minor: BASE_GAS_PRICE_CEILING_WEI, + default_pending_payable_interval_sec: DEFAULT_PENDING_PAYABLE_INTERVAL_BASE_SEC, contract: BASE_SEPOLIA_TESTNET_CONTRACT_ADDRESS, contract_creation_block: BASE_SEPOLIA_CONTRACT_CREATION_BLOCK, }, @@ -55,6 +59,7 @@ pub const CHAINS: [BlockchainRecord; 7] = [ num_chain_id: POLYGON_AMOY_CHAIN_ID, literal_identifier: POLYGON_AMOY_FULL_IDENTIFIER, gas_price_safe_ceiling_minor: POLYGON_GAS_PRICE_CEILING_WEI, + default_pending_payable_interval_sec: DEFAULT_PENDING_PAYABLE_INTERVAL_POLYGON_SEC, contract: POLYGON_AMOY_TESTNET_CONTRACT_ADDRESS, contract_creation_block: POLYGON_AMOY_CONTRACT_CREATION_BLOCK, }, @@ -63,6 +68,7 @@ pub const CHAINS: [BlockchainRecord; 7] = [ num_chain_id: ETH_ROPSTEN_CHAIN_ID, literal_identifier: ETH_ROPSTEN_FULL_IDENTIFIER, gas_price_safe_ceiling_minor: ETH_GAS_PRICE_CEILING_WEI, + default_pending_payable_interval_sec: DEFAULT_PENDING_PAYABLE_INTERVAL_ETH_SEC, contract: ETH_ROPSTEN_TESTNET_CONTRACT_ADDRESS, contract_creation_block: ETH_ROPSTEN_CONTRACT_CREATION_BLOCK, }, @@ -71,6 +77,7 @@ pub const CHAINS: [BlockchainRecord; 7] = [ num_chain_id: DEV_CHAIN_ID, literal_identifier: DEV_CHAIN_FULL_IDENTIFIER, gas_price_safe_ceiling_minor: DEV_GAS_PRICE_CEILING_WEI, + default_pending_payable_interval_sec: DEFAULT_PENDING_PAYABLE_INTERVAL_DEV_SEC, contract: MULTINODE_TESTNET_CONTRACT_ADDRESS, contract_creation_block: MULTINODE_TESTNET_CONTRACT_CREATION_BLOCK, }, @@ -82,6 +89,7 @@ pub struct BlockchainRecord { pub num_chain_id: u64, pub literal_identifier: &'static str, pub gas_price_safe_ceiling_minor: u128, + pub default_pending_payable_interval_sec: u64, pub contract: Address, pub contract_creation_block: u64, } @@ -128,7 +136,11 @@ const POLYGON_MAINNET_CONTRACT_ADDRESS: Address = H160([ mod tests { use super::*; use crate::blockchains::chains::chain_from_chain_identifier_opt; - use crate::constants::{BASE_MAINNET_CONTRACT_CREATION_BLOCK, WEIS_IN_GWEI}; + use crate::constants::{ + BASE_MAINNET_CONTRACT_CREATION_BLOCK, DEFAULT_PENDING_PAYABLE_INTERVAL_BASE_SEC, + DEFAULT_PENDING_PAYABLE_INTERVAL_DEV_SEC, DEFAULT_PENDING_PAYABLE_INTERVAL_ETH_SEC, + DEFAULT_PENDING_PAYABLE_INTERVAL_POLYGON_SEC, WEIS_IN_GWEI, + }; use std::collections::HashSet; use std::iter::FromIterator; @@ -209,6 +221,7 @@ mod tests { self_id: examined_chain, literal_identifier: "eth-mainnet", gas_price_safe_ceiling_minor: 100 * WEIS_IN_GWEI as u128, + default_pending_payable_interval_sec: DEFAULT_PENDING_PAYABLE_INTERVAL_ETH_SEC, contract: ETH_MAINNET_CONTRACT_ADDRESS, contract_creation_block: ETH_MAINNET_CONTRACT_CREATION_BLOCK, } @@ -226,6 +239,7 @@ mod tests { self_id: examined_chain, literal_identifier: "eth-ropsten", gas_price_safe_ceiling_minor: 100 * WEIS_IN_GWEI as u128, + default_pending_payable_interval_sec: DEFAULT_PENDING_PAYABLE_INTERVAL_ETH_SEC, contract: ETH_ROPSTEN_TESTNET_CONTRACT_ADDRESS, contract_creation_block: ETH_ROPSTEN_CONTRACT_CREATION_BLOCK, } @@ -243,6 +257,7 @@ mod tests { self_id: examined_chain, literal_identifier: "polygon-mainnet", gas_price_safe_ceiling_minor: 200 * WEIS_IN_GWEI as u128, + default_pending_payable_interval_sec: DEFAULT_PENDING_PAYABLE_INTERVAL_POLYGON_SEC, contract: POLYGON_MAINNET_CONTRACT_ADDRESS, contract_creation_block: POLYGON_MAINNET_CONTRACT_CREATION_BLOCK, } @@ -260,6 +275,7 @@ mod tests { self_id: examined_chain, literal_identifier: "polygon-amoy", gas_price_safe_ceiling_minor: 200 * WEIS_IN_GWEI as u128, + default_pending_payable_interval_sec: DEFAULT_PENDING_PAYABLE_INTERVAL_POLYGON_SEC, contract: POLYGON_AMOY_TESTNET_CONTRACT_ADDRESS, contract_creation_block: POLYGON_AMOY_CONTRACT_CREATION_BLOCK, } @@ -277,6 +293,7 @@ mod tests { self_id: examined_chain, literal_identifier: "base-mainnet", gas_price_safe_ceiling_minor: 50 * WEIS_IN_GWEI as u128, + default_pending_payable_interval_sec: DEFAULT_PENDING_PAYABLE_INTERVAL_BASE_SEC, contract: BASE_MAINNET_CONTRACT_ADDRESS, contract_creation_block: BASE_MAINNET_CONTRACT_CREATION_BLOCK, } @@ -294,6 +311,7 @@ mod tests { self_id: examined_chain, literal_identifier: "base-sepolia", gas_price_safe_ceiling_minor: 50 * WEIS_IN_GWEI as u128, + default_pending_payable_interval_sec: DEFAULT_PENDING_PAYABLE_INTERVAL_BASE_SEC, contract: BASE_SEPOLIA_TESTNET_CONTRACT_ADDRESS, contract_creation_block: BASE_SEPOLIA_CONTRACT_CREATION_BLOCK, } @@ -311,6 +329,7 @@ mod tests { self_id: examined_chain, literal_identifier: "dev", gas_price_safe_ceiling_minor: 200 * WEIS_IN_GWEI as u128, + default_pending_payable_interval_sec: DEFAULT_PENDING_PAYABLE_INTERVAL_DEV_SEC, contract: MULTINODE_TESTNET_CONTRACT_ADDRESS, contract_creation_block: MULTINODE_TESTNET_CONTRACT_CREATION_BLOCK, } diff --git a/masq_lib/src/blockchains/chains.rs b/masq_lib/src/blockchains/chains.rs index b7061d899..cedde32d1 100644 --- a/masq_lib/src/blockchains/chains.rs +++ b/masq_lib/src/blockchains/chains.rs @@ -142,6 +142,7 @@ mod tests { self_id: Chain::PolyMainnet, literal_identifier: "", gas_price_safe_ceiling_minor: 0, + default_pending_payable_interval_sec: 0, contract: Default::default(), contract_creation_block: 0, } diff --git a/masq_lib/src/constants.rs b/masq_lib/src/constants.rs index 67338f5a3..fcbec6826 100644 --- a/masq_lib/src/constants.rs +++ b/masq_lib/src/constants.rs @@ -18,24 +18,16 @@ pub const MASQ_URL_PREFIX: &str = "masq://"; pub const CURRENT_LOGFILE_NAME: &str = "MASQNode_rCURRENT.log"; pub const MASQ_PROMPT: &str = "masq> "; -pub const DEFAULT_GAS_PRICE: u64 = 1; //TODO ?? Really -pub const DEFAULT_GAS_PRICE_MARGIN: u64 = 30; - pub const WALLET_ADDRESS_LENGTH: usize = 42; -pub const MASQ_TOTAL_SUPPLY: u64 = 37_500_000; pub const WEIS_IN_GWEI: i128 = 1_000_000_000; -pub const DEFAULT_MAX_BLOCK_COUNT: u64 = 100_000; +pub const COMBINED_PARAMETERS_DELIMITER: char = '|'; pub const PAYLOAD_ZERO_SIZE: usize = 0usize; -pub const ETH_MAINNET_CONTRACT_CREATION_BLOCK: u64 = 11_170_708; -pub const ETH_ROPSTEN_CONTRACT_CREATION_BLOCK: u64 = 8_688_171; -pub const POLYGON_MAINNET_CONTRACT_CREATION_BLOCK: u64 = 14_863_650; -pub const POLYGON_AMOY_CONTRACT_CREATION_BLOCK: u64 = 5_323_366; -pub const BASE_MAINNET_CONTRACT_CREATION_BLOCK: u64 = 19_711_235; -pub const BASE_SEPOLIA_CONTRACT_CREATION_BLOCK: u64 = 14_732_730; -pub const MULTINODE_TESTNET_CONTRACT_CREATION_BLOCK: u64 = 0; +//descriptor +pub const CENTRAL_DELIMITER: char = '@'; +pub const CHAIN_IDENTIFIER_DELIMITER: char = ':'; //Migration versions //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -88,11 +80,11 @@ pub const VALUE_EXCEEDS_ALLOWED_LIMIT: u64 = ACCOUNTANT_PREFIX | 3; //////////////////////////////////////////////////////////////////////////////////////////////////// -pub const COMBINED_PARAMETERS_DELIMITER: char = '|'; +pub const MASQ_TOTAL_SUPPLY: u64 = 37_500_000; -//descriptor -pub const CENTRAL_DELIMITER: char = '@'; -pub const CHAIN_IDENTIFIER_DELIMITER: char = ':'; +pub const DEFAULT_GAS_PRICE: u64 = 1; //TODO ?? Really +pub const DEFAULT_GAS_PRICE_MARGIN: u64 = 30; +pub const DEFAULT_MAX_BLOCK_COUNT: u64 = 100_000; //chains pub const POLYGON_MAINNET_CHAIN_ID: u64 = 137; @@ -114,11 +106,25 @@ pub const ETH_ROPSTEN_FULL_IDENTIFIER: &str = concatcp!(ETH_FAMILY, LINK, "ropst pub const BASE_MAINNET_FULL_IDENTIFIER: &str = concatcp!(BASE_FAMILY, LINK, MAINNET); pub const BASE_SEPOLIA_FULL_IDENTIFIER: &str = concatcp!(BASE_FAMILY, LINK, "sepolia"); pub const DEV_CHAIN_FULL_IDENTIFIER: &str = "dev"; + +pub const ETH_MAINNET_CONTRACT_CREATION_BLOCK: u64 = 11_170_708; +pub const ETH_ROPSTEN_CONTRACT_CREATION_BLOCK: u64 = 8_688_171; +pub const POLYGON_MAINNET_CONTRACT_CREATION_BLOCK: u64 = 14_863_650; +pub const POLYGON_AMOY_CONTRACT_CREATION_BLOCK: u64 = 5_323_366; +pub const BASE_MAINNET_CONTRACT_CREATION_BLOCK: u64 = 19_711_235; +pub const BASE_SEPOLIA_CONTRACT_CREATION_BLOCK: u64 = 14_732_730; +pub const MULTINODE_TESTNET_CONTRACT_CREATION_BLOCK: u64 = 0; + pub const POLYGON_GAS_PRICE_CEILING_WEI: u128 = 200_000_000_000; pub const ETH_GAS_PRICE_CEILING_WEI: u128 = 100_000_000_000; pub const BASE_GAS_PRICE_CEILING_WEI: u128 = 50_000_000_000; pub const DEV_GAS_PRICE_CEILING_WEI: u128 = 200_000_000_000; +pub const DEFAULT_PENDING_PAYABLE_INTERVAL_ETH_SEC: u64 = 600; +pub const DEFAULT_PENDING_PAYABLE_INTERVAL_BASE_SEC: u64 = 120; +pub const DEFAULT_PENDING_PAYABLE_INTERVAL_POLYGON_SEC: u64 = 180; +pub const DEFAULT_PENDING_PAYABLE_INTERVAL_DEV_SEC: u64 = 120; + #[cfg(test)] mod tests { use super::*; @@ -204,6 +210,10 @@ mod tests { assert_eq!(ETH_GAS_PRICE_CEILING_WEI, 100_000_000_000); assert_eq!(BASE_GAS_PRICE_CEILING_WEI, 50_000_000_000); assert_eq!(DEV_GAS_PRICE_CEILING_WEI, 200_000_000_000); + assert_eq!(DEFAULT_PENDING_PAYABLE_INTERVAL_ETH_SEC, 600); + assert_eq!(DEFAULT_PENDING_PAYABLE_INTERVAL_BASE_SEC, 120); + assert_eq!(DEFAULT_PENDING_PAYABLE_INTERVAL_POLYGON_SEC, 180); + assert_eq!(DEFAULT_PENDING_PAYABLE_INTERVAL_DEV_SEC, 120); assert_eq!( CLIENT_REQUEST_PAYLOAD_CURRENT_VERSION, DataVersion { major: 0, minor: 1 } diff --git a/masq_lib/src/shared_schema.rs b/masq_lib/src/shared_schema.rs index 11dfb865f..3a8b194da 100644 --- a/masq_lib/src/shared_schema.rs +++ b/masq_lib/src/shared_schema.rs @@ -20,8 +20,8 @@ pub const CHAIN_HELP: &str = "The blockchain network MASQ Node will configure itself to use. You must ensure the \ Ethereum client specified by --blockchain-service-url communicates with the same blockchain network."; pub const CONFIG_FILE_HELP: &str = - "Optional TOML file containing configuration that doesn't often change. Should contain only \ - scalar items, string or numeric, whose names are exactly the same as the command-line parameters \ + "Optional TOML file containing configuration that seldom changes. Should contain only \ + scalar items, string, or numeric, whose names are exactly the same as the command-line parameters \ they replace (except no '--' prefix). If you specify a relative path, or no path, the Node will \ look for your config file starting in the --data-directory. If you specify an absolute path, \ --data-directory will be ignored when searching for the config file. A few parameters \ @@ -138,9 +138,9 @@ pub const REAL_USER_HELP: &str = like ::."; pub const SCANS_HELP: &str = "The Node, when running, performs various periodic scans, including scanning for payables that need to be paid, \ - for pending payables that have arrived (and are no longer pending), for incoming receivables that need to be \ - recorded, and for delinquent Nodes that need to be banned. If you don't specify this parameter, or if you give \ - it the value 'on', these scans will proceed normally. But if you give the value 'off', the scans won't be \ + for pending payables that have arrived or happened to fail (and are no longer pending), for incoming receivables \ + that need to be recorded, and for delinquent Nodes that need to be banned. If you don't specify this parameter, \ + or if you give it the value 'on', these scans will proceed normally. But if you give the value 'off', the scans won't be \ started when the Node starts, and will have to be triggered later manually and individually with the \ MASQNode-UIv2 'scan' command. (If you don't, you'll most likely be delinquency-banned by all your neighbors.) \ This parameter is most useful for testing."; @@ -183,19 +183,18 @@ pub const PAYMENT_THRESHOLDS_HELP: &str = "\ pub const SCAN_INTERVALS_HELP:&str = "\ These three intervals describe the length of three different scan cycles running automatically in the background \ since the Node has connected to a qualified neighborhood that consists of neighbors enabling a complete 3-hop \ - route. Each parameter can be set independently, but by default are all the same which currently is most desirable \ - for the consistency of service payments to and from your Node. Technically, there doesn't have to be any lower \ + route. Each parameter can be set independently. Technically, there doesn't have to be any lower \ limit for the minimum of time you can set; two scans of the same sort would never run at the same time but the \ next one is always scheduled not earlier than the end of the previous one. These are ever present values, no matter \ if the user's set any value, they have defaults. The parameters must be always supplied all together, delimited by vertical \ bars and in the right order.\n\n\ - 1. Pending Payable Scan Interval: Amount of seconds between two sequential cycles of scanning for payments that are \ - marked as currently pending; the payments were sent to pay our debts, the payable. The purpose of this process is to \ - confirm the status of the pending payment; either the payment transaction was written on blockchain as successful or \ - failed.\n\n\ - 2. Payable Scan Interval: Amount of seconds between two sequential cycles of scanning aimed to find payable accounts \ - of that meet the criteria set by the Payment Thresholds; these accounts are tracked on behalf of our creditors. If \ - they meet the Payment Threshold criteria, our Node will send a debt payment transaction to the creditor in question.\n\n\ + 1. Payable Scan Interval: Amount of seconds between two sequential cycles of scanning aimed to find payable accounts \ + that meet the criteria set by the Payment Thresholds; these accounts are tracked on behalf of our creditors. \ + If they meet the Payment Threshold criteria, our Node will send a debt payment transaction to the creditor in question.\n\n\ + 2. Pending Payable Scan Interval: The time elapsed since the last payable transaction was processed. This scan operates \ + on an irregular schedule and is triggered after new transactions are sent or when failed transactions need to be replaced. \ + The scanner monitors pending transactions and verifies their blockchain status, determining whether each payment was \ + successfully recorded or failed. Any failed transaction is automatically resubmitted as soon as the failure is detected.\n\n\ 3. Receivable Scan Interval: Amount of seconds between two sequential cycles of scanning for payments on the \ blockchain that have been sent by our creditors to us, which are credited against receivables recorded for services \ provided."; @@ -744,8 +743,8 @@ mod tests { ); assert_eq!( CONFIG_FILE_HELP, - "Optional TOML file containing configuration that doesn't often change. Should contain only \ - scalar items, string or numeric, whose names are exactly the same as the command-line parameters \ + "Optional TOML file containing configuration that seldom changes. Should contain only \ + scalar items, string, or numeric, whose names are exactly the same as the command-line parameters \ they replace (except no '--' prefix). If you specify a relative path, or no path, the Node will \ look for your config file starting in the --data-directory. If you specify an absolute path, \ --data-directory will be ignored when searching for the config file. A few parameters \ @@ -883,6 +882,16 @@ mod tests { you start the Node using pkexec or some other method that doesn't populate the SUDO_xxx variables. Use a value \ like ::." ); + assert_eq!( + SCANS_HELP, + "The Node, when running, performs various periodic scans, including scanning for payables that need to be paid, \ + for pending payables that have arrived or happened to fail (and are no longer pending), for incoming receivables \ + that need to be recorded, and for delinquent Nodes that need to be banned. If you don't specify this parameter, \ + or if you give it the value 'on', these scans will proceed normally. But if you give the value 'off', the scans won't be \ + started when the Node starts, and will have to be triggered later manually and individually with the \ + MASQNode-UIv2 'scan' command. (If you don't, you'll most likely be delinquency-banned by all your neighbors.) \ + This parameter is most useful for testing." + ); assert_eq!( DEFAULT_UI_PORT_VALUE.to_string(), @@ -959,19 +968,19 @@ mod tests { SCAN_INTERVALS_HELP, "These three intervals describe the length of three different scan cycles running automatically in the background \ since the Node has connected to a qualified neighborhood that consists of neighbors enabling a complete 3-hop \ - route. Each parameter can be set independently, but by default are all the same which currently is most desirable \ - for the consistency of service payments to and from your Node. Technically, there doesn't have to be any lower \ + route. Each parameter can be set independently. Technically, there doesn't have to be any lower \ limit for the minimum of time you can set; two scans of the same sort would never run at the same time but the \ next one is always scheduled not earlier than the end of the previous one. These are ever present values, no matter \ if the user's set any value, they have defaults. The parameters must be always supplied all together, delimited by \ vertical bars and in the right order.\n\n\ - 1. Pending Payable Scan Interval: Amount of seconds between two sequential cycles of scanning for payments that are \ - marked as currently pending; the payments were sent to pay our debts, the payable. The purpose of this process is to \ - confirm the status of the pending payment; either the payment transaction was written on blockchain as successful or \ - failed.\n\n\ - 2. Payable Scan Interval: Amount of seconds between two sequential cycles of scanning aimed to find payable accounts \ - of that meet the criteria set by the Payment Thresholds; these accounts are tracked on behalf of our creditors. If \ + 1. Payable Scan Interval: Amount of seconds between two sequential cycles of scanning aimed to find payable accounts \ + that meet the criteria set by the Payment Thresholds; these accounts are tracked on behalf of our creditors. If \ they meet the Payment Threshold criteria, our Node will send a debt payment transaction to the creditor in question.\n\n\ + 2. Pending Payable Scan Interval: The time elapsed since the last payable transaction was processed. This scan operates \ + on an irregular schedule and is triggered after new transactions are sent or when failed transactions need \ + to be replaced. The scanner monitors pending transactions and verifies their blockchain status, determining whether \ + each payment was successfully recorded or failed. Any failed transaction is automatically resubmitted as soon \ + as the failure is detected.\n\n\ 3. Receivable Scan Interval: Amount of seconds between two sequential cycles of scanning for payments on the \ blockchain that have been sent by our creditors to us, which are credited against receivables recorded for services \ provided." diff --git a/node/src/accountant/db_access_objects/payable_dao.rs b/node/src/accountant/db_access_objects/payable_dao.rs index 0226c0c68..cff264a58 100644 --- a/node/src/accountant/db_access_objects/payable_dao.rs +++ b/node/src/accountant/db_access_objects/payable_dao.rs @@ -24,7 +24,6 @@ use masq_lib::utils::ExpectValue; use rusqlite::OptionalExtension; use rusqlite::{Error, Row}; use std::fmt::Debug; -use std::str::FromStr; use std::time::SystemTime; use web3::types::H256; @@ -313,39 +312,22 @@ impl PayableDaoReal { let balance_high_bytes_result = row.get(1); let balance_low_bytes_result = row.get(2); let last_paid_timestamp_result = row.get(3); - let pending_payable_rowid_result: Result, Error> = row.get(4); - let pending_payable_hash_result: Result, Error> = row.get(5); match ( wallet_result, balance_high_bytes_result, balance_low_bytes_result, last_paid_timestamp_result, - pending_payable_rowid_result, - pending_payable_hash_result, ) { - ( - Ok(wallet), - Ok(high_bytes), - Ok(low_bytes), - Ok(last_paid_timestamp), - Ok(rowid_opt), - Ok(hash_opt), - ) => Ok(PayableAccount { - wallet, - balance_wei: checked_conversion::(BigIntDivider::reconstitute( - high_bytes, low_bytes, - )), - last_paid_timestamp: utils::from_unix_timestamp(last_paid_timestamp), - pending_payable_opt: rowid_opt.map(|rowid| { - let hash_str = - hash_opt.expect("database corrupt; missing hash but existing rowid"); - PendingPayableId::new( - u64::try_from(rowid).unwrap(), - H256::from_str(&hash_str[2..]) - .unwrap_or_else(|_| panic!("wrong form of tx hash {}", hash_str)), - ) - }), - }), + (Ok(wallet), Ok(high_bytes), Ok(low_bytes), Ok(last_paid_timestamp)) => { + Ok(PayableAccount { + wallet, + balance_wei: checked_conversion::(BigIntDivider::reconstitute( + high_bytes, low_bytes, + )), + last_paid_timestamp: utils::from_unix_timestamp(last_paid_timestamp), + pending_payable_opt: None, + }) + } e => panic!( "Database is corrupt: PAYABLE table columns and/or types: {:?}", e @@ -359,13 +341,9 @@ impl PayableDaoReal { wallet_address, balance_high_b, balance_low_b, - last_paid_timestamp, - pending_payable_rowid, - pending_payable.transaction_hash + last_paid_timestamp from payable - left join pending_payable on - pending_payable.rowid = payable.pending_payable_rowid {} {} order by {}, @@ -560,7 +538,6 @@ mod tests { use rusqlite::ToSql; use rusqlite::{Connection, OpenFlags}; use std::path::Path; - use std::str::FromStr; use std::time::Duration; #[test] @@ -1314,9 +1291,9 @@ mod tests { #[test] fn custom_query_in_top_records_mode_with_default_ordering() { - //Accounts of balances smaller than one gwei don't qualify. - //Two accounts differ only in debt's age but not balance which allows to check doubled ordering, - //here by balance and then by age. + // Accounts of balances smaller than one gwei don't qualify. + // Two accounts differ only in the debt age but not the balance which allows to check double + // ordering, primarily by balance and then age. let now = current_unix_timestamp(); let main_test_setup = accounts_for_tests_of_top_records(now); let subject = custom_query_test_body_for_payable( @@ -1344,13 +1321,7 @@ mod tests { wallet: Wallet::new("0x5555555555555555555555555555555555555555"), balance_wei: 10_000_000_100, last_paid_timestamp: from_unix_timestamp(now - 86_401), - pending_payable_opt: Some(PendingPayableId::new( - 1, - H256::from_str( - "abc4546cce78230a2312e12f3acb78747340456fe5237896666100143abcd223" - ) - .unwrap() - )) + pending_payable_opt: None }, PayableAccount { wallet: Wallet::new("0x4444444444444444444444444444444444444444"), @@ -1364,9 +1335,9 @@ mod tests { #[test] fn custom_query_in_top_records_mode_ordered_by_age() { - //Accounts of balances smaller than one gwei don't qualify. - //Two accounts differ only in balance but not in the debt's age which allows to check doubled ordering, - //here by age and then by balance. + // Accounts of balances smaller than one gwei don't qualify. + // Two accounts differ only in the debt age but not the balance which allows to check double + // ordering, primarily by balance and then age. let now = current_unix_timestamp(); let main_test_setup = accounts_for_tests_of_top_records(now); let subject = custom_query_test_body_for_payable( @@ -1388,13 +1359,7 @@ mod tests { wallet: Wallet::new("0x5555555555555555555555555555555555555555"), balance_wei: 10_000_000_100, last_paid_timestamp: from_unix_timestamp(now - 86_401), - pending_payable_opt: Some(PendingPayableId::new( - 1, - H256::from_str( - "abc4546cce78230a2312e12f3acb78747340456fe5237896666100143abcd223" - ) - .unwrap() - )) + pending_payable_opt: None }, PayableAccount { wallet: Wallet::new("0x1111111111111111111111111111111111111111"), @@ -1433,8 +1398,8 @@ mod tests { #[test] fn custom_query_in_range_mode() { - //Two accounts differ only in debt's age but not balance which allows to check doubled ordering, - //by balance and then by age. + // Two accounts differ only in the debt age but not the balance which allows to check double + // ordering, primarily by balance and then age. let now = current_unix_timestamp(); let main_setup = |conn: &dyn ConnectionWrapper, insert: InsertPayableHelperFn| { insert( @@ -1518,13 +1483,7 @@ mod tests { wallet: Wallet::new("0x2222222222222222222222222222222222222222"), balance_wei: gwei_to_wei(1_800_456_000_u32), last_paid_timestamp: from_unix_timestamp(now - 55_120), - pending_payable_opt: Some(PendingPayableId::new( - 1, - H256::from_str( - "abc4546cce78230a2312e12f3acb78747340456fe5237896666100143abcd223" - ) - .unwrap() - )) + pending_payable_opt: None } ] ); @@ -1690,19 +1649,6 @@ mod tests { .initialize(&home_dir, DbInitializationConfig::test_default()) .unwrap(); main_setup_fn(conn.as_ref(), &insert_payable_record_fn); - - let pending_payable_account: &[&dyn ToSql] = &[ - &String::from("0xabc4546cce78230a2312e12f3acb78747340456fe5237896666100143abcd223"), - &40, - &478945, - &177777777, - &1, - ]; - conn - .prepare("insert into pending_payable (transaction_hash, amount_high_b, amount_low_b, payable_timestamp, attempt) values (?,?,?,?,?)") - .unwrap() - .execute(pending_payable_account) - .unwrap(); PayableDaoReal::new(conn) } } diff --git a/node/src/accountant/db_access_objects/pending_payable_dao.rs b/node/src/accountant/db_access_objects/pending_payable_dao.rs deleted file mode 100644 index 414c364d8..000000000 --- a/node/src/accountant/db_access_objects/pending_payable_dao.rs +++ /dev/null @@ -1,933 +0,0 @@ -// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. - -use crate::accountant::db_access_objects::utils::{ - from_unix_timestamp, to_unix_timestamp, DaoFactoryReal, VigilantRusqliteFlatten, -}; -use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; -use crate::accountant::{checked_conversion, comma_joined_stringifiable}; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::HashAndAmount; -use crate::database::rusqlite_wrappers::ConnectionWrapper; -use crate::sub_lib::wallet::Wallet; -use masq_lib::utils::ExpectValue; -use rusqlite::Row; -use std::collections::HashSet; -use std::fmt::Debug; -use std::str::FromStr; -use std::time::SystemTime; -use web3::types::H256; - -#[derive(Debug, PartialEq, Eq)] -pub enum PendingPayableDaoError { - InsertionFailed(String), - UpdateFailed(String), - SignConversionError(u64), - RecordCannotBeRead, - RecordDeletion(String), - ErrorMarkFailed(String), -} - -#[derive(Debug)] -pub struct TransactionHashes { - pub rowid_results: Vec<(u64, H256)>, - pub no_rowid_results: Vec, -} - -pub trait PendingPayableDao { - // Note that the order of the returned results is not guaranteed - fn fingerprints_rowids(&self, hashes: &[H256]) -> TransactionHashes; - // fn return_all_errorless_fingerprints(&self) -> Vec; - fn insert_new_fingerprints( - &self, - hashes_and_amounts: &[HashAndAmount], - batch_wide_timestamp: SystemTime, - ) -> Result<(), PendingPayableDaoError>; - fn delete_fingerprints(&self, ids: &[u64]) -> Result<(), PendingPayableDaoError>; - fn increment_scan_attempts(&self, ids: &[u64]) -> Result<(), PendingPayableDaoError>; - fn mark_failures(&self, ids: &[u64]) -> Result<(), PendingPayableDaoError>; -} - -impl PendingPayableDao for PendingPayableDaoReal<'_> { - fn fingerprints_rowids(&self, hashes: &[H256]) -> TransactionHashes { - //Vec<(Option, H256)> { - fn hash_and_rowid_in_single_row(row: &Row) -> rusqlite::Result<(u64, H256)> { - let hash_str: String = row.get(0).expectv("hash"); - let hash = H256::from_str(&hash_str[2..]).expect("hash inserted right turned wrong"); - let sqlite_signed_rowid: i64 = row.get(1).expectv("rowid"); - let rowid = u64::try_from(sqlite_signed_rowid).expect("SQlite goes from 1 to i64:MAX"); - Ok((rowid, hash)) - } - - let sql = format!( - "select transaction_hash, rowid from pending_payable where transaction_hash in ({})", - comma_joined_stringifiable(hashes, |hash| format!("'{:?}'", hash)) - ); - - let all_found_records = self - .conn - .prepare(&sql) - .expect("Internal error") - .query_map([], hash_and_rowid_in_single_row) - .expect("map query failed") - .vigilant_flatten() - .collect::>(); - let hashes_of_found_records = all_found_records - .iter() - .map(|(_, hash)| *hash) - .collect::>(); - let hashes_of_missing_rowids = hashes - .iter() - .filter(|hash| !hashes_of_found_records.contains(hash)) - .cloned() - .collect(); - - TransactionHashes { - rowid_results: all_found_records, - no_rowid_results: hashes_of_missing_rowids, - } - } - - // fn return_all_errorless_fingerprints(&self) -> Vec { - // let mut stm = self - // .conn - // .prepare( - // "select rowid, transaction_hash, amount_high_b, amount_low_b, \ - // payable_timestamp, attempt from pending_payable where process_error is null", - // ) - // .expect("Internal error"); - // stm.query_map([], |row| { - // let rowid: u64 = Self::get_with_expect(row, 0); - // let transaction_hash: String = Self::get_with_expect(row, 1); - // let amount_high_bytes: i64 = Self::get_with_expect(row, 2); - // let amount_low_bytes: i64 = Self::get_with_expect(row, 3); - // let timestamp: i64 = Self::get_with_expect(row, 4); - // let attempt: u16 = Self::get_with_expect(row, 5); - // Ok(SentTx { - // rowid, - // timestamp: from_unix_timestamp(timestamp), - // hash: H256::from_str(&transaction_hash[2..]).unwrap_or_else(|e| { - // panic!( - // "Invalid hash format (\"{}\": {:?}) - database corrupt", - // transaction_hash, e - // ) - // }), - // attempt, - // amount_minor: checked_conversion::(BigIntDivider::reconstitute( - // amount_high_bytes, - // amount_low_bytes, - // )), - // process_error: None, - // }) - // }) - // .expect("rusqlite failure") - // .vigilant_flatten() - // .collect() - // } - - fn insert_new_fingerprints( - &self, - hashes_and_amounts: &[HashAndAmount], - batch_wide_timestamp: SystemTime, - ) -> Result<(), PendingPayableDaoError> { - fn values_clause_for_fingerprints_to_insert( - hashes_and_amounts: &[HashAndAmount], - batch_wide_timestamp: SystemTime, - ) -> String { - let time_t = to_unix_timestamp(batch_wide_timestamp); - comma_joined_stringifiable(hashes_and_amounts, |hash_and_amount| { - let amount_checked = checked_conversion::(hash_and_amount.amount); - let (high_bytes, low_bytes) = BigIntDivider::deconstruct(amount_checked); - format!( - "('{:?}', {}, {}, {}, 1, null)", - hash_and_amount.hash, high_bytes, low_bytes, time_t - ) - }) - } - - let insert_sql = format!( - "insert into pending_payable (\ - transaction_hash, amount_high_b, amount_low_b, payable_timestamp, attempt, process_error\ - ) values {}", - values_clause_for_fingerprints_to_insert(hashes_and_amounts, batch_wide_timestamp) - ); - match self - .conn - .prepare(&insert_sql) - .expect("Internal error") - .execute([]) - { - Ok(x) if x == hashes_and_amounts.len() => Ok(()), - Ok(x) => panic!( - "expected {} changed rows but got {}", - hashes_and_amounts.len(), - x - ), - Err(e) => Err(PendingPayableDaoError::InsertionFailed(e.to_string())), - } - } - - fn delete_fingerprints(&self, ids: &[u64]) -> Result<(), PendingPayableDaoError> { - let sql = format!( - "delete from pending_payable where rowid in ({})", - Self::serialize_ids(ids) - ); - match self - .conn - .prepare(&sql) - .expect("delete command wrong") - .execute([]) - { - Ok(x) if x == ids.len() => Ok(()), - Ok(num) => panic!( - "deleting sent tx record, expected {} rows to be changed, but the actual number is {}", - ids.len(), - num - ), - Err(e) => Err(PendingPayableDaoError::RecordDeletion(e.to_string())), - } - } - - fn increment_scan_attempts(&self, ids: &[u64]) -> Result<(), PendingPayableDaoError> { - let sql = format!( - "update pending_payable set attempt = attempt + 1 where rowid in ({})", - Self::serialize_ids(ids) - ); - match self.conn.prepare(&sql).expect("Internal error").execute([]) { - Ok(num) if num == ids.len() => Ok(()), - Ok(num) => panic!( - "Database corrupt: updating fingerprints: expected to update {} rows but did {}", - ids.len(), - num - ), - Err(e) => Err(PendingPayableDaoError::UpdateFailed(e.to_string())), - } - } - - fn mark_failures(&self, ids: &[u64]) -> Result<(), PendingPayableDaoError> { - let sql = format!( - "update pending_payable set process_error = 'ERROR' where rowid in ({})", - Self::serialize_ids(ids) - ); - match self - .conn - .prepare(&sql) - .expect("Internal error") - .execute([]) { - Ok(num) if num == ids.len() => Ok(()), - Ok(num) => - panic!( - "Database corrupt: marking failure at fingerprints: expected to change {} rows but did {}", - ids.len(), num - ) - , - Err(e) => Err(PendingPayableDaoError::ErrorMarkFailed(e.to_string())), - } - } -} - -#[derive(Debug)] -pub struct PendingPayableDaoReal<'a> { - conn: Box, -} - -impl<'a> PendingPayableDaoReal<'a> { - pub fn new(conn: Box) -> Self { - Self { conn } - } - - fn get_with_expect(row: &Row, index: usize) -> T { - row.get(index).expect("database is corrupt") - } - - fn serialize_ids(ids: &[u64]) -> String { - comma_joined_stringifiable(ids, |id| id.to_string()) - } -} - -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::sent_payable_dao::{ - PendingPayableDao, PendingPayableDaoError, PendingPayableDaoReal, - }; - use crate::accountant::db_access_objects::utils::from_unix_timestamp; - use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::HashAndAmount; - use crate::blockchain::test_utils::make_tx_hash; - use crate::database::db_initializer::{ - DbInitializationConfig, DbInitializer, DbInitializerReal, DATABASE_FILE, - }; - use crate::database::rusqlite_wrappers::ConnectionWrapperReal; - use crate::database::test_utils::ConnectionWrapperMock; - use masq_lib::test_utils::utils::ensure_node_home_directory_exists; - use rusqlite::{Connection, OpenFlags}; - use std::str::FromStr; - use std::time::SystemTime; - use web3::types::H256; - - // #[test] - // fn insert_new_fingerprints_happy_path() { - // let home_dir = ensure_node_home_directory_exists( - // "sent_payable_dao", - // "insert_new_fingerprints_happy_path", - // ); - // let wrapped_conn = DbInitializerReal::default() - // .initialize(&home_dir, DbInitializationConfig::test_default()) - // .unwrap(); - // let hash_1 = make_tx_hash(4546); - // let amount_1 = 55556; - // let hash_2 = make_tx_hash(6789); - // let amount_2 = 44445; - // let batch_wide_timestamp = from_unix_timestamp(200_000_000); - // let subject = PendingPayableDaoReal::new(wrapped_conn); - // let hash_and_amount_1 = HashAndAmount { - // hash: hash_1, - // amount_minor: amount_1, - // }; - // let hash_and_amount_2 = HashAndAmount { - // hash: hash_2, - // amount_minor: amount_2, - // }; - // - // let _ = subject - // .insert_new_fingerprints( - // &[hash_and_amount_1, hash_and_amount_2], - // batch_wide_timestamp, - // ) - // .unwrap(); - // - // let records = subject.return_all_errorless_fingerprints(); - // assert_eq!( - // records, - // vec![ - // SentTx { - // rowid: 1, - // timestamp: batch_wide_timestamp, - // hash: hash_and_amount_1.hash, - // attempt: 1, - // amount_minor: hash_and_amount_1.amount, - // process_error: None - // }, - // SentTx { - // rowid: 2, - // timestamp: batch_wide_timestamp, - // hash: hash_and_amount_2.hash, - // attempt: 1, - // amount_minor: hash_and_amount_2.amount, - // process_error: None - // } - // ] - // ) - // } - // - // #[test] - // fn insert_new_fingerprints_sad_path() { - // let home_dir = ensure_node_home_directory_exists( - // "sent_payable_dao", - // "insert_new_fingerprints_sad_path", - // ); - // { - // DbInitializerReal::default() - // .initialize(&home_dir, DbInitializationConfig::test_default()) - // .unwrap(); - // } - // let conn_read_only = Connection::open_with_flags( - // home_dir.join(DATABASE_FILE), - // OpenFlags::SQLITE_OPEN_READ_ONLY, - // ) - // .unwrap(); - // let wrapped_conn = ConnectionWrapperReal::new(conn_read_only); - // let hash = make_tx_hash(45466); - // let amount = 55556; - // let timestamp = from_unix_timestamp(200_000_000); - // let subject = PendingPayableDaoReal::new(Box::new(wrapped_conn)); - // let hash_and_amount = HashAndAmount { hash, amount }; - // - // let result = subject.insert_new_fingerprints(&[hash_and_amount], timestamp); - // - // assert_eq!( - // result, - // Err(PendingPayableDaoError::InsertionFailed( - // "attempt to write a readonly database".to_string() - // )) - // ) - // } - // - // #[test] - // #[should_panic(expected = "expected 1 changed rows but got 0")] - // fn insert_new_fingerprints_number_of_returned_rows_different_than_expected() { - // let setup_conn = Connection::open_in_memory().unwrap(); - // // injecting a by-plan failing statement into the mocked connection in order to provoke - // // a reaction that would've been untestable directly on the table the act is closely coupled with - // let statement = { - // setup_conn - // .execute("create table example (id integer)", []) - // .unwrap(); - // setup_conn.prepare("select id from example").unwrap() - // }; - // let wrapped_conn = ConnectionWrapperMock::default().prepare_result(Ok(statement)); - // let hash_1 = make_tx_hash(4546); - // let amount_1 = 55556; - // let batch_wide_timestamp = from_unix_timestamp(200_000_000); - // let subject = PendingPayableDaoReal::new(Box::new(wrapped_conn)); - // let hash_and_amount = HashAndAmount { - // hash: hash_1, - // amount_minor: amount_1, - // }; - // - // let _ = subject.insert_new_fingerprints(&[hash_and_amount], batch_wide_timestamp); - // } - // - // #[test] - // fn fingerprints_rowids_when_records_reachable() { - // let home_dir = ensure_node_home_directory_exists( - // "sent_payable_dao", - // "fingerprints_rowids_when_records_reachable", - // ); - // let wrapped_conn = DbInitializerReal::default() - // .initialize(&home_dir, DbInitializationConfig::test_default()) - // .unwrap(); - // let subject = PendingPayableDaoReal::new(wrapped_conn); - // let timestamp = from_unix_timestamp(195_000_000); - // // use full range tx hashes because SqLite has tendencies to see the value as a hex and convert it to an integer, - // // then complain about its excessive size if supplied in unquoted strings - // let hash_1 = - // H256::from_str("b4bc263278d3a82a652a8d73a6bfd8ec0ba1a63923bbb4f38147fb8a943da26a") - // .unwrap(); - // let hash_2 = - // H256::from_str("5a2909e7bb71943c82a94d9beb04e230351541fc14619ee8bb9b7372ea88ba39") - // .unwrap(); - // let hash_and_amount_1 = HashAndAmount { - // hash: hash_1, - // amount_minor: 4567, - // }; - // let hash_and_amount_2 = HashAndAmount { - // hash: hash_2, - // amount_minor: 6789, - // }; - // let fingerprints_init_input = vec![hash_and_amount_1, hash_and_amount_2]; - // { - // subject - // .insert_new_fingerprints(&fingerprints_init_input, timestamp) - // .unwrap(); - // } - // - // let result = subject.fingerprints_rowids(&[hash_1, hash_2]); - // - // let first_expected_pair = &(1, hash_1); - // assert!( - // result.rowid_results.contains(first_expected_pair), - // "Returned rowid pairs should have contained {:?} but all it did is {:?}", - // first_expected_pair, - // result.rowid_results - // ); - // let second_expected_pair = &(2, hash_2); - // assert!( - // result.rowid_results.contains(second_expected_pair), - // "Returned rowid pairs should have contained {:?} but all it did is {:?}", - // second_expected_pair, - // result.rowid_results - // ); - // assert_eq!(result.rowid_results.len(), 2); - // } - // - // #[test] - // fn fingerprints_rowids_when_nonexistent_records() { - // let home_dir = ensure_node_home_directory_exists( - // "sent_payable_dao", - // "fingerprints_rowids_when_nonexistent_records", - // ); - // let wrapped_conn = DbInitializerReal::default() - // .initialize(&home_dir, DbInitializationConfig::test_default()) - // .unwrap(); - // let subject = PendingPayableDaoReal::new(wrapped_conn); - // let hash_1 = make_tx_hash(11119); - // 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, 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( - // &[HashAndAmount { - // hash: hash_2, - // amount_minor: 8901234, - // }], - // SystemTime::now(), - // ) - // .unwrap(); - // subject - // .insert_new_fingerprints( - // &[HashAndAmount { - // hash: hash_3, - // amount_minor: 1234567, - // }], - // SystemTime::now(), - // ) - // .unwrap(); - // subject.delete_fingerprints(&[1]).unwrap(); - // - // let result = subject.fingerprints_rowids(&[hash_1, hash_2, hash_3, hash_4]); - // - // assert_eq!(result.rowid_results, vec![(2, hash_3),]); - // assert_eq!(result.no_rowid_results, vec![hash_1, hash_2, hash_4]); - // } - // - // #[test] - // fn return_all_errorless_fingerprints_works_when_no_records_with_error_marks() { - // let home_dir = ensure_node_home_directory_exists( - // "sent_payable_dao", - // "return_all_errorless_fingerprints_works_when_no_records_with_error_marks", - // ); - // let wrapped_conn = DbInitializerReal::default() - // .initialize(&home_dir, DbInitializationConfig::test_default()) - // .unwrap(); - // let subject = PendingPayableDaoReal::new(wrapped_conn); - // let batch_wide_timestamp = from_unix_timestamp(195_000_000); - // let hash_1 = make_tx_hash(11119); - // let amount_1 = 787; - // let hash_2 = make_tx_hash(10000); - // let amount_2 = 333; - // let hash_and_amount_1 = HashAndAmount { - // hash: hash_1, - // amount_minor: amount_1, - // }; - // let hash_and_amount_2 = HashAndAmount { - // hash: hash_2, - // amount_minor: amount_2, - // }; - // - // { - // subject - // .insert_new_fingerprints( - // &[hash_and_amount_1, hash_and_amount_2], - // batch_wide_timestamp, - // ) - // .unwrap(); - // } - // - // let result = subject.return_all_errorless_fingerprints(); - // - // assert_eq!( - // result, - // vec![ - // SentTx { - // rowid: 1, - // timestamp: batch_wide_timestamp, - // hash: hash_1, - // attempt: 1, - // amount_minor: amount_1, - // process_error: None - // }, - // SentTx { - // rowid: 2, - // timestamp: batch_wide_timestamp, - // hash: hash_2, - // attempt: 1, - // amount_minor: amount_2, - // process_error: None - // } - // ] - // ) - // } - // - // #[test] - // fn return_all_errorless_fingerprints_works_when_some_records_with_error_marks() { - // let home_dir = ensure_node_home_directory_exists( - // "sent_payable_dao", - // "return_all_errorless_fingerprints_works_when_some_records_with_error_marks", - // ); - // let wrapped_conn = DbInitializerReal::default() - // .initialize(&home_dir, DbInitializationConfig::test_default()) - // .unwrap(); - // let subject = PendingPayableDaoReal::new(wrapped_conn); - // let timestamp = from_unix_timestamp(198_000_000); - // let hash = make_tx_hash(10000); - // let amount = 333; - // let hash_and_amount_1 = HashAndAmount { - // hash: make_tx_hash(11119), - // amount_minor: 2000, - // }; - // let hash_and_amount_2 = HashAndAmount { hash, amount }; - // { - // subject - // .insert_new_fingerprints(&[hash_and_amount_1, hash_and_amount_2], timestamp) - // .unwrap(); - // subject.mark_failures(&[1]).unwrap(); - // } - // - // let result = subject.return_all_errorless_fingerprints(); - // - // assert_eq!( - // result, - // vec![SentTx { - // rowid: 2, - // timestamp, - // hash, - // attempt: 1, - // amount, - // process_error: None - // }] - // ) - // } - // - // #[test] - // #[should_panic( - // expected = "Invalid hash format (\"silly_hash\": Invalid character 'l' at position 0) - database corrupt" - // )] - // fn return_all_errorless_fingerprints_panics_on_malformed_hash() { - // let home_dir = ensure_node_home_directory_exists( - // "sent_payable_dao", - // "return_all_errorless_fingerprints_panics_on_malformed_hash", - // ); - // let wrapped_conn = DbInitializerReal::default() - // .initialize(&home_dir, DbInitializationConfig::test_default()) - // .unwrap(); - // { - // wrapped_conn - // .prepare("insert into pending_payable \ - // (rowid, transaction_hash, amount_high_b, amount_low_b, payable_timestamp, attempt, process_error) \ - // values (1, 'silly_hash', 4, 111, 10000000000, 1, null)") - // .unwrap() - // .execute([]) - // .unwrap(); - // } - // let subject = PendingPayableDaoReal::new(wrapped_conn); - // - // let _ = subject.return_all_errorless_fingerprints(); - // } - // - // #[test] - // fn delete_fingerprints_happy_path() { - // let home_dir = ensure_node_home_directory_exists( - // "sent_payable_dao", - // "delete_fingerprints_happy_path", - // ); - // let conn = DbInitializerReal::default() - // .initialize(&home_dir, DbInitializationConfig::test_default()) - // .unwrap(); - // let subject = PendingPayableDaoReal::new(conn); - // { - // subject - // .insert_new_fingerprints( - // &[ - // HashAndAmount { - // hash: make_tx_hash(1234), - // amount_minor: 1111, - // }, - // HashAndAmount { - // hash: make_tx_hash(2345), - // amount_minor: 5555, - // }, - // HashAndAmount { - // hash: make_tx_hash(3456), - // amount_minor: 2222, - // }, - // ], - // SystemTime::now(), - // ) - // .unwrap(); - // } - // - // let result = subject.delete_fingerprints(&[2, 3]); - // - // assert_eq!(result, Ok(())); - // let records_in_the_db = subject.return_all_errorless_fingerprints(); - // let record_left_in = &records_in_the_db[0]; - // assert_eq!(record_left_in.hash, make_tx_hash(1234)); - // assert_eq!(record_left_in.rowid, 1); - // assert_eq!(records_in_the_db.len(), 1); - // } - // - // #[test] - // fn delete_fingerprints_sad_path() { - // let home_dir = ensure_node_home_directory_exists( - // "sent_payable_dao", - // "delete_fingerprints_sad_path", - // ); - // { - // DbInitializerReal::default() - // .initialize(&home_dir, DbInitializationConfig::test_default()) - // .unwrap(); - // } - // let conn_read_only = Connection::open_with_flags( - // home_dir.join(DATABASE_FILE), - // OpenFlags::SQLITE_OPEN_READ_ONLY, - // ) - // .unwrap(); - // let wrapped_conn = ConnectionWrapperReal::new(conn_read_only); - // let rowid = 45; - // let subject = PendingPayableDaoReal::new(Box::new(wrapped_conn)); - // - // let result = subject.delete_fingerprints(&[rowid]); - // - // assert_eq!( - // result, - // Err(PendingPayableDaoError::RecordDeletion( - // "attempt to write a readonly database".to_string() - // )) - // ) - // } - // - // #[test] - // #[should_panic( - // expected = "deleting sent tx record, expected 2 rows to be changed, but the actual number is 1" - // )] - // fn delete_fingerprints_changed_different_number_of_rows_than_expected() { - // let home_dir = ensure_node_home_directory_exists( - // "sent_payable_dao", - // "delete_fingerprints_changed_different_number_of_rows_than_expected", - // ); - // let conn = DbInitializerReal::default() - // .initialize(&home_dir, DbInitializationConfig::test_default()) - // .unwrap(); - // let rowid_1 = 1; - // let rowid_2 = 2; - // let subject = PendingPayableDaoReal::new(conn); - // { - // subject - // .insert_new_fingerprints( - // &[HashAndAmount { - // hash: make_tx_hash(666666), - // amount_minor: 5555, - // }], - // SystemTime::now(), - // ) - // .unwrap(); - // } - // - // let _ = subject.delete_fingerprints(&[rowid_1, rowid_2]); - // } - // - // #[test] - // fn increment_scan_attempts_works() { - // let home_dir = ensure_node_home_directory_exists( - // "sent_payable_dao", - // "increment_scan_attempts_works", - // ); - // let conn = DbInitializerReal::default() - // .initialize(&home_dir, DbInitializationConfig::test_default()) - // .unwrap(); - // let hash_1 = make_tx_hash(345); - // let hash_2 = make_tx_hash(456); - // let hash_3 = make_tx_hash(567); - // let hash_and_amount_1 = HashAndAmount { - // hash: hash_1, - // amount_minor: 1122, - // }; - // let hash_and_amount_2 = HashAndAmount { - // hash: hash_2, - // amount_minor: 2233, - // }; - // let hash_and_amount_3 = HashAndAmount { - // hash: hash_3, - // amount_minor: 3344, - // }; - // let timestamp = from_unix_timestamp(190_000_000); - // let subject = PendingPayableDaoReal::new(conn); - // { - // subject - // .insert_new_fingerprints( - // &[hash_and_amount_1, hash_and_amount_2, hash_and_amount_3], - // timestamp, - // ) - // .unwrap(); - // } - // - // let result = subject.increment_scan_attempts(&[2, 3]); - // - // assert_eq!(result, Ok(())); - // let mut all_records = subject.return_all_errorless_fingerprints(); - // assert_eq!(all_records.len(), 3); - // let record_1 = all_records.remove(0); - // assert_eq!(record_1.hash, hash_1); - // assert_eq!(record_1.attempt, 1); - // let record_2 = all_records.remove(0); - // assert_eq!(record_2.hash, hash_2); - // assert_eq!(record_2.attempt, 2); - // let record_3 = all_records.remove(0); - // assert_eq!(record_3.hash, hash_3); - // assert_eq!(record_3.attempt, 2); - // } - // - // #[test] - // fn increment_scan_attempts_works_sad_path() { - // let home_dir = ensure_node_home_directory_exists( - // "sent_payable_dao", - // "increment_scan_attempts_works_sad_path", - // ); - // { - // DbInitializerReal::default() - // .initialize(&home_dir, DbInitializationConfig::test_default()) - // .unwrap(); - // } - // let conn_read_only = Connection::open_with_flags( - // home_dir.join(DATABASE_FILE), - // OpenFlags::SQLITE_OPEN_READ_ONLY, - // ) - // .unwrap(); - // let wrapped_conn = ConnectionWrapperReal::new(conn_read_only); - // let subject = PendingPayableDaoReal::new(Box::new(wrapped_conn)); - // - // let result = subject.increment_scan_attempts(&[1]); - // - // assert_eq!( - // result, - // Err(PendingPayableDaoError::UpdateFailed( - // "attempt to write a readonly database".to_string() - // )) - // ) - // } - // - // #[test] - // #[should_panic( - // expected = "Database corrupt: updating fingerprints: expected to update 2 rows but did 0" - // )] - // fn increment_scan_attempts_panics_on_unexpected_row_change_count() { - // let home_dir = ensure_node_home_directory_exists( - // "sent_payable_dao", - // "increment_scan_attempts_panics_on_unexpected_row_change_count", - // ); - // let conn = DbInitializerReal::default() - // .initialize(&home_dir, DbInitializationConfig::test_default()) - // .unwrap(); - // let subject = PendingPayableDaoReal::new(conn); - // - // let _ = subject.increment_scan_attempts(&[1, 2]); - // } - // - // #[test] - // fn mark_failures_works() { - // let home_dir = - // ensure_node_home_directory_exists("sent_payable_dao", "mark_failures_works"); - // let conn = DbInitializerReal::default() - // .initialize(&home_dir, DbInitializationConfig::test_default()) - // .unwrap(); - // let hash_1 = make_tx_hash(555); - // let amount_1 = 1234; - // let hash_2 = make_tx_hash(666); - // let amount_2 = 2345; - // let hash_and_amount_1 = HashAndAmount { - // hash: hash_1, - // amount_minor: amount_1, - // }; - // let hash_and_amount_2 = HashAndAmount { - // hash: hash_2, - // amount_minor: amount_2, - // }; - // let timestamp = from_unix_timestamp(190_000_000); - // let subject = PendingPayableDaoReal::new(conn); - // { - // subject - // .insert_new_fingerprints(&[hash_and_amount_1, hash_and_amount_2], timestamp) - // .unwrap(); - // } - // - // let result = subject.mark_failures(&[2]); - // - // assert_eq!(result, Ok(())); - // let assert_conn = Connection::open(home_dir.join(DATABASE_FILE)).unwrap(); - // let mut assert_stm = assert_conn - // .prepare("select rowid, transaction_hash, amount_high_b, amount_low_b, payable_timestamp, attempt, process_error from pending_payable") - // .unwrap(); - // let found_fingerprints = assert_stm - // .query_map([], |row| { - // let rowid: u64 = row.get(0).unwrap(); - // let transaction_hash: String = row.get(1).unwrap(); - // let amount_high_b: i64 = row.get(2).unwrap(); - // let amount_low_b: i64 = row.get(3).unwrap(); - // let timestamp: i64 = row.get(4).unwrap(); - // let attempt: u16 = row.get(5).unwrap(); - // let process_error: Option = row.get(6).unwrap(); - // Ok(SentTx { - // rowid, - // timestamp: from_unix_timestamp(timestamp), - // hash: H256::from_str(&transaction_hash[2..]).unwrap(), - // attempt, - // amount_minor: checked_conversion::(BigIntDivider::reconstitute( - // amount_high_b, - // amount_low_b, - // )), - // process_error, - // }) - // }) - // .unwrap() - // .flatten() - // .collect::>(); - // assert_eq!( - // *found_fingerprints, - // vec![ - // SentTx { - // rowid: 1, - // timestamp, - // hash: hash_1, - // attempt: 1, - // amount_minor: amount_1, - // process_error: None - // }, - // SentTx { - // rowid: 2, - // timestamp, - // hash: hash_2, - // attempt: 1, - // amount_minor: amount_2, - // process_error: Some("ERROR".to_string()) - // } - // ] - // ) - // } - // - // #[test] - // fn mark_failures_sad_path() { - // let home_dir = - // ensure_node_home_directory_exists("sent_payable_dao", "mark_failures_sad_path"); - // { - // DbInitializerReal::default() - // .initialize(&home_dir, DbInitializationConfig::test_default()) - // .unwrap(); - // } - // let conn_read_only = Connection::open_with_flags( - // home_dir.join(DATABASE_FILE), - // OpenFlags::SQLITE_OPEN_READ_ONLY, - // ) - // .unwrap(); - // let wrapped_conn = ConnectionWrapperReal::new(conn_read_only); - // let subject = PendingPayableDaoReal::new(Box::new(wrapped_conn)); - // - // let result = subject.mark_failures(&[1]); - // - // assert_eq!( - // result, - // Err(PendingPayableDaoError::ErrorMarkFailed( - // "attempt to write a readonly database".to_string() - // )) - // ) - // } - // - // #[test] - // #[should_panic( - // expected = "Database corrupt: marking failure at fingerprints: expected to change 2 rows but did 0" - // )] - // fn mark_failures_panics_on_wrong_row_change_count() { - // let home_dir = ensure_node_home_directory_exists( - // "sent_payable_dao", - // "mark_failures_panics_on_wrong_row_change_count", - // ); - // let conn = DbInitializerReal::default() - // .initialize(&home_dir, DbInitializationConfig::test_default()) - // .unwrap(); - // let subject = PendingPayableDaoReal::new(conn); - // - // let _ = subject.mark_failures(&[10, 20]); - // } -} diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index b8651d990..b9a3d093b 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -1330,7 +1330,7 @@ mod tests { use ethsign_crypto::Keccak256; use log::Level; use masq_lib::constants::{ - REQUEST_WITH_MUTUALLY_EXCLUSIVE_PARAMS, REQUEST_WITH_NO_VALUES, SCAN_ERROR, + DEFAULT_CHAIN, REQUEST_WITH_MUTUALLY_EXCLUSIVE_PARAMS, REQUEST_WITH_NO_VALUES, SCAN_ERROR, VALUE_EXCEEDS_ALLOWED_LIMIT, }; use masq_lib::messages::TopRecordsOrdering::{Age, Balance}; @@ -1340,7 +1340,7 @@ mod tests { }; 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 masq_lib::ui_gateway::MessagePath::Conversation; use masq_lib::ui_gateway::{MessageBody, MessagePath, NodeFromUiMessage, NodeToUiMessage}; use std::any::TypeId; @@ -1370,7 +1370,7 @@ mod tests { #[test] fn new_calls_factories_properly() { - let config = make_bc_with_defaults(); + let config = make_bc_with_defaults(DEFAULT_CHAIN); let payable_dao_factory_params_arc = Arc::new(Mutex::new(vec![])); let sent_payable_dao_factory_params_arc = Arc::new(Mutex::new(vec![])); let failed_payable_dao_factory_params_arc = Arc::new(Mutex::new(vec![])); @@ -1435,7 +1435,8 @@ mod tests { #[test] fn accountant_have_proper_defaulted_values() { - let bootstrapper_config = make_bc_with_defaults(); + let chain = TEST_DEFAULT_CHAIN; + let bootstrapper_config = make_bc_with_defaults(chain); let payable_dao_factory = Box::new( PayableDaoFactoryMock::new() .make_result(PayableDaoMock::new()) // For Accountant @@ -1475,7 +1476,7 @@ mod tests { ); let financial_statistics = result.financial_statistics().clone(); - let default_scan_intervals = ScanIntervals::default(); + let default_scan_intervals = ScanIntervals::compute_default(chain); assert_eq!( result.scan_schedulers.payable.new_payable_interval, default_scan_intervals.payable_scan_interval @@ -1558,7 +1559,7 @@ mod tests { { init_test_logging(); let mut subject = AccountantBuilder::default() - .bootstrapper_config(make_bc_with_defaults()) + .bootstrapper_config(make_bc_with_defaults(TEST_DEFAULT_CHAIN)) .build(); subject.logger = Logger::new("ConfigChange"); @@ -4537,7 +4538,7 @@ mod tests { #[test] fn report_services_consumed_message_is_received() { init_test_logging(); - let config = make_bc_with_defaults(); + let config = make_bc_with_defaults(TEST_DEFAULT_CHAIN); let more_money_payable_params_arc = Arc::new(Mutex::new(vec![])); let payable_dao_mock = PayableDaoMock::new() .more_money_payable_params(more_money_payable_params_arc.clone()) @@ -4879,7 +4880,7 @@ mod tests { expected = "panic message (processed with: node_lib::sub_lib::utils::crash_request_analyzer)" )] fn accountant_can_be_crashed_properly_but_not_improperly() { - let mut config = make_bc_with_defaults(); + let mut config = make_bc_with_defaults(TEST_DEFAULT_CHAIN); config.crash_point = CrashPoint::Message; let accountant = AccountantBuilder::default() .bootstrapper_config(config) @@ -5936,7 +5937,7 @@ mod tests { let receivable_dao = ReceivableDaoMock::new().total_result(987_654_328_996); let system = System::new("test"); let subject = AccountantBuilder::default() - .bootstrapper_config(make_bc_with_defaults()) + .bootstrapper_config(make_bc_with_defaults(TEST_DEFAULT_CHAIN)) .payable_daos(vec![ForAccountantBody(payable_dao)]) .receivable_daos(vec![ForAccountantBody(receivable_dao)]) .build(); diff --git a/node/src/accountant/scanners/scan_schedulers.rs b/node/src/accountant/scanners/scan_schedulers.rs index 03dad9942..dd23e05bd 100644 --- a/node/src/accountant/scanners/scan_schedulers.rs +++ b/node/src/accountant/scanners/scan_schedulers.rs @@ -385,6 +385,7 @@ mod tests { }; use crate::accountant::scanners::{ManulTriggerError, StartScanError}; use crate::sub_lib::accountant::ScanIntervals; + use crate::test_utils::unshared_test_utils::TEST_SCAN_INTERVALS; use itertools::Itertools; use lazy_static::lazy_static; use masq_lib::logger::Logger; @@ -596,7 +597,7 @@ mod tests { #[test] fn resolve_rescheduling_on_error_works_for_pending_payables_if_externally_triggered() { - let subject = ScanSchedulers::new(ScanIntervals::default(), true); + let subject = ScanSchedulers::new(*TEST_SCAN_INTERVALS, true); let test_name = "resolve_rescheduling_on_error_works_for_pending_payables_if_externally_triggered"; @@ -652,7 +653,7 @@ mod tests { fn resolve_error_for_pending_payables_if_nothing_to_process_and_initial_pending_payable_scan_true( ) { init_test_logging(); - let subject = ScanSchedulers::new(ScanIntervals::default(), true); + let subject = ScanSchedulers::new(*TEST_SCAN_INTERVALS, true); let test_name = "resolve_error_for_pending_payables_if_nothing_to_process_and_initial_pending_payable_scan_true"; let logger = Logger::new(test_name); @@ -687,7 +688,7 @@ mod tests { )] fn resolve_error_for_pending_payables_if_nothing_to_process_and_initial_pending_payable_scan_false( ) { - let subject = ScanSchedulers::new(ScanIntervals::default(), true); + let subject = ScanSchedulers::new(*TEST_SCAN_INTERVALS, true); let _ = subject .reschedule_on_error_resolver @@ -706,7 +707,7 @@ mod tests { init_test_logging(); let test_name = "resolve_error_for_pending_p_if_no_consuming_wallet_found_in_initial_pending_payable_scan"; let logger = Logger::new(test_name); - let subject = ScanSchedulers::new(ScanIntervals::default(), true); + let subject = ScanSchedulers::new(*TEST_SCAN_INTERVALS, true); let scanner = PayableSequenceScanner::PendingPayables { initial_pending_payable_scan: true, }; @@ -740,7 +741,7 @@ mod tests { possible" )] fn pending_p_scan_attempt_if_no_consuming_wallet_found_mustnt_happen_if_not_initial_scan() { - let subject = ScanSchedulers::new(ScanIntervals::default(), true); + let subject = ScanSchedulers::new(*TEST_SCAN_INTERVALS, true); let scanner = PayableSequenceScanner::PendingPayables { initial_pending_payable_scan: false, }; @@ -795,7 +796,7 @@ mod tests { StartScanError::NothingToProcess, StartScanError::NoConsumingWalletFound, ]); - let subject = ScanSchedulers::new(ScanIntervals::default(), true); + let subject = ScanSchedulers::new(*TEST_SCAN_INTERVALS, true); test_forbidden_states(&subject, &inputs, false); test_forbidden_states(&subject, &inputs, true); @@ -805,7 +806,7 @@ mod tests { fn resolve_rescheduling_on_error_works_for_retry_payables_if_externally_triggered() { let test_name = "resolve_rescheduling_on_error_works_for_retry_payables_if_externally_triggered"; - let subject = ScanSchedulers::new(ScanIntervals::default(), false); + let subject = ScanSchedulers::new(*TEST_SCAN_INTERVALS, false); test_what_if_externally_triggered( test_name, @@ -816,7 +817,7 @@ mod tests { #[test] fn any_automatic_scan_with_start_scan_error_is_fatal_for_retry_payables() { - let subject = ScanSchedulers::new(ScanIntervals::default(), true); + let subject = ScanSchedulers::new(*TEST_SCAN_INTERVALS, true); ALL_START_SCAN_ERRORS.iter().for_each(|error| { let panic = catch_unwind(AssertUnwindSafe(|| { @@ -849,7 +850,7 @@ mod tests { fn resolve_rescheduling_on_error_works_for_new_payables_if_externally_triggered() { let test_name = "resolve_rescheduling_on_error_works_for_new_payables_if_externally_triggered"; - let subject = ScanSchedulers::new(ScanIntervals::default(), true); + let subject = ScanSchedulers::new(*TEST_SCAN_INTERVALS, true); test_what_if_externally_triggered( test_name, @@ -864,7 +865,7 @@ mod tests { should never interfere with itself ScanAlreadyRunning { cross_scan_cause_opt: None, started_at:" )] fn resolve_hint_for_new_payables_if_scan_is_already_running_error_and_is_automatic_scan() { - let subject = ScanSchedulers::new(ScanIntervals::default(), true); + let subject = ScanSchedulers::new(*TEST_SCAN_INTERVALS, true); let _ = subject .reschedule_on_error_resolver @@ -890,7 +891,7 @@ mod tests { ]); let logger = Logger::new(test_name); let test_log_handler = TestLogHandler::new(); - let subject = ScanSchedulers::new(ScanIntervals::default(), true); + let subject = ScanSchedulers::new(*TEST_SCAN_INTERVALS, true); inputs.errors.iter().for_each(|error| { let result = subject diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs index 81b612e47..2f777e57b 100644 --- a/node/src/accountant/test_utils.rs +++ b/node/src/accountant/test_utils.rs @@ -52,6 +52,7 @@ use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMoc use crate::test_utils::unshared_test_utils::make_bc_with_defaults; use ethereum_types::U64; use masq_lib::logger::Logger; +use masq_lib::test_utils::utils::TEST_DEFAULT_CHAIN; use rusqlite::{Connection, OpenFlags, Row}; use std::any::type_name; use std::cell::RefCell; @@ -431,7 +432,9 @@ impl AccountantBuilder { } pub fn build(self) -> Accountant { - let config = self.config_opt.unwrap_or(make_bc_with_defaults()); + let config = self + .config_opt + .unwrap_or(make_bc_with_defaults(TEST_DEFAULT_CHAIN)); let payable_dao_factory = self.payable_dao_factory_opt.unwrap_or( PayableDaoFactoryMock::new() .make_result(PayableDaoMock::new()) @@ -967,13 +970,13 @@ impl BannedDaoMock { } pub fn bc_from_earning_wallet(earning_wallet: Wallet) -> BootstrapperConfig { - let mut bc = make_bc_with_defaults(); + let mut bc = make_bc_with_defaults(TEST_DEFAULT_CHAIN); bc.earning_wallet = earning_wallet; bc } pub fn bc_from_wallets(consuming_wallet: Wallet, earning_wallet: Wallet) -> BootstrapperConfig { - let mut bc = make_bc_with_defaults(); + let mut bc = make_bc_with_defaults(TEST_DEFAULT_CHAIN); bc.consuming_wallet_opt = Some(consuming_wallet); bc.earning_wallet = earning_wallet; bc diff --git a/node/src/actor_system_factory.rs b/node/src/actor_system_factory.rs index 8b24da722..61c5ff9c0 100644 --- a/node/src/actor_system_factory.rs +++ b/node/src/actor_system_factory.rs @@ -1167,7 +1167,7 @@ mod tests { log_level: LevelFilter::Off, crash_point: CrashPoint::None, dns_servers: vec![], - scan_intervals_opt: Some(ScanIntervals::default()), + scan_intervals_opt: Some(ScanIntervals::compute_default(TEST_DEFAULT_CHAIN)), automatic_scans_enabled: true, clandestine_discriminator_factories: Vec::new(), ui_gateway_config: UiGatewayConfig { ui_port: 5335 }, diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index 1a5cad399..3458a4140 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -38,7 +38,6 @@ use itertools::Itertools; use masq_lib::blockchains::chains::Chain; use masq_lib::constants::DEFAULT_GAS_PRICE_MARGIN; use masq_lib::logger::Logger; -use masq_lib::messages::ScanType; use masq_lib::ui_gateway::NodeFromUiMessage; use regex::Regex; use std::path::Path; @@ -571,10 +570,17 @@ mod tests { 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_accountant_subs_from_recorder, make_blockchain_bridge_subs_from_recorder, make_recorder, peer_actors_builder}; + use crate::test_utils::recorder::{ + make_accountant_subs_from_recorder, make_blockchain_bridge_subs_from_recorder, + make_recorder, peer_actors_builder, + }; 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, AssertionsMessage, SubsFactoryTestAddrLeaker, ZERO}; + 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, AssertionsMessage, + SubsFactoryTestAddrLeaker, ZERO, + }; use crate::test_utils::{make_paying_wallet, make_wallet}; use actix::System; use ethereum_types::U64; @@ -606,7 +612,7 @@ mod tests { } impl SubsFactory - for SubsFactoryTestAddrLeaker + for SubsFactoryTestAddrLeaker { fn make(&self, addr: &Addr) -> BlockchainBridgeSubs { self.send_leaker_msg_and_return_meaningless_subs( @@ -2205,4 +2211,4 @@ mod tests { assert_eq!(increase_gas_price_by_margin(1_000_000_000), 1_300_000_000); assert_eq!(increase_gas_price_by_margin(9_000_000_000), 11_700_000_000); } -} \ No newline at end of file +} diff --git a/node/src/bootstrapper.rs b/node/src/bootstrapper.rs index 71a0751b0..aa3943183 100644 --- a/node/src/bootstrapper.rs +++ b/node/src/bootstrapper.rs @@ -1233,6 +1233,7 @@ mod tests { vec![SocketAddr::new(IpAddr::from_str("1.2.3.4").unwrap(), 1111)]; let mut unprivileged_config = BootstrapperConfig::new(); //values from unprivileged config + let chain = unprivileged_config.blockchain_bridge_config.chain; let gas_price = 123; let blockchain_url_opt = Some("some.service@earth.abc".to_string()); let clandestine_port_opt = Some(44444); @@ -1252,7 +1253,7 @@ mod tests { unprivileged_config.earning_wallet = earning_wallet.clone(); unprivileged_config.consuming_wallet_opt = consuming_wallet_opt.clone(); unprivileged_config.db_password_opt = db_password_opt.clone(); - unprivileged_config.scan_intervals_opt = Some(ScanIntervals::default()); + unprivileged_config.scan_intervals_opt = Some(ScanIntervals::compute_default(chain)); unprivileged_config.automatic_scans_enabled = true; unprivileged_config.when_pending_too_long_sec = DEFAULT_PENDING_TOO_LONG_SEC; @@ -1276,7 +1277,7 @@ mod tests { assert_eq!(privileged_config.db_password_opt, db_password_opt); assert_eq!( privileged_config.scan_intervals_opt, - Some(ScanIntervals::default()) + Some(ScanIntervals::compute_default(chain)) ); assert_eq!(privileged_config.automatic_scans_enabled, true); assert_eq!( diff --git a/node/src/daemon/setup_reporter.rs b/node/src/daemon/setup_reporter.rs index 3d0a79b6b..b03251842 100644 --- a/node/src/daemon/setup_reporter.rs +++ b/node/src/daemon/setup_reporter.rs @@ -21,7 +21,6 @@ use crate::node_configurator::{ data_directory_from_context, determine_user_specific_data, DirsWrapper, DirsWrapperReal, }; use crate::sub_lib::accountant::PaymentThresholds as PaymentThresholdsFromAccountant; -use crate::sub_lib::accountant::DEFAULT_SCAN_INTERVALS; use crate::sub_lib::neighborhood::NodeDescriptor; use crate::sub_lib::neighborhood::{NeighborhoodMode as NeighborhoodModeEnum, DEFAULT_RATE_PACK}; use crate::sub_lib::utils::make_new_multi_config; @@ -1083,12 +1082,16 @@ impl ValueRetriever for ScanIntervals { fn computed_default( &self, - _bootstrapper_config: &BootstrapperConfig, + bootstrapper_config: &BootstrapperConfig, pc: &dyn PersistentConfiguration, _db_password_opt: &Option, ) -> Option<(String, UiSetupResponseValueStatus)> { let pc_value = pc.scan_intervals().expectv("scan-intervals"); - payment_thresholds_rate_pack_and_scan_intervals(pc_value, *DEFAULT_SCAN_INTERVALS) + let chain = bootstrapper_config.blockchain_bridge_config.chain; + payment_thresholds_rate_pack_and_scan_intervals( + pc_value, + crate::sub_lib::accountant::ScanIntervals::compute_default(chain), + ) } fn is_required(&self, _params: &SetupCluster) -> bool { @@ -1208,7 +1211,9 @@ mod tests { use crate::daemon::dns_inspector::dns_inspector::DnsInspector; use crate::daemon::dns_inspector::DnsInspectionError; use crate::daemon::setup_reporter; - use crate::database::db_initializer::{DbInitializer, DbInitializerReal, DATABASE_FILE}; + use crate::database::db_initializer::{ + DbInitializer, DbInitializerReal, InitializationMode, DATABASE_FILE, + }; use crate::database::rusqlite_wrappers::ConnectionWrapperReal; use crate::db_config::config_dao::{ConfigDao, ConfigDaoReal}; use crate::db_config::persistent_configuration::{ @@ -1229,6 +1234,7 @@ mod tests { use crate::test_utils::unshared_test_utils::{ make_persistent_config_real_with_config_dao_null, make_pre_populated_mocked_directory_wrapper, make_simplified_multi_config, + TEST_SCAN_INTERVALS, }; use crate::test_utils::{assert_string_contains, rate_pack}; use core::option::Option; @@ -1335,15 +1341,19 @@ mod tests { "setup_reporter", "get_modified_setup_database_populated_only_requireds_set", ); + let chain = DEFAULT_CHAIN; + let mut init_config = DbInitializationConfig::test_default(); + if let InitializationMode::CreationAndMigration { external_data } = &mut init_config.mode { + external_data.chain = chain + } else { + panic!("unexpected initialization mode"); + } let data_dir = home_dir.join("data_dir"); - let chain_specific_data_dir = data_dir.join(DEFAULT_CHAIN.rec().literal_identifier); + let chain_specific_data_dir = data_dir.join(chain.rec().literal_identifier); std::fs::create_dir_all(&chain_specific_data_dir).unwrap(); let db_initializer = DbInitializerReal::default(); let conn = db_initializer - .initialize( - &chain_specific_data_dir, - DbInitializationConfig::test_default(), - ) + .initialize(&chain_specific_data_dir, init_config) .unwrap(); let mut config = PersistentConfigurationReal::from(conn); config.change_password(None, "password").unwrap(); @@ -1448,7 +1458,7 @@ mod tests { ), ( "scan-intervals", - &DEFAULT_SCAN_INTERVALS.to_string(), + &accountant::ScanIntervals::compute_default(chain).to_string(), Default, ), ("scans", "on", Default), @@ -3358,6 +3368,7 @@ mod tests { fn rate_pack_computed_default_when_persistent_config_like_default() { assert_computed_default_when_persistent_config_like_default( &RatePack {}, + None, DEFAULT_RATE_PACK.to_string(), ) } @@ -3437,15 +3448,19 @@ mod tests { #[test] fn scan_intervals_computed_default_when_persistent_config_like_default() { + let chain = DEFAULT_CHAIN; + let mut bootstrapper_config = BootstrapperConfig::new(); + bootstrapper_config.blockchain_bridge_config.chain = chain; assert_computed_default_when_persistent_config_like_default( &ScanIntervals {}, - *DEFAULT_SCAN_INTERVALS, + Some(bootstrapper_config), + accountant::ScanIntervals::compute_default(chain), ) } #[test] fn scan_intervals_computed_default_persistent_config_unequal_to_default() { - let mut scan_intervals = *DEFAULT_SCAN_INTERVALS; + let mut scan_intervals = *TEST_SCAN_INTERVALS; scan_intervals.payable_scan_interval = scan_intervals .payable_scan_interval .add(Duration::from_secs(15)); @@ -3469,6 +3484,7 @@ mod tests { fn payment_thresholds_computed_default_when_persistent_config_like_default() { assert_computed_default_when_persistent_config_like_default( &PaymentThresholds {}, + None, DEFAULT_PAYMENT_THRESHOLDS.to_string(), ) } @@ -3491,12 +3507,13 @@ mod tests { fn assert_computed_default_when_persistent_config_like_default( subject: &dyn ValueRetriever, + bootstrapper_config_opt: Option, default: T, ) where T: Display + PartialEq, { - let mut bootstrapper_config = BootstrapperConfig::new(); - //the rate_pack within the mode setting does not determine the result, so I just set a nonsense + let mut bootstrapper_config = bootstrapper_config_opt.unwrap_or(BootstrapperConfig::new()); + //the rate_pack within the mode setting does not affect the result, so I set nonsense bootstrapper_config.neighborhood_config.mode = NeighborhoodModeEnum::OriginateOnly(vec![], rate_pack(0)); let persistent_config = diff --git a/node/src/database/config_dumper.rs b/node/src/database/config_dumper.rs index 17e24899e..a1a435818 100644 --- a/node/src/database/config_dumper.rs +++ b/node/src/database/config_dumper.rs @@ -168,7 +168,8 @@ mod tests { use crate::db_config::typed_config_layer::encode_bytes; use crate::node_configurator::DirsWrapperReal; use crate::node_test_utils::DirsWrapperMock; - use crate::sub_lib::accountant::{DEFAULT_PAYMENT_THRESHOLDS, DEFAULT_SCAN_INTERVALS}; + use crate::sub_lib::accountant; + use crate::sub_lib::accountant::DEFAULT_PAYMENT_THRESHOLDS; use crate::sub_lib::cryptde::PlainData; use crate::sub_lib::neighborhood::{NodeDescriptor, DEFAULT_RATE_PACK}; use crate::test_utils::database_utils::bring_db_0_back_to_life_and_return_connection; @@ -327,6 +328,7 @@ mod tests { .initialize(&database_path, DbInitializationConfig::panic_on_migration()) .unwrap(); let dao = ConfigDaoReal::new(conn); + let chain = Chain::PolyMainnet; assert_value("blockchainServiceUrl", "https://infura.io/ID", &map); assert_value("clandestinePort", "3456", &map); assert_encrypted_value( @@ -340,11 +342,7 @@ mod tests { "0x0123456789012345678901234567890123456789", &map, ); - assert_value( - "chainName", - Chain::PolyMainnet.rec().literal_identifier, - &map, - ); + assert_value("chainName", chain.rec().literal_identifier, &map); assert_value("gasPrice", "1", &map); assert_value( "pastNeighbors", @@ -365,8 +363,12 @@ mod tests { &map, ); assert_value("ratePack", &DEFAULT_RATE_PACK.to_string(), &map); - assert_value("scanIntervals", &DEFAULT_SCAN_INTERVALS.to_string(), &map); - assert!(output.ends_with("\n}\n")) //asserting that there is a blank line at the end + assert_value( + "scanIntervals", + &accountant::ScanIntervals::compute_default(chain).to_string(), + &map, + ); + assert!(output.ends_with("\n}\n")) // To assert a blank line at the end } #[test] @@ -510,7 +512,11 @@ mod tests { &map, ); assert_value("ratePack", &DEFAULT_RATE_PACK.to_string(), &map); - assert_value("scanIntervals", &DEFAULT_SCAN_INTERVALS.to_string(), &map); + assert_value( + "scanIntervals", + &accountant::ScanIntervals::compute_default(Chain::PolyMainnet).to_string(), + &map, + ); } #[test] @@ -586,6 +592,7 @@ mod tests { .initialize(&data_dir, DbInitializationConfig::panic_on_migration()) .unwrap(); let dao = Box::new(ConfigDaoReal::new(conn)); + let chain = Chain::PolyMainnet; assert_value("blockchainServiceUrl", "https://infura.io/ID", &map); assert_value("clandestinePort", "3456", &map); assert_encrypted_value( @@ -599,11 +606,7 @@ mod tests { "0x0123456789012345678901234567890123456789", &map, ); - assert_value( - "chainName", - Chain::PolyMainnet.rec().literal_identifier, - &map, - ); + assert_value("chainName", chain.rec().literal_identifier, &map); assert_value("gasPrice", "1", &map); assert_value( "pastNeighbors", @@ -624,7 +627,11 @@ mod tests { &map, ); assert_value("ratePack", &DEFAULT_RATE_PACK.to_string(), &map); - assert_value("scanIntervals", &DEFAULT_SCAN_INTERVALS.to_string(), &map); + assert_value( + "scanIntervals", + &accountant::ScanIntervals::compute_default(chain).to_string(), + &map, + ); } #[test] diff --git a/node/src/database/db_initializer.rs b/node/src/database/db_initializer.rs index 674786766..6eb69b4a6 100644 --- a/node/src/database/db_initializer.rs +++ b/node/src/database/db_initializer.rs @@ -4,7 +4,8 @@ use crate::database::rusqlite_wrappers::{ConnectionWrapper, ConnectionWrapperRea use crate::database::db_migrations::db_migrator::{DbMigrator, DbMigratorReal}; use crate::db_config::secure_config_layer::EXAMPLE_ENCRYPTED; use crate::neighborhood::DEFAULT_MIN_HOPS; -use crate::sub_lib::accountant::{DEFAULT_PAYMENT_THRESHOLDS, DEFAULT_SCAN_INTERVALS}; +use crate::sub_lib::accountant; +use crate::sub_lib::accountant::DEFAULT_PAYMENT_THRESHOLDS; use crate::sub_lib::neighborhood::DEFAULT_RATE_PACK; use crate::sub_lib::utils::db_connection_launch_panic; use masq_lib::blockchains::chains::Chain; @@ -137,7 +138,6 @@ impl DbInitializerReal { Self::create_payable_table(conn); Self::create_sent_payable_table(conn); Self::create_failed_payable_table(conn); - Self::create_pending_payable_table(conn); Self::create_receivable_table(conn); Self::create_banned_table(conn); } @@ -253,7 +253,7 @@ impl DbInitializerReal { Self::set_config_value( conn, "scan_intervals", - Some(&DEFAULT_SCAN_INTERVALS.to_string()), + Some(&accountant::ScanIntervals::compute_default(external_params.chain).to_string()), false, "scan intervals", ); @@ -311,27 +311,6 @@ impl DbInitializerReal { .expect("Can't create transaction hash index in failed payments"); } - pub fn create_pending_payable_table(conn: &Connection) { - conn.execute( - "create table if not exists pending_payable ( - rowid integer primary key, - transaction_hash text not null, - amount_high_b integer not null, - amount_low_b integer not null, - payable_timestamp integer not null, - attempt integer not null, - process_error text null - )", - [], - ) - .expect("Can't create pending_payable table"); - conn.execute( - "CREATE UNIQUE INDEX pending_payable_hash_idx ON pending_payable (transaction_hash)", - [], - ) - .expect("Can't create transaction hash index in pending payments"); - } - pub fn create_payable_table(conn: &Connection) { conn.execute( "create table if not exists payable ( @@ -736,50 +715,6 @@ mod tests { assert_no_index_exists_for_table(conn.as_ref(), "config") } - #[test] - fn db_initialize_creates_pending_payable_table() { - let home_dir = ensure_node_home_directory_does_not_exist( - "db_initializer", - "db_initialize_creates_pending_payable_table", - ); - let subject = DbInitializerReal::default(); - - let conn = subject - .initialize(&home_dir, DbInitializationConfig::test_default()) - .unwrap(); - - let mut stmt = conn - .prepare( - "SELECT rowid, - transaction_hash, - amount_high_b, - amount_low_b, - payable_timestamp, - attempt, - process_error - FROM pending_payable", - ) - .unwrap(); - let result = stmt.execute([]).unwrap(); - assert_eq!(result, 1); - let expected_key_words: &[&[&str]] = &[ - &["rowid", "integer", "primary", "key"], - &["transaction_hash", "text", "not", "null"], - &["amount_high_b", "integer", "not", "null"], - &["amount_low_b", "integer", "not", "null"], - &["payable_timestamp", "integer", "not", "null"], - &["attempt", "integer", "not", "null"], - &["process_error", "text", "null"], - ]; - assert_create_table_stm_contains_all_parts(&*conn, "pending_payable", expected_key_words); - let expected_key_words: &[&[&str]] = &[&["transaction_hash"]]; - assert_index_stm_is_coupled_with_right_parameter( - conn.as_ref(), - "pending_payable_hash_idx", - expected_key_words, - ) - } - #[test] fn db_initialize_creates_sent_payable_table() { let home_dir = ensure_node_home_directory_does_not_exist( @@ -1117,7 +1052,7 @@ mod tests { verify( &mut config_vec, "scan_intervals", - Some(&DEFAULT_SCAN_INTERVALS.to_string()), + Some(&accountant::ScanIntervals::compute_default(TEST_DEFAULT_CHAIN).to_string()), false, ); verify( diff --git a/node/src/database/db_migrations/migrations/migration_10_to_11.rs b/node/src/database/db_migrations/migrations/migration_10_to_11.rs index 5e4e18368..b3f2a157a 100644 --- a/node/src/database/db_migrations/migrations/migration_10_to_11.rs +++ b/node/src/database/db_migrations/migrations/migration_10_to_11.rs @@ -36,9 +36,12 @@ impl DatabaseMigration for Migrate_10_to_11 { status text not null )"; + let sql_statement_for_pending_payable = "drop table pending_payable"; + declaration_utils.execute_upon_transaction(&[ &sql_statement_for_sent_payable, &sql_statement_for_failed_payable, + &sql_statement_for_pending_payable, ]) } @@ -55,10 +58,7 @@ mod tests { use crate::database::test_utils::{ SQL_ATTRIBUTES_FOR_CREATING_FAILED_PAYABLE, SQL_ATTRIBUTES_FOR_CREATING_SENT_PAYABLE, }; - use crate::test_utils::database_utils::{ - assert_create_table_stm_contains_all_parts, assert_table_exists, - bring_db_0_back_to_life_and_return_connection, make_external_data, - }; + use crate::test_utils::database_utils::{assert_create_table_stm_contains_all_parts, assert_table_does_not_exist, assert_table_exists, bring_db_0_back_to_life_and_return_connection, make_external_data}; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; use masq_lib::test_utils::utils::ensure_node_home_directory_exists; use std::fs::create_dir_all; @@ -103,6 +103,7 @@ mod tests { "failed_payable", SQL_ATTRIBUTES_FOR_CREATING_FAILED_PAYABLE, ); + assert_table_does_not_exist(connection.as_ref(), "pending_payable"); TestLogHandler::new().assert_logs_contain_in_order(vec![ "DbMigrator: Database successfully migrated from version 10 to 11", ]); diff --git a/node/src/database/db_migrations/migrations/migration_5_to_6.rs b/node/src/database/db_migrations/migrations/migration_5_to_6.rs index a5f902cb9..b32e3b2d0 100644 --- a/node/src/database/db_migrations/migrations/migration_5_to_6.rs +++ b/node/src/database/db_migrations/migrations/migration_5_to_6.rs @@ -2,8 +2,10 @@ use crate::database::db_migrations::db_migrator::DatabaseMigration; use crate::database::db_migrations::migrator_utils::DBMigDeclarator; -use crate::sub_lib::accountant::{DEFAULT_PAYMENT_THRESHOLDS, DEFAULT_SCAN_INTERVALS}; +use crate::sub_lib::accountant; +use crate::sub_lib::accountant::DEFAULT_PAYMENT_THRESHOLDS; use crate::sub_lib::neighborhood::DEFAULT_RATE_PACK; +use masq_lib::blockchains::chains::Chain; #[allow(non_camel_case_types)] pub struct Migrate_5_to_6; @@ -19,9 +21,18 @@ impl DatabaseMigration for Migrate_5_to_6 { ); let statement_2 = Self::make_initialization_statement("rate_pack", &DEFAULT_RATE_PACK.to_string()); + let tx = declaration_utils.transaction(); + let chain = tx + .prepare("SELECT value FROM config WHERE name = 'chain_name'") + .expect("internal error") + .query_row([], |row| { + let res_str = row.get::<_, String>(0); + res_str.map(|str| Chain::from(str.as_str())) + }) + .expect("failed to read the chain from db"); let statement_3 = Self::make_initialization_statement( "scan_intervals", - &DEFAULT_SCAN_INTERVALS.to_string(), + &accountant::ScanIntervals::compute_default(chain).to_string(), ); declaration_utils.execute_upon_transaction(&[&statement_1, &statement_2, &statement_3]) } @@ -45,11 +56,13 @@ mod tests { use crate::database::db_initializer::{ DbInitializationConfig, DbInitializer, DbInitializerReal, DATABASE_FILE, }; - use crate::sub_lib::accountant::{DEFAULT_PAYMENT_THRESHOLDS, DEFAULT_SCAN_INTERVALS}; + use crate::sub_lib::accountant; + use crate::sub_lib::accountant::DEFAULT_PAYMENT_THRESHOLDS; use crate::sub_lib::neighborhood::DEFAULT_RATE_PACK; use crate::test_utils::database_utils::{ bring_db_0_back_to_life_and_return_connection, make_external_data, retrieve_config_row, }; + use masq_lib::blockchains::chains::Chain; use masq_lib::test_utils::utils::ensure_node_home_directory_exists; #[test] @@ -59,15 +72,21 @@ mod tests { let db_path = dir_path.join(DATABASE_FILE); let _ = bring_db_0_back_to_life_and_return_connection(&db_path); let subject = DbInitializerReal::default(); - { - subject + let chain = { + let conn = subject .initialize_to_version( &dir_path, 5, DbInitializationConfig::create_or_migrate(make_external_data()), ) .unwrap(); - } + let chain = conn + .prepare("SELECT value FROM config WHERE name = 'chain_name'") + .unwrap() + .query_row([], |row| row.get::<_, String>(0)) + .unwrap(); + chain + }; let result = subject.initialize_to_version( &dir_path, @@ -88,7 +107,12 @@ mod tests { assert_eq!(encrypted, false); let (scan_intervals, encrypted) = retrieve_config_row(connection.as_ref(), "scan_intervals"); - assert_eq!(scan_intervals, Some(DEFAULT_SCAN_INTERVALS.to_string())); + assert_eq!( + scan_intervals, + Some( + accountant::ScanIntervals::compute_default(Chain::from(chain.as_str())).to_string() + ) + ); assert_eq!(encrypted, false); } } diff --git a/node/src/db_config/config_dao_null.rs b/node/src/db_config/config_dao_null.rs index f1fc58cd4..8cd87c075 100644 --- a/node/src/db_config/config_dao_null.rs +++ b/node/src/db_config/config_dao_null.rs @@ -4,13 +4,13 @@ use crate::database::db_initializer::DbInitializerReal; use crate::database::rusqlite_wrappers::TransactionSafeWrapper; use crate::db_config::config_dao::{ConfigDao, ConfigDaoError, ConfigDaoRecord}; use crate::neighborhood::DEFAULT_MIN_HOPS; -use crate::sub_lib::accountant::{DEFAULT_PAYMENT_THRESHOLDS, DEFAULT_SCAN_INTERVALS}; +use crate::sub_lib::accountant; +use crate::sub_lib::accountant::DEFAULT_PAYMENT_THRESHOLDS; use crate::sub_lib::neighborhood::DEFAULT_RATE_PACK; use itertools::Itertools; use masq_lib::blockchains::chains::Chain; use masq_lib::constants::{CURRENT_SCHEMA_VERSION, DEFAULT_GAS_PRICE}; use std::collections::HashMap; - /* This class exists because the Daemon uses the same configuration code that the Node uses, and @@ -139,7 +139,10 @@ impl Default for ConfigDaoNull { ); data.insert( "scan_intervals".to_string(), - (Some(DEFAULT_SCAN_INTERVALS.to_string()), false), + ( + Some(accountant::ScanIntervals::compute_default(Chain::default()).to_string()), + false, + ), ); data.insert("max_block_count".to_string(), (None, false)); Self { data } @@ -208,7 +211,7 @@ mod tests { subject.get("scan_intervals").unwrap(), ConfigDaoRecord::new( "scan_intervals", - Some(&DEFAULT_SCAN_INTERVALS.to_string()), + Some(&accountant::ScanIntervals::compute_default(Chain::default()).to_string()), false ) ); diff --git a/node/src/node_configurator/unprivileged_parse_args_configuration.rs b/node/src/node_configurator/unprivileged_parse_args_configuration.rs index 801aa4456..a66e74c5f 100644 --- a/node/src/node_configurator/unprivileged_parse_args_configuration.rs +++ b/node/src/node_configurator/unprivileged_parse_args_configuration.rs @@ -330,7 +330,7 @@ fn get_public_ip(multi_config: &MultiConfig) -> Result match IpAddr::from_str(&ip_str) { Ok(ip_addr) => Ok(ip_addr), - Err(_) => todo!("Drive in a better error message"), //Err(ConfiguratorError::required("ip", &format! ("blockety blip: '{}'", ip_str), + Err(_) => todo!("Drive in a better error message. The multiconfig wouldn't allow a bad format, though."), //Err(ConfiguratorError::required("ip", &format! ("blockety blip: '{}'", ip_str), }, None => Ok(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))), // sentinel: means "Try Automap" } @@ -494,7 +494,7 @@ fn configure_accountant_config( |pc: &mut dyn PersistentConfiguration, curves| pc.set_payment_thresholds(curves), )?; - check_payment_thresholds(&payment_thresholds)?; + validate_payment_thresholds(&payment_thresholds)?; let scan_intervals = process_combined_params( "scan-intervals", @@ -505,6 +505,8 @@ fn configure_accountant_config( |pc: &mut dyn PersistentConfiguration, intervals| pc.set_scan_intervals(intervals), )?; + validate_scan_intervals(&scan_intervals)?; + let automatic_scans_enabled = value_m!(multi_config, "scans", String).unwrap_or_else(|| "on".to_string()) == "on"; @@ -515,12 +517,13 @@ fn configure_accountant_config( Ok(()) } -fn check_payment_thresholds( +fn validate_payment_thresholds( payment_thresholds: &PaymentThresholds, ) -> Result<(), ConfiguratorError> { if payment_thresholds.debt_threshold_gwei <= payment_thresholds.permanent_debt_allowed_gwei { let msg = format!( - "Value of DebtThresholdGwei ({}) must be bigger than PermanentDebtAllowedGwei ({})", + "Value of DebtThresholdGwei ({}) must be bigger than PermanentDebtAllowedGwei ({}) \ + as the smallest value", payment_thresholds.debt_threshold_gwei, payment_thresholds.permanent_debt_allowed_gwei ); return Err(ConfiguratorError::required("payment-thresholds", &msg)); @@ -534,6 +537,21 @@ fn check_payment_thresholds( Ok(()) } +fn validate_scan_intervals(scan_intervals: &ScanIntervals) -> Result<(), ConfiguratorError> { + if scan_intervals.payable_scan_interval < scan_intervals.pending_payable_scan_interval { + Err(ConfiguratorError::required( + "scan-intervals", + &format!( + "The PendingPayableScanInterval value ({} s) must not exceed the PayableScanInterval \ + value ({} s) and should ideally be approximately half of it", + scan_intervals.pending_payable_scan_interval.as_secs(), + scan_intervals.payable_scan_interval.as_secs()), + )) + } else { + Ok(()) + } +} + fn configure_rate_pack( multi_config: &MultiConfig, persist_config: &mut dyn PersistentConfiguration, @@ -2099,8 +2117,8 @@ mod tests { } #[test] - fn configure_accountant_config_discovers_invalid_payment_thresholds_params_combination_given_from_users_input( - ) { + fn configure_accountant_config_discovers_invalid_payment_thresholds_combination_in_users_input() + { let multi_config = make_simplified_multi_config([ "--payment-thresholds", "19999|10000|1000|20000|1000|20000", @@ -2116,7 +2134,8 @@ mod tests { &mut persistent_config, ); - let expected_msg = "Value of DebtThresholdGwei (19999) must be bigger than PermanentDebtAllowedGwei (20000)"; + let expected_msg = "Value of DebtThresholdGwei (19999) must be bigger than \ + PermanentDebtAllowedGwei (20000) as the smallest value"; assert_eq!( result, Err(ConfiguratorError::required( @@ -2127,14 +2146,15 @@ mod tests { } #[test] - fn check_payment_thresholds_works_for_equal_debt_parameters() { + fn validate_payment_thresholds_works_for_equal_debt_parameters() { let mut payment_thresholds = *DEFAULT_PAYMENT_THRESHOLDS; payment_thresholds.permanent_debt_allowed_gwei = 10000; payment_thresholds.debt_threshold_gwei = 10000; - let result = check_payment_thresholds(&payment_thresholds); + let result = validate_payment_thresholds(&payment_thresholds); - let expected_msg = "Value of DebtThresholdGwei (10000) must be bigger than PermanentDebtAllowedGwei (10000)"; + let expected_msg = "Value of DebtThresholdGwei (10000) must be bigger than \ + PermanentDebtAllowedGwei (10000) as the smallest value"; assert_eq!( result, Err(ConfiguratorError::required( @@ -2145,14 +2165,15 @@ mod tests { } #[test] - fn check_payment_thresholds_works_for_too_small_debt_threshold() { + fn validate_payment_thresholds_works_for_too_small_debt_threshold() { let mut payment_thresholds = *DEFAULT_PAYMENT_THRESHOLDS; payment_thresholds.permanent_debt_allowed_gwei = 10000; payment_thresholds.debt_threshold_gwei = 9999; - let result = check_payment_thresholds(&payment_thresholds); + let result = validate_payment_thresholds(&payment_thresholds); - let expected_msg = "Value of DebtThresholdGwei (9999) must be bigger than PermanentDebtAllowedGwei (10000)"; + let expected_msg = "Value of DebtThresholdGwei (9999) must be bigger than \ + PermanentDebtAllowedGwei (10000) as the smallest value"; assert_eq!( result, Err(ConfiguratorError::required( @@ -2163,7 +2184,8 @@ mod tests { } #[test] - fn check_payment_thresholds_does_not_permit_threshold_interval_longer_than_1_000_000_000_s() { + fn validate_payment_thresholds_does_not_permit_threshold_interval_longer_than_1_000_000_000_s() + { //this goes to the furthest extreme where the delta of debt limits is just 1 gwei, which, //if divided by the slope interval equal or longer 10^9 and rounded, gives 0 let mut payment_thresholds = *DEFAULT_PAYMENT_THRESHOLDS; @@ -2171,7 +2193,7 @@ mod tests { payment_thresholds.debt_threshold_gwei = 101; payment_thresholds.threshold_interval_sec = 1_000_000_001; - let result = check_payment_thresholds(&payment_thresholds); + let result = validate_payment_thresholds(&payment_thresholds); let expected_msg = "Value of ThresholdIntervalSec must not exceed 1,000,000,000 s"; assert_eq!( @@ -2186,6 +2208,28 @@ mod tests { assert_eq!(last_value_possible, -1) } + #[test] + fn configure_accountant_config_discovers_invalid_scan_intervals_combination_in_users_input() { + let multi_config = make_simplified_multi_config(["--scan-intervals", "600|601|600"]); + let mut bootstrapper_config = BootstrapperConfig::new(); + let mut persistent_config = + configure_default_persistent_config(ACCOUNTANT_CONFIG_PARAMS | MAPPING_PROTOCOL) + .set_scan_intervals_result(Ok(())); + + let result = configure_accountant_config( + &multi_config, + &mut bootstrapper_config, + &mut persistent_config, + ); + + let expected_msg = "The PendingPayableScanInterval value (601 s) must not exceed \ + the PayableScanInterval value (600 s) and should ideally be approximately half of it"; + assert_eq!( + result, + Err(ConfiguratorError::required("scan-intervals", expected_msg)) + ) + } + #[test] fn unprivileged_parse_args_with_invalid_consuming_wallet_private_key_reacts_correctly() { running_test(); diff --git a/node/src/sub_lib/accountant.rs b/node/src/sub_lib/accountant.rs index 78379b890..317070c09 100644 --- a/node/src/sub_lib/accountant.rs +++ b/node/src/sub_lib/accountant.rs @@ -17,6 +17,7 @@ use crate::sub_lib::wallet::Wallet; use actix::Recipient; use actix::{Addr, Message}; use lazy_static::lazy_static; +use masq_lib::blockchains::chains::Chain; use masq_lib::ui_gateway::NodeFromUiMessage; use std::fmt::{Debug, Formatter}; use std::str::FromStr; @@ -37,11 +38,6 @@ lazy_static! { threshold_interval_sec: 21600, unban_below_gwei: 500_000_000, }; - pub static ref DEFAULT_SCAN_INTERVALS: ScanIntervals = ScanIntervals { - payable_scan_interval: Duration::from_secs(600), - pending_payable_scan_interval: Duration::from_secs(60), - receivable_scan_interval: Duration::from_secs(600) - }; } //please, alphabetical order @@ -85,9 +81,15 @@ pub struct ScanIntervals { pub receivable_scan_interval: Duration, } -impl Default for ScanIntervals { - fn default() -> Self { - *DEFAULT_SCAN_INTERVALS +impl ScanIntervals { + pub fn compute_default(chain: Chain) -> Self { + Self { + payable_scan_interval: Duration::from_secs(600), + pending_payable_scan_interval: Duration::from_secs( + chain.rec().default_pending_payable_interval_sec, + ), + receivable_scan_interval: Duration::from_secs(600), + } } } @@ -207,12 +209,12 @@ mod tests { use crate::sub_lib::accountant::{ AccountantSubsFactoryReal, DetailedScanType, MessageIdGenerator, MessageIdGeneratorReal, PaymentThresholds, ScanIntervals, SubsFactory, DEFAULT_EARNING_WALLET, - DEFAULT_PAYMENT_THRESHOLDS, DEFAULT_SCAN_INTERVALS, MSG_ID_INCREMENTER, - TEMPORARY_CONSUMING_WALLET, + DEFAULT_PAYMENT_THRESHOLDS, MSG_ID_INCREMENTER, TEMPORARY_CONSUMING_WALLET, }; use crate::sub_lib::wallet::Wallet; use crate::test_utils::recorder::{make_accountant_subs_from_recorder, Recorder}; use actix::Actor; + use masq_lib::blockchains::chains::Chain; use masq_lib::messages::ScanType; use std::str::FromStr; use std::sync::atomic::Ordering; @@ -252,12 +254,6 @@ mod tests { threshold_interval_sec: 21600, unban_below_gwei: 500_000_000, }; - let scan_intervals_expected = ScanIntervals { - payable_scan_interval: Duration::from_secs(600), - pending_payable_scan_interval: Duration::from_secs(60), - receivable_scan_interval: Duration::from_secs(600), - }; - assert_eq!(*DEFAULT_SCAN_INTERVALS, scan_intervals_expected); assert_eq!(*DEFAULT_PAYMENT_THRESHOLDS, payment_thresholds_expected); assert_eq!(*DEFAULT_EARNING_WALLET, default_earning_wallet_expected); assert_eq!( @@ -310,4 +306,34 @@ mod tests { assert_eq!(id, 0) } + + #[test] + fn default_for_scan_intervals_can_be_computed() { + let chain_a = Chain::BaseMainnet; + let chain_b = Chain::PolyMainnet; + + let result_a = ScanIntervals::compute_default(chain_a); + let result_b = ScanIntervals::compute_default(chain_b); + + assert_eq!( + result_a, + ScanIntervals { + payable_scan_interval: Duration::from_secs(600), + pending_payable_scan_interval: Duration::from_secs( + chain_a.rec().default_pending_payable_interval_sec + ), + receivable_scan_interval: Duration::from_secs(600), + } + ); + assert_eq!( + result_b, + ScanIntervals { + payable_scan_interval: Duration::from_secs(600), + pending_payable_scan_interval: Duration::from_secs( + chain_b.rec().default_pending_payable_interval_sec + ), + receivable_scan_interval: Duration::from_secs(600), + } + ); + } } diff --git a/node/src/sub_lib/combined_parameters.rs b/node/src/sub_lib/combined_parameters.rs index 53a3e8488..bd26eb627 100644 --- a/node/src/sub_lib/combined_parameters.rs +++ b/node/src/sub_lib/combined_parameters.rs @@ -307,6 +307,7 @@ mod tests { use super::*; use crate::sub_lib::combined_parameters::CombinedParamsDataTypes::U128; use crate::sub_lib::neighborhood::DEFAULT_RATE_PACK; + use crate::test_utils::unshared_test_utils::TEST_SCAN_INTERVALS; use std::panic::catch_unwind; #[test] @@ -455,7 +456,7 @@ mod tests { let panic_3 = catch_unwind(|| { let _: &[(&str, CombinedParamsDataTypes)] = - (&CombinedParams::ScanIntervals(Initialized(ScanIntervals::default()))).into(); + (&CombinedParams::ScanIntervals(Initialized(*TEST_SCAN_INTERVALS))).into(); }) .unwrap_err(); let panic_3_msg = panic_3.downcast_ref::().unwrap(); @@ -464,7 +465,7 @@ mod tests { panic_3_msg, &format!( "should be called only on uninitialized object, not: ScanIntervals(Initialized({:?}))", - ScanIntervals::default() + *TEST_SCAN_INTERVALS ) ); } @@ -502,7 +503,7 @@ mod tests { ); let panic_3 = catch_unwind(|| { - (&CombinedParams::ScanIntervals(Initialized(ScanIntervals::default()))) + (&CombinedParams::ScanIntervals(Initialized(*TEST_SCAN_INTERVALS))) .initialize_objects(HashMap::new()); }) .unwrap_err(); @@ -512,7 +513,7 @@ mod tests { panic_3_msg, &format!( "should be called only on uninitialized object, not: ScanIntervals(Initialized({:?}))", - ScanIntervals::default() + *TEST_SCAN_INTERVALS ) ); } diff --git a/node/src/test_utils/database_utils.rs b/node/src/test_utils/database_utils.rs index fb8ba3a83..a2b6d9ee1 100644 --- a/node/src/test_utils/database_utils.rs +++ b/node/src/test_utils/database_utils.rs @@ -109,9 +109,10 @@ pub fn assert_table_exists(conn: &dyn ConnectionWrapper, table_name: &str) { } pub fn assert_table_does_not_exist(conn: &dyn ConnectionWrapper, table_name: &str) { - let error_stm = conn - .prepare(&format!("select * from {}", table_name)) - .unwrap_err(); + let error_stm = match conn.prepare(&format!("select * from {}", table_name)) { + Ok(_) => panic!("Table {} should not exist, but it does", table_name), + Err(e) => e, + }; let error_msg = match error_stm { Error::SqliteFailure(_, Some(msg)) => msg, x => panic!("we expected SqliteFailure but we got: {:?}", x), diff --git a/node/src/test_utils/mod.rs b/node/src/test_utils/mod.rs index 546149ae6..601ee7bd1 100644 --- a/node/src/test_utils/mod.rs +++ b/node/src/test_utils/mod.rs @@ -548,6 +548,7 @@ pub mod unshared_test_utils { use crossbeam_channel::{unbounded, Receiver, Sender}; use itertools::Either; use lazy_static::lazy_static; + use masq_lib::blockchains::chains::Chain; use masq_lib::constants::HTTP_PORT; use masq_lib::messages::{ToMessageBody, UiCrashRequest}; use masq_lib::multi_config::MultiConfig; @@ -642,6 +643,14 @@ pub mod unshared_test_utils { MultiConfig::new_test_only(arg_matches) } + lazy_static! { + pub static ref TEST_SCAN_INTERVALS: ScanIntervals = ScanIntervals { + payable_scan_interval: Duration::from_secs(600), + pending_payable_scan_interval: Duration::from_secs(360), + receivable_scan_interval: Duration::from_secs(600), + }; + } + pub const ZERO: u32 = 0b0; pub const MAPPING_PROTOCOL: u32 = 0b000010; pub const ACCOUNTANT_CONFIG_PARAMS: u32 = 0b000100; @@ -686,16 +695,16 @@ pub mod unshared_test_utils { ) -> PersistentConfigurationMock { persistent_config_mock .payment_thresholds_result(Ok(PaymentThresholds::default())) - .scan_intervals_result(Ok(ScanIntervals::default())) + .scan_intervals_result(Ok(*TEST_SCAN_INTERVALS)) } pub fn make_persistent_config_real_with_config_dao_null() -> PersistentConfigurationReal { PersistentConfigurationReal::new(Box::new(ConfigDaoNull::default())) } - pub fn make_bc_with_defaults() -> BootstrapperConfig { + pub fn make_bc_with_defaults(chain: Chain) -> BootstrapperConfig { let mut config = BootstrapperConfig::new(); - config.scan_intervals_opt = Some(ScanIntervals::default()); + config.scan_intervals_opt = Some(ScanIntervals::compute_default(chain)); config.automatic_scans_enabled = true; config.when_pending_too_long_sec = DEFAULT_PENDING_TOO_LONG_SEC; config.payment_thresholds_opt = Some(PaymentThresholds::default());