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..296bfe8d2 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,8 @@ 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::rpc_errors::AppRpcErrorKind; +use crate::blockchain::errors::validation_status::PreviousAttempts; use crate::database::rusqlite_wrappers::ConnectionWrapper; use itertools::Itertools; use masq_lib::utils::ExpectValue; @@ -25,7 +26,7 @@ pub enum FailedPayableDaoError { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum FailureReason { - Submission(AppRpcError), + Submission(AppRpcErrorKind), Reverted, PendingTooLong, } @@ -75,7 +76,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 +382,12 @@ 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::rpc_errors::AppRpcErrorKind; + use crate::blockchain::errors::validation_status::{ + PreviousAttempts, ValidationFailureClockReal, + }; + use crate::blockchain::errors::BlockchainErrorKind; + use crate::blockchain::test_utils::{make_tx_hash, ValidationFailureClockMock}; use crate::database::db_initializer::{ DbInitializationConfig, DbInitializer, DbInitializerReal, }; @@ -390,7 +395,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() { @@ -584,11 +591,8 @@ mod tests { fn failure_reason_from_str_works() { // 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(AppRpcErrorKind::Decoder) ); // Reverted @@ -620,6 +624,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 +640,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(BlockchainErrorKind::AppRpc(AppRpcErrorKind::ServerUnreachable), &validation_failure_clock))) ); assert_eq!( @@ -713,10 +722,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( + BlockchainErrorKind::AppRpc(AppRpcErrorKind::ServerUnreachable), + &ValidationFailureClockReal::default(), + ), + ))) .build(); let tx4 = FailedTxBuilder::default() .hash(make_tx_hash(4)) @@ -768,10 +779,10 @@ mod tests { (tx1.hash, Concluded), ( tx2.hash, - RecheckRequired(ValidationStatus::Reattempting { - attempt: 1, - error: AppRpcError::Remote(RemoteError::Unreachable), - }), + RecheckRequired(ValidationStatus::Reattempting(PreviousAttempts::new( + BlockchainErrorKind::AppRpc(AppRpcErrorKind::ServerUnreachable), + &ValidationFailureClockReal::default(), + ))), ), (tx3.hash, Concluded), ]); @@ -785,10 +796,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( + BlockchainErrorKind::AppRpc(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..a79e8ffbd 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,10 @@ 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::BlockchainErrorKind; + use crate::blockchain::errors::rpc_errors::AppRpcErrorKind; + use crate::blockchain::errors::validation_status::{PreviousAttempts, ValidationFailureClockReal}; + use crate::blockchain::test_utils::{make_block_hash, make_tx_hash, ValidationFailureClockMock}; #[test] fn insert_new_records_works() { @@ -452,10 +456,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( + BlockchainErrorKind::AppRpc(AppRpcErrorKind::ServerUnreachable), + &ValidationFailureClockReal::default(), + ) + .add_attempt( + BlockchainErrorKind::AppRpc(AppRpcErrorKind::ServerUnreachable), + &ValidationFailureClockReal::default(), + ), + ))) .build(); let subject = SentPayableDaoReal::new(wrapped_conn); let txs = vec![tx1, tx2]; @@ -682,10 +692,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( + BlockchainErrorKind::AppRpc(AppRpcErrorKind::ServerUnreachable), + &ValidationFailureClockReal::default(), + ), + ))) .build(); let tx3 = TxBuilder::default() .hash(make_tx_hash(3)) @@ -1169,14 +1181,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(BlockchainErrorKind::AppRpc(AppRpcErrorKind::InvalidResponse), &validation_failure_clock))) ); assert_eq!( diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index 39ead2d76..2ca502557 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -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..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; @@ -3099,7 +3095,7 @@ mod tests { ScanError { scan_type, response_skeleton_opt: None, - msg: "bluh".to_string(), + msg: "blah".to_string(), } } 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..ffdfa4c20 100644 --- a/node/src/blockchain/blockchain_interface/data_structures/errors.rs +++ b/node/src/blockchain/blockchain_interface/data_structures/errors.rs @@ -11,24 +11,22 @@ 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, QueryFailed(String), - UninitializedBlockchainInterface, + UninitializedInterface, } -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::UninitializedBlockchainInterface => { - Either::Left(BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED) - } + Self::UninitializedInterface => Either::Left(BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED), }; write!(f, "Blockchain error: {}", err_spec) } @@ -37,12 +35,12 @@ 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 }, - UninitializedBlockchainInterface, + UninitializedInterface, } impl Display for PayableTransactionError { @@ -69,7 +67,7 @@ impl Display for PayableTransactionError { msg, comma_joined_stringifiable(hashes, |hash| format!("{:?}", hash)) ), - Self::UninitializedBlockchainInterface => { + Self::UninitializedInterface => { write!(f, "{}", BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED) } } @@ -78,10 +76,10 @@ impl Display for PayableTransactionError { #[derive(Clone, Debug, PartialEq, Eq, VariantCount)] pub enum BlockchainAgentBuildError { - GasPrice(BlockchainError), - TransactionFeeBalance(Address, BlockchainError), - ServiceFeeBalance(Address, BlockchainError), - UninitializedBlockchainInterface, + GasPrice(BlockchainInterfaceError), + TransactionFeeBalance(Address, BlockchainInterfaceError), + ServiceFeeBalance(Address, BlockchainInterfaceError), + UninitializedInterface, } impl Display for BlockchainAgentBuildError { @@ -98,7 +96,7 @@ impl Display for BlockchainAgentBuildError { "masq balance for our earning wallet {:#x} due to {}", address, blockchain_e )), - Self::UninitializedBlockchainInterface => { + Self::UninitializedInterface => { Either::Right(BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED.to_string()) } }; @@ -119,7 +117,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 +136,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::UninitializedInterface, ]; 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 +168,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(), ), @@ -182,7 +182,7 @@ mod tests { msg: "Sending to cosmos belongs elsewhere".to_string(), hashes: vec![make_tx_hash(0x6f), make_tx_hash(0xde)], }, - PayableTransactionError::UninitializedBlockchainInterface, + PayableTransactionError::UninitializedInterface, ]; let actual_error_msgs = original_errors.iter().map(to_string).collect::>(); @@ -213,16 +213,16 @@ 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, + BlockchainAgentBuildError::UninitializedInterface, ]; let actual_error_msgs = original_errors.iter().map(to_string).collect::>(); 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 deleted file mode 100644 index 865bea29c..000000000 --- a/node/src/blockchain/errors.rs +++ /dev/null @@ -1,127 +0,0 @@ -use serde_derive::{Deserialize, Serialize}; -use web3::error::Error as Web3Error; - -// Prefixed with App to clearly distinguish app-specific errors from library errors. -#[derive(Debug, Clone, PartialEq, Eq, 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 }, -} - -// 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), - } - } -} - -mod tests { - use crate::blockchain::errors::{AppRpcError, LocalError, RemoteError}; - 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); - }); - } -} diff --git a/node/src/blockchain/errors/internal_errors.rs b/node/src/blockchain/errors/internal_errors.rs new file mode 100644 index 000000000..fb6a4bf63 --- /dev/null +++ b/node/src/blockchain/errors/internal_errors.rs @@ -0,0 +1,51 @@ +// Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, PartialEq, Clone, Eq)] +pub enum InternalError { + PendingTooLongNotReplaced, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum InternalErrorKind { + PendingTooLongNotReplaced, +} + +impl From<&InternalError> for InternalErrorKind { + fn from(error: &InternalError) -> Self { + match error { + InternalError::PendingTooLongNotReplaced => { + InternalErrorKind::PendingTooLongNotReplaced + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn conversion_between_internal_error_and_internal_error_kind_works() { + assert_eq!( + InternalErrorKind::from(&InternalError::PendingTooLongNotReplaced), + InternalErrorKind::PendingTooLongNotReplaced + ); + } + + #[test] + fn app_rpc_error_kind_serialization_deserialization() { + let errors = vec![InternalErrorKind::PendingTooLongNotReplaced]; + + errors.into_iter().for_each(|error| { + let serialized = serde_json::to_string(&error).unwrap(); + let deserialized: InternalErrorKind = serde_json::from_str(&serialized).unwrap(); + assert_eq!( + error, deserialized, + "Failed serde attempt for {:?} that should look like {:?}", + deserialized, error + ); + }); + } +} diff --git a/node/src/blockchain/errors/mod.rs b/node/src/blockchain/errors/mod.rs new file mode 100644 index 000000000..ab18ae9df --- /dev/null +++ b/node/src/blockchain/errors/mod.rs @@ -0,0 +1,49 @@ +// Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::blockchain::errors::internal_errors::{InternalError, InternalErrorKind}; +use crate::blockchain::errors::rpc_errors::{AppRpcError, AppRpcErrorKind}; +use serde_derive::{Deserialize, Serialize}; + +pub mod internal_errors; +pub mod rpc_errors; +pub mod validation_status; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum BlockchainError { + AppRpc(AppRpcError), + Internal(InternalError), +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum BlockchainErrorKind { + AppRpc(AppRpcErrorKind), + Internal(InternalErrorKind), +} + +#[cfg(test)] +mod tests { + use crate::blockchain::errors::internal_errors::InternalErrorKind; + use crate::blockchain::errors::rpc_errors::AppRpcErrorKind; + use crate::blockchain::errors::BlockchainErrorKind; + + #[test] + fn blockchain_error_serialization_deserialization() { + vec![ + ( + BlockchainErrorKind::AppRpc(AppRpcErrorKind::Decoder), + "{\"AppRpc\":\"Decoder\"}", + ), + ( + BlockchainErrorKind::Internal(InternalErrorKind::PendingTooLongNotReplaced), + "{\"Internal\":\"PendingTooLongNotReplaced\"}", + ), + ] + .into_iter() + .for_each(|(err, expected_json)| { + let json = serde_json::to_string(&err).unwrap(); + assert_eq!(json, expected_json); + let deserialized_err = serde_json::from_str::(&json).unwrap(); + assert_eq!(deserialized_err, err); + }) + } +} diff --git a/node/src/blockchain/errors/rpc_errors.rs b/node/src/blockchain/errors/rpc_errors.rs new file mode 100644 index 000000000..4d8482e17 --- /dev/null +++ b/node/src/blockchain/errors/rpc_errors.rs @@ -0,0 +1,215 @@ +// 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 errors from library errors. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum AppRpcError { + Local(LocalError), + Remote(RemoteError), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum LocalError { + Decoder(String), + Internal, + Io(String), + Signing(String), + Transport(String), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum RemoteError { + InvalidResponse(String), + Unreachable, + Web3RpcError { code: i64, message: String }, +} + +// EVM based errors +impl From for 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), + } + } +} + +#[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 +} + +impl From<&AppRpcError> 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), + }, + } + } +} + +#[cfg(test)] +mod tests { + use crate::blockchain::errors::rpc_errors::{ + AppRpcError, AppRpcErrorKind, LocalError, RemoteError, + }; + use web3::error::Error as Web3Error; + + #[test] + fn web3_error_to_failure_reason_conversion_works() { + // Local Errors + assert_eq!( + 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 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 app_rpc_error_kind_serialization_deserialization() { + let errors = vec![ + // Local Errors + AppRpcErrorKind::Decoder, + AppRpcErrorKind::Internal, + AppRpcErrorKind::IO, + AppRpcErrorKind::Signing, + AppRpcErrorKind::Transport, + // Remote Errors + AppRpcErrorKind::InvalidResponse, + AppRpcErrorKind::ServerUnreachable, + AppRpcErrorKind::Web3RpcError(42), + ]; + + errors.into_iter().for_each(|error| { + let serialized = serde_json::to_string(&error).unwrap(); + let deserialized: AppRpcErrorKind = serde_json::from_str(&serialized).unwrap(); + assert_eq!( + error, deserialized, + "Failed serde attempt for {:?} that should look \ + like {:?}", + deserialized, error + ); + }); + } +} diff --git a/node/src/blockchain/errors/validation_status.rs b/node/src/blockchain/errors/validation_status.rs new file mode 100644 index 000000000..72ca28346 --- /dev/null +++ b/node/src/blockchain/errors/validation_status.rs @@ -0,0 +1,154 @@ +// Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::blockchain::errors::rpc_errors::AppRpcErrorKind; +use crate::blockchain::errors::BlockchainErrorKind; +use serde_derive::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::time::SystemTime; + +#[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, +} + +impl PreviousAttempts { + pub fn new(error: BlockchainErrorKind, clock: &dyn ValidationFailureClock) -> Self { + Self { + inner: hashmap!(error => ErrorStats::now(clock)), + } + } + + pub fn add_attempt( + mut self, + error: BlockchainErrorKind, + 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::internal_errors::InternalErrorKind; + + #[test] + fn previous_attempts_and_validation_failure_clock_work_together_fine() { + let validation_failure_clock = ValidationFailureClockReal::default(); + // new() + let timestamp_a = SystemTime::now(); + let subject = PreviousAttempts::new( + BlockchainErrorKind::AppRpc(AppRpcErrorKind::Decoder), + &validation_failure_clock, + ); + // add_attempt() + let timestamp_b = SystemTime::now(); + let subject = subject.add_attempt( + BlockchainErrorKind::Internal(InternalErrorKind::PendingTooLongNotReplaced), + &validation_failure_clock, + ); + let timestamp_c = SystemTime::now(); + let subject = subject.add_attempt( + BlockchainErrorKind::AppRpc(AppRpcErrorKind::IO), + &validation_failure_clock, + ); + let timestamp_d = SystemTime::now(); + let subject = subject.add_attempt( + BlockchainErrorKind::AppRpc(AppRpcErrorKind::Decoder), + &validation_failure_clock, + ); + let subject = subject.add_attempt( + BlockchainErrorKind::AppRpc(AppRpcErrorKind::IO), + &validation_failure_clock, + ); + + let decoder_error_stats = subject + .inner + .get(&BlockchainErrorKind::AppRpc(AppRpcErrorKind::Decoder)) + .unwrap(); + assert!( + timestamp_a <= decoder_error_stats.first_seen + && decoder_error_stats.first_seen <= timestamp_b, + "Was expected from {:?} to {:?} but was {:?}", + timestamp_a, + timestamp_b, + decoder_error_stats.first_seen + ); + assert_eq!(decoder_error_stats.attempts, 2); + let internal_error_stats = subject + .inner + .get(&BlockchainErrorKind::Internal( + InternalErrorKind::PendingTooLongNotReplaced, + )) + .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(&BlockchainErrorKind::AppRpc(AppRpcErrorKind::IO)) + .unwrap(); + assert!( + timestamp_c <= io_error_stats.first_seen && io_error_stats.first_seen <= timestamp_d, + "Was expected from {:?} to {:?} but was {:?}", + timestamp_c, + timestamp_d, + io_error_stats.first_seen + ); + assert_eq!(io_error_stats.attempts, 2); + let other_error_stats = subject + .inner + .get(&BlockchainErrorKind::AppRpc(AppRpcErrorKind::Signing)); + assert_eq!(other_error_stats, None); + } +} diff --git a/node/src/blockchain/test_utils.rs b/node/src/blockchain/test_utils.rs index 6259e8739..f3b354931 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::validation_status::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 + } +} diff --git a/node/src/database/db_initializer.rs b/node/src/database/db_initializer.rs index cbf6008c0..674786766 100644 --- a/node/src/database/db_initializer.rs +++ b/node/src/database/db_initializer.rs @@ -802,8 +802,7 @@ mod tests { gas_price_wei_high_b, gas_price_wei_low_b, nonce, - block_hash, - block_number + status FROM sent_payable", ) .unwrap();