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 ce93a1f17..78554557d 100644 --- a/node/src/accountant/db_access_objects/failed_payable_dao.rs +++ b/node/src/accountant/db_access_objects/failed_payable_dao.rs @@ -1,11 +1,13 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::db_access_objects::failed_payable_dao::FailureRetrieveCondition::UncheckedPendingTooLong; -use crate::accountant::db_access_objects::utils::{TxHash, TxIdentifiers, VigilantRusqliteFlatten}; +use crate::accountant::db_access_objects::utils::{ + DaoFactoryReal, TxHash, TxIdentifiers, VigilantRusqliteFlatten, +}; use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; use crate::accountant::{checked_conversion, comma_joined_stringifiable}; use crate::database::rusqlite_wrappers::ConnectionWrapper; +use itertools::Itertools; use masq_lib::utils::ExpectValue; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::fmt::{Display, Formatter}; use std::str::FromStr; use web3::types::Address; @@ -23,6 +25,26 @@ pub enum FailedPayableDaoError { pub enum FailureReason { PendingTooLong, NonceIssue, + General, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum FailureStatus { + RetryRequired, + RecheckRequired, + Concluded, +} + +impl FromStr for FailureStatus { + type Err = String; + fn from_str(s: &str) -> Result { + match s { + "RetryRequired" => Ok(FailureStatus::RetryRequired), + "RecheckRequired" => Ok(FailureStatus::RecheckRequired), + "Concluded" => Ok(FailureStatus::Concluded), + _ => Err(format!("Invalid FailureStatus: {}", s)), + } + } } impl FromStr for FailureReason { @@ -32,6 +54,7 @@ impl FromStr for FailureReason { match s { "PendingTooLong" => Ok(FailureReason::PendingTooLong), "NonceIssue" => Ok(FailureReason::NonceIssue), + "General" => Ok(FailureReason::General), _ => Err(format!("Invalid FailureReason: {}", s)), } } @@ -46,18 +69,18 @@ pub struct FailedTx { pub gas_price_wei: u128, pub nonce: u64, pub reason: FailureReason, - pub rechecked: bool, + pub status: FailureStatus, } pub enum FailureRetrieveCondition { - UncheckedPendingTooLong, + ByStatus(FailureStatus), } impl Display for FailureRetrieveCondition { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - FailureRetrieveCondition::UncheckedPendingTooLong => { - write!(f, "WHERE reason = 'PendingTooLong' AND rechecked = 0",) + FailureRetrieveCondition::ByStatus(status) => { + write!(f, "WHERE status = '{:?}'", status) } } } @@ -67,7 +90,10 @@ pub trait FailedPayableDao { fn get_tx_identifiers(&self, hashes: &HashSet) -> TxIdentifiers; fn insert_new_records(&self, txs: &[FailedTx]) -> Result<(), FailedPayableDaoError>; fn retrieve_txs(&self, condition: Option) -> Vec; - fn mark_as_rechecked(&self) -> Result<(), FailedPayableDaoError>; + fn update_statuses( + &self, + status_updates: HashMap, + ) -> Result<(), FailedPayableDaoError>; fn delete_records(&self, hashes: &HashSet) -> Result<(), FailedPayableDaoError>; } @@ -128,13 +154,6 @@ impl FailedPayableDao for FailedPayableDaoReal<'_> { ))); } - if let Some(_rechecked_tx) = txs.iter().find(|tx| tx.rechecked) { - return Err(FailedPayableDaoError::InvalidInput(format!( - "Already rechecked transaction(s) provided: {:?}", - txs - ))); - } - let sql = format!( "INSERT INTO failed_payable (\ tx_hash, \ @@ -146,7 +165,7 @@ impl FailedPayableDao for FailedPayableDaoReal<'_> { gas_price_wei_low_b, \ nonce, \ reason, \ - rechecked + status ) VALUES {}", comma_joined_stringifiable(txs, |tx| { let amount_checked = checked_conversion::(tx.amount); @@ -155,7 +174,7 @@ impl FailedPayableDao for FailedPayableDaoReal<'_> { let (gas_price_wei_high_b, gas_price_wei_low_b) = BigIntDivider::deconstruct(gas_price_wei_checked); format!( - "('{:?}', '{:?}', {}, {}, {}, {}, {}, {}, '{:?}', {})", + "('{:?}', '{:?}', {}, {}, {}, {}, {}, {}, '{:?}', '{:?}')", tx.hash, tx.receiver_address, amount_high_b, @@ -165,7 +184,7 @@ impl FailedPayableDao for FailedPayableDaoReal<'_> { gas_price_wei_low_b, tx.nonce, tx.reason, - tx.rechecked + tx.status ) }) ); @@ -196,7 +215,7 @@ impl FailedPayableDao for FailedPayableDaoReal<'_> { gas_price_wei_low_b, \ nonce, \ reason, \ - rechecked \ + status \ FROM failed_payable" .to_string(); let sql = match condition { @@ -227,8 +246,9 @@ impl FailedPayableDao for FailedPayableDaoReal<'_> { let reason_str: String = row.get(8).expectv("reason"); let reason = FailureReason::from_str(&reason_str).expect("Failed to parse FailureReason"); - let rechecked_as_integer: u8 = row.get(9).expectv("rechecked"); - let rechecked = rechecked_as_integer == 1; + let status_str: String = row.get(9).expectv("status"); + let status = + FailureStatus::from_str(&status_str).expect("Failed to parse FailureStatus"); Ok(FailedTx { hash, @@ -238,7 +258,7 @@ impl FailedPayableDao for FailedPayableDaoReal<'_> { gas_price_wei, nonce, reason, - rechecked, + status, }) }) .expect("Failed to execute query") @@ -246,27 +266,40 @@ impl FailedPayableDao for FailedPayableDaoReal<'_> { .collect() } - fn mark_as_rechecked(&self) -> Result<(), FailedPayableDaoError> { - let txs = self.retrieve_txs(Some(UncheckedPendingTooLong)); - let hashes_vec: Vec = txs.iter().map(|tx| tx.hash).collect(); - let hashes_string = comma_joined_stringifiable(&hashes_vec, |hash| format!("'{:?}'", hash)); + fn update_statuses( + &self, + status_updates: HashMap, + ) -> Result<(), FailedPayableDaoError> { + if status_updates.is_empty() { + return Err(FailedPayableDaoError::EmptyInput); + } + + let case_statements = status_updates + .iter() + .map(|(hash, status)| format!("WHEN tx_hash = '{:?}' THEN '{:?}'", hash, status)) + .join(" "); + let tx_hashes = comma_joined_stringifiable(&status_updates.keys().collect_vec(), |hash| { + format!("'{:?}'", hash) + }); let sql = format!( - "UPDATE failed_payable SET rechecked = 1 WHERE tx_hash IN ({})", - hashes_string + "UPDATE failed_payable \ + SET \ + status = CASE \ + {case_statements} \ + END \ + WHERE tx_hash IN ({tx_hashes})" ); match self.conn.prepare(&sql).expect("Internal error").execute([]) { Ok(rows_changed) => { - if rows_changed == txs.len() { + if rows_changed == status_updates.len() { Ok(()) } else { - // This should never occur because we retrieve transaction hashes - // under the condition that all retrieved transactions are unchecked. Err(FailedPayableDaoError::PartialExecution(format!( - "Only {} of {} records has been marked as rechecked.", + "Only {} of {} records had their status updated.", rows_changed, - txs.len(), + status_updates.len(), ))) } } @@ -304,14 +337,27 @@ impl FailedPayableDao for FailedPayableDaoReal<'_> { } } +pub trait FailedPayableDaoFactory { + fn make(&self) -> Box; +} + +impl FailedPayableDaoFactory for DaoFactoryReal { + fn make(&self) -> Box { + Box::new(FailedPayableDaoReal::new(self.make_connection())) + } +} + #[cfg(test)] mod tests { use crate::accountant::db_access_objects::failed_payable_dao::FailureReason::{ - NonceIssue, PendingTooLong, + General, NonceIssue, PendingTooLong, + }; + use crate::accountant::db_access_objects::failed_payable_dao::FailureStatus::{ + Concluded, RecheckRequired, RetryRequired, }; use crate::accountant::db_access_objects::failed_payable_dao::{ FailedPayableDao, FailedPayableDaoError, FailedPayableDaoReal, FailureReason, - FailureRetrieveCondition, + FailureRetrieveCondition, FailureStatus, }; use crate::accountant::db_access_objects::test_utils::{ make_read_only_db_connection, FailedTxBuilder, @@ -324,7 +370,7 @@ mod tests { use crate::database::test_utils::ConnectionWrapperMock; use masq_lib::test_utils::utils::ensure_node_home_directory_exists; use rusqlite::Connection; - use std::collections::HashSet; + use std::collections::{HashMap, HashSet}; use std::str::FromStr; #[test] @@ -379,10 +425,13 @@ mod tests { .initialize(&home_dir, DbInitializationConfig::test_default()) .unwrap(); let hash = make_tx_hash(123); - let tx1 = FailedTxBuilder::default().hash(hash).build(); + let tx1 = FailedTxBuilder::default() + .hash(hash) + .status(RetryRequired) + .build(); let tx2 = FailedTxBuilder::default() .hash(hash) - .rechecked(true) + .status(RecheckRequired) .build(); let subject = FailedPayableDaoReal::new(wrapped_conn); @@ -396,12 +445,12 @@ mod tests { hash: 0x000000000000000000000000000000000000000000000000000000000000007b, \ receiver_address: 0x0000000000000000000000000000000000000000, \ amount: 0, timestamp: 0, gas_price_wei: 0, \ - nonce: 0, reason: PendingTooLong, rechecked: false }, \ + nonce: 0, reason: PendingTooLong, status: RetryRequired }, \ FailedTx { \ hash: 0x000000000000000000000000000000000000000000000000000000000000007b, \ receiver_address: 0x0000000000000000000000000000000000000000, \ amount: 0, timestamp: 0, gas_price_wei: 0, \ - nonce: 0, reason: PendingTooLong, rechecked: true }]" + nonce: 0, reason: PendingTooLong, status: RecheckRequired }]" .to_string() )) ); @@ -417,10 +466,13 @@ mod tests { .initialize(&home_dir, DbInitializationConfig::test_default()) .unwrap(); let hash = make_tx_hash(123); - let tx1 = FailedTxBuilder::default().hash(hash).build(); + let tx1 = FailedTxBuilder::default() + .hash(hash) + .status(RetryRequired) + .build(); let tx2 = FailedTxBuilder::default() .hash(hash) - .rechecked(true) + .status(RecheckRequired) .build(); let subject = FailedPayableDaoReal::new(wrapped_conn); let initial_insertion_result = subject.insert_new_records(&vec![tx1]); @@ -438,37 +490,6 @@ mod tests { ); } - #[test] - fn insert_new_records_throws_err_if_an_already_rechecked_tx_is_supplied() { - let home_dir = ensure_node_home_directory_exists( - "failed_payable_dao", - "insert_new_records_throws_err_if_an_already_rechecked_tx_is_supplied", - ); - let wrapped_conn = DbInitializerReal::default() - .initialize(&home_dir, DbInitializationConfig::test_default()) - .unwrap(); - let subject = FailedPayableDaoReal::new(wrapped_conn); - let tx1 = FailedTxBuilder::default() - .hash(make_tx_hash(1)) - .rechecked(true) - .build(); - let tx2 = FailedTxBuilder::default() - .hash(make_tx_hash(2)) - .rechecked(false) - .build(); - let input = vec![tx1, tx2]; - - let result = subject.insert_new_records(&input); - - assert_eq!( - result, - Err(FailedPayableDaoError::InvalidInput(format!( - "Already rechecked transaction(s) provided: {:?}", - input - ))) - ); - } - #[test] fn insert_new_records_returns_err_if_partially_executed() { let setup_conn = Connection::open_in_memory().unwrap(); @@ -547,18 +568,32 @@ mod tests { Ok(PendingTooLong) ); assert_eq!(FailureReason::from_str("NonceIssue"), Ok(NonceIssue)); + assert_eq!(FailureReason::from_str("General"), Ok(General)); assert_eq!( FailureReason::from_str("InvalidReason"), Err("Invalid FailureReason: InvalidReason".to_string()) ); } + #[test] + fn failure_status_from_str_works() { + assert_eq!(FailureStatus::from_str("RetryRequired"), Ok(RetryRequired)); + assert_eq!( + FailureStatus::from_str("RecheckRequired"), + Ok(RecheckRequired) + ); + assert_eq!(FailureStatus::from_str("Concluded"), Ok(Concluded)); + assert_eq!( + FailureStatus::from_str("InvalidStatus"), + Err("Invalid FailureStatus: InvalidStatus".to_string()) + ); + } + #[test] fn retrieve_condition_display_works() { - let expected_condition = "WHERE reason = 'PendingTooLong' AND rechecked = 0"; assert_eq!( - FailureRetrieveCondition::UncheckedPendingTooLong.to_string(), - expected_condition + FailureRetrieveCondition::ByStatus(RetryRequired).to_string(), + "WHERE status = 'RetryRequired'" ); } @@ -601,32 +636,38 @@ mod tests { .hash(make_tx_hash(1)) .reason(PendingTooLong) .timestamp(now - 3600) - .rechecked(false) + .status(RetryRequired) .build(); let tx2 = FailedTxBuilder::default() .hash(make_tx_hash(2)) .reason(NonceIssue) - .rechecked(false) + .timestamp(now - 3600) + .status(RetryRequired) .build(); let tx3 = FailedTxBuilder::default() .hash(make_tx_hash(3)) .reason(PendingTooLong) - .rechecked(false) + .status(RecheckRequired) + .build(); + let tx4 = FailedTxBuilder::default() + .hash(make_tx_hash(4)) + .reason(PendingTooLong) + .status(Concluded) .timestamp(now - 3000) .build(); subject - .insert_new_records(&vec![tx1.clone(), tx2, tx3.clone()]) + .insert_new_records(&vec![tx1.clone(), tx2.clone(), tx3, tx4]) .unwrap(); - let result = subject.retrieve_txs(Some(FailureRetrieveCondition::UncheckedPendingTooLong)); + let result = subject.retrieve_txs(Some(FailureRetrieveCondition::ByStatus(RetryRequired))); - assert_eq!(result, vec![tx1, tx3]); + assert_eq!(result, vec![tx1, tx2]); } #[test] - fn mark_as_rechecked_works() { + fn update_statuses_works() { let home_dir = - ensure_node_home_directory_exists("failed_payable_dao", "mark_as_rechecked_works"); + ensure_node_home_directory_exists("failed_payable_dao", "update_statuses_works"); let wrapped_conn = DbInitializerReal::default() .initialize(&home_dir, DbInitializationConfig::test_default()) .unwrap(); @@ -634,47 +675,72 @@ mod tests { let tx1 = FailedTxBuilder::default() .hash(make_tx_hash(1)) .reason(NonceIssue) - .rechecked(false) + .status(RetryRequired) .build(); let tx2 = FailedTxBuilder::default() .hash(make_tx_hash(2)) .reason(PendingTooLong) - .rechecked(false) + .status(RetryRequired) .build(); let tx3 = FailedTxBuilder::default() .hash(make_tx_hash(3)) .reason(PendingTooLong) - .rechecked(false) + .status(RecheckRequired) + .build(); + let tx4 = FailedTxBuilder::default() + .hash(make_tx_hash(4)) + .reason(PendingTooLong) + .status(RecheckRequired) .build(); - let tx1_pre_checked_state = tx1.rechecked; - let tx2_pre_checked_state = tx2.rechecked; - let tx3_pre_checked_state = tx3.rechecked; subject - .insert_new_records(&vec![tx1, tx2.clone(), tx3.clone()]) + .insert_new_records(&vec![tx1.clone(), tx2.clone(), tx3.clone(), tx4]) .unwrap(); + let hashmap = HashMap::from([ + (tx1.hash, Concluded), + (tx2.hash, RecheckRequired), + (tx3.hash, Concluded), + ]); - let result = subject.mark_as_rechecked(); + let result = subject.update_statuses(hashmap); let updated_txs = subject.retrieve_txs(None); assert_eq!(result, Ok(())); - assert_eq!(tx1_pre_checked_state, false); - assert_eq!(tx2_pre_checked_state, false); - assert_eq!(tx3_pre_checked_state, false); - assert_eq!(updated_txs[0].rechecked, false); - assert_eq!(updated_txs[1].rechecked, true); - assert_eq!(updated_txs[2].rechecked, true); + assert_eq!(tx1.status, RetryRequired); + assert_eq!(updated_txs[0].status, Concluded); + assert_eq!(tx2.status, RetryRequired); + assert_eq!(updated_txs[1].status, RecheckRequired); + assert_eq!(tx3.status, RecheckRequired); + assert_eq!(updated_txs[2].status, Concluded); + assert_eq!(tx3.status, RecheckRequired); + assert_eq!(updated_txs[3].status, RecheckRequired); + } + + #[test] + fn update_statuses_handles_empty_input_error() { + let home_dir = ensure_node_home_directory_exists( + "failed_payable_dao", + "update_statuses_handles_empty_input_error", + ); + let wrapped_conn = DbInitializerReal::default() + .initialize(&home_dir, DbInitializationConfig::test_default()) + .unwrap(); + let subject = FailedPayableDaoReal::new(wrapped_conn); + + let result = subject.update_statuses(HashMap::new()); + + assert_eq!(result, Err(FailedPayableDaoError::EmptyInput)); } #[test] - fn mark_as_rechecked_handles_sql_error() { + fn update_statuses_handles_sql_error() { let home_dir = ensure_node_home_directory_exists( "failed_payable_dao", - "mark_as_rechecked_handles_sql_error", + "update_statuses_handles_sql_error", ); let wrapped_conn = make_read_only_db_connection(home_dir); let subject = FailedPayableDaoReal::new(Box::new(wrapped_conn)); - let result = subject.mark_as_rechecked(); + let result = subject.update_statuses(HashMap::from([(make_tx_hash(1), RecheckRequired)])); assert_eq!( result, diff --git a/node/src/accountant/db_access_objects/test_utils.rs b/node/src/accountant/db_access_objects/test_utils.rs index 598a4121d..004a76761 100644 --- a/node/src/accountant/db_access_objects/test_utils.rs +++ b/node/src/accountant/db_access_objects/test_utils.rs @@ -6,7 +6,7 @@ use rusqlite::{Connection, OpenFlags}; use crate::accountant::db_access_objects::sent_payable_dao::{ Tx}; use crate::accountant::db_access_objects::utils::{current_unix_timestamp, TxHash}; use web3::types::{Address}; -use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, FailureReason}; +use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, FailureReason, FailureStatus}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::TransactionBlock; use crate::database::db_initializer::{DbInitializationConfig, DbInitializer, DbInitializerReal, DATABASE_FILE}; use crate::database::rusqlite_wrappers::ConnectionWrapperReal; @@ -69,7 +69,7 @@ pub struct FailedTxBuilder { gas_price_wei_opt: Option, nonce_opt: Option, reason_opt: Option, - rechecked_opt: Option, + status_opt: Option, } impl FailedTxBuilder { @@ -97,8 +97,8 @@ impl FailedTxBuilder { self } - pub fn rechecked(mut self, rechecked: bool) -> Self { - self.rechecked_opt = Some(rechecked); + pub fn status(mut self, failure_status: FailureStatus) -> Self { + self.status_opt = Some(failure_status); self } @@ -113,7 +113,9 @@ impl FailedTxBuilder { reason: self .reason_opt .unwrap_or_else(|| FailureReason::PendingTooLong), - rechecked: self.rechecked_opt.unwrap_or_else(|| false), + status: self + .status_opt + .unwrap_or_else(|| FailureStatus::RetryRequired), } } } diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index cbd844bc4..041fe2196 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -520,6 +520,9 @@ impl StartableScanner for Payabl _logger: &Logger, ) -> Result { todo!("Complete me under GH-605") + // 1. Find the failed payables + // 2. Look into the payable DAO to update the amount + // 3. Prepare UnpricedQualifiedPayables } } @@ -1737,6 +1740,17 @@ mod tests { #[test] fn retry_payable_scanner_can_initiate_a_scan() { + // + // Setup Part: + // DAOs: PayableDao, FailedPayableDao + // Fetch data from FailedPayableDao (inject it into Payable Scanner -- allow the change in production code). + // Scanners constructor will require to create it with the Factory -- try it + // Configure it such that it returns at least 2 failed tx + // Once I get those 2 records, I should get hold of those identifiers used in the Payable DAO + // Update the new balance for those transactions + // Modify Payable DAO and add another method, that will return just the corresponding payments + // The account which I get from the PayableDAO can go straight to the QualifiedPayableBeforePriceSelection + todo!("this must be set up under GH-605"); // TODO make sure the QualifiedPayableRawPack will express the difference from // the NewPayable scanner: The QualifiedPayablesBeforeGasPriceSelection needs to carry diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs index a186ff016..a09d734cd 100644 --- a/node/src/accountant/test_utils.rs +++ b/node/src/accountant/test_utils.rs @@ -3,6 +3,10 @@ #![cfg(test)] use crate::accountant::db_access_objects::banned_dao::{BannedDao, BannedDaoFactory}; +use crate::accountant::db_access_objects::failed_payable_dao::{ + FailedPayableDao, FailedPayableDaoError, FailedPayableDaoFactory, FailedTx, + FailureRetrieveCondition, FailureStatus, +}; use crate::accountant::db_access_objects::payable_dao::{ PayableAccount, PayableDao, PayableDaoError, PayableDaoFactory, }; @@ -13,7 +17,7 @@ use crate::accountant::db_access_objects::receivable_dao::{ ReceivableAccount, ReceivableDao, ReceivableDaoError, ReceivableDaoFactory, }; use crate::accountant::db_access_objects::utils::{ - from_unix_timestamp, to_unix_timestamp, CustomQuery, + from_unix_timestamp, to_unix_timestamp, CustomQuery, TxHash, TxIdentifiers, }; use crate::accountant::payment_adjuster::{Adjustment, AnalysisError, PaymentAdjuster}; use crate::accountant::scanners::payable_scanner_extension::msgs::{ @@ -45,6 +49,7 @@ use masq_lib::logger::Logger; use rusqlite::{Connection, OpenFlags, Row}; use std::any::type_name; use std::cell::RefCell; +use std::collections::{HashMap, HashSet}; use std::fmt::Debug; use std::path::Path; use std::rc::Rc; @@ -1077,6 +1082,158 @@ impl PendingPayableDaoFactoryMock { } } +#[derive(Default)] +pub struct FailedPayableDaoMock { + get_tx_identifiers_params: Arc>>>, + get_tx_identifiers_results: RefCell>, + insert_new_records_params: Arc>>>, + insert_new_records_results: RefCell>>, + retrieve_txs_params: Arc>>>, + retrieve_txs_results: RefCell>>, + update_statuses_params: Arc>>>, + update_statuses_results: RefCell>>, + delete_records_params: Arc>>>, + delete_records_results: RefCell>>, +} + +impl FailedPayableDao for FailedPayableDaoMock { + fn get_tx_identifiers(&self, hashes: &HashSet) -> TxIdentifiers { + self.get_tx_identifiers_params + .lock() + .unwrap() + .push(hashes.clone()); + self.get_tx_identifiers_results.borrow_mut().remove(0) + } + + fn insert_new_records(&self, txs: &[FailedTx]) -> Result<(), FailedPayableDaoError> { + self.insert_new_records_params + .lock() + .unwrap() + .push(txs.to_vec()); + self.insert_new_records_results.borrow_mut().remove(0) + } + + fn retrieve_txs(&self, condition: Option) -> Vec { + self.retrieve_txs_params.lock().unwrap().push(condition); + self.retrieve_txs_results.borrow_mut().remove(0) + } + + fn update_statuses( + &self, + status_updates: HashMap, + ) -> Result<(), FailedPayableDaoError> { + self.update_statuses_params + .lock() + .unwrap() + .push(status_updates); + self.update_statuses_results.borrow_mut().remove(0) + } + + fn delete_records(&self, hashes: &HashSet) -> Result<(), FailedPayableDaoError> { + self.delete_records_params + .lock() + .unwrap() + .push(hashes.clone()); + self.delete_records_results.borrow_mut().remove(0) + } +} + +impl FailedPayableDaoMock { + pub fn new() -> Self { + Self::default() + } + + pub fn get_tx_identifiers_params(mut self, params: &Arc>>>) -> Self { + self.get_tx_identifiers_params = params.clone(); + self + } + + pub fn get_tx_identifiers_result(self, result: TxIdentifiers) -> Self { + self.get_tx_identifiers_results.borrow_mut().push(result); + self + } + + pub fn insert_new_records_params(mut self, params: &Arc>>>) -> Self { + self.insert_new_records_params = params.clone(); + self + } + + pub fn insert_new_records_result(self, result: Result<(), FailedPayableDaoError>) -> Self { + self.insert_new_records_results.borrow_mut().push(result); + self + } + + pub fn retrieve_txs_params( + mut self, + params: &Arc>>>, + ) -> Self { + self.retrieve_txs_params = params.clone(); + self + } + + pub fn retrieve_txs_result(self, result: Vec) -> Self { + self.retrieve_txs_results.borrow_mut().push(result); + self + } + + pub fn update_statuses_params( + mut self, + params: &Arc>>>, + ) -> Self { + self.update_statuses_params = params.clone(); + self + } + + pub fn update_statuses_result(self, result: Result<(), FailedPayableDaoError>) -> Self { + self.update_statuses_results.borrow_mut().push(result); + self + } + + pub fn delete_records_params(mut self, params: &Arc>>>) -> Self { + self.delete_records_params = params.clone(); + self + } + + pub fn delete_records_result(self, result: Result<(), FailedPayableDaoError>) -> Self { + self.delete_records_results.borrow_mut().push(result); + self + } +} + +pub struct FailedPayableDaoFactoryMock { + make_params: Arc>>, + make_results: RefCell>>, +} + +impl FailedPayableDaoFactory for FailedPayableDaoFactoryMock { + fn make(&self) -> Box { + if self.make_results.borrow().len() == 0 { + panic!("FailedPayableDao Missing.") + }; + self.make_params.lock().unwrap().push(()); + self.make_results.borrow_mut().remove(0) + } +} + +impl FailedPayableDaoFactoryMock { + pub fn new() -> Self { + Self { + make_params: Arc::new(Mutex::new(vec![])), + make_results: RefCell::new(vec![]), + } + } + + pub fn make_params(mut self, params: &Arc>>) -> Self { + self.make_params = params.clone(); + self + } + + pub fn make_result(self, result: FailedPayableDaoMock) -> Self { + self.make_results.borrow_mut().push(Box::new(result)); + self + } +} + pub struct PayableScannerBuilder { payable_dao: PayableDaoMock, pending_payable_dao: PendingPayableDaoMock, diff --git a/node/src/database/db_initializer.rs b/node/src/database/db_initializer.rs index 86e82aed1..18dded007 100644 --- a/node/src/database/db_initializer.rs +++ b/node/src/database/db_initializer.rs @@ -299,7 +299,7 @@ impl DbInitializerReal { gas_price_wei_low_b integer not null, nonce integer not null, reason text not null, - rechecked integer not null + status text not null )", [], ) @@ -846,7 +846,7 @@ mod tests { gas_price_wei_low_b, nonce, reason, - rechecked + status FROM failed_payable", ) .unwrap(); 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 4dbfd5b5e..bcbf192fc 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 @@ -34,7 +34,7 @@ impl DatabaseMigration for Migrate_10_to_11 { gas_price_wei_low_b integer not null, nonce integer not null, reason text not null, - rechecked integer not null + status text not null )"; declaration_utils.execute_upon_transaction(&[ diff --git a/node/src/database/test_utils/mod.rs b/node/src/database/test_utils/mod.rs index 6e88e1292..b4924e77c 100644 --- a/node/src/database/test_utils/mod.rs +++ b/node/src/database/test_utils/mod.rs @@ -37,7 +37,7 @@ pub const SQL_ATTRIBUTES_FOR_CREATING_FAILED_PAYABLE: &[&[&str]] = &[ &["gas_price_wei_low_b", "integer", "not", "null"], &["nonce", "integer", "not", "null"], &["reason", "text", "not", "null"], - &["rechecked", "integer", "not", "null"], + &["status", "text", "not", "null"], ]; #[derive(Debug, Default)] diff --git a/node/src/hopper/routing_service.rs b/node/src/hopper/routing_service.rs index 478441159..264ec29dc 100644 --- a/node/src/hopper/routing_service.rs +++ b/node/src/hopper/routing_service.rs @@ -529,7 +529,6 @@ mod tests { use masq_lib::test_utils::environment_guard::EnvironmentGuard; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; use masq_lib::test_utils::utils::TEST_DEFAULT_CHAIN; - use std::fmt::format; use std::net::SocketAddr; use std::str::FromStr; use std::time::SystemTime;