From cc70df904aff1c2adade25ef275523c4d572cc0d Mon Sep 17 00:00:00 2001 From: Bert Date: Fri, 15 Aug 2025 22:38:18 +0200 Subject: [PATCH 1/7] 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 2/7] 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 3/7] 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 4/7] 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 5/7] 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 6/7] 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 b44b3a5ef6318258badc4da5ef51fe1271ba6b3c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 19 Aug 2025 12:23:40 +0000 Subject: [PATCH 7/7] Fix critical issues from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix all 'costume' typos to 'custom' in method names - Fix test function name typo 'app_arp' to 'app_rpc' - Improve hash test efficiency using HashSet (O(n) instead of O(n²)) - Add overflow protection for ErrorStats::increment using saturating_add - Replace magic numbers with named constants in hash implementation - Rename dup() method to clone_boxed() for clarity - Make ValidationFailureClockReal a unit struct - Replace unwrap() with expect() in tests for better error messages - Fix error message inconsistency in payable_dao.rs - Fix comment references to 'app_rpc_web3_error_kind' --- .../db_access_objects/payable_dao.rs | 4 +- .../app_rpc_web3_error_kind.rs | 67 +++++++++++-------- .../blockchain_db_error/masq_error_kind.rs | 8 +-- .../errors/blockchain_db_error/mod.rs | 18 ++--- .../app_rpc_web3_error.rs | 6 +- .../blockchain_loggable_error/masq_error.rs | 2 +- .../errors/blockchain_loggable_error/mod.rs | 2 +- node/src/blockchain/errors/common_methods.rs | 2 +- node/src/blockchain/errors/test_utils.rs | 8 +-- .../blockchain/errors/validation_status.rs | 10 +-- 10 files changed, 68 insertions(+), 59 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/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 06a4b7715..d53d0145a 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 @@ -17,11 +17,11 @@ impl BlockchainDbError for AppRpcWeb3ErrorKind { } impl CustomSeDe for AppRpcWeb3ErrorKind { - fn costume_serialize(&self) -> Result { + fn custom_serialize(&self) -> Result { serde_json::to_value(self) } - fn costume_deserialize(str: &str) -> Result, serde_json::Error> + fn custom_deserialize(str: &str) -> Result, serde_json::Error> where Self: Sized, { @@ -39,25 +39,35 @@ impl CommonMethods> for AppRpcWeb3ErrorKind { .map_or(false, |other| self == other) } - fn dup(&self) -> Box { + fn clone_boxed(&self) -> Box { Box::new(self.clone()) } as_any_ref_in_trait_impl!(); } +// Hash discriminants for each error variant +const HASH_DECODER: u8 = 0; +const HASH_INTERNAL: u8 = 1; +const HASH_IO: u8 = 2; +const HASH_SIGNING: u8 = 3; +const HASH_TRANSPORT: u8 = 4; +const HASH_INVALID_RESPONSE: u8 = 5; +const HASH_SERVER_UNREACHABLE: u8 = 6; +const HASH_WEB3_RPC_ERROR: u8 = 7; + impl CustomHash for AppRpcWeb3ErrorKind { - fn costume_hash(&self, hasher: &mut dyn Hasher) { + fn custom_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::Decoder => hasher.write_u8(HASH_DECODER), + AppRpcWeb3ErrorKind::Internal => hasher.write_u8(HASH_INTERNAL), + AppRpcWeb3ErrorKind::IO => hasher.write_u8(HASH_IO), + AppRpcWeb3ErrorKind::Signing => hasher.write_u8(HASH_SIGNING), + AppRpcWeb3ErrorKind::Transport => hasher.write_u8(HASH_TRANSPORT), + AppRpcWeb3ErrorKind::InvalidResponse => hasher.write_u8(HASH_INVALID_RESPONSE), + AppRpcWeb3ErrorKind::ServerUnreachable => hasher.write_u8(HASH_SERVER_UNREACHABLE), AppRpcWeb3ErrorKind::Web3RpcError(code) => { - hasher.write_u8(7); + hasher.write_u8(HASH_WEB3_RPC_ERROR); hasher.write_i64(*code); } } @@ -166,9 +176,10 @@ mod tests { } #[test] - fn hashing_for_app_arp_error_kind_works() { - let mut hasher = DefaultHasher::default(); - let mut hashes = vec![ + fn hashing_for_app_rpc_error_kind_works() { + use std::collections::HashSet; + + let errors = vec![ Box::new(AppRpcWeb3ErrorKind::Decoder) as Box, Box::new(AppRpcWeb3ErrorKind::Internal), Box::new(AppRpcWeb3ErrorKind::IO), @@ -179,21 +190,19 @@ mod tests { 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::>(); + ]; + + let hashes: HashSet = errors + .into_iter() + .map(|blockchain_error| { + let mut hasher = DefaultHasher::default(); + 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); - }); - }) + // If all hashes are unique, the set size should equal the number of errors + assert_eq!(hashes.len(), 10, "Some error kinds produced duplicate hashes"); } #[test] 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 90517a3dc..d8d174cc7 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 @@ -20,11 +20,11 @@ impl BlockchainDbError for MASQErrorKind { } impl CustomSeDe for MASQErrorKind { - fn costume_serialize(&self) -> Result { + fn custom_serialize(&self) -> Result { serde_json::to_value(self) } - fn costume_deserialize(str: &str) -> Result, serde_json::Error> + fn custom_deserialize(str: &str) -> Result, serde_json::Error> where Self: Sized, { @@ -42,7 +42,7 @@ impl CommonMethods> for MASQErrorKind { .map_or(false, |other| self == other) } - fn dup(&self) -> Box { + fn clone_boxed(&self) -> Box { Box::new(self.clone()) } @@ -50,7 +50,7 @@ impl CommonMethods> for MASQErrorKind { } impl CustomHash for MASQErrorKind { - fn costume_hash(&self, hasher: &mut dyn Hasher) { + fn custom_hash(&self, hasher: &mut dyn Hasher) { match self { MASQErrorKind::PendingTooLongNotReplaced => hasher.write_u8(0), } diff --git a/node/src/blockchain/errors/blockchain_db_error/mod.rs b/node/src/blockchain/errors/blockchain_db_error/mod.rs index b47933d52..44ae27c02 100644 --- a/node/src/blockchain/errors/blockchain_db_error/mod.rs +++ b/node/src/blockchain/errors/blockchain_db_error/mod.rs @@ -16,14 +16,14 @@ pub trait BlockchainDbError: CustomSeDe + CustomHash + Debug { } pub trait CustomSeDe { - fn costume_serialize(&self) -> Result; - fn costume_deserialize(str: &str) -> Result, serde_json::Error> + fn custom_serialize(&self) -> Result; + fn custom_deserialize(str: &str) -> Result, serde_json::Error> where Self: Sized; } pub trait CustomHash { - fn costume_hash(&self, hasher: &mut dyn Hasher); + fn custom_hash(&self, hasher: &mut dyn Hasher); } impl SerializeTrait for Box { @@ -31,7 +31,7 @@ impl SerializeTrait for Box { where S: serde::Serializer, { - self.costume_serialize() + self.custom_serialize() .map_err(|e| serde::ser::Error::custom(e))? .serialize(serializer) } @@ -46,11 +46,11 @@ impl<'de> DeserializeTrait<'de> for Box { 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) { + if let Ok(error) = AppRpcWeb3ErrorKind::custom_deserialize(&json_str) { return Ok(error); } - if let Ok(error) = MASQErrorKind::costume_deserialize(&json_str) { + if let Ok(error) = MASQErrorKind::custom_deserialize(&json_str) { return Ok(error); } @@ -63,7 +63,7 @@ impl<'de> DeserializeTrait<'de> for Box { impl Clone for Box { fn clone(&self) -> Self { - self.as_common_methods().dup() + self.as_common_methods().clone_boxed() } } @@ -75,7 +75,7 @@ impl PartialEq for Box { impl Hash for Box { fn hash(&self, state: &mut H) { - self.costume_hash(state) + self.custom_hash(state) } } @@ -99,7 +99,7 @@ mod tests { } #[test] - fn pre_serialization_costume_error_is_well_arranged() { + fn pre_serialization_custom_error_is_well_arranged() { let mock = BlockchainDbErrorMock::default(); let subject: Box = Box::new(mock); 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 87c395209..18b747a50 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 @@ -7,7 +7,7 @@ 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. +// Prefixed with App to clearly distinguish app-specific errors from library errors. #[derive(Debug, Clone, PartialEq, Eq)] pub enum AppRpcWeb3Error { Local(LocalError), @@ -33,7 +33,7 @@ impl CommonMethods> for AppRpcWeb3Error { .map_or(false, |other| self == other) } - fn dup(&self) -> Box { + fn clone_boxed(&self) -> Box { Box::new(self.clone()) } @@ -62,7 +62,7 @@ pub enum RemoteError { Web3RpcError { code: i64, message: String }, } -// EVM based app_rpc_web3_error_kind +// EVM based errors impl From for AppRpcWeb3Error { fn from(error: Web3Error) -> Self { match error { 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 1995a4f68..266377f3c 100644 --- a/node/src/blockchain/errors/blockchain_loggable_error/masq_error.rs +++ b/node/src/blockchain/errors/blockchain_loggable_error/masq_error.rs @@ -31,7 +31,7 @@ impl CommonMethods> for MASQError { .map_or(false, |other| self == other) } - fn dup(&self) -> Box { + fn clone_boxed(&self) -> Box { Box::new(self.clone()) } diff --git a/node/src/blockchain/errors/blockchain_loggable_error/mod.rs b/node/src/blockchain/errors/blockchain_loggable_error/mod.rs index 1b58657db..7cf948b53 100644 --- a/node/src/blockchain/errors/blockchain_loggable_error/mod.rs +++ b/node/src/blockchain/errors/blockchain_loggable_error/mod.rs @@ -21,7 +21,7 @@ impl From> for Box { impl Clone for Box { fn clone(&self) -> Self { - self.as_common_methods().dup() + self.as_common_methods().clone_boxed() } } diff --git a/node/src/blockchain/errors/common_methods.rs b/node/src/blockchain/errors/common_methods.rs index d74b2d4b7..946f86aae 100644 --- a/node/src/blockchain/errors/common_methods.rs +++ b/node/src/blockchain/errors/common_methods.rs @@ -2,6 +2,6 @@ pub trait CommonMethods { fn partial_eq(&self, other: &Other) -> bool; - fn dup(&self) -> Other; + fn clone_boxed(&self) -> Other; as_any_ref_in_trait!(); } diff --git a/node/src/blockchain/errors/test_utils.rs b/node/src/blockchain/errors/test_utils.rs index 9d72e24e9..2e7094b99 100644 --- a/node/src/blockchain/errors/test_utils.rs +++ b/node/src/blockchain/errors/test_utils.rs @@ -47,14 +47,14 @@ impl BlockchainDbError for BlockchainDbErrorMock { } impl CustomSeDe for BlockchainDbErrorMock { - fn costume_serialize(&self) -> Result { + fn custom_serialize(&self) -> Result { Err(serde_json::Error::invalid_type( Unexpected::Char('a'), &"null", )) } - fn costume_deserialize( + fn custom_deserialize( _str: &str, ) -> Result, serde_json::error::Error> where @@ -65,7 +65,7 @@ impl CustomSeDe for BlockchainDbErrorMock { } impl CustomHash for BlockchainDbErrorMock { - fn costume_hash(&self, _hasher: &mut dyn Hasher) { + fn custom_hash(&self, _hasher: &mut dyn Hasher) { unimplemented!("not needed for testing") } } @@ -75,7 +75,7 @@ impl CommonMethods> for BlockchainDbErrorMock { unimplemented!("not needed for testing") } - fn dup(&self) -> Box { + fn clone_boxed(&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..d0b47ebb0 100644 --- a/node/src/blockchain/errors/validation_status.rs +++ b/node/src/blockchain/errors/validation_status.rs @@ -53,7 +53,7 @@ impl ErrorStats { } pub fn increment(&mut self) { - self.attempts += 1; + self.attempts = self.attempts.saturating_add(1); } } @@ -62,7 +62,7 @@ pub trait ValidationFailureClock { } #[derive(Default)] -pub struct ValidationFailureClockReal {} +pub struct ValidationFailureClockReal; impl ValidationFailureClock for ValidationFailureClockReal { fn now(&self) -> SystemTime { @@ -104,7 +104,7 @@ mod tests { let decoder_error_stats = subject .inner .get(&(Box::new(AppRpcWeb3ErrorKind::Decoder) as Box)) - .unwrap(); + .expect("Failed to get decoder error stats"); assert!( timestamp_a <= decoder_error_stats.first_seen && decoder_error_stats.first_seen <= timestamp_b, @@ -117,7 +117,7 @@ mod tests { let internal_error_stats = subject .inner .get(&(Box::new(AppRpcWeb3ErrorKind::Internal) as Box)) - .unwrap(); + .expect("Failed to get internal error stats"); assert!( timestamp_b <= internal_error_stats.first_seen && internal_error_stats.first_seen <= timestamp_c, @@ -130,7 +130,7 @@ mod tests { let io_error_stats = subject .inner .get(&(Box::new(AppRpcWeb3ErrorKind::IO) as Box)) - .unwrap(); + .expect("Failed to get IO error stats"); assert!( timestamp_c <= io_error_stats.first_seen && io_error_stats.first_seen <= timestamp_d, "Was expected from {:?} to {:?} but was {:?}",