diff --git a/masq_lib/src/lib.rs b/masq_lib/src/lib.rs index ae638163e..c7e2b107a 100644 --- a/masq_lib/src/lib.rs +++ b/masq_lib/src/lib.rs @@ -24,6 +24,7 @@ pub mod crash_point; pub mod data_version; pub mod exit_locations; pub mod shared_schema; +pub mod simple_clock; pub mod test_utils; pub mod ui_gateway; pub mod ui_traffic_converter; diff --git a/masq_lib/src/simple_clock.rs b/masq_lib/src/simple_clock.rs new file mode 100644 index 000000000..35bc34e97 --- /dev/null +++ b/masq_lib/src/simple_clock.rs @@ -0,0 +1,16 @@ +// Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use std::time::SystemTime; + +pub trait SimpleClock { + fn now(&self) -> SystemTime; +} + +#[derive(Default)] +pub struct SimpleClockReal {} + +impl SimpleClock for SimpleClockReal { + fn now(&self) -> SystemTime { + SystemTime::now() + } +} diff --git a/masq_lib/src/test_utils/mod.rs b/masq_lib/src/test_utils/mod.rs index 2dd76c962..293b48db0 100644 --- a/masq_lib/src/test_utils/mod.rs +++ b/masq_lib/src/test_utils/mod.rs @@ -5,5 +5,6 @@ pub mod fake_stream_holder; pub mod logging; pub mod mock_blockchain_client_server; pub mod mock_websockets_server; +pub mod simple_clock; pub mod ui_connection; pub mod utils; diff --git a/node/src/accountant/scanners/pending_payable_scanner/test_utils.rs b/masq_lib/src/test_utils/simple_clock.rs similarity index 67% rename from node/src/accountant/scanners/pending_payable_scanner/test_utils.rs rename to masq_lib/src/test_utils/simple_clock.rs index 473fd28cb..d4fa5f29e 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/test_utils.rs +++ b/masq_lib/src/test_utils/simple_clock.rs @@ -1,21 +1,21 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::blockchain::errors::validation_status::ValidationFailureClock; +use crate::simple_clock::SimpleClock; use std::cell::RefCell; use std::time::SystemTime; #[derive(Default)] -pub struct ValidationFailureClockMock { +pub struct SimpleClockMock { now_results: RefCell>, } -impl ValidationFailureClock for ValidationFailureClockMock { +impl SimpleClock for SimpleClockMock { fn now(&self) -> SystemTime { self.now_results.borrow_mut().remove(0) } } -impl ValidationFailureClockMock { +impl SimpleClockMock { pub fn now_result(self, result: SystemTime) -> Self { self.now_results.borrow_mut().push(result); self diff --git a/node/src/accountant/db_access_objects/failed_payable_dao.rs b/node/src/accountant/db_access_objects/failed_payable_dao.rs index 7d4644ffa..2b83fdfab 100644 --- a/node/src/accountant/db_access_objects/failed_payable_dao.rs +++ b/node/src/accountant/db_access_objects/failed_payable_dao.rs @@ -427,17 +427,16 @@ mod tests { }; use crate::accountant::db_access_objects::utils::current_unix_timestamp; use crate::accountant::db_access_objects::Transaction; - use crate::accountant::scanners::pending_payable_scanner::test_utils::ValidationFailureClockMock; use crate::blockchain::errors::rpc_errors::{AppRpcErrorKind, LocalErrorKind, RemoteErrorKind}; - use crate::blockchain::errors::validation_status::{ - PreviousAttempts, ValidationFailureClockReal, ValidationStatus, - }; + use crate::blockchain::errors::validation_status::{PreviousAttempts, ValidationStatus}; use crate::blockchain::errors::BlockchainErrorKind; use crate::blockchain::test_utils::{make_address, make_tx_hash}; use crate::database::db_initializer::{ DbInitializationConfig, DbInitializer, DbInitializerReal, }; use crate::database::test_utils::ConnectionWrapperMock; + use masq_lib::simple_clock::SimpleClockReal; + use masq_lib::test_utils::simple_clock::SimpleClockMock; use masq_lib::test_utils::utils::ensure_node_home_directory_exists; use rusqlite::Connection; use std::collections::{BTreeSet, HashMap}; @@ -701,7 +700,7 @@ mod tests { #[test] fn failure_status_from_str_works() { - let validation_failure_clock = ValidationFailureClockMock::default().now_result( + let validation_failure_clock = SimpleClockMock::default().now_result( SystemTime::UNIX_EPOCH .add(Duration::from_secs(1755080031)) .add(Duration::from_nanos(612180914)), @@ -831,7 +830,7 @@ mod tests { BlockchainErrorKind::AppRpc(AppRpcErrorKind::Remote( RemoteErrorKind::Unreachable, )), - &ValidationFailureClockReal::default(), + &SimpleClockReal::default(), ), ))) .build(); @@ -950,7 +949,7 @@ mod tests { ])) .unwrap(); let timestamp = SystemTime::now(); - let clock = ValidationFailureClockMock::default() + let clock = SimpleClockMock::default() .now_result(timestamp) .now_result(timestamp); let hashmap = HashMap::from([ diff --git a/node/src/accountant/db_access_objects/sent_payable_dao.rs b/node/src/accountant/db_access_objects/sent_payable_dao.rs index d0edbfa34..471e352aa 100644 --- a/node/src/accountant/db_access_objects/sent_payable_dao.rs +++ b/node/src/accountant/db_access_objects/sent_payable_dao.rs @@ -540,13 +540,10 @@ mod tests { make_read_only_db_connection, make_sent_tx, TxBuilder, }; use crate::accountant::db_access_objects::Transaction; - use crate::accountant::scanners::pending_payable_scanner::test_utils::ValidationFailureClockMock; use crate::blockchain::blockchain_interface::data_structures::TxBlock; use crate::blockchain::errors::internal_errors::InternalErrorKind; use crate::blockchain::errors::rpc_errors::{AppRpcErrorKind, LocalErrorKind, RemoteErrorKind}; - use crate::blockchain::errors::validation_status::{ - PreviousAttempts, ValidationFailureClockReal, ValidationStatus, - }; + use crate::blockchain::errors::validation_status::{PreviousAttempts, ValidationStatus}; use crate::blockchain::errors::BlockchainErrorKind; use crate::blockchain::test_utils::{make_address, make_block_hash, make_tx_hash}; use crate::database::db_initializer::{ @@ -554,6 +551,8 @@ mod tests { }; use crate::database::test_utils::ConnectionWrapperMock; use ethereum_types::{H256, U64}; + use masq_lib::simple_clock::SimpleClockReal; + use masq_lib::test_utils::simple_clock::SimpleClockMock; use masq_lib::test_utils::utils::ensure_node_home_directory_exists; use rusqlite::Connection; use std::cmp::Ordering; @@ -578,13 +577,13 @@ mod tests { BlockchainErrorKind::AppRpc(AppRpcErrorKind::Remote( RemoteErrorKind::Unreachable, )), - &ValidationFailureClockReal::default(), + &SimpleClockReal::default(), ) .add_attempt( BlockchainErrorKind::AppRpc(AppRpcErrorKind::Remote( RemoteErrorKind::Unreachable, )), - &ValidationFailureClockReal::default(), + &SimpleClockReal::default(), ), ))) .build(); @@ -822,7 +821,7 @@ mod tests { BlockchainErrorKind::AppRpc(AppRpcErrorKind::Remote( RemoteErrorKind::Unreachable, )), - &ValidationFailureClockReal::default(), + &SimpleClockReal::default(), ), ))) .build(); @@ -1205,7 +1204,7 @@ mod tests { let mut tx2 = make_sent_tx(789); tx2.status = TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new( BlockchainErrorKind::AppRpc(AppRpcErrorKind::Remote(RemoteErrorKind::Unreachable)), - &ValidationFailureClockMock::default().now_result(timestamp_b), + &SimpleClockMock::default().now_result(timestamp_b), ))); let mut tx3 = make_sent_tx(123); tx3.status = TxStatus::Pending(ValidationStatus::Waiting); @@ -1217,7 +1216,7 @@ mod tests { tx1.hash, TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new( BlockchainErrorKind::AppRpc(AppRpcErrorKind::Local(LocalErrorKind::Internal)), - &ValidationFailureClockMock::default().now_result(timestamp_a), + &SimpleClockMock::default().now_result(timestamp_a), ))), ), ( @@ -1227,13 +1226,13 @@ mod tests { BlockchainErrorKind::AppRpc(AppRpcErrorKind::Remote( RemoteErrorKind::Unreachable, )), - &ValidationFailureClockMock::default().now_result(timestamp_b), + &SimpleClockMock::default().now_result(timestamp_b), ) .add_attempt( BlockchainErrorKind::AppRpc(AppRpcErrorKind::Remote( RemoteErrorKind::Unreachable, )), - &ValidationFailureClockReal::default(), + &SimpleClockReal::default(), ), )), ), @@ -1266,7 +1265,7 @@ mod tests { updated_txs[1].status, TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new( BlockchainErrorKind::AppRpc(AppRpcErrorKind::Local(LocalErrorKind::Internal)), - &ValidationFailureClockMock::default().now_result(timestamp_a) + &SimpleClockMock::default().now_result(timestamp_a) ))) ); assert_eq!( @@ -1276,13 +1275,13 @@ mod tests { BlockchainErrorKind::AppRpc(AppRpcErrorKind::Remote( RemoteErrorKind::Unreachable )), - &ValidationFailureClockMock::default().now_result(timestamp_b) + &SimpleClockMock::default().now_result(timestamp_b) ) .add_attempt( BlockchainErrorKind::AppRpc(AppRpcErrorKind::Remote( RemoteErrorKind::Unreachable )), - &ValidationFailureClockReal::default() + &SimpleClockReal::default() ) )) ); @@ -1318,7 +1317,7 @@ mod tests { make_tx_hash(1), TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new( BlockchainErrorKind::AppRpc(AppRpcErrorKind::Remote(RemoteErrorKind::Unreachable)), - &ValidationFailureClockReal::default(), + &SimpleClockReal::default(), ))), )])); @@ -1512,8 +1511,8 @@ mod tests { #[test] fn tx_status_from_str_works() { - let validation_failure_clock = ValidationFailureClockMock::default() - .now_result(UNIX_EPOCH.add(Duration::from_secs(12456))); + let validation_failure_clock = + SimpleClockMock::default().now_result(UNIX_EPOCH.add(Duration::from_secs(12456))); assert_eq!( TxStatus::from_str(r#"{"Pending":"Waiting"}"#).unwrap(), @@ -1579,15 +1578,15 @@ mod tests { let tx_status_1 = TxStatus::Pending(ValidationStatus::Waiting); let tx_status_2 = TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new( BlockchainErrorKind::AppRpc(AppRpcErrorKind::Remote(RemoteErrorKind::InvalidResponse)), - &ValidationFailureClockReal::default(), + &SimpleClockReal::default(), ))); let tx_status_3 = TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new( BlockchainErrorKind::AppRpc(AppRpcErrorKind::Local(LocalErrorKind::Decoder)), - &ValidationFailureClockReal::default(), + &SimpleClockReal::default(), ))); let tx_status_4 = TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new( BlockchainErrorKind::Internal(InternalErrorKind::PendingTooLongNotReplaced), - &ValidationFailureClockReal::default(), + &SimpleClockReal::default(), ))); let tx_status_5 = TxStatus::Confirmed { block_hash: format!("{:?}", make_tx_hash(1)), diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index 5d612ad68..4cb636385 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -776,7 +776,7 @@ impl Accountant { fn handle_report_services_consumed_message(&mut self, msg: ReportServicesConsumedMessage) { let msg_id = self.msg_id(); - debug!( + trace!( self.logger, "MsgId {}: Accruing debt to {} for consuming {} exited bytes", msg_id, @@ -791,7 +791,7 @@ impl Accountant { &msg.exit.earning_wallet, ); msg.routing.iter().for_each(|routing_service| { - debug!( + trace!( self.logger, "MsgId {}: Accruing debt to {} for consuming {} routed bytes", msg_id, @@ -973,6 +973,10 @@ impl Accountant { None => Err(StartScanError::NoConsumingWalletFound), }; + self.scan_schedulers + .payable + .update_last_new_payable_scan_timestamp(); + match result { Ok(scan_message) => { self.qualified_payables_sub_opt @@ -1517,10 +1521,13 @@ mod tests { let financial_statistics = result.financial_statistics().clone(); let default_scan_intervals = ScanIntervals::compute_default(chain); - assert_eq!( - result.scan_schedulers.payable.new_payable_interval, - default_scan_intervals.payable_scan_interval - ); + result + .scan_schedulers + .payable + .dyn_interval_computer + .as_any() + .downcast_ref::() + .unwrap(); assert_eq!( result.scan_schedulers.pending_payable.interval, default_scan_intervals.pending_payable_scan_interval, @@ -1637,7 +1644,8 @@ mod tests { // Making sure we would get a panic if another scan was scheduled subject.scan_schedulers.payable.new_payable_notify_later = Box::new(NotifyLaterHandleMock::default().panic_on_schedule_attempt()); - subject.scan_schedulers.payable.new_payable_interval = Duration::from_secs(100); + subject.scan_schedulers.payable.new_payable_notify = + Box::new(NotifyHandleMock::default().panic_on_schedule_attempt()); let subject_addr = subject.start(); let system = System::new("test"); let ui_message = NodeFromUiMessage { @@ -1969,7 +1977,8 @@ mod tests { // Making sure we would get a panic if another scan was scheduled subject.scan_schedulers.payable.new_payable_notify_later = Box::new(NotifyLaterHandleMock::default().panic_on_schedule_attempt()); - subject.scan_schedulers.payable.new_payable_interval = Duration::from_secs(100); + subject.scan_schedulers.payable.new_payable_notify = + Box::new(NotifyHandleMock::default().panic_on_schedule_attempt()); let subject_addr = subject.start(); let ui_message = NodeFromUiMessage { client_id: 1234, @@ -2338,27 +2347,70 @@ mod tests { } #[test] - fn accountant_sends_qualified_payable_msg_when_qualified_payable_found() { + fn accountant_sends_qualified_payable_msg_for_new_payable_scan_when_qualified_payable_found() { + let new_payable_templates = NewTxTemplates::from(&vec![make_payable_account(123)]); + accountant_sends_qualified_payable_msg_when_qualified_payable_found( + ScanForNewPayables { + response_skeleton_opt: None, + }, + Either::Left(new_payable_templates), + vec![()], + ) + } + + #[test] + fn accountant_sends_qualified_payable_msg_for_retry_payable_scan_when_qualified_payable_found() + { + let retry_payable_templates = RetryTxTemplates(vec![make_retry_tx_template(123)]); + accountant_sends_qualified_payable_msg_when_qualified_payable_found( + ScanForRetryPayables { + response_skeleton_opt: None, + }, + Either::Right(retry_payable_templates), + vec![], + ) + } + + fn accountant_sends_qualified_payable_msg_when_qualified_payable_found( + act_msg: ActorMessage, + initial_templates: Either, + zero_out_params_expected: Vec<()>, + ) where + ActorMessage: Message + Send + 'static, + ActorMessage::Result: Send, + Accountant: Handler, + { + let zero_out_params_arc = Arc::new(Mutex::new(vec![])); let (blockchain_bridge, _, blockchain_bridge_recording_arc) = make_recorder(); - let now = SystemTime::now(); - let payment_thresholds = PaymentThresholds::default(); - let (qualified_payables, _, retrieved_payables) = - make_qualified_and_unqualified_payables(now, &payment_thresholds); - let payable_dao = PayableDaoMock::new().retrieve_payables_result(retrieved_payables); let system = System::new("accountant_sends_qualified_payable_msg_when_qualified_payable_found"); let consuming_wallet = make_paying_wallet(b"consuming"); let mut subject = AccountantBuilder::default() .bootstrapper_config(bc_from_earning_wallet(make_wallet("some_wallet_address"))) .consuming_wallet(consuming_wallet.clone()) - .payable_daos(vec![ForPayableScanner(payable_dao)]) .build(); + let initial_template_msg = InitialTemplatesMessage { + initial_templates, + consuming_wallet, + response_skeleton_opt: None, + }; + let payable_scanner = ScannerMock::default() + .scan_started_at_result(None) + .start_scan_result(Ok(initial_template_msg.clone())); + subject + .scanners + .replace_scanner(ScannerReplacement::Payable(ReplacementType::Mock( + payable_scanner, + ))); subject .scanners .replace_scanner(ScannerReplacement::PendingPayable(ReplacementType::Null)); subject .scanners .replace_scanner(ScannerReplacement::Receivable(ReplacementType::Null)); + subject.scan_schedulers.payable.dyn_interval_computer = Box::new( + NewPayableScanDynIntervalComputerMock::default().zero_out_params(&zero_out_params_arc), + ); let accountant_addr = subject.start(); let accountant_subs = Accountant::make_subs_from(&accountant_addr); let peer_actors = peer_actors_builder() @@ -2366,26 +2418,16 @@ mod tests { .build(); send_bind_message!(accountant_subs, peer_actors); - accountant_addr - .try_send(ScanForNewPayables { - response_skeleton_opt: None, - }) - .unwrap(); + accountant_addr.try_send(act_msg).unwrap(); System::current().stop(); system.run(); let blockchain_bridge_recorder = blockchain_bridge_recording_arc.lock().unwrap(); assert_eq!(blockchain_bridge_recorder.len(), 1); let message = blockchain_bridge_recorder.get_record::(0); - let expected_new_tx_templates = NewTxTemplates::from(&qualified_payables); - assert_eq!( - message, - &InitialTemplatesMessage { - initial_templates: Either::Left(expected_new_tx_templates), - consuming_wallet, - response_skeleton_opt: None, - } - ); + assert_eq!(message, &initial_template_msg); + let zero_out_params = zero_out_params_arc.lock().unwrap(); + assert_eq!(*zero_out_params, zero_out_params_expected) } #[test] @@ -2396,15 +2438,14 @@ mod tests { System::new("automatic_scan_for_new_payables_schedules_another_one_immediately_if_no_qualified_payables_found"); let consuming_wallet = make_paying_wallet(b"consuming"); let mut subject = AccountantBuilder::default() + .bootstrapper_config(make_bc_with_defaults(TEST_DEFAULT_CHAIN)) .consuming_wallet(consuming_wallet) .build(); subject.scan_schedulers.payable.new_payable_notify_later = Box::new( NotifyLaterHandleMock::default().notify_later_params(¬ify_later_params_arc), ); - subject.scan_schedulers.payable.dyn_interval_computer = Box::new( - NewPayableScanDynIntervalComputerMock::default() - .compute_interval_result(Some(Duration::from_secs(500))), - ); + subject.scan_schedulers.payable.new_payable_notify = + Box::new(NotifyHandleMock::default().panic_on_schedule_attempt()); let payable_scanner = ScannerMock::default() .scan_started_at_result(None) .scan_started_at_result(None) @@ -2431,14 +2472,29 @@ mod tests { System::current().stop(); assert_eq!(system.run(), 0); let mut notify_later_params = notify_later_params_arc.lock().unwrap(); - let (msg, interval) = notify_later_params.remove(0); + // As obvious, the next scan is scheduled for the future and should not run immediately. + let (msg, actual_interval) = notify_later_params.remove(0); assert_eq!( msg, ScanForNewPayables { response_skeleton_opt: None } ); - assert_eq!(interval, Duration::from_secs(500)); + let default_scan_intervals = ScanIntervals::compute_default(TEST_DEFAULT_CHAIN); + // The previous last_new_payable_scan_timestamp is UNIX_EPOCH, if the interval was derived + // from that timestamp, it would result in an immediate-scan command. This implies that + // the last_new_payable_scan_timestamp was reset to zero, which is how it is meant to be. + let left_bound = default_scan_intervals + .payable_scan_interval + .checked_sub(Duration::from_secs(5)) + .unwrap(); + let right_bound = default_scan_intervals + .payable_scan_interval + .checked_add(Duration::from_secs(5)) + .unwrap(); + // The divergence should be only a few milliseconds, definitely not seconds; the tested + // interval should be safe for slower machines too. + assert!(left_bound <= actual_interval && actual_interval <= right_bound); assert_eq!(notify_later_params.len(), 0); // Accountant is unbound; therefore, it is guaranteed that sending a message to // the BlockchainBridge wasn't attempted. It would've panicked otherwise. @@ -2794,7 +2850,7 @@ mod tests { .start_scan_params(&scan_params.receivable_start_scan) .start_scan_result(Err(StartScanError::NothingToProcess)); let (subject, new_payable_expected_computed_interval, receivable_scan_interval) = - set_up_subject_for_no_pending_payables_found_startup_test( + set_up_subject_for_no_p_p_found_startup_test( test_name, ¬ify_and_notify_later_params, &compute_interval_params_arc, @@ -2814,7 +2870,7 @@ mod tests { let before = SystemTime::now(); system.run(); let after = SystemTime::now(); - assert_pending_payable_scanner_for_no_pending_payable_found( + assert_pending_payable_scanner_for_no_p_p_found( test_name, consuming_wallet, &scan_params.pending_payable_start_scan, @@ -2822,12 +2878,11 @@ mod tests { before, after, ); - assert_payable_scanner_for_no_pending_payable_found( + assert_payable_scanner_for_no_p_p_found( + &scan_params.payable_start_scan, ¬ify_and_notify_later_params, compute_interval_params_arc, new_payable_expected_computed_interval, - before, - after, ); assert_receivable_scanner( test_name, @@ -2893,8 +2948,8 @@ mod tests { .scan_started_at_result(None) .start_scan_params(&scan_params.receivable_start_scan) .start_scan_result(Err(StartScanError::NothingToProcess)); - let (subject, pending_payable_expected_notify_later_interval, receivable_scan_interval) = - set_up_subject_for_some_pending_payable_found_startup_test( + let (subject, expected_pending_payable_notify_later_interval, receivable_scan_interval) = + set_up_subject_for_some_p_p_found_startup_test( test_name, ¬ify_and_notify_later_params, config, @@ -2948,17 +3003,17 @@ mod tests { let before = SystemTime::now(); system.run(); let after = SystemTime::now(); - assert_pending_payable_scanner_for_some_pending_payable_found( + assert_pending_payable_scanner_for_some_p_p_found( test_name, consuming_wallet.clone(), &scan_params, ¬ify_and_notify_later_params.pending_payables_notify_later, - pending_payable_expected_notify_later_interval, + expected_pending_payable_notify_later_interval, expected_tx_receipts_msg, before, after, ); - assert_payable_scanner_for_some_pending_payable_found( + assert_payable_scanner_for_some_p_p_found( test_name, consuming_wallet, &scan_params, @@ -2998,10 +3053,10 @@ mod tests { receivables_notify_later: Arc>>, } - fn set_up_subject_for_no_pending_payables_found_startup_test( + fn set_up_subject_for_no_p_p_found_startup_test( test_name: &str, notify_and_notify_later_params: &NotifyAndNotifyLaterParams, - compute_interval_params_arc: &Arc>>, + compute_interval_params_arc: &Arc>>, config: BootstrapperConfig, pending_payable_scanner: ScannerMock< RequestTransactionReceipts, @@ -3057,7 +3112,7 @@ mod tests { ) } - fn set_up_subject_for_some_pending_payable_found_startup_test( + fn set_up_subject_for_some_p_p_found_startup_test( test_name: &str, notify_and_notify_later_params: &NotifyAndNotifyLaterParams, config: BootstrapperConfig, @@ -3150,7 +3205,7 @@ mod tests { subject } - fn assert_pending_payable_scanner_for_no_pending_payable_found( + fn assert_pending_payable_scanner_for_no_p_p_found( test_name: &str, consuming_wallet: Wallet, pending_payable_start_scan_params_arc: &Arc< @@ -3162,7 +3217,7 @@ mod tests { act_started_at: SystemTime, act_finished_at: SystemTime, ) { - let pp_logger = pending_payable_common( + let pp_logger = assert_pending_payable_scanner_ran( consuming_wallet, pending_payable_start_scan_params_arc, act_started_at, @@ -3183,7 +3238,7 @@ mod tests { assert_using_the_same_logger(&pp_logger, test_name, Some("pp")); } - fn assert_pending_payable_scanner_for_some_pending_payable_found( + fn assert_pending_payable_scanner_for_some_p_p_found( test_name: &str, consuming_wallet: Wallet, scan_params: &ScanParams, @@ -3195,7 +3250,7 @@ mod tests { act_started_at: SystemTime, act_finished_at: SystemTime, ) { - let pp_start_scan_logger = pending_payable_common( + let pp_start_scan_logger = assert_pending_payable_scanner_ran( consuming_wallet, &scan_params.pending_payable_start_scan, act_started_at, @@ -3226,7 +3281,7 @@ mod tests { ); } - fn pending_payable_common( + fn assert_pending_payable_scanner_ran( consuming_wallet: Wallet, pending_payable_start_scan_params_arc: &Arc< Mutex, Logger, String)>>, @@ -3264,12 +3319,13 @@ mod tests { pp_logger } - fn assert_payable_scanner_for_no_pending_payable_found( + fn assert_payable_scanner_for_no_p_p_found( + payable_scanner_start_scan_arc: &Arc< + Mutex, Logger, String)>>, + >, notify_and_notify_later_params: &NotifyAndNotifyLaterParams, - compute_interval_params_arc: Arc>>, + compute_interval_until_next_new_payable_scan_params_arc: Arc>>, new_payable_expected_computed_interval: Duration, - act_started_at: SystemTime, - act_finished_at: SystemTime, ) { // Note that there is no functionality from the payable scanner actually running. // We only witness it to be scheduled. @@ -3286,10 +3342,19 @@ mod tests { new_payable_expected_computed_interval )] ); - let mut compute_interval_params = compute_interval_params_arc.lock().unwrap(); - let (p_scheduling_now, last_new_payable_scan_timestamp, _) = - compute_interval_params.remove(0); - assert_eq!(last_new_payable_scan_timestamp, UNIX_EPOCH); + let compute_interval_until_next_new_payable_scan_params = + compute_interval_until_next_new_payable_scan_params_arc + .lock() + .unwrap(); + assert_eq!( + *compute_interval_until_next_new_payable_scan_params, + vec![()] + ); + let payable_scanner_start_scan = payable_scanner_start_scan_arc.lock().unwrap(); + assert!( + payable_scanner_start_scan.is_empty(), + "We expected the payable scanner not to run in this test, but it did" + ); let scan_for_new_payables_notify_params = notify_and_notify_later_params .new_payables_notify .lock() @@ -3308,22 +3373,29 @@ mod tests { "We did not expect any scheduling of retry payables, but it happened {:?}", scan_for_retry_payables_notify_params ); - assert!( - act_started_at <= p_scheduling_now && p_scheduling_now <= act_finished_at, - "The payable scan scheduling was supposed to take place between {:?} and {:?} \ - but it was {:?}", - act_started_at, - act_finished_at, - p_scheduling_now - ); } - fn assert_payable_scanner_for_some_pending_payable_found( + fn assert_payable_scanner_for_some_p_p_found( test_name: &str, consuming_wallet: Wallet, scan_params: &ScanParams, notify_and_notify_later_params: &NotifyAndNotifyLaterParams, expected_sent_payables: SentPayables, + ) { + assert_payable_scanner_ran_for_some_p_p_found( + test_name, + consuming_wallet, + scan_params, + expected_sent_payables, + ); + assert_scan_scheduling_for_some_p_p_found(notify_and_notify_later_params); + } + + fn assert_payable_scanner_ran_for_some_p_p_found( + test_name: &str, + consuming_wallet: Wallet, + scan_params: &ScanParams, + expected_sent_payables: SentPayables, ) { let mut payable_start_scan_params = scan_params.payable_start_scan.lock().unwrap(); let (p_wallet, _, p_response_skeleton_opt, p_start_scan_logger, p_trigger_msg_type_str) = @@ -3355,6 +3427,11 @@ mod tests { test_name, Some("retry payable finish"), ); + } + + fn assert_scan_scheduling_for_some_p_p_found( + notify_and_notify_later_params: &NotifyAndNotifyLaterParams, + ) { let scan_for_new_payables_notify_later_params = notify_and_notify_later_params .new_payables_notify_later .lock() @@ -3395,6 +3472,20 @@ mod tests { Mutex>, >, receivable_scan_interval: Duration, + ) { + assert_receivable_scan_ran(test_name, receivable_start_scan_params_arc, earning_wallet); + assert_another_receivable_scan_scheduled( + scan_for_receivables_notify_later_params_arc, + receivable_scan_interval, + ) + } + + fn assert_receivable_scan_ran( + test_name: &str, + receivable_start_scan_params_arc: &Arc< + Mutex, Logger, String)>>, + >, + earning_wallet: Wallet, ) { let mut receivable_start_scan_params = receivable_start_scan_params_arc.lock().unwrap(); let (r_wallet, _r_started_at, r_response_skeleton_opt, r_logger, r_trigger_msg_name_str) = @@ -3403,15 +3494,23 @@ mod tests { assert_eq!(r_response_skeleton_opt, None); assert!( r_trigger_msg_name_str.contains("Receivable"), - "Should contain Receivable but {}", + "Should contain 'Receivable' but {}", r_trigger_msg_name_str ); assert!( receivable_start_scan_params.is_empty(), - "Should be already empty but was {:?}", + "Should be empty by now but was {:?}", receivable_start_scan_params ); assert_using_the_same_logger(&r_logger, test_name, Some("r")); + } + + fn assert_another_receivable_scan_scheduled( + scan_for_receivables_notify_later_params_arc: &Arc< + Mutex>, + >, + receivable_scan_interval: Duration, + ) { let scan_for_receivables_notify_later_params = scan_for_receivables_notify_later_params_arc.lock().unwrap(); assert_eq!( @@ -4693,20 +4792,6 @@ mod tests { ) ] ); - let test_log_handler = TestLogHandler::new(); - - test_log_handler.exists_log_containing(&format!( - "DEBUG: Accountant: MsgId 123: Accruing debt to {} for consuming 1200 exited bytes", - earning_wallet_exit - )); - test_log_handler.exists_log_containing(&format!( - "DEBUG: Accountant: MsgId 123: Accruing debt to {} for consuming 3456 routed bytes", - earning_wallet_routing_1 - )); - test_log_handler.exists_log_containing(&format!( - "DEBUG: Accountant: MsgId 123: Accruing debt to {} for consuming 3456 routed bytes", - earning_wallet_routing_2 - )); } fn assert_that_we_do_not_charge_our_own_wallet_for_consumed_services( @@ -5390,10 +5475,10 @@ mod tests { } #[test] - fn accountant_confirms_all_pending_txs_and_schedules_the_new_payable_scanner_timely() { + fn accountant_confirms_all_pending_txs_and_schedules_new_payable_scanner_timely() { init_test_logging(); let test_name = - "accountant_confirms_all_pending_txs_and_schedules_the_new_payable_scanner_timely"; + "accountant_confirms_all_pending_txs_and_schedules_new_payable_scanner_timely"; let finish_scan_params_arc = Arc::new(Mutex::new(vec![])); let compute_interval_params_arc = Arc::new(Mutex::new(vec![])); let new_payable_notify_later_arc = Arc::new(Mutex::new(vec![])); @@ -5410,23 +5495,12 @@ mod tests { .replace_scanner(ScannerReplacement::PendingPayable(ReplacementType::Mock( pending_payable_scanner, ))); - let last_new_payable_scan_timestamp = SystemTime::now() - .checked_sub(Duration::from_secs(3)) - .unwrap(); - let nominal_interval = Duration::from_secs(6); let expected_computed_interval = Duration::from_secs(3); let dyn_interval_computer = NewPayableScanDynIntervalComputerMock::default() .compute_interval_params(&compute_interval_params_arc) + // This determines the test .compute_interval_result(Some(expected_computed_interval)); - subject.scan_schedulers.payable.new_payable_interval = nominal_interval; subject.scan_schedulers.payable.dyn_interval_computer = Box::new(dyn_interval_computer); - subject - .scan_schedulers - .payable - .inner - .lock() - .unwrap() - .last_new_payable_scan_timestamp = last_new_payable_scan_timestamp; subject.scan_schedulers.payable.new_payable_notify_later = Box::new( NotifyLaterHandleMock::default().notify_later_params(&new_payable_notify_later_arc), ); @@ -5463,15 +5537,7 @@ mod tests { "Should be empty but {:?}", finish_scan_params ); - let mut compute_interval_params = compute_interval_params_arc.lock().unwrap(); - let (_, last_new_payable_timestamp_actual, scan_interval_actual) = - compute_interval_params.remove(0); - assert_eq!( - last_new_payable_timestamp_actual, - last_new_payable_scan_timestamp - ); - assert_eq!(scan_interval_actual, nominal_interval); - assert!(compute_interval_params.is_empty()); + // Here, we see that the next payable scan is scheduled for the future, in the expected interval. let new_payable_notify_later = new_payable_notify_later_arc.lock().unwrap(); assert_eq!( *new_payable_notify_later, @@ -5505,22 +5571,11 @@ mod tests { .replace_scanner(ScannerReplacement::PendingPayable(ReplacementType::Mock( pending_payable_scanner, ))); - let last_new_payable_scan_timestamp = SystemTime::now() - .checked_sub(Duration::from_secs(8)) - .unwrap(); - let nominal_interval = Duration::from_secs(6); let dyn_interval_computer = NewPayableScanDynIntervalComputerMock::default() .compute_interval_params(&compute_interval_params_arc) + // This determines the test .compute_interval_result(None); - subject.scan_schedulers.payable.new_payable_interval = nominal_interval; subject.scan_schedulers.payable.dyn_interval_computer = Box::new(dyn_interval_computer); - subject - .scan_schedulers - .payable - .inner - .lock() - .unwrap() - .last_new_payable_scan_timestamp = last_new_payable_scan_timestamp; subject.scan_schedulers.payable.new_payable_notify_later = Box::new( NotifyLaterHandleMock::default().notify_later_params(&new_payable_notify_later_arc), ); @@ -5554,21 +5609,15 @@ mod tests { "Should be empty but {:?}", finish_scan_params ); - let mut compute_interval_params = compute_interval_params_arc.lock().unwrap(); - let (_, last_new_payable_timestamp_actual, scan_interval_actual) = - compute_interval_params.remove(0); - assert_eq!( - last_new_payable_timestamp_actual, - last_new_payable_scan_timestamp - ); - assert_eq!(scan_interval_actual, nominal_interval); - assert!(compute_interval_params.is_empty()); + let compute_interval_params = compute_interval_params_arc.lock().unwrap(); + assert_eq!(*compute_interval_params, vec![()]); let new_payable_notify_later = new_payable_notify_later_arc.lock().unwrap(); assert!( new_payable_notify_later.is_empty(), "should be empty but was: {:?}", new_payable_notify_later ); + // As a proof, the handle for an immediate launch of the new payable scanner was used let new_payable_notify = new_payable_notify_arc.lock().unwrap(); assert_eq!(*new_payable_notify, vec![ScanForNewPayables::default()]); } @@ -5578,6 +5627,7 @@ mod tests { let new_payable_notify_later_arc = Arc::new(Mutex::new(vec![])); let test_name = "scheduler_for_new_payables_operates_with_proper_now_timestamp"; let mut subject = AccountantBuilder::default() + .bootstrapper_config(make_bc_with_defaults(TEST_DEFAULT_CHAIN)) .logger(Logger::new(test_name)) .build(); let pending_payable_scanner = ScannerMock::new() @@ -5587,21 +5637,21 @@ mod tests { .replace_scanner(ScannerReplacement::PendingPayable(ReplacementType::Mock( pending_payable_scanner, ))); - let last_new_payable_scan_timestamp = SystemTime::now() - .checked_sub(Duration::from_millis(3500)) - .unwrap(); - let new_payable_interval = Duration::from_secs(6); - subject.scan_schedulers.payable.new_payable_interval = new_payable_interval; - subject - .scan_schedulers - .payable - .inner - .lock() - .unwrap() - .last_new_payable_scan_timestamp = last_new_payable_scan_timestamp; subject.scan_schedulers.payable.new_payable_notify_later = Box::new( NotifyLaterHandleMock::default().notify_later_params(&new_payable_notify_later_arc), ); + let default_scan_intervals = ScanIntervals::compute_default(TEST_DEFAULT_CHAIN); + let mut assertion_interval_computer = NewPayableScanDynIntervalComputerReal::new( + default_scan_intervals.payable_scan_interval, + ); + { + subject + .scan_schedulers + .payable + .dyn_interval_computer + .zero_out(); + assertion_interval_computer.zero_out(); + } let system = System::new(test_name); let subject_addr = subject.start(); let (msg, _) = make_tx_receipts_msg(vec![SeedsToMakeUpPayableWithStatus { @@ -5611,26 +5661,15 @@ mod tests { block_number: U64::from(100), }), }]); + let left_side_bound = assertion_interval_computer.compute_interval().unwrap(); subject_addr.try_send(msg).unwrap(); - let before = SystemTime::now(); System::current().stop(); system.run(); - let after = SystemTime::now(); let new_payable_notify_later = new_payable_notify_later_arc.lock().unwrap(); let (_, actual_interval) = new_payable_notify_later[0]; - let interval_computer = NewPayableScanDynIntervalComputerReal::default(); - let left_side_bound = interval_computer - .compute_interval( - before, - last_new_payable_scan_timestamp, - new_payable_interval, - ) - .unwrap(); - let right_side_bound = interval_computer - .compute_interval(after, last_new_payable_scan_timestamp, new_payable_interval) - .unwrap(); + let right_side_bound = assertion_interval_computer.compute_interval().unwrap(); assert!( left_side_bound >= actual_interval && actual_interval >= right_side_bound, "expected actual {:?} to be between {:?} and {:?}", @@ -5802,12 +5841,6 @@ mod tests { |_scanners: &mut Scanners, scan_schedulers: &mut ScanSchedulers| { // Setup let notify_later_params_arc = Arc::new(Mutex::new(vec![])); - scan_schedulers - .payable - .inner - .lock() - .unwrap() - .last_new_payable_scan_timestamp = SystemTime::now(); scan_schedulers.payable.dyn_interval_computer = Box::new( NewPayableScanDynIntervalComputerMock::default() .compute_interval_result(Some(Duration::from_secs(152))), diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index 1dc1c3a9f..787b9a8b2 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -580,7 +580,6 @@ mod tests { }; use crate::accountant::scanners::payable_scanner::utils::PayableScanResult; use crate::accountant::scanners::payable_scanner::PayableScanner; - use crate::accountant::scanners::pending_payable_scanner::test_utils::ValidationFailureClockMock; use crate::accountant::scanners::pending_payable_scanner::utils::{ CurrentPendingPayables, PendingPayableScanResult, RecheckRequiringFailures, TxHashByTable, }; @@ -631,6 +630,7 @@ mod tests { use masq_lib::logger::Logger; use masq_lib::messages::ScanType; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; + use masq_lib::test_utils::simple_clock::SimpleClockMock; use masq_lib::ui_gateway::NodeToUiMessage; use regex::Regex; use rusqlite::{ffi, ErrorCode}; @@ -1440,7 +1440,7 @@ mod tests { failed_tx_5.status = FailureStatus::RecheckRequired(ValidationStatus::Reattempting(PreviousAttempts::new( BlockchainErrorKind::AppRpc(AppRpcErrorKind::Remote(RemoteErrorKind::Unreachable)), - &ValidationFailureClockMock::default().now_result(timestamp_c), + &SimpleClockMock::default().now_result(timestamp_c), ))); let tx_receipt_rpc_error_5 = AppRpcError::Remote(RemoteError::InvalidResponse("game over".to_string())); @@ -1456,7 +1456,7 @@ mod tests { let failed_payable_cache = PendingPayableCacheMock::default() .get_record_by_hash_result(Some(failed_tx_2.clone())) .get_record_by_hash_result(Some(failed_tx_5)); - let validation_failure_clock = ValidationFailureClockMock::default() + let validation_failure_clock = SimpleClockMock::default() .now_result(timestamp_a) .now_result(timestamp_b); let mut pending_payable_scanner = PendingPayableScannerBuilder::new() @@ -1512,7 +1512,7 @@ mod tests { assert_eq!( *update_statuses_pending_payable_params, vec![ - hashmap!(tx_hash_4 => TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new(BlockchainErrorKind::AppRpc(AppRpcErrorKind::Remote(RemoteErrorKind::Unreachable)), &ValidationFailureClockMock::default().now_result(timestamp_a))))) + hashmap!(tx_hash_4 => TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new(BlockchainErrorKind::AppRpc(AppRpcErrorKind::Remote(RemoteErrorKind::Unreachable)), &SimpleClockMock::default().now_result(timestamp_a))))) ] ); let update_statuses_failed_payable_params = @@ -1520,7 +1520,7 @@ mod tests { assert_eq!( *update_statuses_failed_payable_params, vec![ - hashmap!(tx_hash_5 => FailureStatus::RecheckRequired(ValidationStatus::Reattempting(PreviousAttempts::new(BlockchainErrorKind::AppRpc(AppRpcErrorKind::Remote(RemoteErrorKind::Unreachable)), &ValidationFailureClockMock::default().now_result(timestamp_c)).add_attempt(BlockchainErrorKind::AppRpc(AppRpcErrorKind::Remote(RemoteErrorKind::InvalidResponse)), &ValidationFailureClockMock::default().now_result(timestamp_b))))) + hashmap!(tx_hash_5 => FailureStatus::RecheckRequired(ValidationStatus::Reattempting(PreviousAttempts::new(BlockchainErrorKind::AppRpc(AppRpcErrorKind::Remote(RemoteErrorKind::Unreachable)), &SimpleClockMock::default().now_result(timestamp_c)).add_attempt(BlockchainErrorKind::AppRpc(AppRpcErrorKind::Remote(RemoteErrorKind::InvalidResponse)), &SimpleClockMock::default().now_result(timestamp_b))))) ] ); assert_eq!(subject.scan_started_at(ScanType::PendingPayables), None); diff --git a/node/src/accountant/scanners/pending_payable_scanner/mod.rs b/node/src/accountant/scanners/pending_payable_scanner/mod.rs index cc1052d14..499ee0179 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/mod.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/mod.rs @@ -1,6 +1,5 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -pub mod test_utils; mod tx_receipt_interpreter; pub mod utils; @@ -28,15 +27,13 @@ use crate::accountant::{ TxReceiptResult, TxReceiptsMessage, }; use crate::blockchain::blockchain_interface::data_structures::TxBlock; -use crate::blockchain::errors::validation_status::{ - ValidationFailureClock, ValidationFailureClockReal, -}; use crate::sub_lib::accountant::{FinancialStatistics, PaymentThresholds}; use crate::sub_lib::wallet::Wallet; use crate::time_marking_methods; use itertools::{Either, Itertools}; use masq_lib::logger::Logger; use masq_lib::messages::{ScanType, ToMessageBody, UiScanResponse}; +use masq_lib::simple_clock::{SimpleClock, SimpleClockReal}; use masq_lib::ui_gateway::{MessageTarget, NodeToUiMessage}; use std::cell::RefCell; use std::collections::{BTreeSet, HashMap}; @@ -69,7 +66,7 @@ pub struct PendingPayableScanner { pub financial_statistics: Rc>, pub current_sent_payables: Box>, pub yet_unproven_failed_payables: Box>, - pub clock: Box, + pub clock: Box, } impl ExtendedPendingPayablePrivateScanner for PendingPayableScanner {} @@ -159,7 +156,7 @@ impl PendingPayableScanner { financial_statistics, current_sent_payables: Box::new(CurrentPendingPayables::default()), yet_unproven_failed_payables: Box::new(RecheckRequiringFailures::default()), - clock: Box::new(ValidationFailureClockReal::default()), + clock: Box::new(SimpleClockReal::default()), } } @@ -792,7 +789,7 @@ impl PendingPayableScanner { fn prepare_statuses_for_update( failures: &[FailedValidation], - clock: &dyn ValidationFailureClock, + clock: &dyn SimpleClock, logger: &Logger, ) -> HashMap { failures @@ -850,7 +847,6 @@ mod tests { Detection, SentPayableDaoError, TxStatus, }; use crate::accountant::db_access_objects::test_utils::{make_failed_tx, make_sent_tx}; - use crate::accountant::scanners::pending_payable_scanner::test_utils::ValidationFailureClockMock; use crate::accountant::scanners::pending_payable_scanner::utils::{ CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailedValidation, FailedValidationByTable, PendingPayableCache, PendingPayableScanResult, PresortedTxFailure, @@ -870,16 +866,16 @@ mod tests { use crate::blockchain::errors::rpc_errors::{ AppRpcError, AppRpcErrorKind, LocalError, LocalErrorKind, RemoteErrorKind, }; - use crate::blockchain::errors::validation_status::{ - PreviousAttempts, ValidationFailureClockReal, ValidationStatus, - }; + use crate::blockchain::errors::validation_status::{PreviousAttempts, ValidationStatus}; use crate::blockchain::errors::BlockchainErrorKind; use crate::blockchain::test_utils::{make_block_hash, make_tx_hash}; use crate::test_utils::{make_paying_wallet, make_wallet}; use itertools::Itertools; use masq_lib::logger::Logger; use masq_lib::messages::{ToMessageBody, UiScanResponse}; + use masq_lib::simple_clock::SimpleClockReal; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; + use masq_lib::test_utils::simple_clock::SimpleClockMock; use masq_lib::ui_gateway::{MessageTarget, NodeToUiMessage}; use std::collections::{BTreeSet, HashMap}; use std::ops::Sub; @@ -1296,7 +1292,7 @@ mod tests { failed_tx_2.status = FailureStatus::RecheckRequired(ValidationStatus::Reattempting(PreviousAttempts::new( BlockchainErrorKind::AppRpc(AppRpcErrorKind::Local(LocalErrorKind::Internal)), - &ValidationFailureClockMock::default().now_result(timestamp_a), + &SimpleClockMock::default().now_result(timestamp_a), ))); let failed_payable_dao = FailedPayableDaoMock::default() .retrieve_txs_params(&retrieve_failed_txs_params_arc) @@ -1311,7 +1307,7 @@ mod tests { .retrieve_txs_result(btreeset![sent_tx.clone()]) .update_statuses_params(&update_statuses_sent_tx_params_arc) .update_statuses_result(Ok(())); - let validation_failure_clock = ValidationFailureClockMock::default() + let validation_failure_clock = SimpleClockMock::default() .now_result(timestamp_a) .now_result(timestamp_b) .now_result(timestamp_c); @@ -1338,7 +1334,7 @@ mod tests { BlockchainErrorKind::AppRpc(AppRpcErrorKind::Local( LocalErrorKind::Internal, )), - &ValidationFailureClockMock::default().now_result(timestamp_d), + &SimpleClockMock::default().now_result(timestamp_d), ), )), )), @@ -1358,7 +1354,7 @@ mod tests { assert_eq!( *update_statuses_sent_tx_params, vec![ - hashmap![hash_3 => TxStatus::Pending(ValidationStatus::Reattempting (PreviousAttempts::new(BlockchainErrorKind::AppRpc(AppRpcErrorKind::Remote(RemoteErrorKind::InvalidResponse)), &ValidationFailureClockMock::default().now_result(timestamp_a))))] + hashmap![hash_3 => TxStatus::Pending(ValidationStatus::Reattempting (PreviousAttempts::new(BlockchainErrorKind::AppRpc(AppRpcErrorKind::Remote(RemoteErrorKind::InvalidResponse)), &SimpleClockMock::default().now_result(timestamp_a))))] ] ); let mut update_statuses_failed_tx_params = @@ -1370,10 +1366,10 @@ mod tests { .collect::>(); let expected_params = hashmap!( hash_1 => FailureStatus::RecheckRequired( - ValidationStatus::Reattempting(PreviousAttempts::new(BlockchainErrorKind::AppRpc(AppRpcErrorKind::Remote(RemoteErrorKind::Unreachable)), &ValidationFailureClockMock::default().now_result(timestamp_b))) + ValidationStatus::Reattempting(PreviousAttempts::new(BlockchainErrorKind::AppRpc(AppRpcErrorKind::Remote(RemoteErrorKind::Unreachable)), &SimpleClockMock::default().now_result(timestamp_b))) ), hash_2 => FailureStatus::RecheckRequired( - ValidationStatus::Reattempting(PreviousAttempts::new(BlockchainErrorKind::AppRpc(AppRpcErrorKind::Local(LocalErrorKind::Internal)), &ValidationFailureClockMock::default().now_result(timestamp_d)).add_attempt(BlockchainErrorKind::AppRpc(AppRpcErrorKind::Local(LocalErrorKind::Internal)), &ValidationFailureClockReal::default()))) + ValidationStatus::Reattempting(PreviousAttempts::new(BlockchainErrorKind::AppRpc(AppRpcErrorKind::Local(LocalErrorKind::Internal)), &SimpleClockMock::default().now_result(timestamp_d)).add_attempt(BlockchainErrorKind::AppRpc(AppRpcErrorKind::Local(LocalErrorKind::Internal)), &SimpleClockReal::default()))) ).into_iter().sorted_by_key(|(key,_)|*key).collect::>(); assert_eq!(actual_params, expected_params); assert!( @@ -1469,7 +1465,7 @@ mod tests { .update_statuses_result(Err(SentPayableDaoError::InvalidInput("blah".to_string()))); let subject = PendingPayableScannerBuilder::new() .sent_payable_dao(sent_payable_dao) - .validation_failure_clock(Box::new(ValidationFailureClockReal::default())) + .validation_failure_clock(Box::new(SimpleClockReal::default())) .build(); let _ = subject @@ -1492,7 +1488,7 @@ mod tests { .update_statuses_result(Err(FailedPayableDaoError::InvalidInput("blah".to_string()))); let subject = PendingPayableScannerBuilder::new() .failed_payable_dao(failed_payable_dao) - .validation_failure_clock(Box::new(ValidationFailureClockReal::default())) + .validation_failure_clock(Box::new(SimpleClockReal::default())) .build(); let _ = subject @@ -1522,9 +1518,7 @@ mod tests { let subject = PendingPayableScannerBuilder::new() .sent_payable_dao(sent_payable_dao) .failed_payable_dao(failed_payable_dao) - .validation_failure_clock(Box::new( - ValidationFailureClockMock::default().now_result(timestamp), - )) + .validation_failure_clock(Box::new(SimpleClockMock::default().now_result(timestamp))) .build(); let detected_failures = DetectedFailures { tx_failures: vec![PresortedTxFailure::NewEntry(failed_tx_1.clone())], @@ -1550,7 +1544,7 @@ mod tests { assert_eq!( *update_statuses_params, vec![ - hashmap!(tx_hash_2 => TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new(BlockchainErrorKind::AppRpc(AppRpcErrorKind::Local(LocalErrorKind::Internal)), &ValidationFailureClockMock::default().now_result(timestamp))))) + hashmap!(tx_hash_2 => TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new(BlockchainErrorKind::AppRpc(AppRpcErrorKind::Local(LocalErrorKind::Internal)), &SimpleClockMock::default().now_result(timestamp))))) ] ); } diff --git a/node/src/accountant/scanners/pending_payable_scanner/tx_receipt_interpreter.rs b/node/src/accountant/scanners/pending_payable_scanner/tx_receipt_interpreter.rs index 833f5ef21..fc16c5713 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/tx_receipt_interpreter.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/tx_receipt_interpreter.rs @@ -237,13 +237,12 @@ mod tests { use crate::blockchain::errors::rpc_errors::{ AppRpcError, AppRpcErrorKind, LocalError, LocalErrorKind, RemoteError, }; - use crate::blockchain::errors::validation_status::{ - PreviousAttempts, ValidationFailureClockReal, ValidationStatus, - }; + use crate::blockchain::errors::validation_status::{PreviousAttempts, ValidationStatus}; use crate::blockchain::errors::BlockchainErrorKind; use crate::blockchain::test_utils::make_tx_hash; use crate::test_utils::unshared_test_utils::capture_digits_with_separators_from_str; use masq_lib::logger::Logger; + use masq_lib::simple_clock::SimpleClockReal; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; use std::collections::BTreeSet; use std::sync::{Arc, Mutex}; @@ -590,7 +589,7 @@ mod tests { test_name, TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new( BlockchainErrorKind::AppRpc(AppRpcErrorKind::Local(LocalErrorKind::Internal)), - &ValidationFailureClockReal::default(), + &SimpleClockReal::default(), ))), ); } @@ -654,7 +653,7 @@ mod tests { test_name, FailureStatus::RecheckRequired(ValidationStatus::Reattempting(PreviousAttempts::new( BlockchainErrorKind::AppRpc(AppRpcErrorKind::Local(LocalErrorKind::Internal)), - &ValidationFailureClockReal::default(), + &SimpleClockReal::default(), ))), ); } diff --git a/node/src/accountant/scanners/pending_payable_scanner/utils.rs b/node/src/accountant/scanners/pending_payable_scanner/utils.rs index f86984df0..7a1d18eaa 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/utils.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/utils.rs @@ -5,12 +5,11 @@ use crate::accountant::db_access_objects::sent_payable_dao::{SentTx, TxStatus}; use crate::accountant::db_access_objects::utils::TxHash; use crate::accountant::{ResponseSkeleton, TxReceiptResult}; use crate::blockchain::errors::rpc_errors::AppRpcError; -use crate::blockchain::errors::validation_status::{ - PreviousAttempts, ValidationFailureClock, ValidationStatus, -}; +use crate::blockchain::errors::validation_status::{PreviousAttempts, ValidationStatus}; use crate::blockchain::errors::BlockchainErrorKind; use itertools::Either; use masq_lib::logger::Logger; +use masq_lib::simple_clock::SimpleClock; use masq_lib::ui_gateway::NodeToUiMessage; use std::cmp::Ordering; use std::collections::HashMap; @@ -153,7 +152,7 @@ where } } - pub fn new_status(&self, clock: &dyn ValidationFailureClock) -> Option { + pub fn new_status(&self, clock: &dyn SimpleClock) -> Option { self.current_status .update_after_failure(self.validation_failure, clock) } @@ -163,7 +162,7 @@ pub trait UpdatableValidationStatus { fn update_after_failure( &self, error: BlockchainErrorKind, - clock: &dyn ValidationFailureClock, + clock: &dyn SimpleClock, ) -> Option where Self: Sized; @@ -173,7 +172,7 @@ impl UpdatableValidationStatus for TxStatus { fn update_after_failure( &self, error: BlockchainErrorKind, - clock: &dyn ValidationFailureClock, + clock: &dyn SimpleClock, ) -> Option { match self { TxStatus::Pending(ValidationStatus::Waiting) => Some(TxStatus::Pending( @@ -193,7 +192,7 @@ impl UpdatableValidationStatus for FailureStatus { fn update_after_failure( &self, error: BlockchainErrorKind, - clock: &dyn ValidationFailureClock, + clock: &dyn SimpleClock, ) -> Option { match self { FailureStatus::RecheckRequired(ValidationStatus::Waiting) => { @@ -389,20 +388,19 @@ mod tests { use crate::accountant::db_access_objects::failed_payable_dao::FailureStatus; use crate::accountant::db_access_objects::sent_payable_dao::{Detection, TxStatus}; use crate::accountant::db_access_objects::test_utils::{make_failed_tx, make_sent_tx}; - use crate::accountant::scanners::pending_payable_scanner::test_utils::ValidationFailureClockMock; use crate::accountant::scanners::pending_payable_scanner::utils::{ CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailedValidation, FailedValidationByTable, PendingPayableCache, PresortedTxFailure, ReceiptScanReport, RecheckRequiringFailures, Retry, TxByTable, TxHashByTable, }; use crate::blockchain::errors::rpc_errors::{AppRpcErrorKind, LocalErrorKind, RemoteErrorKind}; - use crate::blockchain::errors::validation_status::{ - PreviousAttempts, ValidationFailureClockReal, ValidationStatus, - }; + use crate::blockchain::errors::validation_status::{PreviousAttempts, ValidationStatus}; use crate::blockchain::errors::BlockchainErrorKind; use crate::blockchain::test_utils::make_tx_hash; use masq_lib::logger::Logger; + use masq_lib::simple_clock::SimpleClockReal; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; + use masq_lib::test_utils::simple_clock::SimpleClockMock; use std::cmp::Ordering; use std::collections::BTreeSet; use std::ops::Sub; @@ -456,7 +454,7 @@ mod tests { BlockchainErrorKind::AppRpc(AppRpcErrorKind::Local( LocalErrorKind::Internal, )), - &ValidationFailureClockReal::default(), + &SimpleClockReal::default(), ), )), ), @@ -531,7 +529,7 @@ mod tests { BlockchainErrorKind::AppRpc(AppRpcErrorKind::Local( LocalErrorKind::Internal, )), - &ValidationFailureClockReal::default(), + &SimpleClockReal::default(), ))), )), FailedValidationByTable::FailedPayable(FailedValidation::new( @@ -544,7 +542,7 @@ mod tests { BlockchainErrorKind::AppRpc(AppRpcErrorKind::Local( LocalErrorKind::Internal, )), - &ValidationFailureClockReal::default(), + &SimpleClockReal::default(), ), )), )), @@ -943,7 +941,7 @@ mod tests { let timestamp_a = SystemTime::now(); let timestamp_b = SystemTime::now().sub(Duration::from_secs(11)); let timestamp_c = SystemTime::now().sub(Duration::from_secs(22)); - let clock = ValidationFailureClockMock::default() + let clock = SimpleClockMock::default() .now_result(timestamp_a) .now_result(timestamp_c); let cases = vec![ @@ -958,7 +956,7 @@ mod tests { BlockchainErrorKind::AppRpc(AppRpcErrorKind::Local( LocalErrorKind::Internal, )), - &ValidationFailureClockMock::default().now_result(timestamp_a), + &SimpleClockMock::default().now_result(timestamp_a), ), ))), ), @@ -973,13 +971,13 @@ mod tests { BlockchainErrorKind::AppRpc(AppRpcErrorKind::Local( LocalErrorKind::Internal, )), - &ValidationFailureClockMock::default().now_result(timestamp_b), + &SimpleClockMock::default().now_result(timestamp_b), ) .add_attempt( BlockchainErrorKind::AppRpc(AppRpcErrorKind::Local( LocalErrorKind::Internal, )), - &ValidationFailureClockReal::default(), + &SimpleClockReal::default(), ), )), ), @@ -988,19 +986,19 @@ mod tests { BlockchainErrorKind::AppRpc(AppRpcErrorKind::Remote( RemoteErrorKind::Unreachable, )), - &ValidationFailureClockMock::default().now_result(timestamp_c), + &SimpleClockMock::default().now_result(timestamp_c), ) .add_attempt( BlockchainErrorKind::AppRpc(AppRpcErrorKind::Local( LocalErrorKind::Internal, )), - &ValidationFailureClockMock::default().now_result(timestamp_b), + &SimpleClockMock::default().now_result(timestamp_b), ) .add_attempt( BlockchainErrorKind::AppRpc(AppRpcErrorKind::Local( LocalErrorKind::Internal, )), - &ValidationFailureClockReal::default(), + &SimpleClockReal::default(), ), ))), ), @@ -1016,7 +1014,7 @@ mod tests { let timestamp_a = SystemTime::now().sub(Duration::from_secs(222)); let timestamp_b = SystemTime::now().sub(Duration::from_secs(3333)); let timestamp_c = SystemTime::now().sub(Duration::from_secs(44444)); - let clock = ValidationFailureClockMock::default() + let clock = SimpleClockMock::default() .now_result(timestamp_a) .now_result(timestamp_b); let cases = vec![ @@ -1031,7 +1029,7 @@ mod tests { BlockchainErrorKind::AppRpc(AppRpcErrorKind::Local( LocalErrorKind::Internal, )), - &ValidationFailureClockMock::default().now_result(timestamp_a), + &SimpleClockMock::default().now_result(timestamp_a), )), )), ), @@ -1046,13 +1044,13 @@ mod tests { BlockchainErrorKind::AppRpc(AppRpcErrorKind::Remote( RemoteErrorKind::Unreachable, )), - &ValidationFailureClockMock::default().now_result(timestamp_b), + &SimpleClockMock::default().now_result(timestamp_b), ) .add_attempt( BlockchainErrorKind::AppRpc(AppRpcErrorKind::Remote( RemoteErrorKind::InvalidResponse, )), - &ValidationFailureClockMock::default().now_result(timestamp_c), + &SimpleClockMock::default().now_result(timestamp_c), ), )), ), @@ -1062,19 +1060,19 @@ mod tests { BlockchainErrorKind::AppRpc(AppRpcErrorKind::Remote( RemoteErrorKind::Unreachable, )), - &ValidationFailureClockMock::default().now_result(timestamp_b), + &SimpleClockMock::default().now_result(timestamp_b), ) .add_attempt( BlockchainErrorKind::AppRpc(AppRpcErrorKind::Remote( RemoteErrorKind::InvalidResponse, )), - &ValidationFailureClockMock::default().now_result(timestamp_c), + &SimpleClockMock::default().now_result(timestamp_c), ) .add_attempt( BlockchainErrorKind::AppRpc(AppRpcErrorKind::Remote( RemoteErrorKind::Unreachable, )), - &ValidationFailureClockReal::default(), + &SimpleClockReal::default(), ), ), )), @@ -1088,7 +1086,7 @@ mod tests { #[test] fn failed_validation_new_status_has_no_effect_on_unexpected_tx_status() { - let validation_failure_clock = ValidationFailureClockMock::default(); + let validation_failure_clock = SimpleClockMock::default(); let mal_validated_tx_status = FailedValidation::new( make_tx_hash(123), BlockchainErrorKind::AppRpc(AppRpcErrorKind::Local(LocalErrorKind::Internal)), @@ -1107,7 +1105,7 @@ mod tests { #[test] fn failed_validation_new_status_has_no_effect_on_unexpected_failure_status() { - let validation_failure_clock = ValidationFailureClockMock::default(); + let validation_failure_clock = SimpleClockMock::default(); let mal_validated_failure_statuses = vec![ FailedValidation::new( make_tx_hash(456), diff --git a/node/src/accountant/scanners/scan_schedulers.rs b/node/src/accountant/scanners/scan_schedulers.rs index 2f9cc1d07..dbaf83681 100644 --- a/node/src/accountant/scanners/scan_schedulers.rs +++ b/node/src/accountant/scanners/scan_schedulers.rs @@ -12,8 +12,8 @@ use crate::sub_lib::utils::{ use actix::{Actor, Context, Handler}; use masq_lib::logger::Logger; use masq_lib::messages::ScanType; +use masq_lib::simple_clock::{SimpleClock, SimpleClockReal}; use std::fmt::{Debug, Display, Formatter}; -use std::sync::{Arc, Mutex}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; pub struct ScanSchedulers { @@ -79,8 +79,8 @@ impl From for ScanType { pub struct PayableScanScheduler { pub new_payable_notify_later: Box>, pub dyn_interval_computer: Box, - pub inner: Arc>, - pub new_payable_interval: Duration, + // pub inner: Arc>, + // pub new_payable_interval: Duration, pub new_payable_notify: Box>, pub retry_payable_notify: Box>, } @@ -89,24 +89,18 @@ impl PayableScanScheduler { fn new(new_payable_interval: Duration) -> Self { Self { new_payable_notify_later: Box::new(NotifyLaterHandleReal::default()), - dyn_interval_computer: Box::new(NewPayableScanDynIntervalComputerReal::default()), - inner: Arc::new(Mutex::new(PayableScanSchedulerInner::default())), - new_payable_interval, + dyn_interval_computer: Box::new(NewPayableScanDynIntervalComputerReal::new( + new_payable_interval, + )), + // inner: Arc::new(Mutex::new(PayableScanSchedulerInner::default())), + // new_payable_interval, new_payable_notify: Box::new(NotifyHandleReal::default()), retry_payable_notify: Box::new(NotifyHandleReal::default()), } } pub fn schedule_new_payable_scan(&self, ctx: &mut Context, logger: &Logger) { - let inner = self.inner.lock().expect("couldn't acquire inner"); - let last_new_payable_scan_timestamp = inner.last_new_payable_scan_timestamp; - let new_payable_interval = self.new_payable_interval; - let now = SystemTime::now(); - if let Some(interval) = self.dyn_interval_computer.compute_interval( - now, - last_new_payable_scan_timestamp, - new_payable_interval, - ) { + if let Some(interval) = self.dyn_interval_computer.compute_interval() { debug!( logger, "Scheduling a new-payable scan in {}ms", @@ -132,6 +126,10 @@ impl PayableScanScheduler { } } + pub fn update_last_new_payable_scan_timestamp(&mut self) { + self.dyn_interval_computer.zero_out(); + } + // This message ships into the Accountant's mailbox with no delay. // Can also be triggered by command, following up after the PendingPayableScanner // that requests it. That's why the response skeleton is possible to be used. @@ -152,49 +150,51 @@ impl PayableScanScheduler { } } -pub struct PayableScanSchedulerInner { - pub last_new_payable_scan_timestamp: SystemTime, -} +pub trait NewPayableScanDynIntervalComputer { + fn compute_interval(&self) -> Option; -impl Default for PayableScanSchedulerInner { - fn default() -> Self { - Self { - last_new_payable_scan_timestamp: UNIX_EPOCH, - } - } -} + fn zero_out(&mut self); -pub trait NewPayableScanDynIntervalComputer { - fn compute_interval( - &self, - now: SystemTime, - last_new_payable_scan_timestamp: SystemTime, - interval: Duration, - ) -> Option; + as_any_ref_in_trait!(); } -#[derive(Default)] -pub struct NewPayableScanDynIntervalComputerReal {} +pub struct NewPayableScanDynIntervalComputerReal { + scan_interval: Duration, + last_scan_timestamp: SystemTime, + clock: Box, +} impl NewPayableScanDynIntervalComputer for NewPayableScanDynIntervalComputerReal { - fn compute_interval( - &self, - now: SystemTime, - last_new_payable_scan_timestamp: SystemTime, - interval: Duration, - ) -> Option { + fn compute_interval(&self) -> Option { + let now = self.clock.now(); let elapsed = now - .duration_since(last_new_payable_scan_timestamp) + .duration_since(self.last_scan_timestamp) .unwrap_or_else(|_| { panic!( "Now ({:?}) earlier than past timestamp ({:?})", - now, last_new_payable_scan_timestamp + now, self.last_scan_timestamp ) }); - if elapsed >= interval { + if elapsed >= self.scan_interval { None } else { - Some(interval - elapsed) + Some(self.scan_interval - elapsed) + } + } + + fn zero_out(&mut self) { + self.last_scan_timestamp = SystemTime::now(); + } + + as_any_ref_in_trait_impl!(); +} + +impl NewPayableScanDynIntervalComputerReal { + pub fn new(scan_interval: Duration) -> Self { + Self { + scan_interval, + last_scan_timestamp: UNIX_EPOCH, + clock: Box::new(SimpleClockReal::default()), } } } @@ -338,8 +338,8 @@ impl RescheduleScanOnErrorResolverReal { // StartScanError::NothingToProcess can be evaluated); but may be cautious and // prevent starting the NewPayableScanner. Repeating this scan endlessly may alarm // the user. - // TODO Correctly, a check-point during the bootstrap that wouldn't allow to come - // this far should be the solution. Part of the issue mentioned in GH-799 + // TODO Correctly, a check-point during the bootstrap, not allowing to come + // this far, should be the solution. Part of the issue mentioned in GH-799 ScanReschedulingAfterEarlyStop::Schedule(ScanType::PendingPayables) } else { unreachable!( @@ -383,6 +383,7 @@ mod tests { NewPayableScanDynIntervalComputer, NewPayableScanDynIntervalComputerReal, PayableSequenceScanner, ScanReschedulingAfterEarlyStop, ScanSchedulers, }; + use crate::accountant::scanners::test_utils::NewPayableScanDynIntervalComputerMock; use crate::accountant::scanners::{ManulTriggerError, StartScanError}; use crate::sub_lib::accountant::ScanIntervals; use crate::test_utils::unshared_test_utils::TEST_SCAN_INTERVALS; @@ -391,7 +392,10 @@ mod tests { use masq_lib::logger::Logger; use masq_lib::messages::ScanType; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; + use masq_lib::test_utils::simple_clock::SimpleClockMock; + use masq_lib::test_utils::utils::TEST_DEFAULT_CHAIN; use std::panic::{catch_unwind, AssertUnwindSafe}; + use std::sync::{Arc, Mutex}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; #[test] @@ -405,15 +409,17 @@ mod tests { let schedulers = ScanSchedulers::new(scan_intervals, automatic_scans_enabled); + let payable_interval_computer = schedulers + .payable + .dyn_interval_computer + .as_any() + .downcast_ref::() + .unwrap(); assert_eq!( - schedulers.payable.new_payable_interval, + payable_interval_computer.scan_interval, scan_intervals.payable_scan_interval ); - let payable_scheduler_inner = schedulers.payable.inner.lock().unwrap(); - assert_eq!( - payable_scheduler_inner.last_new_payable_scan_timestamp, - UNIX_EPOCH - ); + assert_eq!(payable_interval_computer.last_scan_timestamp, UNIX_EPOCH); assert_eq!( schedulers.pending_payable.interval, scan_intervals.pending_payable_scan_interval @@ -427,7 +433,7 @@ mod tests { #[test] fn scan_dyn_interval_computer_computes_remaining_time_to_standard_interval_correctly() { - let now = SystemTime::now(); + let (clock, now) = fill_simple_clock_mock_and_return_now(); let inputs = vec![ ( now.checked_sub(Duration::from_secs(32)).unwrap(), @@ -445,12 +451,17 @@ mod tests { Duration::from_secs(4), ), ]; - let subject = NewPayableScanDynIntervalComputerReal::default(); + let mut subject = make_subject(); + subject.clock = Box::new(clock); inputs .into_iter() .for_each(|(past_instant, standard_interval, expected_result)| { - let result = subject.compute_interval(now, past_instant, standard_interval); + subject.scan_interval = standard_interval; + subject.last_scan_timestamp = past_instant; + + let result = subject.compute_interval(); + assert_eq!( result, Some(expected_result), @@ -463,28 +474,33 @@ mod tests { #[test] fn scan_dyn_interval_computer_realizes_the_standard_interval_has_been_exceeded() { - let now = SystemTime::now(); + let (clock, now) = fill_simple_clock_mock_and_return_now(); let inputs = vec![ ( now.checked_sub(Duration::from_millis(32001)).unwrap(), Duration::from_secs(32), ), ( - now.checked_sub(Duration::from_millis(1112)).unwrap(), - Duration::from_millis(1111), + now.checked_sub(Duration::from_nanos(1111112)).unwrap(), + Duration::from_nanos(1111111), ), ( now.checked_sub(Duration::from_secs(200)).unwrap(), Duration::from_secs(123), ), ]; - let subject = NewPayableScanDynIntervalComputerReal::default(); + let mut subject = make_subject(); + subject.clock = Box::new(clock); inputs .into_iter() .enumerate() .for_each(|(idx, (past_instant, standard_interval))| { - let result = subject.compute_interval(now, past_instant, standard_interval); + subject.scan_interval = standard_interval; + subject.last_scan_timestamp = past_instant; + + let result = subject.compute_interval(); + assert_eq!( result, None, @@ -498,13 +514,12 @@ mod tests { #[test] fn scan_dyn_interval_computer_realizes_standard_interval_just_met() { let now = SystemTime::now(); - let subject = NewPayableScanDynIntervalComputerReal::default(); + let mut subject = make_subject(); + subject.last_scan_timestamp = now.checked_sub(Duration::from_secs(180)).unwrap(); + subject.scan_interval = Duration::from_secs(180); + subject.clock = Box::new(SimpleClockMock::default().now_result(now)); - let result = subject.compute_interval( - now, - now.checked_sub(Duration::from_secs(32)).unwrap(), - Duration::from_secs(32), - ); + let result = subject.compute_interval(); assert_eq!( result, @@ -517,8 +532,8 @@ mod tests { #[test] #[cfg(windows)] #[should_panic( - expected = "Now (SystemTime { intervals: 116454735990000000 }) earlier than past timestamp \ - (SystemTime { intervals: 116454736000000000 })" + expected = "Now (SystemTime { intervals: 116454736000000000 }) earlier than past timestamp \ + (SystemTime { intervals: 116454737000000000 })" )] fn scan_dyn_interval_computer_panics() { test_scan_dyn_interval_computer_panics() @@ -527,8 +542,8 @@ mod tests { #[test] #[cfg(not(windows))] #[should_panic( - expected = "Now (SystemTime { tv_sec: 999999, tv_nsec: 0 }) earlier than past timestamp \ - (SystemTime { tv_sec: 1000000, tv_nsec: 0 })" + expected = "Now (SystemTime { tv_sec: 1000000, tv_nsec: 0 }) earlier than past timestamp \ + (SystemTime { tv_sec: 1000001, tv_nsec: 0 })" )] fn scan_dyn_interval_computer_panics() { test_scan_dyn_interval_computer_panics() @@ -538,15 +553,76 @@ mod tests { let now = UNIX_EPOCH .checked_add(Duration::from_secs(1_000_000)) .unwrap(); - let subject = NewPayableScanDynIntervalComputerReal::default(); + let mut subject = make_subject(); + subject.clock = Box::new(SimpleClockMock::default().now_result(now)); + subject.last_scan_timestamp = now.checked_add(Duration::from_secs(1)).unwrap(); - let _ = subject.compute_interval( - now.checked_sub(Duration::from_secs(1)).unwrap(), - now, - Duration::from_secs(32), + let _ = subject.compute_interval(); + } + + #[test] + fn zero_out_works_for_default_subject() { + let mut subject = make_subject(); + let last_scan_timestamp_before = subject.last_scan_timestamp; + let before_act = SystemTime::now(); + + subject.zero_out(); + + let after_act = SystemTime::now(); + let last_scan_timestamp_after = subject.last_scan_timestamp; + assert_eq!(last_scan_timestamp_before, UNIX_EPOCH); + assert!( + before_act <= last_scan_timestamp_after && last_scan_timestamp_after <= after_act, + "we expected the last_scan_timestamp to be reset to now, but it was not" ); } + #[test] + fn zero_out_works_for_general_subject() { + let mut subject = make_subject(); + subject.last_scan_timestamp = SystemTime::now() + .checked_sub(Duration::from_secs(100)) + .unwrap(); + let before_act = SystemTime::now(); + + subject.zero_out(); + + let after_act = SystemTime::now(); + let last_scan_timestamp_after = subject.last_scan_timestamp; + assert!( + before_act <= last_scan_timestamp_after && last_scan_timestamp_after <= after_act, + "we expected the last_scan_timestamp to be reset to now, but it was not" + ); + } + + #[test] + fn update_last_new_payable_scan_timestamp_works() { + let zero_out_params_arc = Arc::new(Mutex::new(vec![])); + let scan_intervals = ScanIntervals::compute_default(TEST_DEFAULT_CHAIN); + let mut subject = ScanSchedulers::new(scan_intervals, true); + subject.payable.dyn_interval_computer = Box::new( + NewPayableScanDynIntervalComputerMock::default().zero_out_params(&zero_out_params_arc), + ); + + subject.payable.update_last_new_payable_scan_timestamp(); + + let zero_out_params = zero_out_params_arc.lock().unwrap(); + assert_eq!(*zero_out_params, vec![()]) + } + + fn make_subject() -> NewPayableScanDynIntervalComputerReal { + // The interval is just a garbage value, we reset it in the tests by injection if needed + NewPayableScanDynIntervalComputerReal::new(Duration::from_secs(100)) + } + + fn fill_simple_clock_mock_and_return_now() -> (SimpleClockMock, SystemTime) { + let now = SystemTime::now(); + ( + (0..3).fold(SimpleClockMock::default(), |clock, _| clock.now_result(now)), + now, + ) + } + lazy_static! { static ref ALL_START_SCAN_ERRORS: Vec = { diff --git a/node/src/accountant/scanners/test_utils.rs b/node/src/accountant/scanners/test_utils.rs index 731fb508d..4b11abee2 100644 --- a/node/src/accountant/scanners/test_utils.rs +++ b/node/src/accountant/scanners/test_utils.rs @@ -336,31 +336,26 @@ impl ScannerMockMarker for ScannerMock>>, + compute_interval_params: Arc>>, compute_interval_results: RefCell>>, + zero_out_params: Arc>>, } impl NewPayableScanDynIntervalComputer for NewPayableScanDynIntervalComputerMock { - fn compute_interval( - &self, - now: SystemTime, - last_new_payable_scan_timestamp: SystemTime, - interval: Duration, - ) -> Option { - self.compute_interval_params.lock().unwrap().push(( - now, - last_new_payable_scan_timestamp, - interval, - )); + fn compute_interval(&self) -> Option { + self.compute_interval_params.lock().unwrap().push(()); self.compute_interval_results.borrow_mut().remove(0) } + + fn zero_out(&mut self) { + self.zero_out_params.lock().unwrap().push(()); + } + + as_any_ref_in_trait_impl!(); } impl NewPayableScanDynIntervalComputerMock { - pub fn compute_interval_params( - mut self, - params: &Arc>>, - ) -> Self { + pub fn compute_interval_params(mut self, params: &Arc>>) -> Self { self.compute_interval_params = params.clone(); self } @@ -369,6 +364,11 @@ impl NewPayableScanDynIntervalComputerMock { self.compute_interval_results.borrow_mut().push(result); self } + + pub fn zero_out_params(mut self, params: &Arc>>) -> Self { + self.zero_out_params = params.clone(); + self + } } pub enum ReplacementType diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs index 8d6fb49ed..b460363ee 100644 --- a/node/src/accountant/test_utils.rs +++ b/node/src/accountant/test_utils.rs @@ -25,14 +25,12 @@ use crate::accountant::payment_adjuster::{Adjustment, AnalysisError, PaymentAdju use crate::accountant::scanners::payable_scanner::msgs::PricedTemplatesMessage; use crate::accountant::scanners::payable_scanner::payment_adjuster_integration::PreparedAdjustment; use crate::accountant::scanners::payable_scanner::utils::PayableThresholdsGauge; -use crate::accountant::scanners::pending_payable_scanner::test_utils::ValidationFailureClockMock; use crate::accountant::scanners::pending_payable_scanner::utils::PendingPayableCache; use crate::accountant::scanners::pending_payable_scanner::PendingPayableScanner; use crate::accountant::scanners::receivable_scanner::ReceivableScanner; use crate::accountant::scanners::test_utils::PendingPayableCacheMock; use crate::accountant::{gwei_to_wei, Accountant}; use crate::blockchain::blockchain_interface::data_structures::{BlockchainTransaction, TxBlock}; -use crate::blockchain::errors::validation_status::ValidationFailureClock; use crate::blockchain::test_utils::make_block_hash; use crate::bootstrapper::BootstrapperConfig; use crate::database::rusqlite_wrappers::TransactionSafeWrapper; @@ -47,6 +45,8 @@ 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::simple_clock::SimpleClock; +use masq_lib::test_utils::simple_clock::SimpleClockMock; use masq_lib::test_utils::utils::TEST_DEFAULT_CHAIN; use rusqlite::{Connection, OpenFlags, Row}; use std::any::type_name; @@ -101,34 +101,6 @@ pub fn make_transaction_block(num: u64) -> TxBlock { } } -pub struct AccountantBuilder { - config_opt: Option, - consuming_wallet_opt: Option, - logger_opt: Option, - payable_dao_factory_opt: Option, - receivable_dao_factory_opt: Option, - sent_payable_dao_factory_opt: Option, - failed_payable_dao_factory_opt: Option, - banned_dao_factory_opt: Option, - config_dao_factory_opt: Option, -} - -impl Default for AccountantBuilder { - fn default() -> Self { - Self { - config_opt: None, - consuming_wallet_opt: None, - logger_opt: None, - payable_dao_factory_opt: None, - receivable_dao_factory_opt: None, - sent_payable_dao_factory_opt: None, - failed_payable_dao_factory_opt: None, - banned_dao_factory_opt: None, - config_dao_factory_opt: None, - } - } -} - pub enum DaoWithDestination { ForAccountantBody(T), ForPendingPayableScanner(T), @@ -276,6 +248,34 @@ const RECEIVABLE_DAOS_ACCOUNTANT_INITIALIZATION_ORDER: [DestinationMarker; 2] = DestinationMarker::ReceivableScanner, ]; +pub struct AccountantBuilder { + config_opt: Option, + consuming_wallet_opt: Option, + logger_opt: Option, + payable_dao_factory_opt: Option, + receivable_dao_factory_opt: Option, + sent_payable_dao_factory_opt: Option, + failed_payable_dao_factory_opt: Option, + banned_dao_factory_opt: Option, + config_dao_factory_opt: Option, +} + +impl Default for AccountantBuilder { + fn default() -> Self { + Self { + config_opt: None, + consuming_wallet_opt: None, + logger_opt: None, + payable_dao_factory_opt: None, + receivable_dao_factory_opt: None, + sent_payable_dao_factory_opt: None, + failed_payable_dao_factory_opt: None, + banned_dao_factory_opt: None, + config_dao_factory_opt: None, + } + } +} + impl AccountantBuilder { pub fn bootstrapper_config(mut self, config: BootstrapperConfig) -> Self { self.config_opt = Some(config); @@ -355,39 +355,6 @@ impl AccountantBuilder { ) } - // pub fn sent_payable_dao(mut self, sent_payable_dao: SentPayableDaoMock) -> Self { - // // TODO: GH-605: Bert Merge Cleanup - Prefer the standard create_or_update_factory! style - as in GH-598 - // match self.sent_payable_dao_factory_opt { - // None => { - // self.sent_payable_dao_factory_opt = - // Some(SentPayableDaoFactoryMock::new().make_result(sent_payable_dao)) - // } - // Some(sent_payable_dao_factory) => { - // self.sent_payable_dao_factory_opt = - // Some(sent_payable_dao_factory.make_result(sent_payable_dao)) - // } - // } - // - // self - // } - // - // pub fn failed_payable_dao(mut self, failed_payable_dao: FailedPayableDaoMock) -> Self { - // // TODO: GH-605: Bert Merge cleanup - Prefer the standard create_or_update_factory! style - as in GH-598 - // - // match self.failed_payable_dao_factory_opt { - // None => { - // self.failed_payable_dao_factory_opt = - // Some(FailedPayableDaoFactoryMock::new().make_result(failed_payable_dao)) - // } - // Some(failed_payable_dao_factory) => { - // self.failed_payable_dao_factory_opt = - // Some(failed_payable_dao_factory.make_result(failed_payable_dao)) - // } - // } - // - // self - // } - //TODO this method seems to be never used? pub fn banned_dao(mut self, banned_dao: BannedDaoMock) -> Self { match self.banned_dao_factory_opt { @@ -1315,7 +1282,7 @@ pub struct PendingPayableScannerBuilder { financial_statistics: FinancialStatistics, current_sent_payables: Box>, yet_unproven_failed_payables: Box>, - clock: Box, + clock: Box, } impl PendingPayableScannerBuilder { @@ -1328,7 +1295,7 @@ impl PendingPayableScannerBuilder { financial_statistics: FinancialStatistics::default(), current_sent_payables: Box::new(PendingPayableCacheMock::default()), yet_unproven_failed_payables: Box::new(PendingPayableCacheMock::default()), - clock: Box::new(ValidationFailureClockMock::default()), + clock: Box::new(SimpleClockMock::default()), } } @@ -1360,7 +1327,7 @@ impl PendingPayableScannerBuilder { self } - pub fn validation_failure_clock(mut self, clock: Box) -> Self { + pub fn validation_failure_clock(mut self, clock: Box) -> Self { self.clock = clock; self } diff --git a/node/src/blockchain/errors/validation_status.rs b/node/src/blockchain/errors/validation_status.rs index a3e8ada27..6346548a6 100644 --- a/node/src/blockchain/errors/validation_status.rs +++ b/node/src/blockchain/errors/validation_status.rs @@ -1,6 +1,7 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::blockchain::errors::BlockchainErrorKind; +use masq_lib::simple_clock::SimpleClock; use serde::de::{SeqAccess, Visitor}; use serde::ser::SerializeSeq; use serde::{ @@ -111,17 +112,13 @@ impl<'de> Visitor<'de> for PreviousAttemptsVisitor { } impl PreviousAttempts { - pub fn new(error: BlockchainErrorKind, clock: &dyn ValidationFailureClock) -> Self { + pub fn new(error: BlockchainErrorKind, clock: &dyn SimpleClock) -> Self { Self { inner: btreemap!(error => ErrorStats::now(clock)), } } - pub fn add_attempt( - mut self, - error: BlockchainErrorKind, - clock: &dyn ValidationFailureClock, - ) -> Self { + pub fn add_attempt(mut self, error: BlockchainErrorKind, clock: &dyn SimpleClock) -> Self { self.inner .entry(error) .and_modify(|stats| stats.increment()) @@ -138,7 +135,7 @@ pub struct ErrorStats { } impl ErrorStats { - pub fn now(clock: &dyn ValidationFailureClock) -> Self { + pub fn now(clock: &dyn SimpleClock) -> Self { Self { first_seen: clock.now(), attempts: 1, @@ -150,26 +147,14 @@ impl ErrorStats { } } -pub trait ValidationFailureClock { - fn now(&self) -> SystemTime; -} - -#[derive(Default)] -pub struct ValidationFailureClockReal {} - -impl ValidationFailureClock for ValidationFailureClockReal { - fn now(&self) -> SystemTime { - SystemTime::now() - } -} - #[cfg(test)] mod tests { use super::*; - use crate::accountant::scanners::pending_payable_scanner::test_utils::ValidationFailureClockMock; use crate::blockchain::errors::internal_errors::InternalErrorKind; use crate::blockchain::errors::rpc_errors::{AppRpcErrorKind, LocalErrorKind}; use crate::test_utils::serde_serializer_mock::{SerdeSerializerMock, SerializeSeqMock}; + use masq_lib::simple_clock::SimpleClockReal; + use masq_lib::test_utils::simple_clock::SimpleClockMock; use serde::ser::Error as SerdeError; use std::collections::BTreeSet; use std::time::Duration; @@ -177,7 +162,7 @@ mod tests { #[test] fn previous_attempts_and_validation_failure_clock_work_together_fine() { - let validation_failure_clock = ValidationFailureClockReal::default(); + let validation_failure_clock = SimpleClockReal::default(); // new() let timestamp_a = SystemTime::now(); let subject = PreviousAttempts::new( @@ -261,7 +246,7 @@ mod tests { // #[test] // fn previous_attempts_hash_works_correctly() { // let now = SystemTime::now(); - // let clock = ValidationFailureClockMock::default() + // let clock = SimpleClockMock::default() // .now_result(now) // .now_result(now) // .now_result(now + Duration::from_secs(2)); @@ -300,7 +285,7 @@ mod tests { #[test] fn previous_attempts_ordering_works_correctly_with_mock() { let now = SystemTime::now(); - let clock = ValidationFailureClockMock::default() + let clock = SimpleClockMock::default() .now_result(now) .now_result(now + Duration::from_secs(1)) .now_result(now + Duration::from_secs(2)) @@ -331,7 +316,7 @@ mod tests { let timestamp = UNIX_EPOCH .checked_add(Duration::from_secs(1234567890)) .unwrap(); - let clock = ValidationFailureClockMock::default().now_result(timestamp); + let clock = SimpleClockMock::default().now_result(timestamp); let result = serde_json::to_string(&PreviousAttempts::new(err, &clock)).unwrap(); @@ -349,7 +334,7 @@ mod tests { let timestamp = UNIX_EPOCH .checked_add(Duration::from_secs(1234567890)) .unwrap(); - let clock = ValidationFailureClockMock::default().now_result(timestamp); + let clock = SimpleClockMock::default().now_result(timestamp); let result = PreviousAttempts::new(err, &clock).serialize(mock); @@ -366,7 +351,7 @@ mod tests { let timestamp = UNIX_EPOCH .checked_add(Duration::from_secs(1234567890)) .unwrap(); - let clock = ValidationFailureClockMock::default().now_result(timestamp); + let clock = SimpleClockMock::default().now_result(timestamp); let result = PreviousAttempts::new(err, &clock).serialize(mock); @@ -383,7 +368,7 @@ mod tests { let timestamp = UNIX_EPOCH .checked_add(Duration::from_secs(1234567890)) .unwrap(); - let clock = ValidationFailureClockMock::default().now_result(timestamp); + let clock = SimpleClockMock::default().now_result(timestamp); let result = PreviousAttempts::new(err, &clock).serialize(mock); @@ -399,7 +384,7 @@ mod tests { let timestamp = UNIX_EPOCH .checked_add(Duration::from_secs(1234567890)) .unwrap(); - let clock = ValidationFailureClockMock::default().now_result(timestamp); + let clock = SimpleClockMock::default().now_result(timestamp); assert_eq!( result.unwrap().inner, btreemap!(BlockchainErrorKind::AppRpc(AppRpcErrorKind::Local(LocalErrorKind::Internal)) => ErrorStats::now(&clock)) @@ -422,7 +407,7 @@ mod tests { #[test] fn validation_status_ordering_works_correctly() { let now = SystemTime::now(); - let clock = ValidationFailureClockMock::default() + let clock = SimpleClockMock::default() .now_result(now) .now_result(now + Duration::from_secs(1));