From 326b79bf801ffdff346c2f4cea5c1da1afcf9fc6 Mon Sep 17 00:00:00 2001 From: Bert <65427484+bertllll@users.noreply.github.com> Date: Tue, 24 Jun 2025 17:39:00 +0200 Subject: [PATCH 01/61] GH-642: interim commit --- node/src/accountant/db_access_objects/mod.rs | 1 - .../db_access_objects/payable_dao.rs | 90 +- .../db_access_objects/pending_payable_dao.rs | 1415 ++++++++--------- .../db_access_objects/sent_payable_dao.rs | 35 +- .../db_access_objects/test_utils.rs | 6 +- node/src/accountant/mod.rs | 217 +-- node/src/accountant/scanners/mod.rs | 854 +++++----- .../src/accountant/scanners/scanners_utils.rs | 180 +-- node/src/accountant/test_utils.rs | 393 +++-- node/src/actor_system_factory.rs | 4 +- node/src/blockchain/blockchain_bridge.rs | 106 +- .../lower_level_interface_web3.rs | 119 +- .../blockchain_interface_web3/utils.rs | 1 - .../data_structures/mod.rs | 1 - node/src/stream_handler_pool.rs | 4 +- node/src/sub_lib/accountant.rs | 4 +- 16 files changed, 1765 insertions(+), 1665 deletions(-) diff --git a/node/src/accountant/db_access_objects/mod.rs b/node/src/accountant/db_access_objects/mod.rs index ae165909a..8782bf620 100644 --- a/node/src/accountant/db_access_objects/mod.rs +++ b/node/src/accountant/db_access_objects/mod.rs @@ -3,7 +3,6 @@ pub mod banned_dao; pub mod failed_payable_dao; pub mod payable_dao; -pub mod pending_payable_dao; pub mod receivable_dao; pub mod sent_payable_dao; mod test_utils; diff --git a/node/src/accountant/db_access_objects/payable_dao.rs b/node/src/accountant/db_access_objects/payable_dao.rs index c7d438a41..6a1cd61bf 100644 --- a/node/src/accountant/db_access_objects/payable_dao.rs +++ b/node/src/accountant/db_access_objects/payable_dao.rs @@ -6,15 +6,11 @@ use crate::accountant::db_big_integer::big_int_db_processor::KeyVariants::{ use crate::accountant::db_big_integer::big_int_db_processor::{BigIntDbProcessor, BigIntDbProcessorReal, BigIntSqlConfig, DisplayableRusqliteParamPair, ParamByUse, SQLParamsBuilder, TableNameDAO, WeiChange, WeiChangeDirection}; use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; use crate::accountant::db_access_objects::utils; -use crate::accountant::db_access_objects::utils::{ - sum_i128_values_from_table, to_unix_timestamp, AssemblerFeeder, CustomQuery, DaoFactoryReal, - RangeStmConfig, TopStmConfig, VigilantRusqliteFlatten, -}; +use crate::accountant::db_access_objects::utils::{sum_i128_values_from_table, to_unix_timestamp, AssemblerFeeder, CustomQuery, DaoFactoryReal, RangeStmConfig, TopStmConfig, VigilantRusqliteFlatten, TxHash, RowId}; use crate::accountant::db_access_objects::payable_dao::mark_pending_payable_associated_functions::{ compose_case_expression, execute_command, serialize_wallets, }; use crate::accountant::{checked_conversion, sign_conversion, PendingPayableId}; -use crate::blockchain::blockchain_bridge::PendingPayableFingerprint; use crate::database::rusqlite_wrappers::ConnectionWrapper; use crate::sub_lib::wallet::Wallet; #[cfg(test)] @@ -26,8 +22,10 @@ use rusqlite::{Error, Row}; use std::fmt::Debug; use std::str::FromStr; use std::time::SystemTime; +use ethabi::Address; use itertools::Either; use web3::types::H256; +use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::ConfirmedTx; #[derive(Debug, PartialEq, Eq)] pub enum PayableDaoError { @@ -53,12 +51,12 @@ pub trait PayableDao: Debug + Send { fn mark_pending_payables_rowids( &self, - wallets_and_rowids: &[(&Wallet, u64)], + wallets_and_rowids: &[MarkOfPendingPayable], ) -> Result<(), PayableDaoError>; fn transactions_confirmed( &self, - confirmed_payables: &[PendingPayableFingerprint], + confirmed_payables: &[ConfirmedTx], ) -> Result<(), PayableDaoError>; fn non_pending_payables(&self) -> Vec; @@ -81,6 +79,17 @@ impl PayableDaoFactory for DaoFactoryReal { } } +pub struct MarkOfPendingPayable{ + pub wallet: Address, + pub rowid: RowId +} + +impl MarkOfPendingPayable { + pub fn new(wallet: Address, rowid: RowId)-> Self { + todo!() + } +} + #[derive(Debug)] pub struct PayableDaoReal { conn: Box, @@ -146,7 +155,7 @@ impl PayableDao for PayableDaoReal { fn transactions_confirmed( &self, - confirmed_payables: &[PendingPayableFingerprint], + confirmed_payables: &[ConfirmedTx], ) -> Result<(), PayableDaoError> { confirmed_payables.iter().try_for_each(|pending_payable_fingerprint| { @@ -557,6 +566,7 @@ mod tests { use rusqlite::{ToSql}; use std::path::Path; use std::str::FromStr; + use crate::accountant::db_access_objects::sent_payable_dao::SentTx; use crate::database::test_utils::ConnectionWrapperMock; #[test] @@ -892,15 +902,17 @@ mod tests { } struct TestSetupValuesHolder { - fingerprint_1: PendingPayableFingerprint, - fingerprint_2: PendingPayableFingerprint, - wallet_1: Wallet, - wallet_2: Wallet, - previous_timestamp_1: SystemTime, - previous_timestamp_2: SystemTime, + account_1: TxWalletAndTimestamp, + account_2: TxWalletAndTimestamp + } + + struct TxWalletAndTimestamp{ + pending_payable: SentTx, + wallet: Wallet, + previous_timestamp: SystemTime, } - fn make_fingerprint_pair_and_insert_initial_payable_records( + fn insert_initial_payable_records_and_return_pending_payable( conn: &dyn ConnectionWrapper, initial_amount_1: u128, initial_amount_2: u128, @@ -933,31 +945,27 @@ mod tests { Some(rowid_2 as i64), ) } - let fingerprint_1 = PendingPayableFingerprint { - rowid: rowid_1, - timestamp: new_payable_timestamp_1, - hash: hash_1, - attempt: 1, - amount: balance_change_1, - process_error: None, - }; - let fingerprint_2 = PendingPayableFingerprint { - rowid: rowid_2, - timestamp: new_payable_timestamp_2, - hash: hash_2, - attempt: 1, - amount: balance_change_2, - process_error: None, - }; - let previous_timestamp_1 = from_unix_timestamp(previous_timestamp_1_s); - let previous_timestamp_2 = from_unix_timestamp(previous_timestamp_2_s); + // let fingerprint_1 = SentTx { + // rowid: rowid_1, + // timestamp: new_payable_timestamp_1, + // hash: hash_1, + // attempt: 1, + // amount: balance_change_1, + // process_error: None, + // }; + // let fingerprint_2 = SentTx { + // rowid: rowid_2, + // timestamp: new_payable_timestamp_2, + // hash: hash_2, + // attempt: 1, + // amount: balance_change_2, + // process_error: None, + // }; + // let previous_timestamp_1 = from_unix_timestamp(previous_timestamp_1_s); + // let previous_timestamp_2 = from_unix_timestamp(previous_timestamp_2_s); TestSetupValuesHolder { - fingerprint_1, - fingerprint_2, - wallet_1, - wallet_2, - previous_timestamp_1, - previous_timestamp_2, + account_1: todo!(), + account_2: todo!() } } @@ -1000,7 +1008,7 @@ mod tests { let boxed_conn = DbInitializerReal::default() .initialize(&home_dir, DbInitializationConfig::test_default()) .unwrap(); - let setup_holder = make_fingerprint_pair_and_insert_initial_payable_records( + let setup_holder = insert_initial_payable_records_and_return_pending_payable( boxed_conn.as_ref(), initial_amount_1, initial_amount_2, @@ -1117,7 +1125,7 @@ mod tests { let conn = DbInitializerReal::default() .initialize(&home_dir, DbInitializationConfig::test_default()) .unwrap(); - let setup_holder = make_fingerprint_pair_and_insert_initial_payable_records( + let setup_holder = insert_initial_payable_records_and_return_pending_payable( conn.as_ref(), 1_111_111, 2_222_222, diff --git a/node/src/accountant/db_access_objects/pending_payable_dao.rs b/node/src/accountant/db_access_objects/pending_payable_dao.rs index e555fcc9a..4e5ef9883 100644 --- a/node/src/accountant/db_access_objects/pending_payable_dao.rs +++ b/node/src/accountant/db_access_objects/pending_payable_dao.rs @@ -5,7 +5,6 @@ use crate::accountant::db_access_objects::utils::{ }; use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; use crate::accountant::{checked_conversion, comma_joined_stringifiable}; -use crate::blockchain::blockchain_bridge::PendingPayableFingerprint; use crate::blockchain::blockchain_interface::blockchain_interface_web3::HashAndAmount; use crate::database::rusqlite_wrappers::ConnectionWrapper; use crate::sub_lib::wallet::Wallet; @@ -33,10 +32,10 @@ pub struct TransactionHashes { pub no_rowid_results: Vec, } -pub trait PendingPayableDao { +pub trait SentPayableDao { // Note that the order of the returned results is not guaranteed fn fingerprints_rowids(&self, hashes: &[H256]) -> TransactionHashes; - fn return_all_errorless_fingerprints(&self) -> Vec; + // fn return_all_errorless_fingerprints(&self) -> Vec; fn insert_new_fingerprints( &self, hashes_and_amounts: &[HashAndAmount], @@ -47,7 +46,7 @@ pub trait PendingPayableDao { fn mark_failures(&self, ids: &[u64]) -> Result<(), PendingPayableDaoError>; } -impl PendingPayableDao for PendingPayableDaoReal<'_> { +impl SentPayableDao for PendingPayableDaoReal<'_> { fn fingerprints_rowids(&self, hashes: &[H256]) -> TransactionHashes { //Vec<(Option, H256)> { fn hash_and_rowid_in_single_row(row: &Row) -> rusqlite::Result<(u64, H256)> { @@ -87,42 +86,42 @@ impl PendingPayableDao for PendingPayableDaoReal<'_> { } } - fn return_all_errorless_fingerprints(&self) -> Vec { - let mut stm = self - .conn - .prepare( - "select rowid, transaction_hash, amount_high_b, amount_low_b, \ - payable_timestamp, attempt from pending_payable where process_error is null", - ) - .expect("Internal error"); - stm.query_map([], |row| { - let rowid: u64 = Self::get_with_expect(row, 0); - let transaction_hash: String = Self::get_with_expect(row, 1); - let amount_high_bytes: i64 = Self::get_with_expect(row, 2); - let amount_low_bytes: i64 = Self::get_with_expect(row, 3); - let timestamp: i64 = Self::get_with_expect(row, 4); - let attempt: u16 = Self::get_with_expect(row, 5); - Ok(PendingPayableFingerprint { - rowid, - timestamp: from_unix_timestamp(timestamp), - hash: H256::from_str(&transaction_hash[2..]).unwrap_or_else(|e| { - panic!( - "Invalid hash format (\"{}\": {:?}) - database corrupt", - transaction_hash, e - ) - }), - attempt, - amount: checked_conversion::(BigIntDivider::reconstitute( - amount_high_bytes, - amount_low_bytes, - )), - process_error: None, - }) - }) - .expect("rusqlite failure") - .vigilant_flatten() - .collect() - } + // fn return_all_errorless_fingerprints(&self) -> Vec { + // let mut stm = self + // .conn + // .prepare( + // "select rowid, transaction_hash, amount_high_b, amount_low_b, \ + // payable_timestamp, attempt from pending_payable where process_error is null", + // ) + // .expect("Internal error"); + // stm.query_map([], |row| { + // let rowid: u64 = Self::get_with_expect(row, 0); + // let transaction_hash: String = Self::get_with_expect(row, 1); + // let amount_high_bytes: i64 = Self::get_with_expect(row, 2); + // let amount_low_bytes: i64 = Self::get_with_expect(row, 3); + // let timestamp: i64 = Self::get_with_expect(row, 4); + // let attempt: u16 = Self::get_with_expect(row, 5); + // Ok(SentTx { + // rowid, + // timestamp: from_unix_timestamp(timestamp), + // hash: H256::from_str(&transaction_hash[2..]).unwrap_or_else(|e| { + // panic!( + // "Invalid hash format (\"{}\": {:?}) - database corrupt", + // transaction_hash, e + // ) + // }), + // attempt, + // amount: checked_conversion::(BigIntDivider::reconstitute( + // amount_high_bytes, + // amount_low_bytes, + // )), + // process_error: None, + // }) + // }) + // .expect("rusqlite failure") + // .vigilant_flatten() + // .collect() + // } fn insert_new_fingerprints( &self, @@ -225,21 +224,6 @@ impl PendingPayableDao for PendingPayableDaoReal<'_> { } } -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct PendingPayable { - pub recipient_wallet: Wallet, - pub hash: H256, -} - -impl PendingPayable { - pub fn new(recipient_wallet: Wallet, hash: H256) -> Self { - Self { - recipient_wallet, - hash, - } - } -} - #[derive(Debug)] pub struct PendingPayableDaoReal<'a> { conn: Box, @@ -260,11 +244,11 @@ impl<'a> PendingPayableDaoReal<'a> { } pub trait PendingPayableDaoFactory { - fn make(&self) -> Box; + fn make(&self) -> Box; } impl PendingPayableDaoFactory for DaoFactoryReal { - fn make(&self) -> Box { + fn make(&self) -> Box { Box::new(PendingPayableDaoReal::new(self.make_connection())) } } @@ -272,12 +256,11 @@ impl PendingPayableDaoFactory for DaoFactoryReal { #[cfg(test)] mod tests { use crate::accountant::checked_conversion; - use crate::accountant::db_access_objects::pending_payable_dao::{ - PendingPayableDao, PendingPayableDaoError, PendingPayableDaoReal, + use crate::accountant::db_access_objects::sent_payable_dao::{ + SentPayableDao, PendingPayableDaoError, PendingPayableDaoReal, }; use crate::accountant::db_access_objects::utils::from_unix_timestamp; use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; - use crate::blockchain::blockchain_bridge::PendingPayableFingerprint; use crate::blockchain::blockchain_interface::blockchain_interface_web3::HashAndAmount; use crate::blockchain::test_utils::make_tx_hash; use crate::database::db_initializer::{ @@ -291,660 +274,660 @@ mod tests { use std::time::SystemTime; use web3::types::H256; - #[test] - fn insert_new_fingerprints_happy_path() { - let home_dir = ensure_node_home_directory_exists( - "pending_payable_dao", - "insert_new_fingerprints_happy_path", - ); - let wrapped_conn = DbInitializerReal::default() - .initialize(&home_dir, DbInitializationConfig::test_default()) - .unwrap(); - let hash_1 = make_tx_hash(4546); - let amount_1 = 55556; - let hash_2 = make_tx_hash(6789); - let amount_2 = 44445; - let batch_wide_timestamp = from_unix_timestamp(200_000_000); - let subject = PendingPayableDaoReal::new(wrapped_conn); - let hash_and_amount_1 = HashAndAmount { - hash: hash_1, - amount: amount_1, - }; - let hash_and_amount_2 = HashAndAmount { - hash: hash_2, - amount: amount_2, - }; - - let _ = subject - .insert_new_fingerprints( - &[hash_and_amount_1, hash_and_amount_2], - batch_wide_timestamp, - ) - .unwrap(); - - let records = subject.return_all_errorless_fingerprints(); - assert_eq!( - records, - vec![ - PendingPayableFingerprint { - rowid: 1, - timestamp: batch_wide_timestamp, - hash: hash_and_amount_1.hash, - attempt: 1, - amount: hash_and_amount_1.amount, - process_error: None - }, - PendingPayableFingerprint { - rowid: 2, - timestamp: batch_wide_timestamp, - hash: hash_and_amount_2.hash, - attempt: 1, - amount: hash_and_amount_2.amount, - process_error: None - } - ] - ) - } - - #[test] - fn insert_new_fingerprints_sad_path() { - let home_dir = ensure_node_home_directory_exists( - "pending_payable_dao", - "insert_new_fingerprints_sad_path", - ); - { - DbInitializerReal::default() - .initialize(&home_dir, DbInitializationConfig::test_default()) - .unwrap(); - } - let conn_read_only = Connection::open_with_flags( - home_dir.join(DATABASE_FILE), - OpenFlags::SQLITE_OPEN_READ_ONLY, - ) - .unwrap(); - let wrapped_conn = ConnectionWrapperReal::new(conn_read_only); - let hash = make_tx_hash(45466); - let amount = 55556; - let timestamp = from_unix_timestamp(200_000_000); - let subject = PendingPayableDaoReal::new(Box::new(wrapped_conn)); - let hash_and_amount = HashAndAmount { hash, amount }; - - let result = subject.insert_new_fingerprints(&[hash_and_amount], timestamp); - - assert_eq!( - result, - Err(PendingPayableDaoError::InsertionFailed( - "attempt to write a readonly database".to_string() - )) - ) - } - - #[test] - #[should_panic(expected = "expected 1 changed rows but got 0")] - fn insert_new_fingerprints_number_of_returned_rows_different_than_expected() { - let setup_conn = Connection::open_in_memory().unwrap(); - // injecting a by-plan failing statement into the mocked connection in order to provoke - // a reaction that would've been untestable directly on the table the act is closely coupled with - let statement = { - setup_conn - .execute("create table example (id integer)", []) - .unwrap(); - setup_conn.prepare("select id from example").unwrap() - }; - let wrapped_conn = ConnectionWrapperMock::default().prepare_result(Ok(statement)); - let hash_1 = make_tx_hash(4546); - let amount_1 = 55556; - let batch_wide_timestamp = from_unix_timestamp(200_000_000); - let subject = PendingPayableDaoReal::new(Box::new(wrapped_conn)); - let hash_and_amount = HashAndAmount { - hash: hash_1, - amount: amount_1, - }; - - let _ = subject.insert_new_fingerprints(&[hash_and_amount], batch_wide_timestamp); - } - - #[test] - fn fingerprints_rowids_when_records_reachable() { - let home_dir = ensure_node_home_directory_exists( - "pending_payable_dao", - "fingerprints_rowids_when_records_reachable", - ); - let wrapped_conn = DbInitializerReal::default() - .initialize(&home_dir, DbInitializationConfig::test_default()) - .unwrap(); - let subject = PendingPayableDaoReal::new(wrapped_conn); - let timestamp = from_unix_timestamp(195_000_000); - // use full range tx hashes because SqLite has tendencies to see the value as a hex and convert it to an integer, - // then complain about its excessive size if supplied in unquoted strings - let hash_1 = - H256::from_str("b4bc263278d3a82a652a8d73a6bfd8ec0ba1a63923bbb4f38147fb8a943da26a") - .unwrap(); - let hash_2 = - H256::from_str("5a2909e7bb71943c82a94d9beb04e230351541fc14619ee8bb9b7372ea88ba39") - .unwrap(); - let hash_and_amount_1 = HashAndAmount { - hash: hash_1, - amount: 4567, - }; - let hash_and_amount_2 = HashAndAmount { - hash: hash_2, - amount: 6789, - }; - let fingerprints_init_input = vec![hash_and_amount_1, hash_and_amount_2]; - { - subject - .insert_new_fingerprints(&fingerprints_init_input, timestamp) - .unwrap(); - } - - let result = subject.fingerprints_rowids(&[hash_1, hash_2]); - - let first_expected_pair = &(1, hash_1); - assert!( - result.rowid_results.contains(first_expected_pair), - "Returned rowid pairs should have contained {:?} but all it did is {:?}", - first_expected_pair, - result.rowid_results - ); - let second_expected_pair = &(2, hash_2); - assert!( - result.rowid_results.contains(second_expected_pair), - "Returned rowid pairs should have contained {:?} but all it did is {:?}", - second_expected_pair, - result.rowid_results - ); - assert_eq!(result.rowid_results.len(), 2); - } - - #[test] - fn fingerprints_rowids_when_nonexistent_records() { - let home_dir = ensure_node_home_directory_exists( - "pending_payable_dao", - "fingerprints_rowids_when_nonexistent_records", - ); - let wrapped_conn = DbInitializerReal::default() - .initialize(&home_dir, DbInitializationConfig::test_default()) - .unwrap(); - let subject = PendingPayableDaoReal::new(wrapped_conn); - let hash_1 = make_tx_hash(11119); - let hash_2 = make_tx_hash(22229); - let hash_3 = make_tx_hash(33339); - let hash_4 = make_tx_hash(44449); - // For more illustrative results, I use the official tooling but also generate one extra record before the chief one for - // this test, and in the end, I delete the first one. It leaves a single record still in but with the rowid 2 instead of - // just an ambiguous 1 - subject - .insert_new_fingerprints( - &[HashAndAmount { - hash: hash_2, - amount: 8901234, - }], - SystemTime::now(), - ) - .unwrap(); - subject - .insert_new_fingerprints( - &[HashAndAmount { - hash: hash_3, - amount: 1234567, - }], - SystemTime::now(), - ) - .unwrap(); - subject.delete_fingerprints(&[1]).unwrap(); - - let result = subject.fingerprints_rowids(&[hash_1, hash_2, hash_3, hash_4]); - - assert_eq!(result.rowid_results, vec![(2, hash_3),]); - assert_eq!(result.no_rowid_results, vec![hash_1, hash_2, hash_4]); - } - - #[test] - fn return_all_errorless_fingerprints_works_when_no_records_with_error_marks() { - let home_dir = ensure_node_home_directory_exists( - "pending_payable_dao", - "return_all_errorless_fingerprints_works_when_no_records_with_error_marks", - ); - let wrapped_conn = DbInitializerReal::default() - .initialize(&home_dir, DbInitializationConfig::test_default()) - .unwrap(); - let subject = PendingPayableDaoReal::new(wrapped_conn); - let batch_wide_timestamp = from_unix_timestamp(195_000_000); - let hash_1 = make_tx_hash(11119); - let amount_1 = 787; - let hash_2 = make_tx_hash(10000); - let amount_2 = 333; - let hash_and_amount_1 = HashAndAmount { - hash: hash_1, - amount: amount_1, - }; - let hash_and_amount_2 = HashAndAmount { - hash: hash_2, - amount: amount_2, - }; - - { - subject - .insert_new_fingerprints( - &[hash_and_amount_1, hash_and_amount_2], - batch_wide_timestamp, - ) - .unwrap(); - } - - let result = subject.return_all_errorless_fingerprints(); - - assert_eq!( - result, - vec![ - PendingPayableFingerprint { - rowid: 1, - timestamp: batch_wide_timestamp, - hash: hash_1, - attempt: 1, - amount: amount_1, - process_error: None - }, - PendingPayableFingerprint { - rowid: 2, - timestamp: batch_wide_timestamp, - hash: hash_2, - attempt: 1, - amount: amount_2, - process_error: None - } - ] - ) - } - - #[test] - fn return_all_errorless_fingerprints_works_when_some_records_with_error_marks() { - let home_dir = ensure_node_home_directory_exists( - "pending_payable_dao", - "return_all_errorless_fingerprints_works_when_some_records_with_error_marks", - ); - let wrapped_conn = DbInitializerReal::default() - .initialize(&home_dir, DbInitializationConfig::test_default()) - .unwrap(); - let subject = PendingPayableDaoReal::new(wrapped_conn); - let timestamp = from_unix_timestamp(198_000_000); - let hash = make_tx_hash(10000); - let amount = 333; - let hash_and_amount_1 = HashAndAmount { - hash: make_tx_hash(11119), - amount: 2000, - }; - let hash_and_amount_2 = HashAndAmount { hash, amount }; - { - subject - .insert_new_fingerprints(&[hash_and_amount_1, hash_and_amount_2], timestamp) - .unwrap(); - subject.mark_failures(&[1]).unwrap(); - } - - let result = subject.return_all_errorless_fingerprints(); - - assert_eq!( - result, - vec![PendingPayableFingerprint { - rowid: 2, - timestamp, - hash, - attempt: 1, - amount, - process_error: None - }] - ) - } - - #[test] - #[should_panic( - expected = "Invalid hash format (\"silly_hash\": Invalid character 'l' at position 0) - database corrupt" - )] - fn return_all_errorless_fingerprints_panics_on_malformed_hash() { - let home_dir = ensure_node_home_directory_exists( - "pending_payable_dao", - "return_all_errorless_fingerprints_panics_on_malformed_hash", - ); - let wrapped_conn = DbInitializerReal::default() - .initialize(&home_dir, DbInitializationConfig::test_default()) - .unwrap(); - { - wrapped_conn - .prepare("insert into pending_payable \ - (rowid, transaction_hash, amount_high_b, amount_low_b, payable_timestamp, attempt, process_error) \ - values (1, 'silly_hash', 4, 111, 10000000000, 1, null)") - .unwrap() - .execute([]) - .unwrap(); - } - let subject = PendingPayableDaoReal::new(wrapped_conn); - - let _ = subject.return_all_errorless_fingerprints(); - } - - #[test] - fn delete_fingerprints_happy_path() { - let home_dir = ensure_node_home_directory_exists( - "pending_payable_dao", - "delete_fingerprints_happy_path", - ); - let conn = DbInitializerReal::default() - .initialize(&home_dir, DbInitializationConfig::test_default()) - .unwrap(); - let subject = PendingPayableDaoReal::new(conn); - { - subject - .insert_new_fingerprints( - &[ - HashAndAmount { - hash: make_tx_hash(1234), - amount: 1111, - }, - HashAndAmount { - hash: make_tx_hash(2345), - amount: 5555, - }, - HashAndAmount { - hash: make_tx_hash(3456), - amount: 2222, - }, - ], - SystemTime::now(), - ) - .unwrap(); - } - - let result = subject.delete_fingerprints(&[2, 3]); - - assert_eq!(result, Ok(())); - let records_in_the_db = subject.return_all_errorless_fingerprints(); - let record_left_in = &records_in_the_db[0]; - assert_eq!(record_left_in.hash, make_tx_hash(1234)); - assert_eq!(record_left_in.rowid, 1); - assert_eq!(records_in_the_db.len(), 1); - } - - #[test] - fn delete_fingerprints_sad_path() { - let home_dir = ensure_node_home_directory_exists( - "pending_payable_dao", - "delete_fingerprints_sad_path", - ); - { - DbInitializerReal::default() - .initialize(&home_dir, DbInitializationConfig::test_default()) - .unwrap(); - } - let conn_read_only = Connection::open_with_flags( - home_dir.join(DATABASE_FILE), - OpenFlags::SQLITE_OPEN_READ_ONLY, - ) - .unwrap(); - let wrapped_conn = ConnectionWrapperReal::new(conn_read_only); - let rowid = 45; - let subject = PendingPayableDaoReal::new(Box::new(wrapped_conn)); - - let result = subject.delete_fingerprints(&[rowid]); - - assert_eq!( - result, - Err(PendingPayableDaoError::RecordDeletion( - "attempt to write a readonly database".to_string() - )) - ) - } - - #[test] - #[should_panic( - expected = "deleting fingerprint, expected 2 rows to be changed, but the actual number is 1" - )] - fn delete_fingerprints_changed_different_number_of_rows_than_expected() { - let home_dir = ensure_node_home_directory_exists( - "pending_payable_dao", - "delete_fingerprints_changed_different_number_of_rows_than_expected", - ); - let conn = DbInitializerReal::default() - .initialize(&home_dir, DbInitializationConfig::test_default()) - .unwrap(); - let rowid_1 = 1; - let rowid_2 = 2; - let subject = PendingPayableDaoReal::new(conn); - { - subject - .insert_new_fingerprints( - &[HashAndAmount { - hash: make_tx_hash(666666), - amount: 5555, - }], - SystemTime::now(), - ) - .unwrap(); - } - - let _ = subject.delete_fingerprints(&[rowid_1, rowid_2]); - } - - #[test] - fn increment_scan_attempts_works() { - let home_dir = ensure_node_home_directory_exists( - "pending_payable_dao", - "increment_scan_attempts_works", - ); - let conn = DbInitializerReal::default() - .initialize(&home_dir, DbInitializationConfig::test_default()) - .unwrap(); - let hash_1 = make_tx_hash(345); - let hash_2 = make_tx_hash(456); - let hash_3 = make_tx_hash(567); - let hash_and_amount_1 = HashAndAmount { - hash: hash_1, - amount: 1122, - }; - let hash_and_amount_2 = HashAndAmount { - hash: hash_2, - amount: 2233, - }; - let hash_and_amount_3 = HashAndAmount { - hash: hash_3, - amount: 3344, - }; - let timestamp = from_unix_timestamp(190_000_000); - let subject = PendingPayableDaoReal::new(conn); - { - subject - .insert_new_fingerprints( - &[hash_and_amount_1, hash_and_amount_2, hash_and_amount_3], - timestamp, - ) - .unwrap(); - } - - let result = subject.increment_scan_attempts(&[2, 3]); - - assert_eq!(result, Ok(())); - let mut all_records = subject.return_all_errorless_fingerprints(); - assert_eq!(all_records.len(), 3); - let record_1 = all_records.remove(0); - assert_eq!(record_1.hash, hash_1); - assert_eq!(record_1.attempt, 1); - let record_2 = all_records.remove(0); - assert_eq!(record_2.hash, hash_2); - assert_eq!(record_2.attempt, 2); - let record_3 = all_records.remove(0); - assert_eq!(record_3.hash, hash_3); - assert_eq!(record_3.attempt, 2); - } - - #[test] - fn increment_scan_attempts_works_sad_path() { - let home_dir = ensure_node_home_directory_exists( - "pending_payable_dao", - "increment_scan_attempts_works_sad_path", - ); - { - DbInitializerReal::default() - .initialize(&home_dir, DbInitializationConfig::test_default()) - .unwrap(); - } - let conn_read_only = Connection::open_with_flags( - home_dir.join(DATABASE_FILE), - OpenFlags::SQLITE_OPEN_READ_ONLY, - ) - .unwrap(); - let wrapped_conn = ConnectionWrapperReal::new(conn_read_only); - let subject = PendingPayableDaoReal::new(Box::new(wrapped_conn)); - - let result = subject.increment_scan_attempts(&[1]); - - assert_eq!( - result, - Err(PendingPayableDaoError::UpdateFailed( - "attempt to write a readonly database".to_string() - )) - ) - } - - #[test] - #[should_panic( - expected = "Database corrupt: updating fingerprints: expected to update 2 rows but did 0" - )] - fn increment_scan_attempts_panics_on_unexpected_row_change_count() { - let home_dir = ensure_node_home_directory_exists( - "pending_payable_dao", - "increment_scan_attempts_panics_on_unexpected_row_change_count", - ); - let conn = DbInitializerReal::default() - .initialize(&home_dir, DbInitializationConfig::test_default()) - .unwrap(); - let subject = PendingPayableDaoReal::new(conn); - - let _ = subject.increment_scan_attempts(&[1, 2]); - } - - #[test] - fn mark_failures_works() { - let home_dir = - ensure_node_home_directory_exists("pending_payable_dao", "mark_failures_works"); - let conn = DbInitializerReal::default() - .initialize(&home_dir, DbInitializationConfig::test_default()) - .unwrap(); - let hash_1 = make_tx_hash(555); - let amount_1 = 1234; - let hash_2 = make_tx_hash(666); - let amount_2 = 2345; - let hash_and_amount_1 = HashAndAmount { - hash: hash_1, - amount: amount_1, - }; - let hash_and_amount_2 = HashAndAmount { - hash: hash_2, - amount: amount_2, - }; - let timestamp = from_unix_timestamp(190_000_000); - let subject = PendingPayableDaoReal::new(conn); - { - subject - .insert_new_fingerprints(&[hash_and_amount_1, hash_and_amount_2], timestamp) - .unwrap(); - } - - let result = subject.mark_failures(&[2]); - - assert_eq!(result, Ok(())); - let assert_conn = Connection::open(home_dir.join(DATABASE_FILE)).unwrap(); - let mut assert_stm = assert_conn - .prepare("select rowid, transaction_hash, amount_high_b, amount_low_b, payable_timestamp, attempt, process_error from pending_payable") - .unwrap(); - let found_fingerprints = assert_stm - .query_map([], |row| { - let rowid: u64 = row.get(0).unwrap(); - let transaction_hash: String = row.get(1).unwrap(); - let amount_high_b: i64 = row.get(2).unwrap(); - let amount_low_b: i64 = row.get(3).unwrap(); - let timestamp: i64 = row.get(4).unwrap(); - let attempt: u16 = row.get(5).unwrap(); - let process_error: Option = row.get(6).unwrap(); - Ok(PendingPayableFingerprint { - rowid, - timestamp: from_unix_timestamp(timestamp), - hash: H256::from_str(&transaction_hash[2..]).unwrap(), - attempt, - amount: checked_conversion::(BigIntDivider::reconstitute( - amount_high_b, - amount_low_b, - )), - process_error, - }) - }) - .unwrap() - .flatten() - .collect::>(); - assert_eq!( - *found_fingerprints, - vec![ - PendingPayableFingerprint { - rowid: 1, - timestamp, - hash: hash_1, - attempt: 1, - amount: amount_1, - process_error: None - }, - PendingPayableFingerprint { - rowid: 2, - timestamp, - hash: hash_2, - attempt: 1, - amount: amount_2, - process_error: Some("ERROR".to_string()) - } - ] - ) - } - - #[test] - fn mark_failures_sad_path() { - let home_dir = - ensure_node_home_directory_exists("pending_payable_dao", "mark_failures_sad_path"); - { - DbInitializerReal::default() - .initialize(&home_dir, DbInitializationConfig::test_default()) - .unwrap(); - } - let conn_read_only = Connection::open_with_flags( - home_dir.join(DATABASE_FILE), - OpenFlags::SQLITE_OPEN_READ_ONLY, - ) - .unwrap(); - let wrapped_conn = ConnectionWrapperReal::new(conn_read_only); - let subject = PendingPayableDaoReal::new(Box::new(wrapped_conn)); - - let result = subject.mark_failures(&[1]); - - assert_eq!( - result, - Err(PendingPayableDaoError::ErrorMarkFailed( - "attempt to write a readonly database".to_string() - )) - ) - } - - #[test] - #[should_panic( - expected = "Database corrupt: marking failure at fingerprints: expected to change 2 rows but did 0" - )] - fn mark_failures_panics_on_wrong_row_change_count() { - let home_dir = ensure_node_home_directory_exists( - "pending_payable_dao", - "mark_failures_panics_on_wrong_row_change_count", - ); - let conn = DbInitializerReal::default() - .initialize(&home_dir, DbInitializationConfig::test_default()) - .unwrap(); - let subject = PendingPayableDaoReal::new(conn); - - let _ = subject.mark_failures(&[10, 20]); - } + // #[test] + // fn insert_new_fingerprints_happy_path() { + // let home_dir = ensure_node_home_directory_exists( + // "sent_payable_dao", + // "insert_new_fingerprints_happy_path", + // ); + // let wrapped_conn = DbInitializerReal::default() + // .initialize(&home_dir, DbInitializationConfig::test_default()) + // .unwrap(); + // let hash_1 = make_tx_hash(4546); + // let amount_1 = 55556; + // let hash_2 = make_tx_hash(6789); + // let amount_2 = 44445; + // let batch_wide_timestamp = from_unix_timestamp(200_000_000); + // let subject = PendingPayableDaoReal::new(wrapped_conn); + // let hash_and_amount_1 = HashAndAmount { + // hash: hash_1, + // amount: amount_1, + // }; + // let hash_and_amount_2 = HashAndAmount { + // hash: hash_2, + // amount: amount_2, + // }; + // + // let _ = subject + // .insert_new_fingerprints( + // &[hash_and_amount_1, hash_and_amount_2], + // batch_wide_timestamp, + // ) + // .unwrap(); + // + // let records = subject.return_all_errorless_fingerprints(); + // assert_eq!( + // records, + // vec![ + // SentTx { + // rowid: 1, + // timestamp: batch_wide_timestamp, + // hash: hash_and_amount_1.hash, + // attempt: 1, + // amount: hash_and_amount_1.amount, + // process_error: None + // }, + // SentTx { + // rowid: 2, + // timestamp: batch_wide_timestamp, + // hash: hash_and_amount_2.hash, + // attempt: 1, + // amount: hash_and_amount_2.amount, + // process_error: None + // } + // ] + // ) + // } + // + // #[test] + // fn insert_new_fingerprints_sad_path() { + // let home_dir = ensure_node_home_directory_exists( + // "sent_payable_dao", + // "insert_new_fingerprints_sad_path", + // ); + // { + // DbInitializerReal::default() + // .initialize(&home_dir, DbInitializationConfig::test_default()) + // .unwrap(); + // } + // let conn_read_only = Connection::open_with_flags( + // home_dir.join(DATABASE_FILE), + // OpenFlags::SQLITE_OPEN_READ_ONLY, + // ) + // .unwrap(); + // let wrapped_conn = ConnectionWrapperReal::new(conn_read_only); + // let hash = make_tx_hash(45466); + // let amount = 55556; + // let timestamp = from_unix_timestamp(200_000_000); + // let subject = PendingPayableDaoReal::new(Box::new(wrapped_conn)); + // let hash_and_amount = HashAndAmount { hash, amount }; + // + // let result = subject.insert_new_fingerprints(&[hash_and_amount], timestamp); + // + // assert_eq!( + // result, + // Err(PendingPayableDaoError::InsertionFailed( + // "attempt to write a readonly database".to_string() + // )) + // ) + // } + // + // #[test] + // #[should_panic(expected = "expected 1 changed rows but got 0")] + // fn insert_new_fingerprints_number_of_returned_rows_different_than_expected() { + // let setup_conn = Connection::open_in_memory().unwrap(); + // // injecting a by-plan failing statement into the mocked connection in order to provoke + // // a reaction that would've been untestable directly on the table the act is closely coupled with + // let statement = { + // setup_conn + // .execute("create table example (id integer)", []) + // .unwrap(); + // setup_conn.prepare("select id from example").unwrap() + // }; + // let wrapped_conn = ConnectionWrapperMock::default().prepare_result(Ok(statement)); + // let hash_1 = make_tx_hash(4546); + // let amount_1 = 55556; + // let batch_wide_timestamp = from_unix_timestamp(200_000_000); + // let subject = PendingPayableDaoReal::new(Box::new(wrapped_conn)); + // let hash_and_amount = HashAndAmount { + // hash: hash_1, + // amount: amount_1, + // }; + // + // let _ = subject.insert_new_fingerprints(&[hash_and_amount], batch_wide_timestamp); + // } + // + // #[test] + // fn fingerprints_rowids_when_records_reachable() { + // let home_dir = ensure_node_home_directory_exists( + // "sent_payable_dao", + // "fingerprints_rowids_when_records_reachable", + // ); + // let wrapped_conn = DbInitializerReal::default() + // .initialize(&home_dir, DbInitializationConfig::test_default()) + // .unwrap(); + // let subject = PendingPayableDaoReal::new(wrapped_conn); + // let timestamp = from_unix_timestamp(195_000_000); + // // use full range tx hashes because SqLite has tendencies to see the value as a hex and convert it to an integer, + // // then complain about its excessive size if supplied in unquoted strings + // let hash_1 = + // H256::from_str("b4bc263278d3a82a652a8d73a6bfd8ec0ba1a63923bbb4f38147fb8a943da26a") + // .unwrap(); + // let hash_2 = + // H256::from_str("5a2909e7bb71943c82a94d9beb04e230351541fc14619ee8bb9b7372ea88ba39") + // .unwrap(); + // let hash_and_amount_1 = HashAndAmount { + // hash: hash_1, + // amount: 4567, + // }; + // let hash_and_amount_2 = HashAndAmount { + // hash: hash_2, + // amount: 6789, + // }; + // let fingerprints_init_input = vec![hash_and_amount_1, hash_and_amount_2]; + // { + // subject + // .insert_new_fingerprints(&fingerprints_init_input, timestamp) + // .unwrap(); + // } + // + // let result = subject.fingerprints_rowids(&[hash_1, hash_2]); + // + // let first_expected_pair = &(1, hash_1); + // assert!( + // result.rowid_results.contains(first_expected_pair), + // "Returned rowid pairs should have contained {:?} but all it did is {:?}", + // first_expected_pair, + // result.rowid_results + // ); + // let second_expected_pair = &(2, hash_2); + // assert!( + // result.rowid_results.contains(second_expected_pair), + // "Returned rowid pairs should have contained {:?} but all it did is {:?}", + // second_expected_pair, + // result.rowid_results + // ); + // assert_eq!(result.rowid_results.len(), 2); + // } + // + // #[test] + // fn fingerprints_rowids_when_nonexistent_records() { + // let home_dir = ensure_node_home_directory_exists( + // "sent_payable_dao", + // "fingerprints_rowids_when_nonexistent_records", + // ); + // let wrapped_conn = DbInitializerReal::default() + // .initialize(&home_dir, DbInitializationConfig::test_default()) + // .unwrap(); + // let subject = PendingPayableDaoReal::new(wrapped_conn); + // let hash_1 = make_tx_hash(11119); + // let hash_2 = make_tx_hash(22229); + // let hash_3 = make_tx_hash(33339); + // let hash_4 = make_tx_hash(44449); + // // For more illustrative results, I use the official tooling but also generate one extra record before the chief one for + // // this test, and in the end, I delete the first one. It leaves a single record still in but with the rowid 2 instead of + // // just an ambiguous 1 + // subject + // .insert_new_fingerprints( + // &[HashAndAmount { + // hash: hash_2, + // amount: 8901234, + // }], + // SystemTime::now(), + // ) + // .unwrap(); + // subject + // .insert_new_fingerprints( + // &[HashAndAmount { + // hash: hash_3, + // amount: 1234567, + // }], + // SystemTime::now(), + // ) + // .unwrap(); + // subject.delete_fingerprints(&[1]).unwrap(); + // + // let result = subject.fingerprints_rowids(&[hash_1, hash_2, hash_3, hash_4]); + // + // assert_eq!(result.rowid_results, vec![(2, hash_3),]); + // assert_eq!(result.no_rowid_results, vec![hash_1, hash_2, hash_4]); + // } + // + // #[test] + // fn return_all_errorless_fingerprints_works_when_no_records_with_error_marks() { + // let home_dir = ensure_node_home_directory_exists( + // "sent_payable_dao", + // "return_all_errorless_fingerprints_works_when_no_records_with_error_marks", + // ); + // let wrapped_conn = DbInitializerReal::default() + // .initialize(&home_dir, DbInitializationConfig::test_default()) + // .unwrap(); + // let subject = PendingPayableDaoReal::new(wrapped_conn); + // let batch_wide_timestamp = from_unix_timestamp(195_000_000); + // let hash_1 = make_tx_hash(11119); + // let amount_1 = 787; + // let hash_2 = make_tx_hash(10000); + // let amount_2 = 333; + // let hash_and_amount_1 = HashAndAmount { + // hash: hash_1, + // amount: amount_1, + // }; + // let hash_and_amount_2 = HashAndAmount { + // hash: hash_2, + // amount: amount_2, + // }; + // + // { + // subject + // .insert_new_fingerprints( + // &[hash_and_amount_1, hash_and_amount_2], + // batch_wide_timestamp, + // ) + // .unwrap(); + // } + // + // let result = subject.return_all_errorless_fingerprints(); + // + // assert_eq!( + // result, + // vec![ + // SentTx { + // rowid: 1, + // timestamp: batch_wide_timestamp, + // hash: hash_1, + // attempt: 1, + // amount: amount_1, + // process_error: None + // }, + // SentTx { + // rowid: 2, + // timestamp: batch_wide_timestamp, + // hash: hash_2, + // attempt: 1, + // amount: amount_2, + // process_error: None + // } + // ] + // ) + // } + // + // #[test] + // fn return_all_errorless_fingerprints_works_when_some_records_with_error_marks() { + // let home_dir = ensure_node_home_directory_exists( + // "sent_payable_dao", + // "return_all_errorless_fingerprints_works_when_some_records_with_error_marks", + // ); + // let wrapped_conn = DbInitializerReal::default() + // .initialize(&home_dir, DbInitializationConfig::test_default()) + // .unwrap(); + // let subject = PendingPayableDaoReal::new(wrapped_conn); + // let timestamp = from_unix_timestamp(198_000_000); + // let hash = make_tx_hash(10000); + // let amount = 333; + // let hash_and_amount_1 = HashAndAmount { + // hash: make_tx_hash(11119), + // amount: 2000, + // }; + // let hash_and_amount_2 = HashAndAmount { hash, amount }; + // { + // subject + // .insert_new_fingerprints(&[hash_and_amount_1, hash_and_amount_2], timestamp) + // .unwrap(); + // subject.mark_failures(&[1]).unwrap(); + // } + // + // let result = subject.return_all_errorless_fingerprints(); + // + // assert_eq!( + // result, + // vec![SentTx { + // rowid: 2, + // timestamp, + // hash, + // attempt: 1, + // amount, + // process_error: None + // }] + // ) + // } + // + // #[test] + // #[should_panic( + // expected = "Invalid hash format (\"silly_hash\": Invalid character 'l' at position 0) - database corrupt" + // )] + // fn return_all_errorless_fingerprints_panics_on_malformed_hash() { + // let home_dir = ensure_node_home_directory_exists( + // "sent_payable_dao", + // "return_all_errorless_fingerprints_panics_on_malformed_hash", + // ); + // let wrapped_conn = DbInitializerReal::default() + // .initialize(&home_dir, DbInitializationConfig::test_default()) + // .unwrap(); + // { + // wrapped_conn + // .prepare("insert into pending_payable \ + // (rowid, transaction_hash, amount_high_b, amount_low_b, payable_timestamp, attempt, process_error) \ + // values (1, 'silly_hash', 4, 111, 10000000000, 1, null)") + // .unwrap() + // .execute([]) + // .unwrap(); + // } + // let subject = PendingPayableDaoReal::new(wrapped_conn); + // + // let _ = subject.return_all_errorless_fingerprints(); + // } + // + // #[test] + // fn delete_fingerprints_happy_path() { + // let home_dir = ensure_node_home_directory_exists( + // "sent_payable_dao", + // "delete_fingerprints_happy_path", + // ); + // let conn = DbInitializerReal::default() + // .initialize(&home_dir, DbInitializationConfig::test_default()) + // .unwrap(); + // let subject = PendingPayableDaoReal::new(conn); + // { + // subject + // .insert_new_fingerprints( + // &[ + // HashAndAmount { + // hash: make_tx_hash(1234), + // amount: 1111, + // }, + // HashAndAmount { + // hash: make_tx_hash(2345), + // amount: 5555, + // }, + // HashAndAmount { + // hash: make_tx_hash(3456), + // amount: 2222, + // }, + // ], + // SystemTime::now(), + // ) + // .unwrap(); + // } + // + // let result = subject.delete_fingerprints(&[2, 3]); + // + // assert_eq!(result, Ok(())); + // let records_in_the_db = subject.return_all_errorless_fingerprints(); + // let record_left_in = &records_in_the_db[0]; + // assert_eq!(record_left_in.hash, make_tx_hash(1234)); + // assert_eq!(record_left_in.rowid, 1); + // assert_eq!(records_in_the_db.len(), 1); + // } + // + // #[test] + // fn delete_fingerprints_sad_path() { + // let home_dir = ensure_node_home_directory_exists( + // "sent_payable_dao", + // "delete_fingerprints_sad_path", + // ); + // { + // DbInitializerReal::default() + // .initialize(&home_dir, DbInitializationConfig::test_default()) + // .unwrap(); + // } + // let conn_read_only = Connection::open_with_flags( + // home_dir.join(DATABASE_FILE), + // OpenFlags::SQLITE_OPEN_READ_ONLY, + // ) + // .unwrap(); + // let wrapped_conn = ConnectionWrapperReal::new(conn_read_only); + // let rowid = 45; + // let subject = PendingPayableDaoReal::new(Box::new(wrapped_conn)); + // + // let result = subject.delete_fingerprints(&[rowid]); + // + // assert_eq!( + // result, + // Err(PendingPayableDaoError::RecordDeletion( + // "attempt to write a readonly database".to_string() + // )) + // ) + // } + // + // #[test] + // #[should_panic( + // expected = "deleting fingerprint, expected 2 rows to be changed, but the actual number is 1" + // )] + // fn delete_fingerprints_changed_different_number_of_rows_than_expected() { + // let home_dir = ensure_node_home_directory_exists( + // "sent_payable_dao", + // "delete_fingerprints_changed_different_number_of_rows_than_expected", + // ); + // let conn = DbInitializerReal::default() + // .initialize(&home_dir, DbInitializationConfig::test_default()) + // .unwrap(); + // let rowid_1 = 1; + // let rowid_2 = 2; + // let subject = PendingPayableDaoReal::new(conn); + // { + // subject + // .insert_new_fingerprints( + // &[HashAndAmount { + // hash: make_tx_hash(666666), + // amount: 5555, + // }], + // SystemTime::now(), + // ) + // .unwrap(); + // } + // + // let _ = subject.delete_fingerprints(&[rowid_1, rowid_2]); + // } + // + // #[test] + // fn increment_scan_attempts_works() { + // let home_dir = ensure_node_home_directory_exists( + // "sent_payable_dao", + // "increment_scan_attempts_works", + // ); + // let conn = DbInitializerReal::default() + // .initialize(&home_dir, DbInitializationConfig::test_default()) + // .unwrap(); + // let hash_1 = make_tx_hash(345); + // let hash_2 = make_tx_hash(456); + // let hash_3 = make_tx_hash(567); + // let hash_and_amount_1 = HashAndAmount { + // hash: hash_1, + // amount: 1122, + // }; + // let hash_and_amount_2 = HashAndAmount { + // hash: hash_2, + // amount: 2233, + // }; + // let hash_and_amount_3 = HashAndAmount { + // hash: hash_3, + // amount: 3344, + // }; + // let timestamp = from_unix_timestamp(190_000_000); + // let subject = PendingPayableDaoReal::new(conn); + // { + // subject + // .insert_new_fingerprints( + // &[hash_and_amount_1, hash_and_amount_2, hash_and_amount_3], + // timestamp, + // ) + // .unwrap(); + // } + // + // let result = subject.increment_scan_attempts(&[2, 3]); + // + // assert_eq!(result, Ok(())); + // let mut all_records = subject.return_all_errorless_fingerprints(); + // assert_eq!(all_records.len(), 3); + // let record_1 = all_records.remove(0); + // assert_eq!(record_1.hash, hash_1); + // assert_eq!(record_1.attempt, 1); + // let record_2 = all_records.remove(0); + // assert_eq!(record_2.hash, hash_2); + // assert_eq!(record_2.attempt, 2); + // let record_3 = all_records.remove(0); + // assert_eq!(record_3.hash, hash_3); + // assert_eq!(record_3.attempt, 2); + // } + // + // #[test] + // fn increment_scan_attempts_works_sad_path() { + // let home_dir = ensure_node_home_directory_exists( + // "sent_payable_dao", + // "increment_scan_attempts_works_sad_path", + // ); + // { + // DbInitializerReal::default() + // .initialize(&home_dir, DbInitializationConfig::test_default()) + // .unwrap(); + // } + // let conn_read_only = Connection::open_with_flags( + // home_dir.join(DATABASE_FILE), + // OpenFlags::SQLITE_OPEN_READ_ONLY, + // ) + // .unwrap(); + // let wrapped_conn = ConnectionWrapperReal::new(conn_read_only); + // let subject = PendingPayableDaoReal::new(Box::new(wrapped_conn)); + // + // let result = subject.increment_scan_attempts(&[1]); + // + // assert_eq!( + // result, + // Err(PendingPayableDaoError::UpdateFailed( + // "attempt to write a readonly database".to_string() + // )) + // ) + // } + // + // #[test] + // #[should_panic( + // expected = "Database corrupt: updating fingerprints: expected to update 2 rows but did 0" + // )] + // fn increment_scan_attempts_panics_on_unexpected_row_change_count() { + // let home_dir = ensure_node_home_directory_exists( + // "sent_payable_dao", + // "increment_scan_attempts_panics_on_unexpected_row_change_count", + // ); + // let conn = DbInitializerReal::default() + // .initialize(&home_dir, DbInitializationConfig::test_default()) + // .unwrap(); + // let subject = PendingPayableDaoReal::new(conn); + // + // let _ = subject.increment_scan_attempts(&[1, 2]); + // } + // + // #[test] + // fn mark_failures_works() { + // let home_dir = + // ensure_node_home_directory_exists("sent_payable_dao", "mark_failures_works"); + // let conn = DbInitializerReal::default() + // .initialize(&home_dir, DbInitializationConfig::test_default()) + // .unwrap(); + // let hash_1 = make_tx_hash(555); + // let amount_1 = 1234; + // let hash_2 = make_tx_hash(666); + // let amount_2 = 2345; + // let hash_and_amount_1 = HashAndAmount { + // hash: hash_1, + // amount: amount_1, + // }; + // let hash_and_amount_2 = HashAndAmount { + // hash: hash_2, + // amount: amount_2, + // }; + // let timestamp = from_unix_timestamp(190_000_000); + // let subject = PendingPayableDaoReal::new(conn); + // { + // subject + // .insert_new_fingerprints(&[hash_and_amount_1, hash_and_amount_2], timestamp) + // .unwrap(); + // } + // + // let result = subject.mark_failures(&[2]); + // + // assert_eq!(result, Ok(())); + // let assert_conn = Connection::open(home_dir.join(DATABASE_FILE)).unwrap(); + // let mut assert_stm = assert_conn + // .prepare("select rowid, transaction_hash, amount_high_b, amount_low_b, payable_timestamp, attempt, process_error from pending_payable") + // .unwrap(); + // let found_fingerprints = assert_stm + // .query_map([], |row| { + // let rowid: u64 = row.get(0).unwrap(); + // let transaction_hash: String = row.get(1).unwrap(); + // let amount_high_b: i64 = row.get(2).unwrap(); + // let amount_low_b: i64 = row.get(3).unwrap(); + // let timestamp: i64 = row.get(4).unwrap(); + // let attempt: u16 = row.get(5).unwrap(); + // let process_error: Option = row.get(6).unwrap(); + // Ok(SentTx { + // rowid, + // timestamp: from_unix_timestamp(timestamp), + // hash: H256::from_str(&transaction_hash[2..]).unwrap(), + // attempt, + // amount: checked_conversion::(BigIntDivider::reconstitute( + // amount_high_b, + // amount_low_b, + // )), + // process_error, + // }) + // }) + // .unwrap() + // .flatten() + // .collect::>(); + // assert_eq!( + // *found_fingerprints, + // vec![ + // SentTx { + // rowid: 1, + // timestamp, + // hash: hash_1, + // attempt: 1, + // amount: amount_1, + // process_error: None + // }, + // SentTx { + // rowid: 2, + // timestamp, + // hash: hash_2, + // attempt: 1, + // amount: amount_2, + // process_error: Some("ERROR".to_string()) + // } + // ] + // ) + // } + // + // #[test] + // fn mark_failures_sad_path() { + // let home_dir = + // ensure_node_home_directory_exists("sent_payable_dao", "mark_failures_sad_path"); + // { + // DbInitializerReal::default() + // .initialize(&home_dir, DbInitializationConfig::test_default()) + // .unwrap(); + // } + // let conn_read_only = Connection::open_with_flags( + // home_dir.join(DATABASE_FILE), + // OpenFlags::SQLITE_OPEN_READ_ONLY, + // ) + // .unwrap(); + // let wrapped_conn = ConnectionWrapperReal::new(conn_read_only); + // let subject = PendingPayableDaoReal::new(Box::new(wrapped_conn)); + // + // let result = subject.mark_failures(&[1]); + // + // assert_eq!( + // result, + // Err(PendingPayableDaoError::ErrorMarkFailed( + // "attempt to write a readonly database".to_string() + // )) + // ) + // } + // + // #[test] + // #[should_panic( + // expected = "Database corrupt: marking failure at fingerprints: expected to change 2 rows but did 0" + // )] + // fn mark_failures_panics_on_wrong_row_change_count() { + // let home_dir = ensure_node_home_directory_exists( + // "sent_payable_dao", + // "mark_failures_panics_on_wrong_row_change_count", + // ); + // let conn = DbInitializerReal::default() + // .initialize(&home_dir, DbInitializationConfig::test_default()) + // .unwrap(); + // let subject = PendingPayableDaoReal::new(conn); + // + // let _ = subject.mark_failures(&[10, 20]); + // } } diff --git a/node/src/accountant/db_access_objects/sent_payable_dao.rs b/node/src/accountant/db_access_objects/sent_payable_dao.rs index 5cdc59047..148813cad 100644 --- a/node/src/accountant/db_access_objects/sent_payable_dao.rs +++ b/node/src/accountant/db_access_objects/sent_payable_dao.rs @@ -7,7 +7,7 @@ use ethereum_types::{H256, U64}; use web3::types::Address; use masq_lib::utils::ExpectValue; use crate::accountant::{checked_conversion, comma_joined_stringifiable}; -use crate::accountant::db_access_objects::utils::{TxHash, TxIdentifiers}; +use crate::accountant::db_access_objects::utils::{DaoFactoryReal, TxHash, TxIdentifiers}; use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock}; use crate::database::rusqlite_wrappers::ConnectionWrapper; @@ -23,7 +23,7 @@ pub enum SentPayableDaoError { } #[derive(Clone, Debug, PartialEq, Eq)] -pub struct Tx { +pub struct SentTx { pub hash: TxHash, pub receiver_address: Address, pub amount: u128, @@ -57,13 +57,13 @@ impl Display for RetrieveCondition { pub trait SentPayableDao { fn get_tx_identifiers(&self, hashes: &HashSet) -> TxIdentifiers; - fn insert_new_records(&self, txs: &[Tx]) -> Result<(), SentPayableDaoError>; - fn retrieve_txs(&self, condition: Option) -> Vec; + fn insert_new_records(&self, txs: &[SentTx]) -> Result<(), SentPayableDaoError>; + fn retrieve_txs(&self, condition: Option) -> Vec; fn update_tx_blocks( &self, hash_map: &HashMap, ) -> Result<(), SentPayableDaoError>; - fn replace_records(&self, new_txs: &[Tx]) -> Result<(), SentPayableDaoError>; + fn replace_records(&self, new_txs: &[SentTx]) -> Result<(), SentPayableDaoError>; fn delete_records(&self, hashes: &HashSet) -> Result<(), SentPayableDaoError>; } @@ -103,7 +103,7 @@ impl SentPayableDao for SentPayableDaoReal<'_> { .collect() } - fn insert_new_records(&self, txs: &[Tx]) -> Result<(), SentPayableDaoError> { + fn insert_new_records(&self, txs: &[SentTx]) -> Result<(), SentPayableDaoError> { if txs.is_empty() { return Err(SentPayableDaoError::EmptyInput); } @@ -178,7 +178,7 @@ impl SentPayableDao for SentPayableDaoReal<'_> { } } - fn retrieve_txs(&self, condition_opt: Option) -> Vec { + fn retrieve_txs(&self, condition_opt: Option) -> Vec { let raw_sql = "SELECT tx_hash, receiver_address, amount_high_b, amount_low_b, \ timestamp, gas_price_wei_high_b, gas_price_wei_low_b, nonce, block_hash, block_number FROM sent_payable" .to_string(); @@ -226,7 +226,7 @@ impl SentPayableDao for SentPayableDaoReal<'_> { _ => panic!("Invalid block details"), }; - Ok(Tx { + Ok(SentTx { hash, receiver_address, amount, @@ -275,12 +275,12 @@ impl SentPayableDao for SentPayableDaoReal<'_> { Ok(()) } - fn replace_records(&self, new_txs: &[Tx]) -> Result<(), SentPayableDaoError> { + fn replace_records(&self, new_txs: &[SentTx]) -> Result<(), SentPayableDaoError> { if new_txs.is_empty() { return Err(SentPayableDaoError::EmptyInput); } - let build_case = |value_fn: fn(&Tx) -> String| { + let build_case = |value_fn: fn(&SentTx) -> String| { new_txs .iter() .map(|tx| format!("WHEN nonce = {} THEN {}", tx.nonce, value_fn(tx))) @@ -398,6 +398,17 @@ impl SentPayableDao for SentPayableDaoReal<'_> { } } +pub trait SentPayableDaoFactory { + fn make(&self) -> Box; +} + +impl SentPayableDaoFactory for DaoFactoryReal { + fn make(&self) -> Box { + todo!() + //Box::new(PendingPayableDaoReal::new(self.make_connection())) + } +} + #[cfg(test)] mod tests { use std::collections::{HashMap, HashSet}; @@ -483,12 +494,12 @@ mod tests { result, Err(SentPayableDaoError::InvalidInput( "Duplicate hashes found in the input. Input Transactions: \ - [Tx { \ + [SentTx { \ hash: 0x00000000000000000000000000000000000000000000000000000000000004d2, \ receiver_address: 0x0000000000000000000000000000000000000000, \ amount: 0, timestamp: 1749204017, gas_price_wei: 0, \ nonce: 0, block_opt: None }, \ - Tx { \ + SentTx { \ hash: 0x00000000000000000000000000000000000000000000000000000000000004d2, \ receiver_address: 0x0000000000000000000000000000000000000000, \ amount: 0, timestamp: 1749204020, gas_price_wei: 0, \ diff --git a/node/src/accountant/db_access_objects/test_utils.rs b/node/src/accountant/db_access_objects/test_utils.rs index 598a4121d..1a9ff6e1d 100644 --- a/node/src/accountant/db_access_objects/test_utils.rs +++ b/node/src/accountant/db_access_objects/test_utils.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use rusqlite::{Connection, OpenFlags}; -use crate::accountant::db_access_objects::sent_payable_dao::{ Tx}; +use crate::accountant::db_access_objects::sent_payable_dao::{ SentTx}; 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}; @@ -47,8 +47,8 @@ impl TxBuilder { self } - pub fn build(self) -> Tx { - Tx { + pub fn build(self) -> SentTx { + SentTx { hash: self.hash_opt.unwrap_or_default(), receiver_address: self.receiver_address_opt.unwrap_or_default(), amount: self.amount_opt.unwrap_or_default(), diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index 24dbdcc68..56dd6cf75 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -14,11 +14,9 @@ use masq_lib::constants::{SCAN_ERROR, WEIS_IN_GWEI}; use std::cell::{Ref, RefCell}; use crate::accountant::db_access_objects::payable_dao::{PayableDao, PayableDaoError}; -use crate::accountant::db_access_objects::pending_payable_dao::PendingPayableDao; +use crate::accountant::db_access_objects::sent_payable_dao::SentPayableDao; use crate::accountant::db_access_objects::receivable_dao::{ReceivableDao, ReceivableDaoError}; -use crate::accountant::db_access_objects::utils::{ - remap_payable_accounts, remap_receivable_accounts, CustomQuery, DaoFactoryReal, -}; +use crate::accountant::db_access_objects::utils::{remap_payable_accounts, remap_receivable_accounts, CustomQuery, DaoFactoryReal, TxHash}; use crate::accountant::financials::visibility_restricted_module::{ check_query_is_within_tech_limits, financials_entry_check, }; @@ -26,7 +24,7 @@ use crate::accountant::scanners::payable_scanner_extension::msgs::{ BlockchainAgentWithContextMessage, QualifiedPayablesMessage, }; use crate::accountant::scanners::{StartScanError, Scanners}; -use crate::blockchain::blockchain_bridge::{BlockMarker, PendingPayableFingerprint, PendingPayableFingerprintSeeds, RetrieveTransactions}; +use crate::blockchain::blockchain_bridge::{BlockMarker, PendingPayableFingerprintSeeds, RetrieveTransactions}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::HashAndAmount; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; use crate::blockchain::blockchain_interface::data_structures::{ @@ -89,7 +87,7 @@ pub struct Accountant { earning_wallet: Wallet, payable_dao: Box, receivable_dao: Box, - pending_payable_dao: Box, + sent_payable_dao: Box, crashable: bool, scanners: Scanners, scan_schedulers: ScanSchedulers, @@ -138,7 +136,7 @@ pub struct ReceivedPayments { #[derive(Debug, PartialEq, Eq, Message, Clone)] pub struct ReportTransactionReceipts { - pub fingerprints_with_receipts: Vec<(TransactionReceiptResult, PendingPayableFingerprint)>, + pub receipt_results: Vec, pub response_skeleton_opt: Option, } @@ -469,7 +467,7 @@ pub trait SkeletonOptHolder { #[derive(Debug, PartialEq, Eq, Message, Clone)] pub struct RequestTransactionReceipts { - pub pending_payable_fingerprints: Vec, + pub tx_hashes: Vec, pub response_skeleton_opt: Option, } @@ -519,7 +517,7 @@ impl Accountant { let earning_wallet = config.earning_wallet.clone(); let financial_statistics = Rc::new(RefCell::new(FinancialStatistics::default())); let payable_dao = dao_factories.payable_dao_factory.make(); - let pending_payable_dao = dao_factories.pending_payable_dao_factory.make(); + let sent_payable_dao = dao_factories.sent_payable_dao_factory.make(); let receivable_dao = dao_factories.receivable_dao_factory.make(); let scan_schedulers = ScanSchedulers::new(scan_intervals, config.automatic_scans_enabled); let scanners = Scanners::new( @@ -534,7 +532,7 @@ impl Accountant { earning_wallet, payable_dao, receivable_dao, - pending_payable_dao, + sent_payable_dao, scanners, crashable: config.crash_point == CrashPoint::Message, scan_schedulers, @@ -1126,7 +1124,7 @@ impl Accountant { }) } match self - .pending_payable_dao + .sent_payable_dao .insert_new_fingerprints(&msg.hashes_and_balances, msg.batch_wide_timestamp) { Ok(_) => debug!( @@ -1149,6 +1147,21 @@ impl Accountant { } } +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PendingPayable { + pub recipient_wallet: Wallet, + pub hash: H256, +} + +impl PendingPayable { + pub fn new(recipient_wallet: Wallet, hash: H256) -> Self { + Self { + recipient_wallet, + hash, + } + } +} + #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct PendingPayableId { pub rowid: u64, @@ -1169,14 +1182,14 @@ impl PendingPayableId { } } -impl From for PendingPayableId { - fn from(pending_payable_fingerprint: PendingPayableFingerprint) -> Self { - Self { - hash: pending_payable_fingerprint.hash, - rowid: pending_payable_fingerprint.rowid, - } - } -} +// impl From for PendingPayableId { +// fn from(pending_payable_fingerprint: SentTx) -> Self { +// Self { +// hash: pending_payable_fingerprint.hash, +// rowid: pending_payable_fingerprint.rowid, +// } +// } +// } pub fn comma_joined_stringifiable(collection: &[T], stringify: F) -> String where @@ -1219,9 +1232,6 @@ mod tests { use crate::accountant::db_access_objects::payable_dao::{ PayableAccount, PayableDaoError, PayableDaoFactory, }; - use crate::accountant::db_access_objects::pending_payable_dao::{ - PendingPayable, PendingPayableDaoError, TransactionHashes, - }; use crate::accountant::db_access_objects::receivable_dao::ReceivableAccount; use crate::accountant::db_access_objects::utils::{from_unix_timestamp, to_unix_timestamp, CustomQuery}; use crate::accountant::payment_adjuster::Adjustment; @@ -1231,7 +1241,7 @@ mod tests { use crate::accountant::test_utils::DaoWithDestination::{ ForAccountantBody, ForPayableScanner, ForPendingPayableScanner, ForReceivableScanner, }; - use crate::accountant::test_utils::{bc_from_earning_wallet, bc_from_wallets, make_payable_account, make_qualified_and_unqualified_payables, make_pending_payable_fingerprint, BannedDaoFactoryMock, ConfigDaoFactoryMock, MessageIdGeneratorMock, PayableDaoFactoryMock, PayableDaoMock, PayableScannerBuilder, PaymentAdjusterMock, PendingPayableDaoFactoryMock, PendingPayableDaoMock, ReceivableDaoFactoryMock, ReceivableDaoMock}; + use crate::accountant::test_utils::{bc_from_earning_wallet, bc_from_wallets, make_payable_account, make_qualified_and_unqualified_payables, make_pending_payable_fingerprint, BannedDaoFactoryMock, ConfigDaoFactoryMock, MessageIdGeneratorMock, PayableDaoFactoryMock, PayableDaoMock, PayableScannerBuilder, PaymentAdjusterMock, PendingPayableDaoFactoryMock, SentPayableDaoMock, ReceivableDaoFactoryMock, ReceivableDaoMock}; use crate::accountant::test_utils::{AccountantBuilder, BannedDaoMock}; use crate::accountant::Accountant; use crate::blockchain::blockchain_interface::blockchain_interface_web3::HashAndAmount; @@ -1286,9 +1296,11 @@ mod tests { use std::sync::Mutex; use std::time::{Duration, UNIX_EPOCH}; use std::vec; + use system_configuration::sys::schema_definitions::kSCPropNetProxiesHTTPSProxy; + use crate::accountant::db_access_objects::sent_payable_dao::{SentPayableDaoError, SentTx}; use crate::accountant::scanners::scan_schedulers::{NewPayableScanDynIntervalComputer, NewPayableScanDynIntervalComputerReal}; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{OperationOutcome, PayableScanResult}; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, TxReceipt, TxStatus}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{ConfirmedTx, TransactionBlock, TxReceipt, TxStatus}; use crate::test_utils::recorder_counter_msgs::SingleTypeCounterMsgSetup; impl Handler> for Accountant { @@ -1322,11 +1334,11 @@ mod tests { .make_result(PayableDaoMock::new()) // For Accountant .make_result(PayableDaoMock::new()) // For Payable Scanner .make_result(PayableDaoMock::new()); // For PendingPayable Scanner - let pending_payable_dao_factory = PendingPayableDaoFactoryMock::new() + let sent_payable_dao_factory = PendingPayableDaoFactoryMock::new() .make_params(&pending_payable_dao_factory_params_arc) - .make_result(PendingPayableDaoMock::new()) // For Accountant - .make_result(PendingPayableDaoMock::new()) // For Payable Scanner - .make_result(PendingPayableDaoMock::new()); // For PendingPayable Scanner + .make_result(SentPayableDaoMock::new()) // For Accountant + .make_result(SentPayableDaoMock::new()) // For Payable Scanner + .make_result(SentPayableDaoMock::new()); // For PendingPayable Scanner let receivable_dao_factory = ReceivableDaoFactoryMock::new() .make_params(&receivable_dao_factory_params_arc) .make_result(ReceivableDaoMock::new()) // For Accountant @@ -1342,7 +1354,7 @@ mod tests { config, DaoFactories { payable_dao_factory: Box::new(payable_dao_factory), - pending_payable_dao_factory: Box::new(pending_payable_dao_factory), + sent_payable_dao_factory: Box::new(sent_payable_dao_factory), receivable_dao_factory: Box::new(receivable_dao_factory), banned_dao_factory: Box::new(banned_dao_factory), config_dao_factory: Box::new(config_dao_factory), @@ -1374,11 +1386,11 @@ mod tests { .make_result(PayableDaoMock::new()) // For Payable Scanner .make_result(PayableDaoMock::new()), // For PendingPayable Scanner ); - let pending_payable_dao_factory = Box::new( + let sent_payable_dao_factory = Box::new( PendingPayableDaoFactoryMock::new() - .make_result(PendingPayableDaoMock::new()) // For Accountant - .make_result(PendingPayableDaoMock::new()) // For Payable Scanner - .make_result(PendingPayableDaoMock::new()), // For PendingPayable Scanner + .make_result(SentPayableDaoMock::new()) // For Accountant + .make_result(SentPayableDaoMock::new()) // For Payable Scanner + .make_result(SentPayableDaoMock::new()), // For PendingPayable Scanner ); let receivable_dao_factory = Box::new( ReceivableDaoFactoryMock::new() @@ -1394,7 +1406,7 @@ mod tests { bootstrapper_config, DaoFactories { payable_dao_factory, - pending_payable_dao_factory, + sent_payable_dao_factory, receivable_dao_factory, banned_dao_factory, config_dao_factory, @@ -1554,14 +1566,14 @@ mod tests { #[test] fn sent_payable_with_response_skeleton_sends_scan_response_to_ui_gateway() { let config = bc_from_earning_wallet(make_wallet("earning_wallet")); - let pending_payable_dao = - PendingPayableDaoMock::default().fingerprints_rowids_result(TransactionHashes { + let sent_payable_dao = + SentPayableDaoMock::default().fingerprints_rowids_result(TransactionHashes { rowid_results: vec![(1, make_tx_hash(123))], no_rowid_results: vec![], }); let payable_dao = PayableDaoMock::default().mark_pending_payables_rowids_result(Ok(())); let mut subject = AccountantBuilder::default() - .pending_payable_daos(vec![ForPayableScanner(pending_payable_dao)]) + .pending_payable_daos(vec![ForPayableScanner(sent_payable_dao)]) .payable_daos(vec![ForPayableScanner(payable_dao)]) .bootstrapper_config(config) .build(); @@ -1824,20 +1836,21 @@ mod tests { receivable_scan_interval: Duration::from_millis(10_000), pending_payable_scan_interval: Duration::from_secs(100), }); - let fingerprint = PendingPayableFingerprint { - rowid: 1234, + let fingerprint = SentTx { timestamp: SystemTime::now(), + gas_price_wei: 0, + nonce: 0, hash: Default::default(), - attempt: 1, amount: 1_000_000, - process_error: None, + receiver_address: Default::default(), + block_opt: None, }; - let pending_payable_dao = PendingPayableDaoMock::default() - .return_all_errorless_fingerprints_result(vec![fingerprint.clone()]); + let sent_payable_dao = SentPayableDaoMock::default() + .retrieve_txs_result(vec![fingerprint.clone()]); let mut subject = AccountantBuilder::default() .consuming_wallet(make_paying_wallet(b"consuming")) .bootstrapper_config(config) - .pending_payable_daos(vec![ForPendingPayableScanner(pending_payable_dao)]) + .pending_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) .build(); let (blockchain_bridge, _, blockchain_bridge_recording_arc) = make_recorder(); let blockchain_bridge = blockchain_bridge @@ -1886,11 +1899,11 @@ mod tests { let payable_dao = PayableDaoMock::default() .transactions_confirmed_params(&transaction_confirmed_params_arc) .transactions_confirmed_result(Ok(())); - let pending_payable_dao = - PendingPayableDaoMock::default().delete_fingerprints_result(Ok(())); + let sent_payable_dao = + SentPayableDaoMock::default().delete_fingerprints_result(Ok(())); let mut subject = AccountantBuilder::default() .payable_daos(vec![ForPendingPayableScanner(payable_dao)]) - .pending_payable_daos(vec![ForPendingPayableScanner(pending_payable_dao)]) + .pending_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) .build(); let (ui_gateway, _, ui_gateway_recording_arc) = make_recorder(); let ui_gateway = @@ -2106,7 +2119,7 @@ mod tests { // TODO when we have more logic in place with the other cards taken in, we'll need to configure these // accordingly let payable_dao = PayableDaoMock::default().transactions_confirmed_result(Ok(())); - let pending_payable = PendingPayableDaoMock::default() + let pending_payable = SentPayableDaoMock::default() .return_all_errorless_fingerprints_result(vec![make_pending_payable_fingerprint()]) .mark_failures_result(Ok(())); let mut subject = AccountantBuilder::default() @@ -3268,11 +3281,11 @@ mod tests { #[test] fn initial_pending_payable_scan_if_some_payables_found() { - let pending_payable_dao = PendingPayableDaoMock::default() + let sent_payable_dao = SentPayableDaoMock::default() .return_all_errorless_fingerprints_result(vec![make_pending_payable_fingerprint()]); let mut subject = AccountantBuilder::default() .consuming_wallet(make_wallet("consuming")) - .pending_payable_daos(vec![ForPendingPayableScanner(pending_payable_dao)]) + .pending_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) .build(); let system = System::new("test"); let (blockchain_bridge, _, blockchain_bridge_recording_arc) = make_recorder(); @@ -3294,11 +3307,11 @@ mod tests { #[test] fn initial_pending_payable_scan_if_no_payables_found() { - let pending_payable_dao = - PendingPayableDaoMock::default().return_all_errorless_fingerprints_result(vec![]); + let sent_payable_dao = + SentPayableDaoMock::default().return_all_errorless_fingerprints_result(vec![]); let mut subject = AccountantBuilder::default() .consuming_wallet(make_wallet("consuming")) - .pending_payable_daos(vec![ForPendingPayableScanner(pending_payable_dao)]) + .pending_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) .build(); let flag_before = subject.scanners.initial_pending_payable_scan(); @@ -3480,21 +3493,21 @@ mod tests { }; let tx_receipt = TxReceipt { transaction_hash, - status: TxStatus::Succeeded(TransactionBlock { - block_hash: make_tx_hash(369369), - block_number: 4444444444u64.into(), + status: TxStatus::Succeeded(ConfirmedTx{ + tx_hash: make_tx_hash(369369), + block: 4444444444u64.into(), }), }; - let pending_payable_fingerprint = make_pending_payable_fingerprint(); + let requested_tx = make_tx_hash( + 234 + ); let counter_msg_3 = ReportTransactionReceipts { - fingerprints_with_receipts: vec![( - TransactionReceiptResult::RpcResponse(tx_receipt), - pending_payable_fingerprint.clone(), - )], + receipt_results: vec![ + TransactionReceiptResult::RpcResponse(tx_receipt)], response_skeleton_opt: None, }; let request_transaction_receipts_msg = RequestTransactionReceipts { - pending_payable_fingerprints: vec![pending_payable_fingerprint], + tx_hashes: vec![requested_tx], response_skeleton_opt: None, }; let qualified_payables_msg = QualifiedPayablesMessage { @@ -4035,23 +4048,25 @@ mod tests { let blockchain_bridge_addr = blockchain_bridge .system_stop_conditions(match_lazily_every_type_id!(RequestTransactionReceipts)) .start(); - let payable_fingerprint_1 = PendingPayableFingerprint { - rowid: 555, + let payable_fingerprint_1 = SentTx { timestamp: from_unix_timestamp(210_000_000), + gas_price_wei: 0, + nonce: 0, hash: make_tx_hash(45678), - attempt: 1, amount: 4444, - process_error: None, + receiver_address: Default::default(), + block_opt: None, }; - let payable_fingerprint_2 = PendingPayableFingerprint { - rowid: 550, + let payable_fingerprint_2 = SentTx { timestamp: from_unix_timestamp(210_000_100), + gas_price_wei: 0, + nonce: 0, hash: make_tx_hash(112233), - attempt: 2, amount: 7999, - process_error: None, + receiver_address: Default::default(), + block_opt: None, }; - let pending_payable_dao = PendingPayableDaoMock::default() + let sent_payable_dao = SentPayableDaoMock::default() .return_all_errorless_fingerprints_result(vec![ payable_fingerprint_1.clone(), payable_fingerprint_2.clone(), @@ -4060,7 +4075,7 @@ mod tests { let system = System::new("pending payable scan"); let mut subject = AccountantBuilder::default() .consuming_wallet(make_paying_wallet(b"consuming")) - .pending_payable_daos(vec![ForPendingPayableScanner(pending_payable_dao)]) + .pending_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) .bootstrapper_config(config) .build(); @@ -4079,7 +4094,8 @@ mod tests { assert_eq!( received_msg, &RequestTransactionReceipts { - pending_payable_fingerprints: vec![payable_fingerprint_1, payable_fingerprint_2], + tx_hashes: todo!(), + // pending_payable_fingerprints: vec![payable_fingerprint_1, payable_fingerprint_2], response_skeleton_opt: None, } ); @@ -4794,7 +4810,7 @@ mod tests { let expected_wallet = make_wallet("paying_you"); let expected_hash = H256::from("transaction_hash".keccak256()); let expected_rowid = 45623; - let pending_payable_dao = PendingPayableDaoMock::default() + let sent_payable_dao = SentPayableDaoMock::default() .fingerprints_rowids_params(&fingerprints_rowids_params_arc) .fingerprints_rowids_result(TransactionHashes { rowid_results: vec![(expected_rowid, expected_hash)], @@ -4808,7 +4824,7 @@ mod tests { let mut subject = AccountantBuilder::default() .bootstrapper_config(bc_from_earning_wallet(make_wallet("some_wallet_address"))) .payable_daos(vec![ForPayableScanner(payable_dao)]) - .pending_payable_daos(vec![ForPayableScanner(pending_payable_dao)]) + .pending_payable_daos(vec![ForPayableScanner(sent_payable_dao)]) .build(); let pending_payable_interval = Duration::from_millis(55); subject.scan_schedulers.pending_payable.interval = pending_payable_interval; @@ -4967,12 +4983,12 @@ mod tests { let payable_dao = PayableDaoMock::default() .transactions_confirmed_params(&transactions_confirmed_params_arc) .transactions_confirmed_result(Ok(())); - let pending_payable_dao = - PendingPayableDaoMock::default().delete_fingerprints_result(Ok(())); + let sent_payable_dao = + SentPayableDaoMock::default().delete_fingerprints_result(Ok(())); let system = System::new("new_payable_scanner_timely"); let mut subject = AccountantBuilder::default() .payable_daos(vec![ForPendingPayableScanner(payable_dao)]) - .pending_payable_daos(vec![ForPendingPayableScanner(pending_payable_dao)]) + .pending_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) .build(); let last_new_payable_scan_timestamp = SystemTime::now() .checked_sub(Duration::from_secs(3)) @@ -5045,11 +5061,11 @@ mod tests { let payable_dao = PayableDaoMock::default() .transactions_confirmed_params(&transactions_confirmed_params_arc) .transactions_confirmed_result(Ok(())); - let pending_payable_dao = - PendingPayableDaoMock::default().delete_fingerprints_result(Ok(())); + let sent_payable_dao = + SentPayableDaoMock::default().delete_fingerprints_result(Ok(())); let mut subject = AccountantBuilder::default() .payable_daos(vec![ForPendingPayableScanner(payable_dao)]) - .pending_payable_daos(vec![ForPendingPayableScanner(pending_payable_dao)]) + .pending_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) .build(); let last_new_payable_scan_timestamp = SystemTime::now() .checked_sub(Duration::from_secs(8)) @@ -5114,12 +5130,12 @@ mod tests { fn scheduler_for_new_payables_operates_with_proper_now_timestamp() { let new_payable_notify_later_arc = Arc::new(Mutex::new(vec![])); let payable_dao = PayableDaoMock::default().transactions_confirmed_result(Ok(())); - let pending_payable_dao = - PendingPayableDaoMock::default().delete_fingerprints_result(Ok(())); + let sent_payable_dao = + SentPayableDaoMock::default().delete_fingerprints_result(Ok(())); let system = System::new("scheduler_for_new_payables_operates_with_proper_now_timestamp"); let mut subject = AccountantBuilder::default() .payable_daos(vec![ForPendingPayableScanner(payable_dao)]) - .pending_payable_daos(vec![ForPendingPayableScanner(pending_payable_dao)]) + .pending_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) .build(); let last_new_payable_scan_timestamp = SystemTime::now() .checked_sub(Duration::from_millis(3500)) @@ -5178,7 +5194,7 @@ mod tests { fn make_report_transaction_receipts_msg( status_txs: Vec, - ) -> (ReportTransactionReceipts, Vec) { + ) -> (ReportTransactionReceipts, Vec) { let (receipt_result_fingerprint_pairs, fingerprints): (Vec<_>, Vec<_>) = status_txs .into_iter() .enumerate() @@ -5188,13 +5204,15 @@ mod tests { transaction_hash, status, }); - let fingerprint = PendingPayableFingerprint { - rowid: idx as u64, + let fingerprint = SentTx { + timestamp: from_unix_timestamp(1_000_000_000 * idx as i64), + gas_price_wei: 0, + nonce: 0, hash: transaction_hash, - attempt: 2, amount: 1_000_000 * idx as u128 * idx as u128, - process_error: None, + receiver_address: Default::default(), + block_opt: None, }; ( (transaction_receipt_result, fingerprint.clone()), @@ -5204,7 +5222,8 @@ mod tests { .unzip(); let msg = ReportTransactionReceipts { - fingerprints_with_receipts: receipt_result_fingerprint_pairs, + receipt_results: todo!(), + //fingerprints_with_receipts: receipt_result_fingerprint_pairs, response_skeleton_opt: None, }; @@ -5215,11 +5234,11 @@ mod tests { fn accountant_handles_inserting_new_fingerprints() { init_test_logging(); let insert_fingerprint_params_arc = Arc::new(Mutex::new(vec![])); - let pending_payable_dao = PendingPayableDaoMock::default() + let sent_payable_dao = SentPayableDaoMock::default() .insert_fingerprints_params(&insert_fingerprint_params_arc) .insert_fingerprints_result(Ok(())); let subject = AccountantBuilder::default() - .pending_payable_daos(vec![ForAccountantBody(pending_payable_dao)]) + .pending_payable_daos(vec![ForAccountantBody(sent_payable_dao)]) .build(); let accountant_addr = subject.start(); let accountant_subs = Accountant::make_subs_from(&accountant_addr); @@ -5262,13 +5281,13 @@ mod tests { } #[test] - fn payable_fingerprint_insertion_clearly_failed_and_we_log_it_at_least() { + fn sent_payable_insertion_clearly_failed_and_we_log_it_at_least() { //despite it doesn't end so here this event would be a cause of a later panic init_test_logging(); - let insert_fingerprint_params_arc = Arc::new(Mutex::new(vec![])); - let pending_payable_dao = PendingPayableDaoMock::default() - .insert_fingerprints_params(&insert_fingerprint_params_arc) - .insert_fingerprints_result(Err(PendingPayableDaoError::InsertionFailed( + let insert_new_records_params_arc = Arc::new(Mutex::new(vec![])); + let sent_payable_dao = SentPayableDaoMock::default() + .insert_new_records_params(&insert_new_records_params_arc) + .insert_new_records_result(Err(SentPayableDaoError::SqlExecutionFailed( "Crashed".to_string(), ))); let amount = 2345; @@ -5278,7 +5297,7 @@ mod tests { amount, }; let subject = AccountantBuilder::default() - .pending_payable_daos(vec![ForAccountantBody(pending_payable_dao)]) + .pending_payable_daos(vec![ForAccountantBody(sent_payable_dao)]) .build(); let timestamp = SystemTime::now(); let report_new_fingerprints = PendingPayableFingerprintSeeds { @@ -5288,7 +5307,7 @@ mod tests { let _ = subject.handle_new_pending_payable_fingerprints(report_new_fingerprints); - let insert_fingerprint_params = insert_fingerprint_params_arc.lock().unwrap(); + let insert_fingerprint_params = insert_new_records_params_arc.lock().unwrap(); assert_eq!( *insert_fingerprint_params, vec![(vec![hash_and_amount], timestamp)] diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index 349ffe3df..0dba50ad2 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -5,24 +5,23 @@ pub mod scan_schedulers; pub mod scanners_utils; pub mod test_utils; -use crate::accountant::db_access_objects::payable_dao::{PayableAccount, PayableDao}; -use crate::accountant::db_access_objects::pending_payable_dao::{PendingPayable, PendingPayableDao}; +use crate::accountant::db_access_objects::payable_dao::{MarkOfPendingPayable, PayableAccount, PayableDao}; use crate::accountant::db_access_objects::receivable_dao::ReceivableDao; use crate::accountant::payment_adjuster::{PaymentAdjuster, PaymentAdjusterReal}; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableTransactingErrorEnum::{ LocallyCausedError, RemotelyCausedErrors, }; -use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{debugging_summary_after_error_separation, err_msg_for_failure_with_expected_but_missing_fingerprints, investigate_debt_extremes, mark_pending_payable_fatal_error, payables_debug_summary, separate_errors, separate_rowids_and_hashes, OperationOutcome, PayableScanResult, PayableThresholdsGauge, PayableThresholdsGaugeReal, PayableTransactingErrorEnum, PendingPayableMetadata}; -use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_none_receipt, handle_status_with_failure, handle_status_with_success, PendingPayableScanReport, PendingPayableScanResult}; +use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{debugging_summary_after_error_separation, err_msg_for_failure_with_expected_but_missing_fingerprints, investigate_debt_extremes, mark_pending_payable_fatal_error, payables_debug_summary, separate_errors, separate_rowids_and_hashes, OperationOutcome, PayableScanResult, PayableThresholdsGauge, PayableThresholdsGaugeReal, PayableTransactingErrorEnum, PendingPayableMissingInDb}; +use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_local_error_fetching_receipts, handle_status_with_failure, handle_successful_tx, PendingPayableScanReport, PendingPayableScanResult}; use crate::accountant::scanners::scanners_utils::receivable_scanner_utils::balance_and_age; -use crate::accountant::{PendingPayableId, ScanError, ScanForPendingPayables, ScanForRetryPayables}; +use crate::accountant::{PendingPayable, PendingPayableId, ScanError, ScanForPendingPayables, ScanForRetryPayables}; use crate::accountant::{ comma_joined_stringifiable, gwei_to_wei, ReceivedPayments, ReportTransactionReceipts, RequestTransactionReceipts, ResponseSkeleton, ScanForNewPayables, ScanForReceivables, SentPayables, }; use crate::accountant::db_access_objects::banned_dao::BannedDao; -use crate::blockchain::blockchain_bridge::{BlockMarker, PendingPayableFingerprint, RetrieveTransactions}; +use crate::blockchain::blockchain_bridge::{BlockMarker, RetrieveTransactions}; use crate::sub_lib::accountant::{ DaoFactories, FinancialStatistics, PaymentThresholds, }; @@ -40,13 +39,18 @@ use std::collections::{HashMap, HashSet}; use std::fmt::Debug; use std::rc::Rc; use std::time::{SystemTime}; +use bytes::Buf; use time::format_description::parse; use time::OffsetDateTime; use variant_count::VariantCount; use web3::types::H256; +use crate::accountant::db_access_objects::failed_payable_dao::FailedTx; +use crate::accountant::db_access_objects::sent_payable_dao::SentTx; +use crate::accountant::db_access_objects::utils::{TxHash, TxIdentifiers}; use crate::accountant::scanners::payable_scanner_extension::{MultistageDualPayableScanner, PreparedAdjustment, SolvencySensitivePaymentInstructor}; use crate::accountant::scanners::payable_scanner_extension::msgs::{BlockchainAgentWithContextMessage, QualifiedPayablesMessage}; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionReceiptResult, TxStatus}; +use crate::accountant::test_utils::SentPayableDao; +use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{ConfirmedTx, TransactionReceiptResult, TxStatus}; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; use crate::db_config::persistent_configuration::{PersistentConfiguration, PersistentConfigurationReal}; @@ -82,14 +86,14 @@ impl Scanners { ) -> Self { let payable = Box::new(PayableScanner::new( dao_factories.payable_dao_factory.make(), - dao_factories.pending_payable_dao_factory.make(), + dao_factories.sent_payable_dao_factory.make(), Rc::clone(&payment_thresholds), Box::new(PaymentAdjusterReal::new()), )); let pending_payable = Box::new(PendingPayableScanner::new( dao_factories.payable_dao_factory.make(), - dao_factories.pending_payable_dao_factory.make(), + dao_factories.sent_payable_dao_factory.make(), Rc::clone(&payment_thresholds), when_pending_too_long_sec, Rc::clone(&financial_statistics), @@ -461,7 +465,7 @@ pub struct PayableScanner { pub payable_threshold_gauge: Box, pub common: ScannerCommon, pub payable_dao: Box, - pub pending_payable_dao: Box, + pub sent_payable_dao: Box, pub payment_adjuster: Box, } @@ -597,14 +601,14 @@ impl SolvencySensitivePaymentInstructor for PayableScanner { impl PayableScanner { pub fn new( payable_dao: Box, - pending_payable_dao: Box, + sent_payable_dao: Box, payment_thresholds: Rc, payment_adjuster: Box, ) -> Self { Self { common: ScannerCommon::new(payment_thresholds), payable_dao, - pending_payable_dao, + sent_payable_dao, payable_threshold_gauge: Box::new(PayableThresholdsGaugeReal::default()), payment_adjuster, } @@ -675,34 +679,30 @@ impl PayableScanner { fn separate_existent_and_nonexistent_fingerprints<'a>( &'a self, sent_payables: &[&'a PendingPayable], - ) -> (Vec, Vec) { - let hashes = sent_payables + ) -> (Vec, Vec) { + let actual_sent_payables_simple_total = sent_payables.len(); + + let actual_sent_payable_hashes_hashset = sent_payables .iter() .map(|pending_payable| pending_payable.hash) - .collect::>(); + .collect::>(); + + if actual_sent_payable_hashes_hashset.len() != actual_sent_payables_simple_total { + todo!("check potential duplicity") + } + let mut sent_payables_hashmap = sent_payables .iter() .map(|payable| (payable.hash, &payable.recipient_wallet)) - .collect::>(); + .collect::>(); - let transaction_hashes = self.pending_payable_dao.fingerprints_rowids(&hashes); - let mut hashes_from_db = transaction_hashes - .rowid_results - .iter() - .map(|(_rowid, hash)| *hash) - .collect::>(); - for hash in &transaction_hashes.no_rowid_results { - hashes_from_db.insert(*hash); - } - let sent_payables_hashes = hashes.iter().copied().collect::>(); + let transaction_hashes = self.sent_payable_dao.get_tx_identifiers(&actual_sent_payable_hashes_hashset); + let + let hashes_from_db = transaction_hashes + .keys() + .collect::>(); - if !Self::is_symmetrical(sent_payables_hashes, hashes_from_db) { - panic!( - "Inconsistency in two maps, they cannot be matched by hashes. Data set directly \ - sent from BlockchainBridge: {:?}, set derived from the DB: {:?}", - sent_payables, transaction_hashes - ) - } + let missing_sent_payables_hashes = actual_sent_payable_hashes_hashset.difference(&hashes_from_db).collect(); let pending_payables_with_rowid = transaction_hashes .rowid_results @@ -711,7 +711,7 @@ impl PayableScanner { let wallet = sent_payables_hashmap .remove(&hash) .expect("expect transaction hash, but it disappear"); - PendingPayableMetadata::new(wallet, hash, Some(rowid)) + PendingPayableMissingInDb::new(wallet, hash, Some(rowid)) }) .collect_vec(); let pending_payables_without_rowid = transaction_hashes @@ -721,22 +721,15 @@ impl PayableScanner { let wallet = sent_payables_hashmap .remove(&hash) .expect("expect transaction hash, but it disappear"); - PendingPayableMetadata::new(wallet, hash, None) + PendingPayableMissingInDb::new(wallet, hash, None) }) .collect_vec(); (pending_payables_with_rowid, pending_payables_without_rowid) } - fn is_symmetrical( - sent_payables_hashes: HashSet, - fingerptint_hashes: HashSet, - ) -> bool { - sent_payables_hashes == fingerptint_hashes - } - fn mark_pending_payable(&self, sent_payments: &[&PendingPayable], logger: &Logger) { - fn missing_fingerprints_msg(nonexistent: &[PendingPayableMetadata]) -> String { + fn missing_fingerprints_msg(nonexistent: &[PendingPayableMissingInDb]) -> String { format!( "Expected pending payable fingerprints for {} were not found; system unreliable", comma_joined_stringifiable(nonexistent, |pp_triple| format!( @@ -746,7 +739,7 @@ impl PayableScanner { ) } fn ready_data_for_supply<'a>( - existent: &'a [PendingPayableMetadata], + existent: &'a [PendingPayableMissingInDb], ) -> Vec<(&'a Wallet, u64)> { existent .iter() @@ -756,7 +749,8 @@ impl PayableScanner { let (existent, nonexistent) = self.separate_existent_and_nonexistent_fingerprints(sent_payments); - let mark_pp_input_data = ready_data_for_supply(&existent); + + // let mark_pp_input_data = ready_data_for_supply(&existent); if !mark_pp_input_data.is_empty() { if let Err(e) = self .payable_dao @@ -815,7 +809,7 @@ impl PayableScanner { comma_joined_stringifiable(hashes, |hash| format!("{:?}", hash)) } let existent_and_nonexistent = self - .pending_payable_dao + .sent_payable_dao .fingerprints_rowids(&hashes_of_failed); let missing_fgp_err_msg_opt = err_msg_for_failure_with_expected_but_missing_fingerprints( existent_and_nonexistent.no_rowid_results, @@ -828,7 +822,7 @@ impl PayableScanner { "Deleting fingerprints for failed transactions {}", serialize_hashes(&hashes) ); - if let Err(e) = self.pending_payable_dao.delete_fingerprints(&ids) { + if let Err(e) = self.sent_payable_dao.delete_fingerprints(&ids) { if let Some(msg) = missing_fgp_err_msg_opt { error!(logger, "{}", msg) }; @@ -849,9 +843,11 @@ impl PayableScanner { pub struct PendingPayableScanner { pub common: ScannerCommon, pub payable_dao: Box, - pub pending_payable_dao: Box, + pub sent_payable_dao: Box, pub when_pending_too_long_sec: u64, pub financial_statistics: Rc>, + pub cached_currently_queried_tx_for_receipts: Vec, //TODO always delete after the scan ends + pub cached_previous_cycle_txs_pending_too_long_opt: Option> //TODO also treat carefully } impl @@ -876,7 +872,7 @@ impl StartableScanner ) -> Result { self.mark_as_started(timestamp); info!(logger, "Scanning for pending payable"); - let filtered_pending_payable = self.pending_payable_dao.return_all_errorless_fingerprints(); + let filtered_pending_payable = self.sent_payable_dao.retrieve_txs(None); match filtered_pending_payable.is_empty() { true => { self.mark_as_ended(logger); @@ -888,8 +884,9 @@ impl StartableScanner "Found {} pending payables to process", filtered_pending_payable.len() ); + let tx_hashes = todo!(); Ok(RequestTransactionReceipts { - pending_payable_fingerprints: filtered_pending_payable, + tx_hashes, response_skeleton_opt, }) } @@ -909,6 +906,13 @@ impl Scanner for PendingPay true => { warning!(logger, "No transaction receipts found."); todo!("This requires the payment retry. GH-631 must be completed first"); + if self.cached_currently_queried_tx_for_receipts.is_empty() { + todo!("unreachable") + } + let failed_txs = self.cached_currently_queried_tx_for_receipts.drain(..) + .map(|sent_tx|{todo!()}) + .collect(); + self.handle_failed_transactions(failed_txs, logger) } false => { debug!( @@ -918,7 +922,7 @@ impl Scanner for PendingPay ); let scan_report = self.handle_receipts_for_pending_transactions(message, logger); let requires_payment_retry = - self.process_transactions_by_reported_state(scan_report, logger); + self.process_transactions_by_their_state(scan_report, logger); self.mark_as_ended(logger); @@ -945,7 +949,7 @@ impl Scanner for PendingPay impl PendingPayableScanner { pub fn new( payable_dao: Box, - pending_payable_dao: Box, + sent_payable_dao: Box, payment_thresholds: Rc, when_pending_too_long_sec: u64, financial_statistics: Rc>, @@ -953,9 +957,11 @@ impl PendingPayableScanner { Self { common: ScannerCommon::new(payment_thresholds), payable_dao, - pending_payable_dao, + sent_payable_dao, when_pending_too_long_sec, financial_statistics, + cached_currently_queried_tx_for_receipts: todo!(), + cached_previous_cycle_txs_pending_too_long_opt: todo!(), } } @@ -965,34 +971,27 @@ impl PendingPayableScanner { logger: &Logger, ) -> PendingPayableScanReport { let scan_report = PendingPayableScanReport::default(); - msg.fingerprints_with_receipts.into_iter().fold( + msg.receipt_results.into_iter().fold( scan_report, - |scan_report_so_far, (receipt_result, fingerprint)| match receipt_result { + |scan_report_so_far, receipt_result| match receipt_result { TransactionReceiptResult::RpcResponse(tx_receipt) => match tx_receipt.status { - TxStatus::Pending => handle_none_receipt( - scan_report_so_far, - fingerprint, - "none was given", - logger, - ), - TxStatus::Failed => { - handle_status_with_failure(scan_report_so_far, fingerprint, logger) + TxStatus::Failed(failed_tx) => { + handle_status_with_failure(scan_report_so_far, failed_tx, logger) } - TxStatus::Succeeded(_) => { - handle_status_with_success(scan_report_so_far, fingerprint, logger) + TxStatus::Succeeded(confirmed_tx) => { + handle_successful_tx(scan_report_so_far, confirmed_tx, logger) } }, - TransactionReceiptResult::LocalError(e) => handle_none_receipt( + TransactionReceiptResult::LocalError(e) => handle_local_error_fetching_receipts( scan_report_so_far, - fingerprint, - &format!("failed due to {}", e), + e, logger, ), }, ) } - fn process_transactions_by_reported_state( + fn process_transactions_by_their_state( &mut self, scan_report: PendingPayableScanReport, logger: &Logger, @@ -1000,46 +999,45 @@ impl PendingPayableScanner { let requires_payments_retry = scan_report.requires_payments_retry(); self.confirm_transactions(scan_report.confirmed, logger); - self.cancel_failed_transactions(scan_report.failures, logger); - self.update_remaining_fingerprints(scan_report.still_pending, logger); + self.handle_failed_transactions(scan_report.failures, logger); requires_payments_retry } - fn update_remaining_fingerprints(&self, ids: Vec, logger: &Logger) { - if !ids.is_empty() { - let rowids = PendingPayableId::rowids(&ids); - match self.pending_payable_dao.increment_scan_attempts(&rowids) { - Ok(_) => trace!( - logger, - "Updated records for rowids: {} ", - comma_joined_stringifiable(&rowids, |id| id.to_string()) - ), - Err(e) => panic!( - "Failure on incrementing scan attempts for fingerprints of {} due to {:?}", - PendingPayableId::serialize_hashes_to_string(&ids), - e - ), - } - } - } - - fn cancel_failed_transactions(&self, ids: Vec, logger: &Logger) { - if !ids.is_empty() { + // fn update_remaining_fingerprints(&self, ids: Vec, logger: &Logger) { + // if !ids.is_empty() { + // let rowids = PendingPayableId::rowids(&ids); + // match self.sent_payable_dao.increment_scan_attempts(&rowids) { + // Ok(_) => trace!( + // logger, + // "Updated records for rowids: {} ", + // comma_joined_stringifiable(&rowids, |id| id.to_string()) + // ), + // Err(e) => panic!( + // "Failure on incrementing scan attempts for fingerprints of {} due to {:?}", + // PendingPayableId::serialize_hashes_to_string(&ids), + // e + // ), + // } + // } + // } + + fn handle_failed_transactions(&self, failures: Vec, logger: &Logger) { + if !failures.is_empty() { //TODO this function is imperfect. It waits for GH-663 - let rowids = PendingPayableId::rowids(&ids); - match self.pending_payable_dao.mark_failures(&rowids) { + let rowids = PendingPayableId::rowids(&failures); + match self.sent_payable_dao.mark_failures(&rowids) { Ok(_) => warning!( logger, "Broken transactions {} marked as an error. You should take over the care \ of those to make sure your debts are going to be settled properly. At the moment, \ there is no automated process fixing that without your assistance", - PendingPayableId::serialize_hashes_to_string(&ids) + PendingPayableId::serialize_hashes_to_string(&failures) ), Err(e) => panic!( "Unsuccessful attempt for transactions {} \ to mark fatal error at payable fingerprint due to {:?}; database unreliable", - PendingPayableId::serialize_hashes_to_string(&ids), + PendingPayableId::serialize_hashes_to_string(&failures), e ), } @@ -1048,55 +1046,56 @@ impl PendingPayableScanner { fn confirm_transactions( &mut self, - fingerprints: Vec, + fingerprints: Vec, logger: &Logger, ) { - fn serialize_hashes(fingerprints: &[PendingPayableFingerprint]) -> String { - comma_joined_stringifiable(fingerprints, |fgp| format!("{:?}", fgp.hash)) - } - - if !fingerprints.is_empty() { - if let Err(e) = self.payable_dao.transactions_confirmed(&fingerprints) { - panic!( - "Unable to cast confirmed pending payables {} into adjustment in the corresponding payable \ - records due to {:?}", serialize_hashes(&fingerprints), e - ) - } else { - self.add_to_the_total_of_paid_payable(&fingerprints, serialize_hashes, logger); - let rowids = fingerprints - .iter() - .map(|fingerprint| fingerprint.rowid) - .collect::>(); - if let Err(e) = self.pending_payable_dao.delete_fingerprints(&rowids) { - panic!("Unable to delete payable fingerprints {} of verified transactions due to {:?}", - serialize_hashes(&fingerprints), e) - } else { - info!( - logger, - "Transactions {} completed their confirmation process succeeding", - serialize_hashes(&fingerprints) - ) - } - } - } + // fn serialize_hashes(fingerprints: &[SentTx]) -> String { + // comma_joined_stringifiable(fingerprints, |fgp| format!("{:?}", fgp.hash)) + // } + // + // if !fingerprints.is_empty() { + // if let Err(e) = self.payable_dao.transactions_confirmed(&fingerprints) { + // panic!( + // "Unable to cast confirmed pending payables {} into adjustment in the corresponding payable \ + // records due to {:?}", serialize_hashes(&fingerprints), e + // ) + // } else { + // self.add_to_the_total_of_paid_payable(&fingerprints, serialize_hashes, logger); + // let rowids = fingerprints + // .iter() + // .map(|fingerprint| fingerprint.rowid) + // .collect::>(); + // if let Err(e) = self.sent_payable_dao.delete_fingerprints(&rowids) { + // panic!("Unable to delete payable fingerprints {} of verified transactions due to {:?}", + // serialize_hashes(&fingerprints), e) + // } else { + // info!( + // logger, + // "Transactions {} completed their confirmation process succeeding", + // serialize_hashes(&fingerprints) + // ) + // } + // } + // } } fn add_to_the_total_of_paid_payable( &mut self, - fingerprints: &[PendingPayableFingerprint], - serialize_hashes: fn(&[PendingPayableFingerprint]) -> String, + fingerprints: &[ConfirmedTx], + serialize_hashes: fn(&[ConfirmedTx]) -> String, logger: &Logger, ) { - fingerprints.iter().for_each(|fingerprint| { - self.financial_statistics - .borrow_mut() - .total_paid_payable_wei += fingerprint.amount - }); - debug!( - logger, - "Confirmation of transactions {}; record for total paid payable was modified", - serialize_hashes(fingerprints) - ); + todo!() + // fingerprints.iter().for_each(|fingerprint| { + // self.financial_statistics + // .borrow_mut() + // .total_paid_payable_wei += fingerprint.amount + // }); + // debug!( + // logger, + // "Confirmation of transactions {}; record for total paid payable was modified", + // serialize_hashes(fingerprints) + // ); } } @@ -1391,17 +1390,14 @@ impl_real_scanner_marker!(PayableScanner, PendingPayableScanner, ReceivableScann #[cfg(test)] mod tests { use crate::accountant::db_access_objects::payable_dao::{PayableAccount, PayableDaoError}; - use crate::accountant::db_access_objects::pending_payable_dao::{ - PendingPayable, PendingPayableDaoError, TransactionHashes, - }; use crate::accountant::db_access_objects::utils::{from_unix_timestamp, to_unix_timestamp}; use crate::accountant::scanners::payable_scanner_extension::msgs::QualifiedPayablesMessage; - use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{OperationOutcome, PayableScanResult, PendingPayableMetadata}; - use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_none_status, handle_status_with_failure, PendingPayableScanReport, PendingPayableScanResult}; + use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{OperationOutcome, PayableScanResult, PendingPayableMissingInDb}; + use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_status_with_failure, PendingPayableScanReport, PendingPayableScanResult}; use crate::accountant::scanners::{Scanner, StartScanError, StartableScanner, PayableScanner, PendingPayableScanner, ReceivableScanner, ScannerCommon, Scanners, MTError}; - use crate::accountant::test_utils::{make_custom_payment_thresholds, make_payable_account, make_qualified_and_unqualified_payables, make_pending_payable_fingerprint, make_receivable_account, BannedDaoFactoryMock, BannedDaoMock, ConfigDaoFactoryMock, PayableDaoFactoryMock, PayableDaoMock, PayableScannerBuilder, PayableThresholdsGaugeMock, PendingPayableDaoFactoryMock, PendingPayableDaoMock, PendingPayableScannerBuilder, ReceivableDaoFactoryMock, ReceivableDaoMock, ReceivableScannerBuilder}; - use crate::accountant::{gwei_to_wei, PendingPayableId, ReceivedPayments, ReportTransactionReceipts, RequestTransactionReceipts, ScanError, ScanForRetryPayables, SentPayables, DEFAULT_PENDING_TOO_LONG_SEC}; - use crate::blockchain::blockchain_bridge::{BlockMarker, PendingPayableFingerprint, RetrieveTransactions}; + use crate::accountant::test_utils::{make_custom_payment_thresholds, make_payable_account, make_qualified_and_unqualified_payables, make_pending_payable_fingerprint, make_receivable_account, BannedDaoFactoryMock, BannedDaoMock, ConfigDaoFactoryMock, PayableDaoFactoryMock, PayableDaoMock, PayableScannerBuilder, PayableThresholdsGaugeMock, PendingPayableDaoFactoryMock, SentPayableDaoMock, PendingPayableScannerBuilder, ReceivableDaoFactoryMock, ReceivableDaoMock, ReceivableScannerBuilder}; + use crate::accountant::{gwei_to_wei, PendingPayableId, ReceivedPayments, ReportTransactionReceipts, RequestTransactionReceipts, ScanError, ScanForRetryPayables, SentPayables, DEFAULT_PENDING_TOO_LONG_SEC, PendingPayable}; + use crate::blockchain::blockchain_bridge::{BlockMarker, RetrieveTransactions}; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; use crate::blockchain::blockchain_interface::data_structures::{ BlockchainTransaction, ProcessedPayableFallible, RpcPayableFailure, @@ -1435,6 +1431,7 @@ mod tests { use web3::Error; use masq_lib::messages::ScanType; use masq_lib::ui_gateway::NodeToUiMessage; + use crate::accountant::db_access_objects::sent_payable_dao::{SentPayableDaoError, SentTx}; use crate::accountant::scanners::test_utils::{assert_timestamps_from_str, parse_system_time_from_str, MarkScanner, NullScanner, ReplacementType, ScannerReplacement}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, TransactionReceiptResult, TxReceipt, TxStatus}; @@ -1520,9 +1517,9 @@ mod tests { let payable_dao_factory = PayableDaoFactoryMock::new() .make_result(PayableDaoMock::new()) .make_result(PayableDaoMock::new()); - let pending_payable_dao_factory = PendingPayableDaoFactoryMock::new() - .make_result(PendingPayableDaoMock::new()) - .make_result(PendingPayableDaoMock::new()); + let sent_payable_dao_factory = PendingPayableDaoFactoryMock::new() + .make_result(SentPayableDaoMock::new()) + .make_result(SentPayableDaoMock::new()); let receivable_dao = ReceivableDaoMock::new(); let receivable_dao_factory = ReceivableDaoFactoryMock::new().make_result(receivable_dao); let banned_dao_factory = BannedDaoFactoryMock::new().make_result(BannedDaoMock::new()); @@ -1543,7 +1540,7 @@ mod tests { let mut scanners = Scanners::new( DaoFactories { payable_dao_factory: Box::new(payable_dao_factory), - pending_payable_dao_factory: Box::new(pending_payable_dao_factory), + sent_payable_dao_factory: Box::new(sent_payable_dao_factory), receivable_dao_factory: Box::new(receivable_dao_factory), banned_dao_factory: Box::new(banned_dao_factory), config_dao_factory: Box::new(config_dao_factory), @@ -1897,7 +1894,7 @@ mod tests { let correct_payable_wallet_3 = make_wallet("booga"); let correct_pending_payable_3 = PendingPayable::new(correct_payable_wallet_3.clone(), correct_payable_hash_3); - let pending_payable_dao = PendingPayableDaoMock::default() + let sent_payable_dao = SentPayableDaoMock::default() .fingerprints_rowids_params(&fingerprints_rowids_params_arc) .fingerprints_rowids_result(TransactionHashes { rowid_results: vec![ @@ -1918,7 +1915,7 @@ mod tests { .mark_pending_payables_rowids_result(Ok(())); let mut payable_scanner = PayableScannerBuilder::new() .payable_dao(payable_dao) - .pending_payable_dao(pending_payable_dao) + .sent_payable_dao(sent_payable_dao) .build(); let logger = Logger::new(test_name); let sent_payable = SentPayables { @@ -2011,13 +2008,13 @@ mod tests { let pending_payables_ref = pending_payables_owned .iter() .collect::>(); - let pending_payable_dao = - PendingPayableDaoMock::new().fingerprints_rowids_result(TransactionHashes { + let sent_payable_dao = + SentPayableDaoMock::new().fingerprints_rowids_result(TransactionHashes { rowid_results: vec![(4, hash_4), (1, hash_1), (3, hash_3), (2, hash_2)], no_rowid_results: vec![], }); let subject = PayableScannerBuilder::new() - .pending_payable_dao(pending_payable_dao) + .sent_payable_dao(sent_payable_dao) .build(); let (existent, nonexistent) = @@ -2026,10 +2023,10 @@ mod tests { assert_eq!( existent, vec![ - PendingPayableMetadata::new(&wallet_4, hash_4, Some(4)), - PendingPayableMetadata::new(&wallet_1, hash_1, Some(1)), - PendingPayableMetadata::new(&wallet_3, hash_3, Some(3)), - PendingPayableMetadata::new(&wallet_2, hash_2, Some(2)), + PendingPayableMissingInDb::new(&wallet_4, hash_4, Some(4)), + PendingPayableMissingInDb::new(&wallet_1, hash_1, Some(1)), + PendingPayableMissingInDb::new(&wallet_3, hash_3, Some(3)), + PendingPayableMissingInDb::new(&wallet_2, hash_2, Some(2)), ] ); assert!(nonexistent.is_empty()) @@ -2083,8 +2080,8 @@ mod tests { .pending_payables .iter() .collect::>(); - let pending_payable_dao = - PendingPayableDaoMock::new().fingerprints_rowids_result(TransactionHashes { + let sent_payable_dao = + SentPayableDaoMock::new().fingerprints_rowids_result(TransactionHashes { rowid_results: vec![ (4, vals.common_hash_1), (1, vals.intruder_for_hash_2), @@ -2093,7 +2090,7 @@ mod tests { no_rowid_results: vec![], }); let subject = PayableScannerBuilder::new() - .pending_payable_dao(pending_payable_dao) + .sent_payable_dao(sent_payable_dao) .build(); subject.separate_existent_and_nonexistent_fingerprints(&pending_payables_ref); @@ -2187,15 +2184,15 @@ mod tests { let payment_1 = PendingPayable::new(make_wallet("booga"), hash_1); let hash_2 = make_tx_hash(0x7b); let payment_2 = PendingPayable::new(make_wallet("agoob"), hash_2); - let pending_payable_dao = - PendingPayableDaoMock::default().fingerprints_rowids_result(TransactionHashes { + let sent_payable_dao = + SentPayableDaoMock::default().fingerprints_rowids_result(TransactionHashes { rowid_results: vec![], no_rowid_results: vec![hash_1, hash_2], }); let payable_dao = PayableDaoMock::new(); let mut subject = PayableScannerBuilder::new() .payable_dao(payable_dao) - .pending_payable_dao(pending_payable_dao) + .sent_payable_dao(sent_payable_dao) .build(); let sent_payable = SentPayables { payment_procedure_result: Ok(vec![ @@ -2210,7 +2207,7 @@ mod tests { fn assert_panic_from_failing_to_mark_pending_payable_rowid( test_name: &str, - pending_payable_dao: PendingPayableDaoMock, + sent_payable_dao: SentPayableDaoMock, hash_1: H256, hash_2: H256, ) { @@ -2221,7 +2218,7 @@ mod tests { )); let mut subject = PayableScannerBuilder::new() .payable_dao(payable_dao) - .pending_payable_dao(pending_payable_dao) + .sent_payable_dao(sent_payable_dao) .build(); let sent_payables = SentPayables { payment_procedure_result: Ok(vec![ @@ -2251,15 +2248,15 @@ mod tests { let test_name = "payable_scanner_mark_pending_payable_only_panics_all_fingerprints_found"; let hash_1 = make_tx_hash(248); let hash_2 = make_tx_hash(139); - let pending_payable_dao = - PendingPayableDaoMock::default().fingerprints_rowids_result(TransactionHashes { + let sent_payable_dao = + SentPayableDaoMock::default().fingerprints_rowids_result(TransactionHashes { rowid_results: vec![(7879, hash_1), (7881, hash_2)], no_rowid_results: vec![], }); assert_panic_from_failing_to_mark_pending_payable_rowid( test_name, - pending_payable_dao, + sent_payable_dao, hash_1, hash_2, ); @@ -2275,15 +2272,15 @@ mod tests { "payable_scanner_mark_pending_payable_panics_nonexistent_fingerprints_also_found"; let hash_1 = make_tx_hash(0xff); let hash_2 = make_tx_hash(0xf8); - let pending_payable_dao = - PendingPayableDaoMock::default().fingerprints_rowids_result(TransactionHashes { + let sent_payable_dao = + SentPayableDaoMock::default().fingerprints_rowids_result(TransactionHashes { rowid_results: vec![(7881, hash_1)], no_rowid_results: vec![hash_2], }); assert_panic_from_failing_to_mark_pending_payable_rowid( test_name, - pending_payable_dao, + sent_payable_dao, hash_1, hash_2, ); @@ -2305,7 +2302,7 @@ mod tests { let first_fingerprint_rowid = 3; let second_fingerprint_rowid = 5; let system = System::new(test_name); - let pending_payable_dao = PendingPayableDaoMock::default() + let sent_payable_dao = SentPayableDaoMock::default() .fingerprints_rowids_params(&fingerprints_rowids_params_arc) .fingerprints_rowids_result(TransactionHashes { rowid_results: vec![ @@ -2317,7 +2314,7 @@ mod tests { .delete_fingerprints_params(&delete_fingerprints_params_arc) .delete_fingerprints_result(Ok(())); let payable_scanner = PayableScannerBuilder::new() - .pending_payable_dao(pending_payable_dao) + .sent_payable_dao(sent_payable_dao) .build(); let logger = Logger::new(test_name); let sent_payable = SentPayables { @@ -2415,16 +2412,16 @@ mod tests { }), response_skeleton_opt: None, }; - let pending_payable_dao = PendingPayableDaoMock::default() - .fingerprints_rowids_result(TransactionHashes { + let sent_payable_dao = SentPayableDaoMock::default() + .retrieve_txs_result(TransactionHashes { rowid_results: vec![(rowid_1, hash_1), (rowid_2, hash_2)], no_rowid_results: vec![], }) - .delete_fingerprints_result(Err(PendingPayableDaoError::RecordDeletion( + .delete_fingerprints_result(Err(SentPayableDaoError::SqlExecutionFailed( "Gosh, I overslept without an alarm set".to_string(), ))); let mut subject = PayableScannerBuilder::new() - .pending_payable_dao(pending_payable_dao) + .sent_payable_dao(sent_payable_dao) .build(); let caught_panic_in_err = catch_unwind(AssertUnwindSafe(|| { @@ -2453,14 +2450,14 @@ mod tests { let hash_1 = make_tx_hash(0x1b669); let hash_2 = make_tx_hash(0x3039); let hash_3 = make_tx_hash(0x223d); - let pending_payable_dao = PendingPayableDaoMock::default() + let sent_payable_dao = SentPayableDaoMock::default() .fingerprints_rowids_result(TransactionHashes { rowid_results: vec![(333, hash_1)], no_rowid_results: vec![hash_2, hash_3], }) .delete_fingerprints_result(Ok(())); let mut subject = PayableScannerBuilder::new() - .pending_payable_dao(pending_payable_dao) + .sent_payable_dao(sent_payable_dao) .build(); let sent_payable = SentPayables { payment_procedure_result: Err(PayableTransactionError::Sending { @@ -2501,16 +2498,16 @@ mod tests { let test_name = "payable_scanner_for_failed_rpcs_one_fingerprint_missing_and_deletion_of_the_other_one_fails"; let existent_record_hash = make_tx_hash(0xb26e); let nonexistent_record_hash = make_tx_hash(0x4d2); - let pending_payable_dao = PendingPayableDaoMock::default() + let sent_payable_dao = SentPayableDaoMock::default() .fingerprints_rowids_result(TransactionHashes { rowid_results: vec![(45, existent_record_hash)], no_rowid_results: vec![nonexistent_record_hash], }) - .delete_fingerprints_result(Err(PendingPayableDaoError::RecordDeletion( + .delete_fingerprints_result(Err(SentPayableDaoError::SqlExecutionFailed( "Another failure. Really???".to_string(), ))); let mut subject = PayableScannerBuilder::new() - .pending_payable_dao(pending_payable_dao) + .sent_payable_dao(sent_payable_dao) .build(); let failed_payment_1 = RpcPayableFailure { rpc_error: Error::Unreachable, @@ -2776,63 +2773,64 @@ mod tests { #[test] fn pending_payable_scanner_can_initiate_a_scan() { - init_test_logging(); - let test_name = "pending_payable_scanner_can_initiate_a_scan"; - let consuming_wallet = make_paying_wallet(b"consuming wallet"); - let now = SystemTime::now(); - let payable_fingerprint_1 = PendingPayableFingerprint { - rowid: 555, - timestamp: from_unix_timestamp(210_000_000), - hash: make_tx_hash(45678), - attempt: 1, - amount: 4444, - process_error: None, - }; - let payable_fingerprint_2 = PendingPayableFingerprint { - rowid: 550, - timestamp: from_unix_timestamp(210_000_100), - hash: make_tx_hash(112233), - attempt: 1, - amount: 7999, - process_error: None, - }; - let fingerprints = vec![payable_fingerprint_1, payable_fingerprint_2]; - let pending_payable_dao = PendingPayableDaoMock::new() - .return_all_errorless_fingerprints_result(fingerprints.clone()); - let mut subject = make_dull_subject(); - let pending_payable_scanner = PendingPayableScannerBuilder::new() - .pending_payable_dao(pending_payable_dao) - .build(); - // Important - subject.aware_of_unresolved_pending_payable = true; - subject.pending_payable = Box::new(pending_payable_scanner); - let payable_scanner = PayableScannerBuilder::new().build(); - subject.payable = Box::new(payable_scanner); - - let result = subject.start_pending_payable_scan_guarded( - &consuming_wallet, - now, - None, - &Logger::new(test_name), - true, - ); - - let no_of_pending_payables = fingerprints.len(); - let is_scan_running = subject.pending_payable.scan_started_at().is_some(); - assert_eq!(is_scan_running, true); - assert_eq!( - result, - Ok(RequestTransactionReceipts { - pending_payable_fingerprints: fingerprints, - response_skeleton_opt: None - }) - ); - TestLogHandler::new().assert_logs_match_in_order(vec![ - &format!("INFO: {test_name}: Scanning for pending payable"), - &format!( - "DEBUG: {test_name}: Found {no_of_pending_payables} pending payables to process" - ), - ]) + todo!("fix me"); + // init_test_logging(); + // let test_name = "pending_payable_scanner_can_initiate_a_scan"; + // let consuming_wallet = make_paying_wallet(b"consuming wallet"); + // let now = SystemTime::now(); + // let payable_fingerprint_1 = SentTx { + // rowid: 555, + // timestamp: from_unix_timestamp(210_000_000), + // hash: make_tx_hash(45678), + // attempt: 1, + // amount: 4444, + // process_error: None, + // }; + // let payable_fingerprint_2 = SentTx { + // rowid: 550, + // timestamp: from_unix_timestamp(210_000_100), + // hash: make_tx_hash(112233), + // attempt: 1, + // amount: 7999, + // process_error: None, + // }; + // let payables = HashSet::from_iter(vec![payable_fingerprint_1, payable_fingerprint_2]); + // let sent_payable_dao = SentPayableDaoMock::new() + // .return_all_errorless_fingerprints_result(payables.clone()); + // let mut subject = make_dull_subject(); + // let pending_payable_scanner = PendingPayableScannerBuilder::new() + // .sent_payable_dao(sent_payable_dao) + // .build(); + // // Important + // subject.aware_of_unresolved_pending_payable = true; + // subject.pending_payable = Box::new(pending_payable_scanner); + // let payable_scanner = PayableScannerBuilder::new().build(); + // subject.payable = Box::new(payable_scanner); + // + // let result = subject.start_pending_payable_scan_guarded( + // &consuming_wallet, + // now, + // None, + // &Logger::new(test_name), + // true, + // ); + // + // let no_of_pending_payables = payables.len(); + // let is_scan_running = subject.pending_payable.scan_started_at().is_some(); + // assert_eq!(is_scan_running, true); + // assert_eq!( + // result, + // Ok(RequestTransactionReceipts { + // pending_payable_fingerprints: payables, + // response_skeleton_opt: None + // }) + // ); + // TestLogHandler::new().assert_logs_match_in_order(vec![ + // &format!("INFO: {test_name}: Scanning for pending payable"), + // &format!( + // "DEBUG: {test_name}: Found {no_of_pending_payables} pending payables to process" + // ), + // ]) } #[test] @@ -2840,10 +2838,10 @@ mod tests { let now = SystemTime::now(); let consuming_wallet = make_paying_wallet(b"consuming"); let mut subject = make_dull_subject(); - let pending_payable_dao = PendingPayableDaoMock::new() + let sent_payable_dao = SentPayableDaoMock::new() .return_all_errorless_fingerprints_result(vec![make_pending_payable_fingerprint()]); let pending_payable_scanner = PendingPayableScannerBuilder::new() - .pending_payable_dao(pending_payable_dao) + .sent_payable_dao(sent_payable_dao) .build(); // Important subject.aware_of_unresolved_pending_payable = true; @@ -2990,10 +2988,10 @@ mod tests { fn pending_payable_scanner_throws_an_error_when_no_fingerprint_is_found() { let now = SystemTime::now(); let consuming_wallet = make_paying_wallet(b"consuming_wallet"); - let pending_payable_dao = - PendingPayableDaoMock::new().return_all_errorless_fingerprints_result(vec![]); + let sent_payable_dao = + SentPayableDaoMock::new().return_all_errorless_fingerprints_result(vec![]); let mut pending_payable_scanner = PendingPayableScannerBuilder::new() - .pending_payable_dao(pending_payable_dao) + .sent_payable_dao(sent_payable_dao) .build(); let result = @@ -3015,28 +3013,28 @@ mod tests { assert_eq!(subject.initial_pending_payable_scan, true); } - fn assert_interpreting_none_status_for_pending_payable( - test_name: &str, - when_pending_too_long_sec: u64, - pending_payable_age_sec: u64, - rowid: u64, - hash: H256, - ) -> PendingPayableScanReport { - init_test_logging(); - let when_sent = SystemTime::now().sub(Duration::from_secs(pending_payable_age_sec)); - let fingerprint = PendingPayableFingerprint { - rowid, - timestamp: when_sent, - hash, - attempt: 1, - amount: 123, - process_error: None, - }; - let logger = Logger::new(test_name); - let scan_report = PendingPayableScanReport::default(); - - handle_none_status(scan_report, fingerprint, when_pending_too_long_sec, &logger) - } + // fn assert_interpreting_none_status_for_pending_payable( + // test_name: &str, + // when_pending_too_long_sec: u64, + // pending_payable_age_sec: u64, + // rowid: u64, + // hash: H256, + // ) -> PendingPayableScanReport { + // init_test_logging(); + // let when_sent = SystemTime::now().sub(Duration::from_secs(pending_payable_age_sec)); + // let fingerprint = SentTx { + // rowid, + // timestamp: when_sent, + // hash, + // attempt: 1, + // amount: 123, + // process_error: None, + // }; + // let logger = Logger::new(test_name); + // let scan_report = PendingPayableScanReport::default(); + // + // handle_none_status(scan_report, fingerprint, when_pending_too_long_sec, &logger) + // } fn assert_log_msg_and_elapsed_time_in_log_makes_sense( expected_msg: &str, @@ -3072,95 +3070,98 @@ mod tests { #[test] fn interpret_transaction_receipt_when_transaction_status_is_none_and_outside_waiting_interval() { - let test_name = "interpret_transaction_receipt_when_transaction_status_is_none_and_outside_waiting_interval"; - let hash = make_tx_hash(0x237); - let rowid = 466; - - let result = assert_interpreting_none_status_for_pending_payable( - test_name, - DEFAULT_PENDING_TOO_LONG_SEC, - DEFAULT_PENDING_TOO_LONG_SEC + 1, - rowid, - hash, - ); - - let elapsed_after = elapsed_since_secs_back(DEFAULT_PENDING_TOO_LONG_SEC + 1); - assert_eq!( - result, - PendingPayableScanReport { - still_pending: vec![], - failures: vec![PendingPayableId::new(rowid, hash)], - confirmed: vec![] - } - ); - let capture_regex = "(\\d+){2}sec"; - assert_log_msg_and_elapsed_time_in_log_makes_sense(&format!( - "ERROR: {}: Pending transaction 0x00000000000000000000000000000000000000\ - 00000000000000000000000237 has exceeded the maximum pending time \\({}sec\\) with the age \ - \\d+sec and the confirmation process is going to be aborted now at the final attempt 1; manual \ - resolution is required from the user to complete the transaction" - , test_name, DEFAULT_PENDING_TOO_LONG_SEC, ), elapsed_after, capture_regex) + todo!("think of if we should preserve it") + // let test_name = "interpret_transaction_receipt_when_transaction_status_is_none_and_outside_waiting_interval"; + // let hash = make_tx_hash(0x237); + // let rowid = 466; + // + // let result = assert_interpreting_none_status_for_pending_payable( + // test_name, + // DEFAULT_PENDING_TOO_LONG_SEC, + // DEFAULT_PENDING_TOO_LONG_SEC + 1, + // rowid, + // hash, + // ); + // + // let elapsed_after = elapsed_since_secs_back(DEFAULT_PENDING_TOO_LONG_SEC + 1); + // assert_eq!( + // result, + // PendingPayableScanReport { + // still_pending: vec![], + // failures: vec![PendingPayableId::new(rowid, hash)], + // confirmed: vec![] + // } + // ); + // let capture_regex = "(\\d+){2}sec"; + // assert_log_msg_and_elapsed_time_in_log_makes_sense(&format!( + // "ERROR: {}: Pending transaction 0x00000000000000000000000000000000000000\ + // 00000000000000000000000237 has exceeded the maximum pending time \\({}sec\\) with the age \ + // \\d+sec and the confirmation process is going to be aborted now at the final attempt 1; manual \ + // resolution is required from the user to complete the transaction" + // , test_name, DEFAULT_PENDING_TOO_LONG_SEC, ), elapsed_after, capture_regex) } #[test] fn interpret_transaction_receipt_when_transaction_status_is_none_and_within_waiting_interval() { - let test_name = "interpret_transaction_receipt_when_transaction_status_is_none_and_within_waiting_interval"; - let hash = make_tx_hash(0x7b); - let rowid = 333; - let pending_payable_age = DEFAULT_PENDING_TOO_LONG_SEC - 1; - - let result = assert_interpreting_none_status_for_pending_payable( - test_name, - DEFAULT_PENDING_TOO_LONG_SEC, - pending_payable_age, - rowid, - hash, - ); - - let elapsed_after_ms = elapsed_since_secs_back(pending_payable_age) * 1000; - assert_eq!( - result, - PendingPayableScanReport { - still_pending: vec![PendingPayableId::new(rowid, hash)], - failures: vec![], - confirmed: vec![] - } - ); - let capture_regex = r#"\s(\d+)ms"#; - assert_log_msg_and_elapsed_time_in_log_makes_sense(&format!( - "INFO: {test_name}: Pending transaction 0x0000000000000000000000000000000000000000000000000\ - 00000000000007b couldn't be confirmed at attempt 1 at \\d+ms after its sending"), elapsed_after_ms, capture_regex); + todo!("think of we can transform it into some other test") + // let test_name = "interpret_transaction_receipt_when_transaction_status_is_none_and_within_waiting_interval"; + // let hash = make_tx_hash(0x7b); + // let rowid = 333; + // let pending_payable_age = DEFAULT_PENDING_TOO_LONG_SEC - 1; + // + // let result = assert_interpreting_none_status_for_pending_payable( + // test_name, + // DEFAULT_PENDING_TOO_LONG_SEC, + // pending_payable_age, + // rowid, + // hash, + // ); + // + // let elapsed_after_ms = elapsed_since_secs_back(pending_payable_age) * 1000; + // assert_eq!( + // result, + // PendingPayableScanReport { + // still_pending: vec![PendingPayableId::new(rowid, hash)], + // failures: vec![], + // confirmed: vec![] + // } + // ); + // let capture_regex = r#"\s(\d+)ms"#; + // assert_log_msg_and_elapsed_time_in_log_makes_sense(&format!( + // "INFO: {test_name}: Pending transaction 0x0000000000000000000000000000000000000000000000000\ + // 00000000000007b couldn't be confirmed at attempt 1 at \\d+ms after its sending"), elapsed_after_ms, capture_regex); } #[test] fn interpret_transaction_receipt_when_transaction_status_is_none_and_time_equals_the_limit() { - let test_name = "interpret_transaction_receipt_when_transaction_status_is_none_and_time_equals_the_limit"; - let hash = make_tx_hash(0x237); - let rowid = 466; - let pending_payable_age = DEFAULT_PENDING_TOO_LONG_SEC; - - let result = assert_interpreting_none_status_for_pending_payable( - test_name, - DEFAULT_PENDING_TOO_LONG_SEC, - pending_payable_age, - rowid, - hash, - ); - - let elapsed_after_ms = elapsed_since_secs_back(pending_payable_age) * 1000; - assert_eq!( - result, - PendingPayableScanReport { - still_pending: vec![PendingPayableId::new(rowid, hash)], - failures: vec![], - confirmed: vec![] - } - ); - let capture_regex = r#"\s(\d+)ms"#; - assert_log_msg_and_elapsed_time_in_log_makes_sense(&format!( - "INFO: {test_name}: Pending transaction 0x0000000000000000000000000000000000000000000000000\ - 000000000000237 couldn't be confirmed at attempt 1 at \\d+ms after its sending", - ), elapsed_after_ms, capture_regex); + todo!("think of we can transform it into some other test") + // let test_name = "interpret_transaction_receipt_when_transaction_status_is_none_and_time_equals_the_limit"; + // let hash = make_tx_hash(0x237); + // let rowid = 466; + // let pending_payable_age = DEFAULT_PENDING_TOO_LONG_SEC; + // + // let result = assert_interpreting_none_status_for_pending_payable( + // test_name, + // DEFAULT_PENDING_TOO_LONG_SEC, + // pending_payable_age, + // rowid, + // hash, + // ); + // + // let elapsed_after_ms = elapsed_since_secs_back(pending_payable_age) * 1000; + // assert_eq!( + // result, + // PendingPayableScanReport { + // still_pending: vec![PendingPayableId::new(rowid, hash)], + // failures: vec![], + // confirmed: vec![] + // } + // ); + // let capture_regex = r#"\s(\d+)ms"#; + // assert_log_msg_and_elapsed_time_in_log_makes_sense(&format!( + // "INFO: {test_name}: Pending transaction 0x0000000000000000000000000000000000000000000000000\ + // 000000000000237 couldn't be confirmed at attempt 1 at \\d+ms after its sending", + // ), elapsed_after_ms, capture_regex); } #[test] @@ -3170,13 +3171,14 @@ mod tests { let mut tx_receipt = TransactionReceipt::default(); tx_receipt.status = Some(U64::from(0)); //failure let hash = make_tx_hash(0xd7); - let fingerprint = PendingPayableFingerprint { - rowid: 777777, + let fingerprint = SentTx { timestamp: SystemTime::now().sub(Duration::from_millis(150000)), + gas_price_wei: 0, + nonce: 0, hash, - attempt: 5, amount: 2222, - process_error: None, + receiver_address: Default::default(), + block_opt: None, }; let logger = Logger::new(test_name); let scan_report = PendingPayableScanReport::default(); @@ -3205,21 +3207,21 @@ mod tests { let subject = PendingPayableScannerBuilder::new().build(); let rowid = 455; let hash = make_tx_hash(0x913); - let fingerprint = PendingPayableFingerprint { - rowid, + let fingerprint = SentTx { timestamp: SystemTime::now().sub(Duration::from_millis(10000)), + gas_price_wei: 0, + nonce: 0, hash, - attempt: 3, amount: 111, - process_error: None, + receiver_address: Default::default(), + block_opt: None, }; let msg = ReportTransactionReceipts { - fingerprints_with_receipts: vec![( + receipt_results: vec![ TransactionReceiptResult::RpcResponse(TxReceipt { transaction_hash: hash, - status: TxStatus::Pending, - }), - fingerprint.clone(), + status: TxStatus::Failed(), + } )], response_skeleton_opt: None, }; @@ -3229,8 +3231,7 @@ mod tests { assert_eq!( result, PendingPayableScanReport { - still_pending: vec![PendingPayableId::new(rowid, hash)], - failures: vec![], + failures: todo!(), confirmed: vec![] } ); @@ -3241,57 +3242,6 @@ mod tests { )); } - #[test] - fn increment_scan_attempts_happy_path() { - let update_remaining_fingerprints_params_arc = Arc::new(Mutex::new(vec![])); - let hash_1 = make_tx_hash(444888); - let rowid_1 = 3456; - let hash_2 = make_tx_hash(444888); - let rowid_2 = 3456; - let pending_payable_dao = PendingPayableDaoMock::default() - .increment_scan_attempts_params(&update_remaining_fingerprints_params_arc) - .increment_scan_attempts_result(Ok(())); - let subject = PendingPayableScannerBuilder::new() - .pending_payable_dao(pending_payable_dao) - .build(); - let transaction_id_1 = PendingPayableId::new(rowid_1, hash_1); - let transaction_id_2 = PendingPayableId::new(rowid_2, hash_2); - - let _ = subject.update_remaining_fingerprints( - vec![transaction_id_1, transaction_id_2], - &Logger::new("test"), - ); - - let update_remaining_fingerprints_params = - update_remaining_fingerprints_params_arc.lock().unwrap(); - assert_eq!( - *update_remaining_fingerprints_params, - vec![vec![rowid_1, rowid_2]] - ) - } - - #[test] - #[should_panic( - expected = "Failure on incrementing scan attempts for fingerprints of \ - 0x000000000000000000000000000000000000000000000000000000000006c9d8 \ - due to UpdateFailed(\"yeah, bad\")" - )] - fn increment_scan_attempts_sad_path() { - let hash = make_tx_hash(0x6c9d8); - let rowid = 3456; - let pending_payable_dao = - PendingPayableDaoMock::default().increment_scan_attempts_result(Err( - PendingPayableDaoError::UpdateFailed("yeah, bad".to_string()), - )); - let subject = PendingPayableScannerBuilder::new() - .pending_payable_dao(pending_payable_dao) - .build(); - let logger = Logger::new("test"); - let transaction_id = PendingPayableId::new(rowid, hash); - - let _ = subject.update_remaining_fingerprints(vec![transaction_id], &logger); - } - #[test] fn update_remaining_fingerprints_does_nothing_if_no_still_pending_transactions_remain() { let subject = PendingPayableScannerBuilder::new().build(); @@ -3302,20 +3252,20 @@ mod tests { } #[test] - fn cancel_failed_transactions_works() { + fn handle_failed_transactions_works() { init_test_logging(); - let test_name = "cancel_failed_transactions_works"; + let test_name = "handle_failed_transactions_works"; let mark_failures_params_arc = Arc::new(Mutex::new(vec![])); - let pending_payable_dao = PendingPayableDaoMock::default() + let sent_payable_dao = SentPayableDaoMock::default() .mark_failures_params(&mark_failures_params_arc) .mark_failures_result(Ok(())); let subject = PendingPayableScannerBuilder::new() - .pending_payable_dao(pending_payable_dao) + .sent_payable_dao(sent_payable_dao) .build(); let id_1 = PendingPayableId::new(2, make_tx_hash(0x7b)); let id_2 = PendingPayableId::new(3, make_tx_hash(0x1c8)); - subject.cancel_failed_transactions(vec![id_1, id_2], &Logger::new(test_name)); + subject.handle_failed_transactions(vec![id_1, id_2], &Logger::new(test_name)); let mark_failures_params = mark_failures_params_arc.lock().unwrap(); assert_eq!(*mark_failures_params, vec![vec![2, 3]]); @@ -3334,25 +3284,25 @@ mod tests { 00000001bc to mark fatal error at payable fingerprint due to UpdateFailed(\"no no no\"); \ database unreliable" )] - fn cancel_failed_transactions_panics_when_it_fails_to_mark_failure() { - let pending_payable_dao = PendingPayableDaoMock::default().mark_failures_result(Err( - PendingPayableDaoError::UpdateFailed("no no no".to_string()), + fn handle_failed_transactions_panics_when_it_fails_to_mark_failure() { + let sent_payable_dao = SentPayableDaoMock::default().mark_failures_result(Err( + SentPayableDaoError::NoChange, )); let subject = PendingPayableScannerBuilder::new() - .pending_payable_dao(pending_payable_dao) + .sent_payable_dao(sent_payable_dao) .build(); let transaction_id_1 = PendingPayableId::new(2, make_tx_hash(333)); let transaction_id_2 = PendingPayableId::new(3, make_tx_hash(444)); let transaction_ids = vec![transaction_id_1, transaction_id_2]; - subject.cancel_failed_transactions(transaction_ids, &Logger::new("test")); + subject.handle_failed_transactions(transaction_ids, &Logger::new("test")); } #[test] - fn cancel_failed_transactions_does_nothing_if_no_tx_failures_detected() { + fn handle_failed_transactions_does_nothing_if_no_tx_failures_detected() { let subject = PendingPayableScannerBuilder::new().build(); - subject.cancel_failed_transactions(vec![], &Logger::new("test")) + subject.handle_failed_transactions(vec![], &Logger::new("test")) //mocked pending payable DAO didn't panic which means we skipped the actual process } @@ -3366,14 +3316,14 @@ mod tests { )] fn confirm_transactions_panics_while_deleting_pending_payable_fingerprint() { let payable_dao = PayableDaoMock::new().transactions_confirmed_result(Ok(())); - let pending_payable_dao = PendingPayableDaoMock::default().delete_fingerprints_result(Err( - PendingPayableDaoError::RecordDeletion( + let sent_payable_dao = SentPayableDaoMock::default().delete_fingerprints_result(Err( + SentPayableDaoError::SqlExecutionFailed( "the database is fooling around with us".to_string(), ), )); let mut subject = PendingPayableScannerBuilder::new() .payable_dao(payable_dao) - .pending_payable_dao(pending_payable_dao) + .sent_payable_dao(sent_payable_dao) .build(); let mut fingerprint_1 = make_pending_payable_fingerprint(); fingerprint_1.rowid = 1; @@ -3402,30 +3352,32 @@ mod tests { let payable_dao = PayableDaoMock::default() .transactions_confirmed_params(&transactions_confirmed_params_arc) .transactions_confirmed_result(Ok(())); - let pending_payable_dao = PendingPayableDaoMock::default() + let sent_payable_dao = SentPayableDaoMock::default() .delete_fingerprints_params(&delete_fingerprints_params_arc) .delete_fingerprints_result(Ok(())); let mut subject = PendingPayableScannerBuilder::new() .payable_dao(payable_dao) - .pending_payable_dao(pending_payable_dao) + .sent_payable_dao(sent_payable_dao) .build(); let rowid_1 = 2; let rowid_2 = 5; - let pending_payable_fingerprint_1 = PendingPayableFingerprint { - rowid: rowid_1, + let pending_payable_fingerprint_1 = SentTx { timestamp: from_unix_timestamp(199_000_000), + gas_price_wei: 0, + nonce: 0, hash: make_tx_hash(0x123), - attempt: 1, amount: 4567, - process_error: None, + receiver_address: Default::default(), + block_opt: None, }; - let pending_payable_fingerprint_2 = PendingPayableFingerprint { - rowid: rowid_2, + let pending_payable_fingerprint_2 = SentTx { timestamp: from_unix_timestamp(200_000_000), + gas_price_wei: 0, + nonce: 0, hash: make_tx_hash(0x567), - attempt: 1, amount: 5555, - process_error: None, + receiver_address: Default::default(), + block_opt: None, }; subject.confirm_transactions( @@ -3488,7 +3440,7 @@ mod tests { #[test] fn total_paid_payable_rises_with_each_bill_paid() { let test_name = "total_paid_payable_rises_with_each_bill_paid"; - let fingerprint_1 = PendingPayableFingerprint { + let fingerprint_1 = SentTx { rowid: 5, timestamp: from_unix_timestamp(189_999_888), hash: make_tx_hash(56789), @@ -3496,7 +3448,7 @@ mod tests { amount: 5478, process_error: None, }; - let fingerprint_2 = PendingPayableFingerprint { + let fingerprint_2 = SentTx { rowid: 6, timestamp: from_unix_timestamp(200_000_011), hash: make_tx_hash(33333), @@ -3505,11 +3457,11 @@ mod tests { process_error: None, }; let payable_dao = PayableDaoMock::default().transactions_confirmed_result(Ok(())); - let pending_payable_dao = - PendingPayableDaoMock::default().delete_fingerprints_result(Ok(())); + let sent_payable_dao = + SentPayableDaoMock::default().delete_fingerprints_result(Ok(())); let mut subject = PendingPayableScannerBuilder::new() .payable_dao(payable_dao) - .pending_payable_dao(pending_payable_dao) + .sent_payable_dao(sent_payable_dao) .build(); let mut financial_statistics = subject.financial_statistics.borrow().clone(); financial_statistics.total_paid_payable_wei += 1111; @@ -3532,10 +3484,10 @@ mod tests { let payable_dao = PayableDaoMock::new() .transactions_confirmed_params(&transactions_confirmed_params_arc) .transactions_confirmed_result(Ok(())); - let pending_payable_dao = PendingPayableDaoMock::new().delete_fingerprints_result(Ok(())); + let sent_payable_dao = SentPayableDaoMock::new().delete_fingerprints_result(Ok(())); let mut pending_payable_scanner = PendingPayableScannerBuilder::new() .payable_dao(payable_dao) - .pending_payable_dao(pending_payable_dao) + .sent_payable_dao(sent_payable_dao) .build(); let transaction_hash_1 = make_tx_hash(4545); let transaction_receipt_1 = TxReceipt { @@ -3545,7 +3497,7 @@ mod tests { block_number: U64::from(1234), }), }; - let fingerprint_1 = PendingPayableFingerprint { + let fingerprint_1 = SentTx { rowid: 5, timestamp: from_unix_timestamp(200_000_000), hash: transaction_hash_1, @@ -3561,7 +3513,7 @@ mod tests { block_number: U64::from(2345), }), }; - let fingerprint_2 = PendingPayableFingerprint { + let fingerprint_2 = SentTx { rowid: 10, timestamp: from_unix_timestamp(199_780_000), hash: transaction_hash_2, @@ -3570,15 +3522,9 @@ mod tests { process_error: None, }; let msg = ReportTransactionReceipts { - fingerprints_with_receipts: vec![ - ( + receipt_results: vec![ TransactionReceiptResult::RpcResponse(transaction_receipt_1), - fingerprint_1.clone(), - ), - ( TransactionReceiptResult::RpcResponse(transaction_receipt_2), - fingerprint_2.clone(), - ), ], response_skeleton_opt: None, }; @@ -3614,7 +3560,7 @@ mod tests { "pending_payable_scanner_handles_report_transaction_receipts_message_with_empty_vector"; let mut pending_payable_scanner = PendingPayableScannerBuilder::new().build(); let msg = ReportTransactionReceipts { - fingerprints_with_receipts: vec![], + receipt_results: vec![], response_skeleton_opt: None, }; pending_payable_scanner.mark_as_started(SystemTime::now()); diff --git a/node/src/accountant/scanners/scanners_utils.rs b/node/src/accountant/scanners/scanners_utils.rs index b50a1388b..4ad7b2680 100644 --- a/node/src/accountant/scanners/scanners_utils.rs +++ b/node/src/accountant/scanners/scanners_utils.rs @@ -6,7 +6,7 @@ pub mod payable_scanner_utils { use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableTransactingErrorEnum::{ LocallyCausedError, RemotelyCausedErrors, }; - use crate::accountant::{comma_joined_stringifiable, SentPayables}; + use crate::accountant::{comma_joined_stringifiable, PendingPayable, SentPayables}; use crate::sub_lib::accountant::PaymentThresholds; use crate::sub_lib::wallet::Wallet; use itertools::Itertools; @@ -15,9 +15,8 @@ pub mod payable_scanner_utils { use std::ops::Not; use std::time::SystemTime; use thousands::Separable; - use web3::types::H256; + use web3::types::{Address, H256}; use masq_lib::ui_gateway::NodeToUiMessage; - use crate::accountant::db_access_objects::pending_payable_dao::PendingPayable; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; use crate::blockchain::blockchain_interface::data_structures::{ProcessedPayableFallible, RpcPayableFailure}; @@ -234,31 +233,28 @@ pub mod payable_scanner_utils { } #[derive(Debug, PartialEq, Eq)] - pub struct PendingPayableMetadata<'a> { - pub recipient: &'a Wallet, + pub struct PendingPayableMissingInDb { + pub recipient: Address, pub hash: H256, - pub rowid_opt: Option, } - impl<'a> PendingPayableMetadata<'a> { + impl PendingPayableMissingInDb { pub fn new( - recipient: &'a Wallet, + recipient: Address, hash: H256, - rowid_opt: Option, - ) -> PendingPayableMetadata<'a> { - PendingPayableMetadata { + ) -> PendingPayableMissingInDb { + PendingPayableMissingInDb { recipient, hash, - rowid_opt, } } } pub fn mark_pending_payable_fatal_error( sent_payments: &[&PendingPayable], - nonexistent: &[PendingPayableMetadata], + nonexistent: &[PendingPayableMissingInDb], error: PayableDaoError, - missing_fingerprints_msg_maker: fn(&[PendingPayableMetadata]) -> String, + missing_fingerprints_msg_maker: fn(&[PendingPayableMissingInDb]) -> String, logger: &Logger, ) { if !nonexistent.is_empty() { @@ -323,16 +319,16 @@ pub mod payable_scanner_utils { pub mod pending_payable_scanner_utils { use crate::accountant::PendingPayableId; - use crate::blockchain::blockchain_bridge::PendingPayableFingerprint; use masq_lib::logger::Logger; use masq_lib::ui_gateway::NodeToUiMessage; use std::time::SystemTime; + use crate::accountant::db_access_objects::failed_payable_dao::FailedTx; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{ConfirmedTx, TxReceiptLocalError}; #[derive(Debug, Default, PartialEq, Eq, Clone)] pub struct PendingPayableScanReport { - pub still_pending: Vec, - pub failures: Vec, - pub confirmed: Vec, + pub failures: Vec, + pub confirmed: Vec, } impl PendingPayableScanReport { @@ -353,99 +349,96 @@ pub mod pending_payable_scanner_utils { .expect("time calculation for elapsed failed") .as_millis() } - - pub fn handle_none_status( + // + // pub fn handle_none_status( + // mut scan_report: PendingPayableScanReport, + // fingerprint: SentTx, + // max_pending_interval: u64, + // logger: &Logger, + // ) -> PendingPayableScanReport { + // info!( + // logger, + // "Pending transaction {:?} couldn't be confirmed at attempt \ + // {} at {}ms after its sending", + // fingerprint.hash, + // fingerprint.attempt, + // elapsed_in_ms(fingerprint.timestamp) + // ); + // let elapsed = fingerprint + // .timestamp + // .elapsed() + // .expect("we should be older now"); + // let elapsed = elapsed.as_secs(); + // if elapsed > max_pending_interval { + // error!( + // logger, + // "Pending transaction {:?} has exceeded the maximum pending time \ + // ({}sec) with the age {}sec and the confirmation process is going to be aborted now \ + // at the final attempt {}; manual resolution is required from the \ + // user to complete the transaction.", + // fingerprint.hash, + // max_pending_interval, + // elapsed, + // fingerprint.attempt + // ); + // scan_report.failures.push(fingerprint.into()) + // } else { + // scan_report.still_pending.push(fingerprint.into()) + // } + // scan_report + // } + + pub fn handle_successful_tx( mut scan_report: PendingPayableScanReport, - fingerprint: PendingPayableFingerprint, - max_pending_interval: u64, + confirmed_tx: ConfirmedTx, logger: &Logger, ) -> PendingPayableScanReport { - info!( - logger, - "Pending transaction {:?} couldn't be confirmed at attempt \ - {} at {}ms after its sending", - fingerprint.hash, - fingerprint.attempt, - elapsed_in_ms(fingerprint.timestamp) - ); - let elapsed = fingerprint - .timestamp - .elapsed() - .expect("we should be older now"); - let elapsed = elapsed.as_secs(); - if elapsed > max_pending_interval { - error!( - logger, - "Pending transaction {:?} has exceeded the maximum pending time \ - ({}sec) with the age {}sec and the confirmation process is going to be aborted now \ - at the final attempt {}; manual resolution is required from the \ - user to complete the transaction.", - fingerprint.hash, - max_pending_interval, - elapsed, - fingerprint.attempt - ); - scan_report.failures.push(fingerprint.into()) - } else { - scan_report.still_pending.push(fingerprint.into()) - } - scan_report - } - - pub fn handle_status_with_success( - mut scan_report: PendingPayableScanReport, - fingerprint: PendingPayableFingerprint, - logger: &Logger, - ) -> PendingPayableScanReport { - info!( - logger, - "Transaction {:?} has been added to the blockchain; detected locally at attempt \ - {} at {}ms after its sending", - fingerprint.hash, - fingerprint.attempt, - elapsed_in_ms(fingerprint.timestamp) - ); - scan_report.confirmed.push(fingerprint); - scan_report + // info!( + // logger, + // "Transaction {:?} has been added to the blockchain; detected locally at attempt \ + // {} at {}ms after its sending", + // fingerprint.hash, + // fingerprint.attempt, + // elapsed_in_ms(fingerprint.timestamp) + // ); + // scan_report.confirmed.push(fingerprint); + // scan_report } //TODO: failures handling is going to need enhancement suggested by GH-693 pub fn handle_status_with_failure( mut scan_report: PendingPayableScanReport, - fingerprint: PendingPayableFingerprint, + tx_failure: FailedTx, logger: &Logger, ) -> PendingPayableScanReport { error!( logger, - "Pending transaction {:?} announced as a failure, interpreting attempt \ - {} after {}ms from the sending", - fingerprint.hash, - fingerprint.attempt, - elapsed_in_ms(fingerprint.timestamp) + "Pending transaction {:?} announced as a failure after {}ms from its sending", + tx_failure.hash, + todo!() //tx_failure.timestamp ); - scan_report.failures.push(fingerprint.into()); + scan_report.failures.push(tx_failure); scan_report } - pub fn handle_none_receipt( + pub fn handle_local_error_fetching_receipts( mut scan_report: PendingPayableScanReport, - payable: PendingPayableFingerprint, - error_msg: &str, + local_error: TxReceiptLocalError, logger: &Logger, ) -> PendingPayableScanReport { - debug!( - logger, - "Interpreting a receipt for transaction {:?} but {}; attempt {}, {}ms since sending", - payable.hash, - error_msg, - payable.attempt, - elapsed_in_ms(payable.timestamp) - ); - - scan_report - .still_pending - .push(PendingPayableId::new(payable.rowid, payable.hash)); - scan_report + // debug!( + // logger, + // "Interpreting a receipt for transaction {:?} but {}; attempt {}, {}ms since sending", + // payable.hash, + // error_msg, + // payable.attempt, + // elapsed_in_ms(payable.timestamp) + // ); + // + // scan_report + // .still_pending + // .push(PendingPayableId::new(payable.rowid, payable.hash)); + // scan_report } } @@ -478,7 +471,7 @@ mod tests { PayableThresholdsGaugeReal, }; use crate::accountant::scanners::scanners_utils::receivable_scanner_utils::balance_and_age; - use crate::accountant::{checked_conversion, gwei_to_wei, SentPayables}; + use crate::accountant::{checked_conversion, gwei_to_wei, PendingPayable, SentPayables}; use crate::blockchain::test_utils::make_tx_hash; use crate::sub_lib::accountant::PaymentThresholds; use crate::test_utils::make_wallet; @@ -486,7 +479,6 @@ mod tests { use masq_lib::logger::Logger; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; use std::time::SystemTime; - use crate::accountant::db_access_objects::pending_payable_dao::PendingPayable; use crate::blockchain::blockchain_interface::data_structures::errors::{BlockchainError, PayableTransactionError}; use crate::blockchain::blockchain_interface::data_structures::{ProcessedPayableFallible, RpcPayableFailure}; diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs index e0e5a6cdd..f10a7a272 100644 --- a/node/src/accountant/test_utils.rs +++ b/node/src/accountant/test_utils.rs @@ -6,22 +6,19 @@ use crate::accountant::db_access_objects::banned_dao::{BannedDao, BannedDaoFacto use crate::accountant::db_access_objects::payable_dao::{ PayableAccount, PayableDao, PayableDaoError, PayableDaoFactory, }; -use crate::accountant::db_access_objects::pending_payable_dao::{ - PendingPayableDao, PendingPayableDaoError, PendingPayableDaoFactory, TransactionHashes, +use crate::accountant::db_access_objects::sent_payable_dao::{ + SentPayableDao, }; 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, -}; +use crate::accountant::db_access_objects::utils::{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::BlockchainAgentWithContextMessage; use crate::accountant::scanners::payable_scanner_extension::PreparedAdjustment; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableThresholdsGauge; use crate::accountant::scanners::{PayableScanner, PendingPayableScanner, ReceivableScanner}; use crate::accountant::{gwei_to_wei, Accountant, DEFAULT_PENDING_TOO_LONG_SEC}; -use crate::blockchain::blockchain_bridge::PendingPayableFingerprint; use crate::blockchain::blockchain_interface::blockchain_interface_web3::HashAndAmount; use crate::blockchain::blockchain_interface::data_structures::BlockchainTransaction; use crate::blockchain::test_utils::make_tx_hash; @@ -42,11 +39,14 @@ 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; use std::sync::{Arc, Mutex}; use std::time::SystemTime; +use crate::accountant::db_access_objects::sent_payable_dao::{RetrieveCondition, SentPayableDaoError, SentTx}; +use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::TransactionBlock; pub fn make_receivable_account(n: u64, expected_delinquent: bool) -> ReceivableAccount { let now = to_unix_timestamp(SystemTime::now()); @@ -270,14 +270,14 @@ impl AccountantBuilder { pub fn pending_payable_daos( mut self, - specially_configured_daos: Vec>, + specially_configured_daos: Vec>, ) -> Self { create_or_update_factory!( specially_configured_daos, PENDING_PAYABLE_DAOS_ACCOUNTANT_INITIALIZATION_ORDER, pending_payable_dao_factory_opt, PendingPayableDaoFactoryMock, - PendingPayableDao, + SentPayableDao, self ) } @@ -342,11 +342,11 @@ impl AccountantBuilder { .make_result(ReceivableDaoMock::new()) .make_result(ReceivableDaoMock::new()), ); - let pending_payable_dao_factory = self.pending_payable_dao_factory_opt.unwrap_or( + let sent_payable_dao_factory = self.pending_payable_dao_factory_opt.unwrap_or( PendingPayableDaoFactoryMock::new() - .make_result(PendingPayableDaoMock::new()) - .make_result(PendingPayableDaoMock::new()) - .make_result(PendingPayableDaoMock::new()), + .make_result(SentPayableDaoMock::new()) + .make_result(SentPayableDaoMock::new()) + .make_result(SentPayableDaoMock::new()), ); let banned_dao_factory = self .banned_dao_factory_opt @@ -358,7 +358,7 @@ impl AccountantBuilder { config, DaoFactories { payable_dao_factory: Box::new(payable_dao_factory), - pending_payable_dao_factory: Box::new(pending_payable_dao_factory), + sent_payable_dao_factory: Box::new(sent_payable_dao_factory), receivable_dao_factory: Box::new(receivable_dao_factory), banned_dao_factory: Box::new(banned_dao_factory), config_dao_factory: Box::new(config_dao_factory), @@ -520,7 +520,7 @@ pub struct PayableDaoMock { non_pending_payables_results: RefCell>>, mark_pending_payables_rowids_params: Arc>>>, mark_pending_payables_rowids_results: RefCell>>, - transactions_confirmed_params: Arc>>>, + transactions_confirmed_params: Arc>>>, transactions_confirmed_results: RefCell>>, custom_query_params: Arc>>>, custom_query_result: RefCell>>>, @@ -561,7 +561,7 @@ impl PayableDao for PayableDaoMock { fn transactions_confirmed( &self, - confirmed_payables: &[PendingPayableFingerprint], + confirmed_payables: &[SentTx], ) -> Result<(), PayableDaoError> { self.transactions_confirmed_params .lock() @@ -635,7 +635,7 @@ impl PayableDaoMock { pub fn transactions_confirmed_params( mut self, - params: &Arc>>>, + params: &Arc>>>, ) -> Self { self.transactions_confirmed_params = params.clone(); self @@ -874,164 +874,297 @@ pub fn bc_from_wallets(consuming_wallet: Wallet, earning_wallet: Wallet) -> Boot bc.earning_wallet = earning_wallet; bc } +// +// #[derive(Default)] +// pub struct SentPayableDaoMock { +// fingerprints_rowids_params: Arc>>>, +// fingerprints_rowids_results: RefCell>, +// delete_fingerprints_params: Arc>>>, +// delete_fingerprints_results: RefCell>>, +// insert_new_fingerprints_params: Arc, SystemTime)>>>, +// insert_new_fingerprints_results: RefCell>>, +// increment_scan_attempts_params: Arc>>>, +// increment_scan_attempts_result: RefCell>>, +// mark_failures_params: Arc>>>, +// mark_failures_results: RefCell>>, +// return_all_errorless_fingerprints_params: Arc>>, +// return_all_errorless_fingerprints_results: RefCell>>, +// pub have_return_all_errorless_fingerprints_shut_down_the_system: bool, +// } +// +// impl SentPayableDao for SentPayableDaoMock { +// fn fingerprints_rowids(&self, hashes: &[H256]) -> TransactionHashes { +// self.fingerprints_rowids_params +// .lock() +// .unwrap() +// .push(hashes.to_vec()); +// self.fingerprints_rowids_results.borrow_mut().remove(0) +// } +// +// fn return_all_errorless_fingerprints(&self) -> Vec { +// self.return_all_errorless_fingerprints_params +// .lock() +// .unwrap() +// .push(()); +// if self.have_return_all_errorless_fingerprints_shut_down_the_system +// && self +// .return_all_errorless_fingerprints_results +// .borrow() +// .is_empty() +// { +// System::current().stop(); +// return vec![]; +// } +// self.return_all_errorless_fingerprints_results +// .borrow_mut() +// .remove(0) +// } +// +// fn insert_new_fingerprints( +// &self, +// hashes_and_amounts: &[HashAndAmount], +// batch_wide_timestamp: SystemTime, +// ) -> Result<(), PendingPayableDaoError> { +// self.insert_new_fingerprints_params +// .lock() +// .unwrap() +// .push((hashes_and_amounts.to_vec(), batch_wide_timestamp)); +// self.insert_new_fingerprints_results.borrow_mut().remove(0) +// } +// +// fn delete_fingerprints(&self, ids: &[u64]) -> Result<(), PendingPayableDaoError> { +// self.delete_fingerprints_params +// .lock() +// .unwrap() +// .push(ids.to_vec()); +// self.delete_fingerprints_results.borrow_mut().remove(0) +// } +// +// fn increment_scan_attempts(&self, ids: &[u64]) -> Result<(), PendingPayableDaoError> { +// self.increment_scan_attempts_params +// .lock() +// .unwrap() +// .push(ids.to_vec()); +// self.increment_scan_attempts_result.borrow_mut().remove(0) +// } +// +// fn mark_failures(&self, ids: &[u64]) -> Result<(), PendingPayableDaoError> { +// self.mark_failures_params.lock().unwrap().push(ids.to_vec()); +// self.mark_failures_results.borrow_mut().remove(0) +// } +// } +// +// impl SentPayableDaoMock { +// pub fn new() -> Self { +// SentPayableDaoMock::default() +// } +// +// pub fn fingerprints_rowids_params(mut self, params: &Arc>>>) -> Self { +// self.fingerprints_rowids_params = params.clone(); +// self +// } +// +// pub fn fingerprints_rowids_result(self, result: TransactionHashes) -> Self { +// self.fingerprints_rowids_results.borrow_mut().push(result); +// self +// } +// +// pub fn insert_fingerprints_params( +// mut self, +// params: &Arc, SystemTime)>>>, +// ) -> Self { +// self.insert_new_fingerprints_params = params.clone(); +// self +// } +// +// pub fn insert_fingerprints_result(self, result: Result<(), PendingPayableDaoError>) -> Self { +// self.insert_new_fingerprints_results +// .borrow_mut() +// .push(result); +// self +// } +// +// pub fn delete_fingerprints_params(mut self, params: &Arc>>>) -> Self { +// self.delete_fingerprints_params = params.clone(); +// self +// } +// +// pub fn delete_fingerprints_result(self, result: Result<(), PendingPayableDaoError>) -> Self { +// self.delete_fingerprints_results.borrow_mut().push(result); +// self +// } +// +// pub fn return_all_errorless_fingerprints_params( +// mut self, +// params: &Arc>>, +// ) -> Self { +// self.return_all_errorless_fingerprints_params = params.clone(); +// self +// } +// +// pub fn return_all_errorless_fingerprints_result( +// self, +// result: Vec, +// ) -> Self { +// self.return_all_errorless_fingerprints_results +// .borrow_mut() +// .push(result); +// self +// } +// +// pub fn mark_failures_params(mut self, params: &Arc>>>) -> Self { +// self.mark_failures_params = params.clone(); +// self +// } +// +// pub fn mark_failures_result(self, result: Result<(), PendingPayableDaoError>) -> Self { +// self.mark_failures_results.borrow_mut().push(result); +// self +// } +// +// pub fn increment_scan_attempts_params(mut self, params: &Arc>>>) -> Self { +// self.increment_scan_attempts_params = params.clone(); +// self +// } +// +// pub fn increment_scan_attempts_result( +// self, +// result: Result<(), PendingPayableDaoError>, +// ) -> Self { +// self.increment_scan_attempts_result +// .borrow_mut() +// .push(result); +// self +// } +// } #[derive(Default)] -pub struct PendingPayableDaoMock { - fingerprints_rowids_params: Arc>>>, - fingerprints_rowids_results: RefCell>, - delete_fingerprints_params: Arc>>>, - delete_fingerprints_results: RefCell>>, - insert_new_fingerprints_params: Arc, SystemTime)>>>, - insert_new_fingerprints_results: RefCell>>, - increment_scan_attempts_params: Arc>>>, - increment_scan_attempts_result: RefCell>>, - mark_failures_params: Arc>>>, - mark_failures_results: RefCell>>, - return_all_errorless_fingerprints_params: Arc>>, - return_all_errorless_fingerprints_results: RefCell>>, - pub have_return_all_errorless_fingerprints_shut_down_the_system: bool, +pub struct SentPayableDaoMock { + 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_tx_block_params: Arc>>>, + update_tx_block_results: RefCell>>, + replace_records_params: Arc>>>, + replace_records_results: RefCell>>, + delete_records_params: Arc>>>, + delete_records_results: RefCell>>, + // TODO necessary? + //pub have_return_all_errorless_fingerprints_shut_down_the_system: bool, } -impl PendingPayableDao for PendingPayableDaoMock { - fn fingerprints_rowids(&self, hashes: &[H256]) -> TransactionHashes { - self.fingerprints_rowids_params - .lock() - .unwrap() - .push(hashes.to_vec()); - self.fingerprints_rowids_results.borrow_mut().remove(0) +impl SentPayableDao for SentPayableDaoMock { + fn get_tx_identifiers(&self, hashes: &HashSet) -> TxIdentifiers{ + self.get_tx_identifiers_params.push(hashes); + self. get_tx_identifiers_results.borrow_mut().remove(0) } - - fn return_all_errorless_fingerprints(&self) -> Vec { - self.return_all_errorless_fingerprints_params - .lock() - .unwrap() - .push(()); - if self.have_return_all_errorless_fingerprints_shut_down_the_system - && self - .return_all_errorless_fingerprints_results - .borrow() - .is_empty() - { - System::current().stop(); - return vec![]; - } - self.return_all_errorless_fingerprints_results - .borrow_mut() - .remove(0) + fn insert_new_records(&self, txs: &[SentTx]) -> Result<(), SentPayableDaoError>{ + self.insert_new_records_params.lock().unwrap().push(txs.to_vec()); + self.insert_new_records_results.borrow_mut().remove(0) } - - fn insert_new_fingerprints( - &self, - hashes_and_amounts: &[HashAndAmount], - batch_wide_timestamp: SystemTime, - ) -> Result<(), PendingPayableDaoError> { - self.insert_new_fingerprints_params - .lock() - .unwrap() - .push((hashes_and_amounts.to_vec(), batch_wide_timestamp)); - self.insert_new_fingerprints_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 delete_fingerprints(&self, ids: &[u64]) -> Result<(), PendingPayableDaoError> { - self.delete_fingerprints_params - .lock() - .unwrap() - .push(ids.to_vec()); - self.delete_fingerprints_results.borrow_mut().remove(0) + fn update_tx_blocks( + &self, + hash_map: &HashMap, + ) -> Result<(), SentPayableDaoError>{ + self.update_tx_block_params.lock().unwrap().push(hash_map.clone()); + self.update_tx_block_results.borrow_mut().remove(0) } - - fn increment_scan_attempts(&self, ids: &[u64]) -> Result<(), PendingPayableDaoError> { - self.increment_scan_attempts_params - .lock() - .unwrap() - .push(ids.to_vec()); - self.increment_scan_attempts_result.borrow_mut().remove(0) + fn replace_records(&self, new_txs: &[SentTx]) -> Result<(), SentPayableDaoError>{ + self.replace_records_params.lock().unwrap().push(new_txs.to_vec()); + self.replace_records_results.borrow_mut().remove(0) } - fn mark_failures(&self, ids: &[u64]) -> Result<(), PendingPayableDaoError> { - self.mark_failures_params.lock().unwrap().push(ids.to_vec()); - self.mark_failures_results.borrow_mut().remove(0) + fn delete_records(&self, hashes: &HashSet) -> Result<(), SentPayableDaoError>{ + self.delete_records_params.lock().unwrap().push(hashes.clone()); + self.delete_records_results.borrow_mut().remove(0) } } -impl PendingPayableDaoMock { +impl SentPayableDaoMock { pub fn new() -> Self { - PendingPayableDaoMock::default() + SentPayableDaoMock::default() } - pub fn fingerprints_rowids_params(mut self, params: &Arc>>>) -> Self { - self.fingerprints_rowids_params = params.clone(); + pub fn get_tx_identifiers_params(mut self, params: &Arc>>>) -> Self { + self.get_tx_identifiers_params = params.clone(); self } - pub fn fingerprints_rowids_result(self, result: TransactionHashes) -> Self { - self.fingerprints_rowids_results.borrow_mut().push(result); + pub fn get_tx_identifiers_result(self, result: TxIdentifiers) -> Self { + self.get_tx_identifiers_results.borrow_mut().push(result); self } - pub fn insert_fingerprints_params( + pub fn insert_new_records_params( mut self, - params: &Arc, SystemTime)>>>, + params: &Arc>>>, ) -> Self { - self.insert_new_fingerprints_params = params.clone(); + self.insert_new_records_params = params.clone(); self } - pub fn insert_fingerprints_result(self, result: Result<(), PendingPayableDaoError>) -> Self { - self.insert_new_fingerprints_results + pub fn insert_new_records_result(self, result: Result<(), SentPayableDaoError>) -> Self { + self.insert_new_records_results .borrow_mut() .push(result); self } - pub fn delete_fingerprints_params(mut self, params: &Arc>>>) -> Self { - self.delete_fingerprints_params = params.clone(); + pub fn retrieve_txs(mut self, params: &Arc>>>) -> Self { + self.retrieve_txs_params = params.clone(); self } - pub fn delete_fingerprints_result(self, result: Result<(), PendingPayableDaoError>) -> Self { - self.delete_fingerprints_results.borrow_mut().push(result); + pub fn retrieve_txs_result(self, result: Vec) -> Self { + self.retrieve_txs_results.borrow_mut().push(result); self } - pub fn return_all_errorless_fingerprints_params( + pub fn update_tx_blocks_params( mut self, - params: &Arc>>, + params: &Arc>>>, ) -> Self { - self.return_all_errorless_fingerprints_params = params.clone(); + self.update_tx_block_params = params.clone(); self } - pub fn return_all_errorless_fingerprints_result( + pub fn update_tx_blocks_result( self, - result: Vec, + result: Result<(), SentPayableDaoError>, ) -> Self { - self.return_all_errorless_fingerprints_results + self.update_tx_block_results .borrow_mut() .push(result); self } - pub fn mark_failures_params(mut self, params: &Arc>>>) -> Self { - self.mark_failures_params = params.clone(); + pub fn replace_records_params(mut self, params: &Arc>>>) -> Self { + self.replace_records_params = params.clone(); self } - pub fn mark_failures_result(self, result: Result<(), PendingPayableDaoError>) -> Self { - self.mark_failures_results.borrow_mut().push(result); + pub fn replace_records_result(self, result: Result<(), SentPayableDaoError>) -> Self { + self.replace_records_results.borrow_mut().push(result); self } - pub fn increment_scan_attempts_params(mut self, params: &Arc>>>) -> Self { - self.increment_scan_attempts_params = params.clone(); + pub fn delete_records_params(mut self, params: &Arc>>>) -> Self { + self.delete_records_params = params.clone(); self } - pub fn increment_scan_attempts_result( + pub fn delete_records_result( self, - result: Result<(), PendingPayableDaoError>, + result: Result<(), SentPayableDaoError>, ) -> Self { - self.increment_scan_attempts_result + self.delete_records_results .borrow_mut() .push(result); self @@ -1040,14 +1173,14 @@ impl PendingPayableDaoMock { pub struct PendingPayableDaoFactoryMock { make_params: Arc>>, - make_results: RefCell>>, + make_results: RefCell>>, } impl PendingPayableDaoFactory for PendingPayableDaoFactoryMock { - fn make(&self) -> Box { + fn make(&self) -> Box { if self.make_results.borrow().len() == 0 { panic!( - "PendingPayableDao Missing. This problem mostly occurs when PendingPayableDao is only supplied for Accountant and not for the Scanner while building Accountant." + "SentPayableDao Missing. This problem mostly occurs when SentPayableDao is only supplied for Accountant and not for the Scanner while building Accountant." ) }; self.make_params.lock().unwrap().push(()); @@ -1068,7 +1201,7 @@ impl PendingPayableDaoFactoryMock { self } - pub fn make_result(self, result: PendingPayableDaoMock) -> Self { + pub fn make_result(self, result: SentPayableDaoMock) -> Self { self.make_results.borrow_mut().push(Box::new(result)); self } @@ -1076,7 +1209,7 @@ impl PendingPayableDaoFactoryMock { pub struct PayableScannerBuilder { payable_dao: PayableDaoMock, - pending_payable_dao: PendingPayableDaoMock, + sent_payable_dao: SentPayableDaoMock, payment_thresholds: PaymentThresholds, payment_adjuster: PaymentAdjusterMock, } @@ -1085,7 +1218,7 @@ impl PayableScannerBuilder { pub fn new() -> Self { Self { payable_dao: PayableDaoMock::new(), - pending_payable_dao: PendingPayableDaoMock::new(), + sent_payable_dao: SentPayableDaoMock::new(), payment_thresholds: PaymentThresholds::default(), payment_adjuster: PaymentAdjusterMock::default(), } @@ -1109,18 +1242,18 @@ impl PayableScannerBuilder { self } - pub fn pending_payable_dao( + pub fn sent_payable_dao( mut self, - pending_payable_dao: PendingPayableDaoMock, + sent_payable_dao: SentPayableDaoMock, ) -> PayableScannerBuilder { - self.pending_payable_dao = pending_payable_dao; + self.sent_payable_dao = sent_payable_dao; self } pub fn build(self) -> PayableScanner { PayableScanner::new( Box::new(self.payable_dao), - Box::new(self.pending_payable_dao), + Box::new(self.sent_payable_dao), Rc::new(self.payment_thresholds), Box::new(self.payment_adjuster), ) @@ -1129,7 +1262,7 @@ impl PayableScannerBuilder { pub struct PendingPayableScannerBuilder { payable_dao: PayableDaoMock, - pending_payable_dao: PendingPayableDaoMock, + sent_payable_dao: SentPayableDaoMock, payment_thresholds: PaymentThresholds, when_pending_too_long_sec: u64, financial_statistics: FinancialStatistics, @@ -1139,7 +1272,7 @@ impl PendingPayableScannerBuilder { pub fn new() -> Self { Self { payable_dao: PayableDaoMock::new(), - pending_payable_dao: PendingPayableDaoMock::new(), + sent_payable_dao: SentPayableDaoMock::new(), payment_thresholds: PaymentThresholds::default(), when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, financial_statistics: FinancialStatistics::default(), @@ -1151,8 +1284,8 @@ impl PendingPayableScannerBuilder { self } - pub fn pending_payable_dao(mut self, pending_payable_dao: PendingPayableDaoMock) -> Self { - self.pending_payable_dao = pending_payable_dao; + pub fn sent_payable_dao(mut self, sent_payable_dao: SentPayableDaoMock) -> Self { + self.sent_payable_dao = sent_payable_dao; self } @@ -1164,7 +1297,7 @@ impl PendingPayableScannerBuilder { pub fn build(self) -> PendingPayableScanner { PendingPayableScanner::new( Box::new(self.payable_dao), - Box::new(self.pending_payable_dao), + Box::new(self.sent_payable_dao), Rc::new(self.payment_thresholds), self.when_pending_too_long_sec, Rc::new(RefCell::new(self.financial_statistics)), @@ -1236,8 +1369,8 @@ pub fn make_custom_payment_thresholds() -> PaymentThresholds { } } -pub fn make_pending_payable_fingerprint() -> PendingPayableFingerprint { - PendingPayableFingerprint { +pub fn make_pending_payable_fingerprint() -> SentTx { + SentTx { rowid: 33, timestamp: from_unix_timestamp(222_222_222), hash: make_tx_hash(456), diff --git a/node/src/actor_system_factory.rs b/node/src/actor_system_factory.rs index 9e15f9c5d..5c12f7fa5 100644 --- a/node/src/actor_system_factory.rs +++ b/node/src/actor_system_factory.rs @@ -473,7 +473,7 @@ impl ActorFactory for ActorFactoryReal { ) -> AccountantSubs { let data_directory = config.data_directory.as_path(); let payable_dao_factory = Box::new(Accountant::dao_factory(data_directory)); - let pending_payable_dao_factory = Box::new(Accountant::dao_factory(data_directory)); + let sent_payable_dao_factory = Box::new(Accountant::dao_factory(data_directory)); let receivable_dao_factory = Box::new(Accountant::dao_factory(data_directory)); let banned_dao_factory = Box::new(Accountant::dao_factory(data_directory)); let config_dao_factory = Box::new(Accountant::dao_factory(data_directory)); @@ -484,7 +484,7 @@ impl ActorFactory for ActorFactoryReal { config, DaoFactories { payable_dao_factory, - pending_payable_dao_factory, + sent_payable_dao_factory, receivable_dao_factory, banned_dao_factory, config_dao_factory, diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index b93d8770c..bc4af2e8a 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -171,17 +171,17 @@ pub struct PendingPayableFingerprintSeeds { pub hashes_and_balances: Vec, } -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct PendingPayableFingerprint { - // Sqlite begins counting from 1 - pub rowid: u64, - pub timestamp: SystemTime, - pub hash: H256, - // We have Sqlite begin counting from 1 - pub attempt: u16, - pub amount: u128, - pub process_error: Option, -} +// #[derive(Debug, PartialEq, Eq, Clone)] +// pub struct SentTx { +// // Sqlite begins counting from 1 +// pub rowid: u64, +// pub timestamp: SystemTime, +// pub hash: H256, +// // We have Sqlite begin counting from 1 +// pub attempt: u16, +// pub amount: u128, +// pub process_error: Option, +// } impl Handler for BlockchainBridge { type Result = (); @@ -403,8 +403,7 @@ impl BlockchainBridge { |(success, fail, pending), transaction_receipt| match transaction_receipt { TransactionReceiptResult::RpcResponse(tx_receipt) => { match tx_receipt.status { - TxStatus::Failed => (success, fail + 1, pending), - TxStatus::Pending => (success, fail, pending + 1), + TxStatus::Failed(_) => (success, fail + 1, pending), TxStatus::Succeeded(_) => (success + 1, fail, pending), } } @@ -422,40 +421,40 @@ impl BlockchainBridge { &mut self, msg: RequestTransactionReceipts, ) -> Box> { - let logger = self.logger.clone(); - let accountant_recipient = self - .pending_payable_confirmation - .report_transaction_receipts_sub_opt - .clone() - .expect("Accountant is unbound"); - - let transaction_hashes = msg - .pending_payable_fingerprints - .iter() - .map(|finger_print| finger_print.hash) - .collect::>(); - Box::new( - self.blockchain_interface - .process_transaction_receipts(transaction_hashes) - .map_err(move |e| e.to_string()) - .and_then(move |transaction_receipts_results| { - Self::log_status_of_tx_receipts(&logger, &transaction_receipts_results); - - let pairs = transaction_receipts_results - .into_iter() - .zip(msg.pending_payable_fingerprints.into_iter()) - .collect_vec(); - - accountant_recipient - .try_send(ReportTransactionReceipts { - fingerprints_with_receipts: pairs, - response_skeleton_opt: msg.response_skeleton_opt, - }) - .expect("Accountant is dead"); - - Ok(()) - }), - ) + todo!() + // let logger = self.logger.clone(); + // let accountant_recipient = self + // .pending_payable_confirmation + // .report_transaction_receipts_sub_opt + // .clone() + // .expect("Accountant is unbound"); + // + // let transaction_hashes = msg + // .pending_payable_fingerprints + // .iter() + // .map(|finger_print| finger_print.hash) + // .collect::>(); + // Box::new( + // self.blockchain_interface + // .process_transaction_receipts(transaction_hashes) + // .map_err(move |e| e.to_string()) + // .and_then(move |transaction_receipts_results| { + // Self::log_status_of_tx_receipts(&logger, &transaction_receipts_results); + // + // let pairs = transaction_receipts_results + // .into_iter() + // .zip(msg.pending_payable_fingerprints.into_iter()) + // .collect_vec(); + // + // accountant_recipient + // .try_send(ReportTransactionReceipts { + // response_skeleton_opt: msg.response_skeleton_opt, + // }) + // .expect("Accountant is dead"); + // + // Ok(()) + // }), + // ) } fn handle_scan_future(&mut self, handler: F, scan_type: ScanType, msg: M) @@ -547,7 +546,6 @@ impl SubsFactory for BlockchainBridgeSub mod tests { use super::*; use crate::accountant::db_access_objects::payable_dao::PayableAccount; - use crate::accountant::db_access_objects::pending_payable_dao::PendingPayable; use crate::accountant::db_access_objects::utils::from_unix_timestamp; use crate::accountant::scanners::payable_scanner_extension::agent_web3::WEB3_MAXIMAL_GAS_LIMIT_MARGIN; use crate::accountant::scanners::payable_scanner_extension::test_utils::BlockchainAgentMock; @@ -1148,7 +1146,7 @@ mod tests { let pending_payable_fingerprint_1 = make_pending_payable_fingerprint(); let hash_1 = pending_payable_fingerprint_1.hash; let hash_2 = make_tx_hash(78989); - let pending_payable_fingerprint_2 = PendingPayableFingerprint { + let pending_payable_fingerprint_2 = SentTx { rowid: 456, timestamp: SystemTime::now(), hash: hash_2, @@ -1317,7 +1315,7 @@ mod tests { let hash_4 = make_tx_hash(11111); let mut fingerprint_1 = make_pending_payable_fingerprint(); fingerprint_1.hash = hash_1; - let fingerprint_2 = PendingPayableFingerprint { + let fingerprint_2 = SentTx { rowid: 454, timestamp: SystemTime::now(), hash: hash_2, @@ -1325,7 +1323,7 @@ mod tests { amount: 3333, process_error: None, }; - let fingerprint_3 = PendingPayableFingerprint { + let fingerprint_3 = SentTx { rowid: 456, timestamp: SystemTime::now(), hash: hash_3, @@ -1333,7 +1331,7 @@ mod tests { amount: 4565, process_error: None, }; - let fingerprint_4 = PendingPayableFingerprint { + let fingerprint_4 = SentTx { rowid: 450, timestamp: from_unix_timestamp(230_000_000), hash: hash_4, @@ -1410,7 +1408,7 @@ mod tests { let report_transaction_recipient: Recipient = accountant_addr.recipient(); let hash_1 = make_tx_hash(0x1b2e6); - let fingerprint_1 = PendingPayableFingerprint { + let fingerprint_1 = SentTx { rowid: 454, timestamp: SystemTime::now(), hash: hash_1, @@ -1418,7 +1416,7 @@ mod tests { amount: 3333, process_error: None, }; - let fingerprint_2 = PendingPayableFingerprint { + let fingerprint_2 = SentTx { rowid: 456, timestamp: SystemTime::now(), hash: make_tx_hash(222444), diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs index b7353b7c2..2c504f8f5 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs @@ -13,68 +13,101 @@ use web3::contract::{Contract, Options}; use web3::transports::{Batch, Http}; use web3::types::{Address, BlockNumber, Filter, Log, TransactionReceipt}; use web3::{Error, Web3}; +use crate::accountant::db_access_objects::failed_payable_dao::FailedTx; +use crate::accountant::db_access_objects::utils::TxHash; #[derive(Debug, PartialEq, Eq, Clone)] pub enum TransactionReceiptResult { RpcResponse(TxReceipt), - LocalError(String), + //TODO shouldn't I write this so that it would refer to the tx by its hash? + LocalError(TxReceiptLocalError), } #[derive(Debug, PartialEq, Eq, Clone)] -pub enum TxStatus { - Failed, - Pending, - Succeeded(TransactionBlock), +pub struct TxReceipt { + pub transaction_hash: H256, + pub status: TxStatus, } -impl FromStr for TxStatus { - type Err = String; - - fn from_str(s: &str) -> Result { - match s { - "Pending" => Ok(TxStatus::Pending), - "Failed" => Ok(TxStatus::Failed), // TODO: GH-631: This should be removed - s if s.starts_with("Succeeded") => { - // The format is "Succeeded(block_number, block_hash)" - let parts: Vec<&str> = s[10..s.len() - 1].split(',').collect(); - if parts.len() != 2 { - return Err("Invalid Succeeded format".to_string()); - } - let block_number: u64 = parts[0] - .parse() - .map_err(|_| "Invalid block number".to_string())?; - let block_hash = - H256::from_str(&parts[1][2..]).map_err(|_| "Invalid block hash".to_string())?; - Ok(TxStatus::Succeeded(TransactionBlock { +impl From for TxReceipt { + fn from(receipt: TransactionReceipt) -> Self { + let status = match (receipt.status, receipt.block_hash, receipt.block_number) { + (Some(status), Some(block_hash), Some(block_number)) if status == U64::from(1) => { + TxStatus::Succeeded(TransactionBlock { block_hash, - block_number: U64::from(block_number), - })) + block_number, + }) } - _ => Err(format!("Unknown status: {}", s)), + (Some(status), _, _) if status == U64::from(0) => TxStatus::Failed, + _ => TxStatus::Pending, + }; + + TxReceipt { + transaction_hash: receipt.transaction_hash, + status, } } } +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum TxStatus { + Failed(FailedTx), + Succeeded(ConfirmedTx), +} + impl Display for TxStatus { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - TxStatus::Failed => write!(f, "Failed"), - TxStatus::Pending => write!(f, "Pending"), + TxStatus::Failed(failed_tx) => todo!(), // write!(f, "Failed"), TxStatus::Succeeded(block) => { write!( f, "Succeeded({},{:?})", - block.block_number, block.block_hash + block.block.block_number, block.block.block_hash ) } } } } +// impl FromStr for TxStatus { +// type Err = String; +// +// fn from_str(s: &str) -> Result { +// match s { +// "Pending" => Ok(TxStatus::Pending), +// "Failed" => Ok(TxStatus::Failed), // TODO: GH-631: This should be removed +// s if s.starts_with("Succeeded") => { +// // The format is "Succeeded(block_number, block_hash)" +// let parts: Vec<&str> = s[10..s.len() - 1].split(',').collect(); +// if parts.len() != 2 { +// return Err("Invalid Succeeded format".to_string()); +// } +// let block_number: u64 = parts[0] +// .parse() +// .map_err(|_| "Invalid block number".to_string())?; +// let block_hash = +// H256::from_str(&parts[1][2..]).map_err(|_| "Invalid block hash".to_string())?; +// Ok(TxStatus::Succeeded(TransactionBlock { +// block_hash, +// block_number: U64::from(block_number), +// })) +// } +// _ => Err(format!("Unknown status: {}", s)), +// } +// } +// } + #[derive(Debug, PartialEq, Eq, Clone)] -pub struct TxReceipt { - pub transaction_hash: H256, - pub status: TxStatus, +pub struct TxReceiptLocalError{ + tx_hash: TxHash, + err_msg: String +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct ConfirmedTx{ + pub tx_hash: TxHash, + pub block: TransactionBlock } #[derive(Debug, Default, PartialEq, Eq, Clone)] @@ -83,26 +116,6 @@ pub struct TransactionBlock { pub block_number: U64, } -impl From for TxReceipt { - fn from(receipt: TransactionReceipt) -> Self { - let status = match (receipt.status, receipt.block_hash, receipt.block_number) { - (Some(status), Some(block_hash), Some(block_number)) if status == U64::from(1) => { - TxStatus::Succeeded(TransactionBlock { - block_hash, - block_number, - }) - } - (Some(status), _, _) if status == U64::from(0) => TxStatus::Failed, - _ => TxStatus::Pending, - }; - - TxReceipt { - transaction_hash: receipt.transaction_hash, - status, - } - } -} - pub struct LowBlockchainIntWeb3 { web3: Web3, web3_batch: Web3>, diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs index 7172987f4..78f7200e9 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs @@ -1,7 +1,6 @@ // Copyright (c) 2024, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::accountant::db_access_objects::payable_dao::PayableAccount; -use crate::accountant::db_access_objects::pending_payable_dao::PendingPayable; use crate::accountant::scanners::payable_scanner_extension::agent_web3::BlockchainAgentWeb3; use crate::accountant::scanners::payable_scanner_extension::blockchain_agent::BlockchainAgent; use crate::blockchain::blockchain_bridge::PendingPayableFingerprintSeeds; diff --git a/node/src/blockchain/blockchain_interface/data_structures/mod.rs b/node/src/blockchain/blockchain_interface/data_structures/mod.rs index a33a1f889..428ef469a 100644 --- a/node/src/blockchain/blockchain_interface/data_structures/mod.rs +++ b/node/src/blockchain/blockchain_interface/data_structures/mod.rs @@ -2,7 +2,6 @@ pub mod errors; -use crate::accountant::db_access_objects::pending_payable_dao::PendingPayable; use crate::blockchain::blockchain_bridge::BlockMarker; use crate::sub_lib::wallet::Wallet; use std::fmt; diff --git a/node/src/stream_handler_pool.rs b/node/src/stream_handler_pool.rs index 470f0c44f..5772e9cf8 100644 --- a/node/src/stream_handler_pool.rs +++ b/node/src/stream_handler_pool.rs @@ -1760,7 +1760,7 @@ mod tests { }) .unwrap(); - tx.send(subject_subs).expect("Tx failure"); + tx.send(subject_subs).expect("SentTx failure"); system.run(); }); @@ -1927,7 +1927,7 @@ mod tests { }) .unwrap(); - tx.send(subject_subs).expect("Tx failure"); + tx.send(subject_subs).expect("SentTx failure"); system.run(); }); diff --git a/node/src/sub_lib/accountant.rs b/node/src/sub_lib/accountant.rs index f1f174e6e..0db335303 100644 --- a/node/src/sub_lib/accountant.rs +++ b/node/src/sub_lib/accountant.rs @@ -1,7 +1,6 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::accountant::db_access_objects::banned_dao::BannedDaoFactory; use crate::accountant::db_access_objects::payable_dao::PayableDaoFactory; -use crate::accountant::db_access_objects::pending_payable_dao::PendingPayableDaoFactory; use crate::accountant::db_access_objects::receivable_dao::ReceivableDaoFactory; use crate::accountant::scanners::payable_scanner_extension::msgs::BlockchainAgentWithContextMessage; use crate::accountant::{ @@ -22,6 +21,7 @@ use std::fmt::{Debug, Formatter}; use std::str::FromStr; use std::sync::atomic::{AtomicU32, Ordering}; use std::time::{Duration, SystemTime}; +use crate::accountant::db_access_objects::sent_payable_dao::SentPayableDaoFactory; lazy_static! { pub static ref DEFAULT_EARNING_WALLET: Wallet = Wallet::from_str("0x27d9A2AC83b493f88ce9B4532EDcf74e95B9788d").expect("Internal error"); @@ -71,7 +71,7 @@ impl PaymentThresholds { pub struct DaoFactories { pub payable_dao_factory: Box, - pub pending_payable_dao_factory: Box, + pub sent_payable_dao_factory: Box, pub receivable_dao_factory: Box, pub banned_dao_factory: Box, pub config_dao_factory: Box, From b73925a6dd57723cda2c5d693c7104dde2879aab Mon Sep 17 00:00:00 2001 From: Bert Date: Tue, 1 Jul 2025 10:18:10 +0200 Subject: [PATCH 02/61] GH-642: interim commit --- masq_lib/src/utils.rs | 69 ++++++++- node/src/accountant/mod.rs | 1 - node/src/accountant/scanners/mod.rs | 226 +++++++++++++++------------- 3 files changed, 184 insertions(+), 112 deletions(-) diff --git a/masq_lib/src/utils.rs b/masq_lib/src/utils.rs index 8d563ef37..21fba8983 100644 --- a/masq_lib/src/utils.rs +++ b/masq_lib/src/utils.rs @@ -482,10 +482,30 @@ macro_rules! hashmap { }; } +#[macro_export(local_inner_macros)] +macro_rules! hashset { + () => { + ::std::collections::HashSet::new() + }; + ($($val:expr,)+) => { + hashset!($($val),+) + }; + ($($value:expr),+) => { + { + let mut _hs = ::std::collections::HashSet::new(); + $( + let _ = _hs.insert($value); + )* + _hs + } + }; +} + + #[cfg(test)] mod tests { use super::*; - use std::collections::HashMap; + use std::collections::{HashMap, HashSet}; use std::env::current_dir; use std::fmt::Write; use std::fs::{create_dir_all, File, OpenOptions}; @@ -814,7 +834,8 @@ mod tests { let hashmap_with_one_element = hashmap!(1 => 2); let hashmap_with_multiple_elements = hashmap!(1 => 2, 10 => 20, 12 => 42); let hashmap_with_trailing_comma = hashmap!(1 => 2, 10 => 20,); - let hashmap_of_string = hashmap!("key" => "val"); + let hashmap_of_string = hashmap!("key_1" => "val_a", "key_2" => "val_b"); + let hashmap_with_duplicate = hashmap!(1 => 2, 1 => 2); let expected_empty_hashmap: HashMap = HashMap::new(); let mut expected_hashmap_with_one_element = HashMap::new(); @@ -827,7 +848,10 @@ mod tests { expected_hashmap_with_trailing_comma.insert(1, 2); expected_hashmap_with_trailing_comma.insert(10, 20); let mut expected_hashmap_of_string = HashMap::new(); - expected_hashmap_of_string.insert("key", "val"); + expected_hashmap_of_string.insert("key_1", "val_a"); + expected_hashmap_of_string.insert("key_2", "val_b"); + let mut expected_hashmap_with_duplicate = HashMap::new(); + expected_hashmap_with_duplicate.insert(1, 2); assert_eq!(empty_hashmap, expected_empty_hashmap); assert_eq!(hashmap_with_one_element, expected_hashmap_with_one_element); assert_eq!( @@ -839,5 +863,44 @@ mod tests { expected_hashmap_with_trailing_comma ); assert_eq!(hashmap_of_string, expected_hashmap_of_string); + assert_eq!(hashmap_with_duplicate, expected_hashmap_with_duplicate); + } + + #[test] + fn hashset_macro_works() { + let empty_hashset: HashSet = hashset!(); + let hashset_with_one_element = hashset!(2); + let hashset_with_multiple_elements = hashset!(2, 20, 42); + let hashset_with_trailing_comma = hashset!(2, 20,); + let hashset_of_string = hashset!("val_a","val_b"); + let hashset_with_duplicate = hashset!(2, 2); + + let expected_empty_hashset: HashSet = HashSet::new(); + let mut expected_hashset_with_one_element = HashSet::new(); + expected_hashset_with_one_element.insert(2); + let mut expected_hashset_with_multiple_elements = HashSet::new(); + expected_hashset_with_multiple_elements.insert(2); + expected_hashset_with_multiple_elements.insert(20); + expected_hashset_with_multiple_elements.insert(42); + let mut expected_hashset_with_trailing_comma = HashSet::new(); + expected_hashset_with_trailing_comma.insert(2); + expected_hashset_with_trailing_comma.insert(20); + let mut expected_hashset_of_string = HashSet::new(); + expected_hashset_of_string.insert("val_a"); + expected_hashset_of_string.insert("val_b"); + let mut expected_hashset_with_duplicate = HashSet::new(); + expected_hashset_with_duplicate.insert(2); + assert_eq!(empty_hashset, expected_empty_hashset); + assert_eq!(hashset_with_one_element, expected_hashset_with_one_element); + assert_eq!( + hashset_with_multiple_elements, + expected_hashset_with_multiple_elements + ); + assert_eq!( + hashset_with_trailing_comma, + expected_hashset_with_trailing_comma + ); + assert_eq!(hashset_of_string, expected_hashset_of_string); + assert_eq!(hashset_with_duplicate, expected_hashset_with_duplicate); } } diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index 56dd6cf75..5920cdc35 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -1296,7 +1296,6 @@ mod tests { use std::sync::Mutex; use std::time::{Duration, UNIX_EPOCH}; use std::vec; - use system_configuration::sys::schema_definitions::kSCPropNetProxiesHTTPSProxy; use crate::accountant::db_access_objects::sent_payable_dao::{SentPayableDaoError, SentTx}; use crate::accountant::scanners::scan_schedulers::{NewPayableScanDynIntervalComputer, NewPayableScanDynIntervalComputerReal}; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{OperationOutcome, PayableScanResult}; diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index 0dba50ad2..d853ae7a1 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -676,58 +676,87 @@ impl PayableScanner { } } - fn separate_existent_and_nonexistent_fingerprints<'a>( - &'a self, - sent_payables: &[&'a PendingPayable], - ) -> (Vec, Vec) { + fn check_for_missing_records( + &self, + sent_payables: &[&PendingPayable], + ) -> Vec { let actual_sent_payables_simple_total = sent_payables.len(); - - let actual_sent_payable_hashes_hashset = sent_payables + let hashset_with_runtime_data_sent_payable_hashes = sent_payables .iter() .map(|pending_payable| pending_payable.hash) .collect::>(); - - if actual_sent_payable_hashes_hashset.len() != actual_sent_payables_simple_total { + if hashset_with_runtime_data_sent_payable_hashes.len() != actual_sent_payables_simple_total { todo!("check potential duplicity") } - + let transaction_hashes_and_rowids_from_db = self.sent_payable_dao.get_tx_identifiers(&hashset_with_runtime_data_sent_payable_hashes); + let hashes_from_db = transaction_hashes_and_rowids_from_db + .keys() + .collect::>(); + let missing_sent_payables_hashes:Vec = hashset_with_runtime_data_sent_payable_hashes.difference(&hashes_from_db).collect(); let mut sent_payables_hashmap = sent_payables .iter() .map(|payable| (payable.hash, &payable.recipient_wallet)) .collect::>(); - - let transaction_hashes = self.sent_payable_dao.get_tx_identifiers(&actual_sent_payable_hashes_hashset); - let - let hashes_from_db = transaction_hashes - .keys() - .collect::>(); - - let missing_sent_payables_hashes = actual_sent_payable_hashes_hashset.difference(&hashes_from_db).collect(); - - let pending_payables_with_rowid = transaction_hashes - .rowid_results - .into_iter() - .map(|(rowid, hash)| { - let wallet = sent_payables_hashmap - .remove(&hash) - .expect("expect transaction hash, but it disappear"); - PendingPayableMissingInDb::new(wallet, hash, Some(rowid)) - }) - .collect_vec(); - let pending_payables_without_rowid = transaction_hashes - .no_rowid_results - .into_iter() - .map(|hash| { - let wallet = sent_payables_hashmap - .remove(&hash) - .expect("expect transaction hash, but it disappear"); - PendingPayableMissingInDb::new(wallet, hash, None) - }) - .collect_vec(); - - (pending_payables_with_rowid, pending_payables_without_rowid) + missing_sent_payables_hashes.into_iter().map(|hash| { + let wallet_address = sent_payables_hashmap + .remove(&hash) + .expectv("wallet") + .address(); + PendingPayableMissingInDb::new(wallet_address, hash) + }).collect() } + // fn separate_existent_and_nonexistent_fingerprints<'a>( + // &'a self, + // sent_payables: &[&'a PendingPayable], + // ) -> (Vec, Vec) { + // let actual_sent_payables_simple_total = sent_payables.len(); + // + // let hashset_with_actual_sent_payable_hashes = sent_payables + // .iter() + // .map(|pending_payable| pending_payable.hash) + // .collect::>(); + // + // if hashset_with_actual_sent_payable_hashes.len() != actual_sent_payables_simple_total { + // todo!("check potential duplicity") + // } + // + // let mut sent_payables_hashmap = sent_payables + // .iter() + // .map(|payable| (payable.hash, &payable.recipient_wallet)) + // .collect::>(); + // + // let transaction_hashes = self.sent_payable_dao.get_tx_identifiers(&hashset_with_actual_sent_payable_hashes); + // let hashes_from_db = transaction_hashes + // .keys() + // .collect::>(); + // + // let missing_sent_payables_hashes = hashset_with_actual_sent_payable_hashes.difference(&hashes_from_db).collect(); + // + // let pending_payables_with_rowid = transaction_hashes + // .rowid_results + // .into_iter() + // .map(|(rowid, hash)| { + // let wallet = sent_payables_hashmap + // .remove(&hash) + // .expect("expect transaction hash, but it disappear"); + // PendingPayableMissingInDb::new(wallet, hash, Some(rowid)) + // }) + // .collect_vec(); + // let pending_payables_without_rowid = transaction_hashes + // .no_rowid_results + // .into_iter() + // .map(|hash| { + // let wallet = sent_payables_hashmap + // .remove(&hash) + // .expect("expect transaction hash, but it disappear"); + // PendingPayableMissingInDb::new(wallet, hash, None) + // }) + // .collect_vec(); + // + // (pending_payables_with_rowid, pending_payables_without_rowid) + // } + fn mark_pending_payable(&self, sent_payments: &[&PendingPayable], logger: &Logger) { fn missing_fingerprints_msg(nonexistent: &[PendingPayableMissingInDb]) -> String { format!( @@ -748,14 +777,13 @@ impl PayableScanner { } let (existent, nonexistent) = - self.separate_existent_and_nonexistent_fingerprints(sent_payments); + self.check_for_missing_records(sent_payments); - // let mark_pp_input_data = ready_data_for_supply(&existent); - if !mark_pp_input_data.is_empty() { + if !existent.is_empty() { if let Err(e) = self .payable_dao .as_ref() - .mark_pending_payables_rowids(&mark_pp_input_data) + .mark_pending_payables_rowids(&existent) { mark_pending_payable_fatal_error( sent_payments, @@ -1421,12 +1449,13 @@ mod tests { use regex::{Regex}; use rusqlite::{ffi, ErrorCode}; use std::cell::RefCell; - use std::collections::HashSet; + use std::collections::{HashMap, HashSet}; use std::ops::Sub; use std::panic::{catch_unwind, AssertUnwindSafe}; use std::rc::Rc; use std::sync::{Arc, Mutex}; use std::time::{Duration, SystemTime}; + use secp256k1secrets::ecdh::SharedSecret; use web3::types::{TransactionReceipt, H256}; use web3::Error; use masq_lib::messages::ScanType; @@ -1871,9 +1900,9 @@ mod tests { fn payable_scanner_handles_sent_payable_message() { init_test_logging(); let test_name = "payable_scanner_handles_sent_payable_message"; - let fingerprints_rowids_params_arc = Arc::new(Mutex::new(vec![])); + let tx_rowids_params_arc = Arc::new(Mutex::new(vec![])); let mark_pending_payables_params_arc = Arc::new(Mutex::new(vec![])); - let delete_fingerprints_params_arc = Arc::new(Mutex::new(vec![])); + let delete_records_params_arc = Arc::new(Mutex::new(vec![])); let correct_payable_hash_1 = make_tx_hash(0x6f); let correct_payable_rowid_1 = 125; let correct_payable_wallet_1 = make_wallet("tralala"); @@ -1895,20 +1924,13 @@ mod tests { let correct_pending_payable_3 = PendingPayable::new(correct_payable_wallet_3.clone(), correct_payable_hash_3); let sent_payable_dao = SentPayableDaoMock::default() - .fingerprints_rowids_params(&fingerprints_rowids_params_arc) - .fingerprints_rowids_result(TransactionHashes { - rowid_results: vec![ - (correct_payable_rowid_3, correct_payable_hash_3), - (correct_payable_rowid_1, correct_payable_hash_1), - ], - no_rowid_results: vec![], - }) - .fingerprints_rowids_result(TransactionHashes { - rowid_results: vec![(failure_payable_rowid_2, failure_payable_hash_2)], - no_rowid_results: vec![], - }) - .delete_fingerprints_params(&delete_fingerprints_params_arc) - .delete_fingerprints_result(Ok(())); + .get_tx_identifiers_params(&tx_rowids_params_arc) + .get_tx_identifiers_result(hashmap!(correct_payable_hash_3 => correct_payable_rowid_3, + correct_payable_hash_1 => correct_payable_rowid_1, + )) + .get_tx_identifiers_result(hashmap!(failure_payable_hash_2 => failure_payable_rowid_2)) + .delete_records_params(&delete_records_params_arc) + .delete_records_result(Ok(())); let payable_dao = PayableDaoMock::new() .mark_pending_payables_rowids_params(&mark_pending_payables_params_arc) .mark_pending_payables_rowids_result(Ok(())) @@ -1946,12 +1968,12 @@ mod tests { assert_eq!(is_scan_running, false); assert_eq!(aware_of_unresolved_pending_payable_before, false); assert_eq!(aware_of_unresolved_pending_payable_after, true); - let fingerprints_rowids_params = fingerprints_rowids_params_arc.lock().unwrap(); + let tx_rowids_params = tx_rowids_params_arc.lock().unwrap(); assert_eq!( - *fingerprints_rowids_params, + *tx_rowids_params, vec![ - vec![correct_payable_hash_1, correct_payable_hash_3], - vec![failure_payable_hash_2] + hashset![correct_payable_hash_1, correct_payable_hash_3], + hashset![failure_payable_hash_2] ] ); let mark_pending_payables_params = mark_pending_payables_params_arc.lock().unwrap(); @@ -1962,10 +1984,10 @@ mod tests { (correct_payable_wallet_1, correct_payable_rowid_1), ]] ); - let delete_fingerprints_params = delete_fingerprints_params_arc.lock().unwrap(); + let delete_fingerprints_params = delete_records_params_arc.lock().unwrap(); assert_eq!( *delete_fingerprints_params, - vec![vec![failure_payable_rowid_2]] + vec![hashset![failure_payable_hash_2]] ); let log_handler = TestLogHandler::new(); log_handler.assert_logs_contain_in_order(vec![ @@ -2009,24 +2031,21 @@ mod tests { .iter() .collect::>(); let sent_payable_dao = - SentPayableDaoMock::new().fingerprints_rowids_result(TransactionHashes { - rowid_results: vec![(4, hash_4), (1, hash_1), (3, hash_3), (2, hash_2)], - no_rowid_results: vec![], - }); + SentPayableDaoMock::new().get_tx_identifiers_result(hashmap!(hash_4 => 4, hash_1 => 1, hash_3 => 3, hash_2 => 2)); let subject = PayableScannerBuilder::new() .sent_payable_dao(sent_payable_dao) .build(); let (existent, nonexistent) = - subject.separate_existent_and_nonexistent_fingerprints(&pending_payables_ref); + subject.check_for_missing_records(&pending_payables_ref); assert_eq!( existent, vec![ - PendingPayableMissingInDb::new(&wallet_4, hash_4, Some(4)), - PendingPayableMissingInDb::new(&wallet_1, hash_1, Some(1)), - PendingPayableMissingInDb::new(&wallet_3, hash_3, Some(3)), - PendingPayableMissingInDb::new(&wallet_2, hash_2, Some(2)), + PendingPayableMissingInDb::new(wallet_4.address(), hash_4), + PendingPayableMissingInDb::new(wallet_1.address(), hash_1), + PendingPayableMissingInDb::new(wallet_3.address(), hash_3), + PendingPayableMissingInDb::new(wallet_2.address(), hash_2), ] ); assert!(nonexistent.is_empty()) @@ -2075,25 +2094,26 @@ mod tests { no_rowid_results: [] }" )] fn two_sourced_information_of_new_pending_payables_and_their_fingerprints_is_not_symmetrical() { - let vals = prepare_values_for_mismatched_setting(); - let pending_payables_ref = vals - .pending_payables - .iter() - .collect::>(); - let sent_payable_dao = - SentPayableDaoMock::new().fingerprints_rowids_result(TransactionHashes { - rowid_results: vec![ - (4, vals.common_hash_1), - (1, vals.intruder_for_hash_2), - (3, vals.common_hash_3), - ], - no_rowid_results: vec![], - }); - let subject = PayableScannerBuilder::new() - .sent_payable_dao(sent_payable_dao) - .build(); - - subject.separate_existent_and_nonexistent_fingerprints(&pending_payables_ref); + todo!("rewrite me to test duplicity, mavbeeee?") + // let vals = prepare_values_for_mismatched_setting(); + // let pending_payables_ref = vals + // .pending_payables + // .iter() + // .collect::>(); + // // let sent_payable_dao = + // // SentPayableDaoMock::new().get_tx_identifiers_result(hashmap!(vals.common_hash_1 => 1, vals.common_hash_3 => 3)); + // // rowid_results: vec![ + // // (4, vals.common_hash_1), + // // (1, vals.intruder_for_hash_2), + // // (3, vals.common_hash_3), + // // ], + // // no_rowid_results: vec![], + // // }); + // let subject = PayableScannerBuilder::new() + // .sent_payable_dao(sent_payable_dao) + // .build(); + // + // subject.check_for_missing_records(&pending_payables_ref); } #[test] @@ -2185,10 +2205,7 @@ mod tests { let hash_2 = make_tx_hash(0x7b); let payment_2 = PendingPayable::new(make_wallet("agoob"), hash_2); let sent_payable_dao = - SentPayableDaoMock::default().fingerprints_rowids_result(TransactionHashes { - rowid_results: vec![], - no_rowid_results: vec![hash_1, hash_2], - }); + SentPayableDaoMock::default().get_tx_identifiers_result(hashmap!()); let payable_dao = PayableDaoMock::new(); let mut subject = PayableScannerBuilder::new() .payable_dao(payable_dao) @@ -2249,10 +2266,7 @@ mod tests { let hash_1 = make_tx_hash(248); let hash_2 = make_tx_hash(139); let sent_payable_dao = - SentPayableDaoMock::default().fingerprints_rowids_result(TransactionHashes { - rowid_results: vec![(7879, hash_1), (7881, hash_2)], - no_rowid_results: vec![], - }); + SentPayableDaoMock::default().get_tx_identifiers_result(hashmap!(hash_1 => 7879, hash_2 => 7881)); assert_panic_from_failing_to_mark_pending_payable_rowid( test_name, @@ -2273,11 +2287,7 @@ mod tests { let hash_1 = make_tx_hash(0xff); let hash_2 = make_tx_hash(0xf8); let sent_payable_dao = - SentPayableDaoMock::default().fingerprints_rowids_result(TransactionHashes { - rowid_results: vec![(7881, hash_1)], - no_rowid_results: vec![hash_2], - }); - + SentPayableDaoMock::default().get_tx_identifiers_result(hashmap!(hash_1 => 7881)); assert_panic_from_failing_to_mark_pending_payable_rowid( test_name, sent_payable_dao, From 0071b8e79c106aa97e49de0f7c63f8bfb70600ea Mon Sep 17 00:00:00 2001 From: Bert Date: Wed, 2 Jul 2025 19:23:04 +0200 Subject: [PATCH 03/61] GH-642: interim commit --- .../db_access_objects/payable_dao.rs | 147 +++++++++--------- node/src/accountant/mod.rs | 29 ++-- node/src/accountant/scanners/mod.rs | 62 +++----- .../src/accountant/scanners/scanners_utils.rs | 10 +- node/src/accountant/test_utils.rs | 70 +++++---- node/src/blockchain/blockchain_bridge.rs | 2 + .../lower_level_interface_web3.rs | 7 +- .../blockchain_interface_web3/utils.rs | 1 + .../data_structures/mod.rs | 1 + 9 files changed, 157 insertions(+), 172 deletions(-) diff --git a/node/src/accountant/db_access_objects/payable_dao.rs b/node/src/accountant/db_access_objects/payable_dao.rs index 6a1cd61bf..0f3cd9282 100644 --- a/node/src/accountant/db_access_objects/payable_dao.rs +++ b/node/src/accountant/db_access_objects/payable_dao.rs @@ -25,7 +25,7 @@ use std::time::SystemTime; use ethabi::Address; use itertools::Either; use web3::types::H256; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::ConfirmedTx; +use crate::accountant::db_access_objects::sent_payable_dao::SentTx; #[derive(Debug, PartialEq, Eq)] pub enum PayableDaoError { @@ -51,12 +51,12 @@ pub trait PayableDao: Debug + Send { fn mark_pending_payables_rowids( &self, - wallets_and_rowids: &[MarkOfPendingPayable], + mark_instructions: &[MarkOfPendingPayable], ) -> Result<(), PayableDaoError>; fn transactions_confirmed( &self, - confirmed_payables: &[ConfirmedTx], + confirmed_payables: &[SentTx], ) -> Result<(), PayableDaoError>; fn non_pending_payables(&self) -> Vec; @@ -132,55 +132,57 @@ impl PayableDao for PayableDaoReal { fn mark_pending_payables_rowids( &self, - wallets_and_rowids: &[(&Wallet, u64)], + mark_instructions: &[MarkOfPendingPayable], ) -> Result<(), PayableDaoError> { - if wallets_and_rowids.is_empty() { - panic!("broken code: empty input is not permit to enter this method") - } - - let case_expr = compose_case_expression(wallets_and_rowids); - let wallets = serialize_wallets(wallets_and_rowids, Some('\'')); - //the Wallet type is secure against SQL injections - let sql = format!( - "update payable set \ - pending_payable_rowid = {} \ - where - pending_payable_rowid is null and wallet_address in ({}) - returning - pending_payable_rowid", - case_expr, wallets, - ); - execute_command(&*self.conn, wallets_and_rowids, &sql) + todo!() + // if wallets_and_rowids.is_empty() { + // panic!("broken code: empty input is not permit to enter this method") + // } + // + // let case_expr = compose_case_expression(wallets_and_rowids); + // let wallets = serialize_wallets(wallets_and_rowids, Some('\'')); + // //the Wallet type is secure against SQL injections + // let sql = format!( + // "update payable set \ + // pending_payable_rowid = {} \ + // where + // pending_payable_rowid is null and wallet_address in ({}) + // returning + // pending_payable_rowid", + // case_expr, wallets, + // ); + // execute_command(&*self.conn, wallets_and_rowids, &sql) } fn transactions_confirmed( &self, - confirmed_payables: &[ConfirmedTx], + confirmed_payables: &[SentTx], ) -> Result<(), PayableDaoError> { - confirmed_payables.iter().try_for_each(|pending_payable_fingerprint| { - - let main_sql = "update payable set \ - balance_high_b = balance_high_b + :balance_high_b, balance_low_b = balance_low_b + :balance_low_b, \ - last_paid_timestamp = :last_paid, pending_payable_rowid = null where pending_payable_rowid = :rowid"; - let update_clause_with_compensated_overflow = "update payable set \ - balance_high_b = :balance_high_b, balance_low_b = :balance_low_b, last_paid_timestamp = :last_paid, \ - pending_payable_rowid = null where pending_payable_rowid = :rowid"; - - let i64_rowid = checked_conversion::(pending_payable_fingerprint.rowid); - let last_paid = to_unix_timestamp(pending_payable_fingerprint.timestamp); - let params = SQLParamsBuilder::default() - .key( PendingPayableRowid(&i64_rowid)) - .wei_change(WeiChange::new( "balance", pending_payable_fingerprint.amount, WeiChangeDirection::Subtraction)) - .other_params(vec![ParamByUse::BeforeAndAfterOverflow(DisplayableRusqliteParamPair::new(":last_paid", &last_paid))]) - .build(); - - self.big_int_db_processor.execute(Either::Left(self.conn.as_ref()), BigIntSqlConfig::new( - main_sql, - update_clause_with_compensated_overflow, - params))?; - - Ok(()) - }) + todo!() + // confirmed_payables.iter().try_for_each(|pending_payable_fingerprint| { + // + // let main_sql = "update payable set \ + // balance_high_b = balance_high_b + :balance_high_b, balance_low_b = balance_low_b + :balance_low_b, \ + // last_paid_timestamp = :last_paid, pending_payable_rowid = null where pending_payable_rowid = :rowid"; + // let update_clause_with_compensated_overflow = "update payable set \ + // balance_high_b = :balance_high_b, balance_low_b = :balance_low_b, last_paid_timestamp = :last_paid, \ + // pending_payable_rowid = null where pending_payable_rowid = :rowid"; + // + // let i64_rowid = checked_conversion::(pending_payable_fingerprint.rowid); + // let last_paid = to_unix_timestamp(pending_payable_fingerprint.timestamp); + // let params = SQLParamsBuilder::default() + // .key( PendingPayableRowid(&i64_rowid)) + // .wei_change(WeiChange::new( "balance", pending_payable_fingerprint.amount, WeiChangeDirection::Subtraction)) + // .other_params(vec![ParamByUse::BeforeAndAfterOverflow(DisplayableRusqliteParamPair::new(":last_paid", &last_paid))]) + // .build(); + // + // self.big_int_db_processor.execute(Either::Left(self.conn.as_ref()), BigIntSqlConfig::new( + // main_sql, + // update_clause_with_compensated_overflow, + // params))?; + // + // Ok(()) + // }) } fn non_pending_payables(&self) -> Vec { @@ -553,7 +555,7 @@ mod tests { use crate::accountant::db_access_objects::utils::{from_unix_timestamp, current_unix_timestamp, to_unix_timestamp}; use crate::accountant::gwei_to_wei; use crate::accountant::db_access_objects::payable_dao::mark_pending_payable_associated_functions::explanatory_extension; - use crate::accountant::test_utils::{assert_account_creation_fn_fails_on_finding_wrong_columns_and_value_types, make_pending_payable_fingerprint, trick_rusqlite_with_read_only_conn}; + use crate::accountant::test_utils::{assert_account_creation_fn_fails_on_finding_wrong_columns_and_value_types, make_payable_account, make_pending_payable_fingerprint, make_sent_tx, trick_rusqlite_with_read_only_conn}; use crate::blockchain::test_utils::make_tx_hash; use crate::database::rusqlite_wrappers::ConnectionWrapperReal; use crate::database::db_initializer::{ @@ -1016,48 +1018,43 @@ mod tests { balance_change_2, ); let subject = PayableDaoReal::new(boxed_conn); - let status_1_before_opt = subject.account_status(&setup_holder.wallet_1); - let status_2_before_opt = subject.account_status(&setup_holder.wallet_2); + let status_1_before_opt = subject.account_status(&setup_holder.account_1.wallet); + let status_2_before_opt = subject.account_status(&setup_holder.account_2.wallet); let result = subject.transactions_confirmed(&[ - setup_holder.fingerprint_1.clone(), - setup_holder.fingerprint_2.clone(), + setup_holder.account_1.pending_payable.clone(), + setup_holder.account_2.pending_payable.clone(), ]); assert_eq!(result, Ok(())); + // TODO yes these are unsensible now but it will eventually be all cleaned up with GH-662 let expected_status_before_1 = PayableAccount { - wallet: setup_holder.wallet_1.clone(), + wallet: setup_holder.account_1.wallet.clone(), balance_wei: initial_amount_1, - last_paid_timestamp: setup_holder.previous_timestamp_1, - pending_payable_opt: Some(PendingPayableId::new( - setup_holder.fingerprint_1.rowid, - H256::from_uint(&U256::from(0)), - )), //hash is just garbage + last_paid_timestamp: setup_holder.account_1.previous_timestamp, + pending_payable_opt: None, //hash is just garbage }; let expected_status_before_2 = PayableAccount { - wallet: setup_holder.wallet_2.clone(), + wallet: setup_holder.account_2.wallet.clone(), balance_wei: initial_amount_2, - last_paid_timestamp: setup_holder.previous_timestamp_2, - pending_payable_opt: Some(PendingPayableId::new( - setup_holder.fingerprint_2.rowid, - H256::from_uint(&U256::from(0)), - )), //hash is just garbage + last_paid_timestamp: setup_holder.account_2.previous_timestamp, + pending_payable_opt: None, //hash is just garbage }; let expected_resulting_status_1 = PayableAccount { - wallet: setup_holder.wallet_1.clone(), + wallet: setup_holder.account_1.wallet.clone(), balance_wei: expected_balance_after_1, - last_paid_timestamp: setup_holder.fingerprint_1.timestamp, + last_paid_timestamp: setup_holder.account_1.previous_timestamp, pending_payable_opt: None, }; let expected_resulting_status_2 = PayableAccount { - wallet: setup_holder.wallet_2.clone(), + wallet: setup_holder.account_2.wallet, balance_wei: expected_balance_after_2, - last_paid_timestamp: setup_holder.fingerprint_2.timestamp, + last_paid_timestamp: setup_holder.account_2.previous_timestamp, pending_payable_opt: None, }; assert_eq!(status_1_before_opt, Some(expected_status_before_1)); assert_eq!(status_2_before_opt, Some(expected_status_before_2)); - let resulting_account_1_opt = subject.account_status(&setup_holder.wallet_1); + let resulting_account_1_opt = subject.account_status(&setup_holder.account_1.wallet); assert_eq!(resulting_account_1_opt, Some(expected_resulting_status_1)); let resulting_account_2_opt = subject.account_status(&setup_holder.wallet_2); assert_eq!(resulting_account_2_opt, Some(expected_resulting_status_2)) @@ -1071,21 +1068,17 @@ mod tests { ); let conn = payable_read_only_conn(&home_dir); let conn_wrapped = Box::new(ConnectionWrapperReal::new(conn)); - let mut pending_payable_fingerprint = make_pending_payable_fingerprint(); - let hash = make_tx_hash(12345); - let rowid = 789; - pending_payable_fingerprint.hash = hash; - pending_payable_fingerprint.rowid = rowid; + let confirmed_transaction = make_sent_tx(5); + let wallet_address = confirmed_transaction.receiver_address; let subject = PayableDaoReal::new(conn_wrapped); - let result = subject.transactions_confirmed(&[pending_payable_fingerprint]); + let result = subject.transactions_confirmed(&[confirmed_transaction]); assert_eq!( result, Err(PayableDaoError::RusqliteError( - "Error from invalid update command for payable table and change of -12345 wei to \ - 'pending_payable_rowid = 789' with error 'attempt to write a readonly database'" - .to_string() + format!("Error from invalid update command for payable table and change of -12345 wei to \ + creditor {} with error 'attempt to write a readonly database'", wallet_address) )) ) } diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index 5920cdc35..a1c76e208 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -1241,7 +1241,7 @@ mod tests { use crate::accountant::test_utils::DaoWithDestination::{ ForAccountantBody, ForPayableScanner, ForPendingPayableScanner, ForReceivableScanner, }; - use crate::accountant::test_utils::{bc_from_earning_wallet, bc_from_wallets, make_payable_account, make_qualified_and_unqualified_payables, make_pending_payable_fingerprint, BannedDaoFactoryMock, ConfigDaoFactoryMock, MessageIdGeneratorMock, PayableDaoFactoryMock, PayableDaoMock, PayableScannerBuilder, PaymentAdjusterMock, PendingPayableDaoFactoryMock, SentPayableDaoMock, ReceivableDaoFactoryMock, ReceivableDaoMock}; + use crate::accountant::test_utils::{bc_from_earning_wallet, bc_from_wallets, make_payable_account, make_qualified_and_unqualified_payables, make_pending_payable_fingerprint, BannedDaoFactoryMock, ConfigDaoFactoryMock, MessageIdGeneratorMock, PayableDaoFactoryMock, PayableDaoMock, PayableScannerBuilder, PaymentAdjusterMock, SentPayableDaoFactoryMock, SentPayableDaoMock, ReceivableDaoFactoryMock, ReceivableDaoMock}; use crate::accountant::test_utils::{AccountantBuilder, BannedDaoMock}; use crate::accountant::Accountant; use crate::blockchain::blockchain_interface::blockchain_interface_web3::HashAndAmount; @@ -1299,7 +1299,7 @@ mod tests { use crate::accountant::db_access_objects::sent_payable_dao::{SentPayableDaoError, SentTx}; use crate::accountant::scanners::scan_schedulers::{NewPayableScanDynIntervalComputer, NewPayableScanDynIntervalComputerReal}; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{OperationOutcome, PayableScanResult}; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{ConfirmedTx, TransactionBlock, TxReceipt, TxStatus}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, TxReceipt, TxStatus}; use crate::test_utils::recorder_counter_msgs::SingleTypeCounterMsgSetup; impl Handler> for Accountant { @@ -1333,7 +1333,7 @@ mod tests { .make_result(PayableDaoMock::new()) // For Accountant .make_result(PayableDaoMock::new()) // For Payable Scanner .make_result(PayableDaoMock::new()); // For PendingPayable Scanner - let sent_payable_dao_factory = PendingPayableDaoFactoryMock::new() + let sent_payable_dao_factory = SentPayableDaoFactoryMock::new() .make_params(&pending_payable_dao_factory_params_arc) .make_result(SentPayableDaoMock::new()) // For Accountant .make_result(SentPayableDaoMock::new()) // For Payable Scanner @@ -1386,7 +1386,7 @@ mod tests { .make_result(PayableDaoMock::new()), // For PendingPayable Scanner ); let sent_payable_dao_factory = Box::new( - PendingPayableDaoFactoryMock::new() + SentPayableDaoFactoryMock::new() .make_result(SentPayableDaoMock::new()) // For Accountant .make_result(SentPayableDaoMock::new()) // For Payable Scanner .make_result(SentPayableDaoMock::new()), // For PendingPayable Scanner @@ -1565,11 +1565,9 @@ mod tests { #[test] fn sent_payable_with_response_skeleton_sends_scan_response_to_ui_gateway() { let config = bc_from_earning_wallet(make_wallet("earning_wallet")); + let tx_hash = make_tx_hash(123); let sent_payable_dao = - SentPayableDaoMock::default().fingerprints_rowids_result(TransactionHashes { - rowid_results: vec![(1, make_tx_hash(123))], - no_rowid_results: vec![], - }); + SentPayableDaoMock::default().get_tx_identifiers_result(hashmap! (tx_hash => 1)); let payable_dao = PayableDaoMock::default().mark_pending_payables_rowids_result(Ok(())); let mut subject = AccountantBuilder::default() .pending_payable_daos(vec![ForPayableScanner(sent_payable_dao)]) @@ -1586,7 +1584,7 @@ mod tests { let sent_payable = SentPayables { payment_procedure_result: Ok(vec![ProcessedPayableFallible::Correct(PendingPayable { recipient_wallet: make_wallet("blah"), - hash: make_tx_hash(123), + hash: tx_hash, })]), response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, @@ -4803,18 +4801,15 @@ mod tests { #[test] fn accountant_processes_sent_payables_and_schedules_pending_payable_scanner() { - let fingerprints_rowids_params_arc = Arc::new(Mutex::new(vec![])); + let get_tx_identifiers_params_arc = Arc::new(Mutex::new(vec![])); let mark_pending_payables_rowids_params_arc = Arc::new(Mutex::new(vec![])); let pending_payable_notify_later_params_arc = Arc::new(Mutex::new(vec![])); let expected_wallet = make_wallet("paying_you"); let expected_hash = H256::from("transaction_hash".keccak256()); let expected_rowid = 45623; let sent_payable_dao = SentPayableDaoMock::default() - .fingerprints_rowids_params(&fingerprints_rowids_params_arc) - .fingerprints_rowids_result(TransactionHashes { - rowid_results: vec![(expected_rowid, expected_hash)], - no_rowid_results: vec![], - }); + .get_tx_identifiers_params(&get_tx_identifiers_params_arc) + .get_tx_identifiers_result(hashmap! (expected_hash => expected_rowid)); let payable_dao = PayableDaoMock::new() .mark_pending_payables_rowids_params(&mark_pending_payables_rowids_params_arc) .mark_pending_payables_rowids_result(Ok(())); @@ -4844,8 +4839,8 @@ mod tests { System::current().stop(); system.run(); - let fingerprints_rowids_params = fingerprints_rowids_params_arc.lock().unwrap(); - assert_eq!(*fingerprints_rowids_params, vec![vec![expected_hash]]); + let get_tx_identifiers_params = get_tx_identifiers_params_arc.lock().unwrap(); + assert_eq!(*get_tx_identifiers_params, vec![hashset!(expected_hash)]); let mark_pending_payables_rowids_params = mark_pending_payables_rowids_params_arc.lock().unwrap(); assert_eq!( diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index d853ae7a1..e1d2da17b 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -45,12 +45,11 @@ use time::OffsetDateTime; use variant_count::VariantCount; use web3::types::H256; use crate::accountant::db_access_objects::failed_payable_dao::FailedTx; -use crate::accountant::db_access_objects::sent_payable_dao::SentTx; +use crate::accountant::db_access_objects::sent_payable_dao::{SentPayableDao, SentTx}; use crate::accountant::db_access_objects::utils::{TxHash, TxIdentifiers}; use crate::accountant::scanners::payable_scanner_extension::{MultistageDualPayableScanner, PreparedAdjustment, SolvencySensitivePaymentInstructor}; use crate::accountant::scanners::payable_scanner_extension::msgs::{BlockchainAgentWithContextMessage, QualifiedPayablesMessage}; -use crate::accountant::test_utils::SentPayableDao; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{ConfirmedTx, TransactionReceiptResult, TxStatus}; +use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionReceiptResult, TxStatus}; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; use crate::db_config::persistent_configuration::{PersistentConfiguration, PersistentConfigurationReal}; @@ -1074,7 +1073,7 @@ impl PendingPayableScanner { fn confirm_transactions( &mut self, - fingerprints: Vec, + fingerprints: Vec, logger: &Logger, ) { // fn serialize_hashes(fingerprints: &[SentTx]) -> String { @@ -1109,8 +1108,8 @@ impl PendingPayableScanner { fn add_to_the_total_of_paid_payable( &mut self, - fingerprints: &[ConfirmedTx], - serialize_hashes: fn(&[ConfirmedTx]) -> String, + confirmed_payments: &[SentTx], + serialize_hashes: fn(&[SentTx]) -> String, logger: &Logger, ) { todo!() @@ -1423,7 +1422,7 @@ mod tests { use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{OperationOutcome, PayableScanResult, PendingPayableMissingInDb}; use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_status_with_failure, PendingPayableScanReport, PendingPayableScanResult}; use crate::accountant::scanners::{Scanner, StartScanError, StartableScanner, PayableScanner, PendingPayableScanner, ReceivableScanner, ScannerCommon, Scanners, MTError}; - use crate::accountant::test_utils::{make_custom_payment_thresholds, make_payable_account, make_qualified_and_unqualified_payables, make_pending_payable_fingerprint, make_receivable_account, BannedDaoFactoryMock, BannedDaoMock, ConfigDaoFactoryMock, PayableDaoFactoryMock, PayableDaoMock, PayableScannerBuilder, PayableThresholdsGaugeMock, PendingPayableDaoFactoryMock, SentPayableDaoMock, PendingPayableScannerBuilder, ReceivableDaoFactoryMock, ReceivableDaoMock, ReceivableScannerBuilder}; + use crate::accountant::test_utils::{make_custom_payment_thresholds, make_payable_account, make_qualified_and_unqualified_payables, make_pending_payable_fingerprint, make_receivable_account, BannedDaoFactoryMock, BannedDaoMock, ConfigDaoFactoryMock, PayableDaoFactoryMock, PayableDaoMock, PayableScannerBuilder, PayableThresholdsGaugeMock, SentPayableDaoFactoryMock, SentPayableDaoMock, PendingPayableScannerBuilder, ReceivableDaoFactoryMock, ReceivableDaoMock, ReceivableScannerBuilder}; use crate::accountant::{gwei_to_wei, PendingPayableId, ReceivedPayments, ReportTransactionReceipts, RequestTransactionReceipts, ScanError, ScanForRetryPayables, SentPayables, DEFAULT_PENDING_TOO_LONG_SEC, PendingPayable}; use crate::blockchain::blockchain_bridge::{BlockMarker, RetrieveTransactions}; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; @@ -1546,7 +1545,7 @@ mod tests { let payable_dao_factory = PayableDaoFactoryMock::new() .make_result(PayableDaoMock::new()) .make_result(PayableDaoMock::new()); - let sent_payable_dao_factory = PendingPayableDaoFactoryMock::new() + let sent_payable_dao_factory = SentPayableDaoFactoryMock::new() .make_result(SentPayableDaoMock::new()) .make_result(SentPayableDaoMock::new()); let receivable_dao = ReceivableDaoMock::new(); @@ -2305,24 +2304,18 @@ mod tests { init_test_logging(); let test_name = "payable_scanner_is_facing_failed_transactions_and_their_fingerprints_exist"; - let fingerprints_rowids_params_arc = Arc::new(Mutex::new(vec![])); - let delete_fingerprints_params_arc = Arc::new(Mutex::new(vec![])); + let get_tx_identifiers_params_arc = Arc::new(Mutex::new(vec![])); + let delete_records_params_arc = Arc::new(Mutex::new(vec![])); let hash_tx_1 = make_tx_hash(0x15b3); let hash_tx_2 = make_tx_hash(0x3039); let first_fingerprint_rowid = 3; let second_fingerprint_rowid = 5; let system = System::new(test_name); let sent_payable_dao = SentPayableDaoMock::default() - .fingerprints_rowids_params(&fingerprints_rowids_params_arc) - .fingerprints_rowids_result(TransactionHashes { - rowid_results: vec![ - (first_fingerprint_rowid, hash_tx_1), - (second_fingerprint_rowid, hash_tx_2), - ], - no_rowid_results: vec![], - }) - .delete_fingerprints_params(&delete_fingerprints_params_arc) - .delete_fingerprints_result(Ok(())); + .get_tx_identifiers_params(&get_tx_identifiers_params_arc) + .get_tx_identifiers_result(hashmap!(hash_tx_1 => first_fingerprint_rowid, hash_tx_2 => second_fingerprint_rowid)) + .delete_records_params(&delete_records_params_arc) + .delete_records_result(Ok(())); let payable_scanner = PayableScannerBuilder::new() .sent_payable_dao(sent_payable_dao) .build(); @@ -2353,15 +2346,15 @@ mod tests { ); assert_eq!(aware_of_unresolved_pending_payable_before, false); assert_eq!(aware_of_unresolved_pending_payable_after, false); - let fingerprints_rowids_params = fingerprints_rowids_params_arc.lock().unwrap(); + let fingerprints_rowids_params = get_tx_identifiers_params_arc.lock().unwrap(); assert_eq!( *fingerprints_rowids_params, - vec![vec![hash_tx_1, hash_tx_2]] + vec![hashset!(hash_tx_1, hash_tx_2)] ); - let delete_fingerprints_params = delete_fingerprints_params_arc.lock().unwrap(); + let delete_fingerprints_params = delete_records_params_arc.lock().unwrap(); assert_eq!( *delete_fingerprints_params, - vec![vec![first_fingerprint_rowid, second_fingerprint_rowid]] + vec![hashset!(hash_tx_1, hash_tx_2)] ); let log_handler = TestLogHandler::new(); log_handler.exists_log_containing(&format!("WARN: {test_name}: \ @@ -2423,11 +2416,8 @@ mod tests { response_skeleton_opt: None, }; let sent_payable_dao = SentPayableDaoMock::default() - .retrieve_txs_result(TransactionHashes { - rowid_results: vec![(rowid_1, hash_1), (rowid_2, hash_2)], - no_rowid_results: vec![], - }) - .delete_fingerprints_result(Err(SentPayableDaoError::SqlExecutionFailed( + .get_tx_identifiers_result(hashmap!(hash_1 => rowid_1, hash_2 => rowid_2)) + .delete_records_result(Err(SentPayableDaoError::SqlExecutionFailed( "Gosh, I overslept without an alarm set".to_string(), ))); let mut subject = PayableScannerBuilder::new() @@ -2461,11 +2451,8 @@ mod tests { let hash_2 = make_tx_hash(0x3039); let hash_3 = make_tx_hash(0x223d); let sent_payable_dao = SentPayableDaoMock::default() - .fingerprints_rowids_result(TransactionHashes { - rowid_results: vec![(333, hash_1)], - no_rowid_results: vec![hash_2, hash_3], - }) - .delete_fingerprints_result(Ok(())); + .get_tx_identifiers_result(hashmap!(hash_1 => 333)) + .delete_records_result(Ok(())); let mut subject = PayableScannerBuilder::new() .sent_payable_dao(sent_payable_dao) .build(); @@ -2509,11 +2496,8 @@ mod tests { let existent_record_hash = make_tx_hash(0xb26e); let nonexistent_record_hash = make_tx_hash(0x4d2); let sent_payable_dao = SentPayableDaoMock::default() - .fingerprints_rowids_result(TransactionHashes { - rowid_results: vec![(45, existent_record_hash)], - no_rowid_results: vec![nonexistent_record_hash], - }) - .delete_fingerprints_result(Err(SentPayableDaoError::SqlExecutionFailed( + .get_tx_identifiers_result(hashmap!(existent_record_hash => 45)) + .delete_records_result(Err(SentPayableDaoError::SqlExecutionFailed( "Another failure. Really???".to_string(), ))); let mut subject = PayableScannerBuilder::new() diff --git a/node/src/accountant/scanners/scanners_utils.rs b/node/src/accountant/scanners/scanners_utils.rs index 4ad7b2680..3f6e03c9a 100644 --- a/node/src/accountant/scanners/scanners_utils.rs +++ b/node/src/accountant/scanners/scanners_utils.rs @@ -318,17 +318,17 @@ pub mod payable_scanner_utils { } pub mod pending_payable_scanner_utils { - use crate::accountant::PendingPayableId; use masq_lib::logger::Logger; use masq_lib::ui_gateway::NodeToUiMessage; use std::time::SystemTime; use crate::accountant::db_access_objects::failed_payable_dao::FailedTx; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{ConfirmedTx, TxReceiptLocalError}; + use crate::accountant::db_access_objects::sent_payable_dao::SentTx; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxReceiptLocalError}; #[derive(Debug, Default, PartialEq, Eq, Clone)] pub struct PendingPayableScanReport { pub failures: Vec, - pub confirmed: Vec, + pub confirmed: Vec, } impl PendingPayableScanReport { @@ -390,9 +390,10 @@ pub mod pending_payable_scanner_utils { pub fn handle_successful_tx( mut scan_report: PendingPayableScanReport, - confirmed_tx: ConfirmedTx, + confirmed_tx: SentTx, logger: &Logger, ) -> PendingPayableScanReport { + todo!() // info!( // logger, // "Transaction {:?} has been added to the blockchain; detected locally at attempt \ @@ -426,6 +427,7 @@ pub mod pending_payable_scanner_utils { local_error: TxReceiptLocalError, logger: &Logger, ) -> PendingPayableScanReport { + todo!() // debug!( // logger, // "Interpreting a receipt for transaction {:?} but {}; attempt {}, {}ms since sending", diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs index f10a7a272..1d13531a0 100644 --- a/node/src/accountant/test_utils.rs +++ b/node/src/accountant/test_utils.rs @@ -3,12 +3,8 @@ #![cfg(test)] use crate::accountant::db_access_objects::banned_dao::{BannedDao, BannedDaoFactory}; -use crate::accountant::db_access_objects::payable_dao::{ - PayableAccount, PayableDao, PayableDaoError, PayableDaoFactory, -}; -use crate::accountant::db_access_objects::sent_payable_dao::{ - SentPayableDao, -}; +use crate::accountant::db_access_objects::payable_dao::{MarkOfPendingPayable, PayableAccount, PayableDao, PayableDaoError, PayableDaoFactory}; +use crate::accountant::db_access_objects::sent_payable_dao::{SentPayableDao, SentPayableDaoFactory}; use crate::accountant::db_access_objects::receivable_dao::{ ReceivableAccount, ReceivableDao, ReceivableDaoError, ReceivableDaoFactory, }; @@ -46,7 +42,7 @@ use std::rc::Rc; use std::sync::{Arc, Mutex}; use std::time::SystemTime; use crate::accountant::db_access_objects::sent_payable_dao::{RetrieveCondition, SentPayableDaoError, SentTx}; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::TransactionBlock; +use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock}; pub fn make_receivable_account(n: u64, expected_delinquent: bool) -> ReceivableAccount { let now = to_unix_timestamp(SystemTime::now()); @@ -84,13 +80,28 @@ pub fn make_payable_account_with_wallet_and_balance_and_timestamp_opt( } } +pub fn make_sent_tx(num: u64)-> SentTx { + if num == 0 { + panic!("num for generating must be greater than 0"); + } + SentTx{ + hash: make_tx_hash(num as u32), + receiver_address: make_wallet(&format!("wallet{}", num)).address(), + amount: gwei_to_wei(num * num), + timestamp: to_unix_timestamp(SystemTime::now()) - (num as i64 * 60), + gas_price_wei: gwei_to_wei(num), + nonce: num, + block_opt: None, + } +} + pub struct AccountantBuilder { config_opt: Option, consuming_wallet_opt: Option, logger_opt: Option, payable_dao_factory_opt: Option, receivable_dao_factory_opt: Option, - pending_payable_dao_factory_opt: Option, + sent_payable_dao_factory_opt: Option, banned_dao_factory_opt: Option, config_dao_factory_opt: Option, } @@ -103,7 +114,7 @@ impl Default for AccountantBuilder { logger_opt: None, payable_dao_factory_opt: None, receivable_dao_factory_opt: None, - pending_payable_dao_factory_opt: None, + sent_payable_dao_factory_opt: None, banned_dao_factory_opt: None, config_dao_factory_opt: None, } @@ -275,8 +286,8 @@ impl AccountantBuilder { create_or_update_factory!( specially_configured_daos, PENDING_PAYABLE_DAOS_ACCOUNTANT_INITIALIZATION_ORDER, - pending_payable_dao_factory_opt, - PendingPayableDaoFactoryMock, + sent_payable_dao_factory_opt, + SentPayableDaoFactoryMock, SentPayableDao, self ) @@ -342,8 +353,8 @@ impl AccountantBuilder { .make_result(ReceivableDaoMock::new()) .make_result(ReceivableDaoMock::new()), ); - let sent_payable_dao_factory = self.pending_payable_dao_factory_opt.unwrap_or( - PendingPayableDaoFactoryMock::new() + let sent_payable_dao_factory = self.sent_payable_dao_factory_opt.unwrap_or( + SentPayableDaoFactoryMock::new() .make_result(SentPayableDaoMock::new()) .make_result(SentPayableDaoMock::new()) .make_result(SentPayableDaoMock::new()), @@ -543,20 +554,21 @@ impl PayableDao for PayableDaoMock { fn mark_pending_payables_rowids( &self, - wallets_and_rowids: &[(&Wallet, u64)], + mark_instructions: &[MarkOfPendingPayable], ) -> Result<(), PayableDaoError> { - self.mark_pending_payables_rowids_params - .lock() - .unwrap() - .push( - wallets_and_rowids - .iter() - .map(|(wallet, id)| ((*wallet).clone(), *id)) - .collect(), - ); - self.mark_pending_payables_rowids_results - .borrow_mut() - .remove(0) + todo!() + // self.mark_pending_payables_rowids_params + // .lock() + // .unwrap() + // .push( + // mark_instructions + // .iter() + // .map(|(wallet, id)| ((*wallet).clone(), *id)) + // .collect(), + // ); + // self.mark_pending_payables_rowids_results + // .borrow_mut() + // .remove(0) } fn transactions_confirmed( @@ -1171,12 +1183,12 @@ impl SentPayableDaoMock { } } -pub struct PendingPayableDaoFactoryMock { +pub struct SentPayableDaoFactoryMock { make_params: Arc>>, make_results: RefCell>>, } -impl PendingPayableDaoFactory for PendingPayableDaoFactoryMock { +impl SentPayableDaoFactory for SentPayableDaoFactoryMock { fn make(&self) -> Box { if self.make_results.borrow().len() == 0 { panic!( @@ -1188,7 +1200,7 @@ impl PendingPayableDaoFactory for PendingPayableDaoFactoryMock { } } -impl PendingPayableDaoFactoryMock { +impl SentPayableDaoFactoryMock { pub fn new() -> Self { Self { make_params: Arc::new(Mutex::new(vec![])), diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index bc4af2e8a..de1bcecbd 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -593,6 +593,8 @@ mod tests { use std::time::{Duration, SystemTime}; use web3::types::{TransactionReceipt, H160}; use masq_lib::constants::DEFAULT_MAX_BLOCK_COUNT; + use crate::accountant::db_access_objects::sent_payable_dao::SentTx; + use crate::accountant::PendingPayable; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, TxReceipt}; impl Handler> for BlockchainBridge { diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs index 2c504f8f5..378ec7a81 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs @@ -15,6 +15,7 @@ use web3::types::{Address, BlockNumber, Filter, Log, TransactionReceipt}; use web3::{Error, Web3}; use crate::accountant::db_access_objects::failed_payable_dao::FailedTx; use crate::accountant::db_access_objects::utils::TxHash; +use crate::sub_lib::wallet::Wallet; #[derive(Debug, PartialEq, Eq, Clone)] pub enum TransactionReceiptResult { @@ -104,12 +105,6 @@ pub struct TxReceiptLocalError{ err_msg: String } -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct ConfirmedTx{ - pub tx_hash: TxHash, - pub block: TransactionBlock -} - #[derive(Debug, Default, PartialEq, Eq, Clone)] pub struct TransactionBlock { pub block_hash: H256, diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs index 78f7200e9..9f8bac478 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs @@ -26,6 +26,7 @@ use web3::transports::{Batch, Http}; use web3::types::{Bytes, SignedTransaction, TransactionParameters, U256}; use web3::Error as Web3Error; use web3::Web3; +use crate::accountant::PendingPayable; #[derive(Debug)] pub struct BlockchainAgentFutureResult { diff --git a/node/src/blockchain/blockchain_interface/data_structures/mod.rs b/node/src/blockchain/blockchain_interface/data_structures/mod.rs index 428ef469a..74a82846a 100644 --- a/node/src/blockchain/blockchain_interface/data_structures/mod.rs +++ b/node/src/blockchain/blockchain_interface/data_structures/mod.rs @@ -8,6 +8,7 @@ use std::fmt; use std::fmt::Formatter; use web3::types::H256; use web3::Error; +use crate::accountant::PendingPayable; #[derive(Clone, Debug, Eq, PartialEq)] pub struct BlockchainTransaction { From da598533d38ea3299d62bb93abcabe63aad5f31b Mon Sep 17 00:00:00 2001 From: Bert Date: Sun, 6 Jul 2025 20:09:13 +0200 Subject: [PATCH 04/61] GH-642: big initial messy reconstruction continuing... --- .../db_access_objects/payable_dao.rs | 245 ++++++----- .../db_access_objects/pending_payable_dao.rs | 4 +- node/src/accountant/mod.rs | 247 ++++++------ node/src/accountant/scanners/mod.rs | 381 ++++++++++-------- .../src/accountant/scanners/scanners_utils.rs | 109 ++--- node/src/accountant/scanners/test_utils.rs | 4 +- node/src/accountant/test_utils.rs | 16 +- node/src/blockchain/blockchain_bridge.rs | 193 ++++----- .../lower_level_interface_web3.rs | 226 ++++++----- .../blockchain_interface_web3/mod.rs | 133 +++--- .../blockchain_interface_web3/utils.rs | 78 ++-- .../data_structures/errors.rs | 14 +- .../data_structures/mod.rs | 3 +- .../blockchain/blockchain_interface/mod.rs | 12 +- node/src/sub_lib/accountant.rs | 8 +- node/src/test_utils/recorder.rs | 12 +- 16 files changed, 829 insertions(+), 856 deletions(-) diff --git a/node/src/accountant/db_access_objects/payable_dao.rs b/node/src/accountant/db_access_objects/payable_dao.rs index 0f3cd9282..c68bbb780 100644 --- a/node/src/accountant/db_access_objects/payable_dao.rs +++ b/node/src/accountant/db_access_objects/payable_dao.rs @@ -555,7 +555,7 @@ mod tests { use crate::accountant::db_access_objects::utils::{from_unix_timestamp, current_unix_timestamp, to_unix_timestamp}; use crate::accountant::gwei_to_wei; use crate::accountant::db_access_objects::payable_dao::mark_pending_payable_associated_functions::explanatory_extension; - use crate::accountant::test_utils::{assert_account_creation_fn_fails_on_finding_wrong_columns_and_value_types, make_payable_account, make_pending_payable_fingerprint, make_sent_tx, trick_rusqlite_with_read_only_conn}; + use crate::accountant::test_utils::{assert_account_creation_fn_fails_on_finding_wrong_columns_and_value_types, make_payable_account, make_sent_tx, trick_rusqlite_with_read_only_conn}; use crate::blockchain::test_utils::make_tx_hash; use crate::database::rusqlite_wrappers::ConnectionWrapperReal; use crate::database::db_initializer::{ @@ -716,75 +716,76 @@ mod tests { fn mark_pending_payables_marks_pending_transactions_for_new_addresses() { //the extra unchanged record checks the safety of right count of changed rows; //experienced serious troubles in the past - let home_dir = ensure_node_home_directory_exists( - "payable_dao", - "mark_pending_payables_marks_pending_transactions_for_new_addresses", - ); - let wallet_0 = make_wallet("wallet"); - let wallet_1 = make_wallet("booga"); - let pending_payable_rowid_1 = 656; - let wallet_2 = make_wallet("bagaboo"); - let pending_payable_rowid_2 = 657; - let boxed_conn = DbInitializerReal::default() - .initialize(&home_dir, DbInitializationConfig::test_default()) - .unwrap(); - { - let insert = "insert into payable (wallet_address, balance_high_b, balance_low_b, \ - last_paid_timestamp) values (?, ?, ?, ?), (?, ?, ?, ?), (?, ?, ?, ?)"; - let mut stm = boxed_conn.prepare(insert).unwrap(); - let params = [ - [&wallet_0 as &dyn ToSql, &12345, &1, &45678], - [&wallet_1, &0, &i64::MAX, &150_000_000], - [&wallet_2, &3, &0, &151_000_000], - ] - .into_iter() - .flatten() - .collect::>(); - stm.execute(params.as_slice()).unwrap(); - } - let subject = PayableDaoReal::new(boxed_conn); - - subject - .mark_pending_payables_rowids(&[ - (&wallet_1, pending_payable_rowid_1), - (&wallet_2, pending_payable_rowid_2), - ]) - .unwrap(); - - let account_statuses = [&wallet_0, &wallet_1, &wallet_2] - .iter() - .map(|wallet| subject.account_status(wallet).unwrap()) - .collect::>(); - assert_eq!( - account_statuses, - vec![ - PayableAccount { - wallet: wallet_0, - balance_wei: u128::try_from(BigIntDivider::reconstitute(12345, 1)).unwrap(), - last_paid_timestamp: from_unix_timestamp(45678), - pending_payable_opt: None, - }, - PayableAccount { - wallet: wallet_1, - balance_wei: u128::try_from(BigIntDivider::reconstitute(0, i64::MAX)).unwrap(), - last_paid_timestamp: from_unix_timestamp(150_000_000), - pending_payable_opt: Some(PendingPayableId::new( - pending_payable_rowid_1, - make_tx_hash(0) - )), - }, - //notice the hashes are garbage generated by a test method not knowing doing better - PayableAccount { - wallet: wallet_2, - balance_wei: u128::try_from(BigIntDivider::reconstitute(3, 0)).unwrap(), - last_paid_timestamp: from_unix_timestamp(151_000_000), - pending_payable_opt: Some(PendingPayableId::new( - pending_payable_rowid_2, - make_tx_hash(0) - )) - } - ] - ) + todo!("shall we preserve this test?") + // let home_dir = ensure_node_home_directory_exists( + // "payable_dao", + // "mark_pending_payables_marks_pending_transactions_for_new_addresses", + // ); + // let wallet_0 = make_wallet("wallet"); + // let wallet_1 = make_wallet("booga"); + // let pending_payable_rowid_1 = 656; + // let wallet_2 = make_wallet("bagaboo"); + // let pending_payable_rowid_2 = 657; + // let boxed_conn = DbInitializerReal::default() + // .initialize(&home_dir, DbInitializationConfig::test_default()) + // .unwrap(); + // { + // let insert = "insert into payable (wallet_address, balance_high_b, balance_low_b, \ + // last_paid_timestamp) values (?, ?, ?, ?), (?, ?, ?, ?), (?, ?, ?, ?)"; + // let mut stm = boxed_conn.prepare(insert).unwrap(); + // let params = [ + // [&wallet_0 as &dyn ToSql, &12345, &1, &45678], + // [&wallet_1, &0, &i64::MAX, &150_000_000], + // [&wallet_2, &3, &0, &151_000_000], + // ] + // .into_iter() + // .flatten() + // .collect::>(); + // stm.execute(params.as_slice()).unwrap(); + // } + // let subject = PayableDaoReal::new(boxed_conn); + // + // subject + // .mark_pending_payables_rowids(&[ + // (&wallet_1, pending_payable_rowid_1), + // (&wallet_2, pending_payable_rowid_2), + // ]) + // .unwrap(); + // + // let account_statuses = [&wallet_0, &wallet_1, &wallet_2] + // .iter() + // .map(|wallet| subject.account_status(wallet).unwrap()) + // .collect::>(); + // assert_eq!( + // account_statuses, + // vec![ + // PayableAccount { + // wallet: wallet_0, + // balance_wei: u128::try_from(BigIntDivider::reconstitute(12345, 1)).unwrap(), + // last_paid_timestamp: from_unix_timestamp(45678), + // pending_payable_opt: None, + // }, + // PayableAccount { + // wallet: wallet_1, + // balance_wei: u128::try_from(BigIntDivider::reconstitute(0, i64::MAX)).unwrap(), + // last_paid_timestamp: from_unix_timestamp(150_000_000), + // pending_payable_opt: Some(PendingPayableId::new( + // pending_payable_rowid_1, + // make_tx_hash(0) + // )), + // }, + // //notice the hashes are garbage generated by a test method not knowing doing better + // PayableAccount { + // wallet: wallet_2, + // balance_wei: u128::try_from(BigIntDivider::reconstitute(3, 0)).unwrap(), + // last_paid_timestamp: from_unix_timestamp(151_000_000), + // pending_payable_opt: Some(PendingPayableId::new( + // pending_payable_rowid_2, + // make_tx_hash(0) + // )) + // } + // ] + // ) } #[test] @@ -803,28 +804,29 @@ mod tests { probably had not managed to complete successfully before another payment was requested: preventive measures failed.")] fn mark_pending_payables_rowids_returned_different_row_count_than_expected_with_one_account_missing_and_one_unmodified( ) { - let home_dir = ensure_node_home_directory_exists( - "payable_dao", - "mark_pending_payables_rowids_returned_different_row_count_than_expected_with_one_account_missing_and_one_unmodified", - ); - let conn = DbInitializerReal::default() - .initialize(&home_dir, DbInitializationConfig::test_default()) - .unwrap(); - let first_wallet = make_wallet("booga"); - let first_rowid = 456; - insert_payable_record_fn( - &*conn, - &first_wallet.to_string(), - 123456, - 789789, - Some(first_rowid), - ); - let subject = PayableDaoReal::new(conn); - - let _ = subject.mark_pending_payables_rowids(&[ - (&first_wallet, first_rowid as u64), - (&make_wallet("yahoo"), 789), - ]); + todo!("shall we preserve this test?") + // let home_dir = ensure_node_home_directory_exists( + // "payable_dao", + // "mark_pending_payables_rowids_returned_different_row_count_than_expected_with_one_account_missing_and_one_unmodified", + // ); + // let conn = DbInitializerReal::default() + // .initialize(&home_dir, DbInitializationConfig::test_default()) + // .unwrap(); + // let first_wallet = make_wallet("booga"); + // let first_rowid = 456; + // insert_payable_record_fn( + // &*conn, + // &first_wallet.to_string(), + // 123456, + // 789789, + // Some(first_rowid), + // ); + // let subject = PayableDaoReal::new(conn); + // + // let _ = subject.mark_pending_payables_rowids(&[ + // (&first_wallet, first_rowid as u64), + // (&make_wallet("yahoo"), 789), + // ]); } #[test] @@ -871,27 +873,28 @@ mod tests { #[test] fn mark_pending_payables_rowids_handles_general_sql_error() { - let home_dir = ensure_node_home_directory_exists( - "payable_dao", - "mark_pending_payables_rowids_handles_general_sql_error", - ); - let wallet = make_wallet("booga"); - let rowid = 656; - let conn = payable_read_only_conn(&home_dir); - let conn_wrapped = ConnectionWrapperReal::new(conn); - let subject = PayableDaoReal::new(Box::new(conn_wrapped)); - - let result = subject.mark_pending_payables_rowids(&[(&wallet, rowid)]); - - assert_eq!( - result, - Err(PayableDaoError::RusqliteError( - "Multi-row update to mark pending payable hit these errors: [SqliteFailure(\ - Error { code: ReadOnly, extended_code: 8 }, Some(\"attempt to write a readonly \ - database\"))]" - .to_string() - )) - ) + todo!("shall we preserve this test?") + // let home_dir = ensure_node_home_directory_exists( + // "payable_dao", + // "mark_pending_payables_rowids_handles_general_sql_error", + // ); + // let wallet = make_wallet("booga"); + // let rowid = 656; + // let conn = payable_read_only_conn(&home_dir); + // let conn_wrapped = ConnectionWrapperReal::new(conn); + // let subject = PayableDaoReal::new(Box::new(conn_wrapped)); + // + // let result = subject.mark_pending_payables_rowids(&[(&wallet, rowid)]); + // + // assert_eq!( + // result, + // Err(PayableDaoError::RusqliteError( + // "Multi-row update to mark pending payable hit these errors: [SqliteFailure(\ + // Error { code: ReadOnly, extended_code: 8 }, Some(\"attempt to write a readonly \ + // database\"))]" + // .to_string() + // )) + // ) } #[test] @@ -1056,7 +1059,7 @@ mod tests { assert_eq!(status_2_before_opt, Some(expected_status_before_2)); let resulting_account_1_opt = subject.account_status(&setup_holder.account_1.wallet); assert_eq!(resulting_account_1_opt, Some(expected_resulting_status_1)); - let resulting_account_2_opt = subject.account_status(&setup_holder.wallet_2); + let resulting_account_2_opt = subject.account_status(&setup_holder.account_2.wallet); assert_eq!(resulting_account_2_opt, Some(expected_resulting_status_2)) } @@ -1087,26 +1090,22 @@ mod tests { #[should_panic( expected = "Overflow detected with 340282366920938463463374607431768211455: cannot be converted from u128 to i128" )] - fn transaction_confirmed_works_for_overflow_from_amount_stored_in_pending_payable_fingerprint() + fn transaction_confirmed_works_for_overflow_from_sent_tx_record() { let home_dir = ensure_node_home_directory_exists( "payable_dao", - "transaction_confirmed_works_for_overflow_from_amount_stored_in_pending_payable_fingerprint", + "transaction_confirmed_works_for_overflow_from_sent_tx_record", ); let subject = PayableDaoReal::new( DbInitializerReal::default() .initialize(&home_dir, DbInitializationConfig::test_default()) .unwrap(), ); - let mut pending_payable_fingerprint = make_pending_payable_fingerprint(); - let hash = make_tx_hash(12345); - let rowid = 789; - pending_payable_fingerprint.hash = hash; - pending_payable_fingerprint.rowid = rowid; - pending_payable_fingerprint.amount = u128::MAX; + let mut sent_tx = make_sent_tx(456); + sent_tx.amount = u128::MAX; //The overflow occurs before we start modifying the payable account so we can have the database empty - let _ = subject.transactions_confirmed(&[pending_payable_fingerprint]); + let _ = subject.transactions_confirmed(&[sent_tx]); } #[test] diff --git a/node/src/accountant/db_access_objects/pending_payable_dao.rs b/node/src/accountant/db_access_objects/pending_payable_dao.rs index 4e5ef9883..7e6557858 100644 --- a/node/src/accountant/db_access_objects/pending_payable_dao.rs +++ b/node/src/accountant/db_access_objects/pending_payable_dao.rs @@ -178,7 +178,7 @@ impl SentPayableDao for PendingPayableDaoReal<'_> { { Ok(x) if x == ids.len() => Ok(()), Ok(num) => panic!( - "deleting fingerprint, expected {} rows to be changed, but the actual number is {}", + "deleting sent tx record, expected {} rows to be changed, but the actual number is {}", ids.len(), num ), @@ -681,7 +681,7 @@ mod tests { // // #[test] // #[should_panic( - // expected = "deleting fingerprint, expected 2 rows to be changed, but the actual number is 1" + // expected = "deleting sent tx record, expected 2 rows to be changed, but the actual number is 1" // )] // fn delete_fingerprints_changed_different_number_of_rows_than_expected() { // let home_dir = ensure_node_home_directory_exists( diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index a1c76e208..4fbdd7c4b 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -14,7 +14,7 @@ use masq_lib::constants::{SCAN_ERROR, WEIS_IN_GWEI}; use std::cell::{Ref, RefCell}; use crate::accountant::db_access_objects::payable_dao::{PayableDao, PayableDaoError}; -use crate::accountant::db_access_objects::sent_payable_dao::SentPayableDao; +use crate::accountant::db_access_objects::sent_payable_dao::{SentPayableDao, SentTx}; use crate::accountant::db_access_objects::receivable_dao::{ReceivableDao, ReceivableDaoError}; use crate::accountant::db_access_objects::utils::{remap_payable_accounts, remap_receivable_accounts, CustomQuery, DaoFactoryReal, TxHash}; use crate::accountant::financials::visibility_restricted_module::{ @@ -24,7 +24,7 @@ use crate::accountant::scanners::payable_scanner_extension::msgs::{ BlockchainAgentWithContextMessage, QualifiedPayablesMessage, }; use crate::accountant::scanners::{StartScanError, Scanners}; -use crate::blockchain::blockchain_bridge::{BlockMarker, PendingPayableFingerprintSeeds, RetrieveTransactions}; +use crate::blockchain::blockchain_bridge::{BlockMarker, RegisterNewPendingSentTxMessage, RetrieveTransactions}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::HashAndAmount; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; use crate::blockchain::blockchain_interface::data_structures::{ @@ -77,7 +77,7 @@ use web3::types::H256; use crate::accountant::scanners::scan_schedulers::{PayableSequenceScanner, ScanRescheduleAfterEarlyStop, ScanSchedulers}; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::OperationOutcome; use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::PendingPayableScanResult; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::TransactionReceiptResult; +use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::TxReceiptResult; pub const CRASH_KEY: &str = "ACCOUNTANT"; pub const DEFAULT_PENDING_TOO_LONG_SEC: u64 = 21_600; //6 hours @@ -135,8 +135,8 @@ pub struct ReceivedPayments { } #[derive(Debug, PartialEq, Eq, Message, Clone)] -pub struct ReportTransactionReceipts { - pub receipt_results: Vec, +pub struct TxStatusReport { + pub results: Vec, pub response_skeleton_opt: Option, } @@ -306,10 +306,10 @@ impl Handler for Accountant { } } -impl Handler for Accountant { +impl Handler for Accountant { type Result = (); - fn handle(&mut self, msg: ReportTransactionReceipts, ctx: &mut Self::Context) -> Self::Result { + fn handle(&mut self, msg: TxStatusReport, ctx: &mut Self::Context) -> Self::Result { let response_skeleton_opt = msg.response_skeleton_opt; match self.scanners.finish_pending_payable_scan(msg, &self.logger) { PendingPayableScanResult::NoPendingPayablesLeft(ui_msg_opt) => { @@ -467,7 +467,7 @@ pub trait SkeletonOptHolder { #[derive(Debug, PartialEq, Eq, Message, Clone)] pub struct RequestTransactionReceipts { - pub tx_hashes: Vec, + pub sent_tx: Vec, pub response_skeleton_opt: Option, } @@ -477,14 +477,14 @@ impl SkeletonOptHolder for RequestTransactionReceipts { } } -impl Handler for Accountant { +impl Handler for Accountant { type Result = (); fn handle( &mut self, - msg: PendingPayableFingerprintSeeds, + msg: RegisterNewPendingSentTxMessage, _ctx: &mut Self::Context, ) -> Self::Result { - self.handle_new_pending_payable_fingerprints(msg) + self.register_new_pending_sent_tx(msg) } } @@ -559,8 +559,8 @@ impl Accountant { report_services_consumed: recipient!(addr, ReportServicesConsumedMessage), report_payable_payments_setup: recipient!(addr, BlockchainAgentWithContextMessage), report_inbound_payments: recipient!(addr, ReceivedPayments), - init_pending_payable_fingerprints: recipient!(addr, PendingPayableFingerprintSeeds), - report_transaction_receipts: recipient!(addr, ReportTransactionReceipts), + init_pending_payable_fingerprints: recipient!(addr, RegisterNewPendingSentTxMessage), + report_transaction_status: recipient!(addr, TxStatusReport), report_sent_payments: recipient!(addr, SentPayables), scan_errors: recipient!(addr, ScanError), ui_message_sub: recipient!(addr, NodeFromUiMessage), @@ -1117,27 +1117,28 @@ impl Accountant { } } - fn handle_new_pending_payable_fingerprints(&self, msg: PendingPayableFingerprintSeeds) { - fn serialize_hashes(fingerprints_data: &[HashAndAmount]) -> String { - comma_joined_stringifiable(fingerprints_data, |hash_and_amount| { - format!("{:?}", hash_and_amount.hash) + fn register_new_pending_sent_tx(&self, msg: RegisterNewPendingSentTxMessage) { + fn serialize_hashes(fingerprints_data: &[SentTx]) -> String { + comma_joined_stringifiable(fingerprints_data, |sent_tx| { + format!("{:?}", sent_tx.hash) }) } + match self .sent_payable_dao - .insert_new_fingerprints(&msg.hashes_and_balances, msg.batch_wide_timestamp) + .insert_new_records(&msg.new_sent_tx) { Ok(_) => debug!( self.logger, "Saved new pending payable fingerprints for: {}", - serialize_hashes(&msg.hashes_and_balances) + serialize_hashes(&msg.new_sent_tx) ), Err(e) => error!( self.logger, "Failed to process new pending payable fingerprints due to '{:?}', \ disabling the automated confirmation for all these transactions: {}", e, - serialize_hashes(&msg.hashes_and_balances) + serialize_hashes(&msg.new_sent_tx) ), } } @@ -1150,11 +1151,11 @@ impl Accountant { #[derive(Clone, Debug, PartialEq, Eq)] pub struct PendingPayable { pub recipient_wallet: Wallet, - pub hash: H256, + pub hash: TxHash, } impl PendingPayable { - pub fn new(recipient_wallet: Wallet, hash: H256) -> Self { + pub fn new(recipient_wallet: Wallet, hash: TxHash) -> Self { Self { recipient_wallet, hash, @@ -1165,11 +1166,11 @@ impl PendingPayable { #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct PendingPayableId { pub rowid: u64, - pub hash: H256, + pub hash: TxHash, } impl PendingPayableId { - pub fn new(rowid: u64, hash: H256) -> Self { + pub fn new(rowid: u64, hash: TxHash) -> Self { Self { rowid, hash } } @@ -1241,7 +1242,7 @@ mod tests { use crate::accountant::test_utils::DaoWithDestination::{ ForAccountantBody, ForPayableScanner, ForPendingPayableScanner, ForReceivableScanner, }; - use crate::accountant::test_utils::{bc_from_earning_wallet, bc_from_wallets, make_payable_account, make_qualified_and_unqualified_payables, make_pending_payable_fingerprint, BannedDaoFactoryMock, ConfigDaoFactoryMock, MessageIdGeneratorMock, PayableDaoFactoryMock, PayableDaoMock, PayableScannerBuilder, PaymentAdjusterMock, SentPayableDaoFactoryMock, SentPayableDaoMock, ReceivableDaoFactoryMock, ReceivableDaoMock}; + use crate::accountant::test_utils::{bc_from_earning_wallet, bc_from_wallets, make_payable_account, make_qualified_and_unqualified_payables, BannedDaoFactoryMock, ConfigDaoFactoryMock, MessageIdGeneratorMock, PayableDaoFactoryMock, PayableDaoMock, PayableScannerBuilder, PaymentAdjusterMock, SentPayableDaoFactoryMock, SentPayableDaoMock, ReceivableDaoFactoryMock, ReceivableDaoMock, make_sent_tx}; use crate::accountant::test_utils::{AccountantBuilder, BannedDaoMock}; use crate::accountant::Accountant; use crate::blockchain::blockchain_interface::blockchain_interface_web3::HashAndAmount; @@ -1299,7 +1300,7 @@ mod tests { use crate::accountant::db_access_objects::sent_payable_dao::{SentPayableDaoError, SentTx}; use crate::accountant::scanners::scan_schedulers::{NewPayableScanDynIntervalComputer, NewPayableScanDynIntervalComputerReal}; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{OperationOutcome, PayableScanResult}; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, TxReceipt, TxStatus}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{SentTxWithLatestStatus, TransactionBlock, TxBlockchainFailure, TxStatus}; use crate::test_utils::recorder_counter_msgs::SingleTypeCounterMsgSetup; impl Handler> for Accountant { @@ -1570,7 +1571,7 @@ mod tests { SentPayableDaoMock::default().get_tx_identifiers_result(hashmap! (tx_hash => 1)); let payable_dao = PayableDaoMock::default().mark_pending_payables_rowids_result(Ok(())); let mut subject = AccountantBuilder::default() - .pending_payable_daos(vec![ForPayableScanner(sent_payable_dao)]) + .sent_payable_daos(vec![ForPayableScanner(sent_payable_dao)]) .payable_daos(vec![ForPayableScanner(payable_dao)]) .bootstrapper_config(config) .build(); @@ -1833,7 +1834,7 @@ mod tests { receivable_scan_interval: Duration::from_millis(10_000), pending_payable_scan_interval: Duration::from_secs(100), }); - let fingerprint = SentTx { + let sent_tx = SentTx { timestamp: SystemTime::now(), gas_price_wei: 0, nonce: 0, @@ -1843,11 +1844,11 @@ mod tests { block_opt: None, }; let sent_payable_dao = SentPayableDaoMock::default() - .retrieve_txs_result(vec![fingerprint.clone()]); + .retrieve_txs_result(vec![sent_tx.clone()]); let mut subject = AccountantBuilder::default() .consuming_wallet(make_paying_wallet(b"consuming")) .bootstrapper_config(config) - .pending_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) + .sent_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) .build(); let (blockchain_bridge, _, blockchain_bridge_recording_arc) = make_recorder(); let blockchain_bridge = blockchain_bridge @@ -1877,7 +1878,7 @@ mod tests { assert_eq!( blockchain_bridge_recording.get_record::(0), &RequestTransactionReceipts { - pending_payable_fingerprints: vec![fingerprint], + pending_payable_fingerprints: vec![sent_tx], response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, context_id: 4321, @@ -1897,10 +1898,10 @@ mod tests { .transactions_confirmed_params(&transaction_confirmed_params_arc) .transactions_confirmed_result(Ok(())); let sent_payable_dao = - SentPayableDaoMock::default().delete_fingerprints_result(Ok(())); + SentPayableDaoMock::default().delete_records_result(Ok(())); let mut subject = AccountantBuilder::default() .payable_daos(vec![ForPendingPayableScanner(payable_dao)]) - .pending_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) + .sent_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) .build(); let (ui_gateway, _, ui_gateway_recording_arc) = make_recorder(); let ui_gateway = @@ -1917,18 +1918,16 @@ mod tests { Box::new(NotifyHandleMock::default().panic_on_schedule_attempt()); subject.ui_message_sub_opt = Some(ui_gateway_addr.recipient()); let subject_addr = subject.start(); - let tx_fingerprint = make_pending_payable_fingerprint(); - let report_tx_receipts = ReportTransactionReceipts { - fingerprints_with_receipts: vec![( - TransactionReceiptResult::RpcResponse(TxReceipt { - transaction_hash: make_tx_hash(777), - status: TxStatus::Succeeded(TransactionBlock { + let sent_tx = make_sent_tx(123); + let report_tx_receipts = TxStatusReport { + results: vec![ + TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( + sent_tx.clone(), + TxStatus::Succeeded(TransactionBlock { block_hash: make_tx_hash(456), block_number: 78901234.into(), - }), - }), - tx_fingerprint.clone(), - )], + }) + ))], response_skeleton_opt, }; @@ -1936,7 +1935,7 @@ mod tests { system.run(); let transaction_confirmed_params = transaction_confirmed_params_arc.lock().unwrap(); - assert_eq!(*transaction_confirmed_params, vec![vec![tx_fingerprint]]); + assert_eq!(*transaction_confirmed_params, vec![vec![sent_tx]]); let ui_gateway_recording = ui_gateway_recording_arc.lock().unwrap(); assert_eq!( ui_gateway_recording.get_record::(0), @@ -2116,13 +2115,15 @@ mod tests { // TODO when we have more logic in place with the other cards taken in, we'll need to configure these // accordingly let payable_dao = PayableDaoMock::default().transactions_confirmed_result(Ok(())); - let pending_payable = SentPayableDaoMock::default() - .return_all_errorless_fingerprints_result(vec![make_pending_payable_fingerprint()]) - .mark_failures_result(Ok(())); + let sent_tx = make_sent_tx(123); + let sent_payable_dao = SentPayableDaoMock::default() + .retrieve_txs_result(vec![sent_tx.clone()]) + // TODO: params assertion?? + .delete_records_result(Ok(())); let mut subject = AccountantBuilder::default() .consuming_wallet(make_wallet("consuming")) .payable_daos(vec![ForPendingPayableScanner(payable_dao)]) - .pending_payable_daos(vec![ForPendingPayableScanner(pending_payable)]) + .sent_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) .build(); subject.scan_schedulers.automatic_scans_enabled = false; let (blockchain_bridge, _, blockchain_bridge_recording_arc) = make_recorder(); @@ -2137,14 +2138,13 @@ mod tests { let system = System::new("test"); let first_counter_msg_setup = setup_for_counter_msg_triggered_via_type_id!( RequestTransactionReceipts, - ReportTransactionReceipts { - fingerprints_with_receipts: vec![( - TransactionReceiptResult::RpcResponse(TxReceipt { - transaction_hash: make_tx_hash(234), - status: TxStatus::Failed - }), - make_pending_payable_fingerprint() - )], + TxStatusReport { + results: vec![ + TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( + sent_tx, + TxStatus::Failed(TxBlockchainFailure::Unknown) + )), + ], response_skeleton_opt }, &subject_addr @@ -2712,7 +2712,7 @@ mod tests { let system = System::new(test_name); let _ = SystemKillerActor::new(Duration::from_secs(10)).start(); let config = bc_from_wallets(consuming_wallet.clone(), earning_wallet.clone()); - let pp_fingerprint = make_pending_payable_fingerprint(); + let sent_tx = make_sent_tx(456); let payable_scanner = ScannerMock::new() .scan_started_at_result(None) .scan_started_at_result(None) @@ -2732,7 +2732,7 @@ mod tests { .scan_started_at_result(None) .start_scan_params(&scan_params.pending_payable_start_scan) .start_scan_result(Ok(RequestTransactionReceipts { - pending_payable_fingerprints: vec![pp_fingerprint], + sent_tx: vec![sent_tx], response_skeleton_opt: None, })) .finish_scan_params(&scan_params.pending_payable_finish_scan) @@ -2753,14 +2753,13 @@ mod tests { let (peer_actors, addresses) = peer_actors_builder().build_and_provide_addresses(); let subject_addr: Addr = subject.start(); let subject_subs = Accountant::make_subs_from(&subject_addr); - let expected_report_transaction_receipts = ReportTransactionReceipts { - fingerprints_with_receipts: vec![( - TransactionReceiptResult::RpcResponse(TxReceipt { - transaction_hash: make_tx_hash(789), - status: TxStatus::Failed, - }), - make_pending_payable_fingerprint(), - )], + let expected_tx_status_report = TxStatusReport { + results: vec![ + TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( + sent_tx, + TxStatus::Failed(TxBlockchainFailure::Unknown), + )) + ], response_skeleton_opt: None, }; let expected_sent_payables = SentPayables { @@ -2772,7 +2771,7 @@ mod tests { }; let blockchain_bridge_counter_msg_setup_for_pending_payable_scanner = setup_for_counter_msg_triggered_via_type_id!( RequestTransactionReceipts, - expected_report_transaction_receipts.clone(), + expected_tx_status_report.clone(), &subject_addr ); let blockchain_bridge_counter_msg_setup_for_payable_scanner = setup_for_counter_msg_triggered_via_type_id!( @@ -2801,7 +2800,7 @@ mod tests { &scan_params, ¬ify_and_notify_later_params.pending_payables_notify_later, pending_payable_expected_notify_later_interval, - expected_report_transaction_receipts, + expected_tx_status_report, before, after, ); @@ -2831,7 +2830,7 @@ mod tests { payable_finish_scan: Arc>>, pending_payable_start_scan: Arc, Logger, String)>>>, - pending_payable_finish_scan: Arc>>, + pending_payable_finish_scan: Arc>>, receivable_start_scan: Arc, Logger, String)>>>, // receivable_finish_scan ... not needed @@ -2853,7 +2852,7 @@ mod tests { config: BootstrapperConfig, pending_payable_scanner: ScannerMock< RequestTransactionReceipts, - ReportTransactionReceipts, + TxStatusReport, PendingPayableScanResult, >, receivable_scanner: ScannerMock< @@ -2912,7 +2911,7 @@ mod tests { payable_scanner: ScannerMock, pending_payable_scanner: ScannerMock< RequestTransactionReceipts, - ReportTransactionReceipts, + TxStatusReport, PendingPayableScanResult, >, receivable_scanner: ScannerMock< @@ -2966,7 +2965,7 @@ mod tests { config: BootstrapperConfig, pending_payable_scanner: ScannerMock< RequestTransactionReceipts, - ReportTransactionReceipts, + TxStatusReport, PendingPayableScanResult, >, receivable_scanner: ScannerMock< @@ -3039,7 +3038,7 @@ mod tests { Mutex>, >, pending_payable_expected_notify_later_interval: Duration, - expected_report_tx_receipts_msg: ReportTransactionReceipts, + expected_report_tx_receipts_msg: TxStatusReport, act_started_at: SystemTime, act_finished_at: SystemTime, ) { @@ -3279,10 +3278,10 @@ mod tests { #[test] fn initial_pending_payable_scan_if_some_payables_found() { let sent_payable_dao = SentPayableDaoMock::default() - .return_all_errorless_fingerprints_result(vec![make_pending_payable_fingerprint()]); + .retrieve_txs_result(vec![make_sent_tx(789)]); let mut subject = AccountantBuilder::default() .consuming_wallet(make_wallet("consuming")) - .pending_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) + .sent_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) .build(); let system = System::new("test"); let (blockchain_bridge, _, blockchain_bridge_recording_arc) = make_recorder(); @@ -3305,10 +3304,10 @@ mod tests { #[test] fn initial_pending_payable_scan_if_no_payables_found() { let sent_payable_dao = - SentPayableDaoMock::default().return_all_errorless_fingerprints_result(vec![]); + SentPayableDaoMock::default().retrieve_txs_result(vec![]); let mut subject = AccountantBuilder::default() .consuming_wallet(make_wallet("consuming")) - .pending_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) + .sent_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) .build(); let flag_before = subject.scanners.initial_pending_payable_scan(); @@ -3481,6 +3480,7 @@ mod tests { response_skeleton_opt: None, }; let transaction_hash = make_tx_hash(789); + let sent_tx = make_sent_tx(456); let creditor_wallet = make_wallet("blah"); let counter_msg_2 = SentPayables { payment_procedure_result: Ok(vec![ProcessedPayableFallible::Correct( @@ -3488,23 +3488,23 @@ mod tests { )]), response_skeleton_opt: None, }; - let tx_receipt = TxReceipt { - transaction_hash, - status: TxStatus::Succeeded(ConfirmedTx{ - tx_hash: make_tx_hash(369369), - block: 4444444444u64.into(), + let tx_with_status = SentTxWithLatestStatus { + sent_tx: sent_tx.clone(), + status: TxStatus::Succeeded(TransactionBlock{ + block_hash: make_tx_hash(369369), + block_number: 4444444444u64.into(), }), }; let requested_tx = make_tx_hash( 234 ); - let counter_msg_3 = ReportTransactionReceipts { - receipt_results: vec![ - TransactionReceiptResult::RpcResponse(tx_receipt)], + let counter_msg_3 = TxStatusReport { + results: vec![ + TxReceiptResult::RpcResponse(tx_with_status)], response_skeleton_opt: None, }; let request_transaction_receipts_msg = RequestTransactionReceipts { - tx_hashes: vec![requested_tx], + sent_tx: vec![sent_tx], response_skeleton_opt: None, }; let qualified_payables_msg = QualifiedPayablesMessage { @@ -4072,7 +4072,7 @@ mod tests { let system = System::new("pending payable scan"); let mut subject = AccountantBuilder::default() .consuming_wallet(make_paying_wallet(b"consuming")) - .pending_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) + .sent_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) .bootstrapper_config(config) .build(); @@ -4818,7 +4818,7 @@ mod tests { let mut subject = AccountantBuilder::default() .bootstrapper_config(bc_from_earning_wallet(make_wallet("some_wallet_address"))) .payable_daos(vec![ForPayableScanner(payable_dao)]) - .pending_payable_daos(vec![ForPayableScanner(sent_payable_dao)]) + .sent_payable_daos(vec![ForPayableScanner(sent_payable_dao)]) .build(); let pending_payable_interval = Duration::from_millis(55); subject.scan_schedulers.pending_payable.interval = pending_payable_interval; @@ -4943,7 +4943,7 @@ mod tests { Box::new(NotifyHandleMock::default().notify_params(&retry_payable_notify_params_arc)); let system = System::new(test_name); let (mut msg, _) = - make_report_transaction_receipts_msg(vec![TxStatus::Pending, TxStatus::Failed]); + make_tx_status_report_msg(vec![TxStatus::Pending, TxStatus::Failed]); let response_skeleton_opt = Some(ResponseSkeleton { client_id: 45, context_id: 7, @@ -4982,7 +4982,7 @@ mod tests { let system = System::new("new_payable_scanner_timely"); let mut subject = AccountantBuilder::default() .payable_daos(vec![ForPendingPayableScanner(payable_dao)]) - .pending_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) + .sent_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) .build(); let last_new_payable_scan_timestamp = SystemTime::now() .checked_sub(Duration::from_secs(3)) @@ -5007,7 +5007,7 @@ mod tests { subject.scan_schedulers.payable.new_payable_notify = Box::new(NotifyHandleMock::default().notify_params(&new_payable_notify_arc)); let subject_addr = subject.start(); - let (msg, two_fingerprints) = make_report_transaction_receipts_msg(vec![ + let (msg, two_fingerprints) = make_tx_status_report_msg(vec![ TxStatus::Succeeded(TransactionBlock { block_hash: make_tx_hash(123), block_number: U64::from(100), @@ -5059,7 +5059,7 @@ mod tests { SentPayableDaoMock::default().delete_fingerprints_result(Ok(())); let mut subject = AccountantBuilder::default() .payable_daos(vec![ForPendingPayableScanner(payable_dao)]) - .pending_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) + .sent_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) .build(); let last_new_payable_scan_timestamp = SystemTime::now() .checked_sub(Duration::from_secs(8)) @@ -5083,7 +5083,7 @@ mod tests { subject.scan_schedulers.payable.new_payable_notify = Box::new(NotifyHandleMock::default().notify_params(&new_payable_notify_arc)); let subject_addr = subject.start(); - let (msg, two_fingerprints) = make_report_transaction_receipts_msg(vec![ + let (msg, two_fingerprints) = make_tx_status_report_msg(vec![ TxStatus::Succeeded(TransactionBlock { block_hash: make_tx_hash(123), block_number: U64::from(100), @@ -5125,11 +5125,11 @@ mod tests { let new_payable_notify_later_arc = Arc::new(Mutex::new(vec![])); let payable_dao = PayableDaoMock::default().transactions_confirmed_result(Ok(())); let sent_payable_dao = - SentPayableDaoMock::default().delete_fingerprints_result(Ok(())); + SentPayableDaoMock::default().delete_records_result(Ok(())); let system = System::new("scheduler_for_new_payables_operates_with_proper_now_timestamp"); let mut subject = AccountantBuilder::default() .payable_daos(vec![ForPendingPayableScanner(payable_dao)]) - .pending_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) + .sent_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) .build(); let last_new_payable_scan_timestamp = SystemTime::now() .checked_sub(Duration::from_millis(3500)) @@ -5147,7 +5147,7 @@ mod tests { NotifyLaterHandleMock::default().notify_later_params(&new_payable_notify_later_arc), ); let subject_addr = subject.start(); - let (msg, _) = make_report_transaction_receipts_msg(vec![ + let (msg, _) = make_tx_status_report_msg(vec![ TxStatus::Succeeded(TransactionBlock { block_hash: make_tx_hash(123), block_number: U64::from(100), @@ -5186,42 +5186,33 @@ mod tests { ); } - fn make_report_transaction_receipts_msg( + fn make_tx_status_report_msg( status_txs: Vec, - ) -> (ReportTransactionReceipts, Vec) { - let (receipt_result_fingerprint_pairs, fingerprints): (Vec<_>, Vec<_>) = status_txs + ) -> (TxStatusReport, Vec) { + let (tx_receipt_results, sent_tx_vec) = status_txs .into_iter() .enumerate() - .map(|(idx, status)| { - let transaction_hash = make_tx_hash(idx as u32); - let transaction_receipt_result = TransactionReceiptResult::RpcResponse(TxReceipt { - transaction_hash, + .fold((vec![], vec![]),|(mut tx_receipt_results, mut sent_tx_vec), (idx, status)| { + let sent_tx = make_sent_tx(idx as u64); + let hash = sent_tx.hash; + let tx_receipt_result = TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( + sent_tx.clone(), status, - }); - let fingerprint = SentTx { - - timestamp: from_unix_timestamp(1_000_000_000 * idx as i64), - gas_price_wei: 0, - nonce: 0, - hash: transaction_hash, - amount: 1_000_000 * idx as u128 * idx as u128, - receiver_address: Default::default(), - block_opt: None, - }; + )); + tx_receipt_results.push(tx_receipt_result); + sent_tx_vec.push(sent_tx); ( - (transaction_receipt_result, fingerprint.clone()), - fingerprint, + tx_receipt_results, + sent_tx_vec, ) - }) - .unzip(); + }); - let msg = ReportTransactionReceipts { - receipt_results: todo!(), - //fingerprints_with_receipts: receipt_result_fingerprint_pairs, + let msg = TxStatusReport { + results: tx_receipt_results, response_skeleton_opt: None, }; - (msg, fingerprints) + (msg, sent_tx_vec) } #[test] @@ -5232,7 +5223,7 @@ mod tests { .insert_fingerprints_params(&insert_fingerprint_params_arc) .insert_fingerprints_result(Ok(())); let subject = AccountantBuilder::default() - .pending_payable_daos(vec![ForAccountantBody(sent_payable_dao)]) + .sent_payable_daos(vec![ForAccountantBody(sent_payable_dao)]) .build(); let accountant_addr = subject.start(); let accountant_subs = Accountant::make_subs_from(&accountant_addr); @@ -5250,7 +5241,7 @@ mod tests { amount: amount_2, }; let init_params = vec![hash_and_amount_1, hash_and_amount_2]; - let init_fingerprints_msg = PendingPayableFingerprintSeeds { + let init_fingerprints_msg = RegisterNewPendingSentTxMessage { batch_wide_timestamp: timestamp, hashes_and_balances: init_params.clone(), }; @@ -5260,7 +5251,7 @@ mod tests { .try_send(init_fingerprints_msg) .unwrap(); - let system = System::new("ordering payment fingerprint test"); + let system = System::new("ordering payment sent tx record test"); System::current().stop(); assert_eq!(system.run(), 0); let insert_fingerprint_params = insert_fingerprint_params_arc.lock().unwrap(); @@ -5291,15 +5282,15 @@ mod tests { amount, }; let subject = AccountantBuilder::default() - .pending_payable_daos(vec![ForAccountantBody(sent_payable_dao)]) + .sent_payable_daos(vec![ForAccountantBody(sent_payable_dao)]) .build(); let timestamp = SystemTime::now(); - let report_new_fingerprints = PendingPayableFingerprintSeeds { + let report_new_fingerprints = RegisterNewPendingSentTxMessage { batch_wide_timestamp: timestamp, hashes_and_balances: vec![hash_and_amount], }; - let _ = subject.handle_new_pending_payable_fingerprints(report_new_fingerprints); + let _ = subject.register_new_pending_sent_tx(report_new_fingerprints); let insert_fingerprint_params = insert_new_records_params_arc.lock().unwrap(); assert_eq!( diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index e1d2da17b..cbf168da8 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -12,12 +12,12 @@ use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableT LocallyCausedError, RemotelyCausedErrors, }; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{debugging_summary_after_error_separation, err_msg_for_failure_with_expected_but_missing_fingerprints, investigate_debt_extremes, mark_pending_payable_fatal_error, payables_debug_summary, separate_errors, separate_rowids_and_hashes, OperationOutcome, PayableScanResult, PayableThresholdsGauge, PayableThresholdsGaugeReal, PayableTransactingErrorEnum, PendingPayableMissingInDb}; -use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_local_error_fetching_receipts, handle_status_with_failure, handle_successful_tx, PendingPayableScanReport, PendingPayableScanResult}; +use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_request_error_fetching_receipts, handle_status_with_failure, handle_successful_tx, PendingPayableScanReport, PendingPayableScanResult}; use crate::accountant::scanners::scanners_utils::receivable_scanner_utils::balance_and_age; use crate::accountant::{PendingPayable, PendingPayableId, ScanError, ScanForPendingPayables, ScanForRetryPayables}; use crate::accountant::{ comma_joined_stringifiable, gwei_to_wei, ReceivedPayments, - ReportTransactionReceipts, RequestTransactionReceipts, ResponseSkeleton, ScanForNewPayables, + TxStatusReport, RequestTransactionReceipts, ResponseSkeleton, ScanForNewPayables, ScanForReceivables, SentPayables, }; use crate::accountant::db_access_objects::banned_dao::BannedDao; @@ -44,12 +44,12 @@ use time::format_description::parse; use time::OffsetDateTime; use variant_count::VariantCount; use web3::types::H256; -use crate::accountant::db_access_objects::failed_payable_dao::FailedTx; -use crate::accountant::db_access_objects::sent_payable_dao::{SentPayableDao, SentTx}; +use crate::accountant::db_access_objects::failed_payable_dao::{FailedPayableDao, FailedTx}; +use crate::accountant::db_access_objects::sent_payable_dao::{RetrieveCondition, SentPayableDao, SentTx}; use crate::accountant::db_access_objects::utils::{TxHash, TxIdentifiers}; use crate::accountant::scanners::payable_scanner_extension::{MultistageDualPayableScanner, PreparedAdjustment, SolvencySensitivePaymentInstructor}; use crate::accountant::scanners::payable_scanner_extension::msgs::{BlockchainAgentWithContextMessage, QualifiedPayablesMessage}; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionReceiptResult, TxStatus}; +use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxReceiptResult, TxStatus}; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; use crate::db_config::persistent_configuration::{PersistentConfiguration, PersistentConfigurationReal}; @@ -62,7 +62,7 @@ pub struct Scanners { dyn PrivateScanner< ScanForPendingPayables, RequestTransactionReceipts, - ReportTransactionReceipts, + TxStatusReport, PendingPayableScanResult, >, >, @@ -93,6 +93,7 @@ impl Scanners { let pending_payable = Box::new(PendingPayableScanner::new( dao_factories.payable_dao_factory.make(), dao_factories.sent_payable_dao_factory.make(), + todo!("Utkarsh could theoretically finish this earlier"), Rc::clone(&payment_thresholds), when_pending_too_long_sec, Rc::clone(&financial_statistics), @@ -258,7 +259,7 @@ impl Scanners { pub fn finish_pending_payable_scan( &mut self, - msg: ReportTransactionReceipts, + msg: TxStatusReport, logger: &Logger, ) -> PendingPayableScanResult { self.pending_payable.finish_scan(msg, logger) @@ -535,9 +536,11 @@ impl Scanner for PayableScanner { debugging_summary_after_error_separation(&sent_payables, &err_opt) ); + // TODO so, is this still properly covered with tests? if !sent_payables.is_empty() { - self.mark_pending_payable(&sent_payables, logger); + self.check_on_missing_sent_tx_records(&sent_payables); } + self.handle_sent_payable_errors(err_opt, logger); self.mark_as_ended(logger); @@ -690,8 +693,9 @@ impl PayableScanner { let transaction_hashes_and_rowids_from_db = self.sent_payable_dao.get_tx_identifiers(&hashset_with_runtime_data_sent_payable_hashes); let hashes_from_db = transaction_hashes_and_rowids_from_db .keys() + .copied() .collect::>(); - let missing_sent_payables_hashes:Vec = hashset_with_runtime_data_sent_payable_hashes.difference(&hashes_from_db).collect(); + let missing_sent_payables_hashes:Vec = hashset_with_runtime_data_sent_payable_hashes.difference(&hashes_from_db).copied().collect(); let mut sent_payables_hashmap = sent_payables .iter() .map(|payable| (payable.hash, &payable.recipient_wallet)) @@ -756,7 +760,7 @@ impl PayableScanner { // (pending_payables_with_rowid, pending_payables_without_rowid) // } - fn mark_pending_payable(&self, sent_payments: &[&PendingPayable], logger: &Logger) { + fn check_on_missing_sent_tx_records(&self, sent_payments: &[&PendingPayable]){ fn missing_fingerprints_msg(nonexistent: &[PendingPayableMissingInDb]) -> String { format!( "Expected pending payable fingerprints for {} were not found; system unreliable", @@ -766,95 +770,121 @@ impl PayableScanner { )) ) } - fn ready_data_for_supply<'a>( - existent: &'a [PendingPayableMissingInDb], - ) -> Vec<(&'a Wallet, u64)> { - existent - .iter() - .map(|pp_triple| (pp_triple.recipient, pp_triple.rowid_opt.expectv("rowid"))) - .collect() - } - let (existent, nonexistent) = + todo!("drive me in"); + let missing_sent_tx_records = self.check_for_missing_records(sent_payments); - - if !existent.is_empty() { - if let Err(e) = self - .payable_dao - .as_ref() - .mark_pending_payables_rowids(&existent) - { - mark_pending_payable_fatal_error( - sent_payments, - &nonexistent, - e, - missing_fingerprints_msg, - logger, - ) - } - debug!( - logger, - "Payables {} marked as pending in the payable table", - comma_joined_stringifiable(sent_payments, |pending_p| format!( - "{:?}", - pending_p.hash - )) - ) - } - if !nonexistent.is_empty() { - panic!("{}", missing_fingerprints_msg(&nonexistent)) + if !missing_sent_tx_records.is_empty() { + panic!("{}", missing_fingerprints_msg(&missing_sent_tx_records)) } } + // TODO this has become dead (GH-662) + fn mark_pending_payable(&self, sent_payments: &[&PendingPayable], logger: &Logger) { + todo!("remove me when the time comes") + // fn missing_fingerprints_msg(nonexistent: &[PendingPayableMissingInDb]) -> String { + // format!( + // "Expected pending payable fingerprints for {} were not found; system unreliable", + // comma_joined_stringifiable(nonexistent, |pp_triple| format!( + // "(tx: {:?}, to wallet: {})", + // pp_triple.hash, pp_triple.recipient + // )) + // ) + // } + // fn ready_data_for_supply<'a>( + // existent: &'a [PendingPayableMissingInDb], + // ) -> Vec<(&'a Wallet, u64)> { + // existent + // .iter() + // .map(|pp_triple| (pp_triple.recipient, pp_triple.rowid_opt.expectv("rowid"))) + // .collect() + // } + // + // // TODO eventually should be taken over by GH-655 + // let missing_sent_tx_records = + // self.check_for_missing_records(sent_payments); + // + // if !existent.is_empty() { + // if let Err(e) = self + // .payable_dao + // .as_ref() + // .mark_pending_payables_rowids(&existent) + // { + // mark_pending_payable_fatal_error( + // sent_payments, + // &nonexistent, + // e, + // missing_fingerprints_msg, + // logger, + // ) + // } + // debug!( + // logger, + // "Payables {} marked as pending in the payable table", + // comma_joined_stringifiable(sent_payments, |pending_p| format!( + // "{:?}", + // pending_p.hash + // )) + // ) + // } + // if !missing_sent_tx_records.is_empty() { + // panic!("{}", missing_fingerprints_msg(&missing_sent_tx_records)) + // } + } + fn handle_sent_payable_errors( &self, err_opt: Option, logger: &Logger, ) { if let Some(err) = err_opt { - match err { + if let LocallyCausedError(PayableTransactionError::Sending { hashes, .. }) - | RemotelyCausedErrors(hashes) => { - self.discard_failed_transactions_with_possible_fingerprints(hashes, logger) - } - non_fatal => + | RemotelyCausedErrors(hashes) = &err { + self.discard_failed_transactions_with_possible_sent_tx_records(hashes, logger) + } else { debug!( logger, "Ignoring a non-fatal error on our end from before the transactions are hashed: {:?}", - non_fatal + err ) } } } - fn discard_failed_transactions_with_possible_fingerprints( + fn discard_failed_transactions_with_possible_sent_tx_records( &self, - hashes_of_failed: Vec, + hashes_of_failed: &HashSet, logger: &Logger, ) { - fn serialize_hashes(hashes: &[H256]) -> String { + fn serialize_hashes(hashes: &[TxHash]) -> String { comma_joined_stringifiable(hashes, |hash| format!("{:?}", hash)) } - let existent_and_nonexistent = self + + let existent_sent_tx_in_db = self .sent_payable_dao - .fingerprints_rowids(&hashes_of_failed); + .get_tx_identifiers(&hashes_of_failed); + + let hashes_of_missing_sent_tx = todo!(); + let missing_fgp_err_msg_opt = err_msg_for_failure_with_expected_but_missing_fingerprints( - existent_and_nonexistent.no_rowid_results, + hashes_of_missing_sent_tx, serialize_hashes, ); - if !existent_and_nonexistent.rowid_results.is_empty() { - let (ids, hashes) = separate_rowids_and_hashes(existent_and_nonexistent.rowid_results); + if !existent_sent_tx_in_db.is_empty() { + //let (ids, hashes) = separate_rowids_and_hashes(existent_sent_tx_in_db.rowid_results); + let hashes = existent_sent_tx_in_db.keys().copied().collect_vec(); warning!( logger, "Deleting fingerprints for failed transactions {}", serialize_hashes(&hashes) ); - if let Err(e) = self.sent_payable_dao.delete_fingerprints(&ids) { + if let Err(e) = self.sent_payable_dao.delete_records(&existent_sent_tx_in_db.keys().copied().collect()) { if let Some(msg) = missing_fgp_err_msg_opt { error!(logger, "{}", msg) }; panic!( - "Database corrupt: payable fingerprint deletion for transactions {} \ + "Database corrupt: sent tx record deletion for transactions {} \ failed due to {:?}", serialize_hashes(&hashes), e @@ -871,6 +901,7 @@ pub struct PendingPayableScanner { pub common: ScannerCommon, pub payable_dao: Box, pub sent_payable_dao: Box, + pub failed_payable_dao: Box, pub when_pending_too_long_sec: u64, pub financial_statistics: Rc>, pub cached_currently_queried_tx_for_receipts: Vec, //TODO always delete after the scan ends @@ -881,7 +912,7 @@ impl PrivateScanner< ScanForPendingPayables, RequestTransactionReceipts, - ReportTransactionReceipts, + TxStatusReport, PendingPayableScanResult, > for PendingPayableScanner { @@ -899,8 +930,8 @@ impl StartableScanner ) -> Result { self.mark_as_started(timestamp); info!(logger, "Scanning for pending payable"); - let filtered_pending_payable = self.sent_payable_dao.retrieve_txs(None); - match filtered_pending_payable.is_empty() { + let pending_sent_txs = self.sent_payable_dao.retrieve_txs(Some(RetrieveCondition::IsPending)); + match pending_sent_txs.is_empty() { true => { self.mark_as_ended(logger); Err(StartScanError::NothingToProcess) @@ -909,11 +940,10 @@ impl StartableScanner debug!( logger, "Found {} pending payables to process", - filtered_pending_payable.len() + pending_sent_txs.len() ); - let tx_hashes = todo!(); Ok(RequestTransactionReceipts { - tx_hashes, + sent_tx: pending_sent_txs, response_skeleton_opt, }) } @@ -921,43 +951,44 @@ impl StartableScanner } } -impl Scanner for PendingPayableScanner { +impl Scanner for PendingPayableScanner { fn finish_scan( &mut self, - message: ReportTransactionReceipts, + message: TxStatusReport, logger: &Logger, ) -> PendingPayableScanResult { let response_skeleton_opt = message.response_skeleton_opt; - let requires_payment_retry = match message.fingerprints_with_receipts.is_empty() { + let payment_retry_required = match message.results.is_empty() { true => { - warning!(logger, "No transaction receipts found."); + warning!(logger, "No corresponding transaction receipts were found."); todo!("This requires the payment retry. GH-631 must be completed first"); - if self.cached_currently_queried_tx_for_receipts.is_empty() { - todo!("unreachable") - } - let failed_txs = self.cached_currently_queried_tx_for_receipts.drain(..) - .map(|sent_tx|{todo!()}) - .collect(); - self.handle_failed_transactions(failed_txs, logger) + // if self.cached_currently_queried_tx_for_receipts.is_empty() { + // todo!("unreachable") + // } + // let failed_txs = self.cached_currently_queried_tx_for_receipts.drain(..) + // .map(|sent_tx|{todo!()}) + // .collect(); + // self.handle_failed_transactions(failed_txs, logger) } false => { debug!( logger, "Processing receipts for {} transactions", - message.fingerprints_with_receipts.len() + message.results.len() ); let scan_report = self.handle_receipts_for_pending_transactions(message, logger); - let requires_payment_retry = + + let payment_retry_required = self.process_transactions_by_their_state(scan_report, logger); self.mark_as_ended(logger); - requires_payment_retry + payment_retry_required } }; - if requires_payment_retry { + if payment_retry_required { PendingPayableScanResult::PaymentRetryRequired } else { let ui_msg_opt = response_skeleton_opt.map(|response_skeleton| NodeToUiMessage { @@ -977,6 +1008,7 @@ impl PendingPayableScanner { pub fn new( payable_dao: Box, sent_payable_dao: Box, + failed_payable_dao: Box, payment_thresholds: Rc, when_pending_too_long_sec: u64, financial_statistics: Rc>, @@ -985,6 +1017,7 @@ impl PendingPayableScanner { common: ScannerCommon::new(payment_thresholds), payable_dao, sent_payable_dao, + failed_payable_dao, when_pending_too_long_sec, financial_statistics, cached_currently_queried_tx_for_receipts: todo!(), @@ -994,22 +1027,31 @@ impl PendingPayableScanner { fn handle_receipts_for_pending_transactions( &self, - msg: ReportTransactionReceipts, + msg: TxStatusReport, logger: &Logger, ) -> PendingPayableScanReport { let scan_report = PendingPayableScanReport::default(); - msg.receipt_results.into_iter().fold( + msg.results.into_iter().fold( scan_report, |scan_report_so_far, receipt_result| match receipt_result { - TransactionReceiptResult::RpcResponse(tx_receipt) => match tx_receipt.status { - TxStatus::Failed(failed_tx) => { + TxReceiptResult::RpcResponse(sent_tx_with_status) => match sent_tx_with_status.status { + TxStatus::Succeeded(tx_block) => { + todo!("check that the tx block is used in logging somehow"); + let completed_sent_tx = SentTx { + block_opt: Some(tx_block), + ..sent_tx_with_status.sent_tx + }; + handle_successful_tx(scan_report_so_far, completed_sent_tx, logger) + } + TxStatus::Failed(reason) => { + let failed_tx = FailedTx::from((sent_tx_with_status.sent_tx, reason)); handle_status_with_failure(scan_report_so_far, failed_tx, logger) } - TxStatus::Succeeded(confirmed_tx) => { - handle_successful_tx(scan_report_so_far, confirmed_tx, logger) + TxStatus::Pending => { + todo!() } }, - TransactionReceiptResult::LocalError(e) => handle_local_error_fetching_receipts( + TxReceiptResult::RequestError(e) => handle_request_error_fetching_receipts( scan_report_so_far, e, logger, @@ -1051,23 +1093,31 @@ impl PendingPayableScanner { fn handle_failed_transactions(&self, failures: Vec, logger: &Logger) { if !failures.is_empty() { - //TODO this function is imperfect. It waits for GH-663 - let rowids = PendingPayableId::rowids(&failures); - match self.sent_payable_dao.mark_failures(&rowids) { - Ok(_) => warning!( - logger, - "Broken transactions {} marked as an error. You should take over the care \ - of those to make sure your debts are going to be settled properly. At the moment, \ - there is no automated process fixing that without your assistance", - PendingPayableId::serialize_hashes_to_string(&failures) - ), - Err(e) => panic!( - "Unsuccessful attempt for transactions {} \ - to mark fatal error at payable fingerprint due to {:?}; database unreliable", - PendingPayableId::serialize_hashes_to_string(&failures), - e - ), + let hashes = failures.iter().map(|failed_tx|failed_tx.hash).collect(); + match self.failed_payable_dao.insert_new_records(&failures) { + Ok(_) => { todo!() + } + Err(e) => todo!() } + match self.sent_payable_dao.delete_records(&hashes) { + // Ok(_) => warning!( + // logger, + // "Broken transactions {} marked as an error. You should take over the care \ + // of those to make sure your debts are going to be settled properly. At the moment, \ + // there is no automated process fixing that without your assistance", + // PendingPayableId::serialize_hashes_to_string(&failures) + // ), + // Err(e) => panic!( + // "Unsuccessful attempt for transactions {} \ + // to mark fatal error at sent tx record due to {:?}; database unreliable", + // PendingPayableId::serialize_hashes_to_string(&failures), + // e + // ), + // } + Ok(_) => todo!(), + Err(e) => todo!() + }; + } } @@ -1090,7 +1140,7 @@ impl PendingPayableScanner { // self.add_to_the_total_of_paid_payable(&fingerprints, serialize_hashes, logger); // let rowids = fingerprints // .iter() - // .map(|fingerprint| fingerprint.rowid) + // .map(|sent_tx| sent_tx.rowid) // .collect::>(); // if let Err(e) = self.sent_payable_dao.delete_fingerprints(&rowids) { // panic!("Unable to delete payable fingerprints {} of verified transactions due to {:?}", @@ -1113,10 +1163,10 @@ impl PendingPayableScanner { logger: &Logger, ) { todo!() - // fingerprints.iter().for_each(|fingerprint| { + // fingerprints.iter().for_each(|sent_tx| { // self.financial_statistics // .borrow_mut() - // .total_paid_payable_wei += fingerprint.amount + // .total_paid_payable_wei += sent_tx.amount // }); // debug!( // logger, @@ -1422,8 +1472,8 @@ mod tests { use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{OperationOutcome, PayableScanResult, PendingPayableMissingInDb}; use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_status_with_failure, PendingPayableScanReport, PendingPayableScanResult}; use crate::accountant::scanners::{Scanner, StartScanError, StartableScanner, PayableScanner, PendingPayableScanner, ReceivableScanner, ScannerCommon, Scanners, MTError}; - use crate::accountant::test_utils::{make_custom_payment_thresholds, make_payable_account, make_qualified_and_unqualified_payables, make_pending_payable_fingerprint, make_receivable_account, BannedDaoFactoryMock, BannedDaoMock, ConfigDaoFactoryMock, PayableDaoFactoryMock, PayableDaoMock, PayableScannerBuilder, PayableThresholdsGaugeMock, SentPayableDaoFactoryMock, SentPayableDaoMock, PendingPayableScannerBuilder, ReceivableDaoFactoryMock, ReceivableDaoMock, ReceivableScannerBuilder}; - use crate::accountant::{gwei_to_wei, PendingPayableId, ReceivedPayments, ReportTransactionReceipts, RequestTransactionReceipts, ScanError, ScanForRetryPayables, SentPayables, DEFAULT_PENDING_TOO_LONG_SEC, PendingPayable}; + use crate::accountant::test_utils::{make_custom_payment_thresholds, make_payable_account, make_qualified_and_unqualified_payables, make_receivable_account, BannedDaoFactoryMock, BannedDaoMock, ConfigDaoFactoryMock, PayableDaoFactoryMock, PayableDaoMock, PayableScannerBuilder, PayableThresholdsGaugeMock, SentPayableDaoFactoryMock, SentPayableDaoMock, PendingPayableScannerBuilder, ReceivableDaoFactoryMock, ReceivableDaoMock, ReceivableScannerBuilder, make_sent_tx}; + use crate::accountant::{gwei_to_wei, PendingPayableId, ReceivedPayments, TxStatusReport, RequestTransactionReceipts, ScanError, ScanForRetryPayables, SentPayables, DEFAULT_PENDING_TOO_LONG_SEC, PendingPayable}; use crate::blockchain::blockchain_bridge::{BlockMarker, RetrieveTransactions}; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; use crate::blockchain::blockchain_interface::data_structures::{ @@ -1461,7 +1511,7 @@ mod tests { use masq_lib::ui_gateway::NodeToUiMessage; use crate::accountant::db_access_objects::sent_payable_dao::{SentPayableDaoError, SentTx}; use crate::accountant::scanners::test_utils::{assert_timestamps_from_str, parse_system_time_from_str, MarkScanner, NullScanner, ReplacementType, ScannerReplacement}; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, TransactionReceiptResult, TxReceipt, TxStatus}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{SentTxWithLatestStatus, TransactionBlock, TxReceiptResult, TxStatus}; impl Scanners { pub fn replace_scanner(&mut self, replacement: ScannerReplacement) { @@ -2432,7 +2482,7 @@ mod tests { let panic_msg = caught_panic.downcast_ref::().unwrap(); assert_eq!( panic_msg, - "Database corrupt: payable fingerprint deletion for transactions \ + "Database corrupt: sent tx record deletion for transactions \ 0x000000000000000000000000000000000000000000000000000000000000007b, 0x00000000000000000000\ 00000000000000000000000000000000000000000315 failed due to RecordDeletion(\"Gosh, I overslept \ without an alarm set\")"); @@ -2489,7 +2539,7 @@ mod tests { #[test] fn payable_scanner_for_failed_rpcs_one_fingerprint_missing_and_deletion_of_the_other_one_fails() { - // Two fatal failures at once, missing fingerprints and fingerprint deletion error are both + // Two fatal failures at once, missing sent tx records and another record deletion error are both // legitimate reasons for panic init_test_logging(); let test_name = "payable_scanner_for_failed_rpcs_one_fingerprint_missing_and_deletion_of_the_other_one_fails"; @@ -2529,7 +2579,7 @@ mod tests { let panic_msg = caught_panic.downcast_ref::().unwrap(); assert_eq!( panic_msg, - "Database corrupt: payable fingerprint deletion for transactions 0x00000000000000000000000\ + "Database corrupt: sent tx record deletion for transactions 0x00000000000000000000000\ 0000000000000000000000000000000000000b26e failed due to RecordDeletion(\"Another failure. Really???\")"); let log_handler = TestLogHandler::new(); log_handler.exists_log_containing(&format!("WARN: {test_name}: Remote transaction failure: 'Server is unreachable' \ @@ -2833,7 +2883,7 @@ mod tests { let consuming_wallet = make_paying_wallet(b"consuming"); let mut subject = make_dull_subject(); let sent_payable_dao = SentPayableDaoMock::new() - .return_all_errorless_fingerprints_result(vec![make_pending_payable_fingerprint()]); + .retrieve_txs_result(vec![make_sent_tx(123)]); let pending_payable_scanner = PendingPayableScannerBuilder::new() .sent_payable_dao(sent_payable_dao) .build(); @@ -3016,7 +3066,7 @@ mod tests { // ) -> PendingPayableScanReport { // init_test_logging(); // let when_sent = SystemTime::now().sub(Duration::from_secs(pending_payable_age_sec)); - // let fingerprint = SentTx { + // let sent_tx = SentTx { // rowid, // timestamp: when_sent, // hash, @@ -3027,7 +3077,7 @@ mod tests { // let logger = Logger::new(test_name); // let scan_report = PendingPayableScanReport::default(); // - // handle_none_status(scan_report, fingerprint, when_pending_too_long_sec, &logger) + // handle_none_status(scan_report, sent_tx, when_pending_too_long_sec, &logger) // } fn assert_log_msg_and_elapsed_time_in_log_makes_sense( @@ -3165,7 +3215,7 @@ mod tests { let mut tx_receipt = TransactionReceipt::default(); tx_receipt.status = Some(U64::from(0)); //failure let hash = make_tx_hash(0xd7); - let fingerprint = SentTx { + let sent_tx = SentTx { timestamp: SystemTime::now().sub(Duration::from_millis(150000)), gas_price_wei: 0, nonce: 0, @@ -3177,12 +3227,11 @@ mod tests { let logger = Logger::new(test_name); let scan_report = PendingPayableScanReport::default(); - let result = handle_status_with_failure(scan_report, fingerprint, &logger); + let result = handle_status_with_failure(scan_report, sent_tx, &logger); assert_eq!( result, PendingPayableScanReport { - still_pending: vec![], failures: vec![PendingPayableId::new(777777, hash,)], confirmed: vec![] } @@ -3201,7 +3250,7 @@ mod tests { let subject = PendingPayableScannerBuilder::new().build(); let rowid = 455; let hash = make_tx_hash(0x913); - let fingerprint = SentTx { + let sent_tx = SentTx { timestamp: SystemTime::now().sub(Duration::from_millis(10000)), gas_price_wei: 0, nonce: 0, @@ -3210,13 +3259,9 @@ mod tests { receiver_address: Default::default(), block_opt: None, }; - let msg = ReportTransactionReceipts { - receipt_results: vec![ - TransactionReceiptResult::RpcResponse(TxReceipt { - transaction_hash: hash, - status: TxStatus::Failed(), - } - )], + let msg = TxStatusReport { + results: vec![ + TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new(sent_tx, TxStatus::Pending))], response_skeleton_opt: None, }; @@ -3275,7 +3320,7 @@ mod tests { #[should_panic( expected = "Unsuccessful attempt for transactions 0x00000000000000000000000000000000000\ 0000000000000000000000000014d, 0x000000000000000000000000000000000000000000000000000000\ - 00000001bc to mark fatal error at payable fingerprint due to UpdateFailed(\"no no no\"); \ + 00000001bc to mark fatal error at sent tx record due to UpdateFailed(\"no no no\"); \ database unreliable" )] fn handle_failed_transactions_panics_when_it_fails_to_mark_failure() { @@ -3308,9 +3353,9 @@ mod tests { 0000000000021a of verified transactions due to RecordDeletion(\"the database \ is fooling around with us\")" )] - fn confirm_transactions_panics_while_deleting_pending_payable_fingerprint() { + fn confirm_transactions_panics_while_deleting_sent_tx_records() { let payable_dao = PayableDaoMock::new().transactions_confirmed_result(Ok(())); - let sent_payable_dao = SentPayableDaoMock::default().delete_fingerprints_result(Err( + let sent_payable_dao = SentPayableDaoMock::default().delete_records_result(Err( SentPayableDaoError::SqlExecutionFailed( "the database is fooling around with us".to_string(), ), @@ -3319,14 +3364,12 @@ mod tests { .payable_dao(payable_dao) .sent_payable_dao(sent_payable_dao) .build(); - let mut fingerprint_1 = make_pending_payable_fingerprint(); - fingerprint_1.rowid = 1; - fingerprint_1.hash = make_tx_hash(0x315); - let mut fingerprint_2 = make_pending_payable_fingerprint(); - fingerprint_2.rowid = 1; - fingerprint_2.hash = make_tx_hash(0x21a); - - subject.confirm_transactions(vec![fingerprint_1, fingerprint_2], &Logger::new("test")); + let mut sent_tx_1 = make_sent_tx(456); + sent_tx_1.hash = make_tx_hash(0x315); + let mut sent_tx_2 = make_sent_tx(789); + sent_tx_2.hash = make_tx_hash(0x21a); + + subject.confirm_transactions(vec![sent_tx_1, sent_tx_2], &Logger::new("test")); } #[test] @@ -3424,11 +3467,10 @@ mod tests { let mut subject = PendingPayableScannerBuilder::new() .payable_dao(payable_dao) .build(); - let mut fingerprint = make_pending_payable_fingerprint(); - fingerprint.rowid = rowid; - fingerprint.hash = hash; + let mut sent_tx = make_sent_tx(456); + sent_tx.hash = hash; - subject.confirm_transactions(vec![fingerprint], &Logger::new("test")); + subject.confirm_transactions(vec![sent_tx], &Logger::new("test")); } #[test] @@ -3478,47 +3520,32 @@ mod tests { let payable_dao = PayableDaoMock::new() .transactions_confirmed_params(&transactions_confirmed_params_arc) .transactions_confirmed_result(Ok(())); - let sent_payable_dao = SentPayableDaoMock::new().delete_fingerprints_result(Ok(())); + let sent_payable_dao = SentPayableDaoMock::new().delete_records_result(Ok(())); let mut pending_payable_scanner = PendingPayableScannerBuilder::new() .payable_dao(payable_dao) .sent_payable_dao(sent_payable_dao) .build(); let transaction_hash_1 = make_tx_hash(4545); - let transaction_receipt_1 = TxReceipt { - transaction_hash: transaction_hash_1, - status: TxStatus::Succeeded(TransactionBlock { + let mut sent_tx_1 = make_sent_tx(123); + sent_tx_1.hash = transaction_hash_1; + let transaction_with_status_1 = SentTxWithLatestStatus::new( + sent_tx_1, + TxStatus::Succeeded(TransactionBlock { block_hash: Default::default(), block_number: U64::from(1234), }), - }; - let fingerprint_1 = SentTx { - rowid: 5, - timestamp: from_unix_timestamp(200_000_000), - hash: transaction_hash_1, - attempt: 2, - amount: 444, - process_error: None, - }; + ); let transaction_hash_2 = make_tx_hash(1234); - let transaction_receipt_2 = TxReceipt { - transaction_hash: transaction_hash_2, - status: TxStatus::Succeeded(TransactionBlock { + let mut sent_tx_2 = make_sent_tx(789); + sent_tx_2.hash = transaction_hash_2; + let transaction_with_status_2 = SentTxWithLatestStatus::new(sent_tx_2, TxStatus::Succeeded(TransactionBlock { block_hash: Default::default(), block_number: U64::from(2345), - }), - }; - let fingerprint_2 = SentTx { - rowid: 10, - timestamp: from_unix_timestamp(199_780_000), - hash: transaction_hash_2, - attempt: 15, - amount: 1212, - process_error: None, - }; - let msg = ReportTransactionReceipts { - receipt_results: vec![ - TransactionReceiptResult::RpcResponse(transaction_receipt_1), - TransactionReceiptResult::RpcResponse(transaction_receipt_2), + })); + let msg = TxStatusReport { + results: vec![ + TxReceiptResult::RpcResponse(transaction_with_status_1), + TxReceiptResult::RpcResponse(transaction_with_status_2), ], response_skeleton_opt: None, }; @@ -3535,7 +3562,7 @@ mod tests { ); assert_eq!( *transactions_confirmed_params, - vec![vec![fingerprint_1, fingerprint_2]] + vec![vec![sent_tx_1, sent_tx_2]] ); assert_eq!(subject.scan_started_at(ScanType::PendingPayables), None); TestLogHandler::new().assert_logs_match_in_order(vec![ @@ -3553,8 +3580,8 @@ mod tests { let test_name = "pending_payable_scanner_handles_report_transaction_receipts_message_with_empty_vector"; let mut pending_payable_scanner = PendingPayableScannerBuilder::new().build(); - let msg = ReportTransactionReceipts { - receipt_results: vec![], + let msg = TxStatusReport { + results: vec![], response_skeleton_opt: None, }; pending_payable_scanner.mark_as_started(SystemTime::now()); @@ -4058,7 +4085,7 @@ mod tests { &logger, &log_handler, ); - assert_elapsed_time_in_mark_as_ended::( + assert_elapsed_time_in_mark_as_ended::( &mut PendingPayableScannerBuilder::new().build(), "PendingPayables", test_name, diff --git a/node/src/accountant/scanners/scanners_utils.rs b/node/src/accountant/scanners/scanners_utils.rs index 3f6e03c9a..10e51662d 100644 --- a/node/src/accountant/scanners/scanners_utils.rs +++ b/node/src/accountant/scanners/scanners_utils.rs @@ -1,7 +1,7 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. pub mod payable_scanner_utils { - use crate::accountant::db_access_objects::utils::ThresholdUtils; + use crate::accountant::db_access_objects::utils::{ThresholdUtils, TxHash}; use crate::accountant::db_access_objects::payable_dao::{PayableAccount, PayableDaoError}; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableTransactingErrorEnum::{ LocallyCausedError, RemotelyCausedErrors, @@ -12,6 +12,7 @@ pub mod payable_scanner_utils { use itertools::Itertools; use masq_lib::logger::Logger; use std::cmp::Ordering; + use std::collections::HashSet; use std::ops::Not; use std::time::SystemTime; use thousands::Separable; @@ -23,7 +24,7 @@ pub mod payable_scanner_utils { #[derive(Debug, PartialEq, Eq)] pub enum PayableTransactingErrorEnum { LocallyCausedError(PayableTransactionError), - RemotelyCausedErrors(Vec), + RemotelyCausedErrors(HashSet), } #[derive(Debug, PartialEq)] @@ -101,6 +102,7 @@ pub mod payable_scanner_utils { oldest.balance_wei, oldest.age) } + // TODO lifetimes simplification??? pub fn separate_errors<'a, 'b>( sent_payables: &'a SentPayables, logger: &'b Logger, @@ -110,15 +112,19 @@ pub mod payable_scanner_utils { if individual_batch_responses.is_empty() { panic!("Broken code: An empty vector of processed payments claiming to be an Ok value") } - let (oks, err_hashes_opt) = + + let separated_txs_by_result = separate_rpc_results(individual_batch_responses, logger); - let remote_errs_opt = err_hashes_opt.map(RemotelyCausedErrors); + + let remote_errs_opt = if separated_txs_by_result.err_results.is_empty() {None} else {Some(RemotelyCausedErrors(separated_txs_by_result.err_results))}; + let oks = separated_txs_by_result.ok_results; + (oks, remote_errs_opt) } Err(e) => { warning!( logger, - "Any persisted data from failed process will be deleted. Caused by: {}", + "Any persisted data from the failed process will be deleted. Caused by: {}", e ); @@ -127,55 +133,46 @@ pub mod payable_scanner_utils { } } - fn separate_rpc_results<'a, 'b>( + fn separate_rpc_results<'a>( batch_request_responses: &'a [ProcessedPayableFallible], - logger: &'b Logger, - ) -> (Vec<&'a PendingPayable>, Option>) { + logger: &Logger, + ) -> SeparatedTxsByResult<'a> { //TODO maybe we can return not tuple but struct with remote_errors_opt member - let (oks, errs) = batch_request_responses + let init = SeparatedTxsByResult::default(); + batch_request_responses .iter() - .fold((vec![], vec![]), |acc, rpc_result| { - fold_guts(acc, rpc_result, logger) - }); - - let errs_opt = if !errs.is_empty() { Some(errs) } else { None }; - - (oks, errs_opt) - } - - fn add_pending_payable<'a>( - (mut oks, errs): (Vec<&'a PendingPayable>, Vec), - pending_payable: &'a PendingPayable, - ) -> SeparateTxsByResult<'a> { - oks.push(pending_payable); - (oks, errs) + .fold(init, |acc, rpc_result| { + separate_rpc_results_fold_guts(acc, rpc_result, logger) + }) } - fn add_rpc_failure((oks, mut errs): SeparateTxsByResult, hash: H256) -> SeparateTxsByResult { - errs.push(hash); - (oks, errs) + #[derive(Default)] + pub struct SeparatedTxsByResult<'a> { + pub ok_results: Vec<&'a PendingPayable>, + pub err_results: HashSet } - type SeparateTxsByResult<'a> = (Vec<&'a PendingPayable>, Vec); - - fn fold_guts<'a, 'b>( - acc: SeparateTxsByResult<'a>, + fn separate_rpc_results_fold_guts<'a>( + mut acc: SeparatedTxsByResult<'a>, rpc_result: &'a ProcessedPayableFallible, - logger: &'b Logger, - ) -> SeparateTxsByResult<'a> { + logger: &Logger, + ) -> SeparatedTxsByResult<'a> { match rpc_result { ProcessedPayableFallible::Correct(pending_payable) => { - add_pending_payable(acc, pending_payable) + acc.ok_results.push(pending_payable); + acc } ProcessedPayableFallible::Failed(RpcPayableFailure { rpc_error, recipient_wallet, hash, }) => { - warning!(logger, "Remote transaction failure: '{}' for payment to {} and transaction hash {:?}. \ - Please check your blockchain service URL configuration.", rpc_error, recipient_wallet, hash + warning!(logger, "Remote transaction failure: '{}' for payment to {} and \ + transaction hash {:?}. Please check your blockchain service URL configuration.", + rpc_error, recipient_wallet, hash ); - add_rpc_failure(acc, *hash) + acc.err_results.insert(*hash); + acc } } } @@ -323,7 +320,7 @@ pub mod pending_payable_scanner_utils { use std::time::SystemTime; use crate::accountant::db_access_objects::failed_payable_dao::FailedTx; use crate::accountant::db_access_objects::sent_payable_dao::SentTx; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxReceiptLocalError}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxBlockchainFailure, TxReceiptRequestError}; #[derive(Debug, Default, PartialEq, Eq, Clone)] pub struct PendingPayableScanReport { @@ -352,7 +349,7 @@ pub mod pending_payable_scanner_utils { // // pub fn handle_none_status( // mut scan_report: PendingPayableScanReport, - // fingerprint: SentTx, + // sent_tx: SentTx, // max_pending_interval: u64, // logger: &Logger, // ) -> PendingPayableScanReport { @@ -360,11 +357,11 @@ pub mod pending_payable_scanner_utils { // logger, // "Pending transaction {:?} couldn't be confirmed at attempt \ // {} at {}ms after its sending", - // fingerprint.hash, - // fingerprint.attempt, - // elapsed_in_ms(fingerprint.timestamp) + // sent_tx.hash, + // sent_tx.attempt, + // elapsed_in_ms(sent_tx.timestamp) // ); - // let elapsed = fingerprint + // let elapsed = sent_tx // .timestamp // .elapsed() // .expect("we should be older now"); @@ -376,14 +373,14 @@ pub mod pending_payable_scanner_utils { // ({}sec) with the age {}sec and the confirmation process is going to be aborted now \ // at the final attempt {}; manual resolution is required from the \ // user to complete the transaction.", - // fingerprint.hash, + // sent_tx.hash, // max_pending_interval, // elapsed, - // fingerprint.attempt + // sent_tx.attempt // ); - // scan_report.failures.push(fingerprint.into()) + // scan_report.failures.push(sent_tx.into()) // } else { - // scan_report.still_pending.push(fingerprint.into()) + // scan_report.still_pending.push(sent_tx.into()) // } // scan_report // } @@ -398,11 +395,11 @@ pub mod pending_payable_scanner_utils { // logger, // "Transaction {:?} has been added to the blockchain; detected locally at attempt \ // {} at {}ms after its sending", - // fingerprint.hash, - // fingerprint.attempt, - // elapsed_in_ms(fingerprint.timestamp) + // sent_tx.hash, + // sent_tx.attempt, + // elapsed_in_ms(sent_tx.timestamp) // ); - // scan_report.confirmed.push(fingerprint); + // scan_report.confirmed.push(sent_tx); // scan_report } @@ -422,9 +419,9 @@ pub mod pending_payable_scanner_utils { scan_report } - pub fn handle_local_error_fetching_receipts( + pub fn handle_request_error_fetching_receipts( mut scan_report: PendingPayableScanReport, - local_error: TxReceiptLocalError, + local_error: TxReceiptRequestError, logger: &Logger, ) -> PendingPayableScanReport { todo!() @@ -442,6 +439,12 @@ pub mod pending_payable_scanner_utils { // .push(PendingPayableId::new(payable.rowid, payable.hash)); // scan_report } + + impl From<(SentTx, TxBlockchainFailure)> for FailedTx { + fn from(_: (SentTx, TxBlockchainFailure)) -> Self { + todo!() + } + } } pub mod receivable_scanner_utils { diff --git a/node/src/accountant/scanners/test_utils.rs b/node/src/accountant/scanners/test_utils.rs index 26aa15dc3..d5b0af8c4 100644 --- a/node/src/accountant/scanners/test_utils.rs +++ b/node/src/accountant/scanners/test_utils.rs @@ -19,7 +19,7 @@ use crate::accountant::scanners::{ Scanner, StartScanError, StartableScanner, }; use crate::accountant::{ - ReceivedPayments, ReportTransactionReceipts, RequestTransactionReceipts, ResponseSkeleton, + ReceivedPayments, TxStatusReport, RequestTransactionReceipts, ResponseSkeleton, SentPayables, }; use crate::blockchain::blockchain_bridge::RetrieveTransactions; @@ -369,7 +369,7 @@ pub enum ScannerReplacement { PendingPayableScanner, ScannerMock< RequestTransactionReceipts, - ReportTransactionReceipts, + TxStatusReport, PendingPayableScanResult, >, >, diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs index 1d13531a0..6f8f3d64b 100644 --- a/node/src/accountant/test_utils.rs +++ b/node/src/accountant/test_utils.rs @@ -279,7 +279,7 @@ impl AccountantBuilder { self } - pub fn pending_payable_daos( + pub fn sent_payable_daos( mut self, specially_configured_daos: Vec>, ) -> Self { @@ -1070,7 +1070,7 @@ pub struct SentPayableDaoMock { impl SentPayableDao for SentPayableDaoMock { fn get_tx_identifiers(&self, hashes: &HashSet) -> TxIdentifiers{ - self.get_tx_identifiers_params.push(hashes); + 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: &[SentTx]) -> Result<(), SentPayableDaoError>{ @@ -1310,6 +1310,7 @@ impl PendingPayableScannerBuilder { PendingPayableScanner::new( Box::new(self.payable_dao), Box::new(self.sent_payable_dao), + todo!("Utkarsh?"), Rc::new(self.payment_thresholds), self.when_pending_too_long_sec, Rc::new(RefCell::new(self.financial_statistics)), @@ -1381,17 +1382,6 @@ pub fn make_custom_payment_thresholds() -> PaymentThresholds { } } -pub fn make_pending_payable_fingerprint() -> SentTx { - SentTx { - rowid: 33, - timestamp: from_unix_timestamp(222_222_222), - hash: make_tx_hash(456), - attempt: 1, - amount: 12345, - process_error: None, - } -} - pub fn make_qualified_and_unqualified_payables( now: SystemTime, payment_thresholds: &PaymentThresholds, diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index de1bcecbd..e634faeef 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -7,7 +7,7 @@ use crate::accountant::{ ReceivedPayments, ResponseSkeleton, ScanError, SentPayables, SkeletonOptHolder, }; -use crate::accountant::{ReportTransactionReceipts, RequestTransactionReceipts}; +use crate::accountant::{TxStatusReport, RequestTransactionReceipts}; use crate::actor_system_factory::SubsFactory; use crate::blockchain::blockchain_interface::blockchain_interface_web3::HashAndAmount; use crate::blockchain::blockchain_interface::data_structures::errors::{ @@ -46,8 +46,9 @@ use ethabi::Hash; use web3::types::H256; use masq_lib::messages::ScanType; use crate::accountant::db_access_objects::payable_dao::PayableAccount; +use crate::accountant::db_access_objects::sent_payable_dao::SentTx; use crate::accountant::scanners::payable_scanner_extension::blockchain_agent::BlockchainAgent; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionReceiptResult, TxStatus}; +use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxReceiptResult, TxStatus}; pub const CRASH_KEY: &str = "BLOCKCHAINBRIDGE"; pub const DEFAULT_BLOCKCHAIN_SERVICE_URL: &str = "https://0.0.0.0"; @@ -65,8 +66,8 @@ pub struct BlockchainBridge { } struct TransactionConfirmationTools { - new_pp_fingerprints_sub_opt: Option>, - report_transaction_receipts_sub_opt: Option>, + new_pp_fingerprints_sub_opt: Option>, + report_transaction_receipts_sub_opt: Option>, } #[derive(PartialEq, Eq)] @@ -94,7 +95,7 @@ impl Handler for BlockchainBridge { Some(msg.peer_actors.accountant.init_pending_payable_fingerprints); self.pending_payable_confirmation .report_transaction_receipts_sub_opt = - Some(msg.peer_actors.accountant.report_transaction_receipts); + Some(msg.peer_actors.accountant.report_transaction_status); self.payable_payments_setup_subs_opt = Some(msg.peer_actors.accountant.report_payable_payments_setup); self.sent_payable_subs_opt = Some(msg.peer_actors.accountant.report_sent_payments); @@ -166,22 +167,15 @@ impl Handler for BlockchainBridge { } #[derive(Debug, Clone, PartialEq, Eq, Message)] -pub struct PendingPayableFingerprintSeeds { - pub batch_wide_timestamp: SystemTime, - pub hashes_and_balances: Vec, +pub struct RegisterNewPendingSentTxMessage { + pub new_sent_tx: Vec } -// #[derive(Debug, PartialEq, Eq, Clone)] -// pub struct SentTx { -// // Sqlite begins counting from 1 -// pub rowid: u64, -// pub timestamp: SystemTime, -// pub hash: H256, -// // We have Sqlite begin counting from 1 -// pub attempt: u16, -// pub amount: u128, -// pub process_error: Option, -// } +impl RegisterNewPendingSentTxMessage { + pub fn new(sent_txs: Vec) -> Self { + todo!() + } +} impl Handler for BlockchainBridge { type Result = (); @@ -394,20 +388,21 @@ impl BlockchainBridge { fn log_status_of_tx_receipts( logger: &Logger, - transaction_receipts_results: &[TransactionReceiptResult], + transaction_receipts_results: &[TxReceiptResult], ) { logger.debug(|| { let (successful_count, failed_count, pending_count) = transaction_receipts_results.iter().fold( (0, 0, 0), |(success, fail, pending), transaction_receipt| match transaction_receipt { - TransactionReceiptResult::RpcResponse(tx_receipt) => { + TxReceiptResult::RpcResponse(tx_receipt) => { match tx_receipt.status { TxStatus::Failed(_) => (success, fail + 1, pending), TxStatus::Succeeded(_) => (success + 1, fail, pending), + TxStatus::Pending => (success, fail, pending + 1), } } - TransactionReceiptResult::LocalError(_) => (success, fail, pending + 1), + TxReceiptResult::RequestError(_) => (success, fail, pending + 1), }, ); format!( @@ -447,7 +442,7 @@ impl BlockchainBridge { // .collect_vec(); // // accountant_recipient - // .try_send(ReportTransactionReceipts { + // .try_send(TxStatusReport { // response_skeleton_opt: msg.response_skeleton_opt, // }) // .expect("Accountant is dead"); @@ -497,7 +492,7 @@ impl BlockchainBridge { ) } - fn new_fingerprints_recipient(&self) -> Recipient { + fn new_fingerprints_recipient(&self) -> Recipient { self.pending_payable_confirmation .new_pp_fingerprints_sub_opt .clone() @@ -549,7 +544,7 @@ mod tests { use crate::accountant::db_access_objects::utils::from_unix_timestamp; use crate::accountant::scanners::payable_scanner_extension::agent_web3::WEB3_MAXIMAL_GAS_LIMIT_MARGIN; use crate::accountant::scanners::payable_scanner_extension::test_utils::BlockchainAgentMock; - use crate::accountant::test_utils::{make_payable_account, make_pending_payable_fingerprint}; + use crate::accountant::test_utils::{make_payable_account, make_sent_tx}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::BlockchainInterfaceWeb3; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError::TransactionID; use crate::blockchain::blockchain_interface::data_structures::errors::{ @@ -595,7 +590,7 @@ mod tests { use masq_lib::constants::DEFAULT_MAX_BLOCK_COUNT; use crate::accountant::db_access_objects::sent_payable_dao::SentTx; use crate::accountant::PendingPayable; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, TxReceipt}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{SentTxWithLatestStatus, TransactionBlock, TxReceiptRequestError}; impl Handler> for BlockchainBridge { type Result = (); @@ -899,7 +894,7 @@ mod tests { let time_after = SystemTime::now(); let accountant_recording = accountant_recording_arc.lock().unwrap(); let pending_payable_fingerprint_seeds_msg = - accountant_recording.get_record::(0); + accountant_recording.get_record::(0); let sent_payables_msg = accountant_recording.get_record::(1); assert_eq!( sent_payables_msg, @@ -985,7 +980,7 @@ mod tests { system.run(); let accountant_recording = accountant_recording_arc.lock().unwrap(); let pending_payable_fingerprint_seeds_msg = - accountant_recording.get_record::(0); + accountant_recording.get_record::(0); let sent_payables_msg = accountant_recording.get_record::(1); let scan_error_msg = accountant_recording.get_record::(2); assert_sending_error( @@ -1145,17 +1140,10 @@ mod tests { fn blockchain_bridge_processes_requests_for_a_complete_and_null_transaction_receipt() { let (accountant, _, accountant_recording_arc) = make_recorder(); let accountant = accountant.system_stop_conditions(match_lazily_every_type_id!(ScanError)); - let pending_payable_fingerprint_1 = make_pending_payable_fingerprint(); - let hash_1 = pending_payable_fingerprint_1.hash; - let hash_2 = make_tx_hash(78989); - let pending_payable_fingerprint_2 = SentTx { - rowid: 456, - timestamp: SystemTime::now(), - hash: hash_2, - attempt: 3, - amount: 4565, - process_error: None, - }; + let sent_tx_1 = make_sent_tx(123); + let hash_1 = sent_tx_1.hash; + let sent_tx_2 = make_sent_tx(456); + let hash_2 = sent_tx_2.hash; let first_response = ReceiptResponseBuilder::default() .status(U64::from(1)) .transaction_hash(hash_1) @@ -1179,9 +1167,9 @@ mod tests { let peer_actors = peer_actors_builder().accountant(accountant).build(); send_bind_message!(subject_subs, peer_actors); let msg = RequestTransactionReceipts { - pending_payable_fingerprints: vec![ - pending_payable_fingerprint_1.clone(), - pending_payable_fingerprint_2.clone(), + sent_tx: vec![ + sent_tx_1.clone(), + sent_tx_2.clone(), ], response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, @@ -1195,26 +1183,21 @@ mod tests { system.run(); let accountant_recording = accountant_recording_arc.lock().unwrap(); assert_eq!(accountant_recording.len(), 1); - let report_transaction_receipt_message = - accountant_recording.get_record::(0); + let tx_status_report_message = + accountant_recording.get_record::(0); let mut expected_receipt = TransactionReceipt::default(); expected_receipt.transaction_hash = hash_1; expected_receipt.status = Some(U64::from(1)); assert_eq!( - report_transaction_receipt_message, - &ReportTransactionReceipts { - fingerprints_with_receipts: vec![ - ( - TransactionReceiptResult::RpcResponse(expected_receipt.into()), - pending_payable_fingerprint_1 - ), - ( - TransactionReceiptResult::RpcResponse(TxReceipt { - transaction_hash: hash_2, - status: TxStatus::Pending - }), - pending_payable_fingerprint_2 - ), + tx_status_report_message, + &TxStatusReport { + results: vec![ + TxReceiptResult::RpcResponse( + SentTxWithLatestStatus::new(sent_tx_1, expected_receipt.into())), + TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( + sent_tx_2, + TxStatus::Pending + )) ], response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, @@ -1304,50 +1287,22 @@ mod tests { let (accountant, _, accountant_recording_arc) = make_recorder(); let accountant_addr = accountant .system_stop_conditions(match_lazily_every_type_id!( - ReportTransactionReceipts, + TxStatusReport, ScanError )) .start(); - let report_transaction_receipt_recipient: Recipient = + let report_transaction_receipt_recipient: Recipient = accountant_addr.clone().recipient(); let scan_error_recipient: Recipient = accountant_addr.recipient(); let hash_1 = make_tx_hash(111334); let hash_2 = make_tx_hash(100000); let hash_3 = make_tx_hash(0x1348d); let hash_4 = make_tx_hash(11111); - let mut fingerprint_1 = make_pending_payable_fingerprint(); - fingerprint_1.hash = hash_1; - let fingerprint_2 = SentTx { - rowid: 454, - timestamp: SystemTime::now(), - hash: hash_2, - attempt: 3, - amount: 3333, - process_error: None, - }; - let fingerprint_3 = SentTx { - rowid: 456, - timestamp: SystemTime::now(), - hash: hash_3, - attempt: 3, - amount: 4565, - process_error: None, - }; - let fingerprint_4 = SentTx { - rowid: 450, - timestamp: from_unix_timestamp(230_000_000), - hash: hash_4, - attempt: 1, - amount: 7879, - process_error: None, - }; - let transaction_receipt = TxReceipt { - transaction_hash: Default::default(), - status: TxStatus::Succeeded(TransactionBlock { - block_hash: Default::default(), - block_number, - }), - }; + let mut sent_tx_1 = make_sent_tx(123); + sent_tx_1.hash = hash_1; + let sent_tx_2 = make_sent_tx(456); + let sent_tx_3 = make_sent_tx(789); + let sent_tx_4 = make_sent_tx(1011); let blockchain_interface = make_blockchain_interface_web3(port); let system = System::new("test_transaction_receipts"); let mut subject = BlockchainBridge::new( @@ -1360,11 +1315,11 @@ mod tests { .report_transaction_receipts_sub_opt = Some(report_transaction_receipt_recipient); subject.scan_error_subs_opt = Some(scan_error_recipient); let msg = RequestTransactionReceipts { - pending_payable_fingerprints: vec![ - fingerprint_1.clone(), - fingerprint_2.clone(), - fingerprint_3.clone(), - fingerprint_4.clone(), + sent_tx: vec![ + sent_tx_1.clone(), + sent_tx_2.clone(), + sent_tx_3.clone(), + sent_tx_4.clone(), ], response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, @@ -1378,15 +1333,21 @@ mod tests { assert_eq!(system.run(), 0); let accountant_recording = accountant_recording_arc.lock().unwrap(); assert_eq!(accountant_recording.len(), 1); - let report_receipts_msg = accountant_recording.get_record::(0); + let report_receipts_msg = accountant_recording.get_record::(0); assert_eq!( *report_receipts_msg, - ReportTransactionReceipts { - fingerprints_with_receipts: vec![ - (TransactionReceiptResult::RpcResponse(TxReceipt{ transaction_hash: hash_1, status: TxStatus::Pending }), fingerprint_1), - (TransactionReceiptResult::RpcResponse(transaction_receipt), fingerprint_2), - (TransactionReceiptResult::RpcResponse(TxReceipt{ transaction_hash: hash_3, status: TxStatus::Pending }), fingerprint_3), - (TransactionReceiptResult::LocalError("RPC error: Error { code: ServerError(429), message: \"The requests per second (RPS) of your requests are higher than your plan allows.\", data: None }".to_string()), fingerprint_4) + TxStatusReport { + results: vec![ + TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new(sent_tx_1, TxStatus::Pending)), + TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new(sent_tx_2, TxStatus::Succeeded(TransactionBlock { + block_hash: Default::default(), + block_number, + }))), + TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new(sent_tx_3, TxStatus::Pending)), + TxReceiptResult::RequestError( + TxReceiptRequestError::new( + sent_tx_4.hash, + "RPC error: Error { code: ServerError(429), message: \"The requests per second (RPS) of your requests are higher than your plan allows.\", data: None }".to_string())) ], response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, @@ -1407,25 +1368,11 @@ mod tests { .system_stop_conditions(match_lazily_every_type_id!(ScanError)) .start(); let scan_error_recipient: Recipient = accountant_addr.clone().recipient(); - let report_transaction_recipient: Recipient = + let report_transaction_recipient: Recipient = accountant_addr.recipient(); let hash_1 = make_tx_hash(0x1b2e6); - let fingerprint_1 = SentTx { - rowid: 454, - timestamp: SystemTime::now(), - hash: hash_1, - attempt: 3, - amount: 3333, - process_error: None, - }; - let fingerprint_2 = SentTx { - rowid: 456, - timestamp: SystemTime::now(), - hash: make_tx_hash(222444), - attempt: 3, - amount: 4565, - process_error: None, - }; + let sent_tx_1 = make_sent_tx(123); + let sent_tx_2 = make_sent_tx(456); let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port).start(); let blockchain_interface = make_blockchain_interface_web3(port); @@ -1439,7 +1386,7 @@ mod tests { .report_transaction_receipts_sub_opt = Some(report_transaction_recipient); subject.scan_error_subs_opt = Some(scan_error_recipient); let msg = RequestTransactionReceipts { - pending_payable_fingerprints: vec![fingerprint_1, fingerprint_2], + sent_tx: vec![sent_tx_1, sent_tx_2], response_skeleton_opt: None, }; let system = System::new("test"); diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs index 378ec7a81..60de73776 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs @@ -13,64 +13,79 @@ use web3::contract::{Contract, Options}; use web3::transports::{Batch, Http}; use web3::types::{Address, BlockNumber, Filter, Log, TransactionReceipt}; use web3::{Error, Web3}; -use crate::accountant::db_access_objects::failed_payable_dao::FailedTx; +use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, FailureReason}; +use crate::accountant::db_access_objects::sent_payable_dao::SentTx; use crate::accountant::db_access_objects::utils::TxHash; use crate::sub_lib::wallet::Wallet; #[derive(Debug, PartialEq, Eq, Clone)] -pub enum TransactionReceiptResult { - RpcResponse(TxReceipt), - //TODO shouldn't I write this so that it would refer to the tx by its hash? - LocalError(TxReceiptLocalError), +pub enum TxReceiptResult { + RpcResponse(SentTxWithLatestStatus), + RequestError(TxReceiptRequestError), } #[derive(Debug, PartialEq, Eq, Clone)] -pub struct TxReceipt { - pub transaction_hash: H256, +pub struct SentTxWithLatestStatus{ + pub sent_tx: SentTx, pub status: TxStatus, } -impl From for TxReceipt { +impl SentTxWithLatestStatus { + pub fn new(sent_tx: SentTx, status: TxStatus) -> Self { + todo!() + } +} + +// #[derive(Debug, PartialEq, Eq, Clone)] +// pub struct LightweightTxReceipt { +// pub transaction_hash: H256, +// pub status: TxStatus, +// } + +impl From for TxStatus { fn from(receipt: TransactionReceipt) -> Self { - let status = match (receipt.status, receipt.block_hash, receipt.block_number) { + match (receipt.status, receipt.block_hash, receipt.block_number) { (Some(status), Some(block_hash), Some(block_number)) if status == U64::from(1) => { TxStatus::Succeeded(TransactionBlock { block_hash, block_number, }) } - (Some(status), _, _) if status == U64::from(0) => TxStatus::Failed, + (Some(status), _, _) if status == U64::from(0) => todo!(), //TxStatus::Failed(TxBlockchainFailure::Unknown), _ => TxStatus::Pending, - }; - - TxReceipt { - transaction_hash: receipt.transaction_hash, - status, } } } #[derive(Debug, PartialEq, Eq, Clone)] pub enum TxStatus { - Failed(FailedTx), - Succeeded(ConfirmedTx), + Failed(TxBlockchainFailure), + Succeeded(TransactionBlock), + Pending, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum TxBlockchainFailure { + Unknown } impl Display for TxStatus { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - TxStatus::Failed(failed_tx) => todo!(), // write!(f, "Failed"), + TxStatus::Failed(reason) => todo!("make sure there is an assertion for this new syntax"), //write!(f, "Failed({:?})", reason), TxStatus::Succeeded(block) => { write!( f, "Succeeded({},{:?})", - block.block.block_number, block.block.block_hash + block.block_number, block.block_hash ) } + TxStatus::Pending => write!(f, "Pending"), } } } +// TODO figure out where this could be used???? // impl FromStr for TxStatus { // type Err = String; // @@ -100,11 +115,21 @@ impl Display for TxStatus { // } #[derive(Debug, PartialEq, Eq, Clone)] -pub struct TxReceiptLocalError{ +pub struct TxReceiptRequestError{ tx_hash: TxHash, err_msg: String } +impl TxReceiptRequestError { + pub fn new(tx_hash: TxHash, err_msg: String) -> Self { + todo!() + // Self { + // tx_hash, + // err_msg + // } + } +} + #[derive(Debug, Default, PartialEq, Eq, Clone)] pub struct TransactionBlock { pub block_hash: H256, @@ -229,7 +254,7 @@ mod tests { use crate::blockchain::blockchain_interface::blockchain_interface_web3::TRANSACTION_LITERAL; use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainError::QueryFailed; use crate::blockchain::blockchain_interface::{BlockchainError, BlockchainInterface}; - use crate::blockchain::test_utils::make_blockchain_interface_web3; + use crate::blockchain::test_utils::{make_blockchain_interface_web3, make_tx_hash}; use crate::sub_lib::wallet::Wallet; use crate::test_utils::make_wallet; use ethereum_types::{H256, U64}; @@ -238,7 +263,9 @@ mod tests { use masq_lib::utils::find_free_port; use std::str::FromStr; use web3::types::{BlockNumber, Bytes, FilterBuilder, Log, TransactionReceipt, U256}; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, TxReceipt, TxStatus}; + use crate::accountant::db_access_objects::sent_payable_dao::SentTx; + use crate::accountant::test_utils::make_sent_tx; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{SentTxWithLatestStatus, TransactionBlock, TxStatus}; #[test] fn get_transaction_fee_balance_works() { @@ -601,15 +628,13 @@ mod tests { #[test] fn transaction_receipt_can_be_converted_to_successful_transaction() { - let tx_receipt: TxReceipt = create_tx_receipt( + let sent_tx_with_status = test_deriving_tx_status_from_tx_receipt_and_adding_to_sent_tx( + make_sent_tx(456), Some(U64::from(1)), - Some(H256::from_low_u64_be(0x1234)), - Some(U64::from(10)), - H256::from_low_u64_be(0x5678), ); - assert_eq!(tx_receipt.transaction_hash, H256::from_low_u64_be(0x5678)); - match tx_receipt.status { + assert_eq!(sent_tx_with_status.sent_tx.hash, H256::from_low_u64_be(0x5678)); + match sent_tx_with_status.status { TxStatus::Succeeded(ref block) => { assert_eq!(block.block_hash, H256::from_low_u64_be(0x1234)); assert_eq!(block.block_number, U64::from(10)); @@ -620,46 +645,40 @@ mod tests { #[test] fn transaction_receipt_can_be_converted_to_failed_transaction() { - let tx_receipt: TxReceipt = create_tx_receipt( + let sent_tx_with_status = test_deriving_tx_status_from_tx_receipt_and_adding_to_sent_tx( + make_sent_tx(12345), Some(U64::from(0)), - None, - None, - H256::from_low_u64_be(0x5678), ); - assert_eq!(tx_receipt.transaction_hash, H256::from_low_u64_be(0x5678)); - assert_eq!(tx_receipt.status, TxStatus::Failed); + assert_eq!(sent_tx_with_status.sent_tx.hash, H256::from_low_u64_be(0x5678)); + assert_eq!(sent_tx_with_status.status, TxStatus::Failed); } #[test] fn transaction_receipt_can_be_converted_to_pending_transaction_no_status() { - let tx_receipt: TxReceipt = - create_tx_receipt(None, None, None, H256::from_low_u64_be(0x5678)); + let sent_tx_with_status = + test_deriving_tx_status_from_tx_receipt_and_adding_to_sent_tx(make_sent_tx(789),None); - assert_eq!(tx_receipt.transaction_hash, H256::from_low_u64_be(0x5678)); - assert_eq!(tx_receipt.status, TxStatus::Pending); + assert_eq!(sent_tx_with_status.sent_tx.hash, H256::from_low_u64_be(0x5678)); + assert_eq!(sent_tx_with_status.status, TxStatus::Pending); } #[test] fn transaction_receipt_can_be_converted_to_pending_transaction_no_block_info() { - let tx_receipt: TxReceipt = create_tx_receipt( + let tx_receipt = test_deriving_tx_status_from_tx_receipt_and_adding_to_sent_tx( + make_sent_tx(123), Some(U64::from(1)), - None, - None, - H256::from_low_u64_be(0x5678), ); - assert_eq!(tx_receipt.transaction_hash, H256::from_low_u64_be(0x5678)); + assert_eq!(tx_receipt.sent_tx.hash, H256::from_low_u64_be(0x5678)); assert_eq!(tx_receipt.status, TxStatus::Pending); } #[test] fn transaction_receipt_can_be_converted_to_pending_transaction_no_status_and_block_info() { - let tx_receipt: TxReceipt = create_tx_receipt( + let tx_receipt = test_deriving_tx_status_from_tx_receipt_and_adding_to_sent_tx( + Some(U64::from(1)), - Some(H256::from_low_u64_be(0x1234)), - None, - H256::from_low_u64_be(0x5678), ); assert_eq!(tx_receipt.transaction_hash, H256::from_low_u64_be(0x5678)); @@ -686,65 +705,65 @@ mod tests { format!("Succeeded({},0x{:x})", block_number, block_hash) ); } - - #[test] - fn tx_status_from_str_works() { - // Test Pending - assert_eq!(TxStatus::from_str("Pending"), Ok(TxStatus::Pending)); - - // Test Failed - assert_eq!(TxStatus::from_str("Failed"), Ok(TxStatus::Failed)); - - // Test Succeeded with valid input - let block_number = 123456789; - let block_hash = H256::from_low_u64_be(0xabcdef); - let input = format!("Succeeded({},0x{:x})", block_number, block_hash); - assert_eq!( - TxStatus::from_str(&input), - Ok(TxStatus::Succeeded(TransactionBlock { - block_hash, - block_number: U64::from(block_number), - })) - ); - - // Test Succeeded with invalid format - assert_eq!( - TxStatus::from_str("Succeeded(123)"), - Err("Invalid Succeeded format".to_string()) - ); - - // Test Succeeded with invalid block number - assert_eq!( - TxStatus::from_str( - "Succeeded(abc,0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef)" - ), - Err("Invalid block number".to_string()) - ); - - // Test Succeeded with invalid block hash - assert_eq!( - TxStatus::from_str("Succeeded(123,0xinvalidhash)"), - Err("Invalid block hash".to_string()) - ); - - // Test unknown status - assert_eq!( - TxStatus::from_str("InProgress"), - Err("Unknown status: InProgress".to_string()) - ); - } - - fn create_tx_receipt( - status: Option, - block_hash: Option, - block_number: Option, + // + // #[test] + // fn tx_status_from_str_works() { + // // Test Pending + // assert_eq!(TxStatus::from_str("Pending"), Ok(TxStatus::Pending)); + // + // // Test Failed + // assert_eq!(TxStatus::from_str("Failed"), Ok(TxStatus::Failed)); + // + // // Test Succeeded with valid input + // let block_number = 123456789; + // let block_hash = H256::from_low_u64_be(0xabcdef); + // let input = format!("Succeeded({},0x{:x})", block_number, block_hash); + // assert_eq!( + // TxStatus::from_str(&input), + // Ok(TxStatus::Succeeded(TransactionBlock { + // block_hash, + // block_number: U64::from(block_number), + // })) + // ); + // + // // Test Succeeded with invalid format + // assert_eq!( + // TxStatus::from_str("Succeeded(123)"), + // Err("Invalid Succeeded format".to_string()) + // ); + // + // // Test Succeeded with invalid block number + // assert_eq!( + // TxStatus::from_str( + // "Succeeded(abc,0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef)" + // ), + // Err("Invalid block number".to_string()) + // ); + // + // // Test Succeeded with invalid block hash + // assert_eq!( + // TxStatus::from_str("Succeeded(123,0xinvalidhash)"), + // Err("Invalid block hash".to_string()) + // ); + // + // // Test unknown status + // assert_eq!( + // TxStatus::from_str("InProgress"), + // Err("Unknown status: InProgress".to_string()) + // ); + // } + + fn test_deriving_tx_status_from_tx_receipt_and_adding_to_sent_tx( + status_opt: Option, + block_hash_opt: Option, + block_number_opt: Option, transaction_hash: H256, - ) -> TxReceipt { + ) -> TxStatus { let receipt = TransactionReceipt { - status, + status: status_opt, root: None, - block_hash, - block_number, + block_hash: block_hash_opt, + block_number: block_number_opt, cumulative_gas_used: Default::default(), gas_used: None, contract_address: None, @@ -753,6 +772,7 @@ mod tests { logs: vec![], logs_bloom: Default::default(), }; + receipt.into() } } diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs index 852b02a4e..8510bf605 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs @@ -22,8 +22,9 @@ use ethereum_types::U64; use web3::transports::{EventLoopHandle, Http}; use web3::types::{Address, Log, H256, U256, FilterBuilder, TransactionReceipt, BlockNumber}; use crate::accountant::db_access_objects::payable_dao::PayableAccount; -use crate::blockchain::blockchain_bridge::{BlockMarker, BlockScanRange, PendingPayableFingerprintSeeds}; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{LowBlockchainIntWeb3, TransactionReceiptResult, TxReceipt, TxStatus}; +use crate::accountant::db_access_objects::sent_payable_dao::SentTx; +use crate::blockchain::blockchain_bridge::{BlockMarker, BlockScanRange, RegisterNewPendingSentTxMessage}; +use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{LowBlockchainIntWeb3, TxReceiptResult, TxStatus, SentTxWithLatestStatus, TxReceiptRequestError}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::utils::{create_blockchain_agent_web3, send_payables_within_batch, BlockchainAgentFutureResult}; const CONTRACT_ABI: &str = indoc!( @@ -206,37 +207,35 @@ impl BlockchainInterface for BlockchainInterfaceWeb3 { fn process_transaction_receipts( &self, - transaction_hashes: Vec, - ) -> Box, Error = BlockchainError>> { + sent_txs: Vec, + ) -> Box, Error = BlockchainError>> { + let transaction_hashes = sent_txs.iter().map(|tx| tx.hash).collect::>(); Box::new( self.lower_interface() - .get_transaction_receipt_in_batch(transaction_hashes.clone()) + .get_transaction_receipt_in_batch(transaction_hashes) .map_err(move |e| e) .and_then(move |batch_response| { Ok(batch_response .into_iter() - .zip(transaction_hashes) - .map(|(response, hash)| match response { + .zip(sent_txs.into_iter()) + .map(|(response, sent_tx)| match response { Ok(result) => { match serde_json::from_value::(result) { Ok(receipt) => { - TransactionReceiptResult::RpcResponse(receipt.into()) + TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new(sent_tx, receipt.into())) } Err(e) => { if e.to_string().contains("invalid type: null") { - TransactionReceiptResult::RpcResponse(TxReceipt { - transaction_hash: hash, - status: TxStatus::Pending, - }) + TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new(sent_tx, TxStatus::Pending)) } else { - TransactionReceiptResult::LocalError(e.to_string()) + TxReceiptResult::RequestError(TxReceiptRequestError::new(sent_tx.hash, e.to_string())) } } } } - Err(e) => TransactionReceiptResult::LocalError(e.to_string()), + Err(e) => TxReceiptResult::RequestError(TxReceiptRequestError::new(sent_tx.hash, e.to_string())), }) - .collect::>()) + .collect::>()) }), ) } @@ -245,7 +244,7 @@ impl BlockchainInterface for BlockchainInterfaceWeb3 { &self, logger: Logger, agent: Box, - fingerprints_recipient: Recipient, + fingerprints_recipient: Recipient, affordable_accounts: Vec, ) -> Box, Error = PayableTransactionError>> { @@ -459,7 +458,8 @@ mod tests { use std::str::FromStr; use web3::transports::Http; use web3::types::{H256, U256}; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, TxReceipt, TxStatus}; + use crate::accountant::test_utils::make_sent_tx; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, TxBlockchainFailure, TxStatus}; #[test] fn constants_are_correct() { @@ -956,26 +956,17 @@ mod tests { #[test] fn process_transaction_receipts_works() { let port = find_free_port(); - let tx_hash_1 = - H256::from_str("a128f9ca1e705cc20a936a24a7fa1df73bad6e0aaf58e8e6ffcc154a7cff6e0e") - .unwrap(); - let tx_hash_2 = - H256::from_str("a128f9ca1e705cc20a936a24a7fa1df73bad6e0aaf58e8e6ffcc154a7cff6e0f") - .unwrap(); - let tx_hash_3 = - H256::from_str("a128f9ca1e705cc20a936a24a7fa1df73bad6e0aaf58e8e6ffcc154a7cff6e0a") - .unwrap(); - let tx_hash_4 = - H256::from_str("a128f9ca1e705cc20a936a24a7fa1df73bad6e0aaf58e8e6ffcc154a7cff6e0b") - .unwrap(); - let tx_hash_5 = - H256::from_str("a128f9ca1e705cc20a936a24a7fa1df73bad6e0aaf58e8e6ffcc154a7cff6e0c") - .unwrap(); - let tx_hash_6 = - H256::from_str("a128f9ca1e705cc20a936a24a7fa1df73bad6e0aaf58e8e6ffcc154a7cff6e0d") - .unwrap(); - let tx_hash_vec = vec![ - tx_hash_1, tx_hash_2, tx_hash_3, tx_hash_4, tx_hash_5, tx_hash_6, + let sent_tx_1 = make_sent_tx(3300); + let sent_tx_2 = + make_sent_tx(3401); + let sent_tx_3 = + make_sent_tx(3502); + let sent_tx_4 = + make_sent_tx(3603); + let sent_tx_5 = make_sent_tx(3704); + let sent_tx_6 = make_sent_tx(3805); + let sent_tx_vec = vec![ + sent_tx_1, sent_tx_2, sent_tx_3, sent_tx_4.clone(), sent_tx_5.clone(), sent_tx_6.clone(), ]; let block_hash = H256::from_str("6d0abccae617442c26104c2bc63d1bc05e1e002e555aec4ab62a46e826b18f18") @@ -986,14 +977,14 @@ mod tests { let status = U64::from(1); let status_failed = U64::from(0); let tx_receipt_response_not_present = ReceiptResponseBuilder::default() - .transaction_hash(tx_hash_4) + .transaction_hash(sent_tx_4.hash) .build(); let tx_receipt_response_failed = ReceiptResponseBuilder::default() - .transaction_hash(tx_hash_5) + .transaction_hash(sent_tx_5.hash) .status(status_failed) .build(); let tx_receipt_response_success = ReceiptResponseBuilder::default() - .transaction_hash(tx_hash_6) + .transaction_hash(sent_tx_6.hash) .block_hash(block_hash) .block_number(block_number) .cumulative_gas_used(cumulative_gas_used) @@ -1018,48 +1009,52 @@ mod tests { let subject = make_blockchain_interface_web3(port); let result = subject - .process_transaction_receipts(tx_hash_vec) + .process_transaction_receipts(sent_tx_vec) .wait() .unwrap(); - assert_eq!(result[0], TransactionReceiptResult::LocalError("RPC error: Error { code: ServerError(429), message: \"The requests per second (RPS) of your requests are higher than your plan allows.\", data: None }".to_string())); + assert_eq!(result[0], TxReceiptResult::RequestError( + TxReceiptRequestError::new( + sent_tx_1.hash, + "RPC error: Error { code: ServerError(429), message: \"The requests per second (RPS) of your requests are higher than your plan allows.\", data: None }".to_string()))); assert_eq!( result[1], - TransactionReceiptResult::RpcResponse(TxReceipt { - transaction_hash: tx_hash_2, - status: TxStatus::Pending - }) + TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( + sent_tx_2, + TxStatus::Pending + )) ); assert_eq!( result[2], - TransactionReceiptResult::LocalError( + TxReceiptResult::RequestError(TxReceiptRequestError::new( + sent_tx_3.hash, "invalid type: string \"trash\", expected struct Receipt".to_string() ) - ); + )); assert_eq!( result[3], - TransactionReceiptResult::RpcResponse(TxReceipt { - transaction_hash: tx_hash_4, - status: TxStatus::Pending - }) - ); + TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( + sent_tx_4, TxStatus::Pending + ) + )); assert_eq!( result[4], - TransactionReceiptResult::RpcResponse(TxReceipt { - transaction_hash: tx_hash_5, - status: TxStatus::Failed, - }) - ); + TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( + sent_tx_5, + TxStatus::Failed(TxBlockchainFailure::Unknown) + ) + )); assert_eq!( result[5], - TransactionReceiptResult::RpcResponse(TxReceipt { - transaction_hash: tx_hash_6, - status: TxStatus::Succeeded(TransactionBlock { + TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( + sent_tx_6, + TxStatus::Succeeded(TransactionBlock { block_hash, block_number, }), - }) - ); + ) + ) + ) } #[test] @@ -1067,13 +1062,11 @@ mod tests { let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port).start(); let subject = make_blockchain_interface_web3(port); - let tx_hash_1 = - H256::from_str("a128f9ca1e705cc20a936a24a7fa1df73bad6e0aaf58e8e6ffcc154a7cff6e0e") - .unwrap(); - let tx_hash_2 = - H256::from_str("a128f9ca1e705cc20a936a24a7fa1df73bad6e0aaf58e8e6ffcc154a7cff6e0f") - .unwrap(); - let tx_hash_vec = vec![tx_hash_1, tx_hash_2]; + let sent_tx_1 = + make_sent_tx(789); + let sent_tx_2 = + make_sent_tx(123); + let tx_hash_vec = vec![sent_tx_1, sent_tx_2]; let error = subject .process_transaction_receipts(tx_hash_vec) diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs index 9f8bac478..0bd88d523 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs @@ -1,9 +1,10 @@ // Copyright (c) 2024, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. +use std::collections::HashSet; use crate::accountant::db_access_objects::payable_dao::PayableAccount; use crate::accountant::scanners::payable_scanner_extension::agent_web3::BlockchainAgentWeb3; use crate::accountant::scanners::payable_scanner_extension::blockchain_agent::BlockchainAgent; -use crate::blockchain::blockchain_bridge::PendingPayableFingerprintSeeds; +use crate::blockchain::blockchain_bridge::RegisterNewPendingSentTxMessage; use crate::blockchain::blockchain_interface::blockchain_interface_web3::{ BlockchainInterfaceWeb3, HashAndAmount, TRANSFER_METHOD_ID, }; @@ -26,6 +27,8 @@ use web3::transports::{Batch, Http}; use web3::types::{Bytes, SignedTransaction, TransactionParameters, U256}; use web3::Error as Web3Error; use web3::Web3; +use crate::accountant::db_access_objects::sent_payable_dao::SentTx; +use crate::accountant::db_access_objects::utils::TxHash; use crate::accountant::PendingPayable; #[derive(Debug)] @@ -40,43 +43,31 @@ pub fn advance_used_nonce(current_nonce: U256) -> U256 { .expect("unexpected limits") } -fn error_with_hashes( - error: Web3Error, - hashes_and_paid_amounts: Vec, -) -> PayableTransactionError { - let hashes = hashes_and_paid_amounts - .into_iter() - .map(|hash_and_amount| hash_and_amount.hash) - .collect(); - PayableTransactionError::Sending { - msg: error.to_string(), - hashes, - } -} - +// TODO using these three vectors like this is dangerous; who guarantees that all three have their +// items sorted in the right order? pub fn merged_output_data( responses: Vec>, - hashes_and_paid_amounts: Vec, + sent_tx_hashes: Vec, accounts: Vec, ) -> Vec { let iterator_with_all_data = responses .into_iter() - .zip(hashes_and_paid_amounts.into_iter()) + .zip(sent_tx_hashes.into_iter()) .zip(accounts.iter()); iterator_with_all_data .map( - |((rpc_result, hash_and_amount), account)| match rpc_result { + |((rpc_result, hash), account)| match rpc_result { Ok(_rpc_result) => { // TODO: GH-547: This rpc_result should be validated ProcessedPayableFallible::Correct(PendingPayable { recipient_wallet: account.wallet.clone(), - hash: hash_and_amount.hash, + hash, }) } Err(rpc_error) => ProcessedPayableFallible::Failed(RpcPayableFailure { rpc_error, recipient_wallet: account.wallet.clone(), - hash: hash_and_amount.hash, + hash, }), }, ) @@ -218,7 +209,7 @@ pub fn sign_and_append_multiple_payments( gas_price_in_wei: u128, mut pending_nonce: U256, accounts: &[PayableAccount], -) -> Vec { +) -> Vec { let mut hash_and_amount_list = vec![]; accounts.iter().for_each(|payable| { debug!( @@ -241,7 +232,8 @@ pub fn sign_and_append_multiple_payments( pending_nonce = advance_used_nonce(pending_nonce); hash_and_amount_list.push(hash_and_amount); }); - hash_and_amount_list + + todo!() } #[allow(clippy::too_many_arguments)] @@ -252,7 +244,7 @@ pub fn send_payables_within_batch( consuming_wallet: Wallet, gas_price_in_wei: u128, pending_nonce: U256, - new_fingerprints_recipient: Recipient, + new_fingerprints_recipient: Recipient, accounts: Vec, ) -> Box, Error = PayableTransactionError> + 'static> { @@ -265,7 +257,7 @@ pub fn send_payables_within_batch( gas_price_in_wei ); - let hashes_and_paid_amounts = sign_and_append_multiple_payments( + let prepared_sent_txs_records = sign_and_append_multiple_payments( logger, chain, web3_batch, @@ -275,16 +267,13 @@ pub fn send_payables_within_batch( &accounts, ); - let timestamp = SystemTime::now(); - let hashes_and_paid_amounts_error = hashes_and_paid_amounts.clone(); - let hashes_and_paid_amounts_ok = hashes_and_paid_amounts.clone(); - - // TODO: We are sending hashes_and_paid_amounts to the Accountant even if the payments fail. + let sent_txs_hashes: Vec = prepared_sent_txs_records.iter().map(|sent_tx|sent_tx.hash).collect(); + let planned_sent_txs_hashes = HashSet::from_iter(sent_txs_hashes.clone().into_iter()); + + let new_pending_sent_tx_message = RegisterNewPendingSentTxMessage::new(prepared_sent_txs_records); + new_fingerprints_recipient - .try_send(PendingPayableFingerprintSeeds { - batch_wide_timestamp: timestamp, - hashes_and_balances: hashes_and_paid_amounts, - }) + .try_send(new_pending_sent_tx_message) .expect("Accountant is dead"); info!( @@ -297,11 +286,15 @@ pub fn send_payables_within_batch( web3_batch .transport() .submit_batch() - .map_err(|e| error_with_hashes(e, hashes_and_paid_amounts_error)) - .and_then(move |batch_response| { + .map_err(move |e| + PayableTransactionError::Sending { + msg: e.to_string(), + hashes: planned_sent_txs_hashes, + } + ).and_then(move |batch_response| { Ok(merged_output_data( batch_response, - hashes_and_paid_amounts_ok, + sent_txs_hashes, accounts, )) }), @@ -612,11 +605,16 @@ mod tests { system.run(); let timestamp_after = SystemTime::now(); let accountant_recording_result = accountant_recording.lock().unwrap(); - let ppfs_message = - accountant_recording_result.get_record::(0); + let rnpst_message = + accountant_recording_result.get_record::(0); assert_eq!(accountant_recording_result.len(), 1); - assert!(timestamp_before <= ppfs_message.batch_wide_timestamp); - assert!(timestamp_after >= ppfs_message.batch_wide_timestamp); + rnpst_message.new_sent_tx.iter().for_each(|tx| { + + + todo!("add more assertions"); + assert!(timestamp_before <= from_unix_timestamp(tx.timestamp)); + assert!(timestamp_after >= from_unix_timestamp(tx.timestamp)); + }); let tlh = TestLogHandler::new(); tlh.exists_log_containing( &format!("DEBUG: {test_name}: Common attributes of payables to be transacted: sender wallet: {}, contract: {:?}, chain_id: {}, gas_price: {}", diff --git a/node/src/blockchain/blockchain_interface/data_structures/errors.rs b/node/src/blockchain/blockchain_interface/data_structures/errors.rs index 3084accfb..a89e021a1 100644 --- a/node/src/blockchain/blockchain_interface/data_structures/errors.rs +++ b/node/src/blockchain/blockchain_interface/data_structures/errors.rs @@ -1,11 +1,13 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. +use std::collections::HashSet; use crate::accountant::comma_joined_stringifiable; -use itertools::Either; +use itertools::{Either, Itertools}; use std::fmt; use std::fmt::{Display, Formatter}; use variant_count::VariantCount; use web3::types::{Address, H256}; +use crate::accountant::db_access_objects::utils::TxHash; const BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED: &str = "Uninitialized blockchain interface. To avoid \ being delinquency-banned, you should restart the Node with a value for blockchain-service-url"; @@ -41,7 +43,7 @@ pub enum PayableTransactionError { TransactionID(BlockchainError), UnusableWallet(String), Signing(String), - Sending { msg: String, hashes: Vec }, + Sending { msg: String, hashes: HashSet }, UninitializedBlockchainInterface, } @@ -63,12 +65,14 @@ impl Display for PayableTransactionError { msg ), Self::Signing(msg) => write!(f, "Signing phase: \"{}\"", msg), - Self::Sending { msg, hashes } => write!( + Self::Sending { msg, hashes } => { + let hashes = hashes.iter().map(|hash| *hash).collect_vec(); + write!( f, "Sending phase: \"{}\". Signed and hashed transactions: {}", msg, - comma_joined_stringifiable(hashes, |hash| format!("{:?}", hash)) - ), + comma_joined_stringifiable(&hashes, |hash| format!("{:?}", hash)) + )}, Self::UninitializedBlockchainInterface => { write!(f, "{}", BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED) } diff --git a/node/src/blockchain/blockchain_interface/data_structures/mod.rs b/node/src/blockchain/blockchain_interface/data_structures/mod.rs index 74a82846a..5d3ca1000 100644 --- a/node/src/blockchain/blockchain_interface/data_structures/mod.rs +++ b/node/src/blockchain/blockchain_interface/data_structures/mod.rs @@ -8,6 +8,7 @@ use std::fmt; use std::fmt::Formatter; use web3::types::H256; use web3::Error; +use crate::accountant::db_access_objects::utils::TxHash; use crate::accountant::PendingPayable; #[derive(Clone, Debug, Eq, PartialEq)] @@ -37,7 +38,7 @@ pub struct RetrievedBlockchainTransactions { pub struct RpcPayableFailure { pub rpc_error: Error, pub recipient_wallet: Wallet, - pub hash: H256, + pub hash: TxHash, } #[derive(Debug, PartialEq, Clone)] diff --git a/node/src/blockchain/blockchain_interface/mod.rs b/node/src/blockchain/blockchain_interface/mod.rs index bdcbf6a91..a0ca922e7 100644 --- a/node/src/blockchain/blockchain_interface/mod.rs +++ b/node/src/blockchain/blockchain_interface/mod.rs @@ -5,7 +5,6 @@ pub mod data_structures; pub mod lower_level_interface; use actix::Recipient; -use ethereum_types::H256; use crate::accountant::scanners::payable_scanner_extension::blockchain_agent::BlockchainAgent; use crate::blockchain::blockchain_interface::data_structures::errors::{BlockchainAgentBuildError, BlockchainError, PayableTransactionError}; use crate::blockchain::blockchain_interface::data_structures::{ProcessedPayableFallible, RetrievedBlockchainTransactions}; @@ -16,8 +15,9 @@ use masq_lib::blockchains::chains::Chain; use web3::types::Address; use masq_lib::logger::Logger; use crate::accountant::db_access_objects::payable_dao::PayableAccount; -use crate::blockchain::blockchain_bridge::{BlockMarker, BlockScanRange, PendingPayableFingerprintSeeds}; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::TransactionReceiptResult; +use crate::accountant::db_access_objects::sent_payable_dao::SentTx; +use crate::blockchain::blockchain_bridge::{BlockMarker, BlockScanRange, RegisterNewPendingSentTxMessage}; +use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::TxReceiptResult; pub trait BlockchainInterface { fn contract_address(&self) -> Address; @@ -40,14 +40,14 @@ pub trait BlockchainInterface { fn process_transaction_receipts( &self, - transaction_hashes: Vec, - ) -> Box, Error = BlockchainError>>; + sent_tx: Vec, + ) -> Box, Error = BlockchainError>>; fn submit_payables_in_batch( &self, logger: Logger, agent: Box, - fingerprints_recipient: Recipient, + fingerprints_recipient: Recipient, affordable_accounts: Vec, ) -> Box, Error = PayableTransactionError>>; diff --git a/node/src/sub_lib/accountant.rs b/node/src/sub_lib/accountant.rs index 0db335303..41b755718 100644 --- a/node/src/sub_lib/accountant.rs +++ b/node/src/sub_lib/accountant.rs @@ -4,11 +4,11 @@ use crate::accountant::db_access_objects::payable_dao::PayableDaoFactory; use crate::accountant::db_access_objects::receivable_dao::ReceivableDaoFactory; use crate::accountant::scanners::payable_scanner_extension::msgs::BlockchainAgentWithContextMessage; use crate::accountant::{ - checked_conversion, Accountant, ReceivedPayments, ReportTransactionReceipts, ScanError, + checked_conversion, Accountant, ReceivedPayments, TxStatusReport, ScanError, SentPayables, }; use crate::actor_system_factory::SubsFactory; -use crate::blockchain::blockchain_bridge::PendingPayableFingerprintSeeds; +use crate::blockchain::blockchain_bridge::RegisterNewPendingSentTxMessage; use crate::db_config::config_dao::ConfigDaoFactory; use crate::sub_lib::neighborhood::ConfigChangeMsg; use crate::sub_lib::peer_actors::{BindMessage, StartMessage}; @@ -100,8 +100,8 @@ pub struct AccountantSubs { pub report_services_consumed: Recipient, pub report_payable_payments_setup: Recipient, pub report_inbound_payments: Recipient, - pub init_pending_payable_fingerprints: Recipient, - pub report_transaction_receipts: Recipient, + pub init_pending_payable_fingerprints: Recipient, + pub report_transaction_status: Recipient, pub report_sent_payments: Recipient, pub scan_errors: Recipient, pub ui_message_sub: Recipient, diff --git a/node/src/test_utils/recorder.rs b/node/src/test_utils/recorder.rs index 6633ee948..417a2faa3 100644 --- a/node/src/test_utils/recorder.rs +++ b/node/src/test_utils/recorder.rs @@ -7,8 +7,8 @@ use crate::accountant::{ ReceivedPayments, RequestTransactionReceipts, ScanError, ScanForNewPayables, ScanForReceivables, SentPayables, }; -use crate::accountant::{ReportTransactionReceipts, ScanForPendingPayables, ScanForRetryPayables}; -use crate::blockchain::blockchain_bridge::PendingPayableFingerprintSeeds; +use crate::accountant::{TxStatusReport, ScanForPendingPayables, ScanForRetryPayables}; +use crate::blockchain::blockchain_bridge::RegisterNewPendingSentTxMessage; use crate::blockchain::blockchain_bridge::RetrieveTransactions; use crate::daemon::crash_notification::CrashNotification; use crate::daemon::DaemonBindMessage; @@ -153,7 +153,7 @@ recorder_message_handler_t_m_p!(NodeFromUiMessage); recorder_message_handler_t_m_p!(NodeToUiMessage); recorder_message_handler_t_m_p!(NoLookupIncipientCoresPackage); recorder_message_handler_t_p!(OutboundPaymentsInstructions); -recorder_message_handler_t_m_p!(PendingPayableFingerprintSeeds); +recorder_message_handler_t_m_p!(RegisterNewPendingSentTxMessage); recorder_message_handler_t_m_p!(PoolBindMessage); recorder_message_handler_t_m_p!(QualifiedPayablesMessage); recorder_message_handler_t_m_p!(ReceivedPayments); @@ -162,7 +162,7 @@ recorder_message_handler_t_m_p!(RemoveStreamMsg); recorder_message_handler_t_m_p!(ReportExitServiceProvidedMessage); recorder_message_handler_t_m_p!(ReportRoutingServiceProvidedMessage); recorder_message_handler_t_m_p!(ReportServicesConsumedMessage); -recorder_message_handler_t_m_p!(ReportTransactionReceipts); +recorder_message_handler_t_m_p!(TxStatusReport); recorder_message_handler_t_m_p!(RequestTransactionReceipts); recorder_message_handler_t_m_p!(RetrieveTransactions); recorder_message_handler_t_m_p!(ScanError); @@ -529,8 +529,8 @@ pub fn make_accountant_subs_from_recorder(addr: &Addr) -> AccountantSu report_services_consumed: recipient!(addr, ReportServicesConsumedMessage), report_payable_payments_setup: recipient!(addr, BlockchainAgentWithContextMessage), report_inbound_payments: recipient!(addr, ReceivedPayments), - init_pending_payable_fingerprints: recipient!(addr, PendingPayableFingerprintSeeds), - report_transaction_receipts: recipient!(addr, ReportTransactionReceipts), + init_pending_payable_fingerprints: recipient!(addr, RegisterNewPendingSentTxMessage), + report_transaction_status: recipient!(addr, TxStatusReport), report_sent_payments: recipient!(addr, SentPayables), scan_errors: recipient!(addr, ScanError), ui_message_sub: recipient!(addr, NodeFromUiMessage), From 9276aadd87eda6f8c595d4ab839d078b64d552d7 Mon Sep 17 00:00:00 2001 From: Bert Date: Mon, 7 Jul 2025 12:51:21 +0200 Subject: [PATCH 05/61] GH-642: big initial messy reconstruction... just realized I may've forgotten to update with the last changes from the other card --- .../db_access_objects/payable_dao.rs | 159 +++++------ node/src/accountant/mod.rs | 34 +-- node/src/accountant/scanners/mod.rs | 265 ++++++++---------- .../src/accountant/scanners/scanners_utils.rs | 53 ++-- .../blockchain_interface_web3/utils.rs | 32 ++- 5 files changed, 272 insertions(+), 271 deletions(-) diff --git a/node/src/accountant/db_access_objects/payable_dao.rs b/node/src/accountant/db_access_objects/payable_dao.rs index c68bbb780..d500241e9 100644 --- a/node/src/accountant/db_access_objects/payable_dao.rs +++ b/node/src/accountant/db_access_objects/payable_dao.rs @@ -568,6 +568,9 @@ mod tests { use rusqlite::{ToSql}; use std::path::Path; use std::str::FromStr; + use std::time::{Duration, UNIX_EPOCH}; + use itertools::Itertools; + use libc::iovec; use crate::accountant::db_access_objects::sent_payable_dao::SentTx; use crate::database::test_utils::ConnectionWrapperMock; @@ -913,64 +916,62 @@ mod tests { struct TxWalletAndTimestamp{ pending_payable: SentTx, - wallet: Wallet, previous_timestamp: SystemTime, } + + struct TestInputs{ + hash: TxHash, + rowid: u64, + previous_timestamp: SystemTime, + new_payable_timestamp: SystemTime, + wallet: Address, + initial_amount_wei: u128, + balance_change: u128, + } - fn insert_initial_payable_records_and_return_pending_payable( + fn insert_initial_payable_records_and_return_sent_txs( conn: &dyn ConnectionWrapper, - initial_amount_1: u128, - initial_amount_2: u128, - balance_change_1: u128, - balance_change_2: u128, + (initial_amount_1, balance_change_1): (u128,u128), + (initial_amount_2, balance_change_2): (u128,u128) ) -> TestSetupValuesHolder { - let hash_1 = make_tx_hash(12345); - let rowid_1 = 789; - let previous_timestamp_1_s = 190_000_000; - let new_payable_timestamp_1 = from_unix_timestamp(199_000_000); - let wallet_1 = make_wallet("bobble"); - let hash_2 = make_tx_hash(54321); - let rowid_2 = 792; - let previous_timestamp_2_s = 187_100_000; - let new_payable_timestamp_2 = from_unix_timestamp(191_333_000); - let wallet_2 = make_wallet("booble bobble"); - { + let now = SystemTime::now(); + let (account_1,account_2) = [TestInputs { + hash: make_tx_hash(12345), + rowid: 789, + previous_timestamp: now.checked_sub(Duration::from_secs(45_000)).unwrap(), + new_payable_timestamp: now.checked_sub(Duration::from_secs(2)).unwrap(), + wallet: make_wallet("bobbles").address(), + initial_amount_wei: initial_amount_1, + balance_change: balance_change_1, + }, + TestInputs{ + hash: make_tx_hash(54321), + rowid: 792, + previous_timestamp: now.checked_sub(Duration::from_secs(22_000)).unwrap(), + new_payable_timestamp: now.checked_sub(Duration::from_secs(2)).unwrap(), + wallet: make_wallet("yet more bobbles").address(), + initial_amount_wei: initial_amount_2, + balance_change: balance_change_2, + }].into_iter().enumerate().map(|(idx, test_inputs)|{ + insert_payable_record_fn( conn, - &wallet_1.to_string(), - i128::try_from(initial_amount_1).unwrap(), - previous_timestamp_1_s, - Some(rowid_1 as i64), + &test_inputs.wallet.to_string(), + i128::try_from(test_inputs.initial_amount_wei).unwrap(), + to_unix_timestamp(test_inputs.previous_timestamp), + // TODO argument will be eliminated in GH-662 + None ); - insert_payable_record_fn( - conn, - &wallet_2.to_string(), - i128::try_from(initial_amount_2).unwrap(), - previous_timestamp_2_s, - Some(rowid_2 as i64), - ) - } - // let fingerprint_1 = SentTx { - // rowid: rowid_1, - // timestamp: new_payable_timestamp_1, - // hash: hash_1, - // attempt: 1, - // amount: balance_change_1, - // process_error: None, - // }; - // let fingerprint_2 = SentTx { - // rowid: rowid_2, - // timestamp: new_payable_timestamp_2, - // hash: hash_2, - // attempt: 1, - // amount: balance_change_2, - // process_error: None, - // }; - // let previous_timestamp_1 = from_unix_timestamp(previous_timestamp_1_s); - // let previous_timestamp_2 = from_unix_timestamp(previous_timestamp_2_s); + + let mut sent_tx = make_sent_tx(idx as u64 * 1234); + sent_tx.amount = test_inputs.balance_change; + + TxWalletAndTimestamp{ pending_payable: sent_tx, previous_timestamp: test_inputs.previous_timestamp } + }).collect_tuple().unwrap(); + TestSetupValuesHolder { - account_1: todo!(), - account_2: todo!() + account_1, + account_2 } } @@ -981,7 +982,7 @@ mod tests { //initial (1, 9999) let initial_changing_end_resulting_values = (initial, 11111, initial as u128 - 11111); //change (-1, abs(i64::MIN) - 11111) - transaction_confirmed_works( + test_transaction_confirmed_works( "transaction_confirmed_works_without_overflow", initial_changing_end_resulting_values, ) @@ -994,35 +995,35 @@ mod tests { //initial (0, 10000) //change (-1, abs(i64::MIN) - 111) //10000 + (abs(i64::MIN) - 111) > i64::MAX -> overflow - transaction_confirmed_works( + test_transaction_confirmed_works( "transaction_confirmed_works_hitting_overflow", initial_changing_end_resulting_values, ) } - fn transaction_confirmed_works( + fn test_transaction_confirmed_works( test_name: &str, (initial_amount_1, balance_change_1, expected_balance_after_1): (u128, u128, u128), ) { let home_dir = ensure_node_home_directory_exists("payable_dao", test_name); - //a hardcoded set that just makes a complement to the crucial, supplied one; this points to the ability of - //handling multiple transactions together + // A hardcoded set that just makes a complement to the crucial, supplied first one; this + // shows the ability to handle multiple transactions together let initial_amount_2 = 5_678_901; let balance_change_2 = 678_902; let expected_balance_after_2 = 4_999_999; let boxed_conn = DbInitializerReal::default() .initialize(&home_dir, DbInitializationConfig::test_default()) .unwrap(); - let setup_holder = insert_initial_payable_records_and_return_pending_payable( + let setup_holder = insert_initial_payable_records_and_return_sent_txs( boxed_conn.as_ref(), - initial_amount_1, - initial_amount_2, - balance_change_1, - balance_change_2, + (initial_amount_1, balance_change_1), + (initial_amount_2, balance_change_2), ); let subject = PayableDaoReal::new(boxed_conn); - let status_1_before_opt = subject.account_status(&setup_holder.account_1.wallet); - let status_2_before_opt = subject.account_status(&setup_holder.account_2.wallet); + let wallet_1 = Wallet::from(setup_holder.account_1.pending_payable.receiver_address); + let wallet_2 = Wallet::from(setup_holder.account_2.pending_payable.receiver_address); + let status_1_before_opt = subject.account_status(&wallet_1); + let status_2_before_opt = subject.account_status(&wallet_2); let result = subject.transactions_confirmed(&[ setup_holder.account_1.pending_payable.clone(), @@ -1032,34 +1033,34 @@ mod tests { assert_eq!(result, Ok(())); // TODO yes these are unsensible now but it will eventually be all cleaned up with GH-662 let expected_status_before_1 = PayableAccount { - wallet: setup_holder.account_1.wallet.clone(), + wallet: wallet_1.clone(), balance_wei: initial_amount_1, last_paid_timestamp: setup_holder.account_1.previous_timestamp, pending_payable_opt: None, //hash is just garbage }; let expected_status_before_2 = PayableAccount { - wallet: setup_holder.account_2.wallet.clone(), + wallet: wallet_2.clone(), balance_wei: initial_amount_2, last_paid_timestamp: setup_holder.account_2.previous_timestamp, pending_payable_opt: None, //hash is just garbage }; let expected_resulting_status_1 = PayableAccount { - wallet: setup_holder.account_1.wallet.clone(), + wallet: wallet_1.clone(), balance_wei: expected_balance_after_1, last_paid_timestamp: setup_holder.account_1.previous_timestamp, pending_payable_opt: None, }; let expected_resulting_status_2 = PayableAccount { - wallet: setup_holder.account_2.wallet, + wallet: wallet_2.clone(), balance_wei: expected_balance_after_2, last_paid_timestamp: setup_holder.account_2.previous_timestamp, pending_payable_opt: None, }; assert_eq!(status_1_before_opt, Some(expected_status_before_1)); assert_eq!(status_2_before_opt, Some(expected_status_before_2)); - let resulting_account_1_opt = subject.account_status(&setup_holder.account_1.wallet); + let resulting_account_1_opt = subject.account_status(&wallet_1); assert_eq!(resulting_account_1_opt, Some(expected_resulting_status_1)); - let resulting_account_2_opt = subject.account_status(&setup_holder.account_2.wallet); + let resulting_account_2_opt = subject.account_status(&wallet_2); assert_eq!(resulting_account_2_opt, Some(expected_resulting_status_2)) } @@ -1117,27 +1118,27 @@ mod tests { let conn = DbInitializerReal::default() .initialize(&home_dir, DbInitializationConfig::test_default()) .unwrap(); - let setup_holder = insert_initial_payable_records_and_return_pending_payable( + let setup_holder = insert_initial_payable_records_and_return_sent_txs( conn.as_ref(), - 1_111_111, - 2_222_222, - 111_111, - 222_222, + (1_111_111,111_111), + (2_222_222,222_222) ); + let wallet_1 = Wallet::from(setup_holder.account_1.pending_payable.receiver_address); + let wallet_2 = Wallet::from(setup_holder.account_2.pending_payable.receiver_address); conn.prepare("delete from payable where wallet_address = ?") .unwrap() - .execute(&[&setup_holder.wallet_2]) + .execute(&[&wallet_1.to_string()]) .unwrap(); let subject = PayableDaoReal::new(conn); let expected_account = PayableAccount { - wallet: setup_holder.wallet_1.clone(), - balance_wei: 1_111_111 - setup_holder.fingerprint_1.amount, - last_paid_timestamp: setup_holder.fingerprint_1.timestamp, + wallet: wallet_1.clone(), + balance_wei: 1_111_111 - setup_holder.account_1.pending_payable.amount, + last_paid_timestamp: from_unix_timestamp(setup_holder.account_1.pending_payable.timestamp), pending_payable_opt: None, }; let result = subject - .transactions_confirmed(&[setup_holder.fingerprint_1, setup_holder.fingerprint_2]); + .transactions_confirmed(&[setup_holder.account_1.pending_payable, setup_holder.account_2.pending_payable]); assert_eq!( result, @@ -1146,9 +1147,9 @@ mod tests { .to_string() )) ); - let account_1_opt = subject.account_status(&setup_holder.wallet_1); + let account_1_opt = subject.account_status(&wallet_1); assert_eq!(account_1_opt, Some(expected_account)); - let account_2_opt = subject.account_status(&setup_holder.wallet_2); + let account_2_opt = subject.account_status(&wallet_2); assert_eq!(account_2_opt, None); } diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index 4fbdd7c4b..61e0c7ac7 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -1835,12 +1835,12 @@ mod tests { pending_payable_scan_interval: Duration::from_secs(100), }); let sent_tx = SentTx { - timestamp: SystemTime::now(), - gas_price_wei: 0, - nonce: 0, - hash: Default::default(), + timestamp: to_unix_timestamp(SystemTime::now()) - 1234, + gas_price_wei: 456_000_000, + nonce: 45, + hash: make_tx_hash(123), amount: 1_000_000, - receiver_address: Default::default(), + receiver_address: make_wallet("receiver_address").address(), block_opt: None, }; let sent_payable_dao = SentPayableDaoMock::default() @@ -1878,7 +1878,7 @@ mod tests { assert_eq!( blockchain_bridge_recording.get_record::(0), &RequestTransactionReceipts { - pending_payable_fingerprints: vec![sent_tx], + sent_tx: vec![sent_tx], response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, context_id: 4321, @@ -4041,12 +4041,13 @@ mod tests { #[test] fn scan_for_pending_payables_finds_still_pending_payables() { init_test_logging(); + let now = SystemTime::now(); let (blockchain_bridge, _, blockchain_bridge_recording_arc) = make_recorder(); let blockchain_bridge_addr = blockchain_bridge .system_stop_conditions(match_lazily_every_type_id!(RequestTransactionReceipts)) .start(); - let payable_fingerprint_1 = SentTx { - timestamp: from_unix_timestamp(210_000_000), + let sent_tx_1 = SentTx { + timestamp: to_unix_timestamp(now.checked_sub(Duration::from_secs(1234)).unwrap()), gas_price_wei: 0, nonce: 0, hash: make_tx_hash(45678), @@ -4054,8 +4055,8 @@ mod tests { receiver_address: Default::default(), block_opt: None, }; - let payable_fingerprint_2 = SentTx { - timestamp: from_unix_timestamp(210_000_100), + let sent_tx_2 = SentTx { + timestamp: to_unix_timestamp(now.checked_sub(Duration::from_secs(3456)).unwrap()), gas_price_wei: 0, nonce: 0, hash: make_tx_hash(112233), @@ -4064,9 +4065,9 @@ mod tests { block_opt: None, }; let sent_payable_dao = SentPayableDaoMock::default() - .return_all_errorless_fingerprints_result(vec![ - payable_fingerprint_1.clone(), - payable_fingerprint_2.clone(), + .retrieve_txs_result(vec![ + sent_tx_1.clone(), + sent_tx_2.clone(), ]); let config = bc_from_earning_wallet(make_wallet("mine")); let system = System::new("pending payable scan"); @@ -4091,8 +4092,7 @@ mod tests { assert_eq!( received_msg, &RequestTransactionReceipts { - tx_hashes: todo!(), - // pending_payable_fingerprints: vec![payable_fingerprint_1, payable_fingerprint_2], + sent_tx: vec![sent_tx_1, sent_tx_2], response_skeleton_opt: None, } ); @@ -4896,7 +4896,7 @@ mod tests { let sent_payable = SentPayables { payment_procedure_result: Err(PayableTransactionError::Sending { msg: "booga".to_string(), - hashes: vec![make_tx_hash(456)], + hashes: hashset![make_tx_hash(456)], }), response_skeleton_opt: None, }; @@ -4943,7 +4943,7 @@ mod tests { Box::new(NotifyHandleMock::default().notify_params(&retry_payable_notify_params_arc)); let system = System::new(test_name); let (mut msg, _) = - make_tx_status_report_msg(vec![TxStatus::Pending, TxStatus::Failed]); + make_tx_status_report_msg(vec![TxStatus::Pending, TxStatus::Failed(TxBlockchainFailure::Unknown)]); let response_skeleton_opt = Some(ResponseSkeleton { client_id: 45, context_id: 7, diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index cbf168da8..f4c975c74 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -12,7 +12,7 @@ use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableT LocallyCausedError, RemotelyCausedErrors, }; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{debugging_summary_after_error_separation, err_msg_for_failure_with_expected_but_missing_fingerprints, investigate_debt_extremes, mark_pending_payable_fatal_error, payables_debug_summary, separate_errors, separate_rowids_and_hashes, OperationOutcome, PayableScanResult, PayableThresholdsGauge, PayableThresholdsGaugeReal, PayableTransactingErrorEnum, PendingPayableMissingInDb}; -use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_request_error_fetching_receipts, handle_status_with_failure, handle_successful_tx, PendingPayableScanReport, PendingPayableScanResult}; +use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_request_error_fetching_receipts, handle_status_with_failure, handle_successful_tx, PendingPayableScanFinalReport, PendingPayableScanResult}; use crate::accountant::scanners::scanners_utils::receivable_scanner_utils::balance_and_age; use crate::accountant::{PendingPayable, PendingPayableId, ScanError, ScanForPendingPayables, ScanForRetryPayables}; use crate::accountant::{ @@ -680,23 +680,27 @@ impl PayableScanner { fn check_for_missing_records( &self, - sent_payables: &[&PendingPayable], + just_reported_sent_payables: &[&PendingPayable], ) -> Vec { - let actual_sent_payables_simple_total = sent_payables.len(); - let hashset_with_runtime_data_sent_payable_hashes = sent_payables + let actual_sent_payables_len = just_reported_sent_payables.len(); + let hashset_with_hashes_to_eliminate_duplicities = just_reported_sent_payables .iter() .map(|pending_payable| pending_payable.hash) .collect::>(); - if hashset_with_runtime_data_sent_payable_hashes.len() != actual_sent_payables_simple_total { + + if hashset_with_hashes_to_eliminate_duplicities.len() != actual_sent_payables_len { todo!("check potential duplicity") } - let transaction_hashes_and_rowids_from_db = self.sent_payable_dao.get_tx_identifiers(&hashset_with_runtime_data_sent_payable_hashes); + + let transaction_hashes_and_rowids_from_db = self.sent_payable_dao.get_tx_identifiers(&hashset_with_hashes_to_eliminate_duplicities); let hashes_from_db = transaction_hashes_and_rowids_from_db .keys() .copied() .collect::>(); - let missing_sent_payables_hashes:Vec = hashset_with_runtime_data_sent_payable_hashes.difference(&hashes_from_db).copied().collect(); - let mut sent_payables_hashmap = sent_payables + + let missing_sent_payables_hashes:Vec = hashset_with_hashes_to_eliminate_duplicities.difference(&hashes_from_db).copied().collect(); + + let mut sent_payables_hashmap = just_reported_sent_payables .iter() .map(|payable| (payable.hash, &payable.recipient_wallet)) .collect::>(); @@ -1029,8 +1033,8 @@ impl PendingPayableScanner { &self, msg: TxStatusReport, logger: &Logger, - ) -> PendingPayableScanReport { - let scan_report = PendingPayableScanReport::default(); + ) -> PendingPayableScanFinalReport { + let scan_report = PendingPayableScanFinalReport::default(); msg.results.into_iter().fold( scan_report, |scan_report_so_far, receipt_result| match receipt_result { @@ -1062,7 +1066,7 @@ impl PendingPayableScanner { fn process_transactions_by_their_state( &mut self, - scan_report: PendingPayableScanReport, + scan_report: PendingPayableScanFinalReport, logger: &Logger, ) -> bool { let requires_payments_retry = scan_report.requires_payments_retry(); @@ -1470,7 +1474,7 @@ mod tests { use crate::accountant::db_access_objects::utils::{from_unix_timestamp, to_unix_timestamp}; use crate::accountant::scanners::payable_scanner_extension::msgs::QualifiedPayablesMessage; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{OperationOutcome, PayableScanResult, PendingPayableMissingInDb}; - use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_status_with_failure, PendingPayableScanReport, PendingPayableScanResult}; + use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_status_with_failure, PendingPayableScanFinalReport, PendingPayableScanResult}; use crate::accountant::scanners::{Scanner, StartScanError, StartableScanner, PayableScanner, PendingPayableScanner, ReceivableScanner, ScannerCommon, Scanners, MTError}; use crate::accountant::test_utils::{make_custom_payment_thresholds, make_payable_account, make_qualified_and_unqualified_payables, make_receivable_account, BannedDaoFactoryMock, BannedDaoMock, ConfigDaoFactoryMock, PayableDaoFactoryMock, PayableDaoMock, PayableScannerBuilder, PayableThresholdsGaugeMock, SentPayableDaoFactoryMock, SentPayableDaoMock, PendingPayableScannerBuilder, ReceivableDaoFactoryMock, ReceivableDaoMock, ReceivableScannerBuilder, make_sent_tx}; use crate::accountant::{gwei_to_wei, PendingPayableId, ReceivedPayments, TxStatusReport, RequestTransactionReceipts, ScanError, ScanForRetryPayables, SentPayables, DEFAULT_PENDING_TOO_LONG_SEC, PendingPayable}; @@ -1509,6 +1513,7 @@ mod tests { use web3::Error; use masq_lib::messages::ScanType; use masq_lib::ui_gateway::NodeToUiMessage; + use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, FailureReason}; use crate::accountant::db_access_objects::sent_payable_dao::{SentPayableDaoError, SentTx}; use crate::accountant::scanners::test_utils::{assert_timestamps_from_str, parse_system_time_from_str, MarkScanner, NullScanner, ReplacementType, ScannerReplacement}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{SentTxWithLatestStatus, TransactionBlock, TxReceiptResult, TxStatus}; @@ -2061,7 +2066,7 @@ mod tests { } #[test] - fn entries_must_be_kept_consistent_and_aligned() { + fn no_missing_records() { let wallet_1 = make_wallet("abc"); let hash_1 = make_tx_hash(123); let wallet_2 = make_wallet("def"); @@ -2085,19 +2090,10 @@ mod tests { .sent_payable_dao(sent_payable_dao) .build(); - let (existent, nonexistent) = + let missing_records = subject.check_for_missing_records(&pending_payables_ref); - assert_eq!( - existent, - vec![ - PendingPayableMissingInDb::new(wallet_4.address(), hash_4), - PendingPayableMissingInDb::new(wallet_1.address(), hash_1), - PendingPayableMissingInDb::new(wallet_3.address(), hash_3), - PendingPayableMissingInDb::new(wallet_2.address(), hash_2), - ] - ); - assert!(nonexistent.is_empty()) + assert!(missing_records.is_empty(), "We thought the vec would be empty but contained: {:?}", missing_records); } struct TestingMismatchedDataAboutPendingPayables { @@ -2165,82 +2161,82 @@ mod tests { // subject.check_for_missing_records(&pending_payables_ref); } - #[test] - fn symmetry_check_happy_path() { - let hash_1 = make_tx_hash(123); - let hash_2 = make_tx_hash(456); - let hash_3 = make_tx_hash(789); - let pending_payables_sent_from_blockchain_bridge = vec![ - PendingPayable::new(make_wallet("abc"), hash_1), - PendingPayable::new(make_wallet("def"), hash_2), - PendingPayable::new(make_wallet("ghi"), hash_3), - ]; - let pending_payables_ref = pending_payables_sent_from_blockchain_bridge - .iter() - .map(|ppayable| ppayable.hash) - .collect::>(); - let hashes_from_fingerprints = vec![(hash_1, 3), (hash_2, 5), (hash_3, 6)] - .iter() - .map(|(hash, _id)| *hash) - .collect::>(); - - let result = PayableScanner::is_symmetrical(pending_payables_ref, hashes_from_fingerprints); - - assert_eq!(result, true) - } - - #[test] - fn symmetry_check_sad_path_for_intruder() { - let vals = prepare_values_for_mismatched_setting(); - let pending_payables_ref_from_blockchain_bridge = vals - .pending_payables - .iter() - .map(|ppayable| ppayable.hash) - .collect::>(); - let rowids_and_hashes_from_fingerprints = vec![ - (vals.common_hash_1, 3), - (vals.intruder_for_hash_2, 5), - (vals.common_hash_3, 6), - ] - .iter() - .map(|(hash, _rowid)| *hash) - .collect::>(); - - let result = PayableScanner::is_symmetrical( - pending_payables_ref_from_blockchain_bridge, - rowids_and_hashes_from_fingerprints, - ); - - assert_eq!(result, false) - } - - #[test] - fn symmetry_check_indifferent_to_wrong_order_on_the_input() { - let hash_1 = make_tx_hash(123); - let hash_2 = make_tx_hash(456); - let hash_3 = make_tx_hash(789); - let pending_payables_sent_from_blockchain_bridge = vec![ - PendingPayable::new(make_wallet("abc"), hash_1), - PendingPayable::new(make_wallet("def"), hash_2), - PendingPayable::new(make_wallet("ghi"), hash_3), - ]; - let bb_returned_p_payables_ref = pending_payables_sent_from_blockchain_bridge - .iter() - .map(|ppayable| ppayable.hash) - .collect::>(); - // Not in ascending order - let rowids_and_hashes_from_fingerprints = vec![(hash_1, 3), (hash_3, 5), (hash_2, 6)] - .iter() - .map(|(hash, _id)| *hash) - .collect::>(); + // #[test] + // fn symmetry_check_happy_path() { + // let hash_1 = make_tx_hash(123); + // let hash_2 = make_tx_hash(456); + // let hash_3 = make_tx_hash(789); + // let pending_payables_sent_from_blockchain_bridge = vec![ + // PendingPayable::new(make_wallet("abc"), hash_1), + // PendingPayable::new(make_wallet("def"), hash_2), + // PendingPayable::new(make_wallet("ghi"), hash_3), + // ]; + // let pending_payables_ref = pending_payables_sent_from_blockchain_bridge + // .iter() + // .map(|ppayable| ppayable.hash) + // .collect::>(); + // let hashes_from_fingerprints = vec![(hash_1, 3), (hash_2, 5), (hash_3, 6)] + // .iter() + // .map(|(hash, _id)| *hash) + // .collect::>(); + // + // let result = PayableScanner::is_symmetrical(pending_payables_ref, hashes_from_fingerprints); + // + // assert_eq!(result, true) + // } - let result = PayableScanner::is_symmetrical( - bb_returned_p_payables_ref, - rowids_and_hashes_from_fingerprints, - ); + // #[test] + // fn symmetry_check_sad_path_for_intruder() { + // let vals = prepare_values_for_mismatched_setting(); + // let pending_payables_ref_from_blockchain_bridge = vals + // .pending_payables + // .iter() + // .map(|ppayable| ppayable.hash) + // .collect::>(); + // let rowids_and_hashes_from_fingerprints = vec![ + // (vals.common_hash_1, 3), + // (vals.intruder_for_hash_2, 5), + // (vals.common_hash_3, 6), + // ] + // .iter() + // .map(|(hash, _rowid)| *hash) + // .collect::>(); + // + // let result = PayableScanner::is_symmetrical( + // pending_payables_ref_from_blockchain_bridge, + // rowids_and_hashes_from_fingerprints, + // ); + // + // assert_eq!(result, false) + // } - assert_eq!(result, true) - } + // #[test] + // fn symmetry_check_indifferent_to_wrong_order_on_the_input() { + // let hash_1 = make_tx_hash(123); + // let hash_2 = make_tx_hash(456); + // let hash_3 = make_tx_hash(789); + // let pending_payables_sent_from_blockchain_bridge = vec![ + // PendingPayable::new(make_wallet("abc"), hash_1), + // PendingPayable::new(make_wallet("def"), hash_2), + // PendingPayable::new(make_wallet("ghi"), hash_3), + // ]; + // let bb_returned_p_payables_ref = pending_payables_sent_from_blockchain_bridge + // .iter() + // .map(|ppayable| ppayable.hash) + // .collect::>(); + // // Not in ascending order + // let rowids_and_hashes_from_fingerprints = vec![(hash_1, 3), (hash_3, 5), (hash_2, 6)] + // .iter() + // .map(|(hash, _id)| *hash) + // .collect::>(); + // + // let result = PayableScanner::is_symmetrical( + // bb_returned_p_payables_ref, + // rowids_and_hashes_from_fingerprints, + // ); + // + // assert_eq!(result, true) + // } #[test] #[should_panic( @@ -2373,7 +2369,7 @@ mod tests { let sent_payable = SentPayables { payment_procedure_result: Err(PayableTransactionError::Sending { msg: "Attempt failed".to_string(), - hashes: vec![hash_tx_1, hash_tx_2], + hashes: hashset![hash_tx_1, hash_tx_2], }), response_skeleton_opt: None, }; @@ -2461,7 +2457,7 @@ mod tests { let sent_payable = SentPayables { payment_procedure_result: Err(PayableTransactionError::Sending { msg: "blah".to_string(), - hashes: vec![hash_1, hash_2], + hashes: hashset![hash_1, hash_2], }), response_skeleton_opt: None, }; @@ -2509,7 +2505,7 @@ mod tests { let sent_payable = SentPayables { payment_procedure_result: Err(PayableTransactionError::Sending { msg: "SQLite migraine".to_string(), - hashes: vec![hash_1, hash_2, hash_3], + hashes: hashset![hash_1, hash_2, hash_3], }), response_skeleton_opt: None, }; @@ -3033,7 +3029,7 @@ mod tests { let now = SystemTime::now(); let consuming_wallet = make_paying_wallet(b"consuming_wallet"); let sent_payable_dao = - SentPayableDaoMock::new().return_all_errorless_fingerprints_result(vec![]); + SentPayableDaoMock::new().retrieve_txs_result(vec![]); let mut pending_payable_scanner = PendingPayableScannerBuilder::new() .sent_payable_dao(sent_payable_dao) .build(); @@ -3063,7 +3059,7 @@ mod tests { // pending_payable_age_sec: u64, // rowid: u64, // hash: H256, - // ) -> PendingPayableScanReport { + // ) -> PendingPayableScanFinalReport { // init_test_logging(); // let when_sent = SystemTime::now().sub(Duration::from_secs(pending_payable_age_sec)); // let sent_tx = SentTx { @@ -3075,7 +3071,7 @@ mod tests { // process_error: None, // }; // let logger = Logger::new(test_name); - // let scan_report = PendingPayableScanReport::default(); + // let scan_report = PendingPayableScanFinalReport::default(); // // handle_none_status(scan_report, sent_tx, when_pending_too_long_sec, &logger) // } @@ -3130,7 +3126,7 @@ mod tests { // let elapsed_after = elapsed_since_secs_back(DEFAULT_PENDING_TOO_LONG_SEC + 1); // assert_eq!( // result, - // PendingPayableScanReport { + // PendingPayableScanFinalReport { // still_pending: vec![], // failures: vec![PendingPayableId::new(rowid, hash)], // confirmed: vec![] @@ -3164,7 +3160,7 @@ mod tests { // let elapsed_after_ms = elapsed_since_secs_back(pending_payable_age) * 1000; // assert_eq!( // result, - // PendingPayableScanReport { + // PendingPayableScanFinalReport { // still_pending: vec![PendingPayableId::new(rowid, hash)], // failures: vec![], // confirmed: vec![] @@ -3195,7 +3191,7 @@ mod tests { // let elapsed_after_ms = elapsed_since_secs_back(pending_payable_age) * 1000; // assert_eq!( // result, - // PendingPayableScanReport { + // PendingPayableScanFinalReport { // still_pending: vec![PendingPayableId::new(rowid, hash)], // failures: vec![], // confirmed: vec![] @@ -3211,28 +3207,27 @@ mod tests { #[test] fn interpret_transaction_receipt_when_transaction_status_is_a_failure() { init_test_logging(); + let now = SystemTime::now(); let test_name = "interpret_transaction_receipt_when_transaction_status_is_a_failure"; - let mut tx_receipt = TransactionReceipt::default(); - tx_receipt.status = Some(U64::from(0)); //failure - let hash = make_tx_hash(0xd7); - let sent_tx = SentTx { - timestamp: SystemTime::now().sub(Duration::from_millis(150000)), - gas_price_wei: 0, - nonce: 0, - hash, - amount: 2222, + let failed_tx = FailedTx { + hash: make_tx_hash(654), receiver_address: Default::default(), - block_opt: None, + amount: 222_333_444, + timestamp: to_unix_timestamp(now) - 150, + gas_price_wei: 156_000_000_000, + nonce: 13, + reason: todo!("I don't have this yet...some kind of blockchain failure"), + rechecked: false, }; let logger = Logger::new(test_name); - let scan_report = PendingPayableScanReport::default(); + let scan_report = PendingPayableScanFinalReport::default(); - let result = handle_status_with_failure(scan_report, sent_tx, &logger); + let result = handle_status_with_failure(scan_report, failed_tx.clone(), &logger); assert_eq!( result, - PendingPayableScanReport { - failures: vec![PendingPayableId::new(777777, hash,)], + PendingPayableScanFinalReport { + failures: vec![failed_tx], confirmed: vec![] } ); @@ -3250,27 +3245,21 @@ mod tests { let subject = PendingPayableScannerBuilder::new().build(); let rowid = 455; let hash = make_tx_hash(0x913); - let sent_tx = SentTx { - timestamp: SystemTime::now().sub(Duration::from_millis(10000)), - gas_price_wei: 0, - nonce: 0, - hash, - amount: 111, - receiver_address: Default::default(), - block_opt: None, - }; + let mut sent_tx = make_sent_tx(456); + sent_tx.hash = hash; let msg = TxStatusReport { results: vec![ - TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new(sent_tx, TxStatus::Pending))], + TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new(sent_tx.clone(), TxStatus::Pending))], response_skeleton_opt: None, }; let result = subject.handle_receipts_for_pending_transactions(msg, &Logger::new(test_name)); + let expected_failed_tx = FailedTx::from(sent_tx); assert_eq!( result, - PendingPayableScanReport { - failures: todo!(), + PendingPayableScanFinalReport { + failures: vec![expected_failed_tx], confirmed: vec![] } ); @@ -3281,15 +3270,6 @@ mod tests { )); } - #[test] - fn update_remaining_fingerprints_does_nothing_if_no_still_pending_transactions_remain() { - let subject = PendingPayableScannerBuilder::new().build(); - - subject.update_remaining_fingerprints(vec![], &Logger::new("test")) - - //mocked pending payable DAO didn't panic which means we skipped the actual process - } - #[test] fn handle_failed_transactions_works() { init_test_logging(); @@ -3298,6 +3278,7 @@ mod tests { let sent_payable_dao = SentPayableDaoMock::default() .mark_failures_params(&mark_failures_params_arc) .mark_failures_result(Ok(())); + let failed_payable_dao = FailedPayableDaoMock::default(); let subject = PendingPayableScannerBuilder::new() .sent_payable_dao(sent_payable_dao) .build(); diff --git a/node/src/accountant/scanners/scanners_utils.rs b/node/src/accountant/scanners/scanners_utils.rs index 10e51662d..8840ce364 100644 --- a/node/src/accountant/scanners/scanners_utils.rs +++ b/node/src/accountant/scanners/scanners_utils.rs @@ -320,15 +320,15 @@ pub mod pending_payable_scanner_utils { use std::time::SystemTime; use crate::accountant::db_access_objects::failed_payable_dao::FailedTx; use crate::accountant::db_access_objects::sent_payable_dao::SentTx; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxBlockchainFailure, TxReceiptRequestError}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxBlockchainFailure, TxReceiptRequestError, TxStatus}; #[derive(Debug, Default, PartialEq, Eq, Clone)] - pub struct PendingPayableScanReport { + pub struct PendingPayableScanFinalReport { pub failures: Vec, pub confirmed: Vec, } - impl PendingPayableScanReport { + impl PendingPayableScanFinalReport { pub fn requires_payments_retry(&self) -> bool { todo!("complete my within GH-642") } @@ -348,11 +348,11 @@ pub mod pending_payable_scanner_utils { } // // pub fn handle_none_status( - // mut scan_report: PendingPayableScanReport, + // mut scan_report: PendingPayableScanFinalReport, // sent_tx: SentTx, // max_pending_interval: u64, // logger: &Logger, - // ) -> PendingPayableScanReport { + // ) -> PendingPayableScanFinalReport { // info!( // logger, // "Pending transaction {:?} couldn't be confirmed at attempt \ @@ -386,10 +386,10 @@ pub mod pending_payable_scanner_utils { // } pub fn handle_successful_tx( - mut scan_report: PendingPayableScanReport, + mut scan_report: PendingPayableScanFinalReport, confirmed_tx: SentTx, logger: &Logger, - ) -> PendingPayableScanReport { + ) -> PendingPayableScanFinalReport { todo!() // info!( // logger, @@ -405,10 +405,10 @@ pub mod pending_payable_scanner_utils { //TODO: failures handling is going to need enhancement suggested by GH-693 pub fn handle_status_with_failure( - mut scan_report: PendingPayableScanReport, + mut scan_report: PendingPayableScanFinalReport, tx_failure: FailedTx, logger: &Logger, - ) -> PendingPayableScanReport { + ) -> PendingPayableScanFinalReport { error!( logger, "Pending transaction {:?} announced as a failure after {}ms from its sending", @@ -420,10 +420,10 @@ pub mod pending_payable_scanner_utils { } pub fn handle_request_error_fetching_receipts( - mut scan_report: PendingPayableScanReport, + mut scan_report: PendingPayableScanFinalReport, local_error: TxReceiptRequestError, logger: &Logger, - ) -> PendingPayableScanReport { + ) -> PendingPayableScanFinalReport { todo!() // debug!( // logger, @@ -440,8 +440,15 @@ pub mod pending_payable_scanner_utils { // scan_report } + // Should be used only for pending txs that linger too long + impl From for FailedTx { + fn from(_: SentTx) -> Self { + todo!() + } + } + impl From<(SentTx, TxBlockchainFailure)> for FailedTx { - fn from(_: (SentTx, TxBlockchainFailure)) -> Self { + fn from((sent_tx, blockchain_failure): (SentTx, TxBlockchainFailure)) -> Self { todo!() } } @@ -572,7 +579,7 @@ mod tests { init_test_logging(); let error = PayableTransactionError::Sending { msg: "Bad luck".to_string(), - hashes: vec![make_tx_hash(0x7b)], + hashes: hashset![make_tx_hash(0x7b)], }; let sent_payable = SentPayables { payment_procedure_result: Err(error.clone()), @@ -613,7 +620,7 @@ mod tests { let (oks, errs) = separate_errors(&sent_payable, &Logger::new("test_logger")); assert_eq!(oks, vec![&payable_ok]); - assert_eq!(errs, Some(RemotelyCausedErrors(vec![make_tx_hash(0x315)]))); + assert_eq!(errs, Some(RemotelyCausedErrors(hashset![make_tx_hash(0x315)]))); TestLogHandler::new().exists_log_containing("WARN: test_logger: Remote transaction failure: \ 'Got invalid response: That jackass screwed it up' for payment to 0x000000000000000000000000\ 00000077686f6f61 and transaction hash 0x0000000000000000000000000000000000000000000000000000\ @@ -823,7 +830,7 @@ mod tests { fn count_total_errors_works_correctly_for_local_error_after_signing() { let error = PayableTransactionError::Sending { msg: "Ouuuups".to_string(), - hashes: vec![make_tx_hash(333), make_tx_hash(666)], + hashes: hashset![make_tx_hash(333), make_tx_hash(666)], }; let sent_payable = Some(LocallyCausedError(error)); @@ -834,7 +841,7 @@ mod tests { #[test] fn count_total_errors_works_correctly_for_remote_errors() { - let sent_payable = Some(RemotelyCausedErrors(vec![ + let sent_payable = Some(RemotelyCausedErrors(hashset![ make_tx_hash(123), make_tx_hash(456), ])); @@ -871,32 +878,32 @@ mod tests { fn requires_payments_retry_says_yes() { todo!("complete this test with GH-604") // let cases = vec![ - // PendingPayableScanReport { + // PendingPayableScanFinalReport { // still_pending: vec![PendingPayableId::new(12, make_tx_hash(456))], // failures: vec![], // confirmed: vec![], // }, - // PendingPayableScanReport { + // PendingPayableScanFinalReport { // still_pending: vec![], // failures: vec![PendingPayableId::new(456, make_tx_hash(1234))], // confirmed: vec![], // }, - // PendingPayableScanReport { + // PendingPayableScanFinalReport { // still_pending: vec![PendingPayableId::new(12, make_tx_hash(456))], // failures: vec![PendingPayableId::new(456, make_tx_hash(1234))], // confirmed: vec![], // }, - // PendingPayableScanReport { + // PendingPayableScanFinalReport { // still_pending: vec![PendingPayableId::new(12, make_tx_hash(456))], // failures: vec![PendingPayableId::new(456, make_tx_hash(1234))], // confirmed: vec![make_pending_payable_fingerprint()], // }, - // PendingPayableScanReport { + // PendingPayableScanFinalReport { // still_pending: vec![PendingPayableId::new(12, make_tx_hash(456))], // failures: vec![], // confirmed: vec![make_pending_payable_fingerprint()], // }, - // PendingPayableScanReport { + // PendingPayableScanFinalReport { // still_pending: vec![], // failures: vec![PendingPayableId::new(456, make_tx_hash(1234))], // confirmed: vec![make_pending_payable_fingerprint()], @@ -916,7 +923,7 @@ mod tests { #[test] fn requires_payments_retry_says_no() { todo!("complete this test with GH-604") - // let report = PendingPayableScanReport { + // let report = PendingPayableScanFinalReport { // still_pending: vec![], // failures: vec![], // confirmed: vec![make_pending_payable_fingerprint()], diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs index 0bd88d523..d11f29f9d 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs @@ -326,9 +326,7 @@ mod tests { use crate::accountant::db_access_objects::utils::from_unix_timestamp; use crate::accountant::gwei_to_wei; use crate::accountant::scanners::payable_scanner_extension::agent_web3::WEB3_MAXIMAL_GAS_LIMIT_MARGIN; - use crate::accountant::test_utils::{ - make_payable_account, make_payable_account_with_wallet_and_balance_and_timestamp_opt, - }; + use crate::accountant::test_utils::{make_payable_account, make_payable_account_with_wallet_and_balance_and_timestamp_opt, make_sent_tx}; use crate::blockchain::bip32::Bip32EncryptionKeyProvider; use crate::blockchain::blockchain_interface::blockchain_interface_web3::{ BlockchainInterfaceWeb3, REQUESTS_IN_PARALLEL, @@ -414,9 +412,9 @@ mod tests { } #[test] - fn send_and_append_multiple_payments_works() { + fn sign_and_append_multiple_payments_works() { let port = find_free_port(); - let logger = Logger::new("send_and_append_multiple_payments_works"); + let logger = Logger::new("sign_and_append_multiple_payments_works"); let (_event_loop_handle, transport) = Http::with_max_parallel( &format!("http://{}:{}", &Ipv4Addr::LOCALHOST, port), REQUESTS_IN_PARALLEL, @@ -429,7 +427,7 @@ mod tests { let consuming_wallet = make_paying_wallet(b"paying_wallet"); let account_1 = make_payable_account(1); let account_2 = make_payable_account(2); - let accounts = vec![account_1, account_2]; + let accounts = vec![account_1.clone(), account_2.clone()]; let result = sign_and_append_multiple_payments( &logger, @@ -441,14 +439,28 @@ mod tests { &accounts, ); + let mut expected_prepared_sent_tx_1 = make_sent_tx(123); + expected_prepared_sent_tx_1.hash = H256::from_str( + "94881436a9c89f48b01651ff491c69e97089daf71ab8cfb240243d7ecf9b38b2" + ) + .unwrap(); + expected_prepared_sent_tx_1.amount = account_1.balance_wei; + expected_prepared_sent_tx_1.nonce = 1; + expected_prepared_sent_tx_1.receiver_address = account_1.wallet.address(); + SentTx{ + hash: Default::default(), + receiver_address: Default::default(), + amount: 0, + timestamp: 0, + gas_price_wei: 0, + nonce: 0, + block_opt: None, + } assert_eq!( result, vec![ HashAndAmount { - hash: H256::from_str( - "94881436a9c89f48b01651ff491c69e97089daf71ab8cfb240243d7ecf9b38b2" - ) - .unwrap(), + hash: , amount: 1000000000 }, HashAndAmount { From ce84ef48ca33405d2ddbde023d15dc46af76c7f0 Mon Sep 17 00:00:00 2001 From: Bert Date: Thu, 10 Jul 2025 16:17:15 +0200 Subject: [PATCH 06/61] GH-642: tests compiling...failing lots of them --- masq_lib/src/utils.rs | 7 +- .../db_access_objects/failed_payable_dao.rs | 21 +- node/src/accountant/db_access_objects/mod.rs | 1 + .../db_access_objects/payable_dao.rs | 133 ++++--- .../db_access_objects/pending_payable_dao.rs | 54 +-- .../db_access_objects/receivable_dao.rs | 6 +- ...able_and_failed_payable_data_conversion.rs | 1 + .../db_access_objects/sent_payable_dao.rs | 28 +- .../db_access_objects/test_utils.rs | 8 +- node/src/accountant/mod.rs | 223 ++++++----- node/src/accountant/scanners/mod.rs | 360 +++++++++--------- .../src/accountant/scanners/scanners_utils.rs | 37 +- node/src/accountant/scanners/test_utils.rs | 9 +- node/src/accountant/test_utils.rs | 178 +++++---- node/src/blockchain/blockchain_bridge.rs | 104 ++--- .../lower_level_interface_web3.rs | 94 ++--- .../blockchain_interface_web3/mod.rs | 77 ++-- .../blockchain_interface_web3/utils.rs | 138 ++++--- .../data_structures/errors.rs | 20 +- .../data_structures/mod.rs | 4 +- .../migrations/migration_4_to_5.rs | 4 +- node/src/sub_lib/accountant.rs | 7 +- node/src/test_utils/recorder.rs | 2 +- 23 files changed, 799 insertions(+), 717 deletions(-) create mode 100644 node/src/accountant/db_access_objects/sent_payable_and_failed_payable_data_conversion.rs diff --git a/masq_lib/src/utils.rs b/masq_lib/src/utils.rs index 21fba8983..618238b95 100644 --- a/masq_lib/src/utils.rs +++ b/masq_lib/src/utils.rs @@ -501,7 +501,6 @@ macro_rules! hashset { }; } - #[cfg(test)] mod tests { use super::*; @@ -863,7 +862,7 @@ mod tests { expected_hashmap_with_trailing_comma ); assert_eq!(hashmap_of_string, expected_hashmap_of_string); - assert_eq!(hashmap_with_duplicate, expected_hashmap_with_duplicate); + assert_eq!(hashmap_with_duplicate, expected_hashmap_with_duplicate); } #[test] @@ -872,7 +871,7 @@ mod tests { let hashset_with_one_element = hashset!(2); let hashset_with_multiple_elements = hashset!(2, 20, 42); let hashset_with_trailing_comma = hashset!(2, 20,); - let hashset_of_string = hashset!("val_a","val_b"); + let hashset_of_string = hashset!("val_a", "val_b"); let hashset_with_duplicate = hashset!(2, 2); let expected_empty_hashset: HashSet = HashSet::new(); @@ -901,6 +900,6 @@ mod tests { expected_hashset_with_trailing_comma ); assert_eq!(hashset_of_string, expected_hashset_of_string); - assert_eq!(hashset_with_duplicate, expected_hashset_with_duplicate); + assert_eq!(hashset_with_duplicate, expected_hashset_with_duplicate); } } 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 78554557d..1d5035722 100644 --- a/node/src/accountant/db_access_objects/failed_payable_dao.rs +++ b/node/src/accountant/db_access_objects/failed_payable_dao.rs @@ -11,6 +11,7 @@ use std::collections::{HashMap, HashSet}; use std::fmt::{Display, Formatter}; use std::str::FromStr; use web3::types::Address; +use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::TransactionBlock; #[derive(Debug, PartialEq, Eq)] pub enum FailedPayableDaoError { @@ -64,9 +65,9 @@ impl FromStr for FailureReason { pub struct FailedTx { pub hash: TxHash, pub receiver_address: Address, - pub amount: u128, + pub amount_minor: u128, pub timestamp: i64, - pub gas_price_wei: u128, + pub gas_price_minor: u128, pub nonce: u64, pub reason: FailureReason, pub status: FailureStatus, @@ -168,8 +169,8 @@ impl FailedPayableDao for FailedPayableDaoReal<'_> { status ) VALUES {}", comma_joined_stringifiable(txs, |tx| { - let amount_checked = checked_conversion::(tx.amount); - let gas_price_wei_checked = checked_conversion::(tx.gas_price_wei); + let amount_checked = checked_conversion::(tx.amount_minor); + let gas_price_wei_checked = checked_conversion::(tx.gas_price_minor); let (amount_high_b, amount_low_b) = BigIntDivider::deconstruct(amount_checked); let (gas_price_wei_high_b, gas_price_wei_low_b) = BigIntDivider::deconstruct(gas_price_wei_checked); @@ -236,11 +237,11 @@ impl FailedPayableDao for FailedPayableDaoReal<'_> { Address::from_str(&receiver_address_str[2..]).expect("Failed to parse Address"); let amount_high_b = row.get(2).expectv("amount_high_b"); let amount_low_b = row.get(3).expectv("amount_low_b"); - let amount = BigIntDivider::reconstitute(amount_high_b, amount_low_b) as u128; + let amount_minor = BigIntDivider::reconstitute(amount_high_b, amount_low_b) as u128; let timestamp = row.get(4).expectv("timestamp"); let gas_price_wei_high_b = row.get(5).expectv("gas_price_wei_high_b"); let gas_price_wei_low_b = row.get(6).expectv("gas_price_wei_low_b"); - let gas_price_wei = + let gas_price_minor = BigIntDivider::reconstitute(gas_price_wei_high_b, gas_price_wei_low_b) as u128; let nonce = row.get(7).expectv("nonce"); let reason_str: String = row.get(8).expectv("reason"); @@ -253,9 +254,9 @@ impl FailedPayableDao for FailedPayableDaoReal<'_> { Ok(FailedTx { hash, receiver_address, - amount, + amount_minor, timestamp, - gas_price_wei, + gas_price_minor, nonce, reason, status, @@ -444,12 +445,12 @@ mod tests { [FailedTx { \ hash: 0x000000000000000000000000000000000000000000000000000000000000007b, \ receiver_address: 0x0000000000000000000000000000000000000000, \ - amount: 0, timestamp: 0, gas_price_wei: 0, \ + amount_minor: 0, timestamp: 0, gas_price_minor: 0, \ nonce: 0, reason: PendingTooLong, status: RetryRequired }, \ FailedTx { \ hash: 0x000000000000000000000000000000000000000000000000000000000000007b, \ receiver_address: 0x0000000000000000000000000000000000000000, \ - amount: 0, timestamp: 0, gas_price_wei: 0, \ + amount_minor: 0, timestamp: 0, gas_price_minor: 0, \ nonce: 0, reason: PendingTooLong, status: RecheckRequired }]" .to_string() )) diff --git a/node/src/accountant/db_access_objects/mod.rs b/node/src/accountant/db_access_objects/mod.rs index 8782bf620..0141e8796 100644 --- a/node/src/accountant/db_access_objects/mod.rs +++ b/node/src/accountant/db_access_objects/mod.rs @@ -4,6 +4,7 @@ pub mod banned_dao; pub mod failed_payable_dao; pub mod payable_dao; pub mod receivable_dao; +pub mod sent_payable_and_failed_payable_data_conversion; pub mod sent_payable_dao; mod test_utils; pub mod utils; diff --git a/node/src/accountant/db_access_objects/payable_dao.rs b/node/src/accountant/db_access_objects/payable_dao.rs index d500241e9..a6221140c 100644 --- a/node/src/accountant/db_access_objects/payable_dao.rs +++ b/node/src/accountant/db_access_objects/payable_dao.rs @@ -46,7 +46,7 @@ pub trait PayableDao: Debug + Send { &self, now: SystemTime, wallet: &Wallet, - amount: u128, + amount_minor: u128, ) -> Result<(), PayableDaoError>; fn mark_pending_payables_rowids( @@ -54,10 +54,7 @@ pub trait PayableDao: Debug + Send { mark_instructions: &[MarkOfPendingPayable], ) -> Result<(), PayableDaoError>; - fn transactions_confirmed( - &self, - confirmed_payables: &[SentTx], - ) -> Result<(), PayableDaoError>; + fn transactions_confirmed(&self, confirmed_payables: &[SentTx]) -> Result<(), PayableDaoError>; fn non_pending_payables(&self) -> Vec; @@ -79,13 +76,13 @@ impl PayableDaoFactory for DaoFactoryReal { } } -pub struct MarkOfPendingPayable{ +pub struct MarkOfPendingPayable { pub wallet: Address, - pub rowid: RowId + pub rowid: RowId, } impl MarkOfPendingPayable { - pub fn new(wallet: Address, rowid: RowId)-> Self { + pub fn new(wallet: Address, rowid: RowId) -> Self { todo!() } } @@ -101,7 +98,7 @@ impl PayableDao for PayableDaoReal { &self, timestamp: SystemTime, wallet: &Wallet, - amount: u128, + amount_minor: u128, ) -> Result<(), PayableDaoError> { let main_sql = "insert into payable (wallet_address, balance_high_b, balance_low_b, last_paid_timestamp, pending_payable_rowid) \ values (:wallet, :balance_high_b, :balance_low_b, :last_paid_timestamp, null) on conflict (wallet_address) do update set \ @@ -114,7 +111,7 @@ impl PayableDao for PayableDaoReal { .key(WalletAddress(wallet)) .wei_change(WeiChange::new( "balance", - amount, + amount_minor, WeiChangeDirection::Addition, )) .other_params(vec![ParamByUse::BeforeOverflowOnly( @@ -138,7 +135,7 @@ impl PayableDao for PayableDaoReal { // if wallets_and_rowids.is_empty() { // panic!("broken code: empty input is not permit to enter this method") // } - // + // // let case_expr = compose_case_expression(wallets_and_rowids); // let wallets = serialize_wallets(wallets_and_rowids, Some('\'')); // //the Wallet type is secure against SQL injections @@ -154,20 +151,17 @@ impl PayableDao for PayableDaoReal { // execute_command(&*self.conn, wallets_and_rowids, &sql) } - fn transactions_confirmed( - &self, - confirmed_payables: &[SentTx], - ) -> Result<(), PayableDaoError> { + fn transactions_confirmed(&self, confirmed_payables: &[SentTx]) -> Result<(), PayableDaoError> { todo!() // confirmed_payables.iter().try_for_each(|pending_payable_fingerprint| { - // + // // let main_sql = "update payable set \ // balance_high_b = balance_high_b + :balance_high_b, balance_low_b = balance_low_b + :balance_low_b, \ // last_paid_timestamp = :last_paid, pending_payable_rowid = null where pending_payable_rowid = :rowid"; // let update_clause_with_compensated_overflow = "update payable set \ // balance_high_b = :balance_high_b, balance_low_b = :balance_low_b, last_paid_timestamp = :last_paid, \ // pending_payable_rowid = null where pending_payable_rowid = :rowid"; - // + // // let i64_rowid = checked_conversion::(pending_payable_fingerprint.rowid); // let last_paid = to_unix_timestamp(pending_payable_fingerprint.timestamp); // let params = SQLParamsBuilder::default() @@ -175,12 +169,12 @@ impl PayableDao for PayableDaoReal { // .wei_change(WeiChange::new( "balance", pending_payable_fingerprint.amount, WeiChangeDirection::Subtraction)) // .other_params(vec![ParamByUse::BeforeAndAfterOverflow(DisplayableRusqliteParamPair::new(":last_paid", &last_paid))]) // .build(); - // + // // self.big_int_db_processor.execute(Either::Left(self.conn.as_ref()), BigIntSqlConfig::new( // main_sql, // update_clause_with_compensated_overflow, // params))?; - // + // // Ok(()) // }) } @@ -747,14 +741,14 @@ mod tests { // stm.execute(params.as_slice()).unwrap(); // } // let subject = PayableDaoReal::new(boxed_conn); - // + // // subject // .mark_pending_payables_rowids(&[ // (&wallet_1, pending_payable_rowid_1), // (&wallet_2, pending_payable_rowid_2), // ]) // .unwrap(); - // + // // let account_statuses = [&wallet_0, &wallet_1, &wallet_2] // .iter() // .map(|wallet| subject.account_status(wallet).unwrap()) @@ -825,7 +819,7 @@ mod tests { // Some(first_rowid), // ); // let subject = PayableDaoReal::new(conn); - // + // // let _ = subject.mark_pending_payables_rowids(&[ // (&first_wallet, first_rowid as u64), // (&make_wallet("yahoo"), 789), @@ -886,9 +880,9 @@ mod tests { // let conn = payable_read_only_conn(&home_dir); // let conn_wrapped = ConnectionWrapperReal::new(conn); // let subject = PayableDaoReal::new(Box::new(conn_wrapped)); - // + // // let result = subject.mark_pending_payables_rowids(&[(&wallet, rowid)]); - // + // // assert_eq!( // result, // Err(PayableDaoError::RusqliteError( @@ -911,15 +905,15 @@ mod tests { struct TestSetupValuesHolder { account_1: TxWalletAndTimestamp, - account_2: TxWalletAndTimestamp + account_2: TxWalletAndTimestamp, } - struct TxWalletAndTimestamp{ + struct TxWalletAndTimestamp { pending_payable: SentTx, previous_timestamp: SystemTime, } - - struct TestInputs{ + + struct TestInputs { hash: TxHash, rowid: u64, previous_timestamp: SystemTime, @@ -931,20 +925,21 @@ mod tests { fn insert_initial_payable_records_and_return_sent_txs( conn: &dyn ConnectionWrapper, - (initial_amount_1, balance_change_1): (u128,u128), - (initial_amount_2, balance_change_2): (u128,u128) + (initial_amount_1, balance_change_1): (u128, u128), + (initial_amount_2, balance_change_2): (u128, u128), ) -> TestSetupValuesHolder { let now = SystemTime::now(); - let (account_1,account_2) = [TestInputs { - hash: make_tx_hash(12345), - rowid: 789, - previous_timestamp: now.checked_sub(Duration::from_secs(45_000)).unwrap(), - new_payable_timestamp: now.checked_sub(Duration::from_secs(2)).unwrap(), - wallet: make_wallet("bobbles").address(), - initial_amount_wei: initial_amount_1, - balance_change: balance_change_1, - }, - TestInputs{ + let (account_1, account_2) = [ + TestInputs { + hash: make_tx_hash(12345), + rowid: 789, + previous_timestamp: now.checked_sub(Duration::from_secs(45_000)).unwrap(), + new_payable_timestamp: now.checked_sub(Duration::from_secs(2)).unwrap(), + wallet: make_wallet("bobbles").address(), + initial_amount_wei: initial_amount_1, + balance_change: balance_change_1, + }, + TestInputs { hash: make_tx_hash(54321), rowid: 792, previous_timestamp: now.checked_sub(Duration::from_secs(22_000)).unwrap(), @@ -952,26 +947,34 @@ mod tests { wallet: make_wallet("yet more bobbles").address(), initial_amount_wei: initial_amount_2, balance_change: balance_change_2, - }].into_iter().enumerate().map(|(idx, test_inputs)|{ - + }, + ] + .into_iter() + .enumerate() + .map(|(idx, test_inputs)| { insert_payable_record_fn( conn, &test_inputs.wallet.to_string(), i128::try_from(test_inputs.initial_amount_wei).unwrap(), to_unix_timestamp(test_inputs.previous_timestamp), // TODO argument will be eliminated in GH-662 - None + None, ); - + let mut sent_tx = make_sent_tx(idx as u64 * 1234); - sent_tx.amount = test_inputs.balance_change; + sent_tx.amount_minor = test_inputs.balance_change; + + TxWalletAndTimestamp { + pending_payable: sent_tx, + previous_timestamp: test_inputs.previous_timestamp, + } + }) + .collect_tuple() + .unwrap(); - TxWalletAndTimestamp{ pending_payable: sent_tx, previous_timestamp: test_inputs.previous_timestamp } - }).collect_tuple().unwrap(); - TestSetupValuesHolder { account_1, - account_2 + account_2, } } @@ -1006,7 +1009,7 @@ mod tests { (initial_amount_1, balance_change_1, expected_balance_after_1): (u128, u128, u128), ) { let home_dir = ensure_node_home_directory_exists("payable_dao", test_name); - // A hardcoded set that just makes a complement to the crucial, supplied first one; this + // A hardcoded set that just makes a complement to the crucial, supplied first one; this // shows the ability to handle multiple transactions together let initial_amount_2 = 5_678_901; let balance_change_2 = 678_902; @@ -1080,10 +1083,11 @@ mod tests { assert_eq!( result, - Err(PayableDaoError::RusqliteError( - format!("Error from invalid update command for payable table and change of -12345 wei to \ - creditor {} with error 'attempt to write a readonly database'", wallet_address) - )) + Err(PayableDaoError::RusqliteError(format!( + "Error from invalid update command for payable table and change of -12345 wei to \ + creditor {} with error 'attempt to write a readonly database'", + wallet_address + ))) ) } @@ -1091,8 +1095,7 @@ mod tests { #[should_panic( expected = "Overflow detected with 340282366920938463463374607431768211455: cannot be converted from u128 to i128" )] - fn transaction_confirmed_works_for_overflow_from_sent_tx_record() - { + fn transaction_confirmed_works_for_overflow_from_sent_tx_record() { let home_dir = ensure_node_home_directory_exists( "payable_dao", "transaction_confirmed_works_for_overflow_from_sent_tx_record", @@ -1103,7 +1106,7 @@ mod tests { .unwrap(), ); let mut sent_tx = make_sent_tx(456); - sent_tx.amount = u128::MAX; + sent_tx.amount_minor = u128::MAX; //The overflow occurs before we start modifying the payable account so we can have the database empty let _ = subject.transactions_confirmed(&[sent_tx]); @@ -1120,8 +1123,8 @@ mod tests { .unwrap(); let setup_holder = insert_initial_payable_records_and_return_sent_txs( conn.as_ref(), - (1_111_111,111_111), - (2_222_222,222_222) + (1_111_111, 111_111), + (2_222_222, 222_222), ); let wallet_1 = Wallet::from(setup_holder.account_1.pending_payable.receiver_address); let wallet_2 = Wallet::from(setup_holder.account_2.pending_payable.receiver_address); @@ -1132,13 +1135,17 @@ mod tests { let subject = PayableDaoReal::new(conn); let expected_account = PayableAccount { wallet: wallet_1.clone(), - balance_wei: 1_111_111 - setup_holder.account_1.pending_payable.amount, - last_paid_timestamp: from_unix_timestamp(setup_holder.account_1.pending_payable.timestamp), + balance_wei: 1_111_111 - setup_holder.account_1.pending_payable.amount_minor, + last_paid_timestamp: from_unix_timestamp( + setup_holder.account_1.pending_payable.timestamp, + ), pending_payable_opt: None, }; - let result = subject - .transactions_confirmed(&[setup_holder.account_1.pending_payable, setup_holder.account_2.pending_payable]); + let result = subject.transactions_confirmed(&[ + setup_holder.account_1.pending_payable, + setup_holder.account_2.pending_payable, + ]); assert_eq!( result, diff --git a/node/src/accountant/db_access_objects/pending_payable_dao.rs b/node/src/accountant/db_access_objects/pending_payable_dao.rs index 7e6557858..451f580d3 100644 --- a/node/src/accountant/db_access_objects/pending_payable_dao.rs +++ b/node/src/accountant/db_access_objects/pending_payable_dao.rs @@ -111,7 +111,7 @@ impl SentPayableDao for PendingPayableDaoReal<'_> { // ) // }), // attempt, - // amount: checked_conversion::(BigIntDivider::reconstitute( + // amount_minor: checked_conversion::(BigIntDivider::reconstitute( // amount_high_bytes, // amount_low_bytes, // )), @@ -291,11 +291,11 @@ mod tests { // let subject = PendingPayableDaoReal::new(wrapped_conn); // let hash_and_amount_1 = HashAndAmount { // hash: hash_1, - // amount: amount_1, + // amount_minor: amount_1, // }; // let hash_and_amount_2 = HashAndAmount { // hash: hash_2, - // amount: amount_2, + // amount_minor: amount_2, // }; // // let _ = subject @@ -314,7 +314,7 @@ mod tests { // timestamp: batch_wide_timestamp, // hash: hash_and_amount_1.hash, // attempt: 1, - // amount: hash_and_amount_1.amount, + // amount_minor: hash_and_amount_1.amount, // process_error: None // }, // SentTx { @@ -322,7 +322,7 @@ mod tests { // timestamp: batch_wide_timestamp, // hash: hash_and_amount_2.hash, // attempt: 1, - // amount: hash_and_amount_2.amount, + // amount_minor: hash_and_amount_2.amount, // process_error: None // } // ] @@ -381,7 +381,7 @@ mod tests { // let subject = PendingPayableDaoReal::new(Box::new(wrapped_conn)); // let hash_and_amount = HashAndAmount { // hash: hash_1, - // amount: amount_1, + // amount_minor: amount_1, // }; // // let _ = subject.insert_new_fingerprints(&[hash_and_amount], batch_wide_timestamp); @@ -408,11 +408,11 @@ mod tests { // .unwrap(); // let hash_and_amount_1 = HashAndAmount { // hash: hash_1, - // amount: 4567, + // amount_minor: 4567, // }; // let hash_and_amount_2 = HashAndAmount { // hash: hash_2, - // amount: 6789, + // amount_minor: 6789, // }; // let fingerprints_init_input = vec![hash_and_amount_1, hash_and_amount_2]; // { @@ -461,7 +461,7 @@ mod tests { // .insert_new_fingerprints( // &[HashAndAmount { // hash: hash_2, - // amount: 8901234, + // amount_minor: 8901234, // }], // SystemTime::now(), // ) @@ -470,7 +470,7 @@ mod tests { // .insert_new_fingerprints( // &[HashAndAmount { // hash: hash_3, - // amount: 1234567, + // amount_minor: 1234567, // }], // SystemTime::now(), // ) @@ -500,11 +500,11 @@ mod tests { // let amount_2 = 333; // let hash_and_amount_1 = HashAndAmount { // hash: hash_1, - // amount: amount_1, + // amount_minor: amount_1, // }; // let hash_and_amount_2 = HashAndAmount { // hash: hash_2, - // amount: amount_2, + // amount_minor: amount_2, // }; // // { @@ -526,7 +526,7 @@ mod tests { // timestamp: batch_wide_timestamp, // hash: hash_1, // attempt: 1, - // amount: amount_1, + // amount_minor: amount_1, // process_error: None // }, // SentTx { @@ -534,7 +534,7 @@ mod tests { // timestamp: batch_wide_timestamp, // hash: hash_2, // attempt: 1, - // amount: amount_2, + // amount_minor: amount_2, // process_error: None // } // ] @@ -556,7 +556,7 @@ mod tests { // let amount = 333; // let hash_and_amount_1 = HashAndAmount { // hash: make_tx_hash(11119), - // amount: 2000, + // amount_minor: 2000, // }; // let hash_and_amount_2 = HashAndAmount { hash, amount }; // { @@ -623,15 +623,15 @@ mod tests { // &[ // HashAndAmount { // hash: make_tx_hash(1234), - // amount: 1111, + // amount_minor: 1111, // }, // HashAndAmount { // hash: make_tx_hash(2345), - // amount: 5555, + // amount_minor: 5555, // }, // HashAndAmount { // hash: make_tx_hash(3456), - // amount: 2222, + // amount_minor: 2222, // }, // ], // SystemTime::now(), @@ -699,7 +699,7 @@ mod tests { // .insert_new_fingerprints( // &[HashAndAmount { // hash: make_tx_hash(666666), - // amount: 5555, + // amount_minor: 5555, // }], // SystemTime::now(), // ) @@ -723,15 +723,15 @@ mod tests { // let hash_3 = make_tx_hash(567); // let hash_and_amount_1 = HashAndAmount { // hash: hash_1, - // amount: 1122, + // amount_minor: 1122, // }; // let hash_and_amount_2 = HashAndAmount { // hash: hash_2, - // amount: 2233, + // amount_minor: 2233, // }; // let hash_and_amount_3 = HashAndAmount { // hash: hash_3, - // amount: 3344, + // amount_minor: 3344, // }; // let timestamp = from_unix_timestamp(190_000_000); // let subject = PendingPayableDaoReal::new(conn); @@ -819,11 +819,11 @@ mod tests { // let amount_2 = 2345; // let hash_and_amount_1 = HashAndAmount { // hash: hash_1, - // amount: amount_1, + // amount_minor: amount_1, // }; // let hash_and_amount_2 = HashAndAmount { // hash: hash_2, - // amount: amount_2, + // amount_minor: amount_2, // }; // let timestamp = from_unix_timestamp(190_000_000); // let subject = PendingPayableDaoReal::new(conn); @@ -854,7 +854,7 @@ mod tests { // timestamp: from_unix_timestamp(timestamp), // hash: H256::from_str(&transaction_hash[2..]).unwrap(), // attempt, - // amount: checked_conversion::(BigIntDivider::reconstitute( + // amount_minor: checked_conversion::(BigIntDivider::reconstitute( // amount_high_b, // amount_low_b, // )), @@ -872,7 +872,7 @@ mod tests { // timestamp, // hash: hash_1, // attempt: 1, - // amount: amount_1, + // amount_minor: amount_1, // process_error: None // }, // SentTx { @@ -880,7 +880,7 @@ mod tests { // timestamp, // hash: hash_2, // attempt: 1, - // amount: amount_2, + // amount_minor: amount_2, // process_error: Some("ERROR".to_string()) // } // ] diff --git a/node/src/accountant/db_access_objects/receivable_dao.rs b/node/src/accountant/db_access_objects/receivable_dao.rs index ad8f52462..9d100c633 100644 --- a/node/src/accountant/db_access_objects/receivable_dao.rs +++ b/node/src/accountant/db_access_objects/receivable_dao.rs @@ -55,7 +55,7 @@ pub trait ReceivableDao { &self, now: SystemTime, wallet: &Wallet, - amount: u128, + amount_minor: u128, ) -> Result<(), ReceivableDaoError>; fn more_money_received( @@ -112,7 +112,7 @@ impl ReceivableDao for ReceivableDaoReal { &self, timestamp: SystemTime, wallet: &Wallet, - amount: u128, + amount_minor: u128, ) -> Result<(), ReceivableDaoError> { let main_sql = "insert into receivable (wallet_address, balance_high_b, balance_low_b, last_received_timestamp) values \ (:wallet, :balance_high_b, :balance_low_b, :last_received_timestamp) on conflict (wallet_address) do update set \ @@ -125,7 +125,7 @@ impl ReceivableDao for ReceivableDaoReal { .key(WalletAddress(wallet)) .wei_change(WeiChange::new( "balance", - amount, + amount_minor, WeiChangeDirection::Addition, )) .other_params(vec![ParamByUse::BeforeOverflowOnly( diff --git a/node/src/accountant/db_access_objects/sent_payable_and_failed_payable_data_conversion.rs b/node/src/accountant/db_access_objects/sent_payable_and_failed_payable_data_conversion.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/node/src/accountant/db_access_objects/sent_payable_and_failed_payable_data_conversion.rs @@ -0,0 +1 @@ + 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 148813cad..0b1f12021 100644 --- a/node/src/accountant/db_access_objects/sent_payable_dao.rs +++ b/node/src/accountant/db_access_objects/sent_payable_dao.rs @@ -26,9 +26,9 @@ pub enum SentPayableDaoError { pub struct SentTx { pub hash: TxHash, pub receiver_address: Address, - pub amount: u128, + pub amount_minor: u128, pub timestamp: i64, - pub gas_price_wei: u128, + pub gas_price_minor: u128, pub nonce: u64, pub block_opt: Option, } @@ -138,8 +138,8 @@ impl SentPayableDao for SentPayableDaoReal<'_> { block_number ) VALUES {}", comma_joined_stringifiable(txs, |tx| { - let amount_checked = checked_conversion::(tx.amount); - let gas_price_wei_checked = checked_conversion::(tx.gas_price_wei); + let amount_checked = checked_conversion::(tx.amount_minor); + let gas_price_wei_checked = checked_conversion::(tx.gas_price_minor); let (amount_high_b, amount_low_b) = BigIntDivider::deconstruct(amount_checked); let (gas_price_wei_high_b, gas_price_wei_low_b) = BigIntDivider::deconstruct(gas_price_wei_checked); @@ -200,11 +200,11 @@ impl SentPayableDao for SentPayableDaoReal<'_> { Address::from_str(&receiver_address_str[2..]).expect("Failed to parse H160"); let amount_high_b = row.get(2).expectv("amount_high_b"); let amount_low_b = row.get(3).expectv("amount_low_b"); - let amount = BigIntDivider::reconstitute(amount_high_b, amount_low_b) as u128; + let amount_minor = BigIntDivider::reconstitute(amount_high_b, amount_low_b) as u128; let timestamp = row.get(4).expectv("timestamp"); let gas_price_wei_high_b = row.get(5).expectv("gas_price_wei_high_b"); let gas_price_wei_low_b = row.get(6).expectv("gas_price_wei_low_b"); - let gas_price_wei = + let gas_price_minor = BigIntDivider::reconstitute(gas_price_wei_high_b, gas_price_wei_low_b) as u128; let nonce = row.get(7).expectv("nonce"); let block_hash_opt: Option = { @@ -229,9 +229,9 @@ impl SentPayableDao for SentPayableDaoReal<'_> { Ok(SentTx { hash, receiver_address, - amount, + amount_minor, timestamp, - gas_price_wei, + gas_price_minor, nonce, block_opt, }) @@ -290,23 +290,23 @@ impl SentPayableDao for SentPayableDaoReal<'_> { let tx_hash_cases = build_case(|tx| format!("'{:?}'", tx.hash)); let receiver_address_cases = build_case(|tx| format!("'{:?}'", tx.receiver_address)); let amount_high_b_cases = build_case(|tx| { - let amount_checked = checked_conversion::(tx.amount); + let amount_checked = checked_conversion::(tx.amount_minor); let (high, _) = BigIntDivider::deconstruct(amount_checked); high.to_string() }); let amount_low_b_cases = build_case(|tx| { - let amount_checked = checked_conversion::(tx.amount); + let amount_checked = checked_conversion::(tx.amount_minor); let (_, low) = BigIntDivider::deconstruct(amount_checked); low.to_string() }); let timestamp_cases = build_case(|tx| tx.timestamp.to_string()); let gas_price_wei_high_b_cases = build_case(|tx| { - let gas_price_wei_checked = checked_conversion::(tx.gas_price_wei); + let gas_price_wei_checked = checked_conversion::(tx.gas_price_minor); let (high, _) = BigIntDivider::deconstruct(gas_price_wei_checked); high.to_string() }); let gas_price_wei_low_b_cases = build_case(|tx| { - let gas_price_wei_checked = checked_conversion::(tx.gas_price_wei); + let gas_price_wei_checked = checked_conversion::(tx.gas_price_minor); let (_, low) = BigIntDivider::deconstruct(gas_price_wei_checked); low.to_string() }); @@ -497,12 +497,12 @@ mod tests { [SentTx { \ hash: 0x00000000000000000000000000000000000000000000000000000000000004d2, \ receiver_address: 0x0000000000000000000000000000000000000000, \ - amount: 0, timestamp: 1749204017, gas_price_wei: 0, \ + amount_minor: 0, timestamp: 1749204017, gas_price_minor: 0, \ nonce: 0, block_opt: None }, \ SentTx { \ hash: 0x00000000000000000000000000000000000000000000000000000000000004d2, \ receiver_address: 0x0000000000000000000000000000000000000000, \ - amount: 0, timestamp: 1749204020, gas_price_wei: 0, \ + amount_minor: 0, timestamp: 1749204020, gas_price_minor: 0, \ nonce: 0, block_opt: Some(TransactionBlock { \ block_hash: 0x0000000000000000000000000000000000000000000000000000000000000000, \ block_number: 0 }) }]" diff --git a/node/src/accountant/db_access_objects/test_utils.rs b/node/src/accountant/db_access_objects/test_utils.rs index 11b85fe17..eb0c5110f 100644 --- a/node/src/accountant/db_access_objects/test_utils.rs +++ b/node/src/accountant/db_access_objects/test_utils.rs @@ -51,9 +51,9 @@ impl TxBuilder { SentTx { hash: self.hash_opt.unwrap_or_default(), receiver_address: self.receiver_address_opt.unwrap_or_default(), - amount: self.amount_opt.unwrap_or_default(), + amount_minor: self.amount_opt.unwrap_or_default(), timestamp: self.timestamp_opt.unwrap_or_else(current_unix_timestamp), - gas_price_wei: self.gas_price_wei_opt.unwrap_or_default(), + gas_price_minor: self.gas_price_wei_opt.unwrap_or_default(), nonce: self.nonce_opt.unwrap_or_default(), block_opt: self.block_opt, } @@ -106,9 +106,9 @@ impl FailedTxBuilder { FailedTx { hash: self.hash_opt.unwrap_or_default(), receiver_address: self.receiver_address_opt.unwrap_or_default(), - amount: self.amount_opt.unwrap_or_default(), + amount_minor: self.amount_opt.unwrap_or_default(), timestamp: self.timestamp_opt.unwrap_or_default(), - gas_price_wei: self.gas_price_wei_opt.unwrap_or_default(), + gas_price_minor: self.gas_price_wei_opt.unwrap_or_default(), nonce: self.nonce_opt.unwrap_or_default(), reason: self .reason_opt diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index 37333255a..825a98c61 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -1119,15 +1119,10 @@ impl Accountant { fn register_new_pending_sent_tx(&self, msg: RegisterNewPendingSentTxMessage) { fn serialize_hashes(fingerprints_data: &[SentTx]) -> String { - comma_joined_stringifiable(fingerprints_data, |sent_tx| { - format!("{:?}", sent_tx.hash) - }) + comma_joined_stringifiable(fingerprints_data, |sent_tx| format!("{:?}", sent_tx.hash)) } - match self - .sent_payable_dao - .insert_new_records(&msg.new_sent_txs) - { + match self.sent_payable_dao.insert_new_records(&msg.new_sent_txs) { Ok(_) => debug!( self.logger, "Saved new pending payable fingerprints for: {}", @@ -1242,7 +1237,7 @@ mod tests { use crate::accountant::test_utils::DaoWithDestination::{ ForAccountantBody, ForPayableScanner, ForPendingPayableScanner, ForReceivableScanner, }; - use crate::accountant::test_utils::{bc_from_earning_wallet, bc_from_wallets, make_payable_account, make_qualified_and_unqualified_payables, BannedDaoFactoryMock, ConfigDaoFactoryMock, MessageIdGeneratorMock, PayableDaoFactoryMock, PayableDaoMock, PayableScannerBuilder, PaymentAdjusterMock, SentPayableDaoFactoryMock, SentPayableDaoMock, ReceivableDaoFactoryMock, ReceivableDaoMock, make_sent_tx}; + use crate::accountant::test_utils::{bc_from_earning_wallet, bc_from_wallets, make_payable_account, make_qualified_and_unqualified_payables, BannedDaoFactoryMock, ConfigDaoFactoryMock, MessageIdGeneratorMock, PayableDaoFactoryMock, PayableDaoMock, PayableScannerBuilder, PaymentAdjusterMock, SentPayableDaoFactoryMock, SentPayableDaoMock, ReceivableDaoFactoryMock, ReceivableDaoMock, make_sent_tx, FailedPayableDaoMock, FailedPayableDaoFactoryMock}; use crate::accountant::test_utils::{make_unpriced_qualified_payables_for_retry_mode, make_priced_qualified_payables}; use crate::accountant::test_utils::{AccountantBuilder, BannedDaoMock}; use crate::accountant::Accountant; @@ -1327,7 +1322,8 @@ mod tests { fn new_calls_factories_properly() { let config = make_bc_with_defaults(); let payable_dao_factory_params_arc = Arc::new(Mutex::new(vec![])); - let pending_payable_dao_factory_params_arc = Arc::new(Mutex::new(vec![])); + let sent_payable_dao_factory_params_arc = Arc::new(Mutex::new(vec![])); + let failed_payable_dao_factory_params_arc = Arc::new(Mutex::new(vec![])); let receivable_dao_factory_params_arc = Arc::new(Mutex::new(vec![])); let banned_dao_factory_params_arc = Arc::new(Mutex::new(vec![])); let config_dao_factory_params_arc = Arc::new(Mutex::new(vec![])); @@ -1337,10 +1333,13 @@ mod tests { .make_result(PayableDaoMock::new()) // For Payable Scanner .make_result(PayableDaoMock::new()); // For PendingPayable Scanner let sent_payable_dao_factory = SentPayableDaoFactoryMock::new() - .make_params(&pending_payable_dao_factory_params_arc) + .make_params(&sent_payable_dao_factory_params_arc) .make_result(SentPayableDaoMock::new()) // For Accountant .make_result(SentPayableDaoMock::new()) // For Payable Scanner .make_result(SentPayableDaoMock::new()); // For PendingPayable Scanner + let failed_payable_dao_factory = FailedPayableDaoFactoryMock::new() + .make_params(&failed_payable_dao_factory_params_arc) + .make_result(FailedPayableDaoMock::new()); // For PendingPayableScanner; let receivable_dao_factory = ReceivableDaoFactoryMock::new() .make_params(&receivable_dao_factory_params_arc) .make_result(ReceivableDaoMock::new()) // For Accountant @@ -1357,6 +1356,7 @@ mod tests { DaoFactories { payable_dao_factory: Box::new(payable_dao_factory), sent_payable_dao_factory: Box::new(sent_payable_dao_factory), + failed_payable_dao_factory: Box::new(failed_payable_dao_factory), receivable_dao_factory: Box::new(receivable_dao_factory), banned_dao_factory: Box::new(banned_dao_factory), config_dao_factory: Box::new(config_dao_factory), @@ -1368,9 +1368,13 @@ mod tests { vec![(), (), ()] ); assert_eq!( - *pending_payable_dao_factory_params_arc.lock().unwrap(), + *sent_payable_dao_factory_params_arc.lock().unwrap(), vec![(), (), ()] ); + assert_eq!( + *failed_payable_dao_factory_params_arc.lock().unwrap(), + vec![()] + ); assert_eq!( *receivable_dao_factory_params_arc.lock().unwrap(), vec![(), ()] @@ -1394,6 +1398,8 @@ mod tests { .make_result(SentPayableDaoMock::new()) // For Payable Scanner .make_result(SentPayableDaoMock::new()), // For PendingPayable Scanner ); + let failed_payable_dao_factory = + Box::new(FailedPayableDaoFactoryMock::new().make_result(FailedPayableDaoMock::new())); // For PendingPayableScanner; let receivable_dao_factory = Box::new( ReceivableDaoFactoryMock::new() .make_result(ReceivableDaoMock::new()) // For Accountant @@ -1409,6 +1415,7 @@ mod tests { DaoFactories { payable_dao_factory, sent_payable_dao_factory, + failed_payable_dao_factory, receivable_dao_factory, banned_dao_factory, config_dao_factory, @@ -1849,15 +1856,15 @@ mod tests { }); let sent_tx = SentTx { timestamp: to_unix_timestamp(SystemTime::now()) - 1234, - gas_price_wei: 456_000_000, + gas_price_minor: 456_000_000, nonce: 45, hash: make_tx_hash(123), - amount: 1_000_000, + amount_minor: 1_000_000, receiver_address: make_wallet("receiver_address").address(), block_opt: None, }; - let sent_payable_dao = SentPayableDaoMock::default() - .retrieve_txs_result(vec![sent_tx.clone()]); + let sent_payable_dao = + SentPayableDaoMock::default().retrieve_txs_result(vec![sent_tx.clone()]); let mut subject = AccountantBuilder::default() .consuming_wallet(make_paying_wallet(b"consuming")) .bootstrapper_config(config) @@ -1910,8 +1917,7 @@ mod tests { let payable_dao = PayableDaoMock::default() .transactions_confirmed_params(&transaction_confirmed_params_arc) .transactions_confirmed_result(Ok(())); - let sent_payable_dao = - SentPayableDaoMock::default().delete_records_result(Ok(())); + let sent_payable_dao = SentPayableDaoMock::default().delete_records_result(Ok(())); let mut subject = AccountantBuilder::default() .payable_daos(vec![ForPendingPayableScanner(payable_dao)]) .sent_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) @@ -1933,14 +1939,13 @@ mod tests { let subject_addr = subject.start(); let sent_tx = make_sent_tx(123); let report_tx_receipts = TxStatusReport { - results: vec![ - TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( - sent_tx.clone(), - TxStatus::Succeeded(TransactionBlock { - block_hash: make_tx_hash(456), - block_number: 78901234.into(), - }) - ))], + results: vec![TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( + sent_tx.clone(), + TxStatus::Succeeded(TransactionBlock { + block_hash: make_tx_hash(456), + block_number: 78901234.into(), + }), + ))], response_skeleton_opt, }; @@ -2152,12 +2157,10 @@ mod tests { let first_counter_msg_setup = setup_for_counter_msg_triggered_via_type_id!( RequestTransactionReceipts, TxStatusReport { - results: vec![ - TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( - sent_tx, - TxStatus::Failed(TxBlockchainFailure::Unknown) - )), - ], + results: vec![TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( + sent_tx, + TxStatus::Failed(TxBlockchainFailure::Unknown) + )),], response_skeleton_opt }, &subject_addr @@ -2752,7 +2755,7 @@ mod tests { .scan_started_at_result(None) .start_scan_params(&scan_params.pending_payable_start_scan) .start_scan_result(Ok(RequestTransactionReceipts { - sent_tx: vec![sent_tx], + sent_tx: vec![sent_tx.clone()], response_skeleton_opt: None, })) .finish_scan_params(&scan_params.pending_payable_finish_scan) @@ -2774,12 +2777,10 @@ mod tests { let subject_addr: Addr = subject.start(); let subject_subs = Accountant::make_subs_from(&subject_addr); let expected_tx_status_report = TxStatusReport { - results: vec![ - TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( - sent_tx, - TxStatus::Failed(TxBlockchainFailure::Unknown), - )) - ], + results: vec![TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( + sent_tx.clone(), + TxStatus::Failed(TxBlockchainFailure::Unknown), + ))], response_skeleton_opt: None, }; let expected_sent_payables = SentPayables { @@ -3297,8 +3298,8 @@ mod tests { #[test] fn initial_pending_payable_scan_if_some_payables_found() { - let sent_payable_dao = SentPayableDaoMock::default() - .retrieve_txs_result(vec![make_sent_tx(789)]); + let sent_payable_dao = + SentPayableDaoMock::default().retrieve_txs_result(vec![make_sent_tx(789)]); let mut subject = AccountantBuilder::default() .consuming_wallet(make_wallet("consuming")) .sent_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) @@ -3323,8 +3324,7 @@ mod tests { #[test] fn initial_pending_payable_scan_if_no_payables_found() { - let sent_payable_dao = - SentPayableDaoMock::default().retrieve_txs_result(vec![]); + let sent_payable_dao = SentPayableDaoMock::default().retrieve_txs_result(vec![]); let mut subject = AccountantBuilder::default() .consuming_wallet(make_wallet("consuming")) .sent_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) @@ -3513,17 +3513,14 @@ mod tests { }; let tx_with_status = SentTxWithLatestStatus { sent_tx: sent_tx.clone(), - status: TxStatus::Succeeded(TransactionBlock{ + status: TxStatus::Succeeded(TransactionBlock { block_hash: make_tx_hash(369369), block_number: 4444444444u64.into(), }), }; - let requested_tx = make_tx_hash( - 234 - ); + let requested_tx = make_tx_hash(234); let counter_msg_3 = TxStatusReport { - results: vec![ - TxReceiptResult::RpcResponse(tx_with_status)], + results: vec![TxReceiptResult::RpcResponse(tx_with_status)], response_skeleton_opt: None, }; let request_transaction_receipts_msg = RequestTransactionReceipts { @@ -4071,27 +4068,24 @@ mod tests { .start(); let sent_tx_1 = SentTx { timestamp: to_unix_timestamp(now.checked_sub(Duration::from_secs(1234)).unwrap()), - gas_price_wei: 0, + gas_price_minor: 0, nonce: 0, hash: make_tx_hash(45678), - amount: 4444, + amount_minor: 4444, receiver_address: Default::default(), block_opt: None, }; let sent_tx_2 = SentTx { timestamp: to_unix_timestamp(now.checked_sub(Duration::from_secs(3456)).unwrap()), - gas_price_wei: 0, + gas_price_minor: 0, nonce: 0, hash: make_tx_hash(112233), - amount: 7999, + amount_minor: 7999, receiver_address: Default::default(), block_opt: None, }; let sent_payable_dao = SentPayableDaoMock::default() - .retrieve_txs_result(vec![ - sent_tx_1.clone(), - sent_tx_2.clone(), - ]); + .retrieve_txs_result(vec![sent_tx_1.clone(), sent_tx_2.clone()]); let config = bc_from_earning_wallet(make_wallet("mine")); let system = System::new("pending payable scan"); let mut subject = AccountantBuilder::default() @@ -4965,8 +4959,10 @@ mod tests { subject.scan_schedulers.payable.retry_payable_notify = Box::new(NotifyHandleMock::default().notify_params(&retry_payable_notify_params_arc)); let system = System::new(test_name); - let (mut msg, _) = - make_tx_status_report_msg(vec![TxStatus::Pending, TxStatus::Failed(TxBlockchainFailure::Unknown)]); + let (mut msg, _) = make_tx_status_report_msg(vec![ + TxStatus::Pending, + TxStatus::Failed(TxBlockchainFailure::Unknown), + ]); let response_skeleton_opt = Some(ResponseSkeleton { client_id: 45, context_id: 7, @@ -5001,10 +4997,9 @@ mod tests { let payable_dao = PayableDaoMock::default() .transactions_confirmed_params(&transactions_confirmed_params_arc) .transactions_confirmed_result(Ok(())); - let sent_payable_dao = - SentPayableDaoMock::default() - .delete_records_params(&delete_records_params_arc) - .delete_records_result(Ok(())); + let sent_payable_dao = SentPayableDaoMock::default() + .delete_records_params(&delete_records_params_arc) + .delete_records_result(Ok(())); let system = System::new("new_payable_scanner_timely"); let mut subject = AccountantBuilder::default() .payable_daos(vec![ForPendingPayableScanner(payable_dao)]) @@ -5033,7 +5028,7 @@ mod tests { subject.scan_schedulers.payable.new_payable_notify = Box::new(NotifyHandleMock::default().notify_params(&new_payable_notify_arc)); let subject_addr = subject.start(); - let (msg, two_fingerprints) = make_tx_status_report_msg(vec![ + let (msg, two_sent_txs) = make_tx_status_report_msg(vec![ TxStatus::Succeeded(TransactionBlock { block_hash: make_tx_hash(123), block_number: U64::from(100), @@ -5048,10 +5043,17 @@ mod tests { System::current().stop(); system.run(); + let hashes = two_sent_txs + .iter() + .map(|sent_tx| sent_tx.hash) + .collect(); let transactions_confirmed_params = transactions_confirmed_params_arc.lock().unwrap(); - assert_eq!(*transactions_confirmed_params, vec![two_fingerprints]); + assert_eq!(*transactions_confirmed_params, vec![two_sent_txs.clone()]); let delete_records_params = delete_records_params_arc.lock().unwrap(); - assert_eq!(*delete_records_params, vec![two_fingerprints.iter().map(|sent_tx|sent_tx.hash).collect()]); + assert_eq!( + *delete_records_params, + vec![hashes] + ); 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); @@ -5084,10 +5086,9 @@ mod tests { let payable_dao = PayableDaoMock::default() .transactions_confirmed_params(&transactions_confirmed_params_arc) .transactions_confirmed_result(Ok(())); - let sent_payable_dao = - SentPayableDaoMock::default() - .delete_records_params(&delete_records_params_arc) - .delete_records_result(Ok(())); + let sent_payable_dao = SentPayableDaoMock::default() + .delete_records_params(&delete_records_params_arc) + .delete_records_result(Ok(())); let mut subject = AccountantBuilder::default() .payable_daos(vec![ForPendingPayableScanner(payable_dao)]) .sent_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) @@ -5114,7 +5115,7 @@ mod tests { subject.scan_schedulers.payable.new_payable_notify = Box::new(NotifyHandleMock::default().notify_params(&new_payable_notify_arc)); let subject_addr = subject.start(); - let (msg, two_fingerprints) = make_tx_status_report_msg(vec![ + let (msg, two_sent_txs) = make_tx_status_report_msg(vec![ TxStatus::Succeeded(TransactionBlock { block_hash: make_tx_hash(123), block_number: U64::from(100), @@ -5130,10 +5131,17 @@ mod tests { let system = System::new("new_payable_scanner_asap"); System::current().stop(); system.run(); + let hashes = two_sent_txs + .iter() + .map(|sent_tx| sent_tx.hash) + .collect(); let transactions_confirmed_params = transactions_confirmed_params_arc.lock().unwrap(); - assert_eq!(*transactions_confirmed_params, vec![two_fingerprints]); + assert_eq!(*transactions_confirmed_params, vec![two_sent_txs]); let delete_records_params = delete_records_params_arc.lock().unwrap(); - assert_eq!(*delete_records_params, vec![two_fingerprints.iter().map(|sent_tx|sent_tx.hash).collect()]); + assert_eq!( + *delete_records_params, + vec![hashes] + ); 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); @@ -5157,8 +5165,7 @@ mod tests { fn scheduler_for_new_payables_operates_with_proper_now_timestamp() { let new_payable_notify_later_arc = Arc::new(Mutex::new(vec![])); let payable_dao = PayableDaoMock::default().transactions_confirmed_result(Ok(())); - let sent_payable_dao = - SentPayableDaoMock::default().delete_records_result(Ok(())); + let sent_payable_dao = SentPayableDaoMock::default().delete_records_result(Ok(())); let system = System::new("scheduler_for_new_payables_operates_with_proper_now_timestamp"); let mut subject = AccountantBuilder::default() .payable_daos(vec![ForPendingPayableScanner(payable_dao)]) @@ -5219,13 +5226,10 @@ mod tests { ); } - fn make_tx_status_report_msg( - status_txs: Vec, - ) -> (TxStatusReport, Vec) { - let (tx_receipt_results, sent_tx_vec) = status_txs - .into_iter() - .enumerate() - .fold((vec![], vec![]),|(mut tx_receipt_results, mut sent_tx_vec), (idx, status)| { + fn make_tx_status_report_msg(status_txs: Vec) -> (TxStatusReport, Vec) { + let (tx_receipt_results, sent_tx_vec) = status_txs.into_iter().enumerate().fold( + (vec![], vec![]), + |(mut tx_receipt_results, mut sent_tx_vec), (idx, status)| { let sent_tx = make_sent_tx(idx as u64); let hash = sent_tx.hash; let tx_receipt_result = TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( @@ -5234,11 +5238,9 @@ mod tests { )); tx_receipt_results.push(tx_receipt_result); sent_tx_vec.push(sent_tx); - ( - tx_receipt_results, - sent_tx_vec, - ) - }); + (tx_receipt_results, sent_tx_vec) + }, + ); let msg = TxStatusReport { results: tx_receipt_results, @@ -5266,24 +5268,19 @@ mod tests { let mut sent_tx_2 = make_sent_tx(789); let hash_2 = make_tx_hash(0x1b207); sent_tx_2.hash = hash_2; - let new_sent_txs = vec![sent_tx_1, sent_tx_2]; - let init_fingerprints_msg = RegisterNewPendingSentTxMessage { - new_sent_txs - }; - + let new_sent_txs = vec![sent_tx_1.clone(), sent_tx_2.clone()]; + let init_fingerprints_msg = RegisterNewPendingSentTxMessage { new_sent_txs }; + let _ = accountant_subs .init_pending_payable_fingerprints .try_send(init_fingerprints_msg) .unwrap(); - + let system = System::new("ordering payment sent tx record test"); System::current().stop(); assert_eq!(system.run(), 0); let insert_fingerprint_params = insert_new_records_params_arc.lock().unwrap(); - assert_eq!( - *insert_fingerprint_params, - vec![vec![sent_tx_1, sent_tx_2]] - ); + assert_eq!(*insert_fingerprint_params, vec![vec![sent_tx_1, sent_tx_2]]); TestLogHandler::new().exists_log_containing( "DEBUG: Accountant: Saved new pending payable fingerprints for: \ 0x000000000000000000000000000000000000000000000000000000000006c81c, 0x000000000000000000000000000000000000000000000000000000000001b207", @@ -5291,40 +5288,40 @@ mod tests { } #[test] - fn sent_payable_insertion_clearly_failed_and_we_log_it_at_least() { - //despite it doesn't end so here this event would be a cause of a later panic + fn sent_payable_insertion_clearly_failed_and_we_log_at_least() { + // Even though it's factually a filed db operation, which is treated by an instant panic + // due to the broken db reliance, this is an exception. We give out some time to complete + // the actual paying and panic soon after when we figure out, from a different place + // that some sent tx records are missing. This should eventually be eliminated by GH-655 init_test_logging(); + let test_name = "sent_payable_insertion_clearly_failed_and_we_log_at_least"; let insert_new_records_params_arc = Arc::new(Mutex::new(vec![])); let sent_payable_dao = SentPayableDaoMock::default() .insert_new_records_params(&insert_new_records_params_arc) .insert_new_records_result(Err(SentPayableDaoError::SqlExecutionFailed( "Crashed".to_string(), ))); - let amount = 2345; - let transaction_hash = make_tx_hash(0x1c8); - let hash_and_amount = HashAndAmount { - hash: transaction_hash, - amount, - }; + let tx_hash = make_tx_hash(0x1c8); + let mut sent_tx = make_sent_tx(456); + sent_tx.hash = tx_hash; let subject = AccountantBuilder::default() .sent_payable_daos(vec![ForAccountantBody(sent_payable_dao)]) .build(); let timestamp = SystemTime::now(); let report_new_fingerprints = RegisterNewPendingSentTxMessage { - batch_wide_timestamp: timestamp, - hashes_and_balances: vec![hash_and_amount], + new_sent_txs: vec![sent_tx.clone()], }; let _ = subject.register_new_pending_sent_tx(report_new_fingerprints); let insert_fingerprint_params = insert_new_records_params_arc.lock().unwrap(); - assert_eq!( - *insert_fingerprint_params, - vec![(vec![hash_and_amount], timestamp)] - ); - TestLogHandler::new().exists_log_containing("ERROR: Accountant: Failed to process \ - new pending payable fingerprints due to 'InsertionFailed(\"Crashed\")', disabling the automated \ - confirmation for all these transactions: 0x00000000000000000000000000000000000000000000000000000000000001c8"); + assert_eq!(*insert_fingerprint_params, vec![vec![sent_tx]]); + TestLogHandler::new().exists_log_containing(&format!( + "ERROR: {test_name}: Failed to \ + process new pending payable fingerprints due to 'InsertionFailed(\"Crashed\")', disabling \ + the automated confirmation for all these transactions: 0x0000000000000000000000000000000000\ + 0000000000000000000000000001c8" + )); } const EXAMPLE_RESPONSE_SKELETON: ResponseSkeleton = ResponseSkeleton { diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index 6b4e55ed3..bdd99c367 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -695,25 +695,34 @@ impl PayableScanner { todo!("check potential duplicity") } - let transaction_hashes_and_rowids_from_db = self.sent_payable_dao.get_tx_identifiers(&hashset_with_hashes_to_eliminate_duplicities); + let transaction_hashes_and_rowids_from_db = self + .sent_payable_dao + .get_tx_identifiers(&hashset_with_hashes_to_eliminate_duplicities); let hashes_from_db = transaction_hashes_and_rowids_from_db .keys() .copied() .collect::>(); - let missing_sent_payables_hashes:Vec = hashset_with_hashes_to_eliminate_duplicities.difference(&hashes_from_db).copied().collect(); + let missing_sent_payables_hashes: Vec = + hashset_with_hashes_to_eliminate_duplicities + .difference(&hashes_from_db) + .copied() + .collect(); let mut sent_payables_hashmap = just_reported_sent_payables .iter() .map(|payable| (payable.hash, &payable.recipient_wallet)) .collect::>(); - missing_sent_payables_hashes.into_iter().map(|hash| { - let wallet_address = sent_payables_hashmap - .remove(&hash) - .expectv("wallet") - .address(); - PendingPayableMissingInDb::new(wallet_address, hash) - }).collect() + missing_sent_payables_hashes + .into_iter() + .map(|hash| { + let wallet_address = sent_payables_hashmap + .remove(&hash) + .expectv("wallet") + .address(); + PendingPayableMissingInDb::new(wallet_address, hash) + }) + .collect() } // fn separate_existent_and_nonexistent_fingerprints<'a>( @@ -767,7 +776,7 @@ impl PayableScanner { // (pending_payables_with_rowid, pending_payables_without_rowid) // } - fn check_on_missing_sent_tx_records(&self, sent_payments: &[&PendingPayable]){ + fn check_on_missing_sent_tx_records(&self, sent_payments: &[&PendingPayable]) { fn missing_fingerprints_msg(nonexistent: &[PendingPayableMissingInDb]) -> String { format!( "Expected pending payable fingerprints for {} were not found; system unreliable", @@ -779,8 +788,7 @@ impl PayableScanner { } todo!("drive me in"); - let missing_sent_tx_records = - self.check_for_missing_records(sent_payments); + let missing_sent_tx_records = self.check_for_missing_records(sent_payments); if !missing_sent_tx_records.is_empty() { panic!("{}", missing_fingerprints_msg(&missing_sent_tx_records)) } @@ -845,12 +853,12 @@ impl PayableScanner { logger: &Logger, ) { if let Some(err) = err_opt { - if let - LocallyCausedError(PayableTransactionError::Sending { hashes, .. }) - | RemotelyCausedErrors(hashes) = &err { - self.discard_failed_transactions_with_possible_sent_tx_records(hashes, logger) - } else { - debug!( + if let LocallyCausedError(PayableTransactionError::Sending { hashes, .. }) + | RemotelyCausedErrors(hashes) = &err + { + self.discard_failed_transactions_with_possible_sent_tx_records(hashes, logger) + } else { + debug!( logger, "Ignoring a non-fatal error on our end from before the transactions are hashed: {:?}", err @@ -868,9 +876,7 @@ impl PayableScanner { comma_joined_stringifiable(hashes, |hash| format!("{:?}", hash)) } - let existent_sent_tx_in_db = self - .sent_payable_dao - .get_tx_identifiers(&hashes_of_failed); + let existent_sent_tx_in_db = self.sent_payable_dao.get_tx_identifiers(&hashes_of_failed); let hashes_of_missing_sent_tx = todo!(); @@ -878,7 +884,7 @@ impl PayableScanner { hashes_of_missing_sent_tx, serialize_hashes, ); - + if !existent_sent_tx_in_db.is_empty() { //let (ids, hashes) = separate_rowids_and_hashes(existent_sent_tx_in_db.rowid_results); let hashes = existent_sent_tx_in_db.keys().copied().collect_vec(); @@ -887,7 +893,10 @@ impl PayableScanner { "Deleting fingerprints for failed transactions {}", serialize_hashes(&hashes) ); - if let Err(e) = self.sent_payable_dao.delete_records(&existent_sent_tx_in_db.keys().copied().collect()) { + if let Err(e) = self + .sent_payable_dao + .delete_records(&existent_sent_tx_in_db.keys().copied().collect()) + { if let Some(msg) = missing_fgp_err_msg_opt { error!(logger, "{}", msg) }; @@ -912,7 +921,7 @@ pub struct PendingPayableScanner { pub failed_payable_dao: Box, pub when_pending_too_long_sec: u64, pub financial_statistics: Rc>, - pub cached_txs_pending_too_long_from_previous_cycle_opt: Option> //TODO also treat carefully + pub cached_txs_pending_too_long_from_previous_cycle_opt: Option>, //TODO also treat carefully } impl @@ -937,7 +946,9 @@ impl StartableScanner ) -> Result { self.mark_as_started(timestamp); info!(logger, "Scanning for pending payable"); - let pending_sent_txs = self.sent_payable_dao.retrieve_txs(Some(RetrieveCondition::IsPending)); + let pending_sent_txs = self + .sent_payable_dao + .retrieve_txs(Some(RetrieveCondition::IsPending)); match pending_sent_txs.is_empty() { true => { self.mark_as_ended(logger); @@ -1037,33 +1048,35 @@ impl PendingPayableScanner { logger: &Logger, ) -> PendingPayableScanFinalReport { let scan_report = PendingPayableScanFinalReport::default(); - msg.results.into_iter().fold( - scan_report, - |scan_report_so_far, receipt_result| match receipt_result { - TxReceiptResult::RpcResponse(sent_tx_with_status) => match sent_tx_with_status.status { - TxStatus::Succeeded(tx_block) => { - todo!("check that the tx block is used in logging somehow"); - let completed_sent_tx = SentTx { - block_opt: Some(tx_block), - ..sent_tx_with_status.sent_tx - }; - handle_successful_tx(scan_report_so_far, completed_sent_tx, logger) - } - TxStatus::Failed(reason) => { - let failed_tx = FailedTx::from((sent_tx_with_status.sent_tx, reason)); - handle_status_with_failure(scan_report_so_far, failed_tx, logger) - } - TxStatus::Pending => { - todo!() + msg.results + .into_iter() + .fold( + scan_report, + |scan_report_so_far, receipt_result| match receipt_result { + TxReceiptResult::RpcResponse(sent_tx_with_status) => match sent_tx_with_status + .status + { + TxStatus::Succeeded(tx_block) => { + todo!("check that the tx block is used in logging somehow"); + let completed_sent_tx = SentTx { + block_opt: Some(tx_block), + ..sent_tx_with_status.sent_tx + }; + handle_successful_tx(scan_report_so_far, completed_sent_tx, logger) + } + TxStatus::Failed(reason) => { + let failed_tx = FailedTx::from((sent_tx_with_status.sent_tx, reason)); + handle_status_with_failure(scan_report_so_far, failed_tx, logger) + } + TxStatus::Pending => { + todo!() + } + }, + TxReceiptResult::RequestError(e) => { + handle_request_error_fetching_receipts(scan_report_so_far, e, logger) } }, - TxReceiptResult::RequestError(e) => handle_request_error_fetching_receipts( - scan_report_so_far, - e, - logger, - ), - }, - ) + ) } fn process_transactions_by_their_state( @@ -1099,11 +1112,12 @@ impl PendingPayableScanner { fn handle_failed_transactions(&self, failures: Vec, logger: &Logger) { if !failures.is_empty() { - let hashes = failures.iter().map(|failed_tx|failed_tx.hash).collect(); + let hashes = failures.iter().map(|failed_tx| failed_tx.hash).collect(); match self.failed_payable_dao.insert_new_records(&failures) { - Ok(_) => { todo!() + Ok(_) => { + todo!() } - Err(e) => todo!() + Err(e) => todo!(), } match self.sent_payable_dao.delete_records(&hashes) { // Ok(_) => warning!( @@ -1121,17 +1135,12 @@ impl PendingPayableScanner { // ), // } Ok(_) => todo!(), - Err(e) => todo!() + Err(e) => todo!(), }; - } } - fn confirm_transactions( - &mut self, - fingerprints: Vec, - logger: &Logger, - ) { + fn confirm_transactions(&mut self, fingerprints: Vec, logger: &Logger) { // fn serialize_hashes(fingerprints: &[SentTx]) -> String { // comma_joined_stringifiable(fingerprints, |fgp| format!("{:?}", fgp.hash)) // } @@ -1478,7 +1487,7 @@ mod tests { use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{OperationOutcome, PayableScanResult, PendingPayableMissingInDb}; use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_status_with_failure, PendingPayableScanFinalReport, PendingPayableScanResult}; use crate::accountant::scanners::{Scanner, StartScanError, StartableScanner, PayableScanner, PendingPayableScanner, ReceivableScanner, ScannerCommon, Scanners, MTError}; - use crate::accountant::test_utils::{make_custom_payment_thresholds, make_payable_account, make_qualified_and_unqualified_payables, make_receivable_account, BannedDaoFactoryMock, BannedDaoMock, ConfigDaoFactoryMock, PayableDaoFactoryMock, PayableDaoMock, PayableScannerBuilder, PayableThresholdsGaugeMock, SentPayableDaoFactoryMock, SentPayableDaoMock, PendingPayableScannerBuilder, ReceivableDaoFactoryMock, ReceivableDaoMock, ReceivableScannerBuilder, make_sent_tx}; + use crate::accountant::test_utils::{make_custom_payment_thresholds, make_payable_account, make_qualified_and_unqualified_payables, make_receivable_account, BannedDaoFactoryMock, BannedDaoMock, ConfigDaoFactoryMock, PayableDaoFactoryMock, PayableDaoMock, PayableScannerBuilder, PayableThresholdsGaugeMock, SentPayableDaoFactoryMock, SentPayableDaoMock, PendingPayableScannerBuilder, ReceivableDaoFactoryMock, ReceivableDaoMock, ReceivableScannerBuilder, make_sent_tx, FailedPayableDaoMock, FailedPayableDaoFactoryMock, make_failed_tx}; use crate::accountant::{gwei_to_wei, PendingPayableId, ReceivedPayments, TxStatusReport, RequestTransactionReceipts, ScanError, ScanForRetryPayables, SentPayables, DEFAULT_PENDING_TOO_LONG_SEC, PendingPayable}; use crate::blockchain::blockchain_bridge::{BlockMarker, RetrieveTransactions}; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; @@ -1505,6 +1514,7 @@ mod tests { use rusqlite::{ffi, ErrorCode}; use std::cell::RefCell; use std::collections::{HashMap, HashSet}; + use std::fmt::format; use std::ops::Sub; use std::panic::{catch_unwind, AssertUnwindSafe}; use std::rc::Rc; @@ -1515,7 +1525,7 @@ mod tests { use web3::Error; use masq_lib::messages::ScanType; use masq_lib::ui_gateway::NodeToUiMessage; - use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, FailureReason}; + use crate::accountant::db_access_objects::failed_payable_dao::{FailedPayableDaoError, FailedTx, FailureReason}; use crate::accountant::db_access_objects::sent_payable_dao::{SentPayableDaoError, SentTx}; use crate::accountant::scanners::test_utils::{assert_timestamps_from_str, parse_system_time_from_str, MarkScanner, NullScanner, ReplacementType, ScannerReplacement}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{SentTxWithLatestStatus, TransactionBlock, TxReceiptResult, TxStatus}; @@ -1605,6 +1615,7 @@ mod tests { let sent_payable_dao_factory = SentPayableDaoFactoryMock::new() .make_result(SentPayableDaoMock::new()) .make_result(SentPayableDaoMock::new()); + let failed_payable_dao_factory = FailedPayableDaoFactoryMock::new(); let receivable_dao = ReceivableDaoMock::new(); let receivable_dao_factory = ReceivableDaoFactoryMock::new().make_result(receivable_dao); let banned_dao_factory = BannedDaoFactoryMock::new().make_result(BannedDaoMock::new()); @@ -1626,6 +1637,7 @@ mod tests { DaoFactories { payable_dao_factory: Box::new(payable_dao_factory), sent_payable_dao_factory: Box::new(sent_payable_dao_factory), + failed_payable_dao_factory: Box::new(failed_payable_dao_factory), receivable_dao_factory: Box::new(receivable_dao_factory), banned_dao_factory: Box::new(banned_dao_factory), config_dao_factory: Box::new(config_dao_factory), @@ -2108,16 +2120,20 @@ mod tests { let pending_payables_ref = pending_payables_owned .iter() .collect::>(); - let sent_payable_dao = - SentPayableDaoMock::new().get_tx_identifiers_result(hashmap!(hash_4 => 4, hash_1 => 1, hash_3 => 3, hash_2 => 2)); + let sent_payable_dao = SentPayableDaoMock::new().get_tx_identifiers_result( + hashmap!(hash_4 => 4, hash_1 => 1, hash_3 => 3, hash_2 => 2), + ); let subject = PayableScannerBuilder::new() .sent_payable_dao(sent_payable_dao) .build(); - let missing_records = - subject.check_for_missing_records(&pending_payables_ref); + let missing_records = subject.check_for_missing_records(&pending_payables_ref); - assert!(missing_records.is_empty(), "We thought the vec would be empty but contained: {:?}", missing_records); + assert!( + missing_records.is_empty(), + "We thought the vec would be empty but contained: {:?}", + missing_records + ); } struct TestingMismatchedDataAboutPendingPayables { @@ -2273,8 +2289,7 @@ mod tests { let payment_1 = PendingPayable::new(make_wallet("booga"), hash_1); let hash_2 = make_tx_hash(0x7b); let payment_2 = PendingPayable::new(make_wallet("agoob"), hash_2); - let sent_payable_dao = - SentPayableDaoMock::default().get_tx_identifiers_result(hashmap!()); + let sent_payable_dao = SentPayableDaoMock::default().get_tx_identifiers_result(hashmap!()); let payable_dao = PayableDaoMock::new(); let mut subject = PayableScannerBuilder::new() .payable_dao(payable_dao) @@ -2334,8 +2349,8 @@ mod tests { let test_name = "payable_scanner_mark_pending_payable_only_panics_all_fingerprints_found"; let hash_1 = make_tx_hash(248); let hash_2 = make_tx_hash(139); - let sent_payable_dao = - SentPayableDaoMock::default().get_tx_identifiers_result(hashmap!(hash_1 => 7879, hash_2 => 7881)); + let sent_payable_dao = SentPayableDaoMock::default() + .get_tx_identifiers_result(hashmap!(hash_1 => 7879, hash_2 => 7881)); assert_panic_from_failing_to_mark_pending_payable_rowid( test_name, @@ -2847,7 +2862,7 @@ mod tests { // timestamp: from_unix_timestamp(210_000_000), // hash: make_tx_hash(45678), // attempt: 1, - // amount: 4444, + // amount_minor: 4444, // process_error: None, // }; // let payable_fingerprint_2 = SentTx { @@ -2855,7 +2870,7 @@ mod tests { // timestamp: from_unix_timestamp(210_000_100), // hash: make_tx_hash(112233), // attempt: 1, - // amount: 7999, + // amount_minor: 7999, // process_error: None, // }; // let payables = HashSet::from_iter(vec![payable_fingerprint_1, payable_fingerprint_2]); @@ -2902,8 +2917,8 @@ mod tests { let now = SystemTime::now(); let consuming_wallet = make_paying_wallet(b"consuming"); let mut subject = make_dull_subject(); - let sent_payable_dao = SentPayableDaoMock::new() - .retrieve_txs_result(vec![make_sent_tx(123)]); + let sent_payable_dao = + SentPayableDaoMock::new().retrieve_txs_result(vec![make_sent_tx(123)]); let pending_payable_scanner = PendingPayableScannerBuilder::new() .sent_payable_dao(sent_payable_dao) .build(); @@ -3052,8 +3067,7 @@ mod tests { fn pending_payable_scanner_throws_an_error_when_no_fingerprint_is_found() { let now = SystemTime::now(); let consuming_wallet = make_paying_wallet(b"consuming_wallet"); - let sent_payable_dao = - SentPayableDaoMock::new().retrieve_txs_result(vec![]); + let sent_payable_dao = SentPayableDaoMock::new().retrieve_txs_result(vec![]); let mut pending_payable_scanner = PendingPayableScannerBuilder::new() .sent_payable_dao(sent_payable_dao) .build(); @@ -3091,7 +3105,7 @@ mod tests { // timestamp: when_sent, // hash, // attempt: 1, - // amount: 123, + // amount_minor: 123, // process_error: None, // }; // let logger = Logger::new(test_name); @@ -3167,7 +3181,7 @@ mod tests { #[test] fn interpret_transaction_receipt_when_transaction_status_is_none_and_within_waiting_interval() { - todo!("think of we can transform it into some other test") + todo!("think of we can transform it into some other test") // let test_name = "interpret_transaction_receipt_when_transaction_status_is_none_and_within_waiting_interval"; // let hash = make_tx_hash(0x7b); // let rowid = 333; @@ -3233,16 +3247,9 @@ mod tests { init_test_logging(); let now = SystemTime::now(); let test_name = "interpret_transaction_receipt_when_transaction_status_is_a_failure"; - let failed_tx = FailedTx { - hash: make_tx_hash(654), - receiver_address: Default::default(), - amount: 222_333_444, - timestamp: to_unix_timestamp(now) - 150, - gas_price_wei: 156_000_000_000, - nonce: 13, - reason: todo!("I don't have this yet...some kind of blockchain failure"), - rechecked: false, - }; + let hash = make_tx_hash(0xabc); + let mut failed_tx = make_failed_tx(2244); + failed_tx.reason = FailureReason::General; let logger = Logger::new(test_name); let scan_report = PendingPayableScanFinalReport::default(); @@ -3256,9 +3263,8 @@ mod tests { } ); TestLogHandler::new().exists_log_matching(&format!( - "ERROR: {test_name}: Pending transaction 0x0000000000000000000000000000000000000000\ - 0000000000000000000000d7 announced as a failure, interpreting attempt 5 after \ - 1500\\d\\dms from the sending" + "INFO: {test_name}: Pending transaction 0x0000000000000000000000000000000000000000\ + 000000000000000000000abc bluh bluh" )); } @@ -3272,8 +3278,10 @@ mod tests { let mut sent_tx = make_sent_tx(456); sent_tx.hash = hash; let msg = TxStatusReport { - results: vec![ - TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new(sent_tx.clone(), TxStatus::Pending))], + results: vec![TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( + sent_tx.clone(), + TxStatus::Pending, + ))], response_skeleton_opt: None, }; @@ -3298,26 +3306,32 @@ mod tests { fn handle_failed_transactions_works() { init_test_logging(); let test_name = "handle_failed_transactions_works"; - let mark_failures_params_arc = Arc::new(Mutex::new(vec![])); + let insert_new_records_params_arc = Arc::new(Mutex::new(vec![])); + let delete_records_params_arc = Arc::new(Mutex::new(vec![])); + let failed_payable_dao = FailedPayableDaoMock::default() + .insert_new_records_params(&insert_new_records_params_arc) + .insert_new_records_result(Ok(())); let sent_payable_dao = SentPayableDaoMock::default() - .mark_failures_params(&mark_failures_params_arc) - .mark_failures_result(Ok(())); - let failed_payable_dao = FailedPayableDaoMock::default(); + .delete_records_params(&delete_records_params_arc) + .delete_records_result(Ok(())); let subject = PendingPayableScannerBuilder::new() .sent_payable_dao(sent_payable_dao) + .failed_payable_dao(failed_payable_dao) .build(); - let id_1 = PendingPayableId::new(2, make_tx_hash(0x7b)); - let id_2 = PendingPayableId::new(3, make_tx_hash(0x1c8)); + let hash_1 = make_tx_hash(0x321); + let hash_2 = make_tx_hash(0x654); + let mut failed_tx_1 = make_failed_tx(123); + failed_tx_1.hash = hash_1; + let mut failed_tx_2 = make_failed_tx(456); + failed_tx_2.hash = hash_2; - subject.handle_failed_transactions(vec![id_1, id_2], &Logger::new(test_name)); + subject.handle_failed_transactions(vec![failed_tx_1, failed_tx_2], &Logger::new(test_name)); - let mark_failures_params = mark_failures_params_arc.lock().unwrap(); - assert_eq!(*mark_failures_params, vec![vec![2, 3]]); + let delete_records_params = delete_records_params_arc.lock().unwrap(); + assert_eq!(*delete_records_params, vec![hashset![hash_1, hash_2]]); TestLogHandler::new().exists_log_containing(&format!( - "WARN: {test_name}: Broken transactions 0x000000000000000000000000000000000000000000000000000000000000007b, \ - 0x00000000000000000000000000000000000000000000000000000000000001c8 marked as an error. You should take over \ - the care of those to make sure your debts are going to be settled properly. At the moment, there is no automated \ - process fixing that without your assistance", + "WARN: {test_name}: Broken transactions 0x0000000000000000000000000000000000000000000000000000000000000321, \ + 0x0000000000000000000000000000000000000000000000000000000000000654 bluh bluh" )); } @@ -3325,21 +3339,23 @@ mod tests { #[should_panic( expected = "Unsuccessful attempt for transactions 0x00000000000000000000000000000000000\ 0000000000000000000000000014d, 0x000000000000000000000000000000000000000000000000000000\ - 00000001bc to mark fatal error at sent tx record due to UpdateFailed(\"no no no\"); \ - database unreliable" + 00000001bc bluh bluh UpdateFailed(\"no no no\")" )] - fn handle_failed_transactions_panics_when_it_fails_to_mark_failure() { - let sent_payable_dao = SentPayableDaoMock::default().mark_failures_result(Err( - SentPayableDaoError::NoChange, - )); + fn handle_failed_transactions_panics_when_it_fails_to_insert_failed_tx_record() { + let failed_payable_dao = FailedPayableDaoMock::default() + .insert_new_records_result(Err(FailedPayableDaoError::NoChange)); let subject = PendingPayableScannerBuilder::new() - .sent_payable_dao(sent_payable_dao) + .failed_payable_dao(failed_payable_dao) .build(); - let transaction_id_1 = PendingPayableId::new(2, make_tx_hash(333)); - let transaction_id_2 = PendingPayableId::new(3, make_tx_hash(444)); - let transaction_ids = vec![transaction_id_1, transaction_id_2]; - - subject.handle_failed_transactions(transaction_ids, &Logger::new("test")); + let hash_1 = make_tx_hash(0x14d); + let hash_2 = make_tx_hash(0x1bc); + let mut failed_tx_1 = make_failed_tx(789); + failed_tx_1.hash = hash_1; + let mut failed_tx_2 = make_failed_tx(456); + failed_tx_2.hash = hash_2; + let failed_txs = vec![failed_tx_1, failed_tx_2]; + + subject.handle_failed_transactions(failed_txs, &Logger::new("test")); } #[test] @@ -3383,78 +3399,57 @@ mod tests { subject.confirm_transactions(vec![], &Logger::new("test")) - //mocked payable DAO didn't panic which means we skipped the actual process + // Mocked payable DAO didn't panic, which means we skipped the actual process } #[test] fn confirm_transactions_works() { init_test_logging(); + let test_name = "confirm_transactions_works"; let transactions_confirmed_params_arc = Arc::new(Mutex::new(vec![])); - let delete_fingerprints_params_arc = Arc::new(Mutex::new(vec![])); + let delete_records_params_arc = Arc::new(Mutex::new(vec![])); let payable_dao = PayableDaoMock::default() .transactions_confirmed_params(&transactions_confirmed_params_arc) .transactions_confirmed_result(Ok(())); let sent_payable_dao = SentPayableDaoMock::default() - .delete_fingerprints_params(&delete_fingerprints_params_arc) - .delete_fingerprints_result(Ok(())); + .delete_records_params(&delete_records_params_arc) + .delete_records_result(Ok(())); + let logger = Logger::new(test_name); let mut subject = PendingPayableScannerBuilder::new() .payable_dao(payable_dao) .sent_payable_dao(sent_payable_dao) .build(); - let rowid_1 = 2; - let rowid_2 = 5; - let pending_payable_fingerprint_1 = SentTx { - timestamp: from_unix_timestamp(199_000_000), - gas_price_wei: 0, - nonce: 0, - hash: make_tx_hash(0x123), - amount: 4567, - receiver_address: Default::default(), - block_opt: None, - }; - let pending_payable_fingerprint_2 = SentTx { - timestamp: from_unix_timestamp(200_000_000), - gas_price_wei: 0, - nonce: 0, - hash: make_tx_hash(0x567), - amount: 5555, - receiver_address: Default::default(), - block_opt: None, - }; + let tx_hash_1 = make_tx_hash(0x123); + let tx_hash_2 = make_tx_hash(0x567); + let mut sent_tx_1 = make_sent_tx(123_123); + sent_tx_1.hash = tx_hash_1; + let mut sent_tx_2 = make_sent_tx(567_567); + sent_tx_2.hash = tx_hash_2; - subject.confirm_transactions( - vec![ - pending_payable_fingerprint_1.clone(), - pending_payable_fingerprint_2.clone(), - ], - &Logger::new("confirm_transactions_works"), - ); + subject.confirm_transactions(vec![sent_tx_1.clone(), sent_tx_2.clone()], &logger); - let confirm_transactions_params = transactions_confirmed_params_arc.lock().unwrap(); + let transactions_confirmed_params = transactions_confirmed_params_arc.lock().unwrap(); assert_eq!( - *confirm_transactions_params, - vec![vec![ - pending_payable_fingerprint_1, - pending_payable_fingerprint_2 - ]] + *transactions_confirmed_params, + vec![vec![sent_tx_1, sent_tx_2]] ); - let delete_fingerprints_params = delete_fingerprints_params_arc.lock().unwrap(); - assert_eq!(*delete_fingerprints_params, vec![vec![rowid_1, rowid_2]]); + let delete_records_params = delete_records_params_arc.lock().unwrap(); + assert_eq!(*delete_records_params, vec![hashset![tx_hash_1, tx_hash_2]]); let log_handler = TestLogHandler::new(); - log_handler.exists_log_containing( - "DEBUG: confirm_transactions_works: \ + log_handler.exists_log_containing(&format!( + "DEBUG: {test_name}: \ Confirmation of transactions \ 0x0000000000000000000000000000000000000000000000000000000000000123, \ 0x0000000000000000000000000000000000000000000000000000000000000567; \ - record for total paid payable was modified", - ); - log_handler.exists_log_containing( + record for total paid payable was modified to bluhbluh", + )); + log_handler.exists_log_containing(&format!( "INFO: confirm_transactions_works: \ Transactions \ 0x0000000000000000000000000000000000000000000000000000000000000123, \ 0x0000000000000000000000000000000000000000000000000000000000000567 \ completed their confirmation process succeeding", - ); + )); } #[test] @@ -3480,26 +3475,12 @@ mod tests { #[test] fn total_paid_payable_rises_with_each_bill_paid() { - let test_name = "total_paid_payable_rises_with_each_bill_paid"; - let fingerprint_1 = SentTx { - rowid: 5, - timestamp: from_unix_timestamp(189_999_888), - hash: make_tx_hash(56789), - attempt: 1, - amount: 5478, - process_error: None, - }; - let fingerprint_2 = SentTx { - rowid: 6, - timestamp: from_unix_timestamp(200_000_011), - hash: make_tx_hash(33333), - attempt: 1, - amount: 6543, - process_error: None, - }; + let mut sent_tx_1 = make_sent_tx(456); + sent_tx_1.amount_minor = 5478; + let mut sent_tx_2 = make_sent_tx(789); + sent_tx_2.amount_minor = 6543; let payable_dao = PayableDaoMock::default().transactions_confirmed_result(Ok(())); - let sent_payable_dao = - SentPayableDaoMock::default().delete_fingerprints_result(Ok(())); + let sent_payable_dao = SentPayableDaoMock::default().delete_records_result(Ok(())); let mut subject = PendingPayableScannerBuilder::new() .payable_dao(payable_dao) .sent_payable_dao(sent_payable_dao) @@ -3509,8 +3490,8 @@ mod tests { subject.financial_statistics.replace(financial_statistics); subject.confirm_transactions( - vec![fingerprint_1.clone(), fingerprint_2.clone()], - &Logger::new(test_name), + vec![sent_tx_1.clone(), sent_tx_2.clone()], + &Logger::new("test"), ); let total_paid_payable = subject.financial_statistics.borrow().total_paid_payable_wei; @@ -3534,7 +3515,7 @@ mod tests { let mut sent_tx_1 = make_sent_tx(123); sent_tx_1.hash = transaction_hash_1; let transaction_with_status_1 = SentTxWithLatestStatus::new( - sent_tx_1, + sent_tx_1.clone(), TxStatus::Succeeded(TransactionBlock { block_hash: Default::default(), block_number: U64::from(1234), @@ -3543,10 +3524,13 @@ mod tests { let transaction_hash_2 = make_tx_hash(1234); let mut sent_tx_2 = make_sent_tx(789); sent_tx_2.hash = transaction_hash_2; - let transaction_with_status_2 = SentTxWithLatestStatus::new(sent_tx_2, TxStatus::Succeeded(TransactionBlock { + let transaction_with_status_2 = SentTxWithLatestStatus::new( + sent_tx_2.clone(), + TxStatus::Succeeded(TransactionBlock { block_hash: Default::default(), block_number: U64::from(2345), - })); + }), + ); let msg = TxStatusReport { results: vec![ TxReceiptResult::RpcResponse(transaction_with_status_1), diff --git a/node/src/accountant/scanners/scanners_utils.rs b/node/src/accountant/scanners/scanners_utils.rs index 8840ce364..3c157e651 100644 --- a/node/src/accountant/scanners/scanners_utils.rs +++ b/node/src/accountant/scanners/scanners_utils.rs @@ -112,13 +112,17 @@ pub mod payable_scanner_utils { if individual_batch_responses.is_empty() { panic!("Broken code: An empty vector of processed payments claiming to be an Ok value") } - + let separated_txs_by_result = separate_rpc_results(individual_batch_responses, logger); - - let remote_errs_opt = if separated_txs_by_result.err_results.is_empty() {None} else {Some(RemotelyCausedErrors(separated_txs_by_result.err_results))}; + + let remote_errs_opt = if separated_txs_by_result.err_results.is_empty() { + None + } else { + Some(RemotelyCausedErrors(separated_txs_by_result.err_results)) + }; let oks = separated_txs_by_result.ok_results; - + (oks, remote_errs_opt) } Err(e) => { @@ -149,7 +153,7 @@ pub mod payable_scanner_utils { #[derive(Default)] pub struct SeparatedTxsByResult<'a> { pub ok_results: Vec<&'a PendingPayable>, - pub err_results: HashSet + pub err_results: HashSet, } fn separate_rpc_results_fold_guts<'a>( @@ -167,9 +171,13 @@ pub mod payable_scanner_utils { recipient_wallet, hash, }) => { - warning!(logger, "Remote transaction failure: '{}' for payment to {} and \ + warning!( + logger, + "Remote transaction failure: '{}' for payment to {} and \ transaction hash {:?}. Please check your blockchain service URL configuration.", - rpc_error, recipient_wallet, hash + rpc_error, + recipient_wallet, + hash ); acc.err_results.insert(*hash); acc @@ -236,14 +244,8 @@ pub mod payable_scanner_utils { } impl PendingPayableMissingInDb { - pub fn new( - recipient: Address, - hash: H256, - ) -> PendingPayableMissingInDb { - PendingPayableMissingInDb { - recipient, - hash, - } + pub fn new(recipient: Address, hash: H256) -> PendingPayableMissingInDb { + PendingPayableMissingInDb { recipient, hash } } } @@ -620,7 +622,10 @@ mod tests { let (oks, errs) = separate_errors(&sent_payable, &Logger::new("test_logger")); assert_eq!(oks, vec![&payable_ok]); - assert_eq!(errs, Some(RemotelyCausedErrors(hashset![make_tx_hash(0x315)]))); + assert_eq!( + errs, + Some(RemotelyCausedErrors(hashset![make_tx_hash(0x315)])) + ); TestLogHandler::new().exists_log_containing("WARN: test_logger: Remote transaction failure: \ 'Got invalid response: That jackass screwed it up' for payment to 0x000000000000000000000000\ 00000077686f6f61 and transaction hash 0x0000000000000000000000000000000000000000000000000000\ diff --git a/node/src/accountant/scanners/test_utils.rs b/node/src/accountant/scanners/test_utils.rs index 8beedffa7..38ecfc3af 100644 --- a/node/src/accountant/scanners/test_utils.rs +++ b/node/src/accountant/scanners/test_utils.rs @@ -19,8 +19,7 @@ use crate::accountant::scanners::{ Scanner, StartScanError, StartableScanner, }; use crate::accountant::{ - ReceivedPayments, TxStatusReport, RequestTransactionReceipts, ResponseSkeleton, - SentPayables, + ReceivedPayments, RequestTransactionReceipts, ResponseSkeleton, SentPayables, TxStatusReport, }; use crate::blockchain::blockchain_bridge::RetrieveTransactions; use crate::sub_lib::blockchain_bridge::{ConsumingWalletBalances, OutboundPaymentsInstructions}; @@ -367,11 +366,7 @@ pub enum ScannerReplacement { PendingPayable( ReplacementType< PendingPayableScanner, - ScannerMock< - RequestTransactionReceipts, - TxStatusReport, - PendingPayableScanResult, - >, + ScannerMock, >, ), Receivable( diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs index b3147513c..6eb0ed3d7 100644 --- a/node/src/accountant/test_utils.rs +++ b/node/src/accountant/test_utils.rs @@ -3,10 +3,7 @@ #![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::failed_payable_dao::{FailedPayableDao, FailedPayableDaoError, FailedPayableDaoFactory, FailedTx, FailureReason, FailureRetrieveCondition, FailureStatus}; use crate::accountant::db_access_objects::payable_dao::{MarkOfPendingPayable, PayableAccount, PayableDao, PayableDaoError, PayableDaoFactory}; use crate::accountant::db_access_objects::sent_payable_dao::{SentPayableDao, SentPayableDaoFactory}; use crate::accountant::db_access_objects::receivable_dao::{ @@ -22,7 +19,6 @@ use crate::accountant::scanners::payable_scanner_extension::PreparedAdjustment; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableThresholdsGauge; use crate::accountant::scanners::{PayableScanner, PendingPayableScanner, ReceivableScanner}; use crate::accountant::{gwei_to_wei, Accountant, DEFAULT_PENDING_TOO_LONG_SEC}; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::HashAndAmount; use crate::blockchain::blockchain_interface::data_structures::BlockchainTransaction; use crate::blockchain::test_utils::make_tx_hash; use crate::bootstrapper::BootstrapperConfig; @@ -36,8 +32,6 @@ use crate::sub_lib::wallet::Wallet; use crate::test_utils::make_wallet; use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMock; use crate::test_utils::unshared_test_utils::make_bc_with_defaults; -use actix::System; -use ethereum_types::H256; use masq_lib::logger::Logger; use rusqlite::{Connection, OpenFlags, Row}; use std::any::type_name; @@ -48,6 +42,7 @@ use std::path::Path; use std::rc::Rc; use std::sync::{Arc, Mutex}; use std::time::SystemTime; +use web3::types::{Address}; use crate::accountant::db_access_objects::sent_payable_dao::{RetrieveCondition, SentPayableDaoError, SentTx}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock}; @@ -87,21 +82,58 @@ pub fn make_payable_account_with_wallet_and_balance_and_timestamp_opt( } } -pub fn make_sent_tx(num: u64)-> SentTx { +pub fn make_sent_tx(num: u64) -> SentTx { if num == 0 { panic!("num for generating must be greater than 0"); } - SentTx{ - hash: make_tx_hash(num as u32), - receiver_address: make_wallet(&format!("wallet{}", num)).address(), - amount: gwei_to_wei(num * num), - timestamp: to_unix_timestamp(SystemTime::now()) - (num as i64 * 60), - gas_price_wei: gwei_to_wei(num), - nonce: num, + let params = TxRecordCommonParts::new(num); + SentTx { + hash: params.hash, + receiver_address: params.receiver_address, + amount_minor: params.amount_minor, + timestamp: params.timestamp, + gas_price_minor: params.gas_price_minor, + nonce: params.nonce, block_opt: None, } } +pub fn make_failed_tx(num: u64) -> FailedTx { + let params = TxRecordCommonParts::new(num); + FailedTx { + hash: params.hash, + receiver_address: params.receiver_address, + amount_minor: params.amount_minor, + timestamp: params.timestamp, + gas_price_minor: params.gas_price_minor, + nonce: params.nonce, + reason: FailureReason::PendingTooLong, + status: FailureStatus::RetryRequired, + } +} + +struct TxRecordCommonParts { + hash: TxHash, + receiver_address: Address, + amount_minor: u128, + timestamp: i64, + gas_price_minor: u128, + nonce: u64, +} + +impl TxRecordCommonParts { + fn new(num: u64) -> Self { + Self { + hash: make_tx_hash(num as u32), + receiver_address: make_wallet(&format!("wallet{}", num)).address(), + amount_minor: gwei_to_wei(num * num), + timestamp: to_unix_timestamp(SystemTime::now()) - (num as i64 * 60), + gas_price_minor: gwei_to_wei(num), + nonce: num, + } + } +} + pub struct AccountantBuilder { config_opt: Option, consuming_wallet_opt: Option, @@ -109,6 +141,7 @@ pub struct AccountantBuilder { 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, } @@ -122,6 +155,7 @@ impl Default for AccountantBuilder { 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, } @@ -366,6 +400,12 @@ impl AccountantBuilder { .make_result(SentPayableDaoMock::new()) .make_result(SentPayableDaoMock::new()), ); + let failed_payable_dao_factory = self.failed_payable_dao_factory_opt.unwrap_or( + FailedPayableDaoFactoryMock::new() + .make_result(FailedPayableDaoMock::new()) + .make_result(FailedPayableDaoMock::new()) + .make_result(FailedPayableDaoMock::new()), + ); let banned_dao_factory = self .banned_dao_factory_opt .unwrap_or(BannedDaoFactoryMock::new().make_result(BannedDaoMock::new())); @@ -377,6 +417,7 @@ impl AccountantBuilder { DaoFactories { payable_dao_factory: Box::new(payable_dao_factory), sent_payable_dao_factory: Box::new(sent_payable_dao_factory), + failed_payable_dao_factory: Box::new(failed_payable_dao_factory), receivable_dao_factory: Box::new(receivable_dao_factory), banned_dao_factory: Box::new(banned_dao_factory), config_dao_factory: Box::new(config_dao_factory), @@ -550,12 +591,13 @@ impl PayableDao for PayableDaoMock { &self, now: SystemTime, wallet: &Wallet, - amount: u128, + amount_minor: u128, ) -> Result<(), PayableDaoError> { - self.more_money_payable_parameters - .lock() - .unwrap() - .push((now, wallet.clone(), amount)); + self.more_money_payable_parameters.lock().unwrap().push(( + now, + wallet.clone(), + amount_minor, + )); self.more_money_payable_results.borrow_mut().remove(0) } @@ -578,10 +620,7 @@ impl PayableDao for PayableDaoMock { // .remove(0) } - fn transactions_confirmed( - &self, - confirmed_payables: &[SentTx], - ) -> Result<(), PayableDaoError> { + fn transactions_confirmed(&self, confirmed_payables: &[SentTx]) -> Result<(), PayableDaoError> { self.transactions_confirmed_params .lock() .unwrap() @@ -652,10 +691,7 @@ impl PayableDaoMock { self } - pub fn transactions_confirmed_params( - mut self, - params: &Arc>>>, - ) -> Self { + pub fn transactions_confirmed_params(mut self, params: &Arc>>>) -> Self { self.transactions_confirmed_params = params.clone(); self } @@ -703,12 +739,13 @@ impl ReceivableDao for ReceivableDaoMock { &self, now: SystemTime, wallet: &Wallet, - amount: u128, + amount_minor: u128, ) -> Result<(), ReceivableDaoError> { - self.more_money_receivable_parameters - .lock() - .unwrap() - .push((now, wallet.clone(), amount)); + self.more_money_receivable_parameters.lock().unwrap().push(( + now, + wallet.clone(), + amount_minor, + )); self.more_money_receivable_results.borrow_mut().remove(0) } @@ -1076,32 +1113,47 @@ pub struct SentPayableDaoMock { } impl SentPayableDao for SentPayableDaoMock { - 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 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: &[SentTx]) -> Result<(), SentPayableDaoError>{ - self.insert_new_records_params.lock().unwrap().push(txs.to_vec()); + fn insert_new_records(&self, txs: &[SentTx]) -> Result<(), SentPayableDaoError> { + 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{ + 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_tx_blocks( &self, hash_map: &HashMap, - ) -> Result<(), SentPayableDaoError>{ - self.update_tx_block_params.lock().unwrap().push(hash_map.clone()); + ) -> Result<(), SentPayableDaoError> { + self.update_tx_block_params + .lock() + .unwrap() + .push(hash_map.clone()); self.update_tx_block_results.borrow_mut().remove(0) } - fn replace_records(&self, new_txs: &[SentTx]) -> Result<(), SentPayableDaoError>{ - self.replace_records_params.lock().unwrap().push(new_txs.to_vec()); + fn replace_records(&self, new_txs: &[SentTx]) -> Result<(), SentPayableDaoError> { + self.replace_records_params + .lock() + .unwrap() + .push(new_txs.to_vec()); self.replace_records_results.borrow_mut().remove(0) } - fn delete_records(&self, hashes: &HashSet) -> Result<(), SentPayableDaoError>{ - self.delete_records_params.lock().unwrap().push(hashes.clone()); + fn delete_records(&self, hashes: &HashSet) -> Result<(), SentPayableDaoError> { + self.delete_records_params + .lock() + .unwrap() + .push(hashes.clone()); self.delete_records_results.borrow_mut().remove(0) } } @@ -1121,18 +1173,13 @@ impl SentPayableDaoMock { self } - pub fn insert_new_records_params( - mut self, - params: &Arc>>>, - ) -> 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<(), SentPayableDaoError>) -> Self { - self.insert_new_records_results - .borrow_mut() - .push(result); + self.insert_new_records_results.borrow_mut().push(result); self } @@ -1154,13 +1201,8 @@ impl SentPayableDaoMock { self } - pub fn update_tx_blocks_result( - self, - result: Result<(), SentPayableDaoError>, - ) -> Self { - self.update_tx_block_results - .borrow_mut() - .push(result); + pub fn update_tx_blocks_result(self, result: Result<(), SentPayableDaoError>) -> Self { + self.update_tx_block_results.borrow_mut().push(result); self } @@ -1179,13 +1221,8 @@ impl SentPayableDaoMock { self } - pub fn delete_records_result( - self, - result: Result<(), SentPayableDaoError>, - ) -> Self { - self.delete_records_results - .borrow_mut() - .push(result); + pub fn delete_records_result(self, result: Result<(), SentPayableDaoError>) -> Self { + self.delete_records_results.borrow_mut().push(result); self } } @@ -1434,6 +1471,7 @@ impl PayableScannerBuilder { pub struct PendingPayableScannerBuilder { payable_dao: PayableDaoMock, sent_payable_dao: SentPayableDaoMock, + failed_payable_dao: FailedPayableDaoMock, payment_thresholds: PaymentThresholds, when_pending_too_long_sec: u64, financial_statistics: FinancialStatistics, @@ -1444,6 +1482,7 @@ impl PendingPayableScannerBuilder { Self { payable_dao: PayableDaoMock::new(), sent_payable_dao: SentPayableDaoMock::new(), + failed_payable_dao: FailedPayableDaoMock::new(), payment_thresholds: PaymentThresholds::default(), when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, financial_statistics: FinancialStatistics::default(), @@ -1460,6 +1499,11 @@ impl PendingPayableScannerBuilder { self } + pub fn failed_payable_dao(mut self, failed_payable_dao: FailedPayableDaoMock) -> Self { + self.failed_payable_dao = failed_payable_dao; + self + } + pub fn when_pending_too_long_sec(mut self, interval: u64) -> Self { self.when_pending_too_long_sec = interval; self @@ -1469,7 +1513,7 @@ impl PendingPayableScannerBuilder { PendingPayableScanner::new( Box::new(self.payable_dao), Box::new(self.sent_payable_dao), - todo!("Utkarsh?"), + Box::new(self.failed_payable_dao), Rc::new(self.payment_thresholds), self.when_pending_too_long_sec, Rc::new(RefCell::new(self.financial_statistics)), diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index 623cafabd..6534afec9 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -167,7 +167,7 @@ impl Handler for BlockchainBridge { #[derive(Debug, Clone, PartialEq, Eq, Message)] pub struct RegisterNewPendingSentTxMessage { - pub new_sent_txs: Vec + pub new_sent_txs: Vec, } impl RegisterNewPendingSentTxMessage { @@ -396,13 +396,11 @@ impl BlockchainBridge { transaction_receipts_results.iter().fold( (0, 0, 0), |(success, fail, pending), transaction_receipt| match transaction_receipt { - TxReceiptResult::RpcResponse(tx_receipt) => { - match tx_receipt.status { - TxStatus::Failed(_) => (success, fail + 1, pending), - TxStatus::Succeeded(_) => (success + 1, fail, pending), - TxStatus::Pending => (success, fail, pending + 1), - } - } + TxReceiptResult::RpcResponse(tx_receipt) => match tx_receipt.status { + TxStatus::Failed(_) => (success, fail + 1, pending), + TxStatus::Succeeded(_) => (success + 1, fail, pending), + TxStatus::Pending => (success, fail, pending + 1), + }, TxReceiptResult::RequestError(_) => (success, fail, pending + 1), }, ); @@ -897,16 +895,15 @@ mod tests { let register_new_pending_sent_tx_msg = accountant_recording.get_record::(0); let sent_payables_msg = accountant_recording.get_record::(1); - let expected_hash = H256::from_str( - "81d20df32920161727cd20e375e53c2f9df40fd80256a236fb39e444c999fb6c" - ) - .unwrap(); + let expected_hash = + H256::from_str("81d20df32920161727cd20e375e53c2f9df40fd80256a236fb39e444c999fb6c") + .unwrap(); assert_eq!( sent_payables_msg, &SentPayables { payment_procedure_result: Ok(vec![Correct(PendingPayable { - recipient_wallet: account.wallet, - hash:expected_hash + recipient_wallet: account.wallet.clone(), + hash: expected_hash })]), response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, @@ -916,13 +913,23 @@ mod tests { ); let first_actual_sent_tx = ®ister_new_pending_sent_tx_msg.new_sent_txs[0]; let second_actual_sent_tx = ®ister_new_pending_sent_tx_msg.new_sent_txs[0]; - assert_eq!(first_actual_sent_tx.receiver_address, account.wallet.address()); + assert_eq!( + first_actual_sent_tx.receiver_address, + account.wallet.address() + ); assert_eq!(first_actual_sent_tx.hash, expected_hash); - assert_eq!(first_actual_sent_tx.amount, account.balance_wei); - assert_eq!(first_actual_sent_tx.gas_price_wei, 111_222_333); - assert_eq!(first_actual_sent_tx.nonce,0); + assert_eq!(first_actual_sent_tx.amount_minor, account.balance_wei); + assert_eq!(first_actual_sent_tx.gas_price_minor, 111_222_333); + assert_eq!(first_actual_sent_tx.nonce, 0); assert_eq!(first_actual_sent_tx.block_opt, None); - assert!(time_before <= from_unix_timestamp(first_actual_sent_tx.timestamp) && from_unix_timestamp(first_actual_sent_tx.timestamp) <= time_after, "We thought the timestamp was between {:?} and {:?}, but it was {:?}", time_before, time_after, from_unix_timestamp(first_actual_sent_tx.timestamp)); + assert!( + time_before <= from_unix_timestamp(first_actual_sent_tx.timestamp) + && from_unix_timestamp(first_actual_sent_tx.timestamp) <= time_after, + "We thought the timestamp was between {:?} and {:?}, but it was {:?}", + time_before, + time_after, + from_unix_timestamp(first_actual_sent_tx.timestamp) + ); assert_eq!(accountant_recording.len(), 2); } @@ -992,14 +999,27 @@ mod tests { .unwrap_err(), "Transport error: Error(IncompleteMessage)", ); - assert_eq!(actual_pending_payable_fingerprint_seeds_msg.new_sent_txs[0].receiver_address, account_wallet.address()); - assert_eq!(actual_pending_payable_fingerprint_seeds_msg.new_sent_txs[0].hash, H256::from_str( - "81d20df32920161727cd20e375e53c2f9df40fd80256a236fb39e444c999fb6c" - ) - .unwrap()); - assert_eq!(actual_pending_payable_fingerprint_seeds_msg.new_sent_txs[0].amount, account.balance_wei); - let number_of_requested_txs = actual_pending_payable_fingerprint_seeds_msg.new_sent_txs.len(); - assert_eq!(number_of_requested_txs, 1, "We expected only one sent tx, but got {}", number_of_requested_txs); + assert_eq!( + actual_pending_payable_fingerprint_seeds_msg.new_sent_txs[0].receiver_address, + account_wallet.address() + ); + assert_eq!( + actual_pending_payable_fingerprint_seeds_msg.new_sent_txs[0].hash, + H256::from_str("81d20df32920161727cd20e375e53c2f9df40fd80256a236fb39e444c999fb6c") + .unwrap() + ); + assert_eq!( + actual_pending_payable_fingerprint_seeds_msg.new_sent_txs[0].amount_minor, + account.balance_wei + ); + let number_of_requested_txs = actual_pending_payable_fingerprint_seeds_msg + .new_sent_txs + .len(); + assert_eq!( + number_of_requested_txs, 1, + "We expected only one sent tx, but got {}", + number_of_requested_txs + ); assert_eq!( *scan_error_msg, ScanError { @@ -1175,10 +1195,7 @@ mod tests { let peer_actors = peer_actors_builder().accountant(accountant).build(); send_bind_message!(subject_subs, peer_actors); let msg = RequestTransactionReceipts { - sent_tx: vec![ - sent_tx_1.clone(), - sent_tx_2.clone(), - ], + sent_tx: vec![sent_tx_1.clone(), sent_tx_2.clone()], response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, context_id: 4321, @@ -1191,8 +1208,7 @@ mod tests { system.run(); let accountant_recording = accountant_recording_arc.lock().unwrap(); assert_eq!(accountant_recording.len(), 1); - let tx_status_report_message = - accountant_recording.get_record::(0); + let tx_status_report_message = accountant_recording.get_record::(0); let mut expected_receipt = TransactionReceipt::default(); expected_receipt.transaction_hash = hash_1; expected_receipt.status = Some(U64::from(1)); @@ -1200,12 +1216,14 @@ mod tests { tx_status_report_message, &TxStatusReport { results: vec![ - TxReceiptResult::RpcResponse( - SentTxWithLatestStatus::new(sent_tx_1, expected_receipt.into())), - TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( - sent_tx_2, - TxStatus::Pending - )) + TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( + sent_tx_1, + expected_receipt.into() + )), + TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( + sent_tx_2, + TxStatus::Pending + )) ], response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, @@ -1294,10 +1312,7 @@ mod tests { .start(); let (accountant, _, accountant_recording_arc) = make_recorder(); let accountant_addr = accountant - .system_stop_conditions(match_lazily_every_type_id!( - TxStatusReport, - ScanError - )) + .system_stop_conditions(match_lazily_every_type_id!(TxStatusReport, ScanError)) .start(); let report_transaction_receipt_recipient: Recipient = accountant_addr.clone().recipient(); @@ -1376,8 +1391,7 @@ mod tests { .system_stop_conditions(match_lazily_every_type_id!(ScanError)) .start(); let scan_error_recipient: Recipient = accountant_addr.clone().recipient(); - let report_transaction_recipient: Recipient = - accountant_addr.recipient(); + let report_transaction_recipient: Recipient = accountant_addr.recipient(); let hash_1 = make_tx_hash(0x1b2e6); let sent_tx_1 = make_sent_tx(123); let sent_tx_2 = make_sent_tx(456); diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs index ab45dc96c..8ff13ff76 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs @@ -1,9 +1,13 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. +use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, FailureReason}; +use crate::accountant::db_access_objects::sent_payable_dao::SentTx; +use crate::accountant::db_access_objects::utils::TxHash; use crate::blockchain::blockchain_interface::blockchain_interface_web3::CONTRACT_ABI; use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainError; use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainError::QueryFailed; use crate::blockchain::blockchain_interface::lower_level_interface::LowBlockchainInt; +use crate::sub_lib::wallet::Wallet; use ethereum_types::{H256, U256, U64}; use futures::Future; use serde_json::Value; @@ -13,10 +17,6 @@ use web3::contract::{Contract, Options}; use web3::transports::{Batch, Http}; use web3::types::{Address, BlockNumber, Filter, Log, TransactionReceipt}; use web3::{Error, Web3}; -use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, FailureReason}; -use crate::accountant::db_access_objects::sent_payable_dao::SentTx; -use crate::accountant::db_access_objects::utils::TxHash; -use crate::sub_lib::wallet::Wallet; #[derive(Debug, PartialEq, Eq, Clone)] pub enum TxReceiptResult { @@ -25,7 +25,7 @@ pub enum TxReceiptResult { } #[derive(Debug, PartialEq, Eq, Clone)] -pub struct SentTxWithLatestStatus{ +pub struct SentTxWithLatestStatus { pub sent_tx: SentTx, pub status: TxStatus, } @@ -60,13 +60,15 @@ pub enum TxStatus { #[derive(Clone, Debug, PartialEq, Eq)] pub enum TxBlockchainFailure { - Unknown + Unknown, } impl Display for TxStatus { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - TxStatus::Failed(reason) => todo!("make sure there is an assertion for this new syntax"), //write!(f, "Failed({:?})", reason), + TxStatus::Failed(reason) => { + todo!("make sure there is an assertion for this new syntax") + } //write!(f, "Failed({:?})", reason), TxStatus::Succeeded(block) => { write!( f, @@ -109,9 +111,9 @@ impl Display for TxStatus { // } #[derive(Debug, PartialEq, Eq, Clone)] -pub struct TxReceiptRequestError{ +pub struct TxReceiptRequestError { tx_hash: TxHash, - err_msg: String + err_msg: String, } impl TxReceiptRequestError { @@ -259,7 +261,7 @@ mod tests { use web3::types::{BlockNumber, Bytes, FilterBuilder, Log, TransactionReceipt, U256}; use crate::accountant::db_access_objects::sent_payable_dao::SentTx; use crate::accountant::test_utils::make_sent_tx; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{SentTxWithLatestStatus, TransactionBlock, TxStatus}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{SentTxWithLatestStatus, TransactionBlock, TxBlockchainFailure, TxStatus}; #[test] fn get_transaction_fee_balance_works() { @@ -622,13 +624,14 @@ mod tests { #[test] fn transaction_receipt_can_be_converted_to_successful_transaction() { - let sent_tx_with_status = test_deriving_tx_status_from_tx_receipt_and_adding_to_sent_tx( - make_sent_tx(456), + let tx_status = test_deriving_tx_status_from_tx_receipt_and_adding_to_sent_tx( Some(U64::from(1)), + Some(H256::from_low_u64_be(0x1234)), + Some(U64::from(10)), + H256::from_low_u64_be(0x5678), ); - - assert_eq!(sent_tx_with_status.sent_tx.hash, H256::from_low_u64_be(0x5678)); - match sent_tx_with_status.status { + + match tx_status { TxStatus::Succeeded(ref block) => { assert_eq!(block.block_hash, H256::from_low_u64_be(0x1234)); assert_eq!(block.block_number, U64::from(10)); @@ -639,50 +642,53 @@ mod tests { #[test] fn transaction_receipt_can_be_converted_to_failed_transaction() { - let sent_tx_with_status = test_deriving_tx_status_from_tx_receipt_and_adding_to_sent_tx( - make_sent_tx(12345), + let tx_status = test_deriving_tx_status_from_tx_receipt_and_adding_to_sent_tx( Some(U64::from(0)), + None, + None, + H256::from_low_u64_be(0x5678), ); - - assert_eq!(sent_tx_with_status.sent_tx.hash, H256::from_low_u64_be(0x5678)); - assert_eq!(sent_tx_with_status.status, TxStatus::Failed); + + assert_eq!(tx_status, TxStatus::Failed(TxBlockchainFailure::Unknown)); } #[test] fn transaction_receipt_can_be_converted_to_pending_transaction_no_status() { - let sent_tx_with_status = - test_deriving_tx_status_from_tx_receipt_and_adding_to_sent_tx(make_sent_tx(789),None); + let tx_status = + test_deriving_tx_status_from_tx_receipt_and_adding_to_sent_tx(None, None, None, H256::from_low_u64_be(0x5678)); - assert_eq!(sent_tx_with_status.sent_tx.hash, H256::from_low_u64_be(0x5678)); - assert_eq!(sent_tx_with_status.status, TxStatus::Pending); + assert_eq!(tx_status, TxStatus::Pending); } #[test] fn transaction_receipt_can_be_converted_to_pending_transaction_no_block_info() { - let tx_receipt = test_deriving_tx_status_from_tx_receipt_and_adding_to_sent_tx( - make_sent_tx(123), + let tx_status = test_deriving_tx_status_from_tx_receipt_and_adding_to_sent_tx( Some(U64::from(1)), + None, + None, + H256::from_low_u64_be(0x5678), ); - assert_eq!(tx_receipt.sent_tx.hash, H256::from_low_u64_be(0x5678)); - assert_eq!(tx_receipt.status, TxStatus::Pending); + assert_eq!(tx_status, TxStatus::Pending); } #[test] fn transaction_receipt_can_be_converted_to_pending_transaction_no_status_and_block_info() { - let tx_receipt = test_deriving_tx_status_from_tx_receipt_and_adding_to_sent_tx( - - Some(U64::from(1)), - ); + let tx_status = + test_deriving_tx_status_from_tx_receipt_and_adding_to_sent_tx( + Some(U64::from(1)), + Some(H256::from_low_u64_be(0x1234)), + None, + H256::from_low_u64_be(0x5678), + ); - assert_eq!(tx_receipt.transaction_hash, H256::from_low_u64_be(0x5678)); - assert_eq!(tx_receipt.status, TxStatus::Pending); + assert_eq!(tx_status, TxStatus::Pending); } #[test] fn tx_status_display_works() { // Test Failed - assert_eq!(TxStatus::Failed.to_string(), "Failed"); + assert_eq!(TxStatus::Failed(TxBlockchainFailure::Unknown).to_string(), "Failed"); // Test Pending assert_eq!(TxStatus::Pending.to_string(), "Pending"); @@ -699,15 +705,15 @@ mod tests { format!("Succeeded({},0x{:x})", block_number, block_hash) ); } - // + // // #[test] // fn tx_status_from_str_works() { // // Test Pending // assert_eq!(TxStatus::from_str("Pending"), Ok(TxStatus::Pending)); - // + // // // Test Failed // assert_eq!(TxStatus::from_str("Failed"), Ok(TxStatus::Failed)); - // + // // // Test Succeeded with valid input // let block_number = 123456789; // let block_hash = H256::from_low_u64_be(0xabcdef); @@ -719,13 +725,13 @@ mod tests { // block_number: U64::from(block_number), // })) // ); - // + // // // Test Succeeded with invalid format // assert_eq!( // TxStatus::from_str("Succeeded(123)"), // Err("Invalid Succeeded format".to_string()) // ); - // + // // // Test Succeeded with invalid block number // assert_eq!( // TxStatus::from_str( @@ -733,13 +739,13 @@ mod tests { // ), // Err("Invalid block number".to_string()) // ); - // + // // // Test Succeeded with invalid block hash // assert_eq!( // TxStatus::from_str("Succeeded(123,0xinvalidhash)"), // Err("Invalid block hash".to_string()) // ); - // + // // // Test unknown status // assert_eq!( // TxStatus::from_str("InProgress"), @@ -748,13 +754,13 @@ mod tests { // } fn test_deriving_tx_status_from_tx_receipt_and_adding_to_sent_tx( - status_opt: Option, + num_status_opt: Option, block_hash_opt: Option, block_number_opt: Option, transaction_hash: H256, ) -> TxStatus { let receipt = TransactionReceipt { - status: status_opt, + status: num_status_opt, root: None, block_hash: block_hash_opt, block_number: block_number_opt, diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs index 73e67e8bf..8bb0e92b2 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs @@ -182,7 +182,7 @@ impl BlockchainInterface for BlockchainInterfaceWeb3 { Box::new( get_gas_price .map_err(BlockchainAgentBuildError::GasPrice) - .and_then(move |gas_price_wei| { + .and_then(move |gas_price_minor| { get_transaction_fee_balance .map_err(move |e| { BlockchainAgentBuildError::TransactionFeeBalance(wallet_address, e) @@ -195,7 +195,7 @@ impl BlockchainInterface for BlockchainInterfaceWeb3 { .and_then(move |masq_token_balance| { let blockchain_agent_future_result = BlockchainAgentFutureResult { - gas_price_wei, + gas_price_minor, transaction_fee_balance, masq_token_balance, }; @@ -227,19 +227,32 @@ impl BlockchainInterface for BlockchainInterfaceWeb3 { .map(|(response, sent_tx)| match response { Ok(result) => { match serde_json::from_value::(result) { - Ok(receipt) => { - TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new(sent_tx, receipt.into())) - } + Ok(receipt) => TxReceiptResult::RpcResponse( + SentTxWithLatestStatus::new(sent_tx, receipt.into()), + ), Err(e) => { if e.to_string().contains("invalid type: null") { - TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new(sent_tx, TxStatus::Pending)) + TxReceiptResult::RpcResponse( + SentTxWithLatestStatus::new( + sent_tx, + TxStatus::Pending, + ), + ) } else { - TxReceiptResult::RequestError(TxReceiptRequestError::new(sent_tx.hash, e.to_string())) + TxReceiptResult::RequestError( + TxReceiptRequestError::new( + sent_tx.hash, + e.to_string(), + ), + ) } } } } - Err(e) => TxReceiptResult::RequestError(TxReceiptRequestError::new(sent_tx.hash, e.to_string())), + Err(e) => TxReceiptResult::RequestError(TxReceiptRequestError::new( + sent_tx.hash, + e.to_string(), + )), }) .collect::>()) }), @@ -282,7 +295,7 @@ impl BlockchainInterface for BlockchainInterfaceWeb3 { #[derive(Debug, Clone, PartialEq, Eq, Copy)] pub struct HashAndAmount { pub hash: H256, - pub amount: u128, + pub amount_minor: u128, } impl BlockchainInterfaceWeb3 { @@ -1046,17 +1059,19 @@ mod tests { fn process_transaction_receipts_works() { let port = find_free_port(); let sent_tx_1 = make_sent_tx(3300); - let sent_tx_2 = - make_sent_tx(3401); - let sent_tx_3 = - make_sent_tx(3502); - let sent_tx_4 = - make_sent_tx(3603); + let sent_tx_2 = make_sent_tx(3401); + let sent_tx_3 = make_sent_tx(3502); + let sent_tx_4 = make_sent_tx(3603); let sent_tx_5 = make_sent_tx(3704); let sent_tx_6 = make_sent_tx(3805); let sent_tx_vec = vec![ - sent_tx_1, sent_tx_2, sent_tx_3, sent_tx_4.clone(), sent_tx_5.clone(), sent_tx_6.clone(), - ]; + &sent_tx_1, + &sent_tx_2, + &sent_tx_3, + &sent_tx_4, + &sent_tx_5, + &sent_tx_6, + ].into_iter().cloned().collect(); let block_hash = H256::from_str("6d0abccae617442c26104c2bc63d1bc05e1e002e555aec4ab62a46e826b18f18") .unwrap(); @@ -1108,31 +1123,26 @@ mod tests { "RPC error: Error { code: ServerError(429), message: \"The requests per second (RPS) of your requests are higher than your plan allows.\", data: None }".to_string()))); assert_eq!( result[1], - TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( - sent_tx_2, - TxStatus::Pending - )) + TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new(sent_tx_2, TxStatus::Pending)) ); assert_eq!( result[2], TxReceiptResult::RequestError(TxReceiptRequestError::new( sent_tx_3.hash, "invalid type: string \"trash\", expected struct Receipt".to_string() - ) - )); + )) + ); assert_eq!( result[3], - TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( - sent_tx_4, TxStatus::Pending - ) - )); + TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new(sent_tx_4, TxStatus::Pending)) + ); assert_eq!( result[4], TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( sent_tx_5, TxStatus::Failed(TxBlockchainFailure::Unknown) - ) - )); + )) + ); assert_eq!( result[5], TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( @@ -1141,8 +1151,7 @@ mod tests { block_hash, block_number, }), - ) - ) + )) ) } @@ -1151,10 +1160,8 @@ mod tests { let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port).start(); let subject = make_blockchain_interface_web3(port); - let sent_tx_1 = - make_sent_tx(789); - let sent_tx_2 = - make_sent_tx(123); + let sent_tx_1 = make_sent_tx(789); + let sent_tx_2 = make_sent_tx(123); let tx_hash_vec = vec![sent_tx_1, sent_tx_2]; let error = subject diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs index d8e1fc1d2..d3f607a08 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs @@ -1,8 +1,10 @@ // Copyright (c) 2024, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use std::collections::HashSet; use crate::accountant::db_access_objects::payable_dao::PayableAccount; +use crate::accountant::db_access_objects::sent_payable_dao::SentTx; +use crate::accountant::db_access_objects::utils::TxHash; use crate::accountant::scanners::payable_scanner_extension::msgs::PricedQualifiedPayables; +use crate::accountant::PendingPayable; use crate::blockchain::blockchain_agent::agent_web3::BlockchainAgentWeb3; use crate::blockchain::blockchain_agent::BlockchainAgent; use crate::blockchain::blockchain_bridge::RegisterNewPendingSentTxMessage; @@ -22,6 +24,7 @@ use masq_lib::constants::WALLET_ADDRESS_LENGTH; use masq_lib::logger::Logger; use secp256k1secrets::SecretKey; use serde_json::Value; +use std::collections::HashSet; use std::iter::once; use std::time::SystemTime; use thousands::Separable; @@ -29,13 +32,10 @@ use web3::transports::{Batch, Http}; use web3::types::{Bytes, SignedTransaction, TransactionParameters, U256}; use web3::Error as Web3Error; use web3::Web3; -use crate::accountant::db_access_objects::sent_payable_dao::SentTx; -use crate::accountant::db_access_objects::utils::TxHash; -use crate::accountant::PendingPayable; #[derive(Debug)] pub struct BlockchainAgentFutureResult { - pub gas_price_wei: U256, + pub gas_price_minor: U256, pub transaction_fee_balance: U256, pub masq_token_balance: U256, } @@ -57,22 +57,20 @@ pub fn merged_output_data( .zip(sent_tx_hashes.into_iter()) .zip(accounts.iter()); iterator_with_all_data - .map( - |((rpc_result, hash), account)| match rpc_result { - Ok(_rpc_result) => { - // TODO: GH-547: This rpc_result should be validated - ProcessedPayableFallible::Correct(PendingPayable { - recipient_wallet: account.wallet.clone(), - hash, - }) - } - Err(rpc_error) => ProcessedPayableFallible::Failed(RpcPayableFailure { - rpc_error, + .map(|((rpc_result, hash), account)| match rpc_result { + Ok(_rpc_result) => { + // TODO: GH-547: This rpc_result should be validated + ProcessedPayableFallible::Correct(PendingPayable { recipient_wallet: account.wallet.clone(), hash, - }), - }, - ) + }) + } + Err(rpc_error) => ProcessedPayableFallible::Failed(RpcPayableFailure { + rpc_error, + recipient_wallet: account.wallet.clone(), + hash, + }), + }) .collect() } @@ -134,11 +132,11 @@ pub fn transmission_log( introduction.chain(body).collect() } -pub fn sign_transaction_data(amount: u128, recipient_wallet: Wallet) -> [u8; 68] { +pub fn sign_transaction_data(amount_minor: u128, recipient_wallet: Wallet) -> [u8; 68] { let mut data = [0u8; 4 + 32 + 32]; data[0..4].copy_from_slice(&TRANSFER_METHOD_ID); data[16..36].copy_from_slice(&recipient_wallet.address().0[..]); - U256::from(amount).to_big_endian(&mut data[36..68]); + U256::from(amount_minor).to_big_endian(&mut data[36..68]); data } @@ -155,11 +153,11 @@ pub fn sign_transaction( web3_batch: &Web3>, recipient_wallet: Wallet, consuming_wallet: Wallet, - amount: u128, + amount_minor: u128, nonce: U256, gas_price_in_wei: u128, ) -> SignedTransaction { - let data = sign_transaction_data(amount, recipient_wallet); + let data = sign_transaction_data(amount_minor, recipient_wallet); let gas_limit = gas_limit(data, chain); // Warning: If you set gas_price or nonce to None in transaction_parameters, sign_transaction // will start making RPC calls which we don't want (Do it at your own risk). @@ -220,7 +218,7 @@ pub fn sign_and_append_payment( HashAndAmount { hash: signed_tx.transaction_hash, - amount: recipient.balance_wei, + amount_minor: recipient.balance_wei, } } @@ -292,10 +290,14 @@ pub fn send_payables_within_batch( &accounts, ); - let sent_txs_hashes: Vec = prepared_sent_txs_records.iter().map(|sent_tx|sent_tx.hash).collect(); + let sent_txs_hashes: Vec = prepared_sent_txs_records + .iter() + .map(|sent_tx| sent_tx.hash) + .collect(); let planned_sent_txs_hashes = HashSet::from_iter(sent_txs_hashes.clone().into_iter()); - let new_pending_sent_tx_message = RegisterNewPendingSentTxMessage::new(prepared_sent_txs_records); + let new_pending_sent_tx_message = + RegisterNewPendingSentTxMessage::new(prepared_sent_txs_records); new_fingerprints_recipient .try_send(new_pending_sent_tx_message) @@ -311,12 +313,11 @@ pub fn send_payables_within_batch( web3_batch .transport() .submit_batch() - .map_err(move |e| - PayableTransactionError::Sending { - msg: e.to_string(), - hashes: planned_sent_txs_hashes, - } - ).and_then(move |batch_response| { + .map_err(move |e| PayableTransactionError::Sending { + msg: e.to_string(), + hashes: planned_sent_txs_hashes, + }) + .and_then(move |batch_response| { Ok(merged_output_data( batch_response, sent_txs_hashes, @@ -340,7 +341,7 @@ pub fn create_blockchain_agent_web3( masq_token_balance_in_minor_units, ); Box::new(BlockchainAgentWeb3::new( - blockchain_agent_future_result.gas_price_wei.as_u128(), + blockchain_agent_future_result.gas_price_minor.as_u128(), gas_limit_const_part, wallet, cons_wallet_balances, @@ -430,7 +431,7 @@ mod tests { "94881436a9c89f48b01651ff491c69e97089daf71ab8cfb240243d7ecf9b38b2" ) .unwrap(), - amount: account.balance_wei + amount_minor: account.balance_wei } ); assert_eq!( @@ -457,8 +458,8 @@ mod tests { let account_1 = make_payable_account(1); let account_2 = make_payable_account(2); let accounts = make_priced_qualified_payables(vec![ - (account_1, 111_111_111), - (account_2, 222_222_222), + (account_1.clone(), 111_111_111), + (account_2.clone(), 222_222_222), ]); let before = SystemTime::now(); @@ -474,26 +475,48 @@ mod tests { let after = SystemTime::now(); let first_actual_sent_tx = result.remove(0); let second_actual_sent_tx = result.remove(0); - assert_eq!(first_actual_sent_tx.receiver_address, account_1.wallet.address()); - assert_eq!(first_actual_sent_tx.hash, H256::from_str( - "94881436a9c89f48b01651ff491c69e97089daf71ab8cfb240243d7ecf9b38b2" - ) - .unwrap()); - assert_eq!(first_actual_sent_tx.amount, account_1.balance_wei); - assert_eq!(first_actual_sent_tx.gas_price_wei, 111_111_111); - assert_eq!(first_actual_sent_tx.nonce,1); + assert_eq!( + first_actual_sent_tx.receiver_address, + account_1.wallet.address() + ); + assert_eq!( + first_actual_sent_tx.hash, + H256::from_str("94881436a9c89f48b01651ff491c69e97089daf71ab8cfb240243d7ecf9b38b2") + .unwrap() + ); + assert_eq!(first_actual_sent_tx.amount_minor, account_1.balance_wei); + assert_eq!(first_actual_sent_tx.gas_price_minor, 111_111_111); + assert_eq!(first_actual_sent_tx.nonce, 1); assert_eq!(first_actual_sent_tx.block_opt, None); - assert!(before <= from_unix_timestamp(first_actual_sent_tx.timestamp) && from_unix_timestamp(first_actual_sent_tx.timestamp) <= after, "We thought the timestamp was between {:?} and {:?}, but it was {:?}", before, after, from_unix_timestamp(first_actual_sent_tx.timestamp)); - assert_eq!(second_actual_sent_tx.receiver_address,account_2.wallet.address()); - assert_eq!(second_actual_sent_tx.hash, H256::from_str( - "5708afd876bc2573f9db984ec6d0e7f8ef222dd9f115643c9b9056d8bef8bbd9" - ) - .unwrap()); - assert_eq!(second_actual_sent_tx.amount, account_2.balance_wei); - assert_eq!(second_actual_sent_tx.gas_price_wei, 222_222_222); - assert_eq!(second_actual_sent_tx.nonce,2); + assert!( + before <= from_unix_timestamp(first_actual_sent_tx.timestamp) + && from_unix_timestamp(first_actual_sent_tx.timestamp) <= after, + "We thought the timestamp was between {:?} and {:?}, but it was {:?}", + before, + after, + from_unix_timestamp(first_actual_sent_tx.timestamp) + ); + assert_eq!( + second_actual_sent_tx.receiver_address, + account_2.wallet.address() + ); + assert_eq!( + second_actual_sent_tx.hash, + H256::from_str("5708afd876bc2573f9db984ec6d0e7f8ef222dd9f115643c9b9056d8bef8bbd9") + .unwrap() + ); + assert_eq!(second_actual_sent_tx.amount_minor, account_2.balance_wei); + assert_eq!(second_actual_sent_tx.gas_price_minor, 222_222_222); + assert_eq!(second_actual_sent_tx.nonce, 2); assert_eq!(second_actual_sent_tx.block_opt, None); - assert!(before <= from_unix_timestamp(second_actual_sent_tx.timestamp) && from_unix_timestamp(second_actual_sent_tx.timestamp) <= after, "We thought the timestamp was between {:?} and {:?}, but it was {:?}", before, after, from_unix_timestamp(second_actual_sent_tx.timestamp)); + assert!( + before <= from_unix_timestamp(second_actual_sent_tx.timestamp) + && from_unix_timestamp(second_actual_sent_tx.timestamp) <= after, + "We thought the timestamp was between {:?} and {:?}, but it was {:?}", + before, + after, + from_unix_timestamp(second_actual_sent_tx.timestamp) + ); } #[test] @@ -625,10 +648,7 @@ mod tests { pending_payable_opt: None, }, ]; - let tx_hashes = vec![ - make_tx_hash(444), - make_tx_hash(333) - ]; + let tx_hashes = vec![make_tx_hash(444), make_tx_hash(333)]; let responses = vec![ Ok(Value::String(String::from("blah"))), Err(web3::Error::Rpc(Error { @@ -701,8 +721,6 @@ mod tests { accountant_recording_result.get_record::(0); assert_eq!(accountant_recording_result.len(), 1); rnpst_message.new_sent_txs.iter().for_each(|tx| { - - todo!("add more assertions"); assert!(timestamp_before <= from_unix_timestamp(tx.timestamp)); assert!(timestamp_after >= from_unix_timestamp(tx.timestamp)); diff --git a/node/src/blockchain/blockchain_interface/data_structures/errors.rs b/node/src/blockchain/blockchain_interface/data_structures/errors.rs index ebddc6efc..0c4b13f0e 100644 --- a/node/src/blockchain/blockchain_interface/data_structures/errors.rs +++ b/node/src/blockchain/blockchain_interface/data_structures/errors.rs @@ -1,13 +1,13 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use std::collections::HashSet; use crate::accountant::comma_joined_stringifiable; +use crate::accountant::db_access_objects::utils::TxHash; use itertools::{Either, Itertools}; +use std::collections::HashSet; use std::fmt; use std::fmt::{Display, Formatter}; use variant_count::VariantCount; use web3::types::{Address, H256}; -use crate::accountant::db_access_objects::utils::TxHash; const BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED: &str = "Uninitialized blockchain interface. To avoid \ being delinquency-banned, you should restart the Node with a value for blockchain-service-url"; @@ -43,7 +43,10 @@ pub enum PayableTransactionError { TransactionID(BlockchainError), UnusableWallet(String), Signing(String), - Sending { msg: String, hashes: HashSet }, + Sending { + msg: String, + hashes: HashSet, + }, UninitializedBlockchainInterface, } @@ -68,11 +71,12 @@ impl Display for PayableTransactionError { Self::Sending { msg, hashes } => { let hashes = hashes.iter().map(|hash| *hash).collect_vec(); write!( - f, - "Sending phase: \"{}\". Signed and hashed transactions: {}", - msg, - comma_joined_stringifiable(&hashes, |hash| format!("{:?}", hash)) - )}, + f, + "Sending phase: \"{}\". Signed and hashed transactions: {}", + msg, + comma_joined_stringifiable(&hashes, |hash| format!("{:?}", hash)) + ) + } Self::UninitializedBlockchainInterface => { write!(f, "{}", BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED) } diff --git a/node/src/blockchain/blockchain_interface/data_structures/mod.rs b/node/src/blockchain/blockchain_interface/data_structures/mod.rs index 5d3ca1000..433c786e2 100644 --- a/node/src/blockchain/blockchain_interface/data_structures/mod.rs +++ b/node/src/blockchain/blockchain_interface/data_structures/mod.rs @@ -2,14 +2,14 @@ pub mod errors; +use crate::accountant::db_access_objects::utils::TxHash; +use crate::accountant::PendingPayable; use crate::blockchain::blockchain_bridge::BlockMarker; use crate::sub_lib::wallet::Wallet; use std::fmt; use std::fmt::Formatter; use web3::types::H256; use web3::Error; -use crate::accountant::db_access_objects::utils::TxHash; -use crate::accountant::PendingPayable; #[derive(Clone, Debug, Eq, PartialEq)] pub struct BlockchainTransaction { diff --git a/node/src/database/db_migrations/migrations/migration_4_to_5.rs b/node/src/database/db_migrations/migrations/migration_4_to_5.rs index 4b5bbb50a..204ab49a5 100644 --- a/node/src/database/db_migrations/migrations/migration_4_to_5.rs +++ b/node/src/database/db_migrations/migrations/migration_4_to_5.rs @@ -150,7 +150,7 @@ mod tests { conn: &dyn ConnectionWrapper, transaction_hash_opt: Option, wallet: &Wallet, - amount: i64, + amount_minor: i64, timestamp: SystemTime, ) { let hash_str = transaction_hash_opt @@ -159,7 +159,7 @@ mod tests { let mut stm = conn.prepare("insert into payable (wallet_address, balance, last_paid_timestamp, pending_payment_transaction) values (?,?,?,?)").unwrap(); let params: &[&dyn ToSql] = &[ &wallet, - &amount, + &amount_minor, &to_unix_timestamp(timestamp), if !hash_str.is_empty() { &hash_str diff --git a/node/src/sub_lib/accountant.rs b/node/src/sub_lib/accountant.rs index e5e0c5f7c..10bb2e8ff 100644 --- a/node/src/sub_lib/accountant.rs +++ b/node/src/sub_lib/accountant.rs @@ -1,11 +1,12 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::accountant::db_access_objects::banned_dao::BannedDaoFactory; +use crate::accountant::db_access_objects::failed_payable_dao::FailedPayableDaoFactory; use crate::accountant::db_access_objects::payable_dao::PayableDaoFactory; use crate::accountant::db_access_objects::receivable_dao::ReceivableDaoFactory; +use crate::accountant::db_access_objects::sent_payable_dao::SentPayableDaoFactory; use crate::accountant::scanners::payable_scanner_extension::msgs::BlockchainAgentWithContextMessage; use crate::accountant::{ - checked_conversion, Accountant, ReceivedPayments, TxStatusReport, ScanError, - SentPayables, + checked_conversion, Accountant, ReceivedPayments, ScanError, SentPayables, TxStatusReport, }; use crate::actor_system_factory::SubsFactory; use crate::blockchain::blockchain_bridge::RegisterNewPendingSentTxMessage; @@ -21,8 +22,6 @@ use std::fmt::{Debug, Formatter}; use std::str::FromStr; use std::sync::atomic::{AtomicU32, Ordering}; use std::time::{Duration, SystemTime}; -use crate::accountant::db_access_objects::failed_payable_dao::FailedPayableDaoFactory; -use crate::accountant::db_access_objects::sent_payable_dao::SentPayableDaoFactory; lazy_static! { pub static ref DEFAULT_EARNING_WALLET: Wallet = Wallet::from_str("0x27d9A2AC83b493f88ce9B4532EDcf74e95B9788d").expect("Internal error"); diff --git a/node/src/test_utils/recorder.rs b/node/src/test_utils/recorder.rs index 417a2faa3..23b8d04bc 100644 --- a/node/src/test_utils/recorder.rs +++ b/node/src/test_utils/recorder.rs @@ -7,7 +7,7 @@ use crate::accountant::{ ReceivedPayments, RequestTransactionReceipts, ScanError, ScanForNewPayables, ScanForReceivables, SentPayables, }; -use crate::accountant::{TxStatusReport, ScanForPendingPayables, ScanForRetryPayables}; +use crate::accountant::{ScanForPendingPayables, ScanForRetryPayables, TxStatusReport}; use crate::blockchain::blockchain_bridge::RegisterNewPendingSentTxMessage; use crate::blockchain::blockchain_bridge::RetrieveTransactions; use crate::daemon::crash_notification::CrashNotification; From 2b3cc5208623ee78c0cb0d2069678de33eeb085d Mon Sep 17 00:00:00 2001 From: Bert Date: Sat, 12 Jul 2025 22:48:14 +0200 Subject: [PATCH 07/61] GH-642: fn confirm_transactions has been reimplemented --- node/src/accountant/mod.rs | 46 +-- node/src/accountant/scanners/mod.rs | 371 ++++++++++-------- .../src/accountant/scanners/scanners_utils.rs | 165 ++++---- node/src/accountant/test_utils.rs | 10 +- node/src/blockchain/blockchain_bridge.rs | 15 +- .../lower_level_interface_web3.rs | 44 ++- .../blockchain_interface_web3/mod.rs | 39 +- 7 files changed, 361 insertions(+), 329 deletions(-) diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index 825a98c61..10dc63d27 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -1297,7 +1297,7 @@ mod tests { use crate::accountant::db_access_objects::sent_payable_dao::{SentPayableDaoError, SentTx}; use crate::accountant::scanners::scan_schedulers::{NewPayableScanDynIntervalComputer, NewPayableScanDynIntervalComputerReal}; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{OperationOutcome, PayableScanResult}; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{SentTxWithLatestStatus, TransactionBlock, TxBlockchainFailure, TxStatus}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxWithStatus, TransactionBlock, TxBlockchainFailure, TxStatus}; use crate::test_utils::recorder_counter_msgs::SingleTypeCounterMsgSetup; impl Handler> for Accountant { @@ -1939,7 +1939,7 @@ mod tests { let subject_addr = subject.start(); let sent_tx = make_sent_tx(123); let report_tx_receipts = TxStatusReport { - results: vec![TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( + results: vec![TxReceiptResult::RpcResponse(TxWithStatus::new( sent_tx.clone(), TxStatus::Succeeded(TransactionBlock { block_hash: make_tx_hash(456), @@ -2157,7 +2157,7 @@ mod tests { let first_counter_msg_setup = setup_for_counter_msg_triggered_via_type_id!( RequestTransactionReceipts, TxStatusReport { - results: vec![TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( + results: vec![TxReceiptResult::RpcResponse(TxWithStatus::new( sent_tx, TxStatus::Failed(TxBlockchainFailure::Unknown) )),], @@ -2777,7 +2777,7 @@ mod tests { let subject_addr: Addr = subject.start(); let subject_subs = Accountant::make_subs_from(&subject_addr); let expected_tx_status_report = TxStatusReport { - results: vec![TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( + results: vec![TxReceiptResult::RpcResponse(TxWithStatus::new( sent_tx.clone(), TxStatus::Failed(TxBlockchainFailure::Unknown), ))], @@ -3511,7 +3511,7 @@ mod tests { )]), response_skeleton_opt: None, }; - let tx_with_status = SentTxWithLatestStatus { + let tx_with_status = TxWithStatus { sent_tx: sent_tx.clone(), status: TxStatus::Succeeded(TransactionBlock { block_hash: make_tx_hash(369369), @@ -5043,17 +5043,11 @@ mod tests { System::current().stop(); system.run(); - let hashes = two_sent_txs - .iter() - .map(|sent_tx| sent_tx.hash) - .collect(); + let hashes = two_sent_txs.iter().map(|sent_tx| sent_tx.hash).collect(); let transactions_confirmed_params = transactions_confirmed_params_arc.lock().unwrap(); assert_eq!(*transactions_confirmed_params, vec![two_sent_txs.clone()]); let delete_records_params = delete_records_params_arc.lock().unwrap(); - assert_eq!( - *delete_records_params, - vec![hashes] - ); + assert_eq!(*delete_records_params, vec![hashes]); 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); @@ -5078,6 +5072,7 @@ mod tests { #[test] fn accountant_confirms_payable_txs_and_schedules_the_delayed_new_payable_scanner_asap() { + init_test_logging(); let transactions_confirmed_params_arc = Arc::new(Mutex::new(vec![])); let delete_records_params_arc = Arc::new(Mutex::new(vec![])); let compute_interval_params_arc = Arc::new(Mutex::new(vec![])); @@ -5118,11 +5113,11 @@ mod tests { let (msg, two_sent_txs) = make_tx_status_report_msg(vec![ TxStatus::Succeeded(TransactionBlock { block_hash: make_tx_hash(123), - block_number: U64::from(100), + block_number: U64::from(111), }), TxStatus::Succeeded(TransactionBlock { block_hash: make_tx_hash(234), - block_number: U64::from(200), + block_number: U64::from(787), }), ]); @@ -5131,17 +5126,11 @@ mod tests { let system = System::new("new_payable_scanner_asap"); System::current().stop(); system.run(); - let hashes = two_sent_txs - .iter() - .map(|sent_tx| sent_tx.hash) - .collect(); + let hashes = two_sent_txs.iter().map(|sent_tx| sent_tx.hash).collect(); let transactions_confirmed_params = transactions_confirmed_params_arc.lock().unwrap(); assert_eq!(*transactions_confirmed_params, vec![two_sent_txs]); let delete_records_params = delete_records_params_arc.lock().unwrap(); - assert_eq!( - *delete_records_params, - vec![hashes] - ); + assert_eq!(*delete_records_params, vec![hashes]); 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); @@ -5158,7 +5147,8 @@ mod tests { new_payable_notify_later ); let new_payable_notify = new_payable_notify_arc.lock().unwrap(); - assert_eq!(*new_payable_notify, vec![ScanForNewPayables::default()]) + assert_eq!(*new_payable_notify, vec![ScanForNewPayables::default()]); + TestLogHandler::new().exists_log_containing("bluuuu"); } #[test] @@ -5230,12 +5220,10 @@ mod tests { let (tx_receipt_results, sent_tx_vec) = status_txs.into_iter().enumerate().fold( (vec![], vec![]), |(mut tx_receipt_results, mut sent_tx_vec), (idx, status)| { - let sent_tx = make_sent_tx(idx as u64); + let sent_tx = make_sent_tx(1 + idx as u64); let hash = sent_tx.hash; - let tx_receipt_result = TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( - sent_tx.clone(), - status, - )); + let tx_receipt_result = + TxReceiptResult::RpcResponse(TxWithStatus::new(sent_tx.clone(), status)); tx_receipt_results.push(tx_receipt_result); sent_tx_vec.push(sent_tx); (tx_receipt_results, sent_tx_vec) diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index bdd99c367..d2a24752c 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -5,14 +5,14 @@ pub mod scan_schedulers; pub mod scanners_utils; pub mod test_utils; -use crate::accountant::db_access_objects::payable_dao::{MarkOfPendingPayable, PayableAccount, PayableDao}; +use crate::accountant::db_access_objects::payable_dao::{MarkOfPendingPayable, PayableAccount, PayableDao, PayableDaoError}; use crate::accountant::db_access_objects::receivable_dao::ReceivableDao; use crate::accountant::payment_adjuster::{PaymentAdjuster, PaymentAdjusterReal}; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableTransactingErrorEnum::{ LocallyCausedError, RemotelyCausedErrors, }; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{debugging_summary_after_error_separation, err_msg_for_failure_with_expected_but_missing_fingerprints, investigate_debt_extremes, mark_pending_payable_fatal_error, payables_debug_summary, separate_errors, separate_rowids_and_hashes, OperationOutcome, PayableScanResult, PayableThresholdsGauge, PayableThresholdsGaugeReal, PayableTransactingErrorEnum, PendingPayableMissingInDb}; -use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_request_error_fetching_receipts, handle_status_with_failure, handle_successful_tx, PendingPayableScanFinalReport, PendingPayableScanResult}; +use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_request_error_fetching_receipts, handle_status_with_failure, handle_successful_tx, PendingPayableScanSummary, PendingPayableScanResult}; use crate::accountant::scanners::scanners_utils::receivable_scanner_utils::balance_and_age; use crate::accountant::{PendingPayable, PendingPayableId, ScanError, ScanForPendingPayables, ScanForRetryPayables}; use crate::accountant::{ @@ -40,16 +40,17 @@ use std::fmt::Debug; use std::rc::Rc; use std::time::{SystemTime}; use bytes::Buf; +use thousands::Separable; use time::format_description::parse; use time::OffsetDateTime; use variant_count::VariantCount; use web3::types::H256; use crate::accountant::db_access_objects::failed_payable_dao::{FailedPayableDao, FailedTx}; -use crate::accountant::db_access_objects::sent_payable_dao::{RetrieveCondition, SentPayableDao, SentTx}; +use crate::accountant::db_access_objects::sent_payable_dao::{RetrieveCondition, SentPayableDao, SentPayableDaoError, SentTx}; use crate::accountant::db_access_objects::utils::{TxHash, TxIdentifiers}; use crate::accountant::scanners::payable_scanner_extension::{MultistageDualPayableScanner, PreparedAdjustment, SolvencySensitivePaymentInstructor}; use crate::accountant::scanners::payable_scanner_extension::msgs::{BlockchainAgentWithContextMessage, QualifiedPayablesMessage, UnpricedQualifiedPayables}; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxReceiptResult, TxStatus}; +use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, TxReceiptResult, TxStatus}; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; use crate::db_config::persistent_configuration::{PersistentConfiguration, PersistentConfigurationReal}; @@ -921,7 +922,7 @@ pub struct PendingPayableScanner { pub failed_payable_dao: Box, pub when_pending_too_long_sec: u64, pub financial_statistics: Rc>, - pub cached_txs_pending_too_long_from_previous_cycle_opt: Option>, //TODO also treat carefully + pub cached_txs_pending_too_long_from_previous_cycle: Vec, //TODO also treat carefully } impl @@ -977,34 +978,23 @@ impl Scanner for PendingPayableScanner ) -> PendingPayableScanResult { let response_skeleton_opt = message.response_skeleton_opt; - let payment_retry_required = match message.results.is_empty() { - true => { - warning!(logger, "No corresponding transaction receipts were found."); - todo!("This requires the payment retry. GH-631 must be completed first"); - // if self.cached_currently_queried_tx_for_receipts.is_empty() { - // todo!("unreachable") - // } - // let failed_txs = self.cached_currently_queried_tx_for_receipts.drain(..) - // .map(|sent_tx|{todo!()}) - // .collect(); - // self.handle_failed_transactions(failed_txs, logger) - } - false => { - debug!( - logger, - "Processing receipts for {} transactions", - message.results.len() - ); - let scan_report = self.handle_receipts_for_pending_transactions(message, logger); + if message.results.is_empty() { + todo!("test me"); + unreachable!("We should not have received an empty list of results") + } - let payment_retry_required = - self.process_transactions_by_their_state(scan_report, logger); + debug!( + logger, + "Processing receipts for {} transactions", + message.results.len() + ); + let scan_report = self.handle_receipts_for_pending_transactions(message, logger); - self.mark_as_ended(logger); + let payment_retry_required = scan_report.requires_payments_retry(); - payment_retry_required - } - }; + self.process_transactions_by_their_state(scan_report, logger); + + self.mark_as_ended(logger); if payment_retry_required { PendingPayableScanResult::PaymentRetryRequired @@ -1038,7 +1028,7 @@ impl PendingPayableScanner { failed_payable_dao, when_pending_too_long_sec, financial_statistics, - cached_txs_pending_too_long_from_previous_cycle_opt: todo!(), + cached_txs_pending_too_long_from_previous_cycle: Vec::new(), } } @@ -1046,32 +1036,32 @@ impl PendingPayableScanner { &self, msg: TxStatusReport, logger: &Logger, - ) -> PendingPayableScanFinalReport { - let scan_report = PendingPayableScanFinalReport::default(); + ) -> PendingPayableScanSummary { + let scan_report = PendingPayableScanSummary::default(); msg.results .into_iter() .fold( scan_report, |scan_report_so_far, receipt_result| match receipt_result { - TxReceiptResult::RpcResponse(sent_tx_with_status) => match sent_tx_with_status - .status - { - TxStatus::Succeeded(tx_block) => { - todo!("check that the tx block is used in logging somehow"); - let completed_sent_tx = SentTx { - block_opt: Some(tx_block), - ..sent_tx_with_status.sent_tx - }; - handle_successful_tx(scan_report_so_far, completed_sent_tx, logger) + TxReceiptResult::RpcResponse(sent_tx_with_status) => { + match sent_tx_with_status.status { + TxStatus::Succeeded(tx_block) => handle_successful_tx( + scan_report_so_far, + sent_tx_with_status.sent_tx, + tx_block, + logger, + ), + TxStatus::Failed(reason) => handle_status_with_failure( + scan_report_so_far, + sent_tx_with_status.sent_tx, + reason, + logger, + ), + TxStatus::Pending => { + todo!() + } } - TxStatus::Failed(reason) => { - let failed_tx = FailedTx::from((sent_tx_with_status.sent_tx, reason)); - handle_status_with_failure(scan_report_so_far, failed_tx, logger) - } - TxStatus::Pending => { - todo!() - } - }, + } TxReceiptResult::RequestError(e) => { handle_request_error_fetching_receipts(scan_report_so_far, e, logger) } @@ -1081,34 +1071,102 @@ impl PendingPayableScanner { fn process_transactions_by_their_state( &mut self, - scan_report: PendingPayableScanFinalReport, + scan_report: PendingPayableScanSummary, logger: &Logger, - ) -> bool { - let requires_payments_retry = scan_report.requires_payments_retry(); - + ) { self.confirm_transactions(scan_report.confirmed, logger); self.handle_failed_transactions(scan_report.failures, logger); + } - requires_payments_retry + fn confirm_transactions(&mut self, confirmed_txs: Vec, logger: &Logger) { + fn transaction_confirmed_panic(confirmed_txs: &[SentTx], e: PayableDaoError) -> ! { + let wallets = confirmed_txs + .iter() + .map(|tx| tx.receiver_address) + .collect_vec(); + panic!( + "Unable to complete the tx confirmation by the adjustment of the payable \ + accounts {} due to {:?}", + comma_joined_stringifiable(&wallets, |wallet| format!("{:?}", wallet)), + e + ) + } + fn update_tx_blocks_panic( + tx_hashes_and_tx_blocks: &HashMap, + e: SentPayableDaoError, + ) -> ! { + panic!( + "Unable to update sent tx records {} with their tx blocks due to {:?}", + comma_joined_stringifiable( + &tx_hashes_and_tx_blocks.keys().collect_vec(), + |tx_hash| format!("{:?}", tx_hash) + ), + e + ) + } + fn log_success( + logger: &Logger, + tx_hashes_and_tx_blocks: &HashMap, + ) { + logger.info(|| { + let pretty_pairs = tx_hashes_and_tx_blocks + .iter() + .map(|(hash, block)| { + format!("{:?} (block number: {})", hash, block.block_number) + }) + .join(", "); + format!("Txs {} have completed", pretty_pairs) + }); + } + + if !confirmed_txs.is_empty() { + if let Err(e) = self.payable_dao.transactions_confirmed(&confirmed_txs) { + transaction_confirmed_panic(&confirmed_txs, e) + } else { + self.add_to_the_total_of_paid_payable(&confirmed_txs, logger); + + let tx_hashes_and_tx_blocks = confirmed_txs + .iter() + .map(|sent_tx| { + ( + sent_tx.hash, + *sent_tx.block_opt.as_ref().expectv("tx block"), + ) + }) + .collect(); + + if let Err(e) = self + .sent_payable_dao + .update_tx_blocks(&tx_hashes_and_tx_blocks) + { + update_tx_blocks_panic(&tx_hashes_and_tx_blocks, e) + } else { + log_success(logger, &tx_hashes_and_tx_blocks); + } + } + } } - // fn update_remaining_fingerprints(&self, ids: Vec, logger: &Logger) { - // if !ids.is_empty() { - // let rowids = PendingPayableId::rowids(&ids); - // match self.sent_payable_dao.increment_scan_attempts(&rowids) { - // Ok(_) => trace!( - // logger, - // "Updated records for rowids: {} ", - // comma_joined_stringifiable(&rowids, |id| id.to_string()) - // ), - // Err(e) => panic!( - // "Failure on incrementing scan attempts for fingerprints of {} due to {:?}", - // PendingPayableId::serialize_hashes_to_string(&ids), - // e - // ), - // } - // } - // } + fn add_to_the_total_of_paid_payable(&mut self, confirmed_payments: &[SentTx], logger: &Logger) { + let to_be_added: u128 = confirmed_payments + .iter() + .map(|sent_tx| sent_tx.amount_minor) + .sum(); + + let total_paid_payable = &mut self + .financial_statistics + .borrow_mut() + .total_paid_payable_wei; + + *total_paid_payable += to_be_added; + + debug!( + logger, + "The total paid payables increased by {} to {} wei", + to_be_added.separate_with_commas(), + total_paid_payable.separate_with_commas() + ); + } fn handle_failed_transactions(&self, failures: Vec, logger: &Logger) { if !failures.is_empty() { @@ -1139,56 +1197,6 @@ impl PendingPayableScanner { }; } } - - fn confirm_transactions(&mut self, fingerprints: Vec, logger: &Logger) { - // fn serialize_hashes(fingerprints: &[SentTx]) -> String { - // comma_joined_stringifiable(fingerprints, |fgp| format!("{:?}", fgp.hash)) - // } - // - // if !fingerprints.is_empty() { - // if let Err(e) = self.payable_dao.transactions_confirmed(&fingerprints) { - // panic!( - // "Unable to cast confirmed pending payables {} into adjustment in the corresponding payable \ - // records due to {:?}", serialize_hashes(&fingerprints), e - // ) - // } else { - // self.add_to_the_total_of_paid_payable(&fingerprints, serialize_hashes, logger); - // let rowids = fingerprints - // .iter() - // .map(|sent_tx| sent_tx.rowid) - // .collect::>(); - // if let Err(e) = self.sent_payable_dao.delete_fingerprints(&rowids) { - // panic!("Unable to delete payable fingerprints {} of verified transactions due to {:?}", - // serialize_hashes(&fingerprints), e) - // } else { - // info!( - // logger, - // "Transactions {} completed their confirmation process succeeding", - // serialize_hashes(&fingerprints) - // ) - // } - // } - // } - } - - fn add_to_the_total_of_paid_payable( - &mut self, - confirmed_payments: &[SentTx], - serialize_hashes: fn(&[SentTx]) -> String, - logger: &Logger, - ) { - todo!() - // fingerprints.iter().for_each(|sent_tx| { - // self.financial_statistics - // .borrow_mut() - // .total_paid_payable_wei += sent_tx.amount - // }); - // debug!( - // logger, - // "Confirmation of transactions {}; record for total paid payable was modified", - // serialize_hashes(fingerprints) - // ); - } } pub struct ReceivableScanner { @@ -1485,16 +1493,16 @@ mod tests { use crate::accountant::db_access_objects::utils::{from_unix_timestamp, to_unix_timestamp}; use crate::accountant::scanners::payable_scanner_extension::msgs::{QualifiedPayablesBeforeGasPriceSelection, QualifiedPayablesMessage, UnpricedQualifiedPayables}; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{OperationOutcome, PayableScanResult, PendingPayableMissingInDb}; - use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_status_with_failure, PendingPayableScanFinalReport, PendingPayableScanResult}; + use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_status_with_failure, PendingPayableScanSummary, PendingPayableScanResult}; use crate::accountant::scanners::{Scanner, StartScanError, StartableScanner, PayableScanner, PendingPayableScanner, ReceivableScanner, ScannerCommon, Scanners, MTError}; - use crate::accountant::test_utils::{make_custom_payment_thresholds, make_payable_account, make_qualified_and_unqualified_payables, make_receivable_account, BannedDaoFactoryMock, BannedDaoMock, ConfigDaoFactoryMock, PayableDaoFactoryMock, PayableDaoMock, PayableScannerBuilder, PayableThresholdsGaugeMock, SentPayableDaoFactoryMock, SentPayableDaoMock, PendingPayableScannerBuilder, ReceivableDaoFactoryMock, ReceivableDaoMock, ReceivableScannerBuilder, make_sent_tx, FailedPayableDaoMock, FailedPayableDaoFactoryMock, make_failed_tx}; + use crate::accountant::test_utils::{make_custom_payment_thresholds, make_payable_account, make_qualified_and_unqualified_payables, make_receivable_account, BannedDaoFactoryMock, BannedDaoMock, ConfigDaoFactoryMock, PayableDaoFactoryMock, PayableDaoMock, PayableScannerBuilder, PayableThresholdsGaugeMock, SentPayableDaoFactoryMock, SentPayableDaoMock, PendingPayableScannerBuilder, ReceivableDaoFactoryMock, ReceivableDaoMock, ReceivableScannerBuilder, make_sent_tx, FailedPayableDaoMock, FailedPayableDaoFactoryMock, make_failed_tx, make_transaction_block}; use crate::accountant::{gwei_to_wei, PendingPayableId, ReceivedPayments, TxStatusReport, RequestTransactionReceipts, ScanError, ScanForRetryPayables, SentPayables, DEFAULT_PENDING_TOO_LONG_SEC, PendingPayable}; use crate::blockchain::blockchain_bridge::{BlockMarker, RetrieveTransactions}; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; use crate::blockchain::blockchain_interface::data_structures::{ BlockchainTransaction, ProcessedPayableFallible, RpcPayableFailure, }; - use crate::blockchain::test_utils::make_tx_hash; + use crate::blockchain::test_utils::{make_block_hash, make_tx_hash}; use crate::database::rusqlite_wrappers::TransactionSafeWrapper; use crate::database::test_utils::transaction_wrapper_mock::TransactionInnerWrapperMockBuilder; use crate::db_config::mocks::ConfigDaoMock; @@ -1528,7 +1536,7 @@ mod tests { use crate::accountant::db_access_objects::failed_payable_dao::{FailedPayableDaoError, FailedTx, FailureReason}; use crate::accountant::db_access_objects::sent_payable_dao::{SentPayableDaoError, SentTx}; use crate::accountant::scanners::test_utils::{assert_timestamps_from_str, parse_system_time_from_str, MarkScanner, NullScanner, ReplacementType, ScannerReplacement}; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{SentTxWithLatestStatus, TransactionBlock, TxReceiptResult, TxStatus}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxWithStatus, TransactionBlock, TxReceiptResult, TxStatus, TxBlockchainFailure}; impl Scanners { pub fn replace_scanner(&mut self, replacement: ScannerReplacement) { @@ -1685,6 +1693,15 @@ mod tests { pending_payable_scanner.common.initiated_at_opt.is_some(), false ); + assert_eq!( + pending_payable_scanner.cached_txs_pending_too_long_from_previous_cycle, + vec![] + ); + assert_eq!( + receivable_scanner.common.payment_thresholds.as_ref(), + &payment_thresholds + ); + assert_eq!(receivable_scanner.common.initiated_at_opt.is_some(), false); assert_eq!( *receivable_scanner.financial_statistics.borrow(), financial_statistics @@ -3097,7 +3114,7 @@ mod tests { // pending_payable_age_sec: u64, // rowid: u64, // hash: H256, - // ) -> PendingPayableScanFinalReport { + // ) -> PendingPayableScanSummary { // init_test_logging(); // let when_sent = SystemTime::now().sub(Duration::from_secs(pending_payable_age_sec)); // let sent_tx = SentTx { @@ -3109,7 +3126,7 @@ mod tests { // process_error: None, // }; // let logger = Logger::new(test_name); - // let scan_report = PendingPayableScanFinalReport::default(); + // let scan_report = PendingPayableScanSummary::default(); // // handle_none_status(scan_report, sent_tx, when_pending_too_long_sec, &logger) // } @@ -3164,7 +3181,7 @@ mod tests { // let elapsed_after = elapsed_since_secs_back(DEFAULT_PENDING_TOO_LONG_SEC + 1); // assert_eq!( // result, - // PendingPayableScanFinalReport { + // PendingPayableScanSummary { // still_pending: vec![], // failures: vec![PendingPayableId::new(rowid, hash)], // confirmed: vec![] @@ -3198,7 +3215,7 @@ mod tests { // let elapsed_after_ms = elapsed_since_secs_back(pending_payable_age) * 1000; // assert_eq!( // result, - // PendingPayableScanFinalReport { + // PendingPayableScanSummary { // still_pending: vec![PendingPayableId::new(rowid, hash)], // failures: vec![], // confirmed: vec![] @@ -3229,7 +3246,7 @@ mod tests { // let elapsed_after_ms = elapsed_since_secs_back(pending_payable_age) * 1000; // assert_eq!( // result, - // PendingPayableScanFinalReport { + // PendingPayableScanSummary { // still_pending: vec![PendingPayableId::new(rowid, hash)], // failures: vec![], // confirmed: vec![] @@ -3248,16 +3265,18 @@ mod tests { let now = SystemTime::now(); let test_name = "interpret_transaction_receipt_when_transaction_status_is_a_failure"; let hash = make_tx_hash(0xabc); - let mut failed_tx = make_failed_tx(2244); - failed_tx.reason = FailureReason::General; + let sent_tx = make_sent_tx(2244); + let failure_reason = TxBlockchainFailure::Unknown; let logger = Logger::new(test_name); - let scan_report = PendingPayableScanFinalReport::default(); + let scan_report = PendingPayableScanSummary::default(); - let result = handle_status_with_failure(scan_report, failed_tx.clone(), &logger); + let result = + handle_status_with_failure(scan_report, sent_tx.clone(), failure_reason, &logger); + let failed_tx = FailedTx::from((sent_tx, failure_reason)); assert_eq!( result, - PendingPayableScanFinalReport { + PendingPayableScanSummary { failures: vec![failed_tx], confirmed: vec![] } @@ -3278,7 +3297,7 @@ mod tests { let mut sent_tx = make_sent_tx(456); sent_tx.hash = hash; let msg = TxStatusReport { - results: vec![TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( + results: vec![TxReceiptResult::RpcResponse(TxWithStatus::new( sent_tx.clone(), TxStatus::Pending, ))], @@ -3290,7 +3309,7 @@ mod tests { let expected_failed_tx = FailedTx::from(sent_tx); assert_eq!( result, - PendingPayableScanFinalReport { + PendingPayableScanSummary { failures: vec![expected_failed_tx], confirmed: vec![] } @@ -3369,16 +3388,16 @@ mod tests { #[test] #[should_panic( - expected = "Unable to delete payable fingerprints 0x000000000000000000000000000000000\ + expected = "Unable to update sent tx records 0x000000000000000000000000000000000\ 0000000000000000000000000000315, 0x00000000000000000000000000000000000000000000000000\ - 0000000000021a of verified transactions due to RecordDeletion(\"the database \ - is fooling around with us\")" + 0000000000021a with their tx blocks due to SqlExecutionFailed(\"The database manager is \ + a funny guy, he's fooling around with us\")" )] - fn confirm_transactions_panics_while_deleting_sent_tx_records() { + fn confirm_transactions_panics_while_updating_sent_payable_records_with_the_tx_blocks() { let payable_dao = PayableDaoMock::new().transactions_confirmed_result(Ok(())); - let sent_payable_dao = SentPayableDaoMock::default().delete_records_result(Err( + let sent_payable_dao = SentPayableDaoMock::default().update_tx_blocks_result(Err( SentPayableDaoError::SqlExecutionFailed( - "the database is fooling around with us".to_string(), + "The database manager is a funny guy, he's fooling around with us".to_string(), ), )); let mut subject = PendingPayableScannerBuilder::new() @@ -3387,8 +3406,10 @@ mod tests { .build(); let mut sent_tx_1 = make_sent_tx(456); sent_tx_1.hash = make_tx_hash(0x315); + sent_tx_1.block_opt = Some(make_transaction_block(678)); let mut sent_tx_2 = make_sent_tx(789); sent_tx_2.hash = make_tx_hash(0x21a); + sent_tx_2.block_opt = Some(make_transaction_block(890)); subject.confirm_transactions(vec![sent_tx_1, sent_tx_2], &Logger::new("test")); } @@ -3407,13 +3428,13 @@ mod tests { init_test_logging(); let test_name = "confirm_transactions_works"; let transactions_confirmed_params_arc = Arc::new(Mutex::new(vec![])); - let delete_records_params_arc = Arc::new(Mutex::new(vec![])); + let update_tx_blocks_params_arc = Arc::new(Mutex::new(vec![])); let payable_dao = PayableDaoMock::default() .transactions_confirmed_params(&transactions_confirmed_params_arc) .transactions_confirmed_result(Ok(())); let sent_payable_dao = SentPayableDaoMock::default() - .delete_records_params(&delete_records_params_arc) - .delete_records_result(Ok(())); + .update_tx_blocks_params(&update_tx_blocks_params_arc) + .update_tx_blocks_result(Ok(())); let logger = Logger::new(test_name); let mut subject = PendingPayableScannerBuilder::new() .payable_dao(payable_dao) @@ -3423,8 +3444,18 @@ mod tests { let tx_hash_2 = make_tx_hash(0x567); let mut sent_tx_1 = make_sent_tx(123_123); sent_tx_1.hash = tx_hash_1; + let tx_block_1 = TransactionBlock { + block_hash: make_block_hash(45), + block_number: 4_578_989_878_u64.into(), + }; + sent_tx_1.block_opt = Some(tx_block_1); let mut sent_tx_2 = make_sent_tx(567_567); sent_tx_2.hash = tx_hash_2; + let tx_block_2 = TransactionBlock { + block_hash: make_block_hash(78), + block_number: 7_898_989_878_u64.into(), + }; + sent_tx_2.block_opt = Some(tx_block_2); subject.confirm_transactions(vec![sent_tx_1.clone(), sent_tx_2.clone()], &logger); @@ -3433,30 +3464,24 @@ mod tests { *transactions_confirmed_params, vec![vec![sent_tx_1, sent_tx_2]] ); - let delete_records_params = delete_records_params_arc.lock().unwrap(); - assert_eq!(*delete_records_params, vec![hashset![tx_hash_1, tx_hash_2]]); + let update_tx_blocks_params = update_tx_blocks_params_arc.lock().unwrap(); + assert_eq!( + *update_tx_blocks_params, + vec![hashmap![tx_hash_1 => tx_block_1, tx_hash_2 => tx_block_2]] + ); let log_handler = TestLogHandler::new(); log_handler.exists_log_containing(&format!( - "DEBUG: {test_name}: \ - Confirmation of transactions \ - 0x0000000000000000000000000000000000000000000000000000000000000123, \ - 0x0000000000000000000000000000000000000000000000000000000000000567; \ - record for total paid payable was modified to bluhbluh", - )); - log_handler.exists_log_containing(&format!( - "INFO: confirm_transactions_works: \ - Transactions \ - 0x0000000000000000000000000000000000000000000000000000000000000123, \ - 0x0000000000000000000000000000000000000000000000000000000000000567 \ - completed their confirmation process succeeding", + "INFO: {test_name}: Txs 0x0000000000000000000000000000000000000000000000000000000000000123 \ + (block number: 4578989878), 0x0000000000000000000000000000000000000000000000000000000000000567 \ + (block number: 7898989878) have completed", )); } #[test] #[should_panic( - expected = "Unable to cast confirmed pending payables 0x0000000000000000000000000000000000000000000\ - 000000000000000000315 into adjustment in the corresponding payable records due to RusqliteError\ - (\"record change not successful\")" + expected = "Unable to complete the tx confirmation by the adjustment of the payable accounts \ + 0x000000000000000000000077616c6c6574343536 due to \ + RusqliteError(\"record change not successful\")" )] fn confirm_transactions_panics_on_unchecking_payable_table() { let hash = make_tx_hash(0x315); @@ -3475,12 +3500,16 @@ mod tests { #[test] fn total_paid_payable_rises_with_each_bill_paid() { + init_test_logging(); + let test_name = "total_paid_payable_rises_with_each_bill_paid"; let mut sent_tx_1 = make_sent_tx(456); sent_tx_1.amount_minor = 5478; + sent_tx_1.block_opt = Some(make_transaction_block(222)); let mut sent_tx_2 = make_sent_tx(789); sent_tx_2.amount_minor = 6543; + sent_tx_2.block_opt = Some(make_transaction_block(333)); let payable_dao = PayableDaoMock::default().transactions_confirmed_result(Ok(())); - let sent_payable_dao = SentPayableDaoMock::default().delete_records_result(Ok(())); + let sent_payable_dao = SentPayableDaoMock::default().update_tx_blocks_result(Ok(())); let mut subject = PendingPayableScannerBuilder::new() .payable_dao(payable_dao) .sent_payable_dao(sent_payable_dao) @@ -3491,11 +3520,15 @@ mod tests { subject.confirm_transactions( vec![sent_tx_1.clone(), sent_tx_2.clone()], - &Logger::new("test"), + &Logger::new(test_name), ); let total_paid_payable = subject.financial_statistics.borrow().total_paid_payable_wei; assert_eq!(total_paid_payable, 1111 + 5478 + 6543); + TestLogHandler::new().exists_log_containing(&format!( + "DEBUG: {test_name}: The total \ + paid payables increased by 12,021 to 13,132 wei" + )); } #[test] @@ -3514,7 +3547,7 @@ mod tests { let transaction_hash_1 = make_tx_hash(4545); let mut sent_tx_1 = make_sent_tx(123); sent_tx_1.hash = transaction_hash_1; - let transaction_with_status_1 = SentTxWithLatestStatus::new( + let transaction_with_status_1 = TxWithStatus::new( sent_tx_1.clone(), TxStatus::Succeeded(TransactionBlock { block_hash: Default::default(), @@ -3524,7 +3557,7 @@ mod tests { let transaction_hash_2 = make_tx_hash(1234); let mut sent_tx_2 = make_sent_tx(789); sent_tx_2.hash = transaction_hash_2; - let transaction_with_status_2 = SentTxWithLatestStatus::new( + let transaction_with_status_2 = TxWithStatus::new( sent_tx_2.clone(), TxStatus::Succeeded(TransactionBlock { block_hash: Default::default(), diff --git a/node/src/accountant/scanners/scanners_utils.rs b/node/src/accountant/scanners/scanners_utils.rs index 3c157e651..8ccf89b3f 100644 --- a/node/src/accountant/scanners/scanners_utils.rs +++ b/node/src/accountant/scanners/scanners_utils.rs @@ -320,19 +320,25 @@ pub mod pending_payable_scanner_utils { use masq_lib::logger::Logger; use masq_lib::ui_gateway::NodeToUiMessage; use std::time::SystemTime; + use masq_lib::utils::ExpectValue; use crate::accountant::db_access_objects::failed_payable_dao::FailedTx; use crate::accountant::db_access_objects::sent_payable_dao::SentTx; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxBlockchainFailure, TxReceiptRequestError, TxStatus}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, TxBlockchainFailure, TxReceiptRequestError, TxStatus}; #[derive(Debug, Default, PartialEq, Eq, Clone)] - pub struct PendingPayableScanFinalReport { + pub struct PendingPayableScanSummary { pub failures: Vec, pub confirmed: Vec, } - impl PendingPayableScanFinalReport { + impl PendingPayableScanSummary { pub fn requires_payments_retry(&self) -> bool { - todo!("complete my within GH-642") + match (self.failures.is_empty(), self.confirmed.is_empty()) { + (true, true) => unreachable!("reading tx receipts gave no results"), + (false, true) => true, + (false, false) => true, + (true, false) => false, + } } } @@ -350,11 +356,11 @@ pub mod pending_payable_scanner_utils { } // // pub fn handle_none_status( - // mut scan_report: PendingPayableScanFinalReport, + // mut scan_report: PendingPayableScanSummary, // sent_tx: SentTx, // max_pending_interval: u64, // logger: &Logger, - // ) -> PendingPayableScanFinalReport { + // ) -> PendingPayableScanSummary { // info!( // logger, // "Pending transaction {:?} couldn't be confirmed at attempt \ @@ -388,44 +394,50 @@ pub mod pending_payable_scanner_utils { // } pub fn handle_successful_tx( - mut scan_report: PendingPayableScanFinalReport, - confirmed_tx: SentTx, + mut scan_report: PendingPayableScanSummary, + sent_tx: SentTx, + tx_block: TransactionBlock, logger: &Logger, - ) -> PendingPayableScanFinalReport { - todo!() - // info!( - // logger, - // "Transaction {:?} has been added to the blockchain; detected locally at attempt \ - // {} at {}ms after its sending", - // sent_tx.hash, - // sent_tx.attempt, - // elapsed_in_ms(sent_tx.timestamp) - // ); - // scan_report.confirmed.push(sent_tx); - // scan_report + ) -> PendingPayableScanSummary { + info!( + logger, + "Acknowledging that tx {:?} was added to block {}.", + sent_tx.hash, + tx_block.block_number, + ); + + let completed_sent_tx = SentTx { + block_opt: Some(tx_block), + ..sent_tx + }; + scan_report.confirmed.push(completed_sent_tx); + scan_report } //TODO: failures handling is going to need enhancement suggested by GH-693 pub fn handle_status_with_failure( - mut scan_report: PendingPayableScanFinalReport, - tx_failure: FailedTx, + mut scan_report: PendingPayableScanSummary, + sent_tx: SentTx, + failure_reason: TxBlockchainFailure, logger: &Logger, - ) -> PendingPayableScanFinalReport { + ) -> PendingPayableScanSummary { + let failed_tx = FailedTx::from((sent_tx, failure_reason)); + + todo!("add to log assertions"); error!( logger, - "Pending transaction {:?} announced as a failure after {}ms from its sending", - tx_failure.hash, - todo!() //tx_failure.timestamp + "Failure of tx {:?} detected: {:?}.", failed_tx.hash, failure_reason ); - scan_report.failures.push(tx_failure); + + scan_report.failures.push(failed_tx); scan_report } pub fn handle_request_error_fetching_receipts( - mut scan_report: PendingPayableScanFinalReport, + mut scan_report: PendingPayableScanSummary, local_error: TxReceiptRequestError, logger: &Logger, - ) -> PendingPayableScanFinalReport { + ) -> PendingPayableScanSummary { todo!() // debug!( // logger, @@ -493,6 +505,8 @@ mod tests { use masq_lib::logger::Logger; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; use std::time::SystemTime; + use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::PendingPayableScanSummary; + use crate::accountant::test_utils::{make_failed_tx, make_sent_tx}; use crate::blockchain::blockchain_interface::data_structures::errors::{BlockchainError, PayableTransactionError}; use crate::blockchain::blockchain_interface::data_structures::{ProcessedPayableFallible, RpcPayableFailure}; @@ -881,61 +895,50 @@ mod tests { #[test] fn requires_payments_retry_says_yes() { - todo!("complete this test with GH-604") - // let cases = vec![ - // PendingPayableScanFinalReport { - // still_pending: vec![PendingPayableId::new(12, make_tx_hash(456))], - // failures: vec![], - // confirmed: vec![], - // }, - // PendingPayableScanFinalReport { - // still_pending: vec![], - // failures: vec![PendingPayableId::new(456, make_tx_hash(1234))], - // confirmed: vec![], - // }, - // PendingPayableScanFinalReport { - // still_pending: vec![PendingPayableId::new(12, make_tx_hash(456))], - // failures: vec![PendingPayableId::new(456, make_tx_hash(1234))], - // confirmed: vec![], - // }, - // PendingPayableScanFinalReport { - // still_pending: vec![PendingPayableId::new(12, make_tx_hash(456))], - // failures: vec![PendingPayableId::new(456, make_tx_hash(1234))], - // confirmed: vec![make_pending_payable_fingerprint()], - // }, - // PendingPayableScanFinalReport { - // still_pending: vec![PendingPayableId::new(12, make_tx_hash(456))], - // failures: vec![], - // confirmed: vec![make_pending_payable_fingerprint()], - // }, - // PendingPayableScanFinalReport { - // still_pending: vec![], - // failures: vec![PendingPayableId::new(456, make_tx_hash(1234))], - // confirmed: vec![make_pending_payable_fingerprint()], - // }, - // ]; - // - // cases.into_iter().enumerate().for_each(|(idx, case)| { - // let result = case.requires_payments_retry(); - // assert_eq!( - // result, true, - // "We expected true, but got false for case of idx {}", - // idx - // ) - // }) + let cases = vec![ + PendingPayableScanSummary { + failures: vec![make_failed_tx(456)], + confirmed: vec![], + }, + PendingPayableScanSummary { + failures: vec![make_failed_tx(123), make_failed_tx(789)], + confirmed: vec![make_sent_tx(777)], + }, + ]; + + cases.into_iter().enumerate().for_each(|(idx, case)| { + let result = case.requires_payments_retry(); + assert_eq!( + result, true, + "We expected true, but got false for case with idx {}", + idx + ) + }) } #[test] fn requires_payments_retry_says_no() { - todo!("complete this test with GH-604") - // let report = PendingPayableScanFinalReport { - // still_pending: vec![], - // failures: vec![], - // confirmed: vec![make_pending_payable_fingerprint()], - // }; - // - // let result = report.requires_payments_retry(); - // - // assert_eq!(result, false) + let report = PendingPayableScanSummary { + failures: vec![], + confirmed: vec![make_sent_tx(123)], + }; + + let result = report.requires_payments_retry(); + + assert_eq!(result, false) + } + + #[test] + #[should_panic( + expected = "internal error: entered unreachable code: reading tx receipts gave \ + no results" + )] + fn requires_payments_retry_meets_invalid_summary() { + let report = PendingPayableScanSummary { + failures: vec![], + confirmed: vec![], + }; + + let _ = report.requires_payments_retry(); } } diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs index 6eb0ed3d7..d9b8af9ac 100644 --- a/node/src/accountant/test_utils.rs +++ b/node/src/accountant/test_utils.rs @@ -20,7 +20,7 @@ use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableT use crate::accountant::scanners::{PayableScanner, PendingPayableScanner, ReceivableScanner}; use crate::accountant::{gwei_to_wei, Accountant, DEFAULT_PENDING_TOO_LONG_SEC}; use crate::blockchain::blockchain_interface::data_structures::BlockchainTransaction; -use crate::blockchain::test_utils::make_tx_hash; +use crate::blockchain::test_utils::{make_block_hash, make_tx_hash}; use crate::bootstrapper::BootstrapperConfig; use crate::database::rusqlite_wrappers::TransactionSafeWrapper; use crate::db_config::config_dao::{ConfigDao, ConfigDaoFactory}; @@ -42,6 +42,7 @@ use std::path::Path; use std::rc::Rc; use std::sync::{Arc, Mutex}; use std::time::SystemTime; +use ethereum_types::U64; use web3::types::{Address}; use crate::accountant::db_access_objects::sent_payable_dao::{RetrieveCondition, SentPayableDaoError, SentTx}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock}; @@ -112,6 +113,13 @@ pub fn make_failed_tx(num: u64) -> FailedTx { } } +pub fn make_transaction_block(num: u64) -> TransactionBlock { + TransactionBlock { + block_hash: make_block_hash(num as u32), + block_number: U64::from(num * num * num), + } +} + struct TxRecordCommonParts { hash: TxHash, receiver_address: Address, diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index 6534afec9..fa853aa2c 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -593,7 +593,7 @@ mod tests { use masq_lib::constants::DEFAULT_MAX_BLOCK_COUNT; use crate::accountant::db_access_objects::sent_payable_dao::SentTx; use crate::accountant::PendingPayable; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{SentTxWithLatestStatus, TransactionBlock, TxReceiptRequestError}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxWithStatus, TransactionBlock, TxReceiptRequestError}; use crate::accountant::scanners::payable_scanner_extension::msgs::{UnpricedQualifiedPayables, QualifiedPayableWithGasPrice}; impl Handler> for BlockchainBridge { @@ -1216,14 +1216,11 @@ mod tests { tx_status_report_message, &TxStatusReport { results: vec![ - TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( + TxReceiptResult::RpcResponse(TxWithStatus::new( sent_tx_1, expected_receipt.into() )), - TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( - sent_tx_2, - TxStatus::Pending - )) + TxReceiptResult::RpcResponse(TxWithStatus::new(sent_tx_2, TxStatus::Pending)) ], response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, @@ -1361,12 +1358,12 @@ mod tests { *report_receipts_msg, TxStatusReport { results: vec![ - TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new(sent_tx_1, TxStatus::Pending)), - TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new(sent_tx_2, TxStatus::Succeeded(TransactionBlock { + TxReceiptResult::RpcResponse(TxWithStatus::new(sent_tx_1, TxStatus::Pending)), + TxReceiptResult::RpcResponse(TxWithStatus::new(sent_tx_2, TxStatus::Succeeded(TransactionBlock { block_hash: Default::default(), block_number, }))), - TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new(sent_tx_3, TxStatus::Pending)), + TxReceiptResult::RpcResponse(TxWithStatus::new(sent_tx_3, TxStatus::Pending)), TxReceiptResult::RequestError( TxReceiptRequestError::new( sent_tx_4.hash, diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs index 8ff13ff76..d6d9d894a 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs @@ -20,19 +20,19 @@ use web3::{Error, Web3}; #[derive(Debug, PartialEq, Eq, Clone)] pub enum TxReceiptResult { - RpcResponse(SentTxWithLatestStatus), + RpcResponse(TxWithStatus), RequestError(TxReceiptRequestError), } #[derive(Debug, PartialEq, Eq, Clone)] -pub struct SentTxWithLatestStatus { +pub struct TxWithStatus { pub sent_tx: SentTx, pub status: TxStatus, } -impl SentTxWithLatestStatus { +impl TxWithStatus { pub fn new(sent_tx: SentTx, status: TxStatus) -> Self { - todo!() + Self { sent_tx, status } } } @@ -58,7 +58,7 @@ pub enum TxStatus { Pending, } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum TxBlockchainFailure { Unknown, } @@ -126,7 +126,7 @@ impl TxReceiptRequestError { } } -#[derive(Debug, Default, PartialEq, Eq, Clone)] +#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)] pub struct TransactionBlock { pub block_hash: H256, pub block_number: U64, @@ -261,7 +261,7 @@ mod tests { use web3::types::{BlockNumber, Bytes, FilterBuilder, Log, TransactionReceipt, U256}; use crate::accountant::db_access_objects::sent_payable_dao::SentTx; use crate::accountant::test_utils::make_sent_tx; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{SentTxWithLatestStatus, TransactionBlock, TxBlockchainFailure, TxStatus}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxWithStatus, TransactionBlock, TxBlockchainFailure, TxStatus}; #[test] fn get_transaction_fee_balance_works() { @@ -630,7 +630,7 @@ mod tests { Some(U64::from(10)), H256::from_low_u64_be(0x5678), ); - + match tx_status { TxStatus::Succeeded(ref block) => { assert_eq!(block.block_hash, H256::from_low_u64_be(0x1234)); @@ -648,14 +648,18 @@ mod tests { None, H256::from_low_u64_be(0x5678), ); - + assert_eq!(tx_status, TxStatus::Failed(TxBlockchainFailure::Unknown)); } #[test] fn transaction_receipt_can_be_converted_to_pending_transaction_no_status() { - let tx_status = - test_deriving_tx_status_from_tx_receipt_and_adding_to_sent_tx(None, None, None, H256::from_low_u64_be(0x5678)); + let tx_status = test_deriving_tx_status_from_tx_receipt_and_adding_to_sent_tx( + None, + None, + None, + H256::from_low_u64_be(0x5678), + ); assert_eq!(tx_status, TxStatus::Pending); } @@ -674,13 +678,12 @@ mod tests { #[test] fn transaction_receipt_can_be_converted_to_pending_transaction_no_status_and_block_info() { - let tx_status = - test_deriving_tx_status_from_tx_receipt_and_adding_to_sent_tx( - Some(U64::from(1)), - Some(H256::from_low_u64_be(0x1234)), - None, - H256::from_low_u64_be(0x5678), - ); + let tx_status = test_deriving_tx_status_from_tx_receipt_and_adding_to_sent_tx( + Some(U64::from(1)), + Some(H256::from_low_u64_be(0x1234)), + None, + H256::from_low_u64_be(0x5678), + ); assert_eq!(tx_status, TxStatus::Pending); } @@ -688,7 +691,10 @@ mod tests { #[test] fn tx_status_display_works() { // Test Failed - assert_eq!(TxStatus::Failed(TxBlockchainFailure::Unknown).to_string(), "Failed"); + assert_eq!( + TxStatus::Failed(TxBlockchainFailure::Unknown).to_string(), + "Failed" + ); // Test Pending assert_eq!(TxStatus::Pending.to_string(), "Pending"); diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs index 8bb0e92b2..17861108d 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs @@ -24,7 +24,7 @@ use crate::accountant::scanners::payable_scanner_extension::msgs::{UnpricedQuali use crate::blockchain::blockchain_agent::BlockchainAgent; use crate::accountant::db_access_objects::sent_payable_dao::SentTx; use crate::blockchain::blockchain_bridge::{BlockMarker, BlockScanRange, RegisterNewPendingSentTxMessage}; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{LowBlockchainIntWeb3, TxReceiptResult, TxStatus, SentTxWithLatestStatus, TxReceiptRequestError}; +use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{LowBlockchainIntWeb3, TxReceiptResult, TxStatus, TxWithStatus, TxReceiptRequestError}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::utils::{create_blockchain_agent_web3, send_payables_within_batch, BlockchainAgentFutureResult}; // TODO We should probably begin to attach these constants to the interfaces more tightly, so that @@ -227,17 +227,16 @@ impl BlockchainInterface for BlockchainInterfaceWeb3 { .map(|(response, sent_tx)| match response { Ok(result) => { match serde_json::from_value::(result) { - Ok(receipt) => TxReceiptResult::RpcResponse( - SentTxWithLatestStatus::new(sent_tx, receipt.into()), - ), + Ok(receipt) => TxReceiptResult::RpcResponse(TxWithStatus::new( + sent_tx, + receipt.into(), + )), Err(e) => { if e.to_string().contains("invalid type: null") { - TxReceiptResult::RpcResponse( - SentTxWithLatestStatus::new( - sent_tx, - TxStatus::Pending, - ), - ) + TxReceiptResult::RpcResponse(TxWithStatus::new( + sent_tx, + TxStatus::Pending, + )) } else { TxReceiptResult::RequestError( TxReceiptRequestError::new( @@ -1065,13 +1064,11 @@ mod tests { let sent_tx_5 = make_sent_tx(3704); let sent_tx_6 = make_sent_tx(3805); let sent_tx_vec = vec![ - &sent_tx_1, - &sent_tx_2, - &sent_tx_3, - &sent_tx_4, - &sent_tx_5, - &sent_tx_6, - ].into_iter().cloned().collect(); + &sent_tx_1, &sent_tx_2, &sent_tx_3, &sent_tx_4, &sent_tx_5, &sent_tx_6, + ] + .into_iter() + .cloned() + .collect(); let block_hash = H256::from_str("6d0abccae617442c26104c2bc63d1bc05e1e002e555aec4ab62a46e826b18f18") .unwrap(); @@ -1123,7 +1120,7 @@ mod tests { "RPC error: Error { code: ServerError(429), message: \"The requests per second (RPS) of your requests are higher than your plan allows.\", data: None }".to_string()))); assert_eq!( result[1], - TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new(sent_tx_2, TxStatus::Pending)) + TxReceiptResult::RpcResponse(TxWithStatus::new(sent_tx_2, TxStatus::Pending)) ); assert_eq!( result[2], @@ -1134,18 +1131,18 @@ mod tests { ); assert_eq!( result[3], - TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new(sent_tx_4, TxStatus::Pending)) + TxReceiptResult::RpcResponse(TxWithStatus::new(sent_tx_4, TxStatus::Pending)) ); assert_eq!( result[4], - TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( + TxReceiptResult::RpcResponse(TxWithStatus::new( sent_tx_5, TxStatus::Failed(TxBlockchainFailure::Unknown) )) ); assert_eq!( result[5], - TxReceiptResult::RpcResponse(SentTxWithLatestStatus::new( + TxReceiptResult::RpcResponse(TxWithStatus::new( sent_tx_6, TxStatus::Succeeded(TransactionBlock { block_hash, From 1b3abb5a0a98b85a4bb7e2de283bb37732d1f557 Mon Sep 17 00:00:00 2001 From: Bert Date: Sat, 12 Jul 2025 23:22:35 +0200 Subject: [PATCH 08/61] GH-642: fn handle_failed_transactions has been reimplemented --- node/src/accountant/scanners/mod.rs | 104 +++++++++++------- .../src/accountant/scanners/scanners_utils.rs | 6 +- 2 files changed, 70 insertions(+), 40 deletions(-) diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index d2a24752c..3e864e55a 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -853,17 +853,25 @@ impl PayableScanner { err_opt: Option, logger: &Logger, ) { + fn decide_on_tx_error_handling( + err: &PayableTransactingErrorEnum, + ) -> Option<&HashSet> { + match err { + LocallyCausedError(PayableTransactionError::Sending { hashes, .. }) + | RemotelyCausedErrors(hashes) => Some(hashes), + _ => None, + } + } + if let Some(err) = err_opt { - if let LocallyCausedError(PayableTransactionError::Sending { hashes, .. }) - | RemotelyCausedErrors(hashes) = &err - { + if let Some(hashes) = decide_on_tx_error_handling(&err) { self.discard_failed_transactions_with_possible_sent_tx_records(hashes, logger) } else { debug!( - logger, - "Ignoring a non-fatal error on our end from before the transactions are hashed: {:?}", - err - ) + logger, + "A non-fatal error on our end from before the tx hashing will be ignored: {:?}", + err + ) } } } @@ -1169,31 +1177,35 @@ impl PendingPayableScanner { } fn handle_failed_transactions(&self, failures: Vec, logger: &Logger) { + fn joint_hashes(hashes: &HashSet) -> String { + comma_joined_stringifiable(&hashes.iter().collect_vec(), |hash| format!("{:?}", hash)) + } + if !failures.is_empty() { - let hashes = failures.iter().map(|failed_tx| failed_tx.hash).collect(); + let hashes = failures + .iter() + .map(|failed_tx| failed_tx.hash) + .collect::>(); match self.failed_payable_dao.insert_new_records(&failures) { Ok(_) => { - todo!() + debug!(logger, "Failed txs {:?} recorded", hashes); } - Err(e) => todo!(), + Err(e) => panic!( + "Unable to record failed txs {} due to {:?}", + joint_hashes(&hashes), + e + ), } match self.sent_payable_dao.delete_records(&hashes) { - // Ok(_) => warning!( - // logger, - // "Broken transactions {} marked as an error. You should take over the care \ - // of those to make sure your debts are going to be settled properly. At the moment, \ - // there is no automated process fixing that without your assistance", - // PendingPayableId::serialize_hashes_to_string(&failures) - // ), - // Err(e) => panic!( - // "Unsuccessful attempt for transactions {} \ - // to mark fatal error at sent tx record due to {:?}; database unreliable", - // PendingPayableId::serialize_hashes_to_string(&failures), - // e - // ), - // } - Ok(_) => todo!(), - Err(e) => todo!(), + Ok(_) => debug!( + logger, + "Deleted sent payable records for failed txs {:?}", hashes + ), + Err(e) => panic!( + "Unable to delete sent_payable entries for failed txs {} due to {:?}", + joint_hashes(&hashes), + e + ), }; } } @@ -3323,8 +3335,6 @@ mod tests { #[test] fn handle_failed_transactions_works() { - init_test_logging(); - let test_name = "handle_failed_transactions_works"; let insert_new_records_params_arc = Arc::new(Mutex::new(vec![])); let delete_records_params_arc = Arc::new(Mutex::new(vec![])); let failed_payable_dao = FailedPayableDaoMock::default() @@ -3344,22 +3354,16 @@ mod tests { let mut failed_tx_2 = make_failed_tx(456); failed_tx_2.hash = hash_2; - subject.handle_failed_transactions(vec![failed_tx_1, failed_tx_2], &Logger::new(test_name)); + subject.handle_failed_transactions(vec![failed_tx_1, failed_tx_2], &Logger::new("test")); let delete_records_params = delete_records_params_arc.lock().unwrap(); assert_eq!(*delete_records_params, vec![hashset![hash_1, hash_2]]); - TestLogHandler::new().exists_log_containing(&format!( - "WARN: {test_name}: Broken transactions 0x0000000000000000000000000000000000000000000000000000000000000321, \ - 0x0000000000000000000000000000000000000000000000000000000000000654 bluh bluh" - )); } #[test] - #[should_panic( - expected = "Unsuccessful attempt for transactions 0x00000000000000000000000000000000000\ - 0000000000000000000000000014d, 0x000000000000000000000000000000000000000000000000000000\ - 00000001bc bluh bluh UpdateFailed(\"no no no\")" - )] + #[should_panic(expected = "Unable to record failed txs \ + 0x000000000000000000000000000000000000000000000000000000000000014d, \ + 0x00000000000000000000000000000000000000000000000000000000000001bc due to NoChange")] fn handle_failed_transactions_panics_when_it_fails_to_insert_failed_tx_record() { let failed_payable_dao = FailedPayableDaoMock::default() .insert_new_records_result(Err(FailedPayableDaoError::NoChange)); @@ -3377,6 +3381,30 @@ mod tests { subject.handle_failed_transactions(failed_txs, &Logger::new("test")); } + #[test] + #[should_panic(expected = "Unable to delete sent_payable entries for failed txs \ + 0x000000000000000000000000000000000000000000000000000000000000014d, \ + 0x00000000000000000000000000000000000000000000000000000000000001bc due to \ + InvalidInput(\"Booga\")")] + fn handle_failed_transactions_panics_when_it_fails_to_delete_obsolete_sent_tx_records() { + let failed_payable_dao = FailedPayableDaoMock::default().insert_new_records_result(Ok(())); + let sent_payable_dao = SentPayableDaoMock::default() + .delete_records_result(Err(SentPayableDaoError::InvalidInput("Booga".to_string()))); + let subject = PendingPayableScannerBuilder::new() + .failed_payable_dao(failed_payable_dao) + .sent_payable_dao(sent_payable_dao) + .build(); + let hash_1 = make_tx_hash(0x14d); + let hash_2 = make_tx_hash(0x1bc); + let mut failed_tx_1 = make_failed_tx(789); + failed_tx_1.hash = hash_1; + let mut failed_tx_2 = make_failed_tx(456); + failed_tx_2.hash = hash_2; + let failed_txs = vec![failed_tx_1, failed_tx_2]; + + subject.handle_failed_transactions(failed_txs, &Logger::new("test")); + } + #[test] fn handle_failed_transactions_does_nothing_if_no_tx_failures_detected() { let subject = PendingPayableScannerBuilder::new().build(); diff --git a/node/src/accountant/scanners/scanners_utils.rs b/node/src/accountant/scanners/scanners_utils.rs index 8ccf89b3f..b7f925400 100644 --- a/node/src/accountant/scanners/scanners_utils.rs +++ b/node/src/accountant/scanners/scanners_utils.rs @@ -424,9 +424,11 @@ pub mod pending_payable_scanner_utils { let failed_tx = FailedTx::from((sent_tx, failure_reason)); todo!("add to log assertions"); - error!( + warning!( logger, - "Failure of tx {:?} detected: {:?}.", failed_tx.hash, failure_reason + "Failure of tx {:?} detected: {:?}.", + failed_tx.hash, + failure_reason ); scan_report.failures.push(failed_tx); From 1573bde2408325f7f4ab5ac7e94dc93fc3046b69 Mon Sep 17 00:00:00 2001 From: Bert Date: Mon, 14 Jul 2025 13:44:51 +0200 Subject: [PATCH 09/61] GH-642: fixed mainly internal, but smaller functions in the pending payable scanner; various From and Display implementations and these sorts --- node/src/accountant/mod.rs | 77 +++--- node/src/accountant/scanners/mod.rs | 239 ++++++------------ .../src/accountant/scanners/scanners_utils.rs | 204 +++++++++------ node/src/accountant/scanners/test_utils.rs | 4 +- .../lower_level_interface_web3.rs | 75 +++++- .../blockchain_interface_web3/mod.rs | 4 +- node/src/test_utils/mod.rs | 39 +++ 7 files changed, 365 insertions(+), 277 deletions(-) diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index 10dc63d27..c93cb6232 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -594,12 +594,12 @@ impl Accountant { byte_rate, payload_size ), - Err(e) => panic!("Recording services provided for {} but has hit fatal database error: {:?}", wallet, e) + Err(e) => panic!("Was recording services provided for {} but hit a fatal database error: {:?}", wallet, e) }; } else { warning!( self.logger, - "Declining to record a receivable against our wallet {} for service we provided", + "Declining to record a receivable against our wallet {} for services we provided", wallet ); } @@ -1237,7 +1237,7 @@ mod tests { use crate::accountant::test_utils::DaoWithDestination::{ ForAccountantBody, ForPayableScanner, ForPendingPayableScanner, ForReceivableScanner, }; - use crate::accountant::test_utils::{bc_from_earning_wallet, bc_from_wallets, make_payable_account, make_qualified_and_unqualified_payables, BannedDaoFactoryMock, ConfigDaoFactoryMock, MessageIdGeneratorMock, PayableDaoFactoryMock, PayableDaoMock, PayableScannerBuilder, PaymentAdjusterMock, SentPayableDaoFactoryMock, SentPayableDaoMock, ReceivableDaoFactoryMock, ReceivableDaoMock, make_sent_tx, FailedPayableDaoMock, FailedPayableDaoFactoryMock}; + use crate::accountant::test_utils::{bc_from_earning_wallet, bc_from_wallets, make_payable_account, make_qualified_and_unqualified_payables, BannedDaoFactoryMock, ConfigDaoFactoryMock, MessageIdGeneratorMock, PayableDaoFactoryMock, PayableDaoMock, PayableScannerBuilder, PaymentAdjusterMock, SentPayableDaoFactoryMock, SentPayableDaoMock, ReceivableDaoFactoryMock, ReceivableDaoMock, make_sent_tx, FailedPayableDaoMock, FailedPayableDaoFactoryMock, make_transaction_block}; use crate::accountant::test_utils::{make_unpriced_qualified_payables_for_retry_mode, make_priced_qualified_payables}; use crate::accountant::test_utils::{AccountantBuilder, BannedDaoMock}; use crate::accountant::Accountant; @@ -1288,6 +1288,7 @@ mod tests { use masq_lib::ui_gateway::MessagePath::Conversation; use masq_lib::ui_gateway::{MessageBody, MessagePath, NodeFromUiMessage, NodeToUiMessage}; use std::any::{TypeId}; + use std::fmt::format; use std::ops::{Sub}; use std::sync::Arc; use std::sync::Mutex; @@ -1297,7 +1298,7 @@ mod tests { use crate::accountant::db_access_objects::sent_payable_dao::{SentPayableDaoError, SentTx}; use crate::accountant::scanners::scan_schedulers::{NewPayableScanDynIntervalComputer, NewPayableScanDynIntervalComputerReal}; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{OperationOutcome, PayableScanResult}; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxWithStatus, TransactionBlock, TxBlockchainFailure, TxStatus}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxWithStatus, TransactionBlock, BlockchainTxFailure, TxStatus}; use crate::test_utils::recorder_counter_msgs::SingleTypeCounterMsgSetup; impl Handler> for Accountant { @@ -2159,7 +2160,7 @@ mod tests { TxStatusReport { results: vec![TxReceiptResult::RpcResponse(TxWithStatus::new( sent_tx, - TxStatus::Failed(TxBlockchainFailure::Unknown) + TxStatus::Failed(BlockchainTxFailure::Unrecognized) )),], response_skeleton_opt }, @@ -2779,7 +2780,7 @@ mod tests { let expected_tx_status_report = TxStatusReport { results: vec![TxReceiptResult::RpcResponse(TxWithStatus::new( sent_tx.clone(), - TxStatus::Failed(TxBlockchainFailure::Unknown), + TxStatus::Failed(BlockchainTxFailure::Unrecognized), ))], response_skeleton_opt: None, }; @@ -4275,7 +4276,7 @@ mod tests { .is_empty()); TestLogHandler::new().exists_log_containing(&format!( - "WARN: Accountant: Declining to record a receivable against our wallet {} for service we provided", + "WARN: Accountant: Declining to record a receivable against our wallet {} for services we provided", consuming_wallet, )); } @@ -4320,7 +4321,7 @@ mod tests { .is_empty()); TestLogHandler::new().exists_log_containing(&format!( - "WARN: Accountant: Declining to record a receivable against our wallet {} for service we provided", + "WARN: Accountant: Declining to record a receivable against our wallet {} for services we provided", earning_wallet, )); } @@ -4412,7 +4413,7 @@ mod tests { .is_empty()); TestLogHandler::new().exists_log_containing(&format!( - "WARN: Accountant: Declining to record a receivable against our wallet {} for service we provided", + "WARN: Accountant: Declining to record a receivable against our wallet {} for services we provided", consuming_wallet )); } @@ -4457,7 +4458,7 @@ mod tests { .is_empty()); TestLogHandler::new().exists_log_containing(&format!( - "WARN: Accountant: Declining to record a receivable against our wallet {} for service we provided", + "WARN: Accountant: Declining to record a receivable against our wallet {} for services we provided", earning_wallet, )); } @@ -4726,8 +4727,8 @@ mod tests { #[test] #[should_panic( - expected = "Recording services provided for 0x000000000000000000000000000000626f6f6761 \ - but has hit fatal database error: RusqliteError(\"we cannot help ourselves; this is baaad\")" + expected = "Was recording services provided for 0x000000000000000000000000000000626f6f6761 \ + but hit a fatal database error: RusqliteError(\"we cannot help ourselves; this is baaad\")" )] fn record_service_provided_panics_on_fatal_errors() { init_test_logging(); @@ -4961,7 +4962,7 @@ mod tests { let system = System::new(test_name); let (mut msg, _) = make_tx_status_report_msg(vec![ TxStatus::Pending, - TxStatus::Failed(TxBlockchainFailure::Unknown), + TxStatus::Failed(BlockchainTxFailure::Unrecognized), ]); let response_skeleton_opt = Some(ResponseSkeleton { client_id: 45, @@ -5073,8 +5074,10 @@ mod tests { #[test] fn accountant_confirms_payable_txs_and_schedules_the_delayed_new_payable_scanner_asap() { init_test_logging(); + let test_name = + "accountant_confirms_payable_txs_and_schedules_the_delayed_new_payable_scanner_asap"; let transactions_confirmed_params_arc = Arc::new(Mutex::new(vec![])); - let delete_records_params_arc = Arc::new(Mutex::new(vec![])); + let update_tx_blocks_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![])); let new_payable_notify_arc = Arc::new(Mutex::new(vec![])); @@ -5082,11 +5085,12 @@ mod tests { .transactions_confirmed_params(&transactions_confirmed_params_arc) .transactions_confirmed_result(Ok(())); let sent_payable_dao = SentPayableDaoMock::default() - .delete_records_params(&delete_records_params_arc) - .delete_records_result(Ok(())); + .update_tx_blocks_params(&update_tx_blocks_params_arc) + .update_tx_blocks_result(Ok(())); let mut subject = AccountantBuilder::default() .payable_daos(vec![ForPendingPayableScanner(payable_dao)]) .sent_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) + .logger(Logger::new(test_name)) .build(); let last_new_payable_scan_timestamp = SystemTime::now() .checked_sub(Duration::from_secs(8)) @@ -5109,28 +5113,26 @@ mod tests { ); subject.scan_schedulers.payable.new_payable_notify = Box::new(NotifyHandleMock::default().notify_params(&new_payable_notify_arc)); + let tx_block_1 = make_transaction_block(4567); + let tx_block_2 = make_transaction_block(1234); let subject_addr = subject.start(); let (msg, two_sent_txs) = make_tx_status_report_msg(vec![ - TxStatus::Succeeded(TransactionBlock { - block_hash: make_tx_hash(123), - block_number: U64::from(111), - }), - TxStatus::Succeeded(TransactionBlock { - block_hash: make_tx_hash(234), - block_number: U64::from(787), - }), + TxStatus::Succeeded(tx_block_1), + TxStatus::Succeeded(tx_block_2), ]); subject_addr.try_send(msg).unwrap(); - let system = System::new("new_payable_scanner_asap"); + let system = System::new(test_name); System::current().stop(); system.run(); - let hashes = two_sent_txs.iter().map(|sent_tx| sent_tx.hash).collect(); let transactions_confirmed_params = transactions_confirmed_params_arc.lock().unwrap(); - assert_eq!(*transactions_confirmed_params, vec![two_sent_txs]); - let delete_records_params = delete_records_params_arc.lock().unwrap(); - assert_eq!(*delete_records_params, vec![hashes]); + assert_eq!(*transactions_confirmed_params, vec![two_sent_txs.clone()]); + let update_tx_blocks_params = update_tx_blocks_params_arc.lock().unwrap(); + assert_eq!( + *update_tx_blocks_params, + vec![hashmap![two_sent_txs[0].hash => tx_block_1, two_sent_txs[1].hash => tx_block_2]] + ); 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); @@ -5148,7 +5150,10 @@ mod tests { ); let new_payable_notify = new_payable_notify_arc.lock().unwrap(); assert_eq!(*new_payable_notify, vec![ScanForNewPayables::default()]); - TestLogHandler::new().exists_log_containing("bluuuu"); + TestLogHandler::new().exists_log_containing(&format!("INFO: {test_name}: \ + Txs 0x0000000000000000000000000000000000000000000000000000000000000001 (block 95256152263), \ + 0x0000000000000000000000000000000000000000000000000000000000000002 (block 1879080904) have \ + completed")); } #[test] @@ -5220,11 +5225,15 @@ mod tests { let (tx_receipt_results, sent_tx_vec) = status_txs.into_iter().enumerate().fold( (vec![], vec![]), |(mut tx_receipt_results, mut sent_tx_vec), (idx, status)| { - let sent_tx = make_sent_tx(1 + idx as u64); - let hash = sent_tx.hash; - let tx_receipt_result = + let mut sent_tx = make_sent_tx(1 + idx as u64); + if let TxStatus::Succeeded(block) = &status { + sent_tx.block_opt = Some(block.clone()); + } + + let result = TxReceiptResult::RpcResponse(TxWithStatus::new(sent_tx.clone(), status)); - tx_receipt_results.push(tx_receipt_result); + + tx_receipt_results.push(result); sent_tx_vec.push(sent_tx); (tx_receipt_results, sent_tx_vec) }, diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index 3e864e55a..92419b019 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -12,7 +12,7 @@ use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableT LocallyCausedError, RemotelyCausedErrors, }; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{debugging_summary_after_error_separation, err_msg_for_failure_with_expected_but_missing_fingerprints, investigate_debt_extremes, mark_pending_payable_fatal_error, payables_debug_summary, separate_errors, separate_rowids_and_hashes, OperationOutcome, PayableScanResult, PayableThresholdsGauge, PayableThresholdsGaugeReal, PayableTransactingErrorEnum, PendingPayableMissingInDb}; -use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_request_error_fetching_receipts, handle_status_with_failure, handle_successful_tx, PendingPayableScanSummary, PendingPayableScanResult}; +use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_status_with_failure, handle_successful_tx, PendingPayableScanSummary, PendingPayableScanResult, handle_still_pending_tx}; use crate::accountant::scanners::scanners_utils::receivable_scanner_utils::balance_and_age; use crate::accountant::{PendingPayable, PendingPayableId, ScanError, ScanForPendingPayables, ScanForRetryPayables}; use crate::accountant::{ @@ -433,7 +433,7 @@ impl ScannerCommon { None => { error!( logger, - "Called scan_finished() for {:?} scanner but timestamp was not found", + "Called scan_finished() for {:?} scanner but could not find any timestamp", scan_type ); } @@ -987,8 +987,7 @@ impl Scanner for PendingPayableScanner let response_skeleton_opt = message.response_skeleton_opt; if message.results.is_empty() { - todo!("test me"); - unreachable!("We should not have received an empty list of results") + unreachable!("We should never receive an empty list of results. Even missing receipts can be interpreted") } debug!( @@ -1065,13 +1064,23 @@ impl PendingPayableScanner { reason, logger, ), - TxStatus::Pending => { - todo!() - } + TxStatus::Pending => handle_still_pending_tx( + scan_report_so_far, + sent_tx_with_status.sent_tx, + logger, + ), } } TxReceiptResult::RequestError(e) => { - handle_request_error_fetching_receipts(scan_report_so_far, e, logger) + todo!() + // debug!( + // logger, + // "Interpreting a receipt for transaction {:?} but {}; attempt {}, {}ms since sending", + // payable.hash, + // error_msg, + // payable.attempt, + // elapsed_in_ms(payable.timestamp) + // ); } }, ) @@ -1082,11 +1091,11 @@ impl PendingPayableScanner { scan_report: PendingPayableScanSummary, logger: &Logger, ) { - self.confirm_transactions(scan_report.confirmed, logger); + self.handle_confirmed_transactions(scan_report.confirmed, logger); self.handle_failed_transactions(scan_report.failures, logger); } - fn confirm_transactions(&mut self, confirmed_txs: Vec, logger: &Logger) { + fn handle_confirmed_transactions(&mut self, confirmed_txs: Vec, logger: &Logger) { fn transaction_confirmed_panic(confirmed_txs: &[SentTx], e: PayableDaoError) -> ! { let wallets = confirmed_txs .iter() @@ -1119,9 +1128,8 @@ impl PendingPayableScanner { logger.info(|| { let pretty_pairs = tx_hashes_and_tx_blocks .iter() - .map(|(hash, block)| { - format!("{:?} (block number: {})", hash, block.block_number) - }) + .sorted() + .map(|(hash, block)| format!("{:?} (block {})", hash, block.block_number)) .join(", "); format!("Txs {} have completed", pretty_pairs) }); @@ -1548,7 +1556,8 @@ mod tests { use crate::accountant::db_access_objects::failed_payable_dao::{FailedPayableDaoError, FailedTx, FailureReason}; use crate::accountant::db_access_objects::sent_payable_dao::{SentPayableDaoError, SentTx}; use crate::accountant::scanners::test_utils::{assert_timestamps_from_str, parse_system_time_from_str, MarkScanner, NullScanner, ReplacementType, ScannerReplacement}; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxWithStatus, TransactionBlock, TxReceiptResult, TxStatus, TxBlockchainFailure}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxWithStatus, TransactionBlock, TxReceiptResult, TxStatus, BlockchainTxFailure}; + use crate::test_utils::unshared_test_utils::capture_numbers_with_separators_from_str; impl Scanners { pub fn replace_scanner(&mut self, replacement: ScannerReplacement) { @@ -3174,111 +3183,14 @@ mod tests { .as_secs() } - #[test] - fn interpret_transaction_receipt_when_transaction_status_is_none_and_outside_waiting_interval() - { - todo!("think of if we should preserve it") - // let test_name = "interpret_transaction_receipt_when_transaction_status_is_none_and_outside_waiting_interval"; - // let hash = make_tx_hash(0x237); - // let rowid = 466; - // - // let result = assert_interpreting_none_status_for_pending_payable( - // test_name, - // DEFAULT_PENDING_TOO_LONG_SEC, - // DEFAULT_PENDING_TOO_LONG_SEC + 1, - // rowid, - // hash, - // ); - // - // let elapsed_after = elapsed_since_secs_back(DEFAULT_PENDING_TOO_LONG_SEC + 1); - // assert_eq!( - // result, - // PendingPayableScanSummary { - // still_pending: vec![], - // failures: vec![PendingPayableId::new(rowid, hash)], - // confirmed: vec![] - // } - // ); - // let capture_regex = "(\\d+){2}sec"; - // assert_log_msg_and_elapsed_time_in_log_makes_sense(&format!( - // "ERROR: {}: Pending transaction 0x00000000000000000000000000000000000000\ - // 00000000000000000000000237 has exceeded the maximum pending time \\({}sec\\) with the age \ - // \\d+sec and the confirmation process is going to be aborted now at the final attempt 1; manual \ - // resolution is required from the user to complete the transaction" - // , test_name, DEFAULT_PENDING_TOO_LONG_SEC, ), elapsed_after, capture_regex) - } - - #[test] - fn interpret_transaction_receipt_when_transaction_status_is_none_and_within_waiting_interval() { - todo!("think of we can transform it into some other test") - // let test_name = "interpret_transaction_receipt_when_transaction_status_is_none_and_within_waiting_interval"; - // let hash = make_tx_hash(0x7b); - // let rowid = 333; - // let pending_payable_age = DEFAULT_PENDING_TOO_LONG_SEC - 1; - // - // let result = assert_interpreting_none_status_for_pending_payable( - // test_name, - // DEFAULT_PENDING_TOO_LONG_SEC, - // pending_payable_age, - // rowid, - // hash, - // ); - // - // let elapsed_after_ms = elapsed_since_secs_back(pending_payable_age) * 1000; - // assert_eq!( - // result, - // PendingPayableScanSummary { - // still_pending: vec![PendingPayableId::new(rowid, hash)], - // failures: vec![], - // confirmed: vec![] - // } - // ); - // let capture_regex = r#"\s(\d+)ms"#; - // assert_log_msg_and_elapsed_time_in_log_makes_sense(&format!( - // "INFO: {test_name}: Pending transaction 0x0000000000000000000000000000000000000000000000000\ - // 00000000000007b couldn't be confirmed at attempt 1 at \\d+ms after its sending"), elapsed_after_ms, capture_regex); - } - - #[test] - fn interpret_transaction_receipt_when_transaction_status_is_none_and_time_equals_the_limit() { - todo!("think of we can transform it into some other test") - // let test_name = "interpret_transaction_receipt_when_transaction_status_is_none_and_time_equals_the_limit"; - // let hash = make_tx_hash(0x237); - // let rowid = 466; - // let pending_payable_age = DEFAULT_PENDING_TOO_LONG_SEC; - // - // let result = assert_interpreting_none_status_for_pending_payable( - // test_name, - // DEFAULT_PENDING_TOO_LONG_SEC, - // pending_payable_age, - // rowid, - // hash, - // ); - // - // let elapsed_after_ms = elapsed_since_secs_back(pending_payable_age) * 1000; - // assert_eq!( - // result, - // PendingPayableScanSummary { - // still_pending: vec![PendingPayableId::new(rowid, hash)], - // failures: vec![], - // confirmed: vec![] - // } - // ); - // let capture_regex = r#"\s(\d+)ms"#; - // assert_log_msg_and_elapsed_time_in_log_makes_sense(&format!( - // "INFO: {test_name}: Pending transaction 0x0000000000000000000000000000000000000000000000000\ - // 000000000000237 couldn't be confirmed at attempt 1 at \\d+ms after its sending", - // ), elapsed_after_ms, capture_regex); - } - #[test] fn interpret_transaction_receipt_when_transaction_status_is_a_failure() { init_test_logging(); - let now = SystemTime::now(); let test_name = "interpret_transaction_receipt_when_transaction_status_is_a_failure"; let hash = make_tx_hash(0xabc); - let sent_tx = make_sent_tx(2244); - let failure_reason = TxBlockchainFailure::Unknown; + let mut sent_tx = make_sent_tx(2244); + sent_tx.hash = hash; + let failure_reason = BlockchainTxFailure::Unrecognized; let logger = Logger::new(test_name); let scan_report = PendingPayableScanSummary::default(); @@ -3293,21 +3205,26 @@ mod tests { confirmed: vec![] } ); - TestLogHandler::new().exists_log_matching(&format!( - "INFO: {test_name}: Pending transaction 0x0000000000000000000000000000000000000000\ - 000000000000000000000abc bluh bluh" + TestLogHandler::new().exists_log_containing(&format!( + "WARN: {test_name}: Tx 0x0000000000000000000000000000000000000000000000000000000000000abc \ + failed on blockchain due to: Failure unrecognized", )); } #[test] - fn handle_pending_txs_with_receipts_handles_none_for_receipt() { + fn handle_transaction_receipt_if_the_tx_keeps_pending() { init_test_logging(); - let test_name = "handle_pending_txs_with_receipts_handles_none_for_receipt"; + let test_name = "handle_transaction_receipt_if_the_tx_keeps_pending"; let subject = PendingPayableScannerBuilder::new().build(); - let rowid = 455; let hash = make_tx_hash(0x913); + let sent_tx_timestamp = to_unix_timestamp( + SystemTime::now() + .checked_sub(Duration::from_secs(120)) + .unwrap(), + ); let mut sent_tx = make_sent_tx(456); sent_tx.hash = hash; + sent_tx.timestamp = sent_tx_timestamp; let msg = TxStatusReport { results: vec![TxReceiptResult::RpcResponse(TxWithStatus::new( sent_tx.clone(), @@ -3315,9 +3232,11 @@ mod tests { ))], response_skeleton_opt: None, }; + let before = SystemTime::now(); let result = subject.handle_receipts_for_pending_transactions(msg, &Logger::new(test_name)); + let after = SystemTime::now(); let expected_failed_tx = FailedTx::from(sent_tx); assert_eq!( result, @@ -3326,11 +3245,30 @@ mod tests { confirmed: vec![] } ); - TestLogHandler::new().exists_log_matching(&format!( - "DEBUG: {test_name}: Interpreting a receipt for transaction \ - 0x0000000000000000000000000000000000000000000000000000000000000913 \ - but none was given; attempt 3, 100\\d\\dms since sending" + let log_handler = TestLogHandler::new(); + let log_idx = log_handler.exists_log_matching(&format!( + "WARN: {test_name}: Pending tx \ + 0x0000000000000000000000000000000000000000000000000000000000000913 could not be \ + confirmed yet after \\d{{1,3}}(,\\d{{3}})* ms and will be retried with a more optimized gas price" )); + let log_msg = log_handler.get_log_at(log_idx); + let str_elapsed_ms = capture_numbers_with_separators_from_str(&log_msg, 3, ','); + let elapsed_ms = str_elapsed_ms[0].replace(",", "").parse::().unwrap(); + let elapsed_ms_when_before = before + .duration_since(from_unix_timestamp(sent_tx_timestamp)) + .unwrap() + .as_millis(); + let elapsed_ms_when_after = after + .duration_since(from_unix_timestamp(sent_tx_timestamp)) + .unwrap() + .as_millis(); + assert!( + elapsed_ms_when_before <= elapsed_ms && elapsed_ms <= elapsed_ms_when_after, + "we expected the elapsed time {} ms to be between {} and {}.", + elapsed_ms, + elapsed_ms_when_before, + elapsed_ms_when_after + ); } #[test] @@ -3421,7 +3359,8 @@ mod tests { 0000000000021a with their tx blocks due to SqlExecutionFailed(\"The database manager is \ a funny guy, he's fooling around with us\")" )] - fn confirm_transactions_panics_while_updating_sent_payable_records_with_the_tx_blocks() { + fn handle_confirmed_transactions_panics_while_updating_sent_payable_records_with_the_tx_blocks() + { let payable_dao = PayableDaoMock::new().transactions_confirmed_result(Ok(())); let sent_payable_dao = SentPayableDaoMock::default().update_tx_blocks_result(Err( SentPayableDaoError::SqlExecutionFailed( @@ -3439,22 +3378,22 @@ mod tests { sent_tx_2.hash = make_tx_hash(0x21a); sent_tx_2.block_opt = Some(make_transaction_block(890)); - subject.confirm_transactions(vec![sent_tx_1, sent_tx_2], &Logger::new("test")); + subject.handle_confirmed_transactions(vec![sent_tx_1, sent_tx_2], &Logger::new("test")); } #[test] - fn confirm_transactions_does_nothing_if_none_found_on_the_blockchain() { + fn handle_confirmed_transactions_does_nothing_if_none_found_on_the_blockchain() { let mut subject = PendingPayableScannerBuilder::new().build(); - subject.confirm_transactions(vec![], &Logger::new("test")) + subject.handle_confirmed_transactions(vec![], &Logger::new("test")) // Mocked payable DAO didn't panic, which means we skipped the actual process } #[test] - fn confirm_transactions_works() { + fn handle_confirmed_transactions_works() { init_test_logging(); - let test_name = "confirm_transactions_works"; + let test_name = "handle_confirmed_transactions_works"; let transactions_confirmed_params_arc = Arc::new(Mutex::new(vec![])); let update_tx_blocks_params_arc = Arc::new(Mutex::new(vec![])); let payable_dao = PayableDaoMock::default() @@ -3485,7 +3424,7 @@ mod tests { }; sent_tx_2.block_opt = Some(tx_block_2); - subject.confirm_transactions(vec![sent_tx_1.clone(), sent_tx_2.clone()], &logger); + subject.handle_confirmed_transactions(vec![sent_tx_1.clone(), sent_tx_2.clone()], &logger); let transactions_confirmed_params = transactions_confirmed_params_arc.lock().unwrap(); assert_eq!( @@ -3500,8 +3439,8 @@ mod tests { let log_handler = TestLogHandler::new(); log_handler.exists_log_containing(&format!( "INFO: {test_name}: Txs 0x0000000000000000000000000000000000000000000000000000000000000123 \ - (block number: 4578989878), 0x0000000000000000000000000000000000000000000000000000000000000567 \ - (block number: 7898989878) have completed", + (block 4578989878), 0x0000000000000000000000000000000000000000000000000000000000000567 \ + (block 7898989878) have completed", )); } @@ -3511,7 +3450,7 @@ mod tests { 0x000000000000000000000077616c6c6574343536 due to \ RusqliteError(\"record change not successful\")" )] - fn confirm_transactions_panics_on_unchecking_payable_table() { + fn handle_confirmed_transactions_panics_on_unchecking_payable_table() { let hash = make_tx_hash(0x315); let rowid = 3; let payable_dao = PayableDaoMock::new().transactions_confirmed_result(Err( @@ -3523,7 +3462,7 @@ mod tests { let mut sent_tx = make_sent_tx(456); sent_tx.hash = hash; - subject.confirm_transactions(vec![sent_tx], &Logger::new("test")); + subject.handle_confirmed_transactions(vec![sent_tx], &Logger::new("test")); } #[test] @@ -3546,7 +3485,7 @@ mod tests { financial_statistics.total_paid_payable_wei += 1111; subject.financial_statistics.replace(financial_statistics); - subject.confirm_transactions( + subject.handle_confirmed_transactions( vec![sent_tx_1.clone(), sent_tx_2.clone()], &Logger::new(test_name), ); @@ -3554,8 +3493,7 @@ mod tests { let total_paid_payable = subject.financial_statistics.borrow().total_paid_payable_wei; assert_eq!(total_paid_payable, 1111 + 5478 + 6543); TestLogHandler::new().exists_log_containing(&format!( - "DEBUG: {test_name}: The total \ - paid payables increased by 12,021 to 13,132 wei" + "DEBUG: {test_name}: The total paid payables increased by 12,021 to 13,132 wei" )); } @@ -3625,10 +3563,11 @@ mod tests { } #[test] + #[should_panic( + expected = "We should never receive an empty list of results. Even missing \ + receipts can be interpreted" + )] fn pending_payable_scanner_handles_empty_report_transaction_receipts_message() { - init_test_logging(); - let test_name = - "pending_payable_scanner_handles_report_transaction_receipts_message_with_empty_vector"; let mut pending_payable_scanner = PendingPayableScannerBuilder::new().build(); let msg = TxStatusReport { results: vec![], @@ -3638,21 +3577,7 @@ mod tests { let mut subject = make_dull_subject(); subject.pending_payable = Box::new(pending_payable_scanner); - let result = subject.finish_pending_payable_scan(msg, &Logger::new(test_name)); - - let is_scan_running = subject.scan_started_at(ScanType::PendingPayables).is_some(); - assert_eq!( - result, - PendingPayableScanResult::NoPendingPayablesLeft(None) - ); - assert_eq!(is_scan_running, false); - let tlh = TestLogHandler::new(); - tlh.exists_log_containing(&format!( - "WARN: {test_name}: No transaction receipts found." - )); - tlh.exists_log_matching(&format!( - "INFO: {test_name}: The PendingPayables scan ended in \\d+ms." - )); + let _ = subject.finish_pending_payable_scan(msg, &Logger::new("test")); } #[test] @@ -4084,7 +4009,7 @@ mod tests { subject.signal_scanner_completion(ScanType::Receivables, SystemTime::now(), &logger); TestLogHandler::new().exists_log_containing(&format!( - "ERROR: {test_name}: Called scan_finished() for Receivables scanner but timestamp was not found" + "ERROR: {test_name}: Called scan_finished() for Receivables scanner but could not find any timestamp" )); } diff --git a/node/src/accountant/scanners/scanners_utils.rs b/node/src/accountant/scanners/scanners_utils.rs index b7f925400..efbc68fd0 100644 --- a/node/src/accountant/scanners/scanners_utils.rs +++ b/node/src/accountant/scanners/scanners_utils.rs @@ -320,10 +320,12 @@ pub mod pending_payable_scanner_utils { use masq_lib::logger::Logger; use masq_lib::ui_gateway::NodeToUiMessage; use std::time::SystemTime; + use thousands::Separable; use masq_lib::utils::ExpectValue; - use crate::accountant::db_access_objects::failed_payable_dao::FailedTx; + use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, FailureReason, FailureStatus}; use crate::accountant::db_access_objects::sent_payable_dao::SentTx; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, TxBlockchainFailure, TxReceiptRequestError, TxStatus}; + use crate::accountant::db_access_objects::utils::from_unix_timestamp; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, BlockchainTxFailure, TxReceiptRequestError, TxStatus}; #[derive(Debug, Default, PartialEq, Eq, Clone)] pub struct PendingPayableScanSummary { @@ -354,44 +356,24 @@ pub mod pending_payable_scanner_utils { .expect("time calculation for elapsed failed") .as_millis() } - // - // pub fn handle_none_status( - // mut scan_report: PendingPayableScanSummary, - // sent_tx: SentTx, - // max_pending_interval: u64, - // logger: &Logger, - // ) -> PendingPayableScanSummary { - // info!( - // logger, - // "Pending transaction {:?} couldn't be confirmed at attempt \ - // {} at {}ms after its sending", - // sent_tx.hash, - // sent_tx.attempt, - // elapsed_in_ms(sent_tx.timestamp) - // ); - // let elapsed = sent_tx - // .timestamp - // .elapsed() - // .expect("we should be older now"); - // let elapsed = elapsed.as_secs(); - // if elapsed > max_pending_interval { - // error!( - // logger, - // "Pending transaction {:?} has exceeded the maximum pending time \ - // ({}sec) with the age {}sec and the confirmation process is going to be aborted now \ - // at the final attempt {}; manual resolution is required from the \ - // user to complete the transaction.", - // sent_tx.hash, - // max_pending_interval, - // elapsed, - // sent_tx.attempt - // ); - // scan_report.failures.push(sent_tx.into()) - // } else { - // scan_report.still_pending.push(sent_tx.into()) - // } - // scan_report - // } + + pub fn handle_still_pending_tx( + mut scan_report: PendingPayableScanSummary, + sent_tx: SentTx, + logger: &Logger, + ) -> PendingPayableScanSummary { + warning!( + logger, + "Pending tx {:?} could not be confirmed yet after {} ms and will be retried with a more \ + optimized gas price", + sent_tx.hash, + elapsed_in_ms(from_unix_timestamp(sent_tx.timestamp)).separate_with_commas() + ); + + let failed_tx = FailedTx::from(sent_tx); + scan_report.failures.push(failed_tx); + scan_report + } pub fn handle_successful_tx( mut scan_report: PendingPayableScanSummary, @@ -401,9 +383,7 @@ pub mod pending_payable_scanner_utils { ) -> PendingPayableScanSummary { info!( logger, - "Acknowledging that tx {:?} was added to block {}.", - sent_tx.hash, - tx_block.block_number, + "Detected tx {:?} added to block {}.", sent_tx.hash, tx_block.block_number, ); let completed_sent_tx = SentTx { @@ -418,15 +398,14 @@ pub mod pending_payable_scanner_utils { pub fn handle_status_with_failure( mut scan_report: PendingPayableScanSummary, sent_tx: SentTx, - failure_reason: TxBlockchainFailure, + failure_reason: BlockchainTxFailure, logger: &Logger, ) -> PendingPayableScanSummary { let failed_tx = FailedTx::from((sent_tx, failure_reason)); - todo!("add to log assertions"); warning!( logger, - "Failure of tx {:?} detected: {:?}.", + "Tx {:?} failed on blockchain due to: {}", failed_tx.hash, failure_reason ); @@ -435,37 +414,42 @@ pub mod pending_payable_scanner_utils { scan_report } - pub fn handle_request_error_fetching_receipts( - mut scan_report: PendingPayableScanSummary, - local_error: TxReceiptRequestError, - logger: &Logger, - ) -> PendingPayableScanSummary { - todo!() - // debug!( - // logger, - // "Interpreting a receipt for transaction {:?} but {}; attempt {}, {}ms since sending", - // payable.hash, - // error_msg, - // payable.attempt, - // elapsed_in_ms(payable.timestamp) - // ); - // - // scan_report - // .still_pending - // .push(PendingPayableId::new(payable.rowid, payable.hash)); - // scan_report - } - // Should be used only for pending txs that linger too long impl From for FailedTx { - fn from(_: SentTx) -> Self { - todo!() + fn from(sent_tx: SentTx) -> Self { + FailedTx { + hash: sent_tx.hash, + receiver_address: sent_tx.receiver_address, + amount_minor: sent_tx.amount_minor, + timestamp: sent_tx.timestamp, + gas_price_minor: sent_tx.gas_price_minor, + nonce: sent_tx.nonce, + reason: FailureReason::PendingTooLong, + status: FailureStatus::RetryRequired, + } } } - impl From<(SentTx, TxBlockchainFailure)> for FailedTx { - fn from((sent_tx, blockchain_failure): (SentTx, TxBlockchainFailure)) -> Self { - todo!() + impl From for FailureReason { + fn from(failure: BlockchainTxFailure) -> Self { + match failure { + BlockchainTxFailure::Unrecognized => FailureReason::General, + } + } + } + + impl From<(SentTx, BlockchainTxFailure)> for FailedTx { + fn from((sent_tx, blockchain_failure): (SentTx, BlockchainTxFailure)) -> Self { + FailedTx { + hash: sent_tx.hash, + receiver_address: sent_tx.receiver_address, + amount_minor: sent_tx.amount_minor, + timestamp: sent_tx.timestamp, + gas_price_minor: sent_tx.gas_price_minor, + nonce: sent_tx.nonce, + reason: blockchain_failure.into(), + status: FailureStatus::RetryRequired, + } } } } @@ -506,9 +490,14 @@ mod tests { use masq_lib::constants::WEIS_IN_GWEI; use masq_lib::logger::Logger; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; - use std::time::SystemTime; + use std::time::{Duration, SystemTime}; + use itertools::Itertools; + use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, FailureReason, FailureStatus}; + use crate::accountant::db_access_objects::sent_payable_dao::SentTx; use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::PendingPayableScanSummary; use crate::accountant::test_utils::{make_failed_tx, make_sent_tx}; + use crate::assert_on_testing_enum_with_all_its_variants; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::BlockchainTxFailure; use crate::blockchain::blockchain_interface::data_structures::errors::{BlockchainError, PayableTransactionError}; use crate::blockchain::blockchain_interface::data_structures::{ProcessedPayableFallible, RpcPayableFailure}; @@ -828,6 +817,75 @@ mod tests { assert_eq!(result, false) } + #[test] + fn conversion_between_blockchain_tx_failure_and_failure_reason_works() { + let input_and_expected_results = + vec![(BlockchainTxFailure::Unrecognized, FailureReason::General)]; + let inputs_len = input_and_expected_results.len(); + + let check_nums = input_and_expected_results + .into_iter() + .map(|(input, failure_reason)| match input { + BlockchainTxFailure::Unrecognized => { + let result = FailureReason::from(input); + assert_eq!(result, failure_reason); + 1 + } + }) + .collect_vec(); + + assert_on_testing_enum_with_all_its_variants!(BlockchainTxFailure, check_nums, inputs_len) + } + + #[test] + fn conversion_from_sent_tx_and_blockchain_tx_failure_to_failed_tx_works() { + let sent_tx = set_up_exemplary_sent_tx_for_tx_failure_conversion_test(); + + let result = FailedTx::from((sent_tx.clone(), BlockchainTxFailure::Unrecognized)); + + assert_conversion_into_failed_tx(result, sent_tx, FailureReason::General) + } + + #[test] + fn conversion_from_sent_tx_straight_into_failed_tx_works() { + let sent_tx = set_up_exemplary_sent_tx_for_tx_failure_conversion_test(); + + let result = FailedTx::from(sent_tx.clone()); + + assert_conversion_into_failed_tx(result, sent_tx, FailureReason::PendingTooLong) + } + + fn set_up_exemplary_sent_tx_for_tx_failure_conversion_test() -> SentTx { + SentTx { + hash: make_tx_hash(789), + receiver_address: make_wallet("receiver").address(), + amount_minor: 123_456_789, + timestamp: to_unix_timestamp( + SystemTime::now() + .checked_sub(Duration::from_secs(10_000)) + .unwrap(), + ), + gas_price_minor: gwei_to_wei(424_u64), + nonce: 456_u64.into(), + block_opt: None, + } + } + + fn assert_conversion_into_failed_tx( + result: FailedTx, + original_sent_tx: SentTx, + expected_failure_reason: FailureReason, + ) { + assert_eq!(result.hash, original_sent_tx.hash); + assert_eq!(result.receiver_address, original_sent_tx.receiver_address); + assert_eq!(result.amount_minor, original_sent_tx.amount_minor); + assert_eq!(result.timestamp, original_sent_tx.timestamp); + assert_eq!(result.gas_price_minor, original_sent_tx.gas_price_minor); + assert_eq!(result.nonce, original_sent_tx.nonce); + assert_eq!(result.status, FailureStatus::RetryRequired); + assert_eq!(result.reason, expected_failure_reason); + } + #[test] fn count_total_errors_says_unknown_number_for_early_local_errors() { let early_local_errors = [ diff --git a/node/src/accountant/scanners/test_utils.rs b/node/src/accountant/scanners/test_utils.rs index 38ecfc3af..6d75adf6a 100644 --- a/node/src/accountant/scanners/test_utils.rs +++ b/node/src/accountant/scanners/test_utils.rs @@ -382,8 +382,8 @@ pub enum MarkScanner<'a> { Started(SystemTime), } -// Cautious: Don't compare to another timestamp on a full match; this timestamp is trimmed in -// nanoseconds down to three digits +// Cautious: Don't compare to another timestamp on an exact match. This timestamp is trimmed in +// nanoseconds down to three digits. Works only for the format bound by TIME_FORMATTING_STRING pub fn parse_system_time_from_str(examined_str: &str) -> Vec { let regex = Regex::new(r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})").unwrap(); let captures = regex.captures_iter(examined_str); diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs index d6d9d894a..bc3fb8100 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs @@ -13,6 +13,7 @@ use futures::Future; use serde_json::Value; use std::fmt::Display; use std::str::FromStr; +use variant_count::VariantCount; use web3::contract::{Contract, Options}; use web3::transports::{Batch, Http}; use web3::types::{Address, BlockNumber, Filter, Log, TransactionReceipt}; @@ -45,7 +46,7 @@ impl From for TxStatus { block_number, }) } - (Some(status), _, _) if status == U64::from(0) => todo!(), //TxStatus::Failed(TxBlockchainFailure::Unknown), + (Some(status), _, _) if status == U64::from(0) => todo!(), //TxStatus::Failed(BlockchainTxFailure::Unrecognized), _ => TxStatus::Pending, } } @@ -53,14 +54,22 @@ impl From for TxStatus { #[derive(Debug, PartialEq, Eq, Clone)] pub enum TxStatus { - Failed(TxBlockchainFailure), + Failed(BlockchainTxFailure), Succeeded(TransactionBlock), Pending, } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum TxBlockchainFailure { - Unknown, +#[derive(Clone, Copy, Debug, PartialEq, Eq, VariantCount)] +pub enum BlockchainTxFailure { + Unrecognized, +} + +impl Display for BlockchainTxFailure { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BlockchainTxFailure::Unrecognized => write!(f, "Failure unrecognized"), + } + } } impl Display for TxStatus { @@ -126,7 +135,7 @@ impl TxReceiptRequestError { } } -#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)] +#[derive(Debug, Default, PartialEq, PartialOrd, Ord, Eq, Clone, Copy)] pub struct TransactionBlock { pub block_hash: H256, pub block_number: U64, @@ -258,10 +267,13 @@ mod tests { use masq_lib::test_utils::mock_blockchain_client_server::MBCSBuilder; use masq_lib::utils::find_free_port; use std::str::FromStr; + use itertools::Itertools; use web3::types::{BlockNumber, Bytes, FilterBuilder, Log, TransactionReceipt, U256}; + use crate::accountant::db_access_objects::failed_payable_dao::FailureReason; use crate::accountant::db_access_objects::sent_payable_dao::SentTx; use crate::accountant::test_utils::make_sent_tx; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxWithStatus, TransactionBlock, TxBlockchainFailure, TxStatus}; + use crate::assert_on_testing_enum_with_all_its_variants; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxWithStatus, TransactionBlock, BlockchainTxFailure, TxStatus}; #[test] fn get_transaction_fee_balance_works() { @@ -649,7 +661,10 @@ mod tests { H256::from_low_u64_be(0x5678), ); - assert_eq!(tx_status, TxStatus::Failed(TxBlockchainFailure::Unknown)); + assert_eq!( + tx_status, + TxStatus::Failed(BlockchainTxFailure::Unrecognized) + ); } #[test] @@ -692,7 +707,7 @@ mod tests { fn tx_status_display_works() { // Test Failed assert_eq!( - TxStatus::Failed(TxBlockchainFailure::Unknown).to_string(), + TxStatus::Failed(BlockchainTxFailure::Unrecognized).to_string(), "Failed" ); @@ -711,6 +726,48 @@ mod tests { format!("Succeeded({},0x{:x})", block_number, block_hash) ); } + + #[test] + fn display_for_blockchain_tx_failure_works() { + let input_and_expected_results = + vec![(BlockchainTxFailure::Unrecognized, "Failure unrecognized")]; + let inputs_len = input_and_expected_results.len(); + + let mut check_nums = input_and_expected_results + .into_iter() + .map(|(input, failure_reason)| match input { + BlockchainTxFailure::Unrecognized => { + let result = input.to_string(); + assert_eq!(result, failure_reason); + 1 + } + }) + .collect_vec(); + + // let initially = check_nums.len(); + // check_nums.dedup(); + // let deduped = check_nums.len(); + // assert_eq!( + // deduped, initially, + // "Some variants were processed more than once. Expected: {}, actual: {}", + // initially, deduped + // ); + // assert_eq!( + // inputs_len, + // BlockchainTxFailure::VARIANT_COUNT, + // "Input should contain one example from each variant. Expected: {}, actual: {}", + // BlockchainTxFailure::VARIANT_COUNT, + // inputs_len + // ); + // assert_eq!( + // deduped, + // BlockchainTxFailure::VARIANT_COUNT, + // "We should've gotten one result for each variant. Expected: {}, actual: {}", + // BlockchainTxFailure::VARIANT_COUNT, + // deduped + // ); + assert_on_testing_enum_with_all_its_variants!(BlockchainTxFailure, check_nums, inputs_len) + } // // #[test] // fn tx_status_from_str_works() { diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs index 17861108d..d78bb0659 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs @@ -476,7 +476,7 @@ mod tests { use crate::blockchain::blockchain_bridge::increase_gas_price_by_margin; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, TxStatus}; use crate::accountant::test_utils::make_sent_tx; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxBlockchainFailure}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{BlockchainTxFailure}; #[test] fn constants_are_correct() { @@ -1137,7 +1137,7 @@ mod tests { result[4], TxReceiptResult::RpcResponse(TxWithStatus::new( sent_tx_5, - TxStatus::Failed(TxBlockchainFailure::Unknown) + TxStatus::Failed(BlockchainTxFailure::Unrecognized) )) ); assert_eq!( diff --git a/node/src/test_utils/mod.rs b/node/src/test_utils/mod.rs index b36199b75..a4bed17fe 100644 --- a/node/src/test_utils/mod.rs +++ b/node/src/test_utils/mod.rs @@ -563,12 +563,51 @@ pub mod unshared_test_utils { use std::sync::{Arc, Mutex}; use std::time::Duration; use std::vec; + use variant_count::VariantCount; #[derive(Message)] pub struct AssertionsMessage { pub assertions: Box, } + pub fn capture_numbers_with_separators_from_str( + surveyed_str: &str, + digit_grouped_by: usize, + separator: char, + ) -> Vec { + let regex = + format!("(\\d{{1,{digit_grouped_by}}}(?:{separator}\\d{{{digit_grouped_by}}})+)"); + let re = regex::Regex::new(®ex).unwrap(); + let captures = re.captures_iter(surveyed_str); + captures.map(|capture| capture[1].to_string()).collect() + } + + #[macro_export] + macro_rules! assert_on_testing_enum_with_all_its_variants { + ($enum_type: ty, $check_nums: expr, $inputs_len: expr) => {{ + let mut check_nums = $check_nums; + let initially = check_nums.len(); + check_nums.dedup(); + let deduped = check_nums.len(); + let official_variant_count = <$enum_type>::VARIANT_COUNT; + assert_eq!( + deduped, initially, + "Some variants were processed more than once. Expected: {}, actual: {}", + initially, deduped + ); + assert_eq!( + $inputs_len, official_variant_count, + "Input should contain one example from each variant. Expected: {}, actual: {}", + official_variant_count, $inputs_len + ); + assert_eq!( + deduped, official_variant_count, + "We should've gotten one result for each variant. Expected: {}, actual: {}", + official_variant_count, deduped + ) + }}; + } + pub fn assert_on_initialization_with_panic_on_migration(data_dir: &Path, act: &A) where A: Fn(&Path) + ?Sized, From c42b6d02a0c0a2417a7a04e717508db329b82dcc Mon Sep 17 00:00:00 2001 From: Bert Date: Mon, 14 Jul 2025 23:26:10 +0200 Subject: [PATCH 10/61] GH-642: progressed quite greatelly; fixed many tests; took action against the mark pending payable rowid fn --- .../db_access_objects/payable_dao.rs | 581 +++++++++--------- .../db_big_integer/big_int_db_processor.rs | 1 + node/src/accountant/scanners/mod.rs | 4 +- .../src/accountant/scanners/scanners_utils.rs | 2 +- node/src/accountant/test_utils.rs | 4 +- node/src/blockchain/blockchain_bridge.rs | 79 ++- .../blockchain_interface_web3/utils.rs | 166 +++-- 7 files changed, 419 insertions(+), 418 deletions(-) diff --git a/node/src/accountant/db_access_objects/payable_dao.rs b/node/src/accountant/db_access_objects/payable_dao.rs index a6221140c..045d39b57 100644 --- a/node/src/accountant/db_access_objects/payable_dao.rs +++ b/node/src/accountant/db_access_objects/payable_dao.rs @@ -1,20 +1,26 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. +use crate::accountant::db_access_objects::sent_payable_dao::SentTx; +use crate::accountant::db_access_objects::utils; +use crate::accountant::db_access_objects::utils::{ + sum_i128_values_from_table, to_unix_timestamp, AssemblerFeeder, CustomQuery, DaoFactoryReal, + RangeStmConfig, RowId, TopStmConfig, TxHash, VigilantRusqliteFlatten, +}; use crate::accountant::db_big_integer::big_int_db_processor::KeyVariants::{ PendingPayableRowid, WalletAddress, }; -use crate::accountant::db_big_integer::big_int_db_processor::{BigIntDbProcessor, BigIntDbProcessorReal, BigIntSqlConfig, DisplayableRusqliteParamPair, ParamByUse, SQLParamsBuilder, TableNameDAO, WeiChange, WeiChangeDirection}; -use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; -use crate::accountant::db_access_objects::utils; -use crate::accountant::db_access_objects::utils::{sum_i128_values_from_table, to_unix_timestamp, AssemblerFeeder, CustomQuery, DaoFactoryReal, RangeStmConfig, TopStmConfig, VigilantRusqliteFlatten, TxHash, RowId}; -use crate::accountant::db_access_objects::payable_dao::mark_pending_payable_associated_functions::{ - compose_case_expression, execute_command, serialize_wallets, +use crate::accountant::db_big_integer::big_int_db_processor::{ + BigIntDbProcessor, BigIntDbProcessorReal, BigIntSqlConfig, DisplayableRusqliteParamPair, + ParamByUse, SQLParamsBuilder, TableNameDAO, WeiChange, WeiChangeDirection, }; +use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; use crate::accountant::{checked_conversion, sign_conversion, PendingPayableId}; use crate::database::rusqlite_wrappers::ConnectionWrapper; use crate::sub_lib::wallet::Wallet; +use ethabi::Address; #[cfg(test)] use ethereum_types::{BigEndianHash, U256}; +use itertools::Either; use masq_lib::utils::ExpectValue; #[cfg(test)] use rusqlite::OptionalExtension; @@ -22,10 +28,7 @@ use rusqlite::{Error, Row}; use std::fmt::Debug; use std::str::FromStr; use std::time::SystemTime; -use ethabi::Address; -use itertools::Either; use web3::types::H256; -use crate::accountant::db_access_objects::sent_payable_dao::SentTx; #[derive(Debug, PartialEq, Eq)] pub enum PayableDaoError { @@ -51,7 +54,7 @@ pub trait PayableDao: Debug + Send { fn mark_pending_payables_rowids( &self, - mark_instructions: &[MarkOfPendingPayable], + mark_instructions: &[MarkPendingPayableID], ) -> Result<(), PayableDaoError>; fn transactions_confirmed(&self, confirmed_payables: &[SentTx]) -> Result<(), PayableDaoError>; @@ -76,12 +79,12 @@ impl PayableDaoFactory for DaoFactoryReal { } } -pub struct MarkOfPendingPayable { +pub struct MarkPendingPayableID { pub wallet: Address, pub rowid: RowId, } -impl MarkOfPendingPayable { +impl MarkPendingPayableID { pub fn new(wallet: Address, rowid: RowId) -> Self { todo!() } @@ -129,9 +132,9 @@ impl PayableDao for PayableDaoReal { fn mark_pending_payables_rowids( &self, - mark_instructions: &[MarkOfPendingPayable], + _mark_instructions: &[MarkPendingPayableID], ) -> Result<(), PayableDaoError> { - todo!() + todo!("Will be an object of removal in GH-662") // if wallets_and_rowids.is_empty() { // panic!("broken code: empty input is not permit to enter this method") // } @@ -152,31 +155,28 @@ impl PayableDao for PayableDaoReal { } fn transactions_confirmed(&self, confirmed_payables: &[SentTx]) -> Result<(), PayableDaoError> { - todo!() - // confirmed_payables.iter().try_for_each(|pending_payable_fingerprint| { - // - // let main_sql = "update payable set \ - // balance_high_b = balance_high_b + :balance_high_b, balance_low_b = balance_low_b + :balance_low_b, \ - // last_paid_timestamp = :last_paid, pending_payable_rowid = null where pending_payable_rowid = :rowid"; - // let update_clause_with_compensated_overflow = "update payable set \ - // balance_high_b = :balance_high_b, balance_low_b = :balance_low_b, last_paid_timestamp = :last_paid, \ - // pending_payable_rowid = null where pending_payable_rowid = :rowid"; - // - // let i64_rowid = checked_conversion::(pending_payable_fingerprint.rowid); - // let last_paid = to_unix_timestamp(pending_payable_fingerprint.timestamp); - // let params = SQLParamsBuilder::default() - // .key( PendingPayableRowid(&i64_rowid)) - // .wei_change(WeiChange::new( "balance", pending_payable_fingerprint.amount, WeiChangeDirection::Subtraction)) - // .other_params(vec![ParamByUse::BeforeAndAfterOverflow(DisplayableRusqliteParamPair::new(":last_paid", &last_paid))]) - // .build(); - // - // self.big_int_db_processor.execute(Either::Left(self.conn.as_ref()), BigIntSqlConfig::new( - // main_sql, - // update_clause_with_compensated_overflow, - // params))?; - // - // Ok(()) - // }) + confirmed_payables.iter().try_for_each(|confirmed_payable| { + let main_sql = "update payable set \ + balance_high_b = balance_high_b + :balance_high_b, balance_low_b = balance_low_b + :balance_low_b, \ + last_paid_timestamp = :last_paid, pending_payable_rowid = null where wallet_address = :wallet"; + let update_clause_with_compensated_overflow = "update payable set \ + balance_high_b = :balance_high_b, balance_low_b = :balance_low_b, last_paid_timestamp = :last_paid, \ + pending_payable_rowid = null where wallet_address = :wallet"; + + let wallet = format!("{:?}", confirmed_payable.receiver_address); + let params = SQLParamsBuilder::default() + .key( WalletAddress(&wallet)) + .wei_change(WeiChange::new("balance", confirmed_payable.amount_minor, WeiChangeDirection::Subtraction)) + .other_params(vec![ParamByUse::BeforeAndAfterOverflow(DisplayableRusqliteParamPair::new(":last_paid", &confirmed_payable.timestamp))]) + .build(); + + self.big_int_db_processor.execute(Either::Left(self.conn.as_ref()), BigIntSqlConfig::new( + main_sql, + update_clause_with_compensated_overflow, + params))?; + + Ok(()) + }) } fn non_pending_payables(&self) -> Vec { @@ -394,179 +394,182 @@ impl TableNameDAO for PayableDaoReal { } } -mod mark_pending_payable_associated_functions { - use crate::accountant::comma_joined_stringifiable; - use crate::accountant::db_access_objects::payable_dao::PayableDaoError; - use crate::accountant::db_access_objects::utils::{ - update_rows_and_return_valid_count, VigilantRusqliteFlatten, - }; - use crate::database::rusqlite_wrappers::ConnectionWrapper; - use crate::sub_lib::wallet::Wallet; - use itertools::Itertools; - use rusqlite::Row; - use std::fmt::Display; - - pub fn execute_command( - conn: &dyn ConnectionWrapper, - wallets_and_rowids: &[(&Wallet, u64)], - sql: &str, - ) -> Result<(), PayableDaoError> { - let mut stm = conn.prepare(sql).expect("Internal Error"); - let validator = validate_row_updated; - let rows_affected_res = update_rows_and_return_valid_count(&mut stm, validator); - - match rows_affected_res { - Ok(rows_affected) => match rows_affected { - num if num == wallets_and_rowids.len() => Ok(()), - num => mismatched_row_count_panic(conn, wallets_and_rowids, num), - }, - Err(errs) => { - let err_msg = format!( - "Multi-row update to mark pending payable hit these errors: {:?}", - errs - ); - Err(PayableDaoError::RusqliteError(err_msg)) - } - } - } - - pub fn compose_case_expression(wallets_and_rowids: &[(&Wallet, u64)]) -> String { - //the Wallet type is secure against SQL injections - fn when_clause((wallet, rowid): &(&Wallet, u64)) -> String { - format!("when wallet_address = '{wallet}' then {rowid}") - } - - format!( - "case {} end", - wallets_and_rowids.iter().map(when_clause).join("\n") - ) - } - - pub fn serialize_wallets( - wallets_and_rowids: &[(&Wallet, u64)], - quotes_opt: Option, - ) -> String { - wallets_and_rowids - .iter() - .map(|(wallet, _)| match quotes_opt { - Some(char) => format!("{}{}{}", char, wallet, char), - None => wallet.to_string(), - }) - .join(", ") - } - - fn validate_row_updated(row: &Row) -> Result { - row.get::>(0).map(|opt| opt.is_some()) - } - - fn mismatched_row_count_panic( - conn: &dyn ConnectionWrapper, - wallets_and_rowids: &[(&Wallet, u64)], - actual_count: usize, - ) -> ! { - let serialized_wallets = serialize_wallets(wallets_and_rowids, None); - let expected_count = wallets_and_rowids.len(); - let extension = explanatory_extension(conn, wallets_and_rowids); - panic!( - "Marking pending payable rowid for wallets {serialized_wallets} affected \ - {actual_count} rows but expected {expected_count}. {extension}" - ) - } - - pub(super) fn explanatory_extension( - conn: &dyn ConnectionWrapper, - wallets_and_rowids: &[(&Wallet, u64)], - ) -> String { - let resulting_pairs_collection = - query_resulting_pairs_of_wallets_and_rowids(conn, wallets_and_rowids); - let resulting_pairs_summary = if resulting_pairs_collection.is_empty() { - "".to_string() - } else { - pairs_in_pretty_string(&resulting_pairs_collection, |rowid_opt: &Option| { - match rowid_opt { - Some(rowid) => Box::new(*rowid), - None => Box::new("N/A"), - } - }) - }; - let wallets_and_non_optional_rowids = - pairs_in_pretty_string(wallets_and_rowids, |rowid: &u64| Box::new(*rowid)); - format!( - "\ - The demanded data according to {} looks different from the resulting state {}!. Operation failed.\n\ - Notes:\n\ - a) if row ids have stayed non-populated it points out that writing failed but without the double payment threat,\n\ - b) if some accounts on the resulting side are missing, other kind of serious issues should be suspected but see other\n\ - points to figure out if you were put in danger of double payment,\n\ - c) seeing ids different from those demanded might be a sign of some payments having been doubled.\n\ - The operation which is supposed to clear out the ids of the payments previously requested for this account\n\ - probably had not managed to complete successfully before another payment was requested: preventive measures failed.\n", - wallets_and_non_optional_rowids, resulting_pairs_summary) - } - - fn query_resulting_pairs_of_wallets_and_rowids( - conn: &dyn ConnectionWrapper, - wallets_and_rowids: &[(&Wallet, u64)], - ) -> Vec<(Wallet, Option)> { - let select_dealt_accounts = - format!( - "select wallet_address, pending_payable_rowid from payable where wallet_address in ({})", - serialize_wallets(wallets_and_rowids, Some('\'')) - ); - let row_processor = |row: &Row| { - Ok(( - row.get::(0) - .expect("database corrupt: wallet addresses found in bad format"), - row.get::>(1) - .expect("database_corrupt: rowid found in bad format"), - )) - }; - conn.prepare(&select_dealt_accounts) - .expect("select failed") - .query_map([], row_processor) - .expect("no args yet binding failed") - .vigilant_flatten() - .collect() - } - - fn pairs_in_pretty_string( - pairs: &[(W, R)], - rowid_pretty_writer: fn(&R) -> Box, - ) -> String { - comma_joined_stringifiable(pairs, |(wallet, rowid)| { - format!( - "( Wallet: {}, Rowid: {} )", - wallet, - rowid_pretty_writer(rowid) - ) - }) - } -} +// TODO Will be an object of removal in GH-662 +// mod mark_pending_payable_associated_functions { +// use crate::accountant::comma_joined_stringifiable; +// use crate::accountant::db_access_objects::payable_dao::{MarkPendingPayableID, PayableDaoError}; +// use crate::accountant::db_access_objects::utils::{ +// update_rows_and_return_valid_count, VigilantRusqliteFlatten, +// }; +// use crate::database::rusqlite_wrappers::ConnectionWrapper; +// use crate::sub_lib::wallet::Wallet; +// use itertools::Itertools; +// use rusqlite::Row; +// use std::fmt::Display; +// +// pub fn execute_command( +// conn: &dyn ConnectionWrapper, +// wallets_and_rowids: &[(&Wallet, u64)], +// sql: &str, +// ) -> Result<(), PayableDaoError> { +// let mut stm = conn.prepare(sql).expect("Internal Error"); +// let validator = validate_row_updated; +// let rows_affected_res = update_rows_and_return_valid_count(&mut stm, validator); +// +// match rows_affected_res { +// Ok(rows_affected) => match rows_affected { +// num if num == wallets_and_rowids.len() => Ok(()), +// num => mismatched_row_count_panic(conn, wallets_and_rowids, num), +// }, +// Err(errs) => { +// let err_msg = format!( +// "Multi-row update to mark pending payable hit these errors: {:?}", +// errs +// ); +// Err(PayableDaoError::RusqliteError(err_msg)) +// } +// } +// } +// +// pub fn compose_case_expression(wallets_and_rowids: &[(&Wallet, u64)]) -> String { +// //the Wallet type is secure against SQL injections +// fn when_clause((wallet, rowid): &(&Wallet, u64)) -> String { +// format!("when wallet_address = '{wallet}' then {rowid}") +// } +// +// format!( +// "case {} end", +// wallets_and_rowids.iter().map(when_clause).join("\n") +// ) +// } +// +// pub fn serialize_wallets( +// wallets_and_rowids: &[MarkPendingPayableID], +// quotes_opt: Option, +// ) -> String { +// wallets_and_rowids +// .iter() +// .map(|(wallet, _)| match quotes_opt { +// Some(char) => format!("{}{}{}", char, wallet, char), +// None => wallet.to_string(), +// }) +// .join(", ") +// } +// +// fn validate_row_updated(row: &Row) -> Result { +// row.get::>(0).map(|opt| opt.is_some()) +// } +// +// fn mismatched_row_count_panic( +// conn: &dyn ConnectionWrapper, +// wallets_and_rowids: &[(&Wallet, u64)], +// actual_count: usize, +// ) -> ! { +// let serialized_wallets = serialize_wallets(wallets_and_rowids, None); +// let expected_count = wallets_and_rowids.len(); +// let extension = explanatory_extension(conn, wallets_and_rowids); +// panic!( +// "Marking pending payable rowid for wallets {serialized_wallets} affected \ +// {actual_count} rows but expected {expected_count}. {extension}" +// ) +// } +// +// pub(super) fn explanatory_extension( +// conn: &dyn ConnectionWrapper, +// wallets_and_rowids: &[(&Wallet, u64)], +// ) -> String { +// let resulting_pairs_collection = +// query_resulting_pairs_of_wallets_and_rowids(conn, wallets_and_rowids); +// let resulting_pairs_summary = if resulting_pairs_collection.is_empty() { +// "".to_string() +// } else { +// pairs_in_pretty_string(&resulting_pairs_collection, |rowid_opt: &Option| { +// match rowid_opt { +// Some(rowid) => Box::new(*rowid), +// None => Box::new("N/A"), +// } +// }) +// }; +// let wallets_and_non_optional_rowids = +// pairs_in_pretty_string(wallets_and_rowids, |rowid: &u64| Box::new(*rowid)); +// format!( +// "\ +// The demanded data according to {} looks different from the resulting state {}!. Operation failed.\n\ +// Notes:\n\ +// a) if row ids have stayed non-populated it points out that writing failed but without the double payment threat,\n\ +// b) if some accounts on the resulting side are missing, other kind of serious issues should be suspected but see other\n\ +// points to figure out if you were put in danger of double payment,\n\ +// c) seeing ids different from those demanded might be a sign of some payments having been doubled.\n\ +// The operation which is supposed to clear out the ids of the payments previously requested for this account\n\ +// probably had not managed to complete successfully before another payment was requested: preventive measures failed.\n", +// wallets_and_non_optional_rowids, resulting_pairs_summary) +// } +// +// fn query_resulting_pairs_of_wallets_and_rowids( +// conn: &dyn ConnectionWrapper, +// wallets_and_rowids: &[(&Wallet, u64)], +// ) -> Vec<(Wallet, Option)> { +// let select_dealt_accounts = +// format!( +// "select wallet_address, pending_payable_rowid from payable where wallet_address in ({})", +// serialize_wallets(wallets_and_rowids, Some('\'')) +// ); +// let row_processor = |row: &Row| { +// Ok(( +// row.get::(0) +// .expect("database corrupt: wallet addresses found in bad format"), +// row.get::>(1) +// .expect("database_corrupt: rowid found in bad format"), +// )) +// }; +// conn.prepare(&select_dealt_accounts) +// .expect("select failed") +// .query_map([], row_processor) +// .expect("no args yet binding failed") +// .vigilant_flatten() +// .collect() +// } +// +// fn pairs_in_pretty_string( +// pairs: &[(W, R)], +// rowid_pretty_writer: fn(&R) -> Box, +// ) -> String { +// comma_joined_stringifiable(pairs, |(wallet, rowid)| { +// format!( +// "( Wallet: {}, Rowid: {} )", +// wallet, +// rowid_pretty_writer(rowid) +// ) +// }) +// } +// } #[cfg(test)] mod tests { use super::*; - use crate::accountant::db_access_objects::utils::{from_unix_timestamp, current_unix_timestamp, to_unix_timestamp}; + use crate::accountant::db_access_objects::sent_payable_dao::SentTx; + use crate::accountant::db_access_objects::utils::{ + current_unix_timestamp, from_unix_timestamp, to_unix_timestamp, + }; use crate::accountant::gwei_to_wei; - use crate::accountant::db_access_objects::payable_dao::mark_pending_payable_associated_functions::explanatory_extension; - use crate::accountant::test_utils::{assert_account_creation_fn_fails_on_finding_wrong_columns_and_value_types, make_payable_account, make_sent_tx, trick_rusqlite_with_read_only_conn}; + use crate::accountant::test_utils::{ + assert_account_creation_fn_fails_on_finding_wrong_columns_and_value_types, make_sent_tx, + trick_rusqlite_with_read_only_conn, + }; use crate::blockchain::test_utils::make_tx_hash; - use crate::database::rusqlite_wrappers::ConnectionWrapperReal; use crate::database::db_initializer::{ DbInitializationConfig, DbInitializer, DbInitializerReal, DATABASE_FILE, }; + use crate::database::rusqlite_wrappers::ConnectionWrapperReal; use crate::test_utils::make_wallet; + use itertools::Itertools; use masq_lib::messages::TopRecordsOrdering::{Age, Balance}; use masq_lib::test_utils::utils::ensure_node_home_directory_exists; + use rusqlite::ToSql; use rusqlite::{Connection, OpenFlags}; - use rusqlite::{ToSql}; use std::path::Path; use std::str::FromStr; - use std::time::{Duration, UNIX_EPOCH}; - use itertools::Itertools; - use libc::iovec; - use crate::accountant::db_access_objects::sent_payable_dao::SentTx; - use crate::database::test_utils::ConnectionWrapperMock; + use std::time::Duration; #[test] fn more_money_payable_works_for_new_address() { @@ -713,7 +716,7 @@ mod tests { fn mark_pending_payables_marks_pending_transactions_for_new_addresses() { //the extra unchanged record checks the safety of right count of changed rows; //experienced serious troubles in the past - todo!("shall we preserve this test?") + // TODO Will be an object of removal in GH-662 // let home_dir = ensure_node_home_directory_exists( // "payable_dao", // "mark_pending_payables_marks_pending_transactions_for_new_addresses", @@ -786,22 +789,22 @@ mod tests { } #[test] - #[should_panic(expected = "\ - Marking pending payable rowid for wallets 0x000000000000000000000000000000626f6f6761, \ - 0x0000000000000000000000000000007961686f6f affected 0 rows but expected 2. \ - The demanded data according to ( Wallet: 0x000000000000000000000000000000626f6f6761, Rowid: 456 ), \ - ( Wallet: 0x0000000000000000000000000000007961686f6f, Rowid: 789 ) looks different from \ - the resulting state ( Wallet: 0x000000000000000000000000000000626f6f6761, Rowid: 456 )!. Operation failed.\n\ - Notes:\n\ - a) if row ids have stayed non-populated it points out that writing failed but without the double payment threat,\n\ - b) if some accounts on the resulting side are missing, other kind of serious issues should be suspected but see other\n\ - points to figure out if you were put in danger of double payment,\n\ - c) seeing ids different from those demanded might be a sign of some payments having been doubled.\n\ - The operation which is supposed to clear out the ids of the payments previously requested for this account\n\ - probably had not managed to complete successfully before another payment was requested: preventive measures failed.")] + // #[should_panic(expected = "\ + // Marking pending payable rowid for wallets 0x000000000000000000000000000000626f6f6761, \ + // 0x0000000000000000000000000000007961686f6f affected 0 rows but expected 2. \ + // The demanded data according to ( Wallet: 0x000000000000000000000000000000626f6f6761, Rowid: 456 ), \ + // ( Wallet: 0x0000000000000000000000000000007961686f6f, Rowid: 789 ) looks different from \ + // the resulting state ( Wallet: 0x000000000000000000000000000000626f6f6761, Rowid: 456 )!. Operation failed.\n\ + // Notes:\n\ + // a) if row ids have stayed non-populated it points out that writing failed but without the double payment threat,\n\ + // b) if some accounts on the resulting side are missing, other kind of serious issues should be suspected but see other\n\ + // points to figure out if you were put in danger of double payment,\n\ + // c) seeing ids different from those demanded might be a sign of some payments having been doubled.\n\ + // The operation which is supposed to clear out the ids of the payments previously requested for this account\n\ + // probably had not managed to complete successfully before another payment was requested: preventive measures failed.")] fn mark_pending_payables_rowids_returned_different_row_count_than_expected_with_one_account_missing_and_one_unmodified( ) { - todo!("shall we preserve this test?") + // TODO Will be an object of removal in GH-662 // let home_dir = ensure_node_home_directory_exists( // "payable_dao", // "mark_pending_payables_rowids_returned_different_row_count_than_expected_with_one_account_missing_and_one_unmodified", @@ -828,60 +831,62 @@ mod tests { #[test] fn explanatory_extension_shows_resulting_account_with_unpopulated_rowid() { - let home_dir = ensure_node_home_directory_exists( - "payable_dao", - "explanatory_extension_shows_resulting_account_with_unpopulated_rowid", - ); - let wallet_1 = make_wallet("hooga"); - let rowid_1 = 550; - let wallet_2 = make_wallet("booga"); - let rowid_2 = 555; - let conn = DbInitializerReal::default() - .initialize(&home_dir, DbInitializationConfig::test_default()) - .unwrap(); - let record_seeds = [ - (&wallet_1.to_string(), 12345, 1_000_000_000, None), - (&wallet_2.to_string(), 23456, 1_000_000_111, Some(540)), - ]; - record_seeds - .into_iter() - .for_each(|(wallet, balance, timestamp, rowid_opt)| { - insert_payable_record_fn(&*conn, wallet, balance, timestamp, rowid_opt) - }); - - let result = explanatory_extension(&*conn, &[(&wallet_1, rowid_1), (&wallet_2, rowid_2)]); - - assert_eq!(result, "\ - The demanded data according to ( Wallet: 0x000000000000000000000000000000686f6f6761, Rowid: 550 ), \ - ( Wallet: 0x000000000000000000000000000000626f6f6761, Rowid: 555 ) looks different from \ - the resulting state ( Wallet: 0x000000000000000000000000000000626f6f6761, Rowid: 540 ), \ - ( Wallet: 0x000000000000000000000000000000686f6f6761, Rowid: N/A )!. \ - Operation failed.\n\ - Notes:\n\ - a) if row ids have stayed non-populated it points out that writing failed but without the double \ - payment threat,\n\ - b) if some accounts on the resulting side are missing, other kind of serious issues should be \ - suspected but see other\npoints to figure out if you were put in danger of double payment,\n\ - c) seeing ids different from those demanded might be a sign of some payments having been doubled.\n\ - The operation which is supposed to clear out the ids of the payments previously requested for \ - this account\nprobably had not managed to complete successfully before another payment was \ - requested: preventive measures failed.\n".to_string()) + // TODO Will be an object of removal in GH-662 + // let home_dir = ensure_node_home_directory_exists( + // "payable_dao", + // "explanatory_extension_shows_resulting_account_with_unpopulated_rowid", + // ); + // let wallet_1 = make_wallet("hooga"); + // let rowid_1 = 550; + // let wallet_2 = make_wallet("booga"); + // let rowid_2 = 555; + // let conn = DbInitializerReal::default() + // .initialize(&home_dir, DbInitializationConfig::test_default()) + // .unwrap(); + // let record_seeds = [ + // (&wallet_1.to_string(), 12345, 1_000_000_000, None), + // (&wallet_2.to_string(), 23456, 1_000_000_111, Some(540)), + // ]; + // record_seeds + // .into_iter() + // .for_each(|(wallet, balance, timestamp, rowid_opt)| { + // insert_payable_record_fn(&*conn, wallet, balance, timestamp, rowid_opt) + // }); + // + // let result = explanatory_extension(&*conn, &[(&wallet_1, rowid_1), (&wallet_2, rowid_2)]); + // + // assert_eq!(result, "\ + // The demanded data according to ( Wallet: 0x000000000000000000000000000000686f6f6761, Rowid: 550 ), \ + // ( Wallet: 0x000000000000000000000000000000626f6f6761, Rowid: 555 ) looks different from \ + // the resulting state ( Wallet: 0x000000000000000000000000000000626f6f6761, Rowid: 540 ), \ + // ( Wallet: 0x000000000000000000000000000000686f6f6761, Rowid: N/A )!. \ + // Operation failed.\n\ + // Notes:\n\ + // a) if row ids have stayed non-populated it points out that writing failed but without the double \ + // payment threat,\n\ + // b) if some accounts on the resulting side are missing, other kind of serious issues should be \ + // suspected but see other\npoints to figure out if you were put in danger of double payment,\n\ + // c) seeing ids different from those demanded might be a sign of some payments having been doubled.\n\ + // The operation which is supposed to clear out the ids of the payments previously requested for \ + // this account\nprobably had not managed to complete successfully before another payment was \ + // requested: preventive measures failed.\n".to_string()) } #[test] fn mark_pending_payables_rowids_handles_general_sql_error() { - todo!("shall we preserve this test?") + // TODO Will be an object of removal in GH-662 // let home_dir = ensure_node_home_directory_exists( // "payable_dao", // "mark_pending_payables_rowids_handles_general_sql_error", // ); // let wallet = make_wallet("booga"); // let rowid = 656; + // let single_mark_instruction = MarkPendingPayableID::new(wallet.address(), rowid); // let conn = payable_read_only_conn(&home_dir); // let conn_wrapped = ConnectionWrapperReal::new(conn); // let subject = PayableDaoReal::new(Box::new(conn_wrapped)); // - // let result = subject.mark_pending_payables_rowids(&[(&wallet, rowid)]); + // let result = subject.mark_pending_payables_rowids(&[single_mark_instruction]); // // assert_eq!( // result, @@ -895,12 +900,13 @@ mod tests { } #[test] - #[should_panic(expected = "broken code: empty input is not permit to enter this method")] + //#[should_panic(expected = "broken code: empty input is not permit to enter this method")] fn mark_pending_payables_rowids_is_strict_about_empty_input() { - let wrapped_conn = ConnectionWrapperMock::default(); - let subject = PayableDaoReal::new(Box::new(wrapped_conn)); - - let _ = subject.mark_pending_payables_rowids(&[]); + // TODO Will be an object of removal in GH-662 + // let wrapped_conn = ConnectionWrapperMock::default(); + // let subject = PayableDaoReal::new(Box::new(wrapped_conn)); + // + // let _ = subject.mark_pending_payables_rowids(&[]); } struct TestSetupValuesHolder { @@ -954,14 +960,17 @@ mod tests { .map(|(idx, test_inputs)| { insert_payable_record_fn( conn, - &test_inputs.wallet.to_string(), + &format!("{:?}", test_inputs.wallet), i128::try_from(test_inputs.initial_amount_wei).unwrap(), to_unix_timestamp(test_inputs.previous_timestamp), // TODO argument will be eliminated in GH-662 None, ); - - let mut sent_tx = make_sent_tx(idx as u64 * 1234); + let mut sent_tx = make_sent_tx((idx as u64 + 1) * 1234); + sent_tx.hash = test_inputs.hash; + sent_tx.amount_minor = test_inputs.balance_change; + sent_tx.receiver_address = test_inputs.wallet; + sent_tx.timestamp = to_unix_timestamp(test_inputs.new_payable_timestamp); sent_tx.amount_minor = test_inputs.balance_change; TxWalletAndTimestamp { @@ -1034,29 +1043,37 @@ mod tests { ]); assert_eq!(result, Ok(())); - // TODO yes these are unsensible now but it will eventually be all cleaned up with GH-662 + let expected_last_paid_timestamp_1 = + from_unix_timestamp(to_unix_timestamp(setup_holder.account_1.previous_timestamp)); + let expected_last_paid_timestamp_2 = + from_unix_timestamp(to_unix_timestamp(setup_holder.account_2.previous_timestamp)); + // TODO yes these pending_payable_opt values are unsensible now but it will eventually be all cleaned up with GH-662 let expected_status_before_1 = PayableAccount { wallet: wallet_1.clone(), balance_wei: initial_amount_1, - last_paid_timestamp: setup_holder.account_1.previous_timestamp, - pending_payable_opt: None, //hash is just garbage + last_paid_timestamp: expected_last_paid_timestamp_1, + pending_payable_opt: None, }; let expected_status_before_2 = PayableAccount { wallet: wallet_2.clone(), balance_wei: initial_amount_2, - last_paid_timestamp: setup_holder.account_2.previous_timestamp, - pending_payable_opt: None, //hash is just garbage + last_paid_timestamp: expected_last_paid_timestamp_2, + pending_payable_opt: None, }; let expected_resulting_status_1 = PayableAccount { wallet: wallet_1.clone(), balance_wei: expected_balance_after_1, - last_paid_timestamp: setup_holder.account_1.previous_timestamp, + last_paid_timestamp: from_unix_timestamp( + setup_holder.account_1.pending_payable.timestamp, + ), pending_payable_opt: None, }; let expected_resulting_status_2 = PayableAccount { wallet: wallet_2.clone(), balance_wei: expected_balance_after_2, - last_paid_timestamp: setup_holder.account_2.previous_timestamp, + last_paid_timestamp: from_unix_timestamp( + setup_holder.account_2.pending_payable.timestamp, + ), pending_payable_opt: None, }; assert_eq!(status_1_before_opt, Some(expected_status_before_1)); @@ -1075,7 +1092,8 @@ mod tests { ); let conn = payable_read_only_conn(&home_dir); let conn_wrapped = Box::new(ConnectionWrapperReal::new(conn)); - let confirmed_transaction = make_sent_tx(5); + let mut confirmed_transaction = make_sent_tx(5); + confirmed_transaction.amount_minor = 12345; let wallet_address = confirmed_transaction.receiver_address; let subject = PayableDaoReal::new(conn_wrapped); @@ -1085,7 +1103,7 @@ mod tests { result, Err(PayableDaoError::RusqliteError(format!( "Error from invalid update command for payable table and change of -12345 wei to \ - creditor {} with error 'attempt to write a readonly database'", + 'wallet_address = {:?}' with error 'attempt to write a readonly database'", wallet_address ))) ) @@ -1130,32 +1148,27 @@ mod tests { let wallet_2 = Wallet::from(setup_holder.account_2.pending_payable.receiver_address); conn.prepare("delete from payable where wallet_address = ?") .unwrap() - .execute(&[&wallet_1.to_string()]) + .execute(&[&wallet_2.to_string()]) .unwrap(); let subject = PayableDaoReal::new(conn); - let expected_account = PayableAccount { - wallet: wallet_1.clone(), - balance_wei: 1_111_111 - setup_holder.account_1.pending_payable.amount_minor, - last_paid_timestamp: from_unix_timestamp( - setup_holder.account_1.pending_payable.timestamp, - ), - pending_payable_opt: None, - }; let result = subject.transactions_confirmed(&[ setup_holder.account_1.pending_payable, setup_holder.account_2.pending_payable, ]); + let expected_err_msg = format!( + "Expected 1 row to be changed for the unique key \ + {} but got this count: 0", + wallet_2 + ); assert_eq!( result, - Err(PayableDaoError::RusqliteError( - "Expected 1 row to be changed for the unique key 792 but got this count: 0" - .to_string() - )) + Err(PayableDaoError::RusqliteError(expected_err_msg)) ); - let account_1_opt = subject.account_status(&wallet_1); - assert_eq!(account_1_opt, Some(expected_account)); + let expected_resulting_balance_1 = 1_111_111 - 111_111; + let account_1 = subject.account_status(&wallet_1).unwrap(); + assert_eq!(account_1.balance_wei, expected_resulting_balance_1); let account_2_opt = subject.account_status(&wallet_2); assert_eq!(account_2_opt, None); } diff --git a/node/src/accountant/db_big_integer/big_int_db_processor.rs b/node/src/accountant/db_big_integer/big_int_db_processor.rs index 3ef15278d..c362e3740 100644 --- a/node/src/accountant/db_big_integer/big_int_db_processor.rs +++ b/node/src/accountant/db_big_integer/big_int_db_processor.rs @@ -322,6 +322,7 @@ pub trait DisplayableParamValue: ToSql + Display {} impl DisplayableParamValue for i64 {} impl DisplayableParamValue for &str {} +impl DisplayableParamValue for String {} impl DisplayableParamValue for Wallet {} #[derive(Default)] diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index 92419b019..e3d9d57b7 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -5,7 +5,7 @@ pub mod scan_schedulers; pub mod scanners_utils; pub mod test_utils; -use crate::accountant::db_access_objects::payable_dao::{MarkOfPendingPayable, PayableAccount, PayableDao, PayableDaoError}; +use crate::accountant::db_access_objects::payable_dao::{MarkPendingPayableID, PayableAccount, PayableDao, PayableDaoError}; use crate::accountant::db_access_objects::receivable_dao::ReceivableDao; use crate::accountant::payment_adjuster::{PaymentAdjuster, PaymentAdjusterReal}; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableTransactingErrorEnum::{ @@ -729,7 +729,7 @@ impl PayableScanner { // fn separate_existent_and_nonexistent_fingerprints<'a>( // &'a self, // sent_payables: &[&'a PendingPayable], - // ) -> (Vec, Vec) { + // ) -> (Vec, Vec) { // let actual_sent_payables_simple_total = sent_payables.len(); // // let hashset_with_actual_sent_payable_hashes = sent_payables diff --git a/node/src/accountant/scanners/scanners_utils.rs b/node/src/accountant/scanners/scanners_utils.rs index efbc68fd0..65bbe926d 100644 --- a/node/src/accountant/scanners/scanners_utils.rs +++ b/node/src/accountant/scanners/scanners_utils.rs @@ -599,7 +599,7 @@ mod tests { assert_eq!(errs, Some(LocallyCausedError(error))); TestLogHandler::new().exists_log_containing( "WARN: test_logger: Any persisted data from \ - failed process will be deleted. Caused by: Sending phase: \"Bad luck\". Signed and hashed \ + the failed process will be deleted. Caused by: Sending phase: \"Bad luck\". Signed and hashed \ transactions: 0x000000000000000000000000000000000000000000000000000000000000007b", ); } diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs index d9b8af9ac..f6d778db2 100644 --- a/node/src/accountant/test_utils.rs +++ b/node/src/accountant/test_utils.rs @@ -4,7 +4,7 @@ use crate::accountant::db_access_objects::banned_dao::{BannedDao, BannedDaoFactory}; use crate::accountant::db_access_objects::failed_payable_dao::{FailedPayableDao, FailedPayableDaoError, FailedPayableDaoFactory, FailedTx, FailureReason, FailureRetrieveCondition, FailureStatus}; -use crate::accountant::db_access_objects::payable_dao::{MarkOfPendingPayable, PayableAccount, PayableDao, PayableDaoError, PayableDaoFactory}; +use crate::accountant::db_access_objects::payable_dao::{MarkPendingPayableID, PayableAccount, PayableDao, PayableDaoError, PayableDaoFactory}; use crate::accountant::db_access_objects::sent_payable_dao::{SentPayableDao, SentPayableDaoFactory}; use crate::accountant::db_access_objects::receivable_dao::{ ReceivableAccount, ReceivableDao, ReceivableDaoError, ReceivableDaoFactory, @@ -611,7 +611,7 @@ impl PayableDao for PayableDaoMock { fn mark_pending_payables_rowids( &self, - mark_instructions: &[MarkOfPendingPayable], + mark_instructions: &[MarkPendingPayableID], ) -> Result<(), PayableDaoError> { todo!() // self.mark_pending_payables_rowids_params diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index fa853aa2c..c333a9f05 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -171,8 +171,8 @@ pub struct RegisterNewPendingSentTxMessage { } impl RegisterNewPendingSentTxMessage { - pub fn new(sent_txs: Vec) -> Self { - todo!() + pub fn new(new_sent_txs: Vec) -> Self { + Self { new_sent_txs } } } @@ -415,40 +415,28 @@ impl BlockchainBridge { &mut self, msg: RequestTransactionReceipts, ) -> Box> { - todo!() - // let logger = self.logger.clone(); - // let accountant_recipient = self - // .pending_payable_confirmation - // .report_transaction_receipts_sub_opt - // .clone() - // .expect("Accountant is unbound"); - // - // let transaction_hashes = msg - // .pending_payable_fingerprints - // .iter() - // .map(|finger_print| finger_print.hash) - // .collect::>(); - // Box::new( - // self.blockchain_interface - // .process_transaction_receipts(transaction_hashes) - // .map_err(move |e| e.to_string()) - // .and_then(move |transaction_receipts_results| { - // Self::log_status_of_tx_receipts(&logger, &transaction_receipts_results); - // - // let pairs = transaction_receipts_results - // .into_iter() - // .zip(msg.pending_payable_fingerprints.into_iter()) - // .collect_vec(); - // - // accountant_recipient - // .try_send(TxStatusReport { - // response_skeleton_opt: msg.response_skeleton_opt, - // }) - // .expect("Accountant is dead"); - // - // Ok(()) - // }), - // ) + let logger = self.logger.clone(); + let accountant_recipient = self + .pending_payable_confirmation + .report_transaction_receipts_sub_opt + .clone() + .expect("Accountant is unbound"); + Box::new( + self.blockchain_interface + .process_transaction_receipts(msg.sent_tx) + .map_err(move |e| e.to_string()) + .and_then(move |tx_receipt_results| { + Self::log_status_of_tx_receipts(&logger, &tx_receipt_results); + accountant_recipient + .try_send(TxStatusReport { + results: tx_receipt_results, + response_skeleton_opt: msg.response_skeleton_opt, + }) + .expect("Accountant is dead"); + + Ok(()) + }), + ) } fn handle_scan_future(&mut self, handler: F, scan_type: ScanType, msg: M) @@ -544,7 +532,7 @@ impl SubsFactory for BlockchainBridgeSub mod tests { use super::*; use crate::accountant::db_access_objects::payable_dao::PayableAccount; - use crate::accountant::db_access_objects::utils::from_unix_timestamp; + use crate::accountant::db_access_objects::utils::{from_unix_timestamp, to_unix_timestamp}; use crate::accountant::scanners::payable_scanner_extension::test_utils::BlockchainAgentMock; use crate::accountant::test_utils::{make_payable_account, make_sent_tx}; use crate::accountant::test_utils::{make_priced_qualified_payables}; @@ -912,7 +900,6 @@ mod tests { } ); let first_actual_sent_tx = ®ister_new_pending_sent_tx_msg.new_sent_txs[0]; - let second_actual_sent_tx = ®ister_new_pending_sent_tx_msg.new_sent_txs[0]; assert_eq!( first_actual_sent_tx.receiver_address, account.wallet.address() @@ -920,11 +907,11 @@ mod tests { assert_eq!(first_actual_sent_tx.hash, expected_hash); assert_eq!(first_actual_sent_tx.amount_minor, account.balance_wei); assert_eq!(first_actual_sent_tx.gas_price_minor, 111_222_333); - assert_eq!(first_actual_sent_tx.nonce, 0); + assert_eq!(first_actual_sent_tx.nonce, 0x20); assert_eq!(first_actual_sent_tx.block_opt, None); assert!( - time_before <= from_unix_timestamp(first_actual_sent_tx.timestamp) - && from_unix_timestamp(first_actual_sent_tx.timestamp) <= time_after, + to_unix_timestamp(time_before) <= first_actual_sent_tx.timestamp + && first_actual_sent_tx.timestamp <= to_unix_timestamp(time_after), "We thought the timestamp was between {:?} and {:?}, but it was {:?}", time_before, time_after, @@ -1167,7 +1154,8 @@ mod tests { #[test] fn blockchain_bridge_processes_requests_for_a_complete_and_null_transaction_receipt() { let (accountant, _, accountant_recording_arc) = make_recorder(); - let accountant = accountant.system_stop_conditions(match_lazily_every_type_id!(ScanError)); + let accountant = + accountant.system_stop_conditions(match_lazily_every_type_id!(TxStatusReport)); let sent_tx_1 = make_sent_tx(123); let hash_1 = sent_tx_1.hash; let sent_tx_2 = make_sent_tx(456); @@ -1989,10 +1977,11 @@ mod tests { ); let system = System::new("test"); let accountant_addr = accountant - .system_stop_conditions(match_lazily_every_type_id!(ScanError)) + .system_stop_conditions(match_lazily_every_type_id!(ReceivedPayments)) .start(); subject.received_payments_subs_opt = Some(accountant_addr.clone().recipient()); subject.scan_error_subs_opt = Some(accountant_addr.recipient()); + subject.handle_scan_future( BlockchainBridge::handle_retrieve_transactions, ScanType::Receivables, @@ -2001,7 +1990,9 @@ mod tests { system.run(); let accountant_recording = accountant_recording_arc.lock().unwrap(); - let msg_opt = accountant_recording.get_record_opt::(0); + let received_msg = accountant_recording.get_record::(0); + assert_eq!(received_msg.new_start_block, BlockMarker::Value(0xc8 + 1)); + let msg_opt = accountant_recording.get_record_opt::(1); assert_eq!(msg_opt, None, "We didnt expect a scan error: {:?}", msg_opt); } diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs index d3f607a08..6bb78e252 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs @@ -2,7 +2,7 @@ use crate::accountant::db_access_objects::payable_dao::PayableAccount; use crate::accountant::db_access_objects::sent_payable_dao::SentTx; -use crate::accountant::db_access_objects::utils::TxHash; +use crate::accountant::db_access_objects::utils::{to_unix_timestamp, TxHash}; use crate::accountant::scanners::payable_scanner_extension::msgs::PricedQualifiedPayables; use crate::accountant::PendingPayable; use crate::blockchain::blockchain_agent::agent_web3::BlockchainAgentWeb3; @@ -39,11 +39,6 @@ pub struct BlockchainAgentFutureResult { pub transaction_fee_balance: U256, pub masq_token_balance: U256, } -pub fn advance_used_nonce(current_nonce: U256) -> U256 { - current_nonce - .checked_add(U256::one()) - .expect("unexpected limits") -} // TODO using these three vectors like this is dangerous; who guarantees that all three have their // items sorted in the right order? @@ -204,7 +199,7 @@ pub fn sign_and_append_payment( consuming_wallet: Wallet, nonce: U256, gas_price_in_wei: u128, -) -> HashAndAmount { +) -> TxHash { let signed_tx = sign_transaction( chain, web3_batch, @@ -216,10 +211,7 @@ pub fn sign_and_append_payment( ); append_signed_transaction_to_batch(web3_batch, signed_tx.raw_transaction); - HashAndAmount { - hash: signed_tx.transaction_hash, - amount_minor: recipient.balance_wei, - } + signed_tx.transaction_hash } pub fn append_signed_transaction_to_batch(web3_batch: &Web3>, raw_transaction: Bytes) { @@ -228,38 +220,50 @@ pub fn append_signed_transaction_to_batch(web3_batch: &Web3>, raw_tr } pub fn sign_and_append_multiple_payments( + now: SystemTime, logger: &Logger, chain: Chain, web3_batch: &Web3>, consuming_wallet: Wallet, - mut pending_nonce: U256, + initial_pending_nonce: U256, accounts: &PricedQualifiedPayables, ) -> Vec { - let mut hash_and_amount_list = vec![]; - accounts.payables.iter().for_each(|payable_pack| { - let payable = &payable_pack.payable; - debug!( - logger, - "Preparing payable future of {} wei to {} with nonce {}", - payable.balance_wei.separate_with_commas(), - payable.wallet, - pending_nonce - ); - - let hash_and_amount = sign_and_append_payment( - chain, - web3_batch, - payable, - consuming_wallet.clone(), - pending_nonce, - payable_pack.gas_price_minor, - ); - - pending_nonce = advance_used_nonce(pending_nonce); - hash_and_amount_list.push(hash_and_amount); - }); - - todo!() + let unix_mow = to_unix_timestamp(now); + accounts + .payables + .iter() + .enumerate() + .map(|(idx, payable_pack)| { + let current_pending_nonce = initial_pending_nonce + U256::from(idx); + let payable = &payable_pack.payable; + debug!( + logger, + "Preparing tx of {} wei to {} with nonce {}", + payable.balance_wei.separate_with_commas(), + payable.wallet, + current_pending_nonce + ); + + let hash = sign_and_append_payment( + chain, + web3_batch, + payable, + consuming_wallet.clone(), + current_pending_nonce, + payable_pack.gas_price_minor, + ); + + SentTx { + hash, + receiver_address: payable.wallet.address(), + amount_minor: payable.balance_wei, + timestamp: unix_mow, + gas_price_minor: payable_pack.gas_price_minor, + nonce: current_pending_nonce.as_u64(), + block_opt: None, + } + }) + .collect() } #[allow(clippy::too_many_arguments)] @@ -281,7 +285,10 @@ pub fn send_payables_within_batch( chain.rec().num_chain_id, ); + let common_timestamp = SystemTime::now(); + let prepared_sent_txs_records = sign_and_append_multiple_payments( + common_timestamp, logger, chain, web3_batch, @@ -426,13 +433,8 @@ mod tests { let mut batch_result = web3_batch.eth().transport().submit_batch().wait().unwrap(); assert_eq!( result, - HashAndAmount { - hash: H256::from_str( - "94881436a9c89f48b01651ff491c69e97089daf71ab8cfb240243d7ecf9b38b2" - ) - .unwrap(), - amount_minor: account.balance_wei - } + H256::from_str("94881436a9c89f48b01651ff491c69e97089daf71ab8cfb240243d7ecf9b38b2") + .unwrap() ); assert_eq!( batch_result.pop().unwrap().unwrap(), @@ -444,6 +446,7 @@ mod tests { #[test] fn sign_and_append_multiple_payments_works() { + let now = SystemTime::now(); let port = find_free_port(); let logger = Logger::new("sign_and_append_multiple_payments_works"); let (_event_loop_handle, transport) = Http::with_max_parallel( @@ -464,6 +467,7 @@ mod tests { let before = SystemTime::now(); let mut result = sign_and_append_multiple_payments( + now, &logger, chain, &web3_batch, @@ -481,21 +485,14 @@ mod tests { ); assert_eq!( first_actual_sent_tx.hash, - H256::from_str("94881436a9c89f48b01651ff491c69e97089daf71ab8cfb240243d7ecf9b38b2") + H256::from_str("374b7d023f4ac7d99e612d82beda494b0747116e9b9dc975b33b865f331ee934") .unwrap() ); assert_eq!(first_actual_sent_tx.amount_minor, account_1.balance_wei); assert_eq!(first_actual_sent_tx.gas_price_minor, 111_111_111); assert_eq!(first_actual_sent_tx.nonce, 1); assert_eq!(first_actual_sent_tx.block_opt, None); - assert!( - before <= from_unix_timestamp(first_actual_sent_tx.timestamp) - && from_unix_timestamp(first_actual_sent_tx.timestamp) <= after, - "We thought the timestamp was between {:?} and {:?}, but it was {:?}", - before, - after, - from_unix_timestamp(first_actual_sent_tx.timestamp) - ); + assert_eq!(first_actual_sent_tx.timestamp, to_unix_timestamp(now)); assert_eq!( second_actual_sent_tx.receiver_address, account_2.wallet.address() @@ -509,14 +506,7 @@ mod tests { assert_eq!(second_actual_sent_tx.gas_price_minor, 222_222_222); assert_eq!(second_actual_sent_tx.nonce, 2); assert_eq!(second_actual_sent_tx.block_opt, None); - assert!( - before <= from_unix_timestamp(second_actual_sent_tx.timestamp) - && from_unix_timestamp(second_actual_sent_tx.timestamp) <= after, - "We thought the timestamp was between {:?} and {:?}, but it was {:?}", - before, - after, - from_unix_timestamp(second_actual_sent_tx.timestamp) - ); + assert_eq!(second_actual_sent_tx.timestamp, to_unix_timestamp(now)); } #[test] @@ -692,7 +682,7 @@ mod tests { REQUESTS_IN_PARALLEL, ) .unwrap(); - let pending_nonce: U256 = 1.into(); + let pending_nonce: U256 = 3.into(); let web3_batch = Web3::new(Batch::new(transport)); let (accountant, _, accountant_recording) = make_recorder(); let logger = Logger::new(test_name); @@ -716,15 +706,31 @@ mod tests { System::current().stop(); system.run(); let timestamp_after = SystemTime::now(); + assert_eq!(result, expected_result); let accountant_recording_result = accountant_recording.lock().unwrap(); let rnpst_message = accountant_recording_result.get_record::(0); assert_eq!(accountant_recording_result.len(), 1); - rnpst_message.new_sent_txs.iter().for_each(|tx| { - todo!("add more assertions"); - assert!(timestamp_before <= from_unix_timestamp(tx.timestamp)); - assert!(timestamp_after >= from_unix_timestamp(tx.timestamp)); - }); + let nonces = 3_64..(accounts.payables.len() as u64 + 3); + rnpst_message + .new_sent_txs + .iter() + .zip(accounts.payables.iter()) + .zip(nonces) + .for_each(|((tx, payable_account), nonce)| { + assert_eq!( + tx.receiver_address, + payable_account.payable.wallet.address() + ); + assert_eq!(tx.amount_minor, payable_account.payable.balance_wei); + assert_eq!(tx.gas_price_minor, payable_account.gas_price_minor); + assert_eq!(tx.nonce, nonce); + assert_eq!(tx.block_opt, None); + assert!( + timestamp_before <= from_unix_timestamp(tx.timestamp) + && from_unix_timestamp(tx.timestamp) <= timestamp_after + ); + }); let tlh = TestLogHandler::new(); tlh.exists_log_containing( &format!("DEBUG: {test_name}: Common attributes of payables to be transacted: sender wallet: {}, contract: {:?}, chain_id: {}", @@ -737,7 +743,6 @@ mod tests { "INFO: {test_name}: {}", transmission_log(chain, &accounts, pending_nonce) )); - assert_eq!(result, expected_result); } #[test] @@ -756,14 +761,14 @@ mod tests { Correct(PendingPayable { recipient_wallet: account_1.wallet.clone(), hash: H256::from_str( - "6e7fa351eef640186f76c629cb74106b3082c8f8a1a9df75ff02fe5bfd4dd1a2", + "0f054a18b49f5c2172acab061e7f4e6f91d1586de1b010d5cb3090b93bae0da3", ) .unwrap(), }), Correct(PendingPayable { recipient_wallet: account_2.wallet.clone(), hash: H256::from_str( - "b67a61b29c0c48d8b63a64fda73b3247e8e2af68082c710325675d4911e113d4", + "6b485dbd4d769b5a19fa57058d612fad99cdd78769db6b3be129f981c42657ac", ) .unwrap(), }), @@ -792,8 +797,8 @@ mod tests { let expected_result = Err(Sending { msg: format!("Transport error: Error(Connect, Os {{ code: {}, kind: ConnectionRefused, message: {:?} }})", os_code, os_msg).to_string(), hashes: hashset![ - H256::from_str("ec7ac48060b75889f949f5e8d301b386198218e60e2635c95cb6b0934a0887ea").unwrap(), - H256::from_str("c2d5059db0ec2fbf15f83d9157eeb0d793d6242de5e73a607935fb5660e7e925").unwrap() + H256::from_str("5bbe90ad19d86b69ee49879cec4b3f8b769223e6a872aae0be88773de2fc3beb").unwrap(), + H256::from_str("a1b609dbe9cc77ad586dbe4e5c1079d6ad76020a353c960928d6daeafd43f366").unwrap() ], }); @@ -834,7 +839,7 @@ mod tests { data: None, }), recipient_wallet: account_1.wallet.clone(), - hash: H256::from_str("6e7fa351eef640186f76c629cb74106b3082c8f8a1a9df75ff02fe5bfd4dd1a2").unwrap(), + hash: H256::from_str("0f054a18b49f5c2172acab061e7f4e6f91d1586de1b010d5cb3090b93bae0da3").unwrap(), }), Failed(RpcPayableFailure { rpc_error: Rpc(Error { @@ -843,7 +848,7 @@ mod tests { data: None, }), recipient_wallet: account_2.wallet.clone(), - hash: H256::from_str("ca6ad0a60daeaf31cbca7ce6e499c0f4ff5870564c5e845de11834f1fc05bd4e").unwrap(), + hash: H256::from_str("d2749ac321b8701d4aba3417ef23482c4792b19d534dccb2834667f5f52fd6c4").unwrap(), }), ]); @@ -877,7 +882,7 @@ mod tests { let expected_result = Ok(vec![ Correct(PendingPayable { recipient_wallet: account_1.wallet.clone(), - hash: H256::from_str("6e7fa351eef640186f76c629cb74106b3082c8f8a1a9df75ff02fe5bfd4dd1a2").unwrap(), + hash: H256::from_str("0f054a18b49f5c2172acab061e7f4e6f91d1586de1b010d5cb3090b93bae0da3").unwrap(), }), Failed(RpcPayableFailure { rpc_error: Rpc(Error { @@ -886,7 +891,7 @@ mod tests { data: None, }), recipient_wallet: account_2.wallet.clone(), - hash: H256::from_str("ca6ad0a60daeaf31cbca7ce6e499c0f4ff5870564c5e845de11834f1fc05bd4e").unwrap(), + hash: H256::from_str("d2749ac321b8701d4aba3417ef23482c4792b19d534dccb2834667f5f52fd6c4").unwrap(), }), ]); @@ -901,15 +906,6 @@ mod tests { ); } - #[test] - fn advance_used_nonce_works() { - let initial_nonce = U256::from(55); - - let result = advance_used_nonce(initial_nonce); - - assert_eq!(result, U256::from(56)) - } - #[test] #[should_panic( expected = "Consuming wallet doesn't contain a secret key: Signature(\"Cannot sign with non-keypair wallet: Address(0x000000000000000000006261645f77616c6c6574).\")" From 6d17bf6b42145c32099889ace2b81ca5a3e0c596 Mon Sep 17 00:00:00 2001 From: Bert Date: Tue, 15 Jul 2025 14:11:49 +0200 Subject: [PATCH 11/61] GH-642: another bunch fixed...down to 24 --- .../db_access_objects/sent_payable_dao.rs | 3 +- node/src/accountant/mod.rs | 51 ++--- node/src/accountant/scanners/mod.rs | 214 +++++++++++------- node/src/accountant/scanners/test_utils.rs | 4 +- node/src/accountant/test_utils.rs | 9 +- node/src/blockchain/blockchain_bridge.rs | 25 +- .../lower_level_interface_web3.rs | 16 +- node/src/sub_lib/accountant.rs | 4 +- node/src/test_utils/recorder.rs | 6 +- 9 files changed, 187 insertions(+), 145 deletions(-) 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 0b1f12021..df0e5149a 100644 --- a/node/src/accountant/db_access_objects/sent_payable_dao.rs +++ b/node/src/accountant/db_access_objects/sent_payable_dao.rs @@ -404,8 +404,7 @@ pub trait SentPayableDaoFactory { impl SentPayableDaoFactory for DaoFactoryReal { fn make(&self) -> Box { - todo!() - //Box::new(PendingPayableDaoReal::new(self.make_connection())) + Box::new(SentPayableDaoReal::new(self.make_connection())) } } diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index c93cb6232..31d468987 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -135,7 +135,7 @@ pub struct ReceivedPayments { } #[derive(Debug, PartialEq, Eq, Message, Clone)] -pub struct TxStatusReport { +pub struct TxReceiptsMessage { pub results: Vec, pub response_skeleton_opt: Option, } @@ -306,10 +306,10 @@ impl Handler for Accountant { } } -impl Handler for Accountant { +impl Handler for Accountant { type Result = (); - fn handle(&mut self, msg: TxStatusReport, ctx: &mut Self::Context) -> Self::Result { + fn handle(&mut self, msg: TxReceiptsMessage, ctx: &mut Self::Context) -> Self::Result { let response_skeleton_opt = msg.response_skeleton_opt; match self.scanners.finish_pending_payable_scan(msg, &self.logger) { PendingPayableScanResult::NoPendingPayablesLeft(ui_msg_opt) => { @@ -560,7 +560,7 @@ impl Accountant { report_payable_payments_setup: recipient!(addr, BlockchainAgentWithContextMessage), report_inbound_payments: recipient!(addr, ReceivedPayments), init_pending_payable_fingerprints: recipient!(addr, RegisterNewPendingSentTxMessage), - report_transaction_status: recipient!(addr, TxStatusReport), + report_transaction_status: recipient!(addr, TxReceiptsMessage), report_sent_payments: recipient!(addr, SentPayables), scan_errors: recipient!(addr, ScanError), ui_message_sub: recipient!(addr, NodeFromUiMessage), @@ -1939,7 +1939,7 @@ mod tests { subject.ui_message_sub_opt = Some(ui_gateway_addr.recipient()); let subject_addr = subject.start(); let sent_tx = make_sent_tx(123); - let report_tx_receipts = TxStatusReport { + let tx_receipts_msg = TxReceiptsMessage { results: vec![TxReceiptResult::RpcResponse(TxWithStatus::new( sent_tx.clone(), TxStatus::Succeeded(TransactionBlock { @@ -1950,7 +1950,7 @@ mod tests { response_skeleton_opt, }; - subject_addr.try_send(report_tx_receipts).unwrap(); + subject_addr.try_send(tx_receipts_msg).unwrap(); system.run(); let transaction_confirmed_params = transaction_confirmed_params_arc.lock().unwrap(); @@ -2157,7 +2157,7 @@ mod tests { let system = System::new("test"); let first_counter_msg_setup = setup_for_counter_msg_triggered_via_type_id!( RequestTransactionReceipts, - TxStatusReport { + TxReceiptsMessage { results: vec![TxReceiptResult::RpcResponse(TxWithStatus::new( sent_tx, TxStatus::Failed(BlockchainTxFailure::Unrecognized) @@ -2777,7 +2777,7 @@ mod tests { let (peer_actors, addresses) = peer_actors_builder().build_and_provide_addresses(); let subject_addr: Addr = subject.start(); let subject_subs = Accountant::make_subs_from(&subject_addr); - let expected_tx_status_report = TxStatusReport { + let expected_tx_receipts_msg = TxReceiptsMessage { results: vec![TxReceiptResult::RpcResponse(TxWithStatus::new( sent_tx.clone(), TxStatus::Failed(BlockchainTxFailure::Unrecognized), @@ -2793,7 +2793,7 @@ mod tests { }; let blockchain_bridge_counter_msg_setup_for_pending_payable_scanner = setup_for_counter_msg_triggered_via_type_id!( RequestTransactionReceipts, - expected_tx_status_report.clone(), + expected_tx_receipts_msg.clone(), &subject_addr ); let blockchain_bridge_counter_msg_setup_for_payable_scanner = setup_for_counter_msg_triggered_via_type_id!( @@ -2822,7 +2822,7 @@ mod tests { &scan_params, ¬ify_and_notify_later_params.pending_payables_notify_later, pending_payable_expected_notify_later_interval, - expected_tx_status_report, + expected_tx_receipts_msg, before, after, ); @@ -2852,7 +2852,7 @@ mod tests { payable_finish_scan: Arc>>, pending_payable_start_scan: Arc, Logger, String)>>>, - pending_payable_finish_scan: Arc>>, + pending_payable_finish_scan: Arc>>, receivable_start_scan: Arc, Logger, String)>>>, // receivable_finish_scan ... not needed @@ -2874,7 +2874,7 @@ mod tests { config: BootstrapperConfig, pending_payable_scanner: ScannerMock< RequestTransactionReceipts, - TxStatusReport, + TxReceiptsMessage, PendingPayableScanResult, >, receivable_scanner: ScannerMock< @@ -2933,7 +2933,7 @@ mod tests { payable_scanner: ScannerMock, pending_payable_scanner: ScannerMock< RequestTransactionReceipts, - TxStatusReport, + TxReceiptsMessage, PendingPayableScanResult, >, receivable_scanner: ScannerMock< @@ -2987,7 +2987,7 @@ mod tests { config: BootstrapperConfig, pending_payable_scanner: ScannerMock< RequestTransactionReceipts, - TxStatusReport, + TxReceiptsMessage, PendingPayableScanResult, >, receivable_scanner: ScannerMock< @@ -3060,7 +3060,7 @@ mod tests { Mutex>, >, pending_payable_expected_notify_later_interval: Duration, - expected_report_tx_receipts_msg: TxStatusReport, + expected_tx_receipts_msg: TxReceiptsMessage, act_started_at: SystemTime, act_finished_at: SystemTime, ) { @@ -3073,12 +3073,9 @@ mod tests { assert_using_the_same_logger(&pp_start_scan_logger, test_name, Some("pp start scan")); let mut pending_payable_finish_scan_params = scan_params.pending_payable_finish_scan.lock().unwrap(); - let (actual_report_tx_receipts_msg, pp_finish_scan_logger) = + let (actual_tx_receipts_msg, pp_finish_scan_logger) = pending_payable_finish_scan_params.remove(0); - assert_eq!( - actual_report_tx_receipts_msg, - expected_report_tx_receipts_msg - ); + assert_eq!(actual_tx_receipts_msg, expected_tx_receipts_msg); assert_using_the_same_logger(&pp_finish_scan_logger, test_name, Some("pp finish scan")); let scan_for_pending_payables_notify_later_params = scan_for_pending_payables_notify_later_params_arc @@ -3520,7 +3517,7 @@ mod tests { }), }; let requested_tx = make_tx_hash(234); - let counter_msg_3 = TxStatusReport { + let counter_msg_3 = TxReceiptsMessage { results: vec![TxReceiptResult::RpcResponse(tx_with_status)], response_skeleton_opt: None, }; @@ -4960,7 +4957,7 @@ mod tests { subject.scan_schedulers.payable.retry_payable_notify = Box::new(NotifyHandleMock::default().notify_params(&retry_payable_notify_params_arc)); let system = System::new(test_name); - let (mut msg, _) = make_tx_status_report_msg(vec![ + let (mut msg, _) = make_tx_receipts_msg(vec![ TxStatus::Pending, TxStatus::Failed(BlockchainTxFailure::Unrecognized), ]); @@ -5029,7 +5026,7 @@ mod tests { subject.scan_schedulers.payable.new_payable_notify = Box::new(NotifyHandleMock::default().notify_params(&new_payable_notify_arc)); let subject_addr = subject.start(); - let (msg, two_sent_txs) = make_tx_status_report_msg(vec![ + let (msg, two_sent_txs) = make_tx_receipts_msg(vec![ TxStatus::Succeeded(TransactionBlock { block_hash: make_tx_hash(123), block_number: U64::from(100), @@ -5116,7 +5113,7 @@ mod tests { let tx_block_1 = make_transaction_block(4567); let tx_block_2 = make_transaction_block(1234); let subject_addr = subject.start(); - let (msg, two_sent_txs) = make_tx_status_report_msg(vec![ + let (msg, two_sent_txs) = make_tx_receipts_msg(vec![ TxStatus::Succeeded(tx_block_1), TxStatus::Succeeded(tx_block_2), ]); @@ -5182,7 +5179,7 @@ mod tests { NotifyLaterHandleMock::default().notify_later_params(&new_payable_notify_later_arc), ); let subject_addr = subject.start(); - let (msg, _) = make_tx_status_report_msg(vec![ + let (msg, _) = make_tx_receipts_msg(vec![ TxStatus::Succeeded(TransactionBlock { block_hash: make_tx_hash(123), block_number: U64::from(100), @@ -5221,7 +5218,7 @@ mod tests { ); } - fn make_tx_status_report_msg(status_txs: Vec) -> (TxStatusReport, Vec) { + fn make_tx_receipts_msg(status_txs: Vec) -> (TxReceiptsMessage, Vec) { let (tx_receipt_results, sent_tx_vec) = status_txs.into_iter().enumerate().fold( (vec![], vec![]), |(mut tx_receipt_results, mut sent_tx_vec), (idx, status)| { @@ -5239,7 +5236,7 @@ mod tests { }, ); - let msg = TxStatusReport { + let msg = TxReceiptsMessage { results: tx_receipt_results, response_skeleton_opt: None, }; diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index e3d9d57b7..38c3f809e 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -17,7 +17,7 @@ use crate::accountant::scanners::scanners_utils::receivable_scanner_utils::balan use crate::accountant::{PendingPayable, PendingPayableId, ScanError, ScanForPendingPayables, ScanForRetryPayables}; use crate::accountant::{ comma_joined_stringifiable, gwei_to_wei, ReceivedPayments, - TxStatusReport, RequestTransactionReceipts, ResponseSkeleton, ScanForNewPayables, + TxReceiptsMessage, RequestTransactionReceipts, ResponseSkeleton, ScanForNewPayables, ScanForReceivables, SentPayables, }; use crate::accountant::db_access_objects::banned_dao::BannedDao; @@ -63,7 +63,7 @@ pub struct Scanners { dyn PrivateScanner< ScanForPendingPayables, RequestTransactionReceipts, - TxStatusReport, + TxReceiptsMessage, PendingPayableScanResult, >, >, @@ -260,7 +260,7 @@ impl Scanners { pub fn finish_pending_payable_scan( &mut self, - msg: TxStatusReport, + msg: TxReceiptsMessage, logger: &Logger, ) -> PendingPayableScanResult { self.pending_payable.finish_scan(msg, logger) @@ -937,7 +937,7 @@ impl PrivateScanner< ScanForPendingPayables, RequestTransactionReceipts, - TxStatusReport, + TxReceiptsMessage, PendingPayableScanResult, > for PendingPayableScanner { @@ -978,10 +978,10 @@ impl StartableScanner } } -impl Scanner for PendingPayableScanner { +impl Scanner for PendingPayableScanner { fn finish_scan( &mut self, - message: TxStatusReport, + message: TxReceiptsMessage, logger: &Logger, ) -> PendingPayableScanResult { let response_skeleton_opt = message.response_skeleton_opt; @@ -1041,7 +1041,7 @@ impl PendingPayableScanner { fn handle_receipts_for_pending_transactions( &self, - msg: TxStatusReport, + msg: TxReceiptsMessage, logger: &Logger, ) -> PendingPayableScanSummary { let scan_report = PendingPayableScanSummary::default(); @@ -1096,48 +1096,9 @@ impl PendingPayableScanner { } fn handle_confirmed_transactions(&mut self, confirmed_txs: Vec, logger: &Logger) { - fn transaction_confirmed_panic(confirmed_txs: &[SentTx], e: PayableDaoError) -> ! { - let wallets = confirmed_txs - .iter() - .map(|tx| tx.receiver_address) - .collect_vec(); - panic!( - "Unable to complete the tx confirmation by the adjustment of the payable \ - accounts {} due to {:?}", - comma_joined_stringifiable(&wallets, |wallet| format!("{:?}", wallet)), - e - ) - } - fn update_tx_blocks_panic( - tx_hashes_and_tx_blocks: &HashMap, - e: SentPayableDaoError, - ) -> ! { - panic!( - "Unable to update sent tx records {} with their tx blocks due to {:?}", - comma_joined_stringifiable( - &tx_hashes_and_tx_blocks.keys().collect_vec(), - |tx_hash| format!("{:?}", tx_hash) - ), - e - ) - } - fn log_success( - logger: &Logger, - tx_hashes_and_tx_blocks: &HashMap, - ) { - logger.info(|| { - let pretty_pairs = tx_hashes_and_tx_blocks - .iter() - .sorted() - .map(|(hash, block)| format!("{:?} (block {})", hash, block.block_number)) - .join(", "); - format!("Txs {} have completed", pretty_pairs) - }); - } - if !confirmed_txs.is_empty() { if let Err(e) = self.payable_dao.transactions_confirmed(&confirmed_txs) { - transaction_confirmed_panic(&confirmed_txs, e) + Self::transaction_confirmed_panic(&confirmed_txs, e) } else { self.add_to_the_total_of_paid_payable(&confirmed_txs, logger); @@ -1155,14 +1116,58 @@ impl PendingPayableScanner { .sent_payable_dao .update_tx_blocks(&tx_hashes_and_tx_blocks) { - update_tx_blocks_panic(&tx_hashes_and_tx_blocks, e) + Self::update_tx_blocks_panic(&tx_hashes_and_tx_blocks, e) } else { - log_success(logger, &tx_hashes_and_tx_blocks); + Self::log_tx_success(logger, &tx_hashes_and_tx_blocks); } } } } + fn transaction_confirmed_panic(confirmed_txs: &[SentTx], e: PayableDaoError) -> ! { + let wallets = confirmed_txs + .iter() + .map(|tx| tx.receiver_address) + .collect_vec(); + panic!( + "Unable to complete the tx confirmation by the adjustment of the payable \ + accounts {} due to {:?}", + comma_joined_stringifiable(&wallets, |wallet| format!("{:?}", wallet)), + e + ) + } + + fn update_tx_blocks_panic( + tx_hashes_and_tx_blocks: &HashMap, + e: SentPayableDaoError, + ) -> ! { + panic!( + "Unable to update sent tx records {} with their tx blocks due to {:?}", + comma_joined_stringifiable( + &tx_hashes_and_tx_blocks.keys().collect_vec(), + |tx_hash| format!("{:?}", tx_hash) + ), + e + ) + } + + fn log_tx_success( + logger: &Logger, + tx_hashes_and_tx_blocks: &HashMap, + ) { + logger.info(|| { + let pretty_pairs = tx_hashes_and_tx_blocks + .iter() + .sorted() + .map(|(hash, block)| format!("{:?} (block {})", hash, block.block_number)) + .join(", "); + match tx_hashes_and_tx_blocks.len() { + 1 => format!("Tx {} has been confirmed", pretty_pairs), + _ => format!("Txs {} have been confirmed", pretty_pairs), + } + }); + } + fn add_to_the_total_of_paid_payable(&mut self, confirmed_payments: &[SentTx], logger: &Logger) { let to_be_added: u128 = confirmed_payments .iter() @@ -1510,13 +1515,13 @@ impl_real_scanner_marker!(PayableScanner, PendingPayableScanner, ReceivableScann #[cfg(test)] mod tests { use crate::accountant::db_access_objects::payable_dao::{PayableAccount, PayableDaoError}; - use crate::accountant::db_access_objects::utils::{from_unix_timestamp, to_unix_timestamp}; + use crate::accountant::db_access_objects::utils::{from_unix_timestamp, to_unix_timestamp, TxHash}; use crate::accountant::scanners::payable_scanner_extension::msgs::{QualifiedPayablesBeforeGasPriceSelection, QualifiedPayablesMessage, UnpricedQualifiedPayables}; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{OperationOutcome, PayableScanResult, PendingPayableMissingInDb}; use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_status_with_failure, PendingPayableScanSummary, PendingPayableScanResult}; use crate::accountant::scanners::{Scanner, StartScanError, StartableScanner, PayableScanner, PendingPayableScanner, ReceivableScanner, ScannerCommon, Scanners, MTError}; use crate::accountant::test_utils::{make_custom_payment_thresholds, make_payable_account, make_qualified_and_unqualified_payables, make_receivable_account, BannedDaoFactoryMock, BannedDaoMock, ConfigDaoFactoryMock, PayableDaoFactoryMock, PayableDaoMock, PayableScannerBuilder, PayableThresholdsGaugeMock, SentPayableDaoFactoryMock, SentPayableDaoMock, PendingPayableScannerBuilder, ReceivableDaoFactoryMock, ReceivableDaoMock, ReceivableScannerBuilder, make_sent_tx, FailedPayableDaoMock, FailedPayableDaoFactoryMock, make_failed_tx, make_transaction_block}; - use crate::accountant::{gwei_to_wei, PendingPayableId, ReceivedPayments, TxStatusReport, RequestTransactionReceipts, ScanError, ScanForRetryPayables, SentPayables, DEFAULT_PENDING_TOO_LONG_SEC, PendingPayable}; + use crate::accountant::{gwei_to_wei, PendingPayableId, ReceivedPayments, TxReceiptsMessage, RequestTransactionReceipts, ScanError, ScanForRetryPayables, SentPayables, DEFAULT_PENDING_TOO_LONG_SEC, PendingPayable}; use crate::blockchain::blockchain_bridge::{BlockMarker, RetrieveTransactions}; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; use crate::blockchain::blockchain_interface::data_structures::{ @@ -1548,6 +1553,7 @@ mod tests { use std::rc::Rc; use std::sync::{Arc, Mutex}; use std::time::{Duration, SystemTime}; + use itertools::Itertools; use secp256k1secrets::ecdh::SharedSecret; use web3::types::{TransactionReceipt, H256}; use web3::Error; @@ -1644,7 +1650,8 @@ mod tests { let sent_payable_dao_factory = SentPayableDaoFactoryMock::new() .make_result(SentPayableDaoMock::new()) .make_result(SentPayableDaoMock::new()); - let failed_payable_dao_factory = FailedPayableDaoFactoryMock::new(); + let failed_payable_dao_factory = + FailedPayableDaoFactoryMock::new().make_result(FailedPayableDaoMock::new()); let receivable_dao = ReceivableDaoMock::new(); let receivable_dao_factory = ReceivableDaoFactoryMock::new().make_result(receivable_dao); let banned_dao_factory = BannedDaoFactoryMock::new().make_result(BannedDaoMock::new()); @@ -3225,7 +3232,7 @@ mod tests { let mut sent_tx = make_sent_tx(456); sent_tx.hash = hash; sent_tx.timestamp = sent_tx_timestamp; - let msg = TxStatusReport { + let msg = TxReceiptsMessage { results: vec![TxReceiptResult::RpcResponse(TxWithStatus::new( sent_tx.clone(), TxStatus::Pending, @@ -3465,6 +3472,40 @@ mod tests { subject.handle_confirmed_transactions(vec![sent_tx], &Logger::new("test")); } + #[test] + fn log_tx_success_is_agnostic_to_singular_or_plural_form() { + init_test_logging(); + let test_name = "log_tx_success_is_agnostic_to_singular_or_plural_form"; + let plural_case_name = format!("{}_testing_plural_case", test_name); + let singular_case_name = format!("{}_testing_singular_case", test_name); + let logger_plural = Logger::new(&plural_case_name); + let logger_singular = Logger::new(&singular_case_name); + let tx_hash_1 = make_tx_hash(0x123); + let tx_hash_2 = make_tx_hash(0x567); + let mut tx_block_1 = make_transaction_block(456); + tx_block_1.block_number = 1_234_501_u64.into(); + let mut tx_block_2 = make_transaction_block(789); + tx_block_2.block_number = 1_234_502_u64.into(); + let mut tx_hashes_and_blocks = hashmap!(tx_hash_1 => tx_block_1, tx_hash_2 => tx_block_2); + + PendingPayableScanner::log_tx_success(&logger_plural, &tx_hashes_and_blocks); + + tx_hashes_and_blocks.remove(&tx_hash_2); + + PendingPayableScanner::log_tx_success(&logger_singular, &tx_hashes_and_blocks); + + let log_handler = TestLogHandler::new(); + log_handler.exists_log_containing(&format!( + "INFO: {plural_case_name}: Txs 0x0000000000000000000000000000000000000000000000000000000000000123 \ + (block 1234501), 0x0000000000000000000000000000000000000000000000000000000000000567 \ + (block 1234502) have been confirmed", + )); + log_handler.exists_log_containing(&format!( + "INFO: {singular_case_name}: Tx 0x0000000000000000000000000000000000000000000000000000000000000123 \ + (block 1234501) has been confirmed", + )); + } + #[test] fn total_paid_payable_rises_with_each_bill_paid() { init_test_logging(); @@ -3498,39 +3539,40 @@ mod tests { } #[test] - fn pending_payable_scanner_handles_report_transaction_receipts_message() { + fn pending_payable_scanner_handles_tx_receipts_message() { init_test_logging(); - let test_name = "pending_payable_scanner_handles_report_transaction_receipts_message"; + let test_name = "pending_payable_scanner_handles_tx_receipts_message"; let transactions_confirmed_params_arc = Arc::new(Mutex::new(vec![])); + let update_tx_blocks_params_arc = Arc::new(Mutex::new(vec![])); let payable_dao = PayableDaoMock::new() .transactions_confirmed_params(&transactions_confirmed_params_arc) .transactions_confirmed_result(Ok(())); - let sent_payable_dao = SentPayableDaoMock::new().delete_records_result(Ok(())); + let sent_payable_dao = SentPayableDaoMock::new() + .update_tx_blocks_params(&update_tx_blocks_params_arc) + .update_tx_blocks_result(Ok(())); let mut pending_payable_scanner = PendingPayableScannerBuilder::new() .payable_dao(payable_dao) .sent_payable_dao(sent_payable_dao) .build(); - let transaction_hash_1 = make_tx_hash(4545); + let tx_hash_1 = make_tx_hash(4545); let mut sent_tx_1 = make_sent_tx(123); - sent_tx_1.hash = transaction_hash_1; - let transaction_with_status_1 = TxWithStatus::new( - sent_tx_1.clone(), - TxStatus::Succeeded(TransactionBlock { - block_hash: Default::default(), - block_number: U64::from(1234), - }), - ); - let transaction_hash_2 = make_tx_hash(1234); + sent_tx_1.hash = tx_hash_1; + let tx_block_1 = TransactionBlock { + block_hash: make_block_hash(333), + block_number: U64::from(1234), + }; + let transaction_with_status_1 = + TxWithStatus::new(sent_tx_1.clone(), TxStatus::Succeeded(tx_block_1)); + let tx_hash_2 = make_tx_hash(1234); let mut sent_tx_2 = make_sent_tx(789); - sent_tx_2.hash = transaction_hash_2; - let transaction_with_status_2 = TxWithStatus::new( - sent_tx_2.clone(), - TxStatus::Succeeded(TransactionBlock { - block_hash: Default::default(), - block_number: U64::from(2345), - }), - ); - let msg = TxStatusReport { + sent_tx_2.hash = tx_hash_2; + let tx_block_2 = TransactionBlock { + block_hash: make_block_hash(222), + block_number: U64::from(2345), + }; + let transaction_with_status_2 = + TxWithStatus::new(sent_tx_2.clone(), TxStatus::Succeeded(tx_block_2)); + let msg = TxReceiptsMessage { results: vec![ TxReceiptResult::RpcResponse(transaction_with_status_1), TxReceiptResult::RpcResponse(transaction_with_status_2), @@ -3543,21 +3585,29 @@ mod tests { let result = subject.finish_pending_payable_scan(msg, &Logger::new(test_name)); - let transactions_confirmed_params = transactions_confirmed_params_arc.lock().unwrap(); assert_eq!( result, PendingPayableScanResult::NoPendingPayablesLeft(None) ); + let transactions_confirmed_params = transactions_confirmed_params_arc.lock().unwrap(); + sent_tx_1.block_opt = Some(tx_block_1); + sent_tx_2.block_opt = Some(tx_block_2); assert_eq!( *transactions_confirmed_params, vec![vec![sent_tx_1, sent_tx_2]] ); + let update_tx_blocks_params = update_tx_blocks_params_arc.lock().unwrap(); + assert_eq!( + *update_tx_blocks_params, + vec![hashmap![tx_hash_1 => tx_block_1, tx_hash_2 => tx_block_2]] + ); assert_eq!(subject.scan_started_at(ScanType::PendingPayables), None); + TestLogHandler::new().assert_logs_match_in_order(vec![ - &format!( - "INFO: {}: Transactions {:?}, {:?} completed their confirmation process succeeding", - test_name, transaction_hash_1, transaction_hash_2 - ), + ®ex::escape(&format!( + "INFO: {}: Txs {:?} (block 2345), {:?} (block 1234) have been confirmed", + test_name, tx_hash_2, tx_hash_1 + )), &format!("INFO: {test_name}: The PendingPayables scan ended in \\d+ms."), ]); } @@ -3569,7 +3619,7 @@ mod tests { )] fn pending_payable_scanner_handles_empty_report_transaction_receipts_message() { let mut pending_payable_scanner = PendingPayableScannerBuilder::new().build(); - let msg = TxStatusReport { + let msg = TxReceiptsMessage { results: vec![], response_skeleton_opt: None, }; @@ -4060,7 +4110,7 @@ mod tests { &logger, &log_handler, ); - assert_elapsed_time_in_mark_as_ended::( + assert_elapsed_time_in_mark_as_ended::( &mut PendingPayableScannerBuilder::new().build(), "PendingPayables", test_name, diff --git a/node/src/accountant/scanners/test_utils.rs b/node/src/accountant/scanners/test_utils.rs index 6d75adf6a..ef8667ca2 100644 --- a/node/src/accountant/scanners/test_utils.rs +++ b/node/src/accountant/scanners/test_utils.rs @@ -19,7 +19,7 @@ use crate::accountant::scanners::{ Scanner, StartScanError, StartableScanner, }; use crate::accountant::{ - ReceivedPayments, RequestTransactionReceipts, ResponseSkeleton, SentPayables, TxStatusReport, + ReceivedPayments, RequestTransactionReceipts, ResponseSkeleton, SentPayables, TxReceiptsMessage, }; use crate::blockchain::blockchain_bridge::RetrieveTransactions; use crate::sub_lib::blockchain_bridge::{ConsumingWalletBalances, OutboundPaymentsInstructions}; @@ -366,7 +366,7 @@ pub enum ScannerReplacement { PendingPayable( ReplacementType< PendingPayableScanner, - ScannerMock, + ScannerMock, >, ), Receivable( diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs index f6d778db2..b250c4971 100644 --- a/node/src/accountant/test_utils.rs +++ b/node/src/accountant/test_utils.rs @@ -1396,9 +1396,6 @@ pub struct FailedPayableDaoFactoryMock { 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) } @@ -1674,10 +1671,10 @@ where { let conn = Connection::open_in_memory().unwrap(); let execute = |sql: &str| conn.execute(sql, []).unwrap(); - execute("create table whatever (exclamations text)"); - execute("insert into whatever (exclamations) values ('Gosh')"); + execute("create table whatever (exclamation text)"); + execute("insert into whatever (exclamation) values ('Gosh')"); - conn.query_row("select exclamations from whatever", [], tested_fn) + conn.query_row("select exclamation from whatever", [], tested_fn) .unwrap(); } diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index c333a9f05..070a06647 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -5,7 +5,7 @@ use crate::accountant::{ ReceivedPayments, ResponseSkeleton, ScanError, SentPayables, SkeletonOptHolder, }; -use crate::accountant::{TxStatusReport, RequestTransactionReceipts}; +use crate::accountant::{TxReceiptsMessage, RequestTransactionReceipts}; use crate::actor_system_factory::SubsFactory; use crate::blockchain::blockchain_interface::blockchain_interface_web3::HashAndAmount; use crate::blockchain::blockchain_interface::data_structures::errors::{ @@ -66,7 +66,7 @@ pub struct BlockchainBridge { struct TransactionConfirmationTools { new_pp_fingerprints_sub_opt: Option>, - report_transaction_receipts_sub_opt: Option>, + report_transaction_receipts_sub_opt: Option>, } #[derive(PartialEq, Eq)] @@ -428,7 +428,7 @@ impl BlockchainBridge { .and_then(move |tx_receipt_results| { Self::log_status_of_tx_receipts(&logger, &tx_receipt_results); accountant_recipient - .try_send(TxStatusReport { + .try_send(TxReceiptsMessage { results: tx_receipt_results, response_skeleton_opt: msg.response_skeleton_opt, }) @@ -1155,7 +1155,7 @@ mod tests { fn blockchain_bridge_processes_requests_for_a_complete_and_null_transaction_receipt() { let (accountant, _, accountant_recording_arc) = make_recorder(); let accountant = - accountant.system_stop_conditions(match_lazily_every_type_id!(TxStatusReport)); + accountant.system_stop_conditions(match_lazily_every_type_id!(TxReceiptsMessage)); let sent_tx_1 = make_sent_tx(123); let hash_1 = sent_tx_1.hash; let sent_tx_2 = make_sent_tx(456); @@ -1196,13 +1196,13 @@ mod tests { system.run(); let accountant_recording = accountant_recording_arc.lock().unwrap(); assert_eq!(accountant_recording.len(), 1); - let tx_status_report_message = accountant_recording.get_record::(0); + let tx_receipts_message = accountant_recording.get_record::(0); let mut expected_receipt = TransactionReceipt::default(); expected_receipt.transaction_hash = hash_1; expected_receipt.status = Some(U64::from(1)); assert_eq!( - tx_status_report_message, - &TxStatusReport { + tx_receipts_message, + &TxReceiptsMessage { results: vec![ TxReceiptResult::RpcResponse(TxWithStatus::new( sent_tx_1, @@ -1297,9 +1297,9 @@ mod tests { .start(); let (accountant, _, accountant_recording_arc) = make_recorder(); let accountant_addr = accountant - .system_stop_conditions(match_lazily_every_type_id!(TxStatusReport, ScanError)) + .system_stop_conditions(match_lazily_every_type_id!(TxReceiptsMessage, ScanError)) .start(); - let report_transaction_receipt_recipient: Recipient = + let report_transaction_receipt_recipient: Recipient = accountant_addr.clone().recipient(); let scan_error_recipient: Recipient = accountant_addr.recipient(); let hash_1 = make_tx_hash(111334); @@ -1341,10 +1341,10 @@ mod tests { assert_eq!(system.run(), 0); let accountant_recording = accountant_recording_arc.lock().unwrap(); assert_eq!(accountant_recording.len(), 1); - let report_receipts_msg = accountant_recording.get_record::(0); + let report_receipts_msg = accountant_recording.get_record::(0); assert_eq!( *report_receipts_msg, - TxStatusReport { + TxReceiptsMessage { results: vec![ TxReceiptResult::RpcResponse(TxWithStatus::new(sent_tx_1, TxStatus::Pending)), TxReceiptResult::RpcResponse(TxWithStatus::new(sent_tx_2, TxStatus::Succeeded(TransactionBlock { @@ -1376,7 +1376,8 @@ mod tests { .system_stop_conditions(match_lazily_every_type_id!(ScanError)) .start(); let scan_error_recipient: Recipient = accountant_addr.clone().recipient(); - let report_transaction_recipient: Recipient = accountant_addr.recipient(); + let report_transaction_recipient: Recipient = + accountant_addr.recipient(); let hash_1 = make_tx_hash(0x1b2e6); let sent_tx_1 = make_sent_tx(123); let sent_tx_2 = make_sent_tx(456); diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs index bc3fb8100..262457d60 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs @@ -46,7 +46,9 @@ impl From for TxStatus { block_number, }) } - (Some(status), _, _) if status == U64::from(0) => todo!(), //TxStatus::Failed(BlockchainTxFailure::Unrecognized), + (Some(status), _, _) if status == U64::from(0) => { + TxStatus::Failed(BlockchainTxFailure::Unrecognized) + } _ => TxStatus::Pending, } } @@ -76,8 +78,8 @@ impl Display for TxStatus { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { TxStatus::Failed(reason) => { - todo!("make sure there is an assertion for this new syntax") - } //write!(f, "Failed({:?})", reason), + write!(f, "Failed(Reason: {})", reason) + } TxStatus::Succeeded(block) => { write!( f, @@ -127,11 +129,7 @@ pub struct TxReceiptRequestError { impl TxReceiptRequestError { pub fn new(tx_hash: TxHash, err_msg: String) -> Self { - todo!() - // Self { - // tx_hash, - // err_msg - // } + Self { tx_hash, err_msg } } } @@ -708,7 +706,7 @@ mod tests { // Test Failed assert_eq!( TxStatus::Failed(BlockchainTxFailure::Unrecognized).to_string(), - "Failed" + "Failed(Reason: Failure unrecognized)" ); // Test Pending diff --git a/node/src/sub_lib/accountant.rs b/node/src/sub_lib/accountant.rs index 10bb2e8ff..7e5fac2c6 100644 --- a/node/src/sub_lib/accountant.rs +++ b/node/src/sub_lib/accountant.rs @@ -6,7 +6,7 @@ use crate::accountant::db_access_objects::receivable_dao::ReceivableDaoFactory; use crate::accountant::db_access_objects::sent_payable_dao::SentPayableDaoFactory; use crate::accountant::scanners::payable_scanner_extension::msgs::BlockchainAgentWithContextMessage; use crate::accountant::{ - checked_conversion, Accountant, ReceivedPayments, ScanError, SentPayables, TxStatusReport, + checked_conversion, Accountant, ReceivedPayments, ScanError, SentPayables, TxReceiptsMessage, }; use crate::actor_system_factory::SubsFactory; use crate::blockchain::blockchain_bridge::RegisterNewPendingSentTxMessage; @@ -102,7 +102,7 @@ pub struct AccountantSubs { pub report_payable_payments_setup: Recipient, pub report_inbound_payments: Recipient, pub init_pending_payable_fingerprints: Recipient, - pub report_transaction_status: Recipient, + pub report_transaction_status: Recipient, pub report_sent_payments: Recipient, pub scan_errors: Recipient, pub ui_message_sub: Recipient, diff --git a/node/src/test_utils/recorder.rs b/node/src/test_utils/recorder.rs index 23b8d04bc..d10880eeb 100644 --- a/node/src/test_utils/recorder.rs +++ b/node/src/test_utils/recorder.rs @@ -7,7 +7,7 @@ use crate::accountant::{ ReceivedPayments, RequestTransactionReceipts, ScanError, ScanForNewPayables, ScanForReceivables, SentPayables, }; -use crate::accountant::{ScanForPendingPayables, ScanForRetryPayables, TxStatusReport}; +use crate::accountant::{ScanForPendingPayables, ScanForRetryPayables, TxReceiptsMessage}; use crate::blockchain::blockchain_bridge::RegisterNewPendingSentTxMessage; use crate::blockchain::blockchain_bridge::RetrieveTransactions; use crate::daemon::crash_notification::CrashNotification; @@ -162,7 +162,7 @@ recorder_message_handler_t_m_p!(RemoveStreamMsg); recorder_message_handler_t_m_p!(ReportExitServiceProvidedMessage); recorder_message_handler_t_m_p!(ReportRoutingServiceProvidedMessage); recorder_message_handler_t_m_p!(ReportServicesConsumedMessage); -recorder_message_handler_t_m_p!(TxStatusReport); +recorder_message_handler_t_m_p!(TxReceiptsMessage); recorder_message_handler_t_m_p!(RequestTransactionReceipts); recorder_message_handler_t_m_p!(RetrieveTransactions); recorder_message_handler_t_m_p!(ScanError); @@ -530,7 +530,7 @@ pub fn make_accountant_subs_from_recorder(addr: &Addr) -> AccountantSu report_payable_payments_setup: recipient!(addr, BlockchainAgentWithContextMessage), report_inbound_payments: recipient!(addr, ReceivedPayments), init_pending_payable_fingerprints: recipient!(addr, RegisterNewPendingSentTxMessage), - report_transaction_status: recipient!(addr, TxStatusReport), + report_transaction_status: recipient!(addr, TxReceiptsMessage), report_sent_payments: recipient!(addr, SentPayables), scan_errors: recipient!(addr, ScanError), ui_message_sub: recipient!(addr, NodeFromUiMessage), From c299f91b4e58c23adfed54f0f55275888fa349a3 Mon Sep 17 00:00:00 2001 From: Bert Date: Wed, 16 Jul 2025 00:51:04 +0200 Subject: [PATCH 12/61] GH-642: another bunch fixed...down to 10 --- node/src/accountant/mod.rs | 10 +- node/src/accountant/scanners/mod.rs | 680 ++++++------------ .../src/accountant/scanners/scanners_utils.rs | 34 +- node/src/blockchain/blockchain_bridge.rs | 11 +- .../data_structures/errors.rs | 6 +- .../blockchain/blockchain_interface/mod.rs | 2 +- 6 files changed, 237 insertions(+), 506 deletions(-) diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index 31d468987..6c442bda9 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -467,7 +467,7 @@ pub trait SkeletonOptHolder { #[derive(Debug, PartialEq, Eq, Message, Clone)] pub struct RequestTransactionReceipts { - pub sent_tx: Vec, + pub sent_txs: Vec, pub response_skeleton_opt: Option, } @@ -1899,7 +1899,7 @@ mod tests { assert_eq!( blockchain_bridge_recording.get_record::(0), &RequestTransactionReceipts { - sent_tx: vec![sent_tx], + sent_txs: vec![sent_tx], response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, context_id: 4321, @@ -2756,7 +2756,7 @@ mod tests { .scan_started_at_result(None) .start_scan_params(&scan_params.pending_payable_start_scan) .start_scan_result(Ok(RequestTransactionReceipts { - sent_tx: vec![sent_tx.clone()], + sent_txs: vec![sent_tx.clone()], response_skeleton_opt: None, })) .finish_scan_params(&scan_params.pending_payable_finish_scan) @@ -3522,7 +3522,7 @@ mod tests { response_skeleton_opt: None, }; let request_transaction_receipts_msg = RequestTransactionReceipts { - sent_tx: vec![sent_tx], + sent_txs: vec![sent_tx], response_skeleton_opt: None, }; let qualified_payables_msg = QualifiedPayablesMessage { @@ -4107,7 +4107,7 @@ mod tests { assert_eq!( received_msg, &RequestTransactionReceipts { - sent_tx: vec![sent_tx_1, sent_tx_2], + sent_txs: vec![sent_tx_1, sent_tx_2], response_skeleton_opt: None, } ); diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index 38c3f809e..ad1ea40e5 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -313,7 +313,7 @@ impl Scanners { } // This is a helper function reducing a boilerplate of complex trait resolving where - // the compiler requires to specify which trigger message distinguish the scan to run. + // the compiler requires to specify which trigger message distinguishes the scan to run. // The payable scanner offers two modes through doubled implementations of StartableScanner // which uses the trigger message type as the only distinction between them. fn start_correct_payable_scanner<'a, TriggerMessage>( @@ -684,16 +684,19 @@ impl PayableScanner { fn check_for_missing_records( &self, - just_reported_sent_payables: &[&PendingPayable], + just_baked_sent_payables: &[&PendingPayable], ) -> Vec { - let actual_sent_payables_len = just_reported_sent_payables.len(); - let hashset_with_hashes_to_eliminate_duplicities = just_reported_sent_payables + let actual_sent_payables_len = just_baked_sent_payables.len(); + let hashset_with_hashes_to_eliminate_duplicities = just_baked_sent_payables .iter() .map(|pending_payable| pending_payable.hash) .collect::>(); if hashset_with_hashes_to_eliminate_duplicities.len() != actual_sent_payables_len { - todo!("check potential duplicity") + panic!( + "Found duplicities in the recent sent txs: {:?}", + just_baked_sent_payables + ); } let transaction_hashes_and_rowids_from_db = self @@ -710,7 +713,7 @@ impl PayableScanner { .copied() .collect(); - let mut sent_payables_hashmap = just_reported_sent_payables + let mut sent_payables_hashmap = just_baked_sent_payables .iter() .map(|payable| (payable.hash, &payable.recipient_wallet)) .collect::>(); @@ -726,69 +729,17 @@ impl PayableScanner { .collect() } - // fn separate_existent_and_nonexistent_fingerprints<'a>( - // &'a self, - // sent_payables: &[&'a PendingPayable], - // ) -> (Vec, Vec) { - // let actual_sent_payables_simple_total = sent_payables.len(); - // - // let hashset_with_actual_sent_payable_hashes = sent_payables - // .iter() - // .map(|pending_payable| pending_payable.hash) - // .collect::>(); - // - // if hashset_with_actual_sent_payable_hashes.len() != actual_sent_payables_simple_total { - // todo!("check potential duplicity") - // } - // - // let mut sent_payables_hashmap = sent_payables - // .iter() - // .map(|payable| (payable.hash, &payable.recipient_wallet)) - // .collect::>(); - // - // let transaction_hashes = self.sent_payable_dao.get_tx_identifiers(&hashset_with_actual_sent_payable_hashes); - // let hashes_from_db = transaction_hashes - // .keys() - // .collect::>(); - // - // let missing_sent_payables_hashes = hashset_with_actual_sent_payable_hashes.difference(&hashes_from_db).collect(); - // - // let pending_payables_with_rowid = transaction_hashes - // .rowid_results - // .into_iter() - // .map(|(rowid, hash)| { - // let wallet = sent_payables_hashmap - // .remove(&hash) - // .expect("expect transaction hash, but it disappear"); - // PendingPayableMissingInDb::new(wallet, hash, Some(rowid)) - // }) - // .collect_vec(); - // let pending_payables_without_rowid = transaction_hashes - // .no_rowid_results - // .into_iter() - // .map(|hash| { - // let wallet = sent_payables_hashmap - // .remove(&hash) - // .expect("expect transaction hash, but it disappear"); - // PendingPayableMissingInDb::new(wallet, hash, None) - // }) - // .collect_vec(); - // - // (pending_payables_with_rowid, pending_payables_without_rowid) - // } - fn check_on_missing_sent_tx_records(&self, sent_payments: &[&PendingPayable]) { fn missing_fingerprints_msg(nonexistent: &[PendingPayableMissingInDb]) -> String { format!( - "Expected pending payable fingerprints for {} were not found; system unreliable", - comma_joined_stringifiable(nonexistent, |pp_triple| format!( - "(tx: {:?}, to wallet: {})", - pp_triple.hash, pp_triple.recipient + "Expected sent-payable records for {} were not found. The system is unreliable", + comma_joined_stringifiable(nonexistent, |missing_sent_tx_ids| format!( + "(tx: {:?}, to wallet: {:?})", + missing_sent_tx_ids.hash, missing_sent_tx_ids.recipient )) ) } - todo!("drive me in"); let missing_sent_tx_records = self.check_for_missing_records(sent_payments); if !missing_sent_tx_records.is_empty() { panic!("{}", missing_fingerprints_msg(&missing_sent_tx_records)) @@ -796,6 +747,7 @@ impl PayableScanner { } // TODO this has become dead (GH-662) + #[allow(dead_code)] fn mark_pending_payable(&self, sent_payments: &[&PendingPayable], logger: &Logger) { todo!("remove me when the time comes") // fn missing_fingerprints_msg(nonexistent: &[PendingPayableMissingInDb]) -> String { @@ -869,7 +821,8 @@ impl PayableScanner { } else { debug!( logger, - "A non-fatal error on our end from before the tx hashing will be ignored: {:?}", + "A non-fatal error {:?} will be ignored as it is from before any tx could \ + even be hashed", err ) } @@ -887,7 +840,16 @@ impl PayableScanner { let existent_sent_tx_in_db = self.sent_payable_dao.get_tx_identifiers(&hashes_of_failed); - let hashes_of_missing_sent_tx = todo!(); + let hashes_of_missing_sent_tx = hashes_of_failed + .difference( + &existent_sent_tx_in_db + .keys() + .copied() + .collect::>(), + ) + .copied() + .sorted() + .collect(); let missing_fgp_err_msg_opt = err_msg_for_failure_with_expected_but_missing_fingerprints( hashes_of_missing_sent_tx, @@ -895,11 +857,14 @@ impl PayableScanner { ); if !existent_sent_tx_in_db.is_empty() { - //let (ids, hashes) = separate_rowids_and_hashes(existent_sent_tx_in_db.rowid_results); - let hashes = existent_sent_tx_in_db.keys().copied().collect_vec(); + let hashes = existent_sent_tx_in_db + .keys() + .copied() + .sorted() + .collect_vec(); warning!( logger, - "Deleting fingerprints for failed transactions {}", + "Deleting sent payable records for {}", serialize_hashes(&hashes) ); if let Err(e) = self @@ -910,8 +875,8 @@ impl PayableScanner { error!(logger, "{}", msg) }; panic!( - "Database corrupt: sent tx record deletion for transactions {} \ - failed due to {:?}", + "Database corrupt: sent payable record deletion for txs {} failed \ + due to {:?}", serialize_hashes(&hashes), e ) @@ -970,7 +935,7 @@ impl StartableScanner pending_sent_txs.len() ); Ok(RequestTransactionReceipts { - sent_tx: pending_sent_txs, + sent_txs: pending_sent_txs, response_skeleton_opt, }) } @@ -992,7 +957,7 @@ impl Scanner for PendingPayableScan debug!( logger, - "Processing receipts for {} transactions", + "Processing receipts for {} txs", message.results.len() ); let scan_report = self.handle_receipts_for_pending_transactions(message, logger); @@ -1130,8 +1095,8 @@ impl PendingPayableScanner { .map(|tx| tx.receiver_address) .collect_vec(); panic!( - "Unable to complete the tx confirmation by the adjustment of the payable \ - accounts {} due to {:?}", + "Unable to complete the tx confirmation by the adjustment of the payable accounts {} \ + due to {:?}", comma_joined_stringifiable(&wallets, |wallet| format!("{:?}", wallet)), e ) @@ -1142,9 +1107,9 @@ impl PendingPayableScanner { e: SentPayableDaoError, ) -> ! { panic!( - "Unable to update sent tx records {} with their tx blocks due to {:?}", + "Unable to update sent payable records {} with their tx blocks due to {:?}", comma_joined_stringifiable( - &tx_hashes_and_tx_blocks.keys().collect_vec(), + &tx_hashes_and_tx_blocks.keys().sorted().collect_vec(), |tx_hash| format!("{:?}", tx_hash) ), e @@ -1191,7 +1156,9 @@ impl PendingPayableScanner { fn handle_failed_transactions(&self, failures: Vec, logger: &Logger) { fn joint_hashes(hashes: &HashSet) -> String { - comma_joined_stringifiable(&hashes.iter().collect_vec(), |hash| format!("{:?}", hash)) + comma_joined_stringifiable(&hashes.iter().sorted().collect_vec(), |hash| { + format!("{:?}", hash) + }) } if !failures.is_empty() { @@ -1201,10 +1168,10 @@ impl PendingPayableScanner { .collect::>(); match self.failed_payable_dao.insert_new_records(&failures) { Ok(_) => { - debug!(logger, "Failed txs {:?} recorded", hashes); + debug!(logger, "Failed payables {:?} recorded", hashes); } Err(e) => panic!( - "Unable to record failed txs {} due to {:?}", + "Unable to record failed payables {} due to {:?}", joint_hashes(&hashes), e ), @@ -2035,7 +2002,7 @@ mod tests { fn payable_scanner_handles_sent_payable_message() { init_test_logging(); let test_name = "payable_scanner_handles_sent_payable_message"; - let tx_rowids_params_arc = Arc::new(Mutex::new(vec![])); + let get_tx_identifiers_params_arc = Arc::new(Mutex::new(vec![])); let mark_pending_payables_params_arc = Arc::new(Mutex::new(vec![])); let delete_records_params_arc = Arc::new(Mutex::new(vec![])); let correct_payable_hash_1 = make_tx_hash(0x6f); @@ -2048,7 +2015,7 @@ mod tests { let failure_payable_wallet_2 = make_wallet("hihihi"); let failure_payable_2 = RpcPayableFailure { rpc_error: Error::InvalidResponse( - "Learn how to write before you send your garbage!".to_string(), + "Ged rid of your illiteracy before you send your garbage!".to_string(), ), recipient_wallet: failure_payable_wallet_2, hash: failure_payable_hash_2, @@ -2059,7 +2026,7 @@ mod tests { let correct_pending_payable_3 = PendingPayable::new(correct_payable_wallet_3.clone(), correct_payable_hash_3); let sent_payable_dao = SentPayableDaoMock::default() - .get_tx_identifiers_params(&tx_rowids_params_arc) + .get_tx_identifiers_params(&get_tx_identifiers_params_arc) .get_tx_identifiers_result(hashmap!(correct_payable_hash_3 => correct_payable_rowid_3, correct_payable_hash_1 => correct_payable_rowid_1, )) @@ -2103,22 +2070,14 @@ mod tests { assert_eq!(is_scan_running, false); assert_eq!(aware_of_unresolved_pending_payable_before, false); assert_eq!(aware_of_unresolved_pending_payable_after, true); - let tx_rowids_params = tx_rowids_params_arc.lock().unwrap(); + let get_tx_identifiers_params = get_tx_identifiers_params_arc.lock().unwrap(); assert_eq!( - *tx_rowids_params, + *get_tx_identifiers_params, vec![ hashset![correct_payable_hash_1, correct_payable_hash_3], hashset![failure_payable_hash_2] ] ); - let mark_pending_payables_params = mark_pending_payables_params_arc.lock().unwrap(); - assert_eq!( - *mark_pending_payables_params, - vec![vec![ - (correct_payable_wallet_3, correct_payable_rowid_3), - (correct_payable_wallet_1, correct_payable_rowid_1), - ]] - ); let delete_fingerprints_params = delete_records_params_arc.lock().unwrap(); assert_eq!( *delete_fingerprints_params, @@ -2127,17 +2086,14 @@ mod tests { let log_handler = TestLogHandler::new(); log_handler.assert_logs_contain_in_order(vec![ &format!( - "WARN: {test_name}: Remote transaction failure: 'Got invalid response: Learn how to write before you send your garbage!' \ - for payment to 0x0000000000000000000000000000686968696869 and transaction hash \ - 0x00000000000000000000000000000000000000000000000000000000000000de. Please check your blockchain service URL configuration" + "WARN: {test_name}: Remote sent payable failure 'Got invalid response: Ged rid of \ + your illiteracy before you send your garbage!' \ + for wallet 0x0000000000000000000000000000686968696869 and tx hash \ + 0x00000000000000000000000000000000000000000000000000000000000000de" ), &format!("DEBUG: {test_name}: Got 2 properly sent payables of 3 attempts"), &format!( - "DEBUG: {test_name}: Payables 0x000000000000000000000000000000000000000000000000000000000000006f, \ - 0x000000000000000000000000000000000000000000000000000000000000014d marked as pending in the payable table" - ), - &format!( - "WARN: {test_name}: Deleting fingerprints for failed transactions \ + "WARN: {test_name}: Deleting sent payable records for \ 0x00000000000000000000000000000000000000000000000000000000000000de" ), ]); @@ -2181,182 +2137,51 @@ mod tests { ); } - struct TestingMismatchedDataAboutPendingPayables { - pending_payables: Vec, - common_hash_1: H256, - common_hash_3: H256, - intruder_for_hash_2: H256, - } - - fn prepare_values_for_mismatched_setting() -> TestingMismatchedDataAboutPendingPayables { + #[test] + #[should_panic( + expected = "Found duplicities in the recent sent txs: [PendingPayable { recipient_wallet: \ + Wallet { kind: Address(0x0000000000000000000000000000000000616263) }, hash: \ + 0x000000000000000000000000000000000000000000000000000000000000007b }, PendingPayable { \ + recipient_wallet: Wallet { kind: Address(0x0000000000000000000000000000000000646566) }, \ + hash: 0x00000000000000000000000000000000000000000000000000000000000001c8 }, \ + PendingPayable { recipient_wallet: Wallet { kind: \ + Address(0x0000000000000000000000000000000000676869) }, hash: \ + 0x00000000000000000000000000000000000000000000000000000000000001c8 }, PendingPayable { \ + recipient_wallet: Wallet { kind: Address(0x00000000000000000000000000000000006a6b6c) }, \ + hash: 0x0000000000000000000000000000000000000000000000000000000000000315 }]" + )] + fn just_baked_pending_payables_contain_duplicities() { let hash_1 = make_tx_hash(123); let hash_2 = make_tx_hash(456); let hash_3 = make_tx_hash(789); - let intruder = make_tx_hash(567); let pending_payables = vec![ PendingPayable::new(make_wallet("abc"), hash_1), PendingPayable::new(make_wallet("def"), hash_2), - PendingPayable::new(make_wallet("ghi"), hash_3), + PendingPayable::new(make_wallet("ghi"), hash_2), + PendingPayable::new(make_wallet("jkl"), hash_3), ]; - TestingMismatchedDataAboutPendingPayables { - pending_payables, - common_hash_1: hash_1, - common_hash_3: hash_3, - intruder_for_hash_2: intruder, - } - } - - #[test] - #[should_panic( - expected = "Inconsistency in two maps, they cannot be matched by hashes. \ - Data set directly sent from BlockchainBridge: \ - [PendingPayable { recipient_wallet: Wallet { kind: Address(0x0000000000000000000000000000000000616263) }, \ - hash: 0x000000000000000000000000000000000000000000000000000000000000007b }, \ - PendingPayable { recipient_wallet: Wallet { kind: Address(0x0000000000000000000000000000000000646566) }, \ - hash: 0x00000000000000000000000000000000000000000000000000000000000001c8 }, \ - PendingPayable { recipient_wallet: Wallet { kind: Address(0x0000000000000000000000000000000000676869) }, \ - hash: 0x0000000000000000000000000000000000000000000000000000000000000315 }], \ - set derived from the DB: \ - TransactionHashes { rowid_results: \ - [(4, 0x000000000000000000000000000000000000000000000000000000000000007b), \ - (1, 0x0000000000000000000000000000000000000000000000000000000000000237), \ - (3, 0x0000000000000000000000000000000000000000000000000000000000000315)], \ - no_rowid_results: [] }" - )] - fn two_sourced_information_of_new_pending_payables_and_their_fingerprints_is_not_symmetrical() { - todo!("rewrite me to test duplicity, mavbeeee?") - // let vals = prepare_values_for_mismatched_setting(); - // let pending_payables_ref = vals - // .pending_payables - // .iter() - // .collect::>(); - // // let sent_payable_dao = - // // SentPayableDaoMock::new().get_tx_identifiers_result(hashmap!(vals.common_hash_1 => 1, vals.common_hash_3 => 3)); - // // rowid_results: vec![ - // // (4, vals.common_hash_1), - // // (1, vals.intruder_for_hash_2), - // // (3, vals.common_hash_3), - // // ], - // // no_rowid_results: vec![], - // // }); - // let subject = PayableScannerBuilder::new() - // .sent_payable_dao(sent_payable_dao) - // .build(); - // - // subject.check_for_missing_records(&pending_payables_ref); - } - - // #[test] - // fn symmetry_check_happy_path() { - // let hash_1 = make_tx_hash(123); - // let hash_2 = make_tx_hash(456); - // let hash_3 = make_tx_hash(789); - // let pending_payables_sent_from_blockchain_bridge = vec![ - // PendingPayable::new(make_wallet("abc"), hash_1), - // PendingPayable::new(make_wallet("def"), hash_2), - // PendingPayable::new(make_wallet("ghi"), hash_3), - // ]; - // let pending_payables_ref = pending_payables_sent_from_blockchain_bridge - // .iter() - // .map(|ppayable| ppayable.hash) - // .collect::>(); - // let hashes_from_fingerprints = vec![(hash_1, 3), (hash_2, 5), (hash_3, 6)] - // .iter() - // .map(|(hash, _id)| *hash) - // .collect::>(); - // - // let result = PayableScanner::is_symmetrical(pending_payables_ref, hashes_from_fingerprints); - // - // assert_eq!(result, true) - // } - - // #[test] - // fn symmetry_check_sad_path_for_intruder() { - // let vals = prepare_values_for_mismatched_setting(); - // let pending_payables_ref_from_blockchain_bridge = vals - // .pending_payables - // .iter() - // .map(|ppayable| ppayable.hash) - // .collect::>(); - // let rowids_and_hashes_from_fingerprints = vec![ - // (vals.common_hash_1, 3), - // (vals.intruder_for_hash_2, 5), - // (vals.common_hash_3, 6), - // ] - // .iter() - // .map(|(hash, _rowid)| *hash) - // .collect::>(); - // - // let result = PayableScanner::is_symmetrical( - // pending_payables_ref_from_blockchain_bridge, - // rowids_and_hashes_from_fingerprints, - // ); - // - // assert_eq!(result, false) - // } - - // #[test] - // fn symmetry_check_indifferent_to_wrong_order_on_the_input() { - // let hash_1 = make_tx_hash(123); - // let hash_2 = make_tx_hash(456); - // let hash_3 = make_tx_hash(789); - // let pending_payables_sent_from_blockchain_bridge = vec![ - // PendingPayable::new(make_wallet("abc"), hash_1), - // PendingPayable::new(make_wallet("def"), hash_2), - // PendingPayable::new(make_wallet("ghi"), hash_3), - // ]; - // let bb_returned_p_payables_ref = pending_payables_sent_from_blockchain_bridge - // .iter() - // .map(|ppayable| ppayable.hash) - // .collect::>(); - // // Not in ascending order - // let rowids_and_hashes_from_fingerprints = vec![(hash_1, 3), (hash_3, 5), (hash_2, 6)] - // .iter() - // .map(|(hash, _id)| *hash) - // .collect::>(); - // - // let result = PayableScanner::is_symmetrical( - // bb_returned_p_payables_ref, - // rowids_and_hashes_from_fingerprints, - // ); - // - // assert_eq!(result, true) - // } - - #[test] - #[should_panic( - expected = "Expected pending payable fingerprints for (tx: 0x0000000000000000000000000000000000000000000000000000000000000315, \ - to wallet: 0x000000000000000000000000000000626f6f6761), (tx: 0x000000000000000000000000000000000000000000000000000000000000007b, \ - to wallet: 0x00000000000000000000000000000061676f6f62) were not found; system unreliable" - )] - fn payable_scanner_panics_when_fingerprints_for_correct_payments_not_found() { - let hash_1 = make_tx_hash(0x315); - let payment_1 = PendingPayable::new(make_wallet("booga"), hash_1); - let hash_2 = make_tx_hash(0x7b); - let payment_2 = PendingPayable::new(make_wallet("agoob"), hash_2); - let sent_payable_dao = SentPayableDaoMock::default().get_tx_identifiers_result(hashmap!()); - let payable_dao = PayableDaoMock::new(); - let mut subject = PayableScannerBuilder::new() - .payable_dao(payable_dao) + let pending_payables_ref = pending_payables.iter().collect::>(); + let sent_payable_dao = SentPayableDaoMock::new() + .get_tx_identifiers_result(hashmap!(hash_1 => 1, hash_2 => 3, hash_3 => 5)); + let subject = PayableScannerBuilder::new() .sent_payable_dao(sent_payable_dao) .build(); - let sent_payable = SentPayables { - payment_procedure_result: Ok(vec![ - ProcessedPayableFallible::Correct(payment_1), - ProcessedPayableFallible::Correct(payment_2), - ]), - response_skeleton_opt: None, - }; - let _ = subject.finish_scan(sent_payable, &Logger::new("test")); + subject.check_for_missing_records(&pending_payables_ref); } - fn assert_panic_from_failing_to_mark_pending_payable_rowid( - test_name: &str, - sent_payable_dao: SentPayableDaoMock, - hash_1: H256, - hash_2: H256, - ) { + #[test] + #[should_panic(expected = "Expected sent-payable records for \ + (tx: 0x00000000000000000000000000000000000000000000000000000000000000f8, \ + to wallet: 0x00000000000000000000000000626c6168323232) \ + were not found. The system is unreliable")] + fn payable_scanner_found_out_nonexistent_sent_tx_records() { + init_test_logging(); + let test_name = "payable_scanner_found_out_nonexistent_sent_tx_records"; + let hash_1 = make_tx_hash(0xff); + let hash_2 = make_tx_hash(0xf8); + let sent_payable_dao = + SentPayableDaoMock::default().get_tx_identifiers_result(hashmap!(hash_1 => 7881)); let payable_1 = PendingPayable::new(make_wallet("blah111"), hash_1); let payable_2 = PendingPayable::new(make_wallet("blah222"), hash_2); let payable_dao = PayableDaoMock::new().mark_pending_payables_rowids_result(Err( @@ -2374,66 +2199,14 @@ mod tests { response_skeleton_opt: None, }; - let caught_panic_in_err = catch_unwind(AssertUnwindSafe(|| { - subject.finish_scan(sent_payables, &Logger::new(test_name)) - })); - - let caught_panic = caught_panic_in_err.unwrap_err(); - let panic_msg = caught_panic.downcast_ref::().unwrap(); - assert_eq!( - panic_msg, - "Unable to create a mark in the payable table for wallets 0x00000000000\ - 000000000000000626c6168313131, 0x00000000000000000000000000626c6168323232 due to \ - SignConversion(9999999999999)" - ); - } - - #[test] - fn payable_scanner_mark_pending_payable_only_panics_all_fingerprints_found() { - init_test_logging(); - let test_name = "payable_scanner_mark_pending_payable_only_panics_all_fingerprints_found"; - let hash_1 = make_tx_hash(248); - let hash_2 = make_tx_hash(139); - let sent_payable_dao = SentPayableDaoMock::default() - .get_tx_identifiers_result(hashmap!(hash_1 => 7879, hash_2 => 7881)); - - assert_panic_from_failing_to_mark_pending_payable_rowid( - test_name, - sent_payable_dao, - hash_1, - hash_2, - ); - - // Missing fingerprints, being an additional issue, would provoke an error log, but not here. - TestLogHandler::new().exists_no_log_containing(&format!("ERROR: {test_name}:")); - } - - #[test] - fn payable_scanner_mark_pending_payable_panics_nonexistent_fingerprints_also_found() { - init_test_logging(); - let test_name = - "payable_scanner_mark_pending_payable_panics_nonexistent_fingerprints_also_found"; - let hash_1 = make_tx_hash(0xff); - let hash_2 = make_tx_hash(0xf8); - let sent_payable_dao = - SentPayableDaoMock::default().get_tx_identifiers_result(hashmap!(hash_1 => 7881)); - assert_panic_from_failing_to_mark_pending_payable_rowid( - test_name, - sent_payable_dao, - hash_1, - hash_2, - ); - - TestLogHandler::new().exists_log_containing(&format!("ERROR: {test_name}: Expected pending payable \ - fingerprints for (tx: 0x00000000000000000000000000000000000000000000000000000000000000f8, to wallet: \ - 0x00000000000000000000000000626c6168323232) were not found; system unreliable")); + subject.finish_scan(sent_payables, &Logger::new(test_name)); } #[test] - fn payable_scanner_is_facing_failed_transactions_and_their_fingerprints_exist() { + fn payable_scanner_is_facing_failed_transactions_and_their_sent_tx_records_exist() { init_test_logging(); let test_name = - "payable_scanner_is_facing_failed_transactions_and_their_fingerprints_exist"; + "payable_scanner_is_facing_failed_transactions_and_their_sent_tx_records_exist"; let get_tx_identifiers_params_arc = Arc::new(Mutex::new(vec![])); let delete_records_params_arc = Arc::new(Mutex::new(vec![])); let hash_tx_1 = make_tx_hash(0x15b3); @@ -2487,15 +2260,20 @@ mod tests { vec![hashset!(hash_tx_1, hash_tx_2)] ); let log_handler = TestLogHandler::new(); - log_handler.exists_log_containing(&format!("WARN: {test_name}: \ - Any persisted data from failed process will be deleted. Caused by: Sending phase: \"Attempt failed\". \ - Signed and hashed transactions: 0x000000000000000000000000000000000000000000000000000\ - 00000000015b3, 0x0000000000000000000000000000000000000000000000000000000000003039")); - log_handler.exists_log_containing( - &format!("WARN: {test_name}: \ - Deleting fingerprints for failed transactions 0x00000000000000000000000000000000000000000000000000000000000015b3, \ + log_handler.exists_log_containing(&format!( + "WARN: {test_name}: \ + Any persisted data from the failed process will be deleted. Caused by: Sending phase: \ + \"Attempt failed\". \ + Signed and hashed txs: \ + 0x00000000000000000000000000000000000000000000000000000000000015b3, \ + 0x0000000000000000000000000000000000000000000000000000000000003039" + )); + log_handler.exists_log_containing(&format!( + "WARN: {test_name}: \ + Deleting sent payable records for \ + 0x00000000000000000000000000000000000000000000000000000000000015b3, \ 0x0000000000000000000000000000000000000000000000000000000000003039", - )); + )); // we haven't supplied any result for mark_pending_payable() and so it's proved uncalled } @@ -2525,8 +2303,8 @@ mod tests { "DEBUG: {test_name}: Got 0 properly sent payables of an unknown number of attempts" )); log_handler.exists_log_containing(&format!( - "DEBUG: {test_name}: Ignoring a non-fatal error on our end from before \ - the transactions are hashed: LocallyCausedError(Signing(\"Some error\"))" + "DEBUG: {test_name}: A non-fatal error LocallyCausedError(Signing(\"Some error\")) \ + will be ignored as it is from before any tx could even be hashed" )); } @@ -2548,7 +2326,7 @@ mod tests { let sent_payable_dao = SentPayableDaoMock::default() .get_tx_identifiers_result(hashmap!(hash_1 => rowid_1, hash_2 => rowid_2)) .delete_records_result(Err(SentPayableDaoError::SqlExecutionFailed( - "Gosh, I overslept without an alarm set".to_string(), + "I overslept since my brain thinks the alarm is just a lullaby".to_string(), ))); let mut subject = PayableScannerBuilder::new() .sent_payable_dao(sent_payable_dao) @@ -2562,10 +2340,10 @@ mod tests { let panic_msg = caught_panic.downcast_ref::().unwrap(); assert_eq!( panic_msg, - "Database corrupt: sent tx record deletion for transactions \ + "Database corrupt: sent payable record deletion for txs \ 0x000000000000000000000000000000000000000000000000000000000000007b, 0x00000000000000000000\ - 00000000000000000000000000000000000000000315 failed due to RecordDeletion(\"Gosh, I overslept \ - without an alarm set\")"); + 00000000000000000000000000000000000000000315 failed due to SqlExecutionFailed(\"I overslept \ + since my brain thinks the alarm is just a lullaby\")"); let log_handler = TestLogHandler::new(); // There is a possible situation when we stumble over missing fingerprints, so we log it. // Here we don't and so any ERROR log shouldn't turn up @@ -2600,18 +2378,23 @@ mod tests { let caught_panic = caught_panic_in_err.unwrap_err(); let panic_msg = caught_panic.downcast_ref::().unwrap(); - assert_eq!(panic_msg, "Ran into failed transactions 0x0000000000000000000000000000000000\ - 000000000000000000000000003039, 0x000000000000000000000000000000000000000000000000000000000000223d \ - with missing fingerprints. System no longer reliable"); + assert_eq!( + panic_msg, + "Ran into failed payables \ + 0x000000000000000000000000000000000000000000000000000000000000223d, \ + 0x0000000000000000000000000000000000000000000000000000000000003039 \ + with missing records. The system is unreliable" + ); let log_handler = TestLogHandler::new(); - log_handler.exists_log_containing( - &format!("WARN: {test_name}: Any persisted data from failed process will be deleted. Caused by: \ - Sending phase: \"SQLite migraine\". Signed and hashed transactions: \ - 0x000000000000000000000000000000000000000000000000000000000001b669, \ - 0x0000000000000000000000000000000000000000000000000000000000003039, \ - 0x000000000000000000000000000000000000000000000000000000000000223d")); log_handler.exists_log_containing(&format!( - "WARN: {test_name}: Deleting fingerprints for failed transactions {:?}", + "WARN: {test_name}: Any persisted data from the failed process will \ + be deleted. Caused by: Sending phase: \"SQLite migraine\". Signed and hashed txs: \ + 0x000000000000000000000000000000000000000000000000000000000000223d, \ + 0x0000000000000000000000000000000000000000000000000000000000003039, \ + 0x000000000000000000000000000000000000000000000000000000000001b669" + )); + log_handler.exists_log_containing(&format!( + "WARN: {test_name}: Deleting sent payable records for {:?}", hash_1 )); } @@ -2619,8 +2402,8 @@ mod tests { #[test] fn payable_scanner_for_failed_rpcs_one_fingerprint_missing_and_deletion_of_the_other_one_fails() { - // Two fatal failures at once, missing sent tx records and another record deletion error are both - // legitimate reasons for panic + // Two fatal failures at once, missing sent tx records and another record deletion error + // are both legitimate reasons for panic init_test_logging(); let test_name = "payable_scanner_for_failed_rpcs_one_fingerprint_missing_and_deletion_of_the_other_one_fails"; let existent_record_hash = make_tx_hash(0xb26e); @@ -2659,20 +2442,33 @@ mod tests { let panic_msg = caught_panic.downcast_ref::().unwrap(); assert_eq!( panic_msg, - "Database corrupt: sent tx record deletion for transactions 0x00000000000000000000000\ - 0000000000000000000000000000000000000b26e failed due to RecordDeletion(\"Another failure. Really???\")"); + "Database corrupt: sent payable record deletion for txs \ + 0x000000000000000000000000000000000000000000000000000000000000b26e failed due to \ + SqlExecutionFailed(\"Another failure. Really???\")" + ); let log_handler = TestLogHandler::new(); - log_handler.exists_log_containing(&format!("WARN: {test_name}: Remote transaction failure: 'Server is unreachable' \ - for payment to 0x0000000000000000000000000000000000616263 and transaction hash 0x00000000000000000000000\ - 0000000000000000000000000000000000000b26e. Please check your blockchain service URL configuration.")); - log_handler.exists_log_containing(&format!("WARN: {test_name}: Remote transaction failure: 'Internal Web3 error' \ - for payment to 0x0000000000000000000000000000000000646566 and transaction hash 0x000000000000000000000000\ - 00000000000000000000000000000000000004d2. Please check your blockchain service URL configuration.")); + log_handler.exists_log_containing(&format!( + "WARN: {test_name}: Remote sent payable \ + failure 'Server is unreachable' for wallet 0x0000000000000000000000000000000000616263 \ + and tx hash 0x000000000000000000000000000000000000000000000000000000000000b26e" + )); + log_handler.exists_log_containing(&format!( + "WARN: {test_name}: Remote sent payable \ + failure 'Internal Web3 error' for wallet 0x0000000000000000000000000000000000646566 \ + and tx hash 0x00000000000000000000000000000000000000000000000000000000000004d2" + )); + log_handler.exists_log_containing(&format!( + "WARN: {test_name}: \ + Please check your blockchain service URL configuration due to detected remote failures" + )); log_handler.exists_log_containing(&format!( "DEBUG: {test_name}: Got 0 properly sent payables of 2 attempts" )); - log_handler.exists_log_containing(&format!("ERROR: {test_name}: Ran into failed transactions 0x0000000000000000\ - 0000000000000000000000000000000000000000000004d2 with missing fingerprints. System no longer reliable")); + log_handler.exists_log_containing(&format!( + "ERROR: {test_name}: Ran into failed \ + payables 0x00000000000000000000000000000000000000000000000000000000000004d2 with missing \ + records. The system is unreliable" + )); } #[test] @@ -2736,7 +2532,7 @@ mod tests { gwei_to_wei(DEFAULT_PAYMENT_THRESHOLDS.permanent_debt_allowed_gwei) )] ) - //no other method was called (absence of panic) and that means we returned early + //no other method was called (absence of panic), and that means we returned early } #[test] @@ -2861,9 +2657,9 @@ mod tests { assert_eq!(result, vec![qualified_payable]); TestLogHandler::new().exists_log_matching(&format!( - "DEBUG: {}: Paying qualified debts:\n999,999,999,000,000,\ - 000 wei owed for \\d+ sec exceeds threshold: 500,000,000,000,000,000 wei; creditor: \ - 0x0000000000000000000000000077616c6c657430", + "DEBUG: {}: Paying qualified debts:\n\ + 999,999,999,000,000,000 wei owed for \\d+ sec exceeds the threshold \ + 500,000,000,000,000,000 wei for creditor 0x0000000000000000000000000077616c6c657430", test_name )); } @@ -2897,64 +2693,48 @@ mod tests { #[test] fn pending_payable_scanner_can_initiate_a_scan() { - todo!("fix me"); - // init_test_logging(); - // let test_name = "pending_payable_scanner_can_initiate_a_scan"; - // let consuming_wallet = make_paying_wallet(b"consuming wallet"); - // let now = SystemTime::now(); - // let payable_fingerprint_1 = SentTx { - // rowid: 555, - // timestamp: from_unix_timestamp(210_000_000), - // hash: make_tx_hash(45678), - // attempt: 1, - // amount_minor: 4444, - // process_error: None, - // }; - // let payable_fingerprint_2 = SentTx { - // rowid: 550, - // timestamp: from_unix_timestamp(210_000_100), - // hash: make_tx_hash(112233), - // attempt: 1, - // amount_minor: 7999, - // process_error: None, - // }; - // let payables = HashSet::from_iter(vec![payable_fingerprint_1, payable_fingerprint_2]); - // let sent_payable_dao = SentPayableDaoMock::new() - // .return_all_errorless_fingerprints_result(payables.clone()); - // let mut subject = make_dull_subject(); - // let pending_payable_scanner = PendingPayableScannerBuilder::new() - // .sent_payable_dao(sent_payable_dao) - // .build(); - // // Important - // subject.aware_of_unresolved_pending_payable = true; - // subject.pending_payable = Box::new(pending_payable_scanner); - // let payable_scanner = PayableScannerBuilder::new().build(); - // subject.payable = Box::new(payable_scanner); - // - // let result = subject.start_pending_payable_scan_guarded( - // &consuming_wallet, - // now, - // None, - // &Logger::new(test_name), - // true, - // ); - // - // let no_of_pending_payables = payables.len(); - // let is_scan_running = subject.pending_payable.scan_started_at().is_some(); - // assert_eq!(is_scan_running, true); - // assert_eq!( - // result, - // Ok(RequestTransactionReceipts { - // pending_payable_fingerprints: payables, - // response_skeleton_opt: None - // }) - // ); - // TestLogHandler::new().assert_logs_match_in_order(vec![ - // &format!("INFO: {test_name}: Scanning for pending payable"), - // &format!( - // "DEBUG: {test_name}: Found {no_of_pending_payables} pending payables to process" - // ), - // ]) + init_test_logging(); + let test_name = "pending_payable_scanner_can_initiate_a_scan"; + let consuming_wallet = make_paying_wallet(b"consuming wallet"); + let now = SystemTime::now(); + let sent_tx_1 = make_sent_tx(456); + let sent_tx_2 = make_sent_tx(789); + let sent_txs = vec![sent_tx_1, sent_tx_2]; + let sent_payable_dao = SentPayableDaoMock::new().retrieve_txs_result(sent_txs.clone()); + let mut subject = make_dull_subject(); + let pending_payable_scanner = PendingPayableScannerBuilder::new() + .sent_payable_dao(sent_payable_dao) + .build(); + // Important + subject.aware_of_unresolved_pending_payable = true; + subject.pending_payable = Box::new(pending_payable_scanner); + let payable_scanner = PayableScannerBuilder::new().build(); + subject.payable = Box::new(payable_scanner); + + let result = subject.start_pending_payable_scan_guarded( + &consuming_wallet, + now, + None, + &Logger::new(test_name), + true, + ); + + let no_of_pending_payables = sent_txs.len(); + let is_scan_running = subject.pending_payable.scan_started_at().is_some(); + assert_eq!(is_scan_running, true); + assert_eq!( + result, + Ok(RequestTransactionReceipts { + sent_txs, + response_skeleton_opt: None + }) + ); + TestLogHandler::new().assert_logs_match_in_order(vec![ + &format!("INFO: {test_name}: Scanning for pending payable"), + &format!( + "DEBUG: {test_name}: Found {no_of_pending_payables} pending payables to process" + ), + ]) } #[test] @@ -3136,60 +2916,6 @@ mod tests { assert_eq!(subject.initial_pending_payable_scan, true); } - // fn assert_interpreting_none_status_for_pending_payable( - // test_name: &str, - // when_pending_too_long_sec: u64, - // pending_payable_age_sec: u64, - // rowid: u64, - // hash: H256, - // ) -> PendingPayableScanSummary { - // init_test_logging(); - // let when_sent = SystemTime::now().sub(Duration::from_secs(pending_payable_age_sec)); - // let sent_tx = SentTx { - // rowid, - // timestamp: when_sent, - // hash, - // attempt: 1, - // amount_minor: 123, - // process_error: None, - // }; - // let logger = Logger::new(test_name); - // let scan_report = PendingPayableScanSummary::default(); - // - // handle_none_status(scan_report, sent_tx, when_pending_too_long_sec, &logger) - // } - - fn assert_log_msg_and_elapsed_time_in_log_makes_sense( - expected_msg: &str, - elapsed_after: u64, - capture_regex: &str, - ) { - let log_handler = TestLogHandler::default(); - let log_idx = log_handler.exists_log_matching(expected_msg); - let log = log_handler.get_log_at(log_idx); - let capture = captures_for_regex_time_in_sec(&log, capture_regex); - assert!(capture <= elapsed_after) - } - - fn captures_for_regex_time_in_sec(stack: &str, capture_regex: &str) -> u64 { - let capture_regex = Regex::new(capture_regex).unwrap(); - let time_str = capture_regex - .captures(stack) - .unwrap() - .get(1) - .unwrap() - .as_str(); - time_str.parse().unwrap() - } - - fn elapsed_since_secs_back(sec: u64) -> u64 { - SystemTime::now() - .sub(Duration::from_secs(sec)) - .elapsed() - .unwrap() - .as_secs() - } - #[test] fn interpret_transaction_receipt_when_transaction_status_is_a_failure() { init_test_logging(); @@ -3306,7 +3032,7 @@ mod tests { } #[test] - #[should_panic(expected = "Unable to record failed txs \ + #[should_panic(expected = "Unable to record failed payables \ 0x000000000000000000000000000000000000000000000000000000000000014d, \ 0x00000000000000000000000000000000000000000000000000000000000001bc due to NoChange")] fn handle_failed_transactions_panics_when_it_fails_to_insert_failed_tx_record() { @@ -3361,9 +3087,9 @@ mod tests { #[test] #[should_panic( - expected = "Unable to update sent tx records 0x000000000000000000000000000000000\ - 0000000000000000000000000000315, 0x00000000000000000000000000000000000000000000000000\ - 0000000000021a with their tx blocks due to SqlExecutionFailed(\"The database manager is \ + expected = "Unable to update sent payable records 0x000000000000000000000000000000000000000\ + 000000000000000000000021a, 0x0000000000000000000000000000000000000000000000000000000000000315 \ + with their tx blocks due to SqlExecutionFailed(\"The database manager is \ a funny guy, he's fooling around with us\")" )] fn handle_confirmed_transactions_panics_while_updating_sent_payable_records_with_the_tx_blocks() @@ -3447,7 +3173,7 @@ mod tests { log_handler.exists_log_containing(&format!( "INFO: {test_name}: Txs 0x0000000000000000000000000000000000000000000000000000000000000123 \ (block 4578989878), 0x0000000000000000000000000000000000000000000000000000000000000567 \ - (block 7898989878) have completed", + (block 7898989878) have been confirmed", )); } @@ -3614,8 +3340,8 @@ mod tests { #[test] #[should_panic( - expected = "We should never receive an empty list of results. Even missing \ - receipts can be interpreted" + expected = "We should never receive an empty list of results. Even missing receipts can \ + be interpreted" )] fn pending_payable_scanner_handles_empty_report_transaction_receipts_message() { let mut pending_payable_scanner = PendingPayableScannerBuilder::new().build(); diff --git a/node/src/accountant/scanners/scanners_utils.rs b/node/src/accountant/scanners/scanners_utils.rs index 65bbe926d..39ad3f93a 100644 --- a/node/src/accountant/scanners/scanners_utils.rs +++ b/node/src/accountant/scanners/scanners_utils.rs @@ -119,6 +119,11 @@ pub mod payable_scanner_utils { let remote_errs_opt = if separated_txs_by_result.err_results.is_empty() { None } else { + warning!( + logger, + "Please check your blockchain service URL configuration due \ + to detected remote failures" + ); Some(RemotelyCausedErrors(separated_txs_by_result.err_results)) }; let oks = separated_txs_by_result.ok_results; @@ -173,8 +178,7 @@ pub mod payable_scanner_utils { }) => { warning!( logger, - "Remote transaction failure: '{}' for payment to {} and \ - transaction hash {:?}. Please check your blockchain service URL configuration.", + "Remote sent payable failure '{}' for wallet {} and tx hash {:?}", rpc_error, recipient_wallet, hash @@ -198,14 +202,14 @@ pub mod payable_scanner_utils { .duration_since(payable.last_paid_timestamp) .expect("Payable time is corrupt"); format!( - "{} wei owed for {} sec exceeds threshold: {} wei; creditor: {}", + "{} wei owed for {} sec exceeds the threshold {} wei for creditor {}", payable.balance_wei.separate_with_commas(), p_age.as_secs(), threshold_point.separate_with_commas(), payable.wallet ) }) - .join("\n") + .join(".\n") }) } @@ -273,7 +277,7 @@ pub mod payable_scanner_utils { serialize_hashes: fn(&[H256]) -> String, ) -> Option { nonexistent.is_empty().not().then_some(format!( - "Ran into failed transactions {} with missing fingerprints. System no longer reliable", + "Ran into failed payables {} with missing records. The system is unreliable", serialize_hashes(&nonexistent), )) } @@ -599,8 +603,8 @@ mod tests { assert_eq!(errs, Some(LocallyCausedError(error))); TestLogHandler::new().exists_log_containing( "WARN: test_logger: Any persisted data from \ - the failed process will be deleted. Caused by: Sending phase: \"Bad luck\". Signed and hashed \ - transactions: 0x000000000000000000000000000000000000000000000000000000000000007b", + the failed process will be deleted. Caused by: Sending phase: \"Bad luck\". Signed and hashed txs: \ + 0x000000000000000000000000000000000000000000000000000000000000007b", ); } @@ -631,10 +635,10 @@ mod tests { errs, Some(RemotelyCausedErrors(hashset![make_tx_hash(0x315)])) ); - TestLogHandler::new().exists_log_containing("WARN: test_logger: Remote transaction failure: \ - 'Got invalid response: That jackass screwed it up' for payment to 0x000000000000000000000000\ - 00000077686f6f61 and transaction hash 0x0000000000000000000000000000000000000000000000000000\ - 000000000315. Please check your blockchain service URL configuration."); + TestLogHandler::new().exists_log_containing("WARN: test_logger: Remote sent payable \ + failure 'Got invalid response: That jackass screwed it up' for wallet 0x00000000000000000000\ + 000000000077686f6f61 and tx hash 0x000000000000000000000000000000000000000000000000000000000\ + 0000315"); } #[test] @@ -697,10 +701,10 @@ mod tests { payables_debug_summary(&qualified_payables_and_threshold_points, &logger); TestLogHandler::new().exists_log_containing("Paying qualified debts:\n\ - 10,002,000,000,000,000 wei owed for 2678400 sec exceeds threshold: \ - 10,000,000,001,152,000 wei; creditor: 0x0000000000000000000000000077616c6c657430\n\ - 999,999,999,000,000,000 wei owed for 86455 sec exceeds threshold: \ - 999,978,993,055,555,580 wei; creditor: 0x0000000000000000000000000077616c6c657431"); + 10,002,000,000,000,000 wei owed for 2678400 sec exceeds the threshold \ + 10,000,000,001,152,000 wei for creditor 0x0000000000000000000000000077616c6c657430.\n\ + 999,999,999,000,000,000 wei owed for 86455 sec exceeds the threshold \ + 999,978,993,055,555,580 wei for creditor 0x0000000000000000000000000077616c6c657431"); } #[test] diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index 070a06647..7c920d703 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -423,7 +423,7 @@ impl BlockchainBridge { .expect("Accountant is unbound"); Box::new( self.blockchain_interface - .process_transaction_receipts(msg.sent_tx) + .process_transaction_receipts(msg.sent_txs) .map_err(move |e| e.to_string()) .and_then(move |tx_receipt_results| { Self::log_status_of_tx_receipts(&logger, &tx_receipt_results); @@ -1016,7 +1016,8 @@ mod tests { context_id: 4321 }), msg: format!( - "ReportAccountsPayable: Sending phase: \"Transport error: Error(IncompleteMessage)\". Signed and hashed transactions: 0x81d20df32920161727cd20e375e53c2f9df40fd80256a236fb39e444c999fb6c" + "ReportAccountsPayable: Sending phase: \"Transport error: Error(IncompleteMessage)\". \ + Signed and hashed txs: 0x81d20df32920161727cd20e375e53c2f9df40fd80256a236fb39e444c999fb6c" ) } ); @@ -1183,7 +1184,7 @@ mod tests { let peer_actors = peer_actors_builder().accountant(accountant).build(); send_bind_message!(subject_subs, peer_actors); let msg = RequestTransactionReceipts { - sent_tx: vec![sent_tx_1.clone(), sent_tx_2.clone()], + sent_txs: vec![sent_tx_1.clone(), sent_tx_2.clone()], response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, context_id: 4321, @@ -1323,7 +1324,7 @@ mod tests { .report_transaction_receipts_sub_opt = Some(report_transaction_receipt_recipient); subject.scan_error_subs_opt = Some(scan_error_recipient); let msg = RequestTransactionReceipts { - sent_tx: vec![ + sent_txs: vec![ sent_tx_1.clone(), sent_tx_2.clone(), sent_tx_3.clone(), @@ -1394,7 +1395,7 @@ mod tests { .report_transaction_receipts_sub_opt = Some(report_transaction_recipient); subject.scan_error_subs_opt = Some(scan_error_recipient); let msg = RequestTransactionReceipts { - sent_tx: vec![sent_tx_1, sent_tx_2], + sent_txs: vec![sent_tx_1, sent_tx_2], response_skeleton_opt: None, }; let system = System::new("test"); diff --git a/node/src/blockchain/blockchain_interface/data_structures/errors.rs b/node/src/blockchain/blockchain_interface/data_structures/errors.rs index 0c4b13f0e..a2d6e0022 100644 --- a/node/src/blockchain/blockchain_interface/data_structures/errors.rs +++ b/node/src/blockchain/blockchain_interface/data_structures/errors.rs @@ -69,10 +69,10 @@ impl Display for PayableTransactionError { ), Self::Signing(msg) => write!(f, "Signing phase: \"{}\"", msg), Self::Sending { msg, hashes } => { - let hashes = hashes.iter().map(|hash| *hash).collect_vec(); + let hashes = hashes.iter().map(|hash| *hash).sorted().collect_vec(); write!( f, - "Sending phase: \"{}\". Signed and hashed transactions: {}", + "Sending phase: \"{}\". Signed and hashed txs: {}", msg, comma_joined_stringifiable(&hashes, |hash| format!("{:?}", hash)) ) @@ -210,7 +210,7 @@ mod tests { LEDGER wallet, stupid.\"", "Signing phase: \"You cannot sign with just three crosses here, clever boy\"", "Sending phase: \"Sending to cosmos belongs elsewhere\". Signed and hashed \ - transactions: 0x000000000000000000000000000000000000000000000000000000000000006f, \ + txs: 0x000000000000000000000000000000000000000000000000000000000000006f, \ 0x00000000000000000000000000000000000000000000000000000000000000de", BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED ]) diff --git a/node/src/blockchain/blockchain_interface/mod.rs b/node/src/blockchain/blockchain_interface/mod.rs index 0092ecaeb..8b9c430ac 100644 --- a/node/src/blockchain/blockchain_interface/mod.rs +++ b/node/src/blockchain/blockchain_interface/mod.rs @@ -40,7 +40,7 @@ pub trait BlockchainInterface { fn process_transaction_receipts( &self, - sent_tx: Vec, + sent_txs: Vec, ) -> Box, Error = BlockchainError>>; fn submit_payables_in_batch( From 7e224e43ef618ee168786af98033dac076d54c84 Mon Sep 17 00:00:00 2001 From: Bert Date: Wed, 16 Jul 2025 13:50:50 +0200 Subject: [PATCH 13/61] GH-642: the base of this card is done --- node/src/accountant/mod.rs | 89 ++++++++++--------- node/src/accountant/scanners/mod.rs | 8 +- .../src/accountant/scanners/scanners_utils.rs | 2 +- node/src/accountant/test_utils.rs | 22 ++++- 4 files changed, 70 insertions(+), 51 deletions(-) diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index 6c442bda9..1b2e4c201 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -1130,10 +1130,10 @@ impl Accountant { ), Err(e) => error!( self.logger, - "Failed to process new pending payable fingerprints due to '{:?}', \ - disabling the automated confirmation for all these transactions: {}", - e, - serialize_hashes(&msg.new_sent_txs) + "Failed to save new pending payable records for {} due to '{:?}' which is integral \ + to the function of the automated tx confirmation", + serialize_hashes(&msg.new_sent_txs), + e ), } } @@ -1294,6 +1294,7 @@ mod tests { use std::sync::Mutex; use std::time::{Duration, UNIX_EPOCH}; use std::vec; + use crate::accountant::db_access_objects::failed_payable_dao::FailedTx; use crate::accountant::scanners::payable_scanner_extension::msgs::UnpricedQualifiedPayables; use crate::accountant::db_access_objects::sent_payable_dao::{SentPayableDaoError, SentTx}; use crate::accountant::scanners::scan_schedulers::{NewPayableScanDynIntervalComputer, NewPayableScanDynIntervalComputerReal}; @@ -1918,7 +1919,7 @@ mod tests { let payable_dao = PayableDaoMock::default() .transactions_confirmed_params(&transaction_confirmed_params_arc) .transactions_confirmed_result(Ok(())); - let sent_payable_dao = SentPayableDaoMock::default().delete_records_result(Ok(())); + let sent_payable_dao = SentPayableDaoMock::default().update_tx_blocks_result(Ok(())); let mut subject = AccountantBuilder::default() .payable_daos(vec![ForPendingPayableScanner(payable_dao)]) .sent_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) @@ -1938,14 +1939,15 @@ mod tests { Box::new(NotifyHandleMock::default().panic_on_schedule_attempt()); subject.ui_message_sub_opt = Some(ui_gateway_addr.recipient()); let subject_addr = subject.start(); - let sent_tx = make_sent_tx(123); + let mut sent_tx = make_sent_tx(123); + let tx_block = TransactionBlock { + block_hash: make_tx_hash(456), + block_number: 78901234.into(), + }; let tx_receipts_msg = TxReceiptsMessage { results: vec![TxReceiptResult::RpcResponse(TxWithStatus::new( sent_tx.clone(), - TxStatus::Succeeded(TransactionBlock { - block_hash: make_tx_hash(456), - block_number: 78901234.into(), - }), + TxStatus::Succeeded(tx_block), ))], response_skeleton_opt, }; @@ -1954,6 +1956,7 @@ mod tests { system.run(); let transaction_confirmed_params = transaction_confirmed_params_arc.lock().unwrap(); + sent_tx.block_opt = Some(tx_block); assert_eq!(*transaction_confirmed_params, vec![vec![sent_tx]]); let ui_gateway_recording = ui_gateway_recording_arc.lock().unwrap(); assert_eq!( @@ -2127,22 +2130,28 @@ mod tests { #[test] fn pending_payable_scan_response_is_sent_to_ui_gateway_when_both_participating_scanners_have_completed( ) { + // TODO now only GH-605 logic is missing let response_skeleton_opt = Some(ResponseSkeleton { client_id: 4555, context_id: 5566, }); - // TODO when we have more logic in place with the other cards taken in, we'll need to configure these - // accordingly + let insert_new_records_params_arc = Arc::new(Mutex::new(vec![])); + let delete_records_params_arc = Arc::new(Mutex::new(vec![])); let payable_dao = PayableDaoMock::default().transactions_confirmed_result(Ok(())); let sent_tx = make_sent_tx(123); + let tx_hash = sent_tx.hash; let sent_payable_dao = SentPayableDaoMock::default() .retrieve_txs_result(vec![sent_tx.clone()]) - // TODO: params assertion?? + .delete_records_params(&delete_records_params_arc) .delete_records_result(Ok(())); + let failed_payable_dao = FailedPayableDaoMock::default() + .insert_new_records_params(&insert_new_records_params_arc) + .insert_new_records_result(Ok(())); let mut subject = AccountantBuilder::default() .consuming_wallet(make_wallet("consuming")) .payable_daos(vec![ForPendingPayableScanner(payable_dao)]) .sent_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) + .failed_payable_daos(vec![ForPendingPayableScanner(failed_payable_dao)]) .build(); subject.scan_schedulers.automatic_scans_enabled = false; let (blockchain_bridge, _, blockchain_bridge_recording_arc) = make_recorder(); @@ -2159,7 +2168,7 @@ mod tests { RequestTransactionReceipts, TxReceiptsMessage { results: vec![TxReceiptResult::RpcResponse(TxWithStatus::new( - sent_tx, + sent_tx.clone(), TxStatus::Failed(BlockchainTxFailure::Unrecognized) )),], response_skeleton_opt @@ -2194,6 +2203,11 @@ mod tests { subject_addr.try_send(pending_payable_request).unwrap(); system.run(); + let insert_new_records_params = insert_new_records_params_arc.lock().unwrap(); + let expected_failed_tx = FailedTx::from((sent_tx, BlockchainTxFailure::Unrecognized)); + assert_eq!(*insert_new_records_params, vec![vec![expected_failed_tx]]); + let delete_records_params = delete_records_params_arc.lock().unwrap(); + assert_eq!(*delete_records_params, vec![hashset![tx_hash]]); let ui_gateway_recording = ui_gateway_recording_arc.lock().unwrap(); assert_eq!( ui_gateway_recording.get_record::(0), @@ -4817,7 +4831,6 @@ mod tests { #[test] fn accountant_processes_sent_payables_and_schedules_pending_payable_scanner() { let get_tx_identifiers_params_arc = Arc::new(Mutex::new(vec![])); - let mark_pending_payables_rowids_params_arc = Arc::new(Mutex::new(vec![])); let pending_payable_notify_later_params_arc = Arc::new(Mutex::new(vec![])); let expected_wallet = make_wallet("paying_you"); let expected_hash = H256::from("transaction_hash".keccak256()); @@ -4825,14 +4838,10 @@ mod tests { let sent_payable_dao = SentPayableDaoMock::default() .get_tx_identifiers_params(&get_tx_identifiers_params_arc) .get_tx_identifiers_result(hashmap! (expected_hash => expected_rowid)); - let payable_dao = PayableDaoMock::new() - .mark_pending_payables_rowids_params(&mark_pending_payables_rowids_params_arc) - .mark_pending_payables_rowids_result(Ok(())); let system = System::new("accountant_processes_sent_payables_and_schedules_pending_payable_scanner"); let mut subject = AccountantBuilder::default() .bootstrapper_config(bc_from_earning_wallet(make_wallet("some_wallet_address"))) - .payable_daos(vec![ForPayableScanner(payable_dao)]) .sent_payable_daos(vec![ForPayableScanner(sent_payable_dao)]) .build(); let pending_payable_interval = Duration::from_millis(55); @@ -4856,12 +4865,6 @@ mod tests { system.run(); let get_tx_identifiers_params = get_tx_identifiers_params_arc.lock().unwrap(); assert_eq!(*get_tx_identifiers_params, vec![hashset!(expected_hash)]); - let mark_pending_payables_rowids_params = - mark_pending_payables_rowids_params_arc.lock().unwrap(); - assert_eq!( - *mark_pending_payables_rowids_params, - vec![vec![(expected_wallet, expected_rowid)]] - ); let pending_payable_notify_later_params = pending_payable_notify_later_params_arc.lock().unwrap(); assert_eq!( @@ -4988,16 +4991,13 @@ mod tests { #[test] fn accountant_confirms_pending_txs_and_schedules_the_new_payable_scanner_timely() { let transactions_confirmed_params_arc = Arc::new(Mutex::new(vec![])); - let delete_records_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![])); let new_payable_notify_arc = Arc::new(Mutex::new(vec![])); let payable_dao = PayableDaoMock::default() .transactions_confirmed_params(&transactions_confirmed_params_arc) .transactions_confirmed_result(Ok(())); - let sent_payable_dao = SentPayableDaoMock::default() - .delete_records_params(&delete_records_params_arc) - .delete_records_result(Ok(())); + let sent_payable_dao = SentPayableDaoMock::default().update_tx_blocks_result(Ok(())); let system = System::new("new_payable_scanner_timely"); let mut subject = AccountantBuilder::default() .payable_daos(vec![ForPendingPayableScanner(payable_dao)]) @@ -5041,11 +5041,8 @@ mod tests { System::current().stop(); system.run(); - let hashes = two_sent_txs.iter().map(|sent_tx| sent_tx.hash).collect(); let transactions_confirmed_params = transactions_confirmed_params_arc.lock().unwrap(); assert_eq!(*transactions_confirmed_params, vec![two_sent_txs.clone()]); - let delete_records_params = delete_records_params_arc.lock().unwrap(); - assert_eq!(*delete_records_params, vec![hashes]); 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); @@ -5150,14 +5147,14 @@ mod tests { TestLogHandler::new().exists_log_containing(&format!("INFO: {test_name}: \ Txs 0x0000000000000000000000000000000000000000000000000000000000000001 (block 95256152263), \ 0x0000000000000000000000000000000000000000000000000000000000000002 (block 1879080904) have \ - completed")); + been confirmed")); } #[test] fn scheduler_for_new_payables_operates_with_proper_now_timestamp() { let new_payable_notify_later_arc = Arc::new(Mutex::new(vec![])); let payable_dao = PayableDaoMock::default().transactions_confirmed_result(Ok(())); - let sent_payable_dao = SentPayableDaoMock::default().delete_records_result(Ok(())); + let sent_payable_dao = SentPayableDaoMock::default().update_tx_blocks_result(Ok(())); let system = System::new("scheduler_for_new_payables_operates_with_proper_now_timestamp"); let mut subject = AccountantBuilder::default() .payable_daos(vec![ForPendingPayableScanner(payable_dao)]) @@ -5295,26 +5292,30 @@ mod tests { .insert_new_records_result(Err(SentPayableDaoError::SqlExecutionFailed( "Crashed".to_string(), ))); - let tx_hash = make_tx_hash(0x1c8); - let mut sent_tx = make_sent_tx(456); - sent_tx.hash = tx_hash; + let tx_hash_1 = make_tx_hash(0x1c8); + let mut sent_tx_1 = make_sent_tx(456); + sent_tx_1.hash = tx_hash_1; + let tx_hash_2 = make_tx_hash(0x1b2); + let mut sent_tx_2 = make_sent_tx(789); + sent_tx_2.hash = tx_hash_2; let subject = AccountantBuilder::default() .sent_payable_daos(vec![ForAccountantBody(sent_payable_dao)]) + .logger(Logger::new(test_name)) .build(); - let timestamp = SystemTime::now(); let report_new_fingerprints = RegisterNewPendingSentTxMessage { - new_sent_txs: vec![sent_tx.clone()], + new_sent_txs: vec![sent_tx_1.clone(), sent_tx_2.clone()], }; let _ = subject.register_new_pending_sent_tx(report_new_fingerprints); let insert_fingerprint_params = insert_new_records_params_arc.lock().unwrap(); - assert_eq!(*insert_fingerprint_params, vec![vec![sent_tx]]); + assert_eq!(*insert_fingerprint_params, vec![vec![sent_tx_1, sent_tx_2]]); TestLogHandler::new().exists_log_containing(&format!( - "ERROR: {test_name}: Failed to \ - process new pending payable fingerprints due to 'InsertionFailed(\"Crashed\")', disabling \ - the automated confirmation for all these transactions: 0x0000000000000000000000000000000000\ - 0000000000000000000000000001c8" + "ERROR: {test_name}: Failed to save new pending payable records for \ + 0x00000000000000000000000000000000000000000000000000000000000001c8, \ + 0x00000000000000000000000000000000000000000000000000000000000001b2 \ + due to 'SqlExecutionFailed(\"Crashed\")' which is integral to the function \ + of the automated tx confirmation" )); } diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index ad1ea40e5..298460f41 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -732,7 +732,7 @@ impl PayableScanner { fn check_on_missing_sent_tx_records(&self, sent_payments: &[&PendingPayable]) { fn missing_fingerprints_msg(nonexistent: &[PendingPayableMissingInDb]) -> String { format!( - "Expected sent-payable records for {} were not found. The system is unreliable", + "Expected sent-payable records for {} were not found. The system has become unreliable", comma_joined_stringifiable(nonexistent, |missing_sent_tx_ids| format!( "(tx: {:?}, to wallet: {:?})", missing_sent_tx_ids.hash, missing_sent_tx_ids.recipient @@ -2174,7 +2174,7 @@ mod tests { #[should_panic(expected = "Expected sent-payable records for \ (tx: 0x00000000000000000000000000000000000000000000000000000000000000f8, \ to wallet: 0x00000000000000000000000000626c6168323232) \ - were not found. The system is unreliable")] + were not found. The system has become unreliable")] fn payable_scanner_found_out_nonexistent_sent_tx_records() { init_test_logging(); let test_name = "payable_scanner_found_out_nonexistent_sent_tx_records"; @@ -2383,7 +2383,7 @@ mod tests { "Ran into failed payables \ 0x000000000000000000000000000000000000000000000000000000000000223d, \ 0x0000000000000000000000000000000000000000000000000000000000003039 \ - with missing records. The system is unreliable" + with missing records. The system has become unreliable" ); let log_handler = TestLogHandler::new(); log_handler.exists_log_containing(&format!( @@ -2467,7 +2467,7 @@ mod tests { log_handler.exists_log_containing(&format!( "ERROR: {test_name}: Ran into failed \ payables 0x00000000000000000000000000000000000000000000000000000000000004d2 with missing \ - records. The system is unreliable" + records. The system has become unreliable" )); } diff --git a/node/src/accountant/scanners/scanners_utils.rs b/node/src/accountant/scanners/scanners_utils.rs index 39ad3f93a..6e3249ecc 100644 --- a/node/src/accountant/scanners/scanners_utils.rs +++ b/node/src/accountant/scanners/scanners_utils.rs @@ -277,7 +277,7 @@ pub mod payable_scanner_utils { serialize_hashes: fn(&[H256]) -> String, ) -> Option { nonexistent.is_empty().not().then_some(format!( - "Ran into failed payables {} with missing records. The system is unreliable", + "Ran into failed payables {} with missing records. The system has become unreliable", serialize_hashes(&nonexistent), )) } diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs index b250c4971..2351a9cb5 100644 --- a/node/src/accountant/test_utils.rs +++ b/node/src/accountant/test_utils.rs @@ -301,7 +301,11 @@ const PAYABLE_DAOS_ACCOUNTANT_INITIALIZATION_ORDER: [DestinationMarker; 3] = [ DestinationMarker::PendingPayableScanner, ]; -const PENDING_PAYABLE_DAOS_ACCOUNTANT_INITIALIZATION_ORDER: [DestinationMarker; 3] = [ +//TODO Utkarsh should also update this +const FAILED_PAYABLE_DAOS_ACCOUNTANT_INITIALIZATION_ORDER: [DestinationMarker; 1] = + [DestinationMarker::PendingPayableScanner]; + +const SENT_PAYABLE_DAOS_ACCOUNTANT_INITIALIZATION_ORDER: [DestinationMarker; 3] = [ DestinationMarker::AccountantBody, DestinationMarker::PayableScanner, DestinationMarker::PendingPayableScanner, @@ -334,7 +338,7 @@ impl AccountantBuilder { ) -> Self { create_or_update_factory!( specially_configured_daos, - PENDING_PAYABLE_DAOS_ACCOUNTANT_INITIALIZATION_ORDER, + SENT_PAYABLE_DAOS_ACCOUNTANT_INITIALIZATION_ORDER, sent_payable_dao_factory_opt, SentPayableDaoFactoryMock, SentPayableDao, @@ -356,6 +360,20 @@ impl AccountantBuilder { ) } + pub fn failed_payable_daos( + mut self, + specially_configured_daos: Vec>, + ) -> Self { + create_or_update_factory!( + specially_configured_daos, + FAILED_PAYABLE_DAOS_ACCOUNTANT_INITIALIZATION_ORDER, + failed_payable_dao_factory_opt, + FailedPayableDaoFactoryMock, + FailedPayableDao, + self + ) + } + pub fn receivable_daos( mut self, specially_configured_daos: Vec>, From 4515d82464abb7245779f0f2555add5f3f633b18 Mon Sep 17 00:00:00 2001 From: Bert Date: Thu, 17 Jul 2025 14:08:46 +0200 Subject: [PATCH 14/61] GH-642: lots of fixes in names --- .../db_access_objects/pending_payable_dao.rs | 10 +- node/src/accountant/mod.rs | 67 ++++--- node/src/accountant/scanners/mod.rs | 109 ++++++------ .../src/accountant/scanners/scanners_utils.rs | 42 ++--- node/src/accountant/test_utils.rs | 165 ------------------ node/src/blockchain/blockchain_bridge.rs | 82 ++++----- .../lower_level_interface_web3.rs | 10 +- .../blockchain_interface_web3/mod.rs | 38 ++-- .../blockchain_interface_web3/utils.rs | 18 +- .../blockchain/blockchain_interface/mod.rs | 4 +- node/src/sub_lib/accountant.rs | 4 +- node/src/test_utils/recorder.rs | 6 +- 12 files changed, 182 insertions(+), 373 deletions(-) diff --git a/node/src/accountant/db_access_objects/pending_payable_dao.rs b/node/src/accountant/db_access_objects/pending_payable_dao.rs index 451f580d3..414c364d8 100644 --- a/node/src/accountant/db_access_objects/pending_payable_dao.rs +++ b/node/src/accountant/db_access_objects/pending_payable_dao.rs @@ -32,7 +32,7 @@ pub struct TransactionHashes { pub no_rowid_results: Vec, } -pub trait SentPayableDao { +pub trait PendingPayableDao { // Note that the order of the returned results is not guaranteed fn fingerprints_rowids(&self, hashes: &[H256]) -> TransactionHashes; // fn return_all_errorless_fingerprints(&self) -> Vec; @@ -46,7 +46,7 @@ pub trait SentPayableDao { fn mark_failures(&self, ids: &[u64]) -> Result<(), PendingPayableDaoError>; } -impl SentPayableDao for PendingPayableDaoReal<'_> { +impl PendingPayableDao for PendingPayableDaoReal<'_> { fn fingerprints_rowids(&self, hashes: &[H256]) -> TransactionHashes { //Vec<(Option, H256)> { fn hash_and_rowid_in_single_row(row: &Row) -> rusqlite::Result<(u64, H256)> { @@ -244,11 +244,11 @@ impl<'a> PendingPayableDaoReal<'a> { } pub trait PendingPayableDaoFactory { - fn make(&self) -> Box; + fn make(&self) -> Box; } impl PendingPayableDaoFactory for DaoFactoryReal { - fn make(&self) -> Box { + fn make(&self) -> Box { Box::new(PendingPayableDaoReal::new(self.make_connection())) } } @@ -257,7 +257,7 @@ impl PendingPayableDaoFactory for DaoFactoryReal { mod tests { use crate::accountant::checked_conversion; use crate::accountant::db_access_objects::sent_payable_dao::{ - SentPayableDao, PendingPayableDaoError, PendingPayableDaoReal, + PendingPayableDao, PendingPayableDaoError, PendingPayableDaoReal, }; use crate::accountant::db_access_objects::utils::from_unix_timestamp; use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index 1b2e4c201..66f317aa8 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -24,7 +24,7 @@ use crate::accountant::scanners::payable_scanner_extension::msgs::{ BlockchainAgentWithContextMessage, QualifiedPayablesMessage, }; use crate::accountant::scanners::{StartScanError, Scanners}; -use crate::blockchain::blockchain_bridge::{BlockMarker, RegisterNewPendingSentTxMessage, RetrieveTransactions}; +use crate::blockchain::blockchain_bridge::{BlockMarker, RegisterNewPendingPayables, RetrieveTransactions}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::HashAndAmount; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; use crate::blockchain::blockchain_interface::data_structures::{ @@ -477,11 +477,11 @@ impl SkeletonOptHolder for RequestTransactionReceipts { } } -impl Handler for Accountant { +impl Handler for Accountant { type Result = (); fn handle( &mut self, - msg: RegisterNewPendingSentTxMessage, + msg: RegisterNewPendingPayables, _ctx: &mut Self::Context, ) -> Self::Result { self.register_new_pending_sent_tx(msg) @@ -559,7 +559,7 @@ impl Accountant { report_services_consumed: recipient!(addr, ReportServicesConsumedMessage), report_payable_payments_setup: recipient!(addr, BlockchainAgentWithContextMessage), report_inbound_payments: recipient!(addr, ReceivedPayments), - init_pending_payable_fingerprints: recipient!(addr, RegisterNewPendingSentTxMessage), + register_new_pending_payables: recipient!(addr, RegisterNewPendingPayables), report_transaction_status: recipient!(addr, TxReceiptsMessage), report_sent_payments: recipient!(addr, SentPayables), scan_errors: recipient!(addr, ScanError), @@ -1117,15 +1117,15 @@ impl Accountant { } } - fn register_new_pending_sent_tx(&self, msg: RegisterNewPendingSentTxMessage) { - fn serialize_hashes(fingerprints_data: &[SentTx]) -> String { - comma_joined_stringifiable(fingerprints_data, |sent_tx| format!("{:?}", sent_tx.hash)) + fn register_new_pending_sent_tx(&self, msg: RegisterNewPendingPayables) { + fn serialize_hashes(sent_txs: &[SentTx]) -> String { + comma_joined_stringifiable(sent_txs, |sent_tx| format!("{:?}", sent_tx.hash)) } match self.sent_payable_dao.insert_new_records(&msg.new_sent_txs) { Ok(_) => debug!( self.logger, - "Saved new pending payable fingerprints for: {}", + "Registered new pending payables for: {}", serialize_hashes(&msg.new_sent_txs) ), Err(e) => error!( @@ -1178,15 +1178,6 @@ impl PendingPayableId { } } -// impl From for PendingPayableId { -// fn from(pending_payable_fingerprint: SentTx) -> Self { -// Self { -// hash: pending_payable_fingerprint.hash, -// rowid: pending_payable_fingerprint.rowid, -// } -// } -// } - pub fn comma_joined_stringifiable(collection: &[T], stringify: F) -> String where F: FnMut(&T) -> String, @@ -1299,7 +1290,7 @@ mod tests { use crate::accountant::db_access_objects::sent_payable_dao::{SentPayableDaoError, SentTx}; use crate::accountant::scanners::scan_schedulers::{NewPayableScanDynIntervalComputer, NewPayableScanDynIntervalComputerReal}; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{OperationOutcome, PayableScanResult}; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxWithStatus, TransactionBlock, BlockchainTxFailure, TxStatus}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxWithStatus, TransactionBlock, BlockchainTxFailure, TxStatus, TxReceiptResult}; use crate::test_utils::recorder_counter_msgs::SingleTypeCounterMsgSetup; impl Handler> for Accountant { @@ -1945,7 +1936,7 @@ mod tests { block_number: 78901234.into(), }; let tx_receipts_msg = TxReceiptsMessage { - results: vec![TxReceiptResult::RpcResponse(TxWithStatus::new( + results: vec![TxReceiptResult::Ok(TxWithStatus::new( sent_tx.clone(), TxStatus::Succeeded(tx_block), ))], @@ -2167,7 +2158,7 @@ mod tests { let first_counter_msg_setup = setup_for_counter_msg_triggered_via_type_id!( RequestTransactionReceipts, TxReceiptsMessage { - results: vec![TxReceiptResult::RpcResponse(TxWithStatus::new( + results: vec![TxReceiptResult::Ok(TxWithStatus::new( sent_tx.clone(), TxStatus::Failed(BlockchainTxFailure::Unrecognized) )),], @@ -2792,7 +2783,7 @@ mod tests { let subject_addr: Addr = subject.start(); let subject_subs = Accountant::make_subs_from(&subject_addr); let expected_tx_receipts_msg = TxReceiptsMessage { - results: vec![TxReceiptResult::RpcResponse(TxWithStatus::new( + results: vec![TxReceiptResult::Ok(TxWithStatus::new( sent_tx.clone(), TxStatus::Failed(BlockchainTxFailure::Unrecognized), ))], @@ -3532,7 +3523,7 @@ mod tests { }; let requested_tx = make_tx_hash(234); let counter_msg_3 = TxReceiptsMessage { - results: vec![TxReceiptResult::RpcResponse(tx_with_status)], + results: vec![TxReceiptResult::Ok(tx_with_status)], response_skeleton_opt: None, }; let request_transaction_receipts_msg = RequestTransactionReceipts { @@ -5224,8 +5215,7 @@ mod tests { sent_tx.block_opt = Some(block.clone()); } - let result = - TxReceiptResult::RpcResponse(TxWithStatus::new(sent_tx.clone(), status)); + let result = TxReceiptResult::Ok(TxWithStatus::new(sent_tx.clone(), status)); tx_receipt_results.push(result); sent_tx_vec.push(sent_tx); @@ -5244,12 +5234,14 @@ mod tests { #[test] fn accountant_handles_registering_new_pending_payables() { init_test_logging(); + let test_name = "accountant_handles_registering_new_pending_payables"; let insert_new_records_params_arc = Arc::new(Mutex::new(vec![])); let sent_payable_dao = SentPayableDaoMock::default() .insert_new_records_params(&insert_new_records_params_arc) .insert_new_records_result(Ok(())); let subject = AccountantBuilder::default() .sent_payable_daos(vec![ForAccountantBody(sent_payable_dao)]) + .logger(Logger::new(test_name)) .build(); let accountant_addr = subject.start(); let accountant_subs = Accountant::make_subs_from(&accountant_addr); @@ -5260,22 +5252,23 @@ mod tests { let hash_2 = make_tx_hash(0x1b207); sent_tx_2.hash = hash_2; let new_sent_txs = vec![sent_tx_1.clone(), sent_tx_2.clone()]; - let init_fingerprints_msg = RegisterNewPendingSentTxMessage { new_sent_txs }; + let msg = RegisterNewPendingPayables { new_sent_txs }; let _ = accountant_subs - .init_pending_payable_fingerprints - .try_send(init_fingerprints_msg) + .register_new_pending_payables + .try_send(msg) .unwrap(); let system = System::new("ordering payment sent tx record test"); System::current().stop(); assert_eq!(system.run(), 0); - let insert_fingerprint_params = insert_new_records_params_arc.lock().unwrap(); - assert_eq!(*insert_fingerprint_params, vec![vec![sent_tx_1, sent_tx_2]]); - TestLogHandler::new().exists_log_containing( - "DEBUG: Accountant: Saved new pending payable fingerprints for: \ - 0x000000000000000000000000000000000000000000000000000000000006c81c, 0x000000000000000000000000000000000000000000000000000000000001b207", - ); + let insert_new_records_params = insert_new_records_params_arc.lock().unwrap(); + assert_eq!(*insert_new_records_params, vec![vec![sent_tx_1, sent_tx_2]]); + TestLogHandler::new().exists_log_containing(&format!( + "DEBUG: {test_name}: Registered new pending payables for: \ + 0x000000000000000000000000000000000000000000000000000000000006c81c, \ + 0x000000000000000000000000000000000000000000000000000000000001b207", + )); } #[test] @@ -5302,14 +5295,14 @@ mod tests { .sent_payable_daos(vec![ForAccountantBody(sent_payable_dao)]) .logger(Logger::new(test_name)) .build(); - let report_new_fingerprints = RegisterNewPendingSentTxMessage { + let msg = RegisterNewPendingPayables { new_sent_txs: vec![sent_tx_1.clone(), sent_tx_2.clone()], }; - let _ = subject.register_new_pending_sent_tx(report_new_fingerprints); + let _ = subject.register_new_pending_sent_tx(msg); - let insert_fingerprint_params = insert_new_records_params_arc.lock().unwrap(); - assert_eq!(*insert_fingerprint_params, vec![vec![sent_tx_1, sent_tx_2]]); + let insert_new_records_params = insert_new_records_params_arc.lock().unwrap(); + assert_eq!(*insert_new_records_params, vec![vec![sent_tx_1, sent_tx_2]]); TestLogHandler::new().exists_log_containing(&format!( "ERROR: {test_name}: Failed to save new pending payable records for \ 0x00000000000000000000000000000000000000000000000000000000000001c8, \ diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index 298460f41..a4b9b3aec 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -11,7 +11,7 @@ use crate::accountant::payment_adjuster::{PaymentAdjuster, PaymentAdjusterReal}; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableTransactingErrorEnum::{ LocallyCausedError, RemotelyCausedErrors, }; -use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{debugging_summary_after_error_separation, err_msg_for_failure_with_expected_but_missing_fingerprints, investigate_debt_extremes, mark_pending_payable_fatal_error, payables_debug_summary, separate_errors, separate_rowids_and_hashes, OperationOutcome, PayableScanResult, PayableThresholdsGauge, PayableThresholdsGaugeReal, PayableTransactingErrorEnum, PendingPayableMissingInDb}; +use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{debugging_summary_after_error_separation, err_msg_for_failure_with_expected_but_missing_sent_tx_record, investigate_debt_extremes, payables_debug_summary, separate_errors, separate_rowids_and_hashes, OperationOutcome, PayableScanResult, PayableThresholdsGauge, PayableThresholdsGaugeReal, PayableTransactingErrorEnum, PendingPayableMissingInDb}; use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_status_with_failure, handle_successful_tx, PendingPayableScanSummary, PendingPayableScanResult, handle_still_pending_tx}; use crate::accountant::scanners::scanners_utils::receivable_scanner_utils::balance_and_age; use crate::accountant::{PendingPayable, PendingPayableId, ScanError, ScanForPendingPayables, ScanForRetryPayables}; @@ -730,7 +730,7 @@ impl PayableScanner { } fn check_on_missing_sent_tx_records(&self, sent_payments: &[&PendingPayable]) { - fn missing_fingerprints_msg(nonexistent: &[PendingPayableMissingInDb]) -> String { + fn missing_record_msg(nonexistent: &[PendingPayableMissingInDb]) -> String { format!( "Expected sent-payable records for {} were not found. The system has become unreliable", comma_joined_stringifiable(nonexistent, |missing_sent_tx_ids| format!( @@ -742,7 +742,7 @@ impl PayableScanner { let missing_sent_tx_records = self.check_for_missing_records(sent_payments); if !missing_sent_tx_records.is_empty() { - panic!("{}", missing_fingerprints_msg(&missing_sent_tx_records)) + panic!("{}", missing_record_msg(&missing_sent_tx_records)) } } @@ -851,7 +851,7 @@ impl PayableScanner { .sorted() .collect(); - let missing_fgp_err_msg_opt = err_msg_for_failure_with_expected_but_missing_fingerprints( + let missing_fgp_err_msg_opt = err_msg_for_failure_with_expected_but_missing_sent_tx_record( hashes_of_missing_sent_tx, serialize_hashes, ); @@ -1015,28 +1015,26 @@ impl PendingPayableScanner { .fold( scan_report, |scan_report_so_far, receipt_result| match receipt_result { - TxReceiptResult::RpcResponse(sent_tx_with_status) => { - match sent_tx_with_status.status { - TxStatus::Succeeded(tx_block) => handle_successful_tx( - scan_report_so_far, - sent_tx_with_status.sent_tx, - tx_block, - logger, - ), - TxStatus::Failed(reason) => handle_status_with_failure( - scan_report_so_far, - sent_tx_with_status.sent_tx, - reason, - logger, - ), - TxStatus::Pending => handle_still_pending_tx( - scan_report_so_far, - sent_tx_with_status.sent_tx, - logger, - ), - } - } - TxReceiptResult::RequestError(e) => { + TxReceiptResult::Ok(sent_tx_with_status) => match sent_tx_with_status.status { + TxStatus::Succeeded(tx_block) => handle_successful_tx( + scan_report_so_far, + sent_tx_with_status.sent_tx, + tx_block, + logger, + ), + TxStatus::Failed(reason) => handle_status_with_failure( + scan_report_so_far, + sent_tx_with_status.sent_tx, + reason, + logger, + ), + TxStatus::Pending => handle_still_pending_tx( + scan_report_so_far, + sent_tx_with_status.sent_tx, + logger, + ), + }, + TxReceiptResult::Err(e) => { todo!() // debug!( // logger, @@ -1281,7 +1279,7 @@ impl ReceivableScanner { { Ok(()) => debug!(logger, "Start block updated to {}", start_block_number), Err(e) => panic!( - "Attempt to set new start block to {} failed due to: {:?}", + "Attempt to advance the start block to {} failed due to: {:?}", start_block_number, e ), } @@ -1529,7 +1527,7 @@ mod tests { use crate::accountant::db_access_objects::failed_payable_dao::{FailedPayableDaoError, FailedTx, FailureReason}; use crate::accountant::db_access_objects::sent_payable_dao::{SentPayableDaoError, SentTx}; use crate::accountant::scanners::test_utils::{assert_timestamps_from_str, parse_system_time_from_str, MarkScanner, NullScanner, ReplacementType, ScannerReplacement}; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxWithStatus, TransactionBlock, TxReceiptResult, TxStatus, BlockchainTxFailure}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxWithStatus, TransactionBlock, TxStatus, BlockchainTxFailure, TxReceiptResult}; use crate::test_utils::unshared_test_utils::capture_numbers_with_separators_from_str; impl Scanners { @@ -2078,9 +2076,9 @@ mod tests { hashset![failure_payable_hash_2] ] ); - let delete_fingerprints_params = delete_records_params_arc.lock().unwrap(); + let delete_records_params = delete_records_params_arc.lock().unwrap(); assert_eq!( - *delete_fingerprints_params, + *delete_records_params, vec![hashset![failure_payable_hash_2]] ); let log_handler = TestLogHandler::new(); @@ -2211,12 +2209,14 @@ mod tests { let delete_records_params_arc = Arc::new(Mutex::new(vec![])); let hash_tx_1 = make_tx_hash(0x15b3); let hash_tx_2 = make_tx_hash(0x3039); - let first_fingerprint_rowid = 3; - let second_fingerprint_rowid = 5; + let first_sent_tx_rowid = 3; + let second_sent_tx_rowid = 5; let system = System::new(test_name); let sent_payable_dao = SentPayableDaoMock::default() .get_tx_identifiers_params(&get_tx_identifiers_params_arc) - .get_tx_identifiers_result(hashmap!(hash_tx_1 => first_fingerprint_rowid, hash_tx_2 => second_fingerprint_rowid)) + .get_tx_identifiers_result( + hashmap!(hash_tx_1 => first_sent_tx_rowid, hash_tx_2 => second_sent_tx_rowid), + ) .delete_records_params(&delete_records_params_arc) .delete_records_result(Ok(())); let payable_scanner = PayableScannerBuilder::new() @@ -2249,16 +2249,10 @@ mod tests { ); assert_eq!(aware_of_unresolved_pending_payable_before, false); assert_eq!(aware_of_unresolved_pending_payable_after, false); - let fingerprints_rowids_params = get_tx_identifiers_params_arc.lock().unwrap(); - assert_eq!( - *fingerprints_rowids_params, - vec![hashset!(hash_tx_1, hash_tx_2)] - ); - let delete_fingerprints_params = delete_records_params_arc.lock().unwrap(); - assert_eq!( - *delete_fingerprints_params, - vec![hashset!(hash_tx_1, hash_tx_2)] - ); + let sent_tx_rowids_params = get_tx_identifiers_params_arc.lock().unwrap(); + assert_eq!(*sent_tx_rowids_params, vec![hashset!(hash_tx_1, hash_tx_2)]); + let delete_records_params = delete_records_params_arc.lock().unwrap(); + assert_eq!(*delete_records_params, vec![hashset!(hash_tx_1, hash_tx_2)]); let log_handler = TestLogHandler::new(); log_handler.exists_log_containing(&format!( "WARN: {test_name}: \ @@ -2309,9 +2303,9 @@ mod tests { } #[test] - fn payable_scanner_finds_fingerprints_for_failed_payments_but_panics_at_their_deletion() { + fn payable_scanner_finds_sent_tx_record_for_failed_payments_but_panics_at_their_deletion() { let test_name = - "payable_scanner_finds_fingerprints_for_failed_payments_but_panics_at_their_deletion"; + "payable_scanner_finds_sent_tx_record_for_failed_payments_but_panics_at_their_deletion"; let rowid_1 = 4; let hash_1 = make_tx_hash(0x7b); let rowid_2 = 6; @@ -2345,16 +2339,16 @@ mod tests { 00000000000000000000000000000000000000000315 failed due to SqlExecutionFailed(\"I overslept \ since my brain thinks the alarm is just a lullaby\")"); let log_handler = TestLogHandler::new(); - // There is a possible situation when we stumble over missing fingerprints, so we log it. + // There's a possibility that we stumble over missing sent tx records, so we log it. // Here we don't and so any ERROR log shouldn't turn up log_handler.exists_no_log_containing(&format!("ERROR: {}", test_name)) } #[test] - fn payable_scanner_panics_for_missing_fingerprints_but_deletion_of_some_works() { + fn payable_scanner_panics_for_missing_sent_tx_records_but_deletion_of_some_works() { init_test_logging(); let test_name = - "payable_scanner_panics_for_missing_fingerprints_but_deletion_of_some_works"; + "payable_scanner_panics_for_missing_sent_tx_records_but_deletion_of_some_works"; let hash_1 = make_tx_hash(0x1b669); let hash_2 = make_tx_hash(0x3039); let hash_3 = make_tx_hash(0x223d); @@ -2400,12 +2394,11 @@ mod tests { } #[test] - fn payable_scanner_for_failed_rpcs_one_fingerprint_missing_and_deletion_of_the_other_one_fails() - { + fn payable_scanner_for_failed_rpcs_one_sent_tx_record_missing_and_deletion_of_another_fails() { // Two fatal failures at once, missing sent tx records and another record deletion error // are both legitimate reasons for panic init_test_logging(); - let test_name = "payable_scanner_for_failed_rpcs_one_fingerprint_missing_and_deletion_of_the_other_one_fails"; + let test_name = "payable_scanner_for_failed_rpcs_one_sent_tx_record_missing_and_deletion_of_another_fails"; let existent_record_hash = make_tx_hash(0xb26e); let nonexistent_record_hash = make_tx_hash(0x4d2); let sent_payable_dao = SentPayableDaoMock::default() @@ -2889,7 +2882,7 @@ mod tests { } #[test] - fn pending_payable_scanner_throws_an_error_when_no_fingerprint_is_found() { + fn pending_payable_scanner_throws_an_error_when_no_sent_tx_record_is_found() { let now = SystemTime::now(); let consuming_wallet = make_paying_wallet(b"consuming_wallet"); let sent_payable_dao = SentPayableDaoMock::new().retrieve_txs_result(vec![]); @@ -2959,7 +2952,7 @@ mod tests { sent_tx.hash = hash; sent_tx.timestamp = sent_tx_timestamp; let msg = TxReceiptsMessage { - results: vec![TxReceiptResult::RpcResponse(TxWithStatus::new( + results: vec![TxReceiptResult::Ok(TxWithStatus::new( sent_tx.clone(), TxStatus::Pending, ))], @@ -3300,8 +3293,8 @@ mod tests { TxWithStatus::new(sent_tx_2.clone(), TxStatus::Succeeded(tx_block_2)); let msg = TxReceiptsMessage { results: vec![ - TxReceiptResult::RpcResponse(transaction_with_status_1), - TxReceiptResult::RpcResponse(transaction_with_status_2), + TxReceiptResult::Ok(transaction_with_status_1), + TxReceiptResult::Ok(transaction_with_status_2), ], response_skeleton_opt: None, }; @@ -3539,8 +3532,10 @@ mod tests { } #[test] - #[should_panic(expected = "Attempt to set new start block to 6709 failed due to: \ - UninterpretableValue(\"Illiterate database manager\")")] + #[should_panic( + expected = "Attempt to advance the start block to 6709 failed due to: \ + UninterpretableValue(\"Illiterate database manager\")" + )] fn no_transactions_received_but_start_block_setting_fails() { init_test_logging(); let test_name = "no_transactions_received_but_start_block_setting_fails"; diff --git a/node/src/accountant/scanners/scanners_utils.rs b/node/src/accountant/scanners/scanners_utils.rs index 6e3249ecc..db3a97109 100644 --- a/node/src/accountant/scanners/scanners_utils.rs +++ b/node/src/accountant/scanners/scanners_utils.rs @@ -253,26 +253,26 @@ pub mod payable_scanner_utils { } } - pub fn mark_pending_payable_fatal_error( - sent_payments: &[&PendingPayable], - nonexistent: &[PendingPayableMissingInDb], - error: PayableDaoError, - missing_fingerprints_msg_maker: fn(&[PendingPayableMissingInDb]) -> String, - logger: &Logger, - ) { - if !nonexistent.is_empty() { - error!(logger, "{}", missing_fingerprints_msg_maker(nonexistent)) - }; - panic!( - "Unable to create a mark in the payable table for wallets {} due to {:?}", - comma_joined_stringifiable(sent_payments, |pending_p| pending_p - .recipient_wallet - .to_string()), - error - ) - } - - pub fn err_msg_for_failure_with_expected_but_missing_fingerprints( + // pub fn mark_pending_payable_fatal_error( + // sent_payments: &[&PendingPayable], + // nonexistent: &[PendingPayableMissingInDb], + // error: PayableDaoError, + // missing_fingerprints_msg_maker: fn(&[PendingPayableMissingInDb]) -> String, + // logger: &Logger, + // ) { + // if !nonexistent.is_empty() { + // error!(logger, "{}", missing_fingerprints_msg_maker(nonexistent)) + // }; + // panic!( + // "Unable to create a mark in the payable table for wallets {} due to {:?}", + // comma_joined_stringifiable(sent_payments, |pending_p| pending_p + // .recipient_wallet + // .to_string()), + // error + // ) + // } + + pub fn err_msg_for_failure_with_expected_but_missing_sent_tx_record( nonexistent: Vec, serialize_hashes: fn(&[H256]) -> String, ) -> Option { @@ -329,7 +329,7 @@ pub mod pending_payable_scanner_utils { use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, FailureReason, FailureStatus}; use crate::accountant::db_access_objects::sent_payable_dao::SentTx; use crate::accountant::db_access_objects::utils::from_unix_timestamp; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, BlockchainTxFailure, TxReceiptRequestError, TxStatus}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, BlockchainTxFailure, TxReceiptError, TxStatus}; #[derive(Debug, Default, PartialEq, Eq, Clone)] pub struct PendingPayableScanSummary { diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs index 2351a9cb5..8c66cce77 100644 --- a/node/src/accountant/test_utils.rs +++ b/node/src/accountant/test_utils.rs @@ -956,169 +956,6 @@ pub fn bc_from_wallets(consuming_wallet: Wallet, earning_wallet: Wallet) -> Boot bc.earning_wallet = earning_wallet; bc } -// -// #[derive(Default)] -// pub struct SentPayableDaoMock { -// fingerprints_rowids_params: Arc>>>, -// fingerprints_rowids_results: RefCell>, -// delete_fingerprints_params: Arc>>>, -// delete_fingerprints_results: RefCell>>, -// insert_new_fingerprints_params: Arc, SystemTime)>>>, -// insert_new_fingerprints_results: RefCell>>, -// increment_scan_attempts_params: Arc>>>, -// increment_scan_attempts_result: RefCell>>, -// mark_failures_params: Arc>>>, -// mark_failures_results: RefCell>>, -// return_all_errorless_fingerprints_params: Arc>>, -// return_all_errorless_fingerprints_results: RefCell>>, -// pub have_return_all_errorless_fingerprints_shut_down_the_system: bool, -// } -// -// impl SentPayableDao for SentPayableDaoMock { -// fn fingerprints_rowids(&self, hashes: &[H256]) -> TransactionHashes { -// self.fingerprints_rowids_params -// .lock() -// .unwrap() -// .push(hashes.to_vec()); -// self.fingerprints_rowids_results.borrow_mut().remove(0) -// } -// -// fn return_all_errorless_fingerprints(&self) -> Vec { -// self.return_all_errorless_fingerprints_params -// .lock() -// .unwrap() -// .push(()); -// if self.have_return_all_errorless_fingerprints_shut_down_the_system -// && self -// .return_all_errorless_fingerprints_results -// .borrow() -// .is_empty() -// { -// System::current().stop(); -// return vec![]; -// } -// self.return_all_errorless_fingerprints_results -// .borrow_mut() -// .remove(0) -// } -// -// fn insert_new_fingerprints( -// &self, -// hashes_and_amounts: &[HashAndAmount], -// batch_wide_timestamp: SystemTime, -// ) -> Result<(), PendingPayableDaoError> { -// self.insert_new_fingerprints_params -// .lock() -// .unwrap() -// .push((hashes_and_amounts.to_vec(), batch_wide_timestamp)); -// self.insert_new_fingerprints_results.borrow_mut().remove(0) -// } -// -// fn delete_fingerprints(&self, ids: &[u64]) -> Result<(), PendingPayableDaoError> { -// self.delete_fingerprints_params -// .lock() -// .unwrap() -// .push(ids.to_vec()); -// self.delete_fingerprints_results.borrow_mut().remove(0) -// } -// -// fn increment_scan_attempts(&self, ids: &[u64]) -> Result<(), PendingPayableDaoError> { -// self.increment_scan_attempts_params -// .lock() -// .unwrap() -// .push(ids.to_vec()); -// self.increment_scan_attempts_result.borrow_mut().remove(0) -// } -// -// fn mark_failures(&self, ids: &[u64]) -> Result<(), PendingPayableDaoError> { -// self.mark_failures_params.lock().unwrap().push(ids.to_vec()); -// self.mark_failures_results.borrow_mut().remove(0) -// } -// } -// -// impl SentPayableDaoMock { -// pub fn new() -> Self { -// SentPayableDaoMock::default() -// } -// -// pub fn fingerprints_rowids_params(mut self, params: &Arc>>>) -> Self { -// self.fingerprints_rowids_params = params.clone(); -// self -// } -// -// pub fn fingerprints_rowids_result(self, result: TransactionHashes) -> Self { -// self.fingerprints_rowids_results.borrow_mut().push(result); -// self -// } -// -// pub fn insert_fingerprints_params( -// mut self, -// params: &Arc, SystemTime)>>>, -// ) -> Self { -// self.insert_new_fingerprints_params = params.clone(); -// self -// } -// -// pub fn insert_fingerprints_result(self, result: Result<(), PendingPayableDaoError>) -> Self { -// self.insert_new_fingerprints_results -// .borrow_mut() -// .push(result); -// self -// } -// -// pub fn delete_fingerprints_params(mut self, params: &Arc>>>) -> Self { -// self.delete_fingerprints_params = params.clone(); -// self -// } -// -// pub fn delete_fingerprints_result(self, result: Result<(), PendingPayableDaoError>) -> Self { -// self.delete_fingerprints_results.borrow_mut().push(result); -// self -// } -// -// pub fn return_all_errorless_fingerprints_params( -// mut self, -// params: &Arc>>, -// ) -> Self { -// self.return_all_errorless_fingerprints_params = params.clone(); -// self -// } -// -// pub fn return_all_errorless_fingerprints_result( -// self, -// result: Vec, -// ) -> Self { -// self.return_all_errorless_fingerprints_results -// .borrow_mut() -// .push(result); -// self -// } -// -// pub fn mark_failures_params(mut self, params: &Arc>>>) -> Self { -// self.mark_failures_params = params.clone(); -// self -// } -// -// pub fn mark_failures_result(self, result: Result<(), PendingPayableDaoError>) -> Self { -// self.mark_failures_results.borrow_mut().push(result); -// self -// } -// -// pub fn increment_scan_attempts_params(mut self, params: &Arc>>>) -> Self { -// self.increment_scan_attempts_params = params.clone(); -// self -// } -// -// pub fn increment_scan_attempts_result( -// self, -// result: Result<(), PendingPayableDaoError>, -// ) -> Self { -// self.increment_scan_attempts_result -// .borrow_mut() -// .push(result); -// self -// } -// } #[derive(Default)] pub struct SentPayableDaoMock { @@ -1134,8 +971,6 @@ pub struct SentPayableDaoMock { replace_records_results: RefCell>>, delete_records_params: Arc>>>, delete_records_results: RefCell>>, - // TODO necessary? - //pub have_return_all_errorless_fingerprints_shut_down_the_system: bool, } impl SentPayableDao for SentPayableDaoMock { diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index 7c920d703..b40dd9885 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -61,12 +61,12 @@ pub struct BlockchainBridge { received_payments_subs_opt: Option>, scan_error_subs_opt: Option>, crashable: bool, - pending_payable_confirmation: TransactionConfirmationTools, + pending_payable_confirmation: TxConfirmationTools, } -struct TransactionConfirmationTools { - new_pp_fingerprints_sub_opt: Option>, - report_transaction_receipts_sub_opt: Option>, +struct TxConfirmationTools { + register_new_pending_payables_sub_opt: Option>, + report_tx_receipts_sub_opt: Option>, } #[derive(PartialEq, Eq)] @@ -90,10 +90,9 @@ impl Handler for BlockchainBridge { fn handle(&mut self, msg: BindMessage, _ctx: &mut Self::Context) -> Self::Result { self.pending_payable_confirmation - .new_pp_fingerprints_sub_opt = - Some(msg.peer_actors.accountant.init_pending_payable_fingerprints); - self.pending_payable_confirmation - .report_transaction_receipts_sub_opt = + .register_new_pending_payables_sub_opt = + Some(msg.peer_actors.accountant.register_new_pending_payables); + self.pending_payable_confirmation.report_tx_receipts_sub_opt = Some(msg.peer_actors.accountant.report_transaction_status); self.payable_payments_setup_subs_opt = Some(msg.peer_actors.accountant.report_payable_payments_setup); @@ -166,11 +165,11 @@ impl Handler for BlockchainBridge { } #[derive(Debug, Clone, PartialEq, Eq, Message)] -pub struct RegisterNewPendingSentTxMessage { +pub struct RegisterNewPendingPayables { pub new_sent_txs: Vec, } -impl RegisterNewPendingSentTxMessage { +impl RegisterNewPendingPayables { pub fn new(new_sent_txs: Vec) -> Self { Self { new_sent_txs } } @@ -199,9 +198,9 @@ impl BlockchainBridge { scan_error_subs_opt: None, crashable, logger: Logger::new("BlockchainBridge"), - pending_payable_confirmation: TransactionConfirmationTools { - new_pp_fingerprints_sub_opt: None, - report_transaction_receipts_sub_opt: None, + pending_payable_confirmation: TxConfirmationTools { + register_new_pending_payables_sub_opt: None, + report_tx_receipts_sub_opt: None, }, } } @@ -396,12 +395,12 @@ impl BlockchainBridge { transaction_receipts_results.iter().fold( (0, 0, 0), |(success, fail, pending), transaction_receipt| match transaction_receipt { - TxReceiptResult::RpcResponse(tx_receipt) => match tx_receipt.status { + TxReceiptResult::Ok(tx_receipt) => match tx_receipt.status { TxStatus::Failed(_) => (success, fail + 1, pending), TxStatus::Succeeded(_) => (success + 1, fail, pending), TxStatus::Pending => (success, fail, pending + 1), }, - TxReceiptResult::RequestError(_) => (success, fail, pending + 1), + TxReceiptResult::Err(_) => (success, fail, pending + 1), }, ); format!( @@ -418,7 +417,7 @@ impl BlockchainBridge { let logger = self.logger.clone(); let accountant_recipient = self .pending_payable_confirmation - .report_transaction_receipts_sub_opt + .report_tx_receipts_sub_opt .clone() .expect("Accountant is unbound"); Box::new( @@ -469,19 +468,19 @@ impl BlockchainBridge { affordable_accounts: PricedQualifiedPayables, ) -> Box, Error = PayableTransactionError>> { - let new_fingerprints_recipient = self.new_fingerprints_recipient(); + let recipient = self.new_pending_payables_recipient(); let logger = self.logger.clone(); self.blockchain_interface.submit_payables_in_batch( logger, agent, - new_fingerprints_recipient, + recipient, affordable_accounts, ) } - fn new_fingerprints_recipient(&self) -> Recipient { + fn new_pending_payables_recipient(&self) -> Recipient { self.pending_payable_confirmation - .new_pp_fingerprints_sub_opt + .register_new_pending_payables_sub_opt .clone() .expect("Accountant unbound") } @@ -581,7 +580,7 @@ mod tests { use masq_lib::constants::DEFAULT_MAX_BLOCK_COUNT; use crate::accountant::db_access_objects::sent_payable_dao::SentTx; use crate::accountant::PendingPayable; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxWithStatus, TransactionBlock, TxReceiptRequestError}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxWithStatus, TransactionBlock, TxReceiptError}; use crate::accountant::scanners::payable_scanner_extension::msgs::{UnpricedQualifiedPayables, QualifiedPayableWithGasPrice}; impl Handler> for BlockchainBridge { @@ -881,7 +880,7 @@ mod tests { let time_after = SystemTime::now(); let accountant_recording = accountant_recording_arc.lock().unwrap(); let register_new_pending_sent_tx_msg = - accountant_recording.get_record::(0); + accountant_recording.get_record::(0); let sent_payables_msg = accountant_recording.get_record::(1); let expected_hash = H256::from_str("81d20df32920161727cd20e375e53c2f9df40fd80256a236fb39e444c999fb6c") @@ -975,8 +974,8 @@ mod tests { system.run(); let accountant_recording = accountant_recording_arc.lock().unwrap(); - let actual_pending_payable_fingerprint_seeds_msg = - accountant_recording.get_record::(0); + let actual_register_new_pending_payables_msg = + accountant_recording.get_record::(0); let sent_payables_msg = accountant_recording.get_record::(1); let scan_error_msg = accountant_recording.get_record::(2); assert_sending_error( @@ -987,21 +986,19 @@ mod tests { "Transport error: Error(IncompleteMessage)", ); assert_eq!( - actual_pending_payable_fingerprint_seeds_msg.new_sent_txs[0].receiver_address, + actual_register_new_pending_payables_msg.new_sent_txs[0].receiver_address, account_wallet.address() ); assert_eq!( - actual_pending_payable_fingerprint_seeds_msg.new_sent_txs[0].hash, + actual_register_new_pending_payables_msg.new_sent_txs[0].hash, H256::from_str("81d20df32920161727cd20e375e53c2f9df40fd80256a236fb39e444c999fb6c") .unwrap() ); assert_eq!( - actual_pending_payable_fingerprint_seeds_msg.new_sent_txs[0].amount_minor, + actual_register_new_pending_payables_msg.new_sent_txs[0].amount_minor, account.balance_wei ); - let number_of_requested_txs = actual_pending_payable_fingerprint_seeds_msg - .new_sent_txs - .len(); + let number_of_requested_txs = actual_register_new_pending_payables_msg.new_sent_txs.len(); assert_eq!( number_of_requested_txs, 1, "We expected only one sent tx, but got {}", @@ -1059,7 +1056,7 @@ mod tests { let (accountant, _, accountant_recording) = make_recorder(); subject .pending_payable_confirmation - .new_pp_fingerprints_sub_opt = Some(accountant.start().recipient()); + .register_new_pending_payables_sub_opt = Some(accountant.start().recipient()); let result = subject .process_payments(msg.agent, msg.affordable_accounts) @@ -1120,7 +1117,7 @@ mod tests { let (accountant, _, accountant_recording) = make_recorder(); subject .pending_payable_confirmation - .new_pp_fingerprints_sub_opt = Some(accountant.start().recipient()); + .register_new_pending_payables_sub_opt = Some(accountant.start().recipient()); let result = subject .process_payments(msg.agent, msg.affordable_accounts) @@ -1205,11 +1202,8 @@ mod tests { tx_receipts_message, &TxReceiptsMessage { results: vec![ - TxReceiptResult::RpcResponse(TxWithStatus::new( - sent_tx_1, - expected_receipt.into() - )), - TxReceiptResult::RpcResponse(TxWithStatus::new(sent_tx_2, TxStatus::Pending)) + TxReceiptResult::Ok(TxWithStatus::new(sent_tx_1, expected_receipt.into())), + TxReceiptResult::Ok(TxWithStatus::new(sent_tx_2, TxStatus::Pending)) ], response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, @@ -1321,7 +1315,7 @@ mod tests { ); subject .pending_payable_confirmation - .report_transaction_receipts_sub_opt = Some(report_transaction_receipt_recipient); + .report_tx_receipts_sub_opt = Some(report_transaction_receipt_recipient); subject.scan_error_subs_opt = Some(scan_error_recipient); let msg = RequestTransactionReceipts { sent_txs: vec![ @@ -1347,14 +1341,14 @@ mod tests { *report_receipts_msg, TxReceiptsMessage { results: vec![ - TxReceiptResult::RpcResponse(TxWithStatus::new(sent_tx_1, TxStatus::Pending)), - TxReceiptResult::RpcResponse(TxWithStatus::new(sent_tx_2, TxStatus::Succeeded(TransactionBlock { + TxReceiptResult::Ok(TxWithStatus::new(sent_tx_1, TxStatus::Pending)), + TxReceiptResult::Ok(TxWithStatus::new(sent_tx_2, TxStatus::Succeeded(TransactionBlock { block_hash: Default::default(), block_number, }))), - TxReceiptResult::RpcResponse(TxWithStatus::new(sent_tx_3, TxStatus::Pending)), - TxReceiptResult::RequestError( - TxReceiptRequestError::new( + TxReceiptResult::Ok(TxWithStatus::new(sent_tx_3, TxStatus::Pending)), + TxReceiptResult::Err( + TxReceiptError::new( sent_tx_4.hash, "RPC error: Error { code: ServerError(429), message: \"The requests per second (RPS) of your requests are higher than your plan allows.\", data: None }".to_string())) ], @@ -1392,7 +1386,7 @@ mod tests { ); subject .pending_payable_confirmation - .report_transaction_receipts_sub_opt = Some(report_transaction_recipient); + .report_tx_receipts_sub_opt = Some(report_transaction_recipient); subject.scan_error_subs_opt = Some(scan_error_recipient); let msg = RequestTransactionReceipts { sent_txs: vec![sent_tx_1, sent_tx_2], diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs index 262457d60..60b50a332 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs @@ -19,11 +19,7 @@ use web3::transports::{Batch, Http}; use web3::types::{Address, BlockNumber, Filter, Log, TransactionReceipt}; use web3::{Error, Web3}; -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum TxReceiptResult { - RpcResponse(TxWithStatus), - RequestError(TxReceiptRequestError), -} +pub type TxReceiptResult = Result; #[derive(Debug, PartialEq, Eq, Clone)] pub struct TxWithStatus { @@ -122,12 +118,12 @@ impl Display for TxStatus { // } #[derive(Debug, PartialEq, Eq, Clone)] -pub struct TxReceiptRequestError { +pub struct TxReceiptError { tx_hash: TxHash, err_msg: String, } -impl TxReceiptRequestError { +impl TxReceiptError { pub fn new(tx_hash: TxHash, err_msg: String) -> Self { Self { tx_hash, err_msg } } diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs index d78bb0659..64396e8d6 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs @@ -23,8 +23,8 @@ use web3::types::{Address, Log, H256, U256, FilterBuilder, TransactionReceipt, B use crate::accountant::scanners::payable_scanner_extension::msgs::{UnpricedQualifiedPayables, PricedQualifiedPayables}; use crate::blockchain::blockchain_agent::BlockchainAgent; use crate::accountant::db_access_objects::sent_payable_dao::SentTx; -use crate::blockchain::blockchain_bridge::{BlockMarker, BlockScanRange, RegisterNewPendingSentTxMessage}; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{LowBlockchainIntWeb3, TxReceiptResult, TxStatus, TxWithStatus, TxReceiptRequestError}; +use crate::blockchain::blockchain_bridge::{BlockMarker, BlockScanRange, RegisterNewPendingPayables}; +use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{LowBlockchainIntWeb3, TxStatus, TxWithStatus, TxReceiptError, TxReceiptResult}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::utils::{create_blockchain_agent_web3, send_payables_within_batch, BlockchainAgentFutureResult}; // TODO We should probably begin to attach these constants to the interfaces more tightly, so that @@ -227,28 +227,26 @@ impl BlockchainInterface for BlockchainInterfaceWeb3 { .map(|(response, sent_tx)| match response { Ok(result) => { match serde_json::from_value::(result) { - Ok(receipt) => TxReceiptResult::RpcResponse(TxWithStatus::new( + Ok(receipt) => TxReceiptResult::Ok(TxWithStatus::new( sent_tx, receipt.into(), )), Err(e) => { if e.to_string().contains("invalid type: null") { - TxReceiptResult::RpcResponse(TxWithStatus::new( + TxReceiptResult::Ok(TxWithStatus::new( sent_tx, TxStatus::Pending, )) } else { - TxReceiptResult::RequestError( - TxReceiptRequestError::new( - sent_tx.hash, - e.to_string(), - ), - ) + TxReceiptResult::Err(TxReceiptError::new( + sent_tx.hash, + e.to_string(), + )) } } } } - Err(e) => TxReceiptResult::RequestError(TxReceiptRequestError::new( + Err(e) => TxReceiptResult::Err(TxReceiptError::new( sent_tx.hash, e.to_string(), )), @@ -262,7 +260,7 @@ impl BlockchainInterface for BlockchainInterfaceWeb3 { &self, logger: Logger, agent: Box, - fingerprints_recipient: Recipient, + new_pending_payables_recipient: Recipient, affordable_accounts: PricedQualifiedPayables, ) -> Box, Error = PayableTransactionError>> { @@ -283,7 +281,7 @@ impl BlockchainInterface for BlockchainInterfaceWeb3 { &web3_batch, consuming_wallet, pending_nonce, - fingerprints_recipient, + new_pending_payables_recipient, affordable_accounts, ) }), @@ -1114,35 +1112,35 @@ mod tests { .wait() .unwrap(); - assert_eq!(result[0], TxReceiptResult::RequestError( - TxReceiptRequestError::new( + assert_eq!(result[0], TxReceiptResult::Err( + TxReceiptError::new( sent_tx_1.hash, "RPC error: Error { code: ServerError(429), message: \"The requests per second (RPS) of your requests are higher than your plan allows.\", data: None }".to_string()))); assert_eq!( result[1], - TxReceiptResult::RpcResponse(TxWithStatus::new(sent_tx_2, TxStatus::Pending)) + TxReceiptResult::Ok(TxWithStatus::new(sent_tx_2, TxStatus::Pending)) ); assert_eq!( result[2], - TxReceiptResult::RequestError(TxReceiptRequestError::new( + TxReceiptResult::Err(TxReceiptError::new( sent_tx_3.hash, "invalid type: string \"trash\", expected struct Receipt".to_string() )) ); assert_eq!( result[3], - TxReceiptResult::RpcResponse(TxWithStatus::new(sent_tx_4, TxStatus::Pending)) + TxReceiptResult::Ok(TxWithStatus::new(sent_tx_4, TxStatus::Pending)) ); assert_eq!( result[4], - TxReceiptResult::RpcResponse(TxWithStatus::new( + TxReceiptResult::Ok(TxWithStatus::new( sent_tx_5, TxStatus::Failed(BlockchainTxFailure::Unrecognized) )) ); assert_eq!( result[5], - TxReceiptResult::RpcResponse(TxWithStatus::new( + TxReceiptResult::Ok(TxWithStatus::new( sent_tx_6, TxStatus::Succeeded(TransactionBlock { block_hash, diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs index 6bb78e252..449ca3d75 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs @@ -7,7 +7,7 @@ use crate::accountant::scanners::payable_scanner_extension::msgs::PricedQualifie use crate::accountant::PendingPayable; use crate::blockchain::blockchain_agent::agent_web3::BlockchainAgentWeb3; use crate::blockchain::blockchain_agent::BlockchainAgent; -use crate::blockchain::blockchain_bridge::RegisterNewPendingSentTxMessage; +use crate::blockchain::blockchain_bridge::RegisterNewPendingPayables; use crate::blockchain::blockchain_interface::blockchain_interface_web3::{ BlockchainInterfaceWeb3, HashAndAmount, TRANSFER_METHOD_ID, }; @@ -273,7 +273,7 @@ pub fn send_payables_within_batch( web3_batch: &Web3>, consuming_wallet: Wallet, pending_nonce: U256, - new_fingerprints_recipient: Recipient, + new_pending_payables_recipient: Recipient, accounts: PricedQualifiedPayables, ) -> Box, Error = PayableTransactionError> + 'static> { @@ -303,11 +303,10 @@ pub fn send_payables_within_batch( .collect(); let planned_sent_txs_hashes = HashSet::from_iter(sent_txs_hashes.clone().into_iter()); - let new_pending_sent_tx_message = - RegisterNewPendingSentTxMessage::new(prepared_sent_txs_records); + let new_pending_payables_message = RegisterNewPendingPayables::new(prepared_sent_txs_records); - new_fingerprints_recipient - .try_send(new_pending_sent_tx_message) + new_pending_payables_recipient + .try_send(new_pending_payables_message) .expect("Accountant is dead"); info!( @@ -688,7 +687,7 @@ mod tests { let logger = Logger::new(test_name); let chain = DEFAULT_CHAIN; let consuming_wallet = make_paying_wallet(b"consuming_wallet"); - let new_fingerprints_recipient = accountant.start().recipient(); + let new_pending_payables_recipient = accountant.start().recipient(); let system = System::new(test_name); let timestamp_before = SystemTime::now(); @@ -698,7 +697,7 @@ mod tests { &web3_batch, consuming_wallet.clone(), pending_nonce, - new_fingerprints_recipient, + new_pending_payables_recipient, accounts.clone(), ) .wait(); @@ -708,8 +707,7 @@ mod tests { let timestamp_after = SystemTime::now(); assert_eq!(result, expected_result); let accountant_recording_result = accountant_recording.lock().unwrap(); - let rnpst_message = - accountant_recording_result.get_record::(0); + let rnpst_message = accountant_recording_result.get_record::(0); assert_eq!(accountant_recording_result.len(), 1); let nonces = 3_64..(accounts.payables.len() as u64 + 3); rnpst_message diff --git a/node/src/blockchain/blockchain_interface/mod.rs b/node/src/blockchain/blockchain_interface/mod.rs index 8b9c430ac..52be01e46 100644 --- a/node/src/blockchain/blockchain_interface/mod.rs +++ b/node/src/blockchain/blockchain_interface/mod.rs @@ -16,7 +16,7 @@ use masq_lib::logger::Logger; use crate::accountant::scanners::payable_scanner_extension::msgs::{PricedQualifiedPayables}; use crate::blockchain::blockchain_agent::BlockchainAgent; use crate::accountant::db_access_objects::sent_payable_dao::SentTx; -use crate::blockchain::blockchain_bridge::{BlockMarker, BlockScanRange, RegisterNewPendingSentTxMessage}; +use crate::blockchain::blockchain_bridge::{BlockMarker, BlockScanRange, RegisterNewPendingPayables}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::TxReceiptResult; pub trait BlockchainInterface { @@ -47,7 +47,7 @@ pub trait BlockchainInterface { &self, logger: Logger, agent: Box, - fingerprints_recipient: Recipient, + new_pending_payables_recipient: Recipient, affordable_accounts: PricedQualifiedPayables, ) -> Box, Error = PayableTransactionError>>; diff --git a/node/src/sub_lib/accountant.rs b/node/src/sub_lib/accountant.rs index 7e5fac2c6..e8d3477e6 100644 --- a/node/src/sub_lib/accountant.rs +++ b/node/src/sub_lib/accountant.rs @@ -9,7 +9,7 @@ use crate::accountant::{ checked_conversion, Accountant, ReceivedPayments, ScanError, SentPayables, TxReceiptsMessage, }; use crate::actor_system_factory::SubsFactory; -use crate::blockchain::blockchain_bridge::RegisterNewPendingSentTxMessage; +use crate::blockchain::blockchain_bridge::RegisterNewPendingPayables; use crate::db_config::config_dao::ConfigDaoFactory; use crate::sub_lib::neighborhood::ConfigChangeMsg; use crate::sub_lib::peer_actors::{BindMessage, StartMessage}; @@ -101,7 +101,7 @@ pub struct AccountantSubs { pub report_services_consumed: Recipient, pub report_payable_payments_setup: Recipient, pub report_inbound_payments: Recipient, - pub init_pending_payable_fingerprints: Recipient, + pub register_new_pending_payables: Recipient, pub report_transaction_status: Recipient, pub report_sent_payments: Recipient, pub scan_errors: Recipient, diff --git a/node/src/test_utils/recorder.rs b/node/src/test_utils/recorder.rs index d10880eeb..ed35378c2 100644 --- a/node/src/test_utils/recorder.rs +++ b/node/src/test_utils/recorder.rs @@ -8,7 +8,7 @@ use crate::accountant::{ ScanForReceivables, SentPayables, }; use crate::accountant::{ScanForPendingPayables, ScanForRetryPayables, TxReceiptsMessage}; -use crate::blockchain::blockchain_bridge::RegisterNewPendingSentTxMessage; +use crate::blockchain::blockchain_bridge::RegisterNewPendingPayables; use crate::blockchain::blockchain_bridge::RetrieveTransactions; use crate::daemon::crash_notification::CrashNotification; use crate::daemon::DaemonBindMessage; @@ -153,7 +153,7 @@ recorder_message_handler_t_m_p!(NodeFromUiMessage); recorder_message_handler_t_m_p!(NodeToUiMessage); recorder_message_handler_t_m_p!(NoLookupIncipientCoresPackage); recorder_message_handler_t_p!(OutboundPaymentsInstructions); -recorder_message_handler_t_m_p!(RegisterNewPendingSentTxMessage); +recorder_message_handler_t_m_p!(RegisterNewPendingPayables); recorder_message_handler_t_m_p!(PoolBindMessage); recorder_message_handler_t_m_p!(QualifiedPayablesMessage); recorder_message_handler_t_m_p!(ReceivedPayments); @@ -529,7 +529,7 @@ pub fn make_accountant_subs_from_recorder(addr: &Addr) -> AccountantSu report_services_consumed: recipient!(addr, ReportServicesConsumedMessage), report_payable_payments_setup: recipient!(addr, BlockchainAgentWithContextMessage), report_inbound_payments: recipient!(addr, ReceivedPayments), - init_pending_payable_fingerprints: recipient!(addr, RegisterNewPendingSentTxMessage), + register_new_pending_payables: recipient!(addr, RegisterNewPendingPayables), report_transaction_status: recipient!(addr, TxReceiptsMessage), report_sent_payments: recipient!(addr, SentPayables), scan_errors: recipient!(addr, ScanError), From 6ef6f0f0ac3aaa4e67e1649d344f8a667e0f0b4b Mon Sep 17 00:00:00 2001 From: Bert Date: Mon, 21 Jul 2025 22:20:16 +0200 Subject: [PATCH 15/61] GH-642: filling cache with failed txs to recheck at startup --- .../db_access_objects/failed_payable_dao.rs | 3 + node/src/accountant/mod.rs | 1 - node/src/accountant/scanners/mod.rs | 147 +++++++++++++++--- node/src/accountant/test_utils.rs | 10 +- 4 files changed, 131 insertions(+), 30 deletions(-) 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 1d5035722..280a3acee 100644 --- a/node/src/accountant/db_access_objects/failed_payable_dao.rs +++ b/node/src/accountant/db_access_objects/failed_payable_dao.rs @@ -27,6 +27,8 @@ pub enum FailureReason { PendingTooLong, NonceIssue, General, + // Orphan + // RequestExecutionFailed } #[derive(Clone, Debug, PartialEq, Eq)] @@ -73,6 +75,7 @@ pub struct FailedTx { pub status: FailureStatus, } +#[derive(Debug, PartialEq, Eq)] pub enum FailureRetrieveCondition { ByStatus(FailureStatus), } diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index 66f317aa8..f530dc478 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -523,7 +523,6 @@ impl Accountant { let scanners = Scanners::new( dao_factories, Rc::new(payment_thresholds), - config.when_pending_too_long_sec, Rc::clone(&financial_statistics), ); diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index a4b9b3aec..0eac9102f 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -45,7 +45,7 @@ use time::format_description::parse; use time::OffsetDateTime; use variant_count::VariantCount; use web3::types::H256; -use crate::accountant::db_access_objects::failed_payable_dao::{FailedPayableDao, FailedTx}; +use crate::accountant::db_access_objects::failed_payable_dao::{FailedPayableDao, FailedTx, FailureRetrieveCondition, FailureStatus}; use crate::accountant::db_access_objects::sent_payable_dao::{RetrieveCondition, SentPayableDao, SentPayableDaoError, SentTx}; use crate::accountant::db_access_objects::utils::{TxHash, TxIdentifiers}; use crate::accountant::scanners::payable_scanner_extension::{MultistageDualPayableScanner, PreparedAdjustment, SolvencySensitivePaymentInstructor}; @@ -81,7 +81,6 @@ impl Scanners { pub fn new( dao_factories: DaoFactories, payment_thresholds: Rc, - when_pending_too_long_sec: u64, financial_statistics: Rc>, ) -> Self { let payable = Box::new(PayableScanner::new( @@ -96,7 +95,6 @@ impl Scanners { dao_factories.sent_payable_dao_factory.make(), dao_factories.failed_payable_dao_factory.make(), Rc::clone(&payment_thresholds), - when_pending_too_long_sec, Rc::clone(&financial_statistics), )); @@ -220,6 +218,7 @@ impl Scanners { } (None, None) => (), } + self.pending_payable .start_scan(wallet, timestamp, response_skeleton_opt, logger) } @@ -893,9 +892,8 @@ pub struct PendingPayableScanner { pub payable_dao: Box, pub sent_payable_dao: Box, pub failed_payable_dao: Box, - pub when_pending_too_long_sec: u64, pub financial_statistics: Rc>, - pub cached_txs_pending_too_long_from_previous_cycle: Vec, //TODO also treat carefully + pub txs_pending_too_long_in_previous_scan_cycle: Vec, //TODO also treat carefully } impl @@ -920,6 +918,7 @@ impl StartableScanner ) -> Result { self.mark_as_started(timestamp); info!(logger, "Scanning for pending payable"); + let pending_sent_txs = self .sent_payable_dao .retrieve_txs(Some(RetrieveCondition::IsPending)); @@ -990,17 +989,18 @@ impl PendingPayableScanner { sent_payable_dao: Box, failed_payable_dao: Box, payment_thresholds: Rc, - when_pending_too_long_sec: u64, financial_statistics: Rc>, ) -> Self { + + let txs_pending_too_long_in_previous_scan_cycle = failed_payable_dao.retrieve_txs(Some(FailureRetrieveCondition::ByStatus(FailureStatus::RecheckRequired))).into_iter().map(|tx| tx.hash).collect(); + Self { common: ScannerCommon::new(payment_thresholds), payable_dao, sent_payable_dao, failed_payable_dao, - when_pending_too_long_sec, financial_statistics, - cached_txs_pending_too_long_from_previous_cycle: Vec::new(), + txs_pending_too_long_in_previous_scan_cycle, } } @@ -1524,10 +1524,10 @@ mod tests { use web3::Error; use masq_lib::messages::ScanType; use masq_lib::ui_gateway::NodeToUiMessage; - use crate::accountant::db_access_objects::failed_payable_dao::{FailedPayableDaoError, FailedTx, FailureReason}; + use crate::accountant::db_access_objects::failed_payable_dao::{FailedPayableDaoError, FailedTx, FailureReason, FailureRetrieveCondition, FailureStatus}; use crate::accountant::db_access_objects::sent_payable_dao::{SentPayableDaoError, SentTx}; use crate::accountant::scanners::test_utils::{assert_timestamps_from_str, parse_system_time_from_str, MarkScanner, NullScanner, ReplacementType, ScannerReplacement}; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxWithStatus, TransactionBlock, TxStatus, BlockchainTxFailure, TxReceiptResult}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxWithStatus, TransactionBlock, TxStatus, BlockchainTxFailure, TxReceiptResult, TxReceiptError}; use crate::test_utils::unshared_test_utils::capture_numbers_with_separators_from_str; impl Scanners { @@ -1609,6 +1609,7 @@ mod tests { #[test] fn scanners_struct_can_be_constructed_with_the_respective_scanners() { + let retrieve_txs_params_arc = Arc::new(Mutex::new(vec![])); let payable_dao_factory = PayableDaoFactoryMock::new() .make_result(PayableDaoMock::new()) .make_result(PayableDaoMock::new()); @@ -1616,16 +1617,15 @@ mod tests { .make_result(SentPayableDaoMock::new()) .make_result(SentPayableDaoMock::new()); let failed_payable_dao_factory = - FailedPayableDaoFactoryMock::new().make_result(FailedPayableDaoMock::new()); - let receivable_dao = ReceivableDaoMock::new(); - let receivable_dao_factory = ReceivableDaoFactoryMock::new().make_result(receivable_dao); + FailedPayableDaoFactoryMock::new() + .make_result(FailedPayableDaoMock::new().retrieve_txs_params(&retrieve_txs_params_arc).retrieve_txs_result(vec![])); + let receivable_dao_factory = ReceivableDaoFactoryMock::new().make_result(ReceivableDaoMock::new()); let banned_dao_factory = BannedDaoFactoryMock::new().make_result(BannedDaoMock::new()); let set_params_arc = Arc::new(Mutex::new(vec![])); let config_dao_mock = ConfigDaoMock::new() .set_params(&set_params_arc) .set_result(Ok(())); let config_dao_factory = ConfigDaoFactoryMock::new().make_result(config_dao_mock); - let when_pending_too_long_sec = 1234; let financial_statistics = FinancialStatistics { total_paid_payable_wei: 1, total_paid_receivable_wei: 2, @@ -1644,7 +1644,6 @@ mod tests { config_dao_factory: Box::new(config_dao_factory), }, Rc::clone(&payment_thresholds_rc), - when_pending_too_long_sec, Rc::new(RefCell::new(financial_statistics.clone())), ); @@ -1670,10 +1669,6 @@ mod tests { assert_eq!(payable_scanner.common.initiated_at_opt.is_some(), false); assert_eq!(scanners.aware_of_unresolved_pending_payable, false); assert_eq!(scanners.initial_pending_payable_scan, true); - assert_eq!( - pending_payable_scanner.when_pending_too_long_sec, - when_pending_too_long_sec - ); assert_eq!( *pending_payable_scanner.financial_statistics.borrow(), financial_statistics @@ -1687,9 +1682,11 @@ mod tests { false ); assert_eq!( - pending_payable_scanner.cached_txs_pending_too_long_from_previous_cycle, + pending_payable_scanner.txs_pending_too_long_in_previous_scan_cycle, vec![] ); + let retrieve_txs_params = retrieve_txs_params_arc.lock().unwrap(); + assert_eq!(*retrieve_txs_params, vec![Some(FailureRetrieveCondition::ByStatus(FailureStatus::RecheckRequired))]); assert_eq!( receivable_scanner.common.payment_thresholds.as_ref(), &payment_thresholds @@ -1718,6 +1715,57 @@ mod tests { initial_rc_count + 3 ); } + + #[test] + fn tx_failures_yet_needing_recheck_uploads_from_db_while_pending_payable_scanner_is_constructed(){ + let retrieve_txs_params_arc = Arc::new(Mutex::new(vec![])); + let payable_dao_factory = PayableDaoFactoryMock::new() + .make_result(PayableDaoMock::new()) + .make_result(PayableDaoMock::new()); + let sent_payable_dao_factory = SentPayableDaoFactoryMock::new() + .make_result(SentPayableDaoMock::new()) + .make_result(SentPayableDaoMock::new()); + let tx_hash_1 = make_tx_hash(111); + let mut failed_tx_1 = make_failed_tx(123); + failed_tx_1.hash = tx_hash_1; + failed_tx_1.status = FailureStatus::RecheckRequired; + let tx_hash_2 = make_tx_hash(222); + let mut failed_tx_2 = make_failed_tx(456); + failed_tx_2.hash = tx_hash_2; + failed_tx_2.status = FailureStatus::RecheckRequired; + let failed_payable_dao_factory = + FailedPayableDaoFactoryMock::new() + .make_result(FailedPayableDaoMock::new() + .retrieve_txs_params(&retrieve_txs_params_arc) + .retrieve_txs_result(vec![failed_tx_1, failed_tx_2])); + let receivable_dao_factory = ReceivableDaoFactoryMock::new().make_result(ReceivableDaoMock::new()); + let banned_dao_factory = BannedDaoFactoryMock::new().make_result(BannedDaoMock::new()); + let config_dao_mock = ConfigDaoMock::new() + .set_result(Ok(())); + let config_dao_factory = ConfigDaoFactoryMock::new().make_result(config_dao_mock); + + let scanners = Scanners::new( + DaoFactories { + payable_dao_factory: Box::new(payable_dao_factory), + sent_payable_dao_factory: Box::new(sent_payable_dao_factory), + failed_payable_dao_factory: Box::new(failed_payable_dao_factory), + receivable_dao_factory: Box::new(receivable_dao_factory), + banned_dao_factory: Box::new(banned_dao_factory), + config_dao_factory: Box::new(config_dao_factory), + }, + Rc::new(make_custom_payment_thresholds()), + Rc::new(RefCell::new(FinancialStatistics::default())), + ); + + let pending_payable_scanner = scanners + .pending_payable + .as_any() + .downcast_ref::() + .unwrap(); + let retrieve_txs_params = retrieve_txs_params_arc.lock().unwrap(); + assert_eq!(*retrieve_txs_params, vec![Some(FailureRetrieveCondition::ByStatus(FailureStatus::RecheckRequired))]); + assert_eq!(pending_payable_scanner.txs_pending_too_long_in_previous_scan_cycle, vec![tx_hash_1, tx_hash_2]); + } #[test] fn new_payable_scanner_can_initiate_a_scan() { @@ -2997,6 +3045,65 @@ mod tests { ); } + #[test] + fn handle_transaction_receipt_if_attempt_to_retrieve_the_receipt_failed() { + init_test_logging(); + let test_name = "handle_transaction_receipt_if_attempt_to_retrieve_the_receipt_failed"; + todo!("finish me when you have a better idea how to implement the backward checks") + // let subject = PendingPayableScannerBuilder::new().build(); + // let hash = make_tx_hash(0x913); + // let sent_tx_timestamp = to_unix_timestamp( + // SystemTime::now() + // .checked_sub(Duration::from_secs(120)) + // .unwrap(), + // ); + // let mut sent_tx = make_sent_tx(456); + // sent_tx.hash = hash; + // sent_tx.timestamp = sent_tx_timestamp; + // let msg = TxReceiptsMessage { + // results: vec![TxReceiptResult::Err(TxReceiptError::new( + // hash, + // "What an epic error".to_string(), + // ))], + // response_skeleton_opt: None, + // }; + // + // let result = subject.handle_receipts_for_pending_transactions(msg, &Logger::new(test_name)); + // + // assert_eq!( + // result, + // PendingPayableScanSummary { + // failures: vec![expected_failed_tx], + // confirmed: vec![] + // } + // ); + // let log_handler = TestLogHandler::new(); + // let log_idx = log_handler.exists_log_matching(&format!( + // "WARN: {test_name}: Pending tx \ + // 0x0000000000000000000000000000000000000000000000000000000000000913 could not be \ + // confirmed yet after \\d{{1,3}}(,\\d{{3}})* ms and will be retried with a more optimized gas price" + // )); + // let log_msg = log_handler.get_log_at(log_idx); + // let str_elapsed_ms = capture_numbers_with_separators_from_str(&log_msg, 3, ','); + // let elapsed_ms = str_elapsed_ms[0].replace(",", "").parse::().unwrap(); + // let elapsed_ms_when_before = before + // .duration_since(from_unix_timestamp(sent_tx_timestamp)) + // .unwrap() + // .as_millis(); + // let elapsed_ms_when_after = after + // .duration_since(from_unix_timestamp(sent_tx_timestamp)) + // .unwrap() + // .as_millis(); + // assert!( + // elapsed_ms_when_before <= elapsed_ms && elapsed_ms <= elapsed_ms_when_after, + // "we expected the elapsed time {} ms to be between {} and {}.", + // elapsed_ms, + // elapsed_ms_when_before, + // elapsed_ms_when_after + // ); + } + + #[test] fn handle_failed_transactions_works() { let insert_new_records_params_arc = Arc::new(Mutex::new(vec![])); diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs index 8c66cce77..c00eb66f6 100644 --- a/node/src/accountant/test_utils.rs +++ b/node/src/accountant/test_utils.rs @@ -1331,7 +1331,6 @@ pub struct PendingPayableScannerBuilder { sent_payable_dao: SentPayableDaoMock, failed_payable_dao: FailedPayableDaoMock, payment_thresholds: PaymentThresholds, - when_pending_too_long_sec: u64, financial_statistics: FinancialStatistics, } @@ -1342,7 +1341,6 @@ impl PendingPayableScannerBuilder { sent_payable_dao: SentPayableDaoMock::new(), failed_payable_dao: FailedPayableDaoMock::new(), payment_thresholds: PaymentThresholds::default(), - when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, financial_statistics: FinancialStatistics::default(), } } @@ -1361,19 +1359,13 @@ impl PendingPayableScannerBuilder { self.failed_payable_dao = failed_payable_dao; self } - - pub fn when_pending_too_long_sec(mut self, interval: u64) -> Self { - self.when_pending_too_long_sec = interval; - self - } - + pub fn build(self) -> PendingPayableScanner { PendingPayableScanner::new( Box::new(self.payable_dao), Box::new(self.sent_payable_dao), Box::new(self.failed_payable_dao), Rc::new(self.payment_thresholds), - self.when_pending_too_long_sec, Rc::new(RefCell::new(self.financial_statistics)), ) } From 4aa18d4568d44dfcda00e76761d657c843deda62 Mon Sep 17 00:00:00 2001 From: Bert Date: Tue, 22 Jul 2025 14:28:38 +0200 Subject: [PATCH 16/61] GH-642: rpc failers during receipt checks can be handled now --- node/src/accountant/mod.rs | 33 ++- node/src/accountant/scanners/mod.rs | 211 ++++++++-------- .../src/accountant/scanners/scanners_utils.rs | 230 ++++++++++++------ node/src/accountant/test_utils.rs | 44 +++- node/src/blockchain/blockchain_bridge.rs | 7 +- .../lower_level_interface_web3.rs | 9 +- .../blockchain_interface_web3/mod.rs | 31 ++- 7 files changed, 360 insertions(+), 205 deletions(-) diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index f530dc478..5dc31b5a5 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -76,7 +76,7 @@ use std::time::SystemTime; use web3::types::H256; use crate::accountant::scanners::scan_schedulers::{PayableSequenceScanner, ScanRescheduleAfterEarlyStop, ScanSchedulers}; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::OperationOutcome; -use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::PendingPayableScanResult; +use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{PendingPayableScanResult, Retry}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::TxReceiptResult; pub const CRASH_KEY: &str = "ACCOUNTANT"; @@ -327,10 +327,14 @@ impl Handler for Accountant { .schedule_new_payable_scan(ctx, &self.logger) } } - PendingPayableScanResult::PaymentRetryRequired => self - .scan_schedulers - .payable - .schedule_retry_payable_scan(ctx, response_skeleton_opt, &self.logger), + PendingPayableScanResult::PaymentRetryRequired(retry) => match retry { + Retry::RetryPayments => self.scan_schedulers.payable.schedule_retry_payable_scan( + ctx, + response_skeleton_opt, + &self.logger, + ), + Retry::RetryReceiptCheck => todo!(), + }, }; } } @@ -1331,7 +1335,7 @@ mod tests { .make_result(SentPayableDaoMock::new()); // For PendingPayable Scanner let failed_payable_dao_factory = FailedPayableDaoFactoryMock::new() .make_params(&failed_payable_dao_factory_params_arc) - .make_result(FailedPayableDaoMock::new()); // For PendingPayableScanner; + .make_result(FailedPayableDaoMock::new().retrieve_txs_result(vec![])); // For PendingPayableScanner; let receivable_dao_factory = ReceivableDaoFactoryMock::new() .make_params(&receivable_dao_factory_params_arc) .make_result(ReceivableDaoMock::new()) // For Accountant @@ -1390,8 +1394,10 @@ mod tests { .make_result(SentPayableDaoMock::new()) // For Payable Scanner .make_result(SentPayableDaoMock::new()), // For PendingPayable Scanner ); - let failed_payable_dao_factory = - Box::new(FailedPayableDaoFactoryMock::new().make_result(FailedPayableDaoMock::new())); // For PendingPayableScanner; + let failed_payable_dao_factory = Box::new( + FailedPayableDaoFactoryMock::new() + .make_result(FailedPayableDaoMock::new().retrieve_txs_result(vec![])), + ); // For PendingPayableScanner; let receivable_dao_factory = Box::new( ReceivableDaoFactoryMock::new() .make_result(ReceivableDaoMock::new()) // For Accountant @@ -2194,7 +2200,8 @@ mod tests { system.run(); let insert_new_records_params = insert_new_records_params_arc.lock().unwrap(); - let expected_failed_tx = FailedTx::from((sent_tx, BlockchainTxFailure::Unrecognized)); + let expected_failed_tx = + FailedTx::from((sent_tx, BlockchainTxFailure::Unrecognized.into())); assert_eq!(*insert_new_records_params, vec![vec![expected_failed_tx]]); let delete_records_params = delete_records_params_arc.lock().unwrap(); assert_eq!(*delete_records_params, vec![hashset![tx_hash]]); @@ -2764,7 +2771,9 @@ mod tests { response_skeleton_opt: None, })) .finish_scan_params(&scan_params.pending_payable_finish_scan) - .finish_scan_result(PendingPayableScanResult::PaymentRetryRequired); + .finish_scan_result(PendingPayableScanResult::PaymentRetryRequired( + Retry::RetryPayments, + )); let receivable_scanner = ScannerMock::new() .scan_started_at_result(None) .start_scan_params(&scan_params.receivable_start_scan) @@ -4941,7 +4950,9 @@ mod tests { .build(); let pending_payable_scanner = ScannerMock::new() .finish_scan_params(&finish_scan_params_arc) - .finish_scan_result(PendingPayableScanResult::PaymentRetryRequired); + .finish_scan_result(PendingPayableScanResult::PaymentRetryRequired( + Retry::RetryPayments, + )); subject .scanners .replace_scanner(ScannerReplacement::PendingPayable(ReplacementType::Mock( diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index 0eac9102f..798c47ce9 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -12,7 +12,7 @@ use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableT LocallyCausedError, RemotelyCausedErrors, }; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{debugging_summary_after_error_separation, err_msg_for_failure_with_expected_but_missing_sent_tx_record, investigate_debt_extremes, payables_debug_summary, separate_errors, separate_rowids_and_hashes, OperationOutcome, PayableScanResult, PayableThresholdsGauge, PayableThresholdsGaugeReal, PayableTransactingErrorEnum, PendingPayableMissingInDb}; -use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_status_with_failure, handle_successful_tx, PendingPayableScanSummary, PendingPayableScanResult, handle_still_pending_tx}; +use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_status_with_failure, handle_successful_tx, PendingPayableScanSummary, PendingPayableScanResult, handle_still_pending_tx, handle_rpc_failure}; use crate::accountant::scanners::scanners_utils::receivable_scanner_utils::balance_and_age; use crate::accountant::{PendingPayable, PendingPayableId, ScanError, ScanForPendingPayables, ScanForRetryPayables}; use crate::accountant::{ @@ -961,14 +961,14 @@ impl Scanner for PendingPayableScan ); let scan_report = self.handle_receipts_for_pending_transactions(message, logger); - let payment_retry_required = scan_report.requires_payments_retry(); + let payment_retry_opt = scan_report.requires_payments_retry(); self.process_transactions_by_their_state(scan_report, logger); self.mark_as_ended(logger); - if payment_retry_required { - PendingPayableScanResult::PaymentRetryRequired + if let Some(retry) = payment_retry_opt { + PendingPayableScanResult::PaymentRetryRequired(retry) } else { let ui_msg_opt = response_skeleton_opt.map(|response_skeleton| NodeToUiMessage { target: MessageTarget::ClientId(response_skeleton.client_id), @@ -991,8 +991,13 @@ impl PendingPayableScanner { payment_thresholds: Rc, financial_statistics: Rc>, ) -> Self { - - let txs_pending_too_long_in_previous_scan_cycle = failed_payable_dao.retrieve_txs(Some(FailureRetrieveCondition::ByStatus(FailureStatus::RecheckRequired))).into_iter().map(|tx| tx.hash).collect(); + let txs_pending_too_long_in_previous_scan_cycle = failed_payable_dao + .retrieve_txs(Some(FailureRetrieveCondition::ByStatus( + FailureStatus::RecheckRequired, + ))) + .into_iter() + .map(|tx| tx.hash) + .collect(); Self { common: ScannerCommon::new(payment_thresholds), @@ -1034,17 +1039,7 @@ impl PendingPayableScanner { logger, ), }, - TxReceiptResult::Err(e) => { - todo!() - // debug!( - // logger, - // "Interpreting a receipt for transaction {:?} but {}; attempt {}, {}ms since sending", - // payable.hash, - // error_msg, - // payable.attempt, - // elapsed_in_ms(payable.timestamp) - // ); - } + TxReceiptResult::Err(e) => handle_rpc_failure(scan_report_so_far, e, logger), }, ) } @@ -1055,7 +1050,7 @@ impl PendingPayableScanner { logger: &Logger, ) { self.handle_confirmed_transactions(scan_report.confirmed, logger); - self.handle_failed_transactions(scan_report.failures, logger); + self.handle_failed_transactions(scan_report.failures_summary.failures, logger); } fn handle_confirmed_transactions(&mut self, confirmed_txs: Vec, logger: &Logger) { @@ -1483,7 +1478,7 @@ mod tests { use crate::accountant::db_access_objects::utils::{from_unix_timestamp, to_unix_timestamp, TxHash}; use crate::accountant::scanners::payable_scanner_extension::msgs::{QualifiedPayablesBeforeGasPriceSelection, QualifiedPayablesMessage, UnpricedQualifiedPayables}; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{OperationOutcome, PayableScanResult, PendingPayableMissingInDb}; - use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_status_with_failure, PendingPayableScanSummary, PendingPayableScanResult}; + use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_status_with_failure, PendingPayableScanSummary, PendingPayableScanResult, FailuresSummary}; use crate::accountant::scanners::{Scanner, StartScanError, StartableScanner, PayableScanner, PendingPayableScanner, ReceivableScanner, ScannerCommon, Scanners, MTError}; use crate::accountant::test_utils::{make_custom_payment_thresholds, make_payable_account, make_qualified_and_unqualified_payables, make_receivable_account, BannedDaoFactoryMock, BannedDaoMock, ConfigDaoFactoryMock, PayableDaoFactoryMock, PayableDaoMock, PayableScannerBuilder, PayableThresholdsGaugeMock, SentPayableDaoFactoryMock, SentPayableDaoMock, PendingPayableScannerBuilder, ReceivableDaoFactoryMock, ReceivableDaoMock, ReceivableScannerBuilder, make_sent_tx, FailedPayableDaoMock, FailedPayableDaoFactoryMock, make_failed_tx, make_transaction_block}; use crate::accountant::{gwei_to_wei, PendingPayableId, ReceivedPayments, TxReceiptsMessage, RequestTransactionReceipts, ScanError, ScanForRetryPayables, SentPayables, DEFAULT_PENDING_TOO_LONG_SEC, PendingPayable}; @@ -1528,6 +1523,7 @@ mod tests { use crate::accountant::db_access_objects::sent_payable_dao::{SentPayableDaoError, SentTx}; use crate::accountant::scanners::test_utils::{assert_timestamps_from_str, parse_system_time_from_str, MarkScanner, NullScanner, ReplacementType, ScannerReplacement}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxWithStatus, TransactionBlock, TxStatus, BlockchainTxFailure, TxReceiptResult, TxReceiptError}; + use crate::blockchain::errors::{AppRpcError, RemoteError}; use crate::test_utils::unshared_test_utils::capture_numbers_with_separators_from_str; impl Scanners { @@ -1616,10 +1612,13 @@ mod tests { let sent_payable_dao_factory = SentPayableDaoFactoryMock::new() .make_result(SentPayableDaoMock::new()) .make_result(SentPayableDaoMock::new()); - let failed_payable_dao_factory = - FailedPayableDaoFactoryMock::new() - .make_result(FailedPayableDaoMock::new().retrieve_txs_params(&retrieve_txs_params_arc).retrieve_txs_result(vec![])); - let receivable_dao_factory = ReceivableDaoFactoryMock::new().make_result(ReceivableDaoMock::new()); + let failed_payable_dao_factory = FailedPayableDaoFactoryMock::new().make_result( + FailedPayableDaoMock::new() + .retrieve_txs_params(&retrieve_txs_params_arc) + .retrieve_txs_result(vec![]), + ); + let receivable_dao_factory = + ReceivableDaoFactoryMock::new().make_result(ReceivableDaoMock::new()); let banned_dao_factory = BannedDaoFactoryMock::new().make_result(BannedDaoMock::new()); let set_params_arc = Arc::new(Mutex::new(vec![])); let config_dao_mock = ConfigDaoMock::new() @@ -1686,7 +1685,12 @@ mod tests { vec![] ); let retrieve_txs_params = retrieve_txs_params_arc.lock().unwrap(); - assert_eq!(*retrieve_txs_params, vec![Some(FailureRetrieveCondition::ByStatus(FailureStatus::RecheckRequired))]); + assert_eq!( + *retrieve_txs_params, + vec![Some(FailureRetrieveCondition::ByStatus( + FailureStatus::RecheckRequired + ))] + ); assert_eq!( receivable_scanner.common.payment_thresholds.as_ref(), &payment_thresholds @@ -1715,9 +1719,10 @@ mod tests { initial_rc_count + 3 ); } - + #[test] - fn tx_failures_yet_needing_recheck_uploads_from_db_while_pending_payable_scanner_is_constructed(){ + fn tx_failures_yet_needing_recheck_uploads_from_db_while_pending_payable_scanner_is_constructed( + ) { let retrieve_txs_params_arc = Arc::new(Mutex::new(vec![])); let payable_dao_factory = PayableDaoFactoryMock::new() .make_result(PayableDaoMock::new()) @@ -1733,15 +1738,15 @@ mod tests { let mut failed_tx_2 = make_failed_tx(456); failed_tx_2.hash = tx_hash_2; failed_tx_2.status = FailureStatus::RecheckRequired; - let failed_payable_dao_factory = - FailedPayableDaoFactoryMock::new() - .make_result(FailedPayableDaoMock::new() - .retrieve_txs_params(&retrieve_txs_params_arc) - .retrieve_txs_result(vec![failed_tx_1, failed_tx_2])); - let receivable_dao_factory = ReceivableDaoFactoryMock::new().make_result(ReceivableDaoMock::new()); + let failed_payable_dao_factory = FailedPayableDaoFactoryMock::new().make_result( + FailedPayableDaoMock::new() + .retrieve_txs_params(&retrieve_txs_params_arc) + .retrieve_txs_result(vec![failed_tx_1, failed_tx_2]), + ); + let receivable_dao_factory = + ReceivableDaoFactoryMock::new().make_result(ReceivableDaoMock::new()); let banned_dao_factory = BannedDaoFactoryMock::new().make_result(BannedDaoMock::new()); - let config_dao_mock = ConfigDaoMock::new() - .set_result(Ok(())); + let config_dao_mock = ConfigDaoMock::new().set_result(Ok(())); let config_dao_factory = ConfigDaoFactoryMock::new().make_result(config_dao_mock); let scanners = Scanners::new( @@ -1763,8 +1768,16 @@ mod tests { .downcast_ref::() .unwrap(); let retrieve_txs_params = retrieve_txs_params_arc.lock().unwrap(); - assert_eq!(*retrieve_txs_params, vec![Some(FailureRetrieveCondition::ByStatus(FailureStatus::RecheckRequired))]); - assert_eq!(pending_payable_scanner.txs_pending_too_long_in_previous_scan_cycle, vec![tx_hash_1, tx_hash_2]); + assert_eq!( + *retrieve_txs_params, + vec![Some(FailureRetrieveCondition::ByStatus( + FailureStatus::RecheckRequired + ))] + ); + assert_eq!( + pending_payable_scanner.txs_pending_too_long_in_previous_scan_cycle, + vec![tx_hash_1, tx_hash_2] + ); } #[test] @@ -2958,24 +2971,28 @@ mod tests { } #[test] - fn interpret_transaction_receipt_when_transaction_status_is_a_failure() { + fn interprets_tx_receipt_when_transaction_status_is_a_failure() { init_test_logging(); - let test_name = "interpret_transaction_receipt_when_transaction_status_is_a_failure"; + let test_name = "interprets_tx_receipt_when_transaction_status_is_a_failure"; let hash = make_tx_hash(0xabc); let mut sent_tx = make_sent_tx(2244); sent_tx.hash = hash; - let failure_reason = BlockchainTxFailure::Unrecognized; + let blockchain_failure = BlockchainTxFailure::Unrecognized; let logger = Logger::new(test_name); let scan_report = PendingPayableScanSummary::default(); let result = - handle_status_with_failure(scan_report, sent_tx.clone(), failure_reason, &logger); + handle_status_with_failure(scan_report, sent_tx.clone(), blockchain_failure, &logger); + let failure_reason = blockchain_failure.into(); let failed_tx = FailedTx::from((sent_tx, failure_reason)); assert_eq!( result, PendingPayableScanSummary { - failures: vec![failed_tx], + failures_summary: FailuresSummary { + failures: vec![failed_tx], + any_rpc_call_failure: false + }, confirmed: vec![] } ); @@ -2986,9 +3003,9 @@ mod tests { } #[test] - fn handle_transaction_receipt_if_the_tx_keeps_pending() { + fn handles_tx_receipt_if_the_tx_keeps_pending() { init_test_logging(); - let test_name = "handle_transaction_receipt_if_the_tx_keeps_pending"; + let test_name = "handles_tx_receipt_if_the_tx_keeps_pending"; let subject = PendingPayableScannerBuilder::new().build(); let hash = make_tx_hash(0x913); let sent_tx_timestamp = to_unix_timestamp( @@ -3011,19 +3028,22 @@ mod tests { let result = subject.handle_receipts_for_pending_transactions(msg, &Logger::new(test_name)); let after = SystemTime::now(); - let expected_failed_tx = FailedTx::from(sent_tx); + let expected_failed_tx = FailedTx::from((sent_tx, FailureReason::PendingTooLong)); assert_eq!( result, PendingPayableScanSummary { - failures: vec![expected_failed_tx], + failures_summary: FailuresSummary { + failures: vec![expected_failed_tx], + any_rpc_call_failure: false + }, confirmed: vec![] } ); let log_handler = TestLogHandler::new(); let log_idx = log_handler.exists_log_matching(&format!( - "WARN: {test_name}: Pending tx \ - 0x0000000000000000000000000000000000000000000000000000000000000913 could not be \ - confirmed yet after \\d{{1,3}}(,\\d{{3}})* ms and will be retried with a more optimized gas price" + "INFO: {test_name}: Tx \ + 0x0000000000000000000000000000000000000000000000000000000000000913 not confirmed within \ + \\d{{1,3}}(,\\d{{3}})* ms. Will resubmit with higher gas price" )); let log_msg = log_handler.get_log_at(log_idx); let str_elapsed_ms = capture_numbers_with_separators_from_str(&log_msg, 3, ','); @@ -3046,63 +3066,48 @@ mod tests { } #[test] - fn handle_transaction_receipt_if_attempt_to_retrieve_the_receipt_failed() { + fn handles_tx_receipt_if_attempt_to_retrieve_the_receipt_failed() { init_test_logging(); - let test_name = "handle_transaction_receipt_if_attempt_to_retrieve_the_receipt_failed"; - todo!("finish me when you have a better idea how to implement the backward checks") - // let subject = PendingPayableScannerBuilder::new().build(); - // let hash = make_tx_hash(0x913); - // let sent_tx_timestamp = to_unix_timestamp( - // SystemTime::now() - // .checked_sub(Duration::from_secs(120)) - // .unwrap(), - // ); - // let mut sent_tx = make_sent_tx(456); - // sent_tx.hash = hash; - // sent_tx.timestamp = sent_tx_timestamp; - // let msg = TxReceiptsMessage { - // results: vec![TxReceiptResult::Err(TxReceiptError::new( - // hash, - // "What an epic error".to_string(), - // ))], - // response_skeleton_opt: None, - // }; - // - // let result = subject.handle_receipts_for_pending_transactions(msg, &Logger::new(test_name)); - // - // assert_eq!( - // result, - // PendingPayableScanSummary { - // failures: vec![expected_failed_tx], - // confirmed: vec![] - // } - // ); - // let log_handler = TestLogHandler::new(); - // let log_idx = log_handler.exists_log_matching(&format!( - // "WARN: {test_name}: Pending tx \ - // 0x0000000000000000000000000000000000000000000000000000000000000913 could not be \ - // confirmed yet after \\d{{1,3}}(,\\d{{3}})* ms and will be retried with a more optimized gas price" - // )); - // let log_msg = log_handler.get_log_at(log_idx); - // let str_elapsed_ms = capture_numbers_with_separators_from_str(&log_msg, 3, ','); - // let elapsed_ms = str_elapsed_ms[0].replace(",", "").parse::().unwrap(); - // let elapsed_ms_when_before = before - // .duration_since(from_unix_timestamp(sent_tx_timestamp)) - // .unwrap() - // .as_millis(); - // let elapsed_ms_when_after = after - // .duration_since(from_unix_timestamp(sent_tx_timestamp)) - // .unwrap() - // .as_millis(); - // assert!( - // elapsed_ms_when_before <= elapsed_ms && elapsed_ms <= elapsed_ms_when_after, - // "we expected the elapsed time {} ms to be between {} and {}.", - // elapsed_ms, - // elapsed_ms_when_before, - // elapsed_ms_when_after - // ); - } + let test_name = "handles_tx_receipt_if_attempt_to_retrieve_the_receipt_failed"; + let subject = PendingPayableScannerBuilder::new().build(); + let hash = make_tx_hash(0x913); + let sent_tx_timestamp = to_unix_timestamp( + SystemTime::now() + .checked_sub(Duration::from_secs(120)) + .unwrap(), + ); + let mut sent_tx = make_sent_tx(456); + sent_tx.hash = hash; + sent_tx.timestamp = sent_tx_timestamp; + let rpc_error = AppRpcError::Remote(RemoteError::InvalidResponse("bluh".to_string())); + let msg = TxReceiptsMessage { + results: vec![TxReceiptResult::Err(TxReceiptError::new( + sent_tx.clone(), + rpc_error.clone(), + ))], + response_skeleton_opt: None, + }; + + let result = subject.handle_receipts_for_pending_transactions(msg, &Logger::new(test_name)); + assert_eq!( + result, + PendingPayableScanSummary { + failures_summary: FailuresSummary { + failures: vec![], + any_rpc_call_failure: true + }, + confirmed: vec![] + } + ); + let log_handler = TestLogHandler::new(); + let log_idx = log_handler.exists_log_containing(&format!( + "WARN: {test_name}: Failed to retrieve tx receipt for \ + 0x0000000000000000000000000000000000000000000000000000000000000913: \ + Remote(InvalidResponse(\"bluh\")). \ + Will retry receipt retrieval next cycle" + )); + } #[test] fn handle_failed_transactions_works() { diff --git a/node/src/accountant/scanners/scanners_utils.rs b/node/src/accountant/scanners/scanners_utils.rs index db3a97109..61f855af3 100644 --- a/node/src/accountant/scanners/scanners_utils.rs +++ b/node/src/accountant/scanners/scanners_utils.rs @@ -325,25 +325,57 @@ pub mod pending_payable_scanner_utils { use masq_lib::ui_gateway::NodeToUiMessage; use std::time::SystemTime; use thousands::Separable; - use masq_lib::utils::ExpectValue; use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, FailureReason, FailureStatus}; use crate::accountant::db_access_objects::sent_payable_dao::SentTx; - use crate::accountant::db_access_objects::utils::from_unix_timestamp; + use crate::accountant::db_access_objects::utils::{from_unix_timestamp, TxHash}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, BlockchainTxFailure, TxReceiptError, TxStatus}; #[derive(Debug, Default, PartialEq, Eq, Clone)] pub struct PendingPayableScanSummary { - pub failures: Vec, + pub failures_summary: FailuresSummary, pub confirmed: Vec, } impl PendingPayableScanSummary { - pub fn requires_payments_retry(&self) -> bool { - match (self.failures.is_empty(), self.confirmed.is_empty()) { - (true, true) => unreachable!("reading tx receipts gave no results"), - (false, true) => true, - (false, false) => true, - (true, false) => false, + pub fn requires_payments_retry(&self) -> Option { + match ( + self.failures_summary.requires_retry(), + self.confirmed.is_empty(), + ) { + (None, true) => unreachable!("reading tx receipts gave no results"), + (None, _) => None, + (Some(retry), _) => Some(retry), + } + } + + fn register_success(&mut self, sent_tx: SentTx) { + self.confirmed.push(sent_tx); + } + + fn register_failure(&mut self, failed_tx: FailedTx) { + self.failures_summary.failures.push(failed_tx); + } + + fn register_rpc_failure(&mut self) { + self.failures_summary.any_rpc_call_failure = true; + } + } + + #[derive(Debug, Default, PartialEq, Eq, Clone)] + pub struct FailuresSummary { + pub failures: Vec, + pub any_rpc_call_failure: bool, + } + + impl FailuresSummary { + fn requires_retry(&self) -> Option { + if !self.failures.is_empty() { + return Some(Retry::RetryPayments); + } + if self.any_rpc_call_failure { + Some(Retry::RetryReceiptCheck) + } else { + None } } } @@ -351,7 +383,13 @@ pub mod pending_payable_scanner_utils { #[derive(Debug, PartialEq, Eq)] pub enum PendingPayableScanResult { NoPendingPayablesLeft(Option), - PaymentRetryRequired, + PaymentRetryRequired(Retry), + } + + #[derive(Debug, PartialEq, Eq)] + pub enum Retry { + RetryPayments, + RetryReceiptCheck, } pub fn elapsed_in_ms(timestamp: SystemTime) -> u128 { @@ -366,16 +404,15 @@ pub mod pending_payable_scanner_utils { sent_tx: SentTx, logger: &Logger, ) -> PendingPayableScanSummary { - warning!( + info!( logger, - "Pending tx {:?} could not be confirmed yet after {} ms and will be retried with a more \ - optimized gas price", + "Tx {:?} not confirmed within {} ms. Will resubmit with higher gas price", sent_tx.hash, elapsed_in_ms(from_unix_timestamp(sent_tx.timestamp)).separate_with_commas() ); - let failed_tx = FailedTx::from(sent_tx); - scan_report.failures.push(failed_tx); + let failed_tx = FailedTx::from((sent_tx, FailureReason::PendingTooLong)); + scan_report.register_failure(failed_tx); scan_report } @@ -394,7 +431,7 @@ pub mod pending_payable_scanner_utils { block_opt: Some(tx_block), ..sent_tx }; - scan_report.confirmed.push(completed_sent_tx); + scan_report.register_success(completed_sent_tx); scan_report } @@ -402,48 +439,49 @@ pub mod pending_payable_scanner_utils { pub fn handle_status_with_failure( mut scan_report: PendingPayableScanSummary, sent_tx: SentTx, - failure_reason: BlockchainTxFailure, + blockchain_failure: BlockchainTxFailure, logger: &Logger, ) -> PendingPayableScanSummary { + let failure_reason = FailureReason::from(blockchain_failure); let failed_tx = FailedTx::from((sent_tx, failure_reason)); warning!( logger, "Tx {:?} failed on blockchain due to: {}", failed_tx.hash, - failure_reason + blockchain_failure ); - scan_report.failures.push(failed_tx); + scan_report.register_failure(failed_tx); scan_report } - // Should be used only for pending txs that linger too long - impl From for FailedTx { - fn from(sent_tx: SentTx) -> Self { - FailedTx { - hash: sent_tx.hash, - receiver_address: sent_tx.receiver_address, - amount_minor: sent_tx.amount_minor, - timestamp: sent_tx.timestamp, - gas_price_minor: sent_tx.gas_price_minor, - nonce: sent_tx.nonce, - reason: FailureReason::PendingTooLong, - status: FailureStatus::RetryRequired, - } - } + pub fn handle_rpc_failure( + mut scan_report: PendingPayableScanSummary, + rpc_error: TxReceiptError, + logger: &Logger, + ) -> PendingPayableScanSummary { + warning!( + logger, + "Failed to retrieve tx receipt for {:?}: {:?}. Will retry receipt retrieval next cycle", + rpc_error.tx.hash, + rpc_error.err + ); + + scan_report.register_rpc_failure(); + scan_report } impl From for FailureReason { fn from(failure: BlockchainTxFailure) -> Self { match failure { - BlockchainTxFailure::Unrecognized => FailureReason::General, + BlockchainTxFailure::Unrecognized => FailureReason::Reverted, } } } - impl From<(SentTx, BlockchainTxFailure)> for FailedTx { - fn from((sent_tx, blockchain_failure): (SentTx, BlockchainTxFailure)) -> Self { + impl From<(SentTx, FailureReason)> for FailedTx { + fn from((sent_tx, failure_reason): (SentTx, FailureReason)) -> Self { FailedTx { hash: sent_tx.hash, receiver_address: sent_tx.receiver_address, @@ -451,7 +489,7 @@ pub mod pending_payable_scanner_utils { timestamp: sent_tx.timestamp, gas_price_minor: sent_tx.gas_price_minor, nonce: sent_tx.nonce, - reason: blockchain_failure.into(), + reason: failure_reason, status: FailureStatus::RetryRequired, } } @@ -498,12 +536,13 @@ mod tests { use itertools::Itertools; use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, FailureReason, FailureStatus}; use crate::accountant::db_access_objects::sent_payable_dao::SentTx; - use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::PendingPayableScanSummary; + use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{FailuresSummary, PendingPayableScanSummary, Retry}; use crate::accountant::test_utils::{make_failed_tx, make_sent_tx}; use crate::assert_on_testing_enum_with_all_its_variants; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::BlockchainTxFailure; use crate::blockchain::blockchain_interface::data_structures::errors::{BlockchainError, PayableTransactionError}; use crate::blockchain::blockchain_interface::data_structures::{ProcessedPayableFallible, RpcPayableFailure}; + use crate::blockchain::errors::{AppRpcError, LocalError}; #[test] fn investigate_debt_extremes_picks_the_most_relevant_records() { @@ -824,7 +863,7 @@ mod tests { #[test] fn conversion_between_blockchain_tx_failure_and_failure_reason_works() { let input_and_expected_results = - vec![(BlockchainTxFailure::Unrecognized, FailureReason::General)]; + vec![(BlockchainTxFailure::Unrecognized, FailureReason::Reverted)]; let inputs_len = input_and_expected_results.len(); let check_nums = input_and_expected_results @@ -842,25 +881,8 @@ mod tests { } #[test] - fn conversion_from_sent_tx_and_blockchain_tx_failure_to_failed_tx_works() { - let sent_tx = set_up_exemplary_sent_tx_for_tx_failure_conversion_test(); - - let result = FailedTx::from((sent_tx.clone(), BlockchainTxFailure::Unrecognized)); - - assert_conversion_into_failed_tx(result, sent_tx, FailureReason::General) - } - - #[test] - fn conversion_from_sent_tx_straight_into_failed_tx_works() { - let sent_tx = set_up_exemplary_sent_tx_for_tx_failure_conversion_test(); - - let result = FailedTx::from(sent_tx.clone()); - - assert_conversion_into_failed_tx(result, sent_tx, FailureReason::PendingTooLong) - } - - fn set_up_exemplary_sent_tx_for_tx_failure_conversion_test() -> SentTx { - SentTx { + fn conversion_from_sent_tx_and_failure_reason_to_failed_tx_works() { + let sent_tx = SentTx { hash: make_tx_hash(789), receiver_address: make_wallet("receiver").address(), amount_minor: 123_456_789, @@ -872,7 +894,20 @@ mod tests { gas_price_minor: gwei_to_wei(424_u64), nonce: 456_u64.into(), block_opt: None, - } + }; + + let result = FailedTx::from((sent_tx.clone(), FailureReason::Reverted)); + let result_2 = FailedTx::from(( + sent_tx.clone(), + FailureReason::Submission(AppRpcError::Local(LocalError::Internal)), + )); + + assert_conversion_into_failed_tx(result, sent_tx.clone(), FailureReason::Reverted); + assert_conversion_into_failed_tx( + result_2, + sent_tx, + FailureReason::Submission(AppRpcError::Local(LocalError::Internal)), + ); } fn assert_conversion_into_failed_tx( @@ -958,14 +993,65 @@ mod tests { } #[test] - fn requires_payments_retry_says_yes() { + fn requires_payments_retry() { + let cases = vec![ + PendingPayableScanSummary { + failures_summary: FailuresSummary { + failures: vec![make_failed_tx(456)], + any_rpc_call_failure: false, + }, + confirmed: vec![], + }, + PendingPayableScanSummary { + failures_summary: FailuresSummary { + failures: vec![make_failed_tx(789)], + any_rpc_call_failure: true, + }, + confirmed: vec![], + }, + PendingPayableScanSummary { + failures_summary: FailuresSummary { + failures: vec![make_failed_tx(123), make_failed_tx(789)], + any_rpc_call_failure: false, + }, + confirmed: vec![make_sent_tx(777)], + }, + PendingPayableScanSummary { + failures_summary: FailuresSummary { + failures: vec![make_failed_tx(123)], + any_rpc_call_failure: true, + }, + confirmed: vec![make_sent_tx(777)], + }, + ]; + + cases.into_iter().enumerate().for_each(|(idx, case)| { + let result = case.requires_payments_retry(); + assert_eq!( + result, + Some(Retry::RetryPayments), + "We expected Some(Retry::RetryPayments), but got {:?} for case with idx {}", + result, + idx + ) + }) + } + + #[test] + fn requires_only_receipt_retrieval_retry() { let cases = vec![ PendingPayableScanSummary { - failures: vec![make_failed_tx(456)], + failures_summary: FailuresSummary { + failures: vec![], + any_rpc_call_failure: true, + }, confirmed: vec![], }, PendingPayableScanSummary { - failures: vec![make_failed_tx(123), make_failed_tx(789)], + failures_summary: FailuresSummary { + failures: vec![], + any_rpc_call_failure: true, + }, confirmed: vec![make_sent_tx(777)], }, ]; @@ -973,8 +1059,10 @@ mod tests { cases.into_iter().enumerate().for_each(|(idx, case)| { let result = case.requires_payments_retry(); assert_eq!( - result, true, - "We expected true, but got false for case with idx {}", + result, + Some(Retry::RetryReceiptCheck), + "We expected Some(Retry::RetryReceiptCheck), but got {:?} for case with idx {}", + result, idx ) }) @@ -983,13 +1071,16 @@ mod tests { #[test] fn requires_payments_retry_says_no() { let report = PendingPayableScanSummary { - failures: vec![], + failures_summary: FailuresSummary { + failures: vec![], + any_rpc_call_failure: false, + }, confirmed: vec![make_sent_tx(123)], }; let result = report.requires_payments_retry(); - assert_eq!(result, false) + assert_eq!(result, None) } #[test] @@ -997,9 +1088,12 @@ mod tests { expected = "internal error: entered unreachable code: reading tx receipts gave \ no results" )] - fn requires_payments_retry_meets_invalid_summary() { + fn requires_payments_retry_with_no_results_in_whole_summary() { let report = PendingPayableScanSummary { - failures: vec![], + failures_summary: FailuresSummary { + failures: vec![], + any_rpc_call_failure: false, + }, confirmed: vec![], }; diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs index c00eb66f6..814027d64 100644 --- a/node/src/accountant/test_utils.rs +++ b/node/src/accountant/test_utils.rs @@ -362,8 +362,15 @@ impl AccountantBuilder { pub fn failed_payable_daos( mut self, - specially_configured_daos: Vec>, + mut specially_configured_daos: Vec>, ) -> Self { + specially_configured_daos.iter_mut().for_each(|dao| { + if let DaoWithDestination::ForPendingPayableScanner(dao) = dao { + let mut extended_queue = vec![vec![]]; + extended_queue.append(&mut dao.retrieve_txs_results.borrow_mut()); + dao.retrieve_txs_results.replace(extended_queue); + } + }); create_or_update_factory!( specially_configured_daos, FAILED_PAYABLE_DAOS_ACCOUNTANT_INITIALIZATION_ORDER, @@ -426,10 +433,11 @@ impl AccountantBuilder { .make_result(SentPayableDaoMock::new()) .make_result(SentPayableDaoMock::new()), ); + let failed_tx_dao_for_pending_payable_scanner = + FailedPayableDaoMock::new().retrieve_txs_result(vec![]); let failed_payable_dao_factory = self.failed_payable_dao_factory_opt.unwrap_or( FailedPayableDaoFactoryMock::new() - .make_result(FailedPayableDaoMock::new()) - .make_result(FailedPayableDaoMock::new()) + .make_result(failed_tx_dao_for_pending_payable_scanner) .make_result(FailedPayableDaoMock::new()), ); let banned_dao_factory = self @@ -1240,6 +1248,28 @@ impl FailedPayableDaoMock { self.delete_records_results.borrow_mut().push(result); self } + + pub fn merge_results_with(self, other: &Self) -> Self { + Self::merge_single_result( + &self.get_tx_identifiers_results, + &other.get_tx_identifiers_results, + ); + Self::merge_single_result( + &self.insert_new_records_results, + &other.insert_new_records_results, + ); + Self::merge_single_result(&self.retrieve_txs_results, &other.retrieve_txs_results); + Self::merge_single_result( + &self.update_statuses_results, + &other.update_statuses_results, + ); + Self::merge_single_result(&self.delete_records_results, &other.delete_records_results); + self + } + + fn merge_single_result(one: &RefCell>, other: &RefCell>) { + one.borrow_mut().append(&mut other.borrow_mut()); + } } pub struct FailedPayableDaoFactoryMock { @@ -1359,8 +1389,12 @@ impl PendingPayableScannerBuilder { self.failed_payable_dao = failed_payable_dao; self } - - pub fn build(self) -> PendingPayableScanner { + + pub fn build(mut self) -> PendingPayableScanner { + self.failed_payable_dao = FailedPayableDaoMock::default() + .retrieve_txs_result(vec![]) + .merge_results_with(&self.failed_payable_dao); + PendingPayableScanner::new( Box::new(self.payable_dao), Box::new(self.sent_payable_dao), diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index b40dd9885..948089371 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -582,6 +582,7 @@ mod tests { use crate::accountant::PendingPayable; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxWithStatus, TransactionBlock, TxReceiptError}; use crate::accountant::scanners::payable_scanner_extension::msgs::{UnpricedQualifiedPayables, QualifiedPayableWithGasPrice}; + use crate::blockchain::errors::{AppRpcError, RemoteError}; impl Handler> for BlockchainBridge { type Result = (); @@ -1292,7 +1293,7 @@ mod tests { .start(); let (accountant, _, accountant_recording_arc) = make_recorder(); let accountant_addr = accountant - .system_stop_conditions(match_lazily_every_type_id!(TxReceiptsMessage, ScanError)) + .system_stop_conditions(match_lazily_every_type_id!(TxReceiptsMessage)) .start(); let report_transaction_receipt_recipient: Recipient = accountant_addr.clone().recipient(); @@ -1349,8 +1350,8 @@ mod tests { TxReceiptResult::Ok(TxWithStatus::new(sent_tx_3, TxStatus::Pending)), TxReceiptResult::Err( TxReceiptError::new( - sent_tx_4.hash, - "RPC error: Error { code: ServerError(429), message: \"The requests per second (RPS) of your requests are higher than your plan allows.\", data: None }".to_string())) + sent_tx_4, + AppRpcError:: Remote(RemoteError::Web3RpcError { code: 429, message: "The requests per second (RPS) of your requests are higher than your plan allows.".to_string()}))) ], response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs index 60b50a332..33e340ccf 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs @@ -7,6 +7,7 @@ use crate::blockchain::blockchain_interface::blockchain_interface_web3::CONTRACT use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainError; use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainError::QueryFailed; use crate::blockchain::blockchain_interface::lower_level_interface::LowBlockchainInt; +use crate::blockchain::errors::AppRpcError; use crate::sub_lib::wallet::Wallet; use ethereum_types::{H256, U256, U64}; use futures::Future; @@ -119,13 +120,13 @@ impl Display for TxStatus { #[derive(Debug, PartialEq, Eq, Clone)] pub struct TxReceiptError { - tx_hash: TxHash, - err_msg: String, + pub tx: SentTx, + pub err: AppRpcError, } impl TxReceiptError { - pub fn new(tx_hash: TxHash, err_msg: String) -> Self { - Self { tx_hash, err_msg } + pub fn new(tx: SentTx, err: AppRpcError) -> Self { + Self { tx, err } } } diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs index 64396e8d6..4f47a3808 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs @@ -26,7 +26,7 @@ use crate::accountant::db_access_objects::sent_payable_dao::SentTx; use crate::blockchain::blockchain_bridge::{BlockMarker, BlockScanRange, RegisterNewPendingPayables}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{LowBlockchainIntWeb3, TxStatus, TxWithStatus, TxReceiptError, TxReceiptResult}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::utils::{create_blockchain_agent_web3, send_payables_within_batch, BlockchainAgentFutureResult}; - +use crate::blockchain::errors::{AppRpcError, RemoteError}; // TODO We should probably begin to attach these constants to the interfaces more tightly, so that // we aren't baffled by which interface they belong with. I suggest to declare them inside // their inherent impl blocks. They will then need to be preceded by the class name @@ -239,17 +239,16 @@ impl BlockchainInterface for BlockchainInterfaceWeb3 { )) } else { TxReceiptResult::Err(TxReceiptError::new( - sent_tx.hash, - e.to_string(), + sent_tx, + AppRpcError::Remote(RemoteError::InvalidResponse( + e.to_string(), + )), )) } } } } - Err(e) => TxReceiptResult::Err(TxReceiptError::new( - sent_tx.hash, - e.to_string(), - )), + Err(e) => TxReceiptResult::Err(TxReceiptError::new(sent_tx, e.into())), }) .collect::>()) }), @@ -1114,8 +1113,16 @@ mod tests { assert_eq!(result[0], TxReceiptResult::Err( TxReceiptError::new( - sent_tx_1.hash, - "RPC error: Error { code: ServerError(429), message: \"The requests per second (RPS) of your requests are higher than your plan allows.\", data: None }".to_string()))); + sent_tx_1, + AppRpcError::Remote( + RemoteError::Web3RpcError { + code: 429, + message: + "The requests per second (RPS) of your requests are higher than your plan allows." + .to_string() + } + ))) + ); assert_eq!( result[1], TxReceiptResult::Ok(TxWithStatus::new(sent_tx_2, TxStatus::Pending)) @@ -1123,8 +1130,10 @@ mod tests { assert_eq!( result[2], TxReceiptResult::Err(TxReceiptError::new( - sent_tx_3.hash, - "invalid type: string \"trash\", expected struct Receipt".to_string() + sent_tx_3, + AppRpcError::Remote(RemoteError::InvalidResponse( + "invalid type: string \"trash\", expected struct Receipt".to_string() + )) )) ); assert_eq!( From 9e2fc52be9b02a21bb7518b4095c0816b79beb67 Mon Sep 17 00:00:00 2001 From: Bert Date: Tue, 22 Jul 2025 14:42:33 +0200 Subject: [PATCH 17/61] GH-642: interim commit --- node/src/accountant/scanners/mod.rs | 31 +++++++-------- .../src/accountant/scanners/scanners_utils.rs | 38 +++++++++---------- 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index 798c47ce9..06a0f1e98 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -12,7 +12,7 @@ use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableT LocallyCausedError, RemotelyCausedErrors, }; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{debugging_summary_after_error_separation, err_msg_for_failure_with_expected_but_missing_sent_tx_record, investigate_debt_extremes, payables_debug_summary, separate_errors, separate_rowids_and_hashes, OperationOutcome, PayableScanResult, PayableThresholdsGauge, PayableThresholdsGaugeReal, PayableTransactingErrorEnum, PendingPayableMissingInDb}; -use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_status_with_failure, handle_successful_tx, PendingPayableScanSummary, PendingPayableScanResult, handle_still_pending_tx, handle_rpc_failure}; +use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_status_with_failure, handle_successful_tx, PendingPayableReport, PendingPayableScanResult, handle_still_pending_tx, handle_rpc_failure}; use crate::accountant::scanners::scanners_utils::receivable_scanner_utils::balance_and_age; use crate::accountant::{PendingPayable, PendingPayableId, ScanError, ScanForPendingPayables, ScanForRetryPayables}; use crate::accountant::{ @@ -959,11 +959,12 @@ impl Scanner for PendingPayableScan "Processing receipts for {} txs", message.results.len() ); - let scan_report = self.handle_receipts_for_pending_transactions(message, logger); + + let scan_report = self.interpret_tx_receipts(message, logger); let payment_retry_opt = scan_report.requires_payments_retry(); - self.process_transactions_by_their_state(scan_report, logger); + self.process_txs_by_their_state(scan_report, logger); self.mark_as_ended(logger); @@ -1009,12 +1010,12 @@ impl PendingPayableScanner { } } - fn handle_receipts_for_pending_transactions( + fn interpret_tx_receipts( &self, msg: TxReceiptsMessage, logger: &Logger, - ) -> PendingPayableScanSummary { - let scan_report = PendingPayableScanSummary::default(); + ) -> PendingPayableReport { + let scan_report = PendingPayableReport::default(); msg.results .into_iter() .fold( @@ -1044,9 +1045,9 @@ impl PendingPayableScanner { ) } - fn process_transactions_by_their_state( + fn process_txs_by_their_state( &mut self, - scan_report: PendingPayableScanSummary, + scan_report: PendingPayableReport, logger: &Logger, ) { self.handle_confirmed_transactions(scan_report.confirmed, logger); @@ -1478,7 +1479,7 @@ mod tests { use crate::accountant::db_access_objects::utils::{from_unix_timestamp, to_unix_timestamp, TxHash}; use crate::accountant::scanners::payable_scanner_extension::msgs::{QualifiedPayablesBeforeGasPriceSelection, QualifiedPayablesMessage, UnpricedQualifiedPayables}; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{OperationOutcome, PayableScanResult, PendingPayableMissingInDb}; - use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_status_with_failure, PendingPayableScanSummary, PendingPayableScanResult, FailuresSummary}; + use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_status_with_failure, PendingPayableReport, PendingPayableScanResult, FailuresSummary}; use crate::accountant::scanners::{Scanner, StartScanError, StartableScanner, PayableScanner, PendingPayableScanner, ReceivableScanner, ScannerCommon, Scanners, MTError}; use crate::accountant::test_utils::{make_custom_payment_thresholds, make_payable_account, make_qualified_and_unqualified_payables, make_receivable_account, BannedDaoFactoryMock, BannedDaoMock, ConfigDaoFactoryMock, PayableDaoFactoryMock, PayableDaoMock, PayableScannerBuilder, PayableThresholdsGaugeMock, SentPayableDaoFactoryMock, SentPayableDaoMock, PendingPayableScannerBuilder, ReceivableDaoFactoryMock, ReceivableDaoMock, ReceivableScannerBuilder, make_sent_tx, FailedPayableDaoMock, FailedPayableDaoFactoryMock, make_failed_tx, make_transaction_block}; use crate::accountant::{gwei_to_wei, PendingPayableId, ReceivedPayments, TxReceiptsMessage, RequestTransactionReceipts, ScanError, ScanForRetryPayables, SentPayables, DEFAULT_PENDING_TOO_LONG_SEC, PendingPayable}; @@ -2979,7 +2980,7 @@ mod tests { sent_tx.hash = hash; let blockchain_failure = BlockchainTxFailure::Unrecognized; let logger = Logger::new(test_name); - let scan_report = PendingPayableScanSummary::default(); + let scan_report = PendingPayableReport::default(); let result = handle_status_with_failure(scan_report, sent_tx.clone(), blockchain_failure, &logger); @@ -2988,7 +2989,7 @@ mod tests { let failed_tx = FailedTx::from((sent_tx, failure_reason)); assert_eq!( result, - PendingPayableScanSummary { + PendingPayableReport { failures_summary: FailuresSummary { failures: vec![failed_tx], any_rpc_call_failure: false @@ -3025,13 +3026,13 @@ mod tests { }; let before = SystemTime::now(); - let result = subject.handle_receipts_for_pending_transactions(msg, &Logger::new(test_name)); + let result = subject.interpret_tx_receipts(msg, &Logger::new(test_name)); let after = SystemTime::now(); let expected_failed_tx = FailedTx::from((sent_tx, FailureReason::PendingTooLong)); assert_eq!( result, - PendingPayableScanSummary { + PendingPayableReport { failures_summary: FailuresSummary { failures: vec![expected_failed_tx], any_rpc_call_failure: false @@ -3088,11 +3089,11 @@ mod tests { response_skeleton_opt: None, }; - let result = subject.handle_receipts_for_pending_transactions(msg, &Logger::new(test_name)); + let result = subject.interpret_tx_receipts(msg, &Logger::new(test_name)); assert_eq!( result, - PendingPayableScanSummary { + PendingPayableReport { failures_summary: FailuresSummary { failures: vec![], any_rpc_call_failure: true diff --git a/node/src/accountant/scanners/scanners_utils.rs b/node/src/accountant/scanners/scanners_utils.rs index 61f855af3..7e89f4a28 100644 --- a/node/src/accountant/scanners/scanners_utils.rs +++ b/node/src/accountant/scanners/scanners_utils.rs @@ -331,12 +331,12 @@ pub mod pending_payable_scanner_utils { use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, BlockchainTxFailure, TxReceiptError, TxStatus}; #[derive(Debug, Default, PartialEq, Eq, Clone)] - pub struct PendingPayableScanSummary { + pub struct PendingPayableReport { pub failures_summary: FailuresSummary, pub confirmed: Vec, } - impl PendingPayableScanSummary { + impl PendingPayableReport { pub fn requires_payments_retry(&self) -> Option { match ( self.failures_summary.requires_retry(), @@ -400,10 +400,10 @@ pub mod pending_payable_scanner_utils { } pub fn handle_still_pending_tx( - mut scan_report: PendingPayableScanSummary, + mut scan_report: PendingPayableReport, sent_tx: SentTx, logger: &Logger, - ) -> PendingPayableScanSummary { + ) -> PendingPayableReport { info!( logger, "Tx {:?} not confirmed within {} ms. Will resubmit with higher gas price", @@ -417,11 +417,11 @@ pub mod pending_payable_scanner_utils { } pub fn handle_successful_tx( - mut scan_report: PendingPayableScanSummary, + mut scan_report: PendingPayableReport, sent_tx: SentTx, tx_block: TransactionBlock, logger: &Logger, - ) -> PendingPayableScanSummary { + ) -> PendingPayableReport { info!( logger, "Detected tx {:?} added to block {}.", sent_tx.hash, tx_block.block_number, @@ -437,11 +437,11 @@ pub mod pending_payable_scanner_utils { //TODO: failures handling is going to need enhancement suggested by GH-693 pub fn handle_status_with_failure( - mut scan_report: PendingPayableScanSummary, + mut scan_report: PendingPayableReport, sent_tx: SentTx, blockchain_failure: BlockchainTxFailure, logger: &Logger, - ) -> PendingPayableScanSummary { + ) -> PendingPayableReport { let failure_reason = FailureReason::from(blockchain_failure); let failed_tx = FailedTx::from((sent_tx, failure_reason)); @@ -457,10 +457,10 @@ pub mod pending_payable_scanner_utils { } pub fn handle_rpc_failure( - mut scan_report: PendingPayableScanSummary, + mut scan_report: PendingPayableReport, rpc_error: TxReceiptError, logger: &Logger, - ) -> PendingPayableScanSummary { + ) -> PendingPayableReport { warning!( logger, "Failed to retrieve tx receipt for {:?}: {:?}. Will retry receipt retrieval next cycle", @@ -536,7 +536,7 @@ mod tests { use itertools::Itertools; use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, FailureReason, FailureStatus}; use crate::accountant::db_access_objects::sent_payable_dao::SentTx; - use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{FailuresSummary, PendingPayableScanSummary, Retry}; + use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{FailuresSummary, PendingPayableReport, Retry}; use crate::accountant::test_utils::{make_failed_tx, make_sent_tx}; use crate::assert_on_testing_enum_with_all_its_variants; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::BlockchainTxFailure; @@ -995,28 +995,28 @@ mod tests { #[test] fn requires_payments_retry() { let cases = vec![ - PendingPayableScanSummary { + PendingPayableReport { failures_summary: FailuresSummary { failures: vec![make_failed_tx(456)], any_rpc_call_failure: false, }, confirmed: vec![], }, - PendingPayableScanSummary { + PendingPayableReport { failures_summary: FailuresSummary { failures: vec![make_failed_tx(789)], any_rpc_call_failure: true, }, confirmed: vec![], }, - PendingPayableScanSummary { + PendingPayableReport { failures_summary: FailuresSummary { failures: vec![make_failed_tx(123), make_failed_tx(789)], any_rpc_call_failure: false, }, confirmed: vec![make_sent_tx(777)], }, - PendingPayableScanSummary { + PendingPayableReport { failures_summary: FailuresSummary { failures: vec![make_failed_tx(123)], any_rpc_call_failure: true, @@ -1040,14 +1040,14 @@ mod tests { #[test] fn requires_only_receipt_retrieval_retry() { let cases = vec![ - PendingPayableScanSummary { + PendingPayableReport { failures_summary: FailuresSummary { failures: vec![], any_rpc_call_failure: true, }, confirmed: vec![], }, - PendingPayableScanSummary { + PendingPayableReport { failures_summary: FailuresSummary { failures: vec![], any_rpc_call_failure: true, @@ -1070,7 +1070,7 @@ mod tests { #[test] fn requires_payments_retry_says_no() { - let report = PendingPayableScanSummary { + let report = PendingPayableReport { failures_summary: FailuresSummary { failures: vec![], any_rpc_call_failure: false, @@ -1089,7 +1089,7 @@ mod tests { no results" )] fn requires_payments_retry_with_no_results_in_whole_summary() { - let report = PendingPayableScanSummary { + let report = PendingPayableReport { failures_summary: FailuresSummary { failures: vec![], any_rpc_call_failure: false, From 0d108844034535b1738c34a0fe87e1ed770c23b9 Mon Sep 17 00:00:00 2001 From: Bert Date: Wed, 23 Jul 2025 20:31:17 +0200 Subject: [PATCH 18/61] GH-642: first I need to finish the impl of the db system of tx statuses...opened in its own PR and then will start from here on --- node/src/accountant/scanners/mod.rs | 73 ++++++++++--------- .../src/accountant/scanners/scanners_utils.rs | 66 +++++++++-------- 2 files changed, 72 insertions(+), 67 deletions(-) diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index 06a0f1e98..5ec104c92 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -12,7 +12,7 @@ use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableT LocallyCausedError, RemotelyCausedErrors, }; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{debugging_summary_after_error_separation, err_msg_for_failure_with_expected_but_missing_sent_tx_record, investigate_debt_extremes, payables_debug_summary, separate_errors, separate_rowids_and_hashes, OperationOutcome, PayableScanResult, PayableThresholdsGauge, PayableThresholdsGaugeReal, PayableTransactingErrorEnum, PendingPayableMissingInDb}; -use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_status_with_failure, handle_successful_tx, PendingPayableReport, PendingPayableScanResult, handle_still_pending_tx, handle_rpc_failure}; +use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_status_with_failure, handle_successful_tx, PendingPayableReport, PendingPayableScanResult, handle_still_pending_tx, handle_rpc_failure, FailuresSummary}; use crate::accountant::scanners::scanners_utils::receivable_scanner_utils::balance_and_age; use crate::accountant::{PendingPayable, PendingPayableId, ScanError, ScanForPendingPayables, ScanForRetryPayables}; use crate::accountant::{ @@ -1051,7 +1051,7 @@ impl PendingPayableScanner { logger: &Logger, ) { self.handle_confirmed_transactions(scan_report.confirmed, logger); - self.handle_failed_transactions(scan_report.failures_summary.failures, logger); + self.handle_failed_transactions(scan_report.failures_summary, logger); } fn handle_confirmed_transactions(&mut self, confirmed_txs: Vec, logger: &Logger) { @@ -1148,40 +1148,41 @@ impl PendingPayableScanner { ); } - fn handle_failed_transactions(&self, failures: Vec, logger: &Logger) { + fn handle_failed_transactions(&self, failures_summary: FailuresSummary, logger: &Logger) { fn joint_hashes(hashes: &HashSet) -> String { comma_joined_stringifiable(&hashes.iter().sorted().collect_vec(), |hash| { format!("{:?}", hash) }) } - if !failures.is_empty() { - let hashes = failures - .iter() - .map(|failed_tx| failed_tx.hash) - .collect::>(); - match self.failed_payable_dao.insert_new_records(&failures) { - Ok(_) => { - debug!(logger, "Failed payables {:?} recorded", hashes); - } - Err(e) => panic!( - "Unable to record failed payables {} due to {:?}", - joint_hashes(&hashes), - e - ), - } - match self.sent_payable_dao.delete_records(&hashes) { - Ok(_) => debug!( - logger, - "Deleted sent payable records for failed txs {:?}", hashes - ), - Err(e) => panic!( - "Unable to delete sent_payable entries for failed txs {} due to {:?}", - joint_hashes(&hashes), - e - ), - }; - } + todo!("fix me... do both, add new records and also just change a status where needed") + // if !failures.is_empty() { + // let hashes = failures + // .iter() + // .map(|failed_tx| failed_tx.hash) + // .collect::>(); + // match self.failed_payable_dao.insert_new_records(&failures) { + // Ok(_) => { + // debug!(logger, "Failed payables {:?} recorded", hashes); + // } + // Err(e) => panic!( + // "Unable to record failed payables {} due to {:?}", + // joint_hashes(&hashes), + // e + // ), + // } + // match self.sent_payable_dao.delete_records(&hashes) { + // Ok(_) => debug!( + // logger, + // "Deleted sent payable records for failed txs {:?}", hashes + // ), + // Err(e) => panic!( + // "Unable to delete sent_payable entries for failed txs {} due to {:?}", + // joint_hashes(&hashes), + // e + // ), + // }; + // } } } @@ -2991,8 +2992,8 @@ mod tests { result, PendingPayableReport { failures_summary: FailuresSummary { - failures: vec![failed_tx], - any_rpc_call_failure: false + tx_failures: vec![failed_tx], + rpc_failures: false }, confirmed: vec![] } @@ -3034,8 +3035,8 @@ mod tests { result, PendingPayableReport { failures_summary: FailuresSummary { - failures: vec![expected_failed_tx], - any_rpc_call_failure: false + tx_failures: vec![expected_failed_tx], + rpc_failures: false }, confirmed: vec![] } @@ -3095,8 +3096,8 @@ mod tests { result, PendingPayableReport { failures_summary: FailuresSummary { - failures: vec![], - any_rpc_call_failure: true + tx_failures: vec![], + rpc_failures: true }, confirmed: vec![] } diff --git a/node/src/accountant/scanners/scanners_utils.rs b/node/src/accountant/scanners/scanners_utils.rs index 7e89f4a28..8d0c46c39 100644 --- a/node/src/accountant/scanners/scanners_utils.rs +++ b/node/src/accountant/scanners/scanners_utils.rs @@ -353,30 +353,31 @@ pub mod pending_payable_scanner_utils { } fn register_failure(&mut self, failed_tx: FailedTx) { - self.failures_summary.failures.push(failed_tx); + self.failures_summary.tx_failures.push(failed_tx); } - fn register_rpc_failure(&mut self) { - self.failures_summary.any_rpc_call_failure = true; + fn register_rpc_failure(&mut self, failed_tx_hash: TxHash) { + // TODO solve me by changing just the status + //self.failures_summary.failures.push(failed_tx); } } #[derive(Debug, Default, PartialEq, Eq, Clone)] pub struct FailuresSummary { - pub failures: Vec, - pub any_rpc_call_failure: bool, + pub tx_failures: Vec, + pub rpc_failures: Vec, } impl FailuresSummary { fn requires_retry(&self) -> Option { - if !self.failures.is_empty() { - return Some(Retry::RetryPayments); - } - if self.any_rpc_call_failure { - Some(Retry::RetryReceiptCheck) - } else { - None - } + todo!("add the logic to combine the two vecs"); + // if self.tx_failures.is_empty() { + // None + // } else if self.tx_failures.iter().any(|failed_tx| failed_tx.status == FailureStatus::RetryRequired) { + // Some(Retry::RetryPayments) + // } else { + // Some(Retry::RetryReceiptCheck) + // } } } @@ -458,6 +459,7 @@ pub mod pending_payable_scanner_utils { pub fn handle_rpc_failure( mut scan_report: PendingPayableReport, + recheck_required_txs: &[TxHash], rpc_error: TxReceiptError, logger: &Logger, ) -> PendingPayableReport { @@ -467,8 +469,10 @@ pub mod pending_payable_scanner_utils { rpc_error.tx.hash, rpc_error.err ); - - scan_report.register_rpc_failure(); + // TODO just to make sure we didn't ball something up badly, could be deduced also without + if !recheck_required_txs.contains(&rpc_error.tx.hash) { + scan_report.register_failure(rpc_error.tx.hash); + } scan_report } @@ -997,29 +1001,29 @@ mod tests { let cases = vec![ PendingPayableReport { failures_summary: FailuresSummary { - failures: vec![make_failed_tx(456)], - any_rpc_call_failure: false, + tx_failures: vec![make_failed_tx(456)], + rpc_failures: false, }, confirmed: vec![], }, PendingPayableReport { failures_summary: FailuresSummary { - failures: vec![make_failed_tx(789)], - any_rpc_call_failure: true, + tx_failures: vec![make_failed_tx(789)], + rpc_failures: true, }, confirmed: vec![], }, PendingPayableReport { failures_summary: FailuresSummary { - failures: vec![make_failed_tx(123), make_failed_tx(789)], - any_rpc_call_failure: false, + tx_failures: vec![make_failed_tx(123), make_failed_tx(789)], + rpc_failures: false, }, confirmed: vec![make_sent_tx(777)], }, PendingPayableReport { failures_summary: FailuresSummary { - failures: vec![make_failed_tx(123)], - any_rpc_call_failure: true, + tx_failures: vec![make_failed_tx(123)], + rpc_failures: true, }, confirmed: vec![make_sent_tx(777)], }, @@ -1042,15 +1046,15 @@ mod tests { let cases = vec![ PendingPayableReport { failures_summary: FailuresSummary { - failures: vec![], - any_rpc_call_failure: true, + tx_failures: vec![], + rpc_failures: true, }, confirmed: vec![], }, PendingPayableReport { failures_summary: FailuresSummary { - failures: vec![], - any_rpc_call_failure: true, + tx_failures: vec![], + rpc_failures: true, }, confirmed: vec![make_sent_tx(777)], }, @@ -1072,8 +1076,8 @@ mod tests { fn requires_payments_retry_says_no() { let report = PendingPayableReport { failures_summary: FailuresSummary { - failures: vec![], - any_rpc_call_failure: false, + tx_failures: vec![], + rpc_failures: false, }, confirmed: vec![make_sent_tx(123)], }; @@ -1091,8 +1095,8 @@ mod tests { fn requires_payments_retry_with_no_results_in_whole_summary() { let report = PendingPayableReport { failures_summary: FailuresSummary { - failures: vec![], - any_rpc_call_failure: false, + tx_failures: vec![], + rpc_failures: false, }, confirmed: vec![], }; From 21368d731fc1b69da99692552162c957cfddef26 Mon Sep 17 00:00:00 2001 From: Bert Date: Tue, 29 Jul 2025 23:45:52 +0200 Subject: [PATCH 19/61] GH-642: pending payable scanner machinery has been given the true skeleton --- .../db_access_objects/failed_payable_dao.rs | 4 +- .../db_access_objects/sent_payable_dao.rs | 1 + node/src/accountant/mod.rs | 343 +++++---- node/src/accountant/scanners/mod.rs | 549 +++++++++---- .../src/accountant/scanners/scanners_utils.rs | 721 ++++++++++++++---- node/src/accountant/test_utils.rs | 22 +- node/src/blockchain/blockchain_bridge.rs | 91 ++- .../lower_level_interface_web3.rs | 65 +- .../blockchain_interface_web3/mod.rs | 114 +-- .../blockchain/blockchain_interface/mod.rs | 3 +- 10 files changed, 1326 insertions(+), 587 deletions(-) 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 3a1cb01c7..18ef021d0 100644 --- a/node/src/accountant/db_access_objects/failed_payable_dao.rs +++ b/node/src/accountant/db_access_objects/failed_payable_dao.rs @@ -94,7 +94,7 @@ pub struct FailedTx { #[derive(Debug, PartialEq, Eq)] pub enum FailureRetrieveCondition { ByStatus(FailureStatus), - EveryRecheckRequiredRecord, + RecheckRequiredRecords, } impl Display for FailureRetrieveCondition { @@ -103,7 +103,7 @@ impl Display for FailureRetrieveCondition { FailureRetrieveCondition::ByStatus(status) => { write!(f, "WHERE status LIKE '{:?}%'", status) } - FailureRetrieveCondition::EveryRecheckRequiredRecord => { + FailureRetrieveCondition::RecheckRequiredRecords => { todo!() } } 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 b611a6157..18fcce326 100644 --- a/node/src/accountant/db_access_objects/sent_payable_dao.rs +++ b/node/src/accountant/db_access_objects/sent_payable_dao.rs @@ -99,6 +99,7 @@ impl Ord for TxConfirmation { } } +#[derive(Debug, PartialEq, Eq)] pub enum RetrieveCondition { IsPending, ByHash(Vec), diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index 5f94086e5..301cdaf40 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -76,7 +76,7 @@ use std::time::SystemTime; use web3::types::H256; use crate::accountant::scanners::scan_schedulers::{PayableSequenceScanner, ScanRescheduleAfterEarlyStop, ScanSchedulers}; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::OperationOutcome; -use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{PendingPayableScanResult, Retry}; +use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{PendingPayableScanResult, Retry, TxHashByTable}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::TxReceiptResult; pub const CRASH_KEY: &str = "ACCOUNTANT"; @@ -333,7 +333,7 @@ impl Handler for Accountant { response_skeleton_opt, &self.logger, ), - Retry::RetryReceiptCheck => todo!(), + Retry::RetryOnlyTxStatusCheck => todo!(), }, }; } @@ -471,7 +471,7 @@ pub trait SkeletonOptHolder { #[derive(Debug, PartialEq, Eq, Message, Clone)] pub struct RequestTransactionReceipts { - pub sent_txs: Vec, + pub tx_hashes: Vec, pub response_skeleton_opt: Option, } @@ -1121,8 +1121,8 @@ impl Accountant { } fn register_new_pending_sent_tx(&self, msg: RegisterNewPendingPayables) { - fn serialize_hashes(sent_txs: &[SentTx]) -> String { - comma_joined_stringifiable(sent_txs, |sent_tx| format!("{:?}", sent_tx.hash)) + fn serialize_hashes(tx_hashes: &[SentTx]) -> String { + comma_joined_stringifiable(tx_hashes, |sent_tx| format!("{:?}", sent_tx.hash)) } match self.sent_payable_dao.insert_new_records(&msg.new_sent_txs) { @@ -1231,7 +1231,7 @@ mod tests { use crate::accountant::test_utils::DaoWithDestination::{ ForAccountantBody, ForPayableScanner, ForPendingPayableScanner, ForReceivableScanner, }; - use crate::accountant::test_utils::{bc_from_earning_wallet, bc_from_wallets, make_payable_account, make_qualified_and_unqualified_payables, BannedDaoFactoryMock, ConfigDaoFactoryMock, MessageIdGeneratorMock, PayableDaoFactoryMock, PayableDaoMock, PayableScannerBuilder, PaymentAdjusterMock, SentPayableDaoFactoryMock, SentPayableDaoMock, ReceivableDaoFactoryMock, ReceivableDaoMock, make_sent_tx, FailedPayableDaoMock, FailedPayableDaoFactoryMock, make_transaction_block, PendingPayableScannerBuilder}; + use crate::accountant::test_utils::{bc_from_earning_wallet, bc_from_wallets, make_payable_account, make_qualified_and_unqualified_payables, BannedDaoFactoryMock, ConfigDaoFactoryMock, MessageIdGeneratorMock, PayableDaoFactoryMock, PayableDaoMock, PayableScannerBuilder, PaymentAdjusterMock, SentPayableDaoFactoryMock, SentPayableDaoMock, ReceivableDaoFactoryMock, ReceivableDaoMock, make_sent_tx, FailedPayableDaoMock, FailedPayableDaoFactoryMock, make_transaction_block, PendingPayableScannerBuilder, make_failed_tx}; use crate::accountant::test_utils::{make_unpriced_qualified_payables_for_retry_mode, make_priced_qualified_payables}; use crate::accountant::test_utils::{AccountantBuilder, BannedDaoMock}; use crate::accountant::Accountant; @@ -1288,13 +1288,14 @@ mod tests { use std::sync::Mutex; use std::time::{Duration, UNIX_EPOCH}; use std::vec; - use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, ValidationStatus}; + use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, FailureRetrieveCondition, ValidationStatus}; use crate::accountant::db_access_objects::failed_payable_dao::ValidationStatus::Waiting; use crate::accountant::scanners::payable_scanner_extension::msgs::UnpricedQualifiedPayables; - use crate::accountant::db_access_objects::sent_payable_dao::{Detection, SentPayableDaoError, SentTx, TxConfirmation, TxStatus}; + use crate::accountant::db_access_objects::sent_payable_dao::{Detection, RetrieveCondition, SentPayableDaoError, SentTx, TxConfirmation, TxStatus}; use crate::accountant::scanners::scan_schedulers::{NewPayableScanDynIntervalComputer, NewPayableScanDynIntervalComputerReal}; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{OperationOutcome, PayableScanResult}; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxWithStatus, TransactionBlock, BlockchainTxFailure, ReceiptCheck, TxReceiptResult}; + use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{TxByTable, TxHashByTable}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{RetrievedTxStatus, TransactionBlock, BlockchainTxFailure, StatusReadFromReceiptCheck, TxReceiptResult, TxReceiptError}; use crate::test_utils::recorder_counter_msgs::SingleTypeCounterMsgSetup; impl Handler> for Accountant { @@ -1853,17 +1854,9 @@ mod tests { receivable_scan_interval: Duration::from_millis(10_000), pending_payable_scan_interval: Duration::from_secs(100), }); - let sent_tx = SentTx { - timestamp: to_unix_timestamp(SystemTime::now()) - 1234, - gas_price_minor: 456_000_000, - nonce: 45, - hash: make_tx_hash(123), - amount_minor: 1_000_000, - receiver_address: make_wallet("receiver_address").address(), - status: TxStatus::Pending(ValidationStatus::Waiting), - }; - let sent_payable_dao = - SentPayableDaoMock::default().retrieve_txs_result(vec![sent_tx.clone()]); + let sent_tx = make_sent_tx(555); + let tx_hash = sent_tx.hash; + let sent_payable_dao = SentPayableDaoMock::default().retrieve_txs_result(vec![sent_tx]); let mut subject = AccountantBuilder::default() .consuming_wallet(make_paying_wallet(b"consuming")) .bootstrapper_config(config) @@ -1897,7 +1890,7 @@ mod tests { assert_eq!( blockchain_bridge_recording.get_record::(0), &RequestTransactionReceipts { - sent_txs: vec![sent_tx], + tx_hashes: vec![TxHashByTable::SentPayable(tx_hash)], response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, context_id: 4321, @@ -1942,9 +1935,9 @@ mod tests { block_number: 78901234.into(), }; let tx_receipts_msg = TxReceiptsMessage { - results: vec![TxReceiptResult::Ok(TxWithStatus::new( - sent_tx.clone(), - ReceiptCheck::TxSucceeded(tx_block), + results: vec![TxReceiptResult::Ok(RetrievedTxStatus::new( + TxHashByTable::SentPayable(sent_tx.hash), + StatusReadFromReceiptCheck::Succeeded(tx_block), ))], response_skeleton_opt, }; @@ -2168,9 +2161,9 @@ mod tests { let first_counter_msg_setup = setup_for_counter_msg_triggered_via_type_id!( RequestTransactionReceipts, TxReceiptsMessage { - results: vec![TxReceiptResult::Ok(TxWithStatus::new( - sent_tx.clone(), - ReceiptCheck::TxFailed(BlockchainTxFailure::Unrecognized) + results: vec![TxReceiptResult::Ok(RetrievedTxStatus::new( + TxHashByTable::SentPayable(sent_tx.hash), + StatusReadFromReceiptCheck::Failed(BlockchainTxFailure::Unrecognized) )),], response_skeleton_opt }, @@ -2748,7 +2741,7 @@ mod tests { let system = System::new(test_name); let _ = SystemKillerActor::new(Duration::from_secs(10)).start(); let config = bc_from_wallets(consuming_wallet.clone(), earning_wallet.clone()); - let sent_tx = make_sent_tx(456); + let tx_hash = make_tx_hash(456); let payable_scanner = ScannerMock::new() .scan_started_at_result(None) .scan_started_at_result(None) @@ -2772,7 +2765,7 @@ mod tests { .scan_started_at_result(None) .start_scan_params(&scan_params.pending_payable_start_scan) .start_scan_result(Ok(RequestTransactionReceipts { - sent_txs: vec![sent_tx.clone()], + tx_hashes: vec![TxHashByTable::SentPayable(tx_hash)], response_skeleton_opt: None, })) .finish_scan_params(&scan_params.pending_payable_finish_scan) @@ -2796,9 +2789,9 @@ mod tests { let subject_addr: Addr = subject.start(); let subject_subs = Accountant::make_subs_from(&subject_addr); let expected_tx_receipts_msg = TxReceiptsMessage { - results: vec![TxReceiptResult::Ok(TxWithStatus::new( - sent_tx.clone(), - ReceiptCheck::TxFailed(BlockchainTxFailure::Unrecognized), + results: vec![TxReceiptResult::Ok(RetrievedTxStatus::new( + TxHashByTable::SentPayable(tx_hash), + StatusReadFromReceiptCheck::Failed(BlockchainTxFailure::Unrecognized), ))], response_skeleton_opt: None, }; @@ -2858,9 +2851,9 @@ mod tests { ¬ify_and_notify_later_params.receivables_notify_later, receivable_scan_interval, ); - // Given the assertions prove that the pending payable scanner has run multiple times - // before the new payable scanner started or was scheduled, the front position belongs to - // the one first mentioned, no doubts. + // Since the assertions proved that the pending payable scanner had run multiple times + // before the new payable scanner started or was scheduled, the front position definitely + // belonged to the one first mentioned. } #[derive(Default)] @@ -2873,7 +2866,6 @@ mod tests { pending_payable_finish_scan: Arc>>, receivable_start_scan: Arc, Logger, String)>>>, - // receivable_finish_scan ... not needed } #[derive(Default)] @@ -3519,7 +3511,7 @@ mod tests { response_skeleton_opt: None, }; let transaction_hash = make_tx_hash(789); - let sent_tx = make_sent_tx(456); + let tx_hash = make_tx_hash(456); let creditor_wallet = make_wallet("blah"); let counter_msg_2 = SentPayables { payment_procedure_result: Ok(vec![ProcessedPayableFallible::Correct( @@ -3527,20 +3519,20 @@ mod tests { )]), response_skeleton_opt: None, }; - let tx_with_status = TxWithStatus { - sent_tx: sent_tx.clone(), - status: ReceiptCheck::TxSucceeded(TransactionBlock { + let tx_with_status = RetrievedTxStatus::new( + TxHashByTable::SentPayable(tx_hash), + StatusReadFromReceiptCheck::Succeeded(TransactionBlock { block_hash: make_tx_hash(369369), block_number: 4444444444u64.into(), }), - }; + ); let requested_tx = make_tx_hash(234); let counter_msg_3 = TxReceiptsMessage { results: vec![TxReceiptResult::Ok(tx_with_status)], response_skeleton_opt: None, }; let request_transaction_receipts_msg = RequestTransactionReceipts { - sent_txs: vec![sent_tx], + tx_hashes: vec![TxHashByTable::SentPayable(tx_hash)], response_skeleton_opt: None, }; let qualified_payables_msg = QualifiedPayablesMessage { @@ -4075,30 +4067,38 @@ mod tests { } #[test] - fn scan_for_pending_payables_finds_still_pending_payables() { + fn scan_for_pending_payables_finds_various_payables() { init_test_logging(); + let test_name = "scan_for_pending_payables_finds_various_payables"; let now = SystemTime::now(); + let start_scan_params_arc = Arc::new(Mutex::new(vec![])); let (blockchain_bridge, _, blockchain_bridge_recording_arc) = make_recorder(); let blockchain_bridge_addr = blockchain_bridge .system_stop_conditions(match_lazily_every_type_id!(RequestTransactionReceipts)) .start(); - let sent_tx_1 = make_sent_tx(456); - let sent_tx_2 = make_sent_tx(789); - let sent_payable_dao = - SentPayableDaoMock::default().retrieve_txs_result(vec![sent_tx_1.clone()]); - let config = bc_from_earning_wallet(make_wallet("mine")); + let tx_hash_1 = make_tx_hash(456); + let tx_hash_2 = make_tx_hash(789); + let tx_hash_3 = make_tx_hash(123); + let expected_composed_msg_for_blockchain_bridge = RequestTransactionReceipts { + tx_hashes: vec![ + TxHashByTable::SentPayable(tx_hash_1), + TxHashByTable::FailedPayable(tx_hash_2), + TxHashByTable::FailedPayable(tx_hash_3), + ], + response_skeleton_opt: None, + }; + let pending_payable_scanner = ScannerMock::new() + .start_scan_params(&start_scan_params_arc) + .start_scan_result(Ok(expected_composed_msg_for_blockchain_bridge.clone())); + let consuming_wallet = make_wallet("consuming"); let system = System::new("pending payable scan"); let mut subject = AccountantBuilder::default() - .consuming_wallet(make_paying_wallet(b"consuming")) - .bootstrapper_config(config) - .build(); - let pending_payable_scanner_real = PendingPayableScannerBuilder::new() - .sent_payable_dao(sent_payable_dao) + .consuming_wallet(consuming_wallet.clone()) .build(); subject .scanners - .replace_scanner(ScannerReplacement::PendingPayable(ReplacementType::Real( - pending_payable_scanner_real, + .replace_scanner(ScannerReplacement::PendingPayable(ReplacementType::Mock( + pending_payable_scanner, ))); subject.request_transaction_receipts_sub_opt = Some(blockchain_bridge_addr.recipient()); let account_addr = subject.start(); @@ -4109,19 +4109,24 @@ mod tests { }) .unwrap(); + let before = SystemTime::now(); system.run(); + let after = SystemTime::now(); + let mut start_scan_params = start_scan_params_arc.lock().unwrap(); + let (wallet, timestamp, response_skeleton_opt, logger, _) = start_scan_params.remove(0); + assert_eq!(wallet, consuming_wallet); + assert!(before <= timestamp && timestamp <= after); + assert_eq!(response_skeleton_opt, None); + assert!( + start_scan_params.is_empty(), + "Should be empty but {:?}", + start_scan_params + ); + assert_using_the_same_logger(&logger, test_name, Some("start scan payable")); let blockchain_bridge_recording = blockchain_bridge_recording_arc.lock().unwrap(); let received_msg = blockchain_bridge_recording.get_record::(0); - assert_eq!( - received_msg, - &RequestTransactionReceipts { - sent_txs: vec![sent_tx_1, sent_tx_2], - response_skeleton_opt: None, - } - ); + assert_eq!(received_msg, &expected_composed_msg_for_blockchain_bridge); assert_eq!(blockchain_bridge_recording.len(), 1); - let log_handler = TestLogHandler::new(); - log_handler.exists_log_containing("DEBUG: Accountant: Found 2 pending payables to process"); } #[test] @@ -4957,8 +4962,14 @@ mod tests { Box::new(NotifyHandleMock::default().notify_params(&retry_payable_notify_params_arc)); let system = System::new(test_name); let (mut msg, _) = make_tx_receipts_msg(vec![ - ReceiptCheck::PendingTx, - ReceiptCheck::TxFailed(BlockchainTxFailure::Unrecognized), + SeedsToMakeUpPayableWithStatus { + tx_hash: TxHashByTable::SentPayable(make_tx_hash(123)), + status: StatusReadFromReceiptCheck::Pending, + }, + SeedsToMakeUpPayableWithStatus { + tx_hash: TxHashByTable::FailedPayable(make_tx_hash(456)), + status: StatusReadFromReceiptCheck::Failed(BlockchainTxFailure::Unrecognized), + }, ]); let response_skeleton_opt = Some(ResponseSkeleton { client_id: 45, @@ -4986,19 +4997,25 @@ mod tests { #[test] fn accountant_confirms_pending_txs_and_schedules_the_new_payable_scanner_timely() { - let transactions_confirmed_params_arc = Arc::new(Mutex::new(vec![])); + init_test_logging(); + let test_name = + "accountant_confirms_pending_txs_and_schedules_the_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![])); let new_payable_notify_arc = Arc::new(Mutex::new(vec![])); - let payable_dao = PayableDaoMock::default() - .transactions_confirmed_params(&transactions_confirmed_params_arc) - .transactions_confirmed_result(Ok(())); - let sent_payable_dao = SentPayableDaoMock::default().confirm_tx_result(Ok(())); let system = System::new("new_payable_scanner_timely"); let mut subject = AccountantBuilder::default() - .payable_daos(vec![ForPendingPayableScanner(payable_dao)]) - .sent_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) + .logger(Logger::new(test_name)) .build(); + let pending_payable_scanner = ScannerMock::new() + .finish_scan_params(&finish_scan_params_arc) + .finish_scan_result(PendingPayableScanResult::NoPendingPayablesLeft(None)); + subject + .scanners + .replace_scanner(ScannerReplacement::PendingPayable(ReplacementType::Mock( + pending_payable_scanner, + ))); let last_new_payable_scan_timestamp = SystemTime::now() .checked_sub(Duration::from_secs(3)) .unwrap(); @@ -5023,22 +5040,35 @@ mod tests { Box::new(NotifyHandleMock::default().notify_params(&new_payable_notify_arc)); let subject_addr = subject.start(); let (msg, two_sent_txs) = make_tx_receipts_msg(vec![ - ReceiptCheck::TxSucceeded(TransactionBlock { - block_hash: make_tx_hash(123), - block_number: U64::from(100), - }), - ReceiptCheck::TxSucceeded(TransactionBlock { - block_hash: make_tx_hash(234), - block_number: U64::from(200), - }), + SeedsToMakeUpPayableWithStatus { + tx_hash: TxHashByTable::SentPayable(make_tx_hash(123)), + status: StatusReadFromReceiptCheck::Succeeded(TransactionBlock { + block_hash: make_tx_hash(123), + block_number: U64::from(100), + }), + }, + SeedsToMakeUpPayableWithStatus { + tx_hash: TxHashByTable::FailedPayable(make_tx_hash(555)), + status: StatusReadFromReceiptCheck::Succeeded(TransactionBlock { + block_hash: make_tx_hash(234), + block_number: U64::from(200), + }), + }, ]); - subject_addr.try_send(msg).unwrap(); + subject_addr.try_send(msg.clone()).unwrap(); System::current().stop(); system.run(); - let transactions_confirmed_params = transactions_confirmed_params_arc.lock().unwrap(); - assert_eq!(*transactions_confirmed_params, vec![two_sent_txs.clone()]); + let mut finish_scan_params = finish_scan_params_arc.lock().unwrap(); + let (captured_msg, logger) = finish_scan_params.remove(0); + assert_eq!(captured_msg, msg); + assert_using_the_same_logger(&logger, test_name, None); + assert!( + finish_scan_params.is_empty(), + "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); @@ -5066,22 +5096,21 @@ mod tests { init_test_logging(); let test_name = "accountant_confirms_payable_txs_and_schedules_the_delayed_new_payable_scanner_asap"; - let transactions_confirmed_params_arc = Arc::new(Mutex::new(vec![])); - let confirm_tx_params_arc = Arc::new(Mutex::new(vec![])); + 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![])); let new_payable_notify_arc = Arc::new(Mutex::new(vec![])); - let payable_dao = PayableDaoMock::default() - .transactions_confirmed_params(&transactions_confirmed_params_arc) - .transactions_confirmed_result(Ok(())); - let sent_payable_dao = SentPayableDaoMock::default() - .confirm_tx_params(&confirm_tx_params_arc) - .confirm_tx_result(Ok(())); let mut subject = AccountantBuilder::default() - .payable_daos(vec![ForPendingPayableScanner(payable_dao)]) - .sent_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) .logger(Logger::new(test_name)) .build(); + let pending_payable_scanner = ScannerMock::new() + .finish_scan_params(&finish_scan_params_arc) + .finish_scan_result(PendingPayableScanResult::NoPendingPayablesLeft(None)); + subject + .scanners + .replace_scanner(ScannerReplacement::PendingPayable(ReplacementType::Mock( + pending_payable_scanner, + ))); let last_new_payable_scan_timestamp = SystemTime::now() .checked_sub(Duration::from_secs(8)) .unwrap(); @@ -5107,23 +5136,29 @@ mod tests { let tx_block_2 = make_transaction_block(1234); let subject_addr = subject.start(); let (msg, two_sent_txs) = make_tx_receipts_msg(vec![ - ReceiptCheck::TxSucceeded(tx_block_1), - ReceiptCheck::TxSucceeded(tx_block_2), + SeedsToMakeUpPayableWithStatus { + tx_hash: TxHashByTable::SentPayable(make_tx_hash(123)), + status: StatusReadFromReceiptCheck::Succeeded(tx_block_1), + }, + SeedsToMakeUpPayableWithStatus { + tx_hash: TxHashByTable::FailedPayable(make_tx_hash(456)), + status: StatusReadFromReceiptCheck::Succeeded(tx_block_2), + }, ]); - subject_addr.try_send(msg).unwrap(); + subject_addr.try_send(msg.clone()).unwrap(); let system = System::new(test_name); System::current().stop(); system.run(); - let transactions_confirmed_params = transactions_confirmed_params_arc.lock().unwrap(); - assert_eq!(*transactions_confirmed_params, vec![two_sent_txs.clone()]); - let confirm_tx_params = confirm_tx_params_arc.lock().unwrap(); - assert_eq!( - *confirm_tx_params, - vec![ - hashmap![two_sent_txs[0].hash => TxConfirmation {block_info: tx_block_1,detection: Detection::Normal}, two_sent_txs[1].hash => TxConfirmation{ block_info: tx_block_2, detection: Detection::Normal }] - ] + let mut finish_scan_params = finish_scan_params_arc.lock().unwrap(); + let (captured_msg, logger) = finish_scan_params.remove(0); + assert_eq!(captured_msg, msg); + assert_using_the_same_logger(&logger, test_name, None); + assert!( + finish_scan_params.is_empty(), + "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) = @@ -5142,22 +5177,22 @@ mod tests { ); let new_payable_notify = new_payable_notify_arc.lock().unwrap(); assert_eq!(*new_payable_notify, vec![ScanForNewPayables::default()]); - TestLogHandler::new().exists_log_containing(&format!("INFO: {test_name}: \ - Txs 0x0000000000000000000000000000000000000000000000000000000000000001 (block 95256152263), \ - 0x0000000000000000000000000000000000000000000000000000000000000002 (block 1879080904) have \ - been confirmed")); } #[test] fn scheduler_for_new_payables_operates_with_proper_now_timestamp() { let new_payable_notify_later_arc = Arc::new(Mutex::new(vec![])); - let payable_dao = PayableDaoMock::default().transactions_confirmed_result(Ok(())); - let sent_payable_dao = SentPayableDaoMock::default().confirm_tx_result(Ok(())); - let system = System::new("scheduler_for_new_payables_operates_with_proper_now_timestamp"); + let test_name = "scheduler_for_new_payables_operates_with_proper_now_timestamp"; let mut subject = AccountantBuilder::default() - .payable_daos(vec![ForPendingPayableScanner(payable_dao)]) - .sent_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) + .logger(Logger::new(test_name)) .build(); + let pending_payable_scanner = ScannerMock::new() + .finish_scan_result(PendingPayableScanResult::NoPendingPayablesLeft(None)); + subject + .scanners + .replace_scanner(ScannerReplacement::PendingPayable(ReplacementType::Mock( + pending_payable_scanner, + ))); let last_new_payable_scan_timestamp = SystemTime::now() .checked_sub(Duration::from_millis(3500)) .unwrap(); @@ -5173,17 +5208,15 @@ mod tests { subject.scan_schedulers.payable.new_payable_notify_later = Box::new( NotifyLaterHandleMock::default().notify_later_params(&new_payable_notify_later_arc), ); + let system = System::new(test_name); let subject_addr = subject.start(); - let (msg, _) = make_tx_receipts_msg(vec![ - ReceiptCheck::TxSucceeded(TransactionBlock { + let (msg, _) = make_tx_receipts_msg(vec![SeedsToMakeUpPayableWithStatus { + tx_hash: TxHashByTable::SentPayable(make_tx_hash(123)), + status: StatusReadFromReceiptCheck::Succeeded(TransactionBlock { block_hash: make_tx_hash(123), block_number: U64::from(100), }), - ReceiptCheck::TxSucceeded(TransactionBlock { - block_hash: make_tx_hash(234), - block_number: U64::from(200), - }), - ]); + }]); subject_addr.try_send(msg).unwrap(); @@ -5213,25 +5246,24 @@ mod tests { ); } - fn make_tx_receipts_msg(status_txs: Vec) -> (TxReceiptsMessage, Vec) { - let (tx_receipt_results, sent_tx_vec) = status_txs.into_iter().enumerate().fold( - (vec![], vec![]), - |(mut tx_receipt_results, mut sent_tx_vec), (idx, status)| { - let mut sent_tx = make_sent_tx(1 + idx as u64); - - if let ReceiptCheck::TxSucceeded(block) = &status { - sent_tx.status = TxStatus::Confirmed { - block_hash: format!("{:?}", block.block_hash), - block_number: block.block_number.as_u64(), - detection: Detection::Normal, - } - } - - let result = TxReceiptResult::Ok(TxWithStatus::new(sent_tx.clone(), status)); + pub struct SeedsToMakeUpPayableWithStatus { + tx_hash: TxHashByTable, + status: StatusReadFromReceiptCheck, + } + fn make_tx_receipts_msg( + seeds: Vec, + ) -> (TxReceiptsMessage, Vec) { + let (tx_receipt_results, tx_record_vec) = seeds.into_iter().enumerate().fold( + (vec![], vec![]), + |(mut tx_receipt_results, mut record_by_table_vec), (idx, seed_params)| { + let tx_hash = seed_params.tx_hash; + let status = seed_params.status; + let (result, record) = + make_receipt_check_result_and_record(tx_hash, status, idx as u64); tx_receipt_results.push(result); - sent_tx_vec.push(sent_tx); - (tx_receipt_results, sent_tx_vec) + record_by_table_vec.push(record); + (tx_receipt_results, record_by_table_vec) }, ); @@ -5240,7 +5272,40 @@ mod tests { response_skeleton_opt: None, }; - (msg, sent_tx_vec) + (msg, tx_record_vec) + } + + fn make_receipt_check_result_and_record( + tx_hash: TxHashByTable, + status: StatusReadFromReceiptCheck, + idx: u64, + ) -> (TxReceiptResult, TxByTable) { + match tx_hash { + TxHashByTable::SentPayable(hash) => { + let mut sent_tx = make_sent_tx(1 + idx); + sent_tx.hash = hash; + + if let StatusReadFromReceiptCheck::Succeeded(block) = &status { + sent_tx.status = TxStatus::Confirmed { + block_hash: format!("{:?}", block.block_hash), + block_number: block.block_number.as_u64(), + detection: Detection::Normal, + } + } + + let result = TxReceiptResult::Ok(RetrievedTxStatus::new(tx_hash, status)); + let record_by_table = TxByTable::SentPayable(sent_tx); + (result, record_by_table) + } + TxHashByTable::FailedPayable(hash) => { + let mut failed_tx = make_failed_tx(1 + idx); + failed_tx.hash = hash; + + let result = TxReceiptResult::Ok(RetrievedTxStatus::new(tx_hash, status)); + let record_by_table = TxByTable::FailedPayable(failed_tx); + (result, record_by_table) + } + } } #[test] diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index f479f0059..073b66025 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -12,7 +12,7 @@ use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableT LocallyCausedError, RemotelyCausedErrors, }; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{debugging_summary_after_error_separation, err_msg_for_failure_with_expected_but_missing_sent_tx_record, investigate_debt_extremes, payables_debug_summary, separate_errors, separate_rowids_and_hashes, OperationOutcome, PayableScanResult, PayableThresholdsGauge, PayableThresholdsGaugeReal, PayableTransactingErrorEnum, PendingPayableMissingInDb}; -use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_status_with_failure, handle_successful_tx, PendingPayableReport, PendingPayableScanResult, handle_still_pending_tx, handle_rpc_failure, DetectedFailures, FailuresRequiringDoubleCheck}; +use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_status_with_failure, handle_successful_tx, ReceiptScanReport, PendingPayableScanResult, handle_still_pending_tx, handle_rpc_failure, DetectedFailures, FailuresRequiringDoubleCheck, CurrentPendingPayables, RecordsCoupledWithReceiptCheckResults, PresortedTxFailure, ValidationStatusUpdate, DetectedConfirmations}; use crate::accountant::scanners::scanners_utils::receivable_scanner_utils::balance_and_age; use crate::accountant::{PendingPayable, PendingPayableId, ScanError, ScanForPendingPayables, ScanForRetryPayables}; use crate::accountant::{ @@ -50,7 +50,7 @@ use crate::accountant::db_access_objects::sent_payable_dao::{RetrieveCondition, use crate::accountant::db_access_objects::utils::{TxHash, TxIdentifiers}; use crate::accountant::scanners::payable_scanner_extension::{MultistageDualPayableScanner, PreparedAdjustment, SolvencySensitivePaymentInstructor}; use crate::accountant::scanners::payable_scanner_extension::msgs::{BlockchainAgentWithContextMessage, QualifiedPayablesMessage, UnpricedQualifiedPayables}; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, TxReceiptResult, ReceiptCheck}; +use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, TxReceiptResult, StatusReadFromReceiptCheck}; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; use crate::db_config::persistent_configuration::{PersistentConfiguration, PersistentConfigurationReal}; @@ -893,7 +893,8 @@ pub struct PendingPayableScanner { pub sent_payable_dao: Box, pub failed_payable_dao: Box, pub financial_statistics: Rc>, - pub previous_soft_failures: FailuresRequiringDoubleCheck, + pub current_sent_payables: CurrentPendingPayables, + pub yet_unproven_failures: FailuresRequiringDoubleCheck, } impl @@ -922,23 +923,33 @@ impl StartableScanner let pending_sent_txs = self .sent_payable_dao .retrieve_txs(Some(RetrieveCondition::IsPending)); - match pending_sent_txs.is_empty() { - true => { - self.mark_as_ended(logger); - Err(StartScanError::NothingToProcess) - } - false => { - debug!( - logger, - "Found {} pending payables to process", - pending_sent_txs.len() - ); - Ok(RequestTransactionReceipts { - sent_txs: pending_sent_txs, - response_skeleton_opt, - }) - } - } + + let unproven_failures = self + .failed_payable_dao + .retrieve_txs(Some(FailureRetrieveCondition::RecheckRequiredRecords)); + + // TODO 1) check non-empty collections + // 2) fill in the respective caches + // 3) form a joint collection for the message + todo!("fix me"); + + // match pending_sent_txs.is_empty() { + // true => { + // self.mark_as_ended(logger); + // Err(StartScanError::NothingToProcess) + // } + // false => { + // debug!( + // logger, + // "Found {} pending payables to process", + // pending_sent_txs.len() + // ); + // Ok(RequestTransactionReceipts { + // tx_hashes: pending_sent_txs, + // response_skeleton_opt, + // }) + // } + // } } } @@ -992,11 +1003,11 @@ impl PendingPayableScanner { payment_thresholds: Rc, financial_statistics: Rc>, ) -> Self { - let previous_soft_failures = FailuresRequiringDoubleCheck::new( - failed_payable_dao.retrieve_txs(Some(FailureRetrieveCondition::ByStatus( - todo!(), //FailureRetrieveCondition::EveryRecheckRequiredRecord - ))), - ); + // let yet_unproven_failures = FailuresRequiringDoubleCheck::new( + // failed_payable_dao.retrieve_txs(Some(FailureRetrieveCondition::ByStatus( + // todo!(), //FailureRetrieveCondition::RecheckRequiredRecords + // ))), + // ); Self { common: ScannerCommon::new(payment_thresholds), @@ -1004,74 +1015,98 @@ impl PendingPayableScanner { sent_payable_dao, failed_payable_dao, financial_statistics, - previous_soft_failures, + current_sent_payables: CurrentPendingPayables::default(), + yet_unproven_failures: FailuresRequiringDoubleCheck::default(), } } fn interpret_tx_receipts( - &self, + &mut self, msg: TxReceiptsMessage, logger: &Logger, - ) -> PendingPayableReport { - let scan_report = PendingPayableReport::default(); - msg.results - .into_iter() - .fold( - scan_report, - |scan_report_so_far, receipt_result| match receipt_result { - TxReceiptResult::Ok(sent_tx_with_status) => match sent_tx_with_status.status { - ReceiptCheck::TxSucceeded(tx_block) => handle_successful_tx( - scan_report_so_far, - self.previous_soft_failures.hashes(), - sent_tx_with_status.sent_tx, - tx_block, - logger, - ), - ReceiptCheck::TxFailed(reason) => handle_status_with_failure( - scan_report_so_far, - sent_tx_with_status.sent_tx, - reason, - logger, - ), - ReceiptCheck::PendingTx => handle_still_pending_tx( - scan_report_so_far, - sent_tx_with_status.sent_tx, - logger, - ), - }, - TxReceiptResult::Err(e) => handle_rpc_failure( - scan_report_so_far, - self.previous_soft_failures.hashes(), - e, - logger, - ), - }, - ) + ) -> ReceiptScanReport { + let records_with_check_results = self.prepare_data_couples(msg); + self.compose_receipt_scan_report(records_with_check_results, logger) } - fn process_txs_by_their_state(&mut self, scan_report: PendingPayableReport, logger: &Logger) { + fn prepare_data_couples( + &mut self, + msg: TxReceiptsMessage, + ) -> RecordsCoupledWithReceiptCheckResults { + //TODO pull the records out from the caches and leave them empty + todo!() + } + + fn compose_receipt_scan_report( + &self, + records_with_check_results: RecordsCoupledWithReceiptCheckResults, + logger: &Logger, + ) -> ReceiptScanReport { + todo!() + // let scan_report = ReceiptScanReport::default(); + // msg.results + // .into_iter() + // .fold( + // scan_report, + // |scan_report_so_far, receipt_result| match receipt_result { + // TxReceiptResult::Ok(sent_tx_with_status) => match sent_tx_with_status.status { + // StatusReadFromReceiptCheck::Succeeded(tx_block) => handle_successful_tx( + // scan_report_so_far, + // self.yet_unproven_failures.hashes(), + // sent_tx_with_status.tx_hash, + // tx_block, + // logger, + // ), + // StatusReadFromReceiptCheck::Failed(reason) => handle_status_with_failure( + // scan_report_so_far, + // sent_tx_with_status.sent_tx, + // reason, + // logger, + // ), + // StatusReadFromReceiptCheck::Pending => handle_still_pending_tx( + // scan_report_so_far, + // sent_tx_with_status.sent_tx, + // logger, + // ), + // }, + // TxReceiptResult::Err(e) => handle_rpc_failure( + // scan_report_so_far, + // self.yet_unproven_failures.hashes(), + // e, + // logger, + // ), + // }, + // ) + } + + fn process_txs_by_their_state(&mut self, scan_report: ReceiptScanReport, logger: &Logger) { // TODO a) confirmation of just newly pending txs // b) confirmations of reclaimed txs self.handle_confirmed_transactions(scan_report.confirmations, logger); self.handle_failed_transactions(scan_report.failures, logger); } - fn handle_confirmed_transactions(&mut self, confirmed_txs: Vec, logger: &Logger) { - if !confirmed_txs.is_empty() { - if let Err(e) = self.payable_dao.transactions_confirmed(&confirmed_txs) { - Self::transaction_confirmed_panic(&confirmed_txs, e) - } else { - self.add_to_the_total_of_paid_payable(&confirmed_txs, logger); - - let tx_confirmations = Self::compose_tx_confirmation_inputs(&confirmed_txs); - - if let Err(e) = self.sent_payable_dao.confirm_tx(&tx_confirmations) { - Self::update_tx_blocks_panic(&tx_confirmations, e) - } else { - Self::log_tx_success(logger, &tx_confirmations); - } - } - } + fn handle_confirmed_transactions( + &mut self, + confirmed_txs: DetectedConfirmations, + logger: &Logger, + ) { + todo!("finish me") + // if !confirmed_txs.is_empty() { + // if let Err(e) = self.payable_dao.transactions_confirmed(&confirmed_txs) { + // Self::transaction_confirmed_panic(&confirmed_txs, e) + // } else { + // self.add_to_the_total_of_paid_payable(&confirmed_txs, logger); + // + // let tx_confirmations = Self::compose_tx_confirmation_inputs(&confirmed_txs); + // + // if let Err(e) = self.sent_payable_dao.confirm_tx(&tx_confirmations) { + // Self::update_tx_blocks_panic(&tx_confirmations, e) + // } else { + // Self::log_tx_success(logger, &tx_confirmations); + // } + // } + // } } fn compose_tx_confirmation_inputs(confirmed_txs: &[SentTx]) -> HashMap { @@ -1146,40 +1181,44 @@ impl PendingPayableScanner { } fn handle_failed_transactions(&self, failures: DetectedFailures, logger: &Logger) { - fn joint_hashes(hashes: &HashSet) -> String { - comma_joined_stringifiable(&hashes.iter().sorted().collect_vec(), |hash| { - format!("{:?}", hash) - }) - } + self.handle_tx_failures(failures.tx_failures, logger); + self.handle_rpc_failures(failures.tx_receipt_rpc_failures, logger); + } - todo!("fix me... do both, add new records and also just change a status where needed") - // if !failures.is_empty() { - // let hashes = failures - // .iter() - // .map(|failed_tx| failed_tx.hash) - // .collect::>(); - // match self.failed_payable_dao.insert_new_records(&failures) { - // Ok(_) => { - // debug!(logger, "Failed payables {:?} recorded", hashes); - // } - // Err(e) => panic!( - // "Unable to record failed payables {} due to {:?}", - // joint_hashes(&hashes), - // e - // ), - // } - // match self.sent_payable_dao.delete_records(&hashes) { - // Ok(_) => debug!( - // logger, - // "Deleted sent payable records for failed txs {:?}", hashes - // ), - // Err(e) => panic!( - // "Unable to delete sent_payable entries for failed txs {} due to {:?}", - // joint_hashes(&hashes), - // e - // ), - // }; - // } + fn handle_tx_failures(&self, failures: Vec, logger: &Logger) { + let (new_failures, rechecks_completed): (Vec, Vec) = + failures.into_iter().fold( + (vec![], vec![]), + |(mut new_failures, mut rechecks_completed), failure| { + match failure { + PresortedTxFailure::NewEntry(failed_tx) => { + todo!() + } + PresortedTxFailure::RecheckCompleted(hash) => { + todo!() + } + } + (new_failures, rechecks_completed) + }, + ); + self.add_new_tx_failures(new_failures, logger); + self.conclude_newly_proven_old_tx_failures(rechecks_completed, logger); + } + + fn add_new_tx_failures(&self, new_failures: Vec, logger: &Logger) { + todo!() + } + + fn conclude_newly_proven_old_tx_failures( + &self, + rechecks_completed: Vec, + logger: &Logger, + ) { + todo!() + } + + fn handle_rpc_failures(&self, failures: Vec, logger: &Logger) { + todo!() } } @@ -1477,10 +1516,10 @@ mod tests { use crate::accountant::db_access_objects::utils::{from_unix_timestamp, to_unix_timestamp, TxHash}; use crate::accountant::scanners::payable_scanner_extension::msgs::{QualifiedPayablesBeforeGasPriceSelection, QualifiedPayablesMessage, UnpricedQualifiedPayables}; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{OperationOutcome, PayableScanResult, PendingPayableMissingInDb}; - use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_status_with_failure, PendingPayableReport, PendingPayableScanResult, DetectedFailures, FailuresRequiringDoubleCheck}; + use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_status_with_failure, ReceiptScanReport, PendingPayableScanResult, DetectedFailures, FailuresRequiringDoubleCheck, TxHashByTable, TxByTable, PresortedTxFailure, PendingPayableCache}; use crate::accountant::scanners::{Scanner, StartScanError, StartableScanner, PayableScanner, PendingPayableScanner, ReceivableScanner, ScannerCommon, Scanners, MTError}; - use crate::accountant::test_utils::{make_custom_payment_thresholds, make_payable_account, make_qualified_and_unqualified_payables, make_receivable_account, BannedDaoFactoryMock, BannedDaoMock, ConfigDaoFactoryMock, PayableDaoFactoryMock, PayableDaoMock, PayableScannerBuilder, PayableThresholdsGaugeMock, SentPayableDaoFactoryMock, SentPayableDaoMock, PendingPayableScannerBuilder, ReceivableDaoFactoryMock, ReceivableDaoMock, ReceivableScannerBuilder, make_sent_tx, FailedPayableDaoMock, FailedPayableDaoFactoryMock, make_failed_tx, make_transaction_block}; - use crate::accountant::{gwei_to_wei, PendingPayableId, ReceivedPayments, TxReceiptsMessage, RequestTransactionReceipts, ScanError, ScanForRetryPayables, SentPayables, DEFAULT_PENDING_TOO_LONG_SEC, PendingPayable}; + use crate::accountant::test_utils::{make_custom_payment_thresholds, make_payable_account, make_qualified_and_unqualified_payables, make_receivable_account, BannedDaoFactoryMock, BannedDaoMock, ConfigDaoFactoryMock, PayableDaoFactoryMock, PayableDaoMock, PayableScannerBuilder, PayableThresholdsGaugeMock, SentPayableDaoFactoryMock, SentPayableDaoMock, PendingPayableScannerBuilder, ReceivableDaoFactoryMock, ReceivableDaoMock, ReceivableScannerBuilder, make_sent_tx, FailedPayableDaoMock, FailedPayableDaoFactoryMock, make_failed_tx, make_transaction_block, bc_from_earning_wallet, AccountantBuilder}; + use crate::accountant::{gwei_to_wei, PendingPayableId, ReceivedPayments, TxReceiptsMessage, RequestTransactionReceipts, ScanError, ScanForRetryPayables, SentPayables, DEFAULT_PENDING_TOO_LONG_SEC, PendingPayable, ScanForPendingPayables}; use crate::blockchain::blockchain_bridge::{BlockMarker, RetrieveTransactions}; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; use crate::blockchain::blockchain_interface::data_structures::{ @@ -1519,10 +1558,12 @@ mod tests { use masq_lib::messages::ScanType; use masq_lib::ui_gateway::NodeToUiMessage; use crate::accountant::db_access_objects::failed_payable_dao::{FailedPayableDaoError, FailedTx, FailureReason, FailureRetrieveCondition, FailureStatus, ValidationStatus}; - use crate::accountant::db_access_objects::sent_payable_dao::{Detection, SentPayableDaoError, SentTx, TxConfirmation, TxStatus}; + use crate::accountant::db_access_objects::sent_payable_dao::{Detection, RetrieveCondition, SentPayableDaoError, SentTx, TxConfirmation, TxStatus}; use crate::accountant::scanners::test_utils::{assert_timestamps_from_str, parse_system_time_from_str, MarkScanner, NullScanner, ReplacementType, ScannerReplacement}; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxWithStatus, TransactionBlock, ReceiptCheck, BlockchainTxFailure, TxReceiptResult, TxReceiptError}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{RetrievedTxStatus, TransactionBlock, StatusReadFromReceiptCheck, BlockchainTxFailure, TxReceiptResult, TxReceiptError}; use crate::blockchain::errors::{AppRpcError, RemoteError}; + use crate::match_lazily_every_type_id; + use crate::test_utils::recorder::make_recorder; use crate::test_utils::unshared_test_utils::capture_numbers_with_separators_from_str; impl Scanners { @@ -1680,13 +1721,13 @@ mod tests { false ); assert_eq!( - pending_payable_scanner.previous_soft_failures, - FailuresRequiringDoubleCheck::new(vec![]) + pending_payable_scanner.yet_unproven_failures, + FailuresRequiringDoubleCheck::default() ); let retrieve_txs_params = retrieve_txs_params_arc.lock().unwrap(); assert_eq!( *retrieve_txs_params, - vec![Some(FailureRetrieveCondition::EveryRecheckRequiredRecord)] + vec![Some(FailureRetrieveCondition::RecheckRequiredRecords)] ); assert_eq!( receivable_scanner.common.payment_thresholds.as_ref(), @@ -1766,11 +1807,14 @@ mod tests { let retrieve_txs_params = retrieve_txs_params_arc.lock().unwrap(); assert_eq!( *retrieve_txs_params, - vec![Some(FailureRetrieveCondition::EveryRecheckRequiredRecord)] + vec![Some(FailureRetrieveCondition::RecheckRequiredRecords)] ); + let mut expected_failures = FailuresRequiringDoubleCheck::default(); + expected_failures + .load_cache(hashmap!(failed_tx_1.hash => failed_tx_1, failed_tx_2.hash => failed_tx_2)); assert_eq!( - pending_payable_scanner.previous_soft_failures, - FailuresRequiringDoubleCheck::new(vec![failed_tx_1, failed_tx_2]) + pending_payable_scanner.yet_unproven_failures, + expected_failures ); } @@ -2739,19 +2783,158 @@ mod tests { .exists_no_log_containing(&format!("DEBUG: {test_name}: Paying qualified debts")); } + //TODO inspire yourself to write the right tests for the pending payable scanner when it starts + // #[test] + // fn scan_for_pending_payables_finds_new_pending_payables() { + // init_test_logging(); + // let now = SystemTime::now(); + // let retrieve_pending_txs_params_arc = Arc::new(Mutex::new(vec![])); + // let retrieve_failed_txs_params_arc = Arc::new(Mutex::new(vec![])); + // let (blockchain_bridge, _, blockchain_bridge_recording_arc) = make_recorder(); + // let blockchain_bridge_addr = blockchain_bridge + // .system_stop_conditions(match_lazily_every_type_id!(RequestTransactionReceipts)) + // .start(); + // let sent_tx_1 = make_sent_tx(456); + // let tx_hash_1 = sent_tx_1.hash; + // let sent_tx_2 = make_sent_tx(789); + // let tx_hash_2 = sent_tx_2.hash; + // let sent_payable_dao = + // SentPayableDaoMock::default().retrieve_txs_params(&retrieve_pending_txs_params_arc).retrieve_txs_result(vec![sent_tx_1, sent_tx_2]); + // let failed_payable_dao = FailedPayableDaoMock::default().retrieve_txs_params(&retrieve_failed_txs_params_arc).retrieve_txs_result(vec![]); + // let config = bc_from_earning_wallet(make_wallet("mine")); + // let system = System::new("pending payable scan"); + // let mut subject = AccountantBuilder::default() + // .consuming_wallet(make_paying_wallet(b"consuming")) + // .bootstrapper_config(config) + // .build(); + // let pending_payable_scanner_real = PendingPayableScannerBuilder::new() + // .sent_payable_dao(sent_payable_dao) + // .build(); + // subject + // .scanners + // .replace_scanner(ScannerReplacement::PendingPayable(ReplacementType::Real( + // pending_payable_scanner_real, + // ))); + // subject.request_transaction_receipts_sub_opt = Some(blockchain_bridge_addr.recipient()); + // let account_addr = subject.start(); + // + // let _ = account_addr + // .try_send(ScanForPendingPayables { + // response_skeleton_opt: None, + // }) + // .unwrap(); + // + // system.run(); + // let retrieve_pending_txs_params = retrieve_pending_txs_params_arc.lock().unwrap(); + // assert_eq!( + // *retrieve_pending_txs_params, + // vec![Some(RetrieveCondition::IsPending)] + // ); + // let retrieve_failed_txs_params = retrieve_failed_txs_params_arc.lock().unwrap(); + // assert_eq!( + // *retrieve_failed_txs_params, + // vec![Some(FailureRetrieveCondition::RecheckRequiredRecords)] + // ); + // let blockchain_bridge_recording = blockchain_bridge_recording_arc.lock().unwrap(); + // let received_msg = blockchain_bridge_recording.get_record::(0); + // assert_eq!( + // received_msg, + // &RequestTransactionReceipts { + // tx_hashes: vec![TxHashByTable::SentPayable(tx_hash_1), TxHashByTable::SentPayable(tx_hash_2)], + // response_skeleton_opt: None, + // } + // ); + // assert_eq!(blockchain_bridge_recording.len(), 1); + // let log_handler = TestLogHandler::new(); + // log_handler.exists_log_containing("DEBUG: Accountant: Found 2 pending payables to process"); + // } + // + // #[test] + // fn scan_for_pending_payables_finds_new_pending_payable_and_unrechecked_failed_payable() { + // init_test_logging(); + // let now = SystemTime::now(); + // let retrieve_pending_txs_params_arc = Arc::new(Mutex::new(vec![])); + // let retrieve_failed_txs_params_arc = Arc::new(Mutex::new(vec![])); + // let (blockchain_bridge, _, blockchain_bridge_recording_arc) = make_recorder(); + // let blockchain_bridge_addr = blockchain_bridge + // .system_stop_conditions(match_lazily_every_type_id!(RequestTransactionReceipts)) + // .start(); + // let sent_tx = make_sent_tx(456); + // let tx_hash_1 = sent_tx.hash; + // let failed_tx_1 = make_failed_tx(789); + // let tx_hash_2 = failed_tx_1.hash; + // let failed_tx_2 = make_failed_tx(123); + // let tx_hash_3 = failed_tx_2.hash; + // let sent_payable_dao = + // SentPayableDaoMock::default().retrieve_txs_result(vec![sent_tx.clone()]); + // let failed_payable_dao = FailedPayableDaoMock::default().retrieve_txs_result(vec![failed_tx_1, failed_tx_2]); + // let config = bc_from_earning_wallet(make_wallet("mine")); + // let system = System::new("pending payable scan"); + // let mut subject = AccountantBuilder::default() + // .consuming_wallet(make_paying_wallet(b"consuming")) + // .bootstrapper_config(config) + // .build(); + // let pending_payable_scanner_real = PendingPayableScannerBuilder::new() + // .sent_payable_dao(sent_payable_dao) + // .build(); + // subject + // .scanners + // .replace_scanner(ScannerReplacement::PendingPayable(ReplacementType::Real( + // pending_payable_scanner_real, + // ))); + // subject.request_transaction_receipts_sub_opt = Some(blockchain_bridge_addr.recipient()); + // let account_addr = subject.start(); + // + // let _ = account_addr + // .try_send(ScanForPendingPayables { + // response_skeleton_opt: None, + // }) + // .unwrap(); + // + // system.run(); + // let retrieve_pending_txs_params = retrieve_pending_txs_params_arc.lock().unwrap(); + // assert_eq!( + // *retrieve_pending_txs_params, + // vec![Some(RetrieveCondition::IsPending)] + // ); + // let retrieve_failed_txs_params = retrieve_failed_txs_params_arc.lock().unwrap(); + // assert_eq!( + // *retrieve_failed_txs_params, + // vec![Some(FailureRetrieveCondition::RecheckRequiredRecords)] + // ); + // let blockchain_bridge_recording = blockchain_bridge_recording_arc.lock().unwrap(); + // let received_msg = blockchain_bridge_recording.get_record::(0); + // assert_eq!( + // received_msg, + // &RequestTransactionReceipts { + // tx_hashes: vec![TxHashByTable::SentPayable(tx_hash_1),TxHashByTable::FailedPayable(tx_hash_2), TxHashByTable::FailedPayable(tx_hash_3)], + // response_skeleton_opt: None, + // } + // ); + // assert_eq!(blockchain_bridge_recording.len(), 1); + // let log_handler = TestLogHandler::new(); + // log_handler.exists_log_containing("DEBUG: Accountant: Found 3 payables to query \ + // receipts for: 1 pending and 2 failed that require recheck"); + // } + + //TODO write two tests instead, according those two above #[test] fn pending_payable_scanner_can_initiate_a_scan() { init_test_logging(); let test_name = "pending_payable_scanner_can_initiate_a_scan"; let consuming_wallet = make_paying_wallet(b"consuming wallet"); let now = SystemTime::now(); - let sent_tx_1 = make_sent_tx(456); - let sent_tx_2 = make_sent_tx(789); - let sent_txs = vec![sent_tx_1, sent_tx_2]; - let sent_payable_dao = SentPayableDaoMock::new().retrieve_txs_result(sent_txs.clone()); + let sent_tx = make_sent_tx(456); + let sent_tx_hash = sent_tx.hash; + let failed_tx = make_failed_tx(789); + let failed_tx_hash = failed_tx.hash; + let sent_payable_dao = SentPayableDaoMock::new().retrieve_txs_result(vec![sent_tx.clone()]); + let failed_payable_dao = + FailedPayableDaoMock::new().retrieve_txs_result(vec![failed_tx.clone()]); let mut subject = make_dull_subject(); let pending_payable_scanner = PendingPayableScannerBuilder::new() .sent_payable_dao(sent_payable_dao) + .failed_payable_dao(failed_payable_dao) .build(); // Important subject.aware_of_unresolved_pending_payable = true; @@ -2767,20 +2950,23 @@ mod tests { true, ); - let no_of_pending_payables = sent_txs.len(); let is_scan_running = subject.pending_payable.scan_started_at().is_some(); assert_eq!(is_scan_running, true); assert_eq!( result, Ok(RequestTransactionReceipts { - sent_txs, + tx_hashes: vec![ + TxHashByTable::SentPayable(sent_tx_hash), + TxHashByTable::FailedPayable(failed_tx.hash) + ], response_skeleton_opt: None }) ); TestLogHandler::new().assert_logs_match_in_order(vec![ &format!("INFO: {test_name}: Scanning for pending payable"), + &format!("DEBUG: {test_name}: Found 1 pending payables to process"), &format!( - "DEBUG: {test_name}: Found {no_of_pending_payables} pending payables to process" + "DEBUG: {test_name}: Found 1 older, failed payables that will be double-checked" ), ]) } @@ -2973,18 +3159,22 @@ mod tests { sent_tx.hash = hash; let blockchain_failure = BlockchainTxFailure::Unrecognized; let logger = Logger::new(test_name); - let scan_report = PendingPayableReport::default(); + let scan_report = ReceiptScanReport::default(); - let result = - handle_status_with_failure(scan_report, sent_tx.clone(), blockchain_failure, &logger); + let result = handle_status_with_failure( + scan_report, + TxByTable::SentPayable(sent_tx.clone()), + blockchain_failure, + &logger, + ); let failure_reason = blockchain_failure.into(); let failed_tx = FailedTx::from((sent_tx, failure_reason)); assert_eq!( result, - PendingPayableReport { + ReceiptScanReport { failures: DetectedFailures { - tx_failures: vec![failed_tx], + tx_failures: vec![PresortedTxFailure::NewEntry(failed_tx)], tx_receipt_rpc_failures: vec![] }, confirmations: vec![] @@ -3000,7 +3190,7 @@ mod tests { fn handles_tx_receipt_if_the_tx_keeps_pending() { init_test_logging(); let test_name = "handles_tx_receipt_if_the_tx_keeps_pending"; - let subject = PendingPayableScannerBuilder::new().build(); + let mut subject = PendingPayableScannerBuilder::new().build(); let hash = make_tx_hash(0x913); let sent_tx_timestamp = to_unix_timestamp( SystemTime::now() @@ -3010,13 +3200,26 @@ mod tests { let mut sent_tx = make_sent_tx(456); sent_tx.hash = hash; sent_tx.timestamp = sent_tx_timestamp; + let failed_tx = make_failed_tx(789); let msg = TxReceiptsMessage { - results: vec![TxReceiptResult::Ok(TxWithStatus::new( - sent_tx.clone(), - ReceiptCheck::PendingTx, - ))], + results: vec![ + TxReceiptResult::Ok(RetrievedTxStatus::new( + TxHashByTable::SentPayable(sent_tx.hash), + StatusReadFromReceiptCheck::Pending, + )), + TxReceiptResult::Ok(RetrievedTxStatus::new( + TxHashByTable::FailedPayable(failed_tx.hash), + StatusReadFromReceiptCheck::Pending, + )), + ], response_skeleton_opt: None, }; + subject + .current_sent_payables + .load_cache(hashmap!(sent_tx.hash => sent_tx.clone())); + subject + .yet_unproven_failures + .load_cache(hashmap!(failed_tx.hash => failed_tx)); let before = SystemTime::now(); let result = subject.interpret_tx_receipts(msg, &Logger::new(test_name)); @@ -3025,9 +3228,9 @@ mod tests { let expected_failed_tx = FailedTx::from((sent_tx, FailureReason::PendingTooLong)); assert_eq!( result, - PendingPayableReport { + ReceiptScanReport { failures: DetectedFailures { - tx_failures: vec![expected_failed_tx], + tx_failures: vec![PresortedTxFailure::NewEntry(expected_failed_tx)], tx_receipt_rpc_failures: vec![] }, confirmations: vec![] @@ -3090,7 +3293,7 @@ mod tests { // // assert_eq!( // result, - // PendingPayableReport { + // ReceiptScanReport { // failures: DetectedFailures { // tx_failures: vec![], // tx_receipt_rpc_failures: vec![V] @@ -3128,7 +3331,10 @@ mod tests { let mut failed_tx_2 = make_failed_tx(456); failed_tx_2.hash = hash_2; let detected_failures = DetectedFailures { - tx_failures: vec![failed_tx_1, failed_tx_2], + tx_failures: vec![ + PresortedTxFailure::NewEntry(failed_tx_1), + PresortedTxFailure::NewEntry(failed_tx_2), + ], tx_receipt_rpc_failures: vec![], }; @@ -3213,7 +3419,10 @@ mod tests { let mut failed_tx_2 = make_failed_tx(456); failed_tx_2.hash = hash_2; let detected_failures = DetectedFailures { - tx_failures: vec![failed_tx_1, failed_tx_2], + tx_failures: vec![ + PresortedTxFailure::NewEntry(failed_tx_1), + PresortedTxFailure::NewEntry(failed_tx_2), + ], tx_receipt_rpc_failures: vec![], }; @@ -3240,7 +3449,10 @@ mod tests { let mut failed_tx_2 = make_failed_tx(456); failed_tx_2.hash = hash_2; let detected_failures = DetectedFailures { - tx_failures: vec![failed_tx_1, failed_tx_2], + tx_failures: vec![ + PresortedTxFailure::NewEntry(failed_tx_1), + PresortedTxFailure::NewEntry(failed_tx_2), + ], tx_receipt_rpc_failures: vec![], }; @@ -3484,7 +3696,7 @@ mod tests { let sent_payable_dao = SentPayableDaoMock::new() .confirm_tx_params(&confirm_tx_params_arc) .confirm_tx_result(Ok(())); - let mut pending_payable_scanner = PendingPayableScannerBuilder::new() + let pending_payable_scanner = PendingPayableScannerBuilder::new() .payable_dao(payable_dao) .sent_payable_dao(sent_payable_dao) .build(); @@ -3495,8 +3707,10 @@ mod tests { block_hash: make_block_hash(333), block_number: U64::from(1234), }; - let transaction_with_status_1 = - TxWithStatus::new(sent_tx_1.clone(), ReceiptCheck::TxSucceeded(tx_block_1)); + let transaction_with_status_1 = RetrievedTxStatus::new( + TxHashByTable::SentPayable(sent_tx_1.hash), + StatusReadFromReceiptCheck::Succeeded(tx_block_1), + ); let tx_hash_2 = make_tx_hash(1234); let mut sent_tx_2 = make_sent_tx(789); sent_tx_2.hash = tx_hash_2; @@ -3504,28 +3718,39 @@ mod tests { block_hash: make_block_hash(222), block_number: U64::from(2345), }; - let transaction_with_status_2 = - TxWithStatus::new(sent_tx_2.clone(), ReceiptCheck::TxSucceeded(tx_block_2)); + let transaction_with_status_2 = RetrievedTxStatus::new( + TxHashByTable::FailedPayable(sent_tx_2.hash), + StatusReadFromReceiptCheck::Succeeded(tx_block_2), + ); let sent_tx_3 = make_sent_tx(456); - let transaction_with_status_3 = - TxWithStatus::new(sent_tx_3.clone(), ReceiptCheck::PendingTx); - let sent_tx_4 = make_sent_tx(789); - let transaction_with_status_4 = TxWithStatus::new( - sent_tx_4.clone(), - ReceiptCheck::TxFailed(BlockchainTxFailure::Unrecognized), + let transaction_with_status_3 = RetrievedTxStatus::new( + TxHashByTable::SentPayable(sent_tx_3.hash), + StatusReadFromReceiptCheck::Pending, ); - let sent_tx_5 = make_sent_tx(123); - let tx_receipt_rpc_error = TxReceiptError::new( - sent_tx_5.clone(), + + let sent_tx_4 = make_sent_tx(123); + let tx_receipt_rpc_error_4 = TxReceiptError::new( + TxHashByTable::SentPayable(sent_tx_4.hash), AppRpcError::Remote(RemoteError::Unreachable), ); + let sent_tx_5 = make_sent_tx(888); + let tx_receipt_rpc_error_5 = TxReceiptError::new( + TxHashByTable::FailedPayable(sent_tx_5.hash), + AppRpcError::Remote(RemoteError::InvalidResponse("game over".to_string())), + ); + let sent_tx_6 = make_sent_tx(789); + let transaction_with_status_6 = RetrievedTxStatus::new( + TxHashByTable::SentPayable(sent_tx_6.hash), + StatusReadFromReceiptCheck::Failed(BlockchainTxFailure::Unrecognized), + ); let msg = TxReceiptsMessage { results: vec![ TxReceiptResult::Ok(transaction_with_status_1), TxReceiptResult::Ok(transaction_with_status_2), TxReceiptResult::Ok(transaction_with_status_3), - TxReceiptResult::Ok(transaction_with_status_4), - TxReceiptResult::Err(tx_receipt_rpc_error), + TxReceiptResult::Err(tx_receipt_rpc_error_4), + TxReceiptResult::Err(tx_receipt_rpc_error_5), + TxReceiptResult::Ok(transaction_with_status_6), ], response_skeleton_opt: None, }; diff --git a/node/src/accountant/scanners/scanners_utils.rs b/node/src/accountant/scanners/scanners_utils.rs index 171a5d518..c721bc512 100644 --- a/node/src/accountant/scanners/scanners_utils.rs +++ b/node/src/accountant/scanners/scanners_utils.rs @@ -321,24 +321,24 @@ pub mod payable_scanner_utils { } pub mod pending_payable_scanner_utils { + use std::collections::HashMap; use masq_lib::logger::Logger; use masq_lib::ui_gateway::NodeToUiMessage; use std::time::SystemTime; - use clap::App; use thousands::Separable; use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, FailureReason, FailureStatus, ValidationStatus}; use crate::accountant::db_access_objects::sent_payable_dao::{SentTx, TxStatus}; use crate::accountant::db_access_objects::utils::{from_unix_timestamp, TxHash}; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, BlockchainTxFailure, TxReceiptError, ReceiptCheck}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, BlockchainTxFailure, TxReceiptError, StatusReadFromReceiptCheck}; use crate::blockchain::errors::AppRpcError; #[derive(Debug, Default, PartialEq, Eq, Clone)] - pub struct PendingPayableReport { + pub struct ReceiptScanReport { pub failures: DetectedFailures, - pub confirmations: Vec, + pub confirmations: DetectedConfirmations, } - impl PendingPayableReport { + impl ReceiptScanReport { pub fn requires_payments_retry(&self) -> Option { match ( self.failures.requires_retry(), @@ -351,14 +351,18 @@ pub mod pending_payable_scanner_utils { } fn register_success(&mut self, sent_tx: SentTx) { - self.confirmations.push(sent_tx); + // self.confirmations.push(sent_tx); } - fn register_new_failure(&mut self, failed_tx: FailedTx) { + fn register_failure_reclaim(&mut self, original_sent_tx: SentTx) { + todo!() + } + + fn register_new_failure(&mut self, failed_tx: PresortedTxFailure) { self.failures.tx_failures.push(failed_tx); } - fn check_off_earlier_failure(&mut self, tx_hash: TxHash) { + fn register_finalization_of_unproven_failure(&mut self, tx_hash: TxHash) { todo!() } @@ -368,30 +372,42 @@ pub mod pending_payable_scanner_utils { } } + #[derive(Debug, Default, PartialEq, Eq, Clone)] pub struct DetectedConfirmations { pub normal_confirmations: Vec, pub reclaims: Vec, } + impl DetectedConfirmations { + fn is_empty(&self) -> bool { + todo!("write a separate test") //self.normal_confirmations.is_empty() && self.reclaims.is_empty() + } + } + #[derive(Debug, Default, PartialEq, Eq, Clone)] pub struct DetectedFailures { - pub tx_failures: Vec, + pub tx_failures: Vec, pub tx_receipt_rpc_failures: Vec, } impl DetectedFailures { fn requires_retry(&self) -> Option { - todo!("add the logic to combine the two vecs"); - // if self.tx_failures.is_empty() { - // None - // } else if self.tx_failures.iter().any(|failed_tx| failed_tx.status == FailureStatus::RetryRequired) { - // Some(Retry::RetryPayments) - // } else { - // Some(Retry::RetryReceiptCheck) - // } + if self.tx_failures.is_empty() && self.tx_receipt_rpc_failures.is_empty() { + None + } else if !self.tx_failures.is_empty() { + Some(Retry::RetryPayments) + } else { + Some(Retry::RetryOnlyTxStatusCheck) + } } } + #[derive(Debug, PartialEq, Eq, Clone)] + pub enum PresortedTxFailure { + NewEntry(FailedTx), + RecheckCompleted(TxHash), + } + #[derive(Debug, PartialEq, Eq, Clone)] pub enum ValidationStatusUpdate { SentPayableRecord(FailedValidation), @@ -404,14 +420,81 @@ pub mod pending_payable_scanner_utils { pub failure: AppRpcError, } - #[derive(Debug, PartialEq, Eq)] + pub trait PendingPayableCache { + fn load_cache(&mut self, records: Collection) + where + Collection: IntoIterator; + fn get_record_by_hash(&mut self, hashes: TxHash) -> Option; + fn ensure_empty_cache(&mut self, logger: &Logger); + } + + #[derive(Debug, PartialEq, Eq, Default)] + pub struct CurrentPendingPayables { + pub(super) sent_payables: HashMap, + } + + impl PendingPayableCache for CurrentPendingPayables { + fn load_cache(&mut self, records: Collection) + where + Collection: IntoIterator, + { + self.sent_payables.extend(records); + } + + fn get_record_by_hash(&mut self, hashes: TxHash) -> Option { + self.sent_payables.remove(&hashes) + } + + fn ensure_empty_cache(&mut self, logger: &Logger) { + if !self.sent_payables.is_empty() { + debug!( + logger, + "Cache misuse - some pending payables left unprocessed: {:?}. Dumping.", + self.sent_payables + ); + } + self.sent_payables.clear() + } + } + + impl CurrentPendingPayables { + pub fn new() -> Self { + Self::default() + } + } + + #[derive(Debug, PartialEq, Eq, Default)] pub struct FailuresRequiringDoubleCheck { - pub failures: Vec, + pub(super) failures: HashMap, + } + + impl PendingPayableCache for FailuresRequiringDoubleCheck { + fn load_cache(&mut self, records: Collection) + where + Collection: IntoIterator, + { + self.failures.extend(records); + } + + fn get_record_by_hash(&mut self, hashes: TxHash) -> Option { + self.failures.remove(&hashes) + } + + fn ensure_empty_cache(&mut self, logger: &Logger) { + if !self.failures.is_empty() { + debug!( + logger, + "Cache misuse - some tx failures left unprocessed: {:?}. Dumping.", + self.failures + ); + } + self.failures.clear() + } } impl FailuresRequiringDoubleCheck { - pub fn new(failures: Vec) -> Self { - todo!() + pub fn new() -> Self { + Self::default() } pub fn hashes(&self) -> &[TxHash] { @@ -428,7 +511,22 @@ pub mod pending_payable_scanner_utils { #[derive(Debug, PartialEq, Eq)] pub enum Retry { RetryPayments, - RetryReceiptCheck, + RetryOnlyTxStatusCheck, + } + + pub struct RecordsCoupledWithReceiptCheckResults { + pub prepared_couples: Vec<(TxByTable, StatusReadFromReceiptCheck)>, + } + + pub enum TxByTable { + SentPayable(SentTx), + FailedPayable(FailedTx), + } + + #[derive(Debug, PartialEq, Eq, Clone, Copy)] + pub enum TxHashByTable { + SentPayable(TxHash), + FailedPayable(TxHash), } pub fn elapsed_in_ms(timestamp: SystemTime) -> u128 { @@ -439,90 +537,109 @@ pub mod pending_payable_scanner_utils { } pub fn handle_still_pending_tx( - mut scan_report: PendingPayableReport, - sent_tx: SentTx, + mut scan_report: ReceiptScanReport, + tx: TxByTable, logger: &Logger, - ) -> PendingPayableReport { - info!( - logger, - "Tx {:?} not confirmed within {} ms. Will resubmit with higher gas price", - sent_tx.hash, - elapsed_in_ms(from_unix_timestamp(sent_tx.timestamp)).separate_with_commas() - ); - - let failed_tx = FailedTx::from((sent_tx, FailureReason::PendingTooLong)); - scan_report.register_new_failure(failed_tx); + ) -> ReceiptScanReport { + match tx { + TxByTable::SentPayable(sent_tx) => { + info!( + logger, + "Tx {:?} not confirmed within {} ms. Will resubmit with higher gas price", + sent_tx.hash, + elapsed_in_ms(from_unix_timestamp(sent_tx.timestamp)).separate_with_commas() + ); + let failed_tx = FailedTx::from((sent_tx, FailureReason::PendingTooLong)); + scan_report.register_new_failure(PresortedTxFailure::NewEntry(failed_tx)); + } + TxByTable::FailedPayable(failed_tx) => todo!("What should I do here??"), // { + // todo!(); + // scan_report.register_finalization_of_unproven_failure(failed_tx.hash); + } scan_report } pub fn handle_successful_tx( - mut scan_report: PendingPayableReport, - unchecked_soft_failures: &[TxHash], - sent_tx: SentTx, + mut scan_report: ReceiptScanReport, + unproven_failures: &[TxHash], + tx: TxByTable, tx_block: TransactionBlock, logger: &Logger, - ) -> PendingPayableReport { - info!( - logger, - "Detected tx {:?} added to block {}.", sent_tx.hash, tx_block.block_number, - ); + ) -> ReceiptScanReport { + match tx { + TxByTable::SentPayable(sent_tx) => { + info!( + logger, + "Detected tx {:?} added to block {}.", sent_tx.hash, tx_block.block_number, + ); - let detection = if !unchecked_soft_failures.contains(&sent_tx.hash) { - todo!() - } else { - todo!() - }; + let detection = if !unproven_failures.contains(&sent_tx.hash) { + todo!() + } else { + todo!() + }; - let completed_sent_tx = SentTx { - status: TxStatus::Confirmed { - block_hash: format!("{:?}", tx_block.block_hash), - block_number: tx_block.block_number.as_u64(), - detection, - }, - ..sent_tx - }; - scan_report.register_success(completed_sent_tx); + let completed_sent_tx = SentTx { + status: TxStatus::Confirmed { + block_hash: format!("{:?}", tx_block.block_hash), + block_number: tx_block.block_number.as_u64(), + detection, + }, + ..sent_tx + }; + scan_report.register_success(completed_sent_tx); + } + TxByTable::FailedPayable(failed_tx) => { + todo!() + } + } scan_report } //TODO: failures handling is going to need enhancement suggested by GH-693 pub fn handle_status_with_failure( - mut scan_report: PendingPayableReport, - sent_tx: SentTx, + mut scan_report: ReceiptScanReport, + tx: TxByTable, blockchain_failure: BlockchainTxFailure, logger: &Logger, - ) -> PendingPayableReport { - let failure_reason = FailureReason::from(blockchain_failure); - let failed_tx = FailedTx::from((sent_tx, failure_reason)); + ) -> ReceiptScanReport { + match tx { + TxByTable::SentPayable(sent_tx) => { + let failure_reason = FailureReason::from(blockchain_failure); + let failed_tx = FailedTx::from((sent_tx, failure_reason)); - warning!( - logger, - "Tx {:?} failed on blockchain due to: {}", - failed_tx.hash, - blockchain_failure - ); + warning!( + logger, + "Tx {:?} failed on blockchain due to: {}", + failed_tx.hash, + blockchain_failure + ); - scan_report.register_new_failure(failed_tx); + scan_report.register_new_failure(PresortedTxFailure::NewEntry(failed_tx)); + } + TxByTable::FailedPayable(failed_tx) => { + todo!() + } + } scan_report } pub fn handle_rpc_failure( - mut scan_report: PendingPayableReport, + mut scan_report: ReceiptScanReport, recheck_required_txs: &[TxHash], rpc_error: TxReceiptError, logger: &Logger, - ) -> PendingPayableReport { + ) -> ReceiptScanReport { warning!( logger, "Failed to retrieve tx receipt for {:?}: {:?}. Will retry receipt retrieval next cycle", - rpc_error.tx.hash, + rpc_error.tx_hash, rpc_error.err ); // TODO just to make sure we didn't ball something up badly, could be deduced also without - let validation_status_update = if !recheck_required_txs.contains(&rpc_error.tx.hash) { - todo!() - } else { - todo!() + let validation_status_update = match rpc_error.tx_hash { + TxHashByTable::SentPayable(hash) => todo!(), + TxHashByTable::FailedPayable(hash) => todo!(), }; scan_report.register_rpc_failure(validation_status_update); scan_report @@ -590,9 +707,10 @@ mod tests { use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; use std::time::{Duration, SystemTime}; use itertools::Itertools; + use regex::Regex; use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, FailureReason, FailureStatus, ValidationStatus}; use crate::accountant::db_access_objects::sent_payable_dao::{SentTx, TxStatus}; - use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{DetectedFailures, FailedValidation, PendingPayableReport, Retry, ValidationStatusUpdate}; + use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{CurrentPendingPayables, DetectedFailures, FailedValidation, FailuresRequiringDoubleCheck, PendingPayableCache, PresortedTxFailure, ReceiptScanReport, Retry, ValidationStatusUpdate}; use crate::accountant::test_utils::{make_failed_tx, make_sent_tx}; use crate::assert_on_testing_enum_with_all_its_variants; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::BlockchainTxFailure; @@ -1050,98 +1168,149 @@ mod tests { #[test] fn requires_payments_retry() { - let cases = vec![ - PendingPayableReport { - failures: DetectedFailures { - tx_failures: vec![make_failed_tx(456)], - tx_receipt_rpc_failures: vec![], - }, - confirmations: vec![], + let make_case_1 = |preprocessed_failures| ReceiptScanReport { + failures: DetectedFailures { + tx_failures: preprocessed_failures, + tx_receipt_rpc_failures: vec![], }, - PendingPayableReport { - failures: DetectedFailures { - tx_failures: vec![make_failed_tx(789)], - tx_receipt_rpc_failures: vec![ValidationStatusUpdate::SentPayableRecord( - FailedValidation { - tx_hash: make_tx_hash(2222), - failure: AppRpcError::Local(LocalError::Internal), - }, - )], - }, - confirmations: vec![], + confirmations: vec![], + }; + let make_case_2 = |preprocessed_failures| ReceiptScanReport { + failures: DetectedFailures { + tx_failures: preprocessed_failures, + tx_receipt_rpc_failures: vec![ValidationStatusUpdate::SentPayableRecord( + FailedValidation { + tx_hash: make_tx_hash(2222), + failure: AppRpcError::Local(LocalError::Internal), + }, + )], }, - PendingPayableReport { - failures: DetectedFailures { - tx_failures: vec![make_failed_tx(123), make_failed_tx(789)], - tx_receipt_rpc_failures: vec![], - }, - confirmations: vec![make_sent_tx(777)], + confirmations: vec![], + }; + let make_case_3 = |preprocessed_failures| ReceiptScanReport { + failures: DetectedFailures { + tx_failures: preprocessed_failures, + tx_receipt_rpc_failures: vec![], }, - PendingPayableReport { - failures: DetectedFailures { - tx_failures: vec![make_failed_tx(123)], - tx_receipt_rpc_failures: vec![ValidationStatusUpdate::FailedPayableRecord( - FailedValidation { - tx_hash: make_tx_hash(12121), - failure: AppRpcError::Remote(RemoteError::InvalidResponse( - "blah".to_string(), - )), - }, - )], - }, - confirmations: vec![make_sent_tx(777)], + confirmations: vec![make_sent_tx(777)], + }; + let make_case_4 = |preprocessed_failures| ReceiptScanReport { + failures: DetectedFailures { + tx_failures: preprocessed_failures, + tx_receipt_rpc_failures: vec![ValidationStatusUpdate::FailedPayableRecord( + FailedValidation { + tx_hash: make_tx_hash(12121), + failure: AppRpcError::Remote(RemoteError::InvalidResponse( + "blah".to_string(), + )), + }, + )], }, + confirmations: vec![make_sent_tx(777)], + }; + let case_makers: Vec<&dyn Fn(Vec) -> ReceiptScanReport> = + vec![&make_case_1, &make_case_2, &make_case_3, &make_case_4]; + let tx_failure_feedings = vec![ + vec![PresortedTxFailure::NewEntry(make_failed_tx(456))], + vec![PresortedTxFailure::RecheckCompleted(make_tx_hash(123))], + vec![ + PresortedTxFailure::NewEntry(make_failed_tx(123)), + PresortedTxFailure::NewEntry(make_failed_tx(456)), + ], + vec![ + PresortedTxFailure::RecheckCompleted(make_tx_hash(654)), + PresortedTxFailure::RecheckCompleted(make_tx_hash(321)), + ], + vec![ + PresortedTxFailure::NewEntry(make_failed_tx(456)), + PresortedTxFailure::RecheckCompleted(make_tx_hash(654)), + ], ]; - - cases.into_iter().enumerate().for_each(|(idx, case)| { - let result = case.requires_payments_retry(); - assert_eq!( - result, - Some(Retry::RetryPayments), - "We expected Some(Retry::RetryPayments), but got {:?} for case with idx {}", - result, - idx - ) - }) + let generated_cases = case_makers.into_iter().fold(vec![], |mut acc, case_maker| { + tx_failure_feedings + .clone() + .into_iter() + .for_each(|failure_feeding| { + let report = case_maker(failure_feeding.clone()); + acc.push(report); + }); + acc + }); + + generated_cases + .into_iter() + .enumerate() + .for_each(|(idx, case)| { + let result = case.requires_payments_retry(); + assert_eq!( + result, + Some(Retry::RetryPayments), + "We expected Some(Retry::RetryPayments), but got {:?} for case with idx {}", + result, + idx + ) + }) } #[test] fn requires_only_receipt_retrieval_retry() { - let cases = vec![ - PendingPayableReport { - failures: DetectedFailures { - tx_failures: vec![], - tx_receipt_rpc_failures: vec![ValidationStatusUpdate::SentPayableRecord( - FailedValidation { - tx_hash: make_tx_hash(2222), - failure: AppRpcError::Local(LocalError::Internal), - }, - )], - }, - confirmations: vec![], + let make_case_1 = |preprocessed_rpc_failures| ReceiptScanReport { + failures: DetectedFailures { + tx_failures: vec![], + tx_receipt_rpc_failures: preprocessed_rpc_failures, }, - PendingPayableReport { - failures: DetectedFailures { - tx_failures: vec![], - tx_receipt_rpc_failures: vec![ValidationStatusUpdate::FailedPayableRecord( - FailedValidation { - tx_hash: make_tx_hash(12121), - failure: AppRpcError::Remote(RemoteError::InvalidResponse( - "blah".to_string(), - )), - }, - )], - }, - confirmations: vec![make_sent_tx(777)], + confirmations: vec![], + }; + let make_case_2 = |preprocessed_rpc_failures| ReceiptScanReport { + failures: DetectedFailures { + tx_failures: vec![], + tx_receipt_rpc_failures: preprocessed_rpc_failures, }, + confirmations: vec![make_sent_tx(777)], + }; + let case_makers: Vec<&dyn Fn(Vec) -> ReceiptScanReport> = + vec![&make_case_1, &make_case_2]; + let rpc_failure_feedings = vec![ + vec![ValidationStatusUpdate::SentPayableRecord( + FailedValidation { + tx_hash: make_tx_hash(2222), + failure: AppRpcError::Local(LocalError::Internal), + }, + )], + vec![ValidationStatusUpdate::FailedPayableRecord( + FailedValidation { + tx_hash: make_tx_hash(1234), + failure: AppRpcError::Remote(RemoteError::Unreachable), + }, + )], + vec![ + ValidationStatusUpdate::SentPayableRecord(FailedValidation { + tx_hash: make_tx_hash(2222), + failure: AppRpcError::Local(LocalError::Internal), + }), + ValidationStatusUpdate::FailedPayableRecord(FailedValidation { + tx_hash: make_tx_hash(1234), + failure: AppRpcError::Remote(RemoteError::Unreachable), + }), + ], ]; - - cases.into_iter().enumerate().for_each(|(idx, case)| { + let generated_cases = case_makers.into_iter().fold(vec![], |mut acc, case_maker| { + rpc_failure_feedings + .clone() + .into_iter() + .for_each(|failure_feeding| { + let report = case_maker(failure_feeding.clone()); + acc.push(report); + }); + acc + }); + + generated_cases.into_iter().enumerate().for_each(|(idx, case)| { let result = case.requires_payments_retry(); assert_eq!( result, - Some(Retry::RetryReceiptCheck), - "We expected Some(Retry::RetryReceiptCheck), but got {:?} for case with idx {}", + Some(Retry::RetryOnlyTxStatusCheck), + "We expected Some(Retry::RetryOnlyTxStatusCheck), but got {:?} for case with idx {}", result, idx ) @@ -1150,7 +1319,7 @@ mod tests { #[test] fn requires_payments_retry_says_no() { - let report = PendingPayableReport { + let report = ReceiptScanReport { failures: DetectedFailures { tx_failures: vec![], tx_receipt_rpc_failures: vec![], @@ -1169,7 +1338,7 @@ mod tests { no results" )] fn requires_payments_retry_with_no_results_in_whole_summary() { - let report = PendingPayableReport { + let report = ReceiptScanReport { failures: DetectedFailures { tx_failures: vec![], tx_receipt_rpc_failures: vec![], @@ -1179,4 +1348,246 @@ mod tests { let _ = report.requires_payments_retry(); } + + #[test] + fn pending_payables_cache_insert_and_get_methods_single_record() { + let mut subject = CurrentPendingPayables::new(); + let sent_tx = make_sent_tx(123); + let tx_hash = sent_tx.hash; + let records = vec![(tx_hash, sent_tx.clone())]; + + let state_before = subject.sent_payables.clone(); + subject.load_cache(records); + let first_attempt = subject.get_record_by_hash(tx_hash); + let second_attempt = subject.get_record_by_hash(tx_hash); + + assert_eq!(state_before, hashmap!()); + assert_eq!(first_attempt, Some(sent_tx)); + assert_eq!(second_attempt, None); + assert!( + subject.sent_payables.is_empty(), + "Should be empty but was {:?}", + subject.sent_payables + ); + } + + #[test] + fn pending_payables_cache_insert_and_get_methods_multiple_records() { + let mut subject = CurrentPendingPayables::new(); + let sent_tx_1 = make_sent_tx(123); + let tx_hash_1 = sent_tx_1.hash; + let sent_tx_2 = make_sent_tx(456); + let tx_hash_2 = sent_tx_2.hash; + let sent_tx_3 = make_sent_tx(789); + let tx_hash_3 = sent_tx_3.hash; + let sent_tx_4 = make_sent_tx(101); + let tx_hash_4 = sent_tx_4.hash; + let nonexistent_tx_hash = make_tx_hash(234); + let records = vec![ + (tx_hash_1, sent_tx_1.clone()), + (tx_hash_2, sent_tx_2.clone()), + (tx_hash_3, sent_tx_3.clone()), + (tx_hash_4, sent_tx_4.clone()), + ]; + + let first_query = subject.get_record_by_hash(tx_hash_1); + subject.load_cache(records); + let second_query = subject.get_record_by_hash(nonexistent_tx_hash); + let third_query = subject.get_record_by_hash(tx_hash_2); + let fourth_query = subject.get_record_by_hash(tx_hash_1); + let fifth_query = subject.get_record_by_hash(tx_hash_4); + let sixth_query = subject.get_record_by_hash(tx_hash_1); + let seventh_query = subject.get_record_by_hash(tx_hash_1); + let eighth_query = subject.get_record_by_hash(tx_hash_3); + + assert_eq!(first_query, None); + assert_eq!(second_query, None); + assert_eq!(third_query, Some(sent_tx_2)); + assert_eq!(fourth_query, Some(sent_tx_1)); + assert_eq!(fifth_query, Some(sent_tx_4)); + assert_eq!(sixth_query, None); + assert_eq!(seventh_query, None); + assert_eq!(eighth_query, Some(sent_tx_3)); + assert!( + subject.sent_payables.is_empty(), + "Expected empty cache, but got {:?}", + subject.sent_payables + ); + } + + #[test] + fn pending_payables_cache_ensure_empty_happy_path() { + init_test_logging(); + let test_name = "pending_payables_cache_ensure_empty_happy_path"; + let mut subject = CurrentPendingPayables::new(); + let sent_tx = make_sent_tx(567); + let tx_hash = sent_tx.hash; + let records = vec![(tx_hash, sent_tx.clone())]; + let logger = Logger::new(test_name); + + subject.load_cache(records); + let _ = subject.get_record_by_hash(tx_hash); + subject.ensure_empty_cache(&logger); + + assert!( + subject.sent_payables.is_empty(), + "Should be empty but was {:?}", + subject.sent_payables + ); + TestLogHandler::default().exists_no_log_containing(&format!( + "DEBUG: {test_name}: \ + Cache misuse - some pending payables left unprocessed:" + )); + } + + #[test] + fn pending_payables_cache_ensure_empty_sad_path() { + init_test_logging(); + let test_name = "pending_payables_cache_ensure_empty_sad_path"; + let mut subject = CurrentPendingPayables::new(); + let sent_tx = make_sent_tx(567); + let tx_hash = sent_tx.hash; + let tx_timestamp = sent_tx.timestamp; + let records = vec![(tx_hash, sent_tx.clone())]; + let logger = Logger::new(test_name); + + subject.load_cache(records); + subject.ensure_empty_cache(&logger); + + assert!( + subject.sent_payables.is_empty(), + "Should be empty but was {:?}", + subject.sent_payables + ); + TestLogHandler::default().exists_log_containing(&format!( + "DEBUG: {test_name}: \ + Cache misuse - some pending payables left unprocessed: \ + {{0x0000000000000000000000000000000000000000000000000000000000000237: SentTx {{ hash: \ + 0x0000000000000000000000000000000000000000000000000000000000000237, receiver_address: \ + 0x000000000000000000000077616c6c6574353637, amount_minor: 321489000000000, timestamp: \ + {tx_timestamp}, gas_price_minor: 567000000000, nonce: 567, status: Pending(Waiting) }}}}. \ + Dumping." + )); + } + + #[test] + fn failure_cache_insert_and_get_methods_single_record() { + let mut subject = FailuresRequiringDoubleCheck::new(); + let failed_tx = make_failed_tx(567); + let tx_hash = failed_tx.hash; + let records = vec![(tx_hash, failed_tx.clone())]; + + let state_before = subject.failures.clone(); + subject.load_cache(records); + let first_attempt = subject.get_record_by_hash(tx_hash); + let second_attempt = subject.get_record_by_hash(tx_hash); + + assert_eq!(state_before, hashmap!()); + assert_eq!(first_attempt, Some(failed_tx)); + assert_eq!(second_attempt, None); + assert!( + subject.failures.is_empty(), + "Should be empty but was {:?}", + subject.failures + ); + } + + #[test] + fn failure_cache_insert_and_get_methods_multiple_records() { + let mut subject = FailuresRequiringDoubleCheck::new(); + let failed_tx_1 = make_failed_tx(123); + let tx_hash_1 = failed_tx_1.hash; + let failed_tx_2 = make_failed_tx(456); + let tx_hash_2 = failed_tx_2.hash; + let failed_tx_3 = make_failed_tx(789); + let tx_hash_3 = failed_tx_3.hash; + let failed_tx_4 = make_failed_tx(101); + let tx_hash_4 = failed_tx_4.hash; + let nonexistent_tx_hash = make_tx_hash(234); + let records = vec![ + (tx_hash_1, failed_tx_1.clone()), + (tx_hash_2, failed_tx_2.clone()), + (tx_hash_3, failed_tx_3.clone()), + (tx_hash_4, failed_tx_4.clone()), + ]; + + let first_query = subject.get_record_by_hash(tx_hash_1); + subject.load_cache(records); + let second_query = subject.get_record_by_hash(nonexistent_tx_hash); + let third_query = subject.get_record_by_hash(tx_hash_2); + let fourth_query = subject.get_record_by_hash(tx_hash_1); + let fifth_query = subject.get_record_by_hash(tx_hash_4); + let sixth_query = subject.get_record_by_hash(tx_hash_1); + let seventh_query = subject.get_record_by_hash(tx_hash_1); + let eighth_query = subject.get_record_by_hash(tx_hash_3); + + assert_eq!(first_query, None); + assert_eq!(second_query, None); + assert_eq!(third_query, Some(failed_tx_2)); + assert_eq!(fourth_query, Some(failed_tx_1)); + assert_eq!(fifth_query, Some(failed_tx_4)); + assert_eq!(sixth_query, None); + assert_eq!(seventh_query, None); + assert_eq!(eighth_query, Some(failed_tx_3)); + assert!( + subject.failures.is_empty(), + "Expected empty cache, but got {:?}", + subject.failures + ); + } + + #[test] + fn failure_cache_ensure_empty_happy_path() { + init_test_logging(); + let test_name = "failure_cache_ensure_empty_happy_path"; + let mut subject = FailuresRequiringDoubleCheck::new(); + let failed_tx = make_failed_tx(567); + let tx_hash = failed_tx.hash; + let records = vec![(tx_hash, failed_tx.clone())]; + let logger = Logger::new(test_name); + + subject.load_cache(records); + let _ = subject.get_record_by_hash(tx_hash); + subject.ensure_empty_cache(&logger); + + assert!( + subject.failures.is_empty(), + "Should be empty but was {:?}", + subject.failures + ); + TestLogHandler::default().exists_no_log_containing(&format!( + "DEBUG: {test_name}: \ + Cache misuse - some tx failures left unprocessed:" + )); + } + + #[test] + fn failure_cache_ensure_empty_sad_path() { + init_test_logging(); + let test_name = "failure_cache_ensure_empty_sad_path"; + let mut subject = FailuresRequiringDoubleCheck::new(); + let failed_tx = make_failed_tx(567); + let tx_hash = failed_tx.hash; + let tx_timestamp = failed_tx.timestamp; + let records = vec![(tx_hash, failed_tx.clone())]; + let logger = Logger::new(test_name); + + subject.load_cache(records); + subject.ensure_empty_cache(&logger); + + assert!( + subject.failures.is_empty(), + "Should be empty but was {:?}", + subject.failures + ); + TestLogHandler::default().exists_log_containing(&format!( + "DEBUG: {test_name}: \ + Cache misuse - some tx failures left unprocessed: \ + {{0x0000000000000000000000000000000000000000000000000000000000000237: FailedTx {{ hash: \ + 0x0000000000000000000000000000000000000000000000000000000000000237, receiver_address: \ + 0x000000000000000000000077616c6c6574353637, amount_minor: 321489000000000, timestamp: \ + {tx_timestamp}, gas_price_minor: 567000000000, nonce: 567, reason: PendingTooLong, status: \ + RetryRequired }}}}. Dumping." + )); + } } diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs index caed8790e..db1a2a5f7 100644 --- a/node/src/accountant/test_utils.rs +++ b/node/src/accountant/test_utils.rs @@ -41,11 +41,11 @@ use std::fmt::Debug; use std::path::Path; use std::rc::Rc; use std::sync::{Arc, Mutex}; -use std::time::SystemTime; +use std::time::{SystemTime, UNIX_EPOCH}; use ethereum_types::U64; use web3::types::{Address}; use crate::accountant::db_access_objects::sent_payable_dao::{RetrieveCondition, SentPayableDaoError, SentTx}; -use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::FailuresRequiringDoubleCheck; +use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{FailuresRequiringDoubleCheck, PendingPayableCache}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock}; pub fn make_receivable_account(n: u64, expected_delinquent: bool) -> ReceivableAccount { @@ -1053,7 +1053,10 @@ impl SentPayableDaoMock { self } - pub fn retrieve_txs(mut self, params: &Arc>>>) -> Self { + pub fn retrieve_txs_params( + mut self, + params: &Arc>>>, + ) -> Self { self.retrieve_txs_params = params.clone(); self } @@ -1363,7 +1366,7 @@ pub struct PendingPayableScannerBuilder { failed_payable_dao: FailedPayableDaoMock, payment_thresholds: PaymentThresholds, financial_statistics: FinancialStatistics, - previous_soft_failures: Vec, + yet_unproven_failures: HashMap, } impl PendingPayableScannerBuilder { @@ -1374,7 +1377,7 @@ impl PendingPayableScannerBuilder { failed_payable_dao: FailedPayableDaoMock::new(), payment_thresholds: PaymentThresholds::default(), financial_statistics: FinancialStatistics::default(), - previous_soft_failures: vec![], + yet_unproven_failures: hashmap!(), } } @@ -1393,8 +1396,8 @@ impl PendingPayableScannerBuilder { self } - pub fn previous_soft_failures(mut self, failures: Vec) -> Self { - self.previous_soft_failures = failures; + pub fn yet_unproven_failures(mut self, failures: HashMap) -> Self { + self.yet_unproven_failures = failures; self } @@ -1411,8 +1414,9 @@ impl PendingPayableScannerBuilder { Rc::new(RefCell::new(self.financial_statistics)), ); - scanner.previous_soft_failures = - FailuresRequiringDoubleCheck::new(self.previous_soft_failures); + scanner + .yet_unproven_failures + .load_cache(self.yet_unproven_failures); scanner } diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index 1c339e19a..4ed79f29d 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -46,7 +46,7 @@ use masq_lib::constants::DEFAULT_GAS_PRICE_MARGIN; use masq_lib::messages::ScanType; use crate::accountant::db_access_objects::payable_dao::PayableAccount; use crate::accountant::db_access_objects::sent_payable_dao::SentTx; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxReceiptResult, ReceiptCheck}; +use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxReceiptResult, StatusReadFromReceiptCheck}; use crate::blockchain::blockchain_agent::BlockchainAgent; pub const CRASH_KEY: &str = "BLOCKCHAINBRIDGE"; @@ -396,9 +396,11 @@ impl BlockchainBridge { (0, 0, 0), |(success, fail, pending), transaction_receipt| match transaction_receipt { TxReceiptResult::Ok(tx_receipt) => match tx_receipt.status { - ReceiptCheck::TxFailed(_) => (success, fail + 1, pending), - ReceiptCheck::TxSucceeded(_) => (success + 1, fail, pending), - ReceiptCheck::PendingTx => (success, fail, pending + 1), + StatusReadFromReceiptCheck::Failed(_) => (success, fail + 1, pending), + StatusReadFromReceiptCheck::Succeeded(_) => { + (success + 1, fail, pending) + } + StatusReadFromReceiptCheck::Pending => (success, fail, pending + 1), }, TxReceiptResult::Err(_) => (success, fail, pending + 1), }, @@ -422,7 +424,7 @@ impl BlockchainBridge { .expect("Accountant is unbound"); Box::new( self.blockchain_interface - .process_transaction_receipts(msg.sent_txs) + .process_transaction_receipts(msg.tx_hashes) .map_err(move |e| e.to_string()) .and_then(move |tx_receipt_results| { Self::log_status_of_tx_receipts(&logger, &tx_receipt_results); @@ -581,8 +583,10 @@ mod tests { use crate::accountant::db_access_objects::failed_payable_dao::ValidationStatus; use crate::accountant::db_access_objects::sent_payable_dao::{SentTx, TxConfirmation, TxStatus}; use crate::accountant::PendingPayable; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxWithStatus, TransactionBlock, TxReceiptError}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{RetrievedTxStatus, TransactionBlock, TxReceiptError}; use crate::accountant::scanners::payable_scanner_extension::msgs::{UnpricedQualifiedPayables, QualifiedPayableWithGasPrice}; + use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::TxByTable::SentPayable; + use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::TxHashByTable; use crate::blockchain::errors::{AppRpcError, RemoteError}; impl Handler> for BlockchainBridge { @@ -1159,13 +1163,11 @@ mod tests { let (accountant, _, accountant_recording_arc) = make_recorder(); let accountant = accountant.system_stop_conditions(match_lazily_every_type_id!(TxReceiptsMessage)); - let sent_tx_1 = make_sent_tx(123); - let hash_1 = sent_tx_1.hash; - let sent_tx_2 = make_sent_tx(456); - let hash_2 = sent_tx_2.hash; + let tx_hash_1 = make_tx_hash(123); + let tx_hash_2 = make_tx_hash(456); let first_response = ReceiptResponseBuilder::default() .status(U64::from(1)) - .transaction_hash(hash_1) + .transaction_hash(tx_hash_1) .build(); let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port) @@ -1186,7 +1188,10 @@ mod tests { let peer_actors = peer_actors_builder().accountant(accountant).build(); send_bind_message!(subject_subs, peer_actors); let msg = RequestTransactionReceipts { - sent_txs: vec![sent_tx_1.clone(), sent_tx_2.clone()], + tx_hashes: vec![ + TxHashByTable::SentPayable(tx_hash_1), + TxHashByTable::FailedPayable(tx_hash_2), + ], response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, context_id: 4321, @@ -1201,14 +1206,20 @@ mod tests { assert_eq!(accountant_recording.len(), 1); let tx_receipts_message = accountant_recording.get_record::(0); let mut expected_receipt = TransactionReceipt::default(); - expected_receipt.transaction_hash = hash_1; + expected_receipt.transaction_hash = tx_hash_1; expected_receipt.status = Some(U64::from(1)); assert_eq!( tx_receipts_message, &TxReceiptsMessage { results: vec![ - TxReceiptResult::Ok(TxWithStatus::new(sent_tx_1, expected_receipt.into())), - TxReceiptResult::Ok(TxWithStatus::new(sent_tx_2, ReceiptCheck::PendingTx)) + TxReceiptResult::Ok(RetrievedTxStatus::new( + TxHashByTable::SentPayable(tx_hash_1), + expected_receipt.into() + )), + TxReceiptResult::Ok(RetrievedTxStatus::new( + TxHashByTable::FailedPayable(tx_hash_2), + StatusReadFromReceiptCheck::Pending + )) ], response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, @@ -1270,8 +1281,7 @@ mod tests { } #[test] - fn handle_request_transaction_receipts_short_circuits_on_failure_from_remote_process_sends_back_all_good_results_and_logs_abort( - ) { + fn handle_request_transaction_receipts_sends_back_results() { init_test_logging(); let port = find_free_port(); let block_number = U64::from(4545454); @@ -1286,13 +1296,13 @@ mod tests { .begin_batch() .raw_response(r#"{ "jsonrpc": "2.0", "id": 1, "result": null }"#.to_string()) .raw_response(tx_receipt_response) - .raw_response(r#"{ "jsonrpc": "2.0", "id": 1, "result": null }"#.to_string()) .err_response( 429, "The requests per second (RPS) of your requests are higher than your plan allows." .to_string(), 7, ) + .raw_response(r#"{ "jsonrpc": "2.0", "id": 1, "result": null }"#.to_string()) .end_batch() .start(); let (accountant, _, accountant_recording_arc) = make_recorder(); @@ -1302,15 +1312,10 @@ mod tests { let report_transaction_receipt_recipient: Recipient = accountant_addr.clone().recipient(); let scan_error_recipient: Recipient = accountant_addr.recipient(); - let hash_1 = make_tx_hash(111334); - let hash_2 = make_tx_hash(100000); - let hash_3 = make_tx_hash(0x1348d); - let hash_4 = make_tx_hash(11111); - let mut sent_tx_1 = make_sent_tx(123); - sent_tx_1.hash = hash_1; - let sent_tx_2 = make_sent_tx(456); - let sent_tx_3 = make_sent_tx(789); - let sent_tx_4 = make_sent_tx(1011); + let tx_hash_1 = make_tx_hash(1334); + let tx_hash_2 = make_tx_hash(1000); + let tx_hash_3 = make_tx_hash(1212); + let tx_hash_4 = make_tx_hash(1111); let blockchain_interface = make_blockchain_interface_web3(port); let system = System::new("test_transaction_receipts"); let mut subject = BlockchainBridge::new( @@ -1323,11 +1328,11 @@ mod tests { .report_tx_receipts_sub_opt = Some(report_transaction_receipt_recipient); subject.scan_error_subs_opt = Some(scan_error_recipient); let msg = RequestTransactionReceipts { - sent_txs: vec![ - sent_tx_1.clone(), - sent_tx_2.clone(), - sent_tx_3.clone(), - sent_tx_4.clone(), + tx_hashes: vec![ + TxHashByTable::SentPayable(tx_hash_1), + TxHashByTable::SentPayable(tx_hash_2), + TxHashByTable::SentPayable(tx_hash_3), + TxHashByTable::SentPayable(tx_hash_4), ], response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, @@ -1346,16 +1351,16 @@ mod tests { *report_receipts_msg, TxReceiptsMessage { results: vec![ - TxReceiptResult::Ok(TxWithStatus::new(sent_tx_1, ReceiptCheck::PendingTx)), - TxReceiptResult::Ok(TxWithStatus::new(sent_tx_2, ReceiptCheck::TxSucceeded(TransactionBlock { + TxReceiptResult::Ok(RetrievedTxStatus::new(TxHashByTable::SentPayable(tx_hash_1), StatusReadFromReceiptCheck::Pending)), + TxReceiptResult::Ok(RetrievedTxStatus::new(TxHashByTable::SentPayable(tx_hash_2), StatusReadFromReceiptCheck::Succeeded(TransactionBlock { block_hash: Default::default(), block_number, }))), - TxReceiptResult::Ok(TxWithStatus::new(sent_tx_3, ReceiptCheck::PendingTx)), TxReceiptResult::Err( TxReceiptError::new( - sent_tx_4, - AppRpcError:: Remote(RemoteError::Web3RpcError { code: 429, message: "The requests per second (RPS) of your requests are higher than your plan allows.".to_string()}))) + TxHashByTable::SentPayable(tx_hash_3), + AppRpcError:: Remote(RemoteError::Web3RpcError { code: 429, message: "The requests per second (RPS) of your requests are higher than your plan allows.".to_string()}))), + TxReceiptResult::Ok(RetrievedTxStatus::new(TxHashByTable::SentPayable(tx_hash_1), StatusReadFromReceiptCheck::Pending)), ], response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, @@ -1369,7 +1374,7 @@ mod tests { } #[test] - fn handle_request_transaction_receipts_short_circuits_if_submit_batch_fails() { + fn handle_request_transaction_receipts_failing_submit_the_batch() { init_test_logging(); let (accountant, _, accountant_recording) = make_recorder(); let accountant_addr = accountant @@ -1378,9 +1383,8 @@ mod tests { let scan_error_recipient: Recipient = accountant_addr.clone().recipient(); let report_transaction_recipient: Recipient = accountant_addr.recipient(); - let hash_1 = make_tx_hash(0x1b2e6); - let sent_tx_1 = make_sent_tx(123); - let sent_tx_2 = make_sent_tx(456); + let tx_hash_1 = make_tx_hash(10101); + let tx_hash_2 = make_tx_hash(10102); let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port).start(); let blockchain_interface = make_blockchain_interface_web3(port); @@ -1394,7 +1398,10 @@ mod tests { .report_tx_receipts_sub_opt = Some(report_transaction_recipient); subject.scan_error_subs_opt = Some(scan_error_recipient); let msg = RequestTransactionReceipts { - sent_txs: vec![sent_tx_1, sent_tx_2], + tx_hashes: vec![ + TxHashByTable::SentPayable(tx_hash_1), + TxHashByTable::FailedPayable(tx_hash_2), + ], response_skeleton_opt: None, }; let system = System::new("test"); diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs index 0e4fc9b5f..fcd7c69d6 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs @@ -3,6 +3,7 @@ use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, FailureReason}; use crate::accountant::db_access_objects::sent_payable_dao::SentTx; use crate::accountant::db_access_objects::utils::TxHash; +use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::TxHashByTable; use crate::blockchain::blockchain_interface::blockchain_interface_web3::CONTRACT_ABI; use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainError; use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainError::QueryFailed; @@ -21,42 +22,42 @@ use web3::transports::{Batch, Http}; use web3::types::{Address, BlockNumber, Filter, Log, TransactionReceipt}; use web3::{Error, Web3}; -pub type TxReceiptResult = Result; +pub type TxReceiptResult = Result; #[derive(Debug, PartialEq, Eq, Clone)] -pub struct TxWithStatus { - pub sent_tx: SentTx, - pub status: ReceiptCheck, +pub struct RetrievedTxStatus { + pub tx_hash: TxHashByTable, + pub status: StatusReadFromReceiptCheck, } -impl TxWithStatus { - pub fn new(sent_tx: SentTx, status: ReceiptCheck) -> Self { - Self { sent_tx, status } +impl RetrievedTxStatus { + pub fn new(tx_hash: TxHashByTable, status: StatusReadFromReceiptCheck) -> Self { + Self { tx_hash, status } } } -impl From for ReceiptCheck { +impl From for StatusReadFromReceiptCheck { fn from(receipt: TransactionReceipt) -> Self { match (receipt.status, receipt.block_hash, receipt.block_number) { (Some(status), Some(block_hash), Some(block_number)) if status == U64::from(1) => { - ReceiptCheck::TxSucceeded(TransactionBlock { + StatusReadFromReceiptCheck::Succeeded(TransactionBlock { block_hash, block_number, }) } (Some(status), _, _) if status == U64::from(0) => { - ReceiptCheck::TxFailed(BlockchainTxFailure::Unrecognized) + StatusReadFromReceiptCheck::Failed(BlockchainTxFailure::Unrecognized) } - _ => ReceiptCheck::PendingTx, + _ => StatusReadFromReceiptCheck::Pending, } } } #[derive(Debug, PartialEq, Eq, Clone)] -pub enum ReceiptCheck { - TxFailed(BlockchainTxFailure), - TxSucceeded(TransactionBlock), - PendingTx, +pub enum StatusReadFromReceiptCheck { + Failed(BlockchainTxFailure), + Succeeded(TransactionBlock), + Pending, } #[derive(Clone, Copy, Debug, PartialEq, Eq, VariantCount)] @@ -72,20 +73,20 @@ impl Display for BlockchainTxFailure { } } -impl Display for ReceiptCheck { +impl Display for StatusReadFromReceiptCheck { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - ReceiptCheck::TxFailed(reason) => { + StatusReadFromReceiptCheck::Failed(reason) => { write!(f, "Failed(Reason: {})", reason) } - ReceiptCheck::TxSucceeded(block) => { + StatusReadFromReceiptCheck::Succeeded(block) => { write!( f, "Succeeded({},{:?})", block.block_number, block.block_hash ) } - ReceiptCheck::PendingTx => write!(f, "Pending"), + StatusReadFromReceiptCheck::Pending => write!(f, "Pending"), } } } @@ -121,13 +122,13 @@ impl Display for ReceiptCheck { #[derive(Debug, PartialEq, Eq, Clone)] pub struct TxReceiptError { - pub tx: SentTx, + pub tx_hash: TxHashByTable, pub err: AppRpcError, } impl TxReceiptError { - pub fn new(tx: SentTx, err: AppRpcError) -> Self { - Self { tx, err } + pub fn new(tx_hash: TxHashByTable, err: AppRpcError) -> Self { + Self { tx_hash, err } } } @@ -269,7 +270,7 @@ mod tests { use crate::accountant::db_access_objects::sent_payable_dao::SentTx; use crate::accountant::test_utils::make_sent_tx; use crate::assert_on_testing_enum_with_all_its_variants; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxWithStatus, TransactionBlock, BlockchainTxFailure, ReceiptCheck}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{RetrievedTxStatus, TransactionBlock, BlockchainTxFailure, StatusReadFromReceiptCheck}; #[test] fn get_transaction_fee_balance_works() { @@ -640,7 +641,7 @@ mod tests { ); match tx_status { - ReceiptCheck::TxSucceeded(ref block) => { + StatusReadFromReceiptCheck::Succeeded(ref block) => { assert_eq!(block.block_hash, H256::from_low_u64_be(0x1234)); assert_eq!(block.block_number, U64::from(10)); } @@ -659,7 +660,7 @@ mod tests { assert_eq!( tx_status, - ReceiptCheck::TxFailed(BlockchainTxFailure::Unrecognized) + StatusReadFromReceiptCheck::Failed(BlockchainTxFailure::Unrecognized) ); } @@ -672,7 +673,7 @@ mod tests { H256::from_low_u64_be(0x5678), ); - assert_eq!(tx_status, ReceiptCheck::PendingTx); + assert_eq!(tx_status, StatusReadFromReceiptCheck::Pending); } #[test] @@ -684,7 +685,7 @@ mod tests { H256::from_low_u64_be(0x5678), ); - assert_eq!(tx_status, ReceiptCheck::PendingTx); + assert_eq!(tx_status, StatusReadFromReceiptCheck::Pending); } #[test] @@ -696,24 +697,24 @@ mod tests { H256::from_low_u64_be(0x5678), ); - assert_eq!(tx_status, ReceiptCheck::PendingTx); + assert_eq!(tx_status, StatusReadFromReceiptCheck::Pending); } #[test] fn tx_status_display_works() { // Test Failed assert_eq!( - ReceiptCheck::TxFailed(BlockchainTxFailure::Unrecognized).to_string(), + StatusReadFromReceiptCheck::Failed(BlockchainTxFailure::Unrecognized).to_string(), "Failed(Reason: Failure unrecognized)" ); // Test Pending - assert_eq!(ReceiptCheck::PendingTx.to_string(), "Pending"); + assert_eq!(StatusReadFromReceiptCheck::Pending.to_string(), "Pending"); // Test Succeeded let block_number = U64::from(12345); let block_hash = H256::from_low_u64_be(0xabcdef); - let succeeded = ReceiptCheck::TxSucceeded(TransactionBlock { + let succeeded = StatusReadFromReceiptCheck::Succeeded(TransactionBlock { block_hash, block_number, }); @@ -817,7 +818,7 @@ mod tests { block_hash_opt: Option, block_number_opt: Option, transaction_hash: H256, - ) -> ReceiptCheck { + ) -> StatusReadFromReceiptCheck { let receipt = TransactionReceipt { status: num_status_opt, root: None, diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs index ab85f9ff6..e60cf52ab 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs @@ -23,8 +23,10 @@ use web3::types::{Address, Log, H256, U256, FilterBuilder, TransactionReceipt, B use crate::accountant::scanners::payable_scanner_extension::msgs::{UnpricedQualifiedPayables, PricedQualifiedPayables}; use crate::blockchain::blockchain_agent::BlockchainAgent; use crate::accountant::db_access_objects::sent_payable_dao::SentTx; +use crate::accountant::db_access_objects::utils::TxHash; +use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::TxHashByTable; use crate::blockchain::blockchain_bridge::{BlockMarker, BlockScanRange, RegisterNewPendingPayables}; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{LowBlockchainIntWeb3, ReceiptCheck, TxWithStatus, TxReceiptError, TxReceiptResult}; +use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{LowBlockchainIntWeb3, StatusReadFromReceiptCheck, RetrievedTxStatus, TxReceiptError, TxReceiptResult}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::utils::{create_blockchain_agent_web3, send_payables_within_batch, BlockchainAgentFutureResult}; use crate::blockchain::errors::{AppRpcError, RemoteError}; // TODO We should probably begin to attach these constants to the interfaces more tightly, so that @@ -213,33 +215,34 @@ impl BlockchainInterface for BlockchainInterfaceWeb3 { fn process_transaction_receipts( &self, - sent_txs: Vec, + tx_hashes: Vec, ) -> Box, Error = BlockchainError>> { - let transaction_hashes = sent_txs.iter().map(|tx| tx.hash).collect::>(); Box::new( self.lower_interface() - .get_transaction_receipt_in_batch(transaction_hashes) + .get_transaction_receipt_in_batch(Self::drain_hashes(&tx_hashes)) .map_err(move |e| e) .and_then(move |batch_response| { + todo!("check that all the retrieved data are in order with the hashes "); + Ok(batch_response .into_iter() - .zip(sent_txs.into_iter()) - .map(|(response, sent_tx)| match response { + .zip(tx_hashes.into_iter()) + .map(|(response, tx_hash)| match response { Ok(result) => { match serde_json::from_value::(result) { - Ok(receipt) => TxReceiptResult::Ok(TxWithStatus::new( - sent_tx, + Ok(receipt) => TxReceiptResult::Ok(RetrievedTxStatus::new( + tx_hash, receipt.into(), )), Err(e) => { if e.to_string().contains("invalid type: null") { - TxReceiptResult::Ok(TxWithStatus::new( - sent_tx, - ReceiptCheck::PendingTx, + TxReceiptResult::Ok(RetrievedTxStatus::new( + tx_hash, + StatusReadFromReceiptCheck::Pending, )) } else { TxReceiptResult::Err(TxReceiptError::new( - sent_tx, + tx_hash, AppRpcError::Remote(RemoteError::InvalidResponse( e.to_string(), )), @@ -248,7 +251,7 @@ impl BlockchainInterface for BlockchainInterfaceWeb3 { } } } - Err(e) => TxReceiptResult::Err(TxReceiptError::new(sent_tx, e.into())), + Err(e) => TxReceiptResult::Err(TxReceiptError::new(tx_hash, e.into())), }) .collect::>()) }), @@ -437,6 +440,16 @@ impl BlockchainInterfaceWeb3 { Ok(transactions) } } + + fn drain_hashes(hashes_by_table: &[TxHashByTable]) -> Vec { + hashes_by_table + .iter() + .map(|hash_by_table| match hash_by_table { + TxHashByTable::SentPayable(hash) => todo!(), + TxHashByTable::FailedPayable(hash) => todo!(), + }) + .collect() + } } #[cfg(test)] @@ -452,7 +465,7 @@ mod tests { BlockchainAgentBuildError, BlockchainError, BlockchainInterface, RetrievedBlockchainTransactions, }; - use crate::blockchain::test_utils::{all_chains, make_blockchain_interface_web3, ReceiptResponseBuilder}; + use crate::blockchain::test_utils::{all_chains, make_blockchain_interface_web3, make_tx_hash, ReceiptResponseBuilder}; use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; use crate::sub_lib::wallet::Wallet; use crate::test_utils::make_paying_wallet; @@ -471,7 +484,7 @@ mod tests { use crate::accountant::scanners::payable_scanner_extension::msgs::{QualifiedPayablesBeforeGasPriceSelection, QualifiedPayableWithGasPrice}; use crate::accountant::test_utils::make_payable_account; use crate::blockchain::blockchain_bridge::increase_gas_price_by_margin; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, ReceiptCheck}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, StatusReadFromReceiptCheck}; use crate::accountant::test_utils::make_sent_tx; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{BlockchainTxFailure}; @@ -1054,18 +1067,20 @@ mod tests { #[test] fn process_transaction_receipts_works() { let port = find_free_port(); - let sent_tx_1 = make_sent_tx(3300); - let sent_tx_2 = make_sent_tx(3401); - let sent_tx_3 = make_sent_tx(3502); - let sent_tx_4 = make_sent_tx(3603); - let sent_tx_5 = make_sent_tx(3704); - let sent_tx_6 = make_sent_tx(3805); - let sent_tx_vec = vec![ - &sent_tx_1, &sent_tx_2, &sent_tx_3, &sent_tx_4, &sent_tx_5, &sent_tx_6, - ] - .into_iter() - .cloned() - .collect(); + let tx_hash_1 = make_tx_hash(3300); + let tx_hash_2 = make_tx_hash(3401); + let tx_hash_3 = make_tx_hash(3502); + // + let tx_hash_4 = make_tx_hash(3603); + let tx_hash_5 = make_tx_hash(3704); + let tx_hash_6 = make_tx_hash(3805); + let tx_hbt_1 = TxHashByTable::FailedPayable(tx_hash_1); + let tx_hbt_2 = TxHashByTable::FailedPayable(tx_hash_2); + let tx_hbt_3 = TxHashByTable::SentPayable(tx_hash_3); + let tx_hbt_4 = TxHashByTable::SentPayable(tx_hash_4); + let tx_hbt_5 = TxHashByTable::SentPayable(tx_hash_5); + let tx_hbt_6 = TxHashByTable::SentPayable(tx_hash_6); + let sent_tx_vec = vec![tx_hbt_1, tx_hbt_2, tx_hbt_3, tx_hbt_4, tx_hbt_5, tx_hbt_6]; let block_hash = H256::from_str("6d0abccae617442c26104c2bc63d1bc05e1e002e555aec4ab62a46e826b18f18") .unwrap(); @@ -1075,14 +1090,14 @@ mod tests { let status = U64::from(1); let status_failed = U64::from(0); let tx_receipt_response_not_present = ReceiptResponseBuilder::default() - .transaction_hash(sent_tx_4.hash) + .transaction_hash(tx_hash_4) .build(); let tx_receipt_response_failed = ReceiptResponseBuilder::default() - .transaction_hash(sent_tx_5.hash) + .transaction_hash(tx_hash_5) .status(status_failed) .build(); let tx_receipt_response_success = ReceiptResponseBuilder::default() - .transaction_hash(sent_tx_6.hash) + .transaction_hash(tx_hash_6) .block_hash(block_hash) .block_number(block_number) .cumulative_gas_used(cumulative_gas_used) @@ -1107,14 +1122,14 @@ mod tests { let subject = make_blockchain_interface_web3(port); let result = subject - .process_transaction_receipts(sent_tx_vec) + .process_transaction_receipts(sent_tx_vec.clone()) .wait() .unwrap(); assert_eq!(result[0], TxReceiptResult::Err( TxReceiptError::new( - sent_tx_1, - AppRpcError::Remote( + tx_hbt_1, + AppRpcError::Remote( RemoteError::Web3RpcError { code: 429, message: @@ -1125,12 +1140,15 @@ mod tests { ); assert_eq!( result[1], - TxReceiptResult::Ok(TxWithStatus::new(sent_tx_2, ReceiptCheck::PendingTx)) + TxReceiptResult::Ok(RetrievedTxStatus::new( + tx_hbt_2, + StatusReadFromReceiptCheck::Pending + )) ); assert_eq!( result[2], TxReceiptResult::Err(TxReceiptError::new( - sent_tx_3, + tx_hbt_3, AppRpcError::Remote(RemoteError::InvalidResponse( "invalid type: string \"trash\", expected struct Receipt".to_string() )) @@ -1138,20 +1156,23 @@ mod tests { ); assert_eq!( result[3], - TxReceiptResult::Ok(TxWithStatus::new(sent_tx_4, ReceiptCheck::PendingTx)) + TxReceiptResult::Ok(RetrievedTxStatus::new( + tx_hbt_4, + StatusReadFromReceiptCheck::Pending + )) ); assert_eq!( result[4], - TxReceiptResult::Ok(TxWithStatus::new( - sent_tx_5, - ReceiptCheck::TxFailed(BlockchainTxFailure::Unrecognized) + TxReceiptResult::Ok(RetrievedTxStatus::new( + tx_hbt_5, + StatusReadFromReceiptCheck::Failed(BlockchainTxFailure::Unrecognized) )) ); assert_eq!( result[5], - TxReceiptResult::Ok(TxWithStatus::new( - sent_tx_6, - ReceiptCheck::TxSucceeded(TransactionBlock { + TxReceiptResult::Ok(RetrievedTxStatus::new( + tx_hbt_6, + StatusReadFromReceiptCheck::Succeeded(TransactionBlock { block_hash, block_number, }), @@ -1164,9 +1185,12 @@ mod tests { let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port).start(); let subject = make_blockchain_interface_web3(port); - let sent_tx_1 = make_sent_tx(789); - let sent_tx_2 = make_sent_tx(123); - let tx_hash_vec = vec![sent_tx_1, sent_tx_2]; + let tx_hash_1 = make_tx_hash(789); + let tx_hash_2 = make_tx_hash(123); + let tx_hash_vec = vec![ + TxHashByTable::SentPayable(tx_hash_1), + TxHashByTable::SentPayable(tx_hash_2), + ]; let error = subject .process_transaction_receipts(tx_hash_vec) diff --git a/node/src/blockchain/blockchain_interface/mod.rs b/node/src/blockchain/blockchain_interface/mod.rs index 52be01e46..dad520b48 100644 --- a/node/src/blockchain/blockchain_interface/mod.rs +++ b/node/src/blockchain/blockchain_interface/mod.rs @@ -16,6 +16,7 @@ use masq_lib::logger::Logger; use crate::accountant::scanners::payable_scanner_extension::msgs::{PricedQualifiedPayables}; use crate::blockchain::blockchain_agent::BlockchainAgent; use crate::accountant::db_access_objects::sent_payable_dao::SentTx; +use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::TxHashByTable; use crate::blockchain::blockchain_bridge::{BlockMarker, BlockScanRange, RegisterNewPendingPayables}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::TxReceiptResult; @@ -40,7 +41,7 @@ pub trait BlockchainInterface { fn process_transaction_receipts( &self, - sent_txs: Vec, + tx_hashes: Vec, ) -> Box, Error = BlockchainError>>; fn submit_payables_in_batch( From c1127c54ebbac17c50d56fed3d861fc55612b78b Mon Sep 17 00:00:00 2001 From: Bert Date: Wed, 30 Jul 2025 16:21:04 +0200 Subject: [PATCH 20/61] GH-642: before creating a new whole folder for scanner utils --- node/src/accountant/mod.rs | 2 +- node/src/accountant/scanners/mod.rs | 141 ++++++---- .../src/accountant/scanners/scanners_utils.rs | 266 ++++++++++-------- 3 files changed, 235 insertions(+), 174 deletions(-) diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index 301cdaf40..25dd3497f 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -333,7 +333,7 @@ impl Handler for Accountant { response_skeleton_opt, &self.logger, ), - Retry::RetryOnlyTxStatusCheck => todo!(), + Retry::RetryTxStatusCheckOnly => todo!(), }, }; } diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index 073b66025..909015dfd 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -12,7 +12,7 @@ use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableT LocallyCausedError, RemotelyCausedErrors, }; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{debugging_summary_after_error_separation, err_msg_for_failure_with_expected_but_missing_sent_tx_record, investigate_debt_extremes, payables_debug_summary, separate_errors, separate_rowids_and_hashes, OperationOutcome, PayableScanResult, PayableThresholdsGauge, PayableThresholdsGaugeReal, PayableTransactingErrorEnum, PendingPayableMissingInDb}; -use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_status_with_failure, handle_successful_tx, ReceiptScanReport, PendingPayableScanResult, handle_still_pending_tx, handle_rpc_failure, DetectedFailures, FailuresRequiringDoubleCheck, CurrentPendingPayables, RecordsCoupledWithReceiptCheckResults, PresortedTxFailure, ValidationStatusUpdate, DetectedConfirmations}; +use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_status_with_failure, handle_successful_tx, ReceiptScanReport, PendingPayableScanResult, handle_still_pending_tx, handle_rpc_failure, DetectedFailures, FailuresRequiringDoubleCheck, CurrentPendingPayables, PresortedTxFailure, ValidationStatusUpdate, DetectedConfirmations, TxCaseToBeInterpreted}; use crate::accountant::scanners::scanners_utils::receivable_scanner_utils::balance_and_age; use crate::accountant::{PendingPayable, PendingPayableId, ScanError, ScanForPendingPayables, ScanForRetryPayables}; use crate::accountant::{ @@ -1025,21 +1025,18 @@ impl PendingPayableScanner { msg: TxReceiptsMessage, logger: &Logger, ) -> ReceiptScanReport { - let records_with_check_results = self.prepare_data_couples(msg); - self.compose_receipt_scan_report(records_with_check_results, logger) + let interpretable_data = self.prepare_cases_to_interpret(msg); + self.compose_receipt_scan_report(interpretable_data, logger) } - fn prepare_data_couples( - &mut self, - msg: TxReceiptsMessage, - ) -> RecordsCoupledWithReceiptCheckResults { + fn prepare_cases_to_interpret(&mut self, msg: TxReceiptsMessage) -> Vec { //TODO pull the records out from the caches and leave them empty todo!() } fn compose_receipt_scan_report( &self, - records_with_check_results: RecordsCoupledWithReceiptCheckResults, + tx_cases: Vec, logger: &Logger, ) -> ReceiptScanReport { todo!() @@ -1516,7 +1513,7 @@ mod tests { use crate::accountant::db_access_objects::utils::{from_unix_timestamp, to_unix_timestamp, TxHash}; use crate::accountant::scanners::payable_scanner_extension::msgs::{QualifiedPayablesBeforeGasPriceSelection, QualifiedPayablesMessage, UnpricedQualifiedPayables}; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{OperationOutcome, PayableScanResult, PendingPayableMissingInDb}; - use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_status_with_failure, ReceiptScanReport, PendingPayableScanResult, DetectedFailures, FailuresRequiringDoubleCheck, TxHashByTable, TxByTable, PresortedTxFailure, PendingPayableCache}; + use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_status_with_failure, ReceiptScanReport, PendingPayableScanResult, DetectedFailures, FailuresRequiringDoubleCheck, TxHashByTable, TxByTable, PresortedTxFailure, PendingPayableCache, DetectedConfirmations}; use crate::accountant::scanners::{Scanner, StartScanError, StartableScanner, PayableScanner, PendingPayableScanner, ReceivableScanner, ScannerCommon, Scanners, MTError}; use crate::accountant::test_utils::{make_custom_payment_thresholds, make_payable_account, make_qualified_and_unqualified_payables, make_receivable_account, BannedDaoFactoryMock, BannedDaoMock, ConfigDaoFactoryMock, PayableDaoFactoryMock, PayableDaoMock, PayableScannerBuilder, PayableThresholdsGaugeMock, SentPayableDaoFactoryMock, SentPayableDaoMock, PendingPayableScannerBuilder, ReceivableDaoFactoryMock, ReceivableDaoMock, ReceivableScannerBuilder, make_sent_tx, FailedPayableDaoMock, FailedPayableDaoFactoryMock, make_failed_tx, make_transaction_block, bc_from_earning_wallet, AccountantBuilder}; use crate::accountant::{gwei_to_wei, PendingPayableId, ReceivedPayments, TxReceiptsMessage, RequestTransactionReceipts, ScanError, ScanForRetryPayables, SentPayables, DEFAULT_PENDING_TOO_LONG_SEC, PendingPayable, ScanForPendingPayables}; @@ -3177,7 +3174,7 @@ mod tests { tx_failures: vec![PresortedTxFailure::NewEntry(failed_tx)], tx_receipt_rpc_failures: vec![] }, - confirmations: vec![] + confirmations: DetectedConfirmations::default() } ); TestLogHandler::new().exists_log_containing(&format!( @@ -3233,7 +3230,7 @@ mod tests { tx_failures: vec![PresortedTxFailure::NewEntry(expected_failed_tx)], tx_receipt_rpc_failures: vec![] }, - confirmations: vec![] + confirmations: DetectedConfirmations::default() } ); let log_handler = TestLogHandler::new(); @@ -3507,14 +3504,21 @@ mod tests { detection: Detection::Normal, }; - subject.handle_confirmed_transactions(vec![sent_tx_1, sent_tx_2], &Logger::new("test")); + subject.handle_confirmed_transactions( + DetectedConfirmations { + normal_confirmations: vec![sent_tx_1, sent_tx_2], + reclaims: vec![], + }, + &Logger::new("test"), + ); } #[test] fn handle_confirmed_transactions_does_nothing_if_none_found_on_the_blockchain() { let mut subject = PendingPayableScannerBuilder::new().build(); - subject.handle_confirmed_transactions(vec![], &Logger::new("test")) + subject + .handle_confirmed_transactions(DetectedConfirmations::default(), &Logger::new("test")) // Mocked payable DAO didn't panic, which means we skipped the actual process } @@ -3538,6 +3542,7 @@ mod tests { .build(); let tx_hash_1 = make_tx_hash(0x123); let tx_hash_2 = make_tx_hash(0x567); + let tx_hash_3 = make_tx_hash(0x913); let mut sent_tx_1 = make_sent_tx(123_123); sent_tx_1.hash = tx_hash_1; let tx_block_1 = TransactionBlock { @@ -3549,19 +3554,36 @@ mod tests { block_number: tx_block_1.block_number.as_u64(), detection: Detection::Normal, }; - let mut sent_tx_2 = make_sent_tx(567_567); + let mut sent_tx_2 = make_sent_tx(987_987); sent_tx_2.hash = tx_hash_2; let tx_block_2 = TransactionBlock { - block_hash: make_block_hash(78), - block_number: 7_898_989_878_u64.into(), + block_hash: make_block_hash(67), + block_number: 6_789_898_789_u64.into(), }; sent_tx_2.status = TxStatus::Confirmed { - block_hash: format!("{:?}", tx_block_2.block_hash), + block_hash: format!("{:?}", make_block_hash(123)), block_number: tx_block_2.block_number.as_u64(), + detection: Detection::Normal, + }; + let mut sent_tx_3 = make_sent_tx(567_567); + sent_tx_3.hash = tx_hash_3; + let tx_block_3 = TransactionBlock { + block_hash: make_block_hash(78), + block_number: 7_898_989_878_u64.into(), + }; + sent_tx_3.status = TxStatus::Confirmed { + block_hash: format!("{:?}", tx_block_3.block_hash), + block_number: tx_block_3.block_number.as_u64(), detection: Detection::Reclaim, }; - subject.handle_confirmed_transactions(vec![sent_tx_1.clone(), sent_tx_2.clone()], &logger); + subject.handle_confirmed_transactions( + DetectedConfirmations { + normal_confirmations: vec![sent_tx_1.clone(), sent_tx_2.clone()], + reclaims: vec![sent_tx_3.clone()], + }, + &logger, + ); let transactions_confirmed_params = transactions_confirmed_params_arc.lock().unwrap(); assert_eq!( @@ -3581,6 +3603,7 @@ mod tests { (block 4578989878), 0x0000000000000000000000000000000000000000000000000000000000000567 \ (block 7898989878) have been confirmed", )); + todo!("take care of the....reclaimmm") } #[test] @@ -3601,7 +3624,13 @@ mod tests { let mut sent_tx = make_sent_tx(456); sent_tx.hash = hash; - subject.handle_confirmed_transactions(vec![sent_tx], &Logger::new("test")); + subject.handle_confirmed_transactions( + DetectedConfirmations { + normal_confirmations: vec![sent_tx], + reclaims: vec![], + }, + &Logger::new("test"), + ); } #[test] @@ -3646,42 +3675,44 @@ mod tests { #[test] fn total_paid_payable_rises_with_each_bill_paid() { - init_test_logging(); - let test_name = "total_paid_payable_rises_with_each_bill_paid"; - let mut sent_tx_1 = make_sent_tx(456); - sent_tx_1.amount_minor = 5478; - sent_tx_1.status = TxStatus::Confirmed { - block_hash: format!("{:?}", make_block_hash(123)), - block_number: 89898, - detection: Detection::Normal, - }; - let mut sent_tx_2 = make_sent_tx(789); - sent_tx_2.amount_minor = 6543; - sent_tx_2.status = TxStatus::Confirmed { - block_hash: format!("{:?}", make_block_hash(321)), - block_number: 67676, - detection: Detection::Reclaim, - }; - let payable_dao = PayableDaoMock::default().transactions_confirmed_result(Ok(())); - let sent_payable_dao = SentPayableDaoMock::default().confirm_tx_result(Ok(())); - let mut subject = PendingPayableScannerBuilder::new() - .payable_dao(payable_dao) - .sent_payable_dao(sent_payable_dao) - .build(); - let mut financial_statistics = subject.financial_statistics.borrow().clone(); - financial_statistics.total_paid_payable_wei += 1111; - subject.financial_statistics.replace(financial_statistics); - - subject.handle_confirmed_transactions( - vec![sent_tx_1.clone(), sent_tx_2.clone()], - &Logger::new(test_name), - ); - - let total_paid_payable = subject.financial_statistics.borrow().total_paid_payable_wei; - assert_eq!(total_paid_payable, 1111 + 5478 + 6543); - TestLogHandler::new().exists_log_containing(&format!( - "DEBUG: {test_name}: The total paid payables increased by 12,021 to 13,132 wei" - )); + todo!("maybe write different tests for normal conf and reclaims") + // init_test_logging(); + // let test_name = "total_paid_payable_rises_with_each_bill_paid"; + // let mut sent_tx_1 = make_sent_tx(456); + // sent_tx_1.amount_minor = 5478; + // sent_tx_1.status = TxStatus::Confirmed { + // block_hash: format!("{:?}", make_block_hash(123)), + // block_number: 89898, + // detection: Detection::Normal, + // }; + // let mut sent_tx_2 = make_sent_tx(789); + // sent_tx_2.amount_minor = 6543; + // sent_tx_2.status = TxStatus::Confirmed { + // block_hash: format!("{:?}", make_block_hash(321)), + // block_number: 67676, + // detection: Detection::Reclaim, + // }; + // let payable_dao = PayableDaoMock::default().transactions_confirmed_result(Ok(())); + // let sent_payable_dao = SentPayableDaoMock::default().confirm_tx_result(Ok(())); + // let mut subject = PendingPayableScannerBuilder::new() + // .payable_dao(payable_dao) + // .sent_payable_dao(sent_payable_dao) + // .build(); + // let mut financial_statistics = subject.financial_statistics.borrow().clone(); + // financial_statistics.total_paid_payable_wei += 1111; + // subject.financial_statistics.replace(financial_statistics); + // + // subject.handle_confirmed_transactions( + // DetectedConfirmations{normal_confirmations: vec![sent_tx_1, sent_tx_2], + // reclaims + // &Logger::new(test_name), + // ); + // + // let total_paid_payable = subject.financial_statistics.borrow().total_paid_payable_wei; + // assert_eq!(total_paid_payable, 1111 + 5478 + 6543); + // TestLogHandler::new().exists_log_containing(&format!( + // "DEBUG: {test_name}: The total paid payables increased by 12,021 to 13,132 wei" + // )); } #[test] diff --git a/node/src/accountant/scanners/scanners_utils.rs b/node/src/accountant/scanners/scanners_utils.rs index c721bc512..c4a90e34e 100644 --- a/node/src/accountant/scanners/scanners_utils.rs +++ b/node/src/accountant/scanners/scanners_utils.rs @@ -379,8 +379,8 @@ pub mod pending_payable_scanner_utils { } impl DetectedConfirmations { - fn is_empty(&self) -> bool { - todo!("write a separate test") //self.normal_confirmations.is_empty() && self.reclaims.is_empty() + pub(super) fn is_empty(&self) -> bool { + self.normal_confirmations.is_empty() && self.reclaims.is_empty() } } @@ -397,7 +397,7 @@ pub mod pending_payable_scanner_utils { } else if !self.tx_failures.is_empty() { Some(Retry::RetryPayments) } else { - Some(Retry::RetryOnlyTxStatusCheck) + Some(Retry::RetryTxStatusCheckOnly) } } } @@ -511,11 +511,12 @@ pub mod pending_payable_scanner_utils { #[derive(Debug, PartialEq, Eq)] pub enum Retry { RetryPayments, - RetryOnlyTxStatusCheck, + RetryTxStatusCheckOnly, } - pub struct RecordsCoupledWithReceiptCheckResults { - pub prepared_couples: Vec<(TxByTable, StatusReadFromReceiptCheck)>, + pub struct TxCaseToBeInterpreted { + pub tx_by_table: TxByTable, + pub retrieved_status: StatusReadFromReceiptCheck, } pub enum TxByTable { @@ -710,7 +711,7 @@ mod tests { use regex::Regex; use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, FailureReason, FailureStatus, ValidationStatus}; use crate::accountant::db_access_objects::sent_payable_dao::{SentTx, TxStatus}; - use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{CurrentPendingPayables, DetectedFailures, FailedValidation, FailuresRequiringDoubleCheck, PendingPayableCache, PresortedTxFailure, ReceiptScanReport, Retry, ValidationStatusUpdate}; + use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailedValidation, FailuresRequiringDoubleCheck, PendingPayableCache, PresortedTxFailure, ReceiptScanReport, Retry, TxByTable, ValidationStatusUpdate}; use crate::accountant::test_utils::{make_failed_tx, make_sent_tx}; use crate::assert_on_testing_enum_with_all_its_variants; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::BlockchainTxFailure; @@ -1167,50 +1168,19 @@ mod tests { } #[test] - fn requires_payments_retry() { - let make_case_1 = |preprocessed_failures| ReceiptScanReport { - failures: DetectedFailures { - tx_failures: preprocessed_failures, - tx_receipt_rpc_failures: vec![], - }, - confirmations: vec![], - }; - let make_case_2 = |preprocessed_failures| ReceiptScanReport { - failures: DetectedFailures { - tx_failures: preprocessed_failures, - tx_receipt_rpc_failures: vec![ValidationStatusUpdate::SentPayableRecord( - FailedValidation { - tx_hash: make_tx_hash(2222), - failure: AppRpcError::Local(LocalError::Internal), - }, - )], - }, - confirmations: vec![], - }; - let make_case_3 = |preprocessed_failures| ReceiptScanReport { - failures: DetectedFailures { - tx_failures: preprocessed_failures, - tx_receipt_rpc_failures: vec![], - }, - confirmations: vec![make_sent_tx(777)], - }; - let make_case_4 = |preprocessed_failures| ReceiptScanReport { - failures: DetectedFailures { - tx_failures: preprocessed_failures, - tx_receipt_rpc_failures: vec![ValidationStatusUpdate::FailedPayableRecord( - FailedValidation { - tx_hash: make_tx_hash(12121), - failure: AppRpcError::Remote(RemoteError::InvalidResponse( - "blah".to_string(), - )), - }, - )], - }, - confirmations: vec![make_sent_tx(777)], + fn detected_confirmations_is_empty_works() { + let subject = DetectedConfirmations { + normal_confirmations: vec![], + reclaims: vec![], }; - let case_makers: Vec<&dyn Fn(Vec) -> ReceiptScanReport> = - vec![&make_case_1, &make_case_2, &make_case_3, &make_case_4]; - let tx_failure_feedings = vec![ + + assert_eq!(subject.is_empty(), true); + } + + #[test] + fn requires_payments_retry() { + // Maximalist approach: exhaustive set of tested variants: + let tx_failures_feedings = vec![ vec![PresortedTxFailure::NewEntry(make_failed_tx(456))], vec![PresortedTxFailure::RecheckCompleted(make_tx_hash(123))], vec![ @@ -1226,50 +1196,71 @@ mod tests { PresortedTxFailure::RecheckCompleted(make_tx_hash(654)), ], ]; - let generated_cases = case_makers.into_iter().fold(vec![], |mut acc, case_maker| { - tx_failure_feedings - .clone() - .into_iter() - .for_each(|failure_feeding| { - let report = case_maker(failure_feeding.clone()); - acc.push(report); - }); - acc - }); + let tx_receipt_rpc_failures_feeding = vec![ + vec![], + vec![ValidationStatusUpdate::SentPayableRecord( + FailedValidation { + tx_hash: make_tx_hash(2222), + failure: AppRpcError::Local(LocalError::Internal), + }, + )], + vec![ValidationStatusUpdate::FailedPayableRecord( + FailedValidation { + tx_hash: make_tx_hash(12121), + failure: AppRpcError::Remote(RemoteError::InvalidResponse("blah".to_string())), + }, + )], + ]; + let detected_confirmations_feeding = vec![ + DetectedConfirmations { + normal_confirmations: vec![], + reclaims: vec![], + }, + DetectedConfirmations { + normal_confirmations: vec![make_sent_tx(777)], + reclaims: vec![make_sent_tx(999)], + }, + DetectedConfirmations { + normal_confirmations: vec![make_sent_tx(777)], + reclaims: vec![], + }, + DetectedConfirmations { + normal_confirmations: vec![], + reclaims: vec![make_sent_tx(999)], + }, + ]; - generated_cases - .into_iter() - .enumerate() - .for_each(|(idx, case)| { - let result = case.requires_payments_retry(); - assert_eq!( - result, - Some(Retry::RetryPayments), - "We expected Some(Retry::RetryPayments), but got {:?} for case with idx {}", - result, - idx - ) - }) + tx_failures_feedings.iter().for_each(|tx_failures| { + tx_receipt_rpc_failures_feeding + .iter() + .for_each(|rpc_failures| { + detected_confirmations_feeding + .iter() + .for_each(|detected_confirmations| { + let case = ReceiptScanReport { + failures: DetectedFailures { + tx_failures: tx_failures.clone(), + tx_receipt_rpc_failures: rpc_failures.clone(), + }, + confirmations: detected_confirmations.clone(), + }; + + let result = case.requires_payments_retry(); + + assert_eq!( + result, + Some(Retry::RetryPayments), + "We expected Some(Retry::RetryPayments) but got {:?} for case {:?}", + result, + case + ); + }) + }) + }); } #[test] fn requires_only_receipt_retrieval_retry() { - let make_case_1 = |preprocessed_rpc_failures| ReceiptScanReport { - failures: DetectedFailures { - tx_failures: vec![], - tx_receipt_rpc_failures: preprocessed_rpc_failures, - }, - confirmations: vec![], - }; - let make_case_2 = |preprocessed_rpc_failures| ReceiptScanReport { - failures: DetectedFailures { - tx_failures: vec![], - tx_receipt_rpc_failures: preprocessed_rpc_failures, - }, - confirmations: vec![make_sent_tx(777)], - }; - let case_makers: Vec<&dyn Fn(Vec) -> ReceiptScanReport> = - vec![&make_case_1, &make_case_2]; let rpc_failure_feedings = vec![ vec![ValidationStatusUpdate::SentPayableRecord( FailedValidation { @@ -1294,42 +1285,78 @@ mod tests { }), ], ]; - let generated_cases = case_makers.into_iter().fold(vec![], |mut acc, case_maker| { - rpc_failure_feedings - .clone() - .into_iter() - .for_each(|failure_feeding| { - let report = case_maker(failure_feeding.clone()); - acc.push(report); - }); - acc - }); + let detected_confirmations_feeding = vec![ + DetectedConfirmations { + normal_confirmations: vec![], + reclaims: vec![], + }, + DetectedConfirmations { + normal_confirmations: vec![make_sent_tx(777)], + reclaims: vec![make_sent_tx(999)], + }, + DetectedConfirmations { + normal_confirmations: vec![make_sent_tx(777)], + reclaims: vec![], + }, + DetectedConfirmations { + normal_confirmations: vec![], + reclaims: vec![make_sent_tx(999)], + }, + ]; - generated_cases.into_iter().enumerate().for_each(|(idx, case)| { - let result = case.requires_payments_retry(); - assert_eq!( - result, - Some(Retry::RetryOnlyTxStatusCheck), - "We expected Some(Retry::RetryOnlyTxStatusCheck), but got {:?} for case with idx {}", - result, - idx - ) - }) + rpc_failure_feedings.into_iter().for_each(|rpc_failures|{ + detected_confirmations_feeding.iter().for_each(|detected_confirmations|{ + let case = ReceiptScanReport { + failures: DetectedFailures { + tx_failures: vec![], // This is the determinant + tx_receipt_rpc_failures: rpc_failures.clone(), + }, + confirmations: detected_confirmations.clone(), + }; + + let result = case.requires_payments_retry(); + + assert_eq!(result, Some(Retry::RetryTxStatusCheckOnly), "We expected Some(Retry::RetryTxStatusCheckOnly) but got {:?} for case {:?}", result, case); + }) + }); } #[test] fn requires_payments_retry_says_no() { - let report = ReceiptScanReport { - failures: DetectedFailures { - tx_failures: vec![], - tx_receipt_rpc_failures: vec![], + let detected_confirmations_feeding = vec![ + DetectedConfirmations { + normal_confirmations: vec![make_sent_tx(777)], + reclaims: vec![make_sent_tx(999)], }, - confirmations: vec![make_sent_tx(123)], - }; + DetectedConfirmations { + normal_confirmations: vec![make_sent_tx(777)], + reclaims: vec![], + }, + DetectedConfirmations { + normal_confirmations: vec![], + reclaims: vec![make_sent_tx(999)], + }, + ]; + + detected_confirmations_feeding + .into_iter() + .for_each(|detected_confirmations| { + let case = ReceiptScanReport { + failures: DetectedFailures { + tx_failures: vec![], + tx_receipt_rpc_failures: vec![], + }, + confirmations: detected_confirmations.clone(), + }; - let result = report.requires_payments_retry(); + let result = case.requires_payments_retry(); - assert_eq!(result, None) + assert_eq!( + result, None, + "We expected None but got {:?} for case {:?}", + result, case + ); + }); } #[test] @@ -1343,7 +1370,10 @@ mod tests { tx_failures: vec![], tx_receipt_rpc_failures: vec![], }, - confirmations: vec![], + confirmations: DetectedConfirmations { + normal_confirmations: vec![], + reclaims: vec![], + }, }; let _ = report.requires_payments_retry(); From e1506ea0a8aef8debf23dae57eeb4e1073ad237f Mon Sep 17 00:00:00 2001 From: Bert Date: Thu, 31 Jul 2025 13:29:14 +0200 Subject: [PATCH 21/61] GH-642: interim commit --- .../db_access_objects/sent_payable_dao.rs | 4 +- .../scanners/pending_payable_scanner/mod.rs | 103 +++++++++--------- .../scanners/pending_payable_scanner/utils.rs | 50 +++++---- node/src/database/rusqlite_wrappers.rs | 12 +- .../test_utils/transaction_wrapper_mock.rs | 8 +- 5 files changed, 94 insertions(+), 83 deletions(-) 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 18fcce326..2fcf4212a 100644 --- a/node/src/accountant/db_access_objects/sent_payable_dao.rs +++ b/node/src/accountant/db_access_objects/sent_payable_dao.rs @@ -129,7 +129,7 @@ pub trait SentPayableDao { //TODO potentially atomically fn confirm_tx( &self, - hash_map: &HashMap, + hash_map: &HashMap, ) -> Result<(), SentPayableDaoError>; fn replace_records(&self, new_txs: &[SentTx]) -> Result<(), SentPayableDaoError>; //TODO potentially atomically @@ -291,7 +291,7 @@ impl SentPayableDao for SentPayableDaoReal<'_> { fn confirm_tx( &self, - hash_map: &HashMap, + hash_map: &HashMap, ) -> Result<(), SentPayableDaoError> { if hash_map.is_empty() { return Err(SentPayableDaoError::EmptyInput); diff --git a/node/src/accountant/scanners/pending_payable_scanner/mod.rs b/node/src/accountant/scanners/pending_payable_scanner/mod.rs index c0f04493c..484f5ea63 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/mod.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/mod.rs @@ -10,11 +10,7 @@ use crate::accountant::db_access_objects::sent_payable_dao::{ RetrieveCondition, SentPayableDao, SentPayableDaoError, SentTx, TxConfirmation, }; use crate::accountant::db_access_objects::utils::TxHash; -use crate::accountant::scanners::pending_payable_scanner::utils::{ - handle_status_with_failure, CurrentPendingPayables, DetectedConfirmations, DetectedFailures, - FailuresRequiringDoubleCheck, PendingPayableScanResult, PresortedTxFailure, ReceiptScanReport, - TxCaseToBeInterpreted, ValidationStatusUpdate, -}; +use crate::accountant::scanners::pending_payable_scanner::utils::{handle_status_with_failure, CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailuresRequiringDoubleCheck, PendingPayableScanResult, PresortedTxFailure, ReceiptScanReport, Retry, TxCaseToBeInterpreted, FailedValidationByTable, TxReclaim, NormalTxConfirmation}; use crate::accountant::scanners::{ PrivateScanner, Scanner, ScannerCommon, StartScanError, StartableScanner, }; @@ -109,33 +105,15 @@ impl Scanner for PendingPayableScan ) -> PendingPayableScanResult { let response_skeleton_opt = message.response_skeleton_opt; - if message.results.is_empty() { - unreachable!("We should never receive an empty list of results. Even missing receipts can be interpreted") - } - - debug!( - logger, - "Processing receipts for {} txs", - message.results.len() - ); - let scan_report = self.interpret_tx_receipts(message, logger); - let payment_retry_opt = scan_report.requires_payments_retry(); + let retry_opt = scan_report.requires_payments_retry(); - self.process_txs_by_their_state(scan_report, logger); + self.process_txs_by_state(scan_report, logger); self.mark_as_ended(logger); - if let Some(retry) = payment_retry_opt { - PendingPayableScanResult::PaymentRetryRequired(retry) - } else { - let ui_msg_opt = response_skeleton_opt.map(|response_skeleton| NodeToUiMessage { - target: MessageTarget::ClientId(response_skeleton.client_id), - body: UiScanResponse {}.tmb(response_skeleton.context_id), - }); - PendingPayableScanResult::NoPendingPayablesLeft(ui_msg_opt) - } + Self::compose_scan_result(retry_opt, response_skeleton_opt) } time_marking_methods!(PendingPayables); @@ -168,11 +146,25 @@ impl PendingPayableScanner { } } + fn emptiness_check(&self, msg: &TxReceiptsMessage){ + if msg.results.is_empty() { + unreachable!("We should never receive an empty list of results. Even missing receipts can be interpreted") + } + } + fn interpret_tx_receipts( &mut self, msg: TxReceiptsMessage, logger: &Logger, ) -> ReceiptScanReport { + self.emptiness_check(&msg); + + debug!( + logger, + "Processing receipts for {} txs", + msg.results.len() + ); + let interpretable_data = self.prepare_cases_to_interpret(msg); self.compose_receipt_scan_report(interpretable_data, logger) } @@ -224,9 +216,7 @@ impl PendingPayableScanner { // ) } - fn process_txs_by_their_state(&mut self, scan_report: ReceiptScanReport, logger: &Logger) { - // TODO a) confirmation of just newly pending txs - // b) confirmations of reclaimed txs + fn process_txs_by_state(&mut self, scan_report: ReceiptScanReport, logger: &Logger) { self.handle_confirmed_transactions(scan_report.confirmations, logger); self.handle_failed_transactions(scan_report.failures, logger); } @@ -236,22 +226,23 @@ impl PendingPayableScanner { confirmed_txs: DetectedConfirmations, logger: &Logger, ) { - todo!("finish me") - // if !confirmed_txs.is_empty() { - // if let Err(e) = self.payable_dao.transactions_confirmed(&confirmed_txs) { - // Self::transaction_confirmed_panic(&confirmed_txs, e) - // } else { - // self.add_to_the_total_of_paid_payable(&confirmed_txs, logger); - // - // let tx_confirmations = Self::compose_tx_confirmation_inputs(&confirmed_txs); - // - // if let Err(e) = self.sent_payable_dao.confirm_tx(&tx_confirmations) { - // Self::update_tx_blocks_panic(&tx_confirmations, e) - // } else { - // Self::log_tx_success(logger, &tx_confirmations); - // } - // } - // } + self.handle_tx_failure_reclaims(confirmed_txs.reclaims, logger); + self.handle_normal_confirmations(confirmed_txs.normal_confirmations, logger); + + } + + fn handle_tx_failure_reclaims(&self, reclaimed: Vec, logger: &Logger){ + if reclaimed.is_empty() { + + } + todo!() + } + + fn handle_normal_confirmations(&self, confirmed_txs: Vec, logger: &Logger){ + if confirmed_txs.is_empty() { + todo!() + } + todo!() } fn compose_tx_confirmation_inputs(confirmed_txs: &[SentTx]) -> HashMap { @@ -346,15 +337,15 @@ impl PendingPayableScanner { (new_failures, rechecks_completed) }, ); - self.add_new_tx_failures(new_failures, logger); - self.conclude_newly_proven_old_tx_failures(rechecks_completed, logger); + self.add_new_failures(new_failures, logger); + self.finalize_unproven_failures(rechecks_completed, logger); } - fn add_new_tx_failures(&self, new_failures: Vec, logger: &Logger) { + fn add_new_failures(&self, new_failures: Vec, logger: &Logger) { todo!() } - fn conclude_newly_proven_old_tx_failures( + fn finalize_unproven_failures( &self, rechecks_completed: Vec, logger: &Logger, @@ -362,9 +353,21 @@ impl PendingPayableScanner { todo!() } - fn handle_rpc_failures(&self, failures: Vec, logger: &Logger) { + fn handle_rpc_failures(&self, failures: Vec, logger: &Logger) { todo!() } + + fn compose_scan_result(retry_opt: Option, response_skeleton_opt: Option) -> PendingPayableScanResult { + if let Some(retry) = retry_opt { + PendingPayableScanResult::PaymentRetryRequired(retry) + } else { + let ui_msg_opt = response_skeleton_opt.map(|response_skeleton| NodeToUiMessage { + target: MessageTarget::ClientId(response_skeleton.client_id), + body: UiScanResponse {}.tmb(response_skeleton.context_id), + }); + PendingPayableScanResult::NoPendingPayablesLeft(ui_msg_opt) + } + } } #[cfg(test)] diff --git a/node/src/accountant/scanners/pending_payable_scanner/utils.rs b/node/src/accountant/scanners/pending_payable_scanner/utils.rs index cfd925d40..53cce4f71 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/utils.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/utils.rs @@ -6,7 +6,7 @@ use masq_lib::ui_gateway::NodeToUiMessage; use std::time::SystemTime; use thousands::Separable; use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, FailureReason, FailureStatus}; -use crate::accountant::db_access_objects::sent_payable_dao::{SentTx, TxStatus}; +use crate::accountant::db_access_objects::sent_payable_dao::{SentTx, TxConfirmation, TxStatus}; use crate::accountant::db_access_objects::utils::{from_unix_timestamp, TxHash}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{BlockchainTxFailure, StatusReadFromReceiptCheck, TransactionBlock, TxReceiptError}; use crate::blockchain::errors::AppRpcError; @@ -45,7 +45,7 @@ impl ReceiptScanReport { todo!() } - fn register_rpc_failure(&mut self, status_update: ValidationStatusUpdate) { + fn register_rpc_failure(&mut self, status_update: FailedValidationByTable) { // TODO solve me by changing just the status //self.failures.failures.push(failed_tx); } @@ -53,8 +53,19 @@ impl ReceiptScanReport { #[derive(Debug, Default, PartialEq, Eq, Clone)] pub struct DetectedConfirmations { - pub normal_confirmations: Vec, - pub reclaims: Vec, + pub normal_confirmations: Vec, + pub reclaims: Vec, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct NormalTxConfirmation{ + pub tx_hash: TxHash, + pub confirmation: TxConfirmation +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct TxReclaim { + pub reclaimed: SentTx } impl DetectedConfirmations { @@ -66,7 +77,7 @@ impl DetectedConfirmations { #[derive(Debug, Default, PartialEq, Eq, Clone)] pub struct DetectedFailures { pub tx_failures: Vec, - pub tx_receipt_rpc_failures: Vec, + pub tx_receipt_rpc_failures: Vec, } impl DetectedFailures { @@ -88,9 +99,9 @@ pub enum PresortedTxFailure { } #[derive(Debug, PartialEq, Eq, Clone)] -pub enum ValidationStatusUpdate { - SentPayableRecord(FailedValidation), - FailedPayableRecord(FailedValidation), +pub enum FailedValidationByTable { + SentPayable(FailedValidation), + FailedPayable(FailedValidation), } #[derive(Debug, PartialEq, Eq, Clone)] @@ -349,16 +360,13 @@ impl From<(SentTx, FailureReason)> for FailedTx { #[cfg(test)] mod tests { - use crate::accountant::scanners::pending_payable_scanner::utils::{ - CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailedValidation, - FailuresRequiringDoubleCheck, PendingPayableCache, PresortedTxFailure, ReceiptScanReport, - Retry, ValidationStatusUpdate, - }; - use crate::accountant::test_utils::{make_failed_tx, make_sent_tx}; + use crate::accountant::scanners::pending_payable_scanner::utils::{CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailedValidation, FailuresRequiringDoubleCheck, PendingPayableCache, PresortedTxFailure, ReceiptScanReport, Retry, FailedValidationByTable, NormalTxConfirmation}; + use crate::accountant::test_utils::{make_failed_tx, make_sent_tx, make_transaction_block}; use crate::blockchain::errors::{AppRpcError, LocalError, RemoteError}; use crate::blockchain::test_utils::make_tx_hash; use masq_lib::logger::Logger; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; + use crate::accountant::db_access_objects::sent_payable_dao::{Detection, TxConfirmation}; #[test] fn detected_confirmations_is_empty_works() { @@ -391,13 +399,13 @@ mod tests { ]; let tx_receipt_rpc_failures_feeding = vec![ vec![], - vec![ValidationStatusUpdate::SentPayableRecord( + vec![FailedValidationByTable::SentPayable( FailedValidation { tx_hash: make_tx_hash(2222), failure: AppRpcError::Local(LocalError::Internal), }, )], - vec![ValidationStatusUpdate::FailedPayableRecord( + vec![FailedValidationByTable::FailedPayable( FailedValidation { tx_hash: make_tx_hash(12121), failure: AppRpcError::Remote(RemoteError::InvalidResponse("blah".to_string())), @@ -410,7 +418,7 @@ mod tests { reclaims: vec![], }, DetectedConfirmations { - normal_confirmations: vec![make_sent_tx(777)], + normal_confirmations: vec![NormalTxConfirmation{ tx_hash: make_tx_hash(456), confirmation: TxConfirmation { block_info: make_transaction_block(123), detection: Detection::Normal } }], reclaims: vec![make_sent_tx(999)], }, DetectedConfirmations { @@ -455,24 +463,24 @@ mod tests { #[test] fn requires_only_receipt_retrieval_retry() { let rpc_failure_feedings = vec![ - vec![ValidationStatusUpdate::SentPayableRecord( + vec![FailedValidationByTable::SentPayable( FailedValidation { tx_hash: make_tx_hash(2222), failure: AppRpcError::Local(LocalError::Internal), }, )], - vec![ValidationStatusUpdate::FailedPayableRecord( + vec![FailedValidationByTable::FailedPayable( FailedValidation { tx_hash: make_tx_hash(1234), failure: AppRpcError::Remote(RemoteError::Unreachable), }, )], vec![ - ValidationStatusUpdate::SentPayableRecord(FailedValidation { + FailedValidationByTable::SentPayable(FailedValidation { tx_hash: make_tx_hash(2222), failure: AppRpcError::Local(LocalError::Internal), }), - ValidationStatusUpdate::FailedPayableRecord(FailedValidation { + FailedValidationByTable::FailedPayable(FailedValidation { tx_hash: make_tx_hash(1234), failure: AppRpcError::Remote(RemoteError::Unreachable), }), diff --git a/node/src/database/rusqlite_wrappers.rs b/node/src/database/rusqlite_wrappers.rs index ec867482f..2177a250b 100644 --- a/node/src/database/rusqlite_wrappers.rs +++ b/node/src/database/rusqlite_wrappers.rs @@ -5,15 +5,15 @@ use crate::masq_lib::utils::ExpectValue; use rusqlite::{Connection, Error, Statement, ToSql, Transaction}; use std::fmt::Debug; -// We were challenged multiple times to device mocks for testing stubborn, hard to tame, data +// We were challenged multiple times to devise mocks for testing stubborn, hard to tame, data // structures from the 'rusqlite' library. After all, we've adopted two of them, the Connection, // that came first, and the Transaction to come much later. Of these, only the former complies // with the standard policy we follow for mock designs. // // The delay until the second one became a thing, even though we would've been glad having it -// on hand much earlier, was caused by vacuum of ideas on how we could create a mock of these +// on hand much earlier, was caused by a vacuum of ideas on how we could create a mock of these // parameters and have it accepted by the compiler. Passing a lot of time, we came up with a hybrid, -// at least. That said, it has costed us a considerably high price of giving up on simplicity. +// at least. That said, it has cost us a considerably high price of giving up on simplicity. // // The firmest blocker of the design has always rooted in a relationship of serialized lifetimes, // affecting each other, that has been so hard to maintain right. Yet the choices made @@ -74,12 +74,12 @@ impl ConnectionWrapperReal { } } -// Whole point of this outer wrapper, that is common to both the real and mock transactions, is to +// The whole point of this outer wrapper that is common to both the real and mock transactions is to // make a chance to deconstruct all components of a transaction in place. It plays a crucial role -// during the final commit. Note that an usual mock based on the direct use of a trait object +// during the final commit. Note that a usual mock based on the direct use of a trait object // cannot be consumed by any of its methods because of the Rust rules for trait objects. They say // clearly that we can access it via '&self', '&mut self' but not 'self'. However, to have a thing -// consume itself we need to be provided with the full ownership. +// consume itself, we need to be provided with the full ownership. // // Leaving remains of an already committed transaction around would expose us to a risk. Let's // imagine somebody trying to make use of it the second time, while the inner element providing diff --git a/node/src/database/test_utils/transaction_wrapper_mock.rs b/node/src/database/test_utils/transaction_wrapper_mock.rs index d0577c72f..d7c927b09 100644 --- a/node/src/database/test_utils/transaction_wrapper_mock.rs +++ b/node/src/database/test_utils/transaction_wrapper_mock.rs @@ -137,7 +137,7 @@ impl TransactionInnerWrapper for TransactionInnerWrapperMock { // is to be formed. // With that said, we're relieved to have at least one working solution now. Speaking of the 'prepare' -// method, an error would be hardly needed because the production code simply unwraps the results by +// method, an error would hardly be needed because the production code simply unwraps the results by // using 'expect'. That is a function excluded from the requirement of writing tests for. // The 'Statement' produced by this method must be better understood. The 'prepare' method has @@ -195,16 +195,16 @@ impl SetupForProdCodeAndAlteredStmts { None => // In the prod code, all the db operations would've happened on a single wrapped txn, // that's why we strive to manipulate a txn also here, not the conn directly. Most - // importantly, sometimes multiple subsequent operations take each the previous one as + // importantly, sometimes multiple later operations take each the previous one as // necessary base. If the continuity is broken the later statement might not work. If // we record some changes on the transaction, other changes tried to be done from // a different connection might meet a different state of the database and thwart the - // efforts. (This behaviour probably depends on the global setup of the db). + // efforts. (This behavior probably depends on the global setup of the db). // // // Also imagine a 'Statement' that wouldn't cause an error whereupon any potential // rollback of this txn should best drag off both the prod code and altered statements - // all together, disappearing. If we did not use this txn some of the changes would stay. + // all together, disappearing. If we did not use this txn some changes would stay. { self.txn_bearing_prod_code_stmts_opt .as_ref() From e8e24280e839aff8f06a9be5c59ff26826845f5a Mon Sep 17 00:00:00 2001 From: Bert Date: Fri, 1 Aug 2025 12:17:58 +0200 Subject: [PATCH 22/61] GH-642: preparing tests before writing the guts of the core fns --- .../db_access_objects/failed_payable_dao.rs | 40 +- .../db_access_objects/sent_payable_dao.rs | 115 ++-- node/src/accountant/mod.rs | 5 +- node/src/accountant/scanners/mod.rs | 10 +- .../scanners/pending_payable_scanner/mod.rs | 516 +++++++++++------- .../scanners/pending_payable_scanner/utils.rs | 106 ++-- node/src/accountant/test_utils.rs | 14 +- .../lower_level_interface_web3.rs | 2 +- 8 files changed, 482 insertions(+), 326 deletions(-) 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 18ef021d0..7a02cfe1c 100644 --- a/node/src/accountant/db_access_objects/failed_payable_dao.rs +++ b/node/src/accountant/db_access_objects/failed_payable_dao.rs @@ -13,6 +13,7 @@ use std::collections::{HashMap, HashSet}; use std::fmt::{Display, Formatter}; use std::str::FromStr; use web3::types::Address; +use crate::accountant::db_access_objects::failed_payable_dao::FailureStatus::RecheckRequired; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::TransactionBlock; #[derive(Debug, PartialEq, Eq)] @@ -93,18 +94,26 @@ pub struct FailedTx { #[derive(Debug, PartialEq, Eq)] pub enum FailureRetrieveCondition { + ByTxHash(Vec), ByStatus(FailureStatus), - RecheckRequiredRecords, + EveryRecheckRequiredRecord, } impl Display for FailureRetrieveCondition { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { + FailureRetrieveCondition::ByTxHash(hashes) => { + write!( + f, + "WHERE tx_hash IN ({})", + comma_joined_stringifiable(hashes, |hash| format!("'{:?}'", hash)) + ) + } FailureRetrieveCondition::ByStatus(status) => { - write!(f, "WHERE status LIKE '{:?}%'", status) + write!(f, "WHERE status = '{}'", status) } - FailureRetrieveCondition::RecheckRequiredRecords => { - todo!() + FailureRetrieveCondition::EveryRecheckRequiredRecord => { + write!(f, "WHERE status LIKE 'RecheckRequired%'") } } } @@ -588,6 +597,29 @@ mod tests { assert_eq!(result.get(&another_present_hash), Some(&2u64)); } + #[test] + fn display_for_failure_retrieve_condition_works() { + let tx_hash_1 = make_tx_hash(123); + let tx_hash_2 = make_tx_hash(456); + assert_eq!(FailureRetrieveCondition::ByTxHash(vec![tx_hash_1, tx_hash_2]).to_string(), + "WHERE tx_hash IN ('0x000000000000000000000000000000000000000000000000000000000000007b', \ + '0x00000000000000000000000000000000000000000000000000000000000001c8')" + ); + assert_eq!( + FailureRetrieveCondition::ByStatus(RetryRequired).to_string(), + "WHERE status = '\"RetryRequired\"'" + ); + assert_eq!( + FailureRetrieveCondition::ByStatus(RecheckRequired(ValidationStatus::Waiting)) + .to_string(), + "WHERE status = '{\"RecheckRequired\":\"Waiting\"}'" + ); + assert_eq!( + FailureRetrieveCondition::EveryRecheckRequiredRecord.to_string(), + "WHERE status LIKE 'RecheckRequired%'" + ); + } + #[test] fn failure_reason_from_str_works() { // Submission error 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 2fcf4212a..7578a39a9 100644 --- a/node/src/accountant/db_access_objects/sent_payable_dao.rs +++ b/node/src/accountant/db_access_objects/sent_payable_dao.rs @@ -70,35 +70,16 @@ pub enum Detection { Reclaim, } -impl From<&TxConfirmation> for TxStatus { - fn from(tx_confirmation: &TxConfirmation) -> Self { +impl From for TxStatus { + fn from(tx_block: TransactionBlock) -> Self { TxStatus::Confirmed { - block_hash: format!("{:?}", tx_confirmation.block_info.block_hash), - block_number: u64::try_from(tx_confirmation.block_info.block_number) - .expect("block number too big"), - detection: tx_confirmation.detection, + block_hash: format!("{:?}", tx_block.block_hash), + block_number: u64::try_from(tx_block.block_number).expect("block number too big"), + detection: Detection::Normal, } } } -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct TxConfirmation { - pub block_info: TransactionBlock, - pub detection: Detection, -} - -impl PartialOrd for TxConfirmation { - fn partial_cmp(&self, other: &Self) -> Option { - todo!() - } -} - -impl Ord for TxConfirmation { - fn cmp(&self, other: &Self) -> Ordering { - todo!() - } -} - #[derive(Debug, PartialEq, Eq)] pub enum RetrieveCondition { IsPending, @@ -297,10 +278,10 @@ impl SentPayableDao for SentPayableDaoReal<'_> { return Err(SentPayableDaoError::EmptyInput); } - for (hash, tx_confirmation) in hash_map { + for (hash, tx_block) in hash_map { let sql = format!( "UPDATE sent_payable SET status = '{}' WHERE tx_hash = '{:?}'", - TxStatus::from(tx_confirmation), + TxStatus::from(*tx_block), hash ); @@ -452,7 +433,7 @@ mod tests { use std::collections::{HashMap, HashSet}; use std::str::FromStr; use std::sync::{Arc, Mutex}; - use crate::accountant::db_access_objects::sent_payable_dao::{Detection, RetrieveCondition, SentPayableDao, SentPayableDaoError, SentPayableDaoReal, TxConfirmation, TxStatus}; + use crate::accountant::db_access_objects::sent_payable_dao::{Detection, RetrieveCondition, SentPayableDao, SentPayableDaoError, SentPayableDaoReal, TxStatus}; use crate::database::db_initializer::{ DbInitializationConfig, DbInitializer, DbInitializerReal, }; @@ -765,23 +746,17 @@ mod tests { let updated_pre_assert_txs = subject.retrieve_txs(Some(ByHash(vec![tx1.hash, tx2.hash]))); let pre_assert_status_tx1 = updated_pre_assert_txs[0].status.clone(); let pre_assert_status_tx2 = updated_pre_assert_txs[1].status.clone(); - let tx_confirmation_1 = TxConfirmation { - block_info: TransactionBlock { - block_hash: make_block_hash(3), - block_number: U64::from(1), - }, - detection: Detection::Normal, + let confirmed_tx_block_1 = TransactionBlock { + block_hash: make_block_hash(3), + block_number: U64::from(1), }; - let tx_confirmation_2 = TxConfirmation { - block_info: TransactionBlock { - block_hash: make_block_hash(4), - block_number: U64::from(2), - }, - detection: Detection::Reclaim, + let confirmed_tx_block_2 = TransactionBlock { + block_hash: make_block_hash(4), + block_number: U64::from(2), }; let hash_map = HashMap::from([ - (tx1.hash, tx_confirmation_1.clone()), - (tx2.hash, tx_confirmation_2.clone()), + (tx1.hash, confirmed_tx_block_1.clone()), + (tx2.hash, confirmed_tx_block_2.clone()), ]); let result = subject.confirm_tx(&hash_map); @@ -795,9 +770,9 @@ mod tests { assert_eq!( updated_txs[0].status, TxStatus::Confirmed { - block_hash: format!("{:?}", tx_confirmation_1.block_info.block_hash), - block_number: tx_confirmation_1.block_info.block_number.as_u64(), - detection: tx_confirmation_1.detection + block_hash: format!("{:?}", confirmed_tx_block_1.block_hash), + block_number: confirmed_tx_block_1.block_number.as_u64(), + detection: Detection::Normal } ); assert_eq!( @@ -807,9 +782,9 @@ mod tests { assert_eq!( updated_txs[1].status, TxStatus::Confirmed { - block_hash: format!("{:?}", tx_confirmation_2.block_info.block_hash), - block_number: tx_confirmation_2.block_info.block_number.as_u64(), - detection: tx_confirmation_2.detection + block_hash: format!("{:?}", confirmed_tx_block_2.block_hash), + block_number: confirmed_tx_block_2.block_number.as_u64(), + detection: Detection::Normal } ); } @@ -851,22 +826,16 @@ mod tests { let hash_map = HashMap::from([ ( existent_hash, - TxConfirmation { - block_info: TransactionBlock { - block_hash: make_block_hash(1), - block_number: U64::from(1), - }, - detection: Detection::Normal, + TransactionBlock { + block_hash: make_block_hash(1), + block_number: U64::from(1), }, ), ( non_existent_hash, - TxConfirmation { - block_info: TransactionBlock { - block_hash: make_block_hash(2), - block_number: U64::from(2), - }, - detection: Detection::Normal, + TransactionBlock { + block_hash: make_block_hash(2), + block_number: U64::from(2), }, ), ]); @@ -893,12 +862,9 @@ mod tests { let hash = make_tx_hash(1); let hash_map = HashMap::from([( hash, - TxConfirmation { - block_info: TransactionBlock { - block_hash: make_block_hash(1), - block_number: U64::default(), - }, - detection: Detection::Normal, + TransactionBlock { + block_hash: make_block_hash(1), + block_number: U64::default(), }, )]); @@ -1238,21 +1204,18 @@ mod tests { } #[test] - fn tx_status_can_be_converted_from_tx_confirmation() { - let tx_confirmation = TxConfirmation { - block_info: TransactionBlock { - block_hash: make_block_hash(6), - block_number: 456789_u64.into(), - }, - detection: Detection::Normal, + fn tx_status_can_be_made_from_transaction_block() { + let tx_block = TransactionBlock { + block_hash: make_block_hash(6), + block_number: 456789_u64.into(), }; assert_eq!( - TxStatus::from(&tx_confirmation), + TxStatus::from(tx_block), TxStatus::Confirmed { - block_hash: format!("{:?}", tx_confirmation.block_info.block_hash), - block_number: u64::try_from(tx_confirmation.block_info.block_number).unwrap(), - detection: tx_confirmation.detection, + block_hash: format!("{:?}", tx_block.block_hash), + block_number: u64::try_from(tx_block.block_number).unwrap(), + detection: Detection::Normal, } ) } diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index a4ea2de1a..b231a0c98 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -1289,7 +1289,7 @@ mod tests { use std::vec; use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, FailureRetrieveCondition, ValidationStatus}; use crate::accountant::scanners::payable_scanner_extension::msgs::UnpricedQualifiedPayables; - use crate::accountant::db_access_objects::sent_payable_dao::{Detection, RetrieveCondition, SentPayableDaoError, SentTx, TxConfirmation, TxStatus}; + use crate::accountant::db_access_objects::sent_payable_dao::{Detection, RetrieveCondition, SentPayableDaoError, SentTx, TxStatus}; use crate::accountant::scanners::pending_payable_scanner::utils::{Retry, TxByTable, TxHashByTable}; use crate::accountant::scanners::scan_schedulers::{NewPayableScanDynIntervalComputer, NewPayableScanDynIntervalComputerReal}; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{OperationOutcome, PayableScanResult}; @@ -4068,7 +4068,6 @@ mod tests { fn scan_for_pending_payables_finds_various_payables() { init_test_logging(); let test_name = "scan_for_pending_payables_finds_various_payables"; - let now = SystemTime::now(); let start_scan_params_arc = Arc::new(Mutex::new(vec![])); let (blockchain_bridge, _, blockchain_bridge_recording_arc) = make_recorder(); let blockchain_bridge_addr = blockchain_bridge @@ -4086,12 +4085,14 @@ mod tests { response_skeleton_opt: None, }; let pending_payable_scanner = ScannerMock::new() + .scan_started_at_result(None) .start_scan_params(&start_scan_params_arc) .start_scan_result(Ok(expected_composed_msg_for_blockchain_bridge.clone())); let consuming_wallet = make_wallet("consuming"); let system = System::new("pending payable scan"); let mut subject = AccountantBuilder::default() .consuming_wallet(consuming_wallet.clone()) + .logger(Logger::new(test_name)) .build(); subject .scanners diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index 7ac1b0db7..91ba4b6ea 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -45,7 +45,7 @@ use time::OffsetDateTime; use variant_count::VariantCount; use web3::types::H256; use crate::accountant::db_access_objects::failed_payable_dao::{FailedPayableDao, FailedTx, FailureRetrieveCondition, FailureStatus}; -use crate::accountant::db_access_objects::sent_payable_dao::{RetrieveCondition, SentPayableDao, SentPayableDaoError, SentTx, TxConfirmation, TxStatus}; +use crate::accountant::db_access_objects::sent_payable_dao::{RetrieveCondition, SentPayableDao, SentPayableDaoError, SentTx, TxStatus}; use crate::accountant::db_access_objects::utils::{TxHash, TxIdentifiers}; use crate::accountant::scanners::payable_scanner_extension::{MultistageDualPayableScanner, PreparedAdjustment, SolvencySensitivePaymentInstructor}; use crate::accountant::scanners::payable_scanner_extension::msgs::{BlockchainAgentWithContextMessage, QualifiedPayablesMessage, UnpricedQualifiedPayables}; @@ -1223,7 +1223,7 @@ mod tests { let retrieve_txs_params = retrieve_txs_params_arc.lock().unwrap(); assert_eq!( *retrieve_txs_params, - vec![Some(FailureRetrieveCondition::RecheckRequiredRecords)] + vec![Some(FailureRetrieveCondition::EveryRecheckRequiredRecord)] ); assert_eq!( receivable_scanner.common.payment_thresholds.as_ref(), @@ -1303,7 +1303,7 @@ mod tests { let retrieve_txs_params = retrieve_txs_params_arc.lock().unwrap(); assert_eq!( *retrieve_txs_params, - vec![Some(FailureRetrieveCondition::RecheckRequiredRecords)] + vec![Some(FailureRetrieveCondition::EveryRecheckRequiredRecord)] ); let mut expected_failures = FailuresRequiringDoubleCheck::default(); expected_failures @@ -2329,7 +2329,7 @@ mod tests { // let retrieve_failed_txs_params = retrieve_failed_txs_params_arc.lock().unwrap(); // assert_eq!( // *retrieve_failed_txs_params, - // vec![Some(FailureRetrieveCondition::RecheckRequiredRecords)] + // vec![Some(FailureRetrieveCondition::EveryRecheckRequiredRecord)] // ); // let blockchain_bridge_recording = blockchain_bridge_recording_arc.lock().unwrap(); // let received_msg = blockchain_bridge_recording.get_record::(0); @@ -2396,7 +2396,7 @@ mod tests { // let retrieve_failed_txs_params = retrieve_failed_txs_params_arc.lock().unwrap(); // assert_eq!( // *retrieve_failed_txs_params, - // vec![Some(FailureRetrieveCondition::RecheckRequiredRecords)] + // vec![Some(FailureRetrieveCondition::EveryRecheckRequiredRecord)] // ); // let blockchain_bridge_recording = blockchain_bridge_recording_arc.lock().unwrap(); // let received_msg = blockchain_bridge_recording.get_record::(0); diff --git a/node/src/accountant/scanners/pending_payable_scanner/mod.rs b/node/src/accountant/scanners/pending_payable_scanner/mod.rs index 484f5ea63..8da9e5d49 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/mod.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/mod.rs @@ -7,7 +7,7 @@ use crate::accountant::db_access_objects::failed_payable_dao::{ }; use crate::accountant::db_access_objects::payable_dao::{PayableDao, PayableDaoError}; use crate::accountant::db_access_objects::sent_payable_dao::{ - RetrieveCondition, SentPayableDao, SentPayableDaoError, SentTx, TxConfirmation, + RetrieveCondition, SentPayableDao, SentPayableDaoError, SentTx, }; use crate::accountant::db_access_objects::utils::TxHash; use crate::accountant::scanners::pending_payable_scanner::utils::{handle_status_with_failure, CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailuresRequiringDoubleCheck, PendingPayableScanResult, PresortedTxFailure, ReceiptScanReport, Retry, TxCaseToBeInterpreted, FailedValidationByTable, TxReclaim, NormalTxConfirmation}; @@ -30,6 +30,7 @@ use std::collections::HashMap; use std::rc::Rc; use std::time::SystemTime; use thousands::Separable; +use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::TransactionBlock; pub struct PendingPayableScanner { pub common: ScannerCommon, @@ -70,7 +71,7 @@ impl StartableScanner let unproven_failures = self .failed_payable_dao - .retrieve_txs(Some(FailureRetrieveCondition::RecheckRequiredRecords)); + .retrieve_txs(Some(FailureRetrieveCondition::EveryRecheckRequiredRecord)); // TODO 1) check non-empty collections // 2) fill in the respective caches @@ -131,7 +132,7 @@ impl PendingPayableScanner { ) -> Self { // let yet_unproven_failures = FailuresRequiringDoubleCheck::new( // failed_payable_dao.retrieve_txs(Some(FailureRetrieveCondition::ByStatus( - // todo!(), //FailureRetrieveCondition::RecheckRequiredRecords + // todo!(), //FailureRetrieveCondition::EveryRecheckRequiredRecord // ))), // ); @@ -146,7 +147,7 @@ impl PendingPayableScanner { } } - fn emptiness_check(&self, msg: &TxReceiptsMessage){ + fn emptiness_check(&self, msg: &TxReceiptsMessage) { if msg.results.is_empty() { unreachable!("We should never receive an empty list of results. Even missing receipts can be interpreted") } @@ -159,11 +160,7 @@ impl PendingPayableScanner { ) -> ReceiptScanReport { self.emptiness_check(&msg); - debug!( - logger, - "Processing receipts for {} txs", - msg.results.len() - ); + debug!(logger, "Processing receipts for {} txs", msg.results.len()); let interpretable_data = self.prepare_cases_to_interpret(msg); self.compose_receipt_scan_report(interpretable_data, logger) @@ -228,24 +225,27 @@ impl PendingPayableScanner { ) { self.handle_tx_failure_reclaims(confirmed_txs.reclaims, logger); self.handle_normal_confirmations(confirmed_txs.normal_confirmations, logger); - } - fn handle_tx_failure_reclaims(&self, reclaimed: Vec, logger: &Logger){ - if reclaimed.is_empty() { - - } + fn handle_tx_failure_reclaims(&self, reclaimed: Vec, logger: &Logger) { + if reclaimed.is_empty() {todo!()} todo!() } - fn handle_normal_confirmations(&self, confirmed_txs: Vec, logger: &Logger){ + fn handle_normal_confirmations( + &self, + confirmed_txs: Vec, + logger: &Logger, + ) { if confirmed_txs.is_empty() { todo!() } todo!() } - fn compose_tx_confirmation_inputs(confirmed_txs: &[SentTx]) -> HashMap { + fn compose_tx_confirmation_inputs( + confirmed_txs: &[SentTx], + ) -> HashMap { todo!() } @@ -263,7 +263,7 @@ impl PendingPayableScanner { } fn update_tx_blocks_panic( - tx_hashes_and_tx_blocks: &HashMap, + tx_hashes_and_tx_blocks: &HashMap, e: SentPayableDaoError, ) -> ! { panic!( @@ -276,16 +276,16 @@ impl PendingPayableScanner { ) } - fn log_tx_success(logger: &Logger, tx_hashes_and_tx_blocks: &HashMap) { + fn log_tx_success( + logger: &Logger, + tx_hashes_and_tx_blocks: &HashMap, + ) { logger.info(|| { let pretty_pairs = tx_hashes_and_tx_blocks .iter() .sorted() .map(|(hash, tx_confirmation)| { - format!( - "{:?} (block {})", - hash, tx_confirmation.block_info.block_number - ) + format!("{:?} (block {})", hash, tx_confirmation.block_number) }) .join(", "); match tx_hashes_and_tx_blocks.len() { @@ -345,11 +345,7 @@ impl PendingPayableScanner { todo!() } - fn finalize_unproven_failures( - &self, - rechecks_completed: Vec, - logger: &Logger, - ) { + fn finalize_unproven_failures(&self, rechecks_completed: Vec, logger: &Logger) { todo!() } @@ -357,7 +353,10 @@ impl PendingPayableScanner { todo!() } - fn compose_scan_result(retry_opt: Option, response_skeleton_opt: Option) -> PendingPayableScanResult { + fn compose_scan_result( + retry_opt: Option, + response_skeleton_opt: Option, + ) -> PendingPayableScanResult { if let Some(retry) = retry_opt { PendingPayableScanResult::PaymentRetryRequired(retry) } else { @@ -376,15 +375,16 @@ mod tests { use std::time::{Duration, SystemTime}; use masq_lib::logger::Logger; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; - use crate::accountant::db_access_objects::failed_payable_dao::{FailedPayableDaoError, FailedTx, FailureReason}; + use crate::accountant::db_access_objects::failed_payable_dao::{FailedPayableDaoError, FailedTx, FailureReason, FailureRetrieveCondition, FailureStatus, ValidationStatus}; use crate::accountant::db_access_objects::payable_dao::PayableDaoError; - use crate::accountant::db_access_objects::sent_payable_dao::{Detection, SentPayableDaoError, TxConfirmation, TxStatus}; + use crate::accountant::db_access_objects::sent_payable_dao::{Detection, RetrieveCondition, SentPayableDaoError, TxStatus}; use crate::accountant::db_access_objects::utils::{from_unix_timestamp, to_unix_timestamp}; use crate::accountant::scanners::pending_payable_scanner::PendingPayableScanner; - use crate::accountant::scanners::pending_payable_scanner::utils::{handle_status_with_failure, DetectedConfirmations, DetectedFailures, PendingPayableCache, PresortedTxFailure, ReceiptScanReport, TxByTable, TxHashByTable}; + use crate::accountant::scanners::pending_payable_scanner::utils::{handle_status_with_failure, DetectedConfirmations, DetectedFailures, FailedValidation, FailedValidationByTable, NormalTxConfirmation, PendingPayableCache, PresortedTxFailure, ReceiptScanReport, TxByTable, TxHashByTable, TxReclaim}; use crate::accountant::test_utils::{make_failed_tx, make_sent_tx, make_transaction_block, FailedPayableDaoMock, PayableDaoMock, PendingPayableScannerBuilder, SentPayableDaoMock}; - use crate::accountant::TxReceiptsMessage; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{BlockchainTxFailure, RetrievedTxStatus, StatusReadFromReceiptCheck, TransactionBlock, TxReceiptResult}; + use crate::accountant::{ResponseSkeleton, TxReceiptsMessage}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{BlockchainTxFailure, RetrievedTxStatus, StatusReadFromReceiptCheck, TransactionBlock, TxReceiptError, TxReceiptResult}; + use crate::blockchain::errors::{AppRpcError, LocalError, RemoteError}; use crate::blockchain::test_utils::{make_block_hash, make_tx_hash}; use crate::test_utils::unshared_test_utils::capture_numbers_with_separators_from_str; @@ -501,51 +501,64 @@ mod tests { } #[test] - fn interprets_right_failing_in_the_attempt_to_retrieve_the_tx_receipt() { - todo!( - "this test should be doubled and written for one case when the tx is still \ - a SentTx and another where it is already FailedTX" - ) - // init_test_logging(); - // let test_name = "interprets_right_failing_in_the_attempt_to_retrieve_the_tx_receipt"; - // let subject = PendingPayableScannerBuilder::new().build(); - // let hash = make_tx_hash(0x913); - // let sent_tx_timestamp = to_unix_timestamp( - // SystemTime::now() - // .checked_sub(Duration::from_secs(120)) - // .unwrap(), - // ); - // let mut sent_tx = make_sent_tx(456); - // sent_tx.hash = hash; - // sent_tx.timestamp = sent_tx_timestamp; - // let rpc_error = AppRpcError::Remote(RemoteError::InvalidResponse("bluh".to_string())); - // let msg = TxReceiptsMessage { - // results: vec![TxReceiptResult::Err(TxReceiptError::new( - // sent_tx.clone(), - // rpc_error.clone(), - // ))], - // response_skeleton_opt: None, - // }; - // - // let result = subject.interpret_tx_receipts(msg, &Logger::new(test_name)); - // - // assert_eq!( - // result, - // ReceiptScanReport { - // failures: DetectedFailures { - // tx_failures: vec![], - // tx_receipt_rpc_failures: vec![V] - // }, - // confirmations: vec![] - // } - // ); - // let log_handler = TestLogHandler::new(); - // let log_idx = log_handler.exists_log_containing(&format!( - // "WARN: {test_name}: Failed to retrieve tx receipt for \ - // 0x0000000000000000000000000000000000000000000000000000000000000913: \ - // Remote(InvalidResponse(\"bluh\")). \ - // Will retry receipt retrieval next cycle" - // )); + fn interprets_a_failing_attempt_to_retrieve_a_tx_receipt() { + init_test_logging(); + let test_name = "interprets_a_failing_attempt_to_retrieve_a_tx_receipt"; + let mut subject = PendingPayableScannerBuilder::new().build(); + let tx_hash_1 = make_tx_hash(0x913); + let tx_hash_2 = make_tx_hash(0x914); + let sent_tx_timestamp = to_unix_timestamp( + SystemTime::now() + .checked_sub(Duration::from_secs(120)) + .unwrap(), + ); + let mut sent_tx = make_sent_tx(456); + sent_tx.hash = tx_hash_1; + sent_tx.timestamp = sent_tx_timestamp; + let rpc_error_1 = AppRpcError::Remote(RemoteError::InvalidResponse("bluh".to_string())); + let rpc_error_2 = AppRpcError::Local(LocalError::Internal); + let msg = TxReceiptsMessage { + results: vec![ + TxReceiptResult::Err(TxReceiptError::new( + TxHashByTable::SentPayable(tx_hash_1), + rpc_error_1.clone(), + )), + TxReceiptResult::Err(TxReceiptError::new( + TxHashByTable::FailedPayable(tx_hash_2), + rpc_error_2.clone(), + )), + ], + response_skeleton_opt: None, + }; + + let result = subject.interpret_tx_receipts(msg, &Logger::new(test_name)); + + assert_eq!( + result, + ReceiptScanReport { + failures: DetectedFailures { + tx_failures: vec![], + tx_receipt_rpc_failures: vec![ + FailedValidationByTable::SentPayable(FailedValidation { + tx_hash: tx_hash_1, + failure: rpc_error_1 + }), + FailedValidationByTable::FailedPayable(FailedValidation { + tx_hash: tx_hash_2, + failure: rpc_error_2 + }) + ] + }, + confirmations: DetectedConfirmations::default() + } + ); + let log_handler = TestLogHandler::new(); + let log_idx = log_handler.exists_log_containing(&format!( + "WARN: {test_name}: Failed to retrieve tx receipt for \ + 0x0000000000000000000000000000000000000000000000000000000000000913: \ + Remote(InvalidResponse(\"bluh\")). \ + Will retry receipt retrieval next cycle" + )); } #[test] @@ -570,74 +583,173 @@ mod tests { failed_tx_2.hash = hash_2; let detected_failures = DetectedFailures { tx_failures: vec![ - PresortedTxFailure::NewEntry(failed_tx_1), - PresortedTxFailure::NewEntry(failed_tx_2), + PresortedTxFailure::NewEntry(failed_tx_1.clone()), + PresortedTxFailure::NewEntry(failed_tx_2.clone()), ], tx_receipt_rpc_failures: vec![], }; subject.handle_failed_transactions(detected_failures, &Logger::new("test")); + let insert_new_records_params = insert_new_records_params_arc.lock().unwrap(); + assert_eq!( + *insert_new_records_params, + vec![vec![failed_tx_1, failed_tx_2]] + ); let delete_records_params = delete_records_params_arc.lock().unwrap(); assert_eq!(*delete_records_params, vec![hashset![hash_1, hash_2]]); } #[test] fn handle_failed_transactions_can_process_receipt_retrieval_rpc_failures() { - todo!("write me up") - // let insert_new_records_params_arc = Arc::new(Mutex::new(vec![])); - // let delete_records_params_arc = Arc::new(Mutex::new(vec![])); - // let failed_payable_dao = FailedPayableDaoMock::default() - // .insert_new_records_params(&insert_new_records_params_arc) - // .insert_new_records_result(Ok(())); - // let sent_payable_dao = SentPayableDaoMock::default() - // .delete_records_params(&delete_records_params_arc) - // .delete_records_result(Ok(())); - // let subject = PendingPayableScannerBuilder::new() - // .sent_payable_dao(sent_payable_dao) - // .failed_payable_dao(failed_payable_dao) - // .build(); - // let hash_1 = make_tx_hash(0x321); - // let hash_2 = make_tx_hash(0x654); - // let mut failed_tx_1 = make_failed_tx(123); - // failed_tx_1.hash = hash_1; - // let mut failed_tx_2 = make_failed_tx(456); - // failed_tx_2.hash = hash_2; - // let detected_failures = DetectedFailures { tx_failures: vec![failed_tx_1,failed_tx_2], tx_receipt_rpc_failures: vec![] }; - // - // subject.handle_failed_transactions(detected_failures, &Logger::new("test")); - // - // let delete_records_params = delete_records_params_arc.lock().unwrap(); - // assert_eq!(*delete_records_params, vec![hashset![hash_1, hash_2]]); + let retrieve_failed_txs_params_arc = Arc::new(Mutex::new(vec![])); + let update_status_params_arc = Arc::new(Mutex::new(vec![])); + let retrieve_sent_txs_params_arc = Arc::new(Mutex::new(vec![])); + let replace_records_params_arc = Arc::new(Mutex::new(vec![])); + let hash_1 = make_tx_hash(0x321); + let hash_2 = make_tx_hash(0x654); + let mut failed_tx_1 = make_failed_tx(123); + failed_tx_1.hash = hash_1; + failed_tx_1.status = FailureStatus::RecheckRequired(ValidationStatus::Waiting); + let mut failed_tx_2 = make_failed_tx(456); + failed_tx_2.hash = hash_2; + failed_tx_2.status = FailureStatus::RecheckRequired(ValidationStatus::Reattempting { + attempt: 1, + error: AppRpcError::Local(LocalError::Internal), + }); + let failed_payable_dao = FailedPayableDaoMock::default() + .retrieve_txs_params(&retrieve_failed_txs_params_arc) + .retrieve_txs_result(vec![failed_tx_1, failed_tx_2]) + .update_statuses_params(&update_status_params_arc) + .update_statuses_result(Ok(())); + let hash_3 = make_tx_hash(0x987); + let mut sent_tx = make_sent_tx(789); + sent_tx.hash = hash_3; + sent_tx.status = TxStatus::Pending(ValidationStatus::Waiting); + let sent_payable_dao = SentPayableDaoMock::default() + .retrieve_txs_params(&retrieve_sent_txs_params_arc) + .retrieve_txs_result(vec![sent_tx.clone()]) + .replace_records_params(&replace_records_params_arc) + .replace_records_result(Ok(())); + let subject = PendingPayableScannerBuilder::new() + .sent_payable_dao(sent_payable_dao) + .failed_payable_dao(failed_payable_dao) + .build(); + let detected_failures = DetectedFailures { + tx_failures: vec![], + tx_receipt_rpc_failures: vec![ + FailedValidationByTable::FailedPayable(FailedValidation { + tx_hash: hash_1, + failure: AppRpcError::Remote(RemoteError::Unreachable), + }), + FailedValidationByTable::FailedPayable(FailedValidation { + tx_hash: hash_2, + failure: AppRpcError::Local(LocalError::Internal), + }), + FailedValidationByTable::SentPayable(FailedValidation { + tx_hash: hash_1, + failure: AppRpcError::Remote(RemoteError::InvalidResponse("Booga".to_string())), + }), + ], + }; + + subject.handle_failed_transactions(detected_failures, &Logger::new("test")); + + let retrieve_failed_txs_params = retrieve_failed_txs_params_arc.lock().unwrap(); + assert_eq!( + *retrieve_failed_txs_params, + vec![Some(FailureRetrieveCondition::ByTxHash(vec![ + hash_1, hash_2 + ]))] + ); + let update_status_params = update_status_params_arc.lock().unwrap(); + assert_eq!( + *update_status_params, + vec![ + hashmap!( + hash_1 => FailureStatus::RecheckRequired( + ValidationStatus::Reattempting { + attempt: 1, + error: AppRpcError::Remote(RemoteError::Unreachable) + } + ), + hash_2 => FailureStatus::RecheckRequired( + ValidationStatus::Reattempting { + attempt: 2, + error: AppRpcError::Local(LocalError::Internal) + } + ) + ) + ] + ); + let retrieve_sent_txs_params = retrieve_sent_txs_params_arc.lock().unwrap(); + assert_eq!( + *retrieve_sent_txs_params, + vec![Some(RetrieveCondition::ByHash(vec![hash_3]))] + ); + let replace_records_params = replace_records_params_arc.lock().unwrap(); + let mut expected_updated_record = sent_tx; + expected_updated_record.status = TxStatus::Pending(ValidationStatus::Reattempting { + attempt: 1, + error: AppRpcError::Remote(RemoteError::InvalidResponse("Booga".to_string())), + }); + assert_eq!(*replace_records_params, vec![vec![expected_updated_record]]); } #[test] fn handle_failed_transactions_can_process_mixed_failures() { - todo!("write me up") - // let insert_new_records_params_arc = Arc::new(Mutex::new(vec![])); - // let delete_records_params_arc = Arc::new(Mutex::new(vec![])); - // let failed_payable_dao = FailedPayableDaoMock::default() - // .insert_new_records_params(&insert_new_records_params_arc) - // .insert_new_records_result(Ok(())); - // let sent_payable_dao = SentPayableDaoMock::default() - // .delete_records_params(&delete_records_params_arc) - // .delete_records_result(Ok(())); - // let subject = PendingPayableScannerBuilder::new() - // .sent_payable_dao(sent_payable_dao) - // .failed_payable_dao(failed_payable_dao) - // .build(); - // let hash_1 = make_tx_hash(0x321); - // let hash_2 = make_tx_hash(0x654); - // let mut failed_tx_1 = make_failed_tx(123); - // failed_tx_1.hash = hash_1; - // let mut failed_tx_2 = make_failed_tx(456); - // failed_tx_2.hash = hash_2; - // let detected_failures = DetectedFailures { tx_failures: vec![failed_tx_1,failed_tx_2], tx_receipt_rpc_failures: vec![] }; - // - // subject.handle_failed_transactions(detected_failures, &Logger::new("test")); - // - // let delete_records_params = delete_records_params_arc.lock().unwrap(); - // assert_eq!(*delete_records_params, vec![hashset![hash_1, hash_2]]); + let insert_new_records_params_arc = Arc::new(Mutex::new(vec![])); + let delete_records_params_arc = Arc::new(Mutex::new(vec![])); + let retrieve_failed_txs_params_arc = Arc::new(Mutex::new(vec![])); + let update_status_params_arc = Arc::new(Mutex::new(vec![])); + let hash_1 = make_tx_hash(0x321); + let hash_2 = make_tx_hash(0x654); + let mut failed_tx_1 = make_failed_tx(123); + failed_tx_1.hash = hash_1; + let mut failed_tx_2 = make_failed_tx(456); + failed_tx_2.hash = hash_2; + let failed_payable_dao = FailedPayableDaoMock::default() + .retrieve_txs_params(&retrieve_failed_txs_params_arc) + .retrieve_txs_result(vec![failed_tx_1.clone()]) + .update_statuses_params(&update_status_params_arc) + .update_statuses_result(Ok(())) + .insert_new_records_params(&insert_new_records_params_arc) + .insert_new_records_result(Ok(())); + let sent_payable_dao = SentPayableDaoMock::default() + .delete_records_params(&delete_records_params_arc) + .delete_records_result(Ok(())); + let subject = PendingPayableScannerBuilder::new() + .sent_payable_dao(sent_payable_dao) + .failed_payable_dao(failed_payable_dao) + .build(); + let detected_failures = DetectedFailures { + tx_failures: vec![PresortedTxFailure::NewEntry(failed_tx_1)], + tx_receipt_rpc_failures: vec![FailedValidationByTable::SentPayable(FailedValidation { + tx_hash: hash_1, + failure: AppRpcError::Local(LocalError::Internal), + })], + }; + + subject.handle_failed_transactions(detected_failures, &Logger::new("test")); + + let retrieve_failed_txs_params = retrieve_failed_txs_params_arc.lock().unwrap(); + assert_eq!( + *retrieve_failed_txs_params, + vec![Some(FailureRetrieveCondition::ByTxHash(vec![ + hash_1, hash_2 + ]))] + ); + let update_status_params = update_status_params_arc.lock().unwrap(); + assert_eq!( + *update_status_params, + vec![ + hashmap!(hash_1 => FailureStatus::RecheckRequired(ValidationStatus::Reattempting {attempt: 1,error: AppRpcError::Local(LocalError::Internal)})) + ] + ); + let insert_new_records_params = insert_new_records_params_arc.lock().unwrap(); + assert_eq!(*insert_new_records_params, vec![vec![failed_tx_2]]); + let delete_records_params = delete_records_params_arc.lock().unwrap(); + assert_eq!(*delete_records_params, vec![hashset![hash_2]]); } #[test] @@ -698,7 +810,7 @@ mod tests { } #[test] - fn handle_failed_transactions_does_nothing_if_no_tx_failures_detected() { + fn handle_failed_transactions_does_nothing_if_no_failure_detected() { let subject = PendingPayableScannerBuilder::new().build(); let detected_failures = DetectedFailures { tx_failures: vec![], @@ -747,7 +859,10 @@ mod tests { subject.handle_confirmed_transactions( DetectedConfirmations { - normal_confirmations: vec![sent_tx_1, sent_tx_2], + normal_confirmations: vec![ + NormalTxConfirmation { tx: sent_tx_1 }, + NormalTxConfirmation { tx: sent_tx_2 }, + ], reclaims: vec![], }, &Logger::new("test"), @@ -755,7 +870,7 @@ mod tests { } #[test] - fn handle_confirmed_transactions_does_nothing_if_none_found_on_the_blockchain() { + fn handle_confirmed_transactions_does_nothing_if_no_confirmation_found_on_the_blockchain() { let mut subject = PendingPayableScannerBuilder::new().build(); subject @@ -770,16 +885,24 @@ mod tests { let test_name = "handle_confirmed_transactions_works"; let transactions_confirmed_params_arc = Arc::new(Mutex::new(vec![])); let confirm_tx_params_arc = Arc::new(Mutex::new(vec![])); + let replace_records_params_arc = Arc::new(Mutex::new(vec![])); + let delete_records_params_arc = Arc::new(Mutex::new(vec![])); let payable_dao = PayableDaoMock::default() .transactions_confirmed_params(&transactions_confirmed_params_arc) .transactions_confirmed_result(Ok(())); let sent_payable_dao = SentPayableDaoMock::default() .confirm_tx_params(&confirm_tx_params_arc) - .confirm_tx_result(Ok(())); + .confirm_tx_result(Ok(())) + .replace_records_params(&replace_records_params_arc) + .replace_records_result(Ok(())); + let failed_payable_dao = FailedPayableDaoMock::default() + .delete_records_params(&delete_records_params_arc) + .delete_records_result(Ok(())); let logger = Logger::new(test_name); let mut subject = PendingPayableScannerBuilder::new() .payable_dao(payable_dao) .sent_payable_dao(sent_payable_dao) + .failed_payable_dao(failed_payable_dao) .build(); let tx_hash_1 = make_tx_hash(0x123); let tx_hash_2 = make_tx_hash(0x567); @@ -820,8 +943,17 @@ mod tests { subject.handle_confirmed_transactions( DetectedConfirmations { - normal_confirmations: vec![sent_tx_1.clone(), sent_tx_2.clone()], - reclaims: vec![sent_tx_3.clone()], + normal_confirmations: vec![ + NormalTxConfirmation { + tx: sent_tx_1.clone(), + }, + NormalTxConfirmation { + tx: sent_tx_2.clone(), + }, + ], + reclaims: vec![TxReclaim { + reclaimed: sent_tx_3.clone(), + }], }, &logger, ); @@ -829,22 +961,23 @@ mod tests { let transactions_confirmed_params = transactions_confirmed_params_arc.lock().unwrap(); assert_eq!( *transactions_confirmed_params, - vec![vec![sent_tx_1, sent_tx_2]] + vec![vec![sent_tx_1, sent_tx_2, sent_tx_3.clone()]] ); let confirm_tx_params = confirm_tx_params_arc.lock().unwrap(); assert_eq!( *confirm_tx_params, - vec![ - hashmap![tx_hash_1 => TxConfirmation { block_info: tx_block_1, detection: Detection::Normal }, tx_hash_2 => TxConfirmation { block_info: tx_block_2, detection: Detection::Reclaim }] - ] + vec![hashmap![tx_hash_1 => tx_block_1, tx_hash_2 => tx_block_2]] ); + let replace_records_params = replace_records_params_arc.lock().unwrap(); + assert_eq!(*replace_records_params, vec![vec![sent_tx_3]]); + let delete_records_params = delete_records_params_arc.lock().unwrap(); + assert_eq!(*delete_records_params, vec![hashset![tx_hash_3]]); let log_handler = TestLogHandler::new(); log_handler.exists_log_containing(&format!( "INFO: {test_name}: Txs 0x0000000000000000000000000000000000000000000000000000000000000123 \ (block 4578989878), 0x0000000000000000000000000000000000000000000000000000000000000567 \ - (block 7898989878) have been confirmed", + (block 7898989878), txxxxbluh (block bluh) have been confirmed", )); - todo!("take care of the....reclaimmm") } #[test] @@ -867,7 +1000,7 @@ mod tests { subject.handle_confirmed_transactions( DetectedConfirmations { - normal_confirmations: vec![sent_tx], + normal_confirmations: vec![NormalTxConfirmation { tx: sent_tx }], reclaims: vec![], }, &Logger::new("test"), @@ -888,13 +1021,7 @@ mod tests { tx_block_1.block_number = 1_234_501_u64.into(); let mut tx_block_2 = make_transaction_block(789); tx_block_2.block_number = 1_234_502_u64.into(); - let mut tx_hashes_and_blocks = hashmap!(tx_hash_1 => TxConfirmation { - block_info: tx_block_1, - detection: Detection::Normal, - }, tx_hash_2 => TxConfirmation{ - block_info: tx_block_2, - detection: Detection::Reclaim, - }); + let mut tx_hashes_and_blocks = hashmap!(tx_hash_1 => tx_block_1, tx_hash_2 => tx_block_2); PendingPayableScanner::log_tx_success(&logger_plural, &tx_hashes_and_blocks); @@ -916,43 +1043,56 @@ mod tests { #[test] fn total_paid_payable_rises_with_each_bill_paid() { - todo!("maybe write different tests for normal conf and reclaims") - // init_test_logging(); - // let test_name = "total_paid_payable_rises_with_each_bill_paid"; - // let mut sent_tx_1 = make_sent_tx(456); - // sent_tx_1.amount_minor = 5478; - // sent_tx_1.status = TxStatus::Confirmed { - // block_hash: format!("{:?}", make_block_hash(123)), - // block_number: 89898, - // detection: Detection::Normal, - // }; - // let mut sent_tx_2 = make_sent_tx(789); - // sent_tx_2.amount_minor = 6543; - // sent_tx_2.status = TxStatus::Confirmed { - // block_hash: format!("{:?}", make_block_hash(321)), - // block_number: 67676, - // detection: Detection::Reclaim, - // }; - // let payable_dao = PayableDaoMock::default().transactions_confirmed_result(Ok(())); - // let sent_payable_dao = SentPayableDaoMock::default().confirm_tx_result(Ok(())); - // let mut subject = PendingPayableScannerBuilder::new() - // .payable_dao(payable_dao) - // .sent_payable_dao(sent_payable_dao) - // .build(); - // let mut financial_statistics = subject.financial_statistics.borrow().clone(); - // financial_statistics.total_paid_payable_wei += 1111; - // subject.financial_statistics.replace(financial_statistics); - // - // subject.handle_confirmed_transactions( - // DetectedConfirmations{normal_confirmations: vec![sent_tx_1, sent_tx_2], - // reclaims - // &Logger::new(test_name), - // ); - // - // let total_paid_payable = subject.financial_statistics.borrow().total_paid_payable_wei; - // assert_eq!(total_paid_payable, 1111 + 5478 + 6543); - // TestLogHandler::new().exists_log_containing(&format!( - // "DEBUG: {test_name}: The total paid payables increased by 12,021 to 13,132 wei" - // )); + init_test_logging(); + let test_name = "total_paid_payable_rises_with_each_bill_paid"; + let mut sent_tx_1 = make_sent_tx(456); + sent_tx_1.amount_minor = 5478; + sent_tx_1.status = TxStatus::Confirmed { + block_hash: format!("{:?}", make_block_hash(123)), + block_number: 89898, + detection: Detection::Normal, + }; + let mut sent_tx_2 = make_sent_tx(789); + sent_tx_2.amount_minor = 3344; + sent_tx_2.status = TxStatus::Confirmed { + block_hash: format!("{:?}", make_block_hash(234)), + block_number: 66312, + detection: Detection::Normal, + }; + let mut sent_tx_3 = make_sent_tx(789); + sent_tx_3.amount_minor = 6543; + sent_tx_3.status = TxStatus::Confirmed { + block_hash: format!("{:?}", make_block_hash(321)), + block_number: 67676, + detection: Detection::Reclaim, + }; + let payable_dao = PayableDaoMock::default().transactions_confirmed_result(Ok(())); + let sent_payable_dao = SentPayableDaoMock::default().confirm_tx_result(Ok(())); + let mut subject = PendingPayableScannerBuilder::new() + .payable_dao(payable_dao) + .sent_payable_dao(sent_payable_dao) + .build(); + let mut financial_statistics = subject.financial_statistics.borrow().clone(); + financial_statistics.total_paid_payable_wei += 1111; + subject.financial_statistics.replace(financial_statistics); + + subject.handle_confirmed_transactions( + DetectedConfirmations { + normal_confirmations: vec![ + NormalTxConfirmation { tx: sent_tx_1 }, + NormalTxConfirmation { tx: sent_tx_2 }, + ], + reclaims: vec![TxReclaim { + reclaimed: sent_tx_3, + }], + }, + &Logger::new(test_name), + ); + + let total_paid_payable = subject.financial_statistics.borrow().total_paid_payable_wei; + assert_eq!(total_paid_payable, 1111 + 5478 + 3344 + 6543); + TestLogHandler::new().exists_log_containing(&format!( + "DEBUG: {test_name}: The total paid payables increased by blouuh to bluuuuuh wei" + )); } } diff --git a/node/src/accountant/scanners/pending_payable_scanner/utils.rs b/node/src/accountant/scanners/pending_payable_scanner/utils.rs index 53cce4f71..c03aea5a0 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/utils.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/utils.rs @@ -6,7 +6,7 @@ use masq_lib::ui_gateway::NodeToUiMessage; use std::time::SystemTime; use thousands::Separable; use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, FailureReason, FailureStatus}; -use crate::accountant::db_access_objects::sent_payable_dao::{SentTx, TxConfirmation, TxStatus}; +use crate::accountant::db_access_objects::sent_payable_dao::{SentTx, TxStatus}; use crate::accountant::db_access_objects::utils::{from_unix_timestamp, TxHash}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{BlockchainTxFailure, StatusReadFromReceiptCheck, TransactionBlock, TxReceiptError}; use crate::blockchain::errors::AppRpcError; @@ -58,14 +58,13 @@ pub struct DetectedConfirmations { } #[derive(Debug, PartialEq, Eq, Clone)] -pub struct NormalTxConfirmation{ - pub tx_hash: TxHash, - pub confirmation: TxConfirmation +pub struct NormalTxConfirmation { + pub tx: SentTx, } #[derive(Debug, PartialEq, Eq, Clone)] pub struct TxReclaim { - pub reclaimed: SentTx + pub reclaimed: SentTx, } impl DetectedConfirmations { @@ -360,13 +359,18 @@ impl From<(SentTx, FailureReason)> for FailedTx { #[cfg(test)] mod tests { - use crate::accountant::scanners::pending_payable_scanner::utils::{CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailedValidation, FailuresRequiringDoubleCheck, PendingPayableCache, PresortedTxFailure, ReceiptScanReport, Retry, FailedValidationByTable, NormalTxConfirmation}; + use crate::accountant::db_access_objects::sent_payable_dao::Detection; + use crate::accountant::db_access_objects::sent_payable_dao::Detection::Normal; + use crate::accountant::scanners::pending_payable_scanner::utils::{ + CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailedValidation, + FailedValidationByTable, FailuresRequiringDoubleCheck, NormalTxConfirmation, + PendingPayableCache, PresortedTxFailure, ReceiptScanReport, Retry, TxReclaim, + }; use crate::accountant::test_utils::{make_failed_tx, make_sent_tx, make_transaction_block}; use crate::blockchain::errors::{AppRpcError, LocalError, RemoteError}; use crate::blockchain::test_utils::make_tx_hash; use masq_lib::logger::Logger; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; - use crate::accountant::db_access_objects::sent_payable_dao::{Detection, TxConfirmation}; #[test] fn detected_confirmations_is_empty_works() { @@ -399,18 +403,14 @@ mod tests { ]; let tx_receipt_rpc_failures_feeding = vec![ vec![], - vec![FailedValidationByTable::SentPayable( - FailedValidation { - tx_hash: make_tx_hash(2222), - failure: AppRpcError::Local(LocalError::Internal), - }, - )], - vec![FailedValidationByTable::FailedPayable( - FailedValidation { - tx_hash: make_tx_hash(12121), - failure: AppRpcError::Remote(RemoteError::InvalidResponse("blah".to_string())), - }, - )], + vec![FailedValidationByTable::SentPayable(FailedValidation { + tx_hash: make_tx_hash(2222), + failure: AppRpcError::Local(LocalError::Internal), + })], + vec![FailedValidationByTable::FailedPayable(FailedValidation { + tx_hash: make_tx_hash(12121), + failure: AppRpcError::Remote(RemoteError::InvalidResponse("blah".to_string())), + })], ]; let detected_confirmations_feeding = vec![ DetectedConfirmations { @@ -418,16 +418,24 @@ mod tests { reclaims: vec![], }, DetectedConfirmations { - normal_confirmations: vec![NormalTxConfirmation{ tx_hash: make_tx_hash(456), confirmation: TxConfirmation { block_info: make_transaction_block(123), detection: Detection::Normal } }], - reclaims: vec![make_sent_tx(999)], + normal_confirmations: vec![NormalTxConfirmation { + tx: make_sent_tx(456), + }], + reclaims: vec![TxReclaim { + reclaimed: make_sent_tx(999), + }], }, DetectedConfirmations { - normal_confirmations: vec![make_sent_tx(777)], + normal_confirmations: vec![NormalTxConfirmation { + tx: make_sent_tx(777), + }], reclaims: vec![], }, DetectedConfirmations { normal_confirmations: vec![], - reclaims: vec![make_sent_tx(999)], + reclaims: vec![TxReclaim { + reclaimed: make_sent_tx(999), + }], }, ]; @@ -463,18 +471,14 @@ mod tests { #[test] fn requires_only_receipt_retrieval_retry() { let rpc_failure_feedings = vec![ - vec![FailedValidationByTable::SentPayable( - FailedValidation { - tx_hash: make_tx_hash(2222), - failure: AppRpcError::Local(LocalError::Internal), - }, - )], - vec![FailedValidationByTable::FailedPayable( - FailedValidation { - tx_hash: make_tx_hash(1234), - failure: AppRpcError::Remote(RemoteError::Unreachable), - }, - )], + vec![FailedValidationByTable::SentPayable(FailedValidation { + tx_hash: make_tx_hash(2222), + failure: AppRpcError::Local(LocalError::Internal), + })], + vec![FailedValidationByTable::FailedPayable(FailedValidation { + tx_hash: make_tx_hash(1234), + failure: AppRpcError::Remote(RemoteError::Unreachable), + })], vec![ FailedValidationByTable::SentPayable(FailedValidation { tx_hash: make_tx_hash(2222), @@ -492,16 +496,24 @@ mod tests { reclaims: vec![], }, DetectedConfirmations { - normal_confirmations: vec![make_sent_tx(777)], - reclaims: vec![make_sent_tx(999)], + normal_confirmations: vec![NormalTxConfirmation { + tx: make_sent_tx(777), + }], + reclaims: vec![TxReclaim { + reclaimed: make_sent_tx(999), + }], }, DetectedConfirmations { - normal_confirmations: vec![make_sent_tx(777)], + normal_confirmations: vec![NormalTxConfirmation { + tx: make_sent_tx(777), + }], reclaims: vec![], }, DetectedConfirmations { normal_confirmations: vec![], - reclaims: vec![make_sent_tx(999)], + reclaims: vec![TxReclaim { + reclaimed: make_sent_tx(999), + }], }, ]; @@ -526,16 +538,24 @@ mod tests { fn requires_payments_retry_says_no() { let detected_confirmations_feeding = vec![ DetectedConfirmations { - normal_confirmations: vec![make_sent_tx(777)], - reclaims: vec![make_sent_tx(999)], + normal_confirmations: vec![NormalTxConfirmation { + tx: make_sent_tx(777), + }], + reclaims: vec![TxReclaim { + reclaimed: make_sent_tx(999), + }], }, DetectedConfirmations { - normal_confirmations: vec![make_sent_tx(777)], + normal_confirmations: vec![NormalTxConfirmation { + tx: make_sent_tx(777), + }], reclaims: vec![], }, DetectedConfirmations { normal_confirmations: vec![], - reclaims: vec![make_sent_tx(999)], + reclaims: vec![TxReclaim { + reclaimed: make_sent_tx(999), + }], }, ]; diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs index 87fcab91d..15f65f3da 100644 --- a/node/src/accountant/test_utils.rs +++ b/node/src/accountant/test_utils.rs @@ -5,7 +5,7 @@ use crate::accountant::db_access_objects::banned_dao::{BannedDao, BannedDaoFactory}; use crate::accountant::db_access_objects::failed_payable_dao::{FailedPayableDao, FailedPayableDaoError, FailedPayableDaoFactory, FailedTx, FailureReason, FailureRetrieveCondition, FailureStatus, ValidationStatus}; use crate::accountant::db_access_objects::payable_dao::{MarkPendingPayableID, PayableAccount, PayableDao, PayableDaoError, PayableDaoFactory}; -use crate::accountant::db_access_objects::sent_payable_dao::{SentPayableDao, SentPayableDaoFactory, TxConfirmation, TxStatus}; +use crate::accountant::db_access_objects::sent_payable_dao::{SentPayableDao, SentPayableDaoFactory, TxStatus}; use crate::accountant::db_access_objects::receivable_dao::{ ReceivableAccount, ReceivableDao, ReceivableDaoError, ReceivableDaoFactory, }; @@ -976,8 +976,8 @@ pub struct SentPayableDaoMock { insert_new_records_results: RefCell>>, retrieve_txs_params: Arc>>>, retrieve_txs_results: RefCell>>, - confirm_tx_params: Arc>>>, - confitm_tx_results: RefCell>>, + confirm_tx_params: Arc>>>, + confirm_tx_results: RefCell>>, replace_records_params: Arc>>>, replace_records_results: RefCell>>, delete_records_params: Arc>>>, @@ -1005,13 +1005,13 @@ impl SentPayableDao for SentPayableDaoMock { } fn confirm_tx( &self, - hash_map: &HashMap, + hash_map: &HashMap, ) -> Result<(), SentPayableDaoError> { self.confirm_tx_params .lock() .unwrap() .push(hash_map.clone()); - self.confitm_tx_results.borrow_mut().remove(0) + self.confirm_tx_results.borrow_mut().remove(0) } fn replace_records(&self, new_txs: &[SentTx]) -> Result<(), SentPayableDaoError> { self.replace_records_params @@ -1070,14 +1070,14 @@ impl SentPayableDaoMock { pub fn confirm_tx_params( mut self, - params: &Arc>>>, + params: &Arc>>>, ) -> Self { self.confirm_tx_params = params.clone(); self } pub fn confirm_tx_result(self, result: Result<(), SentPayableDaoError>) -> Self { - self.confitm_tx_results.borrow_mut().push(result); + self.confirm_tx_results.borrow_mut().push(result); self } diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs index d2c28ac37..afc827f5b 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs @@ -128,7 +128,7 @@ impl TxReceiptError { } } -#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] +#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Ord, PartialOrd, Serialize, Deserialize)] pub struct TransactionBlock { pub block_hash: H256, pub block_number: U64, From b2f4c7726b3ae0e45a1deea1b2826aab8e3f9896 Mon Sep 17 00:00:00 2001 From: Bert Date: Mon, 4 Aug 2025 22:37:51 +0200 Subject: [PATCH 23/61] GH-642: integration of the caches...100% at start_scan, 90% finish_scan --- .../db_access_objects/failed_payable_dao.rs | 23 +- ...able_and_failed_payable_data_conversion.rs | 133 ++++ .../db_access_objects/sent_payable_dao.rs | 22 +- .../src/accountant/db_access_objects/utils.rs | 4 + node/src/accountant/mod.rs | 18 +- node/src/accountant/scanners/mod.rs | 60 +- .../scanners/pending_payable_scanner/mod.rs | 592 +++++++++++++++--- .../scanners/pending_payable_scanner/utils.rs | 180 ++++-- .../src/accountant/scanners/scanners_utils.rs | 45 -- node/src/accountant/scanners/test_utils.rs | 73 ++- node/src/accountant/test_utils.rs | 51 +- node/src/blockchain/blockchain_bridge.rs | 24 +- .../lower_level_interface_web3.rs | 150 ++--- .../blockchain_interface_web3/mod.rs | 38 +- 14 files changed, 1024 insertions(+), 389 deletions(-) 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 7a02cfe1c..eae97696a 100644 --- a/node/src/accountant/db_access_objects/failed_payable_dao.rs +++ b/node/src/accountant/db_access_objects/failed_payable_dao.rs @@ -1,7 +1,5 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::db_access_objects::utils::{ - DaoFactoryReal, TxHash, TxIdentifiers, VigilantRusqliteFlatten, -}; +use crate::accountant::db_access_objects::utils::{DaoFactoryReal, TxHash, TxIdentifiers, TxRecordWithHash, VigilantRusqliteFlatten}; use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; use crate::accountant::{checked_conversion, comma_joined_stringifiable}; use crate::blockchain::errors::AppRpcError; @@ -92,6 +90,12 @@ pub struct FailedTx { pub status: FailureStatus, } +impl TxRecordWithHash for FailedTx { + fn hash(&self) -> TxHash { + self.hash + } +} + #[derive(Debug, PartialEq, Eq)] pub enum FailureRetrieveCondition { ByTxHash(Vec), @@ -397,7 +401,8 @@ mod tests { use crate::accountant::db_access_objects::test_utils::{ make_read_only_db_connection, FailedTxBuilder, }; - use crate::accountant::db_access_objects::utils::current_unix_timestamp; + use crate::accountant::db_access_objects::utils::{current_unix_timestamp, TxRecordWithHash}; + use crate::accountant::test_utils::{make_failed_tx, make_sent_tx}; use crate::blockchain::errors::{AppRpcError, LocalError, RemoteError}; use crate::blockchain::test_utils::make_tx_hash; use crate::database::db_initializer::{ @@ -977,4 +982,14 @@ mod tests { )) ) } + + #[test] + fn tx_record_with_hash_is_implemented_for_failed_tx() { + let failed_tx = make_failed_tx(1234); + let hash = failed_tx.hash; + + let hash_from_trait = failed_tx.hash(); + + assert_eq!(hash_from_trait, hash); + } } diff --git a/node/src/accountant/db_access_objects/sent_payable_and_failed_payable_data_conversion.rs b/node/src/accountant/db_access_objects/sent_payable_and_failed_payable_data_conversion.rs index 8b1378917..5131ea4bc 100644 --- a/node/src/accountant/db_access_objects/sent_payable_and_failed_payable_data_conversion.rs +++ b/node/src/accountant/db_access_objects/sent_payable_and_failed_payable_data_conversion.rs @@ -1 +1,134 @@ +// Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. +use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, FailureReason, FailureStatus}; +use crate::accountant::db_access_objects::sent_payable_dao::{Detection, SentTx, TxStatus}; +use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::TransactionBlock; + +impl From<(FailedTx, TransactionBlock)> for SentTx { + fn from((failed_tx, confirmation_block): (FailedTx, TransactionBlock)) -> Self { + SentTx { + hash: failed_tx.hash, + receiver_address: failed_tx.receiver_address, + amount_minor: failed_tx.amount_minor, + timestamp: failed_tx.timestamp, + gas_price_minor: failed_tx.gas_price_minor, + nonce: failed_tx.nonce, + status: TxStatus::Confirmed { + block_hash: format!("{:?}", confirmation_block.block_hash), + block_number: confirmation_block.block_number.as_u64(), + detection: Detection::Reclaim, + }, + } + } +} + +impl From<(SentTx, FailureReason)> for FailedTx { + fn from((sent_tx, failure_reason): (SentTx, FailureReason)) -> Self { + FailedTx { + hash: sent_tx.hash, + receiver_address: sent_tx.receiver_address, + amount_minor: sent_tx.amount_minor, + timestamp: sent_tx.timestamp, + gas_price_minor: sent_tx.gas_price_minor, + nonce: sent_tx.nonce, + reason: failure_reason, + status: FailureStatus::RetryRequired, + } + } +} + +#[cfg(test)] +mod tests { + use crate::accountant::db_access_objects::failed_payable_dao::{ + FailedTx, FailureReason, FailureStatus, ValidationStatus, + }; + use crate::accountant::db_access_objects::sent_payable_dao::{Detection, SentTx, TxStatus}; + use crate::accountant::db_access_objects::utils::to_unix_timestamp; + use crate::accountant::gwei_to_wei; + use crate::accountant::test_utils::make_transaction_block; + use crate::blockchain::errors::{AppRpcError, LocalError}; + use crate::blockchain::test_utils::make_tx_hash; + use crate::test_utils::make_wallet; + use std::time::{Duration, SystemTime}; + + #[test] + fn sent_tx_record_can_be_converted_from_failed_tx_record() { + let failed_tx = FailedTx { + hash: make_tx_hash(456), + receiver_address: make_wallet("abc").address(), + amount_minor: 456789012, + timestamp: 345678974, + gas_price_minor: 123456789, + nonce: 11, + reason: FailureReason::PendingTooLong, + status: FailureStatus::RetryRequired, + }; + let tx_block = make_transaction_block(789); + + let result = SentTx::from((failed_tx.clone(), tx_block)); + + assert_eq!( + result, + SentTx { + hash: make_tx_hash(456), + receiver_address: make_wallet("abc").address(), + amount_minor: 456789012, + timestamp: 345678974, + gas_price_minor: 123456789, + nonce: 11, + status: TxStatus::Confirmed { + block_hash: + "0x000000000000000000000000000000000000000000000000000000003b9acd15" + .to_string(), + block_number: 491169069, + detection: Detection::Reclaim, + }, + } + ); + } + + #[test] + fn conversion_from_sent_tx_and_failure_reason_to_failed_tx_works() { + let sent_tx = SentTx { + hash: make_tx_hash(789), + receiver_address: make_wallet("receiver").address(), + amount_minor: 123_456_789, + timestamp: to_unix_timestamp( + SystemTime::now() + .checked_sub(Duration::from_secs(10_000)) + .unwrap(), + ), + gas_price_minor: gwei_to_wei(424_u64), + nonce: 456_u64.into(), + status: TxStatus::Pending(ValidationStatus::Waiting), + }; + + let result_1 = FailedTx::from((sent_tx.clone(), FailureReason::Reverted)); + let result_2 = FailedTx::from(( + sent_tx.clone(), + FailureReason::Submission(AppRpcError::Local(LocalError::Internal)), + )); + + assert_conversion_into_failed_tx(result_1, sent_tx.clone(), FailureReason::Reverted); + assert_conversion_into_failed_tx( + result_2, + sent_tx, + FailureReason::Submission(AppRpcError::Local(LocalError::Internal)), + ); + } + + fn assert_conversion_into_failed_tx( + result: FailedTx, + original_sent_tx: SentTx, + expected_failure_reason: FailureReason, + ) { + assert_eq!(result.hash, original_sent_tx.hash); + assert_eq!(result.receiver_address, original_sent_tx.receiver_address); + assert_eq!(result.amount_minor, original_sent_tx.amount_minor); + assert_eq!(result.timestamp, original_sent_tx.timestamp); + assert_eq!(result.gas_price_minor, original_sent_tx.gas_price_minor); + assert_eq!(result.nonce, original_sent_tx.nonce); + assert_eq!(result.status, FailureStatus::RetryRequired); + assert_eq!(result.reason, expected_failure_reason); + } +} 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 7578a39a9..442e1af38 100644 --- a/node/src/accountant/db_access_objects/sent_payable_dao.rs +++ b/node/src/accountant/db_access_objects/sent_payable_dao.rs @@ -8,13 +8,13 @@ use ethereum_types::{H256}; use web3::types::Address; use masq_lib::utils::ExpectValue; use crate::accountant::{checked_conversion, comma_joined_stringifiable}; -use crate::accountant::db_access_objects::utils::{DaoFactoryReal, TxHash, TxIdentifiers}; +use crate::accountant::db_access_objects::utils::{DaoFactoryReal, TxHash, TxIdentifiers, TxRecordWithHash}; use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock}; use crate::database::rusqlite_wrappers::ConnectionWrapper; use itertools::Itertools; use serde_derive::{Deserialize, Serialize}; -use crate::accountant::db_access_objects::failed_payable_dao::ValidationStatus; +use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, ValidationStatus}; #[derive(Debug, PartialEq, Eq)] pub enum SentPayableDaoError { @@ -36,6 +36,12 @@ pub struct SentTx { pub status: TxStatus, } +impl TxRecordWithHash for SentTx { + fn hash(&self) -> TxHash { + self.hash + } +} + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum TxStatus { Pending(ValidationStatus), @@ -445,6 +451,8 @@ mod tests { use crate::accountant::db_access_objects::sent_payable_dao::RetrieveCondition::{ByHash, IsPending}; use crate::accountant::db_access_objects::sent_payable_dao::SentPayableDaoError::{EmptyInput, PartialExecution}; use crate::accountant::db_access_objects::test_utils::{make_read_only_db_connection, TxBuilder}; + use crate::accountant::db_access_objects::utils::TxRecordWithHash; + use crate::accountant::test_utils::make_sent_tx; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock}; use crate::blockchain::errors::{AppRpcError, RemoteError}; use crate::blockchain::test_utils::{make_block_hash, make_tx_hash}; @@ -1219,4 +1227,14 @@ mod tests { } ) } + + #[test] + fn tx_record_with_hash_is_implemented_for_sent_tx() { + let sent_tx = make_sent_tx(1234); + let hash = sent_tx.hash; + + let hash_from_trait = sent_tx.hash(); + + assert_eq!(hash_from_trait, hash); + } } diff --git a/node/src/accountant/db_access_objects/utils.rs b/node/src/accountant/db_access_objects/utils.rs index 8fbc875c2..21c9cdc83 100644 --- a/node/src/accountant/db_access_objects/utils.rs +++ b/node/src/accountant/db_access_objects/utils.rs @@ -46,6 +46,10 @@ pub fn from_unix_timestamp(unix_timestamp: i64) -> SystemTime { SystemTime::UNIX_EPOCH + interval } +pub trait TxRecordWithHash { + fn hash(&self) -> TxHash; +} + pub struct DaoFactoryReal { pub data_directory: PathBuf, pub init_config: DbInitializationConfig, diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index b231a0c98..a3e4387d8 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -1933,10 +1933,10 @@ mod tests { block_number: 78901234.into(), }; let tx_receipts_msg = TxReceiptsMessage { - results: vec![TxReceiptResult::Ok(RetrievedTxStatus::new( + results: vec![TxReceiptResult(Ok(RetrievedTxStatus::new( TxHashByTable::SentPayable(sent_tx.hash), StatusReadFromReceiptCheck::Succeeded(tx_block), - ))], + )))], response_skeleton_opt, }; @@ -2159,10 +2159,10 @@ mod tests { let first_counter_msg_setup = setup_for_counter_msg_triggered_via_type_id!( RequestTransactionReceipts, TxReceiptsMessage { - results: vec![TxReceiptResult::Ok(RetrievedTxStatus::new( + results: vec![TxReceiptResult(Ok(RetrievedTxStatus::new( TxHashByTable::SentPayable(sent_tx.hash), StatusReadFromReceiptCheck::Failed(BlockchainTxFailure::Unrecognized) - )),], + ))),], response_skeleton_opt }, &subject_addr @@ -2787,10 +2787,10 @@ mod tests { let subject_addr: Addr = subject.start(); let subject_subs = Accountant::make_subs_from(&subject_addr); let expected_tx_receipts_msg = TxReceiptsMessage { - results: vec![TxReceiptResult::Ok(RetrievedTxStatus::new( + results: vec![TxReceiptResult(Ok(RetrievedTxStatus::new( TxHashByTable::SentPayable(tx_hash), StatusReadFromReceiptCheck::Failed(BlockchainTxFailure::Unrecognized), - ))], + )))], response_skeleton_opt: None, }; let expected_sent_payables = SentPayables { @@ -3526,7 +3526,7 @@ mod tests { ); let requested_tx = make_tx_hash(234); let counter_msg_3 = TxReceiptsMessage { - results: vec![TxReceiptResult::Ok(tx_with_status)], + results: vec![TxReceiptResult(Ok(tx_with_status))], response_skeleton_opt: None, }; let request_transaction_receipts_msg = RequestTransactionReceipts { @@ -5292,7 +5292,7 @@ mod tests { } } - let result = TxReceiptResult::Ok(RetrievedTxStatus::new(tx_hash, status)); + let result = TxReceiptResult(Ok(RetrievedTxStatus::new(tx_hash, status))); let record_by_table = TxByTable::SentPayable(sent_tx); (result, record_by_table) } @@ -5300,7 +5300,7 @@ mod tests { let mut failed_tx = make_failed_tx(1 + idx); failed_tx.hash = hash; - let result = TxReceiptResult::Ok(RetrievedTxStatus::new(tx_hash, status)); + let result = TxReceiptResult(Ok(RetrievedTxStatus::new(tx_hash, status))); let record_by_table = TxByTable::FailedPayable(failed_tx); (result, record_by_table) } diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index 91ba4b6ea..3b6fec331 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -1054,7 +1054,7 @@ mod tests { use masq_lib::ui_gateway::NodeToUiMessage; use crate::accountant::db_access_objects::failed_payable_dao::{FailureRetrieveCondition, FailureStatus, ValidationStatus}; use crate::accountant::db_access_objects::sent_payable_dao::SentPayableDaoError; - use crate::accountant::scanners::pending_payable_scanner::utils::{FailuresRequiringDoubleCheck, PendingPayableCache, PendingPayableScanResult, TxHashByTable}; + use crate::accountant::scanners::pending_payable_scanner::utils::{RecheckRequiringFailures, PendingPayableCache, PendingPayableScanResult, TxHashByTable}; use crate::accountant::scanners::test_utils::{assert_timestamps_from_str, parse_system_time_from_str, MarkScanner, NullScanner, ReplacementType, ScannerReplacement}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{RetrievedTxStatus, TransactionBlock, StatusReadFromReceiptCheck, BlockchainTxFailure, TxReceiptResult, TxReceiptError}; use crate::blockchain::errors::{AppRpcError, RemoteError}; @@ -1187,10 +1187,10 @@ mod tests { .as_any() .downcast_ref::() .unwrap(); - let pending_payable_scanner = scanners + let mut pending_payable_scanner = scanners .pending_payable - .as_any() - .downcast_ref::() + .as_any_mut() + .downcast_mut::() .unwrap(); let receivable_scanner = scanners .receivable @@ -1216,9 +1216,13 @@ mod tests { pending_payable_scanner.common.initiated_at_opt.is_some(), false ); - assert_eq!( - pending_payable_scanner.yet_unproven_failures, - FailuresRequiringDoubleCheck::default() + let dumped_records = pending_payable_scanner + .yet_unproven_failed_payables + .dump_cache(); + assert!( + dumped_records.is_empty(), + "There should be no yet unproven failures but found {:?}.", + dumped_records ); let retrieve_txs_params = retrieve_txs_params_arc.lock().unwrap(); assert_eq!( @@ -1282,7 +1286,7 @@ mod tests { let config_dao_mock = ConfigDaoMock::new().set_result(Ok(())); let config_dao_factory = ConfigDaoFactoryMock::new().make_result(config_dao_mock); - let scanners = Scanners::new( + let mut scanners = Scanners::new( DaoFactories { payable_dao_factory: Box::new(payable_dao_factory), sent_payable_dao_factory: Box::new(sent_payable_dao_factory), @@ -1295,22 +1299,23 @@ mod tests { Rc::new(RefCell::new(FinancialStatistics::default())), ); - let pending_payable_scanner = scanners - .pending_payable - .as_any() - .downcast_ref::() - .unwrap(); let retrieve_txs_params = retrieve_txs_params_arc.lock().unwrap(); assert_eq!( *retrieve_txs_params, vec![Some(FailureRetrieveCondition::EveryRecheckRequiredRecord)] ); - let mut expected_failures = FailuresRequiringDoubleCheck::default(); - expected_failures - .load_cache(hashmap!(failed_tx_1.hash => failed_tx_1, failed_tx_2.hash => failed_tx_2)); + let mut pending_payable_scanner = scanners + .pending_payable + .as_any_mut() + .downcast_mut::() + .unwrap(); + let mut expected_failures = RecheckRequiringFailures::default(); + expected_failures.load_cache(vec![failed_tx_1, failed_tx_2]); assert_eq!( - pending_payable_scanner.yet_unproven_failures, - expected_failures + pending_payable_scanner + .yet_unproven_failed_payables + .dump_cache(), + expected_failures.dump_cache() ); } @@ -2279,8 +2284,8 @@ mod tests { .exists_no_log_containing(&format!("DEBUG: {test_name}: Paying qualified debts")); } - //TODO inspire yourself to write the right tests for the pending payable scanner when it starts - // #[test] + // //TODO inspire yourself to write the right tests for the pending payable scanner when it starts + // // #[test] // fn scan_for_pending_payables_finds_new_pending_payables() { // init_test_logging(); // let now = SystemTime::now(); @@ -2346,7 +2351,7 @@ mod tests { // } // // #[test] - // fn scan_for_pending_payables_finds_new_pending_payable_and_unrechecked_failed_payable() { + // fn scan_for_pending_payables_finds_new_pending_payable_and_unproven_failed_payable() { // init_test_logging(); // let now = SystemTime::now(); // let retrieve_pending_txs_params_arc = Arc::new(Mutex::new(vec![])); @@ -2413,7 +2418,6 @@ mod tests { // receipts for: 1 pending and 2 failed that require recheck"); // } - //TODO write two tests instead, according those two above #[test] fn pending_payable_scanner_can_initiate_a_scan() { init_test_logging(); @@ -2707,12 +2711,12 @@ mod tests { ); let msg = TxReceiptsMessage { results: vec![ - TxReceiptResult::Ok(transaction_with_status_1), - TxReceiptResult::Ok(transaction_with_status_2), - TxReceiptResult::Ok(transaction_with_status_3), - TxReceiptResult::Err(tx_receipt_rpc_error_4), - TxReceiptResult::Err(tx_receipt_rpc_error_5), - TxReceiptResult::Ok(transaction_with_status_6), + TxReceiptResult(Ok(transaction_with_status_1)), + TxReceiptResult(Ok(transaction_with_status_2)), + TxReceiptResult(Ok(transaction_with_status_3)), + TxReceiptResult(Err(tx_receipt_rpc_error_4)), + TxReceiptResult(Err(tx_receipt_rpc_error_5)), + TxReceiptResult(Ok(transaction_with_status_6)), ], response_skeleton_opt: None, }; diff --git a/node/src/accountant/scanners/pending_payable_scanner/mod.rs b/node/src/accountant/scanners/pending_payable_scanner/mod.rs index 8da9e5d49..f83082e0d 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/mod.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/mod.rs @@ -9,8 +9,8 @@ use crate::accountant::db_access_objects::payable_dao::{PayableDao, PayableDaoEr use crate::accountant::db_access_objects::sent_payable_dao::{ RetrieveCondition, SentPayableDao, SentPayableDaoError, SentTx, }; -use crate::accountant::db_access_objects::utils::TxHash; -use crate::accountant::scanners::pending_payable_scanner::utils::{handle_status_with_failure, CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailuresRequiringDoubleCheck, PendingPayableScanResult, PresortedTxFailure, ReceiptScanReport, Retry, TxCaseToBeInterpreted, FailedValidationByTable, TxReclaim, NormalTxConfirmation}; +use crate::accountant::db_access_objects::utils::{TxHash, TxRecordWithHash}; +use crate::accountant::scanners::pending_payable_scanner::utils::{CurrentPendingPayables, DetectedConfirmations, DetectedFailures, RecheckRequiringFailures, PendingPayableScanResult, PresortedTxFailure, ReceiptScanReport, Retry, TxCaseToBeInterpreted, FailedValidationByTable, TxReclaim, NormalTxConfirmation, PendingPayableCache, TxByTable, TxHashByTable, MismatchReport}; use crate::accountant::scanners::{ PrivateScanner, Scanner, ScannerCommon, StartScanError, StartableScanner, }; @@ -21,7 +21,7 @@ use crate::accountant::{ use crate::sub_lib::accountant::{FinancialStatistics, PaymentThresholds}; use crate::sub_lib::wallet::Wallet; use crate::time_marking_methods; -use itertools::Itertools; +use itertools::{Either, Itertools}; use masq_lib::logger::Logger; use masq_lib::messages::{ScanType, ToMessageBody, UiScanResponse}; use masq_lib::ui_gateway::{MessageTarget, NodeToUiMessage}; @@ -30,7 +30,7 @@ use std::collections::HashMap; use std::rc::Rc; use std::time::SystemTime; use thousands::Separable; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::TransactionBlock; +use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, TxReceiptResult}; pub struct PendingPayableScanner { pub common: ScannerCommon, @@ -38,8 +38,8 @@ pub struct PendingPayableScanner { pub sent_payable_dao: Box, pub failed_payable_dao: Box, pub financial_statistics: Rc>, - pub current_sent_payables: CurrentPendingPayables, - pub yet_unproven_failures: FailuresRequiringDoubleCheck, + pub current_sent_payables: Box>, + pub yet_unproven_failed_payables: Box>, } impl @@ -65,36 +65,31 @@ impl StartableScanner self.mark_as_started(timestamp); info!(logger, "Scanning for pending payable"); - let pending_sent_txs = self - .sent_payable_dao - .retrieve_txs(Some(RetrieveCondition::IsPending)); + let pending_tx_hashes_opt = self.handle_pending_payables(); + let failure_hashes_opt = self.handle_unproven_failures(); - let unproven_failures = self - .failed_payable_dao - .retrieve_txs(Some(FailureRetrieveCondition::EveryRecheckRequiredRecord)); + if pending_tx_hashes_opt.is_none() && failure_hashes_opt.is_none() { + todo!() + // self.mark_as_ended(logger); + // Err(StartScanError::NothingToProcess) + } - // TODO 1) check non-empty collections - // 2) fill in the respective caches - // 3) form a joint collection for the message - todo!("fix me"); - - // match pending_sent_txs.is_empty() { - // true => { - // self.mark_as_ended(logger); - // Err(StartScanError::NothingToProcess) - // } - // false => { - // debug!( - // logger, - // "Found {} pending payables to process", - // pending_sent_txs.len() - // ); - // Ok(RequestTransactionReceipts { - // tx_hashes: pending_sent_txs, - // response_skeleton_opt, - // }) - // } - // } + Self::log_records_found_for_receipt_check( + &pending_tx_hashes_opt, + &failure_hashes_opt, + logger, + ); + + let all_hashes = pending_tx_hashes_opt + .unwrap_or_default() + .into_iter() + .chain(failure_hashes_opt.unwrap_or_default()) + .collect_vec(); + + Ok(RequestTransactionReceipts { + tx_hashes: all_hashes, + response_skeleton_opt, + }) } } @@ -130,7 +125,7 @@ impl PendingPayableScanner { payment_thresholds: Rc, financial_statistics: Rc>, ) -> Self { - // let yet_unproven_failures = FailuresRequiringDoubleCheck::new( + // let yet_unproven_failed_payables = RecheckRequiringFailures::new( // failed_payable_dao.retrieve_txs(Some(FailureRetrieveCondition::ByStatus( // todo!(), //FailureRetrieveCondition::EveryRecheckRequiredRecord // ))), @@ -142,11 +137,61 @@ impl PendingPayableScanner { sent_payable_dao, failed_payable_dao, financial_statistics, - current_sent_payables: CurrentPendingPayables::default(), - yet_unproven_failures: FailuresRequiringDoubleCheck::default(), + current_sent_payables: Box::new(CurrentPendingPayables::default()), + yet_unproven_failed_payables: Box::new(RecheckRequiringFailures::default()), + } + } + + fn handle_pending_payables(&mut self) -> Option> { + let pending_txs = self + .sent_payable_dao + .retrieve_txs(Some(RetrieveCondition::IsPending)); + + if pending_txs.is_empty() { + todo!() //return None + } else { + let pending_tx_hashes = + Self::get_wrapped_hashes(&pending_txs, TxHashByTable::SentPayable); + + if !pending_txs.is_empty() { + self.current_sent_payables.load_cache(pending_txs) + } + + Some(pending_tx_hashes) } } + fn handle_unproven_failures(&mut self) -> Option> { + let failures = self + .failed_payable_dao + .retrieve_txs(Some(FailureRetrieveCondition::EveryRecheckRequiredRecord)); + + if failures.is_empty() { + todo!() //return None + } else { + let failure_hashes = Self::get_wrapped_hashes(&failures, TxHashByTable::FailedPayable); + + if !failures.is_empty() { + self.yet_unproven_failed_payables.load_cache(failures) + } + + Some(failure_hashes) + } + } + + fn get_wrapped_hashes( + records: &[Record], + wrap_the_hash: fn(TxHash) -> TxHashByTable, + ) -> Vec + where + Record: TxRecordWithHash, + { + records + .iter() + .map(|record| wrap_the_hash(record.hash())) + .collect_vec() + } + fn emptiness_check(&self, msg: &TxReceiptsMessage) { if msg.results.is_empty() { unreachable!("We should never receive an empty list of results. Even missing receipts can be interpreted") @@ -162,13 +207,95 @@ impl PendingPayableScanner { debug!(logger, "Processing receipts for {} txs", msg.results.len()); - let interpretable_data = self.prepare_cases_to_interpret(msg); + let interpretable_data = self.prepare_cases_to_interpret(msg, logger); self.compose_receipt_scan_report(interpretable_data, logger) } - fn prepare_cases_to_interpret(&mut self, msg: TxReceiptsMessage) -> Vec { - //TODO pull the records out from the caches and leave them empty - todo!() + fn prepare_cases_to_interpret( + &mut self, + msg: TxReceiptsMessage, + logger: &Logger, + ) -> Vec { + let init: Either, MismatchReport> = Either::Left(vec![]); + let either = msg + .results + .into_iter() + .fold(init, |mut acc, receipt_result| match acc { + Either::Left(cases) => { + let tx_hash = receipt_result.hash(); + + self.resolve_real_query(cases, receipt_result, tx_hash) + } + Either::Right(mut mismatch_report) => { + mismatch_report.remaining_hashes.push(receipt_result.hash()); + Either::Right(mismatch_report) + } + }); + + let cases = match either { + Either::Left(cases) => cases, + Either::Right(mismatch_report) => self.panic_dump(mismatch_report), + }; + + self.current_sent_payables.ensure_empty_cache(logger); + self.yet_unproven_failed_payables.ensure_empty_cache(logger); + + cases + } + + fn resolve_real_query( + &mut self, + mut cases: Vec, + receipt_result: TxReceiptResult, + looked_up_hash: TxHashByTable, /* current_sent_payables: &mut dyn PendingPayableCache, yet_unproven_failed_payables: &mut dyn PendingPayableCache*/ + ) -> Either, MismatchReport> { + match looked_up_hash { + TxHashByTable::SentPayable(tx_hash) => { + match self.current_sent_payables.get_record_by_hash(tx_hash) { + Some(sent_tx) => { + cases.push(TxCaseToBeInterpreted::new( + TxByTable::SentPayable(sent_tx), + receipt_result, + )); + Either::Left(cases) + } + None => Either::Right(MismatchReport { + noticed_at: looked_up_hash, + remaining_hashes: vec![], + }), + } + } + TxHashByTable::FailedPayable(tx_hash) => { + match self + .yet_unproven_failed_payables + .get_record_by_hash(tx_hash) + { + Some(failed_tx) => { + cases.push(TxCaseToBeInterpreted::new( + TxByTable::FailedPayable(failed_tx), + receipt_result, + )); + Either::Left(cases) + } + None => Either::Right(MismatchReport { + noticed_at: looked_up_hash, + remaining_hashes: vec![], + }), + } + } + } + } + + fn panic_dump(&mut self, mismatch_report: MismatchReport) -> ! { + panic!( + "Looking up '{:?}' in the cache, the record could not be found. Dumping \ + the remaining values. Pending payables: {:?}. Unproven failures: {:?}. \ + All yet-to-look-up hashes: {:?}.", + mismatch_report.noticed_at, + self.current_sent_payables.dump_cache(), + self.yet_unproven_failed_payables.dump_cache(), + mismatch_report.remaining_hashes + ) } fn compose_receipt_scan_report( @@ -186,7 +313,7 @@ impl PendingPayableScanner { // TxReceiptResult::Ok(sent_tx_with_status) => match sent_tx_with_status.status { // StatusReadFromReceiptCheck::Succeeded(tx_block) => handle_successful_tx( // scan_report_so_far, - // self.yet_unproven_failures.hashes(), + // self.yet_unproven_failed_payables.hashes(), // sent_tx_with_status.tx_hash, // tx_block, // logger, @@ -205,7 +332,7 @@ impl PendingPayableScanner { // }, // TxReceiptResult::Err(e) => handle_rpc_failure( // scan_report_so_far, - // self.yet_unproven_failures.hashes(), + // self.yet_unproven_failed_payables.hashes(), // e, // logger, // ), @@ -228,7 +355,9 @@ impl PendingPayableScanner { } fn handle_tx_failure_reclaims(&self, reclaimed: Vec, logger: &Logger) { - if reclaimed.is_empty() {todo!()} + if reclaimed.is_empty() { + todo!() + } todo!() } @@ -367,12 +496,33 @@ impl PendingPayableScanner { PendingPayableScanResult::NoPendingPayablesLeft(ui_msg_opt) } } + + fn log_records_found_for_receipt_check( + pending_tx_hashes_opt: &Option>, + failure_hashes_opt: &Option>, + logger: &Logger, + ) { + debug!( + logger, + "Found {} pending payables and {} unfinalized failures to be checked", + pending_tx_hashes_opt + .as_ref() + .map(|hashes| hashes.len()) + .unwrap_or_default(), + failure_hashes_opt + .as_ref() + .map(|hashes| hashes.len()) + .unwrap_or_default(), + ); + } } #[cfg(test)] mod tests { + use std::panic::{catch_unwind, AssertUnwindSafe}; use std::sync::{Arc, Mutex}; use std::time::{Duration, SystemTime}; + use regex::Regex; use masq_lib::logger::Logger; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; use crate::accountant::db_access_objects::failed_payable_dao::{FailedPayableDaoError, FailedTx, FailureReason, FailureRetrieveCondition, FailureStatus, ValidationStatus}; @@ -380,14 +530,314 @@ mod tests { use crate::accountant::db_access_objects::sent_payable_dao::{Detection, RetrieveCondition, SentPayableDaoError, TxStatus}; use crate::accountant::db_access_objects::utils::{from_unix_timestamp, to_unix_timestamp}; use crate::accountant::scanners::pending_payable_scanner::PendingPayableScanner; - use crate::accountant::scanners::pending_payable_scanner::utils::{handle_status_with_failure, DetectedConfirmations, DetectedFailures, FailedValidation, FailedValidationByTable, NormalTxConfirmation, PendingPayableCache, PresortedTxFailure, ReceiptScanReport, TxByTable, TxHashByTable, TxReclaim}; + use crate::accountant::scanners::pending_payable_scanner::utils::{handle_status_with_failure, CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailedValidation, FailedValidationByTable, NormalTxConfirmation, PendingPayableCache, PendingPayableScanResult, PresortedTxFailure, ReceiptScanReport, RecheckRequiringFailures, Retry, TxByTable, TxHashByTable, TxReclaim}; use crate::accountant::test_utils::{make_failed_tx, make_sent_tx, make_transaction_block, FailedPayableDaoMock, PayableDaoMock, PendingPayableScannerBuilder, SentPayableDaoMock}; - use crate::accountant::{ResponseSkeleton, TxReceiptsMessage}; + use crate::accountant::{RequestTransactionReceipts, ResponseSkeleton, TxReceiptsMessage}; + use crate::accountant::scanners::{Scanner, StartableScanner}; + use crate::accountant::scanners::test_utils::PendingPayableCacheMock; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{BlockchainTxFailure, RetrievedTxStatus, StatusReadFromReceiptCheck, TransactionBlock, TxReceiptError, TxReceiptResult}; use crate::blockchain::errors::{AppRpcError, LocalError, RemoteError}; use crate::blockchain::test_utils::{make_block_hash, make_tx_hash}; + use crate::test_utils::make_wallet; use crate::test_utils::unshared_test_utils::capture_numbers_with_separators_from_str; + #[test] + fn start_scan_fills_in_caches_and_returns_msg() { + let sent_tx_1 = make_sent_tx(456); + let sent_tx_hash_1 = sent_tx_1.hash; + let sent_tx_2 = make_sent_tx(789); + let sent_tx_hash_2 = sent_tx_2.hash; + let failed_tx_1 = make_failed_tx(567); + let failed_tx_hash_1 = failed_tx_1.hash; + let failed_tx_2 = make_failed_tx(890); + let failed_tx_hash_2 = failed_tx_2.hash; + let sent_payable_dao = SentPayableDaoMock::new() + .retrieve_txs_result(vec![sent_tx_1.clone(), sent_tx_2.clone()]); + let failed_payable_dao = FailedPayableDaoMock::new() + .retrieve_txs_result(vec![failed_tx_1.clone(), failed_tx_2.clone()]); + let mut subject = PendingPayableScannerBuilder::new() + .sent_payable_dao(sent_payable_dao) + .failed_payable_dao(failed_payable_dao) + .build(); + let logger = Logger::new("start_scan_fills_in_caches_and_returns_msg"); + let pending_payable_cache_before = subject.current_sent_payables.dump_cache(); + let failed_payable_cache_before = subject.yet_unproven_failed_payables.dump_cache(); + + let result = subject.start_scan(&make_wallet("bluh"), SystemTime::now(), None, &logger); + + assert_eq!( + result, + Ok(RequestTransactionReceipts { + tx_hashes: vec![ + TxHashByTable::SentPayable(sent_tx_hash_1), + TxHashByTable::SentPayable(sent_tx_hash_2), + TxHashByTable::FailedPayable(failed_tx_hash_1), + TxHashByTable::FailedPayable(failed_tx_hash_2) + ], + response_skeleton_opt: None + }) + ); + assert!( + pending_payable_cache_before.is_empty(), + "Should have been empty but {:?}", + pending_payable_cache_before + ); + assert!( + failed_payable_cache_before.is_empty(), + "Should have been empty but {:?}", + failed_payable_cache_before + ); + let pending_payable_cache_after = subject.current_sent_payables.dump_cache(); + let failed_payable_cache_after = subject.yet_unproven_failed_payables.dump_cache(); + assert_eq!( + pending_payable_cache_after, + hashmap!(sent_tx_hash_1 => sent_tx_1, sent_tx_hash_2 => sent_tx_2) + ); + assert_eq!( + failed_payable_cache_after, + hashmap!(failed_tx_hash_1 => failed_tx_1, failed_tx_hash_2 => failed_tx_2) + ); + } + + #[test] + fn finish_scan_clears_caches_after_use() { + let pending_payable_ensure_empty_cache_params_arc = Arc::new(Mutex::new(vec![])); + let failed_payable_ensure_empty_cache_params_arc = Arc::new(Mutex::new(vec![])); + // To confirm a fresh, pending tx + let confirm_tx_params_arc = Arc::new(Mutex::new(vec![])); + // To reclaim a confirmed tx believed having been a failure + let replace_records_params_arc = Arc::new(Mutex::new(vec![])); + // To register a failed tx as it's pending too long + let insert_new_failed_records_params_arc = Arc::new(Mutex::new(vec![])); + // To update the validation status at a tx whose receipt couldn't be fetched + let update_statuses_params_arc = Arc::new(Mutex::new(vec![])); + let sent_tx_1 = make_sent_tx(456); + let sent_tx_hash_1 = sent_tx_1.hash; + let sent_tx_2 = make_sent_tx(789); + let sent_tx_hash_2 = sent_tx_2.hash; + let failed_tx_1 = make_failed_tx(567); + let failed_tx_hash_1 = failed_tx_1.hash; + let failed_tx_2 = make_failed_tx(890); + let failed_tx_hash_2 = failed_tx_2.hash; + let sent_payable_dao = SentPayableDaoMock::new() + .confirm_tx_params(&confirm_tx_params_arc) + .replace_records_params(&replace_records_params_arc); + let failed_payable_dao = FailedPayableDaoMock::new() + .insert_new_records_params(&insert_new_failed_records_params_arc) + .update_statuses_params(&update_statuses_params_arc); + let pending_payable_cache = PendingPayableCacheMock::default() + .get_record_by_hash_result(Some(sent_tx_1.clone())) + .get_record_by_hash_result(Some(sent_tx_2)) + .ensure_empty_cache_params(&pending_payable_ensure_empty_cache_params_arc); + let failed_payable_cache = PendingPayableCacheMock::default() + .get_record_by_hash_result(Some(failed_tx_1)) + .get_record_by_hash_result(Some(failed_tx_2)) + .ensure_empty_cache_params(&failed_payable_ensure_empty_cache_params_arc); + let mut subject = PendingPayableScannerBuilder::new() + .pending_payables_cache(Box::new(pending_payable_cache)) + .failed_payables_cache(Box::new(failed_payable_cache)) + .build(); + let logger = Logger::new("test"); + let confirmed_tx_block_sent_tx = make_transaction_block(901); + let confirmed_tx_block_failed_tx = make_transaction_block(902); + let msg = TxReceiptsMessage { + results: vec![ + TxReceiptResult(Ok(RetrievedTxStatus::new( + TxHashByTable::SentPayable(sent_tx_hash_1), + StatusReadFromReceiptCheck::Pending, + ))), + TxReceiptResult(Ok(RetrievedTxStatus::new( + TxHashByTable::SentPayable(sent_tx_hash_2), + StatusReadFromReceiptCheck::Succeeded(confirmed_tx_block_sent_tx), + ))), + TxReceiptResult(Err(TxReceiptError::new( + TxHashByTable::FailedPayable(failed_tx_hash_1), + AppRpcError::Local(LocalError::Internal), + ))), + TxReceiptResult(Ok(RetrievedTxStatus::new( + TxHashByTable::FailedPayable(failed_tx_hash_2), + StatusReadFromReceiptCheck::Succeeded(confirmed_tx_block_failed_tx), + ))), + ], + response_skeleton_opt: None, + }; + + let result = subject.finish_scan(msg, &logger); + + //TODO write db assertion for processing both records + assert_eq!( + result, + PendingPayableScanResult::PaymentRetryRequired(Retry::RetryPayments) + ); + assert!( + subject.current_sent_payables.dump_cache().is_empty(), + "Sent payable cache should have been emptied but {:?}", + subject.current_sent_payables.dump_cache() + ); + assert!( + subject.yet_unproven_failed_payables.dump_cache().is_empty(), + "Failed payable cache Should have been emptied but {:?}", + subject.yet_unproven_failed_payables.dump_cache() + ); + let pending_payable_ensure_empty_cache_params = + pending_payable_ensure_empty_cache_params_arc + .lock() + .unwrap(); + assert_eq!(*pending_payable_ensure_empty_cache_params, vec![()]); + let failed_payable_ensure_empty_cache_params = + failed_payable_ensure_empty_cache_params_arc.lock().unwrap(); + assert_eq!(*failed_payable_ensure_empty_cache_params, vec![()]); + let confirm_tx_params = confirm_tx_params_arc.lock().unwrap(); + assert_eq!( + *confirm_tx_params, + vec![hashmap!(sent_tx_hash_1 => confirmed_tx_block_sent_tx)] + ); + let insert_new_failed_records_params = insert_new_failed_records_params_arc.lock().unwrap(); + assert_eq!( + *insert_new_failed_records_params, + vec![vec![FailedTx::from(( + sent_tx_1, + FailureReason::PendingTooLong + ))]] + ); + let update_statuses_params = update_statuses_params_arc.lock().unwrap(); + assert_eq!( + *update_statuses_params, + vec![ + hashmap!(failed_tx_hash_1 => FailureStatus::RecheckRequired(ValidationStatus::Reattempting { attempt: 1, error: AppRpcError::Local(LocalError::Internal)})) + ] + ); + let replace_records_params = replace_records_params_arc.lock().unwrap(); + assert_eq!(*replace_records_params, vec![vec![]]); + } + + #[test] + fn finish_scan_with_missing_records_inside_caches_noticed_on_missing_sent_tx() { + let sent_tx_1 = make_sent_tx(456); + let sent_tx_hash_1 = sent_tx_1.hash; + let sent_tx_hash_2 = make_tx_hash(777); + let failed_tx_1 = make_failed_tx(567); + let failed_tx_hash_1 = failed_tx_1.hash; + let failed_tx_2 = make_failed_tx(890); + let failed_tx_hash_2 = failed_tx_2.hash; + let mut pending_payable_cache = CurrentPendingPayables::default(); + pending_payable_cache.load_cache(vec![sent_tx_1]); + let mut failed_payable_cache = RecheckRequiringFailures::default(); + failed_payable_cache.load_cache(vec![failed_tx_1, failed_tx_2]); + let mut subject = PendingPayableScannerBuilder::new().build(); + subject.current_sent_payables = Box::new(pending_payable_cache); + subject.yet_unproven_failed_payables = Box::new(failed_payable_cache); + let logger = Logger::new("test"); + let msg = TxReceiptsMessage { + results: vec![ + TxReceiptResult(Ok(RetrievedTxStatus::new( + TxHashByTable::SentPayable(sent_tx_hash_1), + StatusReadFromReceiptCheck::Pending, + ))), + TxReceiptResult(Ok(RetrievedTxStatus::new( + TxHashByTable::SentPayable(sent_tx_hash_2), + StatusReadFromReceiptCheck::Succeeded(make_transaction_block(444)), + ))), + TxReceiptResult(Err(TxReceiptError::new( + TxHashByTable::FailedPayable(failed_tx_hash_1), + AppRpcError::Local(LocalError::Internal), + ))), + TxReceiptResult(Ok(RetrievedTxStatus::new( + TxHashByTable::FailedPayable(failed_tx_hash_2), + StatusReadFromReceiptCheck::Succeeded(make_transaction_block(555)), + ))), + ], + response_skeleton_opt: None, + }; + + let panic = + catch_unwind(AssertUnwindSafe(|| subject.finish_scan(msg, &logger))).unwrap_err(); + + let panic_msg = panic.downcast_ref::().unwrap(); + let regex_str_in_pieces = vec![ + r#"Looking up 'SentPayable\(0x0000000000000000000000000000000000000000000000000000000000000309\)'"#, + r#" in the cache, the record could not be found. Dumping the remaining values. Pending payables: \{\}."#, + r#" Unproven failures: \{0x0000000000000000000000000000000000000000000000000000000000000237: FailedTx \{"#, + r#" hash: 0x0000000000000000000000000000000000000000000000000000000000000237, receiver_address:"#, + r#" 0x000000000000000000000077616c6c6574353637, amount_minor: 321489000000000, timestamp: \d*,"#, + r#" gas_price_minor: 567000000000, nonce: 567, reason: PendingTooLong, status: RetryRequired \},"#, + r#" 0x000000000000000000000000000000000000000000000000000000000000037a: FailedTx \{ hash:"#, + r#" 0x000000000000000000000000000000000000000000000000000000000000037a, receiver_address:"#, + r#" 0x000000000000000000000077616c6c6574383930, amount_minor: 792100000000000, timestamp: \d*,"#, + r#" gas_price_minor: 890000000000, nonce: 890, reason: PendingTooLong, status: RetryRequired \}\}."#, + r#" All yet-to-look-up hashes: \[FailedPayable\(0x000000000000000000000000000000000000000000000000000"#, + r#"0000000000237\), FailedPayable\(0x000000000000000000000000000000000000000000000000000000000000037a\)\]."#, + ]; + let regex_str = regex_str_in_pieces.join(""); + let expected_msg_regex = Regex::new(®ex_str).unwrap(); + assert!( + expected_msg_regex.is_match(panic_msg), + "Expected string that matches this regex '{}' \ + but it couldn't with '{}'", + regex_str, + panic_msg + ); + } + + #[test] + fn finish_scan_with_missing_records_inside_caches_noticed_on_missing_failed_tx() { + let sent_tx_1 = make_sent_tx(456); + let sent_tx_hash_1 = sent_tx_1.hash; + let sent_tx_2 = make_sent_tx(789); + let sent_tx_hash_2 = sent_tx_2.hash; + let failed_tx_1 = make_failed_tx(567); + let failed_tx_hash_1 = failed_tx_1.hash; + let failed_tx_hash_2 = make_tx_hash(901); + let mut pending_payable_cache = CurrentPendingPayables::default(); + pending_payable_cache.load_cache(vec![sent_tx_1, sent_tx_2]); + let mut failed_payable_cache = RecheckRequiringFailures::default(); + failed_payable_cache.load_cache(vec![failed_tx_1]); + let mut subject = PendingPayableScannerBuilder::new().build(); + subject.current_sent_payables = Box::new(pending_payable_cache); + subject.yet_unproven_failed_payables = Box::new(failed_payable_cache); + let logger = Logger::new("test"); + let msg = TxReceiptsMessage { + results: vec![ + TxReceiptResult(Ok(RetrievedTxStatus::new( + TxHashByTable::SentPayable(sent_tx_hash_1), + StatusReadFromReceiptCheck::Pending, + ))), + TxReceiptResult(Ok(RetrievedTxStatus::new( + TxHashByTable::SentPayable(sent_tx_hash_2), + StatusReadFromReceiptCheck::Succeeded(make_transaction_block(444)), + ))), + TxReceiptResult(Err(TxReceiptError::new( + TxHashByTable::FailedPayable(failed_tx_hash_1), + AppRpcError::Local(LocalError::Internal), + ))), + TxReceiptResult(Ok(RetrievedTxStatus::new( + TxHashByTable::FailedPayable(failed_tx_hash_2), + StatusReadFromReceiptCheck::Succeeded(make_transaction_block(555)), + ))), + ], + response_skeleton_opt: None, + }; + + let panic = + catch_unwind(AssertUnwindSafe(|| subject.finish_scan(msg, &logger))).unwrap_err(); + + let panic_msg = panic.downcast_ref::().unwrap(); + let regex_str_in_pieces = vec![ + r#"Looking up 'FailedPayable\(0x0000000000000000000000000000000000000000000000000000000000000385\)'"#, + r#" in the cache, the record could not be found. Dumping the remaining values. Pending payables: \{\}."#, + r#" Unproven failures: \{\}. All yet-to-look-up hashes: \[\]."#, + ]; + let regex_str = regex_str_in_pieces.join(""); + let expected_msg_regex = Regex::new(®ex_str).unwrap(); + assert!( + expected_msg_regex.is_match(panic_msg), + "Expected string that matches this regex '{}' \ + but it couldn't with '{}'", + regex_str, + panic_msg + ); + } + #[test] fn interprets_tx_receipt_when_transaction_status_is_a_failure() { init_test_logging(); @@ -441,23 +891,23 @@ mod tests { let failed_tx = make_failed_tx(789); let msg = TxReceiptsMessage { results: vec![ - TxReceiptResult::Ok(RetrievedTxStatus::new( + TxReceiptResult(Ok(RetrievedTxStatus::new( TxHashByTable::SentPayable(sent_tx.hash), StatusReadFromReceiptCheck::Pending, - )), - TxReceiptResult::Ok(RetrievedTxStatus::new( + ))), + TxReceiptResult(Ok(RetrievedTxStatus::new( TxHashByTable::FailedPayable(failed_tx.hash), StatusReadFromReceiptCheck::Pending, - )), + ))), ], response_skeleton_opt: None, }; subject .current_sent_payables - .load_cache(hashmap!(sent_tx.hash => sent_tx.clone())); + .load_cache(vec![sent_tx.clone()]); subject - .yet_unproven_failures - .load_cache(hashmap!(failed_tx.hash => failed_tx)); + .yet_unproven_failed_payables + .load_cache(vec![failed_tx]); let before = SystemTime::now(); let result = subject.interpret_tx_receipts(msg, &Logger::new(test_name)); @@ -519,14 +969,14 @@ mod tests { let rpc_error_2 = AppRpcError::Local(LocalError::Internal); let msg = TxReceiptsMessage { results: vec![ - TxReceiptResult::Err(TxReceiptError::new( + TxReceiptResult(Err(TxReceiptError::new( TxHashByTable::SentPayable(tx_hash_1), rpc_error_1.clone(), - )), - TxReceiptResult::Err(TxReceiptError::new( + ))), + TxReceiptResult(Err(TxReceiptError::new( TxHashByTable::FailedPayable(tx_hash_2), rpc_error_2.clone(), - )), + ))), ], response_skeleton_opt: None, }; @@ -665,22 +1115,20 @@ mod tests { let update_status_params = update_status_params_arc.lock().unwrap(); assert_eq!( *update_status_params, - vec![ - hashmap!( - hash_1 => FailureStatus::RecheckRequired( - ValidationStatus::Reattempting { - attempt: 1, - error: AppRpcError::Remote(RemoteError::Unreachable) - } - ), - hash_2 => FailureStatus::RecheckRequired( - ValidationStatus::Reattempting { - attempt: 2, - error: AppRpcError::Local(LocalError::Internal) - } - ) + vec![hashmap!( + hash_1 => FailureStatus::RecheckRequired( + ValidationStatus::Reattempting { + attempt: 1, + error: AppRpcError::Remote(RemoteError::Unreachable) + } + ), + hash_2 => FailureStatus::RecheckRequired( + ValidationStatus::Reattempting { + attempt: 2, + error: AppRpcError::Local(LocalError::Internal) + } ) - ] + )] ); let retrieve_sent_txs_params = retrieve_sent_txs_params_arc.lock().unwrap(); assert_eq!( diff --git a/node/src/accountant/scanners/pending_payable_scanner/utils.rs b/node/src/accountant/scanners/pending_payable_scanner/utils.rs index c03aea5a0..928f91f90 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/utils.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/utils.rs @@ -8,7 +8,7 @@ use thousands::Separable; use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, FailureReason, FailureStatus}; use crate::accountant::db_access_objects::sent_payable_dao::{SentTx, TxStatus}; use crate::accountant::db_access_objects::utils::{from_unix_timestamp, TxHash}; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{BlockchainTxFailure, StatusReadFromReceiptCheck, TransactionBlock, TxReceiptError}; +use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{BlockchainTxFailure, StatusReadFromReceiptCheck, TransactionBlock, TxReceiptError, TxReceiptResult}; use crate::blockchain::errors::AppRpcError; #[derive(Debug, Default, PartialEq, Eq, Clone)] @@ -109,12 +109,16 @@ pub struct FailedValidation { pub failure: AppRpcError, } +pub struct MismatchReport { + pub noticed_at: TxHashByTable, + pub remaining_hashes: Vec, +} + pub trait PendingPayableCache { - fn load_cache(&mut self, records: Collection) - where - Collection: IntoIterator; + fn load_cache(&mut self, records: Vec); fn get_record_by_hash(&mut self, hashes: TxHash) -> Option; fn ensure_empty_cache(&mut self, logger: &Logger); + fn dump_cache(&mut self) -> HashMap; } #[derive(Debug, PartialEq, Eq, Default)] @@ -123,11 +127,9 @@ pub struct CurrentPendingPayables { } impl PendingPayableCache for CurrentPendingPayables { - fn load_cache(&mut self, records: Collection) - where - Collection: IntoIterator, - { - self.sent_payables.extend(records); + fn load_cache(&mut self, records: Vec) { + self.sent_payables + .extend(records.into_iter().map(|tx| (tx.hash, tx))); } fn get_record_by_hash(&mut self, hashes: TxHash) -> Option { @@ -144,6 +146,10 @@ impl PendingPayableCache for CurrentPendingPayables { } self.sent_payables.clear() } + + fn dump_cache(&mut self) -> HashMap { + self.sent_payables.drain().collect() + } } impl CurrentPendingPayables { @@ -153,16 +159,14 @@ impl CurrentPendingPayables { } #[derive(Debug, PartialEq, Eq, Default)] -pub struct FailuresRequiringDoubleCheck { +pub struct RecheckRequiringFailures { pub(super) failures: HashMap, } -impl PendingPayableCache for FailuresRequiringDoubleCheck { - fn load_cache(&mut self, records: Collection) - where - Collection: IntoIterator, - { - self.failures.extend(records); +impl PendingPayableCache for RecheckRequiringFailures { + fn load_cache(&mut self, records: Vec) { + self.failures + .extend(records.into_iter().map(|tx| (tx.hash, tx))); } fn get_record_by_hash(&mut self, hashes: TxHash) -> Option { @@ -178,9 +182,13 @@ impl PendingPayableCache for FailuresRequiringDoubleCheck { } self.failures.clear() } + + fn dump_cache(&mut self) -> HashMap { + self.failures.drain().collect() + } } -impl FailuresRequiringDoubleCheck { +impl RecheckRequiringFailures { pub fn new() -> Self { Self::default() } @@ -204,7 +212,16 @@ pub enum Retry { pub struct TxCaseToBeInterpreted { pub tx_by_table: TxByTable, - pub retrieved_status: StatusReadFromReceiptCheck, + pub tx_receipt_result: TxReceiptResult, +} + +impl TxCaseToBeInterpreted { + pub fn new(tx_by_table: TxByTable, tx_receipt_result: TxReceiptResult) -> Self { + Self { + tx_by_table, + tx_receipt_result, + } + } } pub enum TxByTable { @@ -342,29 +359,14 @@ impl From for FailureReason { } } -impl From<(SentTx, FailureReason)> for FailedTx { - fn from((sent_tx, failure_reason): (SentTx, FailureReason)) -> Self { - FailedTx { - hash: sent_tx.hash, - receiver_address: sent_tx.receiver_address, - amount_minor: sent_tx.amount_minor, - timestamp: sent_tx.timestamp, - gas_price_minor: sent_tx.gas_price_minor, - nonce: sent_tx.nonce, - reason: failure_reason, - status: FailureStatus::RetryRequired, - } - } -} - #[cfg(test)] mod tests { use crate::accountant::db_access_objects::sent_payable_dao::Detection; use crate::accountant::db_access_objects::sent_payable_dao::Detection::Normal; use crate::accountant::scanners::pending_payable_scanner::utils::{ CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailedValidation, - FailedValidationByTable, FailuresRequiringDoubleCheck, NormalTxConfirmation, - PendingPayableCache, PresortedTxFailure, ReceiptScanReport, Retry, TxReclaim, + FailedValidationByTable, NormalTxConfirmation, PendingPayableCache, PresortedTxFailure, + ReceiptScanReport, RecheckRequiringFailures, Retry, TxReclaim, }; use crate::accountant::test_utils::{make_failed_tx, make_sent_tx, make_transaction_block}; use crate::blockchain::errors::{AppRpcError, LocalError, RemoteError}; @@ -605,10 +607,10 @@ mod tests { let mut subject = CurrentPendingPayables::new(); let sent_tx = make_sent_tx(123); let tx_hash = sent_tx.hash; - let records = vec![(tx_hash, sent_tx.clone())]; - + let records = vec![sent_tx.clone()]; let state_before = subject.sent_payables.clone(); subject.load_cache(records); + let first_attempt = subject.get_record_by_hash(tx_hash); let second_attempt = subject.get_record_by_hash(tx_hash); @@ -635,10 +637,10 @@ mod tests { let tx_hash_4 = sent_tx_4.hash; let nonexistent_tx_hash = make_tx_hash(234); let records = vec![ - (tx_hash_1, sent_tx_1.clone()), - (tx_hash_2, sent_tx_2.clone()), - (tx_hash_3, sent_tx_3.clone()), - (tx_hash_4, sent_tx_4.clone()), + sent_tx_1.clone(), + sent_tx_2.clone(), + sent_tx_3.clone(), + sent_tx_4.clone(), ]; let first_query = subject.get_record_by_hash(tx_hash_1); @@ -673,16 +675,16 @@ mod tests { let mut subject = CurrentPendingPayables::new(); let sent_tx = make_sent_tx(567); let tx_hash = sent_tx.hash; - let records = vec![(tx_hash, sent_tx.clone())]; + let records = vec![sent_tx.clone()]; let logger = Logger::new(test_name); - subject.load_cache(records); let _ = subject.get_record_by_hash(tx_hash); + subject.ensure_empty_cache(&logger); assert!( subject.sent_payables.is_empty(), - "Should be empty but was {:?}", + "Should be empty by now but was {:?}", subject.sent_payables ); TestLogHandler::default().exists_no_log_containing(&format!( @@ -699,15 +701,15 @@ mod tests { let sent_tx = make_sent_tx(567); let tx_hash = sent_tx.hash; let tx_timestamp = sent_tx.timestamp; - let records = vec![(tx_hash, sent_tx.clone())]; + let records = vec![sent_tx.clone()]; let logger = Logger::new(test_name); - subject.load_cache(records); + subject.ensure_empty_cache(&logger); assert!( subject.sent_payables.is_empty(), - "Should be empty but was {:?}", + "Should be empty by now but was {:?}", subject.sent_payables ); TestLogHandler::default().exists_log_containing(&format!( @@ -721,15 +723,39 @@ mod tests { )); } + #[test] + fn pending_payables_cache_dump_works() { + let mut subject = CurrentPendingPayables::new(); + let sent_tx_1 = make_sent_tx(567); + let tx_hash_1 = sent_tx_1.hash; + let sent_tx_2 = make_sent_tx(456); + let tx_hash_2 = sent_tx_2.hash; + let sent_tx_3 = make_sent_tx(789); + let tx_hash_3 = sent_tx_3.hash; + let records = vec![sent_tx_1.clone(), sent_tx_2.clone(), sent_tx_3.clone()]; + subject.load_cache(records); + + let result = subject.dump_cache(); + + assert_eq!( + result, + hashmap! ( + tx_hash_1 => sent_tx_1, + tx_hash_2 => sent_tx_2, + tx_hash_3 => sent_tx_3 + ) + ); + } + #[test] fn failure_cache_insert_and_get_methods_single_record() { - let mut subject = FailuresRequiringDoubleCheck::new(); + let mut subject = RecheckRequiringFailures::new(); let failed_tx = make_failed_tx(567); let tx_hash = failed_tx.hash; - let records = vec![(tx_hash, failed_tx.clone())]; - + let records = vec![failed_tx.clone()]; let state_before = subject.failures.clone(); subject.load_cache(records); + let first_attempt = subject.get_record_by_hash(tx_hash); let second_attempt = subject.get_record_by_hash(tx_hash); @@ -745,7 +771,7 @@ mod tests { #[test] fn failure_cache_insert_and_get_methods_multiple_records() { - let mut subject = FailuresRequiringDoubleCheck::new(); + let mut subject = RecheckRequiringFailures::new(); let failed_tx_1 = make_failed_tx(123); let tx_hash_1 = failed_tx_1.hash; let failed_tx_2 = make_failed_tx(456); @@ -756,10 +782,10 @@ mod tests { let tx_hash_4 = failed_tx_4.hash; let nonexistent_tx_hash = make_tx_hash(234); let records = vec![ - (tx_hash_1, failed_tx_1.clone()), - (tx_hash_2, failed_tx_2.clone()), - (tx_hash_3, failed_tx_3.clone()), - (tx_hash_4, failed_tx_4.clone()), + failed_tx_1.clone(), + failed_tx_2.clone(), + failed_tx_3.clone(), + failed_tx_4.clone(), ]; let first_query = subject.get_record_by_hash(tx_hash_1); @@ -791,19 +817,19 @@ mod tests { fn failure_cache_ensure_empty_happy_path() { init_test_logging(); let test_name = "failure_cache_ensure_empty_happy_path"; - let mut subject = FailuresRequiringDoubleCheck::new(); + let mut subject = RecheckRequiringFailures::new(); let failed_tx = make_failed_tx(567); let tx_hash = failed_tx.hash; - let records = vec![(tx_hash, failed_tx.clone())]; + let records = vec![failed_tx.clone()]; let logger = Logger::new(test_name); - subject.load_cache(records); let _ = subject.get_record_by_hash(tx_hash); + subject.ensure_empty_cache(&logger); assert!( subject.failures.is_empty(), - "Should be empty but was {:?}", + "Should be empty by now but was {:?}", subject.failures ); TestLogHandler::default().exists_no_log_containing(&format!( @@ -816,19 +842,19 @@ mod tests { fn failure_cache_ensure_empty_sad_path() { init_test_logging(); let test_name = "failure_cache_ensure_empty_sad_path"; - let mut subject = FailuresRequiringDoubleCheck::new(); + let mut subject = RecheckRequiringFailures::new(); let failed_tx = make_failed_tx(567); let tx_hash = failed_tx.hash; let tx_timestamp = failed_tx.timestamp; - let records = vec![(tx_hash, failed_tx.clone())]; + let records = vec![failed_tx.clone()]; let logger = Logger::new(test_name); - subject.load_cache(records); + subject.ensure_empty_cache(&logger); assert!( subject.failures.is_empty(), - "Should be empty but was {:?}", + "Should be empty by now but was {:?}", subject.failures ); TestLogHandler::default().exists_log_containing(&format!( @@ -841,4 +867,32 @@ mod tests { RetryRequired }}}}. Dumping." )); } + + #[test] + fn failure_cache_dump_works() { + let mut subject = RecheckRequiringFailures::new(); + let failed_tx_1 = make_failed_tx(567); + let tx_hash_1 = failed_tx_1.hash; + let failed_tx_2 = make_failed_tx(456); + let tx_hash_2 = failed_tx_2.hash; + let failed_tx_3 = make_failed_tx(789); + let tx_hash_3 = failed_tx_3.hash; + let records = vec![ + failed_tx_1.clone(), + failed_tx_2.clone(), + failed_tx_3.clone(), + ]; + subject.load_cache(records); + + let result = subject.dump_cache(); + + assert_eq!( + result, + hashmap! ( + tx_hash_1 => failed_tx_1, + tx_hash_2 => failed_tx_2, + tx_hash_3 => failed_tx_3 + ) + ); + } } diff --git a/node/src/accountant/scanners/scanners_utils.rs b/node/src/accountant/scanners/scanners_utils.rs index 3d7674066..ecb494dee 100644 --- a/node/src/accountant/scanners/scanners_utils.rs +++ b/node/src/accountant/scanners/scanners_utils.rs @@ -670,51 +670,6 @@ mod tests { assert_on_testing_enum_with_all_its_variants!(BlockchainTxFailure, check_nums, inputs_len) } - #[test] - fn conversion_from_sent_tx_and_failure_reason_to_failed_tx_works() { - let sent_tx = SentTx { - hash: make_tx_hash(789), - receiver_address: make_wallet("receiver").address(), - amount_minor: 123_456_789, - timestamp: to_unix_timestamp( - SystemTime::now() - .checked_sub(Duration::from_secs(10_000)) - .unwrap(), - ), - gas_price_minor: gwei_to_wei(424_u64), - nonce: 456_u64.into(), - status: TxStatus::Pending(ValidationStatus::Waiting), - }; - - let result_1 = FailedTx::from((sent_tx.clone(), FailureReason::Reverted)); - let result_2 = FailedTx::from(( - sent_tx.clone(), - FailureReason::Submission(AppRpcError::Local(LocalError::Internal)), - )); - - assert_conversion_into_failed_tx(result_1, sent_tx.clone(), FailureReason::Reverted); - assert_conversion_into_failed_tx( - result_2, - sent_tx, - FailureReason::Submission(AppRpcError::Local(LocalError::Internal)), - ); - } - - fn assert_conversion_into_failed_tx( - result: FailedTx, - original_sent_tx: SentTx, - expected_failure_reason: FailureReason, - ) { - assert_eq!(result.hash, original_sent_tx.hash); - assert_eq!(result.receiver_address, original_sent_tx.receiver_address); - assert_eq!(result.amount_minor, original_sent_tx.amount_minor); - assert_eq!(result.timestamp, original_sent_tx.timestamp); - assert_eq!(result.gas_price_minor, original_sent_tx.gas_price_minor); - assert_eq!(result.nonce, original_sent_tx.nonce); - assert_eq!(result.status, FailureStatus::RetryRequired); - assert_eq!(result.reason, expected_failure_reason); - } - #[test] fn count_total_errors_says_unknown_number_for_early_local_errors() { let early_local_errors = [ diff --git a/node/src/accountant/scanners/test_utils.rs b/node/src/accountant/scanners/test_utils.rs index 14b058347..a434c9880 100644 --- a/node/src/accountant/scanners/test_utils.rs +++ b/node/src/accountant/scanners/test_utils.rs @@ -2,13 +2,16 @@ #![cfg(test)] +use crate::accountant::db_access_objects::utils::TxHash; use crate::accountant::scanners::payable_scanner_extension::msgs::{ BlockchainAgentWithContextMessage, QualifiedPayablesMessage, }; use crate::accountant::scanners::payable_scanner_extension::{ MultistageDualPayableScanner, PreparedAdjustment, SolvencySensitivePaymentInstructor, }; -use crate::accountant::scanners::pending_payable_scanner::utils::PendingPayableScanResult; +use crate::accountant::scanners::pending_payable_scanner::utils::{ + PendingPayableCache, PendingPayableScanResult, +}; use crate::accountant::scanners::scan_schedulers::{ NewPayableScanDynIntervalComputer, PayableSequenceScanner, RescheduleScanOnErrorResolver, ScanRescheduleAfterEarlyStop, @@ -31,6 +34,7 @@ use masq_lib::ui_gateway::NodeToUiMessage; use regex::Regex; use std::any::type_name; use std::cell::RefCell; +use std::collections::HashMap; use std::sync::{Arc, Mutex}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use time::{format_description, PrimitiveDateTime}; @@ -487,3 +491,70 @@ impl RescheduleScanOnErrorResolverMock { pub fn make_zeroed_consuming_wallet_balances() -> ConsumingWalletBalances { ConsumingWalletBalances::new(0.into(), 0.into()) } + +pub struct PendingPayableCacheMock { + load_cache_params: Arc>>>, + load_cache_results: RefCell>>, + get_record_by_hash_params: Arc>>, + get_record_by_hash_results: RefCell>>, + ensure_empty_cache_params: Arc>>, +} + +impl Default for PendingPayableCacheMock { + fn default() -> Self { + Self { + load_cache_params: Arc::new(Mutex::new(vec![])), + load_cache_results: RefCell::new(vec![]), + get_record_by_hash_params: Arc::new(Mutex::new(vec![])), + get_record_by_hash_results: RefCell::new(vec![]), + ensure_empty_cache_params: Arc::new(Mutex::new(vec![])), + } + } +} + +impl PendingPayableCache for PendingPayableCacheMock { + fn load_cache(&mut self, records: Vec) { + self.load_cache_params.lock().unwrap().push(records); + self.load_cache_results.borrow_mut().remove(0); + } + + fn get_record_by_hash(&mut self, hashes: TxHash) -> Option { + self.get_record_by_hash_params.lock().unwrap().push(hashes); + self.get_record_by_hash_results.borrow_mut().remove(0) + } + + fn ensure_empty_cache(&mut self, logger: &Logger) { + self.ensure_empty_cache_params.lock().unwrap().push(()); + } + + fn dump_cache(&mut self) -> HashMap { + unimplemented!("not needed yet") + } +} + +impl PendingPayableCacheMock { + pub fn load_cache_params(mut self, params: &Arc>>>) -> Self { + self.load_cache_params = params.clone(); + self + } + + pub fn load_cache_result(self, result: HashMap) -> Self { + self.load_cache_results.borrow_mut().push(result); + self + } + + pub fn get_record_by_hash_params(mut self, params: &Arc>>) -> Self { + self.get_record_by_hash_params = params.clone(); + self + } + + pub fn get_record_by_hash_result(self, result: Option) -> Self { + self.get_record_by_hash_results.borrow_mut().push(result); + self + } + + pub fn ensure_empty_cache_params(mut self, params: &Arc>>) -> Self { + self.ensure_empty_cache_params = params.clone(); + self + } +} diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs index 15f65f3da..697afaee5 100644 --- a/node/src/accountant/test_utils.rs +++ b/node/src/accountant/test_utils.rs @@ -48,6 +48,7 @@ use ethereum_types::U64; use web3::types::{Address}; use crate::accountant::db_access_objects::sent_payable_dao::{RetrieveCondition, SentPayableDaoError, SentTx}; use crate::accountant::scanners::pending_payable_scanner::utils::PendingPayableCache; +use crate::accountant::scanners::test_utils::PendingPayableCacheMock; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock}; pub fn make_receivable_account(n: u64, expected_delinquent: bool) -> ReceivableAccount { @@ -1254,28 +1255,6 @@ impl FailedPayableDaoMock { self.delete_records_results.borrow_mut().push(result); self } - - pub fn merge_results_with(self, other: &Self) -> Self { - Self::merge_single_result( - &self.get_tx_identifiers_results, - &other.get_tx_identifiers_results, - ); - Self::merge_single_result( - &self.insert_new_records_results, - &other.insert_new_records_results, - ); - Self::merge_single_result(&self.retrieve_txs_results, &other.retrieve_txs_results); - Self::merge_single_result( - &self.update_statuses_results, - &other.update_statuses_results, - ); - Self::merge_single_result(&self.delete_records_results, &other.delete_records_results); - self - } - - fn merge_single_result(one: &RefCell>, other: &RefCell>) { - one.borrow_mut().append(&mut other.borrow_mut()); - } } pub struct FailedPayableDaoFactoryMock { @@ -1368,7 +1347,8 @@ pub struct PendingPayableScannerBuilder { failed_payable_dao: FailedPayableDaoMock, payment_thresholds: PaymentThresholds, financial_statistics: FinancialStatistics, - yet_unproven_failures: HashMap, + current_sent_payables: Box>, + yet_unproven_failed_payables: Box>, } impl PendingPayableScannerBuilder { @@ -1379,7 +1359,8 @@ impl PendingPayableScannerBuilder { failed_payable_dao: FailedPayableDaoMock::new(), payment_thresholds: PaymentThresholds::default(), financial_statistics: FinancialStatistics::default(), - yet_unproven_failures: hashmap!(), + current_sent_payables: Box::new(PendingPayableCacheMock::default()), + yet_unproven_failed_payables: Box::new(PendingPayableCacheMock::default()), } } @@ -1398,16 +1379,20 @@ impl PendingPayableScannerBuilder { self } - pub fn yet_unproven_failures(mut self, failures: HashMap) -> Self { - self.yet_unproven_failures = failures; + pub fn pending_payables_cache(mut self, cache: Box>) -> Self { + self.current_sent_payables = cache; self } - pub fn build(mut self) -> PendingPayableScanner { - self.failed_payable_dao = FailedPayableDaoMock::default() - .retrieve_txs_result(vec![]) - .merge_results_with(&self.failed_payable_dao); + pub fn failed_payables_cache( + mut self, + failures: Box>, + ) -> Self { + self.yet_unproven_failed_payables = failures; + self + } + pub fn build(mut self) -> PendingPayableScanner { let mut scanner = PendingPayableScanner::new( Box::new(self.payable_dao), Box::new(self.sent_payable_dao), @@ -1416,9 +1401,9 @@ impl PendingPayableScannerBuilder { Rc::new(RefCell::new(self.financial_statistics)), ); - scanner - .yet_unproven_failures - .load_cache(self.yet_unproven_failures); + scanner.current_sent_payables = self.current_sent_payables; + + scanner.yet_unproven_failed_payables = self.yet_unproven_failed_payables; scanner } diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index 7376b9e41..6ee71e6ec 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -395,14 +395,14 @@ impl BlockchainBridge { transaction_receipts_results.iter().fold( (0, 0, 0), |(success, fail, pending), transaction_receipt| match transaction_receipt { - TxReceiptResult::Ok(tx_receipt) => match tx_receipt.status { + TxReceiptResult(Ok(tx_receipt)) => match tx_receipt.status { StatusReadFromReceiptCheck::Failed(_) => (success, fail + 1, pending), StatusReadFromReceiptCheck::Succeeded(_) => { (success + 1, fail, pending) } StatusReadFromReceiptCheck::Pending => (success, fail, pending + 1), }, - TxReceiptResult::Err(_) => (success, fail, pending + 1), + TxReceiptResult(Err(_)) => (success, fail, pending + 1), }, ); format!( @@ -1210,14 +1210,14 @@ mod tests { tx_receipts_message, &TxReceiptsMessage { results: vec![ - TxReceiptResult::Ok(RetrievedTxStatus::new( + TxReceiptResult(Ok(RetrievedTxStatus::new( TxHashByTable::SentPayable(tx_hash_1), expected_receipt.into() - )), - TxReceiptResult::Ok(RetrievedTxStatus::new( + ))), + TxReceiptResult(Ok(RetrievedTxStatus::new( TxHashByTable::FailedPayable(tx_hash_2), StatusReadFromReceiptCheck::Pending - )) + ))) ], response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, @@ -1349,16 +1349,16 @@ mod tests { *report_receipts_msg, TxReceiptsMessage { results: vec![ - TxReceiptResult::Ok(RetrievedTxStatus::new(TxHashByTable::SentPayable(tx_hash_1), StatusReadFromReceiptCheck::Pending)), - TxReceiptResult::Ok(RetrievedTxStatus::new(TxHashByTable::SentPayable(tx_hash_2), StatusReadFromReceiptCheck::Succeeded(TransactionBlock { + TxReceiptResult(Ok(RetrievedTxStatus::new(TxHashByTable::SentPayable(tx_hash_1), StatusReadFromReceiptCheck::Pending))), + TxReceiptResult(Ok(RetrievedTxStatus::new(TxHashByTable::SentPayable(tx_hash_2), StatusReadFromReceiptCheck::Succeeded(TransactionBlock { block_hash: Default::default(), block_number, - }))), - TxReceiptResult::Err( + })))), + TxReceiptResult(Err( TxReceiptError::new( TxHashByTable::SentPayable(tx_hash_3), - AppRpcError:: Remote(RemoteError::Web3RpcError { code: 429, message: "The requests per second (RPS) of your requests are higher than your plan allows.".to_string()}))), - TxReceiptResult::Ok(RetrievedTxStatus::new(TxHashByTable::SentPayable(tx_hash_1), StatusReadFromReceiptCheck::Pending)), + AppRpcError:: Remote(RemoteError::Web3RpcError { code: 429, message: "The requests per second (RPS) of your requests are higher than your plan allows.".to_string()})))), + TxReceiptResult(Ok(RetrievedTxStatus::new(TxHashByTable::SentPayable(tx_hash_1), StatusReadFromReceiptCheck::Pending))), ], response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs index afc827f5b..bf750d5a0 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs @@ -7,6 +7,7 @@ use crate::blockchain::blockchain_interface::data_structures::errors::Blockchain use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainError::QueryFailed; use crate::blockchain::blockchain_interface::lower_level_interface::LowBlockchainInt; use crate::blockchain::errors::AppRpcError; +use actix::Message; use ethereum_types::{H256, U256, U64}; use futures::Future; use serde_derive::{Deserialize, Serialize}; @@ -18,7 +19,17 @@ use web3::transports::{Batch, Http}; use web3::types::{Address, BlockNumber, Filter, Log, TransactionReceipt}; use web3::{Error, Web3}; -pub type TxReceiptResult = Result; +#[derive(Debug, PartialEq, Eq, Message, Clone)] +pub struct TxReceiptResult(pub Result); + +impl TxReceiptResult { + pub fn hash(&self) -> TxHashByTable { + match &self.0 { + Ok(retrieved_tx_status) => retrieved_tx_status.tx_hash, + Err(tx_receipt_error) => tx_receipt_error.tx_hash, + } + } +} #[derive(Debug, PartialEq, Eq, Clone)] pub struct RetrievedTxStatus { @@ -87,35 +98,6 @@ impl Display for StatusReadFromReceiptCheck { } } -// TODO figure out where this could be used???? -// impl FromStr for TxStatus { -// type Err = String; -// -// fn from_str(s: &str) -> Result { -// match s { -// "Pending" => Ok(TxStatus::Pending), -// "Failed" => Ok(TxStatus::Failed), // TODO: GH-631: This should be removed -// s if s.starts_with("Succeeded") => { -// // The format is "Succeeded(block_number, block_hash)" -// let parts: Vec<&str> = s[10..s.len() - 1].split(',').collect(); -// if parts.len() != 2 { -// return Err("Invalid Succeeded format".to_string()); -// } -// let block_number: u64 = parts[0] -// .parse() -// .map_err(|_| "Invalid block number".to_string())?; -// let block_hash = -// H256::from_str(&parts[1][2..]).map_err(|_| "Invalid block hash".to_string())?; -// Ok(TxStatus::Succeeded(TransactionBlock { -// block_hash, -// block_number: U64::from(block_number), -// })) -// } -// _ => Err(format!("Unknown status: {}", s)), -// } -// } -// } - #[derive(Debug, PartialEq, Eq, Clone)] pub struct TxReceiptError { pub tx_hash: TxHashByTable, @@ -264,9 +246,11 @@ mod tests { use web3::types::{BlockNumber, Bytes, FilterBuilder, Log, TransactionReceipt, U256}; use crate::accountant::db_access_objects::failed_payable_dao::FailureReason; use crate::accountant::db_access_objects::sent_payable_dao::SentTx; - use crate::accountant::test_utils::make_sent_tx; + use crate::accountant::scanners::pending_payable_scanner::utils::TxHashByTable; + use crate::accountant::test_utils::{make_sent_tx, make_transaction_block}; use crate::assert_on_testing_enum_with_all_its_variants; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{RetrievedTxStatus, TransactionBlock, BlockchainTxFailure, StatusReadFromReceiptCheck}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{RetrievedTxStatus, TransactionBlock, BlockchainTxFailure, StatusReadFromReceiptCheck, TxReceiptResult, TxReceiptError}; + use crate::blockchain::errors::{AppRpcError, LocalError, RemoteError}; #[test] fn get_transaction_fee_balance_works() { @@ -737,77 +721,41 @@ mod tests { }) .collect_vec(); - // let initially = check_nums.len(); - // check_nums.dedup(); - // let deduped = check_nums.len(); - // assert_eq!( - // deduped, initially, - // "Some variants were processed more than once. Expected: {}, actual: {}", - // initially, deduped - // ); - // assert_eq!( - // inputs_len, - // BlockchainTxFailure::VARIANT_COUNT, - // "Input should contain one example from each variant. Expected: {}, actual: {}", - // BlockchainTxFailure::VARIANT_COUNT, - // inputs_len - // ); - // assert_eq!( - // deduped, - // BlockchainTxFailure::VARIANT_COUNT, - // "We should've gotten one result for each variant. Expected: {}, actual: {}", - // BlockchainTxFailure::VARIANT_COUNT, - // deduped - // ); assert_on_testing_enum_with_all_its_variants!(BlockchainTxFailure, check_nums, inputs_len) } - // - // #[test] - // fn tx_status_from_str_works() { - // // Test Pending - // assert_eq!(TxStatus::from_str("Pending"), Ok(TxStatus::Pending)); - // - // // Test Failed - // assert_eq!(TxStatus::from_str("Failed"), Ok(TxStatus::Failed)); - // - // // Test Succeeded with valid input - // let block_number = 123456789; - // let block_hash = H256::from_low_u64_be(0xabcdef); - // let input = format!("Succeeded({},0x{:x})", block_number, block_hash); - // assert_eq!( - // TxStatus::from_str(&input), - // Ok(TxStatus::Succeeded(TransactionBlock { - // block_hash, - // block_number: U64::from(block_number), - // })) - // ); - // - // // Test Succeeded with invalid format - // assert_eq!( - // TxStatus::from_str("Succeeded(123)"), - // Err("Invalid Succeeded format".to_string()) - // ); - // - // // Test Succeeded with invalid block number - // assert_eq!( - // TxStatus::from_str( - // "Succeeded(abc,0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef)" - // ), - // Err("Invalid block number".to_string()) - // ); - // - // // Test Succeeded with invalid block hash - // assert_eq!( - // TxStatus::from_str("Succeeded(123,0xinvalidhash)"), - // Err("Invalid block hash".to_string()) - // ); - // - // // Test unknown status - // assert_eq!( - // TxStatus::from_str("InProgress"), - // Err("Unknown status: InProgress".to_string()) - // ); - // } + #[test] + fn hash_can_be_fetched_from_tx_receipt_result() { + let hash_1 = TxHashByTable::SentPayable(make_tx_hash(123)); + let hash_2 = TxHashByTable::SentPayable(make_tx_hash(111)); + let hash_3 = TxHashByTable::FailedPayable(make_tx_hash(222)); + let hash_4 = TxHashByTable::FailedPayable(make_tx_hash(321)); + let positive_with_sent_payable = TxReceiptResult(Ok(RetrievedTxStatus::new( + hash_1, + StatusReadFromReceiptCheck::Pending, + ))); + let negative_with_sent_payable = TxReceiptResult(Err(TxReceiptError::new( + hash_2, + AppRpcError::Local(LocalError::Internal), + ))); + let positive_with_failed_payable = TxReceiptResult(Ok(RetrievedTxStatus::new( + hash_3, + StatusReadFromReceiptCheck::Succeeded(make_transaction_block(789)), + ))); + let negative_with_failed_payable = TxReceiptResult(Err(TxReceiptError::new( + hash_4, + AppRpcError::Remote(RemoteError::Unreachable), + ))); + + let result_1 = positive_with_sent_payable.hash(); + let result_2 = negative_with_sent_payable.hash(); + let result_3 = positive_with_failed_payable.hash(); + let result_4 = negative_with_failed_payable.hash(); + + assert_eq!(result_1, hash_1); + assert_eq!(result_2, hash_2); + assert_eq!(result_3, hash_3); + assert_eq!(result_4, hash_4); + } fn test_deriving_tx_status_from_tx_receipt_and_adding_to_sent_tx( num_status_opt: Option, diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs index 59a190532..f6f7f6fce 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs @@ -229,28 +229,28 @@ impl BlockchainInterface for BlockchainInterfaceWeb3 { .map(|(response, tx_hash)| match response { Ok(result) => { match serde_json::from_value::(result) { - Ok(receipt) => TxReceiptResult::Ok(RetrievedTxStatus::new( + Ok(receipt) => TxReceiptResult(Ok(RetrievedTxStatus::new( tx_hash, receipt.into(), - )), + ))), Err(e) => { if e.to_string().contains("invalid type: null") { - TxReceiptResult::Ok(RetrievedTxStatus::new( + TxReceiptResult(Ok(RetrievedTxStatus::new( tx_hash, StatusReadFromReceiptCheck::Pending, - )) + ))) } else { - TxReceiptResult::Err(TxReceiptError::new( + TxReceiptResult(Err(TxReceiptError::new( tx_hash, AppRpcError::Remote(RemoteError::InvalidResponse( e.to_string(), )), - )) + ))) } } } } - Err(e) => TxReceiptResult::Err(TxReceiptError::new(tx_hash, e.into())), + Err(e) => TxReceiptResult(Err(TxReceiptError::new(tx_hash, e.into()))), }) .collect::>()) }), @@ -1125,7 +1125,7 @@ mod tests { .wait() .unwrap(); - assert_eq!(result[0], TxReceiptResult::Err( + assert_eq!(result[0], TxReceiptResult(Err( TxReceiptError::new( tx_hbt_1, AppRpcError::Remote( @@ -1135,47 +1135,47 @@ mod tests { "The requests per second (RPS) of your requests are higher than your plan allows." .to_string() } - ))) + )))) ); assert_eq!( result[1], - TxReceiptResult::Ok(RetrievedTxStatus::new( + TxReceiptResult(Ok(RetrievedTxStatus::new( tx_hbt_2, StatusReadFromReceiptCheck::Pending - )) + ))) ); assert_eq!( result[2], - TxReceiptResult::Err(TxReceiptError::new( + TxReceiptResult(Err(TxReceiptError::new( tx_hbt_3, AppRpcError::Remote(RemoteError::InvalidResponse( "invalid type: string \"trash\", expected struct Receipt".to_string() )) - )) + ))) ); assert_eq!( result[3], - TxReceiptResult::Ok(RetrievedTxStatus::new( + TxReceiptResult(Ok(RetrievedTxStatus::new( tx_hbt_4, StatusReadFromReceiptCheck::Pending - )) + ))) ); assert_eq!( result[4], - TxReceiptResult::Ok(RetrievedTxStatus::new( + TxReceiptResult(Ok(RetrievedTxStatus::new( tx_hbt_5, StatusReadFromReceiptCheck::Failed(BlockchainTxFailure::Unrecognized) - )) + ))) ); assert_eq!( result[5], - TxReceiptResult::Ok(RetrievedTxStatus::new( + TxReceiptResult(Ok(RetrievedTxStatus::new( tx_hbt_6, StatusReadFromReceiptCheck::Succeeded(TransactionBlock { block_hash, block_number, }), - )) + ))) ) } From 2eb5ea48ae4047757292d70134199695de7bdb66 Mon Sep 17 00:00:00 2001 From: Bert Date: Tue, 5 Aug 2025 14:01:41 +0200 Subject: [PATCH 24/61] GH-642: another big portion of work in interpreting the receipts --- .../db_access_objects/failed_payable_dao.rs | 7 +- ...able_and_failed_payable_data_conversion.rs | 6 +- .../db_access_objects/sent_payable_dao.rs | 100 +++++++--- node/src/accountant/mod.rs | 103 ++++++---- node/src/accountant/scanners/mod.rs | 74 ++++--- .../scanners/pending_payable_scanner/mod.rs | 188 ++++++++++++------ .../scanners/pending_payable_scanner/utils.rs | 184 ++++++++++++----- .../src/accountant/scanners/scanners_utils.rs | 6 +- node/src/accountant/test_utils.rs | 34 +++- node/src/blockchain/blockchain_bridge.rs | 54 ++--- .../lower_level_interface_web3.rs | 171 +--------------- .../blockchain_interface_web3/mod.rs | 26 ++- .../data_structures/mod.rs | 181 ++++++++++++++++- .../blockchain/blockchain_interface/mod.rs | 23 ++- 14 files changed, 725 insertions(+), 432 deletions(-) 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 eae97696a..99df3763b 100644 --- a/node/src/accountant/db_access_objects/failed_payable_dao.rs +++ b/node/src/accountant/db_access_objects/failed_payable_dao.rs @@ -1,5 +1,8 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::db_access_objects::utils::{DaoFactoryReal, TxHash, TxIdentifiers, TxRecordWithHash, VigilantRusqliteFlatten}; +use crate::accountant::db_access_objects::failed_payable_dao::FailureStatus::RecheckRequired; +use crate::accountant::db_access_objects::utils::{ + DaoFactoryReal, TxHash, TxIdentifiers, TxRecordWithHash, VigilantRusqliteFlatten, +}; use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; use crate::accountant::{checked_conversion, comma_joined_stringifiable}; use crate::blockchain::errors::AppRpcError; @@ -11,8 +14,6 @@ use std::collections::{HashMap, HashSet}; use std::fmt::{Display, Formatter}; use std::str::FromStr; use web3::types::Address; -use crate::accountant::db_access_objects::failed_payable_dao::FailureStatus::RecheckRequired; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::TransactionBlock; #[derive(Debug, PartialEq, Eq)] pub enum FailedPayableDaoError { diff --git a/node/src/accountant/db_access_objects/sent_payable_and_failed_payable_data_conversion.rs b/node/src/accountant/db_access_objects/sent_payable_and_failed_payable_data_conversion.rs index 5131ea4bc..7a71e98a9 100644 --- a/node/src/accountant/db_access_objects/sent_payable_and_failed_payable_data_conversion.rs +++ b/node/src/accountant/db_access_objects/sent_payable_and_failed_payable_data_conversion.rs @@ -1,8 +1,10 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, FailureReason, FailureStatus}; +use crate::accountant::db_access_objects::failed_payable_dao::{ + FailedTx, FailureReason, FailureStatus, +}; use crate::accountant::db_access_objects::sent_payable_dao::{Detection, SentTx, TxStatus}; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::TransactionBlock; +use crate::blockchain::blockchain_interface::data_structures::TransactionBlock; impl From<(FailedTx, TransactionBlock)> for SentTx { fn from((failed_tx, confirmation_block): (FailedTx, TransactionBlock)) -> Self { 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 442e1af38..5a2a2926e 100644 --- a/node/src/accountant/db_access_objects/sent_payable_dao.rs +++ b/node/src/accountant/db_access_objects/sent_payable_dao.rs @@ -1,20 +1,22 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. +use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, ValidationStatus}; +use crate::accountant::db_access_objects::utils::{ + DaoFactoryReal, TxHash, TxIdentifiers, TxRecordWithHash, +}; +use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; +use crate::accountant::{checked_conversion, comma_joined_stringifiable}; +use crate::blockchain::blockchain_interface::data_structures::TransactionBlock; +use crate::database::rusqlite_wrappers::ConnectionWrapper; +use ethereum_types::H256; +use itertools::Itertools; +use masq_lib::utils::ExpectValue; +use serde_derive::{Deserialize, Serialize}; use std::cmp::Ordering; use std::collections::{HashMap, HashSet}; use std::fmt::{Display, Formatter}; use std::str::FromStr; -use ethereum_types::{H256}; use web3::types::Address; -use masq_lib::utils::ExpectValue; -use crate::accountant::{checked_conversion, comma_joined_stringifiable}; -use crate::accountant::db_access_objects::utils::{DaoFactoryReal, TxHash, TxIdentifiers, TxRecordWithHash}; -use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock}; -use crate::database::rusqlite_wrappers::ConnectionWrapper; -use itertools::Itertools; -use serde_derive::{Deserialize, Serialize}; -use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, ValidationStatus}; #[derive(Debug, PartialEq, Eq)] pub enum SentPayableDaoError { @@ -90,6 +92,7 @@ impl From for TxStatus { pub enum RetrieveCondition { IsPending, ByHash(Vec), + ByNonce(Vec), } impl Display for RetrieveCondition { @@ -105,6 +108,13 @@ impl Display for RetrieveCondition { comma_joined_stringifiable(tx_hashes, |hash| format!("'{:?}'", hash)) ) } + RetrieveCondition::ByNonce(nonces) => { + write!( + f, + "WHERE nonce IN ({})", + comma_joined_stringifiable(nonces, |nonce| nonce.to_string()) + ) + } } } } @@ -436,26 +446,35 @@ impl SentPayableDaoFactory for DaoFactoryReal { #[cfg(test)] mod tests { - use std::collections::{HashMap, HashSet}; - use std::str::FromStr; - use std::sync::{Arc, Mutex}; - use crate::accountant::db_access_objects::sent_payable_dao::{Detection, RetrieveCondition, SentPayableDao, SentPayableDaoError, SentPayableDaoReal, TxStatus}; - use crate::database::db_initializer::{ - DbInitializationConfig, DbInitializer, DbInitializerReal, + use crate::accountant::db_access_objects::failed_payable_dao::ValidationStatus; + use crate::accountant::db_access_objects::sent_payable_dao::RetrieveCondition::{ + ByHash, ByNonce, IsPending, + }; + use crate::accountant::db_access_objects::sent_payable_dao::SentPayableDaoError::{ + EmptyInput, PartialExecution, + }; + use crate::accountant::db_access_objects::sent_payable_dao::{ + Detection, RetrieveCondition, SentPayableDao, SentPayableDaoError, SentPayableDaoReal, + TxStatus, + }; + use crate::accountant::db_access_objects::test_utils::{ + make_read_only_db_connection, TxBuilder, }; - use crate::database::test_utils::ConnectionWrapperMock; - use ethereum_types::{ H256, U64}; - use masq_lib::test_utils::utils::ensure_node_home_directory_exists; - use rusqlite::{Connection}; - use crate::accountant::db_access_objects::failed_payable_dao::{ValidationStatus}; - use crate::accountant::db_access_objects::sent_payable_dao::RetrieveCondition::{ByHash, IsPending}; - use crate::accountant::db_access_objects::sent_payable_dao::SentPayableDaoError::{EmptyInput, PartialExecution}; - use crate::accountant::db_access_objects::test_utils::{make_read_only_db_connection, TxBuilder}; use crate::accountant::db_access_objects::utils::TxRecordWithHash; use crate::accountant::test_utils::make_sent_tx; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock}; + use crate::blockchain::blockchain_interface::data_structures::TransactionBlock; use crate::blockchain::errors::{AppRpcError, RemoteError}; use crate::blockchain::test_utils::{make_block_hash, make_tx_hash}; + use crate::database::db_initializer::{ + DbInitializationConfig, DbInitializer, DbInitializerReal, + }; + use crate::database::test_utils::ConnectionWrapperMock; + use ethereum_types::{H256, U64}; + use masq_lib::test_utils::utils::ensure_node_home_directory_exists; + use rusqlite::Connection; + use std::collections::{HashMap, HashSet}; + use std::str::FromStr; + use std::sync::{Arc, Mutex}; #[test] fn insert_new_records_works() { @@ -658,8 +677,8 @@ mod tests { '0x0000000000000000000000000000000000000000000000000000000123456789', \ '0x0000000000000000000000000000000000000000000000000000000987654321'\ )" - .to_string() ); + assert_eq!(ByNonce(vec![45, 47]).to_string(), "WHERE nonce IN (45, 47)") } #[test] @@ -739,6 +758,35 @@ mod tests { assert_eq!(result, vec![tx1, tx3]); } + #[test] + fn tx_can_be_retrieved_by_nonce() { + let home_dir = + ensure_node_home_directory_exists("sent_payable_dao", "tx_can_be_retrieved_by_nonce"); + let wrapped_conn = DbInitializerReal::default() + .initialize(&home_dir, DbInitializationConfig::test_default()) + .unwrap(); + let subject = SentPayableDaoReal::new(wrapped_conn); + let tx1 = TxBuilder::default() + .hash(make_tx_hash(123)) + .nonce(33) + .build(); + let tx2 = TxBuilder::default() + .hash(make_tx_hash(456)) + .nonce(34) + .build(); + let tx3 = TxBuilder::default() + .hash(make_tx_hash(789)) + .nonce(35) + .build(); + subject + .insert_new_records(&vec![tx1.clone(), tx2, tx3.clone()]) + .unwrap(); + + let result = subject.retrieve_txs(Some(ByNonce(vec![33, 35]))); + + assert_eq!(result, vec![tx1, tx3]); + } + #[test] fn confirm_tx_works() { let home_dir = ensure_node_home_directory_exists("sent_payable_dao", "confirm_tx_works"); diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index a3e4387d8..b0c9a61a6 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -14,21 +14,32 @@ use masq_lib::constants::{SCAN_ERROR, WEIS_IN_GWEI}; use std::cell::{Ref, RefCell}; use crate::accountant::db_access_objects::payable_dao::{PayableDao, PayableDaoError}; -use crate::accountant::db_access_objects::sent_payable_dao::{SentPayableDao, SentTx}; use crate::accountant::db_access_objects::receivable_dao::{ReceivableDao, ReceivableDaoError}; -use crate::accountant::db_access_objects::utils::{remap_payable_accounts, remap_receivable_accounts, CustomQuery, DaoFactoryReal, TxHash}; +use crate::accountant::db_access_objects::sent_payable_dao::{SentPayableDao, SentTx}; +use crate::accountant::db_access_objects::utils::{ + remap_payable_accounts, remap_receivable_accounts, CustomQuery, DaoFactoryReal, TxHash, +}; use crate::accountant::financials::visibility_restricted_module::{ check_query_is_within_tech_limits, financials_entry_check, }; use crate::accountant::scanners::payable_scanner_extension::msgs::{ BlockchainAgentWithContextMessage, QualifiedPayablesMessage, }; -use crate::accountant::scanners::{StartScanError, Scanners}; -use crate::blockchain::blockchain_bridge::{BlockMarker, RegisterNewPendingPayables, RetrieveTransactions}; +use crate::accountant::scanners::pending_payable_scanner::utils::{ + PendingPayableScanResult, Retry, TxHashByTable, +}; +use crate::accountant::scanners::scan_schedulers::{ + PayableSequenceScanner, ScanRescheduleAfterEarlyStop, ScanSchedulers, +}; +use crate::accountant::scanners::scanners_utils::payable_scanner_utils::OperationOutcome; +use crate::accountant::scanners::{Scanners, StartScanError}; +use crate::blockchain::blockchain_bridge::{ + BlockMarker, RegisterNewPendingPayables, RetrieveTransactions, +}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::HashAndAmount; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; use crate::blockchain::blockchain_interface::data_structures::{ - BlockchainTransaction, ProcessedPayableFallible, + BlockchainTransaction, ProcessedPayableFallible, TxReceiptResult, }; use crate::bootstrapper::BootstrapperConfig; use crate::database::db_initializer::DbInitializationConfig; @@ -55,12 +66,11 @@ use itertools::Either; use itertools::Itertools; use masq_lib::crash_point::CrashPoint; use masq_lib::logger::Logger; -use masq_lib::messages::{ScanType, UiFinancialsResponse, UiScanResponse}; use masq_lib::messages::{FromMessageBody, ToMessageBody, UiFinancialsRequest}; use masq_lib::messages::{ - QueryResults, UiFinancialStatistics, UiPayableAccount, UiReceivableAccount, - UiScanRequest, + QueryResults, UiFinancialStatistics, UiPayableAccount, UiReceivableAccount, UiScanRequest, }; +use masq_lib::messages::{ScanType, UiFinancialsResponse, UiScanResponse}; use masq_lib::ui_gateway::MessageTarget::ClientId; use masq_lib::ui_gateway::{MessageBody, MessagePath, MessageTarget}; use masq_lib::ui_gateway::{NodeFromUiMessage, NodeToUiMessage}; @@ -74,10 +84,6 @@ use std::path::Path; use std::rc::Rc; use std::time::SystemTime; use web3::types::H256; -use crate::accountant::scanners::pending_payable_scanner::utils::{PendingPayableScanResult, Retry, TxHashByTable}; -use crate::accountant::scanners::scan_schedulers::{PayableSequenceScanner, ScanRescheduleAfterEarlyStop, ScanSchedulers}; -use crate::accountant::scanners::scanners_utils::payable_scanner_utils::OperationOutcome; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::TxReceiptResult; pub const CRASH_KEY: &str = "ACCOUNTANT"; pub const DEFAULT_PENDING_TOO_LONG_SEC: u64 = 21_600; //6 hours @@ -1219,31 +1225,60 @@ pub fn wei_to_gwei, S: Display + Copy + Div + From> for Accountant { type Result = (); diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index 3b6fec331..e720aeff5 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -52,7 +52,6 @@ use crate::accountant::scanners::payable_scanner_extension::msgs::{BlockchainAge use crate::accountant::scanners::pending_payable_scanner::PendingPayableScanner; use crate::accountant::scanners::pending_payable_scanner::utils::PendingPayableScanResult; use crate::accountant::scanners::receivable_scanner::ReceivableScanner; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, TxReceiptResult, StatusReadFromReceiptCheck}; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; use crate::db_config::persistent_configuration::{PersistentConfigurationReal}; @@ -1008,36 +1007,76 @@ impl_real_scanner_marker!(PayableScanner, PendingPayableScanner, ReceivableScann #[cfg(test)] mod tests { + use crate::accountant::db_access_objects::failed_payable_dao::{ + FailureRetrieveCondition, FailureStatus, ValidationStatus, + }; use crate::accountant::db_access_objects::payable_dao::{PayableAccount, PayableDaoError}; - use crate::accountant::db_access_objects::utils::{from_unix_timestamp, to_unix_timestamp, TxHash}; - use crate::accountant::scanners::payable_scanner_extension::msgs::{QualifiedPayablesBeforeGasPriceSelection, QualifiedPayablesMessage, UnpricedQualifiedPayables}; - use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{OperationOutcome, PayableScanResult}; - use crate::accountant::scanners::{Scanner, StartScanError, StartableScanner, PayableScanner, PendingPayableScanner, ReceivableScanner, ScannerCommon, Scanners, ManulTriggerError}; - use crate::accountant::test_utils::{make_custom_payment_thresholds, make_payable_account, make_qualified_and_unqualified_payables, make_receivable_account, BannedDaoFactoryMock, BannedDaoMock, ConfigDaoFactoryMock, PayableDaoFactoryMock, PayableDaoMock, PayableScannerBuilder, PayableThresholdsGaugeMock, PendingPayableScannerBuilder, ReceivableDaoFactoryMock, ReceivableDaoMock, ReceivableScannerBuilder, make_sent_tx, make_failed_tx, SentPayableDaoMock, FailedPayableDaoMock, FailedPayableDaoFactoryMock, SentPayableDaoFactoryMock}; - use crate::accountant::{gwei_to_wei, PendingPayable, ReceivedPayments, RequestTransactionReceipts, ScanError, ScanForRetryPayables, SentPayables, TxReceiptsMessage, DEFAULT_PENDING_TOO_LONG_SEC}; + use crate::accountant::db_access_objects::sent_payable_dao::SentPayableDaoError; + use crate::accountant::db_access_objects::utils::{ + from_unix_timestamp, to_unix_timestamp, TxHash, + }; + use crate::accountant::scanners::payable_scanner_extension::msgs::{ + QualifiedPayablesBeforeGasPriceSelection, QualifiedPayablesMessage, + UnpricedQualifiedPayables, + }; + use crate::accountant::scanners::pending_payable_scanner::utils::{ + PendingPayableCache, PendingPayableScanResult, RecheckRequiringFailures, TxHashByTable, + }; + use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{ + OperationOutcome, PayableScanResult, + }; + use crate::accountant::scanners::test_utils::{ + assert_timestamps_from_str, parse_system_time_from_str, MarkScanner, NullScanner, + ReplacementType, ScannerReplacement, + }; + use crate::accountant::scanners::{ + ManulTriggerError, PayableScanner, PendingPayableScanner, ReceivableScanner, Scanner, + ScannerCommon, Scanners, StartScanError, StartableScanner, + }; + use crate::accountant::test_utils::{ + make_custom_payment_thresholds, make_failed_tx, make_payable_account, + make_qualified_and_unqualified_payables, make_receivable_account, make_sent_tx, + BannedDaoFactoryMock, BannedDaoMock, ConfigDaoFactoryMock, FailedPayableDaoFactoryMock, + FailedPayableDaoMock, PayableDaoFactoryMock, PayableDaoMock, PayableScannerBuilder, + PayableThresholdsGaugeMock, PendingPayableScannerBuilder, ReceivableDaoFactoryMock, + ReceivableDaoMock, ReceivableScannerBuilder, SentPayableDaoFactoryMock, SentPayableDaoMock, + }; + use crate::accountant::{ + gwei_to_wei, PendingPayable, ReceivedPayments, RequestTransactionReceipts, ScanError, + ScanForRetryPayables, SentPayables, TxReceiptsMessage, DEFAULT_PENDING_TOO_LONG_SEC, + }; use crate::blockchain::blockchain_bridge::{BlockMarker, RetrieveTransactions}; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; use crate::blockchain::blockchain_interface::data_structures::{ - BlockchainTransaction, ProcessedPayableFallible, RpcPayableFailure, + BlockchainTransaction, BlockchainTxFailure, ProcessedPayableFallible, RetrievedTxStatus, + RpcPayableFailure, StatusReadFromReceiptCheck, TransactionBlock, TxReceiptError, + TxReceiptResult, }; + use crate::blockchain::errors::{AppRpcError, RemoteError}; use crate::blockchain::test_utils::{make_block_hash, make_tx_hash}; use crate::database::rusqlite_wrappers::TransactionSafeWrapper; use crate::database::test_utils::transaction_wrapper_mock::TransactionInnerWrapperMockBuilder; use crate::db_config::mocks::ConfigDaoMock; use crate::db_config::persistent_configuration::PersistentConfigError; + use crate::match_lazily_every_type_id; use crate::sub_lib::accountant::{ - DaoFactories, FinancialStatistics, PaymentThresholds, - DEFAULT_PAYMENT_THRESHOLDS, + DaoFactories, FinancialStatistics, PaymentThresholds, DEFAULT_PAYMENT_THRESHOLDS, }; use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMock; + use crate::test_utils::recorder::make_recorder; use crate::test_utils::unshared_test_utils::arbitrary_id_stamp::ArbitraryIdStamp; + use crate::test_utils::unshared_test_utils::capture_numbers_with_separators_from_str; use crate::test_utils::{make_paying_wallet, make_wallet}; use actix::{Message, System}; use ethereum_types::U64; + use itertools::Itertools; use masq_lib::logger::Logger; + use masq_lib::messages::ScanType; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; - use regex::{Regex}; + use masq_lib::ui_gateway::NodeToUiMessage; + use regex::Regex; use rusqlite::{ffi, ErrorCode}; + use secp256k1secrets::ecdh::SharedSecret; use std::cell::RefCell; use std::collections::{HashMap, HashSet}; use std::fmt::format; @@ -1046,21 +1085,8 @@ mod tests { use std::rc::Rc; use std::sync::{Arc, Mutex}; use std::time::{Duration, SystemTime}; - use itertools::Itertools; - use secp256k1secrets::ecdh::SharedSecret; use web3::types::{TransactionReceipt, H256}; use web3::Error; - use masq_lib::messages::ScanType; - use masq_lib::ui_gateway::NodeToUiMessage; - use crate::accountant::db_access_objects::failed_payable_dao::{FailureRetrieveCondition, FailureStatus, ValidationStatus}; - use crate::accountant::db_access_objects::sent_payable_dao::SentPayableDaoError; - use crate::accountant::scanners::pending_payable_scanner::utils::{RecheckRequiringFailures, PendingPayableCache, PendingPayableScanResult, TxHashByTable}; - use crate::accountant::scanners::test_utils::{assert_timestamps_from_str, parse_system_time_from_str, MarkScanner, NullScanner, ReplacementType, ScannerReplacement}; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{RetrievedTxStatus, TransactionBlock, StatusReadFromReceiptCheck, BlockchainTxFailure, TxReceiptResult, TxReceiptError}; - use crate::blockchain::errors::{AppRpcError, RemoteError}; - use crate::match_lazily_every_type_id; - use crate::test_utils::recorder::make_recorder; - use crate::test_utils::unshared_test_utils::capture_numbers_with_separators_from_str; impl Scanners { pub fn replace_scanner(&mut self, replacement: ScannerReplacement) { diff --git a/node/src/accountant/scanners/pending_payable_scanner/mod.rs b/node/src/accountant/scanners/pending_payable_scanner/mod.rs index f83082e0d..1a4cd45fd 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/mod.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/mod.rs @@ -10,7 +10,13 @@ use crate::accountant::db_access_objects::sent_payable_dao::{ RetrieveCondition, SentPayableDao, SentPayableDaoError, SentTx, }; use crate::accountant::db_access_objects::utils::{TxHash, TxRecordWithHash}; -use crate::accountant::scanners::pending_payable_scanner::utils::{CurrentPendingPayables, DetectedConfirmations, DetectedFailures, RecheckRequiringFailures, PendingPayableScanResult, PresortedTxFailure, ReceiptScanReport, Retry, TxCaseToBeInterpreted, FailedValidationByTable, TxReclaim, NormalTxConfirmation, PendingPayableCache, TxByTable, TxHashByTable, MismatchReport}; +use crate::accountant::scanners::pending_payable_scanner::utils::{ + handle_rpc_failure, handle_status_with_failure, handle_still_pending_tx, handle_successful_tx, + CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailedValidationByTable, + MismatchReport, NormalTxConfirmation, PendingPayableCache, PendingPayableScanResult, + PresortedTxFailure, ReceiptScanReport, RecheckRequiringFailures, Retry, TxByTable, + TxCaseToBeInterpreted, TxHashByTable, TxReclaim, +}; use crate::accountant::scanners::{ PrivateScanner, Scanner, ScannerCommon, StartScanError, StartableScanner, }; @@ -18,6 +24,9 @@ use crate::accountant::{ comma_joined_stringifiable, PendingPayableId, RequestTransactionReceipts, ResponseSkeleton, ScanForPendingPayables, TxReceiptsMessage, }; +use crate::blockchain::blockchain_interface::data_structures::{ + StatusReadFromReceiptCheck, TransactionBlock, TxReceiptResult, +}; use crate::sub_lib::accountant::{FinancialStatistics, PaymentThresholds}; use crate::sub_lib::wallet::Wallet; use crate::time_marking_methods; @@ -30,7 +39,6 @@ use std::collections::HashMap; use std::rc::Rc; use std::time::SystemTime; use thousands::Separable; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, TxReceiptResult}; pub struct PendingPayableScanner { pub common: ScannerCommon, @@ -303,41 +311,34 @@ impl PendingPayableScanner { tx_cases: Vec, logger: &Logger, ) -> ReceiptScanReport { - todo!() - // let scan_report = ReceiptScanReport::default(); - // msg.results - // .into_iter() - // .fold( - // scan_report, - // |scan_report_so_far, receipt_result| match receipt_result { - // TxReceiptResult::Ok(sent_tx_with_status) => match sent_tx_with_status.status { - // StatusReadFromReceiptCheck::Succeeded(tx_block) => handle_successful_tx( - // scan_report_so_far, - // self.yet_unproven_failed_payables.hashes(), - // sent_tx_with_status.tx_hash, - // tx_block, - // logger, - // ), - // StatusReadFromReceiptCheck::Failed(reason) => handle_status_with_failure( - // scan_report_so_far, - // sent_tx_with_status.sent_tx, - // reason, - // logger, - // ), - // StatusReadFromReceiptCheck::Pending => handle_still_pending_tx( - // scan_report_so_far, - // sent_tx_with_status.sent_tx, - // logger, - // ), - // }, - // TxReceiptResult::Err(e) => handle_rpc_failure( - // scan_report_so_far, - // self.yet_unproven_failed_payables.hashes(), - // e, - // logger, - // ), - // }, - // ) + let scan_report = ReceiptScanReport::default(); + tx_cases + .into_iter() + .fold(scan_report, |scan_report_so_far, tx_case| { + match tx_case.tx_receipt_result { + TxReceiptResult(Ok(sent_tx_with_status)) => match sent_tx_with_status.status { + StatusReadFromReceiptCheck::Succeeded(tx_block) => handle_successful_tx( + scan_report_so_far, + tx_case.tx_by_table, + tx_block, + logger, + ), + StatusReadFromReceiptCheck::Failed(reason) => handle_status_with_failure( + scan_report_so_far, + tx_case.tx_by_table, + reason, + logger, + ), + StatusReadFromReceiptCheck::Pending => handle_still_pending_tx( + scan_report_so_far, + tx_case.tx_by_table, + &*self.sent_payable_dao, + logger, + ), + }, + TxReceiptResult(Err(e)) => handle_rpc_failure(scan_report_so_far, e, logger), + } + }) } fn process_txs_by_state(&mut self, scan_report: ReceiptScanReport, logger: &Logger) { @@ -519,27 +520,43 @@ impl PendingPayableScanner { #[cfg(test)] mod tests { - use std::panic::{catch_unwind, AssertUnwindSafe}; - use std::sync::{Arc, Mutex}; - use std::time::{Duration, SystemTime}; - use regex::Regex; - use masq_lib::logger::Logger; - use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; - use crate::accountant::db_access_objects::failed_payable_dao::{FailedPayableDaoError, FailedTx, FailureReason, FailureRetrieveCondition, FailureStatus, ValidationStatus}; + use crate::accountant::db_access_objects::failed_payable_dao::{ + FailedPayableDaoError, FailedTx, FailureReason, FailureRetrieveCondition, FailureStatus, + ValidationStatus, + }; use crate::accountant::db_access_objects::payable_dao::PayableDaoError; - use crate::accountant::db_access_objects::sent_payable_dao::{Detection, RetrieveCondition, SentPayableDaoError, TxStatus}; + use crate::accountant::db_access_objects::sent_payable_dao::{ + Detection, RetrieveCondition, SentPayableDaoError, TxStatus, + }; use crate::accountant::db_access_objects::utils::{from_unix_timestamp, to_unix_timestamp}; + use crate::accountant::scanners::pending_payable_scanner::utils::{ + handle_status_with_failure, handle_still_pending_tx, CurrentPendingPayables, + DetectedConfirmations, DetectedFailures, FailedValidation, FailedValidationByTable, + NormalTxConfirmation, PendingPayableCache, PendingPayableScanResult, PresortedTxFailure, + ReceiptScanReport, RecheckRequiringFailures, Retry, TxByTable, TxHashByTable, TxReclaim, + }; use crate::accountant::scanners::pending_payable_scanner::PendingPayableScanner; - use crate::accountant::scanners::pending_payable_scanner::utils::{handle_status_with_failure, CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailedValidation, FailedValidationByTable, NormalTxConfirmation, PendingPayableCache, PendingPayableScanResult, PresortedTxFailure, ReceiptScanReport, RecheckRequiringFailures, Retry, TxByTable, TxHashByTable, TxReclaim}; - use crate::accountant::test_utils::{make_failed_tx, make_sent_tx, make_transaction_block, FailedPayableDaoMock, PayableDaoMock, PendingPayableScannerBuilder, SentPayableDaoMock}; - use crate::accountant::{RequestTransactionReceipts, ResponseSkeleton, TxReceiptsMessage}; - use crate::accountant::scanners::{Scanner, StartableScanner}; use crate::accountant::scanners::test_utils::PendingPayableCacheMock; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{BlockchainTxFailure, RetrievedTxStatus, StatusReadFromReceiptCheck, TransactionBlock, TxReceiptError, TxReceiptResult}; + use crate::accountant::scanners::{Scanner, StartableScanner}; + use crate::accountant::test_utils::{ + make_failed_tx, make_sent_tx, make_transaction_block, FailedPayableDaoMock, PayableDaoMock, + PendingPayableScannerBuilder, SentPayableDaoMock, + }; + use crate::accountant::{RequestTransactionReceipts, ResponseSkeleton, TxReceiptsMessage}; + use crate::blockchain::blockchain_interface::data_structures::{ + BlockchainTxFailure, RetrievedTxStatus, StatusReadFromReceiptCheck, TransactionBlock, + TxReceiptError, TxReceiptResult, + }; use crate::blockchain::errors::{AppRpcError, LocalError, RemoteError}; use crate::blockchain::test_utils::{make_block_hash, make_tx_hash}; use crate::test_utils::make_wallet; use crate::test_utils::unshared_test_utils::capture_numbers_with_separators_from_str; + use masq_lib::logger::Logger; + use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; + use regex::Regex; + use std::panic::{catch_unwind, AssertUnwindSafe}; + use std::sync::{Arc, Mutex}; + use std::time::{Duration, SystemTime}; #[test] fn start_scan_fills_in_caches_and_returns_msg() { @@ -600,7 +617,7 @@ mod tests { } #[test] - fn finish_scan_clears_caches_after_use() { + fn finish_scan_operates_caches_and_clear_them_after_use() { let pending_payable_ensure_empty_cache_params_arc = Arc::new(Mutex::new(vec![])); let failed_payable_ensure_empty_cache_params_arc = Arc::new(Mutex::new(vec![])); // To confirm a fresh, pending tx @@ -877,8 +894,15 @@ mod tests { #[test] fn handles_tx_receipt_if_the_tx_keeps_pending() { init_test_logging(); + let retrieve_txs_params_arc = Arc::new(Mutex::new(vec![])); let test_name = "handles_tx_receipt_if_the_tx_keeps_pending"; - let mut subject = PendingPayableScannerBuilder::new().build(); + let newer_sent_tx_for_older_failed_tx = make_sent_tx(2244); + let sent_payable_dao = SentPayableDaoMock::new() + .retrieve_txs_params(&retrieve_txs_params_arc) + .retrieve_txs_result(vec![newer_sent_tx_for_older_failed_tx]); + let mut subject = PendingPayableScannerBuilder::new() + .sent_payable_dao(sent_payable_dao) + .build(); let hash = make_tx_hash(0x913); let sent_tx_timestamp = to_unix_timestamp( SystemTime::now() @@ -889,6 +913,7 @@ mod tests { sent_tx.hash = hash; sent_tx.timestamp = sent_tx_timestamp; let failed_tx = make_failed_tx(789); + let failed_tx_nonce = failed_tx.nonce; let msg = TxReceiptsMessage { results: vec![ TxReceiptResult(Ok(RetrievedTxStatus::new( @@ -902,6 +927,8 @@ mod tests { ], response_skeleton_opt: None, }; + subject.current_sent_payables = Box::new(CurrentPendingPayables::default()); + subject.yet_unproven_failed_payables = Box::new(RecheckRequiringFailures::default()); subject .current_sent_payables .load_cache(vec![sent_tx.clone()]); @@ -924,6 +951,11 @@ mod tests { confirmations: DetectedConfirmations::default() } ); + let retrieve_txs_params = retrieve_txs_params_arc.lock().unwrap(); + assert_eq!( + *retrieve_txs_params, + vec![Some(RetrieveCondition::ByNonce(vec![failed_tx_nonce]))] + ); let log_handler = TestLogHandler::new(); let log_idx = log_handler.exists_log_matching(&format!( "INFO: {test_name}: Tx \ @@ -948,6 +980,31 @@ mod tests { elapsed_ms_when_before, elapsed_ms_when_after ); + log_handler.exists_log_containing(&format!( + "ERROR: {test_name}: Failed tx on a recheck was found pending by its receipt. Unexpected \ + behavior. Tx 0x0000000000000000000000000000000000000000000000000000000000000315 was \ + supposed to be replaced by the newer \ + 0x00000000000000000000000000000000000000000000000000000000000008c4" + )); + } + + #[test] + #[should_panic( + expected = "Attempted to display a replacement tx for 0x000000000000000000000000000\ + 00000000000000000000000000000000001c8 but couldn't find one in the database" + )] + fn handle_still_pending_tx_if_unexpected_behavior_due_to_already_failed_tx_and_db_retrieval_fails( + ) { + let scan_report = ReceiptScanReport::default(); + let still_pending_tx = make_failed_tx(456); + let sent_payable_dao = SentPayableDaoMock::new().retrieve_txs_result(vec![]); + + let _ = handle_still_pending_tx( + scan_report, + TxByTable::FailedPayable(still_pending_tx), + &sent_payable_dao, + &Logger::new("test"), + ); } #[test] @@ -955,8 +1012,8 @@ mod tests { init_test_logging(); let test_name = "interprets_a_failing_attempt_to_retrieve_a_tx_receipt"; let mut subject = PendingPayableScannerBuilder::new().build(); - let tx_hash_1 = make_tx_hash(0x913); - let tx_hash_2 = make_tx_hash(0x914); + let tx_hash_1 = make_tx_hash(913); + let tx_hash_2 = make_tx_hash(914); let sent_tx_timestamp = to_unix_timestamp( SystemTime::now() .checked_sub(Duration::from_secs(120)) @@ -964,7 +1021,14 @@ mod tests { ); let mut sent_tx = make_sent_tx(456); sent_tx.hash = tx_hash_1; - sent_tx.timestamp = sent_tx_timestamp; + let mut failed_tx = make_failed_tx(789); + failed_tx.hash = tx_hash_2; + subject.current_sent_payables = Box::new(CurrentPendingPayables::default()); + subject.current_sent_payables.load_cache(vec![sent_tx]); + subject.yet_unproven_failed_payables = Box::new(RecheckRequiringFailures::default()); + subject + .yet_unproven_failed_payables + .load_cache(vec![failed_tx]); let rpc_error_1 = AppRpcError::Remote(RemoteError::InvalidResponse("bluh".to_string())); let rpc_error_2 = AppRpcError::Local(LocalError::Internal); let msg = TxReceiptsMessage { @@ -1003,12 +1067,14 @@ mod tests { } ); let log_handler = TestLogHandler::new(); - let log_idx = log_handler.exists_log_containing(&format!( - "WARN: {test_name}: Failed to retrieve tx receipt for \ - 0x0000000000000000000000000000000000000000000000000000000000000913: \ - Remote(InvalidResponse(\"bluh\")). \ - Will retry receipt retrieval next cycle" - )); + let log_idx = log_handler.assert_logs_contain_in_order(vec![ + &format!("WARN: {test_name}: Failed to retrieve tx receipt for SentPayable(0x0000000000\ + 000000000000000000000000000000000000000000000000000391): Remote(InvalidResponse(\"bluh\")). \ + Will retry receipt retrieval next cycle"), + &format!("WARN: {test_name}: Failed to retrieve tx receipt for FailedPayable(0x000000000\ + 0000000000000000000000000000000000000000000000000000392): Local(Internal). Will retry \ + receipt retrieval next cycle" + )]); } #[test] diff --git a/node/src/accountant/scanners/pending_payable_scanner/utils.rs b/node/src/accountant/scanners/pending_payable_scanner/utils.rs index 928f91f90..92c1b733d 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/utils.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/utils.rs @@ -1,15 +1,26 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use std::collections::HashMap; +use crate::accountant::db_access_objects::failed_payable_dao::{ + FailedTx, FailureReason, FailureStatus, +}; +use crate::accountant::db_access_objects::sent_payable_dao::{ + RetrieveCondition, SentPayableDao, SentTx, TxStatus, +}; +use crate::accountant::db_access_objects::utils::{from_unix_timestamp, TxHash}; +use crate::blockchain::blockchain_interface::data_structures::{ + BlockchainTxFailure, TransactionBlock, TxReceiptError, TxReceiptResult, +}; +use crate::blockchain::errors::AppRpcError; +use actix::Message; +use ethereum_types::{H256, U64}; use masq_lib::logger::Logger; use masq_lib::ui_gateway::NodeToUiMessage; +use serde_derive::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::fmt::Display; use std::time::SystemTime; use thousands::Separable; -use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, FailureReason, FailureStatus}; -use crate::accountant::db_access_objects::sent_payable_dao::{SentTx, TxStatus}; -use crate::accountant::db_access_objects::utils::{from_unix_timestamp, TxHash}; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{BlockchainTxFailure, StatusReadFromReceiptCheck, TransactionBlock, TxReceiptError, TxReceiptResult}; -use crate::blockchain::errors::AppRpcError; +use variant_count::VariantCount; #[derive(Debug, Default, PartialEq, Eq, Clone)] pub struct ReceiptScanReport { @@ -46,8 +57,7 @@ impl ReceiptScanReport { } fn register_rpc_failure(&mut self, status_update: FailedValidationByTable) { - // TODO solve me by changing just the status - //self.failures.failures.push(failed_tx); + self.failures.tx_receipt_rpc_failures.push(status_update); } } @@ -103,12 +113,31 @@ pub enum FailedValidationByTable { FailedPayable(FailedValidation), } +impl From for FailedValidationByTable { + fn from(tx_receipt_error: TxReceiptError) -> Self { + match tx_receipt_error.tx_hash { + TxHashByTable::SentPayable(tx_hash) => { + Self::SentPayable(FailedValidation::new(tx_hash, tx_receipt_error.err)) + } + TxHashByTable::FailedPayable(tx_hash) => { + Self::FailedPayable(FailedValidation::new(tx_hash, tx_receipt_error.err)) + } + } + } +} + #[derive(Debug, PartialEq, Eq, Clone)] pub struct FailedValidation { pub tx_hash: TxHash, pub failure: AppRpcError, } +impl FailedValidation { + pub fn new(tx_hash: TxHash, failure: AppRpcError) -> Self { + Self { tx_hash, failure } + } +} + pub struct MismatchReport { pub noticed_at: TxHashByTable, pub remaining_hashes: Vec, @@ -235,6 +264,16 @@ pub enum TxHashByTable { FailedPayable(TxHash), } +impl TxHashByTable { + fn hash(&self) -> TxHash { + todo!() + // match self { + // TxHashByTable::SentPayable(hash) => *hash, + // TxHashByTable::FailedPayable(hash) => *hash, + // } + } +} + pub fn elapsed_in_ms(timestamp: SystemTime) -> u128 { timestamp .elapsed() @@ -245,6 +284,7 @@ pub fn elapsed_in_ms(timestamp: SystemTime) -> u128 { pub fn handle_still_pending_tx( mut scan_report: ReceiptScanReport, tx: TxByTable, + sent_payable_dao: &dyn SentPayableDao, logger: &Logger, ) -> ReceiptScanReport { match tx { @@ -258,16 +298,30 @@ pub fn handle_still_pending_tx( let failed_tx = FailedTx::from((sent_tx, FailureReason::PendingTooLong)); scan_report.register_new_failure(PresortedTxFailure::NewEntry(failed_tx)); } - TxByTable::FailedPayable(failed_tx) => todo!("What should I do here??"), // { - // todo!(); - // scan_report.register_finalization_of_unproven_failure(failed_tx.hash); + TxByTable::FailedPayable(failed_tx) => { + let replacement_tx = sent_payable_dao + .retrieve_txs(Some(RetrieveCondition::ByNonce(vec![failed_tx.nonce]))); + error!( + logger, + "Failed tx on a recheck was found pending by its receipt. \ + Unexpected behavior. Tx {:?} was supposed to be replaced by the newer {:?}", + failed_tx.hash, + replacement_tx + .get(0) + .unwrap_or_else(|| panic!( + "Attempted to display a replacement tx for {:?} but couldn't find one \ + in the database", + failed_tx.hash + )) + .hash + ) + } } scan_report } pub fn handle_successful_tx( mut scan_report: ReceiptScanReport, - unproven_failures: &[TxHash], tx: TxByTable, tx_block: TransactionBlock, logger: &Logger, @@ -279,17 +333,11 @@ pub fn handle_successful_tx( "Detected tx {:?} added to block {}.", sent_tx.hash, tx_block.block_number, ); - let detection = if !unproven_failures.contains(&sent_tx.hash) { - todo!() - } else { - todo!() - }; - let completed_sent_tx = SentTx { status: TxStatus::Confirmed { block_hash: format!("{:?}", tx_block.block_hash), block_number: tx_block.block_number.as_u64(), - detection, + detection: todo!("this must be 'Normal'"), }, ..sent_tx }; @@ -332,7 +380,6 @@ pub fn handle_status_with_failure( pub fn handle_rpc_failure( mut scan_report: ReceiptScanReport, - recheck_required_txs: &[TxHash], rpc_error: TxReceiptError, logger: &Logger, ) -> ReceiptScanReport { @@ -342,11 +389,7 @@ pub fn handle_rpc_failure( rpc_error.tx_hash, rpc_error.err ); - // TODO just to make sure we didn't ball something up badly, could be deduced also without - let validation_status_update = match rpc_error.tx_hash { - TxHashByTable::SentPayable(hash) => todo!(), - TxHashByTable::FailedPayable(hash) => todo!(), - }; + let validation_status_update = FailedValidationByTable::from(rpc_error); scan_report.register_rpc_failure(validation_status_update); scan_report } @@ -361,12 +404,11 @@ impl From for FailureReason { #[cfg(test)] mod tests { - use crate::accountant::db_access_objects::sent_payable_dao::Detection; - use crate::accountant::db_access_objects::sent_payable_dao::Detection::Normal; use crate::accountant::scanners::pending_payable_scanner::utils::{ CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailedValidation, FailedValidationByTable, NormalTxConfirmation, PendingPayableCache, PresortedTxFailure, - ReceiptScanReport, RecheckRequiringFailures, Retry, TxReclaim, + ReceiptScanReport, RecheckRequiringFailures, Retry, TransactionBlock, TxHashByTable, + TxReceiptError, TxReceiptResult, TxReclaim, }; use crate::accountant::test_utils::{make_failed_tx, make_sent_tx, make_transaction_block}; use crate::blockchain::errors::{AppRpcError, LocalError, RemoteError}; @@ -405,14 +447,16 @@ mod tests { ]; let tx_receipt_rpc_failures_feeding = vec![ vec![], - vec![FailedValidationByTable::SentPayable(FailedValidation { - tx_hash: make_tx_hash(2222), - failure: AppRpcError::Local(LocalError::Internal), - })], - vec![FailedValidationByTable::FailedPayable(FailedValidation { - tx_hash: make_tx_hash(12121), - failure: AppRpcError::Remote(RemoteError::InvalidResponse("blah".to_string())), - })], + vec![FailedValidationByTable::SentPayable(FailedValidation::new( + make_tx_hash(2222), + AppRpcError::Local(LocalError::Internal), + ))], + vec![FailedValidationByTable::FailedPayable( + FailedValidation::new( + make_tx_hash(12121), + AppRpcError::Remote(RemoteError::InvalidResponse("blah".to_string())), + ), + )], ]; let detected_confirmations_feeding = vec![ DetectedConfirmations { @@ -473,23 +517,25 @@ mod tests { #[test] fn requires_only_receipt_retrieval_retry() { let rpc_failure_feedings = vec![ - vec![FailedValidationByTable::SentPayable(FailedValidation { - tx_hash: make_tx_hash(2222), - failure: AppRpcError::Local(LocalError::Internal), - })], - vec![FailedValidationByTable::FailedPayable(FailedValidation { - tx_hash: make_tx_hash(1234), - failure: AppRpcError::Remote(RemoteError::Unreachable), - })], + vec![FailedValidationByTable::SentPayable(FailedValidation::new( + make_tx_hash(2222), + AppRpcError::Local(LocalError::Internal), + ))], + vec![FailedValidationByTable::FailedPayable( + FailedValidation::new( + make_tx_hash(1234), + AppRpcError::Remote(RemoteError::Unreachable), + ), + )], vec![ - FailedValidationByTable::SentPayable(FailedValidation { - tx_hash: make_tx_hash(2222), - failure: AppRpcError::Local(LocalError::Internal), - }), - FailedValidationByTable::FailedPayable(FailedValidation { - tx_hash: make_tx_hash(1234), - failure: AppRpcError::Remote(RemoteError::Unreachable), - }), + FailedValidationByTable::SentPayable(FailedValidation::new( + make_tx_hash(2222), + AppRpcError::Local(LocalError::Internal), + )), + FailedValidationByTable::FailedPayable(FailedValidation::new( + make_tx_hash(1234), + AppRpcError::Remote(RemoteError::Unreachable), + )), ], ]; let detected_confirmations_feeding = vec![ @@ -895,4 +941,38 @@ mod tests { ) ); } + + #[test] + fn tx_receipt_error_can_be_converted_to_failed_validation_by_table() { + let tx_hash_sent_tx = make_tx_hash(123); + let api_error_sent_tx = AppRpcError::Local(LocalError::Internal); + let receipt_error_for_sent_tx = TxReceiptError::new( + TxHashByTable::SentPayable(tx_hash_sent_tx), + api_error_sent_tx.clone(), + ); + let tx_hash_failed_tx = make_tx_hash(456); + let api_error_failed_tx = AppRpcError::Remote(RemoteError::Unreachable); + let receipt_error_for_failed_tx = TxReceiptError::new( + TxHashByTable::FailedPayable(tx_hash_failed_tx), + api_error_failed_tx.clone(), + ); + + let result_1 = FailedValidationByTable::from(receipt_error_for_sent_tx); + let result_2 = FailedValidationByTable::from(receipt_error_for_failed_tx); + + assert_eq!( + result_1, + FailedValidationByTable::SentPayable(FailedValidation::new( + tx_hash_sent_tx, + api_error_sent_tx + )) + ); + assert_eq!( + result_2, + FailedValidationByTable::FailedPayable(FailedValidation::new( + tx_hash_failed_tx, + api_error_failed_tx + )) + ); + } } diff --git a/node/src/accountant/scanners/scanners_utils.rs b/node/src/accountant/scanners/scanners_utils.rs index ecb494dee..dcaa99a10 100644 --- a/node/src/accountant/scanners/scanners_utils.rs +++ b/node/src/accountant/scanners/scanners_utils.rs @@ -341,14 +341,10 @@ mod tests { use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; use std::time::{Duration, SystemTime}; use itertools::Itertools; - use regex::Regex; use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, FailureReason, FailureStatus, ValidationStatus}; - use crate::accountant::db_access_objects::sent_payable_dao::{SentTx, TxStatus}; use crate::assert_on_testing_enum_with_all_its_variants; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::BlockchainTxFailure; use crate::blockchain::blockchain_interface::data_structures::errors::{BlockchainError, PayableTransactionError}; - use crate::blockchain::blockchain_interface::data_structures::{ProcessedPayableFallible, RpcPayableFailure}; - use crate::blockchain::errors::{AppRpcError, LocalError, RemoteError}; + use crate::blockchain::blockchain_interface::data_structures::{BlockchainTxFailure, ProcessedPayableFallible, RpcPayableFailure}; #[test] fn investigate_debt_extremes_picks_the_most_relevant_records() { diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs index 697afaee5..01cbae90d 100644 --- a/node/src/accountant/test_utils.rs +++ b/node/src/accountant/test_utils.rs @@ -3,25 +3,41 @@ #![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, FailureReason, FailureRetrieveCondition, FailureStatus, ValidationStatus}; -use crate::accountant::db_access_objects::payable_dao::{MarkPendingPayableID, PayableAccount, PayableDao, PayableDaoError, PayableDaoFactory}; -use crate::accountant::db_access_objects::sent_payable_dao::{SentPayableDao, SentPayableDaoFactory, TxStatus}; +use crate::accountant::db_access_objects::failed_payable_dao::{ + FailedPayableDao, FailedPayableDaoError, FailedPayableDaoFactory, FailedTx, FailureReason, + FailureRetrieveCondition, FailureStatus, ValidationStatus, +}; +use crate::accountant::db_access_objects::payable_dao::{ + MarkPendingPayableID, PayableAccount, PayableDao, PayableDaoError, PayableDaoFactory, +}; 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, TxHash, TxIdentifiers}; +use crate::accountant::db_access_objects::sent_payable_dao::{ + RetrieveCondition, SentPayableDaoError, SentTx, +}; +use crate::accountant::db_access_objects::sent_payable_dao::{ + SentPayableDao, SentPayableDaoFactory, TxStatus, +}; +use crate::accountant::db_access_objects::utils::{ + 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::{ BlockchainAgentWithContextMessage, PricedQualifiedPayables, QualifiedPayableWithGasPrice, QualifiedPayablesBeforeGasPriceSelection, UnpricedQualifiedPayables, }; use crate::accountant::scanners::payable_scanner_extension::PreparedAdjustment; +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::scanners_utils::payable_scanner_utils::PayableThresholdsGauge; +use crate::accountant::scanners::test_utils::PendingPayableCacheMock; use crate::accountant::scanners::PayableScanner; use crate::accountant::{gwei_to_wei, Accountant, DEFAULT_PENDING_TOO_LONG_SEC}; -use crate::blockchain::blockchain_interface::data_structures::BlockchainTransaction; +use crate::blockchain::blockchain_interface::data_structures::{ + BlockchainTransaction, TransactionBlock, +}; use crate::blockchain::test_utils::{make_block_hash, make_tx_hash}; use crate::bootstrapper::BootstrapperConfig; use crate::database::rusqlite_wrappers::TransactionSafeWrapper; @@ -34,6 +50,7 @@ use crate::sub_lib::wallet::Wallet; use crate::test_utils::make_wallet; use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMock; use crate::test_utils::unshared_test_utils::make_bc_with_defaults; +use ethereum_types::U64; use masq_lib::logger::Logger; use rusqlite::{Connection, OpenFlags, Row}; use std::any::type_name; @@ -44,12 +61,7 @@ use std::path::Path; use std::rc::Rc; use std::sync::{Arc, Mutex}; use std::time::{SystemTime, UNIX_EPOCH}; -use ethereum_types::U64; -use web3::types::{Address}; -use crate::accountant::db_access_objects::sent_payable_dao::{RetrieveCondition, SentPayableDaoError, SentTx}; -use crate::accountant::scanners::pending_payable_scanner::utils::PendingPayableCache; -use crate::accountant::scanners::test_utils::PendingPayableCacheMock; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock}; +use web3::types::Address; pub fn make_receivable_account(n: u64, expected_delinquent: bool) -> ReceivableAccount { let now = to_unix_timestamp(SystemTime::now()); diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index 6ee71e6ec..2c3f4d130 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -1,17 +1,23 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::scanners::payable_scanner_extension::msgs::{BlockchainAgentWithContextMessage, QualifiedPayablesMessage, PricedQualifiedPayables}; +use crate::accountant::db_access_objects::payable_dao::PayableAccount; +use crate::accountant::db_access_objects::sent_payable_dao::SentTx; +use crate::accountant::scanners::payable_scanner_extension::msgs::{ + BlockchainAgentWithContextMessage, PricedQualifiedPayables, QualifiedPayablesMessage, +}; use crate::accountant::{ - ReceivedPayments, ResponseSkeleton, ScanError, - SentPayables, SkeletonOptHolder, + ReceivedPayments, ResponseSkeleton, ScanError, SentPayables, SkeletonOptHolder, }; -use crate::accountant::{TxReceiptsMessage, RequestTransactionReceipts}; +use crate::accountant::{RequestTransactionReceipts, TxReceiptsMessage}; use crate::actor_system_factory::SubsFactory; +use crate::blockchain::blockchain_agent::BlockchainAgent; use crate::blockchain::blockchain_interface::blockchain_interface_web3::HashAndAmount; use crate::blockchain::blockchain_interface::data_structures::errors::{ BlockchainError, PayableTransactionError, }; -use crate::blockchain::blockchain_interface::data_structures::ProcessedPayableFallible; +use crate::blockchain::blockchain_interface::data_structures::{ + ProcessedPayableFallible, StatusReadFromReceiptCheck, TxReceiptResult, +}; use crate::blockchain::blockchain_interface::BlockchainInterface; use crate::blockchain::blockchain_interface_initializer::BlockchainInterfaceInitializer; use crate::database::db_initializer::{DbInitializationConfig, DbInitializer, DbInitializerReal}; @@ -19,35 +25,29 @@ use crate::db_config::config_dao::ConfigDaoReal; use crate::db_config::persistent_configuration::{ PersistentConfiguration, PersistentConfigurationReal, }; -use crate::sub_lib::blockchain_bridge::{ - BlockchainBridgeSubs, OutboundPaymentsInstructions, -}; +use crate::sub_lib::blockchain_bridge::{BlockchainBridgeSubs, OutboundPaymentsInstructions}; use crate::sub_lib::peer_actors::BindMessage; use crate::sub_lib::utils::{db_connection_launch_panic, handle_ui_crash_request}; -use crate::sub_lib::wallet::{Wallet}; +use crate::sub_lib::wallet::Wallet; use actix::Actor; use actix::Context; use actix::Handler; use actix::Message; use actix::{Addr, Recipient}; +use ethabi::Hash; use futures::Future; use itertools::Itertools; use masq_lib::blockchains::chains::Chain; +use masq_lib::constants::DEFAULT_GAS_PRICE_MARGIN; use masq_lib::logger::Logger; +use masq_lib::messages::ScanType; use masq_lib::ui_gateway::NodeFromUiMessage; use regex::Regex; use std::path::Path; use std::string::ToString; use std::sync::{Arc, Mutex}; use std::time::SystemTime; -use ethabi::Hash; use web3::types::H256; -use masq_lib::constants::DEFAULT_GAS_PRICE_MARGIN; -use masq_lib::messages::ScanType; -use crate::accountant::db_access_objects::payable_dao::PayableAccount; -use crate::accountant::db_access_objects::sent_payable_dao::SentTx; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxReceiptResult, StatusReadFromReceiptCheck}; -use crate::blockchain::blockchain_agent::BlockchainAgent; pub const CRASH_KEY: &str = "BLOCKCHAINBRIDGE"; pub const DEFAULT_BLOCKCHAIN_SERVICE_URL: &str = "https://0.0.0.0"; @@ -532,19 +532,28 @@ impl SubsFactory for BlockchainBridgeSub #[cfg(test)] mod tests { use super::*; + use crate::accountant::db_access_objects::failed_payable_dao::ValidationStatus; use crate::accountant::db_access_objects::payable_dao::PayableAccount; + use crate::accountant::db_access_objects::sent_payable_dao::TxStatus; use crate::accountant::db_access_objects::utils::{from_unix_timestamp, to_unix_timestamp}; + use crate::accountant::scanners::payable_scanner_extension::msgs::{ + QualifiedPayableWithGasPrice, UnpricedQualifiedPayables, + }; use crate::accountant::scanners::payable_scanner_extension::test_utils::BlockchainAgentMock; + use crate::accountant::scanners::pending_payable_scanner::utils::TxHashByTable; + use crate::accountant::test_utils::make_priced_qualified_payables; use crate::accountant::test_utils::{make_payable_account, make_sent_tx}; - use crate::accountant::test_utils::{make_priced_qualified_payables}; + use crate::accountant::PendingPayable; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError::TransactionID; use crate::blockchain::blockchain_interface::data_structures::errors::{ BlockchainAgentBuildError, PayableTransactionError, }; use crate::blockchain::blockchain_interface::data_structures::ProcessedPayableFallible::Correct; use crate::blockchain::blockchain_interface::data_structures::{ - BlockchainTransaction, RetrievedBlockchainTransactions, + BlockchainTransaction, RetrievedBlockchainTransactions, RetrievedTxStatus, + TransactionBlock, TxReceiptError, }; + use crate::blockchain::errors::{AppRpcError, RemoteError}; use crate::blockchain::test_utils::{ make_blockchain_interface_web3, make_tx_hash, ReceiptResponseBuilder, }; @@ -565,6 +574,7 @@ mod tests { use crate::test_utils::{make_paying_wallet, make_wallet}; use actix::System; use ethereum_types::U64; + use masq_lib::constants::DEFAULT_MAX_BLOCK_COUNT; use masq_lib::test_utils::logging::init_test_logging; use masq_lib::test_utils::logging::TestLogHandler; use masq_lib::test_utils::mock_blockchain_client_server::MBCSBuilder; @@ -578,14 +588,6 @@ mod tests { use std::sync::{Arc, Mutex}; use std::time::{Duration, SystemTime}; use web3::types::{TransactionReceipt, H160}; - use masq_lib::constants::DEFAULT_MAX_BLOCK_COUNT; - use crate::accountant::db_access_objects::failed_payable_dao::ValidationStatus; - use crate::accountant::db_access_objects::sent_payable_dao::{TxStatus}; - use crate::accountant::PendingPayable; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{RetrievedTxStatus, TransactionBlock, TxReceiptError}; - use crate::accountant::scanners::payable_scanner_extension::msgs::{UnpricedQualifiedPayables, QualifiedPayableWithGasPrice}; - use crate::accountant::scanners::pending_payable_scanner::utils::TxHashByTable; - use crate::blockchain::errors::{AppRpcError, RemoteError}; impl Handler> for BlockchainBridge { type Result = (); diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs index bf750d5a0..833ac3cbd 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs @@ -1,10 +1,12 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::accountant::db_access_objects::failed_payable_dao::FailureReason; -use crate::accountant::scanners::pending_payable_scanner::utils::TxHashByTable; use crate::blockchain::blockchain_interface::blockchain_interface_web3::CONTRACT_ABI; use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainError; use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainError::QueryFailed; +use crate::blockchain::blockchain_interface::data_structures::{ + BlockchainTxFailure, StatusReadFromReceiptCheck, TransactionBlock, +}; use crate::blockchain::blockchain_interface::lower_level_interface::LowBlockchainInt; use crate::blockchain::errors::AppRpcError; use actix::Message; @@ -19,30 +21,6 @@ use web3::transports::{Batch, Http}; use web3::types::{Address, BlockNumber, Filter, Log, TransactionReceipt}; use web3::{Error, Web3}; -#[derive(Debug, PartialEq, Eq, Message, Clone)] -pub struct TxReceiptResult(pub Result); - -impl TxReceiptResult { - pub fn hash(&self) -> TxHashByTable { - match &self.0 { - Ok(retrieved_tx_status) => retrieved_tx_status.tx_hash, - Err(tx_receipt_error) => tx_receipt_error.tx_hash, - } - } -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct RetrievedTxStatus { - pub tx_hash: TxHashByTable, - pub status: StatusReadFromReceiptCheck, -} - -impl RetrievedTxStatus { - pub fn new(tx_hash: TxHashByTable, status: StatusReadFromReceiptCheck) -> Self { - Self { tx_hash, status } - } -} - impl From for StatusReadFromReceiptCheck { fn from(receipt: TransactionReceipt) -> Self { match (receipt.status, receipt.block_hash, receipt.block_number) { @@ -60,62 +38,6 @@ impl From for StatusReadFromReceiptCheck { } } -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum StatusReadFromReceiptCheck { - Failed(BlockchainTxFailure), - Succeeded(TransactionBlock), - Pending, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, VariantCount)] -pub enum BlockchainTxFailure { - Unrecognized, -} - -impl Display for BlockchainTxFailure { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - BlockchainTxFailure::Unrecognized => write!(f, "Failure unrecognized"), - } - } -} - -impl Display for StatusReadFromReceiptCheck { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - StatusReadFromReceiptCheck::Failed(reason) => { - write!(f, "Failed(Reason: {})", reason) - } - StatusReadFromReceiptCheck::Succeeded(block) => { - write!( - f, - "Succeeded({},{:?})", - block.block_number, block.block_hash - ) - } - StatusReadFromReceiptCheck::Pending => write!(f, "Pending"), - } - } -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct TxReceiptError { - pub tx_hash: TxHashByTable, - pub err: AppRpcError, -} - -impl TxReceiptError { - pub fn new(tx_hash: TxHashByTable, err: AppRpcError) -> Self { - Self { tx_hash, err } - } -} - -#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Ord, PartialOrd, Serialize, Deserialize)] -pub struct TransactionBlock { - pub block_hash: H256, - pub block_number: U64, -} - pub struct LowBlockchainIntWeb3 { web3: Web3, web3_batch: Web3>, @@ -234,7 +156,7 @@ mod tests { use crate::blockchain::blockchain_interface::blockchain_interface_web3::TRANSACTION_LITERAL; use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainError::QueryFailed; use crate::blockchain::blockchain_interface::{BlockchainError, BlockchainInterface}; - use crate::blockchain::test_utils::{make_blockchain_interface_web3, make_tx_hash}; + use crate::blockchain::test_utils::{make_blockchain_interface_web3}; use crate::sub_lib::wallet::Wallet; use crate::test_utils::make_wallet; use ethereum_types::{H256, U64}; @@ -244,13 +166,7 @@ mod tests { use std::str::FromStr; use itertools::Itertools; use web3::types::{BlockNumber, Bytes, FilterBuilder, Log, TransactionReceipt, U256}; - use crate::accountant::db_access_objects::failed_payable_dao::FailureReason; - use crate::accountant::db_access_objects::sent_payable_dao::SentTx; - use crate::accountant::scanners::pending_payable_scanner::utils::TxHashByTable; - use crate::accountant::test_utils::{make_sent_tx, make_transaction_block}; - use crate::assert_on_testing_enum_with_all_its_variants; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{RetrievedTxStatus, TransactionBlock, BlockchainTxFailure, StatusReadFromReceiptCheck, TxReceiptResult, TxReceiptError}; - use crate::blockchain::errors::{AppRpcError, LocalError, RemoteError}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{BlockchainTxFailure, StatusReadFromReceiptCheck}; #[test] fn get_transaction_fee_balance_works() { @@ -680,83 +596,6 @@ mod tests { assert_eq!(tx_status, StatusReadFromReceiptCheck::Pending); } - #[test] - fn tx_status_display_works() { - // Test Failed - assert_eq!( - StatusReadFromReceiptCheck::Failed(BlockchainTxFailure::Unrecognized).to_string(), - "Failed(Reason: Failure unrecognized)" - ); - - // Test Pending - assert_eq!(StatusReadFromReceiptCheck::Pending.to_string(), "Pending"); - - // Test Succeeded - let block_number = U64::from(12345); - let block_hash = H256::from_low_u64_be(0xabcdef); - let succeeded = StatusReadFromReceiptCheck::Succeeded(TransactionBlock { - block_hash, - block_number, - }); - assert_eq!( - succeeded.to_string(), - format!("Succeeded({},0x{:x})", block_number, block_hash) - ); - } - - #[test] - fn display_for_blockchain_tx_failure_works() { - let input_and_expected_results = - vec![(BlockchainTxFailure::Unrecognized, "Failure unrecognized")]; - let inputs_len = input_and_expected_results.len(); - - let mut check_nums = input_and_expected_results - .into_iter() - .map(|(input, failure_reason)| match input { - BlockchainTxFailure::Unrecognized => { - let result = input.to_string(); - assert_eq!(result, failure_reason); - 1 - } - }) - .collect_vec(); - - assert_on_testing_enum_with_all_its_variants!(BlockchainTxFailure, check_nums, inputs_len) - } - #[test] - fn hash_can_be_fetched_from_tx_receipt_result() { - let hash_1 = TxHashByTable::SentPayable(make_tx_hash(123)); - let hash_2 = TxHashByTable::SentPayable(make_tx_hash(111)); - let hash_3 = TxHashByTable::FailedPayable(make_tx_hash(222)); - let hash_4 = TxHashByTable::FailedPayable(make_tx_hash(321)); - let positive_with_sent_payable = TxReceiptResult(Ok(RetrievedTxStatus::new( - hash_1, - StatusReadFromReceiptCheck::Pending, - ))); - let negative_with_sent_payable = TxReceiptResult(Err(TxReceiptError::new( - hash_2, - AppRpcError::Local(LocalError::Internal), - ))); - let positive_with_failed_payable = TxReceiptResult(Ok(RetrievedTxStatus::new( - hash_3, - StatusReadFromReceiptCheck::Succeeded(make_transaction_block(789)), - ))); - let negative_with_failed_payable = TxReceiptResult(Err(TxReceiptError::new( - hash_4, - AppRpcError::Remote(RemoteError::Unreachable), - ))); - - let result_1 = positive_with_sent_payable.hash(); - let result_2 = negative_with_sent_payable.hash(); - let result_3 = positive_with_failed_payable.hash(); - let result_4 = negative_with_failed_payable.hash(); - - assert_eq!(result_1, hash_1); - assert_eq!(result_2, hash_2); - assert_eq!(result_3, hash_3); - assert_eq!(result_4, hash_4); - } - fn test_deriving_tx_status_from_tx_receipt_and_adding_to_sent_tx( num_status_opt: Option, block_hash_opt: Option, diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs index f6f7f6fce..86f07e5e6 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs @@ -5,7 +5,7 @@ mod utils; use std::cmp::PartialEq; use crate::blockchain::blockchain_interface::data_structures::errors::{BlockchainError, PayableTransactionError}; -use crate::blockchain::blockchain_interface::data_structures::{BlockchainTransaction, ProcessedPayableFallible}; +use crate::blockchain::blockchain_interface::data_structures::{BlockchainTransaction, ProcessedPayableFallible, RetrievedTxStatus, StatusReadFromReceiptCheck, TxReceiptError, TxReceiptResult}; use crate::blockchain::blockchain_interface::lower_level_interface::LowBlockchainInt; use crate::blockchain::blockchain_interface::RetrievedBlockchainTransactions; use crate::blockchain::blockchain_interface::{BlockchainAgentBuildError, BlockchainInterface}; @@ -25,7 +25,7 @@ use crate::blockchain::blockchain_agent::BlockchainAgent; use crate::accountant::db_access_objects::utils::TxHash; use crate::accountant::scanners::pending_payable_scanner::utils::TxHashByTable; use crate::blockchain::blockchain_bridge::{BlockMarker, BlockScanRange, RegisterNewPendingPayables}; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{LowBlockchainIntWeb3, StatusReadFromReceiptCheck, RetrievedTxStatus, TxReceiptError, TxReceiptResult}; +use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::LowBlockchainIntWeb3; use crate::blockchain::blockchain_interface::blockchain_interface_web3::utils::{create_blockchain_agent_web3, send_payables_within_batch, BlockchainAgentFutureResult}; use crate::blockchain::errors::{AppRpcError, RemoteError}; // TODO We should probably begin to attach these constants to the interfaces more tightly, so that @@ -454,17 +454,29 @@ impl BlockchainInterfaceWeb3 { #[cfg(test)] mod tests { use super::*; + use crate::accountant::scanners::payable_scanner_extension::msgs::{ + QualifiedPayableWithGasPrice, QualifiedPayablesBeforeGasPriceSelection, + UnpricedQualifiedPayables, + }; + use crate::accountant::scanners::pending_payable_scanner::utils::TxHashByTable; + use crate::accountant::test_utils::make_payable_account; + use crate::accountant::test_utils::make_sent_tx; + use crate::blockchain::blockchain_bridge::increase_gas_price_by_margin; use crate::blockchain::blockchain_interface::blockchain_interface_web3::{ BlockchainInterfaceWeb3, CONTRACT_ABI, REQUESTS_IN_PARALLEL, TRANSACTION_LITERAL, TRANSFER_METHOD_ID, }; use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainError::QueryFailed; - use crate::blockchain::blockchain_interface::data_structures::BlockchainTransaction; + use crate::blockchain::blockchain_interface::data_structures::{ + BlockchainTransaction, BlockchainTxFailure, TransactionBlock, + }; use crate::blockchain::blockchain_interface::{ BlockchainAgentBuildError, BlockchainError, BlockchainInterface, RetrievedBlockchainTransactions, }; - use crate::blockchain::test_utils::{all_chains, make_blockchain_interface_web3, make_tx_hash, ReceiptResponseBuilder}; + use crate::blockchain::test_utils::{ + all_chains, make_blockchain_interface_web3, make_tx_hash, ReceiptResponseBuilder, + }; use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; use crate::sub_lib::wallet::Wallet; use crate::test_utils::make_paying_wallet; @@ -480,12 +492,6 @@ mod tests { use std::str::FromStr; use web3::transports::Http; use web3::types::{H256, U256}; - use crate::accountant::scanners::payable_scanner_extension::msgs::{QualifiedPayablesBeforeGasPriceSelection, QualifiedPayableWithGasPrice, UnpricedQualifiedPayables}; - use crate::accountant::test_utils::make_payable_account; - use crate::blockchain::blockchain_bridge::increase_gas_price_by_margin; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, StatusReadFromReceiptCheck}; - use crate::accountant::test_utils::make_sent_tx; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{BlockchainTxFailure}; #[test] fn constants_are_correct() { diff --git a/node/src/blockchain/blockchain_interface/data_structures/mod.rs b/node/src/blockchain/blockchain_interface/data_structures/mod.rs index 433c786e2..12a545911 100644 --- a/node/src/blockchain/blockchain_interface/data_structures/mod.rs +++ b/node/src/blockchain/blockchain_interface/data_structures/mod.rs @@ -3,11 +3,17 @@ pub mod errors; use crate::accountant::db_access_objects::utils::TxHash; +use crate::accountant::scanners::pending_payable_scanner::utils::TxHashByTable; use crate::accountant::PendingPayable; use crate::blockchain::blockchain_bridge::BlockMarker; +use crate::blockchain::errors::AppRpcError; use crate::sub_lib::wallet::Wallet; +use actix::Message; +use ethereum_types::U64; +use serde_derive::{Deserialize, Serialize}; use std::fmt; -use std::fmt::Formatter; +use std::fmt::{Display, Formatter}; +use variant_count::VariantCount; use web3::types::H256; use web3::Error; @@ -46,3 +52,176 @@ pub enum ProcessedPayableFallible { Correct(PendingPayable), Failed(RpcPayableFailure), } + +#[derive(Debug, PartialEq, Eq, Message, Clone)] +pub struct TxReceiptResult(pub Result); + +impl TxReceiptResult { + pub fn hash(&self) -> TxHashByTable { + match &self.0 { + Ok(retrieved_tx_status) => retrieved_tx_status.tx_hash, + Err(tx_receipt_error) => tx_receipt_error.tx_hash, + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct RetrievedTxStatus { + pub tx_hash: TxHashByTable, + pub status: StatusReadFromReceiptCheck, +} + +impl RetrievedTxStatus { + pub fn new(tx_hash: TxHashByTable, status: StatusReadFromReceiptCheck) -> Self { + Self { tx_hash, status } + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum StatusReadFromReceiptCheck { + Failed(BlockchainTxFailure), + Succeeded(TransactionBlock), + Pending, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, VariantCount)] +pub enum BlockchainTxFailure { + Unrecognized, +} + +impl Display for BlockchainTxFailure { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BlockchainTxFailure::Unrecognized => write!(f, "Failure unrecognized"), + } + } +} + +impl Display for StatusReadFromReceiptCheck { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + StatusReadFromReceiptCheck::Failed(reason) => { + write!(f, "Failed(Reason: {})", reason) + } + StatusReadFromReceiptCheck::Succeeded(block) => { + write!( + f, + "Succeeded({},{:?})", + block.block_number, block.block_hash + ) + } + StatusReadFromReceiptCheck::Pending => write!(f, "Pending"), + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct TxReceiptError { + pub tx_hash: TxHashByTable, + pub err: AppRpcError, +} + +impl TxReceiptError { + pub fn new(tx_hash: TxHashByTable, err: AppRpcError) -> Self { + Self { tx_hash, err } + } +} + +#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Ord, PartialOrd, Serialize, Deserialize)] +pub struct TransactionBlock { + pub block_hash: H256, + pub block_number: U64, +} + +#[cfg(test)] +mod tests { + use crate::accountant::scanners::pending_payable_scanner::utils::TxHashByTable; + use crate::accountant::test_utils::make_transaction_block; + use crate::assert_on_testing_enum_with_all_its_variants; + use crate::blockchain::blockchain_interface::data_structures::{ + BlockchainTxFailure, RetrievedTxStatus, StatusReadFromReceiptCheck, TransactionBlock, + TxReceiptError, TxReceiptResult, + }; + use crate::blockchain::errors::{AppRpcError, LocalError, RemoteError}; + use crate::blockchain::test_utils::make_tx_hash; + use ethereum_types::{H256, U64}; + use itertools::Itertools; + + #[test] + fn tx_status_display_works() { + // Test Failed + assert_eq!( + StatusReadFromReceiptCheck::Failed(BlockchainTxFailure::Unrecognized).to_string(), + "Failed(Reason: Failure unrecognized)" + ); + + // Test Pending + assert_eq!(StatusReadFromReceiptCheck::Pending.to_string(), "Pending"); + + // Test Succeeded + let block_number = U64::from(12345); + let block_hash = H256::from_low_u64_be(0xabcdef); + let succeeded = StatusReadFromReceiptCheck::Succeeded(TransactionBlock { + block_hash, + block_number, + }); + assert_eq!( + succeeded.to_string(), + format!("Succeeded({},0x{:x})", block_number, block_hash) + ); + } + + #[test] + fn display_for_blockchain_tx_failure_works() { + let input_and_expected_results = + vec![(BlockchainTxFailure::Unrecognized, "Failure unrecognized")]; + let inputs_len = input_and_expected_results.len(); + + let mut check_nums = input_and_expected_results + .into_iter() + .map(|(input, failure_reason)| match input { + BlockchainTxFailure::Unrecognized => { + let result = input.to_string(); + assert_eq!(result, failure_reason); + 1 + } + }) + .collect_vec(); + + assert_on_testing_enum_with_all_its_variants!(BlockchainTxFailure, check_nums, inputs_len) + } + + #[test] + fn hash_can_be_fetched_from_tx_receipt_result() { + let hash_1 = TxHashByTable::SentPayable(make_tx_hash(123)); + let hash_2 = TxHashByTable::SentPayable(make_tx_hash(111)); + let hash_3 = TxHashByTable::FailedPayable(make_tx_hash(222)); + let hash_4 = TxHashByTable::FailedPayable(make_tx_hash(321)); + let positive_with_sent_payable = TxReceiptResult(Ok(RetrievedTxStatus::new( + hash_1, + StatusReadFromReceiptCheck::Pending, + ))); + let negative_with_sent_payable = TxReceiptResult(Err(TxReceiptError::new( + hash_2, + AppRpcError::Local(LocalError::Internal), + ))); + let positive_with_failed_payable = TxReceiptResult(Ok(RetrievedTxStatus::new( + hash_3, + StatusReadFromReceiptCheck::Succeeded(make_transaction_block(789)), + ))); + let negative_with_failed_payable = TxReceiptResult(Err(TxReceiptError::new( + hash_4, + AppRpcError::Remote(RemoteError::Unreachable), + ))); + + let result_1 = positive_with_sent_payable.hash(); + let result_2 = negative_with_sent_payable.hash(); + let result_3 = positive_with_failed_payable.hash(); + let result_4 = negative_with_failed_payable.hash(); + + assert_eq!(result_1, hash_1); + assert_eq!(result_2, hash_2); + assert_eq!(result_3, hash_3); + assert_eq!(result_4, hash_4); + } +} diff --git a/node/src/blockchain/blockchain_interface/mod.rs b/node/src/blockchain/blockchain_interface/mod.rs index 04631d942..ce788d594 100644 --- a/node/src/blockchain/blockchain_interface/mod.rs +++ b/node/src/blockchain/blockchain_interface/mod.rs @@ -4,20 +4,25 @@ pub mod blockchain_interface_web3; pub mod data_structures; pub mod lower_level_interface; -use actix::Recipient; -use crate::blockchain::blockchain_interface::data_structures::errors::{BlockchainAgentBuildError, BlockchainError, PayableTransactionError}; -use crate::blockchain::blockchain_interface::data_structures::{ProcessedPayableFallible, RetrievedBlockchainTransactions}; +use crate::accountant::scanners::payable_scanner_extension::msgs::PricedQualifiedPayables; +use crate::accountant::scanners::pending_payable_scanner::utils::TxHashByTable; +use crate::blockchain::blockchain_agent::BlockchainAgent; +use crate::blockchain::blockchain_bridge::{ + BlockMarker, BlockScanRange, RegisterNewPendingPayables, +}; +use crate::blockchain::blockchain_interface::data_structures::errors::{ + BlockchainAgentBuildError, BlockchainError, PayableTransactionError, +}; +use crate::blockchain::blockchain_interface::data_structures::{ + ProcessedPayableFallible, RetrievedBlockchainTransactions, TxReceiptResult, +}; use crate::blockchain::blockchain_interface::lower_level_interface::LowBlockchainInt; use crate::sub_lib::wallet::Wallet; +use actix::Recipient; use futures::Future; use masq_lib::blockchains::chains::Chain; -use web3::types::Address; use masq_lib::logger::Logger; -use crate::accountant::scanners::payable_scanner_extension::msgs::{PricedQualifiedPayables}; -use crate::accountant::scanners::pending_payable_scanner::utils::TxHashByTable; -use crate::blockchain::blockchain_agent::BlockchainAgent; -use crate::blockchain::blockchain_bridge::{BlockMarker, BlockScanRange, RegisterNewPendingPayables}; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::TxReceiptResult; +use web3::types::Address; pub trait BlockchainInterface { fn contract_address(&self) -> Address; From e16c21ab342984970905205d5c6150b73da1c08a Mon Sep 17 00:00:00 2001 From: Bert Date: Wed, 6 Aug 2025 22:50:56 +0200 Subject: [PATCH 25/61] GH-642: finishing tests for the receipt interpretation but I should rearrange the code a bit - maybe to add a separative class --- .../scanners/pending_payable_scanner/mod.rs | 166 ++++++++++++++++-- .../scanners/pending_payable_scanner/utils.rs | 64 ++++--- .../data_structures/mod.rs | 6 +- 3 files changed, 187 insertions(+), 49 deletions(-) diff --git a/node/src/accountant/scanners/pending_payable_scanner/mod.rs b/node/src/accountant/scanners/pending_payable_scanner/mod.rs index 1a4cd45fd..d12a17a23 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/mod.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/mod.rs @@ -11,11 +11,11 @@ use crate::accountant::db_access_objects::sent_payable_dao::{ }; use crate::accountant::db_access_objects::utils::{TxHash, TxRecordWithHash}; use crate::accountant::scanners::pending_payable_scanner::utils::{ - handle_rpc_failure, handle_status_with_failure, handle_still_pending_tx, handle_successful_tx, - CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailedValidationByTable, - MismatchReport, NormalTxConfirmation, PendingPayableCache, PendingPayableScanResult, - PresortedTxFailure, ReceiptScanReport, RecheckRequiringFailures, Retry, TxByTable, - TxCaseToBeInterpreted, TxHashByTable, TxReclaim, + handle_rpc_failure, handle_status_with_failure, handle_still_pending_tx, + handle_tx_confirmation, CurrentPendingPayables, DetectedConfirmations, DetectedFailures, + FailedValidationByTable, MismatchReport, NormalTxConfirmation, PendingPayableCache, + PendingPayableScanResult, PresortedTxFailure, ReceiptScanReport, RecheckRequiringFailures, + Retry, TxByTable, TxCaseToBeInterpreted, TxHashByTable, TxReclaim, }; use crate::accountant::scanners::{ PrivateScanner, Scanner, ScannerCommon, StartScanError, StartableScanner, @@ -317,7 +317,7 @@ impl PendingPayableScanner { .fold(scan_report, |scan_report_so_far, tx_case| { match tx_case.tx_receipt_result { TxReceiptResult(Ok(sent_tx_with_status)) => match sent_tx_with_status.status { - StatusReadFromReceiptCheck::Succeeded(tx_block) => handle_successful_tx( + StatusReadFromReceiptCheck::Succeeded(tx_block) => handle_tx_confirmation( scan_report_so_far, tx_case.tx_by_table, tx_block, @@ -526,14 +526,15 @@ mod tests { }; use crate::accountant::db_access_objects::payable_dao::PayableDaoError; use crate::accountant::db_access_objects::sent_payable_dao::{ - Detection, RetrieveCondition, SentPayableDaoError, TxStatus, + Detection, RetrieveCondition, SentPayableDaoError, SentTx, TxStatus, }; use crate::accountant::db_access_objects::utils::{from_unix_timestamp, to_unix_timestamp}; use crate::accountant::scanners::pending_payable_scanner::utils::{ - handle_status_with_failure, handle_still_pending_tx, CurrentPendingPayables, - DetectedConfirmations, DetectedFailures, FailedValidation, FailedValidationByTable, - NormalTxConfirmation, PendingPayableCache, PendingPayableScanResult, PresortedTxFailure, - ReceiptScanReport, RecheckRequiringFailures, Retry, TxByTable, TxHashByTable, TxReclaim, + handle_status_with_failure, handle_still_pending_tx, handle_tx_confirmation, + CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailedValidation, + FailedValidationByTable, NormalTxConfirmation, PendingPayableCache, + PendingPayableScanResult, PresortedTxFailure, ReceiptScanReport, RecheckRequiringFailures, + Retry, TxByTable, TxHashByTable, TxReclaim, }; use crate::accountant::scanners::pending_payable_scanner::PendingPayableScanner; use crate::accountant::scanners::test_utils::PendingPayableCacheMock; @@ -856,9 +857,99 @@ mod tests { } #[test] - fn interprets_tx_receipt_when_transaction_status_is_a_failure() { + fn interprets_receipt_for_pending_tx_if_it_is_a_success() { init_test_logging(); - let test_name = "interprets_tx_receipt_when_transaction_status_is_a_failure"; + let test_name = "interprets_tx_receipt_if_it_is_a_success"; + let hash = make_tx_hash(0xcdef); + let mut sent_tx = make_sent_tx(2244); + sent_tx.hash = hash; + sent_tx.status = TxStatus::Pending(ValidationStatus::Waiting); + let tx_block = make_transaction_block(1234); + let logger = Logger::new(test_name); + let scan_report = ReceiptScanReport::default(); + + let result = handle_tx_confirmation( + scan_report, + TxByTable::SentPayable(sent_tx.clone()), + tx_block, + &logger, + ); + + let mut updated_tx = sent_tx; + updated_tx.status = TxStatus::Confirmed { + block_hash: "0x000000000000000000000000000000000000000000000000000000003b9aced2" + .to_string(), + block_number: 1879080904, + detection: Detection::Normal, + }; + assert_eq!( + result, + ReceiptScanReport { + failures: DetectedFailures::default(), + confirmations: DetectedConfirmations { + normal_confirmations: vec![NormalTxConfirmation { tx: updated_tx }], + reclaims: vec![] + } + } + ); + TestLogHandler::new().exists_log_containing(&format!( + "INFO: {test_name}: Pending tx 0x0000000000000000000000000000000000000000000000000000000\ + 00000cdef was confirmed on the blockchain", + )); + } + + #[test] + fn interprets_receipt_for_failed_tx_being_rechecked_when_it_is_a_success() { + init_test_logging(); + let test_name = "interprets_receipt_for_failed_tx_being_rechecked_when_it_is_a_success"; + let hash = make_tx_hash(0xcdef); + let mut failed_tx = make_failed_tx(2244); + failed_tx.hash = hash; + failed_tx.status = FailureStatus::RecheckRequired(ValidationStatus::Waiting); + failed_tx.reason = FailureReason::PendingTooLong; + let tx_block = make_transaction_block(1234); + let logger = Logger::new(test_name); + let scan_report = ReceiptScanReport::default(); + + let result = handle_tx_confirmation( + scan_report, + TxByTable::FailedPayable(failed_tx.clone()), + tx_block, + &logger, + ); + + let sent_tx = SentTx::from((failed_tx, tx_block)); + assert!( + matches!( + sent_tx.status, + TxStatus::Confirmed { + detection: Detection::Reclaim, + .. + } + ), + "We expected reclaimed tx, but it says: {:?}", + sent_tx + ); + assert_eq!( + result, + ReceiptScanReport { + failures: DetectedFailures::default(), + confirmations: DetectedConfirmations { + normal_confirmations: vec![], + reclaims: vec![TxReclaim { reclaimed: sent_tx }] + } + } + ); + TestLogHandler::new().exists_log_containing(&format!( + "INFO: {test_name}: Failed tx 0x0000000000000000000000000000000000000000000000000000000\ + 00000cdef was later confirmed on the blockchain and will be reclaimed", + )); + } + + #[test] + fn interprets_tx_receipt_for_pending_tx_when_tx_status_reveals_failure() { + init_test_logging(); + let test_name = "interprets_tx_receipt_for_pending_tx_when_transaction_status_says_failure"; let hash = make_tx_hash(0xabc); let mut sent_tx = make_sent_tx(2244); sent_tx.hash = hash; @@ -886,16 +977,53 @@ mod tests { } ); TestLogHandler::new().exists_log_containing(&format!( - "WARN: {test_name}: Tx 0x0000000000000000000000000000000000000000000000000000000000000abc \ - failed on blockchain due to: Failure unrecognized", + "WARN: {test_name}: Pending tx 0x0000000000000000000000000000000000000000000000000000000\ + 000000abc failed on blockchain due to: Unrecognized failure", + )); + } + + #[test] + fn interprets_tx_receipt_for_failed_tx_when_tx_status_reveals_failure() { + init_test_logging(); + let test_name = "interprets_tx_receipt_for_failed_tx_when_tx_status_reveals_failure"; + let tx_hash = make_tx_hash(0xabc); + let mut failed_tx = make_failed_tx(2244); + failed_tx.hash = tx_hash; + failed_tx.status = FailureStatus::RecheckRequired(ValidationStatus::Waiting); + failed_tx.reason = FailureReason::PendingTooLong; + let blockchain_failure = BlockchainTxFailure::Unrecognized; + let logger = Logger::new(test_name); + let scan_report = ReceiptScanReport::default(); + + let result = handle_status_with_failure( + scan_report, + TxByTable::FailedPayable(failed_tx.clone()), + blockchain_failure, + &logger, + ); + + assert_eq!( + result, + ReceiptScanReport { + failures: DetectedFailures { + tx_failures: vec![PresortedTxFailure::RecheckCompleted(tx_hash)], + tx_receipt_rpc_failures: vec![] + }, + confirmations: DetectedConfirmations::default() + } + ); + TestLogHandler::new().exists_log_containing(&format!( + "DEBUG: {test_name}: Failed tx 0x000000000000000000000000000000000000000000000000000000\ + 0000000abc on a recheck after \"PendingTooLong\". Status will be changed to \"Concluded\" \ + due to blockchain failure: Unrecognized failure", )); } #[test] - fn handles_tx_receipt_if_the_tx_keeps_pending() { + fn interprets_tx_receipt_if_the_tx_keeps_pending() { init_test_logging(); let retrieve_txs_params_arc = Arc::new(Mutex::new(vec![])); - let test_name = "handles_tx_receipt_if_the_tx_keeps_pending"; + let test_name = "interprets_tx_receipt_if_the_tx_keeps_pending"; let newer_sent_tx_for_older_failed_tx = make_sent_tx(2244); let sent_payable_dao = SentPayableDaoMock::new() .retrieve_txs_params(&retrieve_txs_params_arc) @@ -1008,9 +1136,9 @@ mod tests { } #[test] - fn interprets_a_failing_attempt_to_retrieve_a_tx_receipt() { + fn interprets_tx_receipt_for_a_failed_retrieval_of_a_tx_receipt() { init_test_logging(); - let test_name = "interprets_a_failing_attempt_to_retrieve_a_tx_receipt"; + let test_name = "interprets_tx_receipt_for_a_failed_retrieval_of_a_tx_receipt"; let mut subject = PendingPayableScannerBuilder::new().build(); let tx_hash_1 = make_tx_hash(913); let tx_hash_2 = make_tx_hash(914); diff --git a/node/src/accountant/scanners/pending_payable_scanner/utils.rs b/node/src/accountant/scanners/pending_payable_scanner/utils.rs index 92c1b733d..5c67b8f77 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/utils.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/utils.rs @@ -4,7 +4,7 @@ use crate::accountant::db_access_objects::failed_payable_dao::{ FailedTx, FailureReason, FailureStatus, }; use crate::accountant::db_access_objects::sent_payable_dao::{ - RetrieveCondition, SentPayableDao, SentTx, TxStatus, + Detection, RetrieveCondition, SentPayableDao, SentTx, TxStatus, }; use crate::accountant::db_access_objects::utils::{from_unix_timestamp, TxHash}; use crate::blockchain::blockchain_interface::data_structures::{ @@ -40,12 +40,12 @@ impl ReceiptScanReport { } } - fn register_success(&mut self, sent_tx: SentTx) { - // self.confirmations.push(sent_tx); + fn register_confirmed_tx(&mut self, confirmation: NormalTxConfirmation) { + self.confirmations.normal_confirmations.push(confirmation); } - fn register_failure_reclaim(&mut self, original_sent_tx: SentTx) { - todo!() + fn register_failure_reclaim(&mut self, reclaim: TxReclaim) { + self.confirmations.reclaims.push(reclaim) } fn register_new_failure(&mut self, failed_tx: PresortedTxFailure) { @@ -264,16 +264,6 @@ pub enum TxHashByTable { FailedPayable(TxHash), } -impl TxHashByTable { - fn hash(&self) -> TxHash { - todo!() - // match self { - // TxHashByTable::SentPayable(hash) => *hash, - // TxHashByTable::FailedPayable(hash) => *hash, - // } - } -} - pub fn elapsed_in_ms(timestamp: SystemTime) -> u128 { timestamp .elapsed() @@ -303,14 +293,14 @@ pub fn handle_still_pending_tx( .retrieve_txs(Some(RetrieveCondition::ByNonce(vec![failed_tx.nonce]))); error!( logger, - "Failed tx on a recheck was found pending by its receipt. \ - Unexpected behavior. Tx {:?} was supposed to be replaced by the newer {:?}", + "Failed tx on a recheck was found pending by its receipt. Unexpected behavior. \ + Tx {:?} was supposed to be replaced by the newer {:?}", failed_tx.hash, replacement_tx .get(0) .unwrap_or_else(|| panic!( - "Attempted to display a replacement tx for {:?} but couldn't find one \ - in the database", + "Attempted to display a replacement tx for {:?} but couldn't find \ + one in the database", failed_tx.hash )) .hash @@ -320,7 +310,7 @@ pub fn handle_still_pending_tx( scan_report } -pub fn handle_successful_tx( +pub fn handle_tx_confirmation( mut scan_report: ReceiptScanReport, tx: TxByTable, tx_block: TransactionBlock, @@ -330,27 +320,38 @@ pub fn handle_successful_tx( TxByTable::SentPayable(sent_tx) => { info!( logger, - "Detected tx {:?} added to block {}.", sent_tx.hash, tx_block.block_number, + "Pending tx {:?} was confirmed on the blockchain", sent_tx.hash, ); let completed_sent_tx = SentTx { status: TxStatus::Confirmed { block_hash: format!("{:?}", tx_block.block_hash), block_number: tx_block.block_number.as_u64(), - detection: todo!("this must be 'Normal'"), + detection: Detection::Normal, }, ..sent_tx }; - scan_report.register_success(completed_sent_tx); + let tx_confirmation = NormalTxConfirmation { + tx: completed_sent_tx, + }; + scan_report.register_confirmed_tx(tx_confirmation); } TxByTable::FailedPayable(failed_tx) => { - todo!() + info!( + logger, + "Failed tx {:?} was later confirmed on the blockchain and will be reclaimed", + failed_tx.hash + ); + + let sent_tx = SentTx::from((failed_tx, tx_block)); + let reclaim = TxReclaim { reclaimed: sent_tx }; + scan_report.register_failure_reclaim(reclaim); } } scan_report } -//TODO: failures handling is going to need enhancement suggested by GH-693 +//TODO: failures handling might need enhancement suggested by GH-693 pub fn handle_status_with_failure( mut scan_report: ReceiptScanReport, tx: TxByTable, @@ -364,7 +365,7 @@ pub fn handle_status_with_failure( warning!( logger, - "Tx {:?} failed on blockchain due to: {}", + "Pending tx {:?} failed on the blockchain due to: {}", failed_tx.hash, blockchain_failure ); @@ -372,7 +373,16 @@ pub fn handle_status_with_failure( scan_report.register_new_failure(PresortedTxFailure::NewEntry(failed_tx)); } TxByTable::FailedPayable(failed_tx) => { - todo!() + debug!( + logger, + "Failed tx {:?} on a recheck after {}. Status will be changed to \ + \"Concluded\" due to blockchain failure: {}", + failed_tx.hash, + failed_tx.reason, + blockchain_failure + ); + + scan_report.register_new_failure(PresortedTxFailure::RecheckCompleted(failed_tx.hash)); } } scan_report diff --git a/node/src/blockchain/blockchain_interface/data_structures/mod.rs b/node/src/blockchain/blockchain_interface/data_structures/mod.rs index 12a545911..fd84a5832 100644 --- a/node/src/blockchain/blockchain_interface/data_structures/mod.rs +++ b/node/src/blockchain/blockchain_interface/data_structures/mod.rs @@ -92,7 +92,7 @@ pub enum BlockchainTxFailure { impl Display for BlockchainTxFailure { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - BlockchainTxFailure::Unrecognized => write!(f, "Failure unrecognized"), + BlockchainTxFailure::Unrecognized => write!(f, "Unrecognized failure"), } } } @@ -152,7 +152,7 @@ mod tests { // Test Failed assert_eq!( StatusReadFromReceiptCheck::Failed(BlockchainTxFailure::Unrecognized).to_string(), - "Failed(Reason: Failure unrecognized)" + "Failed(Reason: Unrecognized failure)" ); // Test Pending @@ -174,7 +174,7 @@ mod tests { #[test] fn display_for_blockchain_tx_failure_works() { let input_and_expected_results = - vec![(BlockchainTxFailure::Unrecognized, "Failure unrecognized")]; + vec![(BlockchainTxFailure::Unrecognized, "Unrecognized failure")]; let inputs_len = input_and_expected_results.len(); let mut check_nums = input_and_expected_results From a0e9df77ace6f27c837de57c681d464cac8e0ce0 Mon Sep 17 00:00:00 2001 From: Bert Date: Thu, 7 Aug 2025 18:01:51 +0200 Subject: [PATCH 26/61] GH-642: mod structure changed, new file for TxReceiptInterpreter --- .../scanners/pending_payable_scanner/mod.rs | 415 +----------- .../tx_receipt_interpreter.rs | 618 ++++++++++++++++++ .../scanners/pending_payable_scanner/utils.rs | 150 +---- 3 files changed, 641 insertions(+), 542 deletions(-) create mode 100644 node/src/accountant/scanners/pending_payable_scanner/tx_receipt_interpreter.rs diff --git a/node/src/accountant/scanners/pending_payable_scanner/mod.rs b/node/src/accountant/scanners/pending_payable_scanner/mod.rs index d12a17a23..73945450a 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/mod.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/mod.rs @@ -1,5 +1,6 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. +mod tx_receipt_interpreter; pub mod utils; use crate::accountant::db_access_objects::failed_payable_dao::{ @@ -10,12 +11,12 @@ use crate::accountant::db_access_objects::sent_payable_dao::{ RetrieveCondition, SentPayableDao, SentPayableDaoError, SentTx, }; use crate::accountant::db_access_objects::utils::{TxHash, TxRecordWithHash}; +use crate::accountant::scanners::pending_payable_scanner::tx_receipt_interpreter::TxReceiptInterpreter; use crate::accountant::scanners::pending_payable_scanner::utils::{ - handle_rpc_failure, handle_status_with_failure, handle_still_pending_tx, - handle_tx_confirmation, CurrentPendingPayables, DetectedConfirmations, DetectedFailures, - FailedValidationByTable, MismatchReport, NormalTxConfirmation, PendingPayableCache, - PendingPayableScanResult, PresortedTxFailure, ReceiptScanReport, RecheckRequiringFailures, - Retry, TxByTable, TxCaseToBeInterpreted, TxHashByTable, TxReclaim, + CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailedValidationByTable, + MismatchReport, NormalTxConfirmation, PendingPayableCache, PendingPayableScanResult, + PresortedTxFailure, ReceiptScanReport, RecheckRequiringFailures, Retry, TxByTable, + TxCaseToBeInterpreted, TxHashByTable, TxReclaim, }; use crate::accountant::scanners::{ PrivateScanner, Scanner, ScannerCommon, StartScanError, StartableScanner, @@ -216,7 +217,11 @@ impl PendingPayableScanner { debug!(logger, "Processing receipts for {} txs", msg.results.len()); let interpretable_data = self.prepare_cases_to_interpret(msg, logger); - self.compose_receipt_scan_report(interpretable_data, logger) + TxReceiptInterpreter::default().compose_receipt_scan_report( + interpretable_data, + &self, + logger, + ) } fn prepare_cases_to_interpret( @@ -255,7 +260,7 @@ impl PendingPayableScanner { &mut self, mut cases: Vec, receipt_result: TxReceiptResult, - looked_up_hash: TxHashByTable, /* current_sent_payables: &mut dyn PendingPayableCache, yet_unproven_failed_payables: &mut dyn PendingPayableCache*/ + looked_up_hash: TxHashByTable, ) -> Either, MismatchReport> { match looked_up_hash { TxHashByTable::SentPayable(tx_hash) => { @@ -306,41 +311,6 @@ impl PendingPayableScanner { ) } - fn compose_receipt_scan_report( - &self, - tx_cases: Vec, - logger: &Logger, - ) -> ReceiptScanReport { - let scan_report = ReceiptScanReport::default(); - tx_cases - .into_iter() - .fold(scan_report, |scan_report_so_far, tx_case| { - match tx_case.tx_receipt_result { - TxReceiptResult(Ok(sent_tx_with_status)) => match sent_tx_with_status.status { - StatusReadFromReceiptCheck::Succeeded(tx_block) => handle_tx_confirmation( - scan_report_so_far, - tx_case.tx_by_table, - tx_block, - logger, - ), - StatusReadFromReceiptCheck::Failed(reason) => handle_status_with_failure( - scan_report_so_far, - tx_case.tx_by_table, - reason, - logger, - ), - StatusReadFromReceiptCheck::Pending => handle_still_pending_tx( - scan_report_so_far, - tx_case.tx_by_table, - &*self.sent_payable_dao, - logger, - ), - }, - TxReceiptResult(Err(e)) => handle_rpc_failure(scan_report_so_far, e, logger), - } - }) - } - fn process_txs_by_state(&mut self, scan_report: ReceiptScanReport, logger: &Logger) { self.handle_confirmed_transactions(scan_report.confirmations, logger); self.handle_failed_transactions(scan_report.failures, logger); @@ -530,7 +500,6 @@ mod tests { }; use crate::accountant::db_access_objects::utils::{from_unix_timestamp, to_unix_timestamp}; use crate::accountant::scanners::pending_payable_scanner::utils::{ - handle_status_with_failure, handle_still_pending_tx, handle_tx_confirmation, CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailedValidation, FailedValidationByTable, NormalTxConfirmation, PendingPayableCache, PendingPayableScanResult, PresortedTxFailure, ReceiptScanReport, RecheckRequiringFailures, @@ -543,21 +512,20 @@ mod tests { make_failed_tx, make_sent_tx, make_transaction_block, FailedPayableDaoMock, PayableDaoMock, PendingPayableScannerBuilder, SentPayableDaoMock, }; - use crate::accountant::{RequestTransactionReceipts, ResponseSkeleton, TxReceiptsMessage}; + use crate::accountant::{RequestTransactionReceipts, TxReceiptsMessage}; use crate::blockchain::blockchain_interface::data_structures::{ - BlockchainTxFailure, RetrievedTxStatus, StatusReadFromReceiptCheck, TransactionBlock, - TxReceiptError, TxReceiptResult, + RetrievedTxStatus, StatusReadFromReceiptCheck, TransactionBlock, TxReceiptError, + TxReceiptResult, }; use crate::blockchain::errors::{AppRpcError, LocalError, RemoteError}; use crate::blockchain::test_utils::{make_block_hash, make_tx_hash}; use crate::test_utils::make_wallet; - use crate::test_utils::unshared_test_utils::capture_numbers_with_separators_from_str; use masq_lib::logger::Logger; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; use regex::Regex; use std::panic::{catch_unwind, AssertUnwindSafe}; use std::sync::{Arc, Mutex}; - use std::time::{Duration, SystemTime}; + use std::time::SystemTime; #[test] fn start_scan_fills_in_caches_and_returns_msg() { @@ -578,6 +546,8 @@ mod tests { .failed_payable_dao(failed_payable_dao) .build(); let logger = Logger::new("start_scan_fills_in_caches_and_returns_msg"); + subject.current_sent_payables = Box::new(CurrentPendingPayables::default()); + subject.yet_unproven_failed_payables = Box::new(RecheckRequiringFailures::default()); let pending_payable_cache_before = subject.current_sent_payables.dump_cache(); let failed_payable_cache_before = subject.yet_unproven_failed_payables.dump_cache(); @@ -856,355 +826,6 @@ mod tests { ); } - #[test] - fn interprets_receipt_for_pending_tx_if_it_is_a_success() { - init_test_logging(); - let test_name = "interprets_tx_receipt_if_it_is_a_success"; - let hash = make_tx_hash(0xcdef); - let mut sent_tx = make_sent_tx(2244); - sent_tx.hash = hash; - sent_tx.status = TxStatus::Pending(ValidationStatus::Waiting); - let tx_block = make_transaction_block(1234); - let logger = Logger::new(test_name); - let scan_report = ReceiptScanReport::default(); - - let result = handle_tx_confirmation( - scan_report, - TxByTable::SentPayable(sent_tx.clone()), - tx_block, - &logger, - ); - - let mut updated_tx = sent_tx; - updated_tx.status = TxStatus::Confirmed { - block_hash: "0x000000000000000000000000000000000000000000000000000000003b9aced2" - .to_string(), - block_number: 1879080904, - detection: Detection::Normal, - }; - assert_eq!( - result, - ReceiptScanReport { - failures: DetectedFailures::default(), - confirmations: DetectedConfirmations { - normal_confirmations: vec![NormalTxConfirmation { tx: updated_tx }], - reclaims: vec![] - } - } - ); - TestLogHandler::new().exists_log_containing(&format!( - "INFO: {test_name}: Pending tx 0x0000000000000000000000000000000000000000000000000000000\ - 00000cdef was confirmed on the blockchain", - )); - } - - #[test] - fn interprets_receipt_for_failed_tx_being_rechecked_when_it_is_a_success() { - init_test_logging(); - let test_name = "interprets_receipt_for_failed_tx_being_rechecked_when_it_is_a_success"; - let hash = make_tx_hash(0xcdef); - let mut failed_tx = make_failed_tx(2244); - failed_tx.hash = hash; - failed_tx.status = FailureStatus::RecheckRequired(ValidationStatus::Waiting); - failed_tx.reason = FailureReason::PendingTooLong; - let tx_block = make_transaction_block(1234); - let logger = Logger::new(test_name); - let scan_report = ReceiptScanReport::default(); - - let result = handle_tx_confirmation( - scan_report, - TxByTable::FailedPayable(failed_tx.clone()), - tx_block, - &logger, - ); - - let sent_tx = SentTx::from((failed_tx, tx_block)); - assert!( - matches!( - sent_tx.status, - TxStatus::Confirmed { - detection: Detection::Reclaim, - .. - } - ), - "We expected reclaimed tx, but it says: {:?}", - sent_tx - ); - assert_eq!( - result, - ReceiptScanReport { - failures: DetectedFailures::default(), - confirmations: DetectedConfirmations { - normal_confirmations: vec![], - reclaims: vec![TxReclaim { reclaimed: sent_tx }] - } - } - ); - TestLogHandler::new().exists_log_containing(&format!( - "INFO: {test_name}: Failed tx 0x0000000000000000000000000000000000000000000000000000000\ - 00000cdef was later confirmed on the blockchain and will be reclaimed", - )); - } - - #[test] - fn interprets_tx_receipt_for_pending_tx_when_tx_status_reveals_failure() { - init_test_logging(); - let test_name = "interprets_tx_receipt_for_pending_tx_when_transaction_status_says_failure"; - let hash = make_tx_hash(0xabc); - let mut sent_tx = make_sent_tx(2244); - sent_tx.hash = hash; - let blockchain_failure = BlockchainTxFailure::Unrecognized; - let logger = Logger::new(test_name); - let scan_report = ReceiptScanReport::default(); - - let result = handle_status_with_failure( - scan_report, - TxByTable::SentPayable(sent_tx.clone()), - blockchain_failure, - &logger, - ); - - let failure_reason = blockchain_failure.into(); - let failed_tx = FailedTx::from((sent_tx, failure_reason)); - assert_eq!( - result, - ReceiptScanReport { - failures: DetectedFailures { - tx_failures: vec![PresortedTxFailure::NewEntry(failed_tx)], - tx_receipt_rpc_failures: vec![] - }, - confirmations: DetectedConfirmations::default() - } - ); - TestLogHandler::new().exists_log_containing(&format!( - "WARN: {test_name}: Pending tx 0x0000000000000000000000000000000000000000000000000000000\ - 000000abc failed on blockchain due to: Unrecognized failure", - )); - } - - #[test] - fn interprets_tx_receipt_for_failed_tx_when_tx_status_reveals_failure() { - init_test_logging(); - let test_name = "interprets_tx_receipt_for_failed_tx_when_tx_status_reveals_failure"; - let tx_hash = make_tx_hash(0xabc); - let mut failed_tx = make_failed_tx(2244); - failed_tx.hash = tx_hash; - failed_tx.status = FailureStatus::RecheckRequired(ValidationStatus::Waiting); - failed_tx.reason = FailureReason::PendingTooLong; - let blockchain_failure = BlockchainTxFailure::Unrecognized; - let logger = Logger::new(test_name); - let scan_report = ReceiptScanReport::default(); - - let result = handle_status_with_failure( - scan_report, - TxByTable::FailedPayable(failed_tx.clone()), - blockchain_failure, - &logger, - ); - - assert_eq!( - result, - ReceiptScanReport { - failures: DetectedFailures { - tx_failures: vec![PresortedTxFailure::RecheckCompleted(tx_hash)], - tx_receipt_rpc_failures: vec![] - }, - confirmations: DetectedConfirmations::default() - } - ); - TestLogHandler::new().exists_log_containing(&format!( - "DEBUG: {test_name}: Failed tx 0x000000000000000000000000000000000000000000000000000000\ - 0000000abc on a recheck after \"PendingTooLong\". Status will be changed to \"Concluded\" \ - due to blockchain failure: Unrecognized failure", - )); - } - - #[test] - fn interprets_tx_receipt_if_the_tx_keeps_pending() { - init_test_logging(); - let retrieve_txs_params_arc = Arc::new(Mutex::new(vec![])); - let test_name = "interprets_tx_receipt_if_the_tx_keeps_pending"; - let newer_sent_tx_for_older_failed_tx = make_sent_tx(2244); - let sent_payable_dao = SentPayableDaoMock::new() - .retrieve_txs_params(&retrieve_txs_params_arc) - .retrieve_txs_result(vec![newer_sent_tx_for_older_failed_tx]); - let mut subject = PendingPayableScannerBuilder::new() - .sent_payable_dao(sent_payable_dao) - .build(); - let hash = make_tx_hash(0x913); - let sent_tx_timestamp = to_unix_timestamp( - SystemTime::now() - .checked_sub(Duration::from_secs(120)) - .unwrap(), - ); - let mut sent_tx = make_sent_tx(456); - sent_tx.hash = hash; - sent_tx.timestamp = sent_tx_timestamp; - let failed_tx = make_failed_tx(789); - let failed_tx_nonce = failed_tx.nonce; - let msg = TxReceiptsMessage { - results: vec![ - TxReceiptResult(Ok(RetrievedTxStatus::new( - TxHashByTable::SentPayable(sent_tx.hash), - StatusReadFromReceiptCheck::Pending, - ))), - TxReceiptResult(Ok(RetrievedTxStatus::new( - TxHashByTable::FailedPayable(failed_tx.hash), - StatusReadFromReceiptCheck::Pending, - ))), - ], - response_skeleton_opt: None, - }; - subject.current_sent_payables = Box::new(CurrentPendingPayables::default()); - subject.yet_unproven_failed_payables = Box::new(RecheckRequiringFailures::default()); - subject - .current_sent_payables - .load_cache(vec![sent_tx.clone()]); - subject - .yet_unproven_failed_payables - .load_cache(vec![failed_tx]); - let before = SystemTime::now(); - - let result = subject.interpret_tx_receipts(msg, &Logger::new(test_name)); - - let after = SystemTime::now(); - let expected_failed_tx = FailedTx::from((sent_tx, FailureReason::PendingTooLong)); - assert_eq!( - result, - ReceiptScanReport { - failures: DetectedFailures { - tx_failures: vec![PresortedTxFailure::NewEntry(expected_failed_tx)], - tx_receipt_rpc_failures: vec![] - }, - confirmations: DetectedConfirmations::default() - } - ); - let retrieve_txs_params = retrieve_txs_params_arc.lock().unwrap(); - assert_eq!( - *retrieve_txs_params, - vec![Some(RetrieveCondition::ByNonce(vec![failed_tx_nonce]))] - ); - let log_handler = TestLogHandler::new(); - let log_idx = log_handler.exists_log_matching(&format!( - "INFO: {test_name}: Tx \ - 0x0000000000000000000000000000000000000000000000000000000000000913 not confirmed within \ - \\d{{1,3}}(,\\d{{3}})* ms. Will resubmit with higher gas price" - )); - let log_msg = log_handler.get_log_at(log_idx); - let str_elapsed_ms = capture_numbers_with_separators_from_str(&log_msg, 3, ','); - let elapsed_ms = str_elapsed_ms[0].replace(",", "").parse::().unwrap(); - let elapsed_ms_when_before = before - .duration_since(from_unix_timestamp(sent_tx_timestamp)) - .unwrap() - .as_millis(); - let elapsed_ms_when_after = after - .duration_since(from_unix_timestamp(sent_tx_timestamp)) - .unwrap() - .as_millis(); - assert!( - elapsed_ms_when_before <= elapsed_ms && elapsed_ms <= elapsed_ms_when_after, - "we expected the elapsed time {} ms to be between {} and {}.", - elapsed_ms, - elapsed_ms_when_before, - elapsed_ms_when_after - ); - log_handler.exists_log_containing(&format!( - "ERROR: {test_name}: Failed tx on a recheck was found pending by its receipt. Unexpected \ - behavior. Tx 0x0000000000000000000000000000000000000000000000000000000000000315 was \ - supposed to be replaced by the newer \ - 0x00000000000000000000000000000000000000000000000000000000000008c4" - )); - } - - #[test] - #[should_panic( - expected = "Attempted to display a replacement tx for 0x000000000000000000000000000\ - 00000000000000000000000000000000001c8 but couldn't find one in the database" - )] - fn handle_still_pending_tx_if_unexpected_behavior_due_to_already_failed_tx_and_db_retrieval_fails( - ) { - let scan_report = ReceiptScanReport::default(); - let still_pending_tx = make_failed_tx(456); - let sent_payable_dao = SentPayableDaoMock::new().retrieve_txs_result(vec![]); - - let _ = handle_still_pending_tx( - scan_report, - TxByTable::FailedPayable(still_pending_tx), - &sent_payable_dao, - &Logger::new("test"), - ); - } - - #[test] - fn interprets_tx_receipt_for_a_failed_retrieval_of_a_tx_receipt() { - init_test_logging(); - let test_name = "interprets_tx_receipt_for_a_failed_retrieval_of_a_tx_receipt"; - let mut subject = PendingPayableScannerBuilder::new().build(); - let tx_hash_1 = make_tx_hash(913); - let tx_hash_2 = make_tx_hash(914); - let sent_tx_timestamp = to_unix_timestamp( - SystemTime::now() - .checked_sub(Duration::from_secs(120)) - .unwrap(), - ); - let mut sent_tx = make_sent_tx(456); - sent_tx.hash = tx_hash_1; - let mut failed_tx = make_failed_tx(789); - failed_tx.hash = tx_hash_2; - subject.current_sent_payables = Box::new(CurrentPendingPayables::default()); - subject.current_sent_payables.load_cache(vec![sent_tx]); - subject.yet_unproven_failed_payables = Box::new(RecheckRequiringFailures::default()); - subject - .yet_unproven_failed_payables - .load_cache(vec![failed_tx]); - let rpc_error_1 = AppRpcError::Remote(RemoteError::InvalidResponse("bluh".to_string())); - let rpc_error_2 = AppRpcError::Local(LocalError::Internal); - let msg = TxReceiptsMessage { - results: vec![ - TxReceiptResult(Err(TxReceiptError::new( - TxHashByTable::SentPayable(tx_hash_1), - rpc_error_1.clone(), - ))), - TxReceiptResult(Err(TxReceiptError::new( - TxHashByTable::FailedPayable(tx_hash_2), - rpc_error_2.clone(), - ))), - ], - response_skeleton_opt: None, - }; - - let result = subject.interpret_tx_receipts(msg, &Logger::new(test_name)); - - assert_eq!( - result, - ReceiptScanReport { - failures: DetectedFailures { - tx_failures: vec![], - tx_receipt_rpc_failures: vec![ - FailedValidationByTable::SentPayable(FailedValidation { - tx_hash: tx_hash_1, - failure: rpc_error_1 - }), - FailedValidationByTable::FailedPayable(FailedValidation { - tx_hash: tx_hash_2, - failure: rpc_error_2 - }) - ] - }, - confirmations: DetectedConfirmations::default() - } - ); - let log_handler = TestLogHandler::new(); - let log_idx = log_handler.assert_logs_contain_in_order(vec![ - &format!("WARN: {test_name}: Failed to retrieve tx receipt for SentPayable(0x0000000000\ - 000000000000000000000000000000000000000000000000000391): Remote(InvalidResponse(\"bluh\")). \ - Will retry receipt retrieval next cycle"), - &format!("WARN: {test_name}: Failed to retrieve tx receipt for FailedPayable(0x000000000\ - 0000000000000000000000000000000000000000000000000000392): Local(Internal). Will retry \ - receipt retrieval next cycle" - )]); - } - #[test] fn handle_failed_transactions_can_process_standard_tx_failures() { let insert_new_records_params_arc = Arc::new(Mutex::new(vec![])); 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 new file mode 100644 index 000000000..1d76bee02 --- /dev/null +++ b/node/src/accountant/scanners/pending_payable_scanner/tx_receipt_interpreter.rs @@ -0,0 +1,618 @@ +// Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, FailureReason}; +use crate::accountant::db_access_objects::sent_payable_dao::{ + Detection, RetrieveCondition, SentPayableDao, SentTx, TxStatus, +}; +use crate::accountant::db_access_objects::utils::from_unix_timestamp; +use crate::accountant::scanners::pending_payable_scanner::utils::{ + FailedValidationByTable, NormalTxConfirmation, PresortedTxFailure, ReceiptScanReport, + TxByTable, TxCaseToBeInterpreted, TxReclaim, +}; +use crate::accountant::scanners::pending_payable_scanner::PendingPayableScanner; +use crate::accountant::TxReceiptsMessage; +use crate::blockchain::blockchain_interface::data_structures::{ + BlockchainTxFailure, StatusReadFromReceiptCheck, TransactionBlock, TxReceiptError, + TxReceiptResult, +}; +use masq_lib::logger::Logger; +use std::time::SystemTime; +use thousands::Separable; + +#[derive(Default)] +pub struct TxReceiptInterpreter {} + +impl TxReceiptInterpreter { + pub fn compose_receipt_scan_report( + &self, + tx_cases: Vec, + pending_payable_scanner: &PendingPayableScanner, + logger: &Logger, + ) -> ReceiptScanReport { + let scan_report = ReceiptScanReport::default(); + tx_cases + .into_iter() + .fold(scan_report, |scan_report_so_far, tx_case| { + match tx_case.tx_receipt_result { + TxReceiptResult(Ok(sent_tx_with_status)) => match sent_tx_with_status.status { + StatusReadFromReceiptCheck::Succeeded(tx_block) => { + Self::handle_tx_confirmation( + scan_report_so_far, + tx_case.tx_by_table, + tx_block, + logger, + ) + } + StatusReadFromReceiptCheck::Failed(reason) => { + Self::handle_status_with_failure( + scan_report_so_far, + tx_case.tx_by_table, + reason, + logger, + ) + } + StatusReadFromReceiptCheck::Pending => Self::handle_still_pending_tx( + scan_report_so_far, + tx_case.tx_by_table, + &*pending_payable_scanner.sent_payable_dao, + logger, + ), + }, + TxReceiptResult(Err(e)) => { + Self::handle_rpc_failure(scan_report_so_far, e, logger) + } + } + }) + } + + fn handle_still_pending_tx( + mut scan_report: ReceiptScanReport, + tx: TxByTable, + sent_payable_dao: &dyn SentPayableDao, + logger: &Logger, + ) -> ReceiptScanReport { + match tx { + TxByTable::SentPayable(sent_tx) => { + info!( + logger, + "Tx {:?} not confirmed within {} ms. Will resubmit with higher gas price", + sent_tx.hash, + Self::elapsed_in_ms(from_unix_timestamp(sent_tx.timestamp)) + .separate_with_commas() + ); + let failed_tx = FailedTx::from((sent_tx, FailureReason::PendingTooLong)); + scan_report.register_new_failure(PresortedTxFailure::NewEntry(failed_tx)); + } + TxByTable::FailedPayable(failed_tx) => { + let replacement_tx = sent_payable_dao + .retrieve_txs(Some(RetrieveCondition::ByNonce(vec![failed_tx.nonce]))); + error!( + logger, + "Failed tx on a recheck was found pending by its receipt. Unexpected behavior. \ + Tx {:?} was supposed to be replaced by the newer {:?}", + failed_tx.hash, + replacement_tx + .get(0) + .unwrap_or_else(|| panic!( + "Attempted to display a replacement tx for {:?} but couldn't find \ + one in the database", + failed_tx.hash + )) + .hash + ) + } + } + scan_report + } + + fn elapsed_in_ms(timestamp: SystemTime) -> u128 { + timestamp + .elapsed() + .expect("time calculation for elapsed failed") + .as_millis() + } + + fn handle_tx_confirmation( + mut scan_report: ReceiptScanReport, + tx: TxByTable, + tx_block: TransactionBlock, + logger: &Logger, + ) -> ReceiptScanReport { + match tx { + TxByTable::SentPayable(sent_tx) => { + info!( + logger, + "Pending tx {:?} was confirmed on the blockchain", sent_tx.hash, + ); + + let completed_sent_tx = SentTx { + status: TxStatus::Confirmed { + block_hash: format!("{:?}", tx_block.block_hash), + block_number: tx_block.block_number.as_u64(), + detection: Detection::Normal, + }, + ..sent_tx + }; + let tx_confirmation = NormalTxConfirmation { + tx: completed_sent_tx, + }; + scan_report.register_confirmed_tx(tx_confirmation); + } + TxByTable::FailedPayable(failed_tx) => { + info!( + logger, + "Failed tx {:?} was later confirmed on the blockchain and will be reclaimed", + failed_tx.hash + ); + + let sent_tx = SentTx::from((failed_tx, tx_block)); + let reclaim = TxReclaim { reclaimed: sent_tx }; + scan_report.register_failure_reclaim(reclaim); + } + } + scan_report + } + + //TODO: failures handling might need enhancement suggested by GH-693 + fn handle_status_with_failure( + mut scan_report: ReceiptScanReport, + tx: TxByTable, + blockchain_failure: BlockchainTxFailure, + logger: &Logger, + ) -> ReceiptScanReport { + match tx { + TxByTable::SentPayable(sent_tx) => { + let failure_reason = FailureReason::from(blockchain_failure); + let failed_tx = FailedTx::from((sent_tx, failure_reason)); + + warning!( + logger, + "Pending tx {:?} failed on the blockchain due to: {}", + failed_tx.hash, + blockchain_failure + ); + + scan_report.register_new_failure(PresortedTxFailure::NewEntry(failed_tx)); + } + TxByTable::FailedPayable(failed_tx) => { + debug!( + logger, + "Failed tx {:?} on a recheck after {}. Status will be changed to \ + \"Concluded\" due to blockchain failure: {}", + failed_tx.hash, + failed_tx.reason, + blockchain_failure + ); + + scan_report + .register_new_failure(PresortedTxFailure::RecheckCompleted(failed_tx.hash)); + } + } + scan_report + } + + fn handle_rpc_failure( + mut scan_report: ReceiptScanReport, + rpc_error: TxReceiptError, + logger: &Logger, + ) -> ReceiptScanReport { + warning!( + logger, + "Failed to retrieve tx receipt for {:?}: {:?}. Will retry receipt retrieval next cycle", + rpc_error.tx_hash, + rpc_error.err + ); + let validation_status_update = FailedValidationByTable::from(rpc_error); + scan_report.register_rpc_failure(validation_status_update); + scan_report + } +} + +#[cfg(test)] +mod tests { + use crate::accountant::db_access_objects::failed_payable_dao::{ + FailedTx, FailureReason, FailureStatus, ValidationStatus, + }; + use crate::accountant::db_access_objects::sent_payable_dao::{ + Detection, RetrieveCondition, SentTx, TxStatus, + }; + use crate::accountant::db_access_objects::utils::{from_unix_timestamp, to_unix_timestamp}; + use crate::accountant::scanners::pending_payable_scanner::tx_receipt_interpreter::TxReceiptInterpreter; + use crate::accountant::scanners::pending_payable_scanner::utils::{ + DetectedConfirmations, DetectedFailures, FailedValidation, FailedValidationByTable, + NormalTxConfirmation, PresortedTxFailure, ReceiptScanReport, RecheckRequiringFailures, + TxByTable, TxHashByTable, TxReclaim, + }; + use crate::accountant::test_utils::{ + make_failed_tx, make_sent_tx, make_transaction_block, SentPayableDaoMock, + }; + use crate::blockchain::blockchain_interface::data_structures::{ + BlockchainTxFailure, TxReceiptError, + }; + use crate::blockchain::errors::{AppRpcError, LocalError, RemoteError}; + use crate::blockchain::test_utils::make_tx_hash; + use crate::test_utils::unshared_test_utils::capture_numbers_with_separators_from_str; + use masq_lib::logger::Logger; + use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; + use std::sync::{Arc, Mutex}; + use std::time::{Duration, SystemTime}; + + #[test] + fn interprets_receipt_for_pending_tx_if_it_is_a_success() { + init_test_logging(); + let test_name = "interprets_tx_receipt_if_it_is_a_success"; + let hash = make_tx_hash(0xcdef); + let mut sent_tx = make_sent_tx(2244); + sent_tx.hash = hash; + sent_tx.status = TxStatus::Pending(ValidationStatus::Waiting); + let tx_block = make_transaction_block(1234); + let logger = Logger::new(test_name); + let scan_report = ReceiptScanReport::default(); + + let result = TxReceiptInterpreter::handle_tx_confirmation( + scan_report, + TxByTable::SentPayable(sent_tx.clone()), + tx_block, + &logger, + ); + + let mut updated_tx = sent_tx; + updated_tx.status = TxStatus::Confirmed { + block_hash: "0x000000000000000000000000000000000000000000000000000000003b9aced2" + .to_string(), + block_number: 1879080904, + detection: Detection::Normal, + }; + assert_eq!( + result, + ReceiptScanReport { + failures: DetectedFailures::default(), + confirmations: DetectedConfirmations { + normal_confirmations: vec![NormalTxConfirmation { tx: updated_tx }], + reclaims: vec![] + } + } + ); + TestLogHandler::new().exists_log_containing(&format!( + "INFO: {test_name}: Pending tx 0x0000000000000000000000000000000000000000000000000000000\ + 00000cdef was confirmed on the blockchain", + )); + } + + #[test] + fn interprets_receipt_for_failed_tx_being_rechecked_when_it_is_a_success() { + init_test_logging(); + let test_name = "interprets_receipt_for_failed_tx_being_rechecked_when_it_is_a_success"; + let hash = make_tx_hash(0xcdef); + let mut failed_tx = make_failed_tx(2244); + failed_tx.hash = hash; + failed_tx.status = FailureStatus::RecheckRequired(ValidationStatus::Waiting); + failed_tx.reason = FailureReason::PendingTooLong; + let tx_block = make_transaction_block(1234); + let logger = Logger::new(test_name); + let scan_report = ReceiptScanReport::default(); + + let result = TxReceiptInterpreter::handle_tx_confirmation( + scan_report, + TxByTable::FailedPayable(failed_tx.clone()), + tx_block, + &logger, + ); + + let sent_tx = SentTx::from((failed_tx, tx_block)); + assert!( + matches!( + sent_tx.status, + TxStatus::Confirmed { + detection: Detection::Reclaim, + .. + } + ), + "We expected reclaimed tx, but it says: {:?}", + sent_tx + ); + assert_eq!( + result, + ReceiptScanReport { + failures: DetectedFailures::default(), + confirmations: DetectedConfirmations { + normal_confirmations: vec![], + reclaims: vec![TxReclaim { reclaimed: sent_tx }] + } + } + ); + TestLogHandler::new().exists_log_containing(&format!( + "INFO: {test_name}: Failed tx 0x0000000000000000000000000000000000000000000000000000000\ + 00000cdef was later confirmed on the blockchain and will be reclaimed", + )); + } + + #[test] + fn interprets_tx_receipt_for_pending_tx_when_tx_status_reveals_failure() { + init_test_logging(); + let test_name = "interprets_tx_receipt_for_pending_tx_when_transaction_status_says_failure"; + let hash = make_tx_hash(0xabc); + let mut sent_tx = make_sent_tx(2244); + sent_tx.hash = hash; + let blockchain_failure = BlockchainTxFailure::Unrecognized; + let logger = Logger::new(test_name); + let scan_report = ReceiptScanReport::default(); + + let result = TxReceiptInterpreter::handle_status_with_failure( + scan_report, + TxByTable::SentPayable(sent_tx.clone()), + blockchain_failure, + &logger, + ); + + let failure_reason = blockchain_failure.into(); + let failed_tx = FailedTx::from((sent_tx, failure_reason)); + assert_eq!( + result, + ReceiptScanReport { + failures: DetectedFailures { + tx_failures: vec![PresortedTxFailure::NewEntry(failed_tx)], + tx_receipt_rpc_failures: vec![] + }, + confirmations: DetectedConfirmations::default() + } + ); + TestLogHandler::new().exists_log_containing(&format!( + "WARN: {test_name}: Pending tx 0x0000000000000000000000000000000000000000000000000000000\ + 000000abc failed on the blockchain due to: Unrecognized failure", + )); + } + + #[test] + fn interprets_tx_receipt_for_failed_tx_when_tx_status_reveals_failure() { + init_test_logging(); + let test_name = "interprets_tx_receipt_for_failed_tx_when_tx_status_reveals_failure"; + let tx_hash = make_tx_hash(0xabc); + let mut failed_tx = make_failed_tx(2244); + failed_tx.hash = tx_hash; + failed_tx.status = FailureStatus::RecheckRequired(ValidationStatus::Waiting); + failed_tx.reason = FailureReason::PendingTooLong; + let blockchain_failure = BlockchainTxFailure::Unrecognized; + let logger = Logger::new(test_name); + let scan_report = ReceiptScanReport::default(); + + let result = TxReceiptInterpreter::handle_status_with_failure( + scan_report, + TxByTable::FailedPayable(failed_tx.clone()), + blockchain_failure, + &logger, + ); + + assert_eq!( + result, + ReceiptScanReport { + failures: DetectedFailures { + tx_failures: vec![PresortedTxFailure::RecheckCompleted(tx_hash)], + tx_receipt_rpc_failures: vec![] + }, + confirmations: DetectedConfirmations::default() + } + ); + TestLogHandler::new().exists_log_containing(&format!( + "DEBUG: {test_name}: Failed tx 0x000000000000000000000000000000000000000000000000000000\ + 0000000abc on a recheck after \"PendingTooLong\". Status will be changed to \"Concluded\" \ + due to blockchain failure: Unrecognized failure", + )); + } + + #[test] + fn interprets_tx_receipt_for_pending_payable_if_the_tx_keeps_pending() { + init_test_logging(); + let retrieve_txs_params_arc = Arc::new(Mutex::new(vec![])); + let test_name = "interprets_tx_receipt_for_pending_payable_if_the_tx_keeps_pending"; + let newer_sent_tx_for_older_failed_tx = make_sent_tx(2244); + let sent_payable_dao = SentPayableDaoMock::new() + .retrieve_txs_params(&retrieve_txs_params_arc) + .retrieve_txs_result(vec![newer_sent_tx_for_older_failed_tx]); + let hash = make_tx_hash(0x913); + let sent_tx_timestamp = to_unix_timestamp( + SystemTime::now() + .checked_sub(Duration::from_secs(120)) + .unwrap(), + ); + let mut sent_tx = make_sent_tx(456); + sent_tx.hash = hash; + sent_tx.timestamp = sent_tx_timestamp; + let scan_report = ReceiptScanReport::default(); + let before = SystemTime::now(); + + let result = TxReceiptInterpreter::handle_still_pending_tx( + scan_report, + TxByTable::SentPayable(sent_tx.clone()), + &sent_payable_dao, + &Logger::new(test_name), + ); + + let after = SystemTime::now(); + let expected_failed_tx = FailedTx::from((sent_tx, FailureReason::PendingTooLong)); + assert_eq!( + result, + ReceiptScanReport { + failures: DetectedFailures { + tx_failures: vec![PresortedTxFailure::NewEntry(expected_failed_tx)], + tx_receipt_rpc_failures: vec![] + }, + confirmations: DetectedConfirmations::default() + } + ); + let log_handler = TestLogHandler::new(); + let log_idx = log_handler.exists_log_matching(&format!( + "INFO: {test_name}: Tx \ + 0x0000000000000000000000000000000000000000000000000000000000000913 not confirmed within \ + \\d{{1,3}}(,\\d{{3}})* ms. Will resubmit with higher gas price" + )); + let log_msg = log_handler.get_log_at(log_idx); + let str_elapsed_ms = capture_numbers_with_separators_from_str(&log_msg, 3, ','); + let elapsed_ms = str_elapsed_ms[0].replace(",", "").parse::().unwrap(); + let elapsed_ms_when_before = before + .duration_since(from_unix_timestamp(sent_tx_timestamp)) + .unwrap() + .as_millis(); + let elapsed_ms_when_after = after + .duration_since(from_unix_timestamp(sent_tx_timestamp)) + .unwrap() + .as_millis(); + assert!( + elapsed_ms_when_before <= elapsed_ms && elapsed_ms <= elapsed_ms_when_after, + "we expected the elapsed time {} ms to be between {} and {}.", + elapsed_ms, + elapsed_ms_when_before, + elapsed_ms_when_after + ); + } + + #[test] + fn interprets_tx_receipt_for_supposedly_failed_tx_if_the_tx_keeps_pending() { + // TODO I don't know yet what todo to with this... + // Maybe implement so that we go on like this but we'd compute cases like this and + // stop it if unwinding too long??? (Remember that it would require a new exception for + // the scan report left possibly empty...and still avoiding a panic somehow...) + todo!("this is a puzzle"); + init_test_logging(); + let retrieve_txs_params_arc = Arc::new(Mutex::new(vec![])); + let test_name = "interprets_tx_receipt_for_supposedly_failed_tx_if_the_tx_keeps_pending"; + let newer_sent_tx_for_older_failed_tx = make_sent_tx(2244); + let sent_payable_dao = SentPayableDaoMock::new() + .retrieve_txs_params(&retrieve_txs_params_arc) + .retrieve_txs_result(vec![newer_sent_tx_for_older_failed_tx]); + let hash = make_tx_hash(0x913); + let failed_tx = make_failed_tx(789); + let failed_tx_nonce = failed_tx.nonce; + let scan_report = ReceiptScanReport::default(); + let before = SystemTime::now(); + + let result = TxReceiptInterpreter::handle_still_pending_tx( + scan_report, + TxByTable::FailedPayable(failed_tx.clone()), + &sent_payable_dao, + &Logger::new(test_name), + ); + + let after = SystemTime::now(); + assert_eq!( + result, + ReceiptScanReport { + // TODO we cannot leave all these collections empty, that equals a panic + failures: DetectedFailures { + tx_failures: vec![], + tx_receipt_rpc_failures: vec![] + }, + confirmations: DetectedConfirmations::default() + } + ); + let retrieve_txs_params = retrieve_txs_params_arc.lock().unwrap(); + assert_eq!( + *retrieve_txs_params, + vec![Some(RetrieveCondition::ByNonce(vec![failed_tx_nonce]))] + ); + TestLogHandler::new().exists_log_containing(&format!( + "ERROR: {test_name}: Failed tx on a recheck was found pending by its receipt. Unexpected \ + behavior. Tx 0x0000000000000000000000000000000000000000000000000000000000000315 was \ + supposed to be replaced by the newer \ + 0x00000000000000000000000000000000000000000000000000000000000008c4" + )); + } + + #[test] + #[should_panic( + expected = "Attempted to display a replacement tx for 0x000000000000000000000000000\ + 00000000000000000000000000000000001c8 but couldn't find one in the database" + )] + fn handle_still_pending_tx_if_unexpected_behavior_due_to_already_failed_tx_and_db_retrieval_fails( + ) { + let scan_report = ReceiptScanReport::default(); + let still_pending_tx = make_failed_tx(456); + let sent_payable_dao = SentPayableDaoMock::new().retrieve_txs_result(vec![]); + + let _ = TxReceiptInterpreter::handle_still_pending_tx( + scan_report, + TxByTable::FailedPayable(still_pending_tx), + &sent_payable_dao, + &Logger::new("test"), + ); + } + + #[test] + fn interprets_tx_a_failed_retrieval_of_the_receipt_for_pending_payable() { + init_test_logging(); + let test_name = "interprets_tx_a_failed_retrieval_of_the_receipt_for_pending_payable"; + let tx_hash = make_tx_hash(913); + let mut sent_tx = make_sent_tx(456); + sent_tx.hash = tx_hash; + let rpc_error = AppRpcError::Remote(RemoteError::InvalidResponse("bluh".to_string())); + let tx_receipt_error = + TxReceiptError::new(TxHashByTable::SentPayable(tx_hash), rpc_error.clone()); + let scan_report = ReceiptScanReport::default(); + + let result = TxReceiptInterpreter::handle_rpc_failure( + scan_report, + tx_receipt_error, + &Logger::new(test_name), + ); + + assert_eq!( + result, + ReceiptScanReport { + failures: DetectedFailures { + tx_failures: vec![], + tx_receipt_rpc_failures: vec![FailedValidationByTable::SentPayable( + FailedValidation { + tx_hash, + failure: rpc_error + } + ),] + }, + confirmations: DetectedConfirmations::default() + } + ); + TestLogHandler::new().exists_log_containing( + &format!("WARN: {test_name}: Failed to retrieve tx receipt for SentPayable(0x0000000000\ + 000000000000000000000000000000000000000000000000000391): Remote(InvalidResponse(\"bluh\")). \ + Will retry receipt retrieval next cycle")); + } + + #[test] + fn interprets_tx_a_failed_retrieval_of_the_receipt_for_failed_tx() { + init_test_logging(); + let test_name = "interprets_tx_a_failed_retrieval_of_the_receipt_for_failed_tx"; + let tx_hash = make_tx_hash(914); + let mut failed_tx = make_failed_tx(789); + failed_tx.hash = tx_hash; + let rpc_error = AppRpcError::Local(LocalError::Internal); + let tx_receipt_error = + TxReceiptError::new(TxHashByTable::FailedPayable(tx_hash), rpc_error.clone()); + let scan_report = ReceiptScanReport::default(); + + let result = TxReceiptInterpreter::handle_rpc_failure( + scan_report, + tx_receipt_error, + &Logger::new(test_name), + ); + + assert_eq!( + result, + ReceiptScanReport { + failures: DetectedFailures { + tx_failures: vec![], + tx_receipt_rpc_failures: vec![FailedValidationByTable::FailedPayable( + FailedValidation { + tx_hash, + failure: rpc_error + } + ),] + }, + confirmations: DetectedConfirmations::default() + } + ); + TestLogHandler::new().exists_log_containing(&format!( + "WARN: {test_name}: Failed to retrieve tx receipt for FailedPayable(0x000000000\ + 0000000000000000000000000000000000000000000000000000392): Local(Internal). Will retry \ + receipt retrieval next cycle" + )); + } +} diff --git a/node/src/accountant/scanners/pending_payable_scanner/utils.rs b/node/src/accountant/scanners/pending_payable_scanner/utils.rs index 5c67b8f77..5c6f45081 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/utils.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/utils.rs @@ -40,23 +40,23 @@ impl ReceiptScanReport { } } - fn register_confirmed_tx(&mut self, confirmation: NormalTxConfirmation) { + pub(super) fn register_confirmed_tx(&mut self, confirmation: NormalTxConfirmation) { self.confirmations.normal_confirmations.push(confirmation); } - fn register_failure_reclaim(&mut self, reclaim: TxReclaim) { + pub(super) fn register_failure_reclaim(&mut self, reclaim: TxReclaim) { self.confirmations.reclaims.push(reclaim) } - fn register_new_failure(&mut self, failed_tx: PresortedTxFailure) { + pub(super) fn register_new_failure(&mut self, failed_tx: PresortedTxFailure) { self.failures.tx_failures.push(failed_tx); } - fn register_finalization_of_unproven_failure(&mut self, tx_hash: TxHash) { + pub(super) fn register_finalization_of_unproven_failure(&mut self, tx_hash: TxHash) { todo!() } - fn register_rpc_failure(&mut self, status_update: FailedValidationByTable) { + pub(super) fn register_rpc_failure(&mut self, status_update: FailedValidationByTable) { self.failures.tx_receipt_rpc_failures.push(status_update); } } @@ -264,146 +264,6 @@ pub enum TxHashByTable { FailedPayable(TxHash), } -pub fn elapsed_in_ms(timestamp: SystemTime) -> u128 { - timestamp - .elapsed() - .expect("time calculation for elapsed failed") - .as_millis() -} - -pub fn handle_still_pending_tx( - mut scan_report: ReceiptScanReport, - tx: TxByTable, - sent_payable_dao: &dyn SentPayableDao, - logger: &Logger, -) -> ReceiptScanReport { - match tx { - TxByTable::SentPayable(sent_tx) => { - info!( - logger, - "Tx {:?} not confirmed within {} ms. Will resubmit with higher gas price", - sent_tx.hash, - elapsed_in_ms(from_unix_timestamp(sent_tx.timestamp)).separate_with_commas() - ); - let failed_tx = FailedTx::from((sent_tx, FailureReason::PendingTooLong)); - scan_report.register_new_failure(PresortedTxFailure::NewEntry(failed_tx)); - } - TxByTable::FailedPayable(failed_tx) => { - let replacement_tx = sent_payable_dao - .retrieve_txs(Some(RetrieveCondition::ByNonce(vec![failed_tx.nonce]))); - error!( - logger, - "Failed tx on a recheck was found pending by its receipt. Unexpected behavior. \ - Tx {:?} was supposed to be replaced by the newer {:?}", - failed_tx.hash, - replacement_tx - .get(0) - .unwrap_or_else(|| panic!( - "Attempted to display a replacement tx for {:?} but couldn't find \ - one in the database", - failed_tx.hash - )) - .hash - ) - } - } - scan_report -} - -pub fn handle_tx_confirmation( - mut scan_report: ReceiptScanReport, - tx: TxByTable, - tx_block: TransactionBlock, - logger: &Logger, -) -> ReceiptScanReport { - match tx { - TxByTable::SentPayable(sent_tx) => { - info!( - logger, - "Pending tx {:?} was confirmed on the blockchain", sent_tx.hash, - ); - - let completed_sent_tx = SentTx { - status: TxStatus::Confirmed { - block_hash: format!("{:?}", tx_block.block_hash), - block_number: tx_block.block_number.as_u64(), - detection: Detection::Normal, - }, - ..sent_tx - }; - let tx_confirmation = NormalTxConfirmation { - tx: completed_sent_tx, - }; - scan_report.register_confirmed_tx(tx_confirmation); - } - TxByTable::FailedPayable(failed_tx) => { - info!( - logger, - "Failed tx {:?} was later confirmed on the blockchain and will be reclaimed", - failed_tx.hash - ); - - let sent_tx = SentTx::from((failed_tx, tx_block)); - let reclaim = TxReclaim { reclaimed: sent_tx }; - scan_report.register_failure_reclaim(reclaim); - } - } - scan_report -} - -//TODO: failures handling might need enhancement suggested by GH-693 -pub fn handle_status_with_failure( - mut scan_report: ReceiptScanReport, - tx: TxByTable, - blockchain_failure: BlockchainTxFailure, - logger: &Logger, -) -> ReceiptScanReport { - match tx { - TxByTable::SentPayable(sent_tx) => { - let failure_reason = FailureReason::from(blockchain_failure); - let failed_tx = FailedTx::from((sent_tx, failure_reason)); - - warning!( - logger, - "Pending tx {:?} failed on the blockchain due to: {}", - failed_tx.hash, - blockchain_failure - ); - - scan_report.register_new_failure(PresortedTxFailure::NewEntry(failed_tx)); - } - TxByTable::FailedPayable(failed_tx) => { - debug!( - logger, - "Failed tx {:?} on a recheck after {}. Status will be changed to \ - \"Concluded\" due to blockchain failure: {}", - failed_tx.hash, - failed_tx.reason, - blockchain_failure - ); - - scan_report.register_new_failure(PresortedTxFailure::RecheckCompleted(failed_tx.hash)); - } - } - scan_report -} - -pub fn handle_rpc_failure( - mut scan_report: ReceiptScanReport, - rpc_error: TxReceiptError, - logger: &Logger, -) -> ReceiptScanReport { - warning!( - logger, - "Failed to retrieve tx receipt for {:?}: {:?}. Will retry receipt retrieval next cycle", - rpc_error.tx_hash, - rpc_error.err - ); - let validation_status_update = FailedValidationByTable::from(rpc_error); - scan_report.register_rpc_failure(validation_status_update); - scan_report -} - impl From for FailureReason { fn from(failure: BlockchainTxFailure) -> Self { match failure { From d8d6ef1f29a2fb20330ed3b7b005b0f46dbca276 Mon Sep 17 00:00:00 2001 From: Bert Date: Thu, 7 Aug 2025 18:32:52 +0200 Subject: [PATCH 27/61] GH-642: fixed two unreliable tests --- .../scanners/pending_payable_scanner/mod.rs | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/node/src/accountant/scanners/pending_payable_scanner/mod.rs b/node/src/accountant/scanners/pending_payable_scanner/mod.rs index 73945450a..44eaaf0b5 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/mod.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/mod.rs @@ -300,13 +300,21 @@ impl PendingPayableScanner { } fn panic_dump(&mut self, mismatch_report: MismatchReport) -> ! { + fn rearrange(hashmap: HashMap) -> Vec { + hashmap + .into_iter() + .sorted_by_key(|(tx_hash, _)| *tx_hash) + .map(|(_, record)| record) + .collect_vec() + } + panic!( "Looking up '{:?}' in the cache, the record could not be found. Dumping \ the remaining values. Pending payables: {:?}. Unproven failures: {:?}. \ All yet-to-look-up hashes: {:?}.", mismatch_report.noticed_at, - self.current_sent_payables.dump_cache(), - self.yet_unproven_failed_payables.dump_cache(), + rearrange(self.current_sent_payables.dump_cache()), + rearrange(self.yet_unproven_failed_payables.dump_cache()), mismatch_report.remaining_hashes ) } @@ -744,15 +752,15 @@ mod tests { let panic_msg = panic.downcast_ref::().unwrap(); let regex_str_in_pieces = vec![ r#"Looking up 'SentPayable\(0x0000000000000000000000000000000000000000000000000000000000000309\)'"#, - r#" in the cache, the record could not be found. Dumping the remaining values. Pending payables: \{\}."#, - r#" Unproven failures: \{0x0000000000000000000000000000000000000000000000000000000000000237: FailedTx \{"#, + r#" in the cache, the record could not be found. Dumping the remaining values. Pending payables: \[\]."#, + r#" Unproven failures: \[FailedTx \{"#, r#" hash: 0x0000000000000000000000000000000000000000000000000000000000000237, receiver_address:"#, r#" 0x000000000000000000000077616c6c6574353637, amount_minor: 321489000000000, timestamp: \d*,"#, r#" gas_price_minor: 567000000000, nonce: 567, reason: PendingTooLong, status: RetryRequired \},"#, - r#" 0x000000000000000000000000000000000000000000000000000000000000037a: FailedTx \{ hash:"#, + r#" FailedTx \{ hash:"#, r#" 0x000000000000000000000000000000000000000000000000000000000000037a, receiver_address:"#, r#" 0x000000000000000000000077616c6c6574383930, amount_minor: 792100000000000, timestamp: \d*,"#, - r#" gas_price_minor: 890000000000, nonce: 890, reason: PendingTooLong, status: RetryRequired \}\}."#, + r#" gas_price_minor: 890000000000, nonce: 890, reason: PendingTooLong, status: RetryRequired \}\]."#, r#" All yet-to-look-up hashes: \[FailedPayable\(0x000000000000000000000000000000000000000000000000000"#, r#"0000000000237\), FailedPayable\(0x000000000000000000000000000000000000000000000000000000000000037a\)\]."#, ]; @@ -760,8 +768,7 @@ mod tests { let expected_msg_regex = Regex::new(®ex_str).unwrap(); assert!( expected_msg_regex.is_match(panic_msg), - "Expected string that matches this regex '{}' \ - but it couldn't with '{}'", + "Expected string that matches this regex '{}' but it couldn't with '{}'", regex_str, panic_msg ); @@ -812,15 +819,14 @@ mod tests { let panic_msg = panic.downcast_ref::().unwrap(); let regex_str_in_pieces = vec![ r#"Looking up 'FailedPayable\(0x0000000000000000000000000000000000000000000000000000000000000385\)'"#, - r#" in the cache, the record could not be found. Dumping the remaining values. Pending payables: \{\}."#, - r#" Unproven failures: \{\}. All yet-to-look-up hashes: \[\]."#, + r#" in the cache, the record could not be found. Dumping the remaining values. Pending payables: \[\]."#, + r#" Unproven failures: \[\]. All yet-to-look-up hashes: \[\]."#, ]; let regex_str = regex_str_in_pieces.join(""); let expected_msg_regex = Regex::new(®ex_str).unwrap(); assert!( expected_msg_regex.is_match(panic_msg), - "Expected string that matches this regex '{}' \ - but it couldn't with '{}'", + "Expected string that matches this regex '{}' but it couldn't with '{}'", regex_str, panic_msg ); From 7c51118dfd3978a8d4610f6cd31a33ef67a97af6 Mon Sep 17 00:00:00 2001 From: Bert Date: Thu, 7 Aug 2025 20:13:47 +0200 Subject: [PATCH 28/61] GH-642: interim commit --- .../scanners/pending_payable_scanner/mod.rs | 84 +++++++++++++++---- 1 file changed, 70 insertions(+), 14 deletions(-) diff --git a/node/src/accountant/scanners/pending_payable_scanner/mod.rs b/node/src/accountant/scanners/pending_payable_scanner/mod.rs index 44eaaf0b5..b3b5d8472 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/mod.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/mod.rs @@ -36,7 +36,7 @@ use masq_lib::logger::Logger; use masq_lib::messages::{ScanType, ToMessageBody, UiScanResponse}; use masq_lib::ui_gateway::{MessageTarget, NodeToUiMessage}; use std::cell::RefCell; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::rc::Rc; use std::time::SystemTime; use thousands::Separable; @@ -333,13 +333,16 @@ impl PendingPayableScanner { self.handle_normal_confirmations(confirmed_txs.normal_confirmations, logger); } + // TODO a single test for emptiness here fn handle_tx_failure_reclaims(&self, reclaimed: Vec, logger: &Logger) { if reclaimed.is_empty() { todo!() } + todo!() } + // TODO a single test for emptiness here fn handle_normal_confirmations( &self, confirmed_txs: Vec, @@ -348,6 +351,7 @@ impl PendingPayableScanner { if confirmed_txs.is_empty() { todo!() } + todo!() } @@ -430,34 +434,80 @@ impl PendingPayableScanner { } fn handle_tx_failures(&self, failures: Vec, logger: &Logger) { - let (new_failures, rechecks_completed): (Vec, Vec) = - failures.into_iter().fold( - (vec![], vec![]), - |(mut new_failures, mut rechecks_completed), failure| { + #[derive(Default)] + struct GroupedFailures { + new_failures: Vec, + rechecks_completed: Vec, + } + + let grouped_failures = + failures + .into_iter() + .fold(GroupedFailures::default(), |mut acc, failure| { match failure { PresortedTxFailure::NewEntry(failed_tx) => { - todo!() + acc.new_failures.push(failed_tx); } PresortedTxFailure::RecheckCompleted(hash) => { todo!() } } - (new_failures, rechecks_completed) - }, - ); - self.add_new_failures(new_failures, logger); - self.finalize_unproven_failures(rechecks_completed, logger); + acc + }); + + self.add_new_failures(grouped_failures.new_failures, logger); + self.finalize_unproven_failures(grouped_failures.rechecks_completed, logger); } + // TODO a single test for emptiness here fn add_new_failures(&self, new_failures: Vec, logger: &Logger) { - todo!() + fn prepare_hashset(failures: &[FailedTx]) -> HashSet { + failures.iter().map(|failure| failure.hash).collect() + } + fn log_procedure_finished(logger: &Logger, new_failures: &[FailedTx]) { + info!( + logger, + "Failed txs {:?} were processed in the db", + comma_joined_stringifiable(new_failures, |failure| format!("{:?}", failure.hash)) + ) + } + + if new_failures.is_empty() { + todo!() + } + + if let Err(e) = self.failed_payable_dao.insert_new_records(&new_failures) { + todo!() + } + + match self + .sent_payable_dao + .delete_records(&prepare_hashset(&new_failures)) + { + Ok(_) => { + log_procedure_finished(logger, &new_failures); + } + Err(_) => { + todo!() + } + } } + // TODO a single test for emptiness here fn finalize_unproven_failures(&self, rechecks_completed: Vec, logger: &Logger) { + if rechecks_completed.is_empty() { + todo!() + } + todo!() } + // TODO a single test for emptiness here fn handle_rpc_failures(&self, failures: Vec, logger: &Logger) { + if failures.is_empty() { + todo!() + } + todo!() } @@ -531,6 +581,7 @@ mod tests { use masq_lib::logger::Logger; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; use regex::Regex; + use std::fmt::format; use std::panic::{catch_unwind, AssertUnwindSafe}; use std::sync::{Arc, Mutex}; use std::time::SystemTime; @@ -660,7 +711,6 @@ mod tests { let result = subject.finish_scan(msg, &logger); - //TODO write db assertion for processing both records assert_eq!( result, PendingPayableScanResult::PaymentRetryRequired(Retry::RetryPayments) @@ -834,6 +884,8 @@ mod tests { #[test] fn handle_failed_transactions_can_process_standard_tx_failures() { + init_test_logging(); + let test_name = "handle_failed_transactions_can_process_standard_tx_failures"; let insert_new_records_params_arc = Arc::new(Mutex::new(vec![])); let delete_records_params_arc = Arc::new(Mutex::new(vec![])); let failed_payable_dao = FailedPayableDaoMock::default() @@ -860,7 +912,7 @@ mod tests { tx_receipt_rpc_failures: vec![], }; - subject.handle_failed_transactions(detected_failures, &Logger::new("test")); + subject.handle_failed_transactions(detected_failures, &Logger::new(test_name)); let insert_new_records_params = insert_new_records_params_arc.lock().unwrap(); assert_eq!( @@ -869,6 +921,10 @@ mod tests { ); let delete_records_params = delete_records_params_arc.lock().unwrap(); assert_eq!(*delete_records_params, vec![hashset![hash_1, hash_2]]); + TestLogHandler::new().exists_log_containing(&format!( + "INFO: {test_name}: Failed txs + bluh were processed in the db" + )); } #[test] From a0454aa42152915eb0d8c0bf572c685aeab91473 Mon Sep 17 00:00:00 2001 From: Bert Date: Sat, 9 Aug 2025 15:52:09 +0200 Subject: [PATCH 29/61] GH-642: worked away on the implementation of handling failed txs --- .../db_access_objects/failed_payable_dao.rs | 10 +- .../db_access_objects/sent_payable_dao.rs | 143 +++++++- .../scanners/pending_payable_scanner/mod.rs | 343 +++++++++++++----- .../tx_receipt_interpreter.rs | 93 ++++- .../scanners/pending_payable_scanner/utils.rs | 255 ++++++++++++- node/src/accountant/test_utils.rs | 30 +- 6 files changed, 741 insertions(+), 133 deletions(-) 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 99df3763b..f29f98b73 100644 --- a/node/src/accountant/db_access_objects/failed_payable_dao.rs +++ b/node/src/accountant/db_access_objects/failed_payable_dao.rs @@ -131,7 +131,7 @@ pub trait FailedPayableDao { fn retrieve_txs(&self, condition: Option) -> Vec; fn update_statuses( &self, - status_updates: HashMap, + status_updates: &HashMap, ) -> Result<(), FailedPayableDaoError>; //TODO potentially atomically fn delete_records(&self, hashes: &HashSet) -> Result<(), FailedPayableDaoError>; @@ -308,7 +308,7 @@ impl FailedPayableDao for FailedPayableDaoReal<'_> { fn update_statuses( &self, - status_updates: HashMap, + status_updates: &HashMap, ) -> Result<(), FailedPayableDaoError> { if status_updates.is_empty() { return Err(FailedPayableDaoError::EmptyInput); @@ -822,7 +822,7 @@ mod tests { (tx3.hash, Concluded), ]); - let result = subject.update_statuses(hashmap); + let result = subject.update_statuses(&hashmap); let updated_txs = subject.retrieve_txs(None); assert_eq!(result, Ok(())); @@ -856,7 +856,7 @@ mod tests { .unwrap(); let subject = FailedPayableDaoReal::new(wrapped_conn); - let result = subject.update_statuses(HashMap::new()); + let result = subject.update_statuses(&HashMap::new()); assert_eq!(result, Err(FailedPayableDaoError::EmptyInput)); } @@ -870,7 +870,7 @@ mod tests { let wrapped_conn = make_read_only_db_connection(home_dir); let subject = FailedPayableDaoReal::new(Box::new(wrapped_conn)); - let result = subject.update_statuses(HashMap::from([(make_tx_hash(1), Concluded)])); + let result = subject.update_statuses(&HashMap::from([(make_tx_hash(1), Concluded)])); assert_eq!( result, 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 5a2a2926e..e35b54678 100644 --- a/node/src/accountant/db_access_objects/sent_payable_dao.rs +++ b/node/src/accountant/db_access_objects/sent_payable_dao.rs @@ -129,6 +129,10 @@ pub trait SentPayableDao { hash_map: &HashMap, ) -> Result<(), SentPayableDaoError>; fn replace_records(&self, new_txs: &[SentTx]) -> Result<(), SentPayableDaoError>; + fn update_statuses( + &self, + hash_map: &HashMap, + ) -> Result<(), SentPayableDaoError>; //TODO potentially atomically fn delete_records(&self, hashes: &HashSet) -> Result<(), SentPayableDaoError>; } @@ -404,6 +408,13 @@ impl SentPayableDao for SentPayableDaoReal<'_> { } } + fn update_statuses( + &self, + hash_map: &HashMap, + ) -> Result<(), SentPayableDaoError> { + todo!() + } + fn delete_records(&self, hashes: &HashSet) -> Result<(), SentPayableDaoError> { if hashes.is_empty() { return Err(SentPayableDaoError::EmptyInput); @@ -446,7 +457,15 @@ impl SentPayableDaoFactory for DaoFactoryReal { #[cfg(test)] mod tests { - use crate::accountant::db_access_objects::failed_payable_dao::ValidationStatus; + use crate::accountant::db_access_objects::failed_payable_dao::FailureReason::{ + PendingTooLong, Reverted, + }; + use crate::accountant::db_access_objects::failed_payable_dao::FailureStatus::{ + Concluded, RecheckRequired, RetryRequired, + }; + use crate::accountant::db_access_objects::failed_payable_dao::{ + FailedPayableDaoError, FailedPayableDaoReal, ValidationStatus, + }; use crate::accountant::db_access_objects::sent_payable_dao::RetrieveCondition::{ ByHash, ByNonce, IsPending, }; @@ -458,12 +477,12 @@ mod tests { TxStatus, }; use crate::accountant::db_access_objects::test_utils::{ - make_read_only_db_connection, TxBuilder, + make_read_only_db_connection, FailedTxBuilder, TxBuilder, }; use crate::accountant::db_access_objects::utils::TxRecordWithHash; use crate::accountant::test_utils::make_sent_tx; use crate::blockchain::blockchain_interface::data_structures::TransactionBlock; - use crate::blockchain::errors::{AppRpcError, RemoteError}; + use crate::blockchain::errors::{AppRpcError, LocalError, RemoteError}; use crate::blockchain::test_utils::{make_block_hash, make_tx_hash}; use crate::database::db_initializer::{ DbInitializationConfig, DbInitializer, DbInitializerReal, @@ -473,6 +492,7 @@ mod tests { use masq_lib::test_utils::utils::ensure_node_home_directory_exists; use rusqlite::Connection; use std::collections::{HashMap, HashSet}; + use std::fmt::format; use std::str::FromStr; use std::sync::{Arc, Mutex}; @@ -1037,6 +1057,123 @@ mod tests { ) } + #[test] + fn update_statuses_works() { + let home_dir = + ensure_node_home_directory_exists("sent_payable_dao", "update_statuses_works"); + let wrapped_conn = DbInitializerReal::default() + .initialize(&home_dir, DbInitializationConfig::test_default()) + .unwrap(); + let subject = SentPayableDaoReal::new(wrapped_conn); + let mut tx1 = make_sent_tx(456); + tx1.status = TxStatus::Pending(ValidationStatus::Waiting); + let mut tx2 = make_sent_tx(789); + tx2.status = TxStatus::Pending(ValidationStatus::Reattempting { + attempt: 2, + error: AppRpcError::Remote(RemoteError::Unreachable), + }); + let mut tx3 = make_sent_tx(123); + tx3.status = TxStatus::Pending(ValidationStatus::Waiting); + subject + .insert_new_records(&vec![tx1.clone(), tx2.clone(), tx3.clone()]) + .unwrap(); + let hashmap = HashMap::from([ + ( + tx1.hash, + TxStatus::Pending(ValidationStatus::Reattempting { + attempt: 1, + error: AppRpcError::Local(LocalError::Internal), + }), + ), + ( + tx2.hash, + TxStatus::Pending(ValidationStatus::Reattempting { + attempt: 2, + error: AppRpcError::Remote(RemoteError::Unreachable), + }), + ), + ( + tx3.hash, + TxStatus::Confirmed { + block_hash: + "0x0000000000000000000000000000000000000000000000000000000000000002" + .to_string(), + block_number: 123, + detection: Detection::Normal, + }, + ), + ]); + + let result = subject.update_statuses(&hashmap); + + let updated_txs = subject.retrieve_txs(None); + assert_eq!(result, Ok(())); + assert_eq!( + updated_txs[0].status, + TxStatus::Pending(ValidationStatus::Reattempting { + attempt: 1, + error: AppRpcError::Local(LocalError::Internal) + }) + ); + assert_eq!( + updated_txs[1].status, + TxStatus::Pending(ValidationStatus::Reattempting { + attempt: 2, + error: AppRpcError::Remote(RemoteError::Unreachable) + }) + ); + assert_eq!( + updated_txs[2].status, + TxStatus::Confirmed { + block_hash: "0x0000000000000000000000000000000000000000000000000000000000000002" + .to_string(), + block_number: 123, + detection: Detection::Normal, + } + ); + } + + #[test] + fn update_statuses_handles_empty_input_error() { + let home_dir = ensure_node_home_directory_exists( + "sent_payable_dao", + "update_statuses_handles_empty_input_error", + ); + let wrapped_conn = DbInitializerReal::default() + .initialize(&home_dir, DbInitializationConfig::test_default()) + .unwrap(); + let subject = SentPayableDaoReal::new(wrapped_conn); + + let result = subject.update_statuses(&HashMap::new()); + + assert_eq!(result, Err(SentPayableDaoError::EmptyInput)); + } + + #[test] + fn update_statuses_handles_sql_error() { + let home_dir = ensure_node_home_directory_exists( + "sent_payable_dao", + "update_statuses_handles_sql_error", + ); + let wrapped_conn = make_read_only_db_connection(home_dir); + let subject = SentPayableDaoReal::new(Box::new(wrapped_conn)); + + let result = subject.update_statuses(&HashMap::from([( + make_tx_hash(1), + TxStatus::Pending(ValidationStatus::Reattempting { + attempt: 1, + error: AppRpcError::Remote(RemoteError::Unreachable), + }), + )])); + + assert_eq!( + result, + Err(SentPayableDaoError::SqlExecutionFailed( + "attempt to write a readonly database".to_string() + )) + ); + } + #[test] fn replace_records_works_as_expected() { let home_dir = ensure_node_home_directory_exists( diff --git a/node/src/accountant/scanners/pending_payable_scanner/mod.rs b/node/src/accountant/scanners/pending_payable_scanner/mod.rs index b3b5d8472..335cec06a 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/mod.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/mod.rs @@ -4,19 +4,19 @@ mod tx_receipt_interpreter; pub mod utils; use crate::accountant::db_access_objects::failed_payable_dao::{ - FailedPayableDao, FailedTx, FailureRetrieveCondition, + FailedPayableDao, FailedTx, FailureRetrieveCondition, FailureStatus, }; use crate::accountant::db_access_objects::payable_dao::{PayableDao, PayableDaoError}; use crate::accountant::db_access_objects::sent_payable_dao::{ - RetrieveCondition, SentPayableDao, SentPayableDaoError, SentTx, + RetrieveCondition, SentPayableDao, SentPayableDaoError, SentTx, TxStatus, }; use crate::accountant::db_access_objects::utils::{TxHash, TxRecordWithHash}; use crate::accountant::scanners::pending_payable_scanner::tx_receipt_interpreter::TxReceiptInterpreter; use crate::accountant::scanners::pending_payable_scanner::utils::{ - CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailedValidationByTable, - MismatchReport, NormalTxConfirmation, PendingPayableCache, PendingPayableScanResult, - PresortedTxFailure, ReceiptScanReport, RecheckRequiringFailures, Retry, TxByTable, - TxCaseToBeInterpreted, TxHashByTable, TxReclaim, + CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailedValidation, + FailedValidationByTable, MismatchReport, NormalTxConfirmation, PendingPayableCache, + PendingPayableScanResult, PresortedTxFailure, ReceiptScanReport, RecheckRequiringFailures, + Retry, TxByTable, TxCaseToBeInterpreted, TxHashByTable, TxReclaim, UpdatableValidationStatus, }; use crate::accountant::scanners::{ PrivateScanner, Scanner, ScannerCommon, StartScanError, StartableScanner, @@ -37,6 +37,7 @@ use masq_lib::messages::{ScanType, ToMessageBody, UiScanResponse}; use masq_lib::ui_gateway::{MessageTarget, NodeToUiMessage}; use std::cell::RefCell; use std::collections::{HashMap, HashSet}; +use std::fmt::Display; use std::rc::Rc; use std::time::SystemTime; use thousands::Separable; @@ -84,8 +85,8 @@ impl StartableScanner } Self::log_records_found_for_receipt_check( - &pending_tx_hashes_opt, - &failure_hashes_opt, + pending_tx_hashes_opt.as_ref(), + failure_hashes_opt.as_ref(), logger, ); @@ -134,12 +135,6 @@ impl PendingPayableScanner { payment_thresholds: Rc, financial_statistics: Rc>, ) -> Self { - // let yet_unproven_failed_payables = RecheckRequiringFailures::new( - // failed_payable_dao.retrieve_txs(Some(FailureRetrieveCondition::ByStatus( - // todo!(), //FailureRetrieveCondition::EveryRecheckRequiredRecord - // ))), - // ); - Self { common: ScannerCommon::new(payment_thresholds), payable_dao, @@ -233,7 +228,7 @@ impl PendingPayableScanner { let either = msg .results .into_iter() - .fold(init, |mut acc, receipt_result| match acc { + .fold(init, |acc, receipt_result| match acc { Either::Left(cases) => { let tx_hash = receipt_result.hash(); @@ -333,26 +328,39 @@ impl PendingPayableScanner { self.handle_normal_confirmations(confirmed_txs.normal_confirmations, logger); } - // TODO a single test for emptiness here fn handle_tx_failure_reclaims(&self, reclaimed: Vec, logger: &Logger) { if reclaimed.is_empty() { - todo!() + return; } todo!() } - // TODO a single test for emptiness here fn handle_normal_confirmations( &self, confirmed_txs: Vec, logger: &Logger, ) { if confirmed_txs.is_empty() { - todo!() + return; } todo!() + // if !confirmed_txs.is_empty() { + // if let Err(e) = self.payable_dao.transactions_confirmed(&confirmed_txs) { + // Self::transaction_confirmed_panic(&confirmed_txs, e) + // } else { + // self.add_to_the_total_of_paid_payable(&confirmed_txs, logger); + // + // let tx_confirmations = Self::compose_tx_confirmation_inputs(&confirmed_txs); + // + // if let Err(e) = self.sent_payable_dao.confirm_tx(&tx_confirmations) { + // Self::update_tx_blocks_panic(&tx_confirmations, e) + // } else { + // Self::log_tx_success(logger, &tx_confirmations); + // } + // } + // } } fn compose_tx_confirmation_inputs( @@ -459,7 +467,6 @@ impl PendingPayableScanner { self.finalize_unproven_failures(grouped_failures.rechecks_completed, logger); } - // TODO a single test for emptiness here fn add_new_failures(&self, new_failures: Vec, logger: &Logger) { fn prepare_hashset(failures: &[FailedTx]) -> HashSet { failures.iter().map(|failure| failure.hash).collect() @@ -467,17 +474,21 @@ impl PendingPayableScanner { fn log_procedure_finished(logger: &Logger, new_failures: &[FailedTx]) { info!( logger, - "Failed txs {:?} were processed in the db", + "Failed txs {} were processed in the db", comma_joined_stringifiable(new_failures, |failure| format!("{:?}", failure.hash)) ) } if new_failures.is_empty() { - todo!() + return; } if let Err(e) = self.failed_payable_dao.insert_new_records(&new_failures) { - todo!() + panic!( + "Unable to persist failed txs {} due to {:?}", + comma_joined_stringifiable(&new_failures, |failure| format!("{:?}", failure.hash)), + e + ) } match self @@ -487,28 +498,123 @@ impl PendingPayableScanner { Ok(_) => { log_procedure_finished(logger, &new_failures); } - Err(_) => { - todo!() + Err(e) => { + panic!( + "Unable to purge sent payable records for failed txs {} due to {:?}", + comma_joined_stringifiable(&new_failures, |failure| format!( + "{:?}", + failure.hash + )), + e + ) } } } - // TODO a single test for emptiness here fn finalize_unproven_failures(&self, rechecks_completed: Vec, logger: &Logger) { if rechecks_completed.is_empty() { - todo!() + return; } todo!() } - // TODO a single test for emptiness here fn handle_rpc_failures(&self, failures: Vec, logger: &Logger) { if failures.is_empty() { - todo!() + return; } - todo!() + let (sent_payable_failures, failed_payable_failures): ( + Vec>, + Vec>, + ) = failures.into_iter().partition_map(|failure| match failure { + FailedValidationByTable::SentPayable(failed_validation) => { + Either::Left(failed_validation) + } + FailedValidationByTable::FailedPayable(failed_validation) => { + Either::Right(failed_validation) + } + }); + + self.update_validation_status_for_sent_txs(sent_payable_failures, logger); + + self.update_validation_status_for_failed_txs(failed_payable_failures, logger); + } + + fn update_validation_status_for_sent_txs( + &self, + sent_payable_failures: Vec>, + logger: &Logger, + ) { + if !sent_payable_failures.is_empty() { + let updatable = Self::prepare_statuses_for_update(&sent_payable_failures, logger); + if !updatable.is_empty() { + match self.sent_payable_dao.update_statuses(&updatable) { + Ok(_) => { + info!(logger, "Pending-tx statuses were processed in the db for validation failure of txs {}", comma_joined_stringifiable(&sent_payable_failures, |failure|{ + format!("{:?}", failure.tx_hash) + })) + } + Err(_) => { + todo!() + } + } + } + } + } + + fn update_validation_status_for_failed_txs( + &self, + failed_txs_validation_failures: Vec>, + logger: &Logger, + ) { + if !failed_txs_validation_failures.is_empty() { + let updatable = + Self::prepare_statuses_for_update(&failed_txs_validation_failures, logger); + if !updatable.is_empty() { + match self.failed_payable_dao.update_statuses(&updatable) { + Ok(_) => { + info!(logger, "Failed-tx statuses were processed in the db for validation failure of txs {}", comma_joined_stringifiable(&failed_txs_validation_failures, |failure|{ + format!("{:?}", failure.tx_hash) + })) + } + Err(_) => { + todo!() + } + } + } + } + } + + fn prepare_statuses_for_update( + failures: &[FailedValidation], + logger: &Logger, + ) -> HashMap { + failures + .iter() + .flat_map(|failure| { + failure + .new_status() + .map(|tx_status| (failure.tx_hash, tx_status)) + .or_else(|| { + debug!( + logger, + "{}", + PendingPayableScanner::status_not_updatable_log_msg( + &failure.current_status + ) + ); + None + }) + }) + .collect() + } + + fn status_not_updatable_log_msg(status: &dyn Display) -> String { + format!( + "Handling a validation failure, but the status {} cannot be updated.", + status + ) } fn compose_scan_result( @@ -527,21 +633,19 @@ impl PendingPayableScanner { } fn log_records_found_for_receipt_check( - pending_tx_hashes_opt: &Option>, - failure_hashes_opt: &Option>, + pending_tx_hashes_opt: Option<&Vec>, + failure_hashes_opt: Option<&Vec>, logger: &Logger, ) { + fn resolve_optional_vec(vec_opt: Option<&Vec>) -> usize { + vec_opt.map(|hashes| hashes.len()).unwrap_or_default() + } + debug!( logger, "Found {} pending payables and {} unfinalized failures to be checked", - pending_tx_hashes_opt - .as_ref() - .map(|hashes| hashes.len()) - .unwrap_or_default(), - failure_hashes_opt - .as_ref() - .map(|hashes| hashes.len()) - .unwrap_or_default(), + resolve_optional_vec(pending_tx_hashes_opt), + resolve_optional_vec(failure_hashes_opt) ); } } @@ -922,19 +1026,22 @@ mod tests { let delete_records_params = delete_records_params_arc.lock().unwrap(); assert_eq!(*delete_records_params, vec![hashset![hash_1, hash_2]]); TestLogHandler::new().exists_log_containing(&format!( - "INFO: {test_name}: Failed txs - bluh were processed in the db" + "INFO: {test_name}: Failed txs 0x0000000000000000000000000000000000000000000000000000000000000321, \ + 0x0000000000000000000000000000000000000000000000000000000000000654 were processed in the db" )); } #[test] fn handle_failed_transactions_can_process_receipt_retrieval_rpc_failures() { + init_test_logging(); + let test_name = "handle_failed_transactions_can_process_receipt_retrieval_rpc_failures"; let retrieve_failed_txs_params_arc = Arc::new(Mutex::new(vec![])); - let update_status_params_arc = Arc::new(Mutex::new(vec![])); + let update_statuses_sent_tx_params_arc = Arc::new(Mutex::new(vec![])); let retrieve_sent_txs_params_arc = Arc::new(Mutex::new(vec![])); - let replace_records_params_arc = Arc::new(Mutex::new(vec![])); + let update_statuses_failed_tx_params_arc = Arc::new(Mutex::new(vec![])); let hash_1 = make_tx_hash(0x321); let hash_2 = make_tx_hash(0x654); + let hash_3 = make_tx_hash(0x987); let mut failed_tx_1 = make_failed_tx(123); failed_tx_1.hash = hash_1; failed_tx_1.status = FailureStatus::RecheckRequired(ValidationStatus::Waiting); @@ -947,17 +1054,16 @@ mod tests { let failed_payable_dao = FailedPayableDaoMock::default() .retrieve_txs_params(&retrieve_failed_txs_params_arc) .retrieve_txs_result(vec![failed_tx_1, failed_tx_2]) - .update_statuses_params(&update_status_params_arc) + .update_statuses_params(&update_statuses_sent_tx_params_arc) .update_statuses_result(Ok(())); - let hash_3 = make_tx_hash(0x987); let mut sent_tx = make_sent_tx(789); sent_tx.hash = hash_3; sent_tx.status = TxStatus::Pending(ValidationStatus::Waiting); let sent_payable_dao = SentPayableDaoMock::default() .retrieve_txs_params(&retrieve_sent_txs_params_arc) .retrieve_txs_result(vec![sent_tx.clone()]) - .replace_records_params(&replace_records_params_arc) - .replace_records_result(Ok(())); + .update_statuses_params(&update_statuses_failed_tx_params_arc) + .update_statuses_result(Ok(())); let subject = PendingPayableScannerBuilder::new() .sent_payable_dao(sent_payable_dao) .failed_payable_dao(failed_payable_dao) @@ -965,33 +1071,32 @@ mod tests { let detected_failures = DetectedFailures { tx_failures: vec![], tx_receipt_rpc_failures: vec![ - FailedValidationByTable::FailedPayable(FailedValidation { - tx_hash: hash_1, - failure: AppRpcError::Remote(RemoteError::Unreachable), - }), - FailedValidationByTable::FailedPayable(FailedValidation { - tx_hash: hash_2, - failure: AppRpcError::Local(LocalError::Internal), - }), - FailedValidationByTable::SentPayable(FailedValidation { - tx_hash: hash_1, - failure: AppRpcError::Remote(RemoteError::InvalidResponse("Booga".to_string())), - }), + FailedValidationByTable::FailedPayable(FailedValidation::new( + hash_1, + AppRpcError::Remote(RemoteError::Unreachable), + FailureStatus::RecheckRequired(ValidationStatus::Waiting), + )), + FailedValidationByTable::FailedPayable(FailedValidation::new( + hash_2, + AppRpcError::Local(LocalError::Internal), + FailureStatus::RecheckRequired(ValidationStatus::Reattempting { + attempt: 1, + error: AppRpcError::Local(LocalError::Internal), + }), + )), + FailedValidationByTable::SentPayable(FailedValidation::new( + hash_3, + AppRpcError::Remote(RemoteError::InvalidResponse("Booga".to_string())), + TxStatus::Pending(ValidationStatus::Waiting), + )), ], }; - subject.handle_failed_transactions(detected_failures, &Logger::new("test")); + subject.handle_failed_transactions(detected_failures, &Logger::new(test_name)); - let retrieve_failed_txs_params = retrieve_failed_txs_params_arc.lock().unwrap(); + let update_statuses_sent_tx_params = update_statuses_sent_tx_params_arc.lock().unwrap(); assert_eq!( - *retrieve_failed_txs_params, - vec![Some(FailureRetrieveCondition::ByTxHash(vec![ - hash_1, hash_2 - ]))] - ); - let update_status_params = update_status_params_arc.lock().unwrap(); - assert_eq!( - *update_status_params, + *update_statuses_sent_tx_params, vec![hashmap!( hash_1 => FailureStatus::RecheckRequired( ValidationStatus::Reattempting { @@ -1007,18 +1112,81 @@ mod tests { ) )] ); - let retrieve_sent_txs_params = retrieve_sent_txs_params_arc.lock().unwrap(); + let update_statuses_failed_tx_params = update_statuses_failed_tx_params_arc.lock().unwrap(); assert_eq!( - *retrieve_sent_txs_params, - vec![Some(RetrieveCondition::ByHash(vec![hash_3]))] + *update_statuses_failed_tx_params, + vec![ + hashmap![hash_3 => TxStatus::Pending(ValidationStatus::Reattempting { + attempt: 1, + error: AppRpcError::Remote(RemoteError::InvalidResponse("Booga".to_string())), + })] + ] ); - let replace_records_params = replace_records_params_arc.lock().unwrap(); - let mut expected_updated_record = sent_tx; - expected_updated_record.status = TxStatus::Pending(ValidationStatus::Reattempting { - attempt: 1, - error: AppRpcError::Remote(RemoteError::InvalidResponse("Booga".to_string())), - }); - assert_eq!(*replace_records_params, vec![vec![expected_updated_record]]); + let test_log_handler = TestLogHandler::new(); + test_log_handler.exists_log_containing(&format!( + "INFO: {test_name}: Pending-tx statuses were processed in the db for validation failure \ + of txs 0x0000000000000000000000000000000000000000000000000000000000000987" + )); + test_log_handler.exists_log_containing(&format!( + "INFO: {test_name}: Failed-tx statuses were processed in the db for validation failure \ + of txs 0x0000000000000000000000000000000000000000000000000000000000000321, \ + 0x0000000000000000000000000000000000000000000000000000000000000654" + )); + let expectedly_missing_log_msg_fragment = "Handling a validation failure, but the status"; + let otherwise_possible_log_msg = + PendingPayableScanner::status_not_updatable_log_msg(&"Something"); + assert!( + otherwise_possible_log_msg.contains(expectedly_missing_log_msg_fragment), + "We expected to select a true log fragment '{}', but it is not included in '{}'", + expectedly_missing_log_msg_fragment, + otherwise_possible_log_msg + ); + test_log_handler.exists_no_log_containing(&format!( + "DEBUG: {test_name}: {}", + expectedly_missing_log_msg_fragment + )) + } + + #[test] + fn handle_rpc_failures_when_requested_for_a_status_which_cannot_be_updated() { + init_test_logging(); + let test_name = "handle_rpc_failures_when_requested_for_a_status_which_cannot_be_updated"; + let hash_1 = make_tx_hash(0x321); + let hash_2 = make_tx_hash(0x654); + let subject = PendingPayableScannerBuilder::new().build(); + + subject.handle_rpc_failures( + vec![ + FailedValidationByTable::FailedPayable(FailedValidation::new( + hash_1, + AppRpcError::Remote(RemoteError::Unreachable), + FailureStatus::RetryRequired, + )), + FailedValidationByTable::SentPayable(FailedValidation::new( + hash_2, + AppRpcError::Remote(RemoteError::InvalidResponse("Booga".to_string())), + TxStatus::Confirmed { + block_hash: "abc".to_string(), + block_number: 0, + detection: Detection::Normal, + }, + )), + ], + &Logger::new(test_name), + ); + + let test_log_handler = TestLogHandler::new(); + test_log_handler.exists_no_log_containing(&format!("INFO: {test_name}: ")); + test_log_handler.exists_log_containing(&format!( + "DEBUG: {test_name}: Handling a validation failure, but the status \ + {{\"Confirmed\":{{\"block_hash\":\"abc\",\"block_number\":0,\"detection\":\"Normal\"}}}} cannot be updated.", + )); + test_log_handler.exists_log_containing(&format!( + "DEBUG: {test_name}: Handling a validation failure, but the status \"RetryRequired\" \ + cannot be updated." + )); + // It didn't panic, which means none of the DAO methods was called because the DAOs are + // mocked in this test } #[test] @@ -1049,10 +1217,13 @@ mod tests { .build(); let detected_failures = DetectedFailures { tx_failures: vec![PresortedTxFailure::NewEntry(failed_tx_1)], - tx_receipt_rpc_failures: vec![FailedValidationByTable::SentPayable(FailedValidation { - tx_hash: hash_1, - failure: AppRpcError::Local(LocalError::Internal), - })], + tx_receipt_rpc_failures: vec![FailedValidationByTable::SentPayable( + FailedValidation::new( + hash_1, + AppRpcError::Local(LocalError::Internal), + TxStatus::Pending(ValidationStatus::Waiting), + ), + )], }; subject.handle_failed_transactions(detected_failures, &Logger::new("test")); @@ -1078,7 +1249,7 @@ mod tests { } #[test] - #[should_panic(expected = "Unable to record failed payables \ + #[should_panic(expected = "Unable to persist failed txs \ 0x000000000000000000000000000000000000000000000000000000000000014d, \ 0x00000000000000000000000000000000000000000000000000000000000001bc due to NoChange")] fn handle_failed_transactions_panics_when_it_fails_to_insert_failed_tx_record() { @@ -1105,7 +1276,7 @@ mod tests { } #[test] - #[should_panic(expected = "Unable to delete sent_payable entries for failed txs \ + #[should_panic(expected = "Unable to purge sent payable records for failed txs \ 0x000000000000000000000000000000000000000000000000000000000000014d, \ 0x00000000000000000000000000000000000000000000000000000000000001bc due to \ InvalidInput(\"Booga\")")] 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 1d76bee02..6105b6284 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 @@ -59,7 +59,7 @@ impl TxReceiptInterpreter { ), }, TxReceiptResult(Err(e)) => { - Self::handle_rpc_failure(scan_report_so_far, e, logger) + Self::handle_rpc_failure(scan_report_so_far, tx_case.tx_by_table, e, logger) } } }) @@ -193,6 +193,7 @@ impl TxReceiptInterpreter { fn handle_rpc_failure( mut scan_report: ReceiptScanReport, + tx_by_table: TxByTable, rpc_error: TxReceiptError, logger: &Logger, ) -> ReceiptScanReport { @@ -202,7 +203,14 @@ impl TxReceiptInterpreter { rpc_error.tx_hash, rpc_error.err ); - let validation_status_update = FailedValidationByTable::from(rpc_error); + let validation_status_update = match tx_by_table { + TxByTable::SentPayable(sent_tx) => { + FailedValidationByTable::from((rpc_error, sent_tx.status)) + } + TxByTable::FailedPayable(failed_tx) => { + FailedValidationByTable::from((rpc_error, failed_tx.status)) + } + }; scan_report.register_rpc_failure(validation_status_update); scan_report } @@ -538,12 +546,38 @@ mod tests { } #[test] - fn interprets_tx_a_failed_retrieval_of_the_receipt_for_pending_payable() { + fn interprets_failed_retrieval_of_receipt_for_pending_payable_in_first_attempt() { + let test_name = + "interprets_failed_retrieval_of_receipt_for_pending_payable_in_first_attempt"; + + test_failed_retrieval_of_receipt_for_pending_payable( + test_name, + TxStatus::Pending(ValidationStatus::Waiting), + ); + } + + #[test] + fn interprets_failed_retrieval_of_receipt_for_pending_payable_as_reattempt() { + let test_name = "interprets_failed_retrieval_of_receipt_for_pending_payable_as_reattempt"; + + test_failed_retrieval_of_receipt_for_pending_payable( + test_name, + TxStatus::Pending(ValidationStatus::Reattempting { + attempt: 1, + error: AppRpcError::Local(LocalError::Internal), + }), + ); + } + + fn test_failed_retrieval_of_receipt_for_pending_payable( + test_name: &str, + current_tx_status: TxStatus, + ) { init_test_logging(); - let test_name = "interprets_tx_a_failed_retrieval_of_the_receipt_for_pending_payable"; let tx_hash = make_tx_hash(913); let mut sent_tx = make_sent_tx(456); sent_tx.hash = tx_hash; + sent_tx.status = current_tx_status.clone(); let rpc_error = AppRpcError::Remote(RemoteError::InvalidResponse("bluh".to_string())); let tx_receipt_error = TxReceiptError::new(TxHashByTable::SentPayable(tx_hash), rpc_error.clone()); @@ -551,6 +585,7 @@ mod tests { let result = TxReceiptInterpreter::handle_rpc_failure( scan_report, + TxByTable::SentPayable(sent_tx), tx_receipt_error, &Logger::new(test_name), ); @@ -561,10 +596,7 @@ mod tests { failures: DetectedFailures { tx_failures: vec![], tx_receipt_rpc_failures: vec![FailedValidationByTable::SentPayable( - FailedValidation { - tx_hash, - failure: rpc_error - } + FailedValidation::new(tx_hash, rpc_error, current_tx_status) ),] }, confirmations: DetectedConfirmations::default() @@ -577,12 +609,37 @@ mod tests { } #[test] - fn interprets_tx_a_failed_retrieval_of_the_receipt_for_failed_tx() { + fn interprets_failed_retrieval_of_receipt_for_failed_tx_in_first_attempt() { + let test_name = "interprets_failed_retrieval_of_receipt_for_failed_tx_in_first_attempt"; + + test_failed_retrieval_of_receipt_for_failed_tx( + test_name, + FailureStatus::RecheckRequired(ValidationStatus::Waiting), + ); + } + + #[test] + fn interprets_failed_retrieval_of_receipt_for_failed_tx_as_reattempt() { + let test_name = "interprets_failed_retrieval_of_receipt_for_failed_tx_as_reattempt"; + + test_failed_retrieval_of_receipt_for_failed_tx( + test_name, + FailureStatus::RecheckRequired(ValidationStatus::Reattempting { + attempt: 1, + error: AppRpcError::Local(LocalError::Internal), + }), + ); + } + + fn test_failed_retrieval_of_receipt_for_failed_tx( + test_name: &str, + current_failure_status: FailureStatus, + ) { init_test_logging(); - let test_name = "interprets_tx_a_failed_retrieval_of_the_receipt_for_failed_tx"; let tx_hash = make_tx_hash(914); - let mut failed_tx = make_failed_tx(789); + let mut failed_tx = make_failed_tx(456); failed_tx.hash = tx_hash; + failed_tx.status = current_failure_status.clone(); let rpc_error = AppRpcError::Local(LocalError::Internal); let tx_receipt_error = TxReceiptError::new(TxHashByTable::FailedPayable(tx_hash), rpc_error.clone()); @@ -590,6 +647,7 @@ mod tests { let result = TxReceiptInterpreter::handle_rpc_failure( scan_report, + TxByTable::FailedPayable(failed_tx), tx_receipt_error, &Logger::new(test_name), ); @@ -600,19 +658,16 @@ mod tests { failures: DetectedFailures { tx_failures: vec![], tx_receipt_rpc_failures: vec![FailedValidationByTable::FailedPayable( - FailedValidation { - tx_hash, - failure: rpc_error - } - ),] + FailedValidation::new(tx_hash, rpc_error, current_failure_status) + )] }, confirmations: DetectedConfirmations::default() } ); TestLogHandler::new().exists_log_containing(&format!( - "WARN: {test_name}: Failed to retrieve tx receipt for FailedPayable(0x000000000\ - 0000000000000000000000000000000000000000000000000000392): Local(Internal). Will retry \ - receipt retrieval next cycle" + "WARN: {test_name}: Failed to retrieve tx receipt for FailedPayable(0x0000000000\ + 000000000000000000000000000000000000000000000000000392): Local(Internal). \ + Will retry receipt retrieval next cycle" )); } } diff --git a/node/src/accountant/scanners/pending_payable_scanner/utils.rs b/node/src/accountant/scanners/pending_payable_scanner/utils.rs index 5c6f45081..27d7a8ef6 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/utils.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/utils.rs @@ -1,7 +1,7 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::accountant::db_access_objects::failed_payable_dao::{ - FailedTx, FailureReason, FailureStatus, + FailedTx, FailureReason, FailureStatus, ValidationStatus, }; use crate::accountant::db_access_objects::sent_payable_dao::{ Detection, RetrieveCondition, SentPayableDao, SentTx, TxStatus, @@ -109,32 +109,112 @@ pub enum PresortedTxFailure { #[derive(Debug, PartialEq, Eq, Clone)] pub enum FailedValidationByTable { - SentPayable(FailedValidation), - FailedPayable(FailedValidation), + SentPayable(FailedValidation), + FailedPayable(FailedValidation), } -impl From for FailedValidationByTable { - fn from(tx_receipt_error: TxReceiptError) -> Self { +impl From<(TxReceiptError, TxStatus)> for FailedValidationByTable { + fn from((tx_receipt_error, current_status): (TxReceiptError, TxStatus)) -> Self { match tx_receipt_error.tx_hash { - TxHashByTable::SentPayable(tx_hash) => { - Self::SentPayable(FailedValidation::new(tx_hash, tx_receipt_error.err)) - } + TxHashByTable::SentPayable(tx_hash) => Self::SentPayable(FailedValidation::new( + tx_hash, + tx_receipt_error.err, + current_status, + )), + TxHashByTable::FailedPayable(tx_hash) => { - Self::FailedPayable(FailedValidation::new(tx_hash, tx_receipt_error.err)) + todo!() + } + } + } +} + +impl From<(TxReceiptError, FailureStatus)> for FailedValidationByTable { + fn from((tx_receipt_error, current_status): (TxReceiptError, FailureStatus)) -> Self { + match tx_receipt_error.tx_hash { + TxHashByTable::FailedPayable(tx_hash) => Self::FailedPayable(FailedValidation::new( + tx_hash, + tx_receipt_error.err, + current_status, + )), + TxHashByTable::SentPayable(tx_hash) => { + todo!() } } } } #[derive(Debug, PartialEq, Eq, Clone)] -pub struct FailedValidation { +pub struct FailedValidation { pub tx_hash: TxHash, - pub failure: AppRpcError, + pub validation_failure: AppRpcError, + pub current_status: RecordStatus, +} + +impl FailedValidation +where + RecordStatus: UpdatableValidationStatus, +{ + pub fn new( + tx_hash: TxHash, + validation_failure: AppRpcError, + current_status: RecordStatus, + ) -> Self { + Self { + tx_hash, + validation_failure, + current_status, + } + } + + pub fn new_status(&self) -> Option { + self.current_status + .update_after_failure(self.validation_failure.clone()) + } } -impl FailedValidation { - pub fn new(tx_hash: TxHash, failure: AppRpcError) -> Self { - Self { tx_hash, failure } +pub trait UpdatableValidationStatus { + fn update_after_failure(&self, error: AppRpcError) -> Option + where + Self: Sized; +} + +impl UpdatableValidationStatus for TxStatus { + fn update_after_failure(&self, error: AppRpcError) -> Option { + match self { + TxStatus::Pending(ValidationStatus::Waiting) => { + Some(TxStatus::Pending(ValidationStatus::Reattempting { + attempt: 1, + error, + })) + } + TxStatus::Pending(ValidationStatus::Reattempting { attempt, .. }) => { + Some(TxStatus::Pending(ValidationStatus::Reattempting { + attempt: attempt + 1, + error, + })) + } + TxStatus::Confirmed { .. } => None, + } + } +} + +impl UpdatableValidationStatus for FailureStatus { + fn update_after_failure(&self, error: AppRpcError) -> Option { + match self { + FailureStatus::RecheckRequired(ValidationStatus::Waiting) => { + Some(FailureStatus::RecheckRequired( + ValidationStatus::Reattempting { attempt: 1, error }, + )) + } + FailureStatus::RecheckRequired(ValidationStatus::Reattempting { attempt, .. }) => Some( + FailureStatus::RecheckRequired(ValidationStatus::Reattempting { + attempt: attempt + 1, + error, + }), + ), + FailureStatus::RetryRequired | FailureStatus::Concluded => None, + } } } @@ -274,6 +354,10 @@ impl From for FailureReason { #[cfg(test)] mod tests { + use crate::accountant::db_access_objects::failed_payable_dao::{ + FailureStatus, ValidationStatus, + }; + use crate::accountant::db_access_objects::sent_payable_dao::{Detection, TxStatus}; use crate::accountant::scanners::pending_payable_scanner::utils::{ CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailedValidation, FailedValidationByTable, NormalTxConfirmation, PendingPayableCache, PresortedTxFailure, @@ -285,6 +369,7 @@ mod tests { use crate::blockchain::test_utils::make_tx_hash; use masq_lib::logger::Logger; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; + use std::vec; #[test] fn detected_confirmations_is_empty_works() { @@ -320,11 +405,16 @@ mod tests { vec![FailedValidationByTable::SentPayable(FailedValidation::new( make_tx_hash(2222), AppRpcError::Local(LocalError::Internal), + TxStatus::Pending(ValidationStatus::Waiting), ))], vec![FailedValidationByTable::FailedPayable( FailedValidation::new( make_tx_hash(12121), AppRpcError::Remote(RemoteError::InvalidResponse("blah".to_string())), + FailureStatus::RecheckRequired(ValidationStatus::Reattempting { + attempt: 1, + error: AppRpcError::Local(LocalError::Internal), + }), ), )], ]; @@ -390,21 +480,31 @@ mod tests { vec![FailedValidationByTable::SentPayable(FailedValidation::new( make_tx_hash(2222), AppRpcError::Local(LocalError::Internal), + TxStatus::Pending(ValidationStatus::Waiting), ))], vec![FailedValidationByTable::FailedPayable( FailedValidation::new( make_tx_hash(1234), AppRpcError::Remote(RemoteError::Unreachable), + FailureStatus::RecheckRequired(ValidationStatus::Waiting), ), )], vec![ FailedValidationByTable::SentPayable(FailedValidation::new( make_tx_hash(2222), AppRpcError::Local(LocalError::Internal), + TxStatus::Pending(ValidationStatus::Reattempting { + attempt: 1, + error: AppRpcError::Local(LocalError::Internal), + }), )), FailedValidationByTable::FailedPayable(FailedValidation::new( make_tx_hash(1234), AppRpcError::Remote(RemoteError::Unreachable), + FailureStatus::RecheckRequired(ValidationStatus::Reattempting { + attempt: 1, + error: AppRpcError::Local(LocalError::Internal), + }), )), ], ]; @@ -827,22 +927,141 @@ mod tests { api_error_failed_tx.clone(), ); - let result_1 = FailedValidationByTable::from(receipt_error_for_sent_tx); - let result_2 = FailedValidationByTable::from(receipt_error_for_failed_tx); + let result_1 = FailedValidationByTable::from(( + receipt_error_for_sent_tx, + TxStatus::Pending(ValidationStatus::Waiting), + )); + let result_2 = FailedValidationByTable::from(( + receipt_error_for_failed_tx, + FailureStatus::RecheckRequired(ValidationStatus::Waiting), + )); assert_eq!( result_1, FailedValidationByTable::SentPayable(FailedValidation::new( tx_hash_sent_tx, - api_error_sent_tx + api_error_sent_tx, + TxStatus::Pending(ValidationStatus::Waiting) )) ); assert_eq!( result_2, FailedValidationByTable::FailedPayable(FailedValidation::new( tx_hash_failed_tx, - api_error_failed_tx + api_error_failed_tx, + FailureStatus::RecheckRequired(ValidationStatus::Waiting) )) ); } + + #[test] + fn failed_validation_new_status_works_fine() { + let mal_validated_tx_statuses = vec![ + ( + FailedValidation::new( + make_tx_hash(123), + AppRpcError::Local(LocalError::Internal), + TxStatus::Pending(ValidationStatus::Waiting), + ), + Some(TxStatus::Pending(ValidationStatus::Reattempting { + attempt: 1, + error: AppRpcError::Local(LocalError::Internal), + })), + ), + ( + FailedValidation::new( + make_tx_hash(123), + AppRpcError::Remote(RemoteError::Unreachable), + TxStatus::Pending(ValidationStatus::Reattempting { + attempt: 2, + error: AppRpcError::Local(LocalError::Internal), + }), + ), + Some(TxStatus::Pending(ValidationStatus::Reattempting { + attempt: 3, + error: AppRpcError::Remote(RemoteError::Unreachable), + })), + ), + ]; + let mal_validated_failure_statuses = vec![ + ( + FailedValidation::new( + make_tx_hash(456), + AppRpcError::Local(LocalError::Internal), + FailureStatus::RecheckRequired(ValidationStatus::Waiting), + ), + Some(FailureStatus::RecheckRequired( + ValidationStatus::Reattempting { + attempt: 1, + error: AppRpcError::Local(LocalError::Internal), + }, + )), + ), + ( + FailedValidation::new( + make_tx_hash(456), + AppRpcError::Remote(RemoteError::Unreachable), + FailureStatus::RecheckRequired(ValidationStatus::Reattempting { + attempt: 2, + error: AppRpcError::Remote(RemoteError::Unreachable), + }), + ), + Some(FailureStatus::RecheckRequired( + ValidationStatus::Reattempting { + attempt: 3, + error: AppRpcError::Remote(RemoteError::Unreachable), + }, + )), + ), + ]; + + mal_validated_tx_statuses.into_iter().for_each( + |(failed_validation, expected_tx_status)| { + assert_eq!(failed_validation.new_status(), expected_tx_status); + }, + ); + mal_validated_failure_statuses.into_iter().for_each( + |(failed_validation, expected_failed_tx_status)| { + assert_eq!(failed_validation.new_status(), expected_failed_tx_status); + }, + ) + } + + #[test] + fn failed_validation_new_status_has_no_effect_on_unexpected_current_status() { + let mal_validated_tx_status = FailedValidation::new( + make_tx_hash(123), + AppRpcError::Local(LocalError::Internal), + TxStatus::Confirmed { + block_hash: "".to_string(), + block_number: 0, + detection: Detection::Normal, + }, + ); + let mal_validated_failure_statuses = vec![ + FailedValidation::new( + make_tx_hash(456), + AppRpcError::Local(LocalError::Internal), + FailureStatus::RetryRequired, + ), + FailedValidation::new( + make_tx_hash(789), + AppRpcError::Remote(RemoteError::Unreachable), + FailureStatus::Concluded, + ), + ]; + + assert_eq!(mal_validated_tx_status.new_status(), None); + mal_validated_failure_statuses + .into_iter() + .enumerate() + .for_each(|(idx, failed_validation)| { + let result = failed_validation.new_status(); + assert_eq!( + result, None, + "Failed validation should evaluate to 'None' but was '{:?}' for idx: {}", + result, idx + ) + }); + } } diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs index 01cbae90d..49fec7ccc 100644 --- a/node/src/accountant/test_utils.rs +++ b/node/src/accountant/test_utils.rs @@ -991,6 +991,8 @@ pub struct SentPayableDaoMock { retrieve_txs_results: RefCell>>, confirm_tx_params: Arc>>>, confirm_tx_results: RefCell>>, + update_statuses_params: Arc>>>, + update_statuses_results: RefCell>>, replace_records_params: Arc>>>, replace_records_results: RefCell>>, delete_records_params: Arc>>>, @@ -1034,6 +1036,17 @@ impl SentPayableDao for SentPayableDaoMock { self.replace_records_results.borrow_mut().remove(0) } + fn update_statuses( + &self, + hash_map: &HashMap, + ) -> Result<(), SentPayableDaoError> { + self.update_statuses_params + .lock() + .unwrap() + .push(hash_map.clone()); + self.update_statuses_results.borrow_mut().remove(0) + } + fn delete_records(&self, hashes: &HashSet) -> Result<(), SentPayableDaoError> { self.delete_records_params .lock() @@ -1104,6 +1117,19 @@ impl SentPayableDaoMock { 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<(), SentPayableDaoError>) -> 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 @@ -1189,12 +1215,12 @@ impl FailedPayableDao for FailedPayableDaoMock { fn update_statuses( &self, - status_updates: HashMap, + status_updates: &HashMap, ) -> Result<(), FailedPayableDaoError> { self.update_statuses_params .lock() .unwrap() - .push(status_updates); + .push(status_updates.clone()); self.update_statuses_results.borrow_mut().remove(0) } From d984e40b4efa28dec400ece09693ef75ca524072 Mon Sep 17 00:00:00 2001 From: Bert Date: Sat, 9 Aug 2025 20:11:23 +0200 Subject: [PATCH 30/61] GH-642: more todos!() gone --- node/src/accountant/scanners/mod.rs | 17 -- .../scanners/pending_payable_scanner/mod.rs | 159 ++++++++++++++---- 2 files changed, 122 insertions(+), 54 deletions(-) diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index e720aeff5..c849d9d59 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -2648,23 +2648,6 @@ mod tests { ); } - #[test] - fn pending_payable_scanner_throws_an_error_when_no_sent_tx_record_is_found() { - let now = SystemTime::now(); - let consuming_wallet = make_paying_wallet(b"consuming_wallet"); - let sent_payable_dao = SentPayableDaoMock::new().retrieve_txs_result(vec![]); - let mut pending_payable_scanner = PendingPayableScannerBuilder::new() - .sent_payable_dao(sent_payable_dao) - .build(); - - let result = - pending_payable_scanner.start_scan(&consuming_wallet, now, None, &Logger::new("test")); - - let is_scan_running = pending_payable_scanner.scan_started_at().is_some(); - assert_eq!(result, Err(StartScanError::NothingToProcess)); - assert_eq!(is_scan_running, false); - } - #[test] fn check_general_conditions_for_pending_payable_scan_if_it_is_initial_pending_payable_scan() { let mut subject = make_dull_subject(); diff --git a/node/src/accountant/scanners/pending_payable_scanner/mod.rs b/node/src/accountant/scanners/pending_payable_scanner/mod.rs index 335cec06a..5c21eb67e 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/mod.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/mod.rs @@ -79,9 +79,8 @@ impl StartableScanner let failure_hashes_opt = self.handle_unproven_failures(); if pending_tx_hashes_opt.is_none() && failure_hashes_opt.is_none() { - todo!() - // self.mark_as_ended(logger); - // Err(StartScanError::NothingToProcess) + self.mark_as_ended(logger); + return Err(StartScanError::NothingToProcess); } Self::log_records_found_for_receipt_check( @@ -125,6 +124,8 @@ impl Scanner for PendingPayableScan time_marking_methods!(PendingPayables); as_any_ref_in_trait_impl!(); + + as_any_mut_in_trait_impl!(); } impl PendingPayableScanner { @@ -151,9 +152,7 @@ impl PendingPayableScanner { .sent_payable_dao .retrieve_txs(Some(RetrieveCondition::IsPending)); - if pending_txs.is_empty() { - todo!() //return None - } else { + if !pending_txs.is_empty() { let pending_tx_hashes = Self::get_wrapped_hashes(&pending_txs, TxHashByTable::SentPayable); @@ -162,6 +161,8 @@ impl PendingPayableScanner { } Some(pending_tx_hashes) + } else { + None } } @@ -170,9 +171,7 @@ impl PendingPayableScanner { .failed_payable_dao .retrieve_txs(Some(FailureRetrieveCondition::EveryRecheckRequiredRecord)); - if failures.is_empty() { - todo!() //return None - } else { + if !failures.is_empty() { let failure_hashes = Self::get_wrapped_hashes(&failures, TxHashByTable::FailedPayable); if !failures.is_empty() { @@ -180,6 +179,8 @@ impl PendingPayableScanner { } Some(failure_hashes) + } else { + None } } @@ -202,6 +203,21 @@ impl PendingPayableScanner { } } + fn compose_scan_result( + retry_opt: Option, + response_skeleton_opt: Option, + ) -> PendingPayableScanResult { + if let Some(retry) = retry_opt { + PendingPayableScanResult::PaymentRetryRequired(retry) + } else { + let ui_msg_opt = response_skeleton_opt.map(|response_skeleton| NodeToUiMessage { + target: MessageTarget::ClientId(response_skeleton.client_id), + body: UiScanResponse {}.tmb(response_skeleton.context_id), + }); + PendingPayableScanResult::NoPendingPayablesLeft(ui_msg_opt) + } + } + fn interpret_tx_receipts( &mut self, msg: TxReceiptsMessage, @@ -551,12 +567,21 @@ impl PendingPayableScanner { if !updatable.is_empty() { match self.sent_payable_dao.update_statuses(&updatable) { Ok(_) => { - info!(logger, "Pending-tx statuses were processed in the db for validation failure of txs {}", comma_joined_stringifiable(&sent_payable_failures, |failure|{ - format!("{:?}", failure.tx_hash) - })) + info!( + logger, + "Pending-tx statuses were processed in the db for validation \ + failure of txs {}", + comma_joined_stringifiable(&sent_payable_failures, |failure| { + format!("{:?}", failure.tx_hash) + }) + ) } - Err(_) => { - todo!() + Err(e) => { + panic!( + "Unable to update pending-tx statuses for validation failures '{:?}' \ + due to {:?}", + sent_payable_failures, e + ) } } } @@ -574,12 +599,22 @@ impl PendingPayableScanner { if !updatable.is_empty() { match self.failed_payable_dao.update_statuses(&updatable) { Ok(_) => { - info!(logger, "Failed-tx statuses were processed in the db for validation failure of txs {}", comma_joined_stringifiable(&failed_txs_validation_failures, |failure|{ - format!("{:?}", failure.tx_hash) - })) + info!( + logger, + "Failed-tx statuses were processed in the db for validation \ + failure of txs {}", + comma_joined_stringifiable( + &failed_txs_validation_failures, + |failure| { format!("{:?}", failure.tx_hash) } + ) + ) } - Err(_) => { - todo!() + Err(e) => { + panic!( + "Unable to update failed-tx statuses for validation failures '{:?}' \ + due to {:?}", + failed_txs_validation_failures, e + ) } } } @@ -617,21 +652,6 @@ impl PendingPayableScanner { ) } - fn compose_scan_result( - retry_opt: Option, - response_skeleton_opt: Option, - ) -> PendingPayableScanResult { - if let Some(retry) = retry_opt { - PendingPayableScanResult::PaymentRetryRequired(retry) - } else { - let ui_msg_opt = response_skeleton_opt.map(|response_skeleton| NodeToUiMessage { - target: MessageTarget::ClientId(response_skeleton.client_id), - body: UiScanResponse {}.tmb(response_skeleton.context_id), - }); - PendingPayableScanResult::NoPendingPayablesLeft(ui_msg_opt) - } - } - fn log_records_found_for_receipt_check( pending_tx_hashes_opt: Option<&Vec>, failure_hashes_opt: Option<&Vec>, @@ -669,7 +689,7 @@ mod tests { }; use crate::accountant::scanners::pending_payable_scanner::PendingPayableScanner; use crate::accountant::scanners::test_utils::PendingPayableCacheMock; - use crate::accountant::scanners::{Scanner, StartableScanner}; + use crate::accountant::scanners::{Scanner, StartScanError, StartableScanner}; use crate::accountant::test_utils::{ make_failed_tx, make_sent_tx, make_transaction_block, FailedPayableDaoMock, PayableDaoMock, PendingPayableScannerBuilder, SentPayableDaoMock, @@ -681,7 +701,7 @@ mod tests { }; use crate::blockchain::errors::{AppRpcError, LocalError, RemoteError}; use crate::blockchain::test_utils::{make_block_hash, make_tx_hash}; - use crate::test_utils::make_wallet; + use crate::test_utils::{make_paying_wallet, make_wallet}; use masq_lib::logger::Logger; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; use regex::Regex; @@ -986,6 +1006,24 @@ mod tests { ); } + #[test] + fn throws_an_error_when_no_records_to_process_were_found() { + let now = SystemTime::now(); + let consuming_wallet = make_paying_wallet(b"consuming_wallet"); + let sent_payable_dao = SentPayableDaoMock::new().retrieve_txs_result(vec![]); + let failed_payable_dao = FailedPayableDaoMock::new().retrieve_txs_result(vec![]); + let mut subject = PendingPayableScannerBuilder::new() + .failed_payable_dao(failed_payable_dao) + .sent_payable_dao(sent_payable_dao) + .build(); + + let result = subject.start_scan(&consuming_wallet, now, None, &Logger::new("test")); + + let is_scan_running = subject.scan_started_at().is_some(); + assert_eq!(result, Err(StartScanError::NothingToProcess)); + assert_eq!(is_scan_running, false); + } + #[test] fn handle_failed_transactions_can_process_standard_tx_failures() { init_test_logging(); @@ -1179,7 +1217,8 @@ mod tests { test_log_handler.exists_no_log_containing(&format!("INFO: {test_name}: ")); test_log_handler.exists_log_containing(&format!( "DEBUG: {test_name}: Handling a validation failure, but the status \ - {{\"Confirmed\":{{\"block_hash\":\"abc\",\"block_number\":0,\"detection\":\"Normal\"}}}} cannot be updated.", + {{\"Confirmed\":{{\"block_hash\":\"abc\",\"block_number\":0,\"detection\":\"Normal\"}}}} \ + cannot be updated.", )); test_log_handler.exists_log_containing(&format!( "DEBUG: {test_name}: Handling a validation failure, but the status \"RetryRequired\" \ @@ -1189,6 +1228,52 @@ mod tests { // mocked in this test } + #[test] + #[should_panic( + expected = "Unable to update pending-tx statuses for validation failures \ + '[FailedValidation { tx_hash: 0x00000000000000000000000000000000000000000000000000000000000001c8, \ + validation_failure: Local(Internal), current_status: Pending(Waiting) }]' due to \ + InvalidInput(\"bluh\")" + )] + fn update_validation_status_for_sent_txs_panics_on_update_statuses() { + let failed_validation = FailedValidation::new( + make_tx_hash(456), + AppRpcError::Local(LocalError::Internal), + TxStatus::Pending(ValidationStatus::Waiting), + ); + let sent_payable_dao = SentPayableDaoMock::default() + .update_statuses_result(Err(SentPayableDaoError::InvalidInput("bluh".to_string()))); + let subject = PendingPayableScannerBuilder::new() + .sent_payable_dao(sent_payable_dao) + .build(); + + let _ = subject + .update_validation_status_for_sent_txs(vec![failed_validation], &Logger::new("test")); + } + + #[test] + #[should_panic( + expected = "Unable to update failed-tx statuses for validation failures \ + '[FailedValidation { tx_hash: 0x00000000000000000000000000000000000000000000000000000000000001c8, \ + validation_failure: Local(Internal), current_status: RecheckRequired(Waiting) }]' due to \ + InvalidInput(\"bluh\")" + )] + fn update_validation_status_for_failed_txs_panics_on_update_statuses() { + let failed_validation = FailedValidation::new( + make_tx_hash(456), + AppRpcError::Local(LocalError::Internal), + FailureStatus::RecheckRequired(ValidationStatus::Waiting), + ); + let failed_payable_dao = FailedPayableDaoMock::default() + .update_statuses_result(Err(FailedPayableDaoError::InvalidInput("bluh".to_string()))); + let subject = PendingPayableScannerBuilder::new() + .failed_payable_dao(failed_payable_dao) + .build(); + + let _ = subject + .update_validation_status_for_failed_txs(vec![failed_validation], &Logger::new("test")); + } + #[test] fn handle_failed_transactions_can_process_mixed_failures() { let insert_new_records_params_arc = Arc::new(Mutex::new(vec![])); From 97507564fe51ce5716db71c7f4d1a0042941b050 Mon Sep 17 00:00:00 2001 From: Bert Date: Sun, 10 Aug 2025 10:34:39 +0200 Subject: [PATCH 31/61] GH-642: processing failures is done; next tx confiramtions --- .../scanners/pending_payable_scanner/mod.rs | 138 ++++++++++++++---- 1 file changed, 109 insertions(+), 29 deletions(-) diff --git a/node/src/accountant/scanners/pending_payable_scanner/mod.rs b/node/src/accountant/scanners/pending_payable_scanner/mod.rs index 5c21eb67e..7f1a1466f 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/mod.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/mod.rs @@ -472,8 +472,8 @@ impl PendingPayableScanner { PresortedTxFailure::NewEntry(failed_tx) => { acc.new_failures.push(failed_tx); } - PresortedTxFailure::RecheckCompleted(hash) => { - todo!() + PresortedTxFailure::RecheckCompleted(tx_hash) => { + acc.rechecks_completed.push(tx_hash); } } acc @@ -528,11 +528,42 @@ impl PendingPayableScanner { } fn finalize_unproven_failures(&self, rechecks_completed: Vec, logger: &Logger) { + fn prepare_hashmap(rechecks_completed: &[TxHash]) -> HashMap { + rechecks_completed + .iter() + .map(|tx_hash| (tx_hash.clone(), FailureStatus::Concluded)) + .collect() + } + if rechecks_completed.is_empty() { return; } - todo!() + match self + .failed_payable_dao + .update_statuses(&prepare_hashmap(&rechecks_completed)) + { + Ok(_) => { + debug!( + logger, + "Concluded failures that had required rechecks: {}.", + comma_joined_stringifiable(&rechecks_completed, |tx_hash| format!( + "{:?}", + tx_hash + )) + ); + } + Err(e) => { + panic!( + "Unable to conclude rechecks for failed txs {} due to {:?}", + comma_joined_stringifiable(&rechecks_completed, |tx_hash| format!( + "{:?}", + tx_hash + )), + e + ) + } + } } fn handle_rpc_failures(&self, failures: Vec, logger: &Logger) { @@ -1278,21 +1309,18 @@ mod tests { fn handle_failed_transactions_can_process_mixed_failures() { let insert_new_records_params_arc = Arc::new(Mutex::new(vec![])); let delete_records_params_arc = Arc::new(Mutex::new(vec![])); - let retrieve_failed_txs_params_arc = Arc::new(Mutex::new(vec![])); let update_status_params_arc = Arc::new(Mutex::new(vec![])); - let hash_1 = make_tx_hash(0x321); - let hash_2 = make_tx_hash(0x654); + let tx_hash_1 = make_tx_hash(0x321); + let tx_hash_2 = make_tx_hash(0x654); let mut failed_tx_1 = make_failed_tx(123); - failed_tx_1.hash = hash_1; + failed_tx_1.hash = tx_hash_1; let mut failed_tx_2 = make_failed_tx(456); - failed_tx_2.hash = hash_2; + failed_tx_2.hash = tx_hash_2; let failed_payable_dao = FailedPayableDaoMock::default() - .retrieve_txs_params(&retrieve_failed_txs_params_arc) - .retrieve_txs_result(vec![failed_tx_1.clone()]) - .update_statuses_params(&update_status_params_arc) - .update_statuses_result(Ok(())) .insert_new_records_params(&insert_new_records_params_arc) - .insert_new_records_result(Ok(())); + .insert_new_records_result(Ok(())) + .update_statuses_params(&update_status_params_arc) + .update_statuses_result(Ok(())); let sent_payable_dao = SentPayableDaoMock::default() .delete_records_params(&delete_records_params_arc) .delete_records_result(Ok(())); @@ -1301,10 +1329,10 @@ mod tests { .failed_payable_dao(failed_payable_dao) .build(); let detected_failures = DetectedFailures { - tx_failures: vec![PresortedTxFailure::NewEntry(failed_tx_1)], + tx_failures: vec![PresortedTxFailure::NewEntry(failed_tx_1.clone())], tx_receipt_rpc_failures: vec![FailedValidationByTable::SentPayable( FailedValidation::new( - hash_1, + tx_hash_2, AppRpcError::Local(LocalError::Internal), TxStatus::Pending(ValidationStatus::Waiting), ), @@ -1313,24 +1341,17 @@ mod tests { subject.handle_failed_transactions(detected_failures, &Logger::new("test")); - let retrieve_failed_txs_params = retrieve_failed_txs_params_arc.lock().unwrap(); - assert_eq!( - *retrieve_failed_txs_params, - vec![Some(FailureRetrieveCondition::ByTxHash(vec![ - hash_1, hash_2 - ]))] - ); - let update_status_params = update_status_params_arc.lock().unwrap(); + let insert_new_records_params = insert_new_records_params_arc.lock().unwrap(); + assert_eq!(*insert_new_records_params, vec![vec![failed_tx_1]]); + let delete_records_params = delete_records_params_arc.lock().unwrap(); + assert_eq!(*delete_records_params, vec![hashset![tx_hash_1]]); + let update_statuses_params = update_status_params_arc.lock().unwrap(); assert_eq!( - *update_status_params, + *update_statuses_params, vec![ - hashmap!(hash_1 => FailureStatus::RecheckRequired(ValidationStatus::Reattempting {attempt: 1,error: AppRpcError::Local(LocalError::Internal)})) + hashmap!(tx_hash_2 => FailureStatus::RecheckRequired(ValidationStatus::Reattempting {attempt: 1,error: AppRpcError::Local(LocalError::Internal)})) ] ); - let insert_new_records_params = insert_new_records_params_arc.lock().unwrap(); - assert_eq!(*insert_new_records_params, vec![vec![failed_tx_2]]); - let delete_records_params = delete_records_params_arc.lock().unwrap(); - assert_eq!(*delete_records_params, vec![hashset![hash_2]]); } #[test] @@ -1390,6 +1411,65 @@ mod tests { subject.handle_failed_transactions(detected_failures, &Logger::new("test")); } + #[test] + fn handle_failed_transactions_can_conclude_rechecked_failures() { + let update_status_params_arc = Arc::new(Mutex::new(vec![])); + let tx_hash_1 = make_tx_hash(0x321); + let tx_hash_2 = make_tx_hash(0x654); + let mut failed_tx_1 = make_failed_tx(123); + failed_tx_1.hash = tx_hash_1; + let mut failed_tx_2 = make_failed_tx(456); + failed_tx_2.hash = tx_hash_2; + let failed_payable_dao = FailedPayableDaoMock::default() + .update_statuses_params(&update_status_params_arc) + .update_statuses_result(Ok(())); + let subject = PendingPayableScannerBuilder::new() + .failed_payable_dao(failed_payable_dao) + .build(); + let detected_failures = DetectedFailures { + tx_failures: vec![ + PresortedTxFailure::RecheckCompleted(tx_hash_1), + PresortedTxFailure::RecheckCompleted(tx_hash_2), + ], + tx_receipt_rpc_failures: vec![], + }; + + subject.handle_failed_transactions(detected_failures, &Logger::new("test")); + + let update_status_params = update_status_params_arc.lock().unwrap(); + assert_eq!( + *update_status_params, + vec![ + hashmap!(tx_hash_1 => FailureStatus::Concluded, tx_hash_2 => FailureStatus::Concluded), + ] + ); + } + + #[test] + #[should_panic(expected = "Unable to conclude rechecks for failed txs \ + 0x0000000000000000000000000000000000000000000000000000000000000321, \ + 0x0000000000000000000000000000000000000000000000000000000000000654 due to \ + InvalidInput(\"Booga\")")] + fn concluding_rechecks_fails_on_updating_statuses() { + let tx_hash_1 = make_tx_hash(0x321); + let tx_hash_2 = make_tx_hash(0x654); + let failed_payable_dao = FailedPayableDaoMock::default().update_statuses_result(Err( + FailedPayableDaoError::InvalidInput("Booga".to_string()), + )); + let subject = PendingPayableScannerBuilder::new() + .failed_payable_dao(failed_payable_dao) + .build(); + let detected_failures = DetectedFailures { + tx_failures: vec![ + PresortedTxFailure::RecheckCompleted(tx_hash_1), + PresortedTxFailure::RecheckCompleted(tx_hash_2), + ], + tx_receipt_rpc_failures: vec![], + }; + + subject.handle_failed_transactions(detected_failures, &Logger::new("test")); + } + #[test] fn handle_failed_transactions_does_nothing_if_no_failure_detected() { let subject = PendingPayableScannerBuilder::new().build(); From f245a51cf7f4ae68df8df367e6a4f53b905db883 Mon Sep 17 00:00:00 2001 From: Bert Date: Sun, 10 Aug 2025 21:38:19 +0200 Subject: [PATCH 32/61] GH-642: tx reclaim implemented --- ...able_and_failed_payable_data_conversion.rs | 6 +- .../db_access_objects/sent_payable_dao.rs | 30 +- node/src/accountant/mod.rs | 12 +- node/src/accountant/scanners/mod.rs | 7 +- .../scanners/pending_payable_scanner/mod.rs | 414 ++++++++++++++---- .../tx_receipt_interpreter.rs | 5 +- .../scanners/pending_payable_scanner/utils.rs | 6 +- node/src/accountant/test_utils.rs | 20 +- node/src/blockchain/blockchain_bridge.rs | 6 +- .../lower_level_interface_web3.rs | 4 +- .../blockchain_interface_web3/mod.rs | 4 +- .../data_structures/mod.rs | 8 +- 12 files changed, 383 insertions(+), 139 deletions(-) diff --git a/node/src/accountant/db_access_objects/sent_payable_and_failed_payable_data_conversion.rs b/node/src/accountant/db_access_objects/sent_payable_and_failed_payable_data_conversion.rs index 7a71e98a9..379ef4d62 100644 --- a/node/src/accountant/db_access_objects/sent_payable_and_failed_payable_data_conversion.rs +++ b/node/src/accountant/db_access_objects/sent_payable_and_failed_payable_data_conversion.rs @@ -4,10 +4,10 @@ use crate::accountant::db_access_objects::failed_payable_dao::{ FailedTx, FailureReason, FailureStatus, }; use crate::accountant::db_access_objects::sent_payable_dao::{Detection, SentTx, TxStatus}; -use crate::blockchain::blockchain_interface::data_structures::TransactionBlock; +use crate::blockchain::blockchain_interface::data_structures::TxBlock; -impl From<(FailedTx, TransactionBlock)> for SentTx { - fn from((failed_tx, confirmation_block): (FailedTx, TransactionBlock)) -> Self { +impl From<(FailedTx, TxBlock)> for SentTx { + fn from((failed_tx, confirmation_block): (FailedTx, TxBlock)) -> Self { SentTx { hash: failed_tx.hash, receiver_address: failed_tx.receiver_address, 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 e35b54678..d2ffbd9f7 100644 --- a/node/src/accountant/db_access_objects/sent_payable_dao.rs +++ b/node/src/accountant/db_access_objects/sent_payable_dao.rs @@ -6,7 +6,7 @@ use crate::accountant::db_access_objects::utils::{ }; use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; use crate::accountant::{checked_conversion, comma_joined_stringifiable}; -use crate::blockchain::blockchain_interface::data_structures::TransactionBlock; +use crate::blockchain::blockchain_interface::data_structures::TxBlock; use crate::database::rusqlite_wrappers::ConnectionWrapper; use ethereum_types::H256; use itertools::Itertools; @@ -78,8 +78,8 @@ pub enum Detection { Reclaim, } -impl From for TxStatus { - fn from(tx_block: TransactionBlock) -> Self { +impl From for TxStatus { + fn from(tx_block: TxBlock) -> Self { TxStatus::Confirmed { block_hash: format!("{:?}", tx_block.block_hash), block_number: u64::try_from(tx_block.block_number).expect("block number too big"), @@ -124,10 +124,7 @@ pub trait SentPayableDao { fn insert_new_records(&self, txs: &[SentTx]) -> Result<(), SentPayableDaoError>; fn retrieve_txs(&self, condition: Option) -> Vec; //TODO potentially atomically - fn confirm_tx( - &self, - hash_map: &HashMap, - ) -> Result<(), SentPayableDaoError>; + fn confirm_tx(&self, hash_map: &HashMap) -> Result<(), SentPayableDaoError>; fn replace_records(&self, new_txs: &[SentTx]) -> Result<(), SentPayableDaoError>; fn update_statuses( &self, @@ -290,10 +287,7 @@ impl SentPayableDao for SentPayableDaoReal<'_> { .collect() } - fn confirm_tx( - &self, - hash_map: &HashMap, - ) -> Result<(), SentPayableDaoError> { + fn confirm_tx(&self, hash_map: &HashMap) -> Result<(), SentPayableDaoError> { if hash_map.is_empty() { return Err(SentPayableDaoError::EmptyInput); } @@ -481,7 +475,7 @@ mod tests { }; use crate::accountant::db_access_objects::utils::TxRecordWithHash; use crate::accountant::test_utils::make_sent_tx; - use crate::blockchain::blockchain_interface::data_structures::TransactionBlock; + use crate::blockchain::blockchain_interface::data_structures::TxBlock; use crate::blockchain::errors::{AppRpcError, LocalError, RemoteError}; use crate::blockchain::test_utils::{make_block_hash, make_tx_hash}; use crate::database::db_initializer::{ @@ -822,11 +816,11 @@ mod tests { let updated_pre_assert_txs = subject.retrieve_txs(Some(ByHash(vec![tx1.hash, tx2.hash]))); let pre_assert_status_tx1 = updated_pre_assert_txs[0].status.clone(); let pre_assert_status_tx2 = updated_pre_assert_txs[1].status.clone(); - let confirmed_tx_block_1 = TransactionBlock { + let confirmed_tx_block_1 = TxBlock { block_hash: make_block_hash(3), block_number: U64::from(1), }; - let confirmed_tx_block_2 = TransactionBlock { + let confirmed_tx_block_2 = TxBlock { block_hash: make_block_hash(4), block_number: U64::from(2), }; @@ -902,14 +896,14 @@ mod tests { let hash_map = HashMap::from([ ( existent_hash, - TransactionBlock { + TxBlock { block_hash: make_block_hash(1), block_number: U64::from(1), }, ), ( non_existent_hash, - TransactionBlock { + TxBlock { block_hash: make_block_hash(2), block_number: U64::from(2), }, @@ -938,7 +932,7 @@ mod tests { let hash = make_tx_hash(1); let hash_map = HashMap::from([( hash, - TransactionBlock { + TxBlock { block_hash: make_block_hash(1), block_number: U64::default(), }, @@ -1398,7 +1392,7 @@ mod tests { #[test] fn tx_status_can_be_made_from_transaction_block() { - let tx_block = TransactionBlock { + let tx_block = TxBlock { block_hash: make_block_hash(6), block_number: 456789_u64.into(), }; diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index b0c9a61a6..cb432aa4f 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -1271,7 +1271,7 @@ mod tests { use crate::accountant::Accountant; use crate::blockchain::blockchain_interface::blockchain_interface_web3::HashAndAmount; use crate::blockchain::blockchain_interface::data_structures::{ - BlockchainTxFailure, RetrievedTxStatus, StatusReadFromReceiptCheck, TransactionBlock, + BlockchainTxFailure, RetrievedTxStatus, StatusReadFromReceiptCheck, TxBlock, TxReceiptResult, }; use crate::blockchain::test_utils::make_tx_hash; @@ -1959,7 +1959,7 @@ mod tests { subject.ui_message_sub_opt = Some(ui_gateway_addr.recipient()); let subject_addr = subject.start(); let mut sent_tx = make_sent_tx(123); - let tx_block = TransactionBlock { + let tx_block = TxBlock { block_hash: make_tx_hash(456), block_number: 78901234.into(), }; @@ -3550,7 +3550,7 @@ mod tests { }; let tx_with_status = RetrievedTxStatus::new( TxHashByTable::SentPayable(tx_hash), - StatusReadFromReceiptCheck::Succeeded(TransactionBlock { + StatusReadFromReceiptCheck::Succeeded(TxBlock { block_hash: make_tx_hash(369369), block_number: 4444444444u64.into(), }), @@ -5072,14 +5072,14 @@ mod tests { let (msg, two_sent_txs) = make_tx_receipts_msg(vec![ SeedsToMakeUpPayableWithStatus { tx_hash: TxHashByTable::SentPayable(make_tx_hash(123)), - status: StatusReadFromReceiptCheck::Succeeded(TransactionBlock { + status: StatusReadFromReceiptCheck::Succeeded(TxBlock { block_hash: make_tx_hash(123), block_number: U64::from(100), }), }, SeedsToMakeUpPayableWithStatus { tx_hash: TxHashByTable::FailedPayable(make_tx_hash(555)), - status: StatusReadFromReceiptCheck::Succeeded(TransactionBlock { + status: StatusReadFromReceiptCheck::Succeeded(TxBlock { block_hash: make_tx_hash(234), block_number: U64::from(200), }), @@ -5242,7 +5242,7 @@ mod tests { let subject_addr = subject.start(); let (msg, _) = make_tx_receipts_msg(vec![SeedsToMakeUpPayableWithStatus { tx_hash: TxHashByTable::SentPayable(make_tx_hash(123)), - status: StatusReadFromReceiptCheck::Succeeded(TransactionBlock { + status: StatusReadFromReceiptCheck::Succeeded(TxBlock { block_hash: make_tx_hash(123), block_number: U64::from(100), }), diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index c849d9d59..0892bd562 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -1049,8 +1049,7 @@ mod tests { use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; use crate::blockchain::blockchain_interface::data_structures::{ BlockchainTransaction, BlockchainTxFailure, ProcessedPayableFallible, RetrievedTxStatus, - RpcPayableFailure, StatusReadFromReceiptCheck, TransactionBlock, TxReceiptError, - TxReceiptResult, + RpcPayableFailure, StatusReadFromReceiptCheck, TxBlock, TxReceiptError, TxReceiptResult, }; use crate::blockchain::errors::{AppRpcError, RemoteError}; use crate::blockchain::test_utils::{make_block_hash, make_tx_hash}; @@ -2678,7 +2677,7 @@ mod tests { let tx_hash_1 = make_tx_hash(4545); let mut sent_tx_1 = make_sent_tx(123); sent_tx_1.hash = tx_hash_1; - let tx_block_1 = TransactionBlock { + let tx_block_1 = TxBlock { block_hash: make_block_hash(333), block_number: U64::from(1234), }; @@ -2689,7 +2688,7 @@ mod tests { let tx_hash_2 = make_tx_hash(1234); let mut sent_tx_2 = make_sent_tx(789); sent_tx_2.hash = tx_hash_2; - let tx_block_2 = TransactionBlock { + let tx_block_2 = TxBlock { block_hash: make_block_hash(222), block_number: U64::from(2345), }; diff --git a/node/src/accountant/scanners/pending_payable_scanner/mod.rs b/node/src/accountant/scanners/pending_payable_scanner/mod.rs index 7f1a1466f..1982912d4 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/mod.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/mod.rs @@ -4,7 +4,7 @@ mod tx_receipt_interpreter; pub mod utils; use crate::accountant::db_access_objects::failed_payable_dao::{ - FailedPayableDao, FailedTx, FailureRetrieveCondition, FailureStatus, + FailedPayableDao, FailedPayableDaoError, FailedTx, FailureRetrieveCondition, FailureStatus, }; use crate::accountant::db_access_objects::payable_dao::{PayableDao, PayableDaoError}; use crate::accountant::db_access_objects::sent_payable_dao::{ @@ -26,7 +26,7 @@ use crate::accountant::{ ScanForPendingPayables, TxReceiptsMessage, }; use crate::blockchain::blockchain_interface::data_structures::{ - StatusReadFromReceiptCheck, TransactionBlock, TxReceiptResult, + StatusReadFromReceiptCheck, TxBlock, TxReceiptResult, }; use crate::sub_lib::accountant::{FinancialStatistics, PaymentThresholds}; use crate::sub_lib::wallet::Wallet; @@ -345,11 +345,77 @@ impl PendingPayableScanner { } fn handle_tx_failure_reclaims(&self, reclaimed: Vec, logger: &Logger) { + fn collect_hashes_and_blocks(reclaimed: &[TxReclaim]) -> Vec<(TxHash, u64)> { + reclaimed + .iter() + .map(|reclaim| { + let tx_block_num = if let TxStatus::Confirmed { block_number, .. } = + reclaim.reclaimed.status + { + block_number + } else { + unreachable!( + "Processing a reclaim for tx {:?} which isn't filled with \ + the confirmation details", + reclaim.reclaimed.hash + ) + }; + (reclaim.reclaimed.hash, tx_block_num) + }) + .collect() + } + fn isolate_hashes(reclaimed: &[(TxHash, u64)]) -> HashSet { + reclaimed.iter().map(|(tx_hash, _)| *tx_hash).collect() + } + fn prepare_sent_tx_records(reclaimed: Vec) -> Vec { + reclaimed + .into_iter() + .map(|reclaim| reclaim.reclaimed) + .collect() + } + if reclaimed.is_empty() { return; } - todo!() + let hashes_and_blocks = collect_hashes_and_blocks(&reclaimed); + match self + .sent_payable_dao + .replace_records(&prepare_sent_tx_records(reclaimed)) + { + Ok(_) => { + debug!(logger, "Replaced records for txs being reclaimed") + } + Err(e) => { + panic!("Unable to proceed in a reclaim as the replacement of sent tx records {} failed due to: {:?}", comma_joined_stringifiable(&hashes_and_blocks, |(tx_hash,_)|{ + format!("{:?}", tx_hash) + }), e) + } + } + + match self + .failed_payable_dao + .delete_records(&isolate_hashes(&hashes_and_blocks)) + { + Ok(_) => { + info!( + logger, + "Reclaimed txs {} as confirmed on-chain", + comma_joined_stringifiable(&hashes_and_blocks, |(tx_hash, block_number)| { + format!("{:?} (block {})", tx_hash, block_number) + }) + ) + } + Err(e) => { + panic!( + "Unable to delete failed tx records {} to finish the reclaims due to: {:?}", + comma_joined_stringifiable(&hashes_and_blocks, |(tx_hash, _)| { + format!("{:?}", tx_hash) + }), + e + ) + } + } } fn handle_normal_confirmations( @@ -379,9 +445,7 @@ impl PendingPayableScanner { // } } - fn compose_tx_confirmation_inputs( - confirmed_txs: &[SentTx], - ) -> HashMap { + fn compose_tx_confirmation_inputs(confirmed_txs: &[SentTx]) -> HashMap { todo!() } @@ -392,18 +456,18 @@ impl PendingPayableScanner { .collect_vec(); panic!( "Unable to complete the tx confirmation by the adjustment of the payable accounts {} \ - due to {:?}", + due to: {:?}", comma_joined_stringifiable(&wallets, |wallet| format!("{:?}", wallet)), e ) } fn update_tx_blocks_panic( - tx_hashes_and_tx_blocks: &HashMap, + tx_hashes_and_tx_blocks: &HashMap, e: SentPayableDaoError, ) -> ! { panic!( - "Unable to update sent payable records {} by their tx blocks due to {:?}", + "Unable to update sent payable records {} by their tx blocks due to: {:?}", comma_joined_stringifiable( &tx_hashes_and_tx_blocks.keys().sorted().collect_vec(), |tx_hash| format!("{:?}", tx_hash) @@ -412,10 +476,7 @@ impl PendingPayableScanner { ) } - fn log_tx_success( - logger: &Logger, - tx_hashes_and_tx_blocks: &HashMap, - ) { + fn log_tx_success(logger: &Logger, tx_hashes_and_tx_blocks: &HashMap) { logger.info(|| { let pretty_pairs = tx_hashes_and_tx_blocks .iter() @@ -501,7 +562,7 @@ impl PendingPayableScanner { if let Err(e) = self.failed_payable_dao.insert_new_records(&new_failures) { panic!( - "Unable to persist failed txs {} due to {:?}", + "Unable to persist failed txs {} due to: {:?}", comma_joined_stringifiable(&new_failures, |failure| format!("{:?}", failure.hash)), e ) @@ -516,7 +577,7 @@ impl PendingPayableScanner { } Err(e) => { panic!( - "Unable to purge sent payable records for failed txs {} due to {:?}", + "Unable to purge sent payable records for failed txs {} due to: {:?}", comma_joined_stringifiable(&new_failures, |failure| format!( "{:?}", failure.hash @@ -555,7 +616,7 @@ impl PendingPayableScanner { } Err(e) => { panic!( - "Unable to conclude rechecks for failed txs {} due to {:?}", + "Unable to conclude rechecks for failed txs {} due to: {:?}", comma_joined_stringifiable(&rechecks_completed, |tx_hash| format!( "{:?}", tx_hash @@ -610,7 +671,7 @@ impl PendingPayableScanner { Err(e) => { panic!( "Unable to update pending-tx statuses for validation failures '{:?}' \ - due to {:?}", + due to: {:?}", sent_payable_failures, e ) } @@ -643,7 +704,7 @@ impl PendingPayableScanner { Err(e) => { panic!( "Unable to update failed-tx statuses for validation failures '{:?}' \ - due to {:?}", + due to: {:?}", failed_txs_validation_failures, e ) } @@ -704,19 +765,17 @@ impl PendingPayableScanner { #[cfg(test)] mod tests { use crate::accountant::db_access_objects::failed_payable_dao::{ - FailedPayableDaoError, FailedTx, FailureReason, FailureRetrieveCondition, FailureStatus, - ValidationStatus, + FailedPayableDaoError, FailedTx, FailureReason, FailureStatus, ValidationStatus, }; use crate::accountant::db_access_objects::payable_dao::PayableDaoError; use crate::accountant::db_access_objects::sent_payable_dao::{ Detection, RetrieveCondition, SentPayableDaoError, SentTx, TxStatus, }; - use crate::accountant::db_access_objects::utils::{from_unix_timestamp, to_unix_timestamp}; use crate::accountant::scanners::pending_payable_scanner::utils::{ CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailedValidation, FailedValidationByTable, NormalTxConfirmation, PendingPayableCache, PendingPayableScanResult, PresortedTxFailure, ReceiptScanReport, RecheckRequiringFailures, - Retry, TxByTable, TxHashByTable, TxReclaim, + Retry, TxHashByTable, TxReclaim, }; use crate::accountant::scanners::pending_payable_scanner::PendingPayableScanner; use crate::accountant::scanners::test_utils::PendingPayableCacheMock; @@ -727,8 +786,7 @@ mod tests { }; use crate::accountant::{RequestTransactionReceipts, TxReceiptsMessage}; use crate::blockchain::blockchain_interface::data_structures::{ - RetrievedTxStatus, StatusReadFromReceiptCheck, TransactionBlock, TxReceiptError, - TxReceiptResult, + RetrievedTxStatus, StatusReadFromReceiptCheck, TxBlock, TxReceiptError, TxReceiptResult, }; use crate::blockchain::errors::{AppRpcError, LocalError, RemoteError}; use crate::blockchain::test_utils::{make_block_hash, make_tx_hash}; @@ -1055,6 +1113,19 @@ mod tests { assert_eq!(is_scan_running, false); } + #[test] + fn handle_failed_transactions_does_nothing_if_no_failure_detected() { + let subject = PendingPayableScannerBuilder::new().build(); + let detected_failures = DetectedFailures { + tx_failures: vec![], + tx_receipt_rpc_failures: vec![], + }; + + subject.handle_failed_transactions(detected_failures, &Logger::new("test")) + + //mocked pending payable DAO didn't panic which means we skipped the actual process + } + #[test] fn handle_failed_transactions_can_process_standard_tx_failures() { init_test_logging(); @@ -1263,7 +1334,7 @@ mod tests { #[should_panic( expected = "Unable to update pending-tx statuses for validation failures \ '[FailedValidation { tx_hash: 0x00000000000000000000000000000000000000000000000000000000000001c8, \ - validation_failure: Local(Internal), current_status: Pending(Waiting) }]' due to \ + validation_failure: Local(Internal), current_status: Pending(Waiting) }]' due to: \ InvalidInput(\"bluh\")" )] fn update_validation_status_for_sent_txs_panics_on_update_statuses() { @@ -1286,7 +1357,7 @@ mod tests { #[should_panic( expected = "Unable to update failed-tx statuses for validation failures \ '[FailedValidation { tx_hash: 0x00000000000000000000000000000000000000000000000000000000000001c8, \ - validation_failure: Local(Internal), current_status: RecheckRequired(Waiting) }]' due to \ + validation_failure: Local(Internal), current_status: RecheckRequired(Waiting) }]' due to: \ InvalidInput(\"bluh\")" )] fn update_validation_status_for_failed_txs_panics_on_update_statuses() { @@ -1318,10 +1389,10 @@ mod tests { failed_tx_2.hash = tx_hash_2; let failed_payable_dao = FailedPayableDaoMock::default() .insert_new_records_params(&insert_new_records_params_arc) - .insert_new_records_result(Ok(())) - .update_statuses_params(&update_status_params_arc) - .update_statuses_result(Ok(())); + .insert_new_records_result(Ok(())); let sent_payable_dao = SentPayableDaoMock::default() + .update_statuses_params(&update_status_params_arc) + .update_statuses_result(Ok(())) .delete_records_params(&delete_records_params_arc) .delete_records_result(Ok(())); let subject = PendingPayableScannerBuilder::new() @@ -1349,7 +1420,7 @@ mod tests { assert_eq!( *update_statuses_params, vec![ - hashmap!(tx_hash_2 => FailureStatus::RecheckRequired(ValidationStatus::Reattempting {attempt: 1,error: AppRpcError::Local(LocalError::Internal)})) + hashmap!(tx_hash_2 => TxStatus::Pending(ValidationStatus::Reattempting {attempt: 1,error: AppRpcError::Local(LocalError::Internal)})) ] ); } @@ -1357,7 +1428,7 @@ mod tests { #[test] #[should_panic(expected = "Unable to persist failed txs \ 0x000000000000000000000000000000000000000000000000000000000000014d, \ - 0x00000000000000000000000000000000000000000000000000000000000001bc due to NoChange")] + 0x00000000000000000000000000000000000000000000000000000000000001bc due to: NoChange")] fn handle_failed_transactions_panics_when_it_fails_to_insert_failed_tx_record() { let failed_payable_dao = FailedPayableDaoMock::default() .insert_new_records_result(Err(FailedPayableDaoError::NoChange)); @@ -1384,7 +1455,7 @@ mod tests { #[test] #[should_panic(expected = "Unable to purge sent payable records for failed txs \ 0x000000000000000000000000000000000000000000000000000000000000014d, \ - 0x00000000000000000000000000000000000000000000000000000000000001bc due to \ + 0x00000000000000000000000000000000000000000000000000000000000001bc due to: \ InvalidInput(\"Booga\")")] fn handle_failed_transactions_panics_when_it_fails_to_delete_obsolete_sent_tx_records() { let failed_payable_dao = FailedPayableDaoMock::default().insert_new_records_result(Ok(())); @@ -1448,7 +1519,7 @@ mod tests { #[test] #[should_panic(expected = "Unable to conclude rechecks for failed txs \ 0x0000000000000000000000000000000000000000000000000000000000000321, \ - 0x0000000000000000000000000000000000000000000000000000000000000654 due to \ + 0x0000000000000000000000000000000000000000000000000000000000000654 due to: \ InvalidInput(\"Booga\")")] fn concluding_rechecks_fails_on_updating_statuses() { let tx_hash_1 = make_tx_hash(0x321); @@ -1471,79 +1542,221 @@ mod tests { } #[test] - fn handle_failed_transactions_does_nothing_if_no_failure_detected() { - let subject = PendingPayableScannerBuilder::new().build(); - let detected_failures = DetectedFailures { - tx_failures: vec![], - tx_receipt_rpc_failures: vec![], + fn handle_confirmed_transactions_does_nothing_if_no_confirmation_found_on_the_blockchain() { + let mut subject = PendingPayableScannerBuilder::new().build(); + + subject + .handle_confirmed_transactions(DetectedConfirmations::default(), &Logger::new("test")) + + // Mocked payable DAO didn't panic, which means we skipped the actual process + } + + #[test] + fn handle_confirmed_transactions_only_failure_reclaims_happy_path() { + init_test_logging(); + let test_name = "handle_confirmed_transactions_only_failure_reclaims_happy_path"; + let replace_records_params_arc = Arc::new(Mutex::new(vec![])); + let delete_records_params_arc = Arc::new(Mutex::new(vec![])); + let sent_payable_dao = SentPayableDaoMock::default() + .replace_records_params(&replace_records_params_arc) + .replace_records_result(Ok(())); + let failed_payable_dao = FailedPayableDaoMock::default() + .delete_records_params(&delete_records_params_arc) + .delete_records_result(Ok(())); + let logger = Logger::new(test_name); + let mut subject = PendingPayableScannerBuilder::new() + .sent_payable_dao(sent_payable_dao) + .failed_payable_dao(failed_payable_dao) + .build(); + let tx_hash_1 = make_tx_hash(0x123); + let tx_hash_2 = make_tx_hash(0x567); + let mut sent_tx_1 = make_sent_tx(123_123); + sent_tx_1.hash = tx_hash_1; + let tx_block_1 = TxBlock { + block_hash: make_block_hash(45), + block_number: 4_578_989_878_u64.into(), + }; + sent_tx_1.status = TxStatus::Confirmed { + block_hash: format!("{:?}", tx_block_1.block_hash), + block_number: tx_block_1.block_number.as_u64(), + detection: Detection::Normal, + }; + let mut sent_tx_2 = make_sent_tx(987_987); + sent_tx_2.hash = tx_hash_2; + let tx_block_2 = TxBlock { + block_hash: make_block_hash(67), + block_number: 6_789_898_789_u64.into(), + }; + sent_tx_2.status = TxStatus::Confirmed { + block_hash: format!("{:?}", make_block_hash(123)), + block_number: tx_block_2.block_number.as_u64(), + detection: Detection::Normal, }; - subject.handle_failed_transactions(detected_failures, &Logger::new("test")) + subject.handle_confirmed_transactions( + DetectedConfirmations { + normal_confirmations: vec![], + reclaims: vec![ + TxReclaim { + reclaimed: sent_tx_1.clone(), + }, + TxReclaim { + reclaimed: sent_tx_2.clone(), + }, + ], + }, + &logger, + ); - //mocked pending payable DAO didn't panic which means we skipped the actual process + let replace_records_params = replace_records_params_arc.lock().unwrap(); + assert_eq!(*replace_records_params, vec![vec![sent_tx_1, sent_tx_2]]); + let delete_records_params = delete_records_params_arc.lock().unwrap(); + assert_eq!(*delete_records_params, vec![hashset![tx_hash_1, tx_hash_2]]); + let log_handler = TestLogHandler::new(); + log_handler.exists_log_containing(&format!( + "INFO: {test_name}: Reclaimed txs 0x0000000000000000000000000000000000000000000000000000000000000123 \ + (block 4578989878), 0x0000000000000000000000000000000000000000000000000000000000000567 \ + (block 6789898789) as confirmed on-chain", + )); } #[test] #[should_panic( - expected = "Unable to update sent payable records 0x000000000000000000000000000000000000000\ - 000000000000000000000021a, 0x0000000000000000000000000000000000000000000000000000000000000315 \ - by their tx blocks due to SqlExecutionFailed(\"The database manager is \ - a funny guy, he's fooling around with us\")" + expected = "Unable to proceed in a reclaim as the replacement of sent tx records \ + 0x0000000000000000000000000000000000000000000000000000000000000123, \ + 0x0000000000000000000000000000000000000000000000000000000000000567 \ + failed due to: NoChange" )] - fn handle_confirmed_transactions_panics_while_updating_sent_payable_records_with_the_tx_blocks() - { - let payable_dao = PayableDaoMock::new().transactions_confirmed_result(Ok(())); - let sent_payable_dao = SentPayableDaoMock::default().confirm_tx_result(Err( - SentPayableDaoError::SqlExecutionFailed( - "The database manager is a funny guy, he's fooling around with us".to_string(), - ), - )); + fn failure_reclaim_fails_on_replace_sent_tx_record() { + let sent_payable_dao = SentPayableDaoMock::default() + .replace_records_result(Err(SentPayableDaoError::NoChange)); let mut subject = PendingPayableScannerBuilder::new() - .payable_dao(payable_dao) .sent_payable_dao(sent_payable_dao) .build(); - let mut sent_tx_1 = make_sent_tx(456); - let block = make_transaction_block(678); - sent_tx_1.hash = make_tx_hash(0x315); + let tx_hash_1 = make_tx_hash(0x123); + let tx_hash_2 = make_tx_hash(0x567); + let mut sent_tx_1 = make_sent_tx(123_123); + sent_tx_1.hash = tx_hash_1; + let tx_block_1 = TxBlock { + block_hash: make_block_hash(45), + block_number: 4_578_989_878_u64.into(), + }; sent_tx_1.status = TxStatus::Confirmed { - block_hash: format!("{:?}", block.block_hash), - block_number: block.block_number.as_u64(), + block_hash: format!("{:?}", tx_block_1.block_hash), + block_number: tx_block_1.block_number.as_u64(), detection: Detection::Normal, }; - let mut sent_tx_2 = make_sent_tx(789); - sent_tx_2.hash = make_tx_hash(0x21a); + let mut sent_tx_2 = make_sent_tx(987_987); + sent_tx_2.hash = tx_hash_2; + let tx_block_2 = TxBlock { + block_hash: make_block_hash(67), + block_number: 6_789_898_789_u64.into(), + }; sent_tx_2.status = TxStatus::Confirmed { - block_hash: format!("{:?}", block.block_hash), - block_number: block.block_number.as_u64(), + block_hash: format!("{:?}", make_block_hash(123)), + block_number: tx_block_2.block_number.as_u64(), detection: Detection::Normal, }; subject.handle_confirmed_transactions( DetectedConfirmations { - normal_confirmations: vec![ - NormalTxConfirmation { tx: sent_tx_1 }, - NormalTxConfirmation { tx: sent_tx_2 }, + normal_confirmations: vec![], + reclaims: vec![ + TxReclaim { + reclaimed: sent_tx_1.clone(), + }, + TxReclaim { + reclaimed: sent_tx_2.clone(), + }, ], - reclaims: vec![], }, &Logger::new("test"), ); } #[test] - fn handle_confirmed_transactions_does_nothing_if_no_confirmation_found_on_the_blockchain() { - let mut subject = PendingPayableScannerBuilder::new().build(); + #[should_panic(expected = "Unable to delete failed tx records \ + 0x0000000000000000000000000000000000000000000000000000000000000123, \ + 0x0000000000000000000000000000000000000000000000000000000000000567 \ + to finish the reclaims due to: EmptyInput")] + fn failure_reclaim_fails_on_delete_failed_tx_record() { + let sent_payable_dao = SentPayableDaoMock::default().replace_records_result(Ok(())); + let failed_payable_dao = FailedPayableDaoMock::default() + .delete_records_result(Err(FailedPayableDaoError::EmptyInput)); + let mut subject = PendingPayableScannerBuilder::new() + .sent_payable_dao(sent_payable_dao) + .failed_payable_dao(failed_payable_dao) + .build(); + let tx_hash_1 = make_tx_hash(0x123); + let tx_hash_2 = make_tx_hash(0x567); + let mut sent_tx_1 = make_sent_tx(123_123); + sent_tx_1.hash = tx_hash_1; + let tx_block_1 = TxBlock { + block_hash: make_block_hash(45), + block_number: 4_578_989_878_u64.into(), + }; + sent_tx_1.status = TxStatus::Confirmed { + block_hash: format!("{:?}", tx_block_1.block_hash), + block_number: tx_block_1.block_number.as_u64(), + detection: Detection::Normal, + }; + let mut sent_tx_2 = make_sent_tx(987_987); + sent_tx_2.hash = tx_hash_2; + let tx_block_2 = TxBlock { + block_hash: make_block_hash(67), + block_number: 6_789_898_789_u64.into(), + }; + sent_tx_2.status = TxStatus::Confirmed { + block_hash: format!("{:?}", make_block_hash(123)), + block_number: tx_block_2.block_number.as_u64(), + detection: Detection::Normal, + }; - subject - .handle_confirmed_transactions(DetectedConfirmations::default(), &Logger::new("test")) + subject.handle_confirmed_transactions( + DetectedConfirmations { + normal_confirmations: vec![], + reclaims: vec![ + TxReclaim { + reclaimed: sent_tx_1.clone(), + }, + TxReclaim { + reclaimed: sent_tx_2.clone(), + }, + ], + }, + &Logger::new("test"), + ); + } - // Mocked payable DAO didn't panic, which means we skipped the actual process + #[test] + #[should_panic( + expected = "internal error: entered unreachable code: Processing a reclaim for \ + tx 0x0000000000000000000000000000000000000000000000000000000000000123 which isn't filled with \ + the confirmation details" + )] + fn handle_failure_reclaim_meets_a_record_without_confirmation_details() { + let mut subject = PendingPayableScannerBuilder::new().build(); + let tx_hash = make_tx_hash(0x123); + let mut sent_tx = make_sent_tx(123_123); + sent_tx.hash = tx_hash; + // Here, it should be confirmed already in this status + sent_tx.status = TxStatus::Pending(ValidationStatus::Waiting); + + subject.handle_confirmed_transactions( + DetectedConfirmations { + normal_confirmations: vec![], + reclaims: vec![TxReclaim { + reclaimed: sent_tx.clone(), + }], + }, + &Logger::new("test"), + ); } #[test] - fn handle_confirmed_transactions_works() { + fn handle_confirmed_transactions_mixed_confirmations_work() { init_test_logging(); - let test_name = "handle_confirmed_transactions_works"; + let test_name = "handle_confirmed_transactions_mixed_confirmations_work"; let transactions_confirmed_params_arc = Arc::new(Mutex::new(vec![])); let confirm_tx_params_arc = Arc::new(Mutex::new(vec![])); let replace_records_params_arc = Arc::new(Mutex::new(vec![])); @@ -1570,7 +1783,7 @@ mod tests { let tx_hash_3 = make_tx_hash(0x913); let mut sent_tx_1 = make_sent_tx(123_123); sent_tx_1.hash = tx_hash_1; - let tx_block_1 = TransactionBlock { + let tx_block_1 = TxBlock { block_hash: make_block_hash(45), block_number: 4_578_989_878_u64.into(), }; @@ -1581,7 +1794,7 @@ mod tests { }; let mut sent_tx_2 = make_sent_tx(987_987); sent_tx_2.hash = tx_hash_2; - let tx_block_2 = TransactionBlock { + let tx_block_2 = TxBlock { block_hash: make_block_hash(67), block_number: 6_789_898_789_u64.into(), }; @@ -1592,7 +1805,7 @@ mod tests { }; let mut sent_tx_3 = make_sent_tx(567_567); sent_tx_3.hash = tx_hash_3; - let tx_block_3 = TransactionBlock { + let tx_block_3 = TxBlock { block_hash: make_block_hash(78), block_number: 7_898_989_878_u64.into(), }; @@ -1641,10 +1854,57 @@ mod tests { )); } + #[test] + #[should_panic( + expected = "Unable to update sent payable records 0x000000000000000000000000000000000000000\ + 000000000000000000000021a, 0x0000000000000000000000000000000000000000000000000000000000000315 \ + by their tx blocks due to: SqlExecutionFailed(\"The database manager is \ + a funny guy, he's fooling around with us\")" + )] + fn handle_confirmed_transactions_panics_while_updating_sent_payable_records_with_the_tx_blocks() + { + let payable_dao = PayableDaoMock::new().transactions_confirmed_result(Ok(())); + let sent_payable_dao = SentPayableDaoMock::default().confirm_tx_result(Err( + SentPayableDaoError::SqlExecutionFailed( + "The database manager is a funny guy, he's fooling around with us".to_string(), + ), + )); + let mut subject = PendingPayableScannerBuilder::new() + .payable_dao(payable_dao) + .sent_payable_dao(sent_payable_dao) + .build(); + let mut sent_tx_1 = make_sent_tx(456); + let block = make_transaction_block(678); + sent_tx_1.hash = make_tx_hash(0x315); + sent_tx_1.status = TxStatus::Confirmed { + block_hash: format!("{:?}", block.block_hash), + block_number: block.block_number.as_u64(), + detection: Detection::Normal, + }; + let mut sent_tx_2 = make_sent_tx(789); + sent_tx_2.hash = make_tx_hash(0x21a); + sent_tx_2.status = TxStatus::Confirmed { + block_hash: format!("{:?}", block.block_hash), + block_number: block.block_number.as_u64(), + detection: Detection::Normal, + }; + + subject.handle_confirmed_transactions( + DetectedConfirmations { + normal_confirmations: vec![ + NormalTxConfirmation { tx: sent_tx_1 }, + NormalTxConfirmation { tx: sent_tx_2 }, + ], + reclaims: vec![], + }, + &Logger::new("test"), + ); + } + #[test] #[should_panic( expected = "Unable to complete the tx confirmation by the adjustment of the payable accounts \ - 0x000000000000000000000077616c6c6574343536 due to \ + 0x000000000000000000000077616c6c6574343536 due to: \ RusqliteError(\"record change not successful\")" )] fn handle_confirmed_transactions_panics_on_unchecking_payable_table() { 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 6105b6284..a6706c1f1 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 @@ -12,8 +12,7 @@ use crate::accountant::scanners::pending_payable_scanner::utils::{ use crate::accountant::scanners::pending_payable_scanner::PendingPayableScanner; use crate::accountant::TxReceiptsMessage; use crate::blockchain::blockchain_interface::data_structures::{ - BlockchainTxFailure, StatusReadFromReceiptCheck, TransactionBlock, TxReceiptError, - TxReceiptResult, + BlockchainTxFailure, StatusReadFromReceiptCheck, TxBlock, TxReceiptError, TxReceiptResult, }; use masq_lib::logger::Logger; use std::time::SystemTime; @@ -115,7 +114,7 @@ impl TxReceiptInterpreter { fn handle_tx_confirmation( mut scan_report: ReceiptScanReport, tx: TxByTable, - tx_block: TransactionBlock, + tx_block: TxBlock, logger: &Logger, ) -> ReceiptScanReport { match tx { diff --git a/node/src/accountant/scanners/pending_payable_scanner/utils.rs b/node/src/accountant/scanners/pending_payable_scanner/utils.rs index 27d7a8ef6..c882ce092 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/utils.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/utils.rs @@ -8,7 +8,7 @@ use crate::accountant::db_access_objects::sent_payable_dao::{ }; use crate::accountant::db_access_objects::utils::{from_unix_timestamp, TxHash}; use crate::blockchain::blockchain_interface::data_structures::{ - BlockchainTxFailure, TransactionBlock, TxReceiptError, TxReceiptResult, + BlockchainTxFailure, TxBlock, TxReceiptError, TxReceiptResult, }; use crate::blockchain::errors::AppRpcError; use actix::Message; @@ -361,8 +361,8 @@ mod tests { use crate::accountant::scanners::pending_payable_scanner::utils::{ CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailedValidation, FailedValidationByTable, NormalTxConfirmation, PendingPayableCache, PresortedTxFailure, - ReceiptScanReport, RecheckRequiringFailures, Retry, TransactionBlock, TxHashByTable, - TxReceiptError, TxReceiptResult, TxReclaim, + ReceiptScanReport, RecheckRequiringFailures, Retry, TxBlock, TxHashByTable, TxReceiptError, + TxReceiptResult, TxReclaim, }; use crate::accountant::test_utils::{make_failed_tx, make_sent_tx, make_transaction_block}; use crate::blockchain::errors::{AppRpcError, LocalError, RemoteError}; diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs index 49fec7ccc..0f3f779e5 100644 --- a/node/src/accountant/test_utils.rs +++ b/node/src/accountant/test_utils.rs @@ -35,9 +35,7 @@ use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableT use crate::accountant::scanners::test_utils::PendingPayableCacheMock; use crate::accountant::scanners::PayableScanner; use crate::accountant::{gwei_to_wei, Accountant, DEFAULT_PENDING_TOO_LONG_SEC}; -use crate::blockchain::blockchain_interface::data_structures::{ - BlockchainTransaction, TransactionBlock, -}; +use crate::blockchain::blockchain_interface::data_structures::{BlockchainTransaction, TxBlock}; use crate::blockchain::test_utils::{make_block_hash, make_tx_hash}; use crate::bootstrapper::BootstrapperConfig; use crate::database::rusqlite_wrappers::TransactionSafeWrapper; @@ -129,8 +127,8 @@ pub fn make_failed_tx(num: u64) -> FailedTx { } } -pub fn make_transaction_block(num: u64) -> TransactionBlock { - TransactionBlock { +pub fn make_transaction_block(num: u64) -> TxBlock { + TxBlock { block_hash: make_block_hash(num as u32), block_number: U64::from(num * num * num), } @@ -989,7 +987,7 @@ pub struct SentPayableDaoMock { insert_new_records_results: RefCell>>, retrieve_txs_params: Arc>>>, retrieve_txs_results: RefCell>>, - confirm_tx_params: Arc>>>, + confirm_tx_params: Arc>>>, confirm_tx_results: RefCell>>, update_statuses_params: Arc>>>, update_statuses_results: RefCell>>, @@ -1018,10 +1016,7 @@ impl SentPayableDao for SentPayableDaoMock { self.retrieve_txs_params.lock().unwrap().push(condition); self.retrieve_txs_results.borrow_mut().remove(0) } - fn confirm_tx( - &self, - hash_map: &HashMap, - ) -> Result<(), SentPayableDaoError> { + fn confirm_tx(&self, hash_map: &HashMap) -> Result<(), SentPayableDaoError> { self.confirm_tx_params .lock() .unwrap() @@ -1094,10 +1089,7 @@ impl SentPayableDaoMock { self } - pub fn confirm_tx_params( - mut self, - params: &Arc>>>, - ) -> Self { + pub fn confirm_tx_params(mut self, params: &Arc>>>) -> Self { self.confirm_tx_params = params.clone(); self } diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index 2c3f4d130..33c07e732 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -550,8 +550,8 @@ mod tests { }; use crate::blockchain::blockchain_interface::data_structures::ProcessedPayableFallible::Correct; use crate::blockchain::blockchain_interface::data_structures::{ - BlockchainTransaction, RetrievedBlockchainTransactions, RetrievedTxStatus, - TransactionBlock, TxReceiptError, + BlockchainTransaction, RetrievedBlockchainTransactions, RetrievedTxStatus, TxBlock, + TxReceiptError, }; use crate::blockchain::errors::{AppRpcError, RemoteError}; use crate::blockchain::test_utils::{ @@ -1352,7 +1352,7 @@ mod tests { TxReceiptsMessage { results: vec![ TxReceiptResult(Ok(RetrievedTxStatus::new(TxHashByTable::SentPayable(tx_hash_1), StatusReadFromReceiptCheck::Pending))), - TxReceiptResult(Ok(RetrievedTxStatus::new(TxHashByTable::SentPayable(tx_hash_2), StatusReadFromReceiptCheck::Succeeded(TransactionBlock { + TxReceiptResult(Ok(RetrievedTxStatus::new(TxHashByTable::SentPayable(tx_hash_2), StatusReadFromReceiptCheck::Succeeded(TxBlock { block_hash: Default::default(), block_number, })))), diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs index 833ac3cbd..6bff9940a 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs @@ -5,7 +5,7 @@ use crate::blockchain::blockchain_interface::blockchain_interface_web3::CONTRACT use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainError; use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainError::QueryFailed; use crate::blockchain::blockchain_interface::data_structures::{ - BlockchainTxFailure, StatusReadFromReceiptCheck, TransactionBlock, + BlockchainTxFailure, StatusReadFromReceiptCheck, TxBlock, }; use crate::blockchain::blockchain_interface::lower_level_interface::LowBlockchainInt; use crate::blockchain::errors::AppRpcError; @@ -25,7 +25,7 @@ impl From for StatusReadFromReceiptCheck { fn from(receipt: TransactionReceipt) -> Self { match (receipt.status, receipt.block_hash, receipt.block_number) { (Some(status), Some(block_hash), Some(block_number)) if status == U64::from(1) => { - StatusReadFromReceiptCheck::Succeeded(TransactionBlock { + StatusReadFromReceiptCheck::Succeeded(TxBlock { block_hash, block_number, }) diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs index 86f07e5e6..6e70adc9b 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs @@ -468,7 +468,7 @@ mod tests { }; use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainError::QueryFailed; use crate::blockchain::blockchain_interface::data_structures::{ - BlockchainTransaction, BlockchainTxFailure, TransactionBlock, + BlockchainTransaction, BlockchainTxFailure, TxBlock, }; use crate::blockchain::blockchain_interface::{ BlockchainAgentBuildError, BlockchainError, BlockchainInterface, @@ -1177,7 +1177,7 @@ mod tests { result[5], TxReceiptResult(Ok(RetrievedTxStatus::new( tx_hbt_6, - StatusReadFromReceiptCheck::Succeeded(TransactionBlock { + StatusReadFromReceiptCheck::Succeeded(TxBlock { block_hash, block_number, }), diff --git a/node/src/blockchain/blockchain_interface/data_structures/mod.rs b/node/src/blockchain/blockchain_interface/data_structures/mod.rs index fd84a5832..7fbc72aeb 100644 --- a/node/src/blockchain/blockchain_interface/data_structures/mod.rs +++ b/node/src/blockchain/blockchain_interface/data_structures/mod.rs @@ -80,7 +80,7 @@ impl RetrievedTxStatus { #[derive(Debug, PartialEq, Eq, Clone)] pub enum StatusReadFromReceiptCheck { Failed(BlockchainTxFailure), - Succeeded(TransactionBlock), + Succeeded(TxBlock), Pending, } @@ -128,7 +128,7 @@ impl TxReceiptError { } #[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Ord, PartialOrd, Serialize, Deserialize)] -pub struct TransactionBlock { +pub struct TxBlock { pub block_hash: H256, pub block_number: U64, } @@ -139,7 +139,7 @@ mod tests { use crate::accountant::test_utils::make_transaction_block; use crate::assert_on_testing_enum_with_all_its_variants; use crate::blockchain::blockchain_interface::data_structures::{ - BlockchainTxFailure, RetrievedTxStatus, StatusReadFromReceiptCheck, TransactionBlock, + BlockchainTxFailure, RetrievedTxStatus, StatusReadFromReceiptCheck, TxBlock, TxReceiptError, TxReceiptResult, }; use crate::blockchain::errors::{AppRpcError, LocalError, RemoteError}; @@ -161,7 +161,7 @@ mod tests { // Test Succeeded let block_number = U64::from(12345); let block_hash = H256::from_low_u64_be(0xabcdef); - let succeeded = StatusReadFromReceiptCheck::Succeeded(TransactionBlock { + let succeeded = StatusReadFromReceiptCheck::Succeeded(TxBlock { block_hash, block_number, }); From a01a1b5c835101b7cd1e4bff2d5e3f6a6cd07a1e Mon Sep 17 00:00:00 2001 From: Bert Date: Mon, 11 Aug 2025 20:08:18 +0200 Subject: [PATCH 33/61] GH-642: finished the brain functions in PPS --- .../db_access_objects/sent_payable_dao.rs | 12 +- .../scanners/pending_payable_scanner/mod.rs | 440 ++++++++++-------- node/src/accountant/test_utils.rs | 2 +- 3 files changed, 257 insertions(+), 197 deletions(-) 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 d2ffbd9f7..e55846d49 100644 --- a/node/src/accountant/db_access_objects/sent_payable_dao.rs +++ b/node/src/accountant/db_access_objects/sent_payable_dao.rs @@ -124,7 +124,7 @@ pub trait SentPayableDao { fn insert_new_records(&self, txs: &[SentTx]) -> Result<(), SentPayableDaoError>; fn retrieve_txs(&self, condition: Option) -> Vec; //TODO potentially atomically - fn confirm_tx(&self, hash_map: &HashMap) -> Result<(), SentPayableDaoError>; + fn confirm_txs(&self, hash_map: &HashMap) -> Result<(), SentPayableDaoError>; fn replace_records(&self, new_txs: &[SentTx]) -> Result<(), SentPayableDaoError>; fn update_statuses( &self, @@ -287,7 +287,7 @@ impl SentPayableDao for SentPayableDaoReal<'_> { .collect() } - fn confirm_tx(&self, hash_map: &HashMap) -> Result<(), SentPayableDaoError> { + fn confirm_txs(&self, hash_map: &HashMap) -> Result<(), SentPayableDaoError> { if hash_map.is_empty() { return Err(SentPayableDaoError::EmptyInput); } @@ -829,7 +829,7 @@ mod tests { (tx2.hash, confirmed_tx_block_2.clone()), ]); - let result = subject.confirm_tx(&hash_map); + let result = subject.confirm_txs(&hash_map); let updated_txs = subject.retrieve_txs(Some(ByHash(vec![tx1.hash, tx2.hash]))); assert_eq!(result, Ok(())); @@ -874,7 +874,7 @@ mod tests { subject.insert_new_records(&vec![tx]).unwrap(); let hash_map = HashMap::new(); - let result = subject.confirm_tx(&hash_map); + let result = subject.confirm_txs(&hash_map); assert_eq!(result, Err(SentPayableDaoError::EmptyInput)); } @@ -910,7 +910,7 @@ mod tests { ), ]); - let result = subject.confirm_tx(&hash_map); + let result = subject.confirm_txs(&hash_map); assert_eq!( result, @@ -938,7 +938,7 @@ mod tests { }, )]); - let result = subject.confirm_tx(&hash_map); + let result = subject.confirm_txs(&hash_map); assert_eq!( result, diff --git a/node/src/accountant/scanners/pending_payable_scanner/mod.rs b/node/src/accountant/scanners/pending_payable_scanner/mod.rs index 1982912d4..c6756dac1 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/mod.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/mod.rs @@ -4,7 +4,7 @@ mod tx_receipt_interpreter; pub mod utils; use crate::accountant::db_access_objects::failed_payable_dao::{ - FailedPayableDao, FailedPayableDaoError, FailedTx, FailureRetrieveCondition, FailureStatus, + FailedPayableDao, FailedTx, FailureRetrieveCondition, FailureStatus, }; use crate::accountant::db_access_objects::payable_dao::{PayableDao, PayableDaoError}; use crate::accountant::db_access_objects::sent_payable_dao::{ @@ -22,12 +22,10 @@ use crate::accountant::scanners::{ PrivateScanner, Scanner, ScannerCommon, StartScanError, StartableScanner, }; use crate::accountant::{ - comma_joined_stringifiable, PendingPayableId, RequestTransactionReceipts, ResponseSkeleton, + comma_joined_stringifiable, RequestTransactionReceipts, ResponseSkeleton, ScanForPendingPayables, TxReceiptsMessage, }; -use crate::blockchain::blockchain_interface::data_structures::{ - StatusReadFromReceiptCheck, TxBlock, TxReceiptResult, -}; +use crate::blockchain::blockchain_interface::data_structures::{TxBlock, TxReceiptResult}; use crate::sub_lib::accountant::{FinancialStatistics, PaymentThresholds}; use crate::sub_lib::wallet::Wallet; use crate::time_marking_methods; @@ -39,8 +37,10 @@ use std::cell::RefCell; use std::collections::{HashMap, HashSet}; use std::fmt::Display; use std::rc::Rc; +use std::str::FromStr; use std::time::SystemTime; use thousands::Separable; +use web3::types::H256; pub struct PendingPayableScanner { pub common: ScannerCommon, @@ -199,7 +199,10 @@ impl PendingPayableScanner { fn emptiness_check(&self, msg: &TxReceiptsMessage) { if msg.results.is_empty() { - unreachable!("We should never receive an empty list of results. Even missing receipts can be interpreted") + unreachable!( + "We should never receive an empty list of results. \ + Even missing receipts can be interpreted" + ) } } @@ -344,52 +347,39 @@ impl PendingPayableScanner { self.handle_normal_confirmations(confirmed_txs.normal_confirmations, logger); } - fn handle_tx_failure_reclaims(&self, reclaimed: Vec, logger: &Logger) { - fn collect_hashes_and_blocks(reclaimed: &[TxReclaim]) -> Vec<(TxHash, u64)> { - reclaimed - .iter() - .map(|reclaim| { - let tx_block_num = if let TxStatus::Confirmed { block_number, .. } = - reclaim.reclaimed.status - { - block_number - } else { - unreachable!( - "Processing a reclaim for tx {:?} which isn't filled with \ - the confirmation details", - reclaim.reclaimed.hash - ) - }; - (reclaim.reclaimed.hash, tx_block_num) - }) - .collect() - } - fn isolate_hashes(reclaimed: &[(TxHash, u64)]) -> HashSet { - reclaimed.iter().map(|(tx_hash, _)| *tx_hash).collect() - } - fn prepare_sent_tx_records(reclaimed: Vec) -> Vec { - reclaimed + fn handle_tx_failure_reclaims(&mut self, reclaimed: Vec, logger: &Logger) { + fn unwrap_into_sent_tx(confirmed_txs: Vec) -> Vec { + confirmed_txs .into_iter() .map(|reclaim| reclaim.reclaimed) .collect() } + fn isolate_hashes(reclaimed: &[(TxHash, TxBlock)]) -> HashSet { + reclaimed.iter().map(|(tx_hash, _)| *tx_hash).collect() + } if reclaimed.is_empty() { return; } - let hashes_and_blocks = collect_hashes_and_blocks(&reclaimed); - match self - .sent_payable_dao - .replace_records(&prepare_sent_tx_records(reclaimed)) - { + let sent_txs_to_reclaim = unwrap_into_sent_tx(reclaimed); + let hashes_and_blocks = Self::collect_hashes_and_blocks(&sent_txs_to_reclaim) + .into_iter() + .sorted() + .collect_vec(); + match self.sent_payable_dao.replace_records(&sent_txs_to_reclaim) { Ok(_) => { debug!(logger, "Replaced records for txs being reclaimed") } Err(e) => { - panic!("Unable to proceed in a reclaim as the replacement of sent tx records {} failed due to: {:?}", comma_joined_stringifiable(&hashes_and_blocks, |(tx_hash,_)|{ - format!("{:?}", tx_hash) - }), e) + panic!( + "Unable to proceed in a reclaim as the replacement of sent tx records \ + {} failed due to: {:?}", + comma_joined_stringifiable(&hashes_and_blocks, |(tx_hash, _)| { + format!("{:?}", tx_hash) + }), + e + ) } } @@ -401,8 +391,8 @@ impl PendingPayableScanner { info!( logger, "Reclaimed txs {} as confirmed on-chain", - comma_joined_stringifiable(&hashes_and_blocks, |(tx_hash, block_number)| { - format!("{:?} (block {})", tx_hash, block_number) + comma_joined_stringifiable(&hashes_and_blocks, |(tx_hash, tx_block)| { + format!("{:?} (block {})", tx_hash, tx_block.block_number) }) ) } @@ -416,64 +406,89 @@ impl PendingPayableScanner { ) } } + + self.add_to_the_total_of_paid_payable(&sent_txs_to_reclaim, logger) + } + + fn collect_hashes_and_blocks(reclaimed: &[SentTx]) -> HashMap { + reclaimed + .iter() + .map(|reclaim| { + let tx_block = if let TxStatus::Confirmed { block_hash, block_number, .. } = + &reclaim.status + { + TxBlock{ + block_hash: H256::from_str(&block_hash[2..]).expect("Failed to construct hash from str"), + block_number: (*block_number).into() + } + } else { + unreachable!( + "Processing a reclaim for tx {:?} which isn't filled with the confirmation details", + reclaim.hash + ) + }; + (reclaim.hash, tx_block) + }) + .collect() } fn handle_normal_confirmations( - &self, + &mut self, confirmed_txs: Vec, logger: &Logger, ) { + fn unwrap_into_sent_tx(confirmed_txs: Vec) -> Vec { + confirmed_txs + .into_iter() + .map(|normal_tx_confirmation| normal_tx_confirmation.tx) + .collect() + } + fn transaction_confirmed_panic(confirmed_txs: &[SentTx], e: PayableDaoError) -> ! { + panic!( + "Unable to complete the tx confirmation by the adjustment of the payable \ + accounts {} due to: {:?}", + comma_joined_stringifiable( + &confirmed_txs + .iter() + .map(|tx| tx.receiver_address) + .collect_vec(), + |wallet| format!("{:?}", wallet) + ), + e + ) + } + fn update_tx_blocks_panic( + tx_hashes_and_tx_blocks: &HashMap, + e: SentPayableDaoError, + ) -> ! { + panic!( + "Unable to update sent payable records {} by their tx blocks due to: {:?}", + comma_joined_stringifiable( + &tx_hashes_and_tx_blocks.keys().sorted().collect_vec(), + |tx_hash| format!("{:?}", tx_hash) + ), + e + ) + } + if confirmed_txs.is_empty() { return; } - todo!() - // if !confirmed_txs.is_empty() { - // if let Err(e) = self.payable_dao.transactions_confirmed(&confirmed_txs) { - // Self::transaction_confirmed_panic(&confirmed_txs, e) - // } else { - // self.add_to_the_total_of_paid_payable(&confirmed_txs, logger); - // - // let tx_confirmations = Self::compose_tx_confirmation_inputs(&confirmed_txs); - // - // if let Err(e) = self.sent_payable_dao.confirm_tx(&tx_confirmations) { - // Self::update_tx_blocks_panic(&tx_confirmations, e) - // } else { - // Self::log_tx_success(logger, &tx_confirmations); - // } - // } - // } - } - - fn compose_tx_confirmation_inputs(confirmed_txs: &[SentTx]) -> HashMap { - todo!() - } + let confirmed_sent_txs = unwrap_into_sent_tx(confirmed_txs); + if let Err(e) = self.payable_dao.transactions_confirmed(&confirmed_sent_txs) { + transaction_confirmed_panic(&confirmed_sent_txs, e) + } else { + let tx_confirmations = Self::collect_hashes_and_blocks(&confirmed_sent_txs); - fn transaction_confirmed_panic(confirmed_txs: &[SentTx], e: PayableDaoError) -> ! { - let wallets = confirmed_txs - .iter() - .map(|tx| tx.receiver_address) - .collect_vec(); - panic!( - "Unable to complete the tx confirmation by the adjustment of the payable accounts {} \ - due to: {:?}", - comma_joined_stringifiable(&wallets, |wallet| format!("{:?}", wallet)), - e - ) - } + if let Err(e) = self.sent_payable_dao.confirm_txs(&tx_confirmations) { + update_tx_blocks_panic(&tx_confirmations, e) + } else { + Self::log_tx_success(logger, &tx_confirmations); + } - fn update_tx_blocks_panic( - tx_hashes_and_tx_blocks: &HashMap, - e: SentPayableDaoError, - ) -> ! { - panic!( - "Unable to update sent payable records {} by their tx blocks due to: {:?}", - comma_joined_stringifiable( - &tx_hashes_and_tx_blocks.keys().sorted().collect_vec(), - |tx_hash| format!("{:?}", tx_hash) - ), - e - ) + self.add_to_the_total_of_paid_payable(&confirmed_sent_txs, logger); + } } fn log_tx_success(logger: &Logger, tx_hashes_and_tx_blocks: &HashMap) { @@ -486,8 +501,8 @@ impl PendingPayableScanner { }) .join(", "); match tx_hashes_and_tx_blocks.len() { - 1 => format!("Tx {} has been confirmed", pretty_pairs), - _ => format!("Txs {} have been confirmed", pretty_pairs), + 1 => format!("Tx {} was confirmed", pretty_pairs), + _ => format!("Txs {} were confirmed", pretty_pairs), } }); } @@ -661,8 +676,8 @@ impl PendingPayableScanner { Ok(_) => { info!( logger, - "Pending-tx statuses were processed in the db for validation \ - failure of txs {}", + "Pending-tx statuses were processed in the db for validation failure \ + of txs {}", comma_joined_stringifiable(&sent_payable_failures, |failure| { format!("{:?}", failure.tx_hash) }) @@ -693,8 +708,8 @@ impl PendingPayableScanner { Ok(_) => { info!( logger, - "Failed-tx statuses were processed in the db for validation \ - failure of txs {}", + "Failed-tx statuses were processed in the db for validation failure \ + of txs {}", comma_joined_stringifiable( &failed_txs_validation_failures, |failure| { format!("{:?}", failure.tx_hash) } @@ -765,17 +780,17 @@ impl PendingPayableScanner { #[cfg(test)] mod tests { use crate::accountant::db_access_objects::failed_payable_dao::{ - FailedPayableDaoError, FailedTx, FailureReason, FailureStatus, ValidationStatus, + FailedPayableDaoError, FailureStatus, ValidationStatus, }; use crate::accountant::db_access_objects::payable_dao::PayableDaoError; use crate::accountant::db_access_objects::sent_payable_dao::{ - Detection, RetrieveCondition, SentPayableDaoError, SentTx, TxStatus, + Detection, SentPayableDaoError, TxStatus, }; use crate::accountant::scanners::pending_payable_scanner::utils::{ CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailedValidation, FailedValidationByTable, NormalTxConfirmation, PendingPayableCache, - PendingPayableScanResult, PresortedTxFailure, ReceiptScanReport, RecheckRequiringFailures, - Retry, TxHashByTable, TxReclaim, + PendingPayableScanResult, PresortedTxFailure, RecheckRequiringFailures, Retry, + TxHashByTable, TxReclaim, }; use crate::accountant::scanners::pending_payable_scanner::PendingPayableScanner; use crate::accountant::scanners::test_utils::PendingPayableCacheMock; @@ -794,7 +809,6 @@ mod tests { use masq_lib::logger::Logger; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; use regex::Regex; - use std::fmt::format; use std::panic::{catch_unwind, AssertUnwindSafe}; use std::sync::{Arc, Mutex}; use std::time::SystemTime; @@ -860,17 +874,11 @@ mod tests { } #[test] - fn finish_scan_operates_caches_and_clear_them_after_use() { - let pending_payable_ensure_empty_cache_params_arc = Arc::new(Mutex::new(vec![])); - let failed_payable_ensure_empty_cache_params_arc = Arc::new(Mutex::new(vec![])); - // To confirm a fresh, pending tx - let confirm_tx_params_arc = Arc::new(Mutex::new(vec![])); - // To reclaim a confirmed tx believed having been a failure - let replace_records_params_arc = Arc::new(Mutex::new(vec![])); - // To register a failed tx as it's pending too long - let insert_new_failed_records_params_arc = Arc::new(Mutex::new(vec![])); - // To update the validation status at a tx whose receipt couldn't be fetched - let update_statuses_params_arc = Arc::new(Mutex::new(vec![])); + fn finish_scan_operates_caches_and_clears_them_after_use() { + let get_record_by_hash_failed_payable_cache_params_arc = Arc::new(Mutex::new(vec![])); + let get_record_by_hash_sent_payable_cache_params_arc = Arc::new(Mutex::new(vec![])); + let ensure_empty_cache_failed_payable_params_arc = Arc::new(Mutex::new(vec![])); + let ensure_empty_cache_sent_payable_params_arc = Arc::new(Mutex::new(vec![])); let sent_tx_1 = make_sent_tx(456); let sent_tx_hash_1 = sent_tx_1.hash; let sent_tx_2 = make_sent_tx(789); @@ -879,21 +887,28 @@ mod tests { let failed_tx_hash_1 = failed_tx_1.hash; let failed_tx_2 = make_failed_tx(890); let failed_tx_hash_2 = failed_tx_2.hash; + let payable_dao = PayableDaoMock::new().transactions_confirmed_result(Ok(())); let sent_payable_dao = SentPayableDaoMock::new() - .confirm_tx_params(&confirm_tx_params_arc) - .replace_records_params(&replace_records_params_arc); + .confirm_tx_result(Ok(())) + .replace_records_result(Ok(())) //TODO needed?? + .delete_records_result(Ok(())); let failed_payable_dao = FailedPayableDaoMock::new() - .insert_new_records_params(&insert_new_failed_records_params_arc) - .update_statuses_params(&update_statuses_params_arc); + .insert_new_records_result(Ok(())) + .delete_records_result(Ok(())); //TODO Needed? let pending_payable_cache = PendingPayableCacheMock::default() + .get_record_by_hash_params(&get_record_by_hash_sent_payable_cache_params_arc) .get_record_by_hash_result(Some(sent_tx_1.clone())) .get_record_by_hash_result(Some(sent_tx_2)) - .ensure_empty_cache_params(&pending_payable_ensure_empty_cache_params_arc); + .ensure_empty_cache_params(&ensure_empty_cache_sent_payable_params_arc); let failed_payable_cache = PendingPayableCacheMock::default() + .get_record_by_hash_params(&get_record_by_hash_failed_payable_cache_params_arc) .get_record_by_hash_result(Some(failed_tx_1)) .get_record_by_hash_result(Some(failed_tx_2)) - .ensure_empty_cache_params(&failed_payable_ensure_empty_cache_params_arc); + .ensure_empty_cache_params(&ensure_empty_cache_failed_payable_params_arc); let mut subject = PendingPayableScannerBuilder::new() + .payable_dao(payable_dao) + .sent_payable_dao(sent_payable_dao) + .failed_payable_dao(failed_payable_dao) .pending_payables_cache(Box::new(pending_payable_cache)) .failed_payables_cache(Box::new(failed_payable_cache)) .build(); @@ -928,46 +943,28 @@ mod tests { result, PendingPayableScanResult::PaymentRetryRequired(Retry::RetryPayments) ); - assert!( - subject.current_sent_payables.dump_cache().is_empty(), - "Sent payable cache should have been emptied but {:?}", - subject.current_sent_payables.dump_cache() - ); - assert!( - subject.yet_unproven_failed_payables.dump_cache().is_empty(), - "Failed payable cache Should have been emptied but {:?}", - subject.yet_unproven_failed_payables.dump_cache() - ); - let pending_payable_ensure_empty_cache_params = - pending_payable_ensure_empty_cache_params_arc + let get_record_by_hash_failed_payable_cache_params = + get_record_by_hash_failed_payable_cache_params_arc .lock() .unwrap(); - assert_eq!(*pending_payable_ensure_empty_cache_params, vec![()]); - let failed_payable_ensure_empty_cache_params = - failed_payable_ensure_empty_cache_params_arc.lock().unwrap(); - assert_eq!(*failed_payable_ensure_empty_cache_params, vec![()]); - let confirm_tx_params = confirm_tx_params_arc.lock().unwrap(); assert_eq!( - *confirm_tx_params, - vec![hashmap!(sent_tx_hash_1 => confirmed_tx_block_sent_tx)] + *get_record_by_hash_failed_payable_cache_params, + vec![failed_tx_hash_1, failed_tx_hash_2] ); - let insert_new_failed_records_params = insert_new_failed_records_params_arc.lock().unwrap(); + let get_record_by_hash_sent_payable_cache_params = + get_record_by_hash_sent_payable_cache_params_arc + .lock() + .unwrap(); assert_eq!( - *insert_new_failed_records_params, - vec![vec![FailedTx::from(( - sent_tx_1, - FailureReason::PendingTooLong - ))]] + *get_record_by_hash_sent_payable_cache_params, + vec![sent_tx_hash_1, sent_tx_hash_2] ); - let update_statuses_params = update_statuses_params_arc.lock().unwrap(); - assert_eq!( - *update_statuses_params, - vec![ - hashmap!(failed_tx_hash_1 => FailureStatus::RecheckRequired(ValidationStatus::Reattempting { attempt: 1, error: AppRpcError::Local(LocalError::Internal)})) - ] - ); - let replace_records_params = replace_records_params_arc.lock().unwrap(); - assert_eq!(*replace_records_params, vec![vec![]]); + let pending_payable_ensure_empty_cache_params = + ensure_empty_cache_sent_payable_params_arc.lock().unwrap(); + assert_eq!(*pending_payable_ensure_empty_cache_params, vec![()]); + let failed_payable_ensure_empty_cache_params = + ensure_empty_cache_failed_payable_params_arc.lock().unwrap(); + assert_eq!(*failed_payable_ensure_empty_cache_params, vec![()]); } #[test] @@ -1552,9 +1549,9 @@ mod tests { } #[test] - fn handle_confirmed_transactions_only_failure_reclaims_happy_path() { + fn handles_failure_reclaims_alone() { init_test_logging(); - let test_name = "handle_confirmed_transactions_only_failure_reclaims_happy_path"; + let test_name = "handles_failure_reclaims_alone"; let replace_records_params_arc = Arc::new(Mutex::new(vec![])); let delete_records_params_arc = Arc::new(Mutex::new(vec![])); let sent_payable_dao = SentPayableDaoMock::default() @@ -1754,33 +1751,24 @@ mod tests { } #[test] - fn handle_confirmed_transactions_mixed_confirmations_work() { + fn handles_normal_confirmations_alone() { init_test_logging(); - let test_name = "handle_confirmed_transactions_mixed_confirmations_work"; + let test_name = "handles_normal_confirmations_alone"; let transactions_confirmed_params_arc = Arc::new(Mutex::new(vec![])); let confirm_tx_params_arc = Arc::new(Mutex::new(vec![])); - let replace_records_params_arc = Arc::new(Mutex::new(vec![])); - let delete_records_params_arc = Arc::new(Mutex::new(vec![])); let payable_dao = PayableDaoMock::default() .transactions_confirmed_params(&transactions_confirmed_params_arc) .transactions_confirmed_result(Ok(())); let sent_payable_dao = SentPayableDaoMock::default() .confirm_tx_params(&confirm_tx_params_arc) - .confirm_tx_result(Ok(())) - .replace_records_params(&replace_records_params_arc) - .replace_records_result(Ok(())); - let failed_payable_dao = FailedPayableDaoMock::default() - .delete_records_params(&delete_records_params_arc) - .delete_records_result(Ok(())); + .confirm_tx_result(Ok(())); let logger = Logger::new(test_name); let mut subject = PendingPayableScannerBuilder::new() .payable_dao(payable_dao) .sent_payable_dao(sent_payable_dao) - .failed_payable_dao(failed_payable_dao) .build(); let tx_hash_1 = make_tx_hash(0x123); let tx_hash_2 = make_tx_hash(0x567); - let tx_hash_3 = make_tx_hash(0x913); let mut sent_tx_1 = make_sent_tx(123_123); sent_tx_1.hash = tx_hash_1; let tx_block_1 = TxBlock { @@ -1799,21 +1787,10 @@ mod tests { block_number: 6_789_898_789_u64.into(), }; sent_tx_2.status = TxStatus::Confirmed { - block_hash: format!("{:?}", make_block_hash(123)), + block_hash: format!("{:?}", tx_block_2.block_hash), block_number: tx_block_2.block_number.as_u64(), detection: Detection::Normal, }; - let mut sent_tx_3 = make_sent_tx(567_567); - sent_tx_3.hash = tx_hash_3; - let tx_block_3 = TxBlock { - block_hash: make_block_hash(78), - block_number: 7_898_989_878_u64.into(), - }; - sent_tx_3.status = TxStatus::Confirmed { - block_hash: format!("{:?}", tx_block_3.block_hash), - block_number: tx_block_3.block_number.as_u64(), - detection: Detection::Reclaim, - }; subject.handle_confirmed_transactions( DetectedConfirmations { @@ -1825,9 +1802,7 @@ mod tests { tx: sent_tx_2.clone(), }, ], - reclaims: vec![TxReclaim { - reclaimed: sent_tx_3.clone(), - }], + reclaims: vec![], }, &logger, ); @@ -1835,22 +1810,100 @@ mod tests { let transactions_confirmed_params = transactions_confirmed_params_arc.lock().unwrap(); assert_eq!( *transactions_confirmed_params, - vec![vec![sent_tx_1, sent_tx_2, sent_tx_3.clone()]] + vec![vec![sent_tx_1, sent_tx_2]] ); let confirm_tx_params = confirm_tx_params_arc.lock().unwrap(); assert_eq!( *confirm_tx_params, vec![hashmap![tx_hash_1 => tx_block_1, tx_hash_2 => tx_block_2]] ); - let replace_records_params = replace_records_params_arc.lock().unwrap(); - assert_eq!(*replace_records_params, vec![vec![sent_tx_3]]); - let delete_records_params = delete_records_params_arc.lock().unwrap(); - assert_eq!(*delete_records_params, vec![hashset![tx_hash_3]]); let log_handler = TestLogHandler::new(); log_handler.exists_log_containing(&format!( "INFO: {test_name}: Txs 0x0000000000000000000000000000000000000000000000000000000000000123 \ (block 4578989878), 0x0000000000000000000000000000000000000000000000000000000000000567 \ - (block 7898989878), txxxxbluh (block bluh) have been confirmed", + (block 6789898789) were confirmed", + )); + } + + #[test] + fn mixed_tx_confirmations_work() { + init_test_logging(); + let test_name = "mixed_tx_confirmations_work"; + let transactions_confirmed_params_arc = Arc::new(Mutex::new(vec![])); + let confirm_tx_params_arc = Arc::new(Mutex::new(vec![])); + let replace_records_params_arc = Arc::new(Mutex::new(vec![])); + let delete_records_params_arc = Arc::new(Mutex::new(vec![])); + let payable_dao = PayableDaoMock::default() + .transactions_confirmed_params(&transactions_confirmed_params_arc) + .transactions_confirmed_result(Ok(())); + let sent_payable_dao = SentPayableDaoMock::default() + .confirm_tx_params(&confirm_tx_params_arc) + .confirm_tx_result(Ok(())) + .replace_records_params(&replace_records_params_arc) + .replace_records_result(Ok(())); + let failed_payable_dao = FailedPayableDaoMock::default() + .delete_records_params(&delete_records_params_arc) + .delete_records_result(Ok(())); + let logger = Logger::new(test_name); + let mut subject = PendingPayableScannerBuilder::new() + .payable_dao(payable_dao) + .sent_payable_dao(sent_payable_dao) + .failed_payable_dao(failed_payable_dao) + .build(); + let tx_hash_1 = make_tx_hash(0x123); + let tx_hash_2 = make_tx_hash(0x913); + let mut sent_tx_1 = make_sent_tx(123_123); + sent_tx_1.hash = tx_hash_1; + let tx_block_1 = TxBlock { + block_hash: make_block_hash(45), + block_number: 4_578_989_878_u64.into(), + }; + sent_tx_1.status = TxStatus::Confirmed { + block_hash: format!("{:?}", tx_block_1.block_hash), + block_number: tx_block_1.block_number.as_u64(), + detection: Detection::Normal, + }; + let mut sent_tx_2 = make_sent_tx(567_567); + sent_tx_2.hash = tx_hash_2; + let tx_block_3 = TxBlock { + block_hash: make_block_hash(78), + block_number: 7_898_989_878_u64.into(), + }; + sent_tx_2.status = TxStatus::Confirmed { + block_hash: format!("{:?}", tx_block_3.block_hash), + block_number: tx_block_3.block_number.as_u64(), + detection: Detection::Reclaim, + }; + + subject.handle_confirmed_transactions( + DetectedConfirmations { + normal_confirmations: vec![NormalTxConfirmation { + tx: sent_tx_1.clone(), + }], + reclaims: vec![TxReclaim { + reclaimed: sent_tx_2.clone(), + }], + }, + &logger, + ); + + let transactions_confirmed_params = transactions_confirmed_params_arc.lock().unwrap(); + assert_eq!(*transactions_confirmed_params, vec![vec![sent_tx_1]]); + let confirm_tx_params = confirm_tx_params_arc.lock().unwrap(); + assert_eq!(*confirm_tx_params, vec![hashmap![tx_hash_1 => tx_block_1]]); + let replace_records_params = replace_records_params_arc.lock().unwrap(); + assert_eq!(*replace_records_params, vec![vec![sent_tx_2]]); + let delete_records_params = delete_records_params_arc.lock().unwrap(); + assert_eq!(*delete_records_params, vec![hashset![tx_hash_2]]); + let log_handler = TestLogHandler::new(); + log_handler.exists_log_containing(&format!( + "INFO: {test_name}: Reclaimed txs \ + 0x0000000000000000000000000000000000000000000000000000000000000913 (block 7898989878) \ + as confirmed on-chain", + )); + log_handler.exists_log_containing(&format!( + "INFO: {test_name}: Tx 0x0000000000000000000000000000000000000000000000000000000000000123 \ + (block 4578989878) was confirmed", )); } @@ -1954,11 +2007,11 @@ mod tests { log_handler.exists_log_containing(&format!( "INFO: {plural_case_name}: Txs 0x0000000000000000000000000000000000000000000000000000000000000123 \ (block 1234501), 0x0000000000000000000000000000000000000000000000000000000000000567 \ - (block 1234502) have been confirmed", + (block 1234502) were confirmed", )); log_handler.exists_log_containing(&format!( "INFO: {singular_case_name}: Tx 0x0000000000000000000000000000000000000000000000000000000000000123 \ - (block 1234501) has been confirmed", + (block 1234501) was confirmed", )); } @@ -1988,9 +2041,13 @@ mod tests { detection: Detection::Reclaim, }; let payable_dao = PayableDaoMock::default().transactions_confirmed_result(Ok(())); - let sent_payable_dao = SentPayableDaoMock::default().confirm_tx_result(Ok(())); + let sent_payable_dao = SentPayableDaoMock::default() + .confirm_tx_result(Ok(())) + .replace_records_result(Ok(())); + let failed_payable_dao = FailedPayableDaoMock::default().delete_records_result(Ok(())); let mut subject = PendingPayableScannerBuilder::new() .payable_dao(payable_dao) + .failed_payable_dao(failed_payable_dao) .sent_payable_dao(sent_payable_dao) .build(); let mut financial_statistics = subject.financial_statistics.borrow().clone(); @@ -2012,8 +2069,11 @@ mod tests { let total_paid_payable = subject.financial_statistics.borrow().total_paid_payable_wei; assert_eq!(total_paid_payable, 1111 + 5478 + 3344 + 6543); - TestLogHandler::new().exists_log_containing(&format!( - "DEBUG: {test_name}: The total paid payables increased by blouuh to bluuuuuh wei" - )); + TestLogHandler::new().assert_logs_contain_in_order(vec![ + &format!("DEBUG: {test_name}: The total paid payables increased by 6,543 to 7,654 wei"), + &format!( + "DEBUG: {test_name}: The total paid payables increased by 8,822 to 16,476 wei" + ), + ]); } } diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs index 0f3f779e5..916de0300 100644 --- a/node/src/accountant/test_utils.rs +++ b/node/src/accountant/test_utils.rs @@ -1016,7 +1016,7 @@ impl SentPayableDao for SentPayableDaoMock { self.retrieve_txs_params.lock().unwrap().push(condition); self.retrieve_txs_results.borrow_mut().remove(0) } - fn confirm_tx(&self, hash_map: &HashMap) -> Result<(), SentPayableDaoError> { + fn confirm_txs(&self, hash_map: &HashMap) -> Result<(), SentPayableDaoError> { self.confirm_tx_params .lock() .unwrap() From eddcec4b922c69c0ba59d5394de4f20c99be60e2 Mon Sep 17 00:00:00 2001 From: Bert Date: Wed, 13 Aug 2025 19:00:03 +0200 Subject: [PATCH 34/61] GH-642: ValidationStatus extension - huge chunk of work; still some failing tests remain --- .../db_access_objects/failed_payable_dao.rs | 52 +-- ...able_and_failed_payable_data_conversion.rs | 4 +- .../db_access_objects/sent_payable_dao.rs | 112 +++--- .../db_access_objects/test_utils.rs | 3 +- node/src/accountant/mod.rs | 4 +- node/src/accountant/scanners/mod.rs | 271 ++++++++------- .../scanners/pending_payable_scanner/mod.rs | 127 ++++--- .../pending_payable_scanner/test_utils.rs | 23 ++ .../tx_receipt_interpreter.rs | 48 +-- .../scanners/pending_payable_scanner/utils.rs | 327 ++++++++++++------ .../scanners/receivable_scanner/mod.rs | 2 +- .../src/accountant/scanners/scanners_utils.rs | 4 +- node/src/accountant/test_utils.rs | 23 +- node/src/blockchain/blockchain_bridge.rs | 3 +- .../blockchain_interface_web3/utils.rs | 2 +- node/src/blockchain/errors.rs | 193 ++++++++++- 16 files changed, 812 insertions(+), 386 deletions(-) create mode 100644 node/src/accountant/scanners/pending_payable_scanner/test_utils.rs 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 f29f98b73..b3185d2dd 100644 --- a/node/src/accountant/db_access_objects/failed_payable_dao.rs +++ b/node/src/accountant/db_access_objects/failed_payable_dao.rs @@ -5,7 +5,7 @@ use crate::accountant::db_access_objects::utils::{ }; use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; use crate::accountant::{checked_conversion, comma_joined_stringifiable}; -use crate::blockchain::errors::AppRpcError; +use crate::blockchain::errors::{AppRpcError, ValidationStatus}; use crate::database::rusqlite_wrappers::ConnectionWrapper; use itertools::Itertools; use masq_lib::utils::ExpectValue; @@ -73,12 +73,6 @@ impl FromStr for FailureStatus { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum ValidationStatus { - Waiting, - Reattempting { attempt: usize, error: AppRpcError }, -} - #[derive(Clone, Debug, PartialEq, Eq)] pub struct FailedTx { pub hash: TxHash, @@ -403,8 +397,12 @@ mod tests { make_read_only_db_connection, FailedTxBuilder, }; use crate::accountant::db_access_objects::utils::{current_unix_timestamp, TxRecordWithHash}; + use crate::accountant::scanners::pending_payable_scanner::test_utils::ValidationFailureClockMock; + use crate::accountant::scanners::pending_payable_scanner::utils::ValidationFailureClockReal; use crate::accountant::test_utils::{make_failed_tx, make_sent_tx}; - use crate::blockchain::errors::{AppRpcError, LocalError, RemoteError}; + use crate::blockchain::errors::{ + AppRpcError, AppRpcErrorKind, LocalError, PreviousAttempts, RemoteError, + }; use crate::blockchain::test_utils::make_tx_hash; use crate::database::db_initializer::{ DbInitializationConfig, DbInitializer, DbInitializerReal, @@ -413,7 +411,9 @@ mod tests { use masq_lib::test_utils::utils::ensure_node_home_directory_exists; use rusqlite::Connection; use std::collections::{HashMap, HashSet}; + use std::ops::Add; use std::str::FromStr; + use std::time::{Duration, SystemTime}; #[test] fn insert_new_records_works() { @@ -666,6 +666,11 @@ mod tests { #[test] fn failure_status_from_str_works() { + let validation_failure_clock = ValidationFailureClockMock::default().now_result( + SystemTime::UNIX_EPOCH + .add(Duration::from_secs(1755080031)) + .add(Duration::from_nanos(612180914)), + ); assert_eq!( FailureStatus::from_str("\"RetryRequired\"").unwrap(), FailureStatus::RetryRequired @@ -677,8 +682,8 @@ mod tests { ); assert_eq!( - FailureStatus::from_str(r#"{"RecheckRequired":{"Reattempting":{"attempt":2,"error":{"Remote":"Unreachable"}}}}"#).unwrap(), - FailureStatus::RecheckRequired(ValidationStatus::Reattempting { attempt: 2, error: AppRpcError::Remote(RemoteError::Unreachable) }) + FailureStatus::from_str(r#"{"RecheckRequired":{"Reattempting":{"ServerUnreachable":{"firstSeen":{"secs_since_epoch":1755080031,"nanos_since_epoch":612180914},"attempts":1}}}}"#).unwrap(), + FailureStatus::RecheckRequired(ValidationStatus::Reattempting( PreviousAttempts::new(AppRpcErrorKind::ServerUnreachable, &validation_failure_clock))) ); assert_eq!( @@ -759,10 +764,12 @@ mod tests { let tx3 = FailedTxBuilder::default() .hash(make_tx_hash(3)) .reason(PendingTooLong) - .status(RecheckRequired(ValidationStatus::Reattempting { - attempt: 1, - error: AppRpcError::Remote(RemoteError::Unreachable), - })) + .status(RecheckRequired(ValidationStatus::Reattempting( + PreviousAttempts::new( + AppRpcErrorKind::ServerUnreachable, + &ValidationFailureClockReal::default(), + ), + ))) .build(); let tx4 = FailedTxBuilder::default() .hash(make_tx_hash(4)) @@ -810,14 +817,15 @@ mod tests { subject .insert_new_records(&vec![tx1.clone(), tx2.clone(), tx3.clone(), tx4.clone()]) .unwrap(); + let now = SystemTime::now(); let hashmap = HashMap::from([ (tx1.hash, Concluded), ( tx2.hash, - RecheckRequired(ValidationStatus::Reattempting { - attempt: 1, - error: AppRpcError::Remote(RemoteError::Unreachable), - }), + RecheckRequired(ValidationStatus::Reattempting(PreviousAttempts::new( + AppRpcErrorKind::ServerUnreachable, + &ValidationFailureClockMock::default().now_result(now), + ))), ), (tx3.hash, Concluded), ]); @@ -831,10 +839,10 @@ mod tests { assert_eq!(tx2.status, RecheckRequired(ValidationStatus::Waiting)); assert_eq!( updated_txs[1].status, - RecheckRequired(ValidationStatus::Reattempting { - attempt: 1, - error: AppRpcError::Remote(RemoteError::Unreachable) - }) + RecheckRequired(ValidationStatus::Reattempting(PreviousAttempts::new( + AppRpcErrorKind::ServerUnreachable, + &ValidationFailureClockMock::default().now_result(now) + ))) ); assert_eq!(tx3.status, RetryRequired); assert_eq!(updated_txs[2].status, Concluded); diff --git a/node/src/accountant/db_access_objects/sent_payable_and_failed_payable_data_conversion.rs b/node/src/accountant/db_access_objects/sent_payable_and_failed_payable_data_conversion.rs index 379ef4d62..abf5347e8 100644 --- a/node/src/accountant/db_access_objects/sent_payable_and_failed_payable_data_conversion.rs +++ b/node/src/accountant/db_access_objects/sent_payable_and_failed_payable_data_conversion.rs @@ -42,13 +42,13 @@ impl From<(SentTx, FailureReason)> for FailedTx { #[cfg(test)] mod tests { use crate::accountant::db_access_objects::failed_payable_dao::{ - FailedTx, FailureReason, FailureStatus, ValidationStatus, + FailedTx, FailureReason, FailureStatus, }; use crate::accountant::db_access_objects::sent_payable_dao::{Detection, SentTx, TxStatus}; use crate::accountant::db_access_objects::utils::to_unix_timestamp; use crate::accountant::gwei_to_wei; use crate::accountant::test_utils::make_transaction_block; - use crate::blockchain::errors::{AppRpcError, LocalError}; + use crate::blockchain::errors::{AppRpcError, LocalError, ValidationStatus}; use crate::blockchain::test_utils::make_tx_hash; use crate::test_utils::make_wallet; use std::time::{Duration, SystemTime}; 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 e55846d49..760e33f5e 100644 --- a/node/src/accountant/db_access_objects/sent_payable_dao.rs +++ b/node/src/accountant/db_access_objects/sent_payable_dao.rs @@ -1,12 +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::{FailedTx, ValidationStatus}; +use crate::accountant::db_access_objects::failed_payable_dao::FailedTx; use crate::accountant::db_access_objects::utils::{ DaoFactoryReal, TxHash, TxIdentifiers, TxRecordWithHash, }; use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; use crate::accountant::{checked_conversion, comma_joined_stringifiable}; use crate::blockchain::blockchain_interface::data_structures::TxBlock; +use crate::blockchain::errors::ValidationStatus; use crate::database::rusqlite_wrappers::ConnectionWrapper; use ethereum_types::H256; use itertools::Itertools; @@ -451,15 +452,6 @@ impl SentPayableDaoFactory for DaoFactoryReal { #[cfg(test)] mod tests { - use crate::accountant::db_access_objects::failed_payable_dao::FailureReason::{ - PendingTooLong, Reverted, - }; - use crate::accountant::db_access_objects::failed_payable_dao::FailureStatus::{ - Concluded, RecheckRequired, RetryRequired, - }; - use crate::accountant::db_access_objects::failed_payable_dao::{ - FailedPayableDaoError, FailedPayableDaoReal, ValidationStatus, - }; use crate::accountant::db_access_objects::sent_payable_dao::RetrieveCondition::{ ByHash, ByNonce, IsPending, }; @@ -471,12 +463,16 @@ mod tests { TxStatus, }; use crate::accountant::db_access_objects::test_utils::{ - make_read_only_db_connection, FailedTxBuilder, TxBuilder, + make_read_only_db_connection, TxBuilder, }; use crate::accountant::db_access_objects::utils::TxRecordWithHash; + use crate::accountant::scanners::pending_payable_scanner::test_utils::ValidationFailureClockMock; + use crate::accountant::scanners::pending_payable_scanner::utils::ValidationFailureClockReal; use crate::accountant::test_utils::make_sent_tx; use crate::blockchain::blockchain_interface::data_structures::TxBlock; - use crate::blockchain::errors::{AppRpcError, LocalError, RemoteError}; + use crate::blockchain::errors::{ + AppRpcError, AppRpcErrorKind, LocalError, PreviousAttempts, RemoteError, ValidationStatus, + }; use crate::blockchain::test_utils::{make_block_hash, make_tx_hash}; use crate::database::db_initializer::{ DbInitializationConfig, DbInitializer, DbInitializerReal, @@ -487,8 +483,10 @@ mod tests { use rusqlite::Connection; use std::collections::{HashMap, HashSet}; use std::fmt::format; + use std::ops::{Add, Sub}; use std::str::FromStr; use std::sync::{Arc, Mutex}; + use std::time::{Duration, SystemTime, UNIX_EPOCH}; #[test] fn insert_new_records_works() { @@ -497,13 +495,17 @@ mod tests { let wrapped_conn = DbInitializerReal::default() .initialize(&home_dir, DbInitializationConfig::test_default()) .unwrap(); + let validation_failure_clock = ValidationFailureClockReal::default(); let tx1 = TxBuilder::default().hash(make_tx_hash(1)).build(); let tx2 = TxBuilder::default() .hash(make_tx_hash(2)) - .status(TxStatus::Pending(ValidationStatus::Reattempting { - attempt: 2, - error: AppRpcError::Remote(RemoteError::Unreachable), - })) + .status(TxStatus::Pending(ValidationStatus::Reattempting( + PreviousAttempts::new(AppRpcErrorKind::Decoder, &validation_failure_clock) + .add_attempt( + AppRpcErrorKind::ServerUnreachable, + &validation_failure_clock, + ), + ))) .build(); let subject = SentPayableDaoReal::new(wrapped_conn); let txs = vec![tx1, tx2]; @@ -730,10 +732,12 @@ mod tests { .build(); let tx2 = TxBuilder::default() .hash(make_tx_hash(2)) - .status(TxStatus::Pending(ValidationStatus::Reattempting { - attempt: 1, - error: AppRpcError::Remote(RemoteError::Unreachable), - })) + .status(TxStatus::Pending(ValidationStatus::Reattempting( + PreviousAttempts::new( + AppRpcErrorKind::ServerUnreachable, + &ValidationFailureClockReal::default(), + ), + ))) .build(); let tx3 = TxBuilder::default() .hash(make_tx_hash(3)) @@ -1058,14 +1062,16 @@ mod tests { let wrapped_conn = DbInitializerReal::default() .initialize(&home_dir, DbInitializationConfig::test_default()) .unwrap(); + let timestamp_a = SystemTime::now(); + let timestamp_b = SystemTime::now().sub(Duration::from_millis(1234)); let subject = SentPayableDaoReal::new(wrapped_conn); let mut tx1 = make_sent_tx(456); tx1.status = TxStatus::Pending(ValidationStatus::Waiting); let mut tx2 = make_sent_tx(789); - tx2.status = TxStatus::Pending(ValidationStatus::Reattempting { - attempt: 2, - error: AppRpcError::Remote(RemoteError::Unreachable), - }); + tx2.status = TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new( + AppRpcErrorKind::ServerUnreachable, + &ValidationFailureClockMock::default().now_result(timestamp_a), + ))); let mut tx3 = make_sent_tx(123); tx3.status = TxStatus::Pending(ValidationStatus::Waiting); subject @@ -1074,17 +1080,23 @@ mod tests { let hashmap = HashMap::from([ ( tx1.hash, - TxStatus::Pending(ValidationStatus::Reattempting { - attempt: 1, - error: AppRpcError::Local(LocalError::Internal), - }), + TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new( + AppRpcErrorKind::Internal, + &ValidationFailureClockMock::default().now_result(timestamp_b), + ))), ), ( tx2.hash, - TxStatus::Pending(ValidationStatus::Reattempting { - attempt: 2, - error: AppRpcError::Remote(RemoteError::Unreachable), - }), + TxStatus::Pending(ValidationStatus::Reattempting( + PreviousAttempts::new( + AppRpcErrorKind::ServerUnreachable, + &ValidationFailureClockReal::default(), + ) + .add_attempt( + AppRpcErrorKind::ServerUnreachable, + &ValidationFailureClockReal::default(), + ), + )), ), ( tx3.hash, @@ -1104,17 +1116,23 @@ mod tests { assert_eq!(result, Ok(())); assert_eq!( updated_txs[0].status, - TxStatus::Pending(ValidationStatus::Reattempting { - attempt: 1, - error: AppRpcError::Local(LocalError::Internal) - }) + TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new( + AppRpcErrorKind::Internal, + &ValidationFailureClockMock::default().now_result(timestamp_b) + ))) ); assert_eq!( updated_txs[1].status, - TxStatus::Pending(ValidationStatus::Reattempting { - attempt: 2, - error: AppRpcError::Remote(RemoteError::Unreachable) - }) + TxStatus::Pending(ValidationStatus::Reattempting( + PreviousAttempts::new( + AppRpcErrorKind::ServerUnreachable, + &ValidationFailureClockMock::default().now_result(timestamp_a) + ) + .add_attempt( + AppRpcErrorKind::ServerUnreachable, + &ValidationFailureClockReal::default() + ) + )) ); assert_eq!( updated_txs[2].status, @@ -1154,10 +1172,10 @@ mod tests { let result = subject.update_statuses(&HashMap::from([( make_tx_hash(1), - TxStatus::Pending(ValidationStatus::Reattempting { - attempt: 1, - error: AppRpcError::Remote(RemoteError::Unreachable), - }), + TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new( + AppRpcErrorKind::ServerUnreachable, + &ValidationFailureClockReal::default(), + ))), )])); assert_eq!( @@ -1353,9 +1371,11 @@ mod tests { TxStatus::Pending(ValidationStatus::Waiting) ); + let validation_failure_clock = ValidationFailureClockMock::default() + .now_result(UNIX_EPOCH.add(Duration::from_secs(12456))); assert_eq!( - TxStatus::from_str(r#"{"Pending":{"Reattempting":{"attempt":3,"error":{"Remote":{"InvalidResponse":"bluh"}}}}}"#).unwrap(), - TxStatus::Pending(ValidationStatus::Reattempting { attempt: 3, error: AppRpcError::Remote(RemoteError::InvalidResponse("bluh".to_string())) }) + TxStatus::from_str(r#"{"Pending":{"Reattempting":{"InvalidResponse":{"secs_since_epoch":12456,"nanos_since_epoch":0},"attempts":1}}}"#).unwrap(), + TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new(AppRpcErrorKind::InvalidResponse, &validation_failure_clock))) ); assert_eq!( diff --git a/node/src/accountant/db_access_objects/test_utils.rs b/node/src/accountant/db_access_objects/test_utils.rs index 0132f1309..aeaf4e62e 100644 --- a/node/src/accountant/db_access_objects/test_utils.rs +++ b/node/src/accountant/db_access_objects/test_utils.rs @@ -2,10 +2,11 @@ #![cfg(test)] use crate::accountant::db_access_objects::failed_payable_dao::{ - FailedTx, FailureReason, FailureStatus, ValidationStatus, + FailedTx, FailureReason, FailureStatus, }; use crate::accountant::db_access_objects::sent_payable_dao::{SentTx, TxStatus}; use crate::accountant::db_access_objects::utils::{current_unix_timestamp, TxHash}; +use crate::blockchain::errors::ValidationStatus; use crate::database::db_initializer::{ DbInitializationConfig, DbInitializer, DbInitializerReal, DATABASE_FILE, }; diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index cb432aa4f..8f3e71d2d 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -1225,9 +1225,7 @@ pub fn wei_to_gwei, S: Display + Copy + Div + From() - .unwrap(); - let mut expected_failures = RecheckRequiringFailures::default(); - expected_failures.load_cache(vec![failed_tx_1, failed_tx_2]); - assert_eq!( - pending_payable_scanner - .yet_unproven_failed_payables - .dump_cache(), - expected_failures.dump_cache() - ); - } - #[test] fn new_payable_scanner_can_initiate_a_scan() { init_test_logging(); @@ -2460,6 +2396,8 @@ mod tests { let pending_payable_scanner = PendingPayableScannerBuilder::new() .sent_payable_dao(sent_payable_dao) .failed_payable_dao(failed_payable_dao) + .pending_payable_cache(Box::new(CurrentPendingPayables::default())) + .failed_payable_cache(Box::new(RecheckRequiringFailures::default())) .build(); // Important subject.aware_of_unresolved_pending_payable = true; @@ -2489,10 +2427,7 @@ mod tests { ); TestLogHandler::new().assert_logs_match_in_order(vec![ &format!("INFO: {test_name}: Scanning for pending payable"), - &format!("DEBUG: {test_name}: Found 1 pending payables to process"), - &format!( - "DEBUG: {test_name}: Found 1 older, failed payables that will be double-checked" - ), + &format!("DEBUG: {test_name}: Found 1 pending payables and 1 unfinalized failures to process"), ]) } @@ -2503,8 +2438,13 @@ mod tests { let mut subject = make_dull_subject(); let sent_payable_dao = SentPayableDaoMock::new().retrieve_txs_result(vec![make_sent_tx(123)]); + let failed_payable_dao = + FailedPayableDaoMock::new().retrieve_txs_result(vec![make_failed_tx(456)]); let pending_payable_scanner = PendingPayableScannerBuilder::new() .sent_payable_dao(sent_payable_dao) + .failed_payable_dao(failed_payable_dao) + .pending_payable_cache(Box::new(CurrentPendingPayables::default())) + .failed_payable_cache(Box::new(RecheckRequiringFailures::default())) .build(); // Important subject.aware_of_unresolved_pending_payable = true; @@ -2662,18 +2602,37 @@ mod tests { fn pending_payable_scanner_handles_tx_receipts_message() { init_test_logging(); let test_name = "pending_payable_scanner_handles_tx_receipts_message"; + // Normal confirmation let transactions_confirmed_params_arc = Arc::new(Mutex::new(vec![])); let confirm_tx_params_arc = Arc::new(Mutex::new(vec![])); + // FailedTx reclaim + let replace_records_params_arc = Arc::new(Mutex::new(vec![])); + // New tx failure + let insert_new_records_params_arc = Arc::new(Mutex::new(vec![])); + // Validation failures + let update_statuses_pending_payable_params_arc = Arc::new(Mutex::new(vec![])); + let update_statuses_failed_payable_params_arc = Arc::new(Mutex::new(vec![])); + let timestamp_a = SystemTime::now(); + let timestamp_b = SystemTime::now().sub(Duration::from_millis(12)); + let timestamp_c = SystemTime::now().sub(Duration::from_millis(1234)); let payable_dao = PayableDaoMock::new() .transactions_confirmed_params(&transactions_confirmed_params_arc) .transactions_confirmed_result(Ok(())); let sent_payable_dao = SentPayableDaoMock::new() .confirm_tx_params(&confirm_tx_params_arc) - .confirm_tx_result(Ok(())); - let pending_payable_scanner = PendingPayableScannerBuilder::new() - .payable_dao(payable_dao) - .sent_payable_dao(sent_payable_dao) - .build(); + .confirm_tx_result(Ok(())) + .update_statuses_params(&update_statuses_pending_payable_params_arc) + .update_statuses_result(Ok(())) + .replace_records_result(Ok(())) + .delete_records_result(Ok(())) + .replace_records_params(&replace_records_params_arc) + .replace_records_result(Ok(())); + let failed_payable_dao = FailedPayableDaoMock::new() + .insert_new_records_params(&insert_new_records_params_arc) + .insert_new_records_result(Ok(())) + .update_statuses_params(&update_statuses_failed_payable_params_arc) + .update_statuses_result(Ok(())) + .delete_records_result(Ok(())); let tx_hash_1 = make_tx_hash(4545); let mut sent_tx_1 = make_sent_tx(123); sent_tx_1.hash = tx_hash_1; @@ -2686,37 +2645,68 @@ mod tests { StatusReadFromReceiptCheck::Succeeded(tx_block_1), ); let tx_hash_2 = make_tx_hash(1234); - let mut sent_tx_2 = make_sent_tx(789); - sent_tx_2.hash = tx_hash_2; + let mut failed_tx_2 = make_failed_tx(789); + failed_tx_2.hash = tx_hash_2; let tx_block_2 = TxBlock { block_hash: make_block_hash(222), block_number: U64::from(2345), }; let transaction_with_status_2 = RetrievedTxStatus::new( - TxHashByTable::FailedPayable(sent_tx_2.hash), + TxHashByTable::FailedPayable(failed_tx_2.hash), StatusReadFromReceiptCheck::Succeeded(tx_block_2), ); - let sent_tx_3 = make_sent_tx(456); + let tx_hash_3 = make_tx_hash(2345); + let mut sent_tx_3 = make_sent_tx(456); + sent_tx_3.hash = tx_hash_3; let transaction_with_status_3 = RetrievedTxStatus::new( - TxHashByTable::SentPayable(sent_tx_3.hash), + TxHashByTable::SentPayable(tx_hash_3), StatusReadFromReceiptCheck::Pending, ); - - let sent_tx_4 = make_sent_tx(123); + let mut sent_tx_4 = make_sent_tx(4567); + let tx_hash_4 = sent_tx_4.hash; + sent_tx_4.status = TxStatus::Pending(ValidationStatus::Waiting); let tx_receipt_rpc_error_4 = TxReceiptError::new( TxHashByTable::SentPayable(sent_tx_4.hash), AppRpcError::Remote(RemoteError::Unreachable), ); - let sent_tx_5 = make_sent_tx(888); + let tx_hash_5 = make_tx_hash(7890); + let mut failed_tx_5 = make_failed_tx(888); + failed_tx_5.hash = tx_hash_5; + failed_tx_5.status = + FailureStatus::RecheckRequired(ValidationStatus::Reattempting(PreviousAttempts::new( + AppRpcErrorKind::ServerUnreachable, + &ValidationFailureClockMock::default().now_result(timestamp_c), + ))); let tx_receipt_rpc_error_5 = TxReceiptError::new( - TxHashByTable::FailedPayable(sent_tx_5.hash), + TxHashByTable::FailedPayable(failed_tx_5.hash), AppRpcError::Remote(RemoteError::InvalidResponse("game over".to_string())), ); + let tx_hash_6 = make_tx_hash(2345); let sent_tx_6 = make_sent_tx(789); + let tx_hash_6 = sent_tx_6.hash; let transaction_with_status_6 = RetrievedTxStatus::new( TxHashByTable::SentPayable(sent_tx_6.hash), StatusReadFromReceiptCheck::Failed(BlockchainTxFailure::Unrecognized), ); + let pending_payable_cache = PendingPayableCacheMock::default() + .get_record_by_hash_result(Some(sent_tx_1.clone())) + .get_record_by_hash_result(Some(sent_tx_3.clone())) + .get_record_by_hash_result(Some(sent_tx_4)) + .get_record_by_hash_result(Some(sent_tx_6.clone())); + 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() + .now_result(timestamp_a) + .now_result(timestamp_b); + let mut pending_payable_scanner = PendingPayableScannerBuilder::new() + .payable_dao(payable_dao) + .sent_payable_dao(sent_payable_dao) + .failed_payable_dao(failed_payable_dao) + .pending_payable_cache(Box::new(pending_payable_cache)) + .failed_payable_cache(Box::new(failed_payable_cache)) + .validation_failure_clock(Box::new(validation_failure_clock)) + .build(); let msg = TxReceiptsMessage { results: vec![ TxReceiptResult(Ok(transaction_with_status_1)), @@ -2728,38 +2718,63 @@ mod tests { ], response_skeleton_opt: None, }; - todo!("finish me when it's clearer how you should threat all those cases") - // pending_payable_scanner.mark_as_started(SystemTime::now()); - // let mut subject = make_dull_subject(); - // subject.pending_payable = Box::new(pending_payable_scanner); - // - // let result = subject.finish_pending_payable_scan(msg, &Logger::new(test_name)); - // - // assert_eq!( - // result, - // PendingPayableScanResult::NoPendingPayablesLeft(None) - // ); - // let transactions_confirmed_params = transactions_confirmed_params_arc.lock().unwrap(); - // sent_tx_1.block_opt = Some(tx_block_1); - // sent_tx_2.block_opt = Some(tx_block_2); - // assert_eq!( - // *transactions_confirmed_params, - // vec![vec![sent_tx_1, sent_tx_2]] - // ); - // let confirm_tx_params = confirm_tx_params_arc.lock().unwrap(); - // assert_eq!( - // *confirm_tx_params, - // vec![hashmap![tx_hash_1 => tx_block_1, tx_hash_2 => tx_block_2]] - // ); - // assert_eq!(subject.scan_started_at(ScanType::PendingPayables), None); - // - // TestLogHandler::new().assert_logs_match_in_order(vec![ - // ®ex::escape(&format!( - // "INFO: {}: Txs {:?} (block 2345), {:?} (block 1234) have been confirmed", - // test_name, tx_hash_2, tx_hash_1 - // )), - // &format!("INFO: {test_name}: The PendingPayables scan ended in \\d+ms."), - // ]); + pending_payable_scanner.mark_as_started(SystemTime::now()); + let mut subject = make_dull_subject(); + subject.pending_payable = Box::new(pending_payable_scanner); + + let result = subject.finish_pending_payable_scan(msg, &Logger::new(test_name)); + + assert_eq!( + result, + PendingPayableScanResult::PaymentRetryRequired(Retry::RetryPayments) + ); + let transactions_confirmed_params = transactions_confirmed_params_arc.lock().unwrap(); + sent_tx_1.status = TxStatus::Confirmed { + block_hash: format!("{:?}", tx_block_1.block_hash), + block_number: tx_block_1.block_number.as_u64(), + detection: Detection::Normal, + }; + let sent_tx_2 = SentTx::from((failed_tx_2, tx_block_2)); + assert_eq!(*transactions_confirmed_params, vec![vec![sent_tx_1]]); + let confirm_tx_params = confirm_tx_params_arc.lock().unwrap(); + assert_eq!(*confirm_tx_params, vec![hashmap![tx_hash_1 => tx_block_1]]); + let replace_records_params = replace_records_params_arc.lock().unwrap(); + assert_eq!(*replace_records_params, vec![vec![sent_tx_2]]); + let insert_new_records_params = insert_new_records_params_arc.lock().unwrap(); + let expected_failure_for_tx_3 = FailedTx::from((sent_tx_3, FailureReason::PendingTooLong)); + let expected_failure_for_tx_6 = FailedTx::from((sent_tx_6, FailureReason::Reverted)); + assert_eq!( + *insert_new_records_params, + vec![vec![expected_failure_for_tx_3, expected_failure_for_tx_6]] + ); + let update_statuses_pending_payable_params = + update_statuses_pending_payable_params_arc.lock().unwrap(); + assert_eq!( + *update_statuses_pending_payable_params, + vec![ + hashmap!(tx_hash_4 => TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new(AppRpcErrorKind::ServerUnreachable, &ValidationFailureClockMock::default().now_result(timestamp_a))))) + ] + ); + let update_statuses_failed_payable_params = + update_statuses_failed_payable_params_arc.lock().unwrap(); + assert_eq!( + *update_statuses_failed_payable_params, + vec![ + hashmap!(tx_hash_5 => FailureStatus::RecheckRequired(ValidationStatus::Reattempting(PreviousAttempts::new(AppRpcErrorKind::ServerUnreachable, &ValidationFailureClockMock::default().now_result(timestamp_c)).add_attempt(AppRpcErrorKind::InvalidResponse, &ValidationFailureClockMock::default().now_result(timestamp_b))))) + ] + ); + assert_eq!(subject.scan_started_at(ScanType::PendingPayables), None); + let test_log_handler = TestLogHandler::new(); + test_log_handler.exists_log_containing(&format!( + "DEBUG: {test_name}: Processing receipts for 6 txs" + )); + test_log_handler.exists_log_containing(&format!("WARN: {test_name}: Failed to retrieve tx receipt for SentPayable(0x00000000000000000000000000000000000000000000000000000000000011d7): Remote(Unreachable). Will retry receipt retrieval next cycle")); + test_log_handler.exists_log_containing(&format!("WARN: {test_name}: Failed to retrieve tx receipt for FailedPayable(0x0000000000000000000000000000000000000000000000000000000000001ed2): Remote(InvalidResponse(\"game over\")). Will retry receipt retrieval next cycle")); + test_log_handler.exists_log_containing(&format!("INFO: {test_name}: Reclaimed txs 0x00000000000000000000000000000000000000000000000000000000000004d2 (block 2345) as confirmed on-chain")); + test_log_handler.exists_log_containing(&format!( + "INFO: {test_name}: Tx 0x00000000000000000000000000000000000000000000000000000000000011c1 (block 1234) was confirmed", + )); + test_log_handler.exists_log_containing(&format!("INFO: {test_name}: Failed txs 0x0000000000000000000000000000000000000000000000000000000000000929, 0x0000000000000000000000000000000000000000000000000000000000000315 were processed in the db")); } #[test] diff --git a/node/src/accountant/scanners/pending_payable_scanner/mod.rs b/node/src/accountant/scanners/pending_payable_scanner/mod.rs index c6756dac1..107459000 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/mod.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/mod.rs @@ -1,5 +1,6 @@ // 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; @@ -17,6 +18,7 @@ use crate::accountant::scanners::pending_payable_scanner::utils::{ FailedValidationByTable, MismatchReport, NormalTxConfirmation, PendingPayableCache, PendingPayableScanResult, PresortedTxFailure, ReceiptScanReport, RecheckRequiringFailures, Retry, TxByTable, TxCaseToBeInterpreted, TxHashByTable, TxReclaim, UpdatableValidationStatus, + ValidationFailureClock, ValidationFailureClockReal, }; use crate::accountant::scanners::{ PrivateScanner, Scanner, ScannerCommon, StartScanError, StartableScanner, @@ -50,6 +52,7 @@ pub struct PendingPayableScanner { pub financial_statistics: Rc>, pub current_sent_payables: Box>, pub yet_unproven_failed_payables: Box>, + pub validation_failure_clock: Box, } impl @@ -144,6 +147,7 @@ impl PendingPayableScanner { financial_statistics, current_sent_payables: Box::new(CurrentPendingPayables::default()), yet_unproven_failed_payables: Box::new(RecheckRequiringFailures::default()), + validation_failure_clock: Box::new(ValidationFailureClockReal::default()), } } @@ -670,7 +674,11 @@ impl PendingPayableScanner { logger: &Logger, ) { if !sent_payable_failures.is_empty() { - let updatable = Self::prepare_statuses_for_update(&sent_payable_failures, logger); + let updatable = Self::prepare_statuses_for_update( + &sent_payable_failures, + &*self.validation_failure_clock, + logger, + ); if !updatable.is_empty() { match self.sent_payable_dao.update_statuses(&updatable) { Ok(_) => { @@ -701,8 +709,11 @@ impl PendingPayableScanner { logger: &Logger, ) { if !failed_txs_validation_failures.is_empty() { - let updatable = - Self::prepare_statuses_for_update(&failed_txs_validation_failures, logger); + let updatable = Self::prepare_statuses_for_update( + &failed_txs_validation_failures, + &*self.validation_failure_clock, + logger, + ); if !updatable.is_empty() { match self.failed_payable_dao.update_statuses(&updatable) { Ok(_) => { @@ -730,13 +741,14 @@ impl PendingPayableScanner { fn prepare_statuses_for_update( failures: &[FailedValidation], + clock: &dyn ValidationFailureClock, logger: &Logger, ) -> HashMap { failures .iter() .flat_map(|failure| { failure - .new_status() + .new_status(clock) .map(|tx_status| (failure.tx_hash, tx_status)) .or_else(|| { debug!( @@ -770,7 +782,7 @@ impl PendingPayableScanner { debug!( logger, - "Found {} pending payables and {} unfinalized failures to be checked", + "Found {} pending payables and {} unfinalized failures to process", resolve_optional_vec(pending_tx_hashes_opt), resolve_optional_vec(failure_hashes_opt) ); @@ -780,17 +792,18 @@ impl PendingPayableScanner { #[cfg(test)] mod tests { use crate::accountant::db_access_objects::failed_payable_dao::{ - FailedPayableDaoError, FailureStatus, ValidationStatus, + FailedPayableDaoError, FailureStatus, }; use crate::accountant::db_access_objects::payable_dao::PayableDaoError; use crate::accountant::db_access_objects::sent_payable_dao::{ Detection, SentPayableDaoError, TxStatus, }; + use crate::accountant::scanners::pending_payable_scanner::test_utils::ValidationFailureClockMock; use crate::accountant::scanners::pending_payable_scanner::utils::{ CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailedValidation, FailedValidationByTable, NormalTxConfirmation, PendingPayableCache, PendingPayableScanResult, PresortedTxFailure, RecheckRequiringFailures, Retry, - TxHashByTable, TxReclaim, + TxHashByTable, TxReclaim, ValidationFailureClockReal, }; use crate::accountant::scanners::pending_payable_scanner::PendingPayableScanner; use crate::accountant::scanners::test_utils::PendingPayableCacheMock; @@ -803,15 +816,20 @@ mod tests { use crate::blockchain::blockchain_interface::data_structures::{ RetrievedTxStatus, StatusReadFromReceiptCheck, TxBlock, TxReceiptError, TxReceiptResult, }; - use crate::blockchain::errors::{AppRpcError, LocalError, RemoteError}; + use crate::blockchain::errors::{ + AppRpcError, AppRpcErrorKind, LocalError, PreviousAttempts, RemoteError, ValidationStatus, + }; 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::test_utils::logging::{init_test_logging, TestLogHandler}; use regex::Regex; + use std::collections::HashMap; + use std::ops::{Add, Sub}; use std::panic::{catch_unwind, AssertUnwindSafe}; use std::sync::{Arc, Mutex}; - use std::time::SystemTime; + use std::time::{Duration, SystemTime}; #[test] fn start_scan_fills_in_caches_and_returns_msg() { @@ -830,10 +848,10 @@ mod tests { let mut subject = PendingPayableScannerBuilder::new() .sent_payable_dao(sent_payable_dao) .failed_payable_dao(failed_payable_dao) + .pending_payable_cache(Box::new(CurrentPendingPayables::default())) + .failed_payable_cache(Box::new(RecheckRequiringFailures::default())) .build(); let logger = Logger::new("start_scan_fills_in_caches_and_returns_msg"); - subject.current_sent_payables = Box::new(CurrentPendingPayables::default()); - subject.yet_unproven_failed_payables = Box::new(RecheckRequiringFailures::default()); let pending_payable_cache_before = subject.current_sent_payables.dump_cache(); let failed_payable_cache_before = subject.yet_unproven_failed_payables.dump_cache(); @@ -890,11 +908,11 @@ mod tests { let payable_dao = PayableDaoMock::new().transactions_confirmed_result(Ok(())); let sent_payable_dao = SentPayableDaoMock::new() .confirm_tx_result(Ok(())) - .replace_records_result(Ok(())) //TODO needed?? + .replace_records_result(Ok(())) .delete_records_result(Ok(())); let failed_payable_dao = FailedPayableDaoMock::new() .insert_new_records_result(Ok(())) - .delete_records_result(Ok(())); //TODO Needed? + .delete_records_result(Ok(())); let pending_payable_cache = PendingPayableCacheMock::default() .get_record_by_hash_params(&get_record_by_hash_sent_payable_cache_params_arc) .get_record_by_hash_result(Some(sent_tx_1.clone())) @@ -909,8 +927,8 @@ mod tests { .payable_dao(payable_dao) .sent_payable_dao(sent_payable_dao) .failed_payable_dao(failed_payable_dao) - .pending_payables_cache(Box::new(pending_payable_cache)) - .failed_payables_cache(Box::new(failed_payable_cache)) + .pending_payable_cache(Box::new(pending_payable_cache)) + .failed_payable_cache(Box::new(failed_payable_cache)) .build(); let logger = Logger::new("test"); let confirmed_tx_block_sent_tx = make_transaction_block(901); @@ -1179,19 +1197,24 @@ mod tests { let hash_1 = make_tx_hash(0x321); let hash_2 = make_tx_hash(0x654); let hash_3 = make_tx_hash(0x987); + let timestamp_a = SystemTime::now(); + let timestamp_b = SystemTime::now().sub(Duration::from_secs(1)); + let timestamp_c = SystemTime::now().sub(Duration::from_secs(2)); + let timestamp_d = SystemTime::now().sub(Duration::from_secs(3)); let mut failed_tx_1 = make_failed_tx(123); failed_tx_1.hash = hash_1; failed_tx_1.status = FailureStatus::RecheckRequired(ValidationStatus::Waiting); let mut failed_tx_2 = make_failed_tx(456); failed_tx_2.hash = hash_2; - failed_tx_2.status = FailureStatus::RecheckRequired(ValidationStatus::Reattempting { - attempt: 1, - error: AppRpcError::Local(LocalError::Internal), - }); + failed_tx_2.status = + FailureStatus::RecheckRequired(ValidationStatus::Reattempting(PreviousAttempts::new( + AppRpcErrorKind::Internal, + &ValidationFailureClockMock::default().now_result(timestamp_a), + ))); let failed_payable_dao = FailedPayableDaoMock::default() .retrieve_txs_params(&retrieve_failed_txs_params_arc) .retrieve_txs_result(vec![failed_tx_1, failed_tx_2]) - .update_statuses_params(&update_statuses_sent_tx_params_arc) + .update_statuses_params(&update_statuses_failed_tx_params_arc) .update_statuses_result(Ok(())); let mut sent_tx = make_sent_tx(789); sent_tx.hash = hash_3; @@ -1199,11 +1222,16 @@ mod tests { let sent_payable_dao = SentPayableDaoMock::default() .retrieve_txs_params(&retrieve_sent_txs_params_arc) .retrieve_txs_result(vec![sent_tx.clone()]) - .update_statuses_params(&update_statuses_failed_tx_params_arc) + .update_statuses_params(&update_statuses_sent_tx_params_arc) .update_statuses_result(Ok(())); + let validation_failure_clock = ValidationFailureClockMock::default() + .now_result(timestamp_a) + .now_result(timestamp_b) + .now_result(timestamp_c); let subject = PendingPayableScannerBuilder::new() .sent_payable_dao(sent_payable_dao) .failed_payable_dao(failed_payable_dao) + .validation_failure_clock(Box::new(validation_failure_clock)) .build(); let detected_failures = DetectedFailures { tx_failures: vec![], @@ -1216,10 +1244,12 @@ mod tests { FailedValidationByTable::FailedPayable(FailedValidation::new( hash_2, AppRpcError::Local(LocalError::Internal), - FailureStatus::RecheckRequired(ValidationStatus::Reattempting { - attempt: 1, - error: AppRpcError::Local(LocalError::Internal), - }), + FailureStatus::RecheckRequired(ValidationStatus::Reattempting( + PreviousAttempts::new( + AppRpcErrorKind::Internal, + &ValidationFailureClockMock::default().now_result(timestamp_d), + ), + )), )), FailedValidationByTable::SentPayable(FailedValidation::new( hash_3, @@ -1234,30 +1264,29 @@ mod tests { let update_statuses_sent_tx_params = update_statuses_sent_tx_params_arc.lock().unwrap(); assert_eq!( *update_statuses_sent_tx_params, - vec![hashmap!( + vec![ + hashmap![hash_3 => TxStatus::Pending(ValidationStatus::Reattempting (PreviousAttempts::new(AppRpcErrorKind::InvalidResponse, &ValidationFailureClockMock::default().now_result(timestamp_a))))] + ] + ); + let mut update_statuses_failed_tx_params = + update_statuses_failed_tx_params_arc.lock().unwrap(); + let actual_params = update_statuses_failed_tx_params + .remove(0) + .into_iter() + .sorted_by_key(|(key, _)| *key) + .collect::>(); + let expected_params = hashmap!( hash_1 => FailureStatus::RecheckRequired( - ValidationStatus::Reattempting { - attempt: 1, - error: AppRpcError::Remote(RemoteError::Unreachable) - } + ValidationStatus::Reattempting(PreviousAttempts::new(AppRpcErrorKind::ServerUnreachable, &ValidationFailureClockMock::default().now_result(timestamp_b))) ), hash_2 => FailureStatus::RecheckRequired( - ValidationStatus::Reattempting { - attempt: 2, - error: AppRpcError::Local(LocalError::Internal) - } - ) - )] - ); - let update_statuses_failed_tx_params = update_statuses_failed_tx_params_arc.lock().unwrap(); - assert_eq!( - *update_statuses_failed_tx_params, - vec![ - hashmap![hash_3 => TxStatus::Pending(ValidationStatus::Reattempting { - attempt: 1, - error: AppRpcError::Remote(RemoteError::InvalidResponse("Booga".to_string())), - })] - ] + ValidationStatus::Reattempting(PreviousAttempts::new(AppRpcErrorKind::Internal, &ValidationFailureClockMock::default().now_result(timestamp_d)).add_attempt(AppRpcErrorKind::Internal, &ValidationFailureClockReal::default()))) + ).into_iter().sorted_by_key(|(key,_)|*key).collect::>(); + assert_eq!(actual_params, expected_params); + assert!( + update_statuses_failed_tx_params.is_empty(), + "Should be empty but: {:?}", + update_statuses_sent_tx_params ); let test_log_handler = TestLogHandler::new(); test_log_handler.exists_log_containing(&format!( @@ -1380,6 +1409,7 @@ mod tests { let update_status_params_arc = Arc::new(Mutex::new(vec![])); let tx_hash_1 = make_tx_hash(0x321); let tx_hash_2 = make_tx_hash(0x654); + let timestamp = SystemTime::now(); let mut failed_tx_1 = make_failed_tx(123); failed_tx_1.hash = tx_hash_1; let mut failed_tx_2 = make_failed_tx(456); @@ -1395,6 +1425,9 @@ 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), + )) .build(); let detected_failures = DetectedFailures { tx_failures: vec![PresortedTxFailure::NewEntry(failed_tx_1.clone())], @@ -1417,7 +1450,7 @@ mod tests { assert_eq!( *update_statuses_params, vec![ - hashmap!(tx_hash_2 => TxStatus::Pending(ValidationStatus::Reattempting {attempt: 1,error: AppRpcError::Local(LocalError::Internal)})) + hashmap!(tx_hash_2 => TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new(AppRpcErrorKind::Internal, &ValidationFailureClockMock::default().now_result(timestamp))))) ] ); } diff --git a/node/src/accountant/scanners/pending_payable_scanner/test_utils.rs b/node/src/accountant/scanners/pending_payable_scanner/test_utils.rs new file mode 100644 index 000000000..6dedde9e2 --- /dev/null +++ b/node/src/accountant/scanners/pending_payable_scanner/test_utils.rs @@ -0,0 +1,23 @@ +// Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::accountant::scanners::pending_payable_scanner::utils::ValidationFailureClock; +use std::cell::RefCell; +use std::time::SystemTime; + +#[derive(Default)] +pub struct ValidationFailureClockMock { + now_results: RefCell>, +} + +impl ValidationFailureClock for ValidationFailureClockMock { + fn now(&self) -> SystemTime { + self.now_results.borrow_mut().remove(0) + } +} + +impl ValidationFailureClockMock { + pub fn now_result(self, result: SystemTime) -> Self { + self.now_results.borrow_mut().push(result); + self + } +} 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 a6706c1f1..c1c876950 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 @@ -10,7 +10,6 @@ use crate::accountant::scanners::pending_payable_scanner::utils::{ TxByTable, TxCaseToBeInterpreted, TxReclaim, }; use crate::accountant::scanners::pending_payable_scanner::PendingPayableScanner; -use crate::accountant::TxReceiptsMessage; use crate::blockchain::blockchain_interface::data_structures::{ BlockchainTxFailure, StatusReadFromReceiptCheck, TxBlock, TxReceiptError, TxReceiptResult, }; @@ -80,7 +79,7 @@ impl TxReceiptInterpreter { .separate_with_commas() ); let failed_tx = FailedTx::from((sent_tx, FailureReason::PendingTooLong)); - scan_report.register_new_failure(PresortedTxFailure::NewEntry(failed_tx)); + scan_report.register_new_failure(failed_tx); } TxByTable::FailedPayable(failed_tx) => { let replacement_tx = sent_payable_dao @@ -121,7 +120,7 @@ impl TxReceiptInterpreter { TxByTable::SentPayable(sent_tx) => { info!( logger, - "Pending tx {:?} was confirmed on the blockchain", sent_tx.hash, + "Pending tx {:?} was confirmed on-chain", sent_tx.hash, ); let completed_sent_tx = SentTx { @@ -140,7 +139,7 @@ impl TxReceiptInterpreter { TxByTable::FailedPayable(failed_tx) => { info!( logger, - "Failed tx {:?} was later confirmed on the blockchain and will be reclaimed", + "Failed tx {:?} was later confirmed on-chain and will be reclaimed", failed_tx.hash ); @@ -166,25 +165,24 @@ impl TxReceiptInterpreter { warning!( logger, - "Pending tx {:?} failed on the blockchain due to: {}", + "Pending tx {:?} failed on-chain due to: {}", failed_tx.hash, blockchain_failure ); - scan_report.register_new_failure(PresortedTxFailure::NewEntry(failed_tx)); + scan_report.register_new_failure(failed_tx); } TxByTable::FailedPayable(failed_tx) => { debug!( logger, - "Failed tx {:?} on a recheck after {}. Status will be changed to \ - \"Concluded\" due to blockchain failure: {}", + "Failed tx {:?} on a recheck after {}. Status will be changed to \"Concluded\" \ + due to blockchain failure: {}", failed_tx.hash, failed_tx.reason, blockchain_failure ); - scan_report - .register_new_failure(PresortedTxFailure::RecheckCompleted(failed_tx.hash)); + scan_report.register_finalization_of_unproven_failure(failed_tx.hash); } } scan_report @@ -218,7 +216,7 @@ impl TxReceiptInterpreter { #[cfg(test)] mod tests { use crate::accountant::db_access_objects::failed_payable_dao::{ - FailedTx, FailureReason, FailureStatus, ValidationStatus, + FailedTx, FailureReason, FailureStatus, }; use crate::accountant::db_access_objects::sent_payable_dao::{ Detection, RetrieveCondition, SentTx, TxStatus, @@ -228,7 +226,7 @@ mod tests { use crate::accountant::scanners::pending_payable_scanner::utils::{ DetectedConfirmations, DetectedFailures, FailedValidation, FailedValidationByTable, NormalTxConfirmation, PresortedTxFailure, ReceiptScanReport, RecheckRequiringFailures, - TxByTable, TxHashByTable, TxReclaim, + TxByTable, TxHashByTable, TxReclaim, ValidationFailureClockReal, }; use crate::accountant::test_utils::{ make_failed_tx, make_sent_tx, make_transaction_block, SentPayableDaoMock, @@ -236,7 +234,9 @@ mod tests { use crate::blockchain::blockchain_interface::data_structures::{ BlockchainTxFailure, TxReceiptError, }; - use crate::blockchain::errors::{AppRpcError, LocalError, RemoteError}; + use crate::blockchain::errors::{ + AppRpcError, AppRpcErrorKind, LocalError, PreviousAttempts, RemoteError, ValidationStatus, + }; use crate::blockchain::test_utils::make_tx_hash; use crate::test_utils::unshared_test_utils::capture_numbers_with_separators_from_str; use masq_lib::logger::Logger; @@ -282,7 +282,7 @@ mod tests { ); TestLogHandler::new().exists_log_containing(&format!( "INFO: {test_name}: Pending tx 0x0000000000000000000000000000000000000000000000000000000\ - 00000cdef was confirmed on the blockchain", + 00000cdef was confirmed on-chain", )); } @@ -330,7 +330,7 @@ mod tests { ); TestLogHandler::new().exists_log_containing(&format!( "INFO: {test_name}: Failed tx 0x0000000000000000000000000000000000000000000000000000000\ - 00000cdef was later confirmed on the blockchain and will be reclaimed", + 00000cdef was later confirmed on-chain and will be reclaimed", )); } @@ -366,7 +366,7 @@ mod tests { ); TestLogHandler::new().exists_log_containing(&format!( "WARN: {test_name}: Pending tx 0x0000000000000000000000000000000000000000000000000000000\ - 000000abc failed on the blockchain due to: Unrecognized failure", + 000000abc failed on-chain due to: Unrecognized failure", )); } @@ -561,10 +561,10 @@ mod tests { test_failed_retrieval_of_receipt_for_pending_payable( test_name, - TxStatus::Pending(ValidationStatus::Reattempting { - attempt: 1, - error: AppRpcError::Local(LocalError::Internal), - }), + TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new( + AppRpcErrorKind::Internal, + &ValidationFailureClockReal::default(), + ))), ); } @@ -623,10 +623,10 @@ mod tests { test_failed_retrieval_of_receipt_for_failed_tx( test_name, - FailureStatus::RecheckRequired(ValidationStatus::Reattempting { - attempt: 1, - error: AppRpcError::Local(LocalError::Internal), - }), + FailureStatus::RecheckRequired(ValidationStatus::Reattempting(PreviousAttempts::new( + AppRpcErrorKind::Internal, + &ValidationFailureClockReal::default(), + ))), ); } diff --git a/node/src/accountant/scanners/pending_payable_scanner/utils.rs b/node/src/accountant/scanners/pending_payable_scanner/utils.rs index c882ce092..b79b0b74e 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/utils.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/utils.rs @@ -1,26 +1,21 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::accountant::db_access_objects::failed_payable_dao::{ - FailedTx, FailureReason, FailureStatus, ValidationStatus, + FailedTx, FailureReason, FailureStatus, }; -use crate::accountant::db_access_objects::sent_payable_dao::{ - Detection, RetrieveCondition, SentPayableDao, SentTx, TxStatus, -}; -use crate::accountant::db_access_objects::utils::{from_unix_timestamp, TxHash}; +use crate::accountant::db_access_objects::sent_payable_dao::{SentPayableDao, SentTx, TxStatus}; +use crate::accountant::db_access_objects::utils::TxHash; use crate::blockchain::blockchain_interface::data_structures::{ BlockchainTxFailure, TxBlock, TxReceiptError, TxReceiptResult, }; -use crate::blockchain::errors::AppRpcError; +use crate::blockchain::errors::{AppRpcError, PreviousAttempts, ValidationStatus}; use actix::Message; -use ethereum_types::{H256, U64}; use masq_lib::logger::Logger; use masq_lib::ui_gateway::NodeToUiMessage; -use serde_derive::{Deserialize, Serialize}; use std::collections::HashMap; use std::fmt::Display; use std::time::SystemTime; use thousands::Separable; -use variant_count::VariantCount; #[derive(Debug, Default, PartialEq, Eq, Clone)] pub struct ReceiptScanReport { @@ -48,12 +43,16 @@ impl ReceiptScanReport { self.confirmations.reclaims.push(reclaim) } - pub(super) fn register_new_failure(&mut self, failed_tx: PresortedTxFailure) { - self.failures.tx_failures.push(failed_tx); + pub(super) fn register_new_failure(&mut self, failed_tx: FailedTx) { + self.failures + .tx_failures + .push(PresortedTxFailure::NewEntry(failed_tx)); } pub(super) fn register_finalization_of_unproven_failure(&mut self, tx_hash: TxHash) { - todo!() + self.failures + .tx_failures + .push(PresortedTxFailure::RecheckCompleted(tx_hash)); } pub(super) fn register_rpc_failure(&mut self, status_update: FailedValidationByTable) { @@ -123,7 +122,9 @@ impl From<(TxReceiptError, TxStatus)> for FailedValidationByTable { )), TxHashByTable::FailedPayable(tx_hash) => { - todo!() + unreachable!( + "Mismatch in the type of tx record (failed tx) and status type (TxStatus)" + ) } } } @@ -138,7 +139,9 @@ impl From<(TxReceiptError, FailureStatus)> for FailedValidationByTable { current_status, )), TxHashByTable::SentPayable(tx_hash) => { - todo!() + unreachable!( + "Mismatch in the type of tx record (sent tx) and status type (FailureStatus)" + ) } } } @@ -167,32 +170,49 @@ where } } - pub fn new_status(&self) -> Option { + pub fn new_status(&self, clock: &dyn ValidationFailureClock) -> Option { self.current_status - .update_after_failure(self.validation_failure.clone()) + .update_after_failure(self.validation_failure.clone(), clock) + } +} + +pub trait ValidationFailureClock { + fn now(&self) -> SystemTime; +} + +#[derive(Default)] +pub struct ValidationFailureClockReal {} + +impl ValidationFailureClock for ValidationFailureClockReal { + fn now(&self) -> SystemTime { + todo!() } } pub trait UpdatableValidationStatus { - fn update_after_failure(&self, error: AppRpcError) -> Option + fn update_after_failure( + &self, + error: AppRpcError, + clock: &dyn ValidationFailureClock, + ) -> Option where Self: Sized; } impl UpdatableValidationStatus for TxStatus { - fn update_after_failure(&self, error: AppRpcError) -> Option { + fn update_after_failure( + &self, + error: AppRpcError, + clock: &dyn ValidationFailureClock, + ) -> Option { match self { - TxStatus::Pending(ValidationStatus::Waiting) => { - Some(TxStatus::Pending(ValidationStatus::Reattempting { - attempt: 1, - error, - })) - } - TxStatus::Pending(ValidationStatus::Reattempting { attempt, .. }) => { - Some(TxStatus::Pending(ValidationStatus::Reattempting { - attempt: attempt + 1, - error, - })) + TxStatus::Pending(ValidationStatus::Waiting) => Some(TxStatus::Pending( + ValidationStatus::Reattempting(PreviousAttempts::new(error.into(), clock)), + )), + TxStatus::Pending(ValidationStatus::Reattempting(previous_attempts)) => { + Some(TxStatus::Pending(ValidationStatus::Reattempting( + previous_attempts.clone().add_attempt(error.into(), clock), + ))) } TxStatus::Confirmed { .. } => None, } @@ -200,19 +220,24 @@ impl UpdatableValidationStatus for TxStatus { } impl UpdatableValidationStatus for FailureStatus { - fn update_after_failure(&self, error: AppRpcError) -> Option { + fn update_after_failure( + &self, + error: AppRpcError, + clock: &dyn ValidationFailureClock, + ) -> Option { match self { FailureStatus::RecheckRequired(ValidationStatus::Waiting) => { Some(FailureStatus::RecheckRequired( - ValidationStatus::Reattempting { attempt: 1, error }, + ValidationStatus::Reattempting(PreviousAttempts::new(error.into(), clock)), + )) + } + FailureStatus::RecheckRequired(ValidationStatus::Reattempting(previous_attempts)) => { + Some(FailureStatus::RecheckRequired( + ValidationStatus::Reattempting( + previous_attempts.clone().add_attempt(error.into(), clock), + ), )) } - FailureStatus::RecheckRequired(ValidationStatus::Reattempting { attempt, .. }) => Some( - FailureStatus::RecheckRequired(ValidationStatus::Reattempting { - attempt: attempt + 1, - error, - }), - ), FailureStatus::RetryRequired | FailureStatus::Concluded => None, } } @@ -301,10 +326,6 @@ impl RecheckRequiringFailures { pub fn new() -> Self { Self::default() } - - pub fn hashes(&self) -> &[TxHash] { - todo!() - } } #[derive(Debug, PartialEq, Eq)] @@ -354,21 +375,24 @@ impl From for FailureReason { #[cfg(test)] mod tests { - use crate::accountant::db_access_objects::failed_payable_dao::{ - FailureStatus, ValidationStatus, - }; + use crate::accountant::db_access_objects::failed_payable_dao::FailureStatus; use crate::accountant::db_access_objects::sent_payable_dao::{Detection, TxStatus}; + use crate::accountant::scanners::pending_payable_scanner::test_utils::ValidationFailureClockMock; use crate::accountant::scanners::pending_payable_scanner::utils::{ CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailedValidation, FailedValidationByTable, NormalTxConfirmation, PendingPayableCache, PresortedTxFailure, ReceiptScanReport, RecheckRequiringFailures, Retry, TxBlock, TxHashByTable, TxReceiptError, - TxReceiptResult, TxReclaim, + TxReceiptResult, TxReclaim, ValidationFailureClockReal, }; use crate::accountant::test_utils::{make_failed_tx, make_sent_tx, make_transaction_block}; - use crate::blockchain::errors::{AppRpcError, LocalError, RemoteError}; + use crate::blockchain::errors::{ + AppRpcError, AppRpcErrorKind, LocalError, PreviousAttempts, RemoteError, ValidationStatus, + }; use crate::blockchain::test_utils::make_tx_hash; use masq_lib::logger::Logger; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; + use std::ops::{Add, Sub}; + use std::time::{Duration, SystemTime}; use std::vec; #[test] @@ -411,10 +435,12 @@ mod tests { FailedValidation::new( make_tx_hash(12121), AppRpcError::Remote(RemoteError::InvalidResponse("blah".to_string())), - FailureStatus::RecheckRequired(ValidationStatus::Reattempting { - attempt: 1, - error: AppRpcError::Local(LocalError::Internal), - }), + FailureStatus::RecheckRequired(ValidationStatus::Reattempting( + PreviousAttempts::new( + AppRpcErrorKind::Internal, + &ValidationFailureClockReal::default(), + ), + )), ), )], ]; @@ -493,18 +519,20 @@ mod tests { FailedValidationByTable::SentPayable(FailedValidation::new( make_tx_hash(2222), AppRpcError::Local(LocalError::Internal), - TxStatus::Pending(ValidationStatus::Reattempting { - attempt: 1, - error: AppRpcError::Local(LocalError::Internal), - }), + TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new( + AppRpcErrorKind::Internal, + &ValidationFailureClockReal::default(), + ))), )), FailedValidationByTable::FailedPayable(FailedValidation::new( make_tx_hash(1234), AppRpcError::Remote(RemoteError::Unreachable), - FailureStatus::RecheckRequired(ValidationStatus::Reattempting { - attempt: 1, - error: AppRpcError::Local(LocalError::Internal), - }), + FailureStatus::RecheckRequired(ValidationStatus::Reattempting( + PreviousAttempts::new( + AppRpcErrorKind::Internal, + &ValidationFailureClockReal::default(), + ), + )), )), ], ]; @@ -619,7 +647,7 @@ mod tests { } #[test] - fn pending_payables_cache_insert_and_get_methods_single_record() { + fn pending_payable_cache_insert_and_get_methods_single_record() { let mut subject = CurrentPendingPayables::new(); let sent_tx = make_sent_tx(123); let tx_hash = sent_tx.hash; @@ -641,7 +669,7 @@ mod tests { } #[test] - fn pending_payables_cache_insert_and_get_methods_multiple_records() { + fn pending_payable_cache_insert_and_get_methods_multiple_records() { let mut subject = CurrentPendingPayables::new(); let sent_tx_1 = make_sent_tx(123); let tx_hash_1 = sent_tx_1.hash; @@ -685,9 +713,9 @@ mod tests { } #[test] - fn pending_payables_cache_ensure_empty_happy_path() { + fn pending_payable_cache_ensure_empty_happy_path() { init_test_logging(); - let test_name = "pending_payables_cache_ensure_empty_happy_path"; + let test_name = "pending_payable_cache_ensure_empty_happy_path"; let mut subject = CurrentPendingPayables::new(); let sent_tx = make_sent_tx(567); let tx_hash = sent_tx.hash; @@ -710,9 +738,9 @@ mod tests { } #[test] - fn pending_payables_cache_ensure_empty_sad_path() { + fn pending_payable_cache_ensure_empty_sad_path() { init_test_logging(); - let test_name = "pending_payables_cache_ensure_empty_sad_path"; + let test_name = "pending_payable_cache_ensure_empty_sad_path"; let mut subject = CurrentPendingPayables::new(); let sent_tx = make_sent_tx(567); let tx_hash = sent_tx.hash; @@ -740,7 +768,7 @@ mod tests { } #[test] - fn pending_payables_cache_dump_works() { + fn pending_payable_cache_dump_works() { let mut subject = CurrentPendingPayables::new(); let sent_tx_1 = make_sent_tx(567); let tx_hash_1 = sent_tx_1.hash; @@ -955,7 +983,47 @@ mod tests { } #[test] - fn failed_validation_new_status_works_fine() { + #[should_panic( + expected = "Mismatch in the type of tx record (failed tx) and status type \ + (TxStatus)" + )] + fn tx_status_mismatch_in_conversion_to_failed_validation_by_table() { + let tx_hash = make_tx_hash(123); + let api_error = AppRpcError::Local(LocalError::Internal); + let mismatched_receipt_error = + TxReceiptError::new(TxHashByTable::FailedPayable(tx_hash), api_error); + + let _ = FailedValidationByTable::from(( + mismatched_receipt_error, + TxStatus::Pending(ValidationStatus::Waiting), + )); + } + + #[test] + #[should_panic( + expected = "Mismatch in the type of tx record (sent tx) and status type \ + (FailureStatus)" + )] + fn tx_status_mismatch_in_conversion_to_failed_validation_by_table_2() { + let tx_hash = make_tx_hash(123); + let api_error = AppRpcError::Local(LocalError::Internal); + let mismatched_receipt_error = + TxReceiptError::new(TxHashByTable::SentPayable(tx_hash), api_error); + + let _ = FailedValidationByTable::from(( + mismatched_receipt_error, + FailureStatus::RecheckRequired(ValidationStatus::Waiting), + )); + } + + #[test] + fn failed_validation_new_status_works_for_tx_statuses() { + 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 validation_failure_clock = ValidationFailureClockMock::default() + .now_result(timestamp_a) + .now_result(timestamp_c); let mal_validated_tx_statuses = vec![ ( FailedValidation::new( @@ -963,26 +1031,63 @@ mod tests { AppRpcError::Local(LocalError::Internal), TxStatus::Pending(ValidationStatus::Waiting), ), - Some(TxStatus::Pending(ValidationStatus::Reattempting { - attempt: 1, - error: AppRpcError::Local(LocalError::Internal), - })), + Some(TxStatus::Pending(ValidationStatus::Reattempting( + PreviousAttempts::new( + AppRpcErrorKind::Internal, + &ValidationFailureClockMock::default().now_result(timestamp_a), + ), + ))), ), ( FailedValidation::new( make_tx_hash(123), AppRpcError::Remote(RemoteError::Unreachable), - TxStatus::Pending(ValidationStatus::Reattempting { - attempt: 2, - error: AppRpcError::Local(LocalError::Internal), - }), + TxStatus::Pending(ValidationStatus::Reattempting( + PreviousAttempts::new( + AppRpcErrorKind::Internal, + &ValidationFailureClockMock::default().now_result(timestamp_b), + ) + .add_attempt( + AppRpcErrorKind::Internal, + &ValidationFailureClockReal::default(), + ), + )), ), - Some(TxStatus::Pending(ValidationStatus::Reattempting { - attempt: 3, - error: AppRpcError::Remote(RemoteError::Unreachable), - })), + Some(TxStatus::Pending(ValidationStatus::Reattempting( + PreviousAttempts::new( + AppRpcErrorKind::ServerUnreachable, + &ValidationFailureClockMock::default().now_result(timestamp_c), + ) + .add_attempt( + AppRpcErrorKind::Internal, + &ValidationFailureClockMock::default().now_result(timestamp_b), + ) + .add_attempt( + AppRpcErrorKind::Internal, + &ValidationFailureClockReal::default(), + ), + ))), ), ]; + + mal_validated_tx_statuses.into_iter().for_each( + |(failed_validation, expected_tx_status)| { + assert_eq!( + failed_validation.new_status(&validation_failure_clock), + expected_tx_status + ); + }, + ); + } + + #[test] + fn failed_validation_new_status_works_for_failure_statuses() { + 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 validation_failure_clock = ValidationFailureClockMock::default() + .now_result(timestamp_a) + .now_result(timestamp_b); let mal_validated_failure_statuses = vec![ ( FailedValidation::new( @@ -991,44 +1096,59 @@ mod tests { FailureStatus::RecheckRequired(ValidationStatus::Waiting), ), Some(FailureStatus::RecheckRequired( - ValidationStatus::Reattempting { - attempt: 1, - error: AppRpcError::Local(LocalError::Internal), - }, + ValidationStatus::Reattempting(PreviousAttempts::new( + AppRpcErrorKind::Internal, + &ValidationFailureClockMock::default().now_result(timestamp_a), + )), )), ), ( FailedValidation::new( make_tx_hash(456), AppRpcError::Remote(RemoteError::Unreachable), - FailureStatus::RecheckRequired(ValidationStatus::Reattempting { - attempt: 2, - error: AppRpcError::Remote(RemoteError::Unreachable), - }), + FailureStatus::RecheckRequired(ValidationStatus::Reattempting( + PreviousAttempts::new( + AppRpcErrorKind::ServerUnreachable, + &ValidationFailureClockMock::default().now_result(timestamp_b), + ) + .add_attempt( + AppRpcErrorKind::InvalidResponse, + &ValidationFailureClockMock::default().now_result(timestamp_c), + ), + )), ), Some(FailureStatus::RecheckRequired( - ValidationStatus::Reattempting { - attempt: 3, - error: AppRpcError::Remote(RemoteError::Unreachable), - }, + ValidationStatus::Reattempting( + PreviousAttempts::new( + AppRpcErrorKind::ServerUnreachable, + &ValidationFailureClockMock::default().now_result(timestamp_b), + ) + .add_attempt( + AppRpcErrorKind::InvalidResponse, + &ValidationFailureClockMock::default().now_result(timestamp_c), + ) + .add_attempt( + AppRpcErrorKind::ServerUnreachable, + &ValidationFailureClockReal::default(), + ), + ), )), ), ]; - mal_validated_tx_statuses.into_iter().for_each( - |(failed_validation, expected_tx_status)| { - assert_eq!(failed_validation.new_status(), expected_tx_status); - }, - ); mal_validated_failure_statuses.into_iter().for_each( |(failed_validation, expected_failed_tx_status)| { - assert_eq!(failed_validation.new_status(), expected_failed_tx_status); + assert_eq!( + failed_validation.new_status(&validation_failure_clock), + expected_failed_tx_status + ); }, ) } #[test] - fn failed_validation_new_status_has_no_effect_on_unexpected_current_status() { + fn failed_validation_new_status_has_no_effect_on_unexpected_tx_status() { + let validation_failure_clock = ValidationFailureClockMock::default(); let mal_validated_tx_status = FailedValidation::new( make_tx_hash(123), AppRpcError::Local(LocalError::Internal), @@ -1038,6 +1158,16 @@ mod tests { detection: Detection::Normal, }, ); + + assert_eq!( + mal_validated_tx_status.new_status(&validation_failure_clock), + None + ); + } + + #[test] + fn failed_validation_new_status_has_no_effect_on_unexpected_failure_status() { + let validation_failure_clock = ValidationFailureClockMock::default(); let mal_validated_failure_statuses = vec![ FailedValidation::new( make_tx_hash(456), @@ -1051,12 +1181,11 @@ mod tests { ), ]; - assert_eq!(mal_validated_tx_status.new_status(), None); mal_validated_failure_statuses .into_iter() .enumerate() .for_each(|(idx, failed_validation)| { - let result = failed_validation.new_status(); + let result = failed_validation.new_status(&validation_failure_clock); assert_eq!( result, None, "Failed validation should evaluate to 'None' but was '{:?}' for idx: {}", diff --git a/node/src/accountant/scanners/receivable_scanner/mod.rs b/node/src/accountant/scanners/receivable_scanner/mod.rs index b7222df0d..eff0df95e 100644 --- a/node/src/accountant/scanners/receivable_scanner/mod.rs +++ b/node/src/accountant/scanners/receivable_scanner/mod.rs @@ -111,7 +111,7 @@ impl ReceivableScanner { { Ok(()) => debug!(logger, "Start block updated to {}", start_block_number), Err(e) => panic!( - "Attempt to set new start block to {} failed due to: {:?}", + "Attempt to advance the start block to {} failed due to: {:?}", start_block_number, e ), } diff --git a/node/src/accountant/scanners/scanners_utils.rs b/node/src/accountant/scanners/scanners_utils.rs index dcaa99a10..e18e5f249 100644 --- a/node/src/accountant/scanners/scanners_utils.rs +++ b/node/src/accountant/scanners/scanners_utils.rs @@ -339,9 +339,9 @@ mod tests { use masq_lib::constants::WEIS_IN_GWEI; use masq_lib::logger::Logger; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; - use std::time::{Duration, SystemTime}; + use std::time::{SystemTime}; use itertools::Itertools; - use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, FailureReason, FailureStatus, ValidationStatus}; + use crate::accountant::db_access_objects::failed_payable_dao::{FailureReason}; use crate::assert_on_testing_enum_with_all_its_variants; use crate::blockchain::blockchain_interface::data_structures::errors::{BlockchainError, PayableTransactionError}; use crate::blockchain::blockchain_interface::data_structures::{BlockchainTxFailure, ProcessedPayableFallible, RpcPayableFailure}; diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs index 916de0300..8fe1fcca3 100644 --- a/node/src/accountant/test_utils.rs +++ b/node/src/accountant/test_utils.rs @@ -5,7 +5,7 @@ use crate::accountant::db_access_objects::banned_dao::{BannedDao, BannedDaoFactory}; use crate::accountant::db_access_objects::failed_payable_dao::{ FailedPayableDao, FailedPayableDaoError, FailedPayableDaoFactory, FailedTx, FailureReason, - FailureRetrieveCondition, FailureStatus, ValidationStatus, + FailureRetrieveCondition, FailureStatus, }; use crate::accountant::db_access_objects::payable_dao::{ MarkPendingPayableID, PayableAccount, PayableDao, PayableDaoError, PayableDaoFactory, @@ -28,7 +28,10 @@ use crate::accountant::scanners::payable_scanner_extension::msgs::{ QualifiedPayablesBeforeGasPriceSelection, UnpricedQualifiedPayables, }; use crate::accountant::scanners::payable_scanner_extension::PreparedAdjustment; -use crate::accountant::scanners::pending_payable_scanner::utils::PendingPayableCache; +use crate::accountant::scanners::pending_payable_scanner::test_utils::ValidationFailureClockMock; +use crate::accountant::scanners::pending_payable_scanner::utils::{ + PendingPayableCache, ValidationFailureClock, ValidationFailureClockReal, +}; use crate::accountant::scanners::pending_payable_scanner::PendingPayableScanner; use crate::accountant::scanners::receivable_scanner::ReceivableScanner; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableThresholdsGauge; @@ -36,6 +39,7 @@ use crate::accountant::scanners::test_utils::PendingPayableCacheMock; use crate::accountant::scanners::PayableScanner; use crate::accountant::{gwei_to_wei, Accountant, DEFAULT_PENDING_TOO_LONG_SEC}; use crate::blockchain::blockchain_interface::data_structures::{BlockchainTransaction, TxBlock}; +use crate::blockchain::errors::ValidationStatus; use crate::blockchain::test_utils::{make_block_hash, make_tx_hash}; use crate::bootstrapper::BootstrapperConfig; use crate::database::rusqlite_wrappers::TransactionSafeWrapper; @@ -1379,6 +1383,7 @@ pub struct PendingPayableScannerBuilder { financial_statistics: FinancialStatistics, current_sent_payables: Box>, yet_unproven_failed_payables: Box>, + validation_failure_clock: Box, } impl PendingPayableScannerBuilder { @@ -1391,6 +1396,7 @@ impl PendingPayableScannerBuilder { financial_statistics: FinancialStatistics::default(), current_sent_payables: Box::new(PendingPayableCacheMock::default()), yet_unproven_failed_payables: Box::new(PendingPayableCacheMock::default()), + validation_failure_clock: Box::new(ValidationFailureClockMock::default()), } } @@ -1409,12 +1415,12 @@ impl PendingPayableScannerBuilder { self } - pub fn pending_payables_cache(mut self, cache: Box>) -> Self { + pub fn pending_payable_cache(mut self, cache: Box>) -> Self { self.current_sent_payables = cache; self } - pub fn failed_payables_cache( + pub fn failed_payable_cache( mut self, failures: Box>, ) -> Self { @@ -1422,6 +1428,11 @@ impl PendingPayableScannerBuilder { self } + pub fn validation_failure_clock(mut self, clock: Box) -> Self { + self.validation_failure_clock = clock; + self + } + pub fn build(mut self) -> PendingPayableScanner { let mut scanner = PendingPayableScanner::new( Box::new(self.payable_dao), @@ -1430,11 +1441,9 @@ impl PendingPayableScannerBuilder { Rc::new(self.payment_thresholds), Rc::new(RefCell::new(self.financial_statistics)), ); - scanner.current_sent_payables = self.current_sent_payables; - scanner.yet_unproven_failed_payables = self.yet_unproven_failed_payables; - + scanner.validation_failure_clock = self.validation_failure_clock; scanner } } diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index 33c07e732..67f8f9e28 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -532,7 +532,6 @@ impl SubsFactory for BlockchainBridgeSub #[cfg(test)] mod tests { use super::*; - use crate::accountant::db_access_objects::failed_payable_dao::ValidationStatus; use crate::accountant::db_access_objects::payable_dao::PayableAccount; use crate::accountant::db_access_objects::sent_payable_dao::TxStatus; use crate::accountant::db_access_objects::utils::{from_unix_timestamp, to_unix_timestamp}; @@ -553,7 +552,7 @@ mod tests { BlockchainTransaction, RetrievedBlockchainTransactions, RetrievedTxStatus, TxBlock, TxReceiptError, }; - use crate::blockchain::errors::{AppRpcError, RemoteError}; + use crate::blockchain::errors::{AppRpcError, RemoteError, ValidationStatus}; use crate::blockchain::test_utils::{ make_blockchain_interface_web3, make_tx_hash, ReceiptResponseBuilder, }; diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs index 95b3cf407..c9dfed24f 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs @@ -1,6 +1,5 @@ // Copyright (c) 2024, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::db_access_objects::failed_payable_dao::ValidationStatus; use crate::accountant::db_access_objects::payable_dao::PayableAccount; use crate::accountant::db_access_objects::sent_payable_dao::{SentTx, TxStatus}; use crate::accountant::db_access_objects::utils::{to_unix_timestamp, TxHash}; @@ -16,6 +15,7 @@ use crate::blockchain::blockchain_interface::data_structures::errors::PayableTra use crate::blockchain::blockchain_interface::data_structures::{ ProcessedPayableFallible, RpcPayableFailure, }; +use crate::blockchain::errors::ValidationStatus; use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; use crate::sub_lib::wallet::Wallet; use actix::Recipient; diff --git a/node/src/blockchain/errors.rs b/node/src/blockchain/errors.rs index 865bea29c..e751474ae 100644 --- a/node/src/blockchain/errors.rs +++ b/node/src/blockchain/errors.rs @@ -1,5 +1,10 @@ +use crate::accountant::scanners::pending_payable_scanner::utils::ValidationFailureClock; +use serde::ser::SerializeStruct; use serde_derive::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::time::SystemTime; use web3::error::Error as Web3Error; +use websocket::url::quirks::hash; // Prefixed with App to clearly distinguish app-specific errors from library errors. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -24,6 +29,92 @@ pub enum RemoteError { Web3RpcError { code: i64, message: String }, } +#[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum AppRpcErrorKind { + // Local + Decoder, + Internal, + IO, + Signing, + Transport, + + // Remote + InvalidResponse, + ServerUnreachable, + Web3RpcError(i64), // Keep only the stable error code +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ErrorStats { + #[serde(rename = "firstSeen")] + pub first_seen: SystemTime, + pub attempts: u16, +} + +impl ErrorStats { + pub fn now(clock: &dyn ValidationFailureClock) -> Self { + Self { + first_seen: clock.now(), + attempts: 1, + } + } + + pub fn increment(&mut self) { + self.attempts += 1; + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PreviousAttempts { + #[serde(flatten)] + inner: HashMap, +} + +impl PreviousAttempts { + pub fn new(error: AppRpcErrorKind, clock: &dyn ValidationFailureClock) -> Self { + Self { + inner: hashmap!(error => ErrorStats::now(clock)), + } + } + + pub fn add_attempt( + mut self, + error: AppRpcErrorKind, + clock: &dyn ValidationFailureClock, + ) -> Self { + self.inner + .entry(error) + .and_modify(|stats| stats.increment()) + .or_insert_with(|| ErrorStats::now(clock)); + self + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum ValidationStatus { + Waiting, + Reattempting(PreviousAttempts), +} + +impl From for AppRpcErrorKind { + fn from(err: AppRpcError) -> Self { + match err { + AppRpcError::Local(local) => match local { + LocalError::Decoder(_) => Self::Decoder, + LocalError::Internal => Self::Internal, + LocalError::Io(_) => Self::IO, + LocalError::Signing(_) => Self::Signing, + LocalError::Transport(_) => Self::Transport, + }, + AppRpcError::Remote(remote) => match remote { + RemoteError::InvalidResponse(_) => Self::InvalidResponse, + RemoteError::Unreachable => Self::ServerUnreachable, + RemoteError::Web3RpcError { code, .. } => Self::Web3RpcError(code), + }, + } + } +} + // EVM based errors impl From for AppRpcError { fn from(error: Web3Error) -> Self { @@ -51,8 +142,13 @@ impl From for AppRpcError { } } +#[cfg(test)] mod tests { - use crate::blockchain::errors::{AppRpcError, LocalError, RemoteError}; + use crate::accountant::scanners::pending_payable_scanner::utils::ValidationFailureClockReal; + use crate::blockchain::errors::{ + AppRpcError, AppRpcErrorKind, LocalError, PreviousAttempts, RemoteError, + }; + use std::time::SystemTime; use web3::error::Error as Web3Error; #[test] @@ -124,4 +220,99 @@ mod tests { assert_eq!(error, deserialized, "Error: {:?}", error); }); } + + #[test] + fn previous_attempts_and_validation_failure_clock_work_together_fine() { + let validation_failure_clock = ValidationFailureClockReal::default(); + // new() + let timestamp_a = SystemTime::now(); + let subject = PreviousAttempts::new(AppRpcErrorKind::Decoder, &validation_failure_clock); + // add_attempt() + let timestamp_b = SystemTime::now(); + let subject = subject.add_attempt(AppRpcErrorKind::Internal, &validation_failure_clock); + let timestamp_c = SystemTime::now(); + let subject = subject.add_attempt(AppRpcErrorKind::IO, &validation_failure_clock); + let timestamp_d = SystemTime::now(); + let subject = subject.add_attempt(AppRpcErrorKind::Decoder, &validation_failure_clock); + let subject = subject.add_attempt(AppRpcErrorKind::IO, &validation_failure_clock); + + let decoder_error_stats = subject.inner.get(&AppRpcErrorKind::Decoder).unwrap(); + assert!( + timestamp_a <= decoder_error_stats.first_seen + && decoder_error_stats.first_seen <= timestamp_b, + "Was expected from {:?} to {:?} but was {:?}", + timestamp_a, + timestamp_b, + decoder_error_stats.first_seen + ); + assert_eq!(decoder_error_stats.attempts, 2); + let internal_error_stats = subject.inner.get(&AppRpcErrorKind::Internal).unwrap(); + assert!( + timestamp_b <= internal_error_stats.first_seen + && internal_error_stats.first_seen <= timestamp_c, + "Was expected from {:?} to {:?} but was {:?}", + timestamp_b, + timestamp_c, + internal_error_stats.first_seen + ); + assert_eq!(internal_error_stats.attempts, 1); + let io_error_stats = subject.inner.get(&AppRpcErrorKind::IO).unwrap(); + assert!( + timestamp_c <= io_error_stats.first_seen && io_error_stats.first_seen <= timestamp_d, + "Was expected from {:?} to {:?} but was {:?}", + timestamp_c, + timestamp_d, + io_error_stats.first_seen + ); + assert_eq!(io_error_stats.attempts, 2); + let other_error_stats = subject.inner.get(&AppRpcErrorKind::Signing); + assert_eq!(other_error_stats, None); + } + + #[test] + fn conversion_between_app_rpc_error_and_app_rpc_error_kind_works() { + assert_eq!( + AppRpcErrorKind::from(AppRpcError::Local(LocalError::Decoder( + "Decoder error".to_string() + ))), + AppRpcErrorKind::Decoder + ); + assert_eq!( + AppRpcErrorKind::from(AppRpcError::Local(LocalError::Internal)), + AppRpcErrorKind::Internal + ); + assert_eq!( + AppRpcErrorKind::from(AppRpcError::Local(LocalError::Io("IO error".to_string()))), + AppRpcErrorKind::IO + ); + assert_eq!( + AppRpcErrorKind::from(AppRpcError::Local(LocalError::Signing( + "Signing error".to_string() + ))), + AppRpcErrorKind::Signing + ); + assert_eq!( + AppRpcErrorKind::from(AppRpcError::Local(LocalError::Transport( + "Transport error".to_string() + ))), + AppRpcErrorKind::Transport + ); + assert_eq!( + AppRpcErrorKind::from(AppRpcError::Remote(RemoteError::InvalidResponse( + "Invalid response".to_string() + ))), + AppRpcErrorKind::InvalidResponse + ); + assert_eq!( + AppRpcErrorKind::from(AppRpcError::Remote(RemoteError::Unreachable)), + AppRpcErrorKind::ServerUnreachable + ); + assert_eq!( + AppRpcErrorKind::from(AppRpcError::Remote(RemoteError::Web3RpcError { + code: 55, + message: "Booga".to_string() + })), + AppRpcErrorKind::Web3RpcError(55) + ); + } } From 3f102e716a336d4a2fd012c413fff7594774533c Mon Sep 17 00:00:00 2001 From: Bert Date: Fri, 15 Aug 2025 10:31:29 +0200 Subject: [PATCH 35/61] GH-642: interim commit (some of the Validation error stuff will have to be fixed) --- .../db_access_objects/failed_payable_dao.rs | 1 + .../db_access_objects/sent_payable_dao.rs | 55 ++++++++++++--- node/src/accountant/mod.rs | 23 +++++-- node/src/accountant/scanners/mod.rs | 6 +- .../scanners/pending_payable_scanner/mod.rs | 29 +++++--- .../tx_receipt_interpreter.rs | 52 +++++++++----- .../scanners/pending_payable_scanner/utils.rs | 60 +++++++++------- node/src/accountant/test_utils.rs | 2 +- node/src/blockchain/errors.rs | 68 +++++++++++++------ 9 files changed, 202 insertions(+), 94 deletions(-) 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 b3185d2dd..d4110f898 100644 --- a/node/src/accountant/db_access_objects/failed_payable_dao.rs +++ b/node/src/accountant/db_access_objects/failed_payable_dao.rs @@ -851,6 +851,7 @@ mod tests { updated_txs[3].status, RecheckRequired(ValidationStatus::Waiting) ); + assert_eq!(updated_txs.len(), 4); } #[test] 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 760e33f5e..c193dd51c 100644 --- a/node/src/accountant/db_access_objects/sent_payable_dao.rs +++ b/node/src/accountant/db_access_objects/sent_payable_dao.rs @@ -1,6 +1,6 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::db_access_objects::failed_payable_dao::FailedTx; +use crate::accountant::db_access_objects::failed_payable_dao::{FailedPayableDaoError, FailedTx}; use crate::accountant::db_access_objects::utils::{ DaoFactoryReal, TxHash, TxIdentifiers, TxRecordWithHash, }; @@ -405,9 +405,43 @@ impl SentPayableDao for SentPayableDaoReal<'_> { fn update_statuses( &self, - hash_map: &HashMap, + status_updates: &HashMap, ) -> Result<(), SentPayableDaoError> { - todo!() + if status_updates.is_empty() { + return Err(SentPayableDaoError::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 sent_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 == status_updates.len() { + Ok(()) + } else { + Err(SentPayableDaoError::PartialExecution(format!( + "Only {} of {} records had their status updated.", + rows_changed, + status_updates.len(), + ))) + } + } + Err(e) => Err(SentPayableDaoError::SqlExecutionFailed(e.to_string())), + } } fn delete_records(&self, hashes: &HashSet) -> Result<(), SentPayableDaoError> { @@ -1062,7 +1096,7 @@ mod tests { let wrapped_conn = DbInitializerReal::default() .initialize(&home_dir, DbInitializationConfig::test_default()) .unwrap(); - let timestamp_a = SystemTime::now(); + let timestamp_a = SystemTime::now().sub(Duration::from_millis(11)); let timestamp_b = SystemTime::now().sub(Duration::from_millis(1234)); let subject = SentPayableDaoReal::new(wrapped_conn); let mut tx1 = make_sent_tx(456); @@ -1070,7 +1104,7 @@ mod tests { let mut tx2 = make_sent_tx(789); tx2.status = TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new( AppRpcErrorKind::ServerUnreachable, - &ValidationFailureClockMock::default().now_result(timestamp_a), + &ValidationFailureClockMock::default().now_result(timestamp_b), ))); let mut tx3 = make_sent_tx(123); tx3.status = TxStatus::Pending(ValidationStatus::Waiting); @@ -1082,7 +1116,7 @@ mod tests { tx1.hash, TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new( AppRpcErrorKind::Internal, - &ValidationFailureClockMock::default().now_result(timestamp_b), + &ValidationFailureClockMock::default().now_result(timestamp_a), ))), ), ( @@ -1090,7 +1124,7 @@ mod tests { TxStatus::Pending(ValidationStatus::Reattempting( PreviousAttempts::new( AppRpcErrorKind::ServerUnreachable, - &ValidationFailureClockReal::default(), + &ValidationFailureClockMock::default().now_result(timestamp_b), ) .add_attempt( AppRpcErrorKind::ServerUnreachable, @@ -1118,7 +1152,7 @@ mod tests { updated_txs[0].status, TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new( AppRpcErrorKind::Internal, - &ValidationFailureClockMock::default().now_result(timestamp_b) + &ValidationFailureClockMock::default().now_result(timestamp_a) ))) ); assert_eq!( @@ -1126,7 +1160,7 @@ mod tests { TxStatus::Pending(ValidationStatus::Reattempting( PreviousAttempts::new( AppRpcErrorKind::ServerUnreachable, - &ValidationFailureClockMock::default().now_result(timestamp_a) + &ValidationFailureClockMock::default().now_result(timestamp_b) ) .add_attempt( AppRpcErrorKind::ServerUnreachable, @@ -1143,6 +1177,7 @@ mod tests { detection: Detection::Normal, } ); + assert_eq!(updated_txs.len(), 3) } #[test] @@ -1374,7 +1409,7 @@ mod tests { let validation_failure_clock = ValidationFailureClockMock::default() .now_result(UNIX_EPOCH.add(Duration::from_secs(12456))); assert_eq!( - TxStatus::from_str(r#"{"Pending":{"Reattempting":{"InvalidResponse":{"secs_since_epoch":12456,"nanos_since_epoch":0},"attempts":1}}}"#).unwrap(), + TxStatus::from_str(r#"{"Pending":{"Reattempting":{"InvalidResponse":{"firstSeen":{"secs_since_epoch":12456,"nanos_since_epoch":0},"attempts":1}}}}"#).unwrap(), TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new(AppRpcErrorKind::InvalidResponse, &validation_failure_clock))) ); diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index 8f3e71d2d..820c078a0 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -1247,8 +1247,8 @@ mod tests { OperationOutcome, PayableScanResult, }; use crate::accountant::scanners::test_utils::{ - MarkScanner, NewPayableScanDynIntervalComputerMock, ReplacementType, - RescheduleScanOnErrorResolverMock, ScannerMock, ScannerReplacement, + MarkScanner, NewPayableScanDynIntervalComputerMock, PendingPayableCacheMock, + ReplacementType, RescheduleScanOnErrorResolverMock, ScannerMock, ScannerReplacement, }; use crate::accountant::scanners::StartScanError; use crate::accountant::test_utils::DaoWithDestination::{ @@ -1272,6 +1272,7 @@ mod tests { BlockchainTxFailure, RetrievedTxStatus, StatusReadFromReceiptCheck, TxBlock, TxReceiptResult, }; + use crate::blockchain::errors::ValidationStatus; use crate::blockchain::test_utils::make_tx_hash; use crate::database::rusqlite_wrappers::TransactionSafeWrapper; use crate::database::test_utils::transaction_wrapper_mock::TransactionInnerWrapperMockBuilder; @@ -1937,10 +1938,21 @@ mod tests { .transactions_confirmed_params(&transaction_confirmed_params_arc) .transactions_confirmed_result(Ok(())); let sent_payable_dao = SentPayableDaoMock::default().confirm_tx_result(Ok(())); - let mut subject = AccountantBuilder::default() - .payable_daos(vec![ForPendingPayableScanner(payable_dao)]) - .sent_payable_daos(vec![ForPendingPayableScanner(sent_payable_dao)]) + let mut subject = AccountantBuilder::default().build(); + let mut sent_tx = make_sent_tx(123); + sent_tx.status = TxStatus::Pending(ValidationStatus::Waiting); + let sent_payable_cache = + PendingPayableCacheMock::default().get_record_by_hash_result(Some(sent_tx.clone())); + let pending_payable_scanner = PendingPayableScannerBuilder::new() + .payable_dao(payable_dao) + .sent_payable_dao(sent_payable_dao) + .sent_payable_cache(Box::new(sent_payable_cache)) .build(); + subject + .scanners + .replace_scanner(ScannerReplacement::PendingPayable(ReplacementType::Real( + pending_payable_scanner, + ))); let (ui_gateway, _, ui_gateway_recording_arc) = make_recorder(); let ui_gateway = ui_gateway.system_stop_conditions(match_lazily_every_type_id!(NodeToUiMessage)); @@ -1956,7 +1968,6 @@ mod tests { Box::new(NotifyHandleMock::default().panic_on_schedule_attempt()); subject.ui_message_sub_opt = Some(ui_gateway_addr.recipient()); let subject_addr = subject.start(); - let mut sent_tx = make_sent_tx(123); let tx_block = TxBlock { block_hash: make_tx_hash(456), block_number: 78901234.into(), diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index 8c53957d0..4b6e5e60d 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -2396,7 +2396,7 @@ mod tests { let pending_payable_scanner = PendingPayableScannerBuilder::new() .sent_payable_dao(sent_payable_dao) .failed_payable_dao(failed_payable_dao) - .pending_payable_cache(Box::new(CurrentPendingPayables::default())) + .sent_payable_cache(Box::new(CurrentPendingPayables::default())) .failed_payable_cache(Box::new(RecheckRequiringFailures::default())) .build(); // Important @@ -2443,7 +2443,7 @@ mod tests { let pending_payable_scanner = PendingPayableScannerBuilder::new() .sent_payable_dao(sent_payable_dao) .failed_payable_dao(failed_payable_dao) - .pending_payable_cache(Box::new(CurrentPendingPayables::default())) + .sent_payable_cache(Box::new(CurrentPendingPayables::default())) .failed_payable_cache(Box::new(RecheckRequiringFailures::default())) .build(); // Important @@ -2703,7 +2703,7 @@ mod tests { .payable_dao(payable_dao) .sent_payable_dao(sent_payable_dao) .failed_payable_dao(failed_payable_dao) - .pending_payable_cache(Box::new(pending_payable_cache)) + .sent_payable_cache(Box::new(pending_payable_cache)) .failed_payable_cache(Box::new(failed_payable_cache)) .validation_failure_clock(Box::new(validation_failure_clock)) .build(); diff --git a/node/src/accountant/scanners/pending_payable_scanner/mod.rs b/node/src/accountant/scanners/pending_payable_scanner/mod.rs index 107459000..abd4971b5 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/mod.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/mod.rs @@ -801,7 +801,7 @@ mod tests { use crate::accountant::scanners::pending_payable_scanner::test_utils::ValidationFailureClockMock; use crate::accountant::scanners::pending_payable_scanner::utils::{ CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailedValidation, - FailedValidationByTable, NormalTxConfirmation, PendingPayableCache, + FailedValidationByTable, FailedValidationError, NormalTxConfirmation, PendingPayableCache, PendingPayableScanResult, PresortedTxFailure, RecheckRequiringFailures, Retry, TxHashByTable, TxReclaim, ValidationFailureClockReal, }; @@ -820,6 +820,7 @@ mod tests { AppRpcError, AppRpcErrorKind, LocalError, PreviousAttempts, RemoteError, ValidationStatus, }; use crate::blockchain::test_utils::{make_block_hash, make_tx_hash}; + use crate::neighborhood::overall_connection_status::ConnectionStage::Failed; use crate::test_utils::{make_paying_wallet, make_wallet}; use itertools::Itertools; use masq_lib::logger::Logger; @@ -848,7 +849,7 @@ mod tests { let mut subject = PendingPayableScannerBuilder::new() .sent_payable_dao(sent_payable_dao) .failed_payable_dao(failed_payable_dao) - .pending_payable_cache(Box::new(CurrentPendingPayables::default())) + .sent_payable_cache(Box::new(CurrentPendingPayables::default())) .failed_payable_cache(Box::new(RecheckRequiringFailures::default())) .build(); let logger = Logger::new("start_scan_fills_in_caches_and_returns_msg"); @@ -927,7 +928,7 @@ mod tests { .payable_dao(payable_dao) .sent_payable_dao(sent_payable_dao) .failed_payable_dao(failed_payable_dao) - .pending_payable_cache(Box::new(pending_payable_cache)) + .sent_payable_cache(Box::new(pending_payable_cache)) .failed_payable_cache(Box::new(failed_payable_cache)) .build(); let logger = Logger::new("test"); @@ -1238,12 +1239,12 @@ mod tests { tx_receipt_rpc_failures: vec![ FailedValidationByTable::FailedPayable(FailedValidation::new( hash_1, - AppRpcError::Remote(RemoteError::Unreachable), + FailedValidationError::Known(AppRpcError::Remote(RemoteError::Unreachable)), FailureStatus::RecheckRequired(ValidationStatus::Waiting), )), FailedValidationByTable::FailedPayable(FailedValidation::new( hash_2, - AppRpcError::Local(LocalError::Internal), + FailedValidationError::Known(AppRpcError::Local(LocalError::Internal)), FailureStatus::RecheckRequired(ValidationStatus::Reattempting( PreviousAttempts::new( AppRpcErrorKind::Internal, @@ -1253,7 +1254,9 @@ mod tests { )), FailedValidationByTable::SentPayable(FailedValidation::new( hash_3, - AppRpcError::Remote(RemoteError::InvalidResponse("Booga".to_string())), + FailedValidationError::Known(AppRpcError::Remote( + RemoteError::InvalidResponse("Booga".to_string()), + )), TxStatus::Pending(ValidationStatus::Waiting), )), ], @@ -1325,12 +1328,14 @@ mod tests { vec![ FailedValidationByTable::FailedPayable(FailedValidation::new( hash_1, - AppRpcError::Remote(RemoteError::Unreachable), + FailedValidationError::Known(AppRpcError::Remote(RemoteError::Unreachable)), FailureStatus::RetryRequired, )), FailedValidationByTable::SentPayable(FailedValidation::new( hash_2, - AppRpcError::Remote(RemoteError::InvalidResponse("Booga".to_string())), + FailedValidationError::Known(AppRpcError::Remote( + RemoteError::InvalidResponse("Booga".to_string()), + )), TxStatus::Confirmed { block_hash: "abc".to_string(), block_number: 0, @@ -1366,13 +1371,14 @@ mod tests { fn update_validation_status_for_sent_txs_panics_on_update_statuses() { let failed_validation = FailedValidation::new( make_tx_hash(456), - AppRpcError::Local(LocalError::Internal), + FailedValidationError::Known(AppRpcError::Local(LocalError::Internal)), TxStatus::Pending(ValidationStatus::Waiting), ); let sent_payable_dao = SentPayableDaoMock::default() .update_statuses_result(Err(SentPayableDaoError::InvalidInput("bluh".to_string()))); let subject = PendingPayableScannerBuilder::new() .sent_payable_dao(sent_payable_dao) + .validation_failure_clock(Box::new(ValidationFailureClockReal::default())) .build(); let _ = subject @@ -1389,13 +1395,14 @@ mod tests { fn update_validation_status_for_failed_txs_panics_on_update_statuses() { let failed_validation = FailedValidation::new( make_tx_hash(456), - AppRpcError::Local(LocalError::Internal), + FailedValidationError::Known(AppRpcError::Local(LocalError::Internal)), FailureStatus::RecheckRequired(ValidationStatus::Waiting), ); let failed_payable_dao = FailedPayableDaoMock::default() .update_statuses_result(Err(FailedPayableDaoError::InvalidInput("bluh".to_string()))); let subject = PendingPayableScannerBuilder::new() .failed_payable_dao(failed_payable_dao) + .validation_failure_clock(Box::new(ValidationFailureClockReal::default())) .build(); let _ = subject @@ -1434,7 +1441,7 @@ mod tests { tx_receipt_rpc_failures: vec![FailedValidationByTable::SentPayable( FailedValidation::new( tx_hash_2, - AppRpcError::Local(LocalError::Internal), + FailedValidationError::Known(AppRpcError::Local(LocalError::Internal)), TxStatus::Pending(ValidationStatus::Waiting), ), )], 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 c1c876950..8f846182d 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 @@ -6,8 +6,8 @@ use crate::accountant::db_access_objects::sent_payable_dao::{ }; use crate::accountant::db_access_objects::utils::from_unix_timestamp; use crate::accountant::scanners::pending_payable_scanner::utils::{ - FailedValidationByTable, NormalTxConfirmation, PresortedTxFailure, ReceiptScanReport, - TxByTable, TxCaseToBeInterpreted, TxReclaim, + FailedValidation, FailedValidationByTable, FailedValidationError, NormalTxConfirmation, + PresortedTxFailure, ReceiptScanReport, TxByTable, TxCaseToBeInterpreted, TxReclaim, }; use crate::accountant::scanners::pending_payable_scanner::PendingPayableScanner; use crate::blockchain::blockchain_interface::data_structures::{ @@ -97,7 +97,17 @@ impl TxReceiptInterpreter { failed_tx.hash )) .hash - ) + ); + if failed_tx.reason != FailureReason::PendingTooLong { + todo!("panic here") + } + scan_report.register_rpc_failure(FailedValidationByTable::FailedPayable( + FailedValidation::new( + failed_tx.hash, + FailedValidationError::TxResubmissionFailed, + failed_tx.status, + ), + )) } } scan_report @@ -225,8 +235,8 @@ mod tests { use crate::accountant::scanners::pending_payable_scanner::tx_receipt_interpreter::TxReceiptInterpreter; use crate::accountant::scanners::pending_payable_scanner::utils::{ DetectedConfirmations, DetectedFailures, FailedValidation, FailedValidationByTable, - NormalTxConfirmation, PresortedTxFailure, ReceiptScanReport, RecheckRequiringFailures, - TxByTable, TxHashByTable, TxReclaim, ValidationFailureClockReal, + FailedValidationError, NormalTxConfirmation, PresortedTxFailure, ReceiptScanReport, + RecheckRequiringFailures, TxByTable, TxHashByTable, TxReclaim, ValidationFailureClockReal, }; use crate::accountant::test_utils::{ make_failed_tx, make_sent_tx, make_transaction_block, SentPayableDaoMock, @@ -475,11 +485,6 @@ mod tests { #[test] fn interprets_tx_receipt_for_supposedly_failed_tx_if_the_tx_keeps_pending() { - // TODO I don't know yet what todo to with this... - // Maybe implement so that we go on like this but we'd compute cases like this and - // stop it if unwinding too long??? (Remember that it would require a new exception for - // the scan report left possibly empty...and still avoiding a panic somehow...) - todo!("this is a puzzle"); init_test_logging(); let retrieve_txs_params_arc = Arc::new(Mutex::new(vec![])); let test_name = "interprets_tx_receipt_for_supposedly_failed_tx_if_the_tx_keeps_pending"; @@ -487,9 +492,11 @@ mod tests { let sent_payable_dao = SentPayableDaoMock::new() .retrieve_txs_params(&retrieve_txs_params_arc) .retrieve_txs_result(vec![newer_sent_tx_for_older_failed_tx]); - let hash = make_tx_hash(0x913); - let failed_tx = make_failed_tx(789); + let tx_hash = make_tx_hash(0x913); + let mut failed_tx = make_failed_tx(789); let failed_tx_nonce = failed_tx.nonce; + failed_tx.hash = tx_hash; + failed_tx.status = FailureStatus::RecheckRequired(ValidationStatus::Waiting); let scan_report = ReceiptScanReport::default(); let before = SystemTime::now(); @@ -504,10 +511,15 @@ mod tests { assert_eq!( result, ReceiptScanReport { - // TODO we cannot leave all these collections empty, that equals a panic failures: DetectedFailures { tx_failures: vec![], - tx_receipt_rpc_failures: vec![] + tx_receipt_rpc_failures: vec![FailedValidationByTable::FailedPayable( + FailedValidation::new( + tx_hash, + FailedValidationError::TxResubmissionFailed, + FailureStatus::RecheckRequired(ValidationStatus::Waiting) + ) + )] }, confirmations: DetectedConfirmations::default() } @@ -595,7 +607,11 @@ mod tests { failures: DetectedFailures { tx_failures: vec![], tx_receipt_rpc_failures: vec![FailedValidationByTable::SentPayable( - FailedValidation::new(tx_hash, rpc_error, current_tx_status) + FailedValidation::new( + tx_hash, + FailedValidationError::Known(rpc_error), + current_tx_status + ) ),] }, confirmations: DetectedConfirmations::default() @@ -657,7 +673,11 @@ mod tests { failures: DetectedFailures { tx_failures: vec![], tx_receipt_rpc_failures: vec![FailedValidationByTable::FailedPayable( - FailedValidation::new(tx_hash, rpc_error, current_failure_status) + FailedValidation::new( + tx_hash, + FailedValidationError::Known(rpc_error), + current_failure_status + ) )] }, confirmations: DetectedConfirmations::default() diff --git a/node/src/accountant/scanners/pending_payable_scanner/utils.rs b/node/src/accountant/scanners/pending_payable_scanner/utils.rs index b79b0b74e..3be4a72a0 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/utils.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/utils.rs @@ -117,7 +117,7 @@ impl From<(TxReceiptError, TxStatus)> for FailedValidationByTable { match tx_receipt_error.tx_hash { TxHashByTable::SentPayable(tx_hash) => Self::SentPayable(FailedValidation::new( tx_hash, - tx_receipt_error.err, + FailedValidationError::Known(tx_receipt_error.err), current_status, )), @@ -135,7 +135,7 @@ impl From<(TxReceiptError, FailureStatus)> for FailedValidationByTable { match tx_receipt_error.tx_hash { TxHashByTable::FailedPayable(tx_hash) => Self::FailedPayable(FailedValidation::new( tx_hash, - tx_receipt_error.err, + FailedValidationError::Known(tx_receipt_error.err), current_status, )), TxHashByTable::SentPayable(tx_hash) => { @@ -150,17 +150,23 @@ impl From<(TxReceiptError, FailureStatus)> for FailedValidationByTable { #[derive(Debug, PartialEq, Eq, Clone)] pub struct FailedValidation { pub tx_hash: TxHash, - pub validation_failure: AppRpcError, + pub validation_failure: FailedValidationError, pub current_status: RecordStatus, } +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum FailedValidationError { + Known(AppRpcError), + TxResubmissionFailed, +} + impl FailedValidation where RecordStatus: UpdatableValidationStatus, { pub fn new( tx_hash: TxHash, - validation_failure: AppRpcError, + validation_failure: FailedValidationError, current_status: RecordStatus, ) -> Self { Self { @@ -185,14 +191,14 @@ pub struct ValidationFailureClockReal {} impl ValidationFailureClock for ValidationFailureClockReal { fn now(&self) -> SystemTime { - todo!() + SystemTime::now() } } pub trait UpdatableValidationStatus { fn update_after_failure( &self, - error: AppRpcError, + error: FailedValidationError, clock: &dyn ValidationFailureClock, ) -> Option where @@ -202,7 +208,7 @@ pub trait UpdatableValidationStatus { impl UpdatableValidationStatus for TxStatus { fn update_after_failure( &self, - error: AppRpcError, + error: FailedValidationError, clock: &dyn ValidationFailureClock, ) -> Option { match self { @@ -222,7 +228,7 @@ impl UpdatableValidationStatus for TxStatus { impl UpdatableValidationStatus for FailureStatus { fn update_after_failure( &self, - error: AppRpcError, + error: FailedValidationError, clock: &dyn ValidationFailureClock, ) -> Option { match self { @@ -380,9 +386,9 @@ mod tests { use crate::accountant::scanners::pending_payable_scanner::test_utils::ValidationFailureClockMock; use crate::accountant::scanners::pending_payable_scanner::utils::{ CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailedValidation, - FailedValidationByTable, NormalTxConfirmation, PendingPayableCache, PresortedTxFailure, - ReceiptScanReport, RecheckRequiringFailures, Retry, TxBlock, TxHashByTable, TxReceiptError, - TxReceiptResult, TxReclaim, ValidationFailureClockReal, + FailedValidationByTable, FailedValidationError, NormalTxConfirmation, PendingPayableCache, + PresortedTxFailure, ReceiptScanReport, RecheckRequiringFailures, Retry, TxBlock, + TxHashByTable, TxReceiptError, TxReceiptResult, TxReclaim, ValidationFailureClockReal, }; use crate::accountant::test_utils::{make_failed_tx, make_sent_tx, make_transaction_block}; use crate::blockchain::errors::{ @@ -428,13 +434,15 @@ mod tests { vec![], vec![FailedValidationByTable::SentPayable(FailedValidation::new( make_tx_hash(2222), - AppRpcError::Local(LocalError::Internal), + FailedValidationError::Known(AppRpcError::Local(LocalError::Internal)), TxStatus::Pending(ValidationStatus::Waiting), ))], vec![FailedValidationByTable::FailedPayable( FailedValidation::new( make_tx_hash(12121), - AppRpcError::Remote(RemoteError::InvalidResponse("blah".to_string())), + FailedValidationError::Known(AppRpcError::Remote( + RemoteError::InvalidResponse("blah".to_string()), + )), FailureStatus::RecheckRequired(ValidationStatus::Reattempting( PreviousAttempts::new( AppRpcErrorKind::Internal, @@ -505,20 +513,20 @@ mod tests { let rpc_failure_feedings = vec![ vec![FailedValidationByTable::SentPayable(FailedValidation::new( make_tx_hash(2222), - AppRpcError::Local(LocalError::Internal), + FailedValidationError::Known(AppRpcError::Local(LocalError::Internal)), TxStatus::Pending(ValidationStatus::Waiting), ))], vec![FailedValidationByTable::FailedPayable( FailedValidation::new( make_tx_hash(1234), - AppRpcError::Remote(RemoteError::Unreachable), + FailedValidationError::Known(AppRpcError::Remote(RemoteError::Unreachable)), FailureStatus::RecheckRequired(ValidationStatus::Waiting), ), )], vec![ FailedValidationByTable::SentPayable(FailedValidation::new( make_tx_hash(2222), - AppRpcError::Local(LocalError::Internal), + FailedValidationError::Known(AppRpcError::Local(LocalError::Internal)), TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new( AppRpcErrorKind::Internal, &ValidationFailureClockReal::default(), @@ -526,7 +534,7 @@ mod tests { )), FailedValidationByTable::FailedPayable(FailedValidation::new( make_tx_hash(1234), - AppRpcError::Remote(RemoteError::Unreachable), + FailedValidationError::Known(AppRpcError::Remote(RemoteError::Unreachable)), FailureStatus::RecheckRequired(ValidationStatus::Reattempting( PreviousAttempts::new( AppRpcErrorKind::Internal, @@ -968,7 +976,7 @@ mod tests { result_1, FailedValidationByTable::SentPayable(FailedValidation::new( tx_hash_sent_tx, - api_error_sent_tx, + FailedValidationError::Known(api_error_sent_tx), TxStatus::Pending(ValidationStatus::Waiting) )) ); @@ -976,7 +984,7 @@ mod tests { result_2, FailedValidationByTable::FailedPayable(FailedValidation::new( tx_hash_failed_tx, - api_error_failed_tx, + FailedValidationError::Known(api_error_failed_tx), FailureStatus::RecheckRequired(ValidationStatus::Waiting) )) ); @@ -1028,7 +1036,7 @@ mod tests { ( FailedValidation::new( make_tx_hash(123), - AppRpcError::Local(LocalError::Internal), + FailedValidationError::Known(AppRpcError::Local(LocalError::Internal)), TxStatus::Pending(ValidationStatus::Waiting), ), Some(TxStatus::Pending(ValidationStatus::Reattempting( @@ -1041,7 +1049,7 @@ mod tests { ( FailedValidation::new( make_tx_hash(123), - AppRpcError::Remote(RemoteError::Unreachable), + FailedValidationError::Known(AppRpcError::Remote(RemoteError::Unreachable)), TxStatus::Pending(ValidationStatus::Reattempting( PreviousAttempts::new( AppRpcErrorKind::Internal, @@ -1092,7 +1100,7 @@ mod tests { ( FailedValidation::new( make_tx_hash(456), - AppRpcError::Local(LocalError::Internal), + FailedValidationError::Known(AppRpcError::Local(LocalError::Internal)), FailureStatus::RecheckRequired(ValidationStatus::Waiting), ), Some(FailureStatus::RecheckRequired( @@ -1105,7 +1113,7 @@ mod tests { ( FailedValidation::new( make_tx_hash(456), - AppRpcError::Remote(RemoteError::Unreachable), + FailedValidationError::Known(AppRpcError::Remote(RemoteError::Unreachable)), FailureStatus::RecheckRequired(ValidationStatus::Reattempting( PreviousAttempts::new( AppRpcErrorKind::ServerUnreachable, @@ -1151,7 +1159,7 @@ mod tests { let validation_failure_clock = ValidationFailureClockMock::default(); let mal_validated_tx_status = FailedValidation::new( make_tx_hash(123), - AppRpcError::Local(LocalError::Internal), + FailedValidationError::Known(AppRpcError::Local(LocalError::Internal)), TxStatus::Confirmed { block_hash: "".to_string(), block_number: 0, @@ -1171,12 +1179,12 @@ mod tests { let mal_validated_failure_statuses = vec![ FailedValidation::new( make_tx_hash(456), - AppRpcError::Local(LocalError::Internal), + FailedValidationError::Known(AppRpcError::Local(LocalError::Internal)), FailureStatus::RetryRequired, ), FailedValidation::new( make_tx_hash(789), - AppRpcError::Remote(RemoteError::Unreachable), + FailedValidationError::Known(AppRpcError::Remote(RemoteError::Unreachable)), FailureStatus::Concluded, ), ]; diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs index 8fe1fcca3..affac26f1 100644 --- a/node/src/accountant/test_utils.rs +++ b/node/src/accountant/test_utils.rs @@ -1415,7 +1415,7 @@ impl PendingPayableScannerBuilder { self } - pub fn pending_payable_cache(mut self, cache: Box>) -> Self { + pub fn sent_payable_cache(mut self, cache: Box>) -> Self { self.current_sent_payables = cache; self } diff --git a/node/src/blockchain/errors.rs b/node/src/blockchain/errors.rs index e751474ae..10051eadc 100644 --- a/node/src/blockchain/errors.rs +++ b/node/src/blockchain/errors.rs @@ -1,4 +1,6 @@ -use crate::accountant::scanners::pending_payable_scanner::utils::ValidationFailureClock; +use crate::accountant::scanners::pending_payable_scanner::utils::{ + FailedValidationError, ValidationFailureClock, +}; use serde::ser::SerializeStruct; use serde_derive::{Deserialize, Serialize}; use std::collections::HashMap; @@ -96,21 +98,31 @@ pub enum ValidationStatus { Reattempting(PreviousAttempts), } -impl From for AppRpcErrorKind { - fn from(err: AppRpcError) -> Self { +enum ErrorKinds { + AppRcpError(AppRpcErrorKind), + Uninterpretable(UninterpretabilityReason), +} + +enum UninterpretabilityReason { + FailedTxLeftPending, +} + +impl From for AppRpcErrorKind { + fn from(err: FailedValidationError) -> Self { match err { - AppRpcError::Local(local) => match local { + FailedValidationError::Known(AppRpcError::Local(local)) => match local { LocalError::Decoder(_) => Self::Decoder, LocalError::Internal => Self::Internal, LocalError::Io(_) => Self::IO, LocalError::Signing(_) => Self::Signing, LocalError::Transport(_) => Self::Transport, }, - AppRpcError::Remote(remote) => match remote { + FailedValidationError::Known(AppRpcError::Remote(remote)) => match remote { RemoteError::InvalidResponse(_) => Self::InvalidResponse, RemoteError::Unreachable => Self::ServerUnreachable, RemoteError::Web3RpcError { code, .. } => Self::Web3RpcError(code), }, + FailedValidationError::TxResubmissionFailed => Self::Unknown, } } } @@ -144,7 +156,9 @@ impl From for AppRpcError { #[cfg(test)] mod tests { - use crate::accountant::scanners::pending_payable_scanner::utils::ValidationFailureClockReal; + use crate::accountant::scanners::pending_payable_scanner::utils::{ + FailedValidationError, ValidationFailureClockReal, + }; use crate::blockchain::errors::{ AppRpcError, AppRpcErrorKind, LocalError, PreviousAttempts, RemoteError, }; @@ -272,47 +286,59 @@ mod tests { #[test] fn conversion_between_app_rpc_error_and_app_rpc_error_kind_works() { assert_eq!( - AppRpcErrorKind::from(AppRpcError::Local(LocalError::Decoder( - "Decoder error".to_string() + AppRpcErrorKind::from(FailedValidationError::Known(AppRpcError::Local( + LocalError::Decoder("Decoder error".to_string()) ))), AppRpcErrorKind::Decoder ); assert_eq!( - AppRpcErrorKind::from(AppRpcError::Local(LocalError::Internal)), + AppRpcErrorKind::from(FailedValidationError::Known(AppRpcError::Local( + LocalError::Internal + ))), AppRpcErrorKind::Internal ); assert_eq!( - AppRpcErrorKind::from(AppRpcError::Local(LocalError::Io("IO error".to_string()))), + AppRpcErrorKind::from(FailedValidationError::Known(AppRpcError::Local( + LocalError::Io("IO error".to_string()) + ))), AppRpcErrorKind::IO ); assert_eq!( - AppRpcErrorKind::from(AppRpcError::Local(LocalError::Signing( - "Signing error".to_string() + AppRpcErrorKind::from(FailedValidationError::Known(AppRpcError::Local( + LocalError::Signing("Signing error".to_string()) ))), AppRpcErrorKind::Signing ); assert_eq!( - AppRpcErrorKind::from(AppRpcError::Local(LocalError::Transport( - "Transport error".to_string() + AppRpcErrorKind::from(FailedValidationError::Known(AppRpcError::Local( + LocalError::Transport("Transport error".to_string()) ))), AppRpcErrorKind::Transport ); assert_eq!( - AppRpcErrorKind::from(AppRpcError::Remote(RemoteError::InvalidResponse( - "Invalid response".to_string() + AppRpcErrorKind::from(FailedValidationError::Known(AppRpcError::Remote( + RemoteError::InvalidResponse("Invalid response".to_string()) ))), AppRpcErrorKind::InvalidResponse ); assert_eq!( - AppRpcErrorKind::from(AppRpcError::Remote(RemoteError::Unreachable)), + AppRpcErrorKind::from(FailedValidationError::Known(AppRpcError::Remote( + RemoteError::Unreachable + ))), AppRpcErrorKind::ServerUnreachable ); assert_eq!( - AppRpcErrorKind::from(AppRpcError::Remote(RemoteError::Web3RpcError { - code: 55, - message: "Booga".to_string() - })), + AppRpcErrorKind::from(FailedValidationError::Known(AppRpcError::Remote( + RemoteError::Web3RpcError { + code: 55, + message: "Booga".to_string() + } + ))), AppRpcErrorKind::Web3RpcError(55) ); + assert_eq!( + AppRpcErrorKind::from(FailedValidationError::TxResubmissionFailed), + AppRpcErrorKind::Unknown + ); } } From cc70df904aff1c2adade25ef275523c4d572cc0d Mon Sep 17 00:00:00 2001 From: Bert Date: Fri, 15 Aug 2025 22:38:18 +0200 Subject: [PATCH 36/61] GH-683: savepoint --- .../db_access_objects/failed_payable_dao.rs | 61 ++- .../db_access_objects/sent_payable_dao.rs | 36 +- .../src/accountant/scanners/scanners_utils.rs | 6 +- node/src/blockchain/blockchain_bridge.rs | 27 +- .../lower_level_interface_web3.rs | 37 +- .../blockchain_interface_web3/mod.rs | 36 +- .../data_structures/errors.rs | 42 +- .../lower_level_interface.rs | 16 +- .../blockchain/blockchain_interface/mod.rs | 6 +- node/src/blockchain/errors.rs | 463 +++++++++++++++++- node/src/blockchain/test_utils.rs | 21 + 11 files changed, 635 insertions(+), 116 deletions(-) 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 7d9f2ae47..48d7d8e54 100644 --- a/node/src/accountant/db_access_objects/failed_payable_dao.rs +++ b/node/src/accountant/db_access_objects/failed_payable_dao.rs @@ -4,7 +4,7 @@ use crate::accountant::db_access_objects::utils::{ }; use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; use crate::accountant::{checked_conversion, comma_joined_stringifiable}; -use crate::blockchain::errors::AppRpcError; +use crate::blockchain::errors::{AppRpcError, BlockchainDbError, PreviousAttempts}; use crate::database::rusqlite_wrappers::ConnectionWrapper; use itertools::Itertools; use masq_lib::utils::ExpectValue; @@ -25,7 +25,7 @@ pub enum FailedPayableDaoError { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum FailureReason { - Submission(AppRpcError), + Submission(PreviousAttempts), Reverted, PendingTooLong, } @@ -75,7 +75,7 @@ impl FromStr for FailureStatus { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum ValidationStatus { Waiting, - Reattempting { attempt: usize, error: AppRpcError }, + Reattempting(PreviousAttempts), } #[derive(Clone, Debug, PartialEq, Eq)] @@ -381,8 +381,11 @@ mod tests { make_read_only_db_connection, FailedTxBuilder, }; use crate::accountant::db_access_objects::utils::current_unix_timestamp; - use crate::blockchain::errors::{AppRpcError, LocalError, RemoteError}; - use crate::blockchain::test_utils::make_tx_hash; + use crate::blockchain::errors::{ + AppRpcError, AppRpcErrorKind, LocalError, PreviousAttempts, RemoteError, + ValidationFailureClockReal, + }; + use crate::blockchain::test_utils::{make_tx_hash, ValidationFailureClockMock}; use crate::database::db_initializer::{ DbInitializationConfig, DbInitializer, DbInitializerReal, }; @@ -390,7 +393,9 @@ mod tests { use masq_lib::test_utils::utils::ensure_node_home_directory_exists; use rusqlite::Connection; use std::collections::{HashMap, HashSet}; + use std::ops::Add; use std::str::FromStr; + use std::time::{Duration, SystemTime}; #[test] fn insert_new_records_works() { @@ -582,13 +587,14 @@ mod tests { #[test] fn failure_reason_from_str_works() { + let validation_failure_clock = ValidationFailureClockMock::default(); // Submission error assert_eq!( - FailureReason::from_str(r#"{"Submission":{"Local":{"Decoder":"Test decoder error"}}}"#) - .unwrap(), - FailureReason::Submission(AppRpcError::Local(LocalError::Decoder( - "Test decoder error".to_string() - ))) + FailureReason::from_str(r#"{"Submission":{"Local":{"Decoder"}}}"#).unwrap(), + FailureReason::Submission(PreviousAttempts::new( + Box::new(AppRpcErrorKind::Decoder), + &validation_failure_clock + )) ); // Reverted @@ -620,6 +626,11 @@ mod tests { #[test] fn failure_status_from_str_works() { + let validation_failure_clock = ValidationFailureClockMock::default().now_result( + SystemTime::UNIX_EPOCH + .add(Duration::from_secs(1755080031)) + .add(Duration::from_nanos(612180914)), + ); assert_eq!( FailureStatus::from_str("\"RetryRequired\"").unwrap(), FailureStatus::RetryRequired @@ -631,8 +642,8 @@ mod tests { ); assert_eq!( - FailureStatus::from_str(r#"{"RecheckRequired":{"Reattempting":{"attempt":2,"error":{"Remote":"Unreachable"}}}}"#).unwrap(), - FailureStatus::RecheckRequired(ValidationStatus::Reattempting { attempt: 2, error: AppRpcError::Remote(RemoteError::Unreachable) }) + FailureStatus::from_str(r#"{"RecheckRequired":{"Reattempting":{"ServerUnreachable":{"firstSeen":{"secs_since_epoch":1755080031,"nanos_since_epoch":612180914},"attempts":1}}}}"#).unwrap(), + FailureStatus::RecheckRequired(ValidationStatus::Reattempting( PreviousAttempts::new(Box::new(AppRpcErrorKind::ServerUnreachable), &validation_failure_clock))) ); assert_eq!( @@ -713,10 +724,12 @@ mod tests { let tx3 = FailedTxBuilder::default() .hash(make_tx_hash(3)) .reason(PendingTooLong) - .status(RecheckRequired(ValidationStatus::Reattempting { - attempt: 1, - error: AppRpcError::Remote(RemoteError::Unreachable), - })) + .status(RecheckRequired(ValidationStatus::Reattempting( + PreviousAttempts::new( + Box::new(AppRpcErrorKind::ServerUnreachable), + &ValidationFailureClockReal::default(), + ), + ))) .build(); let tx4 = FailedTxBuilder::default() .hash(make_tx_hash(4)) @@ -768,10 +781,10 @@ mod tests { (tx1.hash, Concluded), ( tx2.hash, - RecheckRequired(ValidationStatus::Reattempting { - attempt: 1, - error: AppRpcError::Remote(RemoteError::Unreachable), - }), + RecheckRequired(ValidationStatus::Reattempting(PreviousAttempts::new( + Box::new(AppRpcErrorKind::ServerUnreachable), + &ValidationFailureClockReal::default(), + ))), ), (tx3.hash, Concluded), ]); @@ -785,10 +798,10 @@ mod tests { assert_eq!(tx2.status, RecheckRequired(ValidationStatus::Waiting)); assert_eq!( updated_txs[1].status, - RecheckRequired(ValidationStatus::Reattempting { - attempt: 1, - error: AppRpcError::Remote(RemoteError::Unreachable) - }) + RecheckRequired(ValidationStatus::Reattempting(PreviousAttempts::new( + Box::new(AppRpcErrorKind::ServerUnreachable), + &ValidationFailureClockReal::default() + ))) ); assert_eq!(tx3.status, RetryRequired); assert_eq!(updated_txs[2].status, Concluded); 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 ac3fbec86..cb62f609b 100644 --- a/node/src/accountant/db_access_objects/sent_payable_dao.rs +++ b/node/src/accountant/db_access_objects/sent_payable_dao.rs @@ -424,8 +424,10 @@ impl SentPayableDao for SentPayableDaoReal<'_> { #[cfg(test)] mod tests { use std::collections::{HashMap, HashSet}; + use std::ops::Add; use std::str::FromStr; use std::sync::{Arc, Mutex}; + use std::time::{Duration, UNIX_EPOCH}; use crate::accountant::db_access_objects::sent_payable_dao::{Detection, RetrieveCondition, SentPayableDao, SentPayableDaoError, SentPayableDaoReal, TxConfirmation, TxStatus}; use crate::database::db_initializer::{ DbInitializationConfig, DbInitializer, DbInitializerReal, @@ -439,8 +441,8 @@ mod tests { use crate::accountant::db_access_objects::sent_payable_dao::SentPayableDaoError::{EmptyInput, PartialExecution}; use crate::accountant::db_access_objects::test_utils::{make_read_only_db_connection, TxBuilder}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock}; - use crate::blockchain::errors::{AppRpcError, RemoteError}; - use crate::blockchain::test_utils::{make_block_hash, make_tx_hash}; + use crate::blockchain::errors::{AppRpcError, AppRpcErrorKind, PreviousAttempts, RemoteError, ValidationFailureClockReal}; + use crate::blockchain::test_utils::{make_block_hash, make_tx_hash, ValidationFailureClockMock}; #[test] fn insert_new_records_works() { @@ -452,10 +454,16 @@ mod tests { let tx1 = TxBuilder::default().hash(make_tx_hash(1)).build(); let tx2 = TxBuilder::default() .hash(make_tx_hash(2)) - .status(TxStatus::Pending(ValidationStatus::Reattempting { - attempt: 2, - error: AppRpcError::Remote(RemoteError::Unreachable), - })) + .status(TxStatus::Pending(ValidationStatus::Reattempting( + PreviousAttempts::new( + Box::new(AppRpcErrorKind::ServerUnreachable), + &ValidationFailureClockReal::default(), + ) + .add_attempt( + Box::new(AppRpcErrorKind::ServerUnreachable), + &ValidationFailureClockReal::default(), + ), + ))) .build(); let subject = SentPayableDaoReal::new(wrapped_conn); let txs = vec![tx1, tx2]; @@ -682,10 +690,12 @@ mod tests { .build(); let tx2 = TxBuilder::default() .hash(make_tx_hash(2)) - .status(TxStatus::Pending(ValidationStatus::Reattempting { - attempt: 1, - error: AppRpcError::Remote(RemoteError::Unreachable), - })) + .status(TxStatus::Pending(ValidationStatus::Reattempting( + PreviousAttempts::new( + Box::new(AppRpcErrorKind::ServerUnreachable), + &ValidationFailureClockReal::default(), + ), + ))) .build(); let tx3 = TxBuilder::default() .hash(make_tx_hash(3)) @@ -1169,14 +1179,16 @@ mod tests { #[test] fn tx_status_from_str_works() { + let validation_failure_clock = ValidationFailureClockMock::default() + .now_result(UNIX_EPOCH.add(Duration::from_secs(12456))); assert_eq!( TxStatus::from_str(r#"{"Pending":"Waiting"}"#).unwrap(), TxStatus::Pending(ValidationStatus::Waiting) ); assert_eq!( - TxStatus::from_str(r#"{"Pending":{"Reattempting":{"attempt":3,"error":{"Remote":{"InvalidResponse":"bluh"}}}}}"#).unwrap(), - TxStatus::Pending(ValidationStatus::Reattempting { attempt: 3, error: AppRpcError::Remote(RemoteError::InvalidResponse("bluh".to_string())) }) + TxStatus::from_str(r#"{"Pending":{"Reattempting":{"InvalidResponse":{"firstSeen":{"secs_since_epoch":12456,"nanos_since_epoch":0},"attempts":1}}}}"#).unwrap(), + TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new(Box::new(AppRpcErrorKind::InvalidResponse), &validation_failure_clock))) ); assert_eq!( diff --git a/node/src/accountant/scanners/scanners_utils.rs b/node/src/accountant/scanners/scanners_utils.rs index 971030e14..3747728ab 100644 --- a/node/src/accountant/scanners/scanners_utils.rs +++ b/node/src/accountant/scanners/scanners_utils.rs @@ -342,7 +342,7 @@ mod tests { use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; use std::time::SystemTime; use crate::accountant::db_access_objects::pending_payable_dao::PendingPayable; - use crate::blockchain::blockchain_interface::data_structures::errors::{BlockchainError, PayableTransactionError}; + use crate::blockchain::blockchain_interface::data_structures::errors::{BlockchainInterfaceError, PayableTransactionError}; use crate::blockchain::blockchain_interface::data_structures::{ProcessedPayableFallible, RpcPayableFailure}; #[test] @@ -645,11 +645,11 @@ mod tests { #[test] fn count_total_errors_says_unknown_number_for_early_local_errors() { let early_local_errors = [ - PayableTransactionError::TransactionID(BlockchainError::QueryFailed( + PayableTransactionError::TransactionID(BlockchainInterfaceError::QueryFailed( "blah".to_string(), )), PayableTransactionError::MissingConsumingWallet, - PayableTransactionError::GasPriceQueryFailed(BlockchainError::QueryFailed( + PayableTransactionError::GasPriceQueryFailed(BlockchainInterfaceError::QueryFailed( "ouch".to_string(), )), PayableTransactionError::UnusableWallet("fooo".to_string()), diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index e427ab934..421fc6bd5 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -9,7 +9,7 @@ use crate::accountant::{ReportTransactionReceipts, RequestTransactionReceipts}; use crate::actor_system_factory::SubsFactory; use crate::blockchain::blockchain_interface::blockchain_interface_web3::HashAndAmount; use crate::blockchain::blockchain_interface::data_structures::errors::{ - BlockchainError, PayableTransactionError, + BlockchainInterfaceError, PayableTransactionError, }; use crate::blockchain::blockchain_interface::data_structures::ProcessedPayableFallible; use crate::blockchain::blockchain_interface::BlockchainInterface; @@ -505,12 +505,13 @@ impl BlockchainBridge { .expect("Accountant unbound") } - pub fn extract_max_block_count(error: BlockchainError) -> Option { + pub fn extract_max_block_count(error: BlockchainInterfaceError) -> Option { let regex_result = Regex::new(r".* (max: |allowed for your plan: |is limited to |block range limit \(|exceeds max block range )(?P\d+).*") .expect("Invalid regex"); let max_block_count = match error { - BlockchainError::QueryFailed(msg) => match regex_result.captures(msg.as_str()) { + BlockchainInterfaceError::QueryFailed(msg) => match regex_result.captures(msg.as_str()) + { Some(captures) => match captures.name("max_block_count") { Some(m) => match m.as_str().parse::() { Ok(value) => Some(value), @@ -821,7 +822,7 @@ mod tests { assert_eq!(accountant_recording.len(), 0); let service_fee_balance_error = BlockchainAgentBuildError::ServiceFeeBalance( consuming_wallet.address(), - BlockchainError::QueryFailed( + BlockchainInterfaceError::QueryFailed( "Api error: Transport error: Error(IncompleteMessage)".to_string(), ), ); @@ -1129,7 +1130,7 @@ mod tests { let error_result = result.unwrap_err(); assert_eq!( error_result, - TransactionID(BlockchainError::QueryFailed( + TransactionID(BlockchainInterfaceError::QueryFailed( "Decoder error: Error(\"0x prefix is missing\", line: 0, column: 0) for wallet 0x2581…7849".to_string() )) ); @@ -2127,7 +2128,7 @@ mod tests { #[test] fn extract_max_block_range_from_error_response() { - let result = BlockchainError::QueryFailed("RPC error: Error { code: ServerError(-32005), message: \"eth_getLogs block range too large, range: 33636, max: 3500\", data: None }".to_string()); + let result = BlockchainInterfaceError::QueryFailed("RPC error: Error { code: ServerError(-32005), message: \"eth_getLogs block range too large, range: 33636, max: 3500\", data: None }".to_string()); let max_block_count = BlockchainBridge::extract_max_block_count(result); @@ -2136,7 +2137,7 @@ mod tests { #[test] fn extract_max_block_range_from_pokt_error_response() { - let result = BlockchainError::QueryFailed("Rpc(Error { code: ServerError(-32001), message: \"Relay request failed validation: invalid relay request: eth_getLogs block range limit (100000 blocks) exceeded\", data: None })".to_string()); + let result = BlockchainInterfaceError::QueryFailed("Rpc(Error { code: ServerError(-32001), message: \"Relay request failed validation: invalid relay request: eth_getLogs block range limit (100000 blocks) exceeded\", data: None })".to_string()); let max_block_count = BlockchainBridge::extract_max_block_count(result); @@ -2152,7 +2153,7 @@ mod tests { */ #[test] fn extract_max_block_range_for_ankr_error_response() { - let result = BlockchainError::QueryFailed("RPC error: Error { code: ServerError(-32600), message: \"block range is too wide\", data: None }".to_string()); + let result = BlockchainInterfaceError::QueryFailed("RPC error: Error { code: ServerError(-32600), message: \"block range is too wide\", data: None }".to_string()); let max_block_count = BlockchainBridge::extract_max_block_count(result); @@ -2165,7 +2166,7 @@ mod tests { */ #[test] fn extract_max_block_range_for_matic_vigil_error_response() { - let result = BlockchainError::QueryFailed("RPC error: Error { code: ServerError(-32005), message: \"Blockheight too far in the past. Check params passed to eth_getLogs or eth_call requests.Range of blocks allowed for your plan: 1000\", data: None }".to_string()); + let result = BlockchainInterfaceError::QueryFailed("RPC error: Error { code: ServerError(-32005), message: \"Blockheight too far in the past. Check params passed to eth_getLogs or eth_call requests.Range of blocks allowed for your plan: 1000\", data: None }".to_string()); let max_block_count = BlockchainBridge::extract_max_block_count(result); @@ -2178,7 +2179,7 @@ mod tests { */ #[test] fn extract_max_block_range_for_blockpi_error_response() { - let result = BlockchainError::QueryFailed("RPC error: Error { code: ServerError(-32005), message: \"eth_getLogs is limited to 1024 block range. Please check the parameter requirements at https://docs.blockpi.io/documentations/api-reference\", data: None }".to_string()); + let result = BlockchainInterfaceError::QueryFailed("RPC error: Error { code: ServerError(-32005), message: \"eth_getLogs is limited to 1024 block range. Please check the parameter requirements at https://docs.blockpi.io/documentations/api-reference\", data: None }".to_string()); let max_block_count = BlockchainBridge::extract_max_block_count(result); @@ -2193,7 +2194,7 @@ mod tests { #[test] fn extract_max_block_range_for_blastapi_error_response() { - let result = BlockchainError::QueryFailed("RPC error: Error { code: ServerError(-32601), message: \"Method not found\", data: \"'eth_getLogs' is not available on our public API. Head over to https://docs.blastapi.io/blast-documentation/tutorials-and-guides/using-blast-to-get-a-blockchain-endpoint for more information\" }".to_string()); + let result = BlockchainInterfaceError::QueryFailed("RPC error: Error { code: ServerError(-32601), message: \"Method not found\", data: \"'eth_getLogs' is not available on our public API. Head over to https://docs.blastapi.io/blast-documentation/tutorials-and-guides/using-blast-to-get-a-blockchain-endpoint for more information\" }".to_string()); let max_block_count = BlockchainBridge::extract_max_block_count(result); @@ -2202,7 +2203,7 @@ mod tests { #[test] fn extract_max_block_range_for_nodies_error_response() { - let result = BlockchainError::QueryFailed("RPC error: Error { code: InvalidParams, message: \"query exceeds max block range 100000\", data: None }".to_string()); + let result = BlockchainInterfaceError::QueryFailed("RPC error: Error { code: InvalidParams, message: \"query exceeds max block range 100000\", data: None }".to_string()); let max_block_count = BlockchainBridge::extract_max_block_count(result); @@ -2211,7 +2212,7 @@ mod tests { #[test] fn extract_max_block_range_for_expected_batch_got_single_error_response() { - let result = BlockchainError::QueryFailed( + let result = BlockchainInterfaceError::QueryFailed( "Got invalid response: Expected batch, got single.".to_string(), ); diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs index b91e2c924..c93c07b53 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs @@ -1,8 +1,8 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::blockchain::blockchain_interface::blockchain_interface_web3::CONTRACT_ABI; -use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainError; -use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainError::QueryFailed; +use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainInterfaceError; +use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainInterfaceError::QueryFailed; use crate::blockchain::blockchain_interface::lower_level_interface::LowBlockchainInt; use ethereum_types::{H256, U256, U64}; use futures::Future; @@ -115,7 +115,7 @@ impl LowBlockchainInt for LowBlockchainIntWeb3 { fn get_transaction_fee_balance( &self, address: Address, - ) -> Box> { + ) -> Box> { Box::new( self.web3 .eth() @@ -127,7 +127,7 @@ impl LowBlockchainInt for LowBlockchainIntWeb3 { fn get_service_fee_balance( &self, address: Address, - ) -> Box> { + ) -> Box> { Box::new( self.contract .query("balanceOf", address, None, Options::default(), None) @@ -135,7 +135,7 @@ impl LowBlockchainInt for LowBlockchainIntWeb3 { ) } - fn get_gas_price(&self) -> Box> { + fn get_gas_price(&self) -> Box> { Box::new( self.web3 .eth() @@ -144,7 +144,7 @@ impl LowBlockchainInt for LowBlockchainIntWeb3 { ) } - fn get_block_number(&self) -> Box> { + fn get_block_number(&self) -> Box> { Box::new( self.web3 .eth() @@ -156,7 +156,7 @@ impl LowBlockchainInt for LowBlockchainIntWeb3 { fn get_transaction_id( &self, address: Address, - ) -> Box> { + ) -> Box> { Box::new( self.web3 .eth() @@ -168,7 +168,7 @@ impl LowBlockchainInt for LowBlockchainIntWeb3 { fn get_transaction_receipt_in_batch( &self, hash_vec: Vec, - ) -> Box>, Error = BlockchainError>> { + ) -> Box>, Error = BlockchainInterfaceError>> { hash_vec.into_iter().for_each(|hash| { self.web3_batch.eth().transaction_receipt(hash); }); @@ -188,7 +188,7 @@ impl LowBlockchainInt for LowBlockchainIntWeb3 { fn get_transaction_logs( &self, filter: Filter, - ) -> Box, Error = BlockchainError>> { + ) -> Box, Error = BlockchainInterfaceError>> { Box::new( self.web3 .eth() @@ -220,8 +220,8 @@ impl LowBlockchainIntWeb3 { #[cfg(test)] mod tests { use crate::blockchain::blockchain_interface::blockchain_interface_web3::TRANSACTION_LITERAL; - use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainError::QueryFailed; - use crate::blockchain::blockchain_interface::{BlockchainError, BlockchainInterface}; + use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainInterfaceError::QueryFailed; + use crate::blockchain::blockchain_interface::{BlockchainInterfaceError, BlockchainInterface}; use crate::blockchain::test_utils::make_blockchain_interface_web3; use crate::sub_lib::wallet::Wallet; use crate::test_utils::make_wallet; @@ -269,7 +269,9 @@ mod tests { .wait(); match result { - Err(BlockchainError::QueryFailed(msg)) if msg.contains("invalid hex character: Q") => { + Err(BlockchainInterfaceError::QueryFailed(msg)) + if msg.contains("invalid hex character: Q") => + { () } x => panic!("Expected complaint about hex character, but got {:?}", x), @@ -377,7 +379,9 @@ mod tests { .wait(); match result { - Err(BlockchainError::QueryFailed(msg)) if msg.contains("invalid hex character: Q") => { + Err(BlockchainInterfaceError::QueryFailed(msg)) + if msg.contains("invalid hex character: Q") => + { () } x => panic!("Expected complaint about hex character, but got {:?}", x), @@ -430,8 +434,11 @@ mod tests { .wait(); let err_msg = match result { - Err(BlockchainError::QueryFailed(msg)) => msg, - x => panic!("Expected BlockchainError::QueryFailed, but got {:?}", x), + Err(BlockchainInterfaceError::QueryFailed(msg)) => msg, + x => panic!( + "Expected BlockchainInterfaceError::QueryFailed, but got {:?}", + x + ), }; assert!( err_msg.contains(expected_err_msg), diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs index 81c7fe62d..bb9cde491 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs @@ -4,7 +4,7 @@ pub mod lower_level_interface_web3; mod utils; use std::cmp::PartialEq; -use crate::blockchain::blockchain_interface::data_structures::errors::{BlockchainError, PayableTransactionError}; +use crate::blockchain::blockchain_interface::data_structures::errors::{BlockchainInterfaceError, PayableTransactionError}; use crate::blockchain::blockchain_interface::data_structures::{BlockchainTransaction, ProcessedPayableFallible}; use crate::blockchain::blockchain_interface::lower_level_interface::LowBlockchainInt; use crate::blockchain::blockchain_interface::RetrievedBlockchainTransactions; @@ -104,7 +104,8 @@ impl BlockchainInterface for BlockchainInterfaceWeb3 { start_block_marker: BlockMarker, scan_range: BlockScanRange, recipient: Address, - ) -> Box> { + ) -> Box> + { let lower_level_interface = self.lower_interface(); let logger = self.logger.clone(); let contract_address = lower_level_interface.get_contract_address(); @@ -213,7 +214,8 @@ impl BlockchainInterface for BlockchainInterfaceWeb3 { fn process_transaction_receipts( &self, transaction_hashes: Vec, - ) -> Box, Error = BlockchainError>> { + ) -> Box, Error = BlockchainInterfaceError>> + { Box::new( self.lower_interface() .get_transaction_receipt_in_batch(transaction_hashes.clone()) @@ -366,7 +368,7 @@ impl BlockchainInterfaceWeb3 { fn calculate_end_block_marker( start_block_marker: BlockMarker, scan_range: BlockScanRange, - rpc_block_number_result: Result, + rpc_block_number_result: Result, logger: &Logger, ) -> BlockMarker { let locally_determined_end_block_marker = match (start_block_marker, scan_range) { @@ -398,9 +400,9 @@ impl BlockchainInterfaceWeb3 { } fn handle_transaction_logs( - logs_result: Result, BlockchainError>, + logs_result: Result, BlockchainInterfaceError>, logger: &Logger, - ) -> Result, BlockchainError> { + ) -> Result, BlockchainInterfaceError> { let logs = logs_result?; let logs_len = logs.len(); if logs @@ -412,7 +414,7 @@ impl BlockchainInterfaceWeb3 { "Invalid response from blockchain server: {:?}", logs ); - Err(BlockchainError::InvalidResponse) + Err(BlockchainInterfaceError::InvalidResponse) } else { let transactions: Vec = Self::extract_transactions_from_logs(logs); @@ -438,10 +440,10 @@ mod tests { BlockchainInterfaceWeb3, CONTRACT_ABI, REQUESTS_IN_PARALLEL, TRANSACTION_LITERAL, TRANSFER_METHOD_ID, }; - use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainError::QueryFailed; + use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainInterfaceError::QueryFailed; use crate::blockchain::blockchain_interface::data_structures::BlockchainTransaction; use crate::blockchain::blockchain_interface::{ - BlockchainAgentBuildError, BlockchainError, BlockchainInterface, + BlockchainAgentBuildError, BlockchainInterfaceError, BlockchainInterface, RetrievedBlockchainTransactions, }; use crate::blockchain::test_utils::{all_chains, make_blockchain_interface_web3, ReceiptResponseBuilder}; @@ -733,7 +735,7 @@ mod tests { assert_eq!( result.expect_err("Expected an Err, got Ok"), - BlockchainError::InvalidResponse + BlockchainInterfaceError::InvalidResponse ); } @@ -757,7 +759,7 @@ mod tests { ) .wait(); - assert_eq!(result, Err(BlockchainError::InvalidResponse)); + assert_eq!(result, Err(BlockchainInterfaceError::InvalidResponse)); } #[test] @@ -1007,7 +1009,7 @@ mod tests { let expected_err_factory = |wallet: &Wallet| { BlockchainAgentBuildError::TransactionFeeBalance( wallet.address(), - BlockchainError::QueryFailed( + BlockchainInterfaceError::QueryFailed( "Transport error: Error(IncompleteMessage)".to_string(), ), ) @@ -1029,7 +1031,7 @@ mod tests { let expected_err_factory = |wallet: &Wallet| { BlockchainAgentBuildError::ServiceFeeBalance( wallet.address(), - BlockchainError::QueryFailed( + BlockchainInterfaceError::QueryFailed( "Api error: Transport error: Error(IncompleteMessage)".to_string(), ), ) @@ -1207,7 +1209,7 @@ mod tests { Subject::calculate_end_block_marker( BlockMarker::Uninitialized, BlockScanRange::NoLimit, - Err(BlockchainError::InvalidResponse), + Err(BlockchainInterfaceError::InvalidResponse), &logger ), BlockMarker::Uninitialized @@ -1225,7 +1227,7 @@ mod tests { Subject::calculate_end_block_marker( BlockMarker::Uninitialized, BlockScanRange::Range(100), - Err(BlockchainError::InvalidResponse), + Err(BlockchainInterfaceError::InvalidResponse), &logger ), BlockMarker::Uninitialized @@ -1243,7 +1245,7 @@ mod tests { Subject::calculate_end_block_marker( BlockMarker::Value(50), BlockScanRange::NoLimit, - Err(BlockchainError::InvalidResponse), + Err(BlockchainInterfaceError::InvalidResponse), &logger ), BlockMarker::Uninitialized @@ -1261,7 +1263,7 @@ mod tests { Subject::calculate_end_block_marker( BlockMarker::Value(50), BlockScanRange::Range(100), - Err(BlockchainError::InvalidResponse), + Err(BlockchainInterfaceError::InvalidResponse), &logger ), BlockMarker::Value(150) diff --git a/node/src/blockchain/blockchain_interface/data_structures/errors.rs b/node/src/blockchain/blockchain_interface/data_structures/errors.rs index 3084accfb..83e91596b 100644 --- a/node/src/blockchain/blockchain_interface/data_structures/errors.rs +++ b/node/src/blockchain/blockchain_interface/data_structures/errors.rs @@ -11,7 +11,7 @@ const BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED: &str = "Uninitialized blockchain int being delinquency-banned, you should restart the Node with a value for blockchain-service-url"; #[derive(Clone, Debug, PartialEq, Eq, VariantCount)] -pub enum BlockchainError { +pub enum BlockchainInterfaceError { InvalidUrl, InvalidAddress, InvalidResponse, @@ -19,13 +19,13 @@ pub enum BlockchainError { UninitializedBlockchainInterface, } -impl Display for BlockchainError { +impl Display for BlockchainInterfaceError { fn fmt(&self, f: &mut Formatter) -> fmt::Result { let err_spec = match self { Self::InvalidUrl => Either::Left("Invalid url"), Self::InvalidAddress => Either::Left("Invalid address"), Self::InvalidResponse => Either::Left("Invalid response"), - Self::QueryFailed(msg) => Either::Right(format!("Query failed: {}", msg)), + Self::QueryFailed(msg) => Either::Right(format!("Query failed: {}", msg)), //TODO this should also incorporate AppRpcError Self::UninitializedBlockchainInterface => { Either::Left(BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED) } @@ -37,8 +37,8 @@ impl Display for BlockchainError { #[derive(Clone, Debug, PartialEq, Eq, VariantCount)] pub enum PayableTransactionError { MissingConsumingWallet, - GasPriceQueryFailed(BlockchainError), - TransactionID(BlockchainError), + GasPriceQueryFailed(BlockchainInterfaceError), + TransactionID(BlockchainInterfaceError), UnusableWallet(String), Signing(String), Sending { msg: String, hashes: Vec }, @@ -78,9 +78,9 @@ impl Display for PayableTransactionError { #[derive(Clone, Debug, PartialEq, Eq, VariantCount)] pub enum BlockchainAgentBuildError { - GasPrice(BlockchainError), - TransactionFeeBalance(Address, BlockchainError), - ServiceFeeBalance(Address, BlockchainError), + GasPrice(BlockchainInterfaceError), + TransactionFeeBalance(Address, BlockchainInterfaceError), + ServiceFeeBalance(Address, BlockchainInterfaceError), UninitializedBlockchainInterface, } @@ -119,7 +119,9 @@ mod tests { use crate::blockchain::blockchain_interface::data_structures::errors::{ PayableTransactionError, BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED, }; - use crate::blockchain::blockchain_interface::{BlockchainAgentBuildError, BlockchainError}; + use crate::blockchain::blockchain_interface::{ + BlockchainAgentBuildError, BlockchainInterfaceError, + }; use crate::blockchain::test_utils::make_tx_hash; use crate::test_utils::make_wallet; use masq_lib::utils::{slice_of_strs_to_vec_of_strings, to_string}; @@ -136,20 +138,20 @@ mod tests { #[test] fn blockchain_error_implements_display() { let original_errors = [ - BlockchainError::InvalidUrl, - BlockchainError::InvalidAddress, - BlockchainError::InvalidResponse, - BlockchainError::QueryFailed( + BlockchainInterfaceError::InvalidUrl, + BlockchainInterfaceError::InvalidAddress, + BlockchainInterfaceError::InvalidResponse, + BlockchainInterfaceError::QueryFailed( "Don't query so often, it gives me a headache".to_string(), ), - BlockchainError::UninitializedBlockchainInterface, + BlockchainInterfaceError::UninitializedBlockchainInterface, ]; let actual_error_msgs = original_errors.iter().map(to_string).collect::>(); assert_eq!( original_errors.len(), - BlockchainError::VARIANT_COUNT, + BlockchainInterfaceError::VARIANT_COUNT, "you forgot to add all variants in this test" ); assert_eq!( @@ -168,10 +170,10 @@ mod tests { fn payable_payment_error_implements_display() { let original_errors = [ PayableTransactionError::MissingConsumingWallet, - PayableTransactionError::GasPriceQueryFailed(BlockchainError::QueryFailed( + PayableTransactionError::GasPriceQueryFailed(BlockchainInterfaceError::QueryFailed( "Gas halves shut, no drop left".to_string(), )), - PayableTransactionError::TransactionID(BlockchainError::InvalidResponse), + PayableTransactionError::TransactionID(BlockchainInterfaceError::InvalidResponse), PayableTransactionError::UnusableWallet( "This is a LEATHER wallet, not LEDGER wallet, stupid.".to_string(), ), @@ -213,14 +215,14 @@ mod tests { fn blockchain_agent_build_error_implements_display() { let wallet = make_wallet("abc"); let original_errors = [ - BlockchainAgentBuildError::GasPrice(BlockchainError::InvalidResponse), + BlockchainAgentBuildError::GasPrice(BlockchainInterfaceError::InvalidResponse), BlockchainAgentBuildError::TransactionFeeBalance( wallet.address(), - BlockchainError::InvalidResponse, + BlockchainInterfaceError::InvalidResponse, ), BlockchainAgentBuildError::ServiceFeeBalance( wallet.address(), - BlockchainError::InvalidAddress, + BlockchainInterfaceError::InvalidAddress, ), BlockchainAgentBuildError::UninitializedBlockchainInterface, ]; diff --git a/node/src/blockchain/blockchain_interface/lower_level_interface.rs b/node/src/blockchain/blockchain_interface/lower_level_interface.rs index c8653f985..6ae07dca2 100644 --- a/node/src/blockchain/blockchain_interface/lower_level_interface.rs +++ b/node/src/blockchain/blockchain_interface/lower_level_interface.rs @@ -1,6 +1,6 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainError; +use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainInterfaceError; use ethereum_types::{H256, U64}; use futures::Future; use serde_json::Value; @@ -15,33 +15,33 @@ pub trait LowBlockchainInt { fn get_transaction_fee_balance( &self, address: Address, - ) -> Box>; + ) -> Box>; fn get_service_fee_balance( &self, address: Address, - ) -> Box>; + ) -> Box>; - fn get_gas_price(&self) -> Box>; + fn get_gas_price(&self) -> Box>; - fn get_block_number(&self) -> Box>; + fn get_block_number(&self) -> Box>; fn get_transaction_id( &self, address: Address, - ) -> Box>; + ) -> Box>; fn get_transaction_receipt_in_batch( &self, hash_vec: Vec, - ) -> Box>, Error = BlockchainError>>; + ) -> Box>, Error = BlockchainInterfaceError>>; fn get_contract_address(&self) -> Address; fn get_transaction_logs( &self, filter: Filter, - ) -> Box, Error = BlockchainError>>; + ) -> Box, Error = BlockchainInterfaceError>>; fn get_web3_batch(&self) -> Web3>; } diff --git a/node/src/blockchain/blockchain_interface/mod.rs b/node/src/blockchain/blockchain_interface/mod.rs index 242bf433f..eb736b2a3 100644 --- a/node/src/blockchain/blockchain_interface/mod.rs +++ b/node/src/blockchain/blockchain_interface/mod.rs @@ -6,7 +6,7 @@ pub mod lower_level_interface; use actix::Recipient; use ethereum_types::H256; -use crate::blockchain::blockchain_interface::data_structures::errors::{BlockchainAgentBuildError, BlockchainError, PayableTransactionError}; +use crate::blockchain::blockchain_interface::data_structures::errors::{BlockchainAgentBuildError, BlockchainInterfaceError, PayableTransactionError}; use crate::blockchain::blockchain_interface::data_structures::{ProcessedPayableFallible, RetrievedBlockchainTransactions}; use crate::blockchain::blockchain_interface::lower_level_interface::LowBlockchainInt; use crate::sub_lib::wallet::Wallet; @@ -31,7 +31,7 @@ pub trait BlockchainInterface { start_block: BlockMarker, scan_range: BlockScanRange, recipient: Address, - ) -> Box>; + ) -> Box>; fn introduce_blockchain_agent( &self, @@ -41,7 +41,7 @@ pub trait BlockchainInterface { fn process_transaction_receipts( &self, transaction_hashes: Vec, - ) -> Box, Error = BlockchainError>>; + ) -> Box, Error = BlockchainInterfaceError>>; fn submit_payables_in_batch( &self, diff --git a/node/src/blockchain/errors.rs b/node/src/blockchain/errors.rs index 865bea29c..d6bd1a2c5 100644 --- a/node/src/blockchain/errors.rs +++ b/node/src/blockchain/errors.rs @@ -1,6 +1,101 @@ +use std::borrow::Borrow; +// Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. +use serde::{Deserialize as DeserializeTrait, Serialize as SerializeTrait}; use serde_derive::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::fmt::{Debug, Formatter}; +use std::hash::{Hash, Hasher}; +use std::time::SystemTime; use web3::error::Error as Web3Error; +impl SerializeTrait for Box { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + todo!() + } +} + +impl<'de> DeserializeTrait<'de> for Box { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + todo!() + } +} + +impl Clone for Box { + fn clone(&self) -> Self { + self.dup() + } +} + +impl PartialEq for Box { + fn eq(&self, other: &Self) -> bool { + todo!() + } +} + +impl Hash for Box { + fn hash(&self, state: &mut H) { + self.costume_hash_fn(state) + } +} + +impl Eq for Box {} + +pub trait BlockchainDbError: Debug { + fn serialize(&self) -> String; + fn deserialize(str: &str) -> Box + where + Self: Sized; + fn partial_eq(&self, other: &dyn BlockchainDbError) -> bool; + fn costume_hash_fn(&self, hasher: &mut dyn Hasher); + fn dup(&self) -> Box; + as_any_ref_in_trait!(); +} + +impl BlockchainDbError for AppRpcErrorKind { + fn serialize(&self) -> String { + todo!() + } + + fn deserialize(str: &str) -> Box + where + Self: Sized, + { + todo!() + } + + fn partial_eq(&self, other: &dyn BlockchainDbError) -> bool { + todo!() + } + + fn costume_hash_fn(&self, hasher: &mut dyn Hasher) { + match self { + AppRpcErrorKind::Decoder => hasher.write_u8(0), + AppRpcErrorKind::Internal => hasher.write_u8(1), + AppRpcErrorKind::IO => hasher.write_u8(2), + AppRpcErrorKind::Signing => hasher.write_u8(3), + AppRpcErrorKind::Transport => hasher.write_u8(4), + AppRpcErrorKind::InvalidResponse => hasher.write_u8(5), + AppRpcErrorKind::ServerUnreachable => hasher.write_u8(6), + AppRpcErrorKind::Web3RpcError(code) => { + hasher.write_u8(7); + hasher.write_i64(*code); + } + } + } + + fn dup(&self) -> Box { + Box::new(self.clone()) + } + + as_any_ref_in_trait_impl!(); +} + // Prefixed with App to clearly distinguish app-specific errors from library errors. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum AppRpcError { @@ -24,6 +119,135 @@ pub enum RemoteError { Web3RpcError { code: i64, message: String }, } +#[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum AppRpcErrorKind { + // Local + Decoder, + Internal, + IO, + Signing, + Transport, + + // Remote + InvalidResponse, + ServerUnreachable, + Web3RpcError(i64), // Keep only the stable error code +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum MASQError { + PendingTooLongNotReplaced, +} + +impl BlockchainDbError for MASQError { + fn serialize(&self) -> String { + todo!() + } + + fn deserialize(str: &str) -> Box + where + Self: Sized, + { + todo!() + } + + fn partial_eq(&self, other: &dyn BlockchainDbError) -> bool { + todo!() + } + + fn costume_hash_fn(&self, hasher: &mut dyn Hasher) { + match self { + MASQError::PendingTooLongNotReplaced => hasher.write_u8(0), + } + } + + fn dup(&self) -> Box { + Box::new(self.clone()) + } + + as_any_ref_in_trait_impl!(); +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ErrorStats { + #[serde(rename = "firstSeen")] + pub first_seen: SystemTime, + pub attempts: u16, +} + +impl ErrorStats { + pub fn now(clock: &dyn ValidationFailureClock) -> Self { + Self { + first_seen: clock.now(), + attempts: 1, + } + } + + pub fn increment(&mut self) { + self.attempts += 1; + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PreviousAttempts { + #[serde(flatten)] + inner: HashMap, ErrorStats>, +} + +impl PreviousAttempts { + pub fn new(error: Box, clock: &dyn ValidationFailureClock) -> Self { + Self { + inner: hashmap!(error => ErrorStats::now(clock)), + } + } + + pub fn add_attempt( + mut self, + error: Box, + clock: &dyn ValidationFailureClock, + ) -> Self { + self.inner + .entry(error) + .and_modify(|stats| stats.increment()) + .or_insert_with(|| ErrorStats::now(clock)); + self + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum ValidationStatus { + Waiting, + Reattempting(PreviousAttempts), +} + +enum ErrorKinds { + AppRcpError(AppRpcErrorKind), + Uninterpretable(UninterpretabilityReason), +} + +enum UninterpretabilityReason { + FailedTxLeftPending, +} + +impl From for AppRpcErrorKind { + fn from(err: AppRpcError) -> Self { + match err { + AppRpcError::Local(local) => match local { + LocalError::Decoder(_) => Self::Decoder, + LocalError::Internal => Self::Internal, + LocalError::Io(_) => Self::IO, + LocalError::Signing(_) => Self::Signing, + LocalError::Transport(_) => Self::Transport, + }, + AppRpcError::Remote(remote) => match remote { + RemoteError::InvalidResponse(_) => Self::InvalidResponse, + RemoteError::Unreachable => Self::ServerUnreachable, + RemoteError::Web3RpcError { code, .. } => Self::Web3RpcError(code), + }, + } + } +} + // EVM based errors impl From for AppRpcError { fn from(error: Web3Error) -> Self { @@ -51,8 +275,30 @@ impl From for AppRpcError { } } +// TODO this is here just to appease the compiler; Jan has this in a diff location +pub trait ValidationFailureClock { + fn now(&self) -> SystemTime; +} + +#[derive(Default)] +pub struct ValidationFailureClockReal {} + +impl ValidationFailureClock for ValidationFailureClockReal { + fn now(&self) -> SystemTime { + SystemTime::now() + } +} +// TODO here it ende + mod tests { - use crate::blockchain::errors::{AppRpcError, LocalError, RemoteError}; + use crate::blockchain::errors::{ + AppRpcError, AppRpcErrorKind, BlockchainDbError, LocalError, MASQError, PreviousAttempts, + RemoteError, ValidationFailureClockReal, + }; + use std::collections::hash_map::DefaultHasher; + use std::fmt::Debug; + use std::hash::{Hash, Hasher}; + use std::time::SystemTime; use web3::error::Error as Web3Error; #[test] @@ -124,4 +370,219 @@ mod tests { assert_eq!(error, deserialized, "Error: {:?}", error); }); } + + #[test] + fn previous_attempts_and_validation_failure_clock_work_together_fine() { + let validation_failure_clock = ValidationFailureClockReal::default(); + // new() + let timestamp_a = SystemTime::now(); + let subject = PreviousAttempts::new( + Box::new(AppRpcErrorKind::Decoder), + &validation_failure_clock, + ); + // add_attempt() + let timestamp_b = SystemTime::now(); + let subject = subject.add_attempt( + Box::new(AppRpcErrorKind::Internal), + &validation_failure_clock, + ); + let timestamp_c = SystemTime::now(); + let subject = subject.add_attempt(Box::new(AppRpcErrorKind::IO), &validation_failure_clock); + let timestamp_d = SystemTime::now(); + let subject = subject.add_attempt( + Box::new(AppRpcErrorKind::Decoder), + &validation_failure_clock, + ); + let subject = subject.add_attempt(Box::new(AppRpcErrorKind::IO), &validation_failure_clock); + + let decoder_error_stats = subject + .inner + .get(&(Box::new(AppRpcErrorKind::Decoder) as Box)) + .unwrap(); + assert!( + timestamp_a <= decoder_error_stats.first_seen + && decoder_error_stats.first_seen <= timestamp_b, + "Was expected from {:?} to {:?} but was {:?}", + timestamp_a, + timestamp_b, + decoder_error_stats.first_seen + ); + assert_eq!(decoder_error_stats.attempts, 2); + let internal_error_stats = subject + .inner + .get(&(Box::new(AppRpcErrorKind::Internal) as Box)) + .unwrap(); + assert!( + timestamp_b <= internal_error_stats.first_seen + && internal_error_stats.first_seen <= timestamp_c, + "Was expected from {:?} to {:?} but was {:?}", + timestamp_b, + timestamp_c, + internal_error_stats.first_seen + ); + assert_eq!(internal_error_stats.attempts, 1); + let io_error_stats = subject + .inner + .get(&(Box::new(AppRpcErrorKind::IO) as Box)) + .unwrap(); + assert!( + timestamp_c <= io_error_stats.first_seen && io_error_stats.first_seen <= timestamp_d, + "Was expected from {:?} to {:?} but was {:?}", + timestamp_c, + timestamp_d, + io_error_stats.first_seen + ); + assert_eq!(io_error_stats.attempts, 2); + let other_error_stats = subject + .inner + .get(&(Box::new(AppRpcErrorKind::Signing) as Box)); + assert_eq!(other_error_stats, None); + } + + #[test] + fn conversion_between_app_rpc_error_and_app_rpc_error_kind_works() { + assert_eq!( + AppRpcErrorKind::from(AppRpcError::Local(LocalError::Decoder( + "Decoder error".to_string() + ))), + AppRpcErrorKind::Decoder + ); + assert_eq!( + AppRpcErrorKind::from(AppRpcError::Local(LocalError::Internal)), + AppRpcErrorKind::Internal + ); + assert_eq!( + AppRpcErrorKind::from(AppRpcError::Local(LocalError::Io("IO error".to_string()))), + AppRpcErrorKind::IO + ); + assert_eq!( + AppRpcErrorKind::from(AppRpcError::Local(LocalError::Signing( + "Signing error".to_string() + ))), + AppRpcErrorKind::Signing + ); + assert_eq!( + AppRpcErrorKind::from(AppRpcError::Local(LocalError::Transport( + "Transport error".to_string() + ))), + AppRpcErrorKind::Transport + ); + assert_eq!( + AppRpcErrorKind::from(AppRpcError::Remote(RemoteError::InvalidResponse( + "Invalid response".to_string() + ))), + AppRpcErrorKind::InvalidResponse + ); + assert_eq!( + AppRpcErrorKind::from(AppRpcError::Remote(RemoteError::Unreachable)), + AppRpcErrorKind::ServerUnreachable + ); + assert_eq!( + AppRpcErrorKind::from(AppRpcError::Remote(RemoteError::Web3RpcError { + code: 55, + message: "Booga".to_string() + })), + AppRpcErrorKind::Web3RpcError(55) + ); + } + + #[test] + fn clone_works_for_blockchain_db_error_wrapping_app_rpc_error_kind() { + let subject: Box = Box::new(AppRpcErrorKind::Web3RpcError(123)); + + let result = subject.clone(); + + test_clone_for_blockchain_db_error::(subject); + } + + #[test] + fn clone_works_for_blockchain_db_error_wrapping_masq_error() { + let subject: Box = Box::new(MASQError::PendingTooLongNotReplaced); + + test_clone_for_blockchain_db_error::(subject); + } + + fn test_clone_for_blockchain_db_error(subject: Box) + where + ErrorType: PartialEq + Debug + 'static, + { + let result = subject.clone(); + + let specified_subject = subject.as_any().downcast_ref::().unwrap(); + let specified_result = result.as_any().downcast_ref::().unwrap(); + assert_eq!(specified_result, specified_subject) + } + #[test] + fn hashing_for_app_arp_error_kind_works() { + let mut hasher = DefaultHasher::default(); + let mut hashes = vec![ + Box::new(AppRpcErrorKind::Decoder) as Box, + Box::new(AppRpcErrorKind::Internal), + Box::new(AppRpcErrorKind::IO), + Box::new(AppRpcErrorKind::Signing), + Box::new(AppRpcErrorKind::Transport), + Box::new(AppRpcErrorKind::InvalidResponse), + Box::new(AppRpcErrorKind::ServerUnreachable), + Box::new(AppRpcErrorKind::Web3RpcError(123)), + Box::new(AppRpcErrorKind::Web3RpcError(124)), + Box::new(AppRpcErrorKind::Web3RpcError(555555)), + ] + .into_iter() + .map(|blockchain_error| { + blockchain_error.hash(&mut hasher); + + hasher.finish() + }) + .collect::>(); + + hashes.clone().iter().for_each(|picked_hash| { + hashes.remove(0); + hashes.iter().for_each(|other_hash| { + assert_ne!(picked_hash, other_hash); + }); + }) + } + + #[test] + fn hashing_for_masq_error_works() { + let mut hasher = DefaultHasher::default(); + let mut hashes = vec![ + Box::new(MASQError::PendingTooLongNotReplaced) as Box, + // Add more types here as there are more types of MASQ errors. + ] + .into_iter() + .map(|blockchain_error| { + blockchain_error.hash(&mut hasher); + + hasher.finish() + }) + .collect::>(); + + hashes.clone().iter().for_each(|picked_hash| { + hashes.remove(0); + hashes.iter().for_each(|other_hash| { + assert_ne!(picked_hash, other_hash); + }); + }) + } + + #[test] + fn serialization_and_deserialization_for_blockchain_db_error_works() { + vec![ + ( + Box::new(AppRpcErrorKind::Web3RpcError(123)) as Box, + "bluh", + ), + (Box::new(AppRpcErrorKind::Internal), "bluh2"), + (Box::new(MASQError::PendingTooLongNotReplaced), "bluh3"), + ] + .into_iter() + .for_each(|(blockchain_error, expected_result)| { + let json_result = serde_json::to_string(&blockchain_error).unwrap(); + assert_eq!(json_result, expected_result); + let trait_object_result = + serde_json::from_str::>(&json_result).unwrap(); + assert_eq!(&trait_object_result, &blockchain_error); + }) + } } diff --git a/node/src/blockchain/test_utils.rs b/node/src/blockchain/test_utils.rs index 6259e8739..4c7099f45 100644 --- a/node/src/blockchain/test_utils.rs +++ b/node/src/blockchain/test_utils.rs @@ -5,6 +5,7 @@ use crate::blockchain::blockchain_interface::blockchain_interface_web3::{ BlockchainInterfaceWeb3, REQUESTS_IN_PARALLEL, }; +use crate::blockchain::errors::ValidationFailureClock; use bip39::{Language, Mnemonic, Seed}; use ethabi::Hash; use ethereum_types::{BigEndianHash, H160, H256, U64}; @@ -13,8 +14,10 @@ use masq_lib::blockchains::chains::Chain; use masq_lib::utils::to_string; use serde::Serialize; use serde_derive::Deserialize; +use std::cell::RefCell; use std::fmt::Debug; use std::net::Ipv4Addr; +use std::time::SystemTime; use web3::transports::{EventLoopHandle, Http}; use web3::types::{Index, Log, SignedTransaction, TransactionReceipt, H2048, U256}; @@ -225,3 +228,21 @@ pub fn transport_error_message() -> String { "Connection refused".to_string() } } + +#[derive(Default)] +pub struct ValidationFailureClockMock { + now_results: RefCell>, +} + +impl ValidationFailureClock for ValidationFailureClockMock { + fn now(&self) -> SystemTime { + self.now_results.borrow_mut().remove(0) + } +} + +impl ValidationFailureClockMock { + pub fn now_result(self, result: SystemTime) -> Self { + self.now_results.borrow_mut().push(result); + self + } +} From b8187eff056023a25376063d8299d8c246e15113 Mon Sep 17 00:00:00 2001 From: Bert Date: Sun, 17 Aug 2025 15:45:51 +0200 Subject: [PATCH 37/61] GH-683: interim commit --- .../db_access_objects/failed_payable_dao.rs | 18 +- .../db_access_objects/payable_dao.rs | 4 +- .../db_access_objects/sent_payable_dao.rs | 11 +- node/src/accountant/mod.rs | 4 +- node/src/accountant/scanners/mod.rs | 2 +- node/src/blockchain/blockchain_bridge.rs | 2 +- .../data_structures/errors.rs | 2 +- node/src/blockchain/errors.rs | 588 ------------------ .../app_rpc_web3_error_kind.rs | 250 ++++++++ .../errors/blockchain_db_error/masq_error.rs | 109 ++++ .../errors/blockchain_db_error/mod.rs | 98 +++ .../blockchain_error/app_rpc_web3_error.rs | 104 ++++ .../blockchain/errors/blockchain_error/mod.rs | 7 + node/src/blockchain/errors/mod.rs | 6 + node/src/blockchain/errors/test_utils.rs | 15 + .../blockchain/errors/validation_status.rs | 147 +++++ node/src/blockchain/test_utils.rs | 2 +- node/src/listener_handler.rs | 2 +- node/src/proxy_client/stream_reader.rs | 2 +- node/src/proxy_client/stream_writer.rs | 2 +- node/src/proxy_server/mod.rs | 2 +- node/src/stream_reader.rs | 2 +- node/src/stream_writer_sorted.rs | 2 +- node/src/stream_writer_unsorted.rs | 2 +- 24 files changed, 766 insertions(+), 617 deletions(-) delete mode 100644 node/src/blockchain/errors.rs create mode 100644 node/src/blockchain/errors/blockchain_db_error/app_rpc_web3_error_kind.rs create mode 100644 node/src/blockchain/errors/blockchain_db_error/masq_error.rs create mode 100644 node/src/blockchain/errors/blockchain_db_error/mod.rs create mode 100644 node/src/blockchain/errors/blockchain_error/app_rpc_web3_error.rs create mode 100644 node/src/blockchain/errors/blockchain_error/mod.rs create mode 100644 node/src/blockchain/errors/mod.rs create mode 100644 node/src/blockchain/errors/test_utils.rs create mode 100644 node/src/blockchain/errors/validation_status.rs 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 48d7d8e54..d6b0a35b2 100644 --- a/node/src/accountant/db_access_objects/failed_payable_dao.rs +++ b/node/src/accountant/db_access_objects/failed_payable_dao.rs @@ -4,7 +4,7 @@ use crate::accountant::db_access_objects::utils::{ }; use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; use crate::accountant::{checked_conversion, comma_joined_stringifiable}; -use crate::blockchain::errors::{AppRpcError, BlockchainDbError, PreviousAttempts}; +use crate::blockchain::errors::validation_status::PreviousAttempts; use crate::database::rusqlite_wrappers::ConnectionWrapper; use itertools::Itertools; use masq_lib::utils::ExpectValue; @@ -381,9 +381,9 @@ mod tests { make_read_only_db_connection, FailedTxBuilder, }; use crate::accountant::db_access_objects::utils::current_unix_timestamp; - use crate::blockchain::errors::{ - AppRpcError, AppRpcErrorKind, LocalError, PreviousAttempts, RemoteError, - ValidationFailureClockReal, + use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; + use crate::blockchain::errors::validation_status::{ + PreviousAttempts, ValidationFailureClockReal, }; use crate::blockchain::test_utils::{make_tx_hash, ValidationFailureClockMock}; use crate::database::db_initializer::{ @@ -592,7 +592,7 @@ mod tests { assert_eq!( FailureReason::from_str(r#"{"Submission":{"Local":{"Decoder"}}}"#).unwrap(), FailureReason::Submission(PreviousAttempts::new( - Box::new(AppRpcErrorKind::Decoder), + Box::new(AppRpcWeb3ErrorKind::Decoder), &validation_failure_clock )) ); @@ -643,7 +643,7 @@ mod tests { assert_eq!( FailureStatus::from_str(r#"{"RecheckRequired":{"Reattempting":{"ServerUnreachable":{"firstSeen":{"secs_since_epoch":1755080031,"nanos_since_epoch":612180914},"attempts":1}}}}"#).unwrap(), - FailureStatus::RecheckRequired(ValidationStatus::Reattempting( PreviousAttempts::new(Box::new(AppRpcErrorKind::ServerUnreachable), &validation_failure_clock))) + FailureStatus::RecheckRequired(ValidationStatus::Reattempting( PreviousAttempts::new(Box::new(AppRpcWeb3ErrorKind::ServerUnreachable), &validation_failure_clock))) ); assert_eq!( @@ -726,7 +726,7 @@ mod tests { .reason(PendingTooLong) .status(RecheckRequired(ValidationStatus::Reattempting( PreviousAttempts::new( - Box::new(AppRpcErrorKind::ServerUnreachable), + Box::new(AppRpcWeb3ErrorKind::ServerUnreachable), &ValidationFailureClockReal::default(), ), ))) @@ -782,7 +782,7 @@ mod tests { ( tx2.hash, RecheckRequired(ValidationStatus::Reattempting(PreviousAttempts::new( - Box::new(AppRpcErrorKind::ServerUnreachable), + Box::new(AppRpcWeb3ErrorKind::ServerUnreachable), &ValidationFailureClockReal::default(), ))), ), @@ -799,7 +799,7 @@ mod tests { assert_eq!( updated_txs[1].status, RecheckRequired(ValidationStatus::Reattempting(PreviousAttempts::new( - Box::new(AppRpcErrorKind::ServerUnreachable), + Box::new(AppRpcWeb3ErrorKind::ServerUnreachable), &ValidationFailureClockReal::default() ))) ); diff --git a/node/src/accountant/db_access_objects/payable_dao.rs b/node/src/accountant/db_access_objects/payable_dao.rs index c7d438a41..f6cebe4ef 100644 --- a/node/src/accountant/db_access_objects/payable_dao.rs +++ b/node/src/accountant/db_access_objects/payable_dao.rs @@ -417,7 +417,7 @@ mod mark_pending_payable_associated_functions { }, Err(errs) => { let err_msg = format!( - "Multi-row update to mark pending payable hit these errors: {:?}", + "Multi-row update to mark pending payable hit these app_rpc_web3_error_kind: {:?}", errs ); Err(PayableDaoError::RusqliteError(err_msg)) @@ -874,7 +874,7 @@ mod tests { assert_eq!( result, Err(PayableDaoError::RusqliteError( - "Multi-row update to mark pending payable hit these errors: [SqliteFailure(\ + "Multi-row update to mark pending payable hit these app_rpc_web3_error_kind: [SqliteFailure(\ Error { code: ReadOnly, extended_code: 8 }, Some(\"attempt to write a readonly \ database\"))]" .to_string() 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 cb62f609b..c17061ee6 100644 --- a/node/src/accountant/db_access_objects/sent_payable_dao.rs +++ b/node/src/accountant/db_access_objects/sent_payable_dao.rs @@ -441,7 +441,8 @@ mod tests { use crate::accountant::db_access_objects::sent_payable_dao::SentPayableDaoError::{EmptyInput, PartialExecution}; use crate::accountant::db_access_objects::test_utils::{make_read_only_db_connection, TxBuilder}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock}; - use crate::blockchain::errors::{AppRpcError, AppRpcErrorKind, PreviousAttempts, RemoteError, ValidationFailureClockReal}; + use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; + use crate::blockchain::errors::validation_status::{PreviousAttempts, ValidationFailureClockReal}; use crate::blockchain::test_utils::{make_block_hash, make_tx_hash, ValidationFailureClockMock}; #[test] @@ -456,11 +457,11 @@ mod tests { .hash(make_tx_hash(2)) .status(TxStatus::Pending(ValidationStatus::Reattempting( PreviousAttempts::new( - Box::new(AppRpcErrorKind::ServerUnreachable), + Box::new(AppRpcWeb3ErrorKind::ServerUnreachable), &ValidationFailureClockReal::default(), ) .add_attempt( - Box::new(AppRpcErrorKind::ServerUnreachable), + Box::new(AppRpcWeb3ErrorKind::ServerUnreachable), &ValidationFailureClockReal::default(), ), ))) @@ -692,7 +693,7 @@ mod tests { .hash(make_tx_hash(2)) .status(TxStatus::Pending(ValidationStatus::Reattempting( PreviousAttempts::new( - Box::new(AppRpcErrorKind::ServerUnreachable), + Box::new(AppRpcWeb3ErrorKind::ServerUnreachable), &ValidationFailureClockReal::default(), ), ))) @@ -1188,7 +1189,7 @@ mod tests { assert_eq!( TxStatus::from_str(r#"{"Pending":{"Reattempting":{"InvalidResponse":{"firstSeen":{"secs_since_epoch":12456,"nanos_since_epoch":0},"attempts":1}}}}"#).unwrap(), - TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new(Box::new(AppRpcErrorKind::InvalidResponse), &validation_failure_clock))) + TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new(Box::new(AppRpcWeb3ErrorKind::InvalidResponse), &validation_failure_clock))) ); assert_eq!( diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index 39ead2d76..845b43272 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -2387,7 +2387,7 @@ mod tests { payable_scanner_mock, ))); subject.ui_message_sub_opt = Some(ui_gateway_addr.recipient()); - // It must be populated because no errors are tolerated at the RetryPayableScanner + // It must be populated because no app_rpc_web3_error_kind are tolerated at the RetryPayableScanner // if automatic scans are on let response_skeleton_opt = Some(ResponseSkeleton { client_id: 789, @@ -4020,7 +4020,7 @@ mod tests { // the first message. Now we reset the state by ending the first scan by a failure and see // that the third scan request is going to be accepted willingly again. addr.try_send(SentPayables { - payment_procedure_result: Err(PayableTransactionError::Signing("bluh".to_string())), + payment_procedure_result: Err(PayableTransactionError::Signing("blah".to_string())), response_skeleton_opt: Some(ResponseSkeleton { client_id: 1122, context_id: 7788, diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index 02aa19459..02d21d467 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -3099,7 +3099,7 @@ mod tests { ScanError { scan_type, response_skeleton_opt: None, - msg: "bluh".to_string(), + msg: "blah".to_string(), } } diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index 421fc6bd5..83fb4b2e1 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -2188,7 +2188,7 @@ mod tests { /* blastapi - completely rejected call on Public endpoint as won't handle eth_getLogs method on public API - [{"jsonrpc":"2.0","id":2,"error":{"code":-32601,"message":"Method not found","data":{"method":""}}},{"jsonrpc":"2.0","id":1,"error":{"code":-32600,"message":"Invalid Request","data":{"message":"Cancelled due to validation errors in batch request"}}}] (edited) + [{"jsonrpc":"2.0","id":2,"error":{"code":-32601,"message":"Method not found","data":{"method":""}}},{"jsonrpc":"2.0","id":1,"error":{"code":-32600,"message":"Invalid Request","data":{"message":"Cancelled due to validation app_rpc_web3_error_kind in batch request"}}}] (edited) [8:50 AM] */ diff --git a/node/src/blockchain/blockchain_interface/data_structures/errors.rs b/node/src/blockchain/blockchain_interface/data_structures/errors.rs index 83e91596b..e168f7d73 100644 --- a/node/src/blockchain/blockchain_interface/data_structures/errors.rs +++ b/node/src/blockchain/blockchain_interface/data_structures/errors.rs @@ -25,7 +25,7 @@ impl Display for BlockchainInterfaceError { Self::InvalidUrl => Either::Left("Invalid url"), Self::InvalidAddress => Either::Left("Invalid address"), Self::InvalidResponse => Either::Left("Invalid response"), - Self::QueryFailed(msg) => Either::Right(format!("Query failed: {}", msg)), //TODO this should also incorporate AppRpcError + Self::QueryFailed(msg) => Either::Right(format!("Query failed: {}", msg)), //TODO this should also incorporate AppRpcWeb3Error Self::UninitializedBlockchainInterface => { Either::Left(BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED) } diff --git a/node/src/blockchain/errors.rs b/node/src/blockchain/errors.rs deleted file mode 100644 index d6bd1a2c5..000000000 --- a/node/src/blockchain/errors.rs +++ /dev/null @@ -1,588 +0,0 @@ -use std::borrow::Borrow; -// Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use serde::{Deserialize as DeserializeTrait, Serialize as SerializeTrait}; -use serde_derive::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::fmt::{Debug, Formatter}; -use std::hash::{Hash, Hasher}; -use std::time::SystemTime; -use web3::error::Error as Web3Error; - -impl SerializeTrait for Box { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - todo!() - } -} - -impl<'de> DeserializeTrait<'de> for Box { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - todo!() - } -} - -impl Clone for Box { - fn clone(&self) -> Self { - self.dup() - } -} - -impl PartialEq for Box { - fn eq(&self, other: &Self) -> bool { - todo!() - } -} - -impl Hash for Box { - fn hash(&self, state: &mut H) { - self.costume_hash_fn(state) - } -} - -impl Eq for Box {} - -pub trait BlockchainDbError: Debug { - fn serialize(&self) -> String; - fn deserialize(str: &str) -> Box - where - Self: Sized; - fn partial_eq(&self, other: &dyn BlockchainDbError) -> bool; - fn costume_hash_fn(&self, hasher: &mut dyn Hasher); - fn dup(&self) -> Box; - as_any_ref_in_trait!(); -} - -impl BlockchainDbError for AppRpcErrorKind { - fn serialize(&self) -> String { - todo!() - } - - fn deserialize(str: &str) -> Box - where - Self: Sized, - { - todo!() - } - - fn partial_eq(&self, other: &dyn BlockchainDbError) -> bool { - todo!() - } - - fn costume_hash_fn(&self, hasher: &mut dyn Hasher) { - match self { - AppRpcErrorKind::Decoder => hasher.write_u8(0), - AppRpcErrorKind::Internal => hasher.write_u8(1), - AppRpcErrorKind::IO => hasher.write_u8(2), - AppRpcErrorKind::Signing => hasher.write_u8(3), - AppRpcErrorKind::Transport => hasher.write_u8(4), - AppRpcErrorKind::InvalidResponse => hasher.write_u8(5), - AppRpcErrorKind::ServerUnreachable => hasher.write_u8(6), - AppRpcErrorKind::Web3RpcError(code) => { - hasher.write_u8(7); - hasher.write_i64(*code); - } - } - } - - fn dup(&self) -> Box { - Box::new(self.clone()) - } - - as_any_ref_in_trait_impl!(); -} - -// Prefixed with App to clearly distinguish app-specific errors from library errors. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum AppRpcError { - Local(LocalError), - Remote(RemoteError), -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum LocalError { - Decoder(String), - Internal, - Io(String), - Signing(String), - Transport(String), -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum RemoteError { - InvalidResponse(String), - Unreachable, - Web3RpcError { code: i64, message: String }, -} - -#[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum AppRpcErrorKind { - // Local - Decoder, - Internal, - IO, - Signing, - Transport, - - // Remote - InvalidResponse, - ServerUnreachable, - Web3RpcError(i64), // Keep only the stable error code -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum MASQError { - PendingTooLongNotReplaced, -} - -impl BlockchainDbError for MASQError { - fn serialize(&self) -> String { - todo!() - } - - fn deserialize(str: &str) -> Box - where - Self: Sized, - { - todo!() - } - - fn partial_eq(&self, other: &dyn BlockchainDbError) -> bool { - todo!() - } - - fn costume_hash_fn(&self, hasher: &mut dyn Hasher) { - match self { - MASQError::PendingTooLongNotReplaced => hasher.write_u8(0), - } - } - - fn dup(&self) -> Box { - Box::new(self.clone()) - } - - as_any_ref_in_trait_impl!(); -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct ErrorStats { - #[serde(rename = "firstSeen")] - pub first_seen: SystemTime, - pub attempts: u16, -} - -impl ErrorStats { - pub fn now(clock: &dyn ValidationFailureClock) -> Self { - Self { - first_seen: clock.now(), - attempts: 1, - } - } - - pub fn increment(&mut self) { - self.attempts += 1; - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct PreviousAttempts { - #[serde(flatten)] - inner: HashMap, ErrorStats>, -} - -impl PreviousAttempts { - pub fn new(error: Box, clock: &dyn ValidationFailureClock) -> Self { - Self { - inner: hashmap!(error => ErrorStats::now(clock)), - } - } - - pub fn add_attempt( - mut self, - error: Box, - clock: &dyn ValidationFailureClock, - ) -> Self { - self.inner - .entry(error) - .and_modify(|stats| stats.increment()) - .or_insert_with(|| ErrorStats::now(clock)); - self - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum ValidationStatus { - Waiting, - Reattempting(PreviousAttempts), -} - -enum ErrorKinds { - AppRcpError(AppRpcErrorKind), - Uninterpretable(UninterpretabilityReason), -} - -enum UninterpretabilityReason { - FailedTxLeftPending, -} - -impl From for AppRpcErrorKind { - fn from(err: AppRpcError) -> Self { - match err { - AppRpcError::Local(local) => match local { - LocalError::Decoder(_) => Self::Decoder, - LocalError::Internal => Self::Internal, - LocalError::Io(_) => Self::IO, - LocalError::Signing(_) => Self::Signing, - LocalError::Transport(_) => Self::Transport, - }, - AppRpcError::Remote(remote) => match remote { - RemoteError::InvalidResponse(_) => Self::InvalidResponse, - RemoteError::Unreachable => Self::ServerUnreachable, - RemoteError::Web3RpcError { code, .. } => Self::Web3RpcError(code), - }, - } - } -} - -// EVM based errors -impl From for AppRpcError { - fn from(error: Web3Error) -> Self { - match error { - // Local Errors - Web3Error::Decoder(error) => AppRpcError::Local(LocalError::Decoder(error)), - Web3Error::Internal => AppRpcError::Local(LocalError::Internal), - Web3Error::Io(error) => AppRpcError::Local(LocalError::Io(error.to_string())), - Web3Error::Signing(error) => { - // This variant cannot be tested due to import limitations. - AppRpcError::Local(LocalError::Signing(error.to_string())) - } - Web3Error::Transport(error) => AppRpcError::Local(LocalError::Transport(error)), - - // Api Errors - Web3Error::InvalidResponse(response) => { - AppRpcError::Remote(RemoteError::InvalidResponse(response)) - } - Web3Error::Rpc(web3_rpc_error) => AppRpcError::Remote(RemoteError::Web3RpcError { - code: web3_rpc_error.code.code(), - message: web3_rpc_error.message, - }), - Web3Error::Unreachable => AppRpcError::Remote(RemoteError::Unreachable), - } - } -} - -// TODO this is here just to appease the compiler; Jan has this in a diff location -pub trait ValidationFailureClock { - fn now(&self) -> SystemTime; -} - -#[derive(Default)] -pub struct ValidationFailureClockReal {} - -impl ValidationFailureClock for ValidationFailureClockReal { - fn now(&self) -> SystemTime { - SystemTime::now() - } -} -// TODO here it ende - -mod tests { - use crate::blockchain::errors::{ - AppRpcError, AppRpcErrorKind, BlockchainDbError, LocalError, MASQError, PreviousAttempts, - RemoteError, ValidationFailureClockReal, - }; - use std::collections::hash_map::DefaultHasher; - use std::fmt::Debug; - use std::hash::{Hash, Hasher}; - use std::time::SystemTime; - use web3::error::Error as Web3Error; - - #[test] - fn web3_error_to_failure_reason_conversion_works() { - // Local Errors - assert_eq!( - AppRpcError::from(Web3Error::Decoder("Decoder error".to_string())), - AppRpcError::Local(LocalError::Decoder("Decoder error".to_string())) - ); - assert_eq!( - AppRpcError::from(Web3Error::Internal), - AppRpcError::Local(LocalError::Internal) - ); - assert_eq!( - AppRpcError::from(Web3Error::Io(std::io::Error::new( - std::io::ErrorKind::Other, - "IO error" - ))), - AppRpcError::Local(LocalError::Io("IO error".to_string())) - ); - assert_eq!( - AppRpcError::from(Web3Error::Transport("Transport error".to_string())), - AppRpcError::Local(LocalError::Transport("Transport error".to_string())) - ); - - // Api Errors - assert_eq!( - AppRpcError::from(Web3Error::InvalidResponse("Invalid response".to_string())), - AppRpcError::Remote(RemoteError::InvalidResponse("Invalid response".to_string())) - ); - assert_eq!( - AppRpcError::from(Web3Error::Rpc(jsonrpc_core::types::error::Error { - code: jsonrpc_core::types::error::ErrorCode::ServerError(42), - message: "RPC error".to_string(), - data: None, - })), - AppRpcError::Remote(RemoteError::Web3RpcError { - code: 42, - message: "RPC error".to_string(), - }) - ); - assert_eq!( - AppRpcError::from(Web3Error::Unreachable), - AppRpcError::Remote(RemoteError::Unreachable) - ); - } - - #[test] - fn app_rpc_error_serialization_deserialization() { - let errors = vec![ - // Local Errors - AppRpcError::Local(LocalError::Decoder("Decoder error".to_string())), - AppRpcError::Local(LocalError::Internal), - AppRpcError::Local(LocalError::Io("IO error".to_string())), - AppRpcError::Local(LocalError::Signing("Signing error".to_string())), - AppRpcError::Local(LocalError::Transport("Transport error".to_string())), - // Remote Errors - AppRpcError::Remote(RemoteError::InvalidResponse("Invalid response".to_string())), - AppRpcError::Remote(RemoteError::Unreachable), - AppRpcError::Remote(RemoteError::Web3RpcError { - code: 42, - message: "RPC error".to_string(), - }), - ]; - - errors.into_iter().for_each(|error| { - let serialized = serde_json::to_string(&error).unwrap(); - let deserialized: AppRpcError = serde_json::from_str(&serialized).unwrap(); - assert_eq!(error, deserialized, "Error: {:?}", error); - }); - } - - #[test] - fn previous_attempts_and_validation_failure_clock_work_together_fine() { - let validation_failure_clock = ValidationFailureClockReal::default(); - // new() - let timestamp_a = SystemTime::now(); - let subject = PreviousAttempts::new( - Box::new(AppRpcErrorKind::Decoder), - &validation_failure_clock, - ); - // add_attempt() - let timestamp_b = SystemTime::now(); - let subject = subject.add_attempt( - Box::new(AppRpcErrorKind::Internal), - &validation_failure_clock, - ); - let timestamp_c = SystemTime::now(); - let subject = subject.add_attempt(Box::new(AppRpcErrorKind::IO), &validation_failure_clock); - let timestamp_d = SystemTime::now(); - let subject = subject.add_attempt( - Box::new(AppRpcErrorKind::Decoder), - &validation_failure_clock, - ); - let subject = subject.add_attempt(Box::new(AppRpcErrorKind::IO), &validation_failure_clock); - - let decoder_error_stats = subject - .inner - .get(&(Box::new(AppRpcErrorKind::Decoder) as Box)) - .unwrap(); - assert!( - timestamp_a <= decoder_error_stats.first_seen - && decoder_error_stats.first_seen <= timestamp_b, - "Was expected from {:?} to {:?} but was {:?}", - timestamp_a, - timestamp_b, - decoder_error_stats.first_seen - ); - assert_eq!(decoder_error_stats.attempts, 2); - let internal_error_stats = subject - .inner - .get(&(Box::new(AppRpcErrorKind::Internal) as Box)) - .unwrap(); - assert!( - timestamp_b <= internal_error_stats.first_seen - && internal_error_stats.first_seen <= timestamp_c, - "Was expected from {:?} to {:?} but was {:?}", - timestamp_b, - timestamp_c, - internal_error_stats.first_seen - ); - assert_eq!(internal_error_stats.attempts, 1); - let io_error_stats = subject - .inner - .get(&(Box::new(AppRpcErrorKind::IO) as Box)) - .unwrap(); - assert!( - timestamp_c <= io_error_stats.first_seen && io_error_stats.first_seen <= timestamp_d, - "Was expected from {:?} to {:?} but was {:?}", - timestamp_c, - timestamp_d, - io_error_stats.first_seen - ); - assert_eq!(io_error_stats.attempts, 2); - let other_error_stats = subject - .inner - .get(&(Box::new(AppRpcErrorKind::Signing) as Box)); - assert_eq!(other_error_stats, None); - } - - #[test] - fn conversion_between_app_rpc_error_and_app_rpc_error_kind_works() { - assert_eq!( - AppRpcErrorKind::from(AppRpcError::Local(LocalError::Decoder( - "Decoder error".to_string() - ))), - AppRpcErrorKind::Decoder - ); - assert_eq!( - AppRpcErrorKind::from(AppRpcError::Local(LocalError::Internal)), - AppRpcErrorKind::Internal - ); - assert_eq!( - AppRpcErrorKind::from(AppRpcError::Local(LocalError::Io("IO error".to_string()))), - AppRpcErrorKind::IO - ); - assert_eq!( - AppRpcErrorKind::from(AppRpcError::Local(LocalError::Signing( - "Signing error".to_string() - ))), - AppRpcErrorKind::Signing - ); - assert_eq!( - AppRpcErrorKind::from(AppRpcError::Local(LocalError::Transport( - "Transport error".to_string() - ))), - AppRpcErrorKind::Transport - ); - assert_eq!( - AppRpcErrorKind::from(AppRpcError::Remote(RemoteError::InvalidResponse( - "Invalid response".to_string() - ))), - AppRpcErrorKind::InvalidResponse - ); - assert_eq!( - AppRpcErrorKind::from(AppRpcError::Remote(RemoteError::Unreachable)), - AppRpcErrorKind::ServerUnreachable - ); - assert_eq!( - AppRpcErrorKind::from(AppRpcError::Remote(RemoteError::Web3RpcError { - code: 55, - message: "Booga".to_string() - })), - AppRpcErrorKind::Web3RpcError(55) - ); - } - - #[test] - fn clone_works_for_blockchain_db_error_wrapping_app_rpc_error_kind() { - let subject: Box = Box::new(AppRpcErrorKind::Web3RpcError(123)); - - let result = subject.clone(); - - test_clone_for_blockchain_db_error::(subject); - } - - #[test] - fn clone_works_for_blockchain_db_error_wrapping_masq_error() { - let subject: Box = Box::new(MASQError::PendingTooLongNotReplaced); - - test_clone_for_blockchain_db_error::(subject); - } - - fn test_clone_for_blockchain_db_error(subject: Box) - where - ErrorType: PartialEq + Debug + 'static, - { - let result = subject.clone(); - - let specified_subject = subject.as_any().downcast_ref::().unwrap(); - let specified_result = result.as_any().downcast_ref::().unwrap(); - assert_eq!(specified_result, specified_subject) - } - #[test] - fn hashing_for_app_arp_error_kind_works() { - let mut hasher = DefaultHasher::default(); - let mut hashes = vec![ - Box::new(AppRpcErrorKind::Decoder) as Box, - Box::new(AppRpcErrorKind::Internal), - Box::new(AppRpcErrorKind::IO), - Box::new(AppRpcErrorKind::Signing), - Box::new(AppRpcErrorKind::Transport), - Box::new(AppRpcErrorKind::InvalidResponse), - Box::new(AppRpcErrorKind::ServerUnreachable), - Box::new(AppRpcErrorKind::Web3RpcError(123)), - Box::new(AppRpcErrorKind::Web3RpcError(124)), - Box::new(AppRpcErrorKind::Web3RpcError(555555)), - ] - .into_iter() - .map(|blockchain_error| { - blockchain_error.hash(&mut hasher); - - hasher.finish() - }) - .collect::>(); - - hashes.clone().iter().for_each(|picked_hash| { - hashes.remove(0); - hashes.iter().for_each(|other_hash| { - assert_ne!(picked_hash, other_hash); - }); - }) - } - - #[test] - fn hashing_for_masq_error_works() { - let mut hasher = DefaultHasher::default(); - let mut hashes = vec![ - Box::new(MASQError::PendingTooLongNotReplaced) as Box, - // Add more types here as there are more types of MASQ errors. - ] - .into_iter() - .map(|blockchain_error| { - blockchain_error.hash(&mut hasher); - - hasher.finish() - }) - .collect::>(); - - hashes.clone().iter().for_each(|picked_hash| { - hashes.remove(0); - hashes.iter().for_each(|other_hash| { - assert_ne!(picked_hash, other_hash); - }); - }) - } - - #[test] - fn serialization_and_deserialization_for_blockchain_db_error_works() { - vec![ - ( - Box::new(AppRpcErrorKind::Web3RpcError(123)) as Box, - "bluh", - ), - (Box::new(AppRpcErrorKind::Internal), "bluh2"), - (Box::new(MASQError::PendingTooLongNotReplaced), "bluh3"), - ] - .into_iter() - .for_each(|(blockchain_error, expected_result)| { - let json_result = serde_json::to_string(&blockchain_error).unwrap(); - assert_eq!(json_result, expected_result); - let trait_object_result = - serde_json::from_str::>(&json_result).unwrap(); - assert_eq!(&trait_object_result, &blockchain_error); - }) - } -} diff --git a/node/src/blockchain/errors/blockchain_db_error/app_rpc_web3_error_kind.rs b/node/src/blockchain/errors/blockchain_db_error/app_rpc_web3_error_kind.rs new file mode 100644 index 000000000..803ddffa4 --- /dev/null +++ b/node/src/blockchain/errors/blockchain_db_error/app_rpc_web3_error_kind.rs @@ -0,0 +1,250 @@ +// Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::blockchain::errors::blockchain_db_error::BlockchainDbError; +use crate::blockchain::errors::blockchain_error::app_rpc_web3_error::{ + AppRpcWeb3Error, LocalError, RemoteError, +}; +use libc::pathconf; +use serde::Serialize as SerializeTrait; +use serde_derive::{Deserialize, Serialize}; +use std::fmt::{Debug, Formatter}; +use std::hash::{Hash, Hasher}; + +impl BlockchainDbError for AppRpcWeb3ErrorKind { + fn serialize_fn(&self) -> Result { + serde_json::to_string(self) //TODO tested?? + } + + fn deserialize_fn(str: &str) -> Result, serde_json::Error> + where + Self: Sized, + { + eprintln!("{:?}", str); + let res: Result = serde_json::from_str(str); + res.map(|kind| Box::new(kind) as Box) + } + + fn partial_eq(&self, other: &Box) -> bool { + other + .as_any() + .downcast_ref::() + .map_or(false, |other| self == other) + } + + fn costume_hash_fn(&self, hasher: &mut dyn Hasher) { + match self { + AppRpcWeb3ErrorKind::Decoder => hasher.write_u8(0), + AppRpcWeb3ErrorKind::Internal => hasher.write_u8(1), + AppRpcWeb3ErrorKind::IO => hasher.write_u8(2), + AppRpcWeb3ErrorKind::Signing => hasher.write_u8(3), + AppRpcWeb3ErrorKind::Transport => hasher.write_u8(4), + AppRpcWeb3ErrorKind::InvalidResponse => hasher.write_u8(5), + AppRpcWeb3ErrorKind::ServerUnreachable => hasher.write_u8(6), + AppRpcWeb3ErrorKind::Web3RpcError(code) => { + hasher.write_u8(7); + hasher.write_i64(*code); + } + } + } + + fn dup(&self) -> Box { + Box::new(self.clone()) + } + + as_any_ref_in_trait_impl!(); +} + +#[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum AppRpcWeb3ErrorKind { + // Local + Decoder, + Internal, + IO, + Signing, + Transport, + + // Remote + InvalidResponse, + ServerUnreachable, + Web3RpcError(i64), // Keep only the stable error code +} + +impl From for AppRpcWeb3ErrorKind { + fn from(err: AppRpcWeb3Error) -> Self { + match err { + AppRpcWeb3Error::Local(local) => match local { + LocalError::Decoder(_) => Self::Decoder, + LocalError::Internal => Self::Internal, + LocalError::Io(_) => Self::IO, + LocalError::Signing(_) => Self::Signing, + LocalError::Transport(_) => Self::Transport, + }, + AppRpcWeb3Error::Remote(remote) => match remote { + RemoteError::InvalidResponse(_) => Self::InvalidResponse, + RemoteError::Unreachable => Self::ServerUnreachable, + RemoteError::Web3RpcError { code, .. } => Self::Web3RpcError(code), + }, + } + } +} + +mod tests { + use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; + use crate::blockchain::errors::blockchain_db_error::masq_error::MASQError; + use crate::blockchain::errors::blockchain_db_error::BlockchainDbError; + use crate::blockchain::errors::blockchain_error::app_rpc_web3_error::{ + AppRpcWeb3Error, LocalError, RemoteError, + }; + use crate::blockchain::errors::test_utils::test_clone_for_blockchain_db_error; + use std::collections::hash_map::DefaultHasher; + use std::fmt::Debug; + use std::hash::{Hash, Hasher}; + + #[test] + fn conversion_between_app_rpc_error_and_app_rpc_error_kind_works() { + assert_eq!( + AppRpcWeb3ErrorKind::from(AppRpcWeb3Error::Local(LocalError::Decoder( + "Decoder error".to_string() + ))), + AppRpcWeb3ErrorKind::Decoder + ); + assert_eq!( + AppRpcWeb3ErrorKind::from(AppRpcWeb3Error::Local(LocalError::Internal)), + AppRpcWeb3ErrorKind::Internal + ); + assert_eq!( + AppRpcWeb3ErrorKind::from(AppRpcWeb3Error::Local(LocalError::Io( + "IO error".to_string() + ))), + AppRpcWeb3ErrorKind::IO + ); + assert_eq!( + AppRpcWeb3ErrorKind::from(AppRpcWeb3Error::Local(LocalError::Signing( + "Signing error".to_string() + ))), + AppRpcWeb3ErrorKind::Signing + ); + assert_eq!( + AppRpcWeb3ErrorKind::from(AppRpcWeb3Error::Local(LocalError::Transport( + "Transport error".to_string() + ))), + AppRpcWeb3ErrorKind::Transport + ); + assert_eq!( + AppRpcWeb3ErrorKind::from(AppRpcWeb3Error::Remote(RemoteError::InvalidResponse( + "Invalid response".to_string() + ))), + AppRpcWeb3ErrorKind::InvalidResponse + ); + assert_eq!( + AppRpcWeb3ErrorKind::from(AppRpcWeb3Error::Remote(RemoteError::Unreachable)), + AppRpcWeb3ErrorKind::ServerUnreachable + ); + assert_eq!( + AppRpcWeb3ErrorKind::from(AppRpcWeb3Error::Remote(RemoteError::Web3RpcError { + code: 55, + message: "Booga".to_string() + })), + AppRpcWeb3ErrorKind::Web3RpcError(55) + ); + } + + #[test] + fn clone_works_for_blockchain_db_error_wrapping_app_rpc_error_kind() { + let subject: Box = Box::new(AppRpcWeb3ErrorKind::Web3RpcError(123)); + + test_clone_for_blockchain_db_error::(subject); + } + + #[test] + fn hashing_for_app_arp_error_kind_works() { + let mut hasher = DefaultHasher::default(); + let mut hashes = vec![ + Box::new(AppRpcWeb3ErrorKind::Decoder) as Box, + Box::new(AppRpcWeb3ErrorKind::Internal), + Box::new(AppRpcWeb3ErrorKind::IO), + Box::new(AppRpcWeb3ErrorKind::Signing), + Box::new(AppRpcWeb3ErrorKind::Transport), + Box::new(AppRpcWeb3ErrorKind::InvalidResponse), + Box::new(AppRpcWeb3ErrorKind::ServerUnreachable), + Box::new(AppRpcWeb3ErrorKind::Web3RpcError(123)), + Box::new(AppRpcWeb3ErrorKind::Web3RpcError(124)), + Box::new(AppRpcWeb3ErrorKind::Web3RpcError(555555)), + ] + .into_iter() + .map(|blockchain_error| { + blockchain_error.hash(&mut hasher); + + hasher.finish() + }) + .collect::>(); + + hashes.clone().iter().for_each(|picked_hash| { + hashes.remove(0); + hashes.iter().for_each(|other_hash| { + assert_ne!(picked_hash, other_hash); + }); + }) + } + + #[test] + fn partial_eq_for_app_rpc_error_kind_works() { + let subject: Box = Box::new(AppRpcWeb3ErrorKind::Web3RpcError(123)); + let other_1: Box = Box::new(AppRpcWeb3ErrorKind::Web3RpcError(124)); + let other_2: Box = Box::new(AppRpcWeb3ErrorKind::Web3RpcError(123)); + let other_3: Box = Box::new(AppRpcWeb3ErrorKind::Internal); + + assert_ne!(&subject, &other_1); + assert_eq!(&subject, &other_2); + assert_ne!(&subject, &other_3); + } + + #[test] + fn app_rpc_error_kind_serialization_deserialization() { + let errors = vec![ + // Local Errors + AppRpcWeb3ErrorKind::Decoder, + AppRpcWeb3ErrorKind::Internal, + AppRpcWeb3ErrorKind::IO, + AppRpcWeb3ErrorKind::Signing, + AppRpcWeb3ErrorKind::Transport, + // Remote Errors + AppRpcWeb3ErrorKind::InvalidResponse, + AppRpcWeb3ErrorKind::ServerUnreachable, + AppRpcWeb3ErrorKind::Web3RpcError(42), + ]; + + errors.into_iter().for_each(|error| { + let serialized = serde_json::to_string(&error).unwrap(); + let deserialized: AppRpcWeb3ErrorKind = serde_json::from_str(&serialized).unwrap(); + assert_eq!( + error, deserialized, + "Failed serde attempt for {:?} that should look \ + like {:?}", + deserialized, error + ); + }); + } + + #[test] + fn serialization_and_deserialization_for_blockchain_db_error_works() { + vec![ + ( + Box::new(AppRpcWeb3ErrorKind::Internal) as Box, + "\"Internal\"", + ), + ( + Box::new(AppRpcWeb3ErrorKind::Web3RpcError(123)), + "{\"Web3RpcError\":123}", + ), + ] + .into_iter() + .for_each(|(blockchain_error, expected_result)| { + let json_result = serde_json::to_string(&blockchain_error).unwrap(); + assert_eq!(json_result, expected_result); + let trait_object_result = + serde_json::from_str::>(&json_result).unwrap(); + assert_eq!(&trait_object_result, &blockchain_error); + }) + } +} diff --git a/node/src/blockchain/errors/blockchain_db_error/masq_error.rs b/node/src/blockchain/errors/blockchain_db_error/masq_error.rs new file mode 100644 index 000000000..75baf4474 --- /dev/null +++ b/node/src/blockchain/errors/blockchain_db_error/masq_error.rs @@ -0,0 +1,109 @@ +// Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::blockchain::errors::blockchain_db_error::BlockchainDbError; +use serde_derive::{Deserialize, Serialize}; +use std::hash::Hasher; +use variant_count::VariantCount; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, VariantCount)] +pub enum MASQError { + PendingTooLongNotReplaced, +} + +impl BlockchainDbError for MASQError { + fn serialize_fn(&self) -> Result { + serde_json::to_string(self) //TODO tested?? + } + + fn deserialize_fn(str: &str) -> Result, serde_json::Error> + where + Self: Sized, + { + let res: Result = serde_json::from_str(str); + res.map(|kind| Box::new(kind) as Box) + } + + fn partial_eq(&self, other: &Box) -> bool { + other + .as_any() + .downcast_ref::() + .map_or(false, |other| self == other) + } + + fn costume_hash_fn(&self, hasher: &mut dyn Hasher) { + match self { + MASQError::PendingTooLongNotReplaced => hasher.write_u8(0), + } + } + + fn dup(&self) -> Box { + Box::new(self.clone()) + } + + as_any_ref_in_trait_impl!(); +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; + use crate::blockchain::errors::test_utils::test_clone_for_blockchain_db_error; + use std::collections::hash_map::DefaultHasher; + use std::hash::Hash; + + #[test] + fn clone_works_for_blockchain_db_error_wrapping_masq_error() { + let subject: Box = Box::new(MASQError::PendingTooLongNotReplaced); + + test_clone_for_blockchain_db_error::(subject); + } + + #[test] + fn hashing_for_masq_error_works() { + let mut hasher = DefaultHasher::default(); + let mut hashes = vec![ + Box::new(MASQError::PendingTooLongNotReplaced) as Box, + // Add more types here as there are more types of MASQ app_rpc_web3_error_kind. + ] + .into_iter() + .map(|blockchain_error| { + blockchain_error.hash(&mut hasher); + + hasher.finish() + }) + .collect::>(); + + hashes.clone().iter().for_each(|picked_hash| { + hashes.remove(0); + hashes.iter().for_each(|other_hash| { + assert_ne!(picked_hash, other_hash); + }); + }) + } + + #[test] + fn partial_eq_for_masq_error_works() { + let subject: Box = Box::new(MASQError::PendingTooLongNotReplaced); + let other: Box = Box::new(MASQError::PendingTooLongNotReplaced); + + assert_eq!(&subject, &other); + // Expand this test as there are more variants of MASQError. + assert_eq!(MASQError::VARIANT_COUNT, 1); + } + + #[test] + fn serialization_and_deserialization_for_blockchain_db_error_works() { + vec![( + Box::new(MASQError::PendingTooLongNotReplaced) as Box, + "\"PendingTooLongNotReplaced\"", + )] + .into_iter() + .for_each(|(blockchain_error, expected_result)| { + let json_result = serde_json::to_string(&blockchain_error).unwrap(); + assert_eq!(json_result, expected_result); + let trait_object_result = + serde_json::from_str::>(&json_result).unwrap(); + assert_eq!(&trait_object_result, &blockchain_error); + }) + } +} diff --git a/node/src/blockchain/errors/blockchain_db_error/mod.rs b/node/src/blockchain/errors/blockchain_db_error/mod.rs new file mode 100644 index 000000000..78037cf15 --- /dev/null +++ b/node/src/blockchain/errors/blockchain_db_error/mod.rs @@ -0,0 +1,98 @@ +// Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +pub mod app_rpc_web3_error_kind; +pub mod masq_error; + +use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; +use crate::blockchain::errors::blockchain_db_error::masq_error::MASQError; +use serde::de::{Error, MapAccess}; +use serde::{Deserialize as DeserializeTrait, Serialize as SerializeTrait}; +use std::fmt::{Debug, Formatter}; +use std::hash::{Hash, Hasher}; + +impl SerializeTrait for Box { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let json_value: serde_json::Value = serde_json::from_str( + &self + .serialize_fn() + .map_err(|e| serde::ser::Error::custom(e))?, + ) // TODO tested? + .map_err(|e| serde::ser::Error::custom(e))?; // TODO tested? + json_value.serialize(serializer) + } +} + +impl<'de> DeserializeTrait<'de> for Box { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let json_value: serde_json::Value = serde_json::Value::deserialize(deserializer)?; //TODO tested? + let json_str = + serde_json::to_string(&json_value).map_err(|e| serde::de::Error::custom(e))?; // TODO tested? + + if let Ok(error) = AppRpcWeb3ErrorKind::deserialize_fn(&json_str) { + return Ok(error); + } + + if let Ok(error) = MASQError::deserialize_fn(&json_str) { + return Ok(error); + } + + Err(serde::de::Error::custom(format!( + "Unable to deserialize BlockchainDbError from: {}", + json_str + ))) + } +} + +impl Clone for Box { + fn clone(&self) -> Self { + self.dup() + } +} + +impl PartialEq for Box { + fn eq(&self, other: &Self) -> bool { + self.partial_eq(other) + } +} + +impl Hash for Box { + fn hash(&self, state: &mut H) { + self.costume_hash_fn(state) + } +} + +impl Eq for Box {} + +pub trait BlockchainDbError: Debug { + fn serialize_fn(&self) -> Result; + fn deserialize_fn(str: &str) -> Result, serde_json::Error> + where + Self: Sized; + fn partial_eq(&self, other: &Box) -> bool; + fn costume_hash_fn(&self, hasher: &mut dyn Hasher); + fn dup(&self) -> Box; + as_any_ref_in_trait!(); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialization_fails() { + let str = "\"bluh\""; + + let err = serde_json::from_str::>(str).unwrap_err(); + + assert_eq!( + err.to_string(), + "Unable to deserialize BlockchainDbError from: \"bluh\"" + ) + } +} diff --git a/node/src/blockchain/errors/blockchain_error/app_rpc_web3_error.rs b/node/src/blockchain/errors/blockchain_error/app_rpc_web3_error.rs new file mode 100644 index 000000000..fe7a376d1 --- /dev/null +++ b/node/src/blockchain/errors/blockchain_error/app_rpc_web3_error.rs @@ -0,0 +1,104 @@ +// Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use serde_derive::{Deserialize, Serialize}; +use web3::error::Error as Web3Error; + +// Prefixed with App to clearly distinguish app-specific app_rpc_web3_error_kind from library app_rpc_web3_error_kind. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum AppRpcWeb3Error { + Local(LocalError), + Remote(RemoteError), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum LocalError { + Decoder(String), + Internal, + Io(String), + Signing(String), + Transport(String), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum RemoteError { + InvalidResponse(String), + Unreachable, + Web3RpcError { code: i64, message: String }, +} + +// EVM based app_rpc_web3_error_kind +impl From for AppRpcWeb3Error { + fn from(error: Web3Error) -> Self { + match error { + // Local Errors + Web3Error::Decoder(error) => AppRpcWeb3Error::Local(LocalError::Decoder(error)), + Web3Error::Internal => AppRpcWeb3Error::Local(LocalError::Internal), + Web3Error::Io(error) => AppRpcWeb3Error::Local(LocalError::Io(error.to_string())), + Web3Error::Signing(error) => { + // This variant cannot be tested due to import limitations. + AppRpcWeb3Error::Local(LocalError::Signing(error.to_string())) + } + Web3Error::Transport(error) => AppRpcWeb3Error::Local(LocalError::Transport(error)), + + // Api Errors + Web3Error::InvalidResponse(response) => { + AppRpcWeb3Error::Remote(RemoteError::InvalidResponse(response)) + } + Web3Error::Rpc(web3_rpc_error) => AppRpcWeb3Error::Remote(RemoteError::Web3RpcError { + code: web3_rpc_error.code.code(), + message: web3_rpc_error.message, + }), + Web3Error::Unreachable => AppRpcWeb3Error::Remote(RemoteError::Unreachable), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn web3_error_to_failure_reason_conversion_works() { + // Local Errors + assert_eq!( + AppRpcWeb3Error::from(Web3Error::Decoder("Decoder error".to_string())), + AppRpcWeb3Error::Local(LocalError::Decoder("Decoder error".to_string())) + ); + assert_eq!( + AppRpcWeb3Error::from(Web3Error::Internal), + AppRpcWeb3Error::Local(LocalError::Internal) + ); + assert_eq!( + AppRpcWeb3Error::from(Web3Error::Io(std::io::Error::new( + std::io::ErrorKind::Other, + "IO error" + ))), + AppRpcWeb3Error::Local(LocalError::Io("IO error".to_string())) + ); + assert_eq!( + AppRpcWeb3Error::from(Web3Error::Transport("Transport error".to_string())), + AppRpcWeb3Error::Local(LocalError::Transport("Transport error".to_string())) + ); + + // Api Errors + assert_eq!( + AppRpcWeb3Error::from(Web3Error::InvalidResponse("Invalid response".to_string())), + AppRpcWeb3Error::Remote(RemoteError::InvalidResponse("Invalid response".to_string())) + ); + assert_eq!( + AppRpcWeb3Error::from(Web3Error::Rpc(jsonrpc_core::types::error::Error { + code: jsonrpc_core::types::error::ErrorCode::ServerError(42), + message: "RPC error".to_string(), + data: None, + })), + AppRpcWeb3Error::Remote(RemoteError::Web3RpcError { + code: 42, + message: "RPC error".to_string(), + }) + ); + assert_eq!( + AppRpcWeb3Error::from(Web3Error::Unreachable), + AppRpcWeb3Error::Remote(RemoteError::Unreachable) + ); + } +} diff --git a/node/src/blockchain/errors/blockchain_error/mod.rs b/node/src/blockchain/errors/blockchain_error/mod.rs new file mode 100644 index 000000000..013ce8a72 --- /dev/null +++ b/node/src/blockchain/errors/blockchain_error/mod.rs @@ -0,0 +1,7 @@ +// Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use std::fmt::Display; + +pub mod app_rpc_web3_error; + +pub trait BlockchainError: Display {} diff --git a/node/src/blockchain/errors/mod.rs b/node/src/blockchain/errors/mod.rs new file mode 100644 index 000000000..e007b792f --- /dev/null +++ b/node/src/blockchain/errors/mod.rs @@ -0,0 +1,6 @@ +// Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +pub mod blockchain_db_error; +pub mod blockchain_error; +mod test_utils; +pub mod validation_status; diff --git a/node/src/blockchain/errors/test_utils.rs b/node/src/blockchain/errors/test_utils.rs new file mode 100644 index 000000000..63e83aed3 --- /dev/null +++ b/node/src/blockchain/errors/test_utils.rs @@ -0,0 +1,15 @@ +// Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::blockchain::errors::blockchain_db_error::BlockchainDbError; +use std::fmt::Debug; + +pub fn test_clone_for_blockchain_db_error(subject: Box) +where + ErrorType: PartialEq + Debug + 'static, +{ + let result = subject.clone(); + + let specified_subject = subject.as_any().downcast_ref::().unwrap(); + let specified_result = result.as_any().downcast_ref::().unwrap(); + assert_eq!(specified_result, specified_subject) +} diff --git a/node/src/blockchain/errors/validation_status.rs b/node/src/blockchain/errors/validation_status.rs new file mode 100644 index 000000000..4f8306d7b --- /dev/null +++ b/node/src/blockchain/errors/validation_status.rs @@ -0,0 +1,147 @@ +// Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::blockchain::errors::blockchain_db_error::BlockchainDbError; +use serde_derive::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::time::SystemTime; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum ValidationStatus { + Waiting, + Reattempting(PreviousAttempts), +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PreviousAttempts { + #[serde(flatten)] + inner: HashMap, ErrorStats>, +} + +impl PreviousAttempts { + pub fn new(error: Box, clock: &dyn ValidationFailureClock) -> Self { + Self { + inner: hashmap!(error => ErrorStats::now(clock)), + } + } + + pub fn add_attempt( + mut self, + error: Box, + clock: &dyn ValidationFailureClock, + ) -> Self { + self.inner + .entry(error) + .and_modify(|stats| stats.increment()) + .or_insert_with(|| ErrorStats::now(clock)); + self + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ErrorStats { + #[serde(rename = "firstSeen")] + pub first_seen: SystemTime, + pub attempts: u16, +} + +impl ErrorStats { + pub fn now(clock: &dyn ValidationFailureClock) -> Self { + Self { + first_seen: clock.now(), + attempts: 1, + } + } + + pub fn increment(&mut self) { + self.attempts += 1; + } +} + +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::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; + + #[test] + fn previous_attempts_and_validation_failure_clock_work_together_fine() { + let validation_failure_clock = ValidationFailureClockReal::default(); + // new() + let timestamp_a = SystemTime::now(); + let subject = PreviousAttempts::new( + Box::new(AppRpcWeb3ErrorKind::Decoder), + &validation_failure_clock, + ); + // add_attempt() + let timestamp_b = SystemTime::now(); + let subject = subject.add_attempt( + Box::new(AppRpcWeb3ErrorKind::Internal), + &validation_failure_clock, + ); + let timestamp_c = SystemTime::now(); + let subject = + subject.add_attempt(Box::new(AppRpcWeb3ErrorKind::IO), &validation_failure_clock); + let timestamp_d = SystemTime::now(); + let subject = subject.add_attempt( + Box::new(AppRpcWeb3ErrorKind::Decoder), + &validation_failure_clock, + ); + let subject = + subject.add_attempt(Box::new(AppRpcWeb3ErrorKind::IO), &validation_failure_clock); + + let decoder_error_stats = subject + .inner + .get(&(Box::new(AppRpcWeb3ErrorKind::Decoder) as Box)) + .unwrap(); + assert!( + timestamp_a <= decoder_error_stats.first_seen + && decoder_error_stats.first_seen <= timestamp_b, + "Was expected from {:?} to {:?} but was {:?}", + timestamp_a, + timestamp_b, + decoder_error_stats.first_seen + ); + assert_eq!(decoder_error_stats.attempts, 2); + let internal_error_stats = subject + .inner + .get(&(Box::new(AppRpcWeb3ErrorKind::Internal) as Box)) + .unwrap(); + assert!( + timestamp_b <= internal_error_stats.first_seen + && internal_error_stats.first_seen <= timestamp_c, + "Was expected from {:?} to {:?} but was {:?}", + timestamp_b, + timestamp_c, + internal_error_stats.first_seen + ); + assert_eq!(internal_error_stats.attempts, 1); + let io_error_stats = subject + .inner + .get(&(Box::new(AppRpcWeb3ErrorKind::IO) as Box)) + .unwrap(); + assert!( + timestamp_c <= io_error_stats.first_seen && io_error_stats.first_seen <= timestamp_d, + "Was expected from {:?} to {:?} but was {:?}", + timestamp_c, + timestamp_d, + io_error_stats.first_seen + ); + assert_eq!(io_error_stats.attempts, 2); + let other_error_stats = subject + .inner + .get(&(Box::new(AppRpcWeb3ErrorKind::Signing) as Box)); + assert_eq!(other_error_stats, None); + } +} diff --git a/node/src/blockchain/test_utils.rs b/node/src/blockchain/test_utils.rs index 4c7099f45..f3b354931 100644 --- a/node/src/blockchain/test_utils.rs +++ b/node/src/blockchain/test_utils.rs @@ -5,7 +5,7 @@ use crate::blockchain::blockchain_interface::blockchain_interface_web3::{ BlockchainInterfaceWeb3, REQUESTS_IN_PARALLEL, }; -use crate::blockchain::errors::ValidationFailureClock; +use crate::blockchain::errors::validation_status::ValidationFailureClock; use bip39::{Language, Mnemonic, Seed}; use ethabi::Hash; use ethereum_types::{BigEndianHash, H160, H256, U64}; diff --git a/node/src/listener_handler.rs b/node/src/listener_handler.rs index 1a63b9083..f595e3a2c 100644 --- a/node/src/listener_handler.rs +++ b/node/src/listener_handler.rs @@ -96,7 +96,7 @@ impl Future for ListenerHandlerReal { } Err(e) => { // TODO FIXME we should kill the entire Node if there is a fatal error in a listener_handler - // TODO this could be exploitable and inefficient: if we keep getting errors, we go into a tight loop and do not return + // TODO this could be exploitable and inefficient: if we keep getting app_rpc_web3_error_kind, we go into a tight loop and do not return error!(self.logger, "Could not accept connection: {}", e); } Ok(Async::NotReady) => return Ok(Async::NotReady), diff --git a/node/src/proxy_client/stream_reader.rs b/node/src/proxy_client/stream_reader.rs index 992b58dbf..5abfe8641 100644 --- a/node/src/proxy_client/stream_reader.rs +++ b/node/src/proxy_client/stream_reader.rs @@ -68,7 +68,7 @@ impl Future for StreamReader { self.shutdown(); return Err(()); } else { - // TODO this could be exploitable and inefficient: if we keep getting non-dead-stream errors, we go into a tight loop and do not return + // TODO this could be exploitable and inefficient: if we keep getting non-dead-stream app_rpc_web3_error_kind, we go into a tight loop and do not return warning!( self.logger, "Continuing after read error on stream from {}: {}", diff --git a/node/src/proxy_client/stream_writer.rs b/node/src/proxy_client/stream_writer.rs index c9842741a..02be017f2 100644 --- a/node/src/proxy_client/stream_writer.rs +++ b/node/src/proxy_client/stream_writer.rs @@ -104,7 +104,7 @@ impl StreamWriter { ); return Err(()); } else { - // TODO this could be exploitable and inefficient: if we keep getting non-dead-stream errors, we go into a tight loop and do not return + // TODO this could be exploitable and inefficient: if we keep getting non-dead-stream app_rpc_web3_error_kind, we go into a tight loop and do not return warning!(self.logger, "Continuing after write error: {}", e); self.sequence_buffer.repush(packet); } diff --git a/node/src/proxy_server/mod.rs b/node/src/proxy_server/mod.rs index d18a7ceba..e465eebd3 100644 --- a/node/src/proxy_server/mod.rs +++ b/node/src/proxy_server/mod.rs @@ -353,7 +353,7 @@ impl ProxyServer { .try_send(TransmitDataMsg { endpoint: Endpoint::Socket(client_addr), last_data: true, - sequence_number: Some(0), // DNS resolution errors always happen on the first request + sequence_number: Some(0), // DNS resolution app_rpc_web3_error_kind always happen on the first request data: from_protocol(proxy_protocol) .server_impersonator() .dns_resolution_failure_response(hostname_opt), diff --git a/node/src/stream_reader.rs b/node/src/stream_reader.rs index 34a7b62bd..a7f816ddd 100644 --- a/node/src/stream_reader.rs +++ b/node/src/stream_reader.rs @@ -68,7 +68,7 @@ impl Future for StreamReaderReal { self.shutdown(); return Err(()); } else { - // TODO this could be exploitable and inefficient: if we keep getting non-dead-stream errors, we go into a tight loop and do not return + // TODO this could be exploitable and inefficient: if we keep getting non-dead-stream app_rpc_web3_error_kind, we go into a tight loop and do not return warning!( self.logger, "Continuing after read error on stream {}: {}", diff --git a/node/src/stream_writer_sorted.rs b/node/src/stream_writer_sorted.rs index cd41dd5dd..26cde1ee9 100644 --- a/node/src/stream_writer_sorted.rs +++ b/node/src/stream_writer_sorted.rs @@ -100,7 +100,7 @@ impl StreamWriterSorted { ); return WriteBufferStatus::StreamInError; } else { - // TODO this could be exploitable and inefficient: if we keep getting non-dead-stream errors, we go into a tight loop and do not return + // TODO this could be exploitable and inefficient: if we keep getting non-dead-stream app_rpc_web3_error_kind, we go into a tight loop and do not return warning!(self.logger, "Continuing after write error: {}", e); self.sequence_buffer.repush(packet); } diff --git a/node/src/stream_writer_unsorted.rs b/node/src/stream_writer_unsorted.rs index 3a6c73925..172b5b997 100644 --- a/node/src/stream_writer_unsorted.rs +++ b/node/src/stream_writer_unsorted.rs @@ -50,7 +50,7 @@ impl Future for StreamWriterUnsorted { return Err(()); } else { self.buf = Some(packet); - // TODO this could be... inefficient, if we keep getting non-dead-stream errors. (we do not return) + // TODO this could be... inefficient, if we keep getting non-dead-stream app_rpc_web3_error_kind. (we do not return) warning!(self.logger, "Continuing after write error: {}", e); } } From 527fe254c0c21a6d83900cf9a3157b51a7b54378 Mon Sep 17 00:00:00 2001 From: Bert Date: Sun, 17 Aug 2025 22:05:58 +0200 Subject: [PATCH 38/61] GH-683: mostly done --- node/src/accountant/scanners/mod.rs | 14 ++-- .../app_rpc_web3_error_kind.rs | 47 ++++++----- .../{masq_error.rs => masq_error_kind.rs} | 69 ++++++++++------ .../errors/blockchain_db_error/mod.rs | 82 ++++++++++++------- .../blockchain_error/app_rpc_web3_error.rs | 60 +++++++++++++- .../errors/blockchain_error/masq_error.rs | 67 +++++++++++++++ .../blockchain/errors/blockchain_error/mod.rs | 25 +++++- .../errors/custom_common_methods.rs | 9 ++ node/src/blockchain/errors/mod.rs | 1 + node/src/blockchain/errors/test_utils.rs | 81 ++++++++++++++++-- 10 files changed, 359 insertions(+), 96 deletions(-) rename node/src/blockchain/errors/blockchain_db_error/{masq_error.rs => masq_error_kind.rs} (53%) create mode 100644 node/src/blockchain/errors/blockchain_error/masq_error.rs create mode 100644 node/src/blockchain/errors/custom_common_methods.rs diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index 02d21d467..d49cf3efc 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -9,19 +9,18 @@ pub mod test_utils; use crate::accountant::db_access_objects::payable_dao::{PayableAccount, PayableDao}; use crate::accountant::db_access_objects::pending_payable_dao::{PendingPayable, PendingPayableDao}; -use crate::accountant::db_access_objects::receivable_dao::ReceivableDao; use crate::accountant::payment_adjuster::{PaymentAdjuster, PaymentAdjusterReal}; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableTransactingErrorEnum::{ LocallyCausedError, RemotelyCausedErrors, }; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{debugging_summary_after_error_separation, err_msg_for_failure_with_expected_but_missing_fingerprints, investigate_debt_extremes, mark_pending_payable_fatal_error, payables_debug_summary, separate_errors, separate_rowids_and_hashes, OperationOutcome, PayableScanResult, PayableThresholdsGauge, PayableThresholdsGaugeReal, PayableTransactingErrorEnum, PendingPayableMetadata}; -use crate::accountant::{PendingPayableId, ScanError, ScanForPendingPayables, ScanForRetryPayables}; +use crate::accountant::{ScanError, ScanForPendingPayables, ScanForRetryPayables}; use crate::accountant::{ comma_joined_stringifiable, gwei_to_wei, ReceivedPayments, ReportTransactionReceipts, RequestTransactionReceipts, ResponseSkeleton, ScanForNewPayables, ScanForReceivables, SentPayables, }; -use crate::blockchain::blockchain_bridge::{BlockMarker, PendingPayableFingerprint, RetrieveTransactions}; +use crate::blockchain::blockchain_bridge::{RetrieveTransactions}; use crate::sub_lib::accountant::{ DaoFactories, FinancialStatistics, PaymentThresholds, }; @@ -48,7 +47,6 @@ use crate::accountant::scanners::payable_scanner_extension::msgs::{BlockchainAge use crate::accountant::scanners::pending_payable_scanner::PendingPayableScanner; use crate::accountant::scanners::pending_payable_scanner::utils::PendingPayableScanResult; use crate::accountant::scanners::receivable_scanner::ReceivableScanner; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxStatus}; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; use crate::db_config::persistent_configuration::{PersistentConfigurationReal}; @@ -976,10 +974,10 @@ mod tests { }; use crate::accountant::db_access_objects::utils::{from_unix_timestamp, to_unix_timestamp}; use crate::accountant::scanners::payable_scanner_extension::msgs::{QualifiedPayablesBeforeGasPriceSelection, QualifiedPayablesMessage, UnpricedQualifiedPayables}; - use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{OperationOutcome, PayableScanResult, PendingPayableMetadata}; + use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{OperationOutcome, PayableScanResult}; use crate::accountant::scanners::{Scanner, StartScanError, StartableScanner, PayableScanner, PendingPayableScanner, ReceivableScanner, ScannerCommon, Scanners, ManulTriggerError}; use crate::accountant::test_utils::{make_custom_payment_thresholds, make_payable_account, make_qualified_and_unqualified_payables, make_pending_payable_fingerprint, make_receivable_account, BannedDaoFactoryMock, BannedDaoMock, ConfigDaoFactoryMock, PayableDaoFactoryMock, PayableDaoMock, PayableScannerBuilder, PayableThresholdsGaugeMock, PendingPayableDaoFactoryMock, PendingPayableDaoMock, PendingPayableScannerBuilder, ReceivableDaoFactoryMock, ReceivableDaoMock, ReceivableScannerBuilder}; - use crate::accountant::{gwei_to_wei, PendingPayableId, ReceivedPayments, ReportTransactionReceipts, RequestTransactionReceipts, ScanError, ScanForRetryPayables, SentPayables, DEFAULT_PENDING_TOO_LONG_SEC}; + use crate::accountant::{gwei_to_wei, ReceivedPayments, ReportTransactionReceipts, RequestTransactionReceipts, ScanError, ScanForRetryPayables, SentPayables}; use crate::blockchain::blockchain_bridge::{BlockMarker, PendingPayableFingerprint, RetrieveTransactions}; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; use crate::blockchain::blockchain_interface::data_structures::{ @@ -1004,13 +1002,11 @@ mod tests { use regex::{Regex}; use rusqlite::{ffi, ErrorCode}; use std::cell::RefCell; - use std::collections::HashSet; - use std::ops::Sub; use std::panic::{catch_unwind, AssertUnwindSafe}; use std::rc::Rc; use std::sync::{Arc, Mutex}; use std::time::{Duration, SystemTime}; - use web3::types::{TransactionReceipt, H256}; + use web3::types::{H256}; use web3::Error; use masq_lib::messages::ScanType; use masq_lib::ui_gateway::NodeToUiMessage; diff --git a/node/src/blockchain/errors/blockchain_db_error/app_rpc_web3_error_kind.rs b/node/src/blockchain/errors/blockchain_db_error/app_rpc_web3_error_kind.rs index 803ddffa4..e577155a0 100644 --- a/node/src/blockchain/errors/blockchain_db_error/app_rpc_web3_error_kind.rs +++ b/node/src/blockchain/errors/blockchain_db_error/app_rpc_web3_error_kind.rs @@ -1,37 +1,53 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::blockchain::errors::blockchain_db_error::BlockchainDbError; +use crate::blockchain::errors::blockchain_db_error::{BlockchainDbError, CustomHash, CustomSeDe}; use crate::blockchain::errors::blockchain_error::app_rpc_web3_error::{ AppRpcWeb3Error, LocalError, RemoteError, }; -use libc::pathconf; -use serde::Serialize as SerializeTrait; +use crate::blockchain::errors::custom_common_methods::CustomCommonMethods; use serde_derive::{Deserialize, Serialize}; -use std::fmt::{Debug, Formatter}; +use serde_json::Value; +use std::fmt::Debug; use std::hash::{Hash, Hasher}; impl BlockchainDbError for AppRpcWeb3ErrorKind { - fn serialize_fn(&self) -> Result { - serde_json::to_string(self) //TODO tested?? + fn as_common_methods(&self) -> &dyn CustomCommonMethods> { + todo!() + } +} + +impl CustomSeDe for AppRpcWeb3ErrorKind { + fn costume_serialize(&self) -> Result { + serde_json::to_value(self) } - fn deserialize_fn(str: &str) -> Result, serde_json::Error> + fn costume_deserialize(str: &str) -> Result, serde_json::Error> where Self: Sized, { - eprintln!("{:?}", str); let res: Result = serde_json::from_str(str); res.map(|kind| Box::new(kind) as Box) } +} +impl CustomCommonMethods> for AppRpcWeb3ErrorKind { fn partial_eq(&self, other: &Box) -> bool { other + .as_common_methods() .as_any() .downcast_ref::() .map_or(false, |other| self == other) } - fn costume_hash_fn(&self, hasher: &mut dyn Hasher) { + fn dup(&self) -> Box { + Box::new(self.clone()) + } + + as_any_ref_in_trait_impl!(); +} + +impl CustomHash for AppRpcWeb3ErrorKind { + fn costume_hash(&self, hasher: &mut dyn Hasher) { match self { AppRpcWeb3ErrorKind::Decoder => hasher.write_u8(0), AppRpcWeb3ErrorKind::Internal => hasher.write_u8(1), @@ -46,12 +62,6 @@ impl BlockchainDbError for AppRpcWeb3ErrorKind { } } } - - fn dup(&self) -> Box { - Box::new(self.clone()) - } - - as_any_ref_in_trait_impl!(); } #[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -88,16 +98,15 @@ impl From for AppRpcWeb3ErrorKind { } } +#[cfg(test)] mod tests { use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; - use crate::blockchain::errors::blockchain_db_error::masq_error::MASQError; use crate::blockchain::errors::blockchain_db_error::BlockchainDbError; use crate::blockchain::errors::blockchain_error::app_rpc_web3_error::{ AppRpcWeb3Error, LocalError, RemoteError, }; - use crate::blockchain::errors::test_utils::test_clone_for_blockchain_db_error; + use crate::blockchain::errors::test_utils::test_clone_impl_for_blockchain_db_error; use std::collections::hash_map::DefaultHasher; - use std::fmt::Debug; use std::hash::{Hash, Hasher}; #[test] @@ -153,7 +162,7 @@ mod tests { fn clone_works_for_blockchain_db_error_wrapping_app_rpc_error_kind() { let subject: Box = Box::new(AppRpcWeb3ErrorKind::Web3RpcError(123)); - test_clone_for_blockchain_db_error::(subject); + test_clone_impl_for_blockchain_db_error::(subject); } #[test] diff --git a/node/src/blockchain/errors/blockchain_db_error/masq_error.rs b/node/src/blockchain/errors/blockchain_db_error/masq_error_kind.rs similarity index 53% rename from node/src/blockchain/errors/blockchain_db_error/masq_error.rs rename to node/src/blockchain/errors/blockchain_db_error/masq_error_kind.rs index 75baf4474..dcdcaba4f 100644 --- a/node/src/blockchain/errors/blockchain_db_error/masq_error.rs +++ b/node/src/blockchain/errors/blockchain_db_error/masq_error_kind.rs @@ -1,41 +1,46 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::blockchain::errors::blockchain_db_error::BlockchainDbError; +use crate::blockchain::errors::blockchain_db_error::{BlockchainDbError, CustomHash, CustomSeDe}; +use crate::blockchain::errors::custom_common_methods::CustomCommonMethods; use serde_derive::{Deserialize, Serialize}; +use serde_json::Value; use std::hash::Hasher; use variant_count::VariantCount; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, VariantCount)] -pub enum MASQError { +pub enum MASQErrorKind { PendingTooLongNotReplaced, } -impl BlockchainDbError for MASQError { - fn serialize_fn(&self) -> Result { - serde_json::to_string(self) //TODO tested?? +impl BlockchainDbError for MASQErrorKind { + fn as_common_methods(&self) -> &dyn CustomCommonMethods> { + todo!() + } +} + +impl CustomSeDe for MASQErrorKind { + fn costume_serialize(&self) -> Result { + serde_json::to_value(self) } - fn deserialize_fn(str: &str) -> Result, serde_json::Error> + fn costume_deserialize(str: &str) -> Result, serde_json::Error> where Self: Sized, { - let res: Result = serde_json::from_str(str); + let res: Result = serde_json::from_str(str); res.map(|kind| Box::new(kind) as Box) } +} +impl CustomCommonMethods> for MASQErrorKind { fn partial_eq(&self, other: &Box) -> bool { other + .as_common_methods() .as_any() - .downcast_ref::() + .downcast_ref::() .map_or(false, |other| self == other) } - fn costume_hash_fn(&self, hasher: &mut dyn Hasher) { - match self { - MASQError::PendingTooLongNotReplaced => hasher.write_u8(0), - } - } - fn dup(&self) -> Box { Box::new(self.clone()) } @@ -43,26 +48,35 @@ impl BlockchainDbError for MASQError { as_any_ref_in_trait_impl!(); } +impl CustomHash for MASQErrorKind { + fn costume_hash(&self, hasher: &mut dyn Hasher) { + match self { + MASQErrorKind::PendingTooLongNotReplaced => hasher.write_u8(0), + } + } +} + #[cfg(test)] mod tests { use super::*; - use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; - use crate::blockchain::errors::test_utils::test_clone_for_blockchain_db_error; + use crate::blockchain::errors::blockchain_error::BlockchainError; + use crate::blockchain::errors::test_utils::test_clone_impl_for_blockchain_db_error; use std::collections::hash_map::DefaultHasher; use std::hash::Hash; #[test] - fn clone_works_for_blockchain_db_error_wrapping_masq_error() { - let subject: Box = Box::new(MASQError::PendingTooLongNotReplaced); + fn clone_works_for_blockchain_db_error_wrapping_masq_error_kind() { + let subject: Box = + Box::new(MASQErrorKind::PendingTooLongNotReplaced); - test_clone_for_blockchain_db_error::(subject); + test_clone_impl_for_blockchain_db_error::(subject); } #[test] - fn hashing_for_masq_error_works() { + fn hashing_for_masq_error_kind_works() { let mut hasher = DefaultHasher::default(); let mut hashes = vec![ - Box::new(MASQError::PendingTooLongNotReplaced) as Box, + Box::new(MASQErrorKind::PendingTooLongNotReplaced) as Box, // Add more types here as there are more types of MASQ app_rpc_web3_error_kind. ] .into_iter() @@ -82,19 +96,20 @@ mod tests { } #[test] - fn partial_eq_for_masq_error_works() { - let subject: Box = Box::new(MASQError::PendingTooLongNotReplaced); - let other: Box = Box::new(MASQError::PendingTooLongNotReplaced); + fn partial_eq_for_masq_error_kind_works() { + let subject: Box = + Box::new(MASQErrorKind::PendingTooLongNotReplaced); + let other: Box = Box::new(MASQErrorKind::PendingTooLongNotReplaced); assert_eq!(&subject, &other); - // Expand this test as there are more variants of MASQError. - assert_eq!(MASQError::VARIANT_COUNT, 1); + // Expand this test as there are more variants of MASQErrorKind. + assert_eq!(MASQErrorKind::VARIANT_COUNT, 1); } #[test] fn serialization_and_deserialization_for_blockchain_db_error_works() { vec![( - Box::new(MASQError::PendingTooLongNotReplaced) as Box, + Box::new(MASQErrorKind::PendingTooLongNotReplaced) as Box, "\"PendingTooLongNotReplaced\"", )] .into_iter() diff --git a/node/src/blockchain/errors/blockchain_db_error/mod.rs b/node/src/blockchain/errors/blockchain_db_error/mod.rs index 78037cf15..5d787be0a 100644 --- a/node/src/blockchain/errors/blockchain_db_error/mod.rs +++ b/node/src/blockchain/errors/blockchain_db_error/mod.rs @@ -1,27 +1,39 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. pub mod app_rpc_web3_error_kind; -pub mod masq_error; +pub mod masq_error_kind; use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; -use crate::blockchain::errors::blockchain_db_error::masq_error::MASQError; -use serde::de::{Error, MapAccess}; +use crate::blockchain::errors::blockchain_db_error::masq_error_kind::MASQErrorKind; +use crate::blockchain::errors::custom_common_methods::CustomCommonMethods; use serde::{Deserialize as DeserializeTrait, Serialize as SerializeTrait}; -use std::fmt::{Debug, Formatter}; +use serde_json::Value; +use std::fmt::Debug; use std::hash::{Hash, Hasher}; +pub trait BlockchainDbError: CustomSeDe + CustomHash + Debug { + fn as_common_methods(&self) -> &dyn CustomCommonMethods>; +} + +pub trait CustomSeDe { + fn costume_serialize(&self) -> Result; + fn costume_deserialize(str: &str) -> Result, serde_json::Error> + where + Self: Sized; +} + +pub trait CustomHash { + fn costume_hash(&self, hasher: &mut dyn Hasher); +} + impl SerializeTrait for Box { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { - let json_value: serde_json::Value = serde_json::from_str( - &self - .serialize_fn() - .map_err(|e| serde::ser::Error::custom(e))?, - ) // TODO tested? - .map_err(|e| serde::ser::Error::custom(e))?; // TODO tested? - json_value.serialize(serializer) + self.costume_serialize() + .map_err(|e| serde::ser::Error::custom(e))? + .serialize(serializer) } } @@ -30,15 +42,15 @@ impl<'de> DeserializeTrait<'de> for Box { where D: serde::Deserializer<'de>, { - let json_value: serde_json::Value = serde_json::Value::deserialize(deserializer)?; //TODO tested? + let json_value: serde_json::Value = serde_json::Value::deserialize(deserializer)?; let json_str = - serde_json::to_string(&json_value).map_err(|e| serde::de::Error::custom(e))?; // TODO tested? + serde_json::to_string(&json_value).map_err(|e| serde::de::Error::custom(e))?; // Untested error - if let Ok(error) = AppRpcWeb3ErrorKind::deserialize_fn(&json_str) { + if let Ok(error) = AppRpcWeb3ErrorKind::costume_deserialize(&json_str) { return Ok(error); } - if let Ok(error) = MASQError::deserialize_fn(&json_str) { + if let Ok(error) = MASQErrorKind::costume_deserialize(&json_str) { return Ok(error); } @@ -51,38 +63,28 @@ impl<'de> DeserializeTrait<'de> for Box { impl Clone for Box { fn clone(&self) -> Self { - self.dup() + self.as_common_methods().dup() } } impl PartialEq for Box { fn eq(&self, other: &Self) -> bool { - self.partial_eq(other) + self.as_common_methods().partial_eq(other) } } impl Hash for Box { fn hash(&self, state: &mut H) { - self.costume_hash_fn(state) + self.costume_hash(state) } } impl Eq for Box {} -pub trait BlockchainDbError: Debug { - fn serialize_fn(&self) -> Result; - fn deserialize_fn(str: &str) -> Result, serde_json::Error> - where - Self: Sized; - fn partial_eq(&self, other: &Box) -> bool; - fn costume_hash_fn(&self, hasher: &mut dyn Hasher); - fn dup(&self) -> Box; - as_any_ref_in_trait!(); -} - #[cfg(test)] mod tests { use super::*; + use crate::blockchain::errors::test_utils::BlockchainDbErrorMock; #[test] fn deserialization_fails() { @@ -95,4 +97,26 @@ mod tests { "Unable to deserialize BlockchainDbError from: \"bluh\"" ) } + + #[test] + fn pre_serialization_costume_error_is_well_arranged() { + let mock = BlockchainDbErrorMock::default(); + let subject: Box = Box::new(mock); + + let res = serde_json::to_string(&subject).unwrap_err(); + + assert_eq!( + res.to_string(), + "invalid type: character `a`, expected null" + ); + } + + #[test] + fn deserialization_other_error() { + let result = + serde_json::from_str::>(r#"{"key":invalid_json_value}"#) + .unwrap_err(); + + assert_eq!(result.to_string(), "expected value at line 1 column 8"); + } } diff --git a/node/src/blockchain/errors/blockchain_error/app_rpc_web3_error.rs b/node/src/blockchain/errors/blockchain_error/app_rpc_web3_error.rs index fe7a376d1..8681537cf 100644 --- a/node/src/blockchain/errors/blockchain_error/app_rpc_web3_error.rs +++ b/node/src/blockchain/errors/blockchain_error/app_rpc_web3_error.rs @@ -1,6 +1,9 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use serde_derive::{Deserialize, Serialize}; +use crate::blockchain::errors::blockchain_db_error::BlockchainDbError; +use crate::blockchain::errors::blockchain_error::BlockchainError; +use crate::blockchain::errors::custom_common_methods::CustomCommonMethods; +use std::fmt::{Display, Formatter}; use web3::error::Error as Web3Error; // Prefixed with App to clearly distinguish app-specific app_rpc_web3_error_kind from library app_rpc_web3_error_kind. @@ -10,6 +13,34 @@ pub enum AppRpcWeb3Error { Remote(RemoteError), } +impl BlockchainError for AppRpcWeb3Error { + fn as_common_methods(&self) -> &dyn CustomCommonMethods> { + self + } + + fn downgrade(&self) -> Box { + todo!() + } +} + +impl CustomCommonMethods> for AppRpcWeb3Error { + fn partial_eq(&self, other: &Box) -> bool { + todo!() + } + + fn dup(&self) -> Box { + Box::new(self.clone()) + } + + as_any_ref_in_trait_impl!(); +} + +impl Display for AppRpcWeb3Error { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub enum LocalError { Decoder(String), @@ -56,6 +87,8 @@ impl From for AppRpcWeb3Error { #[cfg(test)] mod tests { use super::*; + use crate::blockchain::errors::test_utils::test_clone_impl_for_blockchain_error; + use std::vec; #[test] fn web3_error_to_failure_reason_conversion_works() { @@ -101,4 +134,29 @@ mod tests { AppRpcWeb3Error::Remote(RemoteError::Unreachable) ); } + + #[test] + fn clone_works_for_blockchain_error_wrapping_app_rpc_web3_error() { + let subject: Box = + Box::new(AppRpcWeb3Error::Local(LocalError::Internal)); + + test_clone_impl_for_blockchain_error::(subject); + } + + #[test] + fn display_for_blockchain_error_object_works() { + vec![ + AppRpcWeb3Error::Local(LocalError::Decoder("Serious decoder error".to_string())), + AppRpcWeb3Error::Remote(RemoteError::InvalidResponse( + "The most invalid response of all invalid responses".to_string(), + )), + AppRpcWeb3Error::Local(LocalError::Internal), + AppRpcWeb3Error::Remote(RemoteError::Unreachable), + ] + .into_iter() + .for_each(|error| { + let wrapped_as_trait_object: Box = Box::new(error.clone()); + assert_eq!(wrapped_as_trait_object.to_string(), format!("{:?}", error)); + }) + } } diff --git a/node/src/blockchain/errors/blockchain_error/masq_error.rs b/node/src/blockchain/errors/blockchain_error/masq_error.rs new file mode 100644 index 000000000..2743503cd --- /dev/null +++ b/node/src/blockchain/errors/blockchain_error/masq_error.rs @@ -0,0 +1,67 @@ +// Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::blockchain::errors::blockchain_db_error::BlockchainDbError; +use crate::blockchain::errors::blockchain_error::BlockchainError; +use crate::blockchain::errors::custom_common_methods::CustomCommonMethods; +use std::fmt::{Debug, Display, Formatter}; + +#[derive(Debug, PartialEq, Clone)] +pub enum MASQError { + PendingTooLongNotReplaced, +} + +impl BlockchainError for MASQError { + fn as_common_methods(&self) -> &dyn CustomCommonMethods> { + self + } + + fn downgrade(&self) -> Box { + todo!() + } +} + +impl CustomCommonMethods> for MASQError { + fn partial_eq(&self, other: &Box) -> bool { + todo!() + } + + fn dup(&self) -> Box { + Box::new(self.clone()) + } + + as_any_ref_in_trait_impl!(); +} + +impl Display for MASQError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::blockchain::errors::blockchain_error::app_rpc_web3_error::{ + AppRpcWeb3Error, LocalError, RemoteError, + }; + use crate::blockchain::errors::blockchain_error::BlockchainError; + use crate::blockchain::errors::test_utils::test_clone_impl_for_blockchain_error; + use std::fmt::format; + + #[test] + fn clone_works_for_blockchain_error_wrapping_masq_error() { + let subject: Box = Box::new(MASQError::PendingTooLongNotReplaced); + + test_clone_impl_for_blockchain_error::(subject); + } + + #[test] + fn display_for_blockchain_error_object_works() { + vec![MASQError::PendingTooLongNotReplaced] + .into_iter() + .for_each(|error| { + let wrapped_as_trait_object: Box = Box::new(error.clone()); + assert_eq!(wrapped_as_trait_object.to_string(), format!("{:?}", error)); + }) + } +} diff --git a/node/src/blockchain/errors/blockchain_error/mod.rs b/node/src/blockchain/errors/blockchain_error/mod.rs index 013ce8a72..ff63069bd 100644 --- a/node/src/blockchain/errors/blockchain_error/mod.rs +++ b/node/src/blockchain/errors/blockchain_error/mod.rs @@ -1,7 +1,28 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use std::fmt::Display; +use crate::blockchain::errors::blockchain_db_error::BlockchainDbError; +use crate::blockchain::errors::custom_common_methods::CustomCommonMethods; +use std::fmt::{Debug, Display}; pub mod app_rpc_web3_error; +pub mod masq_error; -pub trait BlockchainError: Display {} +// The Display impl is meant to be used for logging purposes. +pub trait BlockchainError: Display + Debug { + fn as_common_methods(&self) -> &dyn CustomCommonMethods>; + fn downgrade(&self) -> Box; +} + +impl Clone for Box { + fn clone(&self) -> Self { + self.as_common_methods().dup() + } +} + +impl PartialEq for Box { + fn eq(&self, other: &Self) -> bool { + todo!() + } +} + +impl Eq for Box {} diff --git a/node/src/blockchain/errors/custom_common_methods.rs b/node/src/blockchain/errors/custom_common_methods.rs new file mode 100644 index 000000000..02be5d62a --- /dev/null +++ b/node/src/blockchain/errors/custom_common_methods.rs @@ -0,0 +1,9 @@ +// Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::blockchain::errors::blockchain_db_error::BlockchainDbError; + +pub trait CustomCommonMethods { + fn partial_eq(&self, other: &Other) -> bool; + fn dup(&self) -> Other; + as_any_ref_in_trait!(); +} diff --git a/node/src/blockchain/errors/mod.rs b/node/src/blockchain/errors/mod.rs index e007b792f..5a7ab5dea 100644 --- a/node/src/blockchain/errors/mod.rs +++ b/node/src/blockchain/errors/mod.rs @@ -2,5 +2,6 @@ pub mod blockchain_db_error; pub mod blockchain_error; +mod custom_common_methods; mod test_utils; pub mod validation_status; diff --git a/node/src/blockchain/errors/test_utils.rs b/node/src/blockchain/errors/test_utils.rs index 63e83aed3..08a5b1c43 100644 --- a/node/src/blockchain/errors/test_utils.rs +++ b/node/src/blockchain/errors/test_utils.rs @@ -1,15 +1,78 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::blockchain::errors::blockchain_db_error::BlockchainDbError; +use crate::blockchain::errors::blockchain_db_error::{BlockchainDbError, CustomHash, CustomSeDe}; +use crate::blockchain::errors::blockchain_error::BlockchainError; +use crate::blockchain::errors::custom_common_methods::CustomCommonMethods; +use serde::de::{Error, Unexpected}; +use serde_json::Value; use std::fmt::Debug; +use std::hash::Hasher; -pub fn test_clone_for_blockchain_db_error(subject: Box) -where - ErrorType: PartialEq + Debug + 'static, -{ - let result = subject.clone(); +macro_rules! test_clone_impl { + ($test_fn_name: ident, $boxed_trait: ident) => { + pub fn $test_fn_name(subject: Box) + where + ErrorType: PartialEq + Debug + 'static, + { + let result = subject.clone(); - let specified_subject = subject.as_any().downcast_ref::().unwrap(); - let specified_result = result.as_any().downcast_ref::().unwrap(); - assert_eq!(specified_result, specified_subject) + let specified_subject = subject + .as_common_methods() + .as_any() + .downcast_ref::() + .unwrap(); + let specified_result = result + .as_common_methods() + .as_any() + .downcast_ref::() + .unwrap(); + assert_eq!(specified_result, specified_subject) + } + }; +} + +test_clone_impl!(test_clone_impl_for_blockchain_db_error, BlockchainDbError); +test_clone_impl!(test_clone_impl_for_blockchain_error, BlockchainError); + +#[derive(Debug, Default)] +pub struct BlockchainDbErrorMock {} + +impl BlockchainDbError for BlockchainDbErrorMock { + fn as_common_methods(&self) -> &dyn CustomCommonMethods> { + unimplemented!("not needed for testing") + } +} + +impl CustomSeDe for BlockchainDbErrorMock { + fn costume_serialize(&self) -> Result { + Err(serde_json::Error::invalid_type( + Unexpected::Char('a'), + &"null", + )) + } + + fn costume_deserialize( + _str: &str, + ) -> Result, serde_json::error::Error> + where + Self: Sized, + { + unimplemented!("not needed for testing") + } +} + +impl CustomHash for BlockchainDbErrorMock { + fn costume_hash(&self, _hasher: &mut dyn Hasher) { + unimplemented!("not needed for testing") + } +} + +impl CustomCommonMethods> for BlockchainDbErrorMock { + fn partial_eq(&self, _other: &Box) -> bool { + unimplemented!("not needed for testing") + } + + fn dup(&self) -> Box { + unimplemented!("not needed for testing") + } } From 47353a61111f8488c93a7ebe331b483c65e4c36d Mon Sep 17 00:00:00 2001 From: Bert Date: Mon, 18 Aug 2025 11:14:13 +0200 Subject: [PATCH 39/61] GH-683: renamed error --- .../blockchain_db_error/masq_error_kind.rs | 2 +- .../app_rpc_web3_error.rs | 16 ++++++++-------- .../masq_error.rs | 18 +++++++++--------- .../mod.rs | 10 +++++----- node/src/blockchain/errors/test_utils.rs | 4 ++-- 5 files changed, 25 insertions(+), 25 deletions(-) rename node/src/blockchain/errors/{blockchain_error => blockchain_loggable_error}/app_rpc_web3_error.rs (90%) rename node/src/blockchain/errors/{blockchain_error => blockchain_loggable_error}/masq_error.rs (69%) rename node/src/blockchain/errors/{blockchain_error => blockchain_loggable_error}/mod.rs (73%) diff --git a/node/src/blockchain/errors/blockchain_db_error/masq_error_kind.rs b/node/src/blockchain/errors/blockchain_db_error/masq_error_kind.rs index dcdcaba4f..00bf62ce9 100644 --- a/node/src/blockchain/errors/blockchain_db_error/masq_error_kind.rs +++ b/node/src/blockchain/errors/blockchain_db_error/masq_error_kind.rs @@ -59,7 +59,7 @@ impl CustomHash for MASQErrorKind { #[cfg(test)] mod tests { use super::*; - use crate::blockchain::errors::blockchain_error::BlockchainError; + use crate::blockchain::errors::blockchain_error::BlockchainLoggableError; use crate::blockchain::errors::test_utils::test_clone_impl_for_blockchain_db_error; use std::collections::hash_map::DefaultHasher; use std::hash::Hash; diff --git a/node/src/blockchain/errors/blockchain_error/app_rpc_web3_error.rs b/node/src/blockchain/errors/blockchain_loggable_error/app_rpc_web3_error.rs similarity index 90% rename from node/src/blockchain/errors/blockchain_error/app_rpc_web3_error.rs rename to node/src/blockchain/errors/blockchain_loggable_error/app_rpc_web3_error.rs index 8681537cf..a776d2702 100644 --- a/node/src/blockchain/errors/blockchain_error/app_rpc_web3_error.rs +++ b/node/src/blockchain/errors/blockchain_loggable_error/app_rpc_web3_error.rs @@ -1,7 +1,7 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::blockchain::errors::blockchain_db_error::BlockchainDbError; -use crate::blockchain::errors::blockchain_error::BlockchainError; +use crate::blockchain::errors::blockchain_error::BlockchainLoggableError; use crate::blockchain::errors::custom_common_methods::CustomCommonMethods; use std::fmt::{Display, Formatter}; use web3::error::Error as Web3Error; @@ -13,8 +13,8 @@ pub enum AppRpcWeb3Error { Remote(RemoteError), } -impl BlockchainError for AppRpcWeb3Error { - fn as_common_methods(&self) -> &dyn CustomCommonMethods> { +impl BlockchainLoggableError for AppRpcWeb3Error { + fn as_common_methods(&self) -> &dyn CustomCommonMethods> { self } @@ -23,12 +23,12 @@ impl BlockchainError for AppRpcWeb3Error { } } -impl CustomCommonMethods> for AppRpcWeb3Error { - fn partial_eq(&self, other: &Box) -> bool { +impl CustomCommonMethods> for AppRpcWeb3Error { + fn partial_eq(&self, other: &Box) -> bool { todo!() } - fn dup(&self) -> Box { + fn dup(&self) -> Box { Box::new(self.clone()) } @@ -137,7 +137,7 @@ mod tests { #[test] fn clone_works_for_blockchain_error_wrapping_app_rpc_web3_error() { - let subject: Box = + let subject: Box = Box::new(AppRpcWeb3Error::Local(LocalError::Internal)); test_clone_impl_for_blockchain_error::(subject); @@ -155,7 +155,7 @@ mod tests { ] .into_iter() .for_each(|error| { - let wrapped_as_trait_object: Box = Box::new(error.clone()); + let wrapped_as_trait_object: Box = Box::new(error.clone()); assert_eq!(wrapped_as_trait_object.to_string(), format!("{:?}", error)); }) } diff --git a/node/src/blockchain/errors/blockchain_error/masq_error.rs b/node/src/blockchain/errors/blockchain_loggable_error/masq_error.rs similarity index 69% rename from node/src/blockchain/errors/blockchain_error/masq_error.rs rename to node/src/blockchain/errors/blockchain_loggable_error/masq_error.rs index 2743503cd..47af4a344 100644 --- a/node/src/blockchain/errors/blockchain_error/masq_error.rs +++ b/node/src/blockchain/errors/blockchain_loggable_error/masq_error.rs @@ -1,7 +1,7 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::blockchain::errors::blockchain_db_error::BlockchainDbError; -use crate::blockchain::errors::blockchain_error::BlockchainError; +use crate::blockchain::errors::blockchain_error::BlockchainLoggableError; use crate::blockchain::errors::custom_common_methods::CustomCommonMethods; use std::fmt::{Debug, Display, Formatter}; @@ -10,8 +10,8 @@ pub enum MASQError { PendingTooLongNotReplaced, } -impl BlockchainError for MASQError { - fn as_common_methods(&self) -> &dyn CustomCommonMethods> { +impl BlockchainLoggableError for MASQError { + fn as_common_methods(&self) -> &dyn CustomCommonMethods> { self } @@ -20,12 +20,12 @@ impl BlockchainError for MASQError { } } -impl CustomCommonMethods> for MASQError { - fn partial_eq(&self, other: &Box) -> bool { +impl CustomCommonMethods> for MASQError { + fn partial_eq(&self, other: &Box) -> bool { todo!() } - fn dup(&self) -> Box { + fn dup(&self) -> Box { Box::new(self.clone()) } @@ -44,13 +44,13 @@ mod tests { use crate::blockchain::errors::blockchain_error::app_rpc_web3_error::{ AppRpcWeb3Error, LocalError, RemoteError, }; - use crate::blockchain::errors::blockchain_error::BlockchainError; + use crate::blockchain::errors::blockchain_error::BlockchainLoggableError; use crate::blockchain::errors::test_utils::test_clone_impl_for_blockchain_error; use std::fmt::format; #[test] fn clone_works_for_blockchain_error_wrapping_masq_error() { - let subject: Box = Box::new(MASQError::PendingTooLongNotReplaced); + let subject: Box = Box::new(MASQError::PendingTooLongNotReplaced); test_clone_impl_for_blockchain_error::(subject); } @@ -60,7 +60,7 @@ mod tests { vec![MASQError::PendingTooLongNotReplaced] .into_iter() .for_each(|error| { - let wrapped_as_trait_object: Box = Box::new(error.clone()); + let wrapped_as_trait_object: Box = Box::new(error.clone()); assert_eq!(wrapped_as_trait_object.to_string(), format!("{:?}", error)); }) } diff --git a/node/src/blockchain/errors/blockchain_error/mod.rs b/node/src/blockchain/errors/blockchain_loggable_error/mod.rs similarity index 73% rename from node/src/blockchain/errors/blockchain_error/mod.rs rename to node/src/blockchain/errors/blockchain_loggable_error/mod.rs index ff63069bd..21d740e21 100644 --- a/node/src/blockchain/errors/blockchain_error/mod.rs +++ b/node/src/blockchain/errors/blockchain_loggable_error/mod.rs @@ -8,21 +8,21 @@ pub mod app_rpc_web3_error; pub mod masq_error; // The Display impl is meant to be used for logging purposes. -pub trait BlockchainError: Display + Debug { - fn as_common_methods(&self) -> &dyn CustomCommonMethods>; +pub trait BlockchainLoggableError: Display + Debug { + fn as_common_methods(&self) -> &dyn CustomCommonMethods>; fn downgrade(&self) -> Box; } -impl Clone for Box { +impl Clone for Box { fn clone(&self) -> Self { self.as_common_methods().dup() } } -impl PartialEq for Box { +impl PartialEq for Box { fn eq(&self, other: &Self) -> bool { todo!() } } -impl Eq for Box {} +impl Eq for Box {} diff --git a/node/src/blockchain/errors/test_utils.rs b/node/src/blockchain/errors/test_utils.rs index 08a5b1c43..0b81f4541 100644 --- a/node/src/blockchain/errors/test_utils.rs +++ b/node/src/blockchain/errors/test_utils.rs @@ -1,7 +1,7 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::blockchain::errors::blockchain_db_error::{BlockchainDbError, CustomHash, CustomSeDe}; -use crate::blockchain::errors::blockchain_error::BlockchainError; +use crate::blockchain::errors::blockchain_error::BlockchainLoggableError; use crate::blockchain::errors::custom_common_methods::CustomCommonMethods; use serde::de::{Error, Unexpected}; use serde_json::Value; @@ -32,7 +32,7 @@ macro_rules! test_clone_impl { } test_clone_impl!(test_clone_impl_for_blockchain_db_error, BlockchainDbError); -test_clone_impl!(test_clone_impl_for_blockchain_error, BlockchainError); +test_clone_impl!(test_clone_impl_for_blockchain_error, BlockchainLoggableError); #[derive(Debug, Default)] pub struct BlockchainDbErrorMock {} From 5055e545eb496174390bf7566072250bb7d768fb Mon Sep 17 00:00:00 2001 From: Bert Date: Mon, 18 Aug 2025 11:16:37 +0200 Subject: [PATCH 40/61] GH-683: additional fix to renaming --- .../errors/blockchain_db_error/app_rpc_web3_error_kind.rs | 4 ++-- .../errors/blockchain_db_error/masq_error_kind.rs | 1 - .../blockchain_loggable_error/app_rpc_web3_error.rs | 2 +- .../errors/blockchain_loggable_error/masq_error.rs | 8 ++------ node/src/blockchain/errors/custom_common_methods.rs | 2 -- node/src/blockchain/errors/mod.rs | 2 +- node/src/blockchain/errors/test_utils.rs | 2 +- 7 files changed, 7 insertions(+), 14 deletions(-) diff --git a/node/src/blockchain/errors/blockchain_db_error/app_rpc_web3_error_kind.rs b/node/src/blockchain/errors/blockchain_db_error/app_rpc_web3_error_kind.rs index e577155a0..9592d7718 100644 --- a/node/src/blockchain/errors/blockchain_db_error/app_rpc_web3_error_kind.rs +++ b/node/src/blockchain/errors/blockchain_db_error/app_rpc_web3_error_kind.rs @@ -1,7 +1,7 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::blockchain::errors::blockchain_db_error::{BlockchainDbError, CustomHash, CustomSeDe}; -use crate::blockchain::errors::blockchain_error::app_rpc_web3_error::{ +use crate::blockchain::errors::blockchain_loggable_error::app_rpc_web3_error::{ AppRpcWeb3Error, LocalError, RemoteError, }; use crate::blockchain::errors::custom_common_methods::CustomCommonMethods; @@ -102,7 +102,7 @@ impl From for AppRpcWeb3ErrorKind { mod tests { use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; use crate::blockchain::errors::blockchain_db_error::BlockchainDbError; - use crate::blockchain::errors::blockchain_error::app_rpc_web3_error::{ + use crate::blockchain::errors::blockchain_loggable_error::app_rpc_web3_error::{ AppRpcWeb3Error, LocalError, RemoteError, }; use crate::blockchain::errors::test_utils::test_clone_impl_for_blockchain_db_error; diff --git a/node/src/blockchain/errors/blockchain_db_error/masq_error_kind.rs b/node/src/blockchain/errors/blockchain_db_error/masq_error_kind.rs index 00bf62ce9..f8dd69e1f 100644 --- a/node/src/blockchain/errors/blockchain_db_error/masq_error_kind.rs +++ b/node/src/blockchain/errors/blockchain_db_error/masq_error_kind.rs @@ -59,7 +59,6 @@ impl CustomHash for MASQErrorKind { #[cfg(test)] mod tests { use super::*; - use crate::blockchain::errors::blockchain_error::BlockchainLoggableError; use crate::blockchain::errors::test_utils::test_clone_impl_for_blockchain_db_error; use std::collections::hash_map::DefaultHasher; use std::hash::Hash; diff --git a/node/src/blockchain/errors/blockchain_loggable_error/app_rpc_web3_error.rs b/node/src/blockchain/errors/blockchain_loggable_error/app_rpc_web3_error.rs index a776d2702..499153fd4 100644 --- a/node/src/blockchain/errors/blockchain_loggable_error/app_rpc_web3_error.rs +++ b/node/src/blockchain/errors/blockchain_loggable_error/app_rpc_web3_error.rs @@ -1,7 +1,7 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::blockchain::errors::blockchain_db_error::BlockchainDbError; -use crate::blockchain::errors::blockchain_error::BlockchainLoggableError; +use crate::blockchain::errors::blockchain_loggable_error::BlockchainLoggableError; use crate::blockchain::errors::custom_common_methods::CustomCommonMethods; use std::fmt::{Display, Formatter}; use web3::error::Error as Web3Error; diff --git a/node/src/blockchain/errors/blockchain_loggable_error/masq_error.rs b/node/src/blockchain/errors/blockchain_loggable_error/masq_error.rs index 47af4a344..11c368841 100644 --- a/node/src/blockchain/errors/blockchain_loggable_error/masq_error.rs +++ b/node/src/blockchain/errors/blockchain_loggable_error/masq_error.rs @@ -1,7 +1,7 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::blockchain::errors::blockchain_db_error::BlockchainDbError; -use crate::blockchain::errors::blockchain_error::BlockchainLoggableError; +use crate::blockchain::errors::blockchain_loggable_error::BlockchainLoggableError; use crate::blockchain::errors::custom_common_methods::CustomCommonMethods; use std::fmt::{Debug, Display, Formatter}; @@ -41,12 +41,8 @@ impl Display for MASQError { #[cfg(test)] mod tests { use super::*; - use crate::blockchain::errors::blockchain_error::app_rpc_web3_error::{ - AppRpcWeb3Error, LocalError, RemoteError, - }; - use crate::blockchain::errors::blockchain_error::BlockchainLoggableError; + use crate::blockchain::errors::blockchain_loggable_error::BlockchainLoggableError; use crate::blockchain::errors::test_utils::test_clone_impl_for_blockchain_error; - use std::fmt::format; #[test] fn clone_works_for_blockchain_error_wrapping_masq_error() { diff --git a/node/src/blockchain/errors/custom_common_methods.rs b/node/src/blockchain/errors/custom_common_methods.rs index 02be5d62a..ed6049517 100644 --- a/node/src/blockchain/errors/custom_common_methods.rs +++ b/node/src/blockchain/errors/custom_common_methods.rs @@ -1,7 +1,5 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::blockchain::errors::blockchain_db_error::BlockchainDbError; - pub trait CustomCommonMethods { fn partial_eq(&self, other: &Other) -> bool; fn dup(&self) -> Other; diff --git a/node/src/blockchain/errors/mod.rs b/node/src/blockchain/errors/mod.rs index 5a7ab5dea..1746f295a 100644 --- a/node/src/blockchain/errors/mod.rs +++ b/node/src/blockchain/errors/mod.rs @@ -1,7 +1,7 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. pub mod blockchain_db_error; -pub mod blockchain_error; +pub mod blockchain_loggable_error; mod custom_common_methods; mod test_utils; pub mod validation_status; diff --git a/node/src/blockchain/errors/test_utils.rs b/node/src/blockchain/errors/test_utils.rs index 0b81f4541..0dd4173b1 100644 --- a/node/src/blockchain/errors/test_utils.rs +++ b/node/src/blockchain/errors/test_utils.rs @@ -1,7 +1,7 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::blockchain::errors::blockchain_db_error::{BlockchainDbError, CustomHash, CustomSeDe}; -use crate::blockchain::errors::blockchain_error::BlockchainLoggableError; +use crate::blockchain::errors::blockchain_loggable_error::BlockchainLoggableError; use crate::blockchain::errors::custom_common_methods::CustomCommonMethods; use serde::de::{Error, Unexpected}; use serde_json::Value; From 7a33d30b2aa89d315df35a0cdafc5822ce6c3de0 Mon Sep 17 00:00:00 2001 From: Bert Date: Mon, 18 Aug 2025 12:19:38 +0200 Subject: [PATCH 41/61] GH-683: finished --- .../app_rpc_web3_error_kind.rs | 30 ++++---- .../blockchain_db_error/masq_error_kind.rs | 50 +++++++++++-- .../errors/blockchain_db_error/mod.rs | 4 +- .../app_rpc_web3_error.rs | 71 +++++++++++++++++-- .../blockchain_loggable_error/masq_error.rs | 36 +++++++--- .../errors/blockchain_loggable_error/mod.rs | 12 +++- ...om_common_methods.rs => common_methods.rs} | 2 +- node/src/blockchain/errors/mod.rs | 2 +- node/src/blockchain/errors/test_utils.rs | 11 +-- 9 files changed, 173 insertions(+), 45 deletions(-) rename node/src/blockchain/errors/{custom_common_methods.rs => common_methods.rs} (83%) diff --git a/node/src/blockchain/errors/blockchain_db_error/app_rpc_web3_error_kind.rs b/node/src/blockchain/errors/blockchain_db_error/app_rpc_web3_error_kind.rs index 9592d7718..06a4b7715 100644 --- a/node/src/blockchain/errors/blockchain_db_error/app_rpc_web3_error_kind.rs +++ b/node/src/blockchain/errors/blockchain_db_error/app_rpc_web3_error_kind.rs @@ -4,15 +4,15 @@ use crate::blockchain::errors::blockchain_db_error::{BlockchainDbError, CustomHa use crate::blockchain::errors::blockchain_loggable_error::app_rpc_web3_error::{ AppRpcWeb3Error, LocalError, RemoteError, }; -use crate::blockchain::errors::custom_common_methods::CustomCommonMethods; +use crate::blockchain::errors::common_methods::CommonMethods; use serde_derive::{Deserialize, Serialize}; use serde_json::Value; use std::fmt::Debug; use std::hash::{Hash, Hasher}; impl BlockchainDbError for AppRpcWeb3ErrorKind { - fn as_common_methods(&self) -> &dyn CustomCommonMethods> { - todo!() + fn as_common_methods(&self) -> &dyn CommonMethods> { + self } } @@ -30,7 +30,7 @@ impl CustomSeDe for AppRpcWeb3ErrorKind { } } -impl CustomCommonMethods> for AppRpcWeb3ErrorKind { +impl CommonMethods> for AppRpcWeb3ErrorKind { fn partial_eq(&self, other: &Box) -> bool { other .as_common_methods() @@ -79,8 +79,8 @@ pub enum AppRpcWeb3ErrorKind { Web3RpcError(i64), // Keep only the stable error code } -impl From for AppRpcWeb3ErrorKind { - fn from(err: AppRpcWeb3Error) -> Self { +impl From<&AppRpcWeb3Error> for AppRpcWeb3ErrorKind { + fn from(err: &AppRpcWeb3Error) -> Self { match err { AppRpcWeb3Error::Local(local) => match local { LocalError::Decoder(_) => Self::Decoder, @@ -92,7 +92,7 @@ impl From for AppRpcWeb3ErrorKind { AppRpcWeb3Error::Remote(remote) => match remote { RemoteError::InvalidResponse(_) => Self::InvalidResponse, RemoteError::Unreachable => Self::ServerUnreachable, - RemoteError::Web3RpcError { code, .. } => Self::Web3RpcError(code), + RemoteError::Web3RpcError { code, .. } => Self::Web3RpcError(*code), }, } } @@ -112,45 +112,45 @@ mod tests { #[test] fn conversion_between_app_rpc_error_and_app_rpc_error_kind_works() { assert_eq!( - AppRpcWeb3ErrorKind::from(AppRpcWeb3Error::Local(LocalError::Decoder( + AppRpcWeb3ErrorKind::from(&AppRpcWeb3Error::Local(LocalError::Decoder( "Decoder error".to_string() ))), AppRpcWeb3ErrorKind::Decoder ); assert_eq!( - AppRpcWeb3ErrorKind::from(AppRpcWeb3Error::Local(LocalError::Internal)), + AppRpcWeb3ErrorKind::from(&AppRpcWeb3Error::Local(LocalError::Internal)), AppRpcWeb3ErrorKind::Internal ); assert_eq!( - AppRpcWeb3ErrorKind::from(AppRpcWeb3Error::Local(LocalError::Io( + AppRpcWeb3ErrorKind::from(&AppRpcWeb3Error::Local(LocalError::Io( "IO error".to_string() ))), AppRpcWeb3ErrorKind::IO ); assert_eq!( - AppRpcWeb3ErrorKind::from(AppRpcWeb3Error::Local(LocalError::Signing( + AppRpcWeb3ErrorKind::from(&AppRpcWeb3Error::Local(LocalError::Signing( "Signing error".to_string() ))), AppRpcWeb3ErrorKind::Signing ); assert_eq!( - AppRpcWeb3ErrorKind::from(AppRpcWeb3Error::Local(LocalError::Transport( + AppRpcWeb3ErrorKind::from(&AppRpcWeb3Error::Local(LocalError::Transport( "Transport error".to_string() ))), AppRpcWeb3ErrorKind::Transport ); assert_eq!( - AppRpcWeb3ErrorKind::from(AppRpcWeb3Error::Remote(RemoteError::InvalidResponse( + AppRpcWeb3ErrorKind::from(&AppRpcWeb3Error::Remote(RemoteError::InvalidResponse( "Invalid response".to_string() ))), AppRpcWeb3ErrorKind::InvalidResponse ); assert_eq!( - AppRpcWeb3ErrorKind::from(AppRpcWeb3Error::Remote(RemoteError::Unreachable)), + AppRpcWeb3ErrorKind::from(&AppRpcWeb3Error::Remote(RemoteError::Unreachable)), AppRpcWeb3ErrorKind::ServerUnreachable ); assert_eq!( - AppRpcWeb3ErrorKind::from(AppRpcWeb3Error::Remote(RemoteError::Web3RpcError { + AppRpcWeb3ErrorKind::from(&AppRpcWeb3Error::Remote(RemoteError::Web3RpcError { code: 55, message: "Booga".to_string() })), diff --git a/node/src/blockchain/errors/blockchain_db_error/masq_error_kind.rs b/node/src/blockchain/errors/blockchain_db_error/masq_error_kind.rs index f8dd69e1f..90517a3dc 100644 --- a/node/src/blockchain/errors/blockchain_db_error/masq_error_kind.rs +++ b/node/src/blockchain/errors/blockchain_db_error/masq_error_kind.rs @@ -1,7 +1,8 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::blockchain::errors::blockchain_db_error::{BlockchainDbError, CustomHash, CustomSeDe}; -use crate::blockchain::errors::custom_common_methods::CustomCommonMethods; +use crate::blockchain::errors::blockchain_loggable_error::masq_error::MASQError; +use crate::blockchain::errors::common_methods::CommonMethods; use serde_derive::{Deserialize, Serialize}; use serde_json::Value; use std::hash::Hasher; @@ -13,8 +14,8 @@ pub enum MASQErrorKind { } impl BlockchainDbError for MASQErrorKind { - fn as_common_methods(&self) -> &dyn CustomCommonMethods> { - todo!() + fn as_common_methods(&self) -> &dyn CommonMethods> { + self } } @@ -32,7 +33,7 @@ impl CustomSeDe for MASQErrorKind { } } -impl CustomCommonMethods> for MASQErrorKind { +impl CommonMethods> for MASQErrorKind { fn partial_eq(&self, other: &Box) -> bool { other .as_common_methods() @@ -56,13 +57,31 @@ impl CustomHash for MASQErrorKind { } } +impl From<&MASQError> for MASQErrorKind { + fn from(masq_error: &MASQError) -> Self { + match masq_error { + MASQError::PendingTooLongNotReplaced => MASQErrorKind::PendingTooLongNotReplaced, + } + } +} + #[cfg(test)] mod tests { use super::*; + use crate::blockchain::errors::blockchain_loggable_error::masq_error::MASQError; + use crate::blockchain::errors::blockchain_loggable_error::BlockchainLoggableError; use crate::blockchain::errors::test_utils::test_clone_impl_for_blockchain_db_error; use std::collections::hash_map::DefaultHasher; use std::hash::Hash; + #[test] + fn conversion_between_masq_error_and_masq_error_kind_works() { + assert_eq!( + MASQErrorKind::from(&MASQError::PendingTooLongNotReplaced), + MASQErrorKind::PendingTooLongNotReplaced + ); + } + #[test] fn clone_works_for_blockchain_db_error_wrapping_masq_error_kind() { let subject: Box = @@ -91,7 +110,9 @@ mod tests { hashes.iter().for_each(|other_hash| { assert_ne!(picked_hash, other_hash); }); - }) + }); + // Expand this test as there are more variants of MASQErrorKind. + assert_eq!(MASQErrorKind::VARIANT_COUNT, 1); } #[test] @@ -118,6 +139,23 @@ mod tests { let trait_object_result = serde_json::from_str::>(&json_result).unwrap(); assert_eq!(&trait_object_result, &blockchain_error); - }) + }); + // Expand this test as there are more variants of MASQErrorKind. + assert_eq!(MASQErrorKind::VARIANT_COUNT, 1); + } + + #[test] + fn blockchain_loggable_error_can_be_converted_to_blockchain_db_error_for_masq_errors() { + let error: Box = + Box::new(MASQError::PendingTooLongNotReplaced); + + let result = >::from(error); + + assert_eq!( + &result, + &(Box::new(MASQErrorKind::PendingTooLongNotReplaced) as Box) + ); + // Expand this test as there are more variants of MASQErrorKind. + assert_eq!(MASQErrorKind::VARIANT_COUNT, 1); } } diff --git a/node/src/blockchain/errors/blockchain_db_error/mod.rs b/node/src/blockchain/errors/blockchain_db_error/mod.rs index 5d787be0a..b47933d52 100644 --- a/node/src/blockchain/errors/blockchain_db_error/mod.rs +++ b/node/src/blockchain/errors/blockchain_db_error/mod.rs @@ -5,14 +5,14 @@ pub mod masq_error_kind; use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; use crate::blockchain::errors::blockchain_db_error::masq_error_kind::MASQErrorKind; -use crate::blockchain::errors::custom_common_methods::CustomCommonMethods; +use crate::blockchain::errors::common_methods::CommonMethods; use serde::{Deserialize as DeserializeTrait, Serialize as SerializeTrait}; use serde_json::Value; use std::fmt::Debug; use std::hash::{Hash, Hasher}; pub trait BlockchainDbError: CustomSeDe + CustomHash + Debug { - fn as_common_methods(&self) -> &dyn CustomCommonMethods>; + fn as_common_methods(&self) -> &dyn CommonMethods>; } pub trait CustomSeDe { diff --git a/node/src/blockchain/errors/blockchain_loggable_error/app_rpc_web3_error.rs b/node/src/blockchain/errors/blockchain_loggable_error/app_rpc_web3_error.rs index 499153fd4..87c395209 100644 --- a/node/src/blockchain/errors/blockchain_loggable_error/app_rpc_web3_error.rs +++ b/node/src/blockchain/errors/blockchain_loggable_error/app_rpc_web3_error.rs @@ -1,8 +1,9 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. +use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; use crate::blockchain::errors::blockchain_db_error::BlockchainDbError; use crate::blockchain::errors::blockchain_loggable_error::BlockchainLoggableError; -use crate::blockchain::errors::custom_common_methods::CustomCommonMethods; +use crate::blockchain::errors::common_methods::CommonMethods; use std::fmt::{Display, Formatter}; use web3::error::Error as Web3Error; @@ -14,18 +15,22 @@ pub enum AppRpcWeb3Error { } impl BlockchainLoggableError for AppRpcWeb3Error { - fn as_common_methods(&self) -> &dyn CustomCommonMethods> { + fn as_common_methods(&self) -> &dyn CommonMethods> { self } fn downgrade(&self) -> Box { - todo!() + Box::new(AppRpcWeb3ErrorKind::from(self)) } } -impl CustomCommonMethods> for AppRpcWeb3Error { +impl CommonMethods> for AppRpcWeb3Error { fn partial_eq(&self, other: &Box) -> bool { - todo!() + other + .as_common_methods() + .as_any() + .downcast_ref::() + .map_or(false, |other| self == other) } fn dup(&self) -> Box { @@ -87,6 +92,7 @@ impl From for AppRpcWeb3Error { #[cfg(test)] mod tests { use super::*; + use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; use crate::blockchain::errors::test_utils::test_clone_impl_for_blockchain_error; use std::vec; @@ -143,6 +149,40 @@ mod tests { test_clone_impl_for_blockchain_error::(subject); } + #[test] + fn partial_eq_for_app_rpc_error_works() { + let subject: Box = + Box::new(AppRpcWeb3Error::Remote(RemoteError::Web3RpcError { + code: 222, + message: "Some message".to_string(), + })); + let other_1: Box = + Box::new(AppRpcWeb3Error::Remote(RemoteError::Unreachable)); + let other_2: Box = + Box::new(AppRpcWeb3Error::Remote(RemoteError::Web3RpcError { + code: 123, + message: "Some message".to_string(), + })); + let other_3: Box = + Box::new(AppRpcWeb3Error::Remote(RemoteError::Web3RpcError { + code: 222, + message: "Some other message".to_string(), + })); + let other_4: Box = + Box::new(AppRpcWeb3Error::Local(LocalError::Internal)); + let other_5: Box = + Box::new(AppRpcWeb3Error::Remote(RemoteError::Web3RpcError { + code: 222, + message: "Some message".to_string(), + })); + + assert_ne!(&subject, &other_1); + assert_ne!(&subject, &other_2); + assert_ne!(&subject, &other_3); + assert_ne!(&subject, &other_4); + assert_eq!(&subject, &other_5); + } + #[test] fn display_for_blockchain_error_object_works() { vec![ @@ -159,4 +199,25 @@ mod tests { assert_eq!(wrapped_as_trait_object.to_string(), format!("{:?}", error)); }) } + + #[test] + fn blockchain_loggable_error_can_be_converted_to_blockchain_db_error_for_app_rpc_web3_errors() { + let error_1: Box = Box::new(AppRpcWeb3Error::Local( + LocalError::Decoder("This is a decoder error".to_string()), + )); + let error_2: Box = + Box::new(AppRpcWeb3Error::Remote(RemoteError::Unreachable)); + + let result_1 = >::from(error_1); + let result_2 = >::from(error_2); + + assert_eq!( + &result_1, + &(Box::new(AppRpcWeb3ErrorKind::Decoder) as Box) + ); + assert_eq!( + &result_2, + &(Box::new(AppRpcWeb3ErrorKind::ServerUnreachable) as Box) + ); + } } diff --git a/node/src/blockchain/errors/blockchain_loggable_error/masq_error.rs b/node/src/blockchain/errors/blockchain_loggable_error/masq_error.rs index 11c368841..1995a4f68 100644 --- a/node/src/blockchain/errors/blockchain_loggable_error/masq_error.rs +++ b/node/src/blockchain/errors/blockchain_loggable_error/masq_error.rs @@ -1,28 +1,34 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. +use crate::blockchain::errors::blockchain_db_error::masq_error_kind::MASQErrorKind; use crate::blockchain::errors::blockchain_db_error::BlockchainDbError; use crate::blockchain::errors::blockchain_loggable_error::BlockchainLoggableError; -use crate::blockchain::errors::custom_common_methods::CustomCommonMethods; +use crate::blockchain::errors::common_methods::CommonMethods; use std::fmt::{Debug, Display, Formatter}; +use variant_count::VariantCount; -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, VariantCount)] pub enum MASQError { PendingTooLongNotReplaced, } impl BlockchainLoggableError for MASQError { - fn as_common_methods(&self) -> &dyn CustomCommonMethods> { + fn as_common_methods(&self) -> &dyn CommonMethods> { self } fn downgrade(&self) -> Box { - todo!() + Box::new(MASQErrorKind::from(self)) } } -impl CustomCommonMethods> for MASQError { +impl CommonMethods> for MASQError { fn partial_eq(&self, other: &Box) -> bool { - todo!() + other + .as_common_methods() + .as_any() + .downcast_ref::() + .map_or(false, |other| self == other) } fn dup(&self) -> Box { @@ -46,17 +52,31 @@ mod tests { #[test] fn clone_works_for_blockchain_error_wrapping_masq_error() { - let subject: Box = Box::new(MASQError::PendingTooLongNotReplaced); + let subject: Box = + Box::new(MASQError::PendingTooLongNotReplaced); test_clone_impl_for_blockchain_error::(subject); } + #[test] + fn partial_eq_for_masq_error_works() { + let subject: Box = + Box::new(MASQError::PendingTooLongNotReplaced); + let other: Box = + Box::new(MASQError::PendingTooLongNotReplaced); + + assert_eq!(&subject, &other); + // Expand this test as there are more variants of MASQError. + assert_eq!(MASQError::VARIANT_COUNT, 1); + } + #[test] fn display_for_blockchain_error_object_works() { vec![MASQError::PendingTooLongNotReplaced] .into_iter() .for_each(|error| { - let wrapped_as_trait_object: Box = Box::new(error.clone()); + let wrapped_as_trait_object: Box = + Box::new(error.clone()); assert_eq!(wrapped_as_trait_object.to_string(), format!("{:?}", error)); }) } diff --git a/node/src/blockchain/errors/blockchain_loggable_error/mod.rs b/node/src/blockchain/errors/blockchain_loggable_error/mod.rs index 21d740e21..1b58657db 100644 --- a/node/src/blockchain/errors/blockchain_loggable_error/mod.rs +++ b/node/src/blockchain/errors/blockchain_loggable_error/mod.rs @@ -1,7 +1,7 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::blockchain::errors::blockchain_db_error::BlockchainDbError; -use crate::blockchain::errors::custom_common_methods::CustomCommonMethods; +use crate::blockchain::errors::common_methods::CommonMethods; use std::fmt::{Debug, Display}; pub mod app_rpc_web3_error; @@ -9,10 +9,16 @@ pub mod masq_error; // The Display impl is meant to be used for logging purposes. pub trait BlockchainLoggableError: Display + Debug { - fn as_common_methods(&self) -> &dyn CustomCommonMethods>; + fn as_common_methods(&self) -> &dyn CommonMethods>; fn downgrade(&self) -> Box; } +impl From> for Box { + fn from(more_verbose_error: Box) -> Self { + more_verbose_error.downgrade() + } +} + impl Clone for Box { fn clone(&self) -> Self { self.as_common_methods().dup() @@ -21,7 +27,7 @@ impl Clone for Box { impl PartialEq for Box { fn eq(&self, other: &Self) -> bool { - todo!() + self.as_common_methods().partial_eq(other) } } diff --git a/node/src/blockchain/errors/custom_common_methods.rs b/node/src/blockchain/errors/common_methods.rs similarity index 83% rename from node/src/blockchain/errors/custom_common_methods.rs rename to node/src/blockchain/errors/common_methods.rs index ed6049517..d74b2d4b7 100644 --- a/node/src/blockchain/errors/custom_common_methods.rs +++ b/node/src/blockchain/errors/common_methods.rs @@ -1,6 +1,6 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -pub trait CustomCommonMethods { +pub trait CommonMethods { fn partial_eq(&self, other: &Other) -> bool; fn dup(&self) -> Other; as_any_ref_in_trait!(); diff --git a/node/src/blockchain/errors/mod.rs b/node/src/blockchain/errors/mod.rs index 1746f295a..91fdd107c 100644 --- a/node/src/blockchain/errors/mod.rs +++ b/node/src/blockchain/errors/mod.rs @@ -2,6 +2,6 @@ pub mod blockchain_db_error; pub mod blockchain_loggable_error; -mod custom_common_methods; +mod common_methods; mod test_utils; pub mod validation_status; diff --git a/node/src/blockchain/errors/test_utils.rs b/node/src/blockchain/errors/test_utils.rs index 0dd4173b1..9d72e24e9 100644 --- a/node/src/blockchain/errors/test_utils.rs +++ b/node/src/blockchain/errors/test_utils.rs @@ -2,7 +2,7 @@ use crate::blockchain::errors::blockchain_db_error::{BlockchainDbError, CustomHash, CustomSeDe}; use crate::blockchain::errors::blockchain_loggable_error::BlockchainLoggableError; -use crate::blockchain::errors::custom_common_methods::CustomCommonMethods; +use crate::blockchain::errors::common_methods::CommonMethods; use serde::de::{Error, Unexpected}; use serde_json::Value; use std::fmt::Debug; @@ -32,13 +32,16 @@ macro_rules! test_clone_impl { } test_clone_impl!(test_clone_impl_for_blockchain_db_error, BlockchainDbError); -test_clone_impl!(test_clone_impl_for_blockchain_error, BlockchainLoggableError); +test_clone_impl!( + test_clone_impl_for_blockchain_error, + BlockchainLoggableError +); #[derive(Debug, Default)] pub struct BlockchainDbErrorMock {} impl BlockchainDbError for BlockchainDbErrorMock { - fn as_common_methods(&self) -> &dyn CustomCommonMethods> { + fn as_common_methods(&self) -> &dyn CommonMethods> { unimplemented!("not needed for testing") } } @@ -67,7 +70,7 @@ impl CustomHash for BlockchainDbErrorMock { } } -impl CustomCommonMethods> for BlockchainDbErrorMock { +impl CommonMethods> for BlockchainDbErrorMock { fn partial_eq(&self, _other: &Box) -> bool { unimplemented!("not needed for testing") } From b4d3a3651ac7ef4bc743b6d3c6ae5df3a2095ae5 Mon Sep 17 00:00:00 2001 From: Bert Date: Mon, 18 Aug 2025 17:23:17 +0200 Subject: [PATCH 42/61] GH-642: finished --- .../db_access_objects/failed_payable_dao.rs | 9 +- .../db_access_objects/payable_dao.rs | 13 +-- ...able_and_failed_payable_data_conversion.rs | 15 ++- .../db_access_objects/sent_payable_dao.rs | 17 ++-- .../db_access_objects/test_utils.rs | 2 +- node/src/accountant/mod.rs | 19 +--- node/src/accountant/scanners/mod.rs | 55 ++++------- .../scanners/pending_payable_scanner/mod.rs | 29 +++--- .../pending_payable_scanner/test_utils.rs | 2 +- .../tx_receipt_interpreter.rs | 51 +++++----- .../scanners/pending_payable_scanner/utils.rs | 98 ++++++++++++------- .../src/accountant/scanners/scanners_utils.rs | 5 +- node/src/accountant/scanners/test_utils.rs | 2 +- node/src/accountant/test_utils.rs | 14 ++- node/src/blockchain/blockchain_bridge.rs | 14 ++- .../lower_level_interface_web3.rs | 6 -- .../blockchain_interface_web3/mod.rs | 49 +++++++--- .../blockchain_interface_web3/utils.rs | 15 ++- .../data_structures/errors.rs | 2 +- .../data_structures/mod.rs | 8 +- node/src/database/db_initializer.rs | 3 +- node/src/test_utils/mod.rs | 1 - 22 files changed, 216 insertions(+), 213 deletions(-) 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 c05aa5881..9ad004e04 100644 --- a/node/src/accountant/db_access_objects/failed_payable_dao.rs +++ b/node/src/accountant/db_access_objects/failed_payable_dao.rs @@ -1,5 +1,4 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::db_access_objects::failed_payable_dao::FailureStatus::RecheckRequired; use crate::accountant::db_access_objects::utils::{ DaoFactoryReal, TxHash, TxIdentifiers, TxRecordWithHash, VigilantRusqliteFlatten, }; @@ -398,9 +397,12 @@ mod tests { }; use crate::accountant::db_access_objects::utils::{current_unix_timestamp, TxRecordWithHash}; use crate::accountant::scanners::pending_payable_scanner::test_utils::ValidationFailureClockMock; - use crate::accountant::test_utils::{make_failed_tx, make_sent_tx}; + use crate::accountant::test_utils::make_failed_tx; + use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; + use crate::blockchain::errors::validation_status::{ + PreviousAttempts, ValidationFailureClockReal, ValidationStatus, + }; use crate::blockchain::test_utils::make_tx_hash; - use crate::blockchain::errors::validation_status::{PreviousAttempts, ValidationFailureClockReal, ValidationStatus}; use crate::database::db_initializer::{ DbInitializationConfig, DbInitializer, DbInitializerReal, }; @@ -411,7 +413,6 @@ mod tests { use std::ops::Add; use std::str::FromStr; use std::time::{Duration, SystemTime, UNIX_EPOCH}; - use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; #[test] fn insert_new_records_works() { diff --git a/node/src/accountant/db_access_objects/payable_dao.rs b/node/src/accountant/db_access_objects/payable_dao.rs index 2edb61414..61d76849f 100644 --- a/node/src/accountant/db_access_objects/payable_dao.rs +++ b/node/src/accountant/db_access_objects/payable_dao.rs @@ -6,9 +6,7 @@ use crate::accountant::db_access_objects::utils::{ sum_i128_values_from_table, to_unix_timestamp, AssemblerFeeder, CustomQuery, DaoFactoryReal, RangeStmConfig, RowId, TopStmConfig, TxHash, VigilantRusqliteFlatten, }; -use crate::accountant::db_big_integer::big_int_db_processor::KeyVariants::{ - PendingPayableRowid, WalletAddress, -}; +use crate::accountant::db_big_integer::big_int_db_processor::KeyVariants::WalletAddress; use crate::accountant::db_big_integer::big_int_db_processor::{ BigIntDbProcessor, BigIntDbProcessorReal, BigIntSqlConfig, DisplayableRusqliteParamPair, ParamByUse, SQLParamsBuilder, TableNameDAO, WeiChange, WeiChangeDirection, @@ -84,12 +82,6 @@ pub struct MarkPendingPayableID { pub rowid: RowId, } -impl MarkPendingPayableID { - pub fn new(wallet: Address, rowid: RowId) -> Self { - todo!() - } -} - #[derive(Debug)] pub struct PayableDaoReal { conn: Box, @@ -921,7 +913,6 @@ mod tests { struct TestInputs { hash: TxHash, - rowid: u64, previous_timestamp: SystemTime, new_payable_timestamp: SystemTime, wallet: Address, @@ -938,7 +929,6 @@ mod tests { let (account_1, account_2) = [ TestInputs { hash: make_tx_hash(12345), - rowid: 789, previous_timestamp: now.checked_sub(Duration::from_secs(45_000)).unwrap(), new_payable_timestamp: now.checked_sub(Duration::from_secs(2)).unwrap(), wallet: make_wallet("bobbles").address(), @@ -947,7 +937,6 @@ mod tests { }, TestInputs { hash: make_tx_hash(54321), - rowid: 792, previous_timestamp: now.checked_sub(Duration::from_secs(22_000)).unwrap(), new_payable_timestamp: now.checked_sub(Duration::from_secs(2)).unwrap(), wallet: make_wallet("yet more bobbles").address(), diff --git a/node/src/accountant/db_access_objects/sent_payable_and_failed_payable_data_conversion.rs b/node/src/accountant/db_access_objects/sent_payable_and_failed_payable_data_conversion.rs index 4602b80d7..09e898a0a 100644 --- a/node/src/accountant/db_access_objects/sent_payable_and_failed_payable_data_conversion.rs +++ b/node/src/accountant/db_access_objects/sent_payable_and_failed_payable_data_conversion.rs @@ -48,12 +48,11 @@ mod tests { use crate::accountant::db_access_objects::utils::to_unix_timestamp; use crate::accountant::gwei_to_wei; use crate::accountant::test_utils::make_transaction_block; + use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; + use crate::blockchain::errors::validation_status::{PreviousAttempts, ValidationStatus}; use crate::blockchain::test_utils::{make_tx_hash, ValidationFailureClockMock}; use crate::test_utils::make_wallet; use std::time::{Duration, SystemTime}; - use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; - use crate::blockchain::errors::blockchain_loggable_error::app_rpc_web3_error::{AppRpcWeb3Error, LocalError}; - use crate::blockchain::errors::validation_status::{PreviousAttempts, ValidationFailureClockReal, ValidationStatus}; #[test] fn sent_tx_record_can_be_converted_from_failed_tx_record() { @@ -111,14 +110,20 @@ mod tests { let result_1 = FailedTx::from((sent_tx.clone(), FailureReason::Reverted)); let result_2 = FailedTx::from(( sent_tx.clone(), - FailureReason::Submission(PreviousAttempts::new(Box::new(AppRpcWeb3ErrorKind::Internal), &ValidationFailureClockMock::default().now_result(timestamp))), + FailureReason::Submission(PreviousAttempts::new( + Box::new(AppRpcWeb3ErrorKind::Internal), + &ValidationFailureClockMock::default().now_result(timestamp), + )), )); assert_conversion_into_failed_tx(result_1, sent_tx.clone(), FailureReason::Reverted); assert_conversion_into_failed_tx( result_2, sent_tx, - FailureReason::Submission(PreviousAttempts::new(Box::new(AppRpcWeb3ErrorKind::Internal), &ValidationFailureClockMock::default().now_result(timestamp))), + FailureReason::Submission(PreviousAttempts::new( + Box::new(AppRpcWeb3ErrorKind::Internal), + &ValidationFailureClockMock::default().now_result(timestamp), + )), ); } 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 e3bdac96c..f550ee06b 100644 --- a/node/src/accountant/db_access_objects/sent_payable_dao.rs +++ b/node/src/accountant/db_access_objects/sent_payable_dao.rs @@ -1,23 +1,21 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::db_access_objects::failed_payable_dao::{FailedPayableDaoError, FailedTx}; use crate::accountant::db_access_objects::utils::{ DaoFactoryReal, TxHash, TxIdentifiers, TxRecordWithHash, }; use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; use crate::accountant::{checked_conversion, comma_joined_stringifiable}; use crate::blockchain::blockchain_interface::data_structures::TxBlock; +use crate::blockchain::errors::validation_status::ValidationStatus; use crate::database::rusqlite_wrappers::ConnectionWrapper; use ethereum_types::H256; use itertools::Itertools; use masq_lib::utils::ExpectValue; use serde_derive::{Deserialize, Serialize}; -use std::cmp::Ordering; use std::collections::{HashMap, HashSet}; use std::fmt::{Display, Formatter}; use std::str::FromStr; use web3::types::Address; -use crate::blockchain::errors::validation_status::ValidationStatus; #[derive(Debug, PartialEq, Eq)] pub enum SentPayableDaoError { @@ -503,6 +501,10 @@ mod tests { use crate::accountant::scanners::pending_payable_scanner::test_utils::ValidationFailureClockMock; use crate::accountant::test_utils::make_sent_tx; use crate::blockchain::blockchain_interface::data_structures::TxBlock; + use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; + use crate::blockchain::errors::validation_status::{ + PreviousAttempts, ValidationFailureClockReal, ValidationStatus, + }; use crate::blockchain::test_utils::{make_block_hash, make_tx_hash}; use crate::database::db_initializer::{ DbInitializationConfig, DbInitializer, DbInitializerReal, @@ -510,11 +512,8 @@ mod tests { use crate::database::test_utils::ConnectionWrapperMock; use ethereum_types::{H256, U64}; use masq_lib::test_utils::utils::ensure_node_home_directory_exists; - use rusqlite::{Connection}; - use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; - use crate::blockchain::errors::validation_status::{PreviousAttempts, ValidationFailureClockReal, ValidationStatus}; + use rusqlite::Connection; use std::collections::{HashMap, HashSet}; - use std::fmt::format; use std::ops::{Add, Sub}; use std::str::FromStr; use std::sync::{Arc, Mutex}; @@ -527,7 +526,6 @@ mod tests { let wrapped_conn = DbInitializerReal::default() .initialize(&home_dir, DbInitializationConfig::test_default()) .unwrap(); - let validation_failure_clock = ValidationFailureClockReal::default(); let tx1 = TxBuilder::default().hash(make_tx_hash(1)).build(); let tx2 = TxBuilder::default() .hash(make_tx_hash(2)) @@ -1404,13 +1402,12 @@ mod tests { fn tx_status_from_str_works() { let validation_failure_clock = ValidationFailureClockMock::default() .now_result(UNIX_EPOCH.add(Duration::from_secs(12456))); + assert_eq!( TxStatus::from_str(r#"{"Pending":"Waiting"}"#).unwrap(), TxStatus::Pending(ValidationStatus::Waiting) ); - let validation_failure_clock = ValidationFailureClockMock::default() - .now_result(UNIX_EPOCH.add(Duration::from_secs(12456))); assert_eq!( TxStatus::from_str(r#"{"Pending":{"Reattempting":{"InvalidResponse":{"firstSeen":{"secs_since_epoch":12456,"nanos_since_epoch":0},"attempts":1}}}}"#).unwrap(), TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new(Box::new(AppRpcWeb3ErrorKind::InvalidResponse), &validation_failure_clock))) diff --git a/node/src/accountant/db_access_objects/test_utils.rs b/node/src/accountant/db_access_objects/test_utils.rs index f9f8433bf..e395aa2de 100644 --- a/node/src/accountant/db_access_objects/test_utils.rs +++ b/node/src/accountant/db_access_objects/test_utils.rs @@ -6,6 +6,7 @@ use crate::accountant::db_access_objects::failed_payable_dao::{ }; use crate::accountant::db_access_objects::sent_payable_dao::{SentTx, TxStatus}; use crate::accountant::db_access_objects::utils::{current_unix_timestamp, TxHash}; +use crate::blockchain::errors::validation_status::ValidationStatus; use crate::database::db_initializer::{ DbInitializationConfig, DbInitializer, DbInitializerReal, DATABASE_FILE, }; @@ -13,7 +14,6 @@ use crate::database::rusqlite_wrappers::ConnectionWrapperReal; use rusqlite::{Connection, OpenFlags}; use std::path::PathBuf; use web3::types::Address; -use crate::blockchain::errors::validation_status::ValidationStatus; #[derive(Default)] pub struct TxBuilder { diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index d82e2d337..0c3185cb3 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -36,7 +36,6 @@ use crate::accountant::scanners::{Scanners, StartScanError}; use crate::blockchain::blockchain_bridge::{ BlockMarker, RegisterNewPendingPayables, RetrieveTransactions, }; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::HashAndAmount; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; use crate::blockchain::blockchain_interface::data_structures::{ BlockchainTransaction, ProcessedPayableFallible, TxReceiptResult, @@ -1177,14 +1176,6 @@ impl PendingPayableId { pub fn new(rowid: u64, hash: TxHash) -> Self { Self { rowid, hash } } - - fn rowids(ids: &[Self]) -> Vec { - ids.iter().map(|id| id.rowid).collect() - } - - fn serialize_hashes_to_string(ids: &[Self]) -> String { - comma_joined_stringifiable(ids, |id| format!("{:?}", id.hash)) - } } pub fn comma_joined_stringifiable(collection: &[T], stringify: F) -> String @@ -1231,7 +1222,7 @@ mod tests { }; use crate::accountant::db_access_objects::receivable_dao::ReceivableAccount; use crate::accountant::db_access_objects::sent_payable_dao::{ - Detection, RetrieveCondition, SentPayableDaoError, SentTx, TxStatus, + Detection, SentPayableDaoError, TxStatus, }; use crate::accountant::db_access_objects::utils::{ from_unix_timestamp, to_unix_timestamp, CustomQuery, @@ -1267,11 +1258,11 @@ mod tests { }; use crate::accountant::test_utils::{AccountantBuilder, BannedDaoMock}; use crate::accountant::Accountant; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::HashAndAmount; use crate::blockchain::blockchain_interface::data_structures::{ BlockchainTxFailure, RetrievedTxStatus, StatusReadFromReceiptCheck, TxBlock, TxReceiptResult, }; + use crate::blockchain::errors::validation_status::ValidationStatus; use crate::blockchain::test_utils::make_tx_hash; use crate::database::rusqlite_wrappers::TransactionSafeWrapper; use crate::database::test_utils::transaction_wrapper_mock::TransactionInnerWrapperMockBuilder; @@ -1324,7 +1315,6 @@ mod tests { use std::sync::Mutex; use std::time::{Duration, UNIX_EPOCH}; use std::vec; - use crate::blockchain::errors::validation_status::ValidationStatus; impl Handler> for Accountant { type Result = (); @@ -3564,7 +3554,6 @@ mod tests { block_number: 4444444444u64.into(), }), ); - let requested_tx = make_tx_hash(234); let counter_msg_3 = TxReceiptsMessage { results: vec![TxReceiptResult(Ok(tx_with_status))], response_skeleton_opt: None, @@ -5078,7 +5067,7 @@ mod tests { subject.scan_schedulers.payable.new_payable_notify = Box::new(NotifyHandleMock::default().notify_params(&new_payable_notify_arc)); let subject_addr = subject.start(); - let (msg, two_sent_txs) = make_tx_receipts_msg(vec![ + let (msg, _) = make_tx_receipts_msg(vec![ SeedsToMakeUpPayableWithStatus { tx_hash: TxHashByTable::SentPayable(make_tx_hash(123)), status: StatusReadFromReceiptCheck::Succeeded(TxBlock { @@ -5174,7 +5163,7 @@ mod tests { let tx_block_1 = make_transaction_block(4567); let tx_block_2 = make_transaction_block(1234); let subject_addr = subject.start(); - let (msg, two_sent_txs) = make_tx_receipts_msg(vec![ + let (msg, _) = make_tx_receipts_msg(vec![ SeedsToMakeUpPayableWithStatus { tx_hash: TxHashByTable::SentPayable(make_tx_hash(123)), status: StatusReadFromReceiptCheck::Succeeded(tx_block_1), diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index cfcfcd669..750405569 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -7,19 +7,19 @@ pub mod scan_schedulers; pub mod scanners_utils; pub mod test_utils; -use crate::accountant::db_access_objects::payable_dao::{MarkPendingPayableID, PayableAccount, PayableDao, PayableDaoError}; +use crate::accountant::db_access_objects::payable_dao::{PayableAccount, PayableDao}; use crate::accountant::payment_adjuster::{PaymentAdjuster, PaymentAdjusterReal}; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableTransactingErrorEnum::{ LocallyCausedError, RemotelyCausedErrors, }; -use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{debugging_summary_after_error_separation, err_msg_for_failure_with_expected_but_missing_sent_tx_record, investigate_debt_extremes, payables_debug_summary, separate_errors, separate_rowids_and_hashes, OperationOutcome, PayableScanResult, PayableThresholdsGauge, PayableThresholdsGaugeReal, PayableTransactingErrorEnum, PendingPayableMissingInDb}; -use crate::accountant::{PendingPayable, PendingPayableId, ScanError, ScanForPendingPayables, ScanForRetryPayables}; +use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{debugging_summary_after_error_separation, err_msg_for_failure_with_expected_but_missing_sent_tx_record, investigate_debt_extremes, payables_debug_summary, separate_errors, OperationOutcome, PayableScanResult, PayableThresholdsGauge, PayableThresholdsGaugeReal, PayableTransactingErrorEnum, PendingPayableMissingInDb}; +use crate::accountant::{PendingPayable, ScanError, ScanForPendingPayables, ScanForRetryPayables}; use crate::accountant::{ comma_joined_stringifiable, gwei_to_wei, ReceivedPayments, TxReceiptsMessage, RequestTransactionReceipts, ResponseSkeleton, ScanForNewPayables, ScanForReceivables, SentPayables, }; -use crate::blockchain::blockchain_bridge::{BlockMarker, RetrieveTransactions}; +use crate::blockchain::blockchain_bridge::{RetrieveTransactions}; use crate::sub_lib::accountant::{ DaoFactories, FinancialStatistics, PaymentThresholds, }; @@ -37,15 +37,11 @@ use std::collections::{HashMap, HashSet}; use std::fmt::Debug; use std::rc::Rc; use std::time::{SystemTime}; -use bytes::Buf; -use thousands::Separable; use time::format_description::parse; use time::OffsetDateTime; use variant_count::VariantCount; -use web3::types::H256; -use crate::accountant::db_access_objects::failed_payable_dao::{FailedPayableDao, FailedTx, FailureRetrieveCondition, FailureStatus}; -use crate::accountant::db_access_objects::sent_payable_dao::{RetrieveCondition, SentPayableDao, SentPayableDaoError, SentTx, TxStatus}; -use crate::accountant::db_access_objects::utils::{TxHash, TxIdentifiers}; +use crate::accountant::db_access_objects::sent_payable_dao::{SentPayableDao}; +use crate::accountant::db_access_objects::utils::{TxHash}; use crate::accountant::scanners::payable_scanner_extension::{MultistageDualPayableScanner, PreparedAdjustment, SolvencySensitivePaymentInstructor}; use crate::accountant::scanners::payable_scanner_extension::msgs::{BlockchainAgentWithContextMessage, QualifiedPayablesMessage, UnpricedQualifiedPayables}; use crate::accountant::scanners::pending_payable_scanner::PendingPayableScanner; @@ -747,7 +743,7 @@ impl PayableScanner { // TODO this has become dead (GH-662) #[allow(dead_code)] - fn mark_pending_payable(&self, sent_payments: &[&PendingPayable], logger: &Logger) { + fn mark_pending_payable(&self, _sent_payments: &[&PendingPayable], _logger: &Logger) { todo!("remove me when the time comes") // fn missing_fingerprints_msg(nonexistent: &[PendingPayableMissingInDb]) -> String { // format!( @@ -1007,23 +1003,21 @@ impl_real_scanner_marker!(PayableScanner, PendingPayableScanner, ReceivableScann #[cfg(test)] mod tests { use crate::accountant::db_access_objects::failed_payable_dao::{ - FailedTx, FailureReason, FailureRetrieveCondition, FailureStatus, + FailedTx, FailureReason, FailureStatus, }; use crate::accountant::db_access_objects::payable_dao::{PayableAccount, PayableDaoError}; use crate::accountant::db_access_objects::sent_payable_dao::{ Detection, SentPayableDaoError, SentTx, TxStatus, }; - use crate::accountant::db_access_objects::utils::{ - from_unix_timestamp, to_unix_timestamp, TxHash, - }; + use crate::accountant::db_access_objects::utils::{from_unix_timestamp, to_unix_timestamp}; use crate::accountant::scanners::payable_scanner_extension::msgs::{ QualifiedPayablesBeforeGasPriceSelection, QualifiedPayablesMessage, UnpricedQualifiedPayables, }; use crate::accountant::scanners::pending_payable_scanner::test_utils::ValidationFailureClockMock; use crate::accountant::scanners::pending_payable_scanner::utils::{ - CurrentPendingPayables, PendingPayableCache, PendingPayableScanResult, - RecheckRequiringFailures, Retry, TxHashByTable, + CurrentPendingPayables, PendingPayableScanResult, RecheckRequiringFailures, Retry, + TxHashByTable, }; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{ OperationOutcome, PayableScanResult, @@ -1046,7 +1040,7 @@ mod tests { }; use crate::accountant::{ gwei_to_wei, PendingPayable, ReceivedPayments, RequestTransactionReceipts, ScanError, - ScanForRetryPayables, SentPayables, TxReceiptsMessage, DEFAULT_PENDING_TOO_LONG_SEC, + ScanForRetryPayables, SentPayables, TxReceiptsMessage, }; use crate::blockchain::blockchain_bridge::{BlockMarker, RetrieveTransactions}; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; @@ -1054,43 +1048,37 @@ mod tests { BlockchainTransaction, BlockchainTxFailure, ProcessedPayableFallible, RetrievedTxStatus, RpcPayableFailure, StatusReadFromReceiptCheck, TxBlock, TxReceiptError, TxReceiptResult, }; + use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; + use crate::blockchain::errors::blockchain_loggable_error::app_rpc_web3_error::{ + AppRpcWeb3Error, RemoteError, + }; + use crate::blockchain::errors::validation_status::{PreviousAttempts, ValidationStatus}; use crate::blockchain::test_utils::{make_block_hash, make_tx_hash}; use crate::database::rusqlite_wrappers::TransactionSafeWrapper; use crate::database::test_utils::transaction_wrapper_mock::TransactionInnerWrapperMockBuilder; use crate::db_config::mocks::ConfigDaoMock; use crate::db_config::persistent_configuration::PersistentConfigError; - use crate::match_lazily_every_type_id; use crate::sub_lib::accountant::{ DaoFactories, FinancialStatistics, PaymentThresholds, DEFAULT_PAYMENT_THRESHOLDS, }; use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMock; - use crate::test_utils::recorder::make_recorder; use crate::test_utils::unshared_test_utils::arbitrary_id_stamp::ArbitraryIdStamp; - use crate::test_utils::unshared_test_utils::capture_numbers_with_separators_from_str; use crate::test_utils::{make_paying_wallet, make_wallet}; use actix::{Message, System}; use ethereum_types::U64; - use itertools::Itertools; use masq_lib::logger::Logger; use masq_lib::messages::ScanType; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; use masq_lib::ui_gateway::NodeToUiMessage; use regex::Regex; use rusqlite::{ffi, ErrorCode}; - use secp256k1secrets::ecdh::SharedSecret; use std::cell::RefCell; - use std::collections::{HashMap, HashSet}; - use std::fmt::format; - use std::ops::{Add, Sub}; + use std::ops::Sub; use std::panic::{catch_unwind, AssertUnwindSafe}; use std::rc::Rc; use std::sync::{Arc, Mutex}; use std::time::{Duration, SystemTime}; - use web3::types::{H256}; use web3::Error; - use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; - use crate::blockchain::errors::blockchain_loggable_error::app_rpc_web3_error::{AppRpcWeb3Error, RemoteError}; - use crate::blockchain::errors::validation_status::{PreviousAttempts, ValidationStatus}; impl Scanners { pub fn replace_scanner(&mut self, replacement: ScannerReplacement) { @@ -1213,7 +1201,7 @@ mod tests { .as_any() .downcast_ref::() .unwrap(); - let mut pending_payable_scanner = scanners + let pending_payable_scanner = scanners .pending_payable .as_any_mut() .downcast_mut::() @@ -2387,7 +2375,6 @@ mod tests { let sent_tx = make_sent_tx(456); let sent_tx_hash = sent_tx.hash; let failed_tx = make_failed_tx(789); - let failed_tx_hash = failed_tx.hash; let sent_payable_dao = SentPayableDaoMock::new().retrieve_txs_result(vec![sent_tx.clone()]); let failed_payable_dao = FailedPayableDaoMock::new().retrieve_txs_result(vec![failed_tx.clone()]); @@ -2681,8 +2668,8 @@ mod tests { AppRpcWeb3Error::Remote(RemoteError::InvalidResponse("game over".to_string())), ); let tx_hash_6 = make_tx_hash(2345); - let sent_tx_6 = make_sent_tx(789); - let tx_hash_6 = sent_tx_6.hash; + let mut sent_tx_6 = make_sent_tx(789); + sent_tx_6.hash = tx_hash_6; let transaction_with_status_6 = RetrievedTxStatus::new( TxHashByTable::SentPayable(sent_tx_6.hash), StatusReadFromReceiptCheck::Failed(BlockchainTxFailure::Unrecognized), diff --git a/node/src/accountant/scanners/pending_payable_scanner/mod.rs b/node/src/accountant/scanners/pending_payable_scanner/mod.rs index 55884a746..445b1b0d7 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/mod.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/mod.rs @@ -27,6 +27,9 @@ use crate::accountant::{ ScanForPendingPayables, TxReceiptsMessage, }; use crate::blockchain::blockchain_interface::data_structures::{TxBlock, TxReceiptResult}; +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; @@ -42,7 +45,6 @@ use std::str::FromStr; use std::time::SystemTime; use thousands::Separable; use web3::types::H256; -use crate::blockchain::errors::validation_status::{ValidationFailureClock, ValidationFailureClockReal}; pub struct PendingPayableScanner { pub common: ScannerCommon, @@ -816,6 +818,13 @@ mod tests { use crate::blockchain::blockchain_interface::data_structures::{ RetrievedTxStatus, StatusReadFromReceiptCheck, TxBlock, TxReceiptError, TxReceiptResult, }; + use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; + use crate::blockchain::errors::blockchain_loggable_error::app_rpc_web3_error::{ + AppRpcWeb3Error, LocalError, RemoteError, + }; + use crate::blockchain::errors::validation_status::{ + PreviousAttempts, ValidationFailureClockReal, ValidationStatus, + }; use crate::blockchain::test_utils::{make_block_hash, make_tx_hash}; use crate::test_utils::{make_paying_wallet, make_wallet}; use itertools::Itertools; @@ -823,13 +832,10 @@ mod tests { use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; use regex::Regex; use std::collections::HashMap; - use std::ops::{Add, Sub}; + use std::ops::Sub; use std::panic::{catch_unwind, AssertUnwindSafe}; use std::sync::{Arc, Mutex}; use std::time::{Duration, SystemTime}; - use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; - use crate::blockchain::errors::blockchain_loggable_error::app_rpc_web3_error::{AppRpcWeb3Error, LocalError, RemoteError}; - use crate::blockchain::errors::validation_status::{PreviousAttempts, ValidationFailureClockReal, ValidationStatus}; #[test] fn start_scan_fills_in_caches_and_returns_msg() { @@ -1253,9 +1259,9 @@ mod tests { )), FailedValidationByTable::SentPayable(FailedValidation::new( hash_3, - Box::new(AppRpcWeb3Error::Remote( - RemoteError::InvalidResponse("Booga".to_string()), - )), + Box::new(AppRpcWeb3Error::Remote(RemoteError::InvalidResponse( + "Booga".to_string(), + ))), TxStatus::Pending(ValidationStatus::Waiting), )), ], @@ -1332,9 +1338,9 @@ mod tests { )), FailedValidationByTable::SentPayable(FailedValidation::new( hash_2, - Box::new(AppRpcWeb3Error::Remote( - RemoteError::InvalidResponse("Booga".to_string()), - )), + Box::new(AppRpcWeb3Error::Remote(RemoteError::InvalidResponse( + "Booga".to_string(), + ))), TxStatus::Confirmed { block_hash: "abc".to_string(), block_number: 0, @@ -2001,7 +2007,6 @@ mod tests { )] fn handle_confirmed_transactions_panics_on_unchecking_payable_table() { let hash = make_tx_hash(0x315); - let rowid = 3; let payable_dao = PayableDaoMock::new().transactions_confirmed_result(Err( PayableDaoError::RusqliteError("record change not successful".to_string()), )); diff --git a/node/src/accountant/scanners/pending_payable_scanner/test_utils.rs b/node/src/accountant/scanners/pending_payable_scanner/test_utils.rs index 384efddc9..473fd28cb 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/test_utils.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/test_utils.rs @@ -1,8 +1,8 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. +use crate::blockchain::errors::validation_status::ValidationFailureClock; use std::cell::RefCell; use std::time::SystemTime; -use crate::blockchain::errors::validation_status::ValidationFailureClock; #[derive(Default)] pub struct ValidationFailureClockMock { 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 d4f5a1759..2e9737f09 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 @@ -6,17 +6,17 @@ use crate::accountant::db_access_objects::sent_payable_dao::{ }; use crate::accountant::db_access_objects::utils::from_unix_timestamp; use crate::accountant::scanners::pending_payable_scanner::utils::{ - FailedValidation, FailedValidationByTable, NormalTxConfirmation, - ReceiptScanReport, TxByTable, TxCaseToBeInterpreted, TxReclaim, + FailedValidation, FailedValidationByTable, NormalTxConfirmation, ReceiptScanReport, TxByTable, + TxCaseToBeInterpreted, TxReclaim, }; use crate::accountant::scanners::pending_payable_scanner::PendingPayableScanner; use crate::blockchain::blockchain_interface::data_structures::{ BlockchainTxFailure, StatusReadFromReceiptCheck, TxBlock, TxReceiptError, TxReceiptResult, }; +use crate::blockchain::errors::blockchain_loggable_error::masq_error::MASQError; use masq_lib::logger::Logger; use std::time::SystemTime; use thousands::Separable; -use crate::blockchain::errors::blockchain_loggable_error::masq_error::MASQError; #[derive(Default)] pub struct TxReceiptInterpreter {} @@ -85,19 +85,22 @@ impl TxReceiptInterpreter { TxByTable::FailedPayable(failed_tx) => { let replacement_tx = sent_payable_dao .retrieve_txs(Some(RetrieveCondition::ByNonce(vec![failed_tx.nonce]))); - let replacement_tx_hash = replacement_tx + let replacement_tx_hash = replacement_tx .get(0) - .unwrap_or_else(|| panic!( - "Attempted to display a replacement tx for {:?} but couldn't find \ + .unwrap_or_else(|| { + panic!( + "Attempted to display a replacement tx for {:?} but couldn't find \ one in the database", - failed_tx.hash - )) + failed_tx.hash + ) + }) .hash; warning!( logger, "Failed tx {:?} on a recheck was found pending on its receipt unexpectedly. \ It was supposed to be replaced by {:?}", - failed_tx.hash, replacement_tx_hash + failed_tx.hash, + replacement_tx_hash ); if failed_tx.reason != FailureReason::PendingTooLong { todo!("panic here") @@ -236,8 +239,8 @@ mod tests { use crate::accountant::scanners::pending_payable_scanner::tx_receipt_interpreter::TxReceiptInterpreter; use crate::accountant::scanners::pending_payable_scanner::utils::{ DetectedConfirmations, DetectedFailures, FailedValidation, FailedValidationByTable, - NormalTxConfirmation, PresortedTxFailure, ReceiptScanReport, - TxByTable, TxHashByTable, TxReclaim, + NormalTxConfirmation, PresortedTxFailure, ReceiptScanReport, TxByTable, TxHashByTable, + TxReclaim, }; use crate::accountant::test_utils::{ make_failed_tx, make_sent_tx, make_transaction_block, SentPayableDaoMock, @@ -245,16 +248,20 @@ mod tests { use crate::blockchain::blockchain_interface::data_structures::{ BlockchainTxFailure, TxReceiptError, }; + use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; + use crate::blockchain::errors::blockchain_loggable_error::app_rpc_web3_error::{ + AppRpcWeb3Error, LocalError, RemoteError, + }; + use crate::blockchain::errors::blockchain_loggable_error::masq_error::MASQError; + use crate::blockchain::errors::validation_status::{ + PreviousAttempts, ValidationFailureClockReal, ValidationStatus, + }; use crate::blockchain::test_utils::make_tx_hash; use crate::test_utils::unshared_test_utils::capture_numbers_with_separators_from_str; use masq_lib::logger::Logger; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; use std::sync::{Arc, Mutex}; use std::time::{Duration, SystemTime}; - use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; - use crate::blockchain::errors::blockchain_loggable_error::app_rpc_web3_error::{AppRpcWeb3Error, LocalError, RemoteError}; - use crate::blockchain::errors::blockchain_loggable_error::masq_error::MASQError; - use crate::blockchain::errors::validation_status::{PreviousAttempts, ValidationFailureClockReal, ValidationStatus}; #[test] fn interprets_receipt_for_pending_tx_if_it_is_a_success() { @@ -501,7 +508,6 @@ mod tests { failed_tx.hash = tx_hash; failed_tx.status = FailureStatus::RecheckRequired(ValidationStatus::Waiting); let scan_report = ReceiptScanReport::default(); - let before = SystemTime::now(); let result = TxReceiptInterpreter::handle_still_pending_tx( scan_report, @@ -510,7 +516,6 @@ mod tests { &Logger::new(test_name), ); - let after = SystemTime::now(); assert_eq!( result, ReceiptScanReport { @@ -609,11 +614,7 @@ mod tests { failures: DetectedFailures { tx_failures: vec![], tx_receipt_rpc_failures: vec![FailedValidationByTable::SentPayable( - FailedValidation::new( - tx_hash, - Box::new(rpc_error), - current_tx_status - ) + FailedValidation::new(tx_hash, Box::new(rpc_error), current_tx_status) ),] }, confirmations: DetectedConfirmations::default() @@ -675,11 +676,7 @@ mod tests { failures: DetectedFailures { tx_failures: vec![], tx_receipt_rpc_failures: vec![FailedValidationByTable::FailedPayable( - FailedValidation::new( - tx_hash, - Box::new(rpc_error), - current_failure_status - ) + FailedValidation::new(tx_hash, Box::new(rpc_error), current_failure_status) )] }, confirmations: DetectedConfirmations::default() diff --git a/node/src/accountant/scanners/pending_payable_scanner/utils.rs b/node/src/accountant/scanners/pending_payable_scanner/utils.rs index 44e0faf82..824e4600e 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/utils.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/utils.rs @@ -3,19 +3,18 @@ use crate::accountant::db_access_objects::failed_payable_dao::{ FailedTx, FailureReason, FailureStatus, }; -use crate::accountant::db_access_objects::sent_payable_dao::{SentPayableDao, SentTx, TxStatus}; +use crate::accountant::db_access_objects::sent_payable_dao::{SentTx, TxStatus}; use crate::accountant::db_access_objects::utils::TxHash; use crate::blockchain::blockchain_interface::data_structures::{ - BlockchainTxFailure, TxBlock, TxReceiptError, TxReceiptResult, + BlockchainTxFailure, TxReceiptError, TxReceiptResult, +}; +use crate::blockchain::errors::blockchain_loggable_error::BlockchainLoggableError; +use crate::blockchain::errors::validation_status::{ + PreviousAttempts, ValidationFailureClock, ValidationStatus, }; -use actix::Message; use masq_lib::logger::Logger; use masq_lib::ui_gateway::NodeToUiMessage; use std::collections::HashMap; -use std::fmt::Display; -use thousands::Separable; -use crate::blockchain::errors::blockchain_loggable_error::BlockchainLoggableError; -use crate::blockchain::errors::validation_status::{PreviousAttempts, ValidationFailureClock, ValidationStatus}; #[derive(Debug, Default, PartialEq, Eq, Clone)] pub struct ReceiptScanReport { @@ -123,7 +122,7 @@ impl From<(TxReceiptError, TxStatus)> for FailedValidationByTable { TxHashByTable::FailedPayable(tx_hash) => { unreachable!( - "Mismatch in the type of tx record (failed tx) and status type (TxStatus)" + "Mismatch in the type of tx record (failed tx) and status type (TxStatus) for {:?}", tx_hash ) } } @@ -140,7 +139,7 @@ impl From<(TxReceiptError, FailureStatus)> for FailedValidationByTable { )), TxHashByTable::SentPayable(tx_hash) => { unreachable!( - "Mismatch in the type of tx record (sent tx) and status type (FailureStatus)" + "Mismatch in the type of tx record (sent tx) and status type (FailureStatus) for {:?}",tx_hash ) } } @@ -155,9 +154,11 @@ pub struct FailedValidation { } // I was forced to implement this manually -impl PartialEq for FailedValidation { +impl PartialEq for FailedValidation { fn eq(&self, other: &Self) -> bool { - self.tx_hash == other.tx_hash && &self.validation_failure == &other.validation_failure && self.current_status == other.current_status + self.tx_hash == other.tx_hash + && &self.validation_failure == &other.validation_failure + && self.current_status == other.current_status } } @@ -369,28 +370,30 @@ impl From for FailureReason { #[cfg(test)] mod tests { - use std::any::{Any, TypeId}; use crate::accountant::db_access_objects::failed_payable_dao::FailureStatus; use crate::accountant::db_access_objects::sent_payable_dao::{Detection, TxStatus}; use crate::accountant::scanners::pending_payable_scanner::test_utils::ValidationFailureClockMock; use crate::accountant::scanners::pending_payable_scanner::utils::{ CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailedValidation, - FailedValidationByTable, NormalTxConfirmation, PendingPayableCache, - PresortedTxFailure, ReceiptScanReport, RecheckRequiringFailures, Retry, TxBlock, - TxHashByTable, TxReceiptError, TxReceiptResult, TxReclaim, + FailedValidationByTable, NormalTxConfirmation, PendingPayableCache, PresortedTxFailure, + ReceiptScanReport, RecheckRequiringFailures, Retry, TxHashByTable, TxReceiptError, + TxReclaim, + }; + use crate::accountant::test_utils::{make_failed_tx, make_sent_tx}; + use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; + use crate::blockchain::errors::blockchain_loggable_error::app_rpc_web3_error::{ + AppRpcWeb3Error, LocalError, RemoteError, + }; + use crate::blockchain::errors::validation_status::{ + PreviousAttempts, ValidationFailureClockReal, ValidationStatus, }; - use crate::accountant::test_utils::{make_failed_tx, make_sent_tx, make_transaction_block}; use crate::blockchain::test_utils::make_tx_hash; use masq_lib::logger::Logger; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; - use std::ops::{Add, Sub}; + use std::any::Any; + use std::ops::Sub; use std::time::{Duration, SystemTime}; use std::vec; - use crate::accountant::db_access_objects::utils::TxHash; - use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; - use crate::blockchain::errors::blockchain_loggable_error::app_rpc_web3_error::{AppRpcWeb3Error, LocalError, RemoteError}; - use crate::blockchain::errors::blockchain_loggable_error::BlockchainLoggableError; - use crate::blockchain::errors::validation_status::{PreviousAttempts, ValidationFailureClockReal, ValidationStatus}; #[test] fn detected_confirmations_is_empty_works() { @@ -431,9 +434,9 @@ mod tests { vec![FailedValidationByTable::FailedPayable( FailedValidation::new( make_tx_hash(12121), - Box::new(AppRpcWeb3Error::Remote( - RemoteError::InvalidResponse("blah".to_string()), - )), + Box::new(AppRpcWeb3Error::Remote(RemoteError::InvalidResponse( + "blah".to_string(), + ))), FailureStatus::RecheckRequired(ValidationStatus::Reattempting( PreviousAttempts::new( Box::new(AppRpcWeb3ErrorKind::Internal), @@ -742,7 +745,6 @@ mod tests { let test_name = "pending_payable_cache_ensure_empty_sad_path"; let mut subject = CurrentPendingPayables::new(); let sent_tx = make_sent_tx(567); - let tx_hash = sent_tx.hash; let tx_timestamp = sent_tx.timestamp; let records = vec![sent_tx.clone()]; let logger = Logger::new(test_name); @@ -887,7 +889,6 @@ mod tests { let test_name = "failure_cache_ensure_empty_sad_path"; let mut subject = RecheckRequiringFailures::new(); let failed_tx = make_failed_tx(567); - let tx_hash = failed_tx.hash; let tx_timestamp = failed_tx.timestamp; let records = vec![failed_tx.clone()]; let logger = Logger::new(test_name); @@ -983,8 +984,8 @@ mod tests { #[test] #[should_panic( - expected = "Mismatch in the type of tx record (failed tx) and status type \ - (TxStatus)" + expected = "Mismatch in the type of tx record (failed tx) and status type (TxStatus) for \ + 0x000000000000000000000000000000000000000000000000000000000000007b" )] fn tx_status_mismatch_in_conversion_to_failed_validation_by_table() { let tx_hash = make_tx_hash(123); @@ -1000,8 +1001,8 @@ mod tests { #[test] #[should_panic( - expected = "Mismatch in the type of tx record (sent tx) and status type \ - (FailureStatus)" + expected = "Mismatch in the type of tx record (sent tx) and status type (FailureStatus) for \ + 0x000000000000000000000000000000000000000000000000000000000000007b" )] fn tx_status_mismatch_in_conversion_to_failed_validation_by_table_2() { let tx_hash = make_tx_hash(123); @@ -1208,16 +1209,37 @@ mod tests { // } #[test] - fn partial_eq_is_implemented_for_failed_validation(){ + fn partial_eq_is_implemented_for_failed_validation() { let correct_hash = make_tx_hash(123); let correct_error = Box::new(AppRpcWeb3Error::Local(LocalError::Internal)); let correct_tx_status = TxStatus::Pending(ValidationStatus::Waiting); - let failed_validation_1 = FailedValidation::new(correct_hash, correct_error.clone(), correct_tx_status.clone()); - let failed_validation_2 = FailedValidation::new(make_tx_hash(345), correct_error.clone(), correct_tx_status.clone()); - let failed_validation_3 = FailedValidation::new(correct_hash, Box::new(AppRpcWeb3Error::Remote(RemoteError::Unreachable)), correct_tx_status.clone()); - let failed_validation_4 = FailedValidation::new(correct_hash, correct_error.clone(), FailureStatus::RecheckRequired(ValidationStatus::Waiting)); - let failed_validation_5 = FailedValidation::new(correct_hash, correct_error.clone(), correct_tx_status); - let failed_validation_6 = FailedValidation::new(correct_hash, correct_error, FailureStatus::RecheckRequired(ValidationStatus::Waiting)); + let failed_validation_1 = FailedValidation::new( + correct_hash, + correct_error.clone(), + correct_tx_status.clone(), + ); + let failed_validation_2 = FailedValidation::new( + make_tx_hash(345), + correct_error.clone(), + correct_tx_status.clone(), + ); + let failed_validation_3 = FailedValidation::new( + correct_hash, + Box::new(AppRpcWeb3Error::Remote(RemoteError::Unreachable)), + correct_tx_status.clone(), + ); + let failed_validation_4 = FailedValidation::new( + correct_hash, + correct_error.clone(), + FailureStatus::RecheckRequired(ValidationStatus::Waiting), + ); + let failed_validation_5 = + FailedValidation::new(correct_hash, correct_error.clone(), correct_tx_status); + let failed_validation_6 = FailedValidation::new( + correct_hash, + correct_error, + FailureStatus::RecheckRequired(ValidationStatus::Waiting), + ); assert_ne!(failed_validation_1, failed_validation_2); assert_ne!(failed_validation_1, failed_validation_3); diff --git a/node/src/accountant/scanners/scanners_utils.rs b/node/src/accountant/scanners/scanners_utils.rs index 51c1d0db8..1188047ab 100644 --- a/node/src/accountant/scanners/scanners_utils.rs +++ b/node/src/accountant/scanners/scanners_utils.rs @@ -2,13 +2,12 @@ pub mod payable_scanner_utils { use crate::accountant::db_access_objects::utils::{ThresholdUtils, TxHash}; - use crate::accountant::db_access_objects::payable_dao::{PayableAccount, PayableDaoError}; + use crate::accountant::db_access_objects::payable_dao::{PayableAccount}; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableTransactingErrorEnum::{ LocallyCausedError, RemotelyCausedErrors, }; - use crate::accountant::{comma_joined_stringifiable, PendingPayable, SentPayables}; + use crate::accountant::{PendingPayable, SentPayables}; use crate::sub_lib::accountant::PaymentThresholds; - use crate::sub_lib::wallet::Wallet; use itertools::Itertools; use masq_lib::logger::Logger; use std::cmp::Ordering; diff --git a/node/src/accountant/scanners/test_utils.rs b/node/src/accountant/scanners/test_utils.rs index a434c9880..dbdef54bb 100644 --- a/node/src/accountant/scanners/test_utils.rs +++ b/node/src/accountant/scanners/test_utils.rs @@ -523,7 +523,7 @@ impl PendingPayableCache for PendingPayableCacheMock { self.get_record_by_hash_results.borrow_mut().remove(0) } - fn ensure_empty_cache(&mut self, logger: &Logger) { + fn ensure_empty_cache(&mut self, _logger: &Logger) { self.ensure_empty_cache_params.lock().unwrap().push(()); } diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs index 35a70f1d5..f9311ce5f 100644 --- a/node/src/accountant/test_utils.rs +++ b/node/src/accountant/test_utils.rs @@ -29,16 +29,15 @@ use crate::accountant::scanners::payable_scanner_extension::msgs::{ }; use crate::accountant::scanners::payable_scanner_extension::PreparedAdjustment; 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::utils::PendingPayableCache; use crate::accountant::scanners::pending_payable_scanner::PendingPayableScanner; use crate::accountant::scanners::receivable_scanner::ReceivableScanner; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableThresholdsGauge; use crate::accountant::scanners::test_utils::PendingPayableCacheMock; use crate::accountant::scanners::PayableScanner; -use crate::accountant::{gwei_to_wei, Accountant, DEFAULT_PENDING_TOO_LONG_SEC}; +use crate::accountant::{gwei_to_wei, Accountant}; use crate::blockchain::blockchain_interface::data_structures::{BlockchainTransaction, TxBlock}; +use crate::blockchain::errors::validation_status::{ValidationFailureClock, ValidationStatus}; use crate::blockchain::test_utils::{make_block_hash, make_tx_hash}; use crate::bootstrapper::BootstrapperConfig; use crate::database::rusqlite_wrappers::TransactionSafeWrapper; @@ -61,9 +60,8 @@ use std::fmt::Debug; use std::path::Path; use std::rc::Rc; use std::sync::{Arc, Mutex}; -use std::time::{SystemTime, UNIX_EPOCH}; +use std::time::SystemTime; use web3::types::Address; -use crate::blockchain::errors::validation_status::{ValidationFailureClock, ValidationStatus}; pub fn make_receivable_account(n: u64, expected_delinquent: bool) -> ReceivableAccount { let now = to_unix_timestamp(SystemTime::now()); @@ -655,7 +653,7 @@ impl PayableDao for PayableDaoMock { fn mark_pending_payables_rowids( &self, - mark_instructions: &[MarkPendingPayableID], + _mark_instructions: &[MarkPendingPayableID], ) -> Result<(), PayableDaoError> { todo!() // self.mark_pending_payables_rowids_params @@ -1433,7 +1431,7 @@ impl PendingPayableScannerBuilder { self } - pub fn build(mut self) -> PendingPayableScanner { + pub fn build(self) -> PendingPayableScanner { let mut scanner = PendingPayableScanner::new( Box::new(self.payable_dao), Box::new(self.sent_payable_dao), diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index e1b1ec096..c1d572f09 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -1,6 +1,5 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::db_access_objects::payable_dao::PayableAccount; use crate::accountant::db_access_objects::sent_payable_dao::SentTx; use crate::accountant::scanners::payable_scanner_extension::msgs::{ BlockchainAgentWithContextMessage, PricedQualifiedPayables, QualifiedPayablesMessage, @@ -11,7 +10,6 @@ use crate::accountant::{ use crate::accountant::{RequestTransactionReceipts, TxReceiptsMessage}; use crate::actor_system_factory::SubsFactory; use crate::blockchain::blockchain_agent::BlockchainAgent; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::HashAndAmount; use crate::blockchain::blockchain_interface::data_structures::errors::{ BlockchainInterfaceError, PayableTransactionError, }; @@ -34,9 +32,7 @@ use actix::Context; use actix::Handler; use actix::Message; use actix::{Addr, Recipient}; -use ethabi::Hash; use futures::Future; -use itertools::Itertools; use masq_lib::blockchains::chains::Chain; use masq_lib::constants::DEFAULT_GAS_PRICE_MARGIN; use masq_lib::logger::Logger; @@ -541,8 +537,8 @@ mod tests { }; use crate::accountant::scanners::payable_scanner_extension::test_utils::BlockchainAgentMock; use crate::accountant::scanners::pending_payable_scanner::utils::TxHashByTable; + use crate::accountant::test_utils::make_payable_account; use crate::accountant::test_utils::make_priced_qualified_payables; - use crate::accountant::test_utils::{make_payable_account, make_sent_tx}; use crate::accountant::PendingPayable; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError::TransactionID; use crate::blockchain::blockchain_interface::data_structures::errors::{ @@ -553,6 +549,10 @@ mod tests { BlockchainTransaction, RetrievedBlockchainTransactions, RetrievedTxStatus, TxBlock, TxReceiptError, }; + use crate::blockchain::errors::blockchain_loggable_error::app_rpc_web3_error::{ + AppRpcWeb3Error, RemoteError, + }; + use crate::blockchain::errors::validation_status::ValidationStatus; use crate::blockchain::test_utils::{ make_blockchain_interface_web3, make_tx_hash, ReceiptResponseBuilder, }; @@ -587,8 +587,6 @@ mod tests { use std::sync::{Arc, Mutex}; use std::time::{Duration, SystemTime}; use web3::types::{TransactionReceipt, H160}; - use crate::blockchain::errors::blockchain_loggable_error::app_rpc_web3_error::{AppRpcWeb3Error, RemoteError}; - use crate::blockchain::errors::validation_status::ValidationStatus; impl Handler> for BlockchainBridge { type Result = (); @@ -1361,7 +1359,7 @@ mod tests { TxReceiptError::new( TxHashByTable::SentPayable(tx_hash_3), AppRpcWeb3Error:: Remote(RemoteError::Web3RpcError { code: 429, message: "The requests per second (RPS) of your requests are higher than your plan allows.".to_string()})))), - TxReceiptResult(Ok(RetrievedTxStatus::new(TxHashByTable::SentPayable(tx_hash_1), StatusReadFromReceiptCheck::Pending))), + TxReceiptResult(Ok(RetrievedTxStatus::new(TxHashByTable::SentPayable(tx_hash_4), StatusReadFromReceiptCheck::Pending))), ], response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs index c18e95d04..024e945aa 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs @@ -1,6 +1,5 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::db_access_objects::failed_payable_dao::FailureReason; use crate::blockchain::blockchain_interface::blockchain_interface_web3::CONTRACT_ABI; use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainInterfaceError; use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainInterfaceError::QueryFailed; @@ -8,13 +7,9 @@ use crate::blockchain::blockchain_interface::data_structures::{ BlockchainTxFailure, StatusReadFromReceiptCheck, TxBlock, }; use crate::blockchain::blockchain_interface::lower_level_interface::LowBlockchainInt; -use actix::Message; use ethereum_types::{H256, U256, U64}; use futures::Future; -use serde_derive::{Deserialize, Serialize}; use serde_json::Value; -use std::fmt::Display; -use variant_count::VariantCount; use web3::contract::{Contract, Options}; use web3::transports::{Batch, Http}; use web3::types::{Address, BlockNumber, Filter, Log, TransactionReceipt}; @@ -163,7 +158,6 @@ mod tests { use masq_lib::test_utils::mock_blockchain_client_server::MBCSBuilder; use masq_lib::utils::find_free_port; use std::str::FromStr; - use itertools::Itertools; use web3::types::{BlockNumber, Bytes, FilterBuilder, Log, TransactionReceipt, U256}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{BlockchainTxFailure, StatusReadFromReceiptCheck}; diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs index 2e328d4ef..096394f7c 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs @@ -216,15 +216,12 @@ impl BlockchainInterface for BlockchainInterfaceWeb3 { fn process_transaction_receipts( &self, tx_hashes: Vec, - ) -> Box, Error = BlockchainInterfaceError>> - { + ) -> Box, Error = BlockchainInterfaceError>> { Box::new( self.lower_interface() - .get_transaction_receipt_in_batch(Self::drain_hashes(&tx_hashes)) + .get_transaction_receipt_in_batch(Self::collect_plain_hashes(&tx_hashes)) .map_err(move |e| e) .and_then(move |batch_response| { - todo!("check that all the retrieved data are in order with the hashes "); - Ok(batch_response .into_iter() .zip(tx_hashes.into_iter()) @@ -244,9 +241,9 @@ impl BlockchainInterface for BlockchainInterfaceWeb3 { } else { TxReceiptResult(Err(TxReceiptError::new( tx_hash, - AppRpcWeb3Error::Remote(RemoteError::InvalidResponse( - e.to_string(), - )), + AppRpcWeb3Error::Remote( + RemoteError::InvalidResponse(e.to_string()), + ), ))) } } @@ -442,12 +439,12 @@ impl BlockchainInterfaceWeb3 { } } - fn drain_hashes(hashes_by_table: &[TxHashByTable]) -> Vec { + fn collect_plain_hashes(hashes_by_table: &[TxHashByTable]) -> Vec { hashes_by_table .iter() .map(|hash_by_table| match hash_by_table { - TxHashByTable::SentPayable(hash) => todo!(), - TxHashByTable::FailedPayable(hash) => todo!(), + TxHashByTable::SentPayable(hash) => *hash, + TxHashByTable::FailedPayable(hash) => *hash, }) .collect() } @@ -462,7 +459,6 @@ mod tests { }; use crate::accountant::scanners::pending_payable_scanner::utils::TxHashByTable; use crate::accountant::test_utils::make_payable_account; - use crate::accountant::test_utils::make_sent_tx; use crate::blockchain::blockchain_bridge::increase_gas_price_by_margin; use crate::blockchain::blockchain_interface::blockchain_interface_web3::{ BlockchainInterfaceWeb3, CONTRACT_ABI, REQUESTS_IN_PARALLEL, TRANSACTION_LITERAL, @@ -1381,4 +1377,33 @@ mod tests { BlockMarker::Uninitialized ); } + + #[test] + fn collect_plain_hashes_works() { + let hash_sent_tx_1 = make_tx_hash(456); + let hash_sent_tx_2 = make_tx_hash(789); + let hash_sent_tx_3 = make_tx_hash(234); + let hash_failed_tx_1 = make_tx_hash(123); + let hash_failed_tx_2 = make_tx_hash(345); + let inputs = vec![ + TxHashByTable::SentPayable(hash_sent_tx_1), + TxHashByTable::FailedPayable(hash_failed_tx_1), + TxHashByTable::SentPayable(hash_sent_tx_2), + TxHashByTable::SentPayable(hash_sent_tx_3), + TxHashByTable::FailedPayable(hash_failed_tx_2), + ]; + + let result = BlockchainInterfaceWeb3::collect_plain_hashes(&inputs); + + assert_eq!( + result, + vec![ + hash_sent_tx_1, + hash_failed_tx_1, + hash_sent_tx_2, + hash_sent_tx_3, + hash_failed_tx_2 + ] + ); + } } diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs index 53068944d..ff3e3a4c7 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs @@ -9,12 +9,13 @@ use crate::blockchain::blockchain_agent::agent_web3::BlockchainAgentWeb3; use crate::blockchain::blockchain_agent::BlockchainAgent; use crate::blockchain::blockchain_bridge::RegisterNewPendingPayables; use crate::blockchain::blockchain_interface::blockchain_interface_web3::{ - BlockchainInterfaceWeb3, HashAndAmount, TRANSFER_METHOD_ID, + BlockchainInterfaceWeb3, TRANSFER_METHOD_ID, }; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; use crate::blockchain::blockchain_interface::data_structures::{ ProcessedPayableFallible, RpcPayableFailure, }; +use crate::blockchain::errors::validation_status::ValidationStatus; use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; use crate::sub_lib::wallet::Wallet; use actix::Recipient; @@ -30,9 +31,7 @@ use std::time::SystemTime; use thousands::Separable; use web3::transports::{Batch, Http}; use web3::types::{Bytes, SignedTransaction, TransactionParameters, U256}; -use web3::Error as Web3Error; use web3::Web3; -use crate::blockchain::errors::validation_status::ValidationStatus; #[derive(Debug)] pub struct BlockchainAgentFutureResult { @@ -465,7 +464,6 @@ mod tests { (account_1.clone(), 111_234_111), (account_2.clone(), 222_432_222), ]); - let before = SystemTime::now(); let mut result = sign_and_append_multiple_payments( now, @@ -477,14 +475,13 @@ mod tests { &accounts, ); - let after = SystemTime::now(); let first_actual_sent_tx = result.remove(0); let second_actual_sent_tx = result.remove(0); assert_prepared_sent_tx_record( first_actual_sent_tx, now, account_1, - "0x374b7d023f4ac7d99e612d82beda494b0747116e9b9dc975b33b865f331ee934", + "0x6b85347ff8edf8b126dffb85e7517ac7af1b23eace4ed5ad099d783fd039b1ee", 1, 111_234_111, ); @@ -492,7 +489,7 @@ mod tests { second_actual_sent_tx, now, account_2, - "0x5708afd876bc2573f9db984ec6d0e7f8ef222dd9f115643c9b9056d8bef8bbd9", + "0x3dac025697b994920c9cd72ab0d2df82a7caaa24d44e78b7c04e223299819d54", 2, 222_432_222, ); @@ -512,8 +509,8 @@ mod tests { H256::from_str(&expected_tx_hash_including_prefix[2..]).unwrap() ); assert_eq!(actual_sent_tx.amount_minor, account_1.balance_wei); - assert_eq!(actual_sent_tx.gas_price_minor, 111_111_111); - assert_eq!(actual_sent_tx.nonce, 1); + assert_eq!(actual_sent_tx.gas_price_minor, expected_gas_price_minor); + assert_eq!(actual_sent_tx.nonce, expected_nonce); assert_eq!( actual_sent_tx.status, TxStatus::Pending(ValidationStatus::Waiting) diff --git a/node/src/blockchain/blockchain_interface/data_structures/errors.rs b/node/src/blockchain/blockchain_interface/data_structures/errors.rs index b050b1dc2..f224d7b71 100644 --- a/node/src/blockchain/blockchain_interface/data_structures/errors.rs +++ b/node/src/blockchain/blockchain_interface/data_structures/errors.rs @@ -7,7 +7,7 @@ use std::collections::HashSet; use std::fmt; use std::fmt::{Display, Formatter}; use variant_count::VariantCount; -use web3::types::{Address, H256}; +use web3::types::Address; const BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED: &str = "Uninitialized blockchain interface. To avoid \ being delinquency-banned, you should restart the Node with a value for blockchain-service-url"; diff --git a/node/src/blockchain/blockchain_interface/data_structures/mod.rs b/node/src/blockchain/blockchain_interface/data_structures/mod.rs index efea6e3af..e0b30940f 100644 --- a/node/src/blockchain/blockchain_interface/data_structures/mod.rs +++ b/node/src/blockchain/blockchain_interface/data_structures/mod.rs @@ -6,6 +6,7 @@ use crate::accountant::db_access_objects::utils::TxHash; use crate::accountant::scanners::pending_payable_scanner::utils::TxHashByTable; use crate::accountant::PendingPayable; use crate::blockchain::blockchain_bridge::BlockMarker; +use crate::blockchain::errors::blockchain_loggable_error::app_rpc_web3_error::AppRpcWeb3Error; use crate::sub_lib::wallet::Wallet; use actix::Message; use ethereum_types::U64; @@ -15,7 +16,6 @@ use std::fmt::{Display, Formatter}; use variant_count::VariantCount; use web3::types::H256; use web3::Error; -use crate::blockchain::errors::blockchain_loggable_error::app_rpc_web3_error::AppRpcWeb3Error; #[derive(Clone, Debug, Eq, PartialEq)] pub struct BlockchainTransaction { @@ -142,10 +142,12 @@ mod tests { BlockchainTxFailure, RetrievedTxStatus, StatusReadFromReceiptCheck, TxBlock, TxReceiptError, TxReceiptResult, }; + use crate::blockchain::errors::blockchain_loggable_error::app_rpc_web3_error::{ + AppRpcWeb3Error, LocalError, RemoteError, + }; use crate::blockchain::test_utils::make_tx_hash; use ethereum_types::{H256, U64}; use itertools::Itertools; - use crate::blockchain::errors::blockchain_loggable_error::app_rpc_web3_error::{AppRpcWeb3Error, LocalError, RemoteError}; #[test] fn tx_status_display_works() { @@ -177,7 +179,7 @@ mod tests { vec![(BlockchainTxFailure::Unrecognized, "Unrecognized failure")]; let inputs_len = input_and_expected_results.len(); - let mut check_nums = input_and_expected_results + let check_nums = input_and_expected_results .into_iter() .map(|(input, failure_reason)| match input { BlockchainTxFailure::Unrecognized => { diff --git a/node/src/database/db_initializer.rs b/node/src/database/db_initializer.rs index cbf6008c0..674786766 100644 --- a/node/src/database/db_initializer.rs +++ b/node/src/database/db_initializer.rs @@ -802,8 +802,7 @@ mod tests { gas_price_wei_high_b, gas_price_wei_low_b, nonce, - block_hash, - block_number + status FROM sent_payable", ) .unwrap(); diff --git a/node/src/test_utils/mod.rs b/node/src/test_utils/mod.rs index a4bed17fe..b3d026c95 100644 --- a/node/src/test_utils/mod.rs +++ b/node/src/test_utils/mod.rs @@ -563,7 +563,6 @@ pub mod unshared_test_utils { use std::sync::{Arc, Mutex}; use std::time::Duration; use std::vec; - use variant_count::VariantCount; #[derive(Message)] pub struct AssertionsMessage { From 97edae5a9f9ceba728338b07b371886950e42dfd Mon Sep 17 00:00:00 2001 From: Bert Date: Wed, 20 Aug 2025 23:06:55 +0200 Subject: [PATCH 43/61] GH-683: fixed for a review --- .../db_access_objects/failed_payable_dao.rs | 12 +- .../db_access_objects/sent_payable_dao.rs | 10 +- .../data_structures/errors.rs | 2 +- .../app_rpc_web3_error_kind.rs | 259 ------------------ .../blockchain_db_error/masq_error_kind.rs | 161 ----------- .../errors/blockchain_db_error/mod.rs | 122 --------- .../app_rpc_web3_error.rs | 223 --------------- .../blockchain_loggable_error/masq_error.rs | 83 ------ .../errors/blockchain_loggable_error/mod.rs | 34 --- node/src/blockchain/errors/common_methods.rs | 7 - node/src/blockchain/errors/internal_errors.rs | 51 ++++ node/src/blockchain/errors/mod.rs | 6 +- node/src/blockchain/errors/rpc_errors.rs | 212 ++++++++++++++ node/src/blockchain/errors/test_utils.rs | 81 ------ .../blockchain/errors/validation_status.rs | 53 +--- 15 files changed, 290 insertions(+), 1026 deletions(-) delete mode 100644 node/src/blockchain/errors/blockchain_db_error/app_rpc_web3_error_kind.rs delete mode 100644 node/src/blockchain/errors/blockchain_db_error/masq_error_kind.rs delete mode 100644 node/src/blockchain/errors/blockchain_db_error/mod.rs delete mode 100644 node/src/blockchain/errors/blockchain_loggable_error/app_rpc_web3_error.rs delete mode 100644 node/src/blockchain/errors/blockchain_loggable_error/masq_error.rs delete mode 100644 node/src/blockchain/errors/blockchain_loggable_error/mod.rs delete mode 100644 node/src/blockchain/errors/common_methods.rs create mode 100644 node/src/blockchain/errors/internal_errors.rs create mode 100644 node/src/blockchain/errors/rpc_errors.rs delete mode 100644 node/src/blockchain/errors/test_utils.rs 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 d6b0a35b2..e06bef024 100644 --- a/node/src/accountant/db_access_objects/failed_payable_dao.rs +++ b/node/src/accountant/db_access_objects/failed_payable_dao.rs @@ -381,7 +381,7 @@ mod tests { make_read_only_db_connection, FailedTxBuilder, }; use crate::accountant::db_access_objects::utils::current_unix_timestamp; - use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; + use crate::blockchain::errors::rpc_errors::RpcErrorKind; use crate::blockchain::errors::validation_status::{ PreviousAttempts, ValidationFailureClockReal, }; @@ -592,7 +592,7 @@ mod tests { assert_eq!( FailureReason::from_str(r#"{"Submission":{"Local":{"Decoder"}}}"#).unwrap(), FailureReason::Submission(PreviousAttempts::new( - Box::new(AppRpcWeb3ErrorKind::Decoder), + RpcErrorKind::Decoder, &validation_failure_clock )) ); @@ -643,7 +643,7 @@ mod tests { assert_eq!( FailureStatus::from_str(r#"{"RecheckRequired":{"Reattempting":{"ServerUnreachable":{"firstSeen":{"secs_since_epoch":1755080031,"nanos_since_epoch":612180914},"attempts":1}}}}"#).unwrap(), - FailureStatus::RecheckRequired(ValidationStatus::Reattempting( PreviousAttempts::new(Box::new(AppRpcWeb3ErrorKind::ServerUnreachable), &validation_failure_clock))) + FailureStatus::RecheckRequired(ValidationStatus::Reattempting( PreviousAttempts::new(RpcErrorKind::ServerUnreachable, &validation_failure_clock))) ); assert_eq!( @@ -726,7 +726,7 @@ mod tests { .reason(PendingTooLong) .status(RecheckRequired(ValidationStatus::Reattempting( PreviousAttempts::new( - Box::new(AppRpcWeb3ErrorKind::ServerUnreachable), + RpcErrorKind::ServerUnreachable, &ValidationFailureClockReal::default(), ), ))) @@ -782,7 +782,7 @@ mod tests { ( tx2.hash, RecheckRequired(ValidationStatus::Reattempting(PreviousAttempts::new( - Box::new(AppRpcWeb3ErrorKind::ServerUnreachable), + RpcErrorKind::ServerUnreachable, &ValidationFailureClockReal::default(), ))), ), @@ -799,7 +799,7 @@ mod tests { assert_eq!( updated_txs[1].status, RecheckRequired(ValidationStatus::Reattempting(PreviousAttempts::new( - Box::new(AppRpcWeb3ErrorKind::ServerUnreachable), + RpcErrorKind::ServerUnreachable, &ValidationFailureClockReal::default() ))) ); 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 c17061ee6..24fb2880e 100644 --- a/node/src/accountant/db_access_objects/sent_payable_dao.rs +++ b/node/src/accountant/db_access_objects/sent_payable_dao.rs @@ -441,7 +441,7 @@ mod tests { use crate::accountant::db_access_objects::sent_payable_dao::SentPayableDaoError::{EmptyInput, PartialExecution}; use crate::accountant::db_access_objects::test_utils::{make_read_only_db_connection, TxBuilder}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock}; - use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; + use crate::blockchain::errors::rpc_errors::RpcErrorKind; use crate::blockchain::errors::validation_status::{PreviousAttempts, ValidationFailureClockReal}; use crate::blockchain::test_utils::{make_block_hash, make_tx_hash, ValidationFailureClockMock}; @@ -457,11 +457,11 @@ mod tests { .hash(make_tx_hash(2)) .status(TxStatus::Pending(ValidationStatus::Reattempting( PreviousAttempts::new( - Box::new(AppRpcWeb3ErrorKind::ServerUnreachable), + RpcErrorKind::ServerUnreachable, &ValidationFailureClockReal::default(), ) .add_attempt( - Box::new(AppRpcWeb3ErrorKind::ServerUnreachable), + RpcErrorKind::ServerUnreachable, &ValidationFailureClockReal::default(), ), ))) @@ -693,7 +693,7 @@ mod tests { .hash(make_tx_hash(2)) .status(TxStatus::Pending(ValidationStatus::Reattempting( PreviousAttempts::new( - Box::new(AppRpcWeb3ErrorKind::ServerUnreachable), + RpcErrorKind::ServerUnreachable, &ValidationFailureClockReal::default(), ), ))) @@ -1189,7 +1189,7 @@ mod tests { assert_eq!( TxStatus::from_str(r#"{"Pending":{"Reattempting":{"InvalidResponse":{"firstSeen":{"secs_since_epoch":12456,"nanos_since_epoch":0},"attempts":1}}}}"#).unwrap(), - TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new(Box::new(AppRpcWeb3ErrorKind::InvalidResponse), &validation_failure_clock))) + TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new(RpcErrorKind::InvalidResponse, &validation_failure_clock))) ); assert_eq!( diff --git a/node/src/blockchain/blockchain_interface/data_structures/errors.rs b/node/src/blockchain/blockchain_interface/data_structures/errors.rs index e168f7d73..027369717 100644 --- a/node/src/blockchain/blockchain_interface/data_structures/errors.rs +++ b/node/src/blockchain/blockchain_interface/data_structures/errors.rs @@ -25,7 +25,7 @@ impl Display for BlockchainInterfaceError { Self::InvalidUrl => Either::Left("Invalid url"), Self::InvalidAddress => Either::Left("Invalid address"), Self::InvalidResponse => Either::Left("Invalid response"), - Self::QueryFailed(msg) => Either::Right(format!("Query failed: {}", msg)), //TODO this should also incorporate AppRpcWeb3Error + Self::QueryFailed(msg) => Either::Right(format!("Query failed: {}", msg)), //TODO this should also incorporate RpcError Self::UninitializedBlockchainInterface => { Either::Left(BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED) } diff --git a/node/src/blockchain/errors/blockchain_db_error/app_rpc_web3_error_kind.rs b/node/src/blockchain/errors/blockchain_db_error/app_rpc_web3_error_kind.rs deleted file mode 100644 index 06a4b7715..000000000 --- a/node/src/blockchain/errors/blockchain_db_error/app_rpc_web3_error_kind.rs +++ /dev/null @@ -1,259 +0,0 @@ -// Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. - -use crate::blockchain::errors::blockchain_db_error::{BlockchainDbError, CustomHash, CustomSeDe}; -use crate::blockchain::errors::blockchain_loggable_error::app_rpc_web3_error::{ - AppRpcWeb3Error, LocalError, RemoteError, -}; -use crate::blockchain::errors::common_methods::CommonMethods; -use serde_derive::{Deserialize, Serialize}; -use serde_json::Value; -use std::fmt::Debug; -use std::hash::{Hash, Hasher}; - -impl BlockchainDbError for AppRpcWeb3ErrorKind { - fn as_common_methods(&self) -> &dyn CommonMethods> { - self - } -} - -impl CustomSeDe for AppRpcWeb3ErrorKind { - fn costume_serialize(&self) -> Result { - serde_json::to_value(self) - } - - fn costume_deserialize(str: &str) -> Result, serde_json::Error> - where - Self: Sized, - { - let res: Result = serde_json::from_str(str); - res.map(|kind| Box::new(kind) as Box) - } -} - -impl CommonMethods> for AppRpcWeb3ErrorKind { - fn partial_eq(&self, other: &Box) -> bool { - other - .as_common_methods() - .as_any() - .downcast_ref::() - .map_or(false, |other| self == other) - } - - fn dup(&self) -> Box { - Box::new(self.clone()) - } - - as_any_ref_in_trait_impl!(); -} - -impl CustomHash for AppRpcWeb3ErrorKind { - fn costume_hash(&self, hasher: &mut dyn Hasher) { - match self { - AppRpcWeb3ErrorKind::Decoder => hasher.write_u8(0), - AppRpcWeb3ErrorKind::Internal => hasher.write_u8(1), - AppRpcWeb3ErrorKind::IO => hasher.write_u8(2), - AppRpcWeb3ErrorKind::Signing => hasher.write_u8(3), - AppRpcWeb3ErrorKind::Transport => hasher.write_u8(4), - AppRpcWeb3ErrorKind::InvalidResponse => hasher.write_u8(5), - AppRpcWeb3ErrorKind::ServerUnreachable => hasher.write_u8(6), - AppRpcWeb3ErrorKind::Web3RpcError(code) => { - hasher.write_u8(7); - hasher.write_i64(*code); - } - } - } -} - -#[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum AppRpcWeb3ErrorKind { - // Local - Decoder, - Internal, - IO, - Signing, - Transport, - - // Remote - InvalidResponse, - ServerUnreachable, - Web3RpcError(i64), // Keep only the stable error code -} - -impl From<&AppRpcWeb3Error> for AppRpcWeb3ErrorKind { - fn from(err: &AppRpcWeb3Error) -> Self { - match err { - AppRpcWeb3Error::Local(local) => match local { - LocalError::Decoder(_) => Self::Decoder, - LocalError::Internal => Self::Internal, - LocalError::Io(_) => Self::IO, - LocalError::Signing(_) => Self::Signing, - LocalError::Transport(_) => Self::Transport, - }, - AppRpcWeb3Error::Remote(remote) => match remote { - RemoteError::InvalidResponse(_) => Self::InvalidResponse, - RemoteError::Unreachable => Self::ServerUnreachable, - RemoteError::Web3RpcError { code, .. } => Self::Web3RpcError(*code), - }, - } - } -} - -#[cfg(test)] -mod tests { - use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; - use crate::blockchain::errors::blockchain_db_error::BlockchainDbError; - use crate::blockchain::errors::blockchain_loggable_error::app_rpc_web3_error::{ - AppRpcWeb3Error, LocalError, RemoteError, - }; - use crate::blockchain::errors::test_utils::test_clone_impl_for_blockchain_db_error; - use std::collections::hash_map::DefaultHasher; - use std::hash::{Hash, Hasher}; - - #[test] - fn conversion_between_app_rpc_error_and_app_rpc_error_kind_works() { - assert_eq!( - AppRpcWeb3ErrorKind::from(&AppRpcWeb3Error::Local(LocalError::Decoder( - "Decoder error".to_string() - ))), - AppRpcWeb3ErrorKind::Decoder - ); - assert_eq!( - AppRpcWeb3ErrorKind::from(&AppRpcWeb3Error::Local(LocalError::Internal)), - AppRpcWeb3ErrorKind::Internal - ); - assert_eq!( - AppRpcWeb3ErrorKind::from(&AppRpcWeb3Error::Local(LocalError::Io( - "IO error".to_string() - ))), - AppRpcWeb3ErrorKind::IO - ); - assert_eq!( - AppRpcWeb3ErrorKind::from(&AppRpcWeb3Error::Local(LocalError::Signing( - "Signing error".to_string() - ))), - AppRpcWeb3ErrorKind::Signing - ); - assert_eq!( - AppRpcWeb3ErrorKind::from(&AppRpcWeb3Error::Local(LocalError::Transport( - "Transport error".to_string() - ))), - AppRpcWeb3ErrorKind::Transport - ); - assert_eq!( - AppRpcWeb3ErrorKind::from(&AppRpcWeb3Error::Remote(RemoteError::InvalidResponse( - "Invalid response".to_string() - ))), - AppRpcWeb3ErrorKind::InvalidResponse - ); - assert_eq!( - AppRpcWeb3ErrorKind::from(&AppRpcWeb3Error::Remote(RemoteError::Unreachable)), - AppRpcWeb3ErrorKind::ServerUnreachable - ); - assert_eq!( - AppRpcWeb3ErrorKind::from(&AppRpcWeb3Error::Remote(RemoteError::Web3RpcError { - code: 55, - message: "Booga".to_string() - })), - AppRpcWeb3ErrorKind::Web3RpcError(55) - ); - } - - #[test] - fn clone_works_for_blockchain_db_error_wrapping_app_rpc_error_kind() { - let subject: Box = Box::new(AppRpcWeb3ErrorKind::Web3RpcError(123)); - - test_clone_impl_for_blockchain_db_error::(subject); - } - - #[test] - fn hashing_for_app_arp_error_kind_works() { - let mut hasher = DefaultHasher::default(); - let mut hashes = vec![ - Box::new(AppRpcWeb3ErrorKind::Decoder) as Box, - Box::new(AppRpcWeb3ErrorKind::Internal), - Box::new(AppRpcWeb3ErrorKind::IO), - Box::new(AppRpcWeb3ErrorKind::Signing), - Box::new(AppRpcWeb3ErrorKind::Transport), - Box::new(AppRpcWeb3ErrorKind::InvalidResponse), - Box::new(AppRpcWeb3ErrorKind::ServerUnreachable), - Box::new(AppRpcWeb3ErrorKind::Web3RpcError(123)), - Box::new(AppRpcWeb3ErrorKind::Web3RpcError(124)), - Box::new(AppRpcWeb3ErrorKind::Web3RpcError(555555)), - ] - .into_iter() - .map(|blockchain_error| { - blockchain_error.hash(&mut hasher); - - hasher.finish() - }) - .collect::>(); - - hashes.clone().iter().for_each(|picked_hash| { - hashes.remove(0); - hashes.iter().for_each(|other_hash| { - assert_ne!(picked_hash, other_hash); - }); - }) - } - - #[test] - fn partial_eq_for_app_rpc_error_kind_works() { - let subject: Box = Box::new(AppRpcWeb3ErrorKind::Web3RpcError(123)); - let other_1: Box = Box::new(AppRpcWeb3ErrorKind::Web3RpcError(124)); - let other_2: Box = Box::new(AppRpcWeb3ErrorKind::Web3RpcError(123)); - let other_3: Box = Box::new(AppRpcWeb3ErrorKind::Internal); - - assert_ne!(&subject, &other_1); - assert_eq!(&subject, &other_2); - assert_ne!(&subject, &other_3); - } - - #[test] - fn app_rpc_error_kind_serialization_deserialization() { - let errors = vec![ - // Local Errors - AppRpcWeb3ErrorKind::Decoder, - AppRpcWeb3ErrorKind::Internal, - AppRpcWeb3ErrorKind::IO, - AppRpcWeb3ErrorKind::Signing, - AppRpcWeb3ErrorKind::Transport, - // Remote Errors - AppRpcWeb3ErrorKind::InvalidResponse, - AppRpcWeb3ErrorKind::ServerUnreachable, - AppRpcWeb3ErrorKind::Web3RpcError(42), - ]; - - errors.into_iter().for_each(|error| { - let serialized = serde_json::to_string(&error).unwrap(); - let deserialized: AppRpcWeb3ErrorKind = serde_json::from_str(&serialized).unwrap(); - assert_eq!( - error, deserialized, - "Failed serde attempt for {:?} that should look \ - like {:?}", - deserialized, error - ); - }); - } - - #[test] - fn serialization_and_deserialization_for_blockchain_db_error_works() { - vec![ - ( - Box::new(AppRpcWeb3ErrorKind::Internal) as Box, - "\"Internal\"", - ), - ( - Box::new(AppRpcWeb3ErrorKind::Web3RpcError(123)), - "{\"Web3RpcError\":123}", - ), - ] - .into_iter() - .for_each(|(blockchain_error, expected_result)| { - let json_result = serde_json::to_string(&blockchain_error).unwrap(); - assert_eq!(json_result, expected_result); - let trait_object_result = - serde_json::from_str::>(&json_result).unwrap(); - assert_eq!(&trait_object_result, &blockchain_error); - }) - } -} diff --git a/node/src/blockchain/errors/blockchain_db_error/masq_error_kind.rs b/node/src/blockchain/errors/blockchain_db_error/masq_error_kind.rs deleted file mode 100644 index 90517a3dc..000000000 --- a/node/src/blockchain/errors/blockchain_db_error/masq_error_kind.rs +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. - -use crate::blockchain::errors::blockchain_db_error::{BlockchainDbError, CustomHash, CustomSeDe}; -use crate::blockchain::errors::blockchain_loggable_error::masq_error::MASQError; -use crate::blockchain::errors::common_methods::CommonMethods; -use serde_derive::{Deserialize, Serialize}; -use serde_json::Value; -use std::hash::Hasher; -use variant_count::VariantCount; - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, VariantCount)] -pub enum MASQErrorKind { - PendingTooLongNotReplaced, -} - -impl BlockchainDbError for MASQErrorKind { - fn as_common_methods(&self) -> &dyn CommonMethods> { - self - } -} - -impl CustomSeDe for MASQErrorKind { - fn costume_serialize(&self) -> Result { - serde_json::to_value(self) - } - - fn costume_deserialize(str: &str) -> Result, serde_json::Error> - where - Self: Sized, - { - let res: Result = serde_json::from_str(str); - res.map(|kind| Box::new(kind) as Box) - } -} - -impl CommonMethods> for MASQErrorKind { - fn partial_eq(&self, other: &Box) -> bool { - other - .as_common_methods() - .as_any() - .downcast_ref::() - .map_or(false, |other| self == other) - } - - fn dup(&self) -> Box { - Box::new(self.clone()) - } - - as_any_ref_in_trait_impl!(); -} - -impl CustomHash for MASQErrorKind { - fn costume_hash(&self, hasher: &mut dyn Hasher) { - match self { - MASQErrorKind::PendingTooLongNotReplaced => hasher.write_u8(0), - } - } -} - -impl From<&MASQError> for MASQErrorKind { - fn from(masq_error: &MASQError) -> Self { - match masq_error { - MASQError::PendingTooLongNotReplaced => MASQErrorKind::PendingTooLongNotReplaced, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::blockchain::errors::blockchain_loggable_error::masq_error::MASQError; - use crate::blockchain::errors::blockchain_loggable_error::BlockchainLoggableError; - use crate::blockchain::errors::test_utils::test_clone_impl_for_blockchain_db_error; - use std::collections::hash_map::DefaultHasher; - use std::hash::Hash; - - #[test] - fn conversion_between_masq_error_and_masq_error_kind_works() { - assert_eq!( - MASQErrorKind::from(&MASQError::PendingTooLongNotReplaced), - MASQErrorKind::PendingTooLongNotReplaced - ); - } - - #[test] - fn clone_works_for_blockchain_db_error_wrapping_masq_error_kind() { - let subject: Box = - Box::new(MASQErrorKind::PendingTooLongNotReplaced); - - test_clone_impl_for_blockchain_db_error::(subject); - } - - #[test] - fn hashing_for_masq_error_kind_works() { - let mut hasher = DefaultHasher::default(); - let mut hashes = vec![ - Box::new(MASQErrorKind::PendingTooLongNotReplaced) as Box, - // Add more types here as there are more types of MASQ app_rpc_web3_error_kind. - ] - .into_iter() - .map(|blockchain_error| { - blockchain_error.hash(&mut hasher); - - hasher.finish() - }) - .collect::>(); - - hashes.clone().iter().for_each(|picked_hash| { - hashes.remove(0); - hashes.iter().for_each(|other_hash| { - assert_ne!(picked_hash, other_hash); - }); - }); - // Expand this test as there are more variants of MASQErrorKind. - assert_eq!(MASQErrorKind::VARIANT_COUNT, 1); - } - - #[test] - fn partial_eq_for_masq_error_kind_works() { - let subject: Box = - Box::new(MASQErrorKind::PendingTooLongNotReplaced); - let other: Box = Box::new(MASQErrorKind::PendingTooLongNotReplaced); - - assert_eq!(&subject, &other); - // Expand this test as there are more variants of MASQErrorKind. - assert_eq!(MASQErrorKind::VARIANT_COUNT, 1); - } - - #[test] - fn serialization_and_deserialization_for_blockchain_db_error_works() { - vec![( - Box::new(MASQErrorKind::PendingTooLongNotReplaced) as Box, - "\"PendingTooLongNotReplaced\"", - )] - .into_iter() - .for_each(|(blockchain_error, expected_result)| { - let json_result = serde_json::to_string(&blockchain_error).unwrap(); - assert_eq!(json_result, expected_result); - let trait_object_result = - serde_json::from_str::>(&json_result).unwrap(); - assert_eq!(&trait_object_result, &blockchain_error); - }); - // Expand this test as there are more variants of MASQErrorKind. - assert_eq!(MASQErrorKind::VARIANT_COUNT, 1); - } - - #[test] - fn blockchain_loggable_error_can_be_converted_to_blockchain_db_error_for_masq_errors() { - let error: Box = - Box::new(MASQError::PendingTooLongNotReplaced); - - let result = >::from(error); - - assert_eq!( - &result, - &(Box::new(MASQErrorKind::PendingTooLongNotReplaced) as Box) - ); - // Expand this test as there are more variants of MASQErrorKind. - assert_eq!(MASQErrorKind::VARIANT_COUNT, 1); - } -} diff --git a/node/src/blockchain/errors/blockchain_db_error/mod.rs b/node/src/blockchain/errors/blockchain_db_error/mod.rs deleted file mode 100644 index b47933d52..000000000 --- a/node/src/blockchain/errors/blockchain_db_error/mod.rs +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. - -pub mod app_rpc_web3_error_kind; -pub mod masq_error_kind; - -use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; -use crate::blockchain::errors::blockchain_db_error::masq_error_kind::MASQErrorKind; -use crate::blockchain::errors::common_methods::CommonMethods; -use serde::{Deserialize as DeserializeTrait, Serialize as SerializeTrait}; -use serde_json::Value; -use std::fmt::Debug; -use std::hash::{Hash, Hasher}; - -pub trait BlockchainDbError: CustomSeDe + CustomHash + Debug { - fn as_common_methods(&self) -> &dyn CommonMethods>; -} - -pub trait CustomSeDe { - fn costume_serialize(&self) -> Result; - fn costume_deserialize(str: &str) -> Result, serde_json::Error> - where - Self: Sized; -} - -pub trait CustomHash { - fn costume_hash(&self, hasher: &mut dyn Hasher); -} - -impl SerializeTrait for Box { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - self.costume_serialize() - .map_err(|e| serde::ser::Error::custom(e))? - .serialize(serializer) - } -} - -impl<'de> DeserializeTrait<'de> for Box { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let json_value: serde_json::Value = serde_json::Value::deserialize(deserializer)?; - let json_str = - serde_json::to_string(&json_value).map_err(|e| serde::de::Error::custom(e))?; // Untested error - - if let Ok(error) = AppRpcWeb3ErrorKind::costume_deserialize(&json_str) { - return Ok(error); - } - - if let Ok(error) = MASQErrorKind::costume_deserialize(&json_str) { - return Ok(error); - } - - Err(serde::de::Error::custom(format!( - "Unable to deserialize BlockchainDbError from: {}", - json_str - ))) - } -} - -impl Clone for Box { - fn clone(&self) -> Self { - self.as_common_methods().dup() - } -} - -impl PartialEq for Box { - fn eq(&self, other: &Self) -> bool { - self.as_common_methods().partial_eq(other) - } -} - -impl Hash for Box { - fn hash(&self, state: &mut H) { - self.costume_hash(state) - } -} - -impl Eq for Box {} - -#[cfg(test)] -mod tests { - use super::*; - use crate::blockchain::errors::test_utils::BlockchainDbErrorMock; - - #[test] - fn deserialization_fails() { - let str = "\"bluh\""; - - let err = serde_json::from_str::>(str).unwrap_err(); - - assert_eq!( - err.to_string(), - "Unable to deserialize BlockchainDbError from: \"bluh\"" - ) - } - - #[test] - fn pre_serialization_costume_error_is_well_arranged() { - let mock = BlockchainDbErrorMock::default(); - let subject: Box = Box::new(mock); - - let res = serde_json::to_string(&subject).unwrap_err(); - - assert_eq!( - res.to_string(), - "invalid type: character `a`, expected null" - ); - } - - #[test] - fn deserialization_other_error() { - let result = - serde_json::from_str::>(r#"{"key":invalid_json_value}"#) - .unwrap_err(); - - assert_eq!(result.to_string(), "expected value at line 1 column 8"); - } -} diff --git a/node/src/blockchain/errors/blockchain_loggable_error/app_rpc_web3_error.rs b/node/src/blockchain/errors/blockchain_loggable_error/app_rpc_web3_error.rs deleted file mode 100644 index 87c395209..000000000 --- a/node/src/blockchain/errors/blockchain_loggable_error/app_rpc_web3_error.rs +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. - -use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; -use crate::blockchain::errors::blockchain_db_error::BlockchainDbError; -use crate::blockchain::errors::blockchain_loggable_error::BlockchainLoggableError; -use crate::blockchain::errors::common_methods::CommonMethods; -use std::fmt::{Display, Formatter}; -use web3::error::Error as Web3Error; - -// Prefixed with App to clearly distinguish app-specific app_rpc_web3_error_kind from library app_rpc_web3_error_kind. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum AppRpcWeb3Error { - Local(LocalError), - Remote(RemoteError), -} - -impl BlockchainLoggableError for AppRpcWeb3Error { - fn as_common_methods(&self) -> &dyn CommonMethods> { - self - } - - fn downgrade(&self) -> Box { - Box::new(AppRpcWeb3ErrorKind::from(self)) - } -} - -impl CommonMethods> for AppRpcWeb3Error { - fn partial_eq(&self, other: &Box) -> bool { - other - .as_common_methods() - .as_any() - .downcast_ref::() - .map_or(false, |other| self == other) - } - - fn dup(&self) -> Box { - Box::new(self.clone()) - } - - as_any_ref_in_trait_impl!(); -} - -impl Display for AppRpcWeb3Error { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum LocalError { - Decoder(String), - Internal, - Io(String), - Signing(String), - Transport(String), -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum RemoteError { - InvalidResponse(String), - Unreachable, - Web3RpcError { code: i64, message: String }, -} - -// EVM based app_rpc_web3_error_kind -impl From for AppRpcWeb3Error { - fn from(error: Web3Error) -> Self { - match error { - // Local Errors - Web3Error::Decoder(error) => AppRpcWeb3Error::Local(LocalError::Decoder(error)), - Web3Error::Internal => AppRpcWeb3Error::Local(LocalError::Internal), - Web3Error::Io(error) => AppRpcWeb3Error::Local(LocalError::Io(error.to_string())), - Web3Error::Signing(error) => { - // This variant cannot be tested due to import limitations. - AppRpcWeb3Error::Local(LocalError::Signing(error.to_string())) - } - Web3Error::Transport(error) => AppRpcWeb3Error::Local(LocalError::Transport(error)), - - // Api Errors - Web3Error::InvalidResponse(response) => { - AppRpcWeb3Error::Remote(RemoteError::InvalidResponse(response)) - } - Web3Error::Rpc(web3_rpc_error) => AppRpcWeb3Error::Remote(RemoteError::Web3RpcError { - code: web3_rpc_error.code.code(), - message: web3_rpc_error.message, - }), - Web3Error::Unreachable => AppRpcWeb3Error::Remote(RemoteError::Unreachable), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; - use crate::blockchain::errors::test_utils::test_clone_impl_for_blockchain_error; - use std::vec; - - #[test] - fn web3_error_to_failure_reason_conversion_works() { - // Local Errors - assert_eq!( - AppRpcWeb3Error::from(Web3Error::Decoder("Decoder error".to_string())), - AppRpcWeb3Error::Local(LocalError::Decoder("Decoder error".to_string())) - ); - assert_eq!( - AppRpcWeb3Error::from(Web3Error::Internal), - AppRpcWeb3Error::Local(LocalError::Internal) - ); - assert_eq!( - AppRpcWeb3Error::from(Web3Error::Io(std::io::Error::new( - std::io::ErrorKind::Other, - "IO error" - ))), - AppRpcWeb3Error::Local(LocalError::Io("IO error".to_string())) - ); - assert_eq!( - AppRpcWeb3Error::from(Web3Error::Transport("Transport error".to_string())), - AppRpcWeb3Error::Local(LocalError::Transport("Transport error".to_string())) - ); - - // Api Errors - assert_eq!( - AppRpcWeb3Error::from(Web3Error::InvalidResponse("Invalid response".to_string())), - AppRpcWeb3Error::Remote(RemoteError::InvalidResponse("Invalid response".to_string())) - ); - assert_eq!( - AppRpcWeb3Error::from(Web3Error::Rpc(jsonrpc_core::types::error::Error { - code: jsonrpc_core::types::error::ErrorCode::ServerError(42), - message: "RPC error".to_string(), - data: None, - })), - AppRpcWeb3Error::Remote(RemoteError::Web3RpcError { - code: 42, - message: "RPC error".to_string(), - }) - ); - assert_eq!( - AppRpcWeb3Error::from(Web3Error::Unreachable), - AppRpcWeb3Error::Remote(RemoteError::Unreachable) - ); - } - - #[test] - fn clone_works_for_blockchain_error_wrapping_app_rpc_web3_error() { - let subject: Box = - Box::new(AppRpcWeb3Error::Local(LocalError::Internal)); - - test_clone_impl_for_blockchain_error::(subject); - } - - #[test] - fn partial_eq_for_app_rpc_error_works() { - let subject: Box = - Box::new(AppRpcWeb3Error::Remote(RemoteError::Web3RpcError { - code: 222, - message: "Some message".to_string(), - })); - let other_1: Box = - Box::new(AppRpcWeb3Error::Remote(RemoteError::Unreachable)); - let other_2: Box = - Box::new(AppRpcWeb3Error::Remote(RemoteError::Web3RpcError { - code: 123, - message: "Some message".to_string(), - })); - let other_3: Box = - Box::new(AppRpcWeb3Error::Remote(RemoteError::Web3RpcError { - code: 222, - message: "Some other message".to_string(), - })); - let other_4: Box = - Box::new(AppRpcWeb3Error::Local(LocalError::Internal)); - let other_5: Box = - Box::new(AppRpcWeb3Error::Remote(RemoteError::Web3RpcError { - code: 222, - message: "Some message".to_string(), - })); - - assert_ne!(&subject, &other_1); - assert_ne!(&subject, &other_2); - assert_ne!(&subject, &other_3); - assert_ne!(&subject, &other_4); - assert_eq!(&subject, &other_5); - } - - #[test] - fn display_for_blockchain_error_object_works() { - vec![ - AppRpcWeb3Error::Local(LocalError::Decoder("Serious decoder error".to_string())), - AppRpcWeb3Error::Remote(RemoteError::InvalidResponse( - "The most invalid response of all invalid responses".to_string(), - )), - AppRpcWeb3Error::Local(LocalError::Internal), - AppRpcWeb3Error::Remote(RemoteError::Unreachable), - ] - .into_iter() - .for_each(|error| { - let wrapped_as_trait_object: Box = Box::new(error.clone()); - assert_eq!(wrapped_as_trait_object.to_string(), format!("{:?}", error)); - }) - } - - #[test] - fn blockchain_loggable_error_can_be_converted_to_blockchain_db_error_for_app_rpc_web3_errors() { - let error_1: Box = Box::new(AppRpcWeb3Error::Local( - LocalError::Decoder("This is a decoder error".to_string()), - )); - let error_2: Box = - Box::new(AppRpcWeb3Error::Remote(RemoteError::Unreachable)); - - let result_1 = >::from(error_1); - let result_2 = >::from(error_2); - - assert_eq!( - &result_1, - &(Box::new(AppRpcWeb3ErrorKind::Decoder) as Box) - ); - assert_eq!( - &result_2, - &(Box::new(AppRpcWeb3ErrorKind::ServerUnreachable) as Box) - ); - } -} diff --git a/node/src/blockchain/errors/blockchain_loggable_error/masq_error.rs b/node/src/blockchain/errors/blockchain_loggable_error/masq_error.rs deleted file mode 100644 index 1995a4f68..000000000 --- a/node/src/blockchain/errors/blockchain_loggable_error/masq_error.rs +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. - -use crate::blockchain::errors::blockchain_db_error::masq_error_kind::MASQErrorKind; -use crate::blockchain::errors::blockchain_db_error::BlockchainDbError; -use crate::blockchain::errors::blockchain_loggable_error::BlockchainLoggableError; -use crate::blockchain::errors::common_methods::CommonMethods; -use std::fmt::{Debug, Display, Formatter}; -use variant_count::VariantCount; - -#[derive(Debug, PartialEq, Clone, VariantCount)] -pub enum MASQError { - PendingTooLongNotReplaced, -} - -impl BlockchainLoggableError for MASQError { - fn as_common_methods(&self) -> &dyn CommonMethods> { - self - } - - fn downgrade(&self) -> Box { - Box::new(MASQErrorKind::from(self)) - } -} - -impl CommonMethods> for MASQError { - fn partial_eq(&self, other: &Box) -> bool { - other - .as_common_methods() - .as_any() - .downcast_ref::() - .map_or(false, |other| self == other) - } - - fn dup(&self) -> Box { - Box::new(self.clone()) - } - - as_any_ref_in_trait_impl!(); -} - -impl Display for MASQError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::blockchain::errors::blockchain_loggable_error::BlockchainLoggableError; - use crate::blockchain::errors::test_utils::test_clone_impl_for_blockchain_error; - - #[test] - fn clone_works_for_blockchain_error_wrapping_masq_error() { - let subject: Box = - Box::new(MASQError::PendingTooLongNotReplaced); - - test_clone_impl_for_blockchain_error::(subject); - } - - #[test] - fn partial_eq_for_masq_error_works() { - let subject: Box = - Box::new(MASQError::PendingTooLongNotReplaced); - let other: Box = - Box::new(MASQError::PendingTooLongNotReplaced); - - assert_eq!(&subject, &other); - // Expand this test as there are more variants of MASQError. - assert_eq!(MASQError::VARIANT_COUNT, 1); - } - - #[test] - fn display_for_blockchain_error_object_works() { - vec![MASQError::PendingTooLongNotReplaced] - .into_iter() - .for_each(|error| { - let wrapped_as_trait_object: Box = - Box::new(error.clone()); - assert_eq!(wrapped_as_trait_object.to_string(), format!("{:?}", error)); - }) - } -} diff --git a/node/src/blockchain/errors/blockchain_loggable_error/mod.rs b/node/src/blockchain/errors/blockchain_loggable_error/mod.rs deleted file mode 100644 index 1b58657db..000000000 --- a/node/src/blockchain/errors/blockchain_loggable_error/mod.rs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. - -use crate::blockchain::errors::blockchain_db_error::BlockchainDbError; -use crate::blockchain::errors::common_methods::CommonMethods; -use std::fmt::{Debug, Display}; - -pub mod app_rpc_web3_error; -pub mod masq_error; - -// The Display impl is meant to be used for logging purposes. -pub trait BlockchainLoggableError: Display + Debug { - fn as_common_methods(&self) -> &dyn CommonMethods>; - fn downgrade(&self) -> Box; -} - -impl From> for Box { - fn from(more_verbose_error: Box) -> Self { - more_verbose_error.downgrade() - } -} - -impl Clone for Box { - fn clone(&self) -> Self { - self.as_common_methods().dup() - } -} - -impl PartialEq for Box { - fn eq(&self, other: &Self) -> bool { - self.as_common_methods().partial_eq(other) - } -} - -impl Eq for Box {} diff --git a/node/src/blockchain/errors/common_methods.rs b/node/src/blockchain/errors/common_methods.rs deleted file mode 100644 index d74b2d4b7..000000000 --- a/node/src/blockchain/errors/common_methods.rs +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. - -pub trait CommonMethods { - fn partial_eq(&self, other: &Other) -> bool; - fn dup(&self) -> Other; - as_any_ref_in_trait!(); -} diff --git a/node/src/blockchain/errors/internal_errors.rs b/node/src/blockchain/errors/internal_errors.rs new file mode 100644 index 000000000..59ca6fd3a --- /dev/null +++ b/node/src/blockchain/errors/internal_errors.rs @@ -0,0 +1,51 @@ +// Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, PartialEq, Clone)] +pub enum InternalError { + PendingTooLongNotReplaced, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum InternalErrorKind { + PendingTooLongNotReplaced, +} + +impl From<&InternalError> for InternalErrorKind { + fn from(error: &InternalError) -> Self { + match error { + InternalError::PendingTooLongNotReplaced => { + InternalErrorKind::PendingTooLongNotReplaced + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn conversion_between_internal_error_and_internal_error_kind_works() { + assert_eq!( + InternalErrorKind::from(&InternalError::PendingTooLongNotReplaced), + InternalErrorKind::PendingTooLongNotReplaced + ); + } + + #[test] + fn app_rpc_error_kind_serialization_deserialization() { + let errors = vec![InternalErrorKind::PendingTooLongNotReplaced]; + + errors.into_iter().for_each(|error| { + let serialized = serde_json::to_string(&error).unwrap(); + let deserialized: InternalErrorKind = serde_json::from_str(&serialized).unwrap(); + assert_eq!( + error, deserialized, + "Failed serde attempt for {:?} that should look like {:?}", + deserialized, error + ); + }); + } +} diff --git a/node/src/blockchain/errors/mod.rs b/node/src/blockchain/errors/mod.rs index 91fdd107c..5fe595769 100644 --- a/node/src/blockchain/errors/mod.rs +++ b/node/src/blockchain/errors/mod.rs @@ -1,7 +1,5 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -pub mod blockchain_db_error; -pub mod blockchain_loggable_error; -mod common_methods; -mod test_utils; +pub mod internal_errors; +pub mod rpc_errors; pub mod validation_status; diff --git a/node/src/blockchain/errors/rpc_errors.rs b/node/src/blockchain/errors/rpc_errors.rs new file mode 100644 index 000000000..21817e45a --- /dev/null +++ b/node/src/blockchain/errors/rpc_errors.rs @@ -0,0 +1,212 @@ +// Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use serde_derive::{Deserialize, Serialize}; +use web3::error::Error as Web3Error; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum RpcError { + Local(LocalError), + Remote(RemoteError), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum LocalError { + Decoder(String), + Internal, + Io(String), + Signing(String), + Transport(String), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum RemoteError { + InvalidResponse(String), + Unreachable, + Web3RpcError { code: i64, message: String }, +} + +// EVM based errors +impl From for RpcError { + fn from(error: Web3Error) -> Self { + match error { + // Local Errors + Web3Error::Decoder(error) => RpcError::Local(LocalError::Decoder(error)), + Web3Error::Internal => RpcError::Local(LocalError::Internal), + Web3Error::Io(error) => RpcError::Local(LocalError::Io(error.to_string())), + Web3Error::Signing(error) => { + // This variant cannot be tested due to import limitations. + RpcError::Local(LocalError::Signing(error.to_string())) + } + Web3Error::Transport(error) => RpcError::Local(LocalError::Transport(error)), + + // Api Errors + Web3Error::InvalidResponse(response) => { + RpcError::Remote(RemoteError::InvalidResponse(response)) + } + Web3Error::Rpc(web3_rpc_error) => RpcError::Remote(RemoteError::Web3RpcError { + code: web3_rpc_error.code.code(), + message: web3_rpc_error.message, + }), + Web3Error::Unreachable => RpcError::Remote(RemoteError::Unreachable), + } + } +} + +#[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum RpcErrorKind { + // Local + Decoder, + Internal, + IO, + Signing, + Transport, + + // Remote + InvalidResponse, + ServerUnreachable, + Web3RpcError(i64), // Keep only the stable error code +} + +impl From<&RpcError> for RpcErrorKind { + fn from(err: &RpcError) -> Self { + match err { + RpcError::Local(local) => match local { + LocalError::Decoder(_) => Self::Decoder, + LocalError::Internal => Self::Internal, + LocalError::Io(_) => Self::IO, + LocalError::Signing(_) => Self::Signing, + LocalError::Transport(_) => Self::Transport, + }, + RpcError::Remote(remote) => match remote { + RemoteError::InvalidResponse(_) => Self::InvalidResponse, + RemoteError::Unreachable => Self::ServerUnreachable, + RemoteError::Web3RpcError { code, .. } => Self::Web3RpcError(*code), + }, + } + } +} + +#[cfg(test)] +mod tests { + use crate::blockchain::errors::rpc_errors::{LocalError, RemoteError, RpcError, RpcErrorKind}; + use web3::error::Error as Web3Error; + + #[test] + fn web3_error_to_failure_reason_conversion_works() { + // Local Errors + assert_eq!( + RpcError::from(Web3Error::Decoder("Decoder error".to_string())), + RpcError::Local(LocalError::Decoder("Decoder error".to_string())) + ); + assert_eq!( + RpcError::from(Web3Error::Internal), + RpcError::Local(LocalError::Internal) + ); + assert_eq!( + RpcError::from(Web3Error::Io(std::io::Error::new( + std::io::ErrorKind::Other, + "IO error" + ))), + RpcError::Local(LocalError::Io("IO error".to_string())) + ); + assert_eq!( + RpcError::from(Web3Error::Transport("Transport error".to_string())), + RpcError::Local(LocalError::Transport("Transport error".to_string())) + ); + + // Api Errors + assert_eq!( + RpcError::from(Web3Error::InvalidResponse("Invalid response".to_string())), + RpcError::Remote(RemoteError::InvalidResponse("Invalid response".to_string())) + ); + assert_eq!( + RpcError::from(Web3Error::Rpc(jsonrpc_core::types::error::Error { + code: jsonrpc_core::types::error::ErrorCode::ServerError(42), + message: "RPC error".to_string(), + data: None, + })), + RpcError::Remote(RemoteError::Web3RpcError { + code: 42, + message: "RPC error".to_string(), + }) + ); + assert_eq!( + RpcError::from(Web3Error::Unreachable), + RpcError::Remote(RemoteError::Unreachable) + ); + } + + #[test] + fn conversion_between_app_rpc_error_and_app_rpc_error_kind_works() { + assert_eq!( + RpcErrorKind::from(&RpcError::Local(LocalError::Decoder( + "Decoder error".to_string() + ))), + RpcErrorKind::Decoder + ); + assert_eq!( + RpcErrorKind::from(&RpcError::Local(LocalError::Internal)), + RpcErrorKind::Internal + ); + assert_eq!( + RpcErrorKind::from(&RpcError::Local(LocalError::Io("IO error".to_string()))), + RpcErrorKind::IO + ); + assert_eq!( + RpcErrorKind::from(&RpcError::Local(LocalError::Signing( + "Signing error".to_string() + ))), + RpcErrorKind::Signing + ); + assert_eq!( + RpcErrorKind::from(&RpcError::Local(LocalError::Transport( + "Transport error".to_string() + ))), + RpcErrorKind::Transport + ); + assert_eq!( + RpcErrorKind::from(&RpcError::Remote(RemoteError::InvalidResponse( + "Invalid response".to_string() + ))), + RpcErrorKind::InvalidResponse + ); + assert_eq!( + RpcErrorKind::from(&RpcError::Remote(RemoteError::Unreachable)), + RpcErrorKind::ServerUnreachable + ); + assert_eq!( + RpcErrorKind::from(&RpcError::Remote(RemoteError::Web3RpcError { + code: 55, + message: "Booga".to_string() + })), + RpcErrorKind::Web3RpcError(55) + ); + } + + #[test] + fn app_rpc_error_kind_serialization_deserialization() { + let errors = vec![ + // Local Errors + RpcErrorKind::Decoder, + RpcErrorKind::Internal, + RpcErrorKind::IO, + RpcErrorKind::Signing, + RpcErrorKind::Transport, + // Remote Errors + RpcErrorKind::InvalidResponse, + RpcErrorKind::ServerUnreachable, + RpcErrorKind::Web3RpcError(42), + ]; + + errors.into_iter().for_each(|error| { + let serialized = serde_json::to_string(&error).unwrap(); + let deserialized: RpcErrorKind = serde_json::from_str(&serialized).unwrap(); + assert_eq!( + error, deserialized, + "Failed serde attempt for {:?} that should look \ + like {:?}", + deserialized, error + ); + }); + } +} diff --git a/node/src/blockchain/errors/test_utils.rs b/node/src/blockchain/errors/test_utils.rs deleted file mode 100644 index 9d72e24e9..000000000 --- a/node/src/blockchain/errors/test_utils.rs +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. - -use crate::blockchain::errors::blockchain_db_error::{BlockchainDbError, CustomHash, CustomSeDe}; -use crate::blockchain::errors::blockchain_loggable_error::BlockchainLoggableError; -use crate::blockchain::errors::common_methods::CommonMethods; -use serde::de::{Error, Unexpected}; -use serde_json::Value; -use std::fmt::Debug; -use std::hash::Hasher; - -macro_rules! test_clone_impl { - ($test_fn_name: ident, $boxed_trait: ident) => { - pub fn $test_fn_name(subject: Box) - where - ErrorType: PartialEq + Debug + 'static, - { - let result = subject.clone(); - - let specified_subject = subject - .as_common_methods() - .as_any() - .downcast_ref::() - .unwrap(); - let specified_result = result - .as_common_methods() - .as_any() - .downcast_ref::() - .unwrap(); - assert_eq!(specified_result, specified_subject) - } - }; -} - -test_clone_impl!(test_clone_impl_for_blockchain_db_error, BlockchainDbError); -test_clone_impl!( - test_clone_impl_for_blockchain_error, - BlockchainLoggableError -); - -#[derive(Debug, Default)] -pub struct BlockchainDbErrorMock {} - -impl BlockchainDbError for BlockchainDbErrorMock { - fn as_common_methods(&self) -> &dyn CommonMethods> { - unimplemented!("not needed for testing") - } -} - -impl CustomSeDe for BlockchainDbErrorMock { - fn costume_serialize(&self) -> Result { - Err(serde_json::Error::invalid_type( - Unexpected::Char('a'), - &"null", - )) - } - - fn costume_deserialize( - _str: &str, - ) -> Result, serde_json::error::Error> - where - Self: Sized, - { - unimplemented!("not needed for testing") - } -} - -impl CustomHash for BlockchainDbErrorMock { - fn costume_hash(&self, _hasher: &mut dyn Hasher) { - unimplemented!("not needed for testing") - } -} - -impl CommonMethods> for BlockchainDbErrorMock { - fn partial_eq(&self, _other: &Box) -> bool { - unimplemented!("not needed for testing") - } - - fn dup(&self) -> Box { - unimplemented!("not needed for testing") - } -} diff --git a/node/src/blockchain/errors/validation_status.rs b/node/src/blockchain/errors/validation_status.rs index 4f8306d7b..c78f2cae6 100644 --- a/node/src/blockchain/errors/validation_status.rs +++ b/node/src/blockchain/errors/validation_status.rs @@ -1,6 +1,6 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::blockchain::errors::blockchain_db_error::BlockchainDbError; +use crate::blockchain::errors::rpc_errors::RpcErrorKind; use serde_derive::{Deserialize, Serialize}; use std::collections::HashMap; use std::time::SystemTime; @@ -14,21 +14,17 @@ pub enum ValidationStatus { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct PreviousAttempts { #[serde(flatten)] - inner: HashMap, ErrorStats>, + inner: HashMap, } impl PreviousAttempts { - pub fn new(error: Box, clock: &dyn ValidationFailureClock) -> Self { + pub fn new(error: RpcErrorKind, clock: &dyn ValidationFailureClock) -> Self { Self { inner: hashmap!(error => ErrorStats::now(clock)), } } - pub fn add_attempt( - mut self, - error: Box, - clock: &dyn ValidationFailureClock, - ) -> Self { + pub fn add_attempt(mut self, error: RpcErrorKind, clock: &dyn ValidationFailureClock) -> Self { self.inner .entry(error) .and_modify(|stats| stats.increment()) @@ -73,38 +69,23 @@ impl ValidationFailureClock for ValidationFailureClockReal { #[cfg(test)] mod tests { use super::*; - use crate::blockchain::errors::blockchain_db_error::app_rpc_web3_error_kind::AppRpcWeb3ErrorKind; #[test] fn previous_attempts_and_validation_failure_clock_work_together_fine() { let validation_failure_clock = ValidationFailureClockReal::default(); // new() let timestamp_a = SystemTime::now(); - let subject = PreviousAttempts::new( - Box::new(AppRpcWeb3ErrorKind::Decoder), - &validation_failure_clock, - ); + let subject = PreviousAttempts::new(RpcErrorKind::Decoder, &validation_failure_clock); // add_attempt() let timestamp_b = SystemTime::now(); - let subject = subject.add_attempt( - Box::new(AppRpcWeb3ErrorKind::Internal), - &validation_failure_clock, - ); + let subject = subject.add_attempt(RpcErrorKind::Internal, &validation_failure_clock); let timestamp_c = SystemTime::now(); - let subject = - subject.add_attempt(Box::new(AppRpcWeb3ErrorKind::IO), &validation_failure_clock); + let subject = subject.add_attempt(RpcErrorKind::IO, &validation_failure_clock); let timestamp_d = SystemTime::now(); - let subject = subject.add_attempt( - Box::new(AppRpcWeb3ErrorKind::Decoder), - &validation_failure_clock, - ); - let subject = - subject.add_attempt(Box::new(AppRpcWeb3ErrorKind::IO), &validation_failure_clock); + let subject = subject.add_attempt(RpcErrorKind::Decoder, &validation_failure_clock); + let subject = subject.add_attempt(RpcErrorKind::IO, &validation_failure_clock); - let decoder_error_stats = subject - .inner - .get(&(Box::new(AppRpcWeb3ErrorKind::Decoder) as Box)) - .unwrap(); + let decoder_error_stats = subject.inner.get(&RpcErrorKind::Decoder).unwrap(); assert!( timestamp_a <= decoder_error_stats.first_seen && decoder_error_stats.first_seen <= timestamp_b, @@ -114,10 +95,7 @@ mod tests { decoder_error_stats.first_seen ); assert_eq!(decoder_error_stats.attempts, 2); - let internal_error_stats = subject - .inner - .get(&(Box::new(AppRpcWeb3ErrorKind::Internal) as Box)) - .unwrap(); + let internal_error_stats = subject.inner.get(&RpcErrorKind::Internal).unwrap(); assert!( timestamp_b <= internal_error_stats.first_seen && internal_error_stats.first_seen <= timestamp_c, @@ -127,10 +105,7 @@ mod tests { internal_error_stats.first_seen ); assert_eq!(internal_error_stats.attempts, 1); - let io_error_stats = subject - .inner - .get(&(Box::new(AppRpcWeb3ErrorKind::IO) as Box)) - .unwrap(); + let io_error_stats = subject.inner.get(&RpcErrorKind::IO).unwrap(); assert!( timestamp_c <= io_error_stats.first_seen && io_error_stats.first_seen <= timestamp_d, "Was expected from {:?} to {:?} but was {:?}", @@ -139,9 +114,7 @@ mod tests { io_error_stats.first_seen ); assert_eq!(io_error_stats.attempts, 2); - let other_error_stats = subject - .inner - .get(&(Box::new(AppRpcWeb3ErrorKind::Signing) as Box)); + let other_error_stats = subject.inner.get(&RpcErrorKind::Signing); assert_eq!(other_error_stats, None); } } From dca1552f1aced6c3a0ff700db86e50c1b4d82785 Mon Sep 17 00:00:00 2001 From: Bert Date: Thu, 21 Aug 2025 00:00:52 +0200 Subject: [PATCH 44/61] GH-683: fixed screwed string replacement --- node/src/accountant/db_access_objects/payable_dao.rs | 4 ++-- node/src/accountant/mod.rs | 2 +- node/src/blockchain/blockchain_bridge.rs | 2 +- node/src/listener_handler.rs | 2 +- node/src/proxy_client/stream_reader.rs | 2 +- node/src/proxy_client/stream_writer.rs | 2 +- node/src/proxy_server/mod.rs | 2 +- node/src/stream_reader.rs | 2 +- node/src/stream_writer_sorted.rs | 2 +- node/src/stream_writer_unsorted.rs | 2 +- 10 files changed, 11 insertions(+), 11 deletions(-) diff --git a/node/src/accountant/db_access_objects/payable_dao.rs b/node/src/accountant/db_access_objects/payable_dao.rs index f6cebe4ef..c7d438a41 100644 --- a/node/src/accountant/db_access_objects/payable_dao.rs +++ b/node/src/accountant/db_access_objects/payable_dao.rs @@ -417,7 +417,7 @@ mod mark_pending_payable_associated_functions { }, Err(errs) => { let err_msg = format!( - "Multi-row update to mark pending payable hit these app_rpc_web3_error_kind: {:?}", + "Multi-row update to mark pending payable hit these errors: {:?}", errs ); Err(PayableDaoError::RusqliteError(err_msg)) @@ -874,7 +874,7 @@ mod tests { assert_eq!( result, Err(PayableDaoError::RusqliteError( - "Multi-row update to mark pending payable hit these app_rpc_web3_error_kind: [SqliteFailure(\ + "Multi-row update to mark pending payable hit these errors: [SqliteFailure(\ Error { code: ReadOnly, extended_code: 8 }, Some(\"attempt to write a readonly \ database\"))]" .to_string() diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index 845b43272..2ca502557 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -2387,7 +2387,7 @@ mod tests { payable_scanner_mock, ))); subject.ui_message_sub_opt = Some(ui_gateway_addr.recipient()); - // It must be populated because no app_rpc_web3_error_kind are tolerated at the RetryPayableScanner + // It must be populated because no errors are tolerated at the RetryPayableScanner // if automatic scans are on let response_skeleton_opt = Some(ResponseSkeleton { client_id: 789, diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index 83fb4b2e1..421fc6bd5 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -2188,7 +2188,7 @@ mod tests { /* blastapi - completely rejected call on Public endpoint as won't handle eth_getLogs method on public API - [{"jsonrpc":"2.0","id":2,"error":{"code":-32601,"message":"Method not found","data":{"method":""}}},{"jsonrpc":"2.0","id":1,"error":{"code":-32600,"message":"Invalid Request","data":{"message":"Cancelled due to validation app_rpc_web3_error_kind in batch request"}}}] (edited) + [{"jsonrpc":"2.0","id":2,"error":{"code":-32601,"message":"Method not found","data":{"method":""}}},{"jsonrpc":"2.0","id":1,"error":{"code":-32600,"message":"Invalid Request","data":{"message":"Cancelled due to validation errors in batch request"}}}] (edited) [8:50 AM] */ diff --git a/node/src/listener_handler.rs b/node/src/listener_handler.rs index f595e3a2c..1a63b9083 100644 --- a/node/src/listener_handler.rs +++ b/node/src/listener_handler.rs @@ -96,7 +96,7 @@ impl Future for ListenerHandlerReal { } Err(e) => { // TODO FIXME we should kill the entire Node if there is a fatal error in a listener_handler - // TODO this could be exploitable and inefficient: if we keep getting app_rpc_web3_error_kind, we go into a tight loop and do not return + // TODO this could be exploitable and inefficient: if we keep getting errors, we go into a tight loop and do not return error!(self.logger, "Could not accept connection: {}", e); } Ok(Async::NotReady) => return Ok(Async::NotReady), diff --git a/node/src/proxy_client/stream_reader.rs b/node/src/proxy_client/stream_reader.rs index 5abfe8641..992b58dbf 100644 --- a/node/src/proxy_client/stream_reader.rs +++ b/node/src/proxy_client/stream_reader.rs @@ -68,7 +68,7 @@ impl Future for StreamReader { self.shutdown(); return Err(()); } else { - // TODO this could be exploitable and inefficient: if we keep getting non-dead-stream app_rpc_web3_error_kind, we go into a tight loop and do not return + // TODO this could be exploitable and inefficient: if we keep getting non-dead-stream errors, we go into a tight loop and do not return warning!( self.logger, "Continuing after read error on stream from {}: {}", diff --git a/node/src/proxy_client/stream_writer.rs b/node/src/proxy_client/stream_writer.rs index 02be017f2..c9842741a 100644 --- a/node/src/proxy_client/stream_writer.rs +++ b/node/src/proxy_client/stream_writer.rs @@ -104,7 +104,7 @@ impl StreamWriter { ); return Err(()); } else { - // TODO this could be exploitable and inefficient: if we keep getting non-dead-stream app_rpc_web3_error_kind, we go into a tight loop and do not return + // TODO this could be exploitable and inefficient: if we keep getting non-dead-stream errors, we go into a tight loop and do not return warning!(self.logger, "Continuing after write error: {}", e); self.sequence_buffer.repush(packet); } diff --git a/node/src/proxy_server/mod.rs b/node/src/proxy_server/mod.rs index e465eebd3..d18a7ceba 100644 --- a/node/src/proxy_server/mod.rs +++ b/node/src/proxy_server/mod.rs @@ -353,7 +353,7 @@ impl ProxyServer { .try_send(TransmitDataMsg { endpoint: Endpoint::Socket(client_addr), last_data: true, - sequence_number: Some(0), // DNS resolution app_rpc_web3_error_kind always happen on the first request + sequence_number: Some(0), // DNS resolution errors always happen on the first request data: from_protocol(proxy_protocol) .server_impersonator() .dns_resolution_failure_response(hostname_opt), diff --git a/node/src/stream_reader.rs b/node/src/stream_reader.rs index a7f816ddd..34a7b62bd 100644 --- a/node/src/stream_reader.rs +++ b/node/src/stream_reader.rs @@ -68,7 +68,7 @@ impl Future for StreamReaderReal { self.shutdown(); return Err(()); } else { - // TODO this could be exploitable and inefficient: if we keep getting non-dead-stream app_rpc_web3_error_kind, we go into a tight loop and do not return + // TODO this could be exploitable and inefficient: if we keep getting non-dead-stream errors, we go into a tight loop and do not return warning!( self.logger, "Continuing after read error on stream {}: {}", diff --git a/node/src/stream_writer_sorted.rs b/node/src/stream_writer_sorted.rs index 26cde1ee9..cd41dd5dd 100644 --- a/node/src/stream_writer_sorted.rs +++ b/node/src/stream_writer_sorted.rs @@ -100,7 +100,7 @@ impl StreamWriterSorted { ); return WriteBufferStatus::StreamInError; } else { - // TODO this could be exploitable and inefficient: if we keep getting non-dead-stream app_rpc_web3_error_kind, we go into a tight loop and do not return + // TODO this could be exploitable and inefficient: if we keep getting non-dead-stream errors, we go into a tight loop and do not return warning!(self.logger, "Continuing after write error: {}", e); self.sequence_buffer.repush(packet); } diff --git a/node/src/stream_writer_unsorted.rs b/node/src/stream_writer_unsorted.rs index 172b5b997..3a6c73925 100644 --- a/node/src/stream_writer_unsorted.rs +++ b/node/src/stream_writer_unsorted.rs @@ -50,7 +50,7 @@ impl Future for StreamWriterUnsorted { return Err(()); } else { self.buf = Some(packet); - // TODO this could be... inefficient, if we keep getting non-dead-stream app_rpc_web3_error_kind. (we do not return) + // TODO this could be... inefficient, if we keep getting non-dead-stream errors. (we do not return) warning!(self.logger, "Continuing after write error: {}", e); } } From 819bdd97173ec74dbe92f0b053b14321a747bfa1 Mon Sep 17 00:00:00 2001 From: Bert Date: Thu, 21 Aug 2025 13:13:49 +0200 Subject: [PATCH 45/61] GH-683: finished fixing it --- .../db_access_objects/failed_payable_dao.rs | 18 ++- .../db_access_objects/sent_payable_dao.rs | 10 +- .../data_structures/errors.rs | 22 ++-- node/src/blockchain/errors/rpc_errors.rs | 113 +++++++++--------- .../blockchain/errors/validation_status.rs | 30 +++-- node/src/database/db_initializer.rs | 3 +- 6 files changed, 99 insertions(+), 97 deletions(-) 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 e06bef024..cbe13b3df 100644 --- a/node/src/accountant/db_access_objects/failed_payable_dao.rs +++ b/node/src/accountant/db_access_objects/failed_payable_dao.rs @@ -4,6 +4,7 @@ use crate::accountant::db_access_objects::utils::{ }; use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; use crate::accountant::{checked_conversion, comma_joined_stringifiable}; +use crate::blockchain::errors::rpc_errors::AppRpcErrorKind; use crate::blockchain::errors::validation_status::PreviousAttempts; use crate::database::rusqlite_wrappers::ConnectionWrapper; use itertools::Itertools; @@ -25,7 +26,7 @@ pub enum FailedPayableDaoError { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum FailureReason { - Submission(PreviousAttempts), + Submission(AppRpcErrorKind), Reverted, PendingTooLong, } @@ -381,7 +382,7 @@ mod tests { make_read_only_db_connection, FailedTxBuilder, }; use crate::accountant::db_access_objects::utils::current_unix_timestamp; - use crate::blockchain::errors::rpc_errors::RpcErrorKind; + use crate::blockchain::errors::rpc_errors::AppRpcErrorKind; use crate::blockchain::errors::validation_status::{ PreviousAttempts, ValidationFailureClockReal, }; @@ -591,10 +592,7 @@ mod tests { // Submission error assert_eq!( FailureReason::from_str(r#"{"Submission":{"Local":{"Decoder"}}}"#).unwrap(), - FailureReason::Submission(PreviousAttempts::new( - RpcErrorKind::Decoder, - &validation_failure_clock - )) + FailureReason::Submission(AppRpcErrorKind::Decoder) ); // Reverted @@ -643,7 +641,7 @@ mod tests { assert_eq!( FailureStatus::from_str(r#"{"RecheckRequired":{"Reattempting":{"ServerUnreachable":{"firstSeen":{"secs_since_epoch":1755080031,"nanos_since_epoch":612180914},"attempts":1}}}}"#).unwrap(), - FailureStatus::RecheckRequired(ValidationStatus::Reattempting( PreviousAttempts::new(RpcErrorKind::ServerUnreachable, &validation_failure_clock))) + FailureStatus::RecheckRequired(ValidationStatus::Reattempting( PreviousAttempts::new(AppRpcErrorKind::ServerUnreachable, &validation_failure_clock))) ); assert_eq!( @@ -726,7 +724,7 @@ mod tests { .reason(PendingTooLong) .status(RecheckRequired(ValidationStatus::Reattempting( PreviousAttempts::new( - RpcErrorKind::ServerUnreachable, + AppRpcErrorKind::ServerUnreachable, &ValidationFailureClockReal::default(), ), ))) @@ -782,7 +780,7 @@ mod tests { ( tx2.hash, RecheckRequired(ValidationStatus::Reattempting(PreviousAttempts::new( - RpcErrorKind::ServerUnreachable, + AppRpcErrorKind::ServerUnreachable, &ValidationFailureClockReal::default(), ))), ), @@ -799,7 +797,7 @@ mod tests { assert_eq!( updated_txs[1].status, RecheckRequired(ValidationStatus::Reattempting(PreviousAttempts::new( - RpcErrorKind::ServerUnreachable, + AppRpcErrorKind::ServerUnreachable, &ValidationFailureClockReal::default() ))) ); 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 24fb2880e..f50667a5e 100644 --- a/node/src/accountant/db_access_objects/sent_payable_dao.rs +++ b/node/src/accountant/db_access_objects/sent_payable_dao.rs @@ -441,7 +441,7 @@ mod tests { use crate::accountant::db_access_objects::sent_payable_dao::SentPayableDaoError::{EmptyInput, PartialExecution}; use crate::accountant::db_access_objects::test_utils::{make_read_only_db_connection, TxBuilder}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock}; - use crate::blockchain::errors::rpc_errors::RpcErrorKind; + use crate::blockchain::errors::rpc_errors::AppRpcErrorKind; use crate::blockchain::errors::validation_status::{PreviousAttempts, ValidationFailureClockReal}; use crate::blockchain::test_utils::{make_block_hash, make_tx_hash, ValidationFailureClockMock}; @@ -457,11 +457,11 @@ mod tests { .hash(make_tx_hash(2)) .status(TxStatus::Pending(ValidationStatus::Reattempting( PreviousAttempts::new( - RpcErrorKind::ServerUnreachable, + AppRpcErrorKind::ServerUnreachable, &ValidationFailureClockReal::default(), ) .add_attempt( - RpcErrorKind::ServerUnreachable, + AppRpcErrorKind::ServerUnreachable, &ValidationFailureClockReal::default(), ), ))) @@ -693,7 +693,7 @@ mod tests { .hash(make_tx_hash(2)) .status(TxStatus::Pending(ValidationStatus::Reattempting( PreviousAttempts::new( - RpcErrorKind::ServerUnreachable, + AppRpcErrorKind::ServerUnreachable, &ValidationFailureClockReal::default(), ), ))) @@ -1189,7 +1189,7 @@ mod tests { assert_eq!( TxStatus::from_str(r#"{"Pending":{"Reattempting":{"InvalidResponse":{"firstSeen":{"secs_since_epoch":12456,"nanos_since_epoch":0},"attempts":1}}}}"#).unwrap(), - TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new(RpcErrorKind::InvalidResponse, &validation_failure_clock))) + TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new(AppRpcErrorKind::InvalidResponse, &validation_failure_clock))) ); assert_eq!( diff --git a/node/src/blockchain/blockchain_interface/data_structures/errors.rs b/node/src/blockchain/blockchain_interface/data_structures/errors.rs index 027369717..ffdfa4c20 100644 --- a/node/src/blockchain/blockchain_interface/data_structures/errors.rs +++ b/node/src/blockchain/blockchain_interface/data_structures/errors.rs @@ -16,7 +16,7 @@ pub enum BlockchainInterfaceError { InvalidAddress, InvalidResponse, QueryFailed(String), - UninitializedBlockchainInterface, + UninitializedInterface, } impl Display for BlockchainInterfaceError { @@ -25,10 +25,8 @@ impl Display for BlockchainInterfaceError { Self::InvalidUrl => Either::Left("Invalid url"), Self::InvalidAddress => Either::Left("Invalid address"), Self::InvalidResponse => Either::Left("Invalid response"), - Self::QueryFailed(msg) => Either::Right(format!("Query failed: {}", msg)), //TODO this should also incorporate RpcError - Self::UninitializedBlockchainInterface => { - Either::Left(BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED) - } + Self::QueryFailed(msg) => Either::Right(format!("Query failed: {}", msg)), + Self::UninitializedInterface => Either::Left(BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED), }; write!(f, "Blockchain error: {}", err_spec) } @@ -42,7 +40,7 @@ pub enum PayableTransactionError { UnusableWallet(String), Signing(String), Sending { msg: String, hashes: Vec }, - UninitializedBlockchainInterface, + UninitializedInterface, } impl Display for PayableTransactionError { @@ -69,7 +67,7 @@ impl Display for PayableTransactionError { msg, comma_joined_stringifiable(hashes, |hash| format!("{:?}", hash)) ), - Self::UninitializedBlockchainInterface => { + Self::UninitializedInterface => { write!(f, "{}", BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED) } } @@ -81,7 +79,7 @@ pub enum BlockchainAgentBuildError { GasPrice(BlockchainInterfaceError), TransactionFeeBalance(Address, BlockchainInterfaceError), ServiceFeeBalance(Address, BlockchainInterfaceError), - UninitializedBlockchainInterface, + UninitializedInterface, } impl Display for BlockchainAgentBuildError { @@ -98,7 +96,7 @@ impl Display for BlockchainAgentBuildError { "masq balance for our earning wallet {:#x} due to {}", address, blockchain_e )), - Self::UninitializedBlockchainInterface => { + Self::UninitializedInterface => { Either::Right(BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED.to_string()) } }; @@ -144,7 +142,7 @@ mod tests { BlockchainInterfaceError::QueryFailed( "Don't query so often, it gives me a headache".to_string(), ), - BlockchainInterfaceError::UninitializedBlockchainInterface, + BlockchainInterfaceError::UninitializedInterface, ]; let actual_error_msgs = original_errors.iter().map(to_string).collect::>(); @@ -184,7 +182,7 @@ mod tests { msg: "Sending to cosmos belongs elsewhere".to_string(), hashes: vec![make_tx_hash(0x6f), make_tx_hash(0xde)], }, - PayableTransactionError::UninitializedBlockchainInterface, + PayableTransactionError::UninitializedInterface, ]; let actual_error_msgs = original_errors.iter().map(to_string).collect::>(); @@ -224,7 +222,7 @@ mod tests { wallet.address(), BlockchainInterfaceError::InvalidAddress, ), - BlockchainAgentBuildError::UninitializedBlockchainInterface, + BlockchainAgentBuildError::UninitializedInterface, ]; let actual_error_msgs = original_errors.iter().map(to_string).collect::>(); diff --git a/node/src/blockchain/errors/rpc_errors.rs b/node/src/blockchain/errors/rpc_errors.rs index 21817e45a..4d8482e17 100644 --- a/node/src/blockchain/errors/rpc_errors.rs +++ b/node/src/blockchain/errors/rpc_errors.rs @@ -3,8 +3,9 @@ use serde_derive::{Deserialize, Serialize}; use web3::error::Error as Web3Error; +// Prefixed with App to clearly distinguish app-specific errors from library errors. #[derive(Debug, Clone, PartialEq, Eq)] -pub enum RpcError { +pub enum AppRpcError { Local(LocalError), Remote(RemoteError), } @@ -26,34 +27,34 @@ pub enum RemoteError { } // EVM based errors -impl From for RpcError { +impl From for AppRpcError { fn from(error: Web3Error) -> Self { match error { // Local Errors - Web3Error::Decoder(error) => RpcError::Local(LocalError::Decoder(error)), - Web3Error::Internal => RpcError::Local(LocalError::Internal), - Web3Error::Io(error) => RpcError::Local(LocalError::Io(error.to_string())), + Web3Error::Decoder(error) => AppRpcError::Local(LocalError::Decoder(error)), + Web3Error::Internal => AppRpcError::Local(LocalError::Internal), + Web3Error::Io(error) => AppRpcError::Local(LocalError::Io(error.to_string())), Web3Error::Signing(error) => { // This variant cannot be tested due to import limitations. - RpcError::Local(LocalError::Signing(error.to_string())) + AppRpcError::Local(LocalError::Signing(error.to_string())) } - Web3Error::Transport(error) => RpcError::Local(LocalError::Transport(error)), + Web3Error::Transport(error) => AppRpcError::Local(LocalError::Transport(error)), // Api Errors Web3Error::InvalidResponse(response) => { - RpcError::Remote(RemoteError::InvalidResponse(response)) + AppRpcError::Remote(RemoteError::InvalidResponse(response)) } - Web3Error::Rpc(web3_rpc_error) => RpcError::Remote(RemoteError::Web3RpcError { + Web3Error::Rpc(web3_rpc_error) => AppRpcError::Remote(RemoteError::Web3RpcError { code: web3_rpc_error.code.code(), message: web3_rpc_error.message, }), - Web3Error::Unreachable => RpcError::Remote(RemoteError::Unreachable), + Web3Error::Unreachable => AppRpcError::Remote(RemoteError::Unreachable), } } } #[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum RpcErrorKind { +pub enum AppRpcErrorKind { // Local Decoder, Internal, @@ -67,17 +68,17 @@ pub enum RpcErrorKind { Web3RpcError(i64), // Keep only the stable error code } -impl From<&RpcError> for RpcErrorKind { - fn from(err: &RpcError) -> Self { +impl From<&AppRpcError> for AppRpcErrorKind { + fn from(err: &AppRpcError) -> Self { match err { - RpcError::Local(local) => match local { + AppRpcError::Local(local) => match local { LocalError::Decoder(_) => Self::Decoder, LocalError::Internal => Self::Internal, LocalError::Io(_) => Self::IO, LocalError::Signing(_) => Self::Signing, LocalError::Transport(_) => Self::Transport, }, - RpcError::Remote(remote) => match remote { + AppRpcError::Remote(remote) => match remote { RemoteError::InvalidResponse(_) => Self::InvalidResponse, RemoteError::Unreachable => Self::ServerUnreachable, RemoteError::Web3RpcError { code, .. } => Self::Web3RpcError(*code), @@ -88,98 +89,100 @@ impl From<&RpcError> for RpcErrorKind { #[cfg(test)] mod tests { - use crate::blockchain::errors::rpc_errors::{LocalError, RemoteError, RpcError, RpcErrorKind}; + use crate::blockchain::errors::rpc_errors::{ + AppRpcError, AppRpcErrorKind, LocalError, RemoteError, + }; use web3::error::Error as Web3Error; #[test] fn web3_error_to_failure_reason_conversion_works() { // Local Errors assert_eq!( - RpcError::from(Web3Error::Decoder("Decoder error".to_string())), - RpcError::Local(LocalError::Decoder("Decoder error".to_string())) + AppRpcError::from(Web3Error::Decoder("Decoder error".to_string())), + AppRpcError::Local(LocalError::Decoder("Decoder error".to_string())) ); assert_eq!( - RpcError::from(Web3Error::Internal), - RpcError::Local(LocalError::Internal) + AppRpcError::from(Web3Error::Internal), + AppRpcError::Local(LocalError::Internal) ); assert_eq!( - RpcError::from(Web3Error::Io(std::io::Error::new( + AppRpcError::from(Web3Error::Io(std::io::Error::new( std::io::ErrorKind::Other, "IO error" ))), - RpcError::Local(LocalError::Io("IO error".to_string())) + AppRpcError::Local(LocalError::Io("IO error".to_string())) ); assert_eq!( - RpcError::from(Web3Error::Transport("Transport error".to_string())), - RpcError::Local(LocalError::Transport("Transport error".to_string())) + AppRpcError::from(Web3Error::Transport("Transport error".to_string())), + AppRpcError::Local(LocalError::Transport("Transport error".to_string())) ); // Api Errors assert_eq!( - RpcError::from(Web3Error::InvalidResponse("Invalid response".to_string())), - RpcError::Remote(RemoteError::InvalidResponse("Invalid response".to_string())) + AppRpcError::from(Web3Error::InvalidResponse("Invalid response".to_string())), + AppRpcError::Remote(RemoteError::InvalidResponse("Invalid response".to_string())) ); assert_eq!( - RpcError::from(Web3Error::Rpc(jsonrpc_core::types::error::Error { + AppRpcError::from(Web3Error::Rpc(jsonrpc_core::types::error::Error { code: jsonrpc_core::types::error::ErrorCode::ServerError(42), message: "RPC error".to_string(), data: None, })), - RpcError::Remote(RemoteError::Web3RpcError { + AppRpcError::Remote(RemoteError::Web3RpcError { code: 42, message: "RPC error".to_string(), }) ); assert_eq!( - RpcError::from(Web3Error::Unreachable), - RpcError::Remote(RemoteError::Unreachable) + AppRpcError::from(Web3Error::Unreachable), + AppRpcError::Remote(RemoteError::Unreachable) ); } #[test] fn conversion_between_app_rpc_error_and_app_rpc_error_kind_works() { assert_eq!( - RpcErrorKind::from(&RpcError::Local(LocalError::Decoder( + AppRpcErrorKind::from(&AppRpcError::Local(LocalError::Decoder( "Decoder error".to_string() ))), - RpcErrorKind::Decoder + AppRpcErrorKind::Decoder ); assert_eq!( - RpcErrorKind::from(&RpcError::Local(LocalError::Internal)), - RpcErrorKind::Internal + AppRpcErrorKind::from(&AppRpcError::Local(LocalError::Internal)), + AppRpcErrorKind::Internal ); assert_eq!( - RpcErrorKind::from(&RpcError::Local(LocalError::Io("IO error".to_string()))), - RpcErrorKind::IO + AppRpcErrorKind::from(&AppRpcError::Local(LocalError::Io("IO error".to_string()))), + AppRpcErrorKind::IO ); assert_eq!( - RpcErrorKind::from(&RpcError::Local(LocalError::Signing( + AppRpcErrorKind::from(&AppRpcError::Local(LocalError::Signing( "Signing error".to_string() ))), - RpcErrorKind::Signing + AppRpcErrorKind::Signing ); assert_eq!( - RpcErrorKind::from(&RpcError::Local(LocalError::Transport( + AppRpcErrorKind::from(&AppRpcError::Local(LocalError::Transport( "Transport error".to_string() ))), - RpcErrorKind::Transport + AppRpcErrorKind::Transport ); assert_eq!( - RpcErrorKind::from(&RpcError::Remote(RemoteError::InvalidResponse( + AppRpcErrorKind::from(&AppRpcError::Remote(RemoteError::InvalidResponse( "Invalid response".to_string() ))), - RpcErrorKind::InvalidResponse + AppRpcErrorKind::InvalidResponse ); assert_eq!( - RpcErrorKind::from(&RpcError::Remote(RemoteError::Unreachable)), - RpcErrorKind::ServerUnreachable + AppRpcErrorKind::from(&AppRpcError::Remote(RemoteError::Unreachable)), + AppRpcErrorKind::ServerUnreachable ); assert_eq!( - RpcErrorKind::from(&RpcError::Remote(RemoteError::Web3RpcError { + AppRpcErrorKind::from(&AppRpcError::Remote(RemoteError::Web3RpcError { code: 55, message: "Booga".to_string() })), - RpcErrorKind::Web3RpcError(55) + AppRpcErrorKind::Web3RpcError(55) ); } @@ -187,20 +190,20 @@ mod tests { fn app_rpc_error_kind_serialization_deserialization() { let errors = vec![ // Local Errors - RpcErrorKind::Decoder, - RpcErrorKind::Internal, - RpcErrorKind::IO, - RpcErrorKind::Signing, - RpcErrorKind::Transport, + AppRpcErrorKind::Decoder, + AppRpcErrorKind::Internal, + AppRpcErrorKind::IO, + AppRpcErrorKind::Signing, + AppRpcErrorKind::Transport, // Remote Errors - RpcErrorKind::InvalidResponse, - RpcErrorKind::ServerUnreachable, - RpcErrorKind::Web3RpcError(42), + AppRpcErrorKind::InvalidResponse, + AppRpcErrorKind::ServerUnreachable, + AppRpcErrorKind::Web3RpcError(42), ]; errors.into_iter().for_each(|error| { let serialized = serde_json::to_string(&error).unwrap(); - let deserialized: RpcErrorKind = serde_json::from_str(&serialized).unwrap(); + let deserialized: AppRpcErrorKind = serde_json::from_str(&serialized).unwrap(); assert_eq!( error, deserialized, "Failed serde attempt for {:?} that should look \ diff --git a/node/src/blockchain/errors/validation_status.rs b/node/src/blockchain/errors/validation_status.rs index c78f2cae6..3a3574cac 100644 --- a/node/src/blockchain/errors/validation_status.rs +++ b/node/src/blockchain/errors/validation_status.rs @@ -1,6 +1,6 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::blockchain::errors::rpc_errors::RpcErrorKind; +use crate::blockchain::errors::rpc_errors::AppRpcErrorKind; use serde_derive::{Deserialize, Serialize}; use std::collections::HashMap; use std::time::SystemTime; @@ -14,17 +14,21 @@ pub enum ValidationStatus { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct PreviousAttempts { #[serde(flatten)] - inner: HashMap, + inner: HashMap, } impl PreviousAttempts { - pub fn new(error: RpcErrorKind, clock: &dyn ValidationFailureClock) -> Self { + pub fn new(error: AppRpcErrorKind, clock: &dyn ValidationFailureClock) -> Self { Self { inner: hashmap!(error => ErrorStats::now(clock)), } } - pub fn add_attempt(mut self, error: RpcErrorKind, clock: &dyn ValidationFailureClock) -> Self { + pub fn add_attempt( + mut self, + error: AppRpcErrorKind, + clock: &dyn ValidationFailureClock, + ) -> Self { self.inner .entry(error) .and_modify(|stats| stats.increment()) @@ -75,17 +79,17 @@ mod tests { let validation_failure_clock = ValidationFailureClockReal::default(); // new() let timestamp_a = SystemTime::now(); - let subject = PreviousAttempts::new(RpcErrorKind::Decoder, &validation_failure_clock); + let subject = PreviousAttempts::new(AppRpcErrorKind::Decoder, &validation_failure_clock); // add_attempt() let timestamp_b = SystemTime::now(); - let subject = subject.add_attempt(RpcErrorKind::Internal, &validation_failure_clock); + let subject = subject.add_attempt(AppRpcErrorKind::Internal, &validation_failure_clock); let timestamp_c = SystemTime::now(); - let subject = subject.add_attempt(RpcErrorKind::IO, &validation_failure_clock); + let subject = subject.add_attempt(AppRpcErrorKind::IO, &validation_failure_clock); let timestamp_d = SystemTime::now(); - let subject = subject.add_attempt(RpcErrorKind::Decoder, &validation_failure_clock); - let subject = subject.add_attempt(RpcErrorKind::IO, &validation_failure_clock); + let subject = subject.add_attempt(AppRpcErrorKind::Decoder, &validation_failure_clock); + let subject = subject.add_attempt(AppRpcErrorKind::IO, &validation_failure_clock); - let decoder_error_stats = subject.inner.get(&RpcErrorKind::Decoder).unwrap(); + let decoder_error_stats = subject.inner.get(&AppRpcErrorKind::Decoder).unwrap(); assert!( timestamp_a <= decoder_error_stats.first_seen && decoder_error_stats.first_seen <= timestamp_b, @@ -95,7 +99,7 @@ mod tests { decoder_error_stats.first_seen ); assert_eq!(decoder_error_stats.attempts, 2); - let internal_error_stats = subject.inner.get(&RpcErrorKind::Internal).unwrap(); + let internal_error_stats = subject.inner.get(&AppRpcErrorKind::Internal).unwrap(); assert!( timestamp_b <= internal_error_stats.first_seen && internal_error_stats.first_seen <= timestamp_c, @@ -105,7 +109,7 @@ mod tests { internal_error_stats.first_seen ); assert_eq!(internal_error_stats.attempts, 1); - let io_error_stats = subject.inner.get(&RpcErrorKind::IO).unwrap(); + let io_error_stats = subject.inner.get(&AppRpcErrorKind::IO).unwrap(); assert!( timestamp_c <= io_error_stats.first_seen && io_error_stats.first_seen <= timestamp_d, "Was expected from {:?} to {:?} but was {:?}", @@ -114,7 +118,7 @@ mod tests { io_error_stats.first_seen ); assert_eq!(io_error_stats.attempts, 2); - let other_error_stats = subject.inner.get(&RpcErrorKind::Signing); + let other_error_stats = subject.inner.get(&AppRpcErrorKind::Signing); assert_eq!(other_error_stats, None); } } diff --git a/node/src/database/db_initializer.rs b/node/src/database/db_initializer.rs index cbf6008c0..674786766 100644 --- a/node/src/database/db_initializer.rs +++ b/node/src/database/db_initializer.rs @@ -802,8 +802,7 @@ mod tests { gas_price_wei_high_b, gas_price_wei_low_b, nonce, - block_hash, - block_number + status FROM sent_payable", ) .unwrap(); From a9505afe9d92b3bf6c418a266378810f2fd8ae2f Mon Sep 17 00:00:00 2001 From: Bert Date: Thu, 21 Aug 2025 16:46:14 +0200 Subject: [PATCH 46/61] GH-683: another fix...BlockchainError --- .../db_access_objects/failed_payable_dao.rs | 10 ++-- .../db_access_objects/sent_payable_dao.rs | 9 ++-- node/src/blockchain/errors/internal_errors.rs | 4 +- node/src/blockchain/errors/mod.rs | 44 +++++++++++++++ .../blockchain/errors/validation_status.rs | 54 ++++++++++++++----- 5 files changed, 98 insertions(+), 23 deletions(-) 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 cbe13b3df..296bfe8d2 100644 --- a/node/src/accountant/db_access_objects/failed_payable_dao.rs +++ b/node/src/accountant/db_access_objects/failed_payable_dao.rs @@ -386,6 +386,7 @@ mod tests { use crate::blockchain::errors::validation_status::{ PreviousAttempts, ValidationFailureClockReal, }; + use crate::blockchain::errors::BlockchainErrorKind; use crate::blockchain::test_utils::{make_tx_hash, ValidationFailureClockMock}; use crate::database::db_initializer::{ DbInitializationConfig, DbInitializer, DbInitializerReal, @@ -588,7 +589,6 @@ mod tests { #[test] fn failure_reason_from_str_works() { - let validation_failure_clock = ValidationFailureClockMock::default(); // Submission error assert_eq!( FailureReason::from_str(r#"{"Submission":{"Local":{"Decoder"}}}"#).unwrap(), @@ -641,7 +641,7 @@ mod tests { assert_eq!( FailureStatus::from_str(r#"{"RecheckRequired":{"Reattempting":{"ServerUnreachable":{"firstSeen":{"secs_since_epoch":1755080031,"nanos_since_epoch":612180914},"attempts":1}}}}"#).unwrap(), - FailureStatus::RecheckRequired(ValidationStatus::Reattempting( PreviousAttempts::new(AppRpcErrorKind::ServerUnreachable, &validation_failure_clock))) + FailureStatus::RecheckRequired(ValidationStatus::Reattempting( PreviousAttempts::new(BlockchainErrorKind::AppRpc(AppRpcErrorKind::ServerUnreachable), &validation_failure_clock))) ); assert_eq!( @@ -724,7 +724,7 @@ mod tests { .reason(PendingTooLong) .status(RecheckRequired(ValidationStatus::Reattempting( PreviousAttempts::new( - AppRpcErrorKind::ServerUnreachable, + BlockchainErrorKind::AppRpc(AppRpcErrorKind::ServerUnreachable), &ValidationFailureClockReal::default(), ), ))) @@ -780,7 +780,7 @@ mod tests { ( tx2.hash, RecheckRequired(ValidationStatus::Reattempting(PreviousAttempts::new( - AppRpcErrorKind::ServerUnreachable, + BlockchainErrorKind::AppRpc(AppRpcErrorKind::ServerUnreachable), &ValidationFailureClockReal::default(), ))), ), @@ -797,7 +797,7 @@ mod tests { assert_eq!( updated_txs[1].status, RecheckRequired(ValidationStatus::Reattempting(PreviousAttempts::new( - AppRpcErrorKind::ServerUnreachable, + BlockchainErrorKind::AppRpc(AppRpcErrorKind::ServerUnreachable), &ValidationFailureClockReal::default() ))) ); 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 f50667a5e..a79e8ffbd 100644 --- a/node/src/accountant/db_access_objects/sent_payable_dao.rs +++ b/node/src/accountant/db_access_objects/sent_payable_dao.rs @@ -441,6 +441,7 @@ mod tests { use crate::accountant::db_access_objects::sent_payable_dao::SentPayableDaoError::{EmptyInput, PartialExecution}; use crate::accountant::db_access_objects::test_utils::{make_read_only_db_connection, TxBuilder}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock}; + use crate::blockchain::errors::BlockchainErrorKind; use crate::blockchain::errors::rpc_errors::AppRpcErrorKind; use crate::blockchain::errors::validation_status::{PreviousAttempts, ValidationFailureClockReal}; use crate::blockchain::test_utils::{make_block_hash, make_tx_hash, ValidationFailureClockMock}; @@ -457,11 +458,11 @@ mod tests { .hash(make_tx_hash(2)) .status(TxStatus::Pending(ValidationStatus::Reattempting( PreviousAttempts::new( - AppRpcErrorKind::ServerUnreachable, + BlockchainErrorKind::AppRpc(AppRpcErrorKind::ServerUnreachable), &ValidationFailureClockReal::default(), ) .add_attempt( - AppRpcErrorKind::ServerUnreachable, + BlockchainErrorKind::AppRpc(AppRpcErrorKind::ServerUnreachable), &ValidationFailureClockReal::default(), ), ))) @@ -693,7 +694,7 @@ mod tests { .hash(make_tx_hash(2)) .status(TxStatus::Pending(ValidationStatus::Reattempting( PreviousAttempts::new( - AppRpcErrorKind::ServerUnreachable, + BlockchainErrorKind::AppRpc(AppRpcErrorKind::ServerUnreachable), &ValidationFailureClockReal::default(), ), ))) @@ -1189,7 +1190,7 @@ mod tests { assert_eq!( TxStatus::from_str(r#"{"Pending":{"Reattempting":{"InvalidResponse":{"firstSeen":{"secs_since_epoch":12456,"nanos_since_epoch":0},"attempts":1}}}}"#).unwrap(), - TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new(AppRpcErrorKind::InvalidResponse, &validation_failure_clock))) + TxStatus::Pending(ValidationStatus::Reattempting(PreviousAttempts::new(BlockchainErrorKind::AppRpc(AppRpcErrorKind::InvalidResponse), &validation_failure_clock))) ); assert_eq!( diff --git a/node/src/blockchain/errors/internal_errors.rs b/node/src/blockchain/errors/internal_errors.rs index 59ca6fd3a..fb6a4bf63 100644 --- a/node/src/blockchain/errors/internal_errors.rs +++ b/node/src/blockchain/errors/internal_errors.rs @@ -2,12 +2,12 @@ use serde_derive::{Deserialize, Serialize}; -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Eq)] pub enum InternalError { PendingTooLongNotReplaced, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum InternalErrorKind { PendingTooLongNotReplaced, } diff --git a/node/src/blockchain/errors/mod.rs b/node/src/blockchain/errors/mod.rs index 5fe595769..ab18ae9df 100644 --- a/node/src/blockchain/errors/mod.rs +++ b/node/src/blockchain/errors/mod.rs @@ -1,5 +1,49 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. +use crate::blockchain::errors::internal_errors::{InternalError, InternalErrorKind}; +use crate::blockchain::errors::rpc_errors::{AppRpcError, AppRpcErrorKind}; +use serde_derive::{Deserialize, Serialize}; + pub mod internal_errors; pub mod rpc_errors; pub mod validation_status; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum BlockchainError { + AppRpc(AppRpcError), + Internal(InternalError), +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum BlockchainErrorKind { + AppRpc(AppRpcErrorKind), + Internal(InternalErrorKind), +} + +#[cfg(test)] +mod tests { + use crate::blockchain::errors::internal_errors::InternalErrorKind; + use crate::blockchain::errors::rpc_errors::AppRpcErrorKind; + use crate::blockchain::errors::BlockchainErrorKind; + + #[test] + fn blockchain_error_serialization_deserialization() { + vec![ + ( + BlockchainErrorKind::AppRpc(AppRpcErrorKind::Decoder), + "{\"AppRpc\":\"Decoder\"}", + ), + ( + BlockchainErrorKind::Internal(InternalErrorKind::PendingTooLongNotReplaced), + "{\"Internal\":\"PendingTooLongNotReplaced\"}", + ), + ] + .into_iter() + .for_each(|(err, expected_json)| { + let json = serde_json::to_string(&err).unwrap(); + assert_eq!(json, expected_json); + let deserialized_err = serde_json::from_str::(&json).unwrap(); + assert_eq!(deserialized_err, err); + }) + } +} diff --git a/node/src/blockchain/errors/validation_status.rs b/node/src/blockchain/errors/validation_status.rs index 3a3574cac..72ca28346 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::rpc_errors::AppRpcErrorKind; +use crate::blockchain::errors::BlockchainErrorKind; use serde_derive::{Deserialize, Serialize}; use std::collections::HashMap; use std::time::SystemTime; @@ -14,11 +15,11 @@ pub enum ValidationStatus { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct PreviousAttempts { #[serde(flatten)] - inner: HashMap, + inner: HashMap, } impl PreviousAttempts { - pub fn new(error: AppRpcErrorKind, clock: &dyn ValidationFailureClock) -> Self { + pub fn new(error: BlockchainErrorKind, clock: &dyn ValidationFailureClock) -> Self { Self { inner: hashmap!(error => ErrorStats::now(clock)), } @@ -26,7 +27,7 @@ impl PreviousAttempts { pub fn add_attempt( mut self, - error: AppRpcErrorKind, + error: BlockchainErrorKind, clock: &dyn ValidationFailureClock, ) -> Self { self.inner @@ -73,23 +74,42 @@ impl ValidationFailureClock for ValidationFailureClockReal { #[cfg(test)] mod tests { use super::*; + use crate::blockchain::errors::internal_errors::InternalErrorKind; #[test] fn previous_attempts_and_validation_failure_clock_work_together_fine() { let validation_failure_clock = ValidationFailureClockReal::default(); // new() let timestamp_a = SystemTime::now(); - let subject = PreviousAttempts::new(AppRpcErrorKind::Decoder, &validation_failure_clock); + let subject = PreviousAttempts::new( + BlockchainErrorKind::AppRpc(AppRpcErrorKind::Decoder), + &validation_failure_clock, + ); // add_attempt() let timestamp_b = SystemTime::now(); - let subject = subject.add_attempt(AppRpcErrorKind::Internal, &validation_failure_clock); + let subject = subject.add_attempt( + BlockchainErrorKind::Internal(InternalErrorKind::PendingTooLongNotReplaced), + &validation_failure_clock, + ); let timestamp_c = SystemTime::now(); - let subject = subject.add_attempt(AppRpcErrorKind::IO, &validation_failure_clock); + let subject = subject.add_attempt( + BlockchainErrorKind::AppRpc(AppRpcErrorKind::IO), + &validation_failure_clock, + ); let timestamp_d = SystemTime::now(); - let subject = subject.add_attempt(AppRpcErrorKind::Decoder, &validation_failure_clock); - let subject = subject.add_attempt(AppRpcErrorKind::IO, &validation_failure_clock); + let subject = subject.add_attempt( + BlockchainErrorKind::AppRpc(AppRpcErrorKind::Decoder), + &validation_failure_clock, + ); + let subject = subject.add_attempt( + BlockchainErrorKind::AppRpc(AppRpcErrorKind::IO), + &validation_failure_clock, + ); - let decoder_error_stats = subject.inner.get(&AppRpcErrorKind::Decoder).unwrap(); + let decoder_error_stats = subject + .inner + .get(&BlockchainErrorKind::AppRpc(AppRpcErrorKind::Decoder)) + .unwrap(); assert!( timestamp_a <= decoder_error_stats.first_seen && decoder_error_stats.first_seen <= timestamp_b, @@ -99,7 +119,12 @@ mod tests { decoder_error_stats.first_seen ); assert_eq!(decoder_error_stats.attempts, 2); - let internal_error_stats = subject.inner.get(&AppRpcErrorKind::Internal).unwrap(); + let internal_error_stats = subject + .inner + .get(&BlockchainErrorKind::Internal( + InternalErrorKind::PendingTooLongNotReplaced, + )) + .unwrap(); assert!( timestamp_b <= internal_error_stats.first_seen && internal_error_stats.first_seen <= timestamp_c, @@ -109,7 +134,10 @@ mod tests { internal_error_stats.first_seen ); assert_eq!(internal_error_stats.attempts, 1); - let io_error_stats = subject.inner.get(&AppRpcErrorKind::IO).unwrap(); + let io_error_stats = subject + .inner + .get(&BlockchainErrorKind::AppRpc(AppRpcErrorKind::IO)) + .unwrap(); assert!( timestamp_c <= io_error_stats.first_seen && io_error_stats.first_seen <= timestamp_d, "Was expected from {:?} to {:?} but was {:?}", @@ -118,7 +146,9 @@ mod tests { io_error_stats.first_seen ); assert_eq!(io_error_stats.attempts, 2); - let other_error_stats = subject.inner.get(&AppRpcErrorKind::Signing); + let other_error_stats = subject + .inner + .get(&BlockchainErrorKind::AppRpc(AppRpcErrorKind::Signing)); assert_eq!(other_error_stats, None); } } From bff1382bd153a227e0743a899f2eaf3889b1778f Mon Sep 17 00:00:00 2001 From: Bert Date: Fri, 22 Aug 2025 12:48:49 +0200 Subject: [PATCH 47/61] GH-642: added unreachable! --- .../tx_receipt_interpreter.rs | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) 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 f38ff4953..64c4ec661 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 @@ -84,6 +84,13 @@ impl TxReceiptInterpreter { scan_report.register_new_failure(failed_tx); } TxByTable::FailedPayable(failed_tx) => { + if failed_tx.reason != FailureReason::PendingTooLong { + unreachable!( + "Transaction is both pending and failed (failure reason: '{:?}'). Should be \ + possible only with the reason 'PendingTooLong'", + failed_tx.reason + ) + } let replacement_tx = sent_payable_dao .retrieve_txs(Some(RetrieveCondition::ByNonce(vec![failed_tx.nonce]))); let replacement_tx_hash = replacement_tx @@ -103,9 +110,6 @@ impl TxReceiptInterpreter { failed_tx.hash, replacement_tx_hash ); - if failed_tx.reason != FailureReason::PendingTooLong { - todo!("panic here") - } scan_report.register_rpc_failure(FailedValidationByTable::FailedPayable( FailedValidation::new( failed_tx.hash, @@ -508,6 +512,7 @@ mod tests { let failed_tx_nonce = failed_tx.nonce; failed_tx.hash = tx_hash; failed_tx.status = FailureStatus::RecheckRequired(ValidationStatus::Waiting); + failed_tx.reason = FailureReason::PendingTooLong; let scan_report = ReceiptScanReport::default(); let result = TxReceiptInterpreter::handle_still_pending_tx( @@ -547,6 +552,31 @@ mod tests { )); } + #[test] + #[should_panic( + expected = "internal error: entered unreachable code: Transaction is both pending \ + and failed (failure reason: 'Reverted'). Should be possible only with the reason 'PendingTooLong'" + )] + fn interprets_failed_tx_recheck_as_still_pending_while_the_failure_reason_wasnt_pending_too_long( + ) { + let mut newer_sent_tx_for_older_failed_tx = make_sent_tx(2244); + newer_sent_tx_for_older_failed_tx.hash = make_tx_hash(0x7c6); + let sent_payable_dao = SentPayableDaoMock::new(); + let tx_hash = make_tx_hash(0x913); + let mut failed_tx = make_failed_tx(789); + failed_tx.hash = tx_hash; + failed_tx.status = FailureStatus::RecheckRequired(ValidationStatus::Waiting); + failed_tx.reason = FailureReason::Reverted; + let scan_report = ReceiptScanReport::default(); + + let _ = TxReceiptInterpreter::handle_still_pending_tx( + scan_report, + TxByTable::FailedPayable(failed_tx), + &sent_payable_dao, + &Logger::new("test"), + ); + } + #[test] #[should_panic( expected = "Attempted to display a replacement tx for 0x000000000000000000000000000\ From 9a13c561eb58cd96efead032db7842e50a054778 Mon Sep 17 00:00:00 2001 From: Bert Date: Fri, 5 Sep 2025 14:22:44 +0200 Subject: [PATCH 48/61] GH-642: before bigger issue addressing --- .../db_access_objects/failed_payable_dao.rs | 5 ++-- .../tx_receipt_interpreter.rs | 2 +- .../scanners/pending_payable_scanner/utils.rs | 2 +- .../src/accountant/scanners/scanners_utils.rs | 23 ++----------------- .../test_utils/transaction_wrapper_mock.rs | 2 +- 5 files changed, 8 insertions(+), 26 deletions(-) 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 49bf44b82..9f585122c 100644 --- a/node/src/accountant/db_access_objects/failed_payable_dao.rs +++ b/node/src/accountant/db_access_objects/failed_payable_dao.rs @@ -28,6 +28,7 @@ pub enum FailedPayableDaoError { pub enum FailureReason { Submission(AppRpcErrorKind), Reverted, + Unrecognized, PendingTooLong, } @@ -203,10 +204,10 @@ impl FailedPayableDao for FailedPayableDaoReal<'_> { ) VALUES {}", comma_joined_stringifiable(txs, |tx| { let amount_checked = checked_conversion::(tx.amount_minor); - let gas_price_wei_checked = checked_conversion::(tx.gas_price_minor); + let gas_price_minor_checked = checked_conversion::(tx.gas_price_minor); let (amount_high_b, amount_low_b) = BigIntDivider::deconstruct(amount_checked); let (gas_price_wei_high_b, gas_price_wei_low_b) = - BigIntDivider::deconstruct(gas_price_wei_checked); + BigIntDivider::deconstruct(gas_price_minor_checked); format!( "('{:?}', '{:?}', {}, {}, {}, {}, {}, {}, '{}', '{}')", tx.hash, 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 64c4ec661..5236cb8af 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 @@ -11,7 +11,7 @@ use crate::accountant::scanners::pending_payable_scanner::utils::{ }; use crate::accountant::scanners::pending_payable_scanner::PendingPayableScanner; use crate::blockchain::blockchain_interface::data_structures::{ - BlockchainTxFailure, StatusReadFromReceiptCheck, TxBlock, TxReceiptError, TxReceiptResult, + StatusReadFromReceiptCheck, TxBlock, TxReceiptError, TxReceiptResult, }; use crate::blockchain::errors::internal_errors::InternalErrorKind; use crate::blockchain::errors::BlockchainErrorKind; diff --git a/node/src/accountant/scanners/pending_payable_scanner/utils.rs b/node/src/accountant/scanners/pending_payable_scanner/utils.rs index 3fbff3022..ee19542d4 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/utils.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/utils.rs @@ -354,7 +354,7 @@ pub enum TxHashByTable { impl From for FailureReason { fn from(failure: BlockchainTxFailure) -> Self { match failure { - BlockchainTxFailure::Unrecognized => FailureReason::Reverted, + BlockchainTxFailure::Unrecognized => FailureReason::Unrecognized, } } } diff --git a/node/src/accountant/scanners/scanners_utils.rs b/node/src/accountant/scanners/scanners_utils.rs index 1188047ab..f3eff674b 100644 --- a/node/src/accountant/scanners/scanners_utils.rs +++ b/node/src/accountant/scanners/scanners_utils.rs @@ -252,25 +252,6 @@ pub mod payable_scanner_utils { } } - // pub fn mark_pending_payable_fatal_error( - // sent_payments: &[&PendingPayable], - // nonexistent: &[PendingPayableMissingInDb], - // error: PayableDaoError, - // missing_fingerprints_msg_maker: fn(&[PendingPayableMissingInDb]) -> String, - // logger: &Logger, - // ) { - // if !nonexistent.is_empty() { - // error!(logger, "{}", missing_fingerprints_msg_maker(nonexistent)) - // }; - // panic!( - // "Unable to create a mark in the payable table for wallets {} due to {:?}", - // comma_joined_stringifiable(sent_payments, |pending_p| pending_p - // .recipient_wallet - // .to_string()), - // error - // ) - // } - pub fn err_msg_for_failure_with_expected_but_missing_sent_tx_record( nonexistent: Vec, serialize_hashes: fn(&[H256]) -> String, @@ -343,7 +324,7 @@ mod tests { use crate::accountant::db_access_objects::failed_payable_dao::{FailureReason}; use crate::assert_on_testing_enum_with_all_its_variants; use crate::blockchain::blockchain_interface::data_structures::errors::{BlockchainInterfaceError, PayableTransactionError}; - use crate::blockchain::blockchain_interface::data_structures::{BlockchainTxFailure, ProcessedPayableFallible, RpcPayableFailure}; + use crate::blockchain::blockchain_interface::data_structures::{ProcessedPayableFallible, RpcPayableFailure}; #[test] fn investigate_debt_extremes_picks_the_most_relevant_records() { @@ -648,7 +629,7 @@ mod tests { #[test] fn conversion_between_blockchain_tx_failure_and_failure_reason_works() { let input_and_expected_results = - vec![(BlockchainTxFailure::Unrecognized, FailureReason::Reverted)]; + vec![(BlockchainTxFailure::Unrecognized, FailureReason::Unrecognized)]; let inputs_len = input_and_expected_results.len(); let check_nums = input_and_expected_results diff --git a/node/src/database/test_utils/transaction_wrapper_mock.rs b/node/src/database/test_utils/transaction_wrapper_mock.rs index d7c927b09..5b9a717e9 100644 --- a/node/src/database/test_utils/transaction_wrapper_mock.rs +++ b/node/src/database/test_utils/transaction_wrapper_mock.rs @@ -195,7 +195,7 @@ impl SetupForProdCodeAndAlteredStmts { None => // In the prod code, all the db operations would've happened on a single wrapped txn, // that's why we strive to manipulate a txn also here, not the conn directly. Most - // importantly, sometimes multiple later operations take each the previous one as + // importantly, sometimes multiple subsequent operations take each the previous one as // necessary base. If the continuity is broken the later statement might not work. If // we record some changes on the transaction, other changes tried to be done from // a different connection might meet a different state of the database and thwart the From b8bbee759a4f14416bcc07ec89ca228d35a75e76 Mon Sep 17 00:00:00 2001 From: Bert Date: Fri, 5 Sep 2025 14:52:08 +0200 Subject: [PATCH 49/61] GH-642: got rid of the BlockchainFailure::Unrecognized layer --- node/src/accountant/mod.rs | 14 ++--- node/src/accountant/scanners/mod.rs | 6 +- .../tx_receipt_interpreter.rs | 57 +++++++------------ .../scanners/pending_payable_scanner/utils.rs | 16 +----- .../src/accountant/scanners/scanners_utils.rs | 23 -------- node/src/blockchain/blockchain_bridge.rs | 2 +- .../lower_level_interface_web3.rs | 13 ++--- .../blockchain_interface_web3/mod.rs | 4 +- .../data_structures/mod.rs | 50 ++-------------- 9 files changed, 42 insertions(+), 143 deletions(-) diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index 807853b7f..3f8de7c97 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -1216,7 +1216,7 @@ pub fn wei_to_gwei, S: Display + Copy + Div + From { - Self::handle_status_with_failure( - scan_report_so_far, - tx_case.tx_by_table, - reason, - logger, - ) - } + StatusReadFromReceiptCheck::Reverted => Self::handle_reverted_tx( + scan_report_so_far, + tx_case.tx_by_table, + logger, + ), StatusReadFromReceiptCheck::Pending => Self::handle_still_pending_tx( scan_report_so_far, tx_case.tx_by_table, @@ -171,34 +168,26 @@ impl TxReceiptInterpreter { } //TODO: failures handling might need enhancement suggested by GH-693 - fn handle_status_with_failure( + fn handle_reverted_tx( mut scan_report: ReceiptScanReport, tx: TxByTable, - blockchain_failure: BlockchainTxFailure, logger: &Logger, ) -> ReceiptScanReport { match tx { TxByTable::SentPayable(sent_tx) => { - let failure_reason = FailureReason::from(blockchain_failure); + let failure_reason = FailureReason::Reverted; let failed_tx = FailedTx::from((sent_tx, failure_reason)); - warning!( - logger, - "Pending tx {:?} failed on-chain due to: {}", - failed_tx.hash, - blockchain_failure - ); + warning!(logger, "Pending tx {:?} was reverted", failed_tx.hash,); scan_report.register_new_failure(failed_tx); } TxByTable::FailedPayable(failed_tx) => { debug!( logger, - "Failed tx {:?} on a recheck after {}. Status will be changed to \"Concluded\" \ - due to blockchain failure: {}", + "Reverted tx {:?} on a recheck after {}. Status will be changed to \"Concluded\"", failed_tx.hash, failed_tx.reason, - blockchain_failure ); scan_report.register_finalization_of_unproven_failure(failed_tx.hash); @@ -250,9 +239,7 @@ mod tests { use crate::accountant::test_utils::{ make_failed_tx, make_sent_tx, make_transaction_block, SentPayableDaoMock, }; - use crate::blockchain::blockchain_interface::data_structures::{ - BlockchainTxFailure, TxReceiptError, - }; + use crate::blockchain::blockchain_interface::data_structures::TxReceiptError; use crate::blockchain::errors::internal_errors::InternalErrorKind; use crate::blockchain::errors::rpc_errors::{ AppRpcError, AppRpcErrorKind, LocalError, RemoteError, @@ -359,25 +346,22 @@ mod tests { } #[test] - fn interprets_tx_receipt_for_pending_tx_when_tx_status_reveals_failure() { + fn interprets_tx_receipt_for_pending_tx_when_tx_status_says_reverted() { init_test_logging(); - let test_name = "interprets_tx_receipt_for_pending_tx_when_transaction_status_says_failure"; + let test_name = "interprets_tx_receipt_for_pending_tx_when_tx_status_says_reverted"; let hash = make_tx_hash(0xabc); let mut sent_tx = make_sent_tx(2244); sent_tx.hash = hash; - let blockchain_failure = BlockchainTxFailure::Unrecognized; let logger = Logger::new(test_name); let scan_report = ReceiptScanReport::default(); - let result = TxReceiptInterpreter::handle_status_with_failure( + let result = TxReceiptInterpreter::handle_reverted_tx( scan_report, TxByTable::SentPayable(sent_tx.clone()), - blockchain_failure, &logger, ); - let failure_reason = blockchain_failure.into(); - let failed_tx = FailedTx::from((sent_tx, failure_reason)); + let failed_tx = FailedTx::from((sent_tx, FailureReason::Reverted)); assert_eq!( result, ReceiptScanReport { @@ -390,12 +374,12 @@ mod tests { ); TestLogHandler::new().exists_log_containing(&format!( "WARN: {test_name}: Pending tx 0x0000000000000000000000000000000000000000000000000000000\ - 000000abc failed on-chain due to: Unrecognized failure", + 000000abc was reverted", )); } #[test] - fn interprets_tx_receipt_for_failed_tx_when_tx_status_reveals_failure() { + fn interprets_tx_receipt_for_failed_tx_when_newly_fetched_tx_status_says_reverted() { init_test_logging(); let test_name = "interprets_tx_receipt_for_failed_tx_when_tx_status_reveals_failure"; let tx_hash = make_tx_hash(0xabc); @@ -403,14 +387,12 @@ mod tests { failed_tx.hash = tx_hash; failed_tx.status = FailureStatus::RecheckRequired(ValidationStatus::Waiting); failed_tx.reason = FailureReason::PendingTooLong; - let blockchain_failure = BlockchainTxFailure::Unrecognized; let logger = Logger::new(test_name); let scan_report = ReceiptScanReport::default(); - let result = TxReceiptInterpreter::handle_status_with_failure( + let result = TxReceiptInterpreter::handle_reverted_tx( scan_report, TxByTable::FailedPayable(failed_tx.clone()), - blockchain_failure, &logger, ); @@ -425,9 +407,8 @@ mod tests { } ); TestLogHandler::new().exists_log_containing(&format!( - "DEBUG: {test_name}: Failed tx 0x000000000000000000000000000000000000000000000000000000\ - 0000000abc on a recheck after \"PendingTooLong\". Status will be changed to \"Concluded\" \ - due to blockchain failure: Unrecognized failure", + "DEBUG: {test_name}: Reverted tx 0x000000000000000000000000000000000000000000000000000000\ + 0000000abc on a recheck after \"PendingTooLong\". Status will be changed to \"Concluded\"", )); } diff --git a/node/src/accountant/scanners/pending_payable_scanner/utils.rs b/node/src/accountant/scanners/pending_payable_scanner/utils.rs index ee19542d4..76ab30272 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/utils.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/utils.rs @@ -1,13 +1,9 @@ // Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::db_access_objects::failed_payable_dao::{ - FailedTx, FailureReason, FailureStatus, -}; +use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, FailureStatus}; use crate::accountant::db_access_objects::sent_payable_dao::{SentTx, TxStatus}; use crate::accountant::db_access_objects::utils::TxHash; -use crate::blockchain::blockchain_interface::data_structures::{ - BlockchainTxFailure, TxReceiptError, TxReceiptResult, -}; +use crate::blockchain::blockchain_interface::data_structures::{TxReceiptError, TxReceiptResult}; use crate::blockchain::errors::validation_status::{ PreviousAttempts, ValidationFailureClock, ValidationStatus, }; @@ -351,14 +347,6 @@ pub enum TxHashByTable { FailedPayable(TxHash), } -impl From for FailureReason { - fn from(failure: BlockchainTxFailure) -> Self { - match failure { - BlockchainTxFailure::Unrecognized => FailureReason::Unrecognized, - } - } -} - #[cfg(test)] mod tests { use crate::accountant::db_access_objects::failed_payable_dao::FailureStatus; diff --git a/node/src/accountant/scanners/scanners_utils.rs b/node/src/accountant/scanners/scanners_utils.rs index f3eff674b..c459f7226 100644 --- a/node/src/accountant/scanners/scanners_utils.rs +++ b/node/src/accountant/scanners/scanners_utils.rs @@ -320,9 +320,6 @@ mod tests { use masq_lib::logger::Logger; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; use std::time::{SystemTime}; - use itertools::Itertools; - use crate::accountant::db_access_objects::failed_payable_dao::{FailureReason}; - use crate::assert_on_testing_enum_with_all_its_variants; use crate::blockchain::blockchain_interface::data_structures::errors::{BlockchainInterfaceError, PayableTransactionError}; use crate::blockchain::blockchain_interface::data_structures::{ProcessedPayableFallible, RpcPayableFailure}; @@ -626,26 +623,6 @@ mod tests { assert_eq!(result, false) } - #[test] - fn conversion_between_blockchain_tx_failure_and_failure_reason_works() { - let input_and_expected_results = - vec![(BlockchainTxFailure::Unrecognized, FailureReason::Unrecognized)]; - let inputs_len = input_and_expected_results.len(); - - let check_nums = input_and_expected_results - .into_iter() - .map(|(input, failure_reason)| match input { - BlockchainTxFailure::Unrecognized => { - let result = FailureReason::from(input); - assert_eq!(result, failure_reason); - 1 - } - }) - .collect_vec(); - - assert_on_testing_enum_with_all_its_variants!(BlockchainTxFailure, check_nums, inputs_len) - } - #[test] fn count_total_errors_says_unknown_number_for_early_local_errors() { let early_local_errors = [ diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index e2a0b9bbe..e630b2a3a 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -392,7 +392,7 @@ impl BlockchainBridge { (0, 0, 0), |(success, fail, pending), transaction_receipt| match transaction_receipt { TxReceiptResult(Ok(tx_receipt)) => match tx_receipt.status { - StatusReadFromReceiptCheck::Failed(_) => (success, fail + 1, pending), + StatusReadFromReceiptCheck::Reverted => (success, fail + 1, pending), StatusReadFromReceiptCheck::Succeeded(_) => { (success + 1, fail, pending) } diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs index 024e945aa..1ad8d1181 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs @@ -4,7 +4,7 @@ use crate::blockchain::blockchain_interface::blockchain_interface_web3::CONTRACT use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainInterfaceError; use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainInterfaceError::QueryFailed; use crate::blockchain::blockchain_interface::data_structures::{ - BlockchainTxFailure, StatusReadFromReceiptCheck, TxBlock, + StatusReadFromReceiptCheck, TxBlock, }; use crate::blockchain::blockchain_interface::lower_level_interface::LowBlockchainInt; use ethereum_types::{H256, U256, U64}; @@ -24,9 +24,7 @@ impl From for StatusReadFromReceiptCheck { block_number, }) } - (Some(status), _, _) if status == U64::from(0) => { - StatusReadFromReceiptCheck::Failed(BlockchainTxFailure::Unrecognized) - } + (Some(status), _, _) if status == U64::from(0) => StatusReadFromReceiptCheck::Reverted, _ => StatusReadFromReceiptCheck::Pending, } } @@ -159,7 +157,7 @@ mod tests { use masq_lib::utils::find_free_port; use std::str::FromStr; use web3::types::{BlockNumber, Bytes, FilterBuilder, Log, TransactionReceipt, U256}; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{BlockchainTxFailure, StatusReadFromReceiptCheck}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{StatusReadFromReceiptCheck}; #[test] fn get_transaction_fee_balance_works() { @@ -554,10 +552,7 @@ mod tests { H256::from_low_u64_be(0x5678), ); - assert_eq!( - tx_status, - StatusReadFromReceiptCheck::Failed(BlockchainTxFailure::Unrecognized) - ); + assert_eq!(tx_status, StatusReadFromReceiptCheck::Reverted); } #[test] diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs index 2407dcfa1..c88d21397 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs @@ -466,7 +466,7 @@ mod tests { }; use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainInterfaceError::QueryFailed; use crate::blockchain::blockchain_interface::data_structures::{ - BlockchainTransaction, BlockchainTxFailure, TxBlock, + BlockchainTransaction, TxBlock, }; use crate::blockchain::blockchain_interface::{ BlockchainAgentBuildError, BlockchainInterfaceError, BlockchainInterface, @@ -1169,7 +1169,7 @@ mod tests { result[4], TxReceiptResult(Ok(RetrievedTxStatus::new( tx_hbt_5, - StatusReadFromReceiptCheck::Failed(BlockchainTxFailure::Unrecognized) + StatusReadFromReceiptCheck::Reverted ))) ); assert_eq!( diff --git a/node/src/blockchain/blockchain_interface/data_structures/mod.rs b/node/src/blockchain/blockchain_interface/data_structures/mod.rs index 1acd57d72..0cf71f798 100644 --- a/node/src/blockchain/blockchain_interface/data_structures/mod.rs +++ b/node/src/blockchain/blockchain_interface/data_structures/mod.rs @@ -13,7 +13,6 @@ use ethereum_types::U64; use serde_derive::{Deserialize, Serialize}; use std::fmt; use std::fmt::{Display, Formatter}; -use variant_count::VariantCount; use web3::types::H256; use web3::Error; @@ -79,29 +78,16 @@ impl RetrievedTxStatus { #[derive(Debug, PartialEq, Eq, Clone)] pub enum StatusReadFromReceiptCheck { - Failed(BlockchainTxFailure), + Reverted, Succeeded(TxBlock), Pending, } -#[derive(Clone, Copy, Debug, PartialEq, Eq, VariantCount)] -pub enum BlockchainTxFailure { - Unrecognized, -} - -impl Display for BlockchainTxFailure { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - BlockchainTxFailure::Unrecognized => write!(f, "Unrecognized failure"), - } - } -} - impl Display for StatusReadFromReceiptCheck { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - StatusReadFromReceiptCheck::Failed(reason) => { - write!(f, "Failed(Reason: {})", reason) + StatusReadFromReceiptCheck::Reverted => { + write!(f, "Reverted") } StatusReadFromReceiptCheck::Succeeded(block) => { write!( @@ -137,23 +123,17 @@ pub struct TxBlock { mod tests { use crate::accountant::scanners::pending_payable_scanner::utils::TxHashByTable; use crate::accountant::test_utils::make_transaction_block; - use crate::assert_on_testing_enum_with_all_its_variants; use crate::blockchain::blockchain_interface::data_structures::{ - BlockchainTxFailure, RetrievedTxStatus, StatusReadFromReceiptCheck, TxBlock, - TxReceiptError, TxReceiptResult, + RetrievedTxStatus, StatusReadFromReceiptCheck, TxBlock, TxReceiptError, TxReceiptResult, }; use crate::blockchain::errors::rpc_errors::{AppRpcError, LocalError, RemoteError}; use crate::blockchain::test_utils::make_tx_hash; use ethereum_types::{H256, U64}; - use itertools::Itertools; #[test] fn tx_status_display_works() { // Test Failed - assert_eq!( - StatusReadFromReceiptCheck::Failed(BlockchainTxFailure::Unrecognized).to_string(), - "Failed(Reason: Unrecognized failure)" - ); + assert_eq!(StatusReadFromReceiptCheck::Reverted.to_string(), "Reverted"); // Test Pending assert_eq!(StatusReadFromReceiptCheck::Pending.to_string(), "Pending"); @@ -171,26 +151,6 @@ mod tests { ); } - #[test] - fn display_for_blockchain_tx_failure_works() { - let input_and_expected_results = - vec![(BlockchainTxFailure::Unrecognized, "Unrecognized failure")]; - let inputs_len = input_and_expected_results.len(); - - let check_nums = input_and_expected_results - .into_iter() - .map(|(input, failure_reason)| match input { - BlockchainTxFailure::Unrecognized => { - let result = input.to_string(); - assert_eq!(result, failure_reason); - 1 - } - }) - .collect_vec(); - - assert_on_testing_enum_with_all_its_variants!(BlockchainTxFailure, check_nums, inputs_len) - } - #[test] fn hash_can_be_fetched_from_tx_receipt_result() { let hash_1 = TxHashByTable::SentPayable(make_tx_hash(123)); From 9498689fa54396396af17f753a0d88ca897a827d Mon Sep 17 00:00:00 2001 From: Bert Date: Fri, 5 Sep 2025 16:01:18 +0200 Subject: [PATCH 50/61] GH-642: savepoint --- .../tx_receipt_interpreter.rs | 4 +- node/src/blockchain/blockchain_bridge.rs | 4 +- .../lower_level_interface_web3.rs | 84 +++++++------------ .../blockchain_interface_web3/mod.rs | 1 - .../blockchain_interface_web3/utils.rs | 4 +- node/src/blockchain/test_utils.rs | 49 +++++++++++ node/src/test_utils/mod.rs | 32 +------ 7 files changed, 86 insertions(+), 92 deletions(-) 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 48ab6a0de..07ac795bf 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 @@ -249,7 +249,7 @@ mod tests { }; use crate::blockchain::errors::BlockchainErrorKind; use crate::blockchain::test_utils::make_tx_hash; - use crate::test_utils::unshared_test_utils::capture_numbers_with_separators_from_str; + use crate::test_utils::unshared_test_utils::capture_digits_with_separators_from_str; use masq_lib::logger::Logger; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; use std::sync::{Arc, Mutex}; @@ -459,7 +459,7 @@ mod tests { \\d{{1,3}}(,\\d{{3}})* ms. Will resubmit with higher gas price" )); let log_msg = log_handler.get_log_at(log_idx); - let str_elapsed_ms = capture_numbers_with_separators_from_str(&log_msg, 3, ','); + let str_elapsed_ms = capture_digits_with_separators_from_str(&log_msg, 3, ','); let elapsed_ms = str_elapsed_ms[0].replace(",", "").parse::().unwrap(); let elapsed_ms_when_before = before .duration_since(from_unix_timestamp(sent_tx_timestamp)) diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index e630b2a3a..258b79ed7 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -882,7 +882,7 @@ mod tests { system.run(); let time_after = SystemTime::now(); let accountant_recording = accountant_recording_arc.lock().unwrap(); - let register_new_pending_sent_tx_msg = + let register_new_pending_payables_msg = accountant_recording.get_record::(0); let sent_payables_msg = accountant_recording.get_record::(1); let expected_hash = @@ -901,7 +901,7 @@ mod tests { }) } ); - let first_actual_sent_tx = ®ister_new_pending_sent_tx_msg.new_sent_txs[0]; + let first_actual_sent_tx = ®ister_new_pending_payables_msg.new_sent_txs[0]; assert_eq!( first_actual_sent_tx.receiver_address, account.wallet.address() diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs index 1ad8d1181..d41c4680d 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs @@ -148,7 +148,7 @@ mod tests { use crate::blockchain::blockchain_interface::blockchain_interface_web3::TRANSACTION_LITERAL; use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainInterfaceError::QueryFailed; use crate::blockchain::blockchain_interface::{BlockchainInterfaceError, BlockchainInterface}; - use crate::blockchain::test_utils::{make_blockchain_interface_web3}; + use crate::blockchain::test_utils::{make_block_hash, make_blockchain_interface_web3, make_tx_hash, TransactionReceiptBuilder}; use crate::sub_lib::wallet::Wallet; use crate::test_utils::make_wallet; use ethereum_types::{H256, U64}; @@ -156,7 +156,7 @@ mod tests { use masq_lib::test_utils::mock_blockchain_client_server::MBCSBuilder; use masq_lib::utils::find_free_port; use std::str::FromStr; - use web3::types::{BlockNumber, Bytes, FilterBuilder, Log, TransactionReceipt, U256}; + use web3::types::{BlockNumber, Bytes, FilterBuilder, Log, U256}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{StatusReadFromReceiptCheck}; #[test] @@ -527,16 +527,17 @@ mod tests { #[test] fn transaction_receipt_can_be_converted_to_successful_transaction() { - let tx_status = test_deriving_tx_status_from_tx_receipt_and_adding_to_sent_tx( - Some(U64::from(1)), - Some(H256::from_low_u64_be(0x1234)), - Some(U64::from(10)), - H256::from_low_u64_be(0x5678), - ); + let tx_status: StatusReadFromReceiptCheck = + TransactionReceiptBuilder::new(make_tx_hash(0x5678)) + .status(U64::from(1)) + .block_hash(make_block_hash(0x1234)) + .block_number(10.into()) + .build() + .into(); match tx_status { StatusReadFromReceiptCheck::Succeeded(ref block) => { - assert_eq!(block.block_hash, H256::from_low_u64_be(0x1234)); + assert_eq!(block.block_hash, make_block_hash(0x1234)); assert_eq!(block.block_number, U64::from(10)); } _ => panic!("Expected status to be Succeeded"), @@ -545,72 +546,43 @@ mod tests { #[test] fn transaction_receipt_can_be_converted_to_failed_transaction() { - let tx_status = test_deriving_tx_status_from_tx_receipt_and_adding_to_sent_tx( - Some(U64::from(0)), - None, - None, - H256::from_low_u64_be(0x5678), - ); + let tx_status: StatusReadFromReceiptCheck = + TransactionReceiptBuilder::new(make_tx_hash(0x5678)) + .status(U64::from(0)) + .build() + .into(); assert_eq!(tx_status, StatusReadFromReceiptCheck::Reverted); } #[test] fn transaction_receipt_can_be_converted_to_pending_transaction_no_status() { - let tx_status = test_deriving_tx_status_from_tx_receipt_and_adding_to_sent_tx( - None, - None, - None, - H256::from_low_u64_be(0x5678), - ); + let tx_status: StatusReadFromReceiptCheck = + TransactionReceiptBuilder::new(make_tx_hash(0x5678)) + .build() + .into(); assert_eq!(tx_status, StatusReadFromReceiptCheck::Pending); } #[test] fn transaction_receipt_can_be_converted_to_pending_transaction_no_block_info() { - let tx_status = test_deriving_tx_status_from_tx_receipt_and_adding_to_sent_tx( - Some(U64::from(1)), - None, - None, - H256::from_low_u64_be(0x5678), - ); + let tx_status: StatusReadFromReceiptCheck = + TransactionReceiptBuilder::new(make_tx_hash(0x5678)) + .status(U64::from(1)) + .build() + .into(); assert_eq!(tx_status, StatusReadFromReceiptCheck::Pending); } #[test] fn transaction_receipt_can_be_converted_to_pending_transaction_no_status_and_block_info() { - let tx_status = test_deriving_tx_status_from_tx_receipt_and_adding_to_sent_tx( - Some(U64::from(1)), - Some(H256::from_low_u64_be(0x1234)), - None, - H256::from_low_u64_be(0x5678), - ); + let tx_status: StatusReadFromReceiptCheck = + TransactionReceiptBuilder::new(make_tx_hash(0x5678)) + .build() + .into(); assert_eq!(tx_status, StatusReadFromReceiptCheck::Pending); } - - fn test_deriving_tx_status_from_tx_receipt_and_adding_to_sent_tx( - num_status_opt: Option, - block_hash_opt: Option, - block_number_opt: Option, - transaction_hash: H256, - ) -> StatusReadFromReceiptCheck { - let receipt = TransactionReceipt { - status: num_status_opt, - root: None, - block_hash: block_hash_opt, - block_number: block_number_opt, - cumulative_gas_used: Default::default(), - gas_used: None, - contract_address: None, - transaction_hash, - transaction_index: Default::default(), - logs: vec![], - logs_bloom: Default::default(), - }; - - receipt.into() - } } diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs index c88d21397..f453b1a79 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs @@ -1074,7 +1074,6 @@ mod tests { let tx_hash_1 = make_tx_hash(3300); let tx_hash_2 = make_tx_hash(3401); let tx_hash_3 = make_tx_hash(3502); - // let tx_hash_4 = make_tx_hash(3603); let tx_hash_5 = make_tx_hash(3704); let tx_hash_6 = make_tx_hash(3805); diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs index ff3e3a4c7..d8e1729f9 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs @@ -717,10 +717,10 @@ mod tests { let timestamp_after = SystemTime::now(); assert_eq!(result, expected_result); let accountant_recording_result = accountant_recording.lock().unwrap(); - let rnpst_message = accountant_recording_result.get_record::(0); + let rnpp_message = accountant_recording_result.get_record::(0); assert_eq!(accountant_recording_result.len(), 1); let nonces = 3_64..(accounts.payables.len() as u64 + 3); - rnpst_message + rnpp_message .new_sent_txs .iter() .zip(accounts.payables.iter()) diff --git a/node/src/blockchain/test_utils.rs b/node/src/blockchain/test_utils.rs index f3b354931..cea8988dd 100644 --- a/node/src/blockchain/test_utils.rs +++ b/node/src/blockchain/test_utils.rs @@ -246,3 +246,52 @@ impl ValidationFailureClockMock { self } } + +pub struct TransactionReceiptBuilder { + status_opt: Option, + block_hash_opt: Option, + block_number_opt: Option, + transaction_hash: H256, +} + +impl TransactionReceiptBuilder { + pub fn new(transaction_hash: H256) -> Self { + Self { + status_opt: None, + block_hash_opt: None, + block_number_opt: None, + transaction_hash, + } + } + + pub fn status(mut self, status: U64) -> Self { + self.status_opt = Some(status); + self + } + + pub fn block_hash(mut self, block_hash: H256) -> Self { + self.block_hash_opt = Some(block_hash); + self + } + + pub fn block_number(mut self, block_number: U64) -> Self { + self.block_number_opt = Some(block_number); + self + } + + pub fn build(self) -> TransactionReceipt { + TransactionReceipt { + status: self.status_opt, + root: None, + block_hash: self.block_hash_opt, + block_number: self.block_number_opt, + cumulative_gas_used: Default::default(), + gas_used: None, + contract_address: None, + transaction_hash: self.transaction_hash, + transaction_index: Default::default(), + logs: vec![], + logs_bloom: Default::default(), + } + } +} diff --git a/node/src/test_utils/mod.rs b/node/src/test_utils/mod.rs index b3d026c95..c8578896a 100644 --- a/node/src/test_utils/mod.rs +++ b/node/src/test_utils/mod.rs @@ -569,44 +569,18 @@ pub mod unshared_test_utils { pub assertions: Box, } - pub fn capture_numbers_with_separators_from_str( + pub fn capture_digits_with_separators_from_str( surveyed_str: &str, - digit_grouped_by: usize, + length_between_separators: usize, separator: char, ) -> Vec { let regex = - format!("(\\d{{1,{digit_grouped_by}}}(?:{separator}\\d{{{digit_grouped_by}}})+)"); + format!("(\\d{{1,{length_between_separators}}}(?:{separator}\\d{{{length_between_separators}}})+)"); let re = regex::Regex::new(®ex).unwrap(); let captures = re.captures_iter(surveyed_str); captures.map(|capture| capture[1].to_string()).collect() } - #[macro_export] - macro_rules! assert_on_testing_enum_with_all_its_variants { - ($enum_type: ty, $check_nums: expr, $inputs_len: expr) => {{ - let mut check_nums = $check_nums; - let initially = check_nums.len(); - check_nums.dedup(); - let deduped = check_nums.len(); - let official_variant_count = <$enum_type>::VARIANT_COUNT; - assert_eq!( - deduped, initially, - "Some variants were processed more than once. Expected: {}, actual: {}", - initially, deduped - ); - assert_eq!( - $inputs_len, official_variant_count, - "Input should contain one example from each variant. Expected: {}, actual: {}", - official_variant_count, $inputs_len - ); - assert_eq!( - deduped, official_variant_count, - "We should've gotten one result for each variant. Expected: {}, actual: {}", - official_variant_count, deduped - ) - }}; - } - pub fn assert_on_initialization_with_panic_on_migration(data_dir: &Path, act: &A) where A: Fn(&Path) + ?Sized, From 08eb7fb805b2eaac5571fa11eeef0cc64a48c8ed Mon Sep 17 00:00:00 2001 From: Bert Date: Sat, 6 Sep 2025 21:51:25 +0200 Subject: [PATCH 51/61] GH-642: hashmap for receipt status result deployed --- .../db_access_objects/failed_payable_dao.rs | 5 +- node/src/accountant/mod.rs | 50 ++++--- node/src/accountant/scanners/mod.rs | 54 +++---- .../scanners/pending_payable_scanner/mod.rs | 82 +++-------- .../tx_receipt_interpreter.rs | 30 ++-- .../scanners/pending_payable_scanner/utils.rs | 136 ++++-------------- node/src/blockchain/blockchain_bridge.rs | 45 +++--- .../blockchain_interface_web3/mod.rs | 70 ++++----- .../data_structures/mod.rs | 66 +-------- .../blockchain/blockchain_interface/mod.rs | 6 +- node/src/blockchain/errors/mod.rs | 1 + 11 files changed, 159 insertions(+), 386 deletions(-) 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 9f585122c..c1819d691 100644 --- a/node/src/accountant/db_access_objects/failed_payable_dao.rs +++ b/node/src/accountant/db_access_objects/failed_payable_dao.rs @@ -62,7 +62,7 @@ impl Display for FailureStatus { match serde_json::to_string(self) { Ok(json) => write!(f, "{}", json), // Untestable - Err(_) => write!(f, ""), + Err(e) => panic!("cat: {:?}, line: {}, column: {}", e.classify(), e.line(), e.column()) //write!(f, ""), } } } @@ -416,6 +416,7 @@ mod tests { use std::ops::Add; use std::str::FromStr; use std::time::{Duration, SystemTime}; + use crate::blockchain::errors::internal_errors::InternalErrorKind; #[test] fn insert_new_records_works() { @@ -652,7 +653,7 @@ mod tests { assert_eq!( FailureReason::from_str("\"UnknownReason\"").unwrap_err(), "unknown variant `UnknownReason`, \ - expected one of `Submission`, `Reverted`, `PendingTooLong` \ + expected one of `Submission`, `Reverted`, `Unrecognized`, `PendingTooLong` \ at line 1 column 15 in '\"UnknownReason\"'" ); diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index 3f8de7c97..b39d50b30 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -37,9 +37,7 @@ use crate::blockchain::blockchain_bridge::{ BlockMarker, RegisterNewPendingPayables, RetrieveTransactions, }; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; -use crate::blockchain::blockchain_interface::data_structures::{ - BlockchainTransaction, ProcessedPayableFallible, TxReceiptResult, -}; +use crate::blockchain::blockchain_interface::data_structures::{BlockchainTransaction, ProcessedPayableFallible, StatusReadFromReceiptCheck}; use crate::bootstrapper::BootstrapperConfig; use crate::database::db_initializer::DbInitializationConfig; use crate::sub_lib::accountant::AccountantSubs; @@ -75,6 +73,7 @@ use masq_lib::ui_gateway::{MessageBody, MessagePath, MessageTarget}; use masq_lib::ui_gateway::{NodeFromUiMessage, NodeToUiMessage}; use masq_lib::utils::ExpectValue; use std::any::type_name; +use std::collections::HashMap; #[cfg(test)] use std::default::Default; use std::fmt::Display; @@ -83,6 +82,7 @@ use std::path::Path; use std::rc::Rc; use std::time::SystemTime; use web3::types::H256; +use crate::blockchain::errors::rpc_errors::AppRpcError; pub const CRASH_KEY: &str = "ACCOUNTANT"; pub const DEFAULT_PENDING_TOO_LONG_SEC: u64 = 21_600; //6 hours @@ -139,9 +139,11 @@ pub struct ReceivedPayments { pub response_skeleton_opt: Option, } +pub type TxReceiptResult = Result; + #[derive(Debug, PartialEq, Eq, Message, Clone)] pub struct TxReceiptsMessage { - pub results: Vec, + pub results: HashMap, pub response_skeleton_opt: Option, } @@ -1259,7 +1261,7 @@ mod tests { use crate::accountant::test_utils::{AccountantBuilder, BannedDaoMock}; use crate::accountant::Accountant; use crate::blockchain::blockchain_interface::data_structures::{ - RetrievedTxStatus, StatusReadFromReceiptCheck, TxBlock, TxReceiptResult, + StatusReadFromReceiptCheck, TxBlock, }; use crate::blockchain::errors::validation_status::ValidationStatus; use crate::blockchain::test_utils::make_tx_hash; @@ -1962,10 +1964,9 @@ mod tests { block_number: 78901234.into(), }; let tx_receipts_msg = TxReceiptsMessage { - results: vec![TxReceiptResult(Ok(RetrievedTxStatus::new( - TxHashByTable::SentPayable(sent_tx.hash), + results: hashmap![TxHashByTable::SentPayable(sent_tx.hash) => Ok( StatusReadFromReceiptCheck::Succeeded(tx_block), - )))], + )], response_skeleton_opt, }; @@ -2188,10 +2189,9 @@ mod tests { let first_counter_msg_setup = setup_for_counter_msg_triggered_via_type_id!( RequestTransactionReceipts, TxReceiptsMessage { - results: vec![TxReceiptResult(Ok(RetrievedTxStatus::new( - TxHashByTable::SentPayable(sent_tx.hash), + results: hashmap![TxHashByTable::SentPayable(sent_tx.hash) => Ok( StatusReadFromReceiptCheck::Reverted - ))),], + ),], response_skeleton_opt }, &subject_addr @@ -2815,10 +2815,9 @@ mod tests { let subject_addr: Addr = subject.start(); let subject_subs = Accountant::make_subs_from(&subject_addr); let expected_tx_receipts_msg = TxReceiptsMessage { - results: vec![TxReceiptResult(Ok(RetrievedTxStatus::new( - TxHashByTable::SentPayable(tx_hash), + results: hashmap![TxHashByTable::SentPayable(tx_hash) => Ok( StatusReadFromReceiptCheck::Reverted, - )))], + )], response_skeleton_opt: None, }; let expected_sent_payables = SentPayables { @@ -3545,15 +3544,14 @@ mod tests { )]), response_skeleton_opt: None, }; - let tx_with_status = RetrievedTxStatus::new( - TxHashByTable::SentPayable(tx_hash), + let tx_status = StatusReadFromReceiptCheck::Succeeded(TxBlock { block_hash: make_tx_hash(369369), block_number: 4444444444u64.into(), - }), + }, ); let counter_msg_3 = TxReceiptsMessage { - results: vec![TxReceiptResult(Ok(tx_with_status))], + results: hashmap![TxHashByTable::SentPayable(tx_hash) => Ok(tx_status)], response_skeleton_opt: None, }; let request_transaction_receipts_msg = RequestTransactionReceipts { @@ -5281,13 +5279,13 @@ mod tests { seeds: Vec, ) -> (TxReceiptsMessage, Vec) { let (tx_receipt_results, tx_record_vec) = seeds.into_iter().enumerate().fold( - (vec![], vec![]), + (hashmap![], vec![]), |(mut tx_receipt_results, mut record_by_table_vec), (idx, seed_params)| { let tx_hash = seed_params.tx_hash; let status = seed_params.status; - let (result, record) = + let (key, value, record) = make_receipt_check_result_and_record(tx_hash, status, idx as u64); - tx_receipt_results.push(result); + tx_receipt_results.insert(key, value); record_by_table_vec.push(record); (tx_receipt_results, record_by_table_vec) }, @@ -5305,7 +5303,7 @@ mod tests { tx_hash: TxHashByTable, status: StatusReadFromReceiptCheck, idx: u64, - ) -> (TxReceiptResult, TxByTable) { + ) -> (TxHashByTable, TxReceiptResult, TxByTable) { match tx_hash { TxHashByTable::SentPayable(hash) => { let mut sent_tx = make_sent_tx(1 + idx); @@ -5319,17 +5317,17 @@ mod tests { } } - let result = TxReceiptResult(Ok(RetrievedTxStatus::new(tx_hash, status))); + let result = Ok(status); let record_by_table = TxByTable::SentPayable(sent_tx); - (result, record_by_table) + (tx_hash, result, record_by_table) } TxHashByTable::FailedPayable(hash) => { let mut failed_tx = make_failed_tx(1 + idx); failed_tx.hash = hash; - let result = TxReceiptResult(Ok(RetrievedTxStatus::new(tx_hash, status))); + let result = Ok(status); let record_by_table = TxByTable::FailedPayable(failed_tx); - (result, record_by_table) + (tx_hash, result, record_by_table) } } } diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index 7e718cb45..5789ea80e 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -1045,8 +1045,8 @@ mod tests { use crate::blockchain::blockchain_bridge::{BlockMarker, RetrieveTransactions}; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; use crate::blockchain::blockchain_interface::data_structures::{ - BlockchainTransaction, ProcessedPayableFallible, RetrievedTxStatus, RpcPayableFailure, - StatusReadFromReceiptCheck, TxBlock, TxReceiptError, TxReceiptResult, + BlockchainTransaction, ProcessedPayableFallible, RpcPayableFailure, + StatusReadFromReceiptCheck, TxBlock, }; use crate::blockchain::errors::rpc_errors::{AppRpcError, AppRpcErrorKind, RemoteError}; use crate::blockchain::errors::validation_status::{PreviousAttempts, ValidationStatus}; @@ -2624,10 +2624,8 @@ mod tests { block_hash: make_block_hash(333), block_number: U64::from(1234), }; - let transaction_with_status_1 = RetrievedTxStatus::new( - TxHashByTable::SentPayable(sent_tx_1.hash), - StatusReadFromReceiptCheck::Succeeded(tx_block_1), - ); + let tx_status_1 = + StatusReadFromReceiptCheck::Succeeded(tx_block_1); let tx_hash_2 = make_tx_hash(1234); let mut failed_tx_2 = make_failed_tx(789); failed_tx_2.hash = tx_hash_2; @@ -2635,24 +2633,18 @@ mod tests { block_hash: make_block_hash(222), block_number: U64::from(2345), }; - let transaction_with_status_2 = RetrievedTxStatus::new( - TxHashByTable::FailedPayable(failed_tx_2.hash), - StatusReadFromReceiptCheck::Succeeded(tx_block_2), - ); + let tx_status_2 = + StatusReadFromReceiptCheck::Succeeded(tx_block_2); let tx_hash_3 = make_tx_hash(2345); let mut sent_tx_3 = make_sent_tx(456); sent_tx_3.hash = tx_hash_3; - let transaction_with_status_3 = RetrievedTxStatus::new( - TxHashByTable::SentPayable(tx_hash_3), - StatusReadFromReceiptCheck::Pending, - ); + let tx_status_3 = + StatusReadFromReceiptCheck::Pending; let mut sent_tx_4 = make_sent_tx(4567); let tx_hash_4 = sent_tx_4.hash; sent_tx_4.status = TxStatus::Pending(ValidationStatus::Waiting); - let tx_receipt_rpc_error_4 = TxReceiptError::new( - TxHashByTable::SentPayable(sent_tx_4.hash), - AppRpcError::Remote(RemoteError::Unreachable), - ); + let tx_receipt_rpc_error_4 = + AppRpcError::Remote(RemoteError::Unreachable); let tx_hash_5 = make_tx_hash(7890); let mut failed_tx_5 = make_failed_tx(888); failed_tx_5.hash = tx_hash_5; @@ -2661,17 +2653,11 @@ mod tests { BlockchainErrorKind::AppRpc(AppRpcErrorKind::ServerUnreachable), &ValidationFailureClockMock::default().now_result(timestamp_c), ))); - let tx_receipt_rpc_error_5 = TxReceiptError::new( - TxHashByTable::FailedPayable(failed_tx_5.hash), - AppRpcError::Remote(RemoteError::InvalidResponse("game over".to_string())), - ); + let tx_receipt_rpc_error_5 = AppRpcError::Remote(RemoteError::InvalidResponse("game over".to_string())); let tx_hash_6 = make_tx_hash(2345); let mut sent_tx_6 = make_sent_tx(789); sent_tx_6.hash = tx_hash_6; - let transaction_with_status_6 = RetrievedTxStatus::new( - TxHashByTable::SentPayable(sent_tx_6.hash), - StatusReadFromReceiptCheck::Reverted, - ); + let tx_status_6 = StatusReadFromReceiptCheck::Reverted; let pending_payable_cache = PendingPayableCacheMock::default() .get_record_by_hash_result(Some(sent_tx_1.clone())) .get_record_by_hash_result(Some(sent_tx_3.clone())) @@ -2692,13 +2678,13 @@ mod tests { .validation_failure_clock(Box::new(validation_failure_clock)) .build(); let msg = TxReceiptsMessage { - results: vec![ - TxReceiptResult(Ok(transaction_with_status_1)), - TxReceiptResult(Ok(transaction_with_status_2)), - TxReceiptResult(Ok(transaction_with_status_3)), - TxReceiptResult(Err(tx_receipt_rpc_error_4)), - TxReceiptResult(Err(tx_receipt_rpc_error_5)), - TxReceiptResult(Ok(transaction_with_status_6)), + results: hashmap![ + TxHashByTable::SentPayable(tx_hash_1) => Ok(tx_status_1), + TxHashByTable::FailedPayable(tx_hash_2) => Ok(tx_status_2), + TxHashByTable::SentPayable(tx_hash_3) => Ok(tx_status_3), + TxHashByTable::SentPayable(tx_hash_4) => Err(tx_receipt_rpc_error_4), + TxHashByTable::FailedPayable(tx_hash_5) => Err(tx_receipt_rpc_error_5), + TxHashByTable::SentPayable(tx_hash_6) => Ok(tx_status_6), ], response_skeleton_opt: None, }; @@ -2769,7 +2755,7 @@ mod tests { fn pending_payable_scanner_handles_empty_report_transaction_receipts_message() { let mut pending_payable_scanner = PendingPayableScannerBuilder::new().build(); let msg = TxReceiptsMessage { - results: vec![], + results: hashmap![], response_skeleton_opt: None, }; pending_payable_scanner.mark_as_started(SystemTime::now()); diff --git a/node/src/accountant/scanners/pending_payable_scanner/mod.rs b/node/src/accountant/scanners/pending_payable_scanner/mod.rs index e4337030f..6b3dcc1c5 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/mod.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/mod.rs @@ -22,11 +22,8 @@ use crate::accountant::scanners::pending_payable_scanner::utils::{ use crate::accountant::scanners::{ PrivateScanner, Scanner, ScannerCommon, StartScanError, StartableScanner, }; -use crate::accountant::{ - comma_joined_stringifiable, RequestTransactionReceipts, ResponseSkeleton, - ScanForPendingPayables, TxReceiptsMessage, -}; -use crate::blockchain::blockchain_interface::data_structures::{TxBlock, TxReceiptResult}; +use crate::accountant::{comma_joined_stringifiable, RequestTransactionReceipts, ResponseSkeleton, ScanForPendingPayables, TxReceiptResult, TxReceiptsMessage}; +use crate::blockchain::blockchain_interface::data_structures::{TxBlock}; use crate::blockchain::errors::validation_status::{ ValidationFailureClock, ValidationFailureClockReal, }; @@ -253,14 +250,12 @@ impl PendingPayableScanner { let either = msg .results .into_iter() - .fold(init, |acc, receipt_result| match acc { + .fold(init, |acc, (tx_hash_by_table, tx_receipt_result)| match acc { Either::Left(cases) => { - let tx_hash = receipt_result.hash(); - - self.resolve_real_query(cases, receipt_result, tx_hash) + self.resolve_real_query(cases, tx_receipt_result, tx_hash_by_table) } Either::Right(mut mismatch_report) => { - mismatch_report.remaining_hashes.push(receipt_result.hash()); + mismatch_report.remaining_hashes.push(tx_hash_by_table); Either::Right(mismatch_report) } }); @@ -816,7 +811,7 @@ mod tests { }; use crate::accountant::{RequestTransactionReceipts, TxReceiptsMessage}; use crate::blockchain::blockchain_interface::data_structures::{ - RetrievedTxStatus, StatusReadFromReceiptCheck, TxBlock, TxReceiptError, TxReceiptResult, + StatusReadFromReceiptCheck, TxBlock, }; use crate::blockchain::errors::rpc_errors::{AppRpcError, AppRpcErrorKind, LocalError}; use crate::blockchain::errors::validation_status::{ @@ -938,23 +933,11 @@ mod tests { let confirmed_tx_block_sent_tx = make_transaction_block(901); let confirmed_tx_block_failed_tx = make_transaction_block(902); let msg = TxReceiptsMessage { - results: vec![ - TxReceiptResult(Ok(RetrievedTxStatus::new( - TxHashByTable::SentPayable(sent_tx_hash_1), - StatusReadFromReceiptCheck::Pending, - ))), - TxReceiptResult(Ok(RetrievedTxStatus::new( - TxHashByTable::SentPayable(sent_tx_hash_2), - StatusReadFromReceiptCheck::Succeeded(confirmed_tx_block_sent_tx), - ))), - TxReceiptResult(Err(TxReceiptError::new( - TxHashByTable::FailedPayable(failed_tx_hash_1), - AppRpcError::Local(LocalError::Internal), - ))), - TxReceiptResult(Ok(RetrievedTxStatus::new( - TxHashByTable::FailedPayable(failed_tx_hash_2), - StatusReadFromReceiptCheck::Succeeded(confirmed_tx_block_failed_tx), - ))), + results: hashmap![ + TxHashByTable::SentPayable(sent_tx_hash_1) => Ok(StatusReadFromReceiptCheck::Pending), + TxHashByTable::SentPayable(sent_tx_hash_2) => Ok(StatusReadFromReceiptCheck::Succeeded(confirmed_tx_block_sent_tx)), + TxHashByTable::FailedPayable(failed_tx_hash_1) => Err(AppRpcError::Local(LocalError::Internal)), + TxHashByTable::FailedPayable(failed_tx_hash_2) => Ok(StatusReadFromReceiptCheck::Succeeded(confirmed_tx_block_failed_tx)) ], response_skeleton_opt: None, }; @@ -1007,23 +990,11 @@ mod tests { subject.yet_unproven_failed_payables = Box::new(failed_payable_cache); let logger = Logger::new("test"); let msg = TxReceiptsMessage { - results: vec![ - TxReceiptResult(Ok(RetrievedTxStatus::new( - TxHashByTable::SentPayable(sent_tx_hash_1), - StatusReadFromReceiptCheck::Pending, - ))), - TxReceiptResult(Ok(RetrievedTxStatus::new( - TxHashByTable::SentPayable(sent_tx_hash_2), - StatusReadFromReceiptCheck::Succeeded(make_transaction_block(444)), - ))), - TxReceiptResult(Err(TxReceiptError::new( - TxHashByTable::FailedPayable(failed_tx_hash_1), - AppRpcError::Local(LocalError::Internal), - ))), - TxReceiptResult(Ok(RetrievedTxStatus::new( - TxHashByTable::FailedPayable(failed_tx_hash_2), - StatusReadFromReceiptCheck::Succeeded(make_transaction_block(555)), - ))), + results: hashmap![TxHashByTable::SentPayable(sent_tx_hash_1) => Ok( + StatusReadFromReceiptCheck::Pending), + TxHashByTable::SentPayable(sent_tx_hash_2) => Ok(StatusReadFromReceiptCheck::Succeeded(make_transaction_block(444))), + TxHashByTable::FailedPayable(failed_tx_hash_1) => Err(AppRpcError::Local(LocalError::Internal)), + TxHashByTable::FailedPayable(failed_tx_hash_2) => Ok(StatusReadFromReceiptCheck::Succeeded(make_transaction_block(555))), ], response_skeleton_opt: None, }; @@ -1074,23 +1045,10 @@ mod tests { subject.yet_unproven_failed_payables = Box::new(failed_payable_cache); let logger = Logger::new("test"); let msg = TxReceiptsMessage { - results: vec![ - TxReceiptResult(Ok(RetrievedTxStatus::new( - TxHashByTable::SentPayable(sent_tx_hash_1), - StatusReadFromReceiptCheck::Pending, - ))), - TxReceiptResult(Ok(RetrievedTxStatus::new( - TxHashByTable::SentPayable(sent_tx_hash_2), - StatusReadFromReceiptCheck::Succeeded(make_transaction_block(444)), - ))), - TxReceiptResult(Err(TxReceiptError::new( - TxHashByTable::FailedPayable(failed_tx_hash_1), - AppRpcError::Local(LocalError::Internal), - ))), - TxReceiptResult(Ok(RetrievedTxStatus::new( - TxHashByTable::FailedPayable(failed_tx_hash_2), - StatusReadFromReceiptCheck::Succeeded(make_transaction_block(555)), - ))), + results: hashmap![TxHashByTable::SentPayable(sent_tx_hash_1) => Ok(StatusReadFromReceiptCheck::Pending), + TxHashByTable::SentPayable(sent_tx_hash_2) => Ok(StatusReadFromReceiptCheck::Succeeded(make_transaction_block(444))), + TxHashByTable::FailedPayable(failed_tx_hash_1) => Err(AppRpcError::Local(LocalError::Internal)), + TxHashByTable::FailedPayable(failed_tx_hash_2) => Ok(StatusReadFromReceiptCheck::Succeeded(make_transaction_block(555))), ], response_skeleton_opt: None, }; 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 07ac795bf..ee2fcf012 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 @@ -11,13 +11,15 @@ use crate::accountant::scanners::pending_payable_scanner::utils::{ }; use crate::accountant::scanners::pending_payable_scanner::PendingPayableScanner; use crate::blockchain::blockchain_interface::data_structures::{ - StatusReadFromReceiptCheck, TxBlock, TxReceiptError, TxReceiptResult, + StatusReadFromReceiptCheck, TxBlock, }; use crate::blockchain::errors::internal_errors::InternalErrorKind; use crate::blockchain::errors::BlockchainErrorKind; use masq_lib::logger::Logger; use std::time::SystemTime; +use itertools::Either; use thousands::Separable; +use crate::blockchain::errors::rpc_errors::AppRpcError; #[derive(Default)] pub struct TxReceiptInterpreter {} @@ -34,7 +36,7 @@ impl TxReceiptInterpreter { .into_iter() .fold(scan_report, |scan_report_so_far, tx_case| { match tx_case.tx_receipt_result { - TxReceiptResult(Ok(sent_tx_with_status)) => match sent_tx_with_status.status { + Ok(tx_status) => match tx_status { StatusReadFromReceiptCheck::Succeeded(tx_block) => { Self::handle_tx_confirmation( scan_report_so_far, @@ -55,7 +57,7 @@ impl TxReceiptInterpreter { logger, ), }, - TxReceiptResult(Err(e)) => { + Err(e) => { Self::handle_rpc_failure(scan_report_so_far, tx_case.tx_by_table, e, logger) } } @@ -199,21 +201,22 @@ impl TxReceiptInterpreter { fn handle_rpc_failure( mut scan_report: ReceiptScanReport, tx_by_table: TxByTable, - rpc_error: TxReceiptError, + rpc_error: AppRpcError, logger: &Logger, ) -> ReceiptScanReport { + let hash = tx_by_table.hash(); warning!( logger, "Failed to retrieve tx receipt for {:?}: {:?}. Will retry receipt retrieval next cycle", - rpc_error.tx_hash, - rpc_error.err + hash, + rpc_error ); let validation_status_update = match tx_by_table { TxByTable::SentPayable(sent_tx) => { - FailedValidationByTable::from((rpc_error, sent_tx.status)) + FailedValidationByTable::new(hash, rpc_error, Either::Left(sent_tx.status)) } TxByTable::FailedPayable(failed_tx) => { - FailedValidationByTable::from((rpc_error, failed_tx.status)) + FailedValidationByTable::new(hash, rpc_error, Either::Right(failed_tx.status)) } }; scan_report.register_rpc_failure(validation_status_update); @@ -233,13 +236,12 @@ mod tests { use crate::accountant::scanners::pending_payable_scanner::tx_receipt_interpreter::TxReceiptInterpreter; use crate::accountant::scanners::pending_payable_scanner::utils::{ DetectedConfirmations, DetectedFailures, FailedValidation, FailedValidationByTable, - NormalTxConfirmation, PresortedTxFailure, ReceiptScanReport, TxByTable, TxHashByTable, + NormalTxConfirmation, PresortedTxFailure, ReceiptScanReport, TxByTable, TxReclaim, }; use crate::accountant::test_utils::{ make_failed_tx, make_sent_tx, make_transaction_block, SentPayableDaoMock, }; - use crate::blockchain::blockchain_interface::data_structures::TxReceiptError; use crate::blockchain::errors::internal_errors::InternalErrorKind; use crate::blockchain::errors::rpc_errors::{ AppRpcError, AppRpcErrorKind, LocalError, RemoteError, @@ -611,14 +613,12 @@ mod tests { sent_tx.hash = tx_hash; sent_tx.status = current_tx_status.clone(); let rpc_error = AppRpcError::Remote(RemoteError::InvalidResponse("bluh".to_string())); - let tx_receipt_error = - TxReceiptError::new(TxHashByTable::SentPayable(tx_hash), rpc_error.clone()); let scan_report = ReceiptScanReport::default(); let result = TxReceiptInterpreter::handle_rpc_failure( scan_report, TxByTable::SentPayable(sent_tx), - tx_receipt_error, + rpc_error.clone(), &Logger::new(test_name), ); @@ -677,14 +677,12 @@ mod tests { failed_tx.hash = tx_hash; failed_tx.status = current_failure_status.clone(); let rpc_error = AppRpcError::Local(LocalError::Internal); - let tx_receipt_error = - TxReceiptError::new(TxHashByTable::FailedPayable(tx_hash), rpc_error.clone()); let scan_report = ReceiptScanReport::default(); let result = TxReceiptInterpreter::handle_rpc_failure( scan_report, TxByTable::FailedPayable(failed_tx), - tx_receipt_error, + rpc_error.clone(), &Logger::new(test_name), ); diff --git a/node/src/accountant/scanners/pending_payable_scanner/utils.rs b/node/src/accountant/scanners/pending_payable_scanner/utils.rs index 76ab30272..7bdf8e1a6 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/utils.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/utils.rs @@ -3,7 +3,6 @@ use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, FailureStatus}; use crate::accountant::db_access_objects::sent_payable_dao::{SentTx, TxStatus}; use crate::accountant::db_access_objects::utils::TxHash; -use crate::blockchain::blockchain_interface::data_structures::{TxReceiptError, TxReceiptResult}; use crate::blockchain::errors::validation_status::{ PreviousAttempts, ValidationFailureClock, ValidationStatus, }; @@ -11,6 +10,9 @@ use crate::blockchain::errors::BlockchainErrorKind; use masq_lib::logger::Logger; use masq_lib::ui_gateway::NodeToUiMessage; use std::collections::HashMap; +use itertools::Either; +use crate::accountant::TxReceiptResult; +use crate::blockchain::errors::rpc_errors::AppRpcError; #[derive(Debug, Default, PartialEq, Eq, Clone)] pub struct ReceiptScanReport { @@ -107,36 +109,22 @@ pub enum FailedValidationByTable { FailedPayable(FailedValidation), } -impl From<(TxReceiptError, TxStatus)> for FailedValidationByTable { - fn from((tx_receipt_error, current_status): (TxReceiptError, TxStatus)) -> Self { - match tx_receipt_error.tx_hash { - TxHashByTable::SentPayable(tx_hash) => Self::SentPayable(FailedValidation::new( - tx_hash, - BlockchainErrorKind::AppRpc(tx_receipt_error.err.into()), - current_status, - )), - - TxHashByTable::FailedPayable(tx_hash) => { - unreachable!( - "Mismatch in the type of tx record (failed tx) and status type (TxStatus) for {:?}", tx_hash - ) +impl FailedValidationByTable { + pub fn new(tx_hash: TxHash, error: AppRpcError, status: Either)-> Self { + match status { + Either::Left(tx_status) => { + Self::SentPayable(FailedValidation::new( + tx_hash, + BlockchainErrorKind::AppRpc(error.into()), + tx_status, + )) } - } - } -} - -impl From<(TxReceiptError, FailureStatus)> for FailedValidationByTable { - fn from((tx_receipt_error, current_status): (TxReceiptError, FailureStatus)) -> Self { - match tx_receipt_error.tx_hash { - TxHashByTable::FailedPayable(tx_hash) => Self::FailedPayable(FailedValidation::new( - tx_hash, - BlockchainErrorKind::AppRpc(tx_receipt_error.err.into()), - current_status, - )), - TxHashByTable::SentPayable(tx_hash) => { - unreachable!( - "Mismatch in the type of tx record (sent tx) and status type (FailureStatus) for {:?}",tx_hash - ) + Either::Right(failure_reason) => { + Self::FailedPayable(FailedValidation::new( + tx_hash, + BlockchainErrorKind::AppRpc(error.into()), + failure_reason, + )) } } } @@ -341,7 +329,13 @@ pub enum TxByTable { FailedPayable(FailedTx), } -#[derive(Debug, PartialEq, Eq, Clone, Copy)] +impl TxByTable { + pub fn hash(&self) -> TxHash { + todo!() + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] pub enum TxHashByTable { SentPayable(TxHash), FailedPayable(TxHash), @@ -355,12 +349,12 @@ mod tests { use crate::accountant::scanners::pending_payable_scanner::utils::{ CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailedValidation, FailedValidationByTable, NormalTxConfirmation, PendingPayableCache, PresortedTxFailure, - ReceiptScanReport, RecheckRequiringFailures, Retry, TxHashByTable, TxReceiptError, + ReceiptScanReport, RecheckRequiringFailures, Retry, TxReclaim, }; use crate::accountant::test_utils::{make_failed_tx, make_sent_tx}; use crate::blockchain::errors::rpc_errors::{ - AppRpcError, AppRpcErrorKind, LocalError, RemoteError, + AppRpcErrorKind }; use crate::blockchain::errors::validation_status::{ PreviousAttempts, ValidationFailureClockReal, ValidationStatus, @@ -916,82 +910,6 @@ mod tests { ); } - #[test] - fn tx_receipt_error_can_be_converted_to_failed_validation_by_table() { - let tx_hash_sent_tx = make_tx_hash(123); - let api_error_sent_tx = AppRpcError::Local(LocalError::Internal); - let receipt_error_for_sent_tx = TxReceiptError::new( - TxHashByTable::SentPayable(tx_hash_sent_tx), - api_error_sent_tx.clone(), - ); - let tx_hash_failed_tx = make_tx_hash(456); - let api_error_failed_tx = AppRpcError::Remote(RemoteError::Unreachable); - let receipt_error_for_failed_tx = TxReceiptError::new( - TxHashByTable::FailedPayable(tx_hash_failed_tx), - api_error_failed_tx.clone(), - ); - - let result_1 = FailedValidationByTable::from(( - receipt_error_for_sent_tx, - TxStatus::Pending(ValidationStatus::Waiting), - )); - let result_2 = FailedValidationByTable::from(( - receipt_error_for_failed_tx, - FailureStatus::RecheckRequired(ValidationStatus::Waiting), - )); - - assert_eq!( - result_1, - FailedValidationByTable::SentPayable(FailedValidation::new( - tx_hash_sent_tx, - BlockchainErrorKind::AppRpc(api_error_sent_tx.into()), - TxStatus::Pending(ValidationStatus::Waiting) - )) - ); - assert_eq!( - result_2, - FailedValidationByTable::FailedPayable(FailedValidation::new( - tx_hash_failed_tx, - BlockchainErrorKind::AppRpc(api_error_failed_tx.into()), - FailureStatus::RecheckRequired(ValidationStatus::Waiting) - )) - ); - } - - #[test] - #[should_panic( - expected = "Mismatch in the type of tx record (failed tx) and status type (TxStatus) for \ - 0x000000000000000000000000000000000000000000000000000000000000007b" - )] - fn tx_status_mismatch_in_conversion_to_failed_validation_by_table() { - let tx_hash = make_tx_hash(123); - let api_error = AppRpcError::Local(LocalError::Internal); - let mismatched_receipt_error = - TxReceiptError::new(TxHashByTable::FailedPayable(tx_hash), api_error); - - let _ = FailedValidationByTable::from(( - mismatched_receipt_error, - TxStatus::Pending(ValidationStatus::Waiting), - )); - } - - #[test] - #[should_panic( - expected = "Mismatch in the type of tx record (sent tx) and status type (FailureStatus) for \ - 0x000000000000000000000000000000000000000000000000000000000000007b" - )] - fn tx_status_mismatch_in_conversion_to_failed_validation_by_table_2() { - let tx_hash = make_tx_hash(123); - let api_error = AppRpcError::Local(LocalError::Internal); - let mismatched_receipt_error = - TxReceiptError::new(TxHashByTable::SentPayable(tx_hash), api_error); - - let _ = FailedValidationByTable::from(( - mismatched_receipt_error, - FailureStatus::RecheckRequired(ValidationStatus::Waiting), - )); - } - #[test] fn failed_validation_new_status_works_for_tx_statuses() { let timestamp_a = SystemTime::now(); diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index 258b79ed7..38e38195c 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -4,9 +4,7 @@ use crate::accountant::db_access_objects::sent_payable_dao::SentTx; use crate::accountant::scanners::payable_scanner_extension::msgs::{ BlockchainAgentWithContextMessage, PricedQualifiedPayables, QualifiedPayablesMessage, }; -use crate::accountant::{ - ReceivedPayments, ResponseSkeleton, ScanError, SentPayables, SkeletonOptHolder, -}; +use crate::accountant::{ReceivedPayments, ResponseSkeleton, ScanError, SentPayables, SkeletonOptHolder, TxReceiptResult}; use crate::accountant::{RequestTransactionReceipts, TxReceiptsMessage}; use crate::actor_system_factory::SubsFactory; use crate::blockchain::blockchain_agent::BlockchainAgent; @@ -14,7 +12,7 @@ use crate::blockchain::blockchain_interface::data_structures::errors::{ BlockchainInterfaceError, PayableTransactionError, }; use crate::blockchain::blockchain_interface::data_structures::{ - ProcessedPayableFallible, StatusReadFromReceiptCheck, TxReceiptResult, + ProcessedPayableFallible, StatusReadFromReceiptCheck, }; use crate::blockchain::blockchain_interface::BlockchainInterface; use crate::blockchain::blockchain_interface_initializer::BlockchainInterfaceInitializer; @@ -43,6 +41,7 @@ use std::path::Path; use std::string::ToString; use std::sync::{Arc, Mutex}; use std::time::SystemTime; +use itertools::Itertools; use web3::types::H256; pub const CRASH_KEY: &str = "BLOCKCHAINBRIDGE"; @@ -384,21 +383,21 @@ impl BlockchainBridge { fn log_status_of_tx_receipts( logger: &Logger, - transaction_receipts_results: &[TxReceiptResult], + transaction_receipts_results: &[&TxReceiptResult], ) { logger.debug(|| { let (successful_count, failed_count, pending_count) = transaction_receipts_results.iter().fold( (0, 0, 0), |(success, fail, pending), transaction_receipt| match transaction_receipt { - TxReceiptResult(Ok(tx_receipt)) => match tx_receipt.status { + Ok(tx_status) => match tx_status { StatusReadFromReceiptCheck::Reverted => (success, fail + 1, pending), StatusReadFromReceiptCheck::Succeeded(_) => { (success + 1, fail, pending) } StatusReadFromReceiptCheck::Pending => (success, fail, pending + 1), }, - TxReceiptResult(Err(_)) => (success, fail, pending + 1), + Err(_) => (success, fail, pending + 1), }, ); format!( @@ -423,7 +422,7 @@ impl BlockchainBridge { .process_transaction_receipts(msg.tx_hashes) .map_err(move |e| e.to_string()) .and_then(move |tx_receipt_results| { - Self::log_status_of_tx_receipts(&logger, tx_receipt_results.as_slice()); + Self::log_status_of_tx_receipts(&logger, tx_receipt_results.values().collect_vec().as_slice()); accountant_recipient .try_send(TxReceiptsMessage { results: tx_receipt_results, @@ -546,8 +545,7 @@ mod tests { }; use crate::blockchain::blockchain_interface::data_structures::ProcessedPayableFallible::Correct; use crate::blockchain::blockchain_interface::data_structures::{ - BlockchainTransaction, RetrievedBlockchainTransactions, RetrievedTxStatus, TxBlock, - TxReceiptError, + BlockchainTransaction, RetrievedBlockchainTransactions, TxBlock, }; use crate::blockchain::errors::rpc_errors::{AppRpcError, RemoteError}; use crate::blockchain::errors::validation_status::ValidationStatus; @@ -1208,15 +1206,13 @@ mod tests { assert_eq!( tx_receipts_message, &TxReceiptsMessage { - results: vec![ - TxReceiptResult(Ok(RetrievedTxStatus::new( - TxHashByTable::SentPayable(tx_hash_1), + results: hashmap![ + TxHashByTable::SentPayable(tx_hash_1) => Ok( expected_receipt.into() - ))), - TxReceiptResult(Ok(RetrievedTxStatus::new( - TxHashByTable::FailedPayable(tx_hash_2), + ), + TxHashByTable::FailedPayable(tx_hash_2) => Ok( StatusReadFromReceiptCheck::Pending - ))) + ) ], response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, @@ -1347,17 +1343,14 @@ mod tests { assert_eq!( *report_receipts_msg, TxReceiptsMessage { - results: vec![ - TxReceiptResult(Ok(RetrievedTxStatus::new(TxHashByTable::SentPayable(tx_hash_1), StatusReadFromReceiptCheck::Pending))), - TxReceiptResult(Ok(RetrievedTxStatus::new(TxHashByTable::SentPayable(tx_hash_2), StatusReadFromReceiptCheck::Succeeded(TxBlock { + results: hashmap![TxHashByTable::SentPayable(tx_hash_1) => Ok(StatusReadFromReceiptCheck::Pending), + TxHashByTable::SentPayable(tx_hash_2) => Ok(StatusReadFromReceiptCheck::Succeeded(TxBlock { block_hash: Default::default(), block_number, - })))), - TxReceiptResult(Err( - TxReceiptError::new( - TxHashByTable::SentPayable(tx_hash_3), - AppRpcError:: Remote(RemoteError::Web3RpcError { code: 429, message: "The requests per second (RPS) of your requests are higher than your plan allows.".to_string()})))), - TxReceiptResult(Ok(RetrievedTxStatus::new(TxHashByTable::SentPayable(tx_hash_4), StatusReadFromReceiptCheck::Pending))), + })), + TxHashByTable::SentPayable(tx_hash_3) => Err( + AppRpcError:: Remote(RemoteError::Web3RpcError { code: 429, message: "The requests per second (RPS) of your requests are higher than your plan allows.".to_string()})), + TxHashByTable::SentPayable(tx_hash_4) => Ok(StatusReadFromReceiptCheck::Pending), ], response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs index f453b1a79..fcb3ebaa4 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs @@ -4,8 +4,9 @@ pub mod lower_level_interface_web3; mod utils; use std::cmp::PartialEq; +use std::collections::HashMap; use crate::blockchain::blockchain_interface::data_structures::errors::{BlockchainInterfaceError, PayableTransactionError}; -use crate::blockchain::blockchain_interface::data_structures::{BlockchainTransaction, ProcessedPayableFallible, RetrievedTxStatus, StatusReadFromReceiptCheck, TxReceiptError, TxReceiptResult}; +use crate::blockchain::blockchain_interface::data_structures::{BlockchainTransaction, ProcessedPayableFallible, StatusReadFromReceiptCheck}; use crate::blockchain::blockchain_interface::lower_level_interface::LowBlockchainInt; use crate::blockchain::blockchain_interface::RetrievedBlockchainTransactions; use crate::blockchain::blockchain_interface::{BlockchainAgentBuildError, BlockchainInterface}; @@ -24,6 +25,7 @@ use crate::accountant::scanners::payable_scanner_extension::msgs::{PricedQualifi use crate::blockchain::blockchain_agent::BlockchainAgent; use crate::accountant::db_access_objects::utils::TxHash; use crate::accountant::scanners::pending_payable_scanner::utils::TxHashByTable; +use crate::accountant::TxReceiptResult; use crate::blockchain::blockchain_bridge::{BlockMarker, BlockScanRange, RegisterNewPendingPayables}; use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::LowBlockchainIntWeb3; use crate::blockchain::blockchain_interface::blockchain_interface_web3::utils::{create_blockchain_agent_web3, send_payables_within_batch, BlockchainAgentFutureResult}; @@ -216,7 +218,7 @@ impl BlockchainInterface for BlockchainInterfaceWeb3 { fn process_transaction_receipts( &self, tx_hashes: Vec, - ) -> Box, Error = BlockchainInterfaceError>> { + ) -> Box, Error = BlockchainInterfaceError>> { Box::new( self.lower_interface() .get_transaction_receipt_in_batch(Self::collect_plain_hashes(&tx_hashes)) @@ -228,30 +230,23 @@ impl BlockchainInterface for BlockchainInterfaceWeb3 { .map(|(response, tx_hash)| match response { Ok(result) => { match serde_json::from_value::(result) { - Ok(receipt) => TxReceiptResult(Ok(RetrievedTxStatus::new( - tx_hash, - receipt.into(), - ))), + Ok(receipt) => (tx_hash, Ok(StatusReadFromReceiptCheck::from(receipt))), Err(e) => { if e.to_string().contains("invalid type: null") { - TxReceiptResult(Ok(RetrievedTxStatus::new( - tx_hash, - StatusReadFromReceiptCheck::Pending, - ))) + (tx_hash, Ok(StatusReadFromReceiptCheck::Pending)) } else { - TxReceiptResult(Err(TxReceiptError::new( - tx_hash, + (tx_hash, Err( AppRpcError::Remote(RemoteError::InvalidResponse( e.to_string(), - )), + ) ))) } } } } - Err(e) => TxReceiptResult(Err(TxReceiptError::new(tx_hash, e.into()))), + Err(e) => (tx_hash, Err(AppRpcError::from(e))), }) - .collect::>()) + .collect::>()) }), ) } @@ -1129,9 +1124,7 @@ mod tests { .wait() .unwrap(); - assert_eq!(result[0], TxReceiptResult(Err( - TxReceiptError::new( - tx_hbt_1, + assert_eq!(result.get(&tx_hbt_1).unwrap(), &Err( AppRpcError::Remote( RemoteError::Web3RpcError { code: 429, @@ -1139,47 +1132,36 @@ mod tests { "The requests per second (RPS) of your requests are higher than your plan allows." .to_string() } - )))) + )) ); assert_eq!( - result[1], - TxReceiptResult(Ok(RetrievedTxStatus::new( - tx_hbt_2, + result.get(&tx_hbt_2).unwrap(), + &Ok( StatusReadFromReceiptCheck::Pending - ))) + ) ); assert_eq!( - result[2], - TxReceiptResult(Err(TxReceiptError::new( - tx_hbt_3, - AppRpcError::Remote(RemoteError::InvalidResponse( + result.get(&tx_hbt_3).unwrap(), + &Err(AppRpcError::Remote(RemoteError::InvalidResponse( "invalid type: string \"trash\", expected struct Receipt".to_string() - )) - ))) + ) + )) ); assert_eq!( - result[3], - TxReceiptResult(Ok(RetrievedTxStatus::new( - tx_hbt_4, - StatusReadFromReceiptCheck::Pending - ))) + result.get(&tx_hbt_4).unwrap(), + &Ok(StatusReadFromReceiptCheck::Pending) ); assert_eq!( - result[4], - TxReceiptResult(Ok(RetrievedTxStatus::new( - tx_hbt_5, - StatusReadFromReceiptCheck::Reverted - ))) + result.get(&tx_hbt_5).unwrap(), + &Ok(StatusReadFromReceiptCheck::Reverted) ); assert_eq!( - result[5], - TxReceiptResult(Ok(RetrievedTxStatus::new( - tx_hbt_6, - StatusReadFromReceiptCheck::Succeeded(TxBlock { + result.get(&tx_hbt_6).unwrap(), + &Ok(StatusReadFromReceiptCheck::Succeeded(TxBlock { block_hash, block_number, }), - ))) + ) ) } diff --git a/node/src/blockchain/blockchain_interface/data_structures/mod.rs b/node/src/blockchain/blockchain_interface/data_structures/mod.rs index 0cf71f798..595692c54 100644 --- a/node/src/blockchain/blockchain_interface/data_structures/mod.rs +++ b/node/src/blockchain/blockchain_interface/data_structures/mod.rs @@ -6,9 +6,7 @@ use crate::accountant::db_access_objects::utils::TxHash; use crate::accountant::scanners::pending_payable_scanner::utils::TxHashByTable; use crate::accountant::PendingPayable; use crate::blockchain::blockchain_bridge::BlockMarker; -use crate::blockchain::errors::rpc_errors::AppRpcError; use crate::sub_lib::wallet::Wallet; -use actix::Message; use ethereum_types::U64; use serde_derive::{Deserialize, Serialize}; use std::fmt; @@ -52,18 +50,6 @@ pub enum ProcessedPayableFallible { Failed(RpcPayableFailure), } -#[derive(Debug, PartialEq, Eq, Message, Clone)] -pub struct TxReceiptResult(pub Result); - -impl TxReceiptResult { - pub fn hash(&self) -> TxHashByTable { - match &self.0 { - Ok(retrieved_tx_status) => retrieved_tx_status.tx_hash, - Err(tx_receipt_error) => tx_receipt_error.tx_hash, - } - } -} - #[derive(Debug, PartialEq, Eq, Clone)] pub struct RetrievedTxStatus { pub tx_hash: TxHashByTable, @@ -101,18 +87,6 @@ impl Display for StatusReadFromReceiptCheck { } } -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct TxReceiptError { - pub tx_hash: TxHashByTable, - pub err: AppRpcError, -} - -impl TxReceiptError { - pub fn new(tx_hash: TxHashByTable, err: AppRpcError) -> Self { - Self { tx_hash, err } - } -} - #[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Ord, PartialOrd, Serialize, Deserialize)] pub struct TxBlock { pub block_hash: H256, @@ -121,13 +95,9 @@ pub struct TxBlock { #[cfg(test)] mod tests { - use crate::accountant::scanners::pending_payable_scanner::utils::TxHashByTable; - use crate::accountant::test_utils::make_transaction_block; use crate::blockchain::blockchain_interface::data_structures::{ - RetrievedTxStatus, StatusReadFromReceiptCheck, TxBlock, TxReceiptError, TxReceiptResult, + StatusReadFromReceiptCheck, TxBlock, }; - use crate::blockchain::errors::rpc_errors::{AppRpcError, LocalError, RemoteError}; - use crate::blockchain::test_utils::make_tx_hash; use ethereum_types::{H256, U64}; #[test] @@ -150,38 +120,4 @@ mod tests { format!("Succeeded({},0x{:x})", block_number, block_hash) ); } - - #[test] - fn hash_can_be_fetched_from_tx_receipt_result() { - let hash_1 = TxHashByTable::SentPayable(make_tx_hash(123)); - let hash_2 = TxHashByTable::SentPayable(make_tx_hash(111)); - let hash_3 = TxHashByTable::FailedPayable(make_tx_hash(222)); - let hash_4 = TxHashByTable::FailedPayable(make_tx_hash(321)); - let positive_with_sent_payable = TxReceiptResult(Ok(RetrievedTxStatus::new( - hash_1, - StatusReadFromReceiptCheck::Pending, - ))); - let negative_with_sent_payable = TxReceiptResult(Err(TxReceiptError::new( - hash_2, - AppRpcError::Local(LocalError::Internal), - ))); - let positive_with_failed_payable = TxReceiptResult(Ok(RetrievedTxStatus::new( - hash_3, - StatusReadFromReceiptCheck::Succeeded(make_transaction_block(789)), - ))); - let negative_with_failed_payable = TxReceiptResult(Err(TxReceiptError::new( - hash_4, - AppRpcError::Remote(RemoteError::Unreachable), - ))); - - let result_1 = positive_with_sent_payable.hash(); - let result_2 = negative_with_sent_payable.hash(); - let result_3 = positive_with_failed_payable.hash(); - let result_4 = negative_with_failed_payable.hash(); - - assert_eq!(result_1, hash_1); - assert_eq!(result_2, hash_2); - assert_eq!(result_3, hash_3); - assert_eq!(result_4, hash_4); - } } diff --git a/node/src/blockchain/blockchain_interface/mod.rs b/node/src/blockchain/blockchain_interface/mod.rs index 8a8f8aad3..bdca40311 100644 --- a/node/src/blockchain/blockchain_interface/mod.rs +++ b/node/src/blockchain/blockchain_interface/mod.rs @@ -4,6 +4,7 @@ pub mod blockchain_interface_web3; pub mod data_structures; pub mod lower_level_interface; +use std::collections::HashMap; use crate::accountant::scanners::payable_scanner_extension::msgs::PricedQualifiedPayables; use crate::accountant::scanners::pending_payable_scanner::utils::TxHashByTable; use crate::blockchain::blockchain_agent::BlockchainAgent; @@ -14,7 +15,7 @@ use crate::blockchain::blockchain_interface::data_structures::errors::{ BlockchainAgentBuildError, BlockchainInterfaceError, PayableTransactionError, }; use crate::blockchain::blockchain_interface::data_structures::{ - ProcessedPayableFallible, RetrievedBlockchainTransactions, TxReceiptResult, + ProcessedPayableFallible, RetrievedBlockchainTransactions, }; use crate::blockchain::blockchain_interface::lower_level_interface::LowBlockchainInt; use crate::sub_lib::wallet::Wallet; @@ -23,6 +24,7 @@ use futures::Future; use masq_lib::blockchains::chains::Chain; use masq_lib::logger::Logger; use web3::types::Address; +use crate::accountant::TxReceiptResult; pub trait BlockchainInterface { fn contract_address(&self) -> Address; @@ -46,7 +48,7 @@ pub trait BlockchainInterface { fn process_transaction_receipts( &self, tx_hashes: Vec, - ) -> Box, Error = BlockchainInterfaceError>>; + ) -> Box, Error = BlockchainInterfaceError>>; fn submit_payables_in_batch( &self, diff --git a/node/src/blockchain/errors/mod.rs b/node/src/blockchain/errors/mod.rs index ab18ae9df..8e84ad35b 100644 --- a/node/src/blockchain/errors/mod.rs +++ b/node/src/blockchain/errors/mod.rs @@ -18,6 +18,7 @@ pub enum BlockchainError { pub enum BlockchainErrorKind { AppRpc(AppRpcErrorKind), Internal(InternalErrorKind), + Booo } #[cfg(test)] From 4f056ef9b114daaf4838db81b6324df893e669bc Mon Sep 17 00:00:00 2001 From: Bert Date: Mon, 8 Sep 2025 13:26:51 +0200 Subject: [PATCH 52/61] GH-642: finally solid... as much as under this card, tests fixed --- .../db_access_objects/failed_payable_dao.rs | 8 +- node/src/accountant/mod.rs | 16 +- node/src/accountant/scanners/mod.rs | 15 +- .../scanners/pending_payable_scanner/mod.rs | 164 +++++--------- .../tx_receipt_interpreter.rs | 27 +-- .../scanners/pending_payable_scanner/utils.rs | 200 +++++++++++------- node/src/accountant/test_utils.rs | 18 +- node/src/blockchain/blockchain_bridge.rs | 11 +- .../blockchain_interface_web3/mod.rs | 38 ++-- .../blockchain/blockchain_interface/mod.rs | 11 +- node/src/blockchain/errors/mod.rs | 1 - 11 files changed, 249 insertions(+), 260 deletions(-) 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 c1819d691..b39e03a87 100644 --- a/node/src/accountant/db_access_objects/failed_payable_dao.rs +++ b/node/src/accountant/db_access_objects/failed_payable_dao.rs @@ -62,7 +62,12 @@ impl Display for FailureStatus { match serde_json::to_string(self) { Ok(json) => write!(f, "{}", json), // Untestable - Err(e) => panic!("cat: {:?}, line: {}, column: {}", e.classify(), e.line(), e.column()) //write!(f, ""), + Err(e) => panic!( + "cat: {:?}, line: {}, column: {}", + e.classify(), + e.line(), + e.column() + ), //write!(f, ""), } } } @@ -416,7 +421,6 @@ mod tests { use std::ops::Add; use std::str::FromStr; use std::time::{Duration, SystemTime}; - use crate::blockchain::errors::internal_errors::InternalErrorKind; #[test] fn insert_new_records_works() { diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index b39d50b30..e3877dd68 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -37,7 +37,10 @@ use crate::blockchain::blockchain_bridge::{ BlockMarker, RegisterNewPendingPayables, RetrieveTransactions, }; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; -use crate::blockchain::blockchain_interface::data_structures::{BlockchainTransaction, ProcessedPayableFallible, StatusReadFromReceiptCheck}; +use crate::blockchain::blockchain_interface::data_structures::{ + BlockchainTransaction, ProcessedPayableFallible, StatusReadFromReceiptCheck, +}; +use crate::blockchain::errors::rpc_errors::AppRpcError; use crate::bootstrapper::BootstrapperConfig; use crate::database::db_initializer::DbInitializationConfig; use crate::sub_lib::accountant::AccountantSubs; @@ -82,7 +85,6 @@ use std::path::Path; use std::rc::Rc; use std::time::SystemTime; use web3::types::H256; -use crate::blockchain::errors::rpc_errors::AppRpcError; pub const CRASH_KEY: &str = "ACCOUNTANT"; pub const DEFAULT_PENDING_TOO_LONG_SEC: u64 = 21_600; //6 hours @@ -3544,12 +3546,10 @@ mod tests { )]), response_skeleton_opt: None, }; - let tx_status = - StatusReadFromReceiptCheck::Succeeded(TxBlock { - block_hash: make_tx_hash(369369), - block_number: 4444444444u64.into(), - }, - ); + let tx_status = StatusReadFromReceiptCheck::Succeeded(TxBlock { + block_hash: make_tx_hash(369369), + block_number: 4444444444u64.into(), + }); let counter_msg_3 = TxReceiptsMessage { results: hashmap![TxHashByTable::SentPayable(tx_hash) => Ok(tx_status)], response_skeleton_opt: None, diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index 5789ea80e..69fb14ec8 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -2624,8 +2624,7 @@ mod tests { block_hash: make_block_hash(333), block_number: U64::from(1234), }; - let tx_status_1 = - StatusReadFromReceiptCheck::Succeeded(tx_block_1); + let tx_status_1 = StatusReadFromReceiptCheck::Succeeded(tx_block_1); let tx_hash_2 = make_tx_hash(1234); let mut failed_tx_2 = make_failed_tx(789); failed_tx_2.hash = tx_hash_2; @@ -2633,18 +2632,15 @@ mod tests { block_hash: make_block_hash(222), block_number: U64::from(2345), }; - let tx_status_2 = - StatusReadFromReceiptCheck::Succeeded(tx_block_2); + let tx_status_2 = StatusReadFromReceiptCheck::Succeeded(tx_block_2); let tx_hash_3 = make_tx_hash(2345); let mut sent_tx_3 = make_sent_tx(456); sent_tx_3.hash = tx_hash_3; - let tx_status_3 = - StatusReadFromReceiptCheck::Pending; + let tx_status_3 = StatusReadFromReceiptCheck::Pending; let mut sent_tx_4 = make_sent_tx(4567); let tx_hash_4 = sent_tx_4.hash; sent_tx_4.status = TxStatus::Pending(ValidationStatus::Waiting); - let tx_receipt_rpc_error_4 = - AppRpcError::Remote(RemoteError::Unreachable); + let tx_receipt_rpc_error_4 = AppRpcError::Remote(RemoteError::Unreachable); let tx_hash_5 = make_tx_hash(7890); let mut failed_tx_5 = make_failed_tx(888); failed_tx_5.hash = tx_hash_5; @@ -2653,7 +2649,8 @@ mod tests { BlockchainErrorKind::AppRpc(AppRpcErrorKind::ServerUnreachable), &ValidationFailureClockMock::default().now_result(timestamp_c), ))); - let tx_receipt_rpc_error_5 = AppRpcError::Remote(RemoteError::InvalidResponse("game over".to_string())); + let tx_receipt_rpc_error_5 = + AppRpcError::Remote(RemoteError::InvalidResponse("game over".to_string())); let tx_hash_6 = make_tx_hash(2345); let mut sent_tx_6 = make_sent_tx(789); sent_tx_6.hash = tx_hash_6; diff --git a/node/src/accountant/scanners/pending_payable_scanner/mod.rs b/node/src/accountant/scanners/pending_payable_scanner/mod.rs index 6b3dcc1c5..2748d5aea 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/mod.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/mod.rs @@ -15,15 +15,18 @@ use crate::accountant::db_access_objects::utils::{TxHash, TxRecordWithHash}; use crate::accountant::scanners::pending_payable_scanner::tx_receipt_interpreter::TxReceiptInterpreter; use crate::accountant::scanners::pending_payable_scanner::utils::{ CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailedValidation, - FailedValidationByTable, MismatchReport, NormalTxConfirmation, PendingPayableCache, - PendingPayableScanResult, PresortedTxFailure, ReceiptScanReport, RecheckRequiringFailures, - Retry, TxByTable, TxCaseToBeInterpreted, TxHashByTable, TxReclaim, UpdatableValidationStatus, + FailedValidationByTable, MismatchReport, PendingPayableCache, PendingPayableScanResult, + PresortedTxFailure, ReceiptScanReport, RecheckRequiringFailures, Retry, TxByTable, + TxCaseToBeInterpreted, TxHashByTable, UpdatableValidationStatus, }; use crate::accountant::scanners::{ PrivateScanner, Scanner, ScannerCommon, StartScanError, StartableScanner, }; -use crate::accountant::{comma_joined_stringifiable, RequestTransactionReceipts, ResponseSkeleton, ScanForPendingPayables, TxReceiptResult, TxReceiptsMessage}; -use crate::blockchain::blockchain_interface::data_structures::{TxBlock}; +use crate::accountant::{ + comma_joined_stringifiable, RequestTransactionReceipts, ResponseSkeleton, + ScanForPendingPayables, TxReceiptResult, TxReceiptsMessage, +}; +use crate::blockchain::blockchain_interface::data_structures::TxBlock; use crate::blockchain::errors::validation_status::{ ValidationFailureClock, ValidationFailureClockReal, }; @@ -250,15 +253,19 @@ impl PendingPayableScanner { let either = msg .results .into_iter() - .fold(init, |acc, (tx_hash_by_table, tx_receipt_result)| match acc { - Either::Left(cases) => { - self.resolve_real_query(cases, tx_receipt_result, tx_hash_by_table) - } - Either::Right(mut mismatch_report) => { - mismatch_report.remaining_hashes.push(tx_hash_by_table); - Either::Right(mismatch_report) - } - }); + .sorted_by_key(|(hash_by_table, _)| hash_by_table.hash()) + .fold( + init, + |acc, (tx_hash_by_table, tx_receipt_result)| match acc { + Either::Left(cases) => { + self.resolve_real_query(cases, tx_receipt_result, tx_hash_by_table) + } + Either::Right(mut mismatch_report) => { + mismatch_report.remaining_hashes.push(tx_hash_by_table); + Either::Right(mismatch_report) + } + }, + ); let cases = match either { Either::Left(cases) => cases, @@ -288,7 +295,7 @@ impl PendingPayableScanner { Either::Left(cases) } None => Either::Right(MismatchReport { - noticed_at: looked_up_hash, + noticed_with: looked_up_hash, remaining_hashes: vec![], }), } @@ -306,7 +313,7 @@ impl PendingPayableScanner { Either::Left(cases) } None => Either::Right(MismatchReport { - noticed_at: looked_up_hash, + noticed_with: looked_up_hash, remaining_hashes: vec![], }), } @@ -327,7 +334,7 @@ impl PendingPayableScanner { "Looking up '{:?}' in the cache, the record could not be found. Dumping \ the remaining values. Pending payables: {:?}. Unproven failures: {:?}. \ All yet-to-look-up hashes: {:?}.", - mismatch_report.noticed_at, + mismatch_report.noticed_with, rearrange(self.current_sent_payables.dump_cache()), rearrange(self.yet_unproven_failed_payables.dump_cache()), mismatch_report.remaining_hashes @@ -348,13 +355,7 @@ impl PendingPayableScanner { self.handle_normal_confirmations(confirmed_txs.normal_confirmations, logger); } - fn handle_tx_failure_reclaims(&mut self, reclaimed: Vec, logger: &Logger) { - fn unwrap_into_sent_tx(confirmed_txs: Vec) -> Vec { - confirmed_txs - .into_iter() - .map(|reclaim| reclaim.reclaimed) - .collect() - } + fn handle_tx_failure_reclaims(&mut self, reclaimed: Vec, logger: &Logger) { fn isolate_hashes(reclaimed: &[(TxHash, TxBlock)]) -> HashSet { reclaimed.iter().map(|(tx_hash, _)| *tx_hash).collect() } @@ -363,12 +364,11 @@ impl PendingPayableScanner { return; } - let sent_txs_to_reclaim = unwrap_into_sent_tx(reclaimed); - let hashes_and_blocks = Self::collect_hashes_and_blocks(&sent_txs_to_reclaim) + let hashes_and_blocks = Self::collect_hashes_and_blocks(&reclaimed) .into_iter() .sorted() .collect_vec(); - match self.sent_payable_dao.replace_records(&sent_txs_to_reclaim) { + match self.sent_payable_dao.replace_records(&reclaimed) { Ok(_) => { debug!(logger, "Replaced records for txs being reclaimed") } @@ -408,7 +408,7 @@ impl PendingPayableScanner { } } - self.add_to_the_total_of_paid_payable(&sent_txs_to_reclaim, logger) + self.add_to_the_total_of_paid_payable(&reclaimed, logger) } fn collect_hashes_and_blocks(reclaimed: &[SentTx]) -> HashMap { @@ -433,17 +433,7 @@ impl PendingPayableScanner { .collect() } - fn handle_normal_confirmations( - &mut self, - confirmed_txs: Vec, - logger: &Logger, - ) { - fn unwrap_into_sent_tx(confirmed_txs: Vec) -> Vec { - confirmed_txs - .into_iter() - .map(|normal_tx_confirmation| normal_tx_confirmation.tx) - .collect() - } + fn handle_normal_confirmations(&mut self, confirmed_txs: Vec, logger: &Logger) { fn transaction_confirmed_panic(confirmed_txs: &[SentTx], e: PayableDaoError) -> ! { panic!( "Unable to complete the tx confirmation by the adjustment of the payable \ @@ -476,11 +466,10 @@ impl PendingPayableScanner { return; } - let confirmed_sent_txs = unwrap_into_sent_tx(confirmed_txs); - if let Err(e) = self.payable_dao.transactions_confirmed(&confirmed_sent_txs) { - transaction_confirmed_panic(&confirmed_sent_txs, e) + if let Err(e) = self.payable_dao.transactions_confirmed(&confirmed_txs) { + transaction_confirmed_panic(&confirmed_txs, e) } else { - let tx_confirmations = Self::collect_hashes_and_blocks(&confirmed_sent_txs); + let tx_confirmations = Self::collect_hashes_and_blocks(&confirmed_txs); if let Err(e) = self.sent_payable_dao.confirm_txs(&tx_confirmations) { update_tx_blocks_panic(&tx_confirmations, e) @@ -488,7 +477,7 @@ impl PendingPayableScanner { Self::log_tx_success(logger, &tx_confirmations); } - self.add_to_the_total_of_paid_payable(&confirmed_sent_txs, logger); + self.add_to_the_total_of_paid_payable(&confirmed_txs, logger); } } @@ -798,9 +787,8 @@ mod tests { use crate::accountant::scanners::pending_payable_scanner::test_utils::ValidationFailureClockMock; use crate::accountant::scanners::pending_payable_scanner::utils::{ CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailedValidation, - FailedValidationByTable, NormalTxConfirmation, PendingPayableCache, - PendingPayableScanResult, PresortedTxFailure, RecheckRequiringFailures, Retry, - TxHashByTable, TxReclaim, + FailedValidationByTable, PendingPayableCache, PendingPayableScanResult, PresortedTxFailure, + RecheckRequiringFailures, Retry, TxHashByTable, }; use crate::accountant::scanners::pending_payable_scanner::PendingPayableScanner; use crate::accountant::scanners::test_utils::PendingPayableCacheMock; @@ -976,7 +964,7 @@ mod tests { fn finish_scan_with_missing_records_inside_caches_noticed_on_missing_sent_tx() { let sent_tx_1 = make_sent_tx(456); let sent_tx_hash_1 = sent_tx_1.hash; - let sent_tx_hash_2 = make_tx_hash(777); + let sent_tx_hash_2 = make_tx_hash(876); let failed_tx_1 = make_failed_tx(567); let failed_tx_hash_1 = failed_tx_1.hash; let failed_tx_2 = make_failed_tx(890); @@ -1004,18 +992,14 @@ mod tests { let panic_msg = panic.downcast_ref::().unwrap(); let regex_str_in_pieces = vec![ - r#"Looking up 'SentPayable\(0x0000000000000000000000000000000000000000000000000000000000000309\)'"#, + r#"Looking up 'SentPayable\(0x000000000000000000000000000000000000000000000000000000000000036c\)'"#, r#" in the cache, the record could not be found. Dumping the remaining values. Pending payables: \[\]."#, - r#" Unproven failures: \[FailedTx \{"#, - r#" hash: 0x0000000000000000000000000000000000000000000000000000000000000237, receiver_address:"#, - r#" 0x000000000000000000000077616c6c6574353637, amount_minor: 321489000000000, timestamp: \d*,"#, - r#" gas_price_minor: 567000000000, nonce: 567, reason: PendingTooLong, status: RetryRequired \},"#, - r#" FailedTx \{ hash:"#, + r#" Unproven failures: \[FailedTx \{ hash:"#, r#" 0x000000000000000000000000000000000000000000000000000000000000037a, receiver_address:"#, r#" 0x000000000000000000000077616c6c6574383930, amount_minor: 792100000000000, timestamp: \d*,"#, r#" gas_price_minor: 890000000000, nonce: 890, reason: PendingTooLong, status: RetryRequired \}\]."#, - r#" All yet-to-look-up hashes: \[FailedPayable\(0x000000000000000000000000000000000000000000000000000"#, - r#"0000000000237\), FailedPayable\(0x000000000000000000000000000000000000000000000000000000000000037a\)\]."#, + r#" All yet-to-look-up hashes: \[FailedPayable\(0x0000000000000000000000000000000000000000"#, + r#"00000000000000000000037a\)\]."#, ]; let regex_str = regex_str_in_pieces.join(""); let expected_msg_regex = Regex::new(®ex_str).unwrap(); @@ -1322,7 +1306,7 @@ mod tests { #[should_panic( expected = "Unable to update pending-tx statuses for validation failures \ '[FailedValidation { tx_hash: 0x00000000000000000000000000000000000000000000000000000000000001c8, \ - validation_failure: Local(Internal), current_status: Pending(Waiting) }]' due to: \ + validation_failure: AppRpc(Internal), current_status: Pending(Waiting) }]' due to: \ InvalidInput(\"bluh\")" )] fn update_validation_status_for_sent_txs_panics_on_update_statuses() { @@ -1346,7 +1330,7 @@ mod tests { #[should_panic( expected = "Unable to update failed-tx statuses for validation failures \ '[FailedValidation { tx_hash: 0x00000000000000000000000000000000000000000000000000000000000001c8, \ - validation_failure: Local(Internal), current_status: RecheckRequired(Waiting) }]' due to: \ + validation_failure: AppRpc(Internal), current_status: RecheckRequired(Waiting) }]' due to: \ InvalidInput(\"bluh\")" )] fn update_validation_status_for_failed_txs_panics_on_update_statuses() { @@ -1590,14 +1574,7 @@ mod tests { subject.handle_confirmed_transactions( DetectedConfirmations { normal_confirmations: vec![], - reclaims: vec![ - TxReclaim { - reclaimed: sent_tx_1.clone(), - }, - TxReclaim { - reclaimed: sent_tx_2.clone(), - }, - ], + reclaims: vec![sent_tx_1.clone(), sent_tx_2.clone()], }, &logger, ); @@ -1655,14 +1632,7 @@ mod tests { subject.handle_confirmed_transactions( DetectedConfirmations { normal_confirmations: vec![], - reclaims: vec![ - TxReclaim { - reclaimed: sent_tx_1.clone(), - }, - TxReclaim { - reclaimed: sent_tx_2.clone(), - }, - ], + reclaims: vec![sent_tx_1.clone(), sent_tx_2.clone()], }, &Logger::new("test"), ); @@ -1709,14 +1679,7 @@ mod tests { subject.handle_confirmed_transactions( DetectedConfirmations { normal_confirmations: vec![], - reclaims: vec![ - TxReclaim { - reclaimed: sent_tx_1.clone(), - }, - TxReclaim { - reclaimed: sent_tx_2.clone(), - }, - ], + reclaims: vec![sent_tx_1.clone(), sent_tx_2.clone()], }, &Logger::new("test"), ); @@ -1739,9 +1702,7 @@ mod tests { subject.handle_confirmed_transactions( DetectedConfirmations { normal_confirmations: vec![], - reclaims: vec![TxReclaim { - reclaimed: sent_tx.clone(), - }], + reclaims: vec![sent_tx.clone()], }, &Logger::new("test"), ); @@ -1791,14 +1752,7 @@ mod tests { subject.handle_confirmed_transactions( DetectedConfirmations { - normal_confirmations: vec![ - NormalTxConfirmation { - tx: sent_tx_1.clone(), - }, - NormalTxConfirmation { - tx: sent_tx_2.clone(), - }, - ], + normal_confirmations: vec![sent_tx_1.clone(), sent_tx_2.clone()], reclaims: vec![], }, &logger, @@ -1874,12 +1828,8 @@ mod tests { subject.handle_confirmed_transactions( DetectedConfirmations { - normal_confirmations: vec![NormalTxConfirmation { - tx: sent_tx_1.clone(), - }], - reclaims: vec![TxReclaim { - reclaimed: sent_tx_2.clone(), - }], + normal_confirmations: vec![sent_tx_1.clone()], + reclaims: vec![sent_tx_2.clone()], }, &logger, ); @@ -1941,10 +1891,7 @@ mod tests { subject.handle_confirmed_transactions( DetectedConfirmations { - normal_confirmations: vec![ - NormalTxConfirmation { tx: sent_tx_1 }, - NormalTxConfirmation { tx: sent_tx_2 }, - ], + normal_confirmations: vec![sent_tx_1, sent_tx_2], reclaims: vec![], }, &Logger::new("test"), @@ -1970,7 +1917,7 @@ mod tests { subject.handle_confirmed_transactions( DetectedConfirmations { - normal_confirmations: vec![NormalTxConfirmation { tx: sent_tx }], + normal_confirmations: vec![sent_tx], reclaims: vec![], }, &Logger::new("test"), @@ -2052,13 +1999,8 @@ mod tests { subject.handle_confirmed_transactions( DetectedConfirmations { - normal_confirmations: vec![ - NormalTxConfirmation { tx: sent_tx_1 }, - NormalTxConfirmation { tx: sent_tx_2 }, - ], - reclaims: vec![TxReclaim { - reclaimed: sent_tx_3, - }], + normal_confirmations: vec![sent_tx_1, sent_tx_2], + reclaims: vec![sent_tx_3], }, &Logger::new(test_name), ); 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 ee2fcf012..8c6b6a044 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 @@ -6,20 +6,20 @@ use crate::accountant::db_access_objects::sent_payable_dao::{ }; use crate::accountant::db_access_objects::utils::from_unix_timestamp; use crate::accountant::scanners::pending_payable_scanner::utils::{ - FailedValidation, FailedValidationByTable, NormalTxConfirmation, ReceiptScanReport, TxByTable, - TxCaseToBeInterpreted, TxReclaim, + ConfirmationType, FailedValidation, FailedValidationByTable, ReceiptScanReport, TxByTable, + TxCaseToBeInterpreted, TxHashByTable, }; use crate::accountant::scanners::pending_payable_scanner::PendingPayableScanner; use crate::blockchain::blockchain_interface::data_structures::{ StatusReadFromReceiptCheck, TxBlock, }; use crate::blockchain::errors::internal_errors::InternalErrorKind; +use crate::blockchain::errors::rpc_errors::AppRpcError; use crate::blockchain::errors::BlockchainErrorKind; +use itertools::Either; use masq_lib::logger::Logger; use std::time::SystemTime; -use itertools::Either; use thousands::Separable; -use crate::blockchain::errors::rpc_errors::AppRpcError; #[derive(Default)] pub struct TxReceiptInterpreter {} @@ -149,10 +149,7 @@ impl TxReceiptInterpreter { }, ..sent_tx }; - let tx_confirmation = NormalTxConfirmation { - tx: completed_sent_tx, - }; - scan_report.register_confirmed_tx(tx_confirmation); + scan_report.register_confirmed_tx(completed_sent_tx, ConfirmationType::Normal); } TxByTable::FailedPayable(failed_tx) => { info!( @@ -162,8 +159,7 @@ impl TxReceiptInterpreter { ); let sent_tx = SentTx::from((failed_tx, tx_block)); - let reclaim = TxReclaim { reclaimed: sent_tx }; - scan_report.register_failure_reclaim(reclaim); + scan_report.register_confirmed_tx(sent_tx, ConfirmationType::Reclaim); } } scan_report @@ -204,13 +200,13 @@ impl TxReceiptInterpreter { rpc_error: AppRpcError, logger: &Logger, ) -> ReceiptScanReport { - let hash = tx_by_table.hash(); warning!( logger, "Failed to retrieve tx receipt for {:?}: {:?}. Will retry receipt retrieval next cycle", - hash, + TxHashByTable::from(&tx_by_table), rpc_error ); + let hash = tx_by_table.hash(); let validation_status_update = match tx_by_table { TxByTable::SentPayable(sent_tx) => { FailedValidationByTable::new(hash, rpc_error, Either::Left(sent_tx.status)) @@ -236,8 +232,7 @@ mod tests { use crate::accountant::scanners::pending_payable_scanner::tx_receipt_interpreter::TxReceiptInterpreter; use crate::accountant::scanners::pending_payable_scanner::utils::{ DetectedConfirmations, DetectedFailures, FailedValidation, FailedValidationByTable, - NormalTxConfirmation, PresortedTxFailure, ReceiptScanReport, TxByTable, - TxReclaim, + PresortedTxFailure, ReceiptScanReport, TxByTable, }; use crate::accountant::test_utils::{ make_failed_tx, make_sent_tx, make_transaction_block, SentPayableDaoMock, @@ -288,7 +283,7 @@ mod tests { ReceiptScanReport { failures: DetectedFailures::default(), confirmations: DetectedConfirmations { - normal_confirmations: vec![NormalTxConfirmation { tx: updated_tx }], + normal_confirmations: vec![updated_tx], reclaims: vec![] } } @@ -337,7 +332,7 @@ mod tests { failures: DetectedFailures::default(), confirmations: DetectedConfirmations { normal_confirmations: vec![], - reclaims: vec![TxReclaim { reclaimed: sent_tx }] + reclaims: vec![sent_tx] } } ); diff --git a/node/src/accountant/scanners/pending_payable_scanner/utils.rs b/node/src/accountant/scanners/pending_payable_scanner/utils.rs index 7bdf8e1a6..d2f5493c2 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/utils.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/utils.rs @@ -3,16 +3,16 @@ use crate::accountant::db_access_objects::failed_payable_dao::{FailedTx, FailureStatus}; use crate::accountant::db_access_objects::sent_payable_dao::{SentTx, TxStatus}; use crate::accountant::db_access_objects::utils::TxHash; +use crate::accountant::TxReceiptResult; +use crate::blockchain::errors::rpc_errors::AppRpcError; use crate::blockchain::errors::validation_status::{ PreviousAttempts, ValidationFailureClock, ValidationStatus, }; use crate::blockchain::errors::BlockchainErrorKind; +use itertools::Either; use masq_lib::logger::Logger; use masq_lib::ui_gateway::NodeToUiMessage; use std::collections::HashMap; -use itertools::Either; -use crate::accountant::TxReceiptResult; -use crate::blockchain::errors::rpc_errors::AppRpcError; #[derive(Debug, Default, PartialEq, Eq, Clone)] pub struct ReceiptScanReport { @@ -32,12 +32,15 @@ impl ReceiptScanReport { } } - pub(super) fn register_confirmed_tx(&mut self, confirmation: NormalTxConfirmation) { - self.confirmations.normal_confirmations.push(confirmation); - } - - pub(super) fn register_failure_reclaim(&mut self, reclaim: TxReclaim) { - self.confirmations.reclaims.push(reclaim) + pub(super) fn register_confirmed_tx( + &mut self, + confirmed_tx: SentTx, + confirmation_type: ConfirmationType, + ) { + match confirmation_type { + ConfirmationType::Normal => self.confirmations.normal_confirmations.push(confirmed_tx), + ConfirmationType::Reclaim => self.confirmations.reclaims.push(confirmed_tx), + } } pub(super) fn register_new_failure(&mut self, failed_tx: FailedTx) { @@ -59,18 +62,8 @@ impl ReceiptScanReport { #[derive(Debug, Default, PartialEq, Eq, Clone)] pub struct DetectedConfirmations { - pub normal_confirmations: Vec, - pub reclaims: Vec, -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct NormalTxConfirmation { - pub tx: SentTx, -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct TxReclaim { - pub reclaimed: SentTx, + pub normal_confirmations: Vec, + pub reclaims: Vec, } impl DetectedConfirmations { @@ -79,6 +72,12 @@ impl DetectedConfirmations { } } +#[derive(Debug, PartialEq, Eq)] +pub enum ConfirmationType { + Normal, + Reclaim, +} + #[derive(Debug, Default, PartialEq, Eq, Clone)] pub struct DetectedFailures { pub tx_failures: Vec, @@ -110,22 +109,22 @@ pub enum FailedValidationByTable { } impl FailedValidationByTable { - pub fn new(tx_hash: TxHash, error: AppRpcError, status: Either)-> Self { + pub fn new( + tx_hash: TxHash, + error: AppRpcError, + status: Either, + ) -> Self { match status { - Either::Left(tx_status) => { - Self::SentPayable(FailedValidation::new( - tx_hash, - BlockchainErrorKind::AppRpc(error.into()), - tx_status, - )) - } - Either::Right(failure_reason) => { - Self::FailedPayable(FailedValidation::new( - tx_hash, - BlockchainErrorKind::AppRpc(error.into()), - failure_reason, - )) - } + Either::Left(tx_status) => Self::SentPayable(FailedValidation::new( + tx_hash, + BlockchainErrorKind::AppRpc(error.into()), + tx_status, + )), + Either::Right(failure_reason) => Self::FailedPayable(FailedValidation::new( + tx_hash, + BlockchainErrorKind::AppRpc(error.into()), + failure_reason, + )), } } } @@ -214,7 +213,7 @@ impl UpdatableValidationStatus for FailureStatus { } pub struct MismatchReport { - pub noticed_at: TxHashByTable, + pub noticed_with: TxHashByTable, pub remaining_hashes: Vec, } @@ -324,6 +323,7 @@ impl TxCaseToBeInterpreted { } } +#[derive(Debug)] pub enum TxByTable { SentPayable(SentTx), FailedPayable(FailedTx), @@ -331,7 +331,10 @@ pub enum TxByTable { impl TxByTable { pub fn hash(&self) -> TxHash { - todo!() + match self { + TxByTable::SentPayable(tx) => tx.hash, + TxByTable::FailedPayable(tx) => tx.hash, + } } } @@ -341,6 +344,24 @@ pub enum TxHashByTable { FailedPayable(TxHash), } +impl TxHashByTable { + pub fn hash(&self) -> TxHash { + match self { + TxHashByTable::SentPayable(hash) => *hash, + TxHashByTable::FailedPayable(hash) => *hash, + } + } +} + +impl From<&TxByTable> for TxHashByTable { + fn from(tx: &TxByTable) -> Self { + match tx { + TxByTable::SentPayable(tx) => TxHashByTable::SentPayable(tx.hash), + TxByTable::FailedPayable(tx) => TxHashByTable::FailedPayable(tx.hash), + } + } +} + #[cfg(test)] mod tests { use crate::accountant::db_access_objects::failed_payable_dao::FailureStatus; @@ -348,14 +369,11 @@ mod tests { use crate::accountant::scanners::pending_payable_scanner::test_utils::ValidationFailureClockMock; use crate::accountant::scanners::pending_payable_scanner::utils::{ CurrentPendingPayables, DetectedConfirmations, DetectedFailures, FailedValidation, - FailedValidationByTable, NormalTxConfirmation, PendingPayableCache, PresortedTxFailure, - ReceiptScanReport, RecheckRequiringFailures, Retry, - TxReclaim, + FailedValidationByTable, PendingPayableCache, PresortedTxFailure, ReceiptScanReport, + RecheckRequiringFailures, Retry, TxByTable, TxHashByTable, }; use crate::accountant::test_utils::{make_failed_tx, make_sent_tx}; - use crate::blockchain::errors::rpc_errors::{ - AppRpcErrorKind - }; + use crate::blockchain::errors::rpc_errors::AppRpcErrorKind; use crate::blockchain::errors::validation_status::{ PreviousAttempts, ValidationFailureClockReal, ValidationStatus, }; @@ -422,24 +440,16 @@ mod tests { reclaims: vec![], }, DetectedConfirmations { - normal_confirmations: vec![NormalTxConfirmation { - tx: make_sent_tx(456), - }], - reclaims: vec![TxReclaim { - reclaimed: make_sent_tx(999), - }], + normal_confirmations: vec![make_sent_tx(456)], + reclaims: vec![make_sent_tx(999)], }, DetectedConfirmations { - normal_confirmations: vec![NormalTxConfirmation { - tx: make_sent_tx(777), - }], + normal_confirmations: vec![make_sent_tx(777)], reclaims: vec![], }, DetectedConfirmations { normal_confirmations: vec![], - reclaims: vec![TxReclaim { - reclaimed: make_sent_tx(999), - }], + reclaims: vec![make_sent_tx(999)], }, ]; @@ -514,24 +524,16 @@ mod tests { reclaims: vec![], }, DetectedConfirmations { - normal_confirmations: vec![NormalTxConfirmation { - tx: make_sent_tx(777), - }], - reclaims: vec![TxReclaim { - reclaimed: make_sent_tx(999), - }], + normal_confirmations: vec![make_sent_tx(777)], + reclaims: vec![make_sent_tx(999)], }, DetectedConfirmations { - normal_confirmations: vec![NormalTxConfirmation { - tx: make_sent_tx(777), - }], + normal_confirmations: vec![make_sent_tx(777)], reclaims: vec![], }, DetectedConfirmations { normal_confirmations: vec![], - reclaims: vec![TxReclaim { - reclaimed: make_sent_tx(999), - }], + reclaims: vec![make_sent_tx(999)], }, ]; @@ -556,24 +558,16 @@ mod tests { fn requires_payments_retry_says_no() { let detected_confirmations_feeding = vec![ DetectedConfirmations { - normal_confirmations: vec![NormalTxConfirmation { - tx: make_sent_tx(777), - }], - reclaims: vec![TxReclaim { - reclaimed: make_sent_tx(999), - }], + normal_confirmations: vec![make_sent_tx(777)], + reclaims: vec![make_sent_tx(999)], }, DetectedConfirmations { - normal_confirmations: vec![NormalTxConfirmation { - tx: make_sent_tx(777), - }], + normal_confirmations: vec![make_sent_tx(777)], reclaims: vec![], }, DetectedConfirmations { normal_confirmations: vec![], - reclaims: vec![TxReclaim { - reclaimed: make_sent_tx(999), - }], + reclaims: vec![make_sent_tx(999)], }, ]; @@ -1087,4 +1081,50 @@ mod tests { ) }); } + + #[test] + fn tx_hash_by_table_provides_plain_hash() { + let expected_hash_a = make_tx_hash(123); + let a = TxHashByTable::SentPayable(expected_hash_a); + let expected_hash_b = make_tx_hash(654); + let b = TxHashByTable::FailedPayable(expected_hash_b); + + let result_a = a.hash(); + let result_b = b.hash(); + + assert_eq!(result_a, expected_hash_a); + assert_eq!(result_b, expected_hash_b); + } + + #[test] + fn tx_by_table_can_provide_hash() { + let sent_tx = make_sent_tx(123); + let expected_hash_a = sent_tx.hash; + let a = TxByTable::SentPayable(sent_tx); + let failed_tx = make_failed_tx(654); + let expected_hash_b = failed_tx.hash; + let b = TxByTable::FailedPayable(failed_tx); + + let result_a = a.hash(); + let result_b = b.hash(); + + assert_eq!(result_a, expected_hash_a); + assert_eq!(result_b, expected_hash_b); + } + + #[test] + fn tx_by_table_can_be_converted_into_tx_hash_by_table() { + let sent_tx = make_sent_tx(123); + let expected_hash_a = sent_tx.hash; + let a = TxByTable::SentPayable(sent_tx); + let failed_tx = make_failed_tx(654); + let expected_hash_b = failed_tx.hash; + let b = TxByTable::FailedPayable(failed_tx); + + let result_a = TxHashByTable::from(&a); + let result_b = TxHashByTable::from(&b); + + assert_eq!(result_a, TxHashByTable::SentPayable(expected_hash_a)); + assert_eq!(result_b, TxHashByTable::FailedPayable(expected_hash_b)); + } } diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs index f9311ce5f..5535ca183 100644 --- a/node/src/accountant/test_utils.rs +++ b/node/src/accountant/test_utils.rs @@ -449,13 +449,9 @@ impl AccountantBuilder { .make_result(SentPayableDaoMock::new()) .make_result(SentPayableDaoMock::new()), ); - let failed_tx_dao_for_pending_payable_scanner = - FailedPayableDaoMock::new().retrieve_txs_result(vec![]); - let failed_payable_dao_factory = self.failed_payable_dao_factory_opt.unwrap_or( - FailedPayableDaoFactoryMock::new() - .make_result(failed_tx_dao_for_pending_payable_scanner) - .make_result(FailedPayableDaoMock::new()), - ); + let failed_payable_dao_factory = self + .failed_payable_dao_factory_opt + .unwrap_or(FailedPayableDaoFactoryMock::new().make_result(FailedPayableDaoMock::new())); let banned_dao_factory = self .banned_dao_factory_opt .unwrap_or(BannedDaoFactoryMock::new().make_result(BannedDaoMock::new())); @@ -493,7 +489,8 @@ impl PayableDaoFactory for PayableDaoFactoryMock { fn make(&self) -> Box { if self.make_results.borrow().len() == 0 { panic!( - "PayableDao Missing. This problem mostly occurs when PayableDao is only supplied for Accountant and not for the Scanner while building Accountant." + "PayableDao Missing. This problem mostly occurs when PayableDao is only supplied \ + for Accountant and not for the Scanner while building Accountant." ) }; self.make_params.lock().unwrap().push(()); @@ -529,7 +526,8 @@ impl ReceivableDaoFactory for ReceivableDaoFactoryMock { fn make(&self) -> Box { if self.make_results.borrow().len() == 0 { panic!( - "ReceivableDao Missing. This problem mostly occurs when ReceivableDao is only supplied for Accountant and not for the Scanner while building Accountant." + "ReceivableDao Missing. This problem mostly occurs when ReceivableDao is only \ + supplied for Accountant and not for the Scanner while building Accountant." ) }; self.make_params.lock().unwrap().push(()); @@ -655,7 +653,7 @@ impl PayableDao for PayableDaoMock { &self, _mark_instructions: &[MarkPendingPayableID], ) -> Result<(), PayableDaoError> { - todo!() + todo!("will be removed in the associated card - GH-662") // self.mark_pending_payables_rowids_params // .lock() // .unwrap() diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index 38e38195c..d1d41337b 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -4,7 +4,9 @@ use crate::accountant::db_access_objects::sent_payable_dao::SentTx; use crate::accountant::scanners::payable_scanner_extension::msgs::{ BlockchainAgentWithContextMessage, PricedQualifiedPayables, QualifiedPayablesMessage, }; -use crate::accountant::{ReceivedPayments, ResponseSkeleton, ScanError, SentPayables, SkeletonOptHolder, TxReceiptResult}; +use crate::accountant::{ + ReceivedPayments, ResponseSkeleton, ScanError, SentPayables, SkeletonOptHolder, TxReceiptResult, +}; use crate::accountant::{RequestTransactionReceipts, TxReceiptsMessage}; use crate::actor_system_factory::SubsFactory; use crate::blockchain::blockchain_agent::BlockchainAgent; @@ -31,6 +33,7 @@ use actix::Handler; use actix::Message; use actix::{Addr, Recipient}; use futures::Future; +use itertools::Itertools; use masq_lib::blockchains::chains::Chain; use masq_lib::constants::DEFAULT_GAS_PRICE_MARGIN; use masq_lib::logger::Logger; @@ -41,7 +44,6 @@ use std::path::Path; use std::string::ToString; use std::sync::{Arc, Mutex}; use std::time::SystemTime; -use itertools::Itertools; use web3::types::H256; pub const CRASH_KEY: &str = "BLOCKCHAINBRIDGE"; @@ -422,7 +424,10 @@ impl BlockchainBridge { .process_transaction_receipts(msg.tx_hashes) .map_err(move |e| e.to_string()) .and_then(move |tx_receipt_results| { - Self::log_status_of_tx_receipts(&logger, tx_receipt_results.values().collect_vec().as_slice()); + Self::log_status_of_tx_receipts( + &logger, + tx_receipt_results.values().collect_vec().as_slice(), + ); accountant_recipient .try_send(TxReceiptsMessage { results: tx_receipt_results, diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs index fcb3ebaa4..4edaea99a 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs @@ -218,7 +218,12 @@ impl BlockchainInterface for BlockchainInterfaceWeb3 { fn process_transaction_receipts( &self, tx_hashes: Vec, - ) -> Box, Error = BlockchainInterfaceError>> { + ) -> Box< + dyn Future< + Item = HashMap, + Error = BlockchainInterfaceError, + >, + > { Box::new( self.lower_interface() .get_transaction_receipt_in_batch(Self::collect_plain_hashes(&tx_hashes)) @@ -230,16 +235,19 @@ impl BlockchainInterface for BlockchainInterfaceWeb3 { .map(|(response, tx_hash)| match response { Ok(result) => { match serde_json::from_value::(result) { - Ok(receipt) => (tx_hash, Ok(StatusReadFromReceiptCheck::from(receipt))), + Ok(receipt) => { + (tx_hash, Ok(StatusReadFromReceiptCheck::from(receipt))) + } Err(e) => { if e.to_string().contains("invalid type: null") { (tx_hash, Ok(StatusReadFromReceiptCheck::Pending)) } else { - (tx_hash, Err( - AppRpcError::Remote(RemoteError::InvalidResponse( - e.to_string(), - ) - ))) + ( + tx_hash, + Err(AppRpcError::Remote( + RemoteError::InvalidResponse(e.to_string()), + )), + ) } } } @@ -1136,16 +1144,13 @@ mod tests { ); assert_eq!( result.get(&tx_hbt_2).unwrap(), - &Ok( - StatusReadFromReceiptCheck::Pending - ) + &Ok(StatusReadFromReceiptCheck::Pending) ); assert_eq!( result.get(&tx_hbt_3).unwrap(), &Err(AppRpcError::Remote(RemoteError::InvalidResponse( - "invalid type: string \"trash\", expected struct Receipt".to_string() - ) - )) + "invalid type: string \"trash\", expected struct Receipt".to_string() + ))) ); assert_eq!( result.get(&tx_hbt_4).unwrap(), @@ -1158,10 +1163,9 @@ mod tests { assert_eq!( result.get(&tx_hbt_6).unwrap(), &Ok(StatusReadFromReceiptCheck::Succeeded(TxBlock { - block_hash, - block_number, - }), - ) + block_hash, + block_number, + }),) ) } diff --git a/node/src/blockchain/blockchain_interface/mod.rs b/node/src/blockchain/blockchain_interface/mod.rs index bdca40311..09961776e 100644 --- a/node/src/blockchain/blockchain_interface/mod.rs +++ b/node/src/blockchain/blockchain_interface/mod.rs @@ -4,9 +4,9 @@ pub mod blockchain_interface_web3; pub mod data_structures; pub mod lower_level_interface; -use std::collections::HashMap; use crate::accountant::scanners::payable_scanner_extension::msgs::PricedQualifiedPayables; use crate::accountant::scanners::pending_payable_scanner::utils::TxHashByTable; +use crate::accountant::TxReceiptResult; use crate::blockchain::blockchain_agent::BlockchainAgent; use crate::blockchain::blockchain_bridge::{ BlockMarker, BlockScanRange, RegisterNewPendingPayables, @@ -23,8 +23,8 @@ use actix::Recipient; use futures::Future; use masq_lib::blockchains::chains::Chain; use masq_lib::logger::Logger; +use std::collections::HashMap; use web3::types::Address; -use crate::accountant::TxReceiptResult; pub trait BlockchainInterface { fn contract_address(&self) -> Address; @@ -48,7 +48,12 @@ pub trait BlockchainInterface { fn process_transaction_receipts( &self, tx_hashes: Vec, - ) -> Box, Error = BlockchainInterfaceError>>; + ) -> Box< + dyn Future< + Item = HashMap, + Error = BlockchainInterfaceError, + >, + >; fn submit_payables_in_batch( &self, diff --git a/node/src/blockchain/errors/mod.rs b/node/src/blockchain/errors/mod.rs index 8e84ad35b..ab18ae9df 100644 --- a/node/src/blockchain/errors/mod.rs +++ b/node/src/blockchain/errors/mod.rs @@ -18,7 +18,6 @@ pub enum BlockchainError { pub enum BlockchainErrorKind { AppRpc(AppRpcErrorKind), Internal(InternalErrorKind), - Booo } #[cfg(test)] From e6ed5d56f4e7c5301b5de479f4d633a6db6a8613 Mon Sep 17 00:00:00 2001 From: Bert Date: Mon, 8 Sep 2025 20:31:01 +0200 Subject: [PATCH 53/61] GH-598-json-hotfix: interim commit --- node/src/accountant/mod.rs | 45 ++++++++++--------- .../accountant/scanners/scan_schedulers.rs | 36 +++++++-------- node/src/accountant/scanners/test_utils.rs | 8 ++-- 3 files changed, 46 insertions(+), 43 deletions(-) diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index e3877dd68..0926c2d93 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -29,7 +29,7 @@ use crate::accountant::scanners::pending_payable_scanner::utils::{ PendingPayableScanResult, Retry, TxHashByTable, }; use crate::accountant::scanners::scan_schedulers::{ - PayableSequenceScanner, ScanRescheduleAfterEarlyStop, ScanSchedulers, + PayableSequenceScanner, ScanReschedulingAfterEarlyStop, ScanSchedulers, }; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::OperationOutcome; use crate::accountant::scanners::{Scanners, StartScanError}; @@ -237,20 +237,20 @@ impl Handler for Accountant { self.handle_request_of_scan_for_pending_payable(response_skeleton_opt); match scheduling_hint { - ScanRescheduleAfterEarlyStop::Schedule(ScanType::Payables) => self + ScanReschedulingAfterEarlyStop::Schedule(ScanType::Payables) => self .scan_schedulers .payable .schedule_new_payable_scan(ctx, &self.logger), - ScanRescheduleAfterEarlyStop::Schedule(ScanType::PendingPayables) => self + ScanReschedulingAfterEarlyStop::Schedule(ScanType::PendingPayables) => self .scan_schedulers .pending_payable .schedule(ctx, &self.logger), - ScanRescheduleAfterEarlyStop::Schedule(scan_type) => unreachable!( + ScanReschedulingAfterEarlyStop::Schedule(scan_type) => unreachable!( "Early stopped pending payable scan was suggested to be followed up \ by the scan for {:?}, which is not supported though", scan_type ), - ScanRescheduleAfterEarlyStop::DoNotSchedule => { + ScanReschedulingAfterEarlyStop::DoNotSchedule => { trace!( self.logger, "No early rescheduling, as the pending payable scan did find results" @@ -274,16 +274,16 @@ impl Handler for Accountant { let scheduling_hint = self.handle_request_of_scan_for_new_payable(response_skeleton); match scheduling_hint { - ScanRescheduleAfterEarlyStop::Schedule(ScanType::Payables) => self + ScanReschedulingAfterEarlyStop::Schedule(ScanType::Payables) => self .scan_schedulers .payable .schedule_new_payable_scan(ctx, &self.logger), - ScanRescheduleAfterEarlyStop::Schedule(other_scan_type) => unreachable!( + ScanReschedulingAfterEarlyStop::Schedule(other_scan_type) => unreachable!( "Early stopped new payable scan was suggested to be followed up by the scan \ for {:?}, which is not supported though", other_scan_type ), - ScanRescheduleAfterEarlyStop::DoNotSchedule => { + ScanReschedulingAfterEarlyStop::DoNotSchedule => { trace!( self.logger, "No early rescheduling, as the new payable scan did find results" @@ -328,8 +328,8 @@ impl Handler for Accountant { .expect("UIGateway is not bound") .try_send(node_to_ui_msg) .expect("UIGateway is dead"); - // Externally triggered scan should never be allowed to spark a procedure that - // would bring over payables with fresh nonces. The job's done. + // Non-automatic pending-payable scan is not permitted to spark a payable scan + // bringing over new payables with fresh nonces. The job's done here. } else { self.scan_schedulers .payable @@ -343,6 +343,9 @@ impl Handler for Accountant { &self.logger, ), Retry::RetryTxStatusCheckOnly => todo!(), + // .scan_schedulers + // .pending_payable + // .schedule(ctx, &self.logger), }, }; } @@ -934,7 +937,7 @@ impl Accountant { fn handle_request_of_scan_for_new_payable( &mut self, response_skeleton_opt: Option, - ) -> ScanRescheduleAfterEarlyStop { + ) -> ScanReschedulingAfterEarlyStop { let result: Result = match self.consuming_wallet_opt.as_ref() { Some(consuming_wallet) => self.scanners.start_new_payable_scan_guarded( @@ -954,7 +957,7 @@ impl Accountant { .expect("BlockchainBridge is unbound") .try_send(scan_message) .expect("BlockchainBridge is dead"); - ScanRescheduleAfterEarlyStop::DoNotSchedule + ScanReschedulingAfterEarlyStop::DoNotSchedule } Err(e) => self.handle_start_scan_error_and_prevent_scan_stall_point( PayableSequenceScanner::NewPayables, @@ -1000,7 +1003,7 @@ impl Accountant { fn handle_request_of_scan_for_pending_payable( &mut self, response_skeleton_opt: Option, - ) -> ScanRescheduleAfterEarlyStop { + ) -> ScanReschedulingAfterEarlyStop { let result: Result = match self.consuming_wallet_opt.as_ref() { Some(consuming_wallet) => self.scanners.start_pending_payable_scan_guarded( @@ -1013,14 +1016,14 @@ impl Accountant { None => Err(StartScanError::NoConsumingWalletFound), }; - let hint: ScanRescheduleAfterEarlyStop = match result { + let hint: ScanReschedulingAfterEarlyStop = match result { Ok(scan_message) => { self.request_transaction_receipts_sub_opt .as_ref() .expect("BlockchainBridge is unbound") .try_send(scan_message) .expect("BlockchainBridge is dead"); - ScanRescheduleAfterEarlyStop::DoNotSchedule + ScanReschedulingAfterEarlyStop::DoNotSchedule } Err(e) => { let initial_pending_payable_scan = self.scanners.initial_pending_payable_scan(); @@ -1046,7 +1049,7 @@ impl Accountant { scanner: PayableSequenceScanner, e: StartScanError, response_skeleton_opt: Option, - ) -> ScanRescheduleAfterEarlyStop { + ) -> ScanReschedulingAfterEarlyStop { let is_externally_triggered = response_skeleton_opt.is_some(); e.log_error(&self.logger, scanner.into(), is_externally_triggered); @@ -3350,7 +3353,7 @@ mod tests { System::current().stop(); system.run(); let flag_after = subject.scanners.initial_pending_payable_scan(); - assert_eq!(hint, ScanRescheduleAfterEarlyStop::DoNotSchedule); + assert_eq!(hint, ScanReschedulingAfterEarlyStop::DoNotSchedule); assert_eq!(flag_before, true); assert_eq!(flag_after, false); let blockchain_bridge_recording = blockchain_bridge_recording_arc.lock().unwrap(); @@ -3371,7 +3374,7 @@ mod tests { let flag_after = subject.scanners.initial_pending_payable_scan(); assert_eq!( hint, - ScanRescheduleAfterEarlyStop::Schedule(ScanType::Payables) + ScanReschedulingAfterEarlyStop::Schedule(ScanType::Payables) ); assert_eq!(flag_before, true); assert_eq!(flag_after, false); @@ -3892,7 +3895,7 @@ mod tests { system.run(); assert_eq!( result, - ScanRescheduleAfterEarlyStop::Schedule(ScanType::Payables) + ScanReschedulingAfterEarlyStop::Schedule(ScanType::Payables) ); let blockchain_bridge_recordings = blockchain_bridge_recordings_arc.lock().unwrap(); assert_eq!(blockchain_bridge_recordings.len(), 0); @@ -3981,7 +3984,7 @@ mod tests { fn start_scan_error_in_new_payables_and_unexpected_reaction_by_receivable_scan_scheduling() { let mut subject = AccountantBuilder::default().build(); let reschedule_on_error_resolver = RescheduleScanOnErrorResolverMock::default() - .resolve_rescheduling_on_error_result(ScanRescheduleAfterEarlyStop::Schedule( + .resolve_rescheduling_on_error_result(ScanReschedulingAfterEarlyStop::Schedule( ScanType::Receivables, )); subject.scan_schedulers.reschedule_on_error_resolver = @@ -4206,7 +4209,7 @@ mod tests { { let mut subject = AccountantBuilder::default().build(); let reschedule_on_error_resolver = RescheduleScanOnErrorResolverMock::default() - .resolve_rescheduling_on_error_result(ScanRescheduleAfterEarlyStop::Schedule( + .resolve_rescheduling_on_error_result(ScanReschedulingAfterEarlyStop::Schedule( ScanType::Receivables, )); subject.scan_schedulers.reschedule_on_error_resolver = diff --git a/node/src/accountant/scanners/scan_schedulers.rs b/node/src/accountant/scanners/scan_schedulers.rs index 7a99605b0..03dad9942 100644 --- a/node/src/accountant/scanners/scan_schedulers.rs +++ b/node/src/accountant/scanners/scan_schedulers.rs @@ -44,7 +44,7 @@ pub enum PayableScanSchedulerError { } #[derive(Debug, PartialEq, Eq)] -pub enum ScanRescheduleAfterEarlyStop { +pub enum ScanReschedulingAfterEarlyStop { Schedule(ScanType), DoNotSchedule, } @@ -245,7 +245,7 @@ pub trait RescheduleScanOnErrorResolver { error: &StartScanError, is_externally_triggered: bool, logger: &Logger, - ) -> ScanRescheduleAfterEarlyStop; + ) -> ScanReschedulingAfterEarlyStop; } #[derive(Default)] @@ -258,7 +258,7 @@ impl RescheduleScanOnErrorResolver for RescheduleScanOnErrorResolverReal { error: &StartScanError, is_externally_triggered: bool, logger: &Logger, - ) -> ScanRescheduleAfterEarlyStop { + ) -> ScanReschedulingAfterEarlyStop { let reschedule_hint = match scanner { PayableSequenceScanner::NewPayables => { Self::resolve_new_payables(error, is_externally_triggered) @@ -285,16 +285,16 @@ impl RescheduleScanOnErrorResolverReal { fn resolve_new_payables( err: &StartScanError, is_externally_triggered: bool, - ) -> ScanRescheduleAfterEarlyStop { + ) -> ScanReschedulingAfterEarlyStop { if is_externally_triggered { - ScanRescheduleAfterEarlyStop::DoNotSchedule + ScanReschedulingAfterEarlyStop::DoNotSchedule } else if matches!(err, StartScanError::ScanAlreadyRunning { .. }) { unreachable!( "an automatic scan of NewPayableScanner should never interfere with itself {:?}", err ) } else { - ScanRescheduleAfterEarlyStop::Schedule(ScanType::Payables) + ScanReschedulingAfterEarlyStop::Schedule(ScanType::Payables) } } @@ -304,9 +304,9 @@ impl RescheduleScanOnErrorResolverReal { fn resolve_retry_payables( err: &StartScanError, is_externally_triggered: bool, - ) -> ScanRescheduleAfterEarlyStop { + ) -> ScanReschedulingAfterEarlyStop { if is_externally_triggered { - ScanRescheduleAfterEarlyStop::DoNotSchedule + ScanReschedulingAfterEarlyStop::DoNotSchedule } else { unreachable!( "{:?} should be impossible with RetryPayableScanner in automatic mode", @@ -319,12 +319,12 @@ impl RescheduleScanOnErrorResolverReal { err: &StartScanError, initial_pending_payable_scan: bool, is_externally_triggered: bool, - ) -> ScanRescheduleAfterEarlyStop { + ) -> ScanReschedulingAfterEarlyStop { if is_externally_triggered { - ScanRescheduleAfterEarlyStop::DoNotSchedule + ScanReschedulingAfterEarlyStop::DoNotSchedule } else if err == &StartScanError::NothingToProcess { if initial_pending_payable_scan { - ScanRescheduleAfterEarlyStop::Schedule(ScanType::Payables) + ScanReschedulingAfterEarlyStop::Schedule(ScanType::Payables) } else { unreachable!( "the automatic pending payable scan should always be requested only in need, \ @@ -340,7 +340,7 @@ impl RescheduleScanOnErrorResolverReal { // 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 - ScanRescheduleAfterEarlyStop::Schedule(ScanType::PendingPayables) + ScanReschedulingAfterEarlyStop::Schedule(ScanType::PendingPayables) } else { unreachable!( "PendingPayableScanner called later than the initial attempt, but \ @@ -359,7 +359,7 @@ impl RescheduleScanOnErrorResolverReal { scanner: PayableSequenceScanner, is_externally_triggered: bool, logger: &Logger, - reschedule_hint: &ScanRescheduleAfterEarlyStop, + reschedule_hint: &ScanReschedulingAfterEarlyStop, ) { let scan_mode = if is_externally_triggered { "Manual" @@ -381,7 +381,7 @@ impl RescheduleScanOnErrorResolverReal { mod tests { use crate::accountant::scanners::scan_schedulers::{ NewPayableScanDynIntervalComputer, NewPayableScanDynIntervalComputerReal, - PayableSequenceScanner, ScanRescheduleAfterEarlyStop, ScanSchedulers, + PayableSequenceScanner, ScanReschedulingAfterEarlyStop, ScanSchedulers, }; use crate::accountant::scanners::{ManulTriggerError, StartScanError}; use crate::sub_lib::accountant::ScanIntervals; @@ -634,7 +634,7 @@ mod tests { assert_eq!( result, - ScanRescheduleAfterEarlyStop::DoNotSchedule, + ScanReschedulingAfterEarlyStop::DoNotSchedule, "We expected DoNotSchedule but got {:?} at idx {} for {:?}", result, idx, @@ -669,7 +669,7 @@ mod tests { assert_eq!( result, - ScanRescheduleAfterEarlyStop::Schedule(ScanType::Payables), + ScanReschedulingAfterEarlyStop::Schedule(ScanType::Payables), "We expected Schedule(Payables) but got {:?}", result, ); @@ -722,7 +722,7 @@ mod tests { assert_eq!( result, - ScanRescheduleAfterEarlyStop::Schedule(ScanType::PendingPayables), + ScanReschedulingAfterEarlyStop::Schedule(ScanType::PendingPayables), "We expected Schedule(PendingPayables) but got {:?} for {:?}", result, scanner @@ -904,7 +904,7 @@ mod tests { assert_eq!( result, - ScanRescheduleAfterEarlyStop::Schedule(ScanType::Payables), + ScanReschedulingAfterEarlyStop::Schedule(ScanType::Payables), "We expected Schedule(Payables) but got '{:?}'", result, ); diff --git a/node/src/accountant/scanners/test_utils.rs b/node/src/accountant/scanners/test_utils.rs index dbdef54bb..a44a0c2ef 100644 --- a/node/src/accountant/scanners/test_utils.rs +++ b/node/src/accountant/scanners/test_utils.rs @@ -14,7 +14,7 @@ use crate::accountant::scanners::pending_payable_scanner::utils::{ }; use crate::accountant::scanners::scan_schedulers::{ NewPayableScanDynIntervalComputer, PayableSequenceScanner, RescheduleScanOnErrorResolver, - ScanRescheduleAfterEarlyStop, + ScanReschedulingAfterEarlyStop, }; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableScanResult; use crate::accountant::scanners::{ @@ -443,7 +443,7 @@ pub fn assert_timestamps_from_str(examined_str: &str, expected_timestamps: Vec>>, - resolve_rescheduling_on_error_results: RefCell>, + resolve_rescheduling_on_error_results: RefCell>, } impl RescheduleScanOnErrorResolver for RescheduleScanOnErrorResolverMock { @@ -453,7 +453,7 @@ impl RescheduleScanOnErrorResolver for RescheduleScanOnErrorResolverMock { error: &StartScanError, is_externally_triggered: bool, logger: &Logger, - ) -> ScanRescheduleAfterEarlyStop { + ) -> ScanReschedulingAfterEarlyStop { self.resolve_rescheduling_on_error_params .lock() .unwrap() @@ -479,7 +479,7 @@ impl RescheduleScanOnErrorResolverMock { } pub fn resolve_rescheduling_on_error_result( self, - result: ScanRescheduleAfterEarlyStop, + result: ScanReschedulingAfterEarlyStop, ) -> Self { self.resolve_rescheduling_on_error_results .borrow_mut() From dbee7b4674f717174d652f10dd13d58efb7f6669 Mon Sep 17 00:00:00 2001 From: Bert Date: Wed, 10 Sep 2025 11:46:16 +0200 Subject: [PATCH 54/61] GH-642: dragging the failing tests down to bare minimum --- .../db_access_objects/payable_dao.rs | 12 +++--- node/src/accountant/mod.rs | 14 +++---- node/src/accountant/scanners/mod.rs | 4 +- .../scanners/pending_payable_scanner/mod.rs | 42 +++++++++++-------- node/src/blockchain/blockchain_bridge.rs | 4 +- .../blockchain_interface_web3/mod.rs | 6 +-- .../blockchain/blockchain_interface/mod.rs | 4 +- 7 files changed, 46 insertions(+), 40 deletions(-) diff --git a/node/src/accountant/db_access_objects/payable_dao.rs b/node/src/accountant/db_access_objects/payable_dao.rs index 4fd9c31c0..0226c0c68 100644 --- a/node/src/accountant/db_access_objects/payable_dao.rs +++ b/node/src/accountant/db_access_objects/payable_dao.rs @@ -78,7 +78,7 @@ impl PayableDaoFactory for DaoFactoryReal { } pub struct MarkPendingPayableID { - pub wallet: Address, + pub receiver_wallet: Address, pub rowid: RowId, } @@ -915,7 +915,7 @@ mod tests { hash: TxHash, previous_timestamp: SystemTime, new_payable_timestamp: SystemTime, - wallet: Address, + receiver_wallet: Address, initial_amount_wei: u128, balance_change: u128, } @@ -931,7 +931,7 @@ mod tests { hash: make_tx_hash(12345), previous_timestamp: now.checked_sub(Duration::from_secs(45_000)).unwrap(), new_payable_timestamp: now.checked_sub(Duration::from_secs(2)).unwrap(), - wallet: make_wallet("bobbles").address(), + receiver_wallet: make_wallet("bobbles").address(), initial_amount_wei: initial_amount_1, balance_change: balance_change_1, }, @@ -939,7 +939,7 @@ mod tests { hash: make_tx_hash(54321), previous_timestamp: now.checked_sub(Duration::from_secs(22_000)).unwrap(), new_payable_timestamp: now.checked_sub(Duration::from_secs(2)).unwrap(), - wallet: make_wallet("yet more bobbles").address(), + receiver_wallet: make_wallet("yet more bobbles").address(), initial_amount_wei: initial_amount_2, balance_change: balance_change_2, }, @@ -949,7 +949,7 @@ mod tests { .map(|(idx, test_inputs)| { insert_payable_record_fn( conn, - &format!("{:?}", test_inputs.wallet), + &format!("{:?}", test_inputs.receiver_wallet), i128::try_from(test_inputs.initial_amount_wei).unwrap(), to_unix_timestamp(test_inputs.previous_timestamp), // TODO argument will be eliminated in GH-662 @@ -958,7 +958,7 @@ mod tests { let mut sent_tx = make_sent_tx((idx as u64 + 1) * 1234); sent_tx.hash = test_inputs.hash; sent_tx.amount_minor = test_inputs.balance_change; - sent_tx.receiver_address = test_inputs.wallet; + sent_tx.receiver_address = test_inputs.receiver_wallet; sent_tx.timestamp = to_unix_timestamp(test_inputs.new_payable_timestamp); sent_tx.amount_minor = test_inputs.balance_change; diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index 906c299e3..832140cd3 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -76,7 +76,7 @@ use masq_lib::ui_gateway::{MessageBody, MessagePath, MessageTarget}; use masq_lib::ui_gateway::{NodeFromUiMessage, NodeToUiMessage}; use masq_lib::utils::ExpectValue; use std::any::type_name; -use std::collections::BTreeMap; +use std::collections::{HashMap}; #[cfg(test)] use std::default::Default; use std::fmt::Display; @@ -145,7 +145,7 @@ pub type TxReceiptResult = Result; #[derive(Debug, PartialEq, Eq, Message, Clone)] pub struct TxReceiptsMessage { - pub results: BTreeMap, + pub results: HashMap, pub response_skeleton_opt: Option, } @@ -1973,7 +1973,7 @@ mod tests { block_number: 78901234.into(), }; let tx_receipts_msg = TxReceiptsMessage { - results: btreemap![TxHashByTable::SentPayable(sent_tx.hash) => Ok( + results: hashmap![TxHashByTable::SentPayable(sent_tx.hash) => Ok( StatusReadFromReceiptCheck::Succeeded(tx_block), )], response_skeleton_opt, @@ -2198,7 +2198,7 @@ mod tests { let first_counter_msg_setup = setup_for_counter_msg_triggered_via_type_id!( RequestTransactionReceipts, TxReceiptsMessage { - results: btreemap![TxHashByTable::SentPayable(sent_tx.hash) => Ok( + results: hashmap![TxHashByTable::SentPayable(sent_tx.hash) => Ok( StatusReadFromReceiptCheck::Reverted ),], response_skeleton_opt @@ -2824,7 +2824,7 @@ mod tests { let subject_addr: Addr = subject.start(); let subject_subs = Accountant::make_subs_from(&subject_addr); let expected_tx_receipts_msg = TxReceiptsMessage { - results: btreemap![TxHashByTable::SentPayable(tx_hash) => Ok( + results: hashmap![TxHashByTable::SentPayable(tx_hash) => Ok( StatusReadFromReceiptCheck::Reverted, )], response_skeleton_opt: None, @@ -3562,7 +3562,7 @@ mod tests { block_number: 4444444444u64.into(), }); let counter_msg_3 = TxReceiptsMessage { - results: btreemap![TxHashByTable::SentPayable(tx_hash) => Ok(tx_status)], + results: hashmap![TxHashByTable::SentPayable(tx_hash) => Ok(tx_status)], response_skeleton_opt: None, }; let request_transaction_receipts_msg = RequestTransactionReceipts { @@ -5290,7 +5290,7 @@ mod tests { seeds: Vec, ) -> (TxReceiptsMessage, Vec) { let (tx_receipt_results, tx_record_vec) = seeds.into_iter().enumerate().fold( - (btreemap![], vec![]), + (hashmap![], vec![]), |(mut tx_receipt_results, mut record_by_table_vec), (idx, seed_params)| { let tx_hash = seed_params.tx_hash; let status = seed_params.status; diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index 51909dd87..377961805 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -2682,7 +2682,7 @@ mod tests { .validation_failure_clock(Box::new(validation_failure_clock)) .build(); let msg = TxReceiptsMessage { - results: btreemap![ + results: hashmap![ TxHashByTable::SentPayable(tx_hash_1) => Ok(tx_status_1), TxHashByTable::FailedPayable(tx_hash_2) => Ok(tx_status_2), TxHashByTable::SentPayable(tx_hash_3) => Ok(tx_status_3), @@ -2759,7 +2759,7 @@ mod tests { fn pending_payable_scanner_handles_empty_report_transaction_receipts_message() { let mut pending_payable_scanner = PendingPayableScannerBuilder::new().build(); let msg = TxReceiptsMessage { - results: btreemap![], + results: hashmap![], response_skeleton_opt: None, }; pending_payable_scanner.mark_as_started(SystemTime::now()); diff --git a/node/src/accountant/scanners/pending_payable_scanner/mod.rs b/node/src/accountant/scanners/pending_payable_scanner/mod.rs index 5162eb960..f73d9c19f 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/mod.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/mod.rs @@ -253,6 +253,8 @@ impl PendingPayableScanner { let either = msg.results .into_iter() + // This must be in for predictability in tests + .sorted_by_key(|(hash_by_table,_)|{ hash_by_table.hash() }) .fold( init, |acc, (tx_hash_by_table, tx_receipt_result)| match acc { @@ -332,7 +334,7 @@ impl PendingPayableScanner { panic!( "Looking up '{:?}' in the cache, the record could not be found. Dumping \ the remaining values. Pending payables: {:?}. Unproven failures: {:?}. \ - All yet-to-look-up hashes: {:?}.", + Hashes yet not looked up: {:?}.", mismatch_report.noticed_with, rearrange(self.current_sent_payables.dump_cache()), rearrange(self.yet_unproven_failed_payables.dump_cache()), @@ -922,7 +924,7 @@ mod tests { let confirmed_tx_block_sent_tx = make_transaction_block(901); let confirmed_tx_block_failed_tx = make_transaction_block(902); let msg = TxReceiptsMessage { - results: btreemap![ + results: hashmap![ TxHashByTable::SentPayable(sent_tx_hash_1) => Ok(StatusReadFromReceiptCheck::Pending), TxHashByTable::SentPayable(sent_tx_hash_2) => Ok(StatusReadFromReceiptCheck::Succeeded(confirmed_tx_block_sent_tx)), TxHashByTable::FailedPayable(failed_tx_hash_1) => Err(AppRpcError::Local(LocalError::Internal)), @@ -963,13 +965,17 @@ mod tests { #[test] fn finish_scan_with_missing_records_inside_caches_noticed_on_missing_sent_tx() { - let sent_tx_1 = make_sent_tx(456); - let sent_tx_hash_1 = sent_tx_1.hash; - let sent_tx_hash_2 = make_tx_hash(876); - let failed_tx_1 = make_failed_tx(567); - let failed_tx_hash_1 = failed_tx_1.hash; - let failed_tx_2 = make_failed_tx(890); - let failed_tx_hash_2 = failed_tx_2.hash; + // Note: the ordering of the hashes matters in this test + let sent_tx_hash_1 = make_tx_hash(0x123); + let mut sent_tx_1 = make_sent_tx(456); + sent_tx_1.hash = sent_tx_hash_1; + let sent_tx_hash_2 = make_tx_hash(0x876); + let failed_tx_hash_1 = make_tx_hash(0x987); + let mut failed_tx_1 = make_failed_tx(567); + failed_tx_1.hash = failed_tx_hash_1; + let failed_tx_hash_2 = make_tx_hash(0x789); + let mut failed_tx_2 = make_failed_tx(890); + failed_tx_2.hash = failed_tx_hash_2; let mut pending_payable_cache = CurrentPendingPayables::default(); pending_payable_cache.load_cache(vec![sent_tx_1]); let mut failed_payable_cache = RecheckRequiringFailures::default(); @@ -979,7 +985,7 @@ mod tests { subject.yet_unproven_failed_payables = Box::new(failed_payable_cache); let logger = Logger::new("test"); let msg = TxReceiptsMessage { - results: btreemap![TxHashByTable::SentPayable(sent_tx_hash_1) => Ok( + results: hashmap![TxHashByTable::SentPayable(sent_tx_hash_1) => Ok( StatusReadFromReceiptCheck::Pending), TxHashByTable::SentPayable(sent_tx_hash_2) => Ok(StatusReadFromReceiptCheck::Succeeded(make_transaction_block(444))), TxHashByTable::FailedPayable(failed_tx_hash_1) => Err(AppRpcError::Local(LocalError::Internal)), @@ -993,14 +999,14 @@ mod tests { let panic_msg = panic.downcast_ref::().unwrap(); let regex_str_in_pieces = vec![ - r#"Looking up 'SentPayable\(0x000000000000000000000000000000000000000000000000000000000000036c\)'"#, + r#"Looking up 'SentPayable\(0x0000000000000000000000000000000000000000000000000000000000000876\)'"#, r#" in the cache, the record could not be found. Dumping the remaining values. Pending payables: \[\]."#, r#" Unproven failures: \[FailedTx \{ hash:"#, - r#" 0x000000000000000000000000000000000000000000000000000000000000037a, receiver_address:"#, - r#" 0x000000000000000000000077616c6c6574383930, amount_minor: 792100000000000, timestamp: \d*,"#, - r#" gas_price_minor: 890000000000, nonce: 890, reason: PendingTooLong, status: RetryRequired \}\]."#, - r#" All yet-to-look-up hashes: \[FailedPayable\(0x0000000000000000000000000000000000000000"#, - r#"00000000000000000000037a\)\]."#, + r#" 0x0000000000000000000000000000000000000000000000000000000000000987, receiver_address:"#, + r#" 0x000000000000000000000077616c6c6574353637, amount_minor: 321489000000000, timestamp: \d*,"#, + r#" gas_price_minor: 567000000000, nonce: 567, reason: PendingTooLong, status: RetryRequired \}\]."#, + r#" Hashes yet not looked up: \[FailedPayable\(0x000000000000000000000000000000000000000"#, + r#"0000000000000000000000987\)\]"#, ]; let regex_str = regex_str_in_pieces.join(""); let expected_msg_regex = Regex::new(®ex_str).unwrap(); @@ -1030,7 +1036,7 @@ mod tests { subject.yet_unproven_failed_payables = Box::new(failed_payable_cache); let logger = Logger::new("test"); let msg = TxReceiptsMessage { - results: btreemap![TxHashByTable::SentPayable(sent_tx_hash_1) => Ok(StatusReadFromReceiptCheck::Pending), + results: hashmap![TxHashByTable::SentPayable(sent_tx_hash_1) => Ok(StatusReadFromReceiptCheck::Pending), TxHashByTable::SentPayable(sent_tx_hash_2) => Ok(StatusReadFromReceiptCheck::Succeeded(make_transaction_block(444))), TxHashByTable::FailedPayable(failed_tx_hash_1) => Err(AppRpcError::Local(LocalError::Internal)), TxHashByTable::FailedPayable(failed_tx_hash_2) => Ok(StatusReadFromReceiptCheck::Succeeded(make_transaction_block(555))), @@ -1045,7 +1051,7 @@ mod tests { let regex_str_in_pieces = vec![ r#"Looking up 'FailedPayable\(0x0000000000000000000000000000000000000000000000000000000000000385\)'"#, r#" in the cache, the record could not be found. Dumping the remaining values. Pending payables: \[\]."#, - r#" Unproven failures: \[\]. All yet-to-look-up hashes: \[\]."#, + r#" Unproven failures: \[\]. Hashes yet not looked up: \[\]."#, ]; let regex_str = regex_str_in_pieces.join(""); let expected_msg_regex = Regex::new(®ex_str).unwrap(); diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index d10c7790a..d1d41337b 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -1211,7 +1211,7 @@ mod tests { assert_eq!( tx_receipts_message, &TxReceiptsMessage { - results: btreemap![ + results: hashmap![ TxHashByTable::SentPayable(tx_hash_1) => Ok( expected_receipt.into() ), @@ -1348,7 +1348,7 @@ mod tests { assert_eq!( *report_receipts_msg, TxReceiptsMessage { - results: btreemap![TxHashByTable::SentPayable(tx_hash_1) => Ok(StatusReadFromReceiptCheck::Pending), + results: hashmap![TxHashByTable::SentPayable(tx_hash_1) => Ok(StatusReadFromReceiptCheck::Pending), TxHashByTable::SentPayable(tx_hash_2) => Ok(StatusReadFromReceiptCheck::Succeeded(TxBlock { block_hash: Default::default(), block_number, diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs index 21f739c55..7178d9d90 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs @@ -4,7 +4,7 @@ pub mod lower_level_interface_web3; mod utils; use std::cmp::PartialEq; -use std::collections::{BTreeMap}; +use std::collections::{HashMap}; use crate::blockchain::blockchain_interface::data_structures::errors::{BlockchainInterfaceError, PayableTransactionError}; use crate::blockchain::blockchain_interface::data_structures::{BlockchainTransaction, ProcessedPayableFallible, StatusReadFromReceiptCheck}; use crate::blockchain::blockchain_interface::lower_level_interface::LowBlockchainInt; @@ -220,7 +220,7 @@ impl BlockchainInterface for BlockchainInterfaceWeb3 { tx_hashes: Vec, ) -> Box< dyn Future< - Item = BTreeMap, + Item = HashMap, Error = BlockchainInterfaceError, >, > { @@ -254,7 +254,7 @@ impl BlockchainInterface for BlockchainInterfaceWeb3 { } Err(e) => (tx_hash, Err(AppRpcError::from(e))), }) - .collect::>()) + .collect::>()) }), ) } diff --git a/node/src/blockchain/blockchain_interface/mod.rs b/node/src/blockchain/blockchain_interface/mod.rs index 9b76dc63b..b32ff302a 100644 --- a/node/src/blockchain/blockchain_interface/mod.rs +++ b/node/src/blockchain/blockchain_interface/mod.rs @@ -23,7 +23,7 @@ use actix::Recipient; use futures::Future; use masq_lib::blockchains::chains::Chain; use masq_lib::logger::Logger; -use std::collections::BTreeMap; +use std::collections::{HashMap}; use web3::types::Address; pub trait BlockchainInterface { @@ -50,7 +50,7 @@ pub trait BlockchainInterface { tx_hashes: Vec, ) -> Box< dyn Future< - Item = BTreeMap, + Item = HashMap, Error = BlockchainInterfaceError, >, >; From 73dc080b8886e4632b8f3a17a681fa7f0fb0f512 Mon Sep 17 00:00:00 2001 From: Bert Date: Wed, 10 Sep 2025 16:47:04 +0200 Subject: [PATCH 55/61] GH-642: interim commit --- node/src/accountant/mod.rs | 2 +- node/src/accountant/scanners/mod.rs | 160 ++---------------- .../scanners/pending_payable_scanner/mod.rs | 84 ++++----- .../scanners/pending_payable_scanner/utils.rs | 150 ++++++++-------- node/src/accountant/scanners/test_utils.rs | 4 +- node/src/accountant/test_utils.rs | 8 +- .../blockchain/blockchain_interface/mod.rs | 2 +- node/src/blockchain/errors/internal_errors.rs | 2 +- node/src/blockchain/errors/mod.rs | 2 +- node/src/blockchain/errors/rpc_errors.rs | 6 +- 10 files changed, 131 insertions(+), 289 deletions(-) diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index 832140cd3..356be1912 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -76,7 +76,7 @@ use masq_lib::ui_gateway::{MessageBody, MessagePath, MessageTarget}; use masq_lib::ui_gateway::{NodeFromUiMessage, NodeToUiMessage}; use masq_lib::utils::ExpectValue; use std::any::type_name; -use std::collections::{HashMap}; +use std::collections::HashMap; #[cfg(test)] use std::default::Default; use std::fmt::Display; diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index 377961805..c564af1f5 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -535,7 +535,6 @@ impl Scanner for PayableScanner { debugging_summary_after_error_separation(&sent_payables, &err_opt) ); - // TODO so, is this still properly covered with tests? if !sent_payables.is_empty() { self.check_on_missing_sent_tx_records(&sent_payables); } @@ -682,31 +681,30 @@ impl PayableScanner { just_baked_sent_payables: &[&PendingPayable], ) -> Vec { let actual_sent_payables_len = just_baked_sent_payables.len(); - let hashset_with_hashes_to_eliminate_duplicities = just_baked_sent_payables + let hashset_with_hashes_to_eliminate_duplicates = just_baked_sent_payables .iter() .map(|pending_payable| pending_payable.hash) .collect::>(); - if hashset_with_hashes_to_eliminate_duplicities.len() != actual_sent_payables_len { + if hashset_with_hashes_to_eliminate_duplicates.len() != actual_sent_payables_len { panic!( - "Found duplicities in the recent sent txs: {:?}", + "Found duplicates in the recent sent txs: {:?}", just_baked_sent_payables ); } let transaction_hashes_and_rowids_from_db = self .sent_payable_dao - .get_tx_identifiers(&hashset_with_hashes_to_eliminate_duplicities); + .get_tx_identifiers(&hashset_with_hashes_to_eliminate_duplicates); let hashes_from_db = transaction_hashes_and_rowids_from_db .keys() .copied() .collect::>(); - let missing_sent_payables_hashes: Vec = - hashset_with_hashes_to_eliminate_duplicities - .difference(&hashes_from_db) - .copied() - .collect(); + let missing_sent_payables_hashes: Vec = hashset_with_hashes_to_eliminate_duplicates + .difference(&hashes_from_db) + .copied() + .collect(); let mut sent_payables_hashmap = just_baked_sent_payables .iter() @@ -1685,7 +1683,7 @@ mod tests { #[test] #[should_panic( - expected = "Found duplicities in the recent sent txs: [PendingPayable { recipient_wallet: \ + expected = "Found duplicates in the recent sent txs: [PendingPayable { recipient_wallet: \ Wallet { kind: Address(0x0000000000000000000000000000000000616263) }, hash: \ 0x000000000000000000000000000000000000000000000000000000000000007b }, PendingPayable { \ recipient_wallet: Wallet { kind: Address(0x0000000000000000000000000000000000646566) }, \ @@ -1696,7 +1694,7 @@ mod tests { recipient_wallet: Wallet { kind: Address(0x00000000000000000000000000000000006a6b6c) }, \ hash: 0x0000000000000000000000000000000000000000000000000000000000000315 }]" )] - fn just_baked_pending_payables_contain_duplicities() { + fn just_baked_pending_payables_contain_duplicates() { let hash_1 = make_tx_hash(123); let hash_2 = make_tx_hash(456); let hash_3 = make_tx_hash(789); @@ -2232,140 +2230,6 @@ mod tests { .exists_no_log_containing(&format!("DEBUG: {test_name}: Paying qualified debts")); } - // //TODO inspire yourself to write the right tests for the pending payable scanner when it starts - // // #[test] - // fn scan_for_pending_payables_finds_new_pending_payables() { - // init_test_logging(); - // let now = SystemTime::now(); - // let retrieve_pending_txs_params_arc = Arc::new(Mutex::new(vec![])); - // let retrieve_failed_txs_params_arc = Arc::new(Mutex::new(vec![])); - // let (blockchain_bridge, _, blockchain_bridge_recording_arc) = make_recorder(); - // let blockchain_bridge_addr = blockchain_bridge - // .system_stop_conditions(match_lazily_every_type_id!(RequestTransactionReceipts)) - // .start(); - // let sent_tx_1 = make_sent_tx(456); - // let tx_hash_1 = sent_tx_1.hash; - // let sent_tx_2 = make_sent_tx(789); - // let tx_hash_2 = sent_tx_2.hash; - // let sent_payable_dao = - // SentPayableDaoMock::default().retrieve_txs_params(&retrieve_pending_txs_params_arc).retrieve_txs_result(vec![sent_tx_1, sent_tx_2]); - // let failed_payable_dao = FailedPayableDaoMock::default().retrieve_txs_params(&retrieve_failed_txs_params_arc).retrieve_txs_result(vec![]); - // let config = bc_from_earning_wallet(make_wallet("mine")); - // let system = System::new("pending payable scan"); - // let mut subject = AccountantBuilder::default() - // .consuming_wallet(make_paying_wallet(b"consuming")) - // .bootstrapper_config(config) - // .build(); - // let pending_payable_scanner_real = PendingPayableScannerBuilder::new() - // .sent_payable_dao(sent_payable_dao) - // .build(); - // subject - // .scanners - // .replace_scanner(ScannerReplacement::PendingPayable(ReplacementType::Real( - // pending_payable_scanner_real, - // ))); - // subject.request_transaction_receipts_sub_opt = Some(blockchain_bridge_addr.recipient()); - // let account_addr = subject.start(); - // - // let _ = account_addr - // .try_send(ScanForPendingPayables { - // response_skeleton_opt: None, - // }) - // .unwrap(); - // - // system.run(); - // let retrieve_pending_txs_params = retrieve_pending_txs_params_arc.lock().unwrap(); - // assert_eq!( - // *retrieve_pending_txs_params, - // vec![Some(RetrieveCondition::IsPending)] - // ); - // let retrieve_failed_txs_params = retrieve_failed_txs_params_arc.lock().unwrap(); - // assert_eq!( - // *retrieve_failed_txs_params, - // vec![Some(FailureRetrieveCondition::EveryRecheckRequiredRecord)] - // ); - // let blockchain_bridge_recording = blockchain_bridge_recording_arc.lock().unwrap(); - // let received_msg = blockchain_bridge_recording.get_record::(0); - // assert_eq!( - // received_msg, - // &RequestTransactionReceipts { - // tx_hashes: vec![TxHashByTable::SentPayable(tx_hash_1), TxHashByTable::SentPayable(tx_hash_2)], - // response_skeleton_opt: None, - // } - // ); - // assert_eq!(blockchain_bridge_recording.len(), 1); - // let log_handler = TestLogHandler::new(); - // log_handler.exists_log_containing("DEBUG: Accountant: Found 2 pending payables to process"); - // } - // - // #[test] - // fn scan_for_pending_payables_finds_new_pending_payable_and_unproven_failed_payable() { - // init_test_logging(); - // let now = SystemTime::now(); - // let retrieve_pending_txs_params_arc = Arc::new(Mutex::new(vec![])); - // let retrieve_failed_txs_params_arc = Arc::new(Mutex::new(vec![])); - // let (blockchain_bridge, _, blockchain_bridge_recording_arc) = make_recorder(); - // let blockchain_bridge_addr = blockchain_bridge - // .system_stop_conditions(match_lazily_every_type_id!(RequestTransactionReceipts)) - // .start(); - // let sent_tx = make_sent_tx(456); - // let tx_hash_1 = sent_tx.hash; - // let failed_tx_1 = make_failed_tx(789); - // let tx_hash_2 = failed_tx_1.hash; - // let failed_tx_2 = make_failed_tx(123); - // let tx_hash_3 = failed_tx_2.hash; - // let sent_payable_dao = - // SentPayableDaoMock::default().retrieve_txs_result(vec![sent_tx.clone()]); - // let failed_payable_dao = FailedPayableDaoMock::default().retrieve_txs_result(vec![failed_tx_1, failed_tx_2]); - // let config = bc_from_earning_wallet(make_wallet("mine")); - // let system = System::new("pending payable scan"); - // let mut subject = AccountantBuilder::default() - // .consuming_wallet(make_paying_wallet(b"consuming")) - // .bootstrapper_config(config) - // .build(); - // let pending_payable_scanner_real = PendingPayableScannerBuilder::new() - // .sent_payable_dao(sent_payable_dao) - // .build(); - // subject - // .scanners - // .replace_scanner(ScannerReplacement::PendingPayable(ReplacementType::Real( - // pending_payable_scanner_real, - // ))); - // subject.request_transaction_receipts_sub_opt = Some(blockchain_bridge_addr.recipient()); - // let account_addr = subject.start(); - // - // let _ = account_addr - // .try_send(ScanForPendingPayables { - // response_skeleton_opt: None, - // }) - // .unwrap(); - // - // system.run(); - // let retrieve_pending_txs_params = retrieve_pending_txs_params_arc.lock().unwrap(); - // assert_eq!( - // *retrieve_pending_txs_params, - // vec![Some(RetrieveCondition::IsPending)] - // ); - // let retrieve_failed_txs_params = retrieve_failed_txs_params_arc.lock().unwrap(); - // assert_eq!( - // *retrieve_failed_txs_params, - // vec![Some(FailureRetrieveCondition::EveryRecheckRequiredRecord)] - // ); - // let blockchain_bridge_recording = blockchain_bridge_recording_arc.lock().unwrap(); - // let received_msg = blockchain_bridge_recording.get_record::(0); - // assert_eq!( - // received_msg, - // &RequestTransactionReceipts { - // tx_hashes: vec![TxHashByTable::SentPayable(tx_hash_1),TxHashByTable::FailedPayable(tx_hash_2), TxHashByTable::FailedPayable(tx_hash_3)], - // response_skeleton_opt: None, - // } - // ); - // assert_eq!(blockchain_bridge_recording.len(), 1); - // let log_handler = TestLogHandler::new(); - // log_handler.exists_log_containing("DEBUG: Accountant: Found 3 payables to query \ - // receipts for: 1 pending and 2 failed that require recheck"); - // } - #[test] fn pending_payable_scanner_can_initiate_a_scan() { init_test_logging(); @@ -2753,8 +2617,8 @@ mod tests { #[test] #[should_panic( - expected = "We should never receive an empty list of results. Even missing receipts can \ - be interpreted" + expected = "We should never receive an empty list of results. Even receipts that could not \ + be retrieved can be interpreted" )] fn pending_payable_scanner_handles_empty_report_transaction_receipts_message() { let mut pending_payable_scanner = PendingPayableScannerBuilder::new().build(); diff --git a/node/src/accountant/scanners/pending_payable_scanner/mod.rs b/node/src/accountant/scanners/pending_payable_scanner/mod.rs index f73d9c19f..92c551609 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/mod.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/mod.rs @@ -54,7 +54,7 @@ pub struct PendingPayableScanner { pub financial_statistics: Rc>, pub current_sent_payables: Box>, pub yet_unproven_failed_payables: Box>, - pub validation_failure_clock: Box, + pub clock: Box, } impl @@ -149,7 +149,7 @@ impl PendingPayableScanner { financial_statistics, current_sent_payables: Box::new(CurrentPendingPayables::default()), yet_unproven_failed_payables: Box::new(RecheckRequiringFailures::default()), - validation_failure_clock: Box::new(ValidationFailureClockReal::default()), + clock: Box::new(ValidationFailureClockReal::default()), } } @@ -158,18 +158,13 @@ impl PendingPayableScanner { .sent_payable_dao .retrieve_txs(Some(RetrieveCondition::IsPending)); - if !pending_txs.is_empty() { - let pending_tx_hashes = - Self::get_wrapped_hashes(&pending_txs, TxHashByTable::SentPayable); - - if !pending_txs.is_empty() { - self.current_sent_payables.load_cache(pending_txs) - } - - Some(pending_tx_hashes) - } else { - None + if pending_txs.is_empty() { + return None; } + + let pending_tx_hashes = Self::get_wrapped_hashes(&pending_txs, TxHashByTable::SentPayable); + self.current_sent_payables.load_cache(pending_txs); + Some(pending_tx_hashes) } fn handle_unproven_failures(&mut self) -> Option> { @@ -177,17 +172,13 @@ impl PendingPayableScanner { .failed_payable_dao .retrieve_txs(Some(FailureRetrieveCondition::EveryRecheckRequiredRecord)); - if !failures.is_empty() { - let failure_hashes = Self::get_wrapped_hashes(&failures, TxHashByTable::FailedPayable); - - if !failures.is_empty() { - self.yet_unproven_failed_payables.load_cache(failures) - } - - Some(failure_hashes) - } else { - None + if failures.is_empty() { + return None; } + + let failure_hashes = Self::get_wrapped_hashes(&failures, TxHashByTable::FailedPayable); + self.yet_unproven_failed_payables.load_cache(failures); + Some(failure_hashes) } fn get_wrapped_hashes( @@ -205,9 +196,9 @@ impl PendingPayableScanner { fn emptiness_check(&self, msg: &TxReceiptsMessage) { if msg.results.is_empty() { - unreachable!( + panic!( "We should never receive an empty list of results. \ - Even missing receipts can be interpreted" + Even receipts that could not be retrieved can be interpreted" ) } } @@ -250,23 +241,23 @@ impl PendingPayableScanner { logger: &Logger, ) -> Vec { let init: Either, MismatchReport> = Either::Left(vec![]); - let either = - msg.results - .into_iter() - // This must be in for predictability in tests - .sorted_by_key(|(hash_by_table,_)|{ hash_by_table.hash() }) - .fold( - init, - |acc, (tx_hash_by_table, tx_receipt_result)| match acc { - Either::Left(cases) => { - self.resolve_real_query(cases, tx_receipt_result, tx_hash_by_table) - } - Either::Right(mut mismatch_report) => { - mismatch_report.remaining_hashes.push(tx_hash_by_table); - Either::Right(mismatch_report) - } - }, - ); + let either = msg + .results + .into_iter() + // This must be in for predictability in tests + .sorted_by_key(|(hash_by_table, _)| hash_by_table.hash()) + .fold( + init, + |acc, (tx_hash_by_table, tx_receipt_result)| match acc { + Either::Left(cases) => { + self.resolve_real_query(cases, tx_receipt_result, tx_hash_by_table) + } + Either::Right(mut mismatch_report) => { + mismatch_report.remaining_hashes.push(tx_hash_by_table); + Either::Right(mismatch_report) + } + }, + ); let cases = match either { Either::Left(cases) => cases, @@ -661,11 +652,8 @@ impl PendingPayableScanner { logger: &Logger, ) { if !sent_payable_failures.is_empty() { - let updatable = Self::prepare_statuses_for_update( - &sent_payable_failures, - &*self.validation_failure_clock, - logger, - ); + let updatable = + Self::prepare_statuses_for_update(&sent_payable_failures, &*self.clock, logger); if !updatable.is_empty() { match self.sent_payable_dao.update_statuses(&updatable) { Ok(_) => { @@ -698,7 +686,7 @@ impl PendingPayableScanner { if !failed_txs_validation_failures.is_empty() { let updatable = Self::prepare_statuses_for_update( &failed_txs_validation_failures, - &*self.validation_failure_clock, + &*self.clock, logger, ); if !updatable.is_empty() { diff --git a/node/src/accountant/scanners/pending_payable_scanner/utils.rs b/node/src/accountant/scanners/pending_payable_scanner/utils.rs index dc53d07ad..72c16056a 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/utils.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/utils.rs @@ -26,7 +26,7 @@ impl ReceiptScanReport { self.failures.requires_retry(), self.confirmations.is_empty(), ) { - (None, true) => unreachable!("reading tx receipts gave no results"), + (None, true) => unreachable!("reading tx receipts gave no results, but always should"), (None, _) => None, (Some(retry), _) => Some(retry), } @@ -154,7 +154,7 @@ where pub fn new_status(&self, clock: &dyn ValidationFailureClock) -> Option { self.current_status - .update_after_failure(self.validation_failure.clone(), clock) + .update_after_failure(self.validation_failure, clock) } } @@ -219,7 +219,7 @@ pub struct MismatchReport { pub trait PendingPayableCache { fn load_cache(&mut self, records: Vec); - fn get_record_by_hash(&mut self, hashes: TxHash) -> Option; + fn get_record_by_hash(&mut self, hash: TxHash) -> Option; fn ensure_empty_cache(&mut self, logger: &Logger); fn dump_cache(&mut self) -> HashMap; } @@ -235,8 +235,8 @@ impl PendingPayableCache for CurrentPendingPayables { .extend(records.into_iter().map(|tx| (tx.hash, tx))); } - fn get_record_by_hash(&mut self, hashes: TxHash) -> Option { - self.sent_payables.remove(&hashes) + fn get_record_by_hash(&mut self, hash: TxHash) -> Option { + self.sent_payables.remove(&hash) } fn ensure_empty_cache(&mut self, logger: &Logger) { @@ -272,8 +272,8 @@ impl PendingPayableCache for RecheckRequiringFailures { .extend(records.into_iter().map(|tx| (tx.hash, tx))); } - fn get_record_by_hash(&mut self, hashes: TxHash) -> Option { - self.failures.remove(&hashes) + fn get_record_by_hash(&mut self, hash: TxHash) -> Option { + self.failures.remove(&hash) } fn ensure_empty_cache(&mut self, logger: &Logger) { @@ -457,33 +457,29 @@ mod tests { }, ]; - tx_failures_feedings.iter().for_each(|tx_failures| { - tx_receipt_rpc_failures_feeding - .iter() - .for_each(|rpc_failures| { - detected_confirmations_feeding - .iter() - .for_each(|detected_confirmations| { - let case = ReceiptScanReport { - failures: DetectedFailures { - tx_failures: tx_failures.clone(), - tx_receipt_rpc_failures: rpc_failures.clone(), - }, - confirmations: detected_confirmations.clone(), - }; - - let result = case.requires_payments_retry(); - - assert_eq!( - result, - Some(Retry::RetryPayments), - "We expected Some(Retry::RetryPayments) but got {:?} for case {:?}", - result, - case - ); - }) - }) - }); + for tx_failures in &tx_failures_feedings { + for rpc_failures in &tx_receipt_rpc_failures_feeding { + for detected_confirmations in &detected_confirmations_feeding { + let case = ReceiptScanReport { + failures: DetectedFailures { + tx_failures: tx_failures.clone(), + tx_receipt_rpc_failures: rpc_failures.clone(), + }, + confirmations: detected_confirmations.clone(), + }; + + let result = case.requires_payments_retry(); + + assert_eq!( + result, + Some(Retry::RetryPayments), + "Expected Some(Retry::RetryPayments) but got {:?} for case {:?}", + result, + case + ); + } + } + } } #[test] @@ -549,8 +545,8 @@ mod tests { }, ]; - rpc_failure_feedings.into_iter().for_each(|rpc_failures|{ - detected_confirmations_feeding.iter().for_each(|detected_confirmations|{ + for rpc_failures in &rpc_failure_feedings { + for detected_confirmations in &detected_confirmations_feeding { let case = ReceiptScanReport { failures: DetectedFailures { tx_failures: vec![], // This is the determinant @@ -561,9 +557,15 @@ mod tests { let result = case.requires_payments_retry(); - assert_eq!(result, Some(Retry::RetryTxStatusCheckOnly), "We expected Some(Retry::RetryTxStatusCheckOnly) but got {:?} for case {:?}", result, case); - }) - }); + assert_eq!( + result, + Some(Retry::RetryTxStatusCheckOnly), + "Expected Some(Retry::RetryTxStatusCheckOnly) but got {:?} for case {:?}", + result, + case + ); + } + } } #[test] @@ -583,31 +585,29 @@ mod tests { }, ]; - detected_confirmations_feeding - .into_iter() - .for_each(|detected_confirmations| { - let case = ReceiptScanReport { - failures: DetectedFailures { - tx_failures: vec![], - tx_receipt_rpc_failures: vec![], - }, - confirmations: detected_confirmations.clone(), - }; - - let result = case.requires_payments_retry(); - - assert_eq!( - result, None, - "We expected None but got {:?} for case {:?}", - result, case - ); - }); + for detected_confirmations in detected_confirmations_feeding { + let case = ReceiptScanReport { + failures: DetectedFailures { + tx_failures: vec![], + tx_receipt_rpc_failures: vec![], + }, + confirmations: detected_confirmations.clone(), + }; + + let result = case.requires_payments_retry(); + + assert_eq!( + result, None, + "We expected None but got {:?} for case {:?}", + result, case + ); + } } #[test] #[should_panic( - expected = "internal error: entered unreachable code: reading tx receipts gave \ - no results" + expected = "internal error: entered unreachable code: reading tx receipts gave no results, \ + but always should" )] fn requires_payments_retry_with_no_results_in_whole_summary() { let report = ReceiptScanReport { @@ -921,10 +921,10 @@ 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 validation_failure_clock = ValidationFailureClockMock::default() + let clock = ValidationFailureClockMock::default() .now_result(timestamp_a) .now_result(timestamp_c); - let mal_validated_tx_statuses = vec![ + let cases = vec![ ( FailedValidation::new( make_tx_hash(123), @@ -984,14 +984,9 @@ mod tests { ), ]; - mal_validated_tx_statuses.into_iter().for_each( - |(failed_validation, expected_tx_status)| { - assert_eq!( - failed_validation.new_status(&validation_failure_clock), - expected_tx_status - ); - }, - ); + cases.into_iter().for_each(|(input, expected)| { + assert_eq!(input.new_status(&clock), expected); + }); } #[test] @@ -999,10 +994,10 @@ 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 validation_failure_clock = ValidationFailureClockMock::default() + let clock = ValidationFailureClockMock::default() .now_result(timestamp_a) .now_result(timestamp_b); - let mal_validated_failure_statuses = vec![ + let cases = vec![ ( FailedValidation::new( make_tx_hash(456), @@ -1064,14 +1059,9 @@ mod tests { ), ]; - mal_validated_failure_statuses.into_iter().for_each( - |(failed_validation, expected_failed_tx_status)| { - assert_eq!( - failed_validation.new_status(&validation_failure_clock), - expected_failed_tx_status - ); - }, - ) + cases.into_iter().for_each(|(input, expected)| { + assert_eq!(input.new_status(&clock), expected); + }) } #[test] diff --git a/node/src/accountant/scanners/test_utils.rs b/node/src/accountant/scanners/test_utils.rs index a44a0c2ef..ecd0781fe 100644 --- a/node/src/accountant/scanners/test_utils.rs +++ b/node/src/accountant/scanners/test_utils.rs @@ -518,8 +518,8 @@ impl PendingPayableCache for PendingPayableCacheMock { self.load_cache_results.borrow_mut().remove(0); } - fn get_record_by_hash(&mut self, hashes: TxHash) -> Option { - self.get_record_by_hash_params.lock().unwrap().push(hashes); + fn get_record_by_hash(&mut self, hash: TxHash) -> Option { + self.get_record_by_hash_params.lock().unwrap().push(hash); self.get_record_by_hash_results.borrow_mut().remove(0) } diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs index 5535ca183..81b612e47 100644 --- a/node/src/accountant/test_utils.rs +++ b/node/src/accountant/test_utils.rs @@ -1379,7 +1379,7 @@ pub struct PendingPayableScannerBuilder { financial_statistics: FinancialStatistics, current_sent_payables: Box>, yet_unproven_failed_payables: Box>, - validation_failure_clock: Box, + clock: Box, } impl PendingPayableScannerBuilder { @@ -1392,7 +1392,7 @@ impl PendingPayableScannerBuilder { financial_statistics: FinancialStatistics::default(), current_sent_payables: Box::new(PendingPayableCacheMock::default()), yet_unproven_failed_payables: Box::new(PendingPayableCacheMock::default()), - validation_failure_clock: Box::new(ValidationFailureClockMock::default()), + clock: Box::new(ValidationFailureClockMock::default()), } } @@ -1425,7 +1425,7 @@ impl PendingPayableScannerBuilder { } pub fn validation_failure_clock(mut self, clock: Box) -> Self { - self.validation_failure_clock = clock; + self.clock = clock; self } @@ -1439,7 +1439,7 @@ impl PendingPayableScannerBuilder { ); scanner.current_sent_payables = self.current_sent_payables; scanner.yet_unproven_failed_payables = self.yet_unproven_failed_payables; - scanner.validation_failure_clock = self.validation_failure_clock; + scanner.clock = self.clock; scanner } } diff --git a/node/src/blockchain/blockchain_interface/mod.rs b/node/src/blockchain/blockchain_interface/mod.rs index b32ff302a..09961776e 100644 --- a/node/src/blockchain/blockchain_interface/mod.rs +++ b/node/src/blockchain/blockchain_interface/mod.rs @@ -23,7 +23,7 @@ use actix::Recipient; use futures::Future; use masq_lib::blockchains::chains::Chain; use masq_lib::logger::Logger; -use std::collections::{HashMap}; +use std::collections::HashMap; use web3::types::Address; pub trait BlockchainInterface { diff --git a/node/src/blockchain/errors/internal_errors.rs b/node/src/blockchain/errors/internal_errors.rs index fb6a4bf63..9982d0667 100644 --- a/node/src/blockchain/errors/internal_errors.rs +++ b/node/src/blockchain/errors/internal_errors.rs @@ -7,7 +7,7 @@ pub enum InternalError { PendingTooLongNotReplaced, } -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum InternalErrorKind { PendingTooLongNotReplaced, } diff --git a/node/src/blockchain/errors/mod.rs b/node/src/blockchain/errors/mod.rs index 5cd1a6f3c..e406a96b1 100644 --- a/node/src/blockchain/errors/mod.rs +++ b/node/src/blockchain/errors/mod.rs @@ -14,7 +14,7 @@ pub enum BlockchainError { Internal(InternalError), } -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum BlockchainErrorKind { AppRpc(AppRpcErrorKind), Internal(InternalErrorKind), diff --git a/node/src/blockchain/errors/rpc_errors.rs b/node/src/blockchain/errors/rpc_errors.rs index e717fbf25..bf78fa53b 100644 --- a/node/src/blockchain/errors/rpc_errors.rs +++ b/node/src/blockchain/errors/rpc_errors.rs @@ -53,13 +53,13 @@ impl From for AppRpcError { } } -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum AppRpcErrorKind { Local(LocalErrorKind), Remote(RemoteErrorKind), } -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum LocalErrorKind { Decoder, Internal, @@ -68,7 +68,7 @@ pub enum LocalErrorKind { Transport, } -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum RemoteErrorKind { InvalidResponse, Unreachable, From 5548e532c4f4e5a99f66cb8b1681354d52e6fc85 Mon Sep 17 00:00:00 2001 From: Bert Date: Wed, 10 Sep 2025 19:07:56 +0200 Subject: [PATCH 56/61] GH-642: before fixing the todo!() left over --- node/src/accountant/scanners/mod.rs | 4 +- .../scanners/pending_payable_scanner/mod.rs | 191 ++++++++++-------- 2 files changed, 110 insertions(+), 85 deletions(-) diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index c564af1f5..14630be55 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -2526,7 +2526,7 @@ mod tests { let mut sent_tx_6 = make_sent_tx(789); sent_tx_6.hash = tx_hash_6; let tx_status_6 = StatusReadFromReceiptCheck::Reverted; - let pending_payable_cache = PendingPayableCacheMock::default() + let sent_payable_cache = PendingPayableCacheMock::default() .get_record_by_hash_result(Some(sent_tx_1.clone())) .get_record_by_hash_result(Some(sent_tx_3.clone())) .get_record_by_hash_result(Some(sent_tx_4)) @@ -2541,7 +2541,7 @@ mod tests { .payable_dao(payable_dao) .sent_payable_dao(sent_payable_dao) .failed_payable_dao(failed_payable_dao) - .sent_payable_cache(Box::new(pending_payable_cache)) + .sent_payable_cache(Box::new(sent_payable_cache)) .failed_payable_cache(Box::new(failed_payable_cache)) .validation_failure_clock(Box::new(validation_failure_clock)) .build(); diff --git a/node/src/accountant/scanners/pending_payable_scanner/mod.rs b/node/src/accountant/scanners/pending_payable_scanner/mod.rs index 92c551609..52e73008a 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/mod.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/mod.rs @@ -348,19 +348,59 @@ impl PendingPayableScanner { } fn handle_tx_failure_reclaims(&mut self, reclaimed: Vec, logger: &Logger) { - fn isolate_hashes(reclaimed: &[(TxHash, TxBlock)]) -> HashSet { - reclaimed.iter().map(|(tx_hash, _)| *tx_hash).collect() - } - if reclaimed.is_empty() { return; } - let hashes_and_blocks = Self::collect_hashes_and_blocks(&reclaimed) + let hashes_and_blocks = Self::collect_and_sort_hashes_and_blocks(&reclaimed); + + self.replace_sent_tx_records(&reclaimed, &hashes_and_blocks, logger); + + self.delete_failed_tx_records(&hashes_and_blocks, logger); + + self.add_to_the_total_of_paid_payable(&reclaimed, logger) + } + + fn isolate_hashes(reclaimed: &[(TxHash, TxBlock)]) -> HashSet { + reclaimed.iter().map(|(tx_hash, _)| *tx_hash).collect() + } + + fn collect_and_sort_hashes_and_blocks(sent_txs: &[SentTx]) -> Vec<(TxHash, TxBlock)> { + Self::collect_hashes_and_blocks(sent_txs) .into_iter() .sorted() - .collect_vec(); - match self.sent_payable_dao.replace_records(&reclaimed) { + .collect_vec() + } + + fn collect_hashes_and_blocks(reclaimed: &[SentTx]) -> HashMap { + reclaimed + .iter() + .map(|reclaim| { + let tx_block = if let TxStatus::Confirmed { block_hash, block_number, .. } = + &reclaim.status + { + TxBlock{ + block_hash: H256::from_str(&block_hash[2..]).expect("Failed to construct hash from str"), + block_number: (*block_number).into() + } + } else { + panic!( + "Processing a reclaim for tx {:?} which isn't filled with the confirmation details", + reclaim.hash + ) + }; + (reclaim.hash, tx_block) + }) + .collect() + } + + fn replace_sent_tx_records( + &self, + sent_txs_to_reclaim: &[SentTx], + hashes_and_blocks: &[(TxHash, TxBlock)], + logger: &Logger, + ) { + match self.sent_payable_dao.replace_records(sent_txs_to_reclaim) { Ok(_) => { debug!(logger, "Replaced records for txs being reclaimed") } @@ -368,23 +408,23 @@ impl PendingPayableScanner { panic!( "Unable to proceed in a reclaim as the replacement of sent tx records \ {} failed due to: {:?}", - comma_joined_stringifiable(&hashes_and_blocks, |(tx_hash, _)| { + comma_joined_stringifiable(hashes_and_blocks, |(tx_hash, _)| { format!("{:?}", tx_hash) }), e ) } } + } - match self - .failed_payable_dao - .delete_records(&isolate_hashes(&hashes_and_blocks)) - { + fn delete_failed_tx_records(&self, hashes_and_blocks: &[(TxHash, TxBlock)], logger: &Logger) { + let hashes = Self::isolate_hashes(hashes_and_blocks); + match self.failed_payable_dao.delete_records(&hashes) { Ok(_) => { info!( logger, "Reclaimed txs {} as confirmed on-chain", - comma_joined_stringifiable(&hashes_and_blocks, |(tx_hash, tx_block)| { + comma_joined_stringifiable(hashes_and_blocks, |(tx_hash, tx_block)| { format!("{:?} (block {})", tx_hash, tx_block.block_number) }) ) @@ -392,84 +432,40 @@ impl PendingPayableScanner { Err(e) => { panic!( "Unable to delete failed tx records {} to finish the reclaims due to: {:?}", - comma_joined_stringifiable(&hashes_and_blocks, |(tx_hash, _)| { + comma_joined_stringifiable(hashes_and_blocks, |(tx_hash, _)| { format!("{:?}", tx_hash) }), e ) } } - - self.add_to_the_total_of_paid_payable(&reclaimed, logger) - } - - fn collect_hashes_and_blocks(reclaimed: &[SentTx]) -> HashMap { - reclaimed - .iter() - .map(|reclaim| { - let tx_block = if let TxStatus::Confirmed { block_hash, block_number, .. } = - &reclaim.status - { - TxBlock{ - block_hash: H256::from_str(&block_hash[2..]).expect("Failed to construct hash from str"), - block_number: (*block_number).into() - } - } else { - unreachable!( - "Processing a reclaim for tx {:?} which isn't filled with the confirmation details", - reclaim.hash - ) - }; - (reclaim.hash, tx_block) - }) - .collect() } fn handle_normal_confirmations(&mut self, confirmed_txs: Vec, logger: &Logger) { - fn transaction_confirmed_panic(confirmed_txs: &[SentTx], e: PayableDaoError) -> ! { - panic!( - "Unable to complete the tx confirmation by the adjustment of the payable \ - accounts {} due to: {:?}", - comma_joined_stringifiable( - &confirmed_txs - .iter() - .map(|tx| tx.receiver_address) - .collect_vec(), - |wallet| format!("{:?}", wallet) - ), - e - ) - } - fn update_tx_blocks_panic( - tx_hashes_and_tx_blocks: &HashMap, - e: SentPayableDaoError, - ) -> ! { - panic!( - "Unable to update sent payable records {} by their tx blocks due to: {:?}", - comma_joined_stringifiable( - &tx_hashes_and_tx_blocks.keys().sorted().collect_vec(), - |tx_hash| format!("{:?}", tx_hash) - ), - e - ) - } - if confirmed_txs.is_empty() { return; } - if let Err(e) = self.payable_dao.transactions_confirmed(&confirmed_txs) { - transaction_confirmed_panic(&confirmed_txs, e) - } else { - let tx_confirmations = Self::collect_hashes_and_blocks(&confirmed_txs); + self.confirm_transactions(&confirmed_txs); - if let Err(e) = self.sent_payable_dao.confirm_txs(&tx_confirmations) { - update_tx_blocks_panic(&tx_confirmations, e) - } else { - Self::log_tx_success(logger, &tx_confirmations); - } + self.update_tx_blocks(&confirmed_txs, logger); + + self.add_to_the_total_of_paid_payable(&confirmed_txs, logger); + } - self.add_to_the_total_of_paid_payable(&confirmed_txs, logger); + fn confirm_transactions(&self, confirmed_sent_txs: &[SentTx]) { + if let Err(e) = self.payable_dao.transactions_confirmed(confirmed_sent_txs) { + Self::transaction_confirmed_panic(confirmed_sent_txs, e); + } + } + + fn update_tx_blocks(&self, confirmed_sent_txs: &[SentTx], logger: &Logger) { + let tx_confirmations = Self::collect_hashes_and_blocks(confirmed_sent_txs); + + if let Err(e) = self.sent_payable_dao.confirm_txs(&tx_confirmations) { + Self::update_tx_blocks_panic(&tx_confirmations, e); + } else { + Self::log_tx_success(logger, &tx_confirmations); } } @@ -489,6 +485,34 @@ impl PendingPayableScanner { }); } + fn transaction_confirmed_panic(confirmed_txs: &[SentTx], e: PayableDaoError) -> ! { + panic!( + "Unable to complete the tx confirmation by the adjustment of the payable accounts \ + {} due to: {:?}", + comma_joined_stringifiable( + &confirmed_txs + .iter() + .map(|tx| tx.receiver_address) + .collect_vec(), + |wallet| format!("{:?}", wallet) + ), + e + ) + } + fn update_tx_blocks_panic( + tx_hashes_and_tx_blocks: &HashMap, + e: SentPayableDaoError, + ) -> ! { + panic!( + "Unable to update sent payable records {} by their tx blocks due to: {:?}", + comma_joined_stringifiable( + &tx_hashes_and_tx_blocks.keys().sorted().collect_vec(), + |tx_hash| format!("{:?}", tx_hash) + ), + e + ) + } + fn add_to_the_total_of_paid_payable(&mut self, confirmed_payments: &[SentTx], logger: &Logger) { let to_be_added: u128 = confirmed_payments .iter() @@ -891,7 +915,7 @@ mod tests { let failed_payable_dao = FailedPayableDaoMock::new() .insert_new_records_result(Ok(())) .delete_records_result(Ok(())); - let pending_payable_cache = PendingPayableCacheMock::default() + let sent_payable_cache = PendingPayableCacheMock::default() .get_record_by_hash_params(&get_record_by_hash_sent_payable_cache_params_arc) .get_record_by_hash_result(Some(sent_tx_1.clone())) .get_record_by_hash_result(Some(sent_tx_2)) @@ -905,7 +929,7 @@ mod tests { .payable_dao(payable_dao) .sent_payable_dao(sent_payable_dao) .failed_payable_dao(failed_payable_dao) - .sent_payable_cache(Box::new(pending_payable_cache)) + .sent_payable_cache(Box::new(sent_payable_cache)) .failed_payable_cache(Box::new(failed_payable_cache)) .build(); let logger = Logger::new("test"); @@ -1079,7 +1103,8 @@ mod tests { subject.handle_failed_transactions(detected_failures, &Logger::new("test")) - //mocked pending payable DAO didn't panic which means we skipped the actual process + // Mocked pending payable DAO without prepared results didn't panic which means none of its + // methods was used in this test } #[test] @@ -1529,7 +1554,8 @@ mod tests { subject .handle_confirmed_transactions(DetectedConfirmations::default(), &Logger::new("test")) - // Mocked payable DAO didn't panic, which means we skipped the actual process + // Mocked payable DAO without prepared results didn't panic, which none of its methods was + // used in this test } #[test] @@ -1690,9 +1716,8 @@ mod tests { #[test] #[should_panic( - expected = "internal error: entered unreachable code: Processing a reclaim for \ - tx 0x0000000000000000000000000000000000000000000000000000000000000123 which isn't filled with \ - the confirmation details" + expected = "Processing a reclaim for tx 0x0000000000000000000000000000000000000000000000000\ + 000000000000123 which isn't filled with the confirmation details" )] fn handle_failure_reclaim_meets_a_record_without_confirmation_details() { let mut subject = PendingPayableScannerBuilder::new().build(); From 40351e92b6f3c001ee991f9ac0846045bb0ad356 Mon Sep 17 00:00:00 2001 From: Bert Date: Thu, 11 Sep 2025 13:32:55 +0200 Subject: [PATCH 57/61] GH-642: finished --- node/src/accountant/mod.rs | 96 +++++++++++++++++-- .../errors/blockchain_db_error/mod.rs | 0 2 files changed, 88 insertions(+), 8 deletions(-) delete mode 100644 node/src/blockchain/errors/blockchain_db_error/mod.rs diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index 356be1912..d1ffb30f3 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -328,8 +328,8 @@ impl Handler for Accountant { .expect("UIGateway is not bound") .try_send(node_to_ui_msg) .expect("UIGateway is dead"); - // Non-automatic pending-payable scan is not permitted to spark a payable scan - // bringing over new payables with fresh nonces. The job's done here. + // Non-automatic scan for pending payables is not permitted to spark a payable + // scan bringing over new payables with fresh nonces. The job's done here. } else { self.scan_schedulers .payable @@ -342,10 +342,10 @@ impl Handler for Accountant { response_skeleton_opt, &self.logger, ), - Retry::RetryTxStatusCheckOnly => todo!(), - // .scan_schedulers - // .pending_payable - // .schedule(ctx, &self.logger), + Retry::RetryTxStatusCheckOnly => self + .scan_schedulers + .pending_payable + .schedule(ctx, &self.logger), }, }; } @@ -4993,6 +4993,12 @@ mod tests { .replace_scanner(ScannerReplacement::PendingPayable(ReplacementType::Mock( pending_payable_scanner, ))); + subject.scan_schedulers.payable.new_payable_notify = + Box::new(NotifyHandleMock::default().panic_on_schedule_attempt()); + subject.scan_schedulers.payable.new_payable_notify_later = + Box::new(NotifyLaterHandleMock::default().panic_on_schedule_attempt()); + subject.scan_schedulers.pending_payable.handle = + Box::new(NotifyLaterHandleMock::default().panic_on_schedule_attempt()); subject.scan_schedulers.payable.retry_payable_notify = Box::new(NotifyHandleMock::default().notify_params(&retry_payable_notify_params_arc)); let system = System::new(test_name); @@ -5031,10 +5037,84 @@ mod tests { } #[test] - fn accountant_confirms_pending_txs_and_schedules_the_new_payable_scanner_timely() { + fn accountant_reschedules_pending_payable_scanner_as_receipt_check_efforts_alone_failed() { + init_test_logging(); + let test_name = + "accountant_reschedules_pending_payable_scanner_as_receipt_check_efforts_alone_failed"; + let finish_scan_params_arc = Arc::new(Mutex::new(vec![])); + let pending_payable_notify_later_params_arc = Arc::new(Mutex::new(vec![])); + let mut subject = AccountantBuilder::default() + .logger(Logger::new(test_name)) + .build(); + let pending_payable_scanner = ScannerMock::new() + .finish_scan_params(&finish_scan_params_arc) + .finish_scan_result(PendingPayableScanResult::PaymentRetryRequired( + Retry::RetryTxStatusCheckOnly, + )); + subject + .scanners + .replace_scanner(ScannerReplacement::PendingPayable(ReplacementType::Mock( + pending_payable_scanner, + ))); + subject.scan_schedulers.payable.retry_payable_notify = + Box::new(NotifyHandleMock::default().panic_on_schedule_attempt()); + subject.scan_schedulers.payable.new_payable_notify = + Box::new(NotifyHandleMock::default().panic_on_schedule_attempt()); + subject.scan_schedulers.payable.new_payable_notify_later = + Box::new(NotifyLaterHandleMock::default().panic_on_schedule_attempt()); + let interval = Duration::from_secs(20); + subject.scan_schedulers.pending_payable.interval = interval; + subject.scan_schedulers.pending_payable.handle = Box::new( + NotifyLaterHandleMock::default() + .notify_later_params(&pending_payable_notify_later_params_arc), + ); + let system = System::new(test_name); + let (mut msg, _) = make_tx_receipts_msg(vec![ + SeedsToMakeUpPayableWithStatus { + tx_hash: TxHashByTable::SentPayable(make_tx_hash(123)), + status: StatusReadFromReceiptCheck::Pending, + }, + SeedsToMakeUpPayableWithStatus { + tx_hash: TxHashByTable::FailedPayable(make_tx_hash(456)), + status: StatusReadFromReceiptCheck::Reverted, + }, + ]); + // Although it all begins with an external trigger, the response skeleton will not be + // propagated eventually to the further stages (see the NotifyLaterHandle assertion holding + // None for the response skeleton) + let response_skeleton_opt = Some(ResponseSkeleton { + client_id: 45, + context_id: 7, + }); + msg.response_skeleton_opt = response_skeleton_opt; + let subject_addr = subject.start(); + + subject_addr.try_send(msg.clone()).unwrap(); + + System::current().stop(); + system.run(); + let mut finish_scan_params = finish_scan_params_arc.lock().unwrap(); + let (msg_actual, logger) = finish_scan_params.remove(0); + assert_eq!(msg_actual, msg); + let pending_payable_notify_later_params = + pending_payable_notify_later_params_arc.lock().unwrap(); + assert_eq!( + *pending_payable_notify_later_params, + vec![( + ScanForPendingPayables { + response_skeleton_opt: None + }, + interval + )] + ); + assert_using_the_same_logger(&logger, test_name, None) + } + + #[test] + fn accountant_confirms_all_pending_txs_and_schedules_the_new_payable_scanner_timely() { init_test_logging(); let test_name = - "accountant_confirms_pending_txs_and_schedules_the_new_payable_scanner_timely"; + "accountant_confirms_all_pending_txs_and_schedules_the_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![])); diff --git a/node/src/blockchain/errors/blockchain_db_error/mod.rs b/node/src/blockchain/errors/blockchain_db_error/mod.rs deleted file mode 100644 index e69de29bb..000000000 From 41ba5ab5b933cf6c489ca48cfa9f4234e7207502 Mon Sep 17 00:00:00 2001 From: Bert Date: Thu, 11 Sep 2025 13:42:01 +0200 Subject: [PATCH 58/61] GH-642: cosmetics --- .../src/accountant/db_access_objects/failed_payable_dao.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) 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 2e67ded0c..9f4b855f8 100644 --- a/node/src/accountant/db_access_objects/failed_payable_dao.rs +++ b/node/src/accountant/db_access_objects/failed_payable_dao.rs @@ -62,12 +62,7 @@ impl Display for FailureStatus { match serde_json::to_string(self) { Ok(json) => write!(f, "{}", json), // Untestable - Err(e) => panic!( - "cat: {:?}, line: {}, column: {}", - e.classify(), - e.line(), - e.column() - ), //write!(f, ""), + Err(e) => write!(f, ""), } } } From 4ab5b6bf408708c0ec182537271e58890beb2aca Mon Sep 17 00:00:00 2001 From: Bert Date: Thu, 11 Sep 2025 13:42:56 +0200 Subject: [PATCH 59/61] GH-642: grrr - cosmetics - forgot e --- node/src/accountant/db_access_objects/failed_payable_dao.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 9f4b855f8..2d6d3b73f 100644 --- a/node/src/accountant/db_access_objects/failed_payable_dao.rs +++ b/node/src/accountant/db_access_objects/failed_payable_dao.rs @@ -62,7 +62,7 @@ impl Display for FailureStatus { match serde_json::to_string(self) { Ok(json) => write!(f, "{}", json), // Untestable - Err(e) => write!(f, ""), + Err(_) => write!(f, ""), } } } From bca75ae04744b6be9e5f95d205b8f5f518b1d44c Mon Sep 17 00:00:00 2001 From: Bert Date: Fri, 12 Sep 2025 13:32:29 +0200 Subject: [PATCH 60/61] GH-642: review 2 addressed --- .../db_access_objects/failed_payable_dao.rs | 3 +- node/src/accountant/mod.rs | 108 +++++++++++++----- node/src/accountant/scanners/mod.rs | 3 +- .../scanners/pending_payable_scanner/mod.rs | 18 ++- .../scanners/pending_payable_scanner/utils.rs | 2 +- node/src/test_utils/mod.rs | 3 +- 6 files changed, 98 insertions(+), 39 deletions(-) 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 2d6d3b73f..7a6e509bc 100644 --- a/node/src/accountant/db_access_objects/failed_payable_dao.rs +++ b/node/src/accountant/db_access_objects/failed_payable_dao.rs @@ -28,7 +28,6 @@ pub enum FailedPayableDaoError { pub enum FailureReason { Submission(AppRpcErrorKind), Reverted, - Unrecognized, PendingTooLong, } @@ -651,7 +650,7 @@ mod tests { assert_eq!( FailureReason::from_str("\"UnknownReason\"").unwrap_err(), "unknown variant `UnknownReason`, \ - expected one of `Submission`, `Reverted`, `Unrecognized`, `PendingTooLong` \ + expected one of `Submission`, `Reverted`, `PendingTooLong` \ at line 1 column 15 in '\"UnknownReason\"'" ); diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index d1ffb30f3..9c02c29ee 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -336,16 +336,21 @@ impl Handler for Accountant { .schedule_new_payable_scan(ctx, &self.logger) } } - PendingPayableScanResult::PaymentRetryRequired(retry) => match retry { - Retry::RetryPayments => self.scan_schedulers.payable.schedule_retry_payable_scan( - ctx, - response_skeleton_opt, - &self.logger, - ), - Retry::RetryTxStatusCheckOnly => self + PendingPayableScanResult::PaymentRetryRequired(retry_either) => match retry_either { + Either::Left(Retry::RetryPayments) => self + .scan_schedulers + .payable + .schedule_retry_payable_scan(ctx, response_skeleton_opt, &self.logger), + Either::Left(Retry::RetryTxStatusCheckOnly) => self .scan_schedulers .pending_payable .schedule(ctx, &self.logger), + Either::Right(node_to_ui_msg) => self + .ui_message_sub_opt + .as_ref() + .expect("UIGateway is not bound") + .try_send(node_to_ui_msg) + .expect("UIGateway is dead"), }, }; } @@ -1270,6 +1275,7 @@ mod tests { use crate::blockchain::blockchain_interface::data_structures::{ StatusReadFromReceiptCheck, TxBlock, }; + use crate::blockchain::errors::rpc_errors::RemoteError; use crate::blockchain::errors::validation_status::ValidationStatus; use crate::blockchain::test_utils::make_tx_hash; use crate::database::rusqlite_wrappers::TransactionSafeWrapper; @@ -2805,7 +2811,7 @@ mod tests { })) .finish_scan_params(&scan_params.pending_payable_finish_scan) .finish_scan_result(PendingPayableScanResult::PaymentRetryRequired( - Retry::RetryPayments, + Either::Left(Retry::RetryPayments), )); let receivable_scanner = ScannerMock::new() .scan_started_at_result(None) @@ -4986,7 +4992,7 @@ mod tests { let pending_payable_scanner = ScannerMock::new() .finish_scan_params(&finish_scan_params_arc) .finish_scan_result(PendingPayableScanResult::PaymentRetryRequired( - Retry::RetryPayments, + Either::Left(Retry::RetryPayments), )); subject .scanners @@ -5049,7 +5055,7 @@ mod tests { let pending_payable_scanner = ScannerMock::new() .finish_scan_params(&finish_scan_params_arc) .finish_scan_result(PendingPayableScanResult::PaymentRetryRequired( - Retry::RetryTxStatusCheckOnly, + Either::Left(Retry::RetryTxStatusCheckOnly), )); subject .scanners @@ -5069,24 +5075,10 @@ mod tests { .notify_later_params(&pending_payable_notify_later_params_arc), ); let system = System::new(test_name); - let (mut msg, _) = make_tx_receipts_msg(vec![ - SeedsToMakeUpPayableWithStatus { - tx_hash: TxHashByTable::SentPayable(make_tx_hash(123)), - status: StatusReadFromReceiptCheck::Pending, - }, - SeedsToMakeUpPayableWithStatus { - tx_hash: TxHashByTable::FailedPayable(make_tx_hash(456)), - status: StatusReadFromReceiptCheck::Reverted, - }, - ]); - // Although it all begins with an external trigger, the response skeleton will not be - // propagated eventually to the further stages (see the NotifyLaterHandle assertion holding - // None for the response skeleton) - let response_skeleton_opt = Some(ResponseSkeleton { - client_id: 45, - context_id: 7, - }); - msg.response_skeleton_opt = response_skeleton_opt; + let msg = TxReceiptsMessage { + results: hashmap!(TxHashByTable::SentPayable(make_tx_hash(123)) => Err(AppRpcError::Remote(RemoteError::Unreachable))), + response_skeleton_opt: None, + }; let subject_addr = subject.start(); subject_addr.try_send(msg.clone()).unwrap(); @@ -5110,6 +5102,66 @@ mod tests { assert_using_the_same_logger(&logger, test_name, None) } + #[test] + fn accountant_sends_ui_msg_for_an_external_scan_trigger_despite_the_need_of_retry_was_detected() + { + init_test_logging(); + let (ui_gateway, _, ui_gateway_recording_arc) = make_recorder(); + let ui_gateway = + ui_gateway.system_stop_conditions(match_lazily_every_type_id!(NodeToUiMessage)); + let test_name = + "accountant_sends_ui_msg_for_an_external_scan_trigger_despite_the_need_of_retry_was_detected"; + let finish_scan_params_arc = Arc::new(Mutex::new(vec![])); + let mut subject = AccountantBuilder::default() + .logger(Logger::new(test_name)) + .build(); + subject.ui_message_sub_opt = Some(ui_gateway.start().recipient()); + let response_skeleton = ResponseSkeleton { + client_id: 123, + context_id: 333, + }; + let node_to_ui_msg = NodeToUiMessage { + target: MessageTarget::ClientId(123), + body: UiScanResponse {}.tmb(333), + }; + let pending_payable_scanner = ScannerMock::new() + .finish_scan_params(&finish_scan_params_arc) + .finish_scan_result(PendingPayableScanResult::PaymentRetryRequired( + Either::Right(node_to_ui_msg.clone()), + )); + subject + .scanners + .replace_scanner(ScannerReplacement::PendingPayable(ReplacementType::Mock( + pending_payable_scanner, + ))); + subject.scan_schedulers.payable.retry_payable_notify = + Box::new(NotifyHandleMock::default().panic_on_schedule_attempt()); + subject.scan_schedulers.payable.new_payable_notify = + Box::new(NotifyHandleMock::default().panic_on_schedule_attempt()); + subject.scan_schedulers.payable.new_payable_notify_later = + Box::new(NotifyLaterHandleMock::default().panic_on_schedule_attempt()); + subject.scan_schedulers.pending_payable.handle = + Box::new(NotifyLaterHandleMock::default().panic_on_schedule_attempt()); + let system = System::new(test_name); + + let msg = TxReceiptsMessage { + results: hashmap!(TxHashByTable::SentPayable(make_tx_hash(123)) => Err(AppRpcError::Remote(RemoteError::Unreachable))), + response_skeleton_opt: Some(response_skeleton), + }; + let subject_addr = subject.start(); + + subject_addr.try_send(msg.clone()).unwrap(); + + system.run(); + let mut finish_scan_params = finish_scan_params_arc.lock().unwrap(); + let (msg_actual, logger) = finish_scan_params.remove(0); + assert_eq!(msg_actual, msg); + let ui_gateway_recording = ui_gateway_recording_arc.lock().unwrap(); + let captured_msg = ui_gateway_recording.get_record::(0); + assert_eq!(captured_msg, &node_to_ui_msg); + assert_using_the_same_logger(&logger, test_name, None) + } + #[test] fn accountant_confirms_all_pending_txs_and_schedules_the_new_payable_scanner_timely() { init_test_logging(); diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index 14630be55..613f41c64 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -1064,6 +1064,7 @@ mod tests { use crate::test_utils::{make_paying_wallet, make_wallet}; use actix::{Message, System}; use ethereum_types::U64; + use itertools::Either; use masq_lib::logger::Logger; use masq_lib::messages::ScanType; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; @@ -2564,7 +2565,7 @@ mod tests { assert_eq!( result, - PendingPayableScanResult::PaymentRetryRequired(Retry::RetryPayments) + PendingPayableScanResult::PaymentRetryRequired(Either::Left(Retry::RetryPayments)) ); let transactions_confirmed_params = transactions_confirmed_params_arc.lock().unwrap(); sent_tx_1.status = TxStatus::Confirmed { diff --git a/node/src/accountant/scanners/pending_payable_scanner/mod.rs b/node/src/accountant/scanners/pending_payable_scanner/mod.rs index 52e73008a..f501a7be2 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/mod.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/mod.rs @@ -208,7 +208,15 @@ impl PendingPayableScanner { response_skeleton_opt: Option, ) -> PendingPayableScanResult { if let Some(retry) = retry_opt { - PendingPayableScanResult::PaymentRetryRequired(retry) + if let Some(response_skeleton) = response_skeleton_opt { + let ui_msg = NodeToUiMessage { + target: MessageTarget::ClientId(response_skeleton.client_id), + body: UiScanResponse {}.tmb(response_skeleton.context_id), + }; + PendingPayableScanResult::PaymentRetryRequired(Either::Right(ui_msg)) + } else { + PendingPayableScanResult::PaymentRetryRequired(Either::Left(retry)) + } } else { let ui_msg_opt = response_skeleton_opt.map(|response_skeleton| NodeToUiMessage { target: MessageTarget::ClientId(response_skeleton.client_id), @@ -823,7 +831,7 @@ mod tests { 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 itertools::{Either, Itertools}; use masq_lib::logger::Logger; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; use regex::Regex; @@ -949,7 +957,7 @@ mod tests { assert_eq!( result, - PendingPayableScanResult::PaymentRetryRequired(Retry::RetryPayments) + PendingPayableScanResult::PaymentRetryRequired(Either::Left(Retry::RetryPayments)) ); let get_record_by_hash_failed_payable_cache_params = get_record_by_hash_failed_payable_cache_params_arc @@ -1554,8 +1562,8 @@ mod tests { subject .handle_confirmed_transactions(DetectedConfirmations::default(), &Logger::new("test")) - // Mocked payable DAO without prepared results didn't panic, which none of its methods was - // used in this test + // Mocked payable DAO without prepared results didn't panic, which means none of its methods + // was used in this test } #[test] diff --git a/node/src/accountant/scanners/pending_payable_scanner/utils.rs b/node/src/accountant/scanners/pending_payable_scanner/utils.rs index 72c16056a..d08808d75 100644 --- a/node/src/accountant/scanners/pending_payable_scanner/utils.rs +++ b/node/src/accountant/scanners/pending_payable_scanner/utils.rs @@ -300,7 +300,7 @@ impl RecheckRequiringFailures { #[derive(Debug, PartialEq, Eq)] pub enum PendingPayableScanResult { NoPendingPayablesLeft(Option), - PaymentRetryRequired(Retry), + PaymentRetryRequired(Either), } #[derive(Debug, PartialEq, Eq)] diff --git a/node/src/test_utils/mod.rs b/node/src/test_utils/mod.rs index 69dd583da..546149ae6 100644 --- a/node/src/test_utils/mod.rs +++ b/node/src/test_utils/mod.rs @@ -946,8 +946,7 @@ pub mod unshared_test_utils { ) -> Box { if self.panic_on_schedule_attempt { panic!( - "Message scheduling request for {:?} and interval {}ms, thought not \ - expected", + "Message scheduling request for {:?} and interval {}ms, thought not expected", msg, interval.as_millis() ); From 24ec9d8e37ce495a8dee91532e56bcec25b6aaa6 Mon Sep 17 00:00:00 2001 From: Bert Date: Fri, 12 Sep 2025 16:35:25 +0200 Subject: [PATCH 61/61] GH-642: added the cache clean-up on getting a scan error --- node/src/accountant/mod.rs | 70 ++++++++++++++++++++++++----- node/src/accountant/scanners/mod.rs | 15 +++++++ 2 files changed, 75 insertions(+), 10 deletions(-) diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index 9c02c29ee..f54a7dbd6 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -5565,7 +5565,7 @@ mod tests { #[test] fn handling_scan_error_for_externally_triggered_payables() { - assert_scan_error_is_handled_properly( + test_scan_error_is_handled_properly( "handling_scan_error_for_externally_triggered_payables", ScanError { scan_type: ScanType::Payables, @@ -5577,19 +5577,21 @@ mod tests { #[test] fn handling_scan_error_for_externally_triggered_pending_payables() { - assert_scan_error_is_handled_properly( + let additional_test_setup_and_assertions = prepare_setup_and_assertion_fns(); + test_scan_error_is_handled_properly_more_specifically( "handling_scan_error_for_externally_triggered_pending_payables", ScanError { scan_type: ScanType::PendingPayables, response_skeleton_opt: Some(EXAMPLE_RESPONSE_SKELETON), msg: EXAMPLE_ERROR_MSG.to_string(), }, + Some(additional_test_setup_and_assertions), ); } #[test] fn handling_scan_error_for_externally_triggered_receivables() { - assert_scan_error_is_handled_properly( + test_scan_error_is_handled_properly( "handling_scan_error_for_externally_triggered_receivables", ScanError { scan_type: ScanType::Receivables, @@ -5601,7 +5603,7 @@ mod tests { #[test] fn handling_scan_error_for_internally_triggered_payables() { - assert_scan_error_is_handled_properly( + test_scan_error_is_handled_properly( "handling_scan_error_for_internally_triggered_payables", ScanError { scan_type: ScanType::Payables, @@ -5613,19 +5615,21 @@ mod tests { #[test] fn handling_scan_error_for_internally_triggered_pending_payables() { - assert_scan_error_is_handled_properly( + let additional_test_setup_and_assertions = prepare_setup_and_assertion_fns(); + test_scan_error_is_handled_properly_more_specifically( "handling_scan_error_for_internally_triggered_pending_payables", ScanError { scan_type: ScanType::PendingPayables, response_skeleton_opt: None, msg: EXAMPLE_ERROR_MSG.to_string(), }, + Some(additional_test_setup_and_assertions), ); } #[test] fn handling_scan_error_for_internally_triggered_receivables() { - assert_scan_error_is_handled_properly( + test_scan_error_is_handled_properly( "handling_scan_error_for_internally_triggered_receivables", ScanError { scan_type: ScanType::Receivables, @@ -5635,6 +5639,34 @@ mod tests { ); } + fn prepare_setup_and_assertion_fns() -> (Box, Box) { + let ensure_empty_cache_sent_tx_params_arc = Arc::new(Mutex::new(vec![])); + let ensure_empty_cache_failed_tx_params_arc = Arc::new(Mutex::new(vec![])); + let sent_payable_cache = PendingPayableCacheMock::default() + .ensure_empty_cache_params(&ensure_empty_cache_sent_tx_params_arc); + let failed_payable_cache = PendingPayableCacheMock::default() + .ensure_empty_cache_params(&ensure_empty_cache_failed_tx_params_arc); + let scanner = PendingPayableScannerBuilder::new() + .sent_payable_cache(Box::new(sent_payable_cache)) + .failed_payable_cache(Box::new(failed_payable_cache)) + .build(); + ( + Box::new(|scanners: &mut Scanners| { + scanners.replace_scanner(ScannerReplacement::PendingPayable( + ReplacementType::Real(scanner), + )); + }) as Box, + Box::new(move || { + let ensure_empty_cache_sent_tx_params = + ensure_empty_cache_sent_tx_params_arc.lock().unwrap(); + assert_eq!(*ensure_empty_cache_sent_tx_params, vec![()]); + let ensure_empty_cache_failed_tx_params = + ensure_empty_cache_failed_tx_params_arc.lock().unwrap(); + assert_eq!(*ensure_empty_cache_failed_tx_params, vec![()]); + }) as Box, + ) + } + #[test] fn financials_request_with_nothing_to_respond_to_is_refused() { let system = System::new("test"); @@ -6380,15 +6412,32 @@ mod tests { let _: u64 = wei_to_gwei(u128::MAX); } - fn assert_scan_error_is_handled_properly(test_name: &str, message: ScanError) { + fn test_scan_error_is_handled_properly(test_name: &str, message: ScanError) { + test_scan_error_is_handled_properly_more_specifically(test_name, message, None) + } + fn test_scan_error_is_handled_properly_more_specifically( + test_name: &str, + message: ScanError, + additional_assertion_opt: Option<(Box, Box)>, + ) { init_test_logging(); let (ui_gateway, _, ui_gateway_recording_arc) = make_recorder(); let mut subject = AccountantBuilder::default() .logger(Logger::new(test_name)) .build(); - subject - .scanners - .reset_scan_started(message.scan_type, MarkScanner::Started(SystemTime::now())); + let (adjust_scanner, run_additional_assertion) = match additional_assertion_opt { + Some(two_functions) => two_functions, + None => ( + Box::new(|scanners: &mut Scanners| { + scanners.reset_scan_started( + message.scan_type, + MarkScanner::Started(SystemTime::now()), + ) + }) as Box, + Box::new(|| ()) as Box, + ), + }; + adjust_scanner(&mut subject.scanners); let subject_addr = subject.start(); let system = System::new("test"); let peer_actors = peer_actors_builder().ui_gateway(ui_gateway).build(); @@ -6445,6 +6494,7 @@ mod tests { )); } } + run_additional_assertion(); } #[test] diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index 613f41c64..71ff0f62c 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -275,6 +275,7 @@ impl Scanners { self.payable.mark_as_ended(logger); } ScanType::PendingPayables => { + self.empty_caches(logger); self.pending_payable.mark_as_ended(logger); } ScanType::Receivables => { @@ -283,6 +284,20 @@ impl Scanners { }; } + fn empty_caches(&mut self, logger: &Logger) { + let pending_payable_scanner = self + .pending_payable + .as_any_mut() + .downcast_mut::() + .expect("mismatched types"); + pending_payable_scanner + .current_sent_payables + .ensure_empty_cache(logger); + pending_payable_scanner + .yet_unproven_failed_payables + .ensure_empty_cache(logger); + } + pub fn try_skipping_payable_adjustment( &self, msg: BlockchainAgentWithContextMessage,