diff --git a/masq/tests/startup_shutdown_tests_integration.rs b/masq/tests/startup_shutdown_tests_integration.rs index a152ec8a4..c66589130 100644 --- a/masq/tests/startup_shutdown_tests_integration.rs +++ b/masq/tests/startup_shutdown_tests_integration.rs @@ -116,7 +116,7 @@ fn handles_startup_and_shutdown_integration() { "--data-directory", dir_path.to_str().unwrap(), "--blockchain-service-url", - "https://example.com", + "https://nonexistentblockchainservice.com", ]); let (stdout, stderr, exit_code) = masq_handle.stop(); diff --git a/masq_lib/src/test_utils/mock_blockchain_client_server.rs b/masq_lib/src/test_utils/mock_blockchain_client_server.rs index cb9e55cf1..96caf1d67 100644 --- a/masq_lib/src/test_utils/mock_blockchain_client_server.rs +++ b/masq_lib/src/test_utils/mock_blockchain_client_server.rs @@ -24,7 +24,7 @@ lazy_static! { pub struct MBCSBuilder { port: u16, - run_on_docker: bool, + run_in_docker: bool, response_batch_opt: Option>, responses: Vec, notifier: Sender<()>, @@ -34,15 +34,15 @@ impl MBCSBuilder { pub fn new(port: u16) -> Self { Self { port, - run_on_docker: false, + run_in_docker: false, response_batch_opt: None, responses: vec![], notifier: unbounded().0, } } - pub fn run_on_docker(mut self) -> Self { - self.run_on_docker = true; + pub fn run_in_docker(mut self) -> Self { + self.run_in_docker = true; self } @@ -112,7 +112,7 @@ impl MBCSBuilder { pub fn start(self) -> MockBlockchainClientServer { let requests = Arc::new(Mutex::new(vec![])); let mut server = MockBlockchainClientServer { - port_or_local_addr: if self.run_on_docker { + port_or_local_addr: if self.run_in_docker { Right(SocketAddr::V4(SocketAddrV4::new( Ipv4Addr::new(172, 18, 0, 1), self.port, @@ -413,4 +413,5 @@ struct ConnectionState { request_accumulator: String, } -// Test for this are located: multinode_integration_tests/src/mock_blockchain_client_server.rs +// TODO GH-805 +// Tests for this are located: multinode_integration_tests/src/mock_blockchain_client_server.rs diff --git a/multinode_integration_tests/src/mock_blockchain_client_server.rs b/multinode_integration_tests/src/mock_blockchain_client_server.rs index 6eef0e839..dc211803c 100644 --- a/multinode_integration_tests/src/mock_blockchain_client_server.rs +++ b/multinode_integration_tests/src/mock_blockchain_client_server.rs @@ -1,6 +1,7 @@ // Copyright (c) 2022, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -// Code has been migrated to masq_lib/src/test_utils/mock_blockchain_client_server.rs +// TODO: GH-805 +// The actual mock server has been migrated to masq_lib/src/test_utils/mock_blockchain_client_server.rs #[cfg(test)] mod tests { @@ -29,7 +30,7 @@ mod tests { let port = find_free_port(); let _subject = MockBlockchainClientServer::builder(port) .response("Thank you and good night", 40) - .run_on_docker() + .run_in_docker() .start(); let mut client = connect(port); let chunks = vec![ @@ -61,7 +62,7 @@ mod tests { let _subject = MockBlockchainClientServer::builder(port) .response("Welcome, and thanks for coming!", 39) .response("Thank you and good night", 40) - .run_on_docker() + .run_in_docker() .start(); let mut client = connect(port); client.write (b"POST /biddle HTTP/1.1\r\nContent-Length: 5\r\n\r\nfirstPOST /biddle HTTP/1.1\r\nContent-Length: 6\r\n\r\nsecond").unwrap(); @@ -85,7 +86,7 @@ mod tests { let port = find_free_port(); let _subject = MockBlockchainClientServer::builder(port) .response("irrelevant".to_string(), 42) - .run_on_docker() + .run_in_docker() .start(); let mut client = connect(port); let request = b"POST /biddle HTTP/1.1\r\n\r\nbody"; @@ -102,7 +103,7 @@ mod tests { let port = find_free_port(); let _subject = MockBlockchainClientServer::builder(port) .response("irrelevant".to_string(), 42) - .run_on_docker() + .run_in_docker() .start(); let mut client = connect(port); let request = b"GET /booga\r\nContent-Length: 4\r\n\r\nbody"; @@ -119,7 +120,7 @@ mod tests { let port = find_free_port(); let _subject = MockBlockchainClientServer::builder(port) .response("irrelevant".to_string(), 42) - .run_on_docker() + .run_in_docker() .start(); let mut client = connect(port); let request = b"GET /booga HTTP/2.0\r\nContent-Length: 4\r\n\r\nbody"; @@ -155,7 +156,7 @@ mod tests { age: 37, }), ) - .run_on_docker() + .run_in_docker() .start(); let mut client = connect(port); @@ -217,7 +218,7 @@ mod tests { }, 42, ) - .run_on_docker() + .run_in_docker() .start(); let mut client = connect(port); let request = diff --git a/multinode_integration_tests/tests/blockchain_interaction_test.rs b/multinode_integration_tests/tests/blockchain_interaction_test.rs index 7f86d0e10..02a063d87 100644 --- a/multinode_integration_tests/tests/blockchain_interaction_test.rs +++ b/multinode_integration_tests/tests/blockchain_interaction_test.rs @@ -59,7 +59,7 @@ fn debtors_are_credited_once_but_not_twice() { }], 1, ) - .run_on_docker() + .run_in_docker() .start(); // Start a real Node pointing at the mock blockchain client with a start block of 1000 let node_config = NodeStartupConfigBuilder::standard() @@ -144,7 +144,7 @@ fn debtors_are_credited_once_but_not_twice() { let config_dao = config_dao(&node_name); assert_eq!( config_dao.get("start_block").unwrap().value_opt.unwrap(), - "2001" + "2000" ); } } diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index 1e7a16a81..a2aea8ad4 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -137,7 +137,7 @@ pub struct ReceivedPayments { // detects any upcoming delinquency later than the more accurate version would. Is this // a problem? Do we want to correct the timestamp? Discuss. pub timestamp: SystemTime, - pub scan_result: Result, + pub payments_and_start_block: PaymentsAndStartBlock, pub response_skeleton_opt: Option, } @@ -501,9 +501,9 @@ impl Accountant { if !self.our_wallet(wallet) { match self.receivable_dao .as_ref() - .more_money_receivable(timestamp,wallet, total_charge) { + .more_money_receivable(timestamp, wallet, total_charge) { Ok(_) => (), - Err(ReceivableDaoError::SignConversion(_)) => error! ( + Err(ReceivableDaoError::SignConversion(_)) => error!( self.logger, "Overflow error recording service provided for {}: service rate {}, byte rate {}, payload size {}. Skipping", wallet, @@ -511,7 +511,7 @@ impl Accountant { byte_rate, payload_size ), - Err(e)=> panic!("Recording services provided for {} but has hit fatal database error: {:?}", wallet, e) + Err(e) => panic!("Recording services provided for {} but has hit fatal database error: {:?}", wallet, e) }; } else { warning!( @@ -535,9 +535,9 @@ impl Accountant { if !self.our_wallet(wallet) { match self.payable_dao .as_ref() - .more_money_payable(timestamp, wallet,total_charge){ + .more_money_payable(timestamp, wallet, total_charge) { Ok(_) => (), - Err(PayableDaoError::SignConversion(_)) => error! ( + Err(PayableDaoError::SignConversion(_)) => error!( self.logger, "Overflow error recording consumed services from {}: total charge {}, service rate {}, byte rate {}, payload size {}. Skipping", wallet, @@ -1404,7 +1404,7 @@ mod tests { subject_addr.try_send(BindMessage { peer_actors }).unwrap(); let received_payments = ReceivedPayments { timestamp: SystemTime::now(), - scan_result: Ok(make_empty_payments_and_start_block()), + payments_and_start_block: make_empty_payments_and_start_block(), response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, context_id: 4321, @@ -1597,6 +1597,10 @@ mod tests { context_id: 4321, }) ); + assert_eq!( + payments_instructions.agent.arbitrary_id_stamp(), + agent_id_stamp + ); assert_eq!(blockchain_bridge_recording.len(), 1); test_use_of_the_same_logger(&logger_clone, test_name) // adjust_payments() did not need a prepared result which means it wasn't reached @@ -1709,6 +1713,10 @@ mod tests { let blockchain_bridge_recording = blockchain_bridge_recording_arc.lock().unwrap(); let payments_instructions = blockchain_bridge_recording.get_record::(0); + assert_eq!( + payments_instructions.agent.arbitrary_id_stamp(), + agent_id_stamp_second_phase + ); assert_eq!( payments_instructions.affordable_accounts, affordable_accounts @@ -2039,13 +2047,14 @@ mod tests { .build(); let system = System::new("accountant_uses_receivables_dao_to_process_received_payments"); let subject = accountant.start(); - let mut scan_result = make_empty_payments_and_start_block(); - scan_result.payments = vec![expected_receivable_1.clone(), expected_receivable_2.clone()]; - scan_result.new_start_block = 123456789; + let mut payments_and_start_block = make_empty_payments_and_start_block(); + payments_and_start_block.payments = + vec![expected_receivable_1.clone(), expected_receivable_2.clone()]; + payments_and_start_block.new_start_block = 123456789; subject .try_send(ReceivedPayments { timestamp: now, - scan_result: Ok(scan_result), + payments_and_start_block, response_skeleton_opt: None, }) .expect("unexpected actix error"); @@ -3431,10 +3440,10 @@ mod tests { init_test_logging(); let port = find_free_port(); let pending_tx_hash_1 = - H256::from_str("d89f74084be2601c816fb85b8eac6541437223ad4851d12e9eb3d6f74570b8ae") + H256::from_str("e66814b2812a80d619813f51aa999c0df84eb79d10f4923b2b7667b30d6b33d3") .unwrap(); let pending_tx_hash_2 = - H256::from_str("05981aa8d6c8ca1661f56a42e6e0c1aa56c9c9d0ecf26755b4388826aad55811") + H256::from_str("0288ef000581b3bca8a2017eac9aea696366f8f1b7437f18d1aad57bccb7032c") .unwrap(); let _blockchain_client_server = MBCSBuilder::new(port) // Blockchain Agent Gas Price @@ -3446,10 +3455,6 @@ mod tests { "0x000000000000000000000000000000000000000000000000000000000000FFFF".to_string(), 0, ) - // Blockchain Agent tx_id - .response("0x2".to_string(), 1) - // gas_price - .response("0x3B9ACA00".to_string(), 1) // Submit payments to blockchain .response("0xFFF0".to_string(), 1) .begin_batch() @@ -3510,7 +3515,6 @@ mod tests { ) .end_batch() .start(); - let non_pending_payables_params_arc = Arc::new(Mutex::new(vec![])); let mark_pending_payable_params_arc = Arc::new(Mutex::new(vec![])); let return_all_errorless_fingerprints_params_arc = Arc::new(Mutex::new(vec![])); @@ -3538,7 +3542,7 @@ mod tests { gwei_to_wei(DEFAULT_PAYMENT_THRESHOLDS.debt_threshold_gwei + 666); let wallet_account_1 = make_wallet("creditor1"); let wallet_account_2 = make_wallet("creditor2"); - let blockchain_interface = make_blockchain_interface_web3(Some(port)); + let blockchain_interface = make_blockchain_interface_web3(port); let consuming_wallet = make_paying_wallet(b"wallet"); let system = System::new("pending_transaction"); let persistent_config_id_stamp = ArbitraryIdStamp::new(); @@ -3546,7 +3550,7 @@ mod tests { .set_arbitrary_id_stamp(persistent_config_id_stamp); let blockchain_bridge = BlockchainBridge::new( Box::new(blockchain_interface), - Box::new(persistent_config), + Arc::new(Mutex::new(persistent_config)), false, ); let account_1 = PayableAccount { @@ -3628,8 +3632,6 @@ mod tests { no_rowid_results: vec![], }); let mut pending_payable_dao_for_pending_payable_scanner = PendingPayableDaoMock::new() - .insert_fingerprints_result(Ok(())) - .insert_fingerprints_result(Ok(())) .return_all_errorless_fingerprints_params(&return_all_errorless_fingerprints_params_arc) .return_all_errorless_fingerprints_result(vec![]) .return_all_errorless_fingerprints_result(vec![ @@ -3706,9 +3708,7 @@ mod tests { assert_eq!(system.run(), 0); let mut mark_pending_payable_params = mark_pending_payable_params_arc.lock().unwrap(); - let mut one_set_of_mark_pending_payable_params = mark_pending_payable_params.remove(0); - assert!(mark_pending_payable_params.is_empty()); let first_payable = one_set_of_mark_pending_payable_params.remove(0); assert_eq!(first_payable.0, wallet_account_1); @@ -3733,7 +3733,7 @@ mod tests { vec![ vec![rowid_for_account_1, rowid_for_account_2], vec![rowid_for_account_1, rowid_for_account_2], - vec![rowid_for_account_2] + vec![rowid_for_account_2], ] ); let mark_failure_params = mark_failure_params_arc.lock().unwrap(); @@ -3771,16 +3771,16 @@ mod tests { ); let log_handler = TestLogHandler::new(); log_handler.exists_log_containing( - "WARN: Accountant: Broken transactions 0xd89f74084be2601c816fb85b8eac6541437223ad4\ - 851d12e9eb3d6f74570b8ae marked as an error. You should take over the care of those to make sure \ + "WARN: Accountant: Broken transactions 0xe66814b2812a80d619813f51aa999c0df84eb79d10f\ + 4923b2b7667b30d6b33d3 marked as an error. You should take over the care of those to make sure \ your debts are going to be settled properly. At the moment, there is no automated process \ fixing that without your assistance"); - log_handler.exists_log_matching("INFO: Accountant: Transaction 0x05981aa8d6c8ca1661f56a42e6e\ - 0c1aa56c9c9d0ecf26755b4388826aad55811 has been added to the blockchain; detected locally at \ + log_handler.exists_log_matching("INFO: Accountant: Transaction 0x0288ef000581b3bca8a2017eac9\ + aea696366f8f1b7437f18d1aad57bccb7032c has been added to the blockchain; detected locally at \ attempt 4 at \\d{2,}ms after its sending"); log_handler.exists_log_containing( - "INFO: Accountant: Transactions 0x05981aa8d6c8ca1661f56a42e6e0c1aa56c9c9d0e\ - cf26755b4388826aad55811 completed their confirmation process succeeding", + "INFO: Accountant: Transactions 0x0288ef000581b3bca8a2017eac9aea696366f8f1b7437f18d1aad5\ + 7bccb7032c completed their confirmation process succeeding", ); } @@ -3864,7 +3864,6 @@ mod tests { let amount_1 = 12345; let hash_2 = make_tx_hash(0x1b207); let amount_2 = 87654; - let hash_and_amount_1 = HashAndAmount { hash: hash_1, amount: amount_1, @@ -3873,7 +3872,6 @@ mod tests { hash: hash_2, amount: amount_2, }; - let init_params = vec![hash_and_amount_1, hash_and_amount_2]; let init_fingerprints_msg = PendingPayableFingerprintSeeds { batch_wide_timestamp: timestamp, @@ -4735,7 +4733,7 @@ mod tests { fn checked_conversion_without_panic() { let result = politely_checked_conversion::(u128::MAX); - assert_eq!(result,Err("Overflow detected with 340282366920938463463374607431768211455: cannot be converted from u128 to i128".to_string())) + assert_eq!(result, Err("Overflow detected with 340282366920938463463374607431768211455: cannot be converted from u128 to i128".to_string())) } #[test] diff --git a/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/agent_null.rs b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/agent_null.rs index faf7d45cc..036d1d72f 100644 --- a/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/agent_null.rs +++ b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/agent_null.rs @@ -27,7 +27,7 @@ impl BlockchainAgent for BlockchainAgentNull { } } - fn agreed_fee_per_computation_unit(&self) -> u64 { + fn agreed_fee_per_computation_unit(&self) -> u128 { self.log_function_call("agreed_fee_per_computation_unit()"); 0 } @@ -37,11 +37,6 @@ impl BlockchainAgent for BlockchainAgentNull { &self.wallet } - fn pending_transaction_id(&self) -> U256 { - self.log_function_call("pending_transaction_id()"); - U256::zero() - } - #[cfg(test)] fn dup(&self) -> Box { intentionally_blank!() @@ -176,17 +171,4 @@ mod tests { assert_eq!(result, &Wallet::null()); assert_error_log(test_name, "consuming_wallet") } - - #[test] - fn null_agent_pending_transaction_id() { - init_test_logging(); - let test_name = "null_agent_pending_transaction_id"; - let mut subject = BlockchainAgentNull::new(); - subject.logger = Logger::new(test_name); - - let result = subject.pending_transaction_id(); - - assert_eq!(result, U256::zero()); - assert_error_log(test_name, "pending_transaction_id"); - } } diff --git a/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/agent_web3.rs b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/agent_web3.rs index 846a8b342..c31c7ebbb 100644 --- a/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/agent_web3.rs +++ b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/agent_web3.rs @@ -3,22 +3,20 @@ use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; use crate::sub_lib::wallet::Wallet; -use web3::types::U256; -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone)] pub struct BlockchainAgentWeb3 { - gas_price_gwei: u64, - gas_limit_const_part: u64, - maximum_added_gas_margin: u64, + gas_price_wei: u128, + gas_limit_const_part: u128, + maximum_added_gas_margin: u128, consuming_wallet: Wallet, consuming_wallet_balances: ConsumingWalletBalances, - pending_transaction_id: U256, } impl BlockchainAgent for BlockchainAgentWeb3 { fn estimated_transaction_fee_total(&self, number_of_transactions: usize) -> u128 { - let gas_price = self.gas_price_gwei as u128; - let max_gas_limit = (self.maximum_added_gas_margin + self.gas_limit_const_part) as u128; + let gas_price = self.gas_price_wei; + let max_gas_limit = self.maximum_added_gas_margin + self.gas_limit_const_part; number_of_transactions as u128 * gas_price * max_gas_limit } @@ -26,38 +24,32 @@ impl BlockchainAgent for BlockchainAgentWeb3 { self.consuming_wallet_balances } - fn agreed_fee_per_computation_unit(&self) -> u64 { - self.gas_price_gwei + fn agreed_fee_per_computation_unit(&self) -> u128 { + self.gas_price_wei } fn consuming_wallet(&self) -> &Wallet { &self.consuming_wallet } - - fn pending_transaction_id(&self) -> U256 { - self.pending_transaction_id - } } // 64 * (64 - 12) ... std transaction has data of 64 bytes and 12 bytes are never used with us; // each non-zero byte costs 64 units of gas -pub const WEB3_MAXIMAL_GAS_LIMIT_MARGIN: u64 = 3328; +pub const WEB3_MAXIMAL_GAS_LIMIT_MARGIN: u128 = 3328; impl BlockchainAgentWeb3 { pub fn new( - gas_price_gwei: u64, - gas_limit_const_part: u64, + gas_price_wei: u128, + gas_limit_const_part: u128, consuming_wallet: Wallet, consuming_wallet_balances: ConsumingWalletBalances, - pending_transaction_id: U256, ) -> Self { Self { - gas_price_gwei, + gas_price_wei, gas_limit_const_part, consuming_wallet, maximum_added_gas_margin: WEB3_MAXIMAL_GAS_LIMIT_MARGIN, consuming_wallet_balances, - pending_transaction_id, } } } @@ -76,7 +68,7 @@ mod tests { #[test] fn constants_are_correct() { - assert_eq!(WEB3_MAXIMAL_GAS_LIMIT_MARGIN, 3328) + assert_eq!(WEB3_MAXIMAL_GAS_LIMIT_MARGIN, 3_328) } #[test] @@ -88,14 +80,12 @@ mod tests { transaction_fee_balance_in_minor_units: U256::from(456_789), masq_token_balance_in_minor_units: U256::from(123_000_000), }; - let pending_transaction_id = U256::from(777); let subject = BlockchainAgentWeb3::new( gas_price_gwei, gas_limit_const_part, consuming_wallet.clone(), consuming_wallet_balances, - pending_transaction_id, ); assert_eq!(subject.agreed_fee_per_computation_unit(), gas_price_gwei); @@ -104,7 +94,6 @@ mod tests { subject.consuming_wallet_balances(), consuming_wallet_balances ); - assert_eq!(subject.pending_transaction_id(), pending_transaction_id) } #[test] @@ -114,14 +103,8 @@ mod tests { transaction_fee_balance_in_minor_units: Default::default(), masq_token_balance_in_minor_units: Default::default(), }; - let nonce = U256::from(55); - let agent = BlockchainAgentWeb3::new( - 444, - 77_777, - consuming_wallet, - consuming_wallet_balances, - nonce, - ); + let agent = + BlockchainAgentWeb3::new(444, 77_777, consuming_wallet, consuming_wallet_balances); let result = agent.estimated_transaction_fee_total(3); diff --git a/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/blockchain_agent.rs b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/blockchain_agent.rs index 3ded6a16b..70918ed77 100644 --- a/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/blockchain_agent.rs +++ b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/blockchain_agent.rs @@ -3,7 +3,6 @@ use crate::arbitrary_id_stamp_in_trait; use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; use crate::sub_lib::wallet::Wallet; -use web3::types::U256; // Table of chains by // @@ -24,9 +23,8 @@ use web3::types::U256; pub trait BlockchainAgent: Send { fn estimated_transaction_fee_total(&self, number_of_transactions: usize) -> u128; fn consuming_wallet_balances(&self) -> ConsumingWalletBalances; - fn agreed_fee_per_computation_unit(&self) -> u64; + fn agreed_fee_per_computation_unit(&self) -> u128; fn consuming_wallet(&self) -> &Wallet; - fn pending_transaction_id(&self) -> U256; #[cfg(test)] fn dup(&self) -> Box { diff --git a/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/test_utils.rs b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/test_utils.rs index 5de2a6b06..ee1706b36 100644 --- a/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/test_utils.rs +++ b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/test_utils.rs @@ -7,15 +7,13 @@ use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; use crate::sub_lib::wallet::Wallet; use crate::test_utils::unshared_test_utils::arbitrary_id_stamp::ArbitraryIdStamp; use crate::{arbitrary_id_stamp_in_trait_impl, set_arbitrary_id_stamp_in_mock_impl}; -use ethereum_types::U256; use std::cell::RefCell; #[derive(Default)] pub struct BlockchainAgentMock { consuming_wallet_balances_results: RefCell>, - agreed_fee_per_computation_unit_results: RefCell>, + agreed_fee_per_computation_unit_results: RefCell>, consuming_wallet_result_opt: Option, - pending_transaction_id_results: RefCell>, arbitrary_id_stamp_opt: Option, } @@ -28,7 +26,7 @@ impl BlockchainAgent for BlockchainAgentMock { todo!("to be implemented by GH-711") } - fn agreed_fee_per_computation_unit(&self) -> u64 { + fn agreed_fee_per_computation_unit(&self) -> u128 { self.agreed_fee_per_computation_unit_results .borrow_mut() .remove(0) @@ -38,10 +36,6 @@ impl BlockchainAgent for BlockchainAgentMock { self.consuming_wallet_result_opt.as_ref().unwrap() } - fn pending_transaction_id(&self) -> U256 { - self.pending_transaction_id_results.borrow_mut().remove(0) - } - fn dup(&self) -> Box { intentionally_blank!() } @@ -57,7 +51,7 @@ impl BlockchainAgentMock { self } - pub fn agreed_fee_per_computation_unit_result(self, result: u64) -> Self { + pub fn agreed_fee_per_computation_unit_result(self, result: u128) -> Self { self.agreed_fee_per_computation_unit_results .borrow_mut() .push(result); @@ -69,12 +63,5 @@ impl BlockchainAgentMock { self } - pub fn pending_transaction_id_result(self, result: U256) -> Self { - self.pending_transaction_id_results - .borrow_mut() - .push(result); - self - } - set_arbitrary_id_stamp_in_mock_impl!(); } diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index 4a37d3385..52fea5877 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -18,11 +18,11 @@ use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{ PayableThresholdsGaugeReal, PayableTransactingErrorEnum, PendingPayableMetadata, }; use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{ - elapsed_in_ms, handle_none_status, handle_status_with_failure, handle_status_with_success, + elapsed_in_ms, handle_status_with_failure, handle_status_with_success, PendingPayableScanReport, }; use crate::accountant::scanners::scanners_utils::receivable_scanner_utils::balance_and_age; -use crate::accountant::{PaymentsAndStartBlock, PendingPayableId, ReceivedPaymentsError}; +use crate::accountant::{PaymentsAndStartBlock, PendingPayableId}; use crate::accountant::{ comma_joined_stringifiable, gwei_to_wei, Accountant, ReceivedPayments, ReportTransactionReceipts, RequestTransactionReceipts, ResponseSkeleton, ScanForPayables, @@ -51,7 +51,7 @@ use std::rc::Rc; use std::time::{Duration, SystemTime}; use time::format_description::parse; use time::OffsetDateTime; -use web3::types::{TransactionReceipt, H256}; +use web3::types::H256; use masq_lib::type_obfuscation::Obfuscated; use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::{PreparedAdjustment, MultistagePayableScanner, SolvencySensitivePaymentInstructor}; use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::msgs::{BlockchainAgentWithContextMessage, QualifiedPayablesMessage}; @@ -497,7 +497,7 @@ impl PayableScanner { ) { if let Some(err) = err_opt { match err { - LocallyCausedError(PayableTransactionError::Sending {hashes, ..}) + LocallyCausedError(PayableTransactionError::Sending { hashes, .. }) | RemotelyCausedErrors(hashes) => { self.discard_failed_transactions_with_possible_fingerprints(hashes, logger) } @@ -654,6 +654,8 @@ impl PendingPayableScanner { msg: ReportTransactionReceipts, logger: &Logger, ) -> PendingPayableScanReport { + // TODO: We want to ensure that failed transactions are not marked still pending, + // and also adjust log levels accordingly. fn handle_none_receipt( mut scan_report: PendingPayableScanReport, payable: PendingPayableFingerprint, @@ -675,12 +677,12 @@ impl PendingPayableScanner { msg.fingerprints_with_receipts.into_iter().fold( scan_report, |scan_report_so_far, (receipt_result, fingerprint)| match receipt_result { - TransactionReceiptResult::Found(receipt) => self.interpret_transaction_receipt( - scan_report_so_far, - &receipt, - fingerprint, - logger, - ), + TransactionReceiptResult::Found(_receipt) => { + handle_status_with_success(scan_report_so_far, fingerprint, logger) + } + TransactionReceiptResult::TransactionFailed(_receipt) => { + handle_status_with_failure(scan_report_so_far, fingerprint, logger) + } TransactionReceiptResult::NotPresent => handle_none_receipt( scan_report_so_far, fingerprint, @@ -697,41 +699,6 @@ impl PendingPayableScanner { ) } - fn interpret_transaction_receipt( - &self, - scan_report: PendingPayableScanReport, - receipt: &TransactionReceipt, - fingerprint: PendingPayableFingerprint, - logger: &Logger, - ) -> PendingPayableScanReport { - const WEB3_SUCCESS: u64 = 1; - const WEB3_FAILURE: u64 = 0; - - match receipt.status { - None => handle_none_status( - scan_report, - fingerprint, - self.when_pending_too_long_sec, - logger, - ), - Some(status_code) => { - let code = status_code.as_u64(); - //TODO: failures handling is going to need enhancement suggested by GH-693 - if code == WEB3_FAILURE { - handle_status_with_failure(scan_report, fingerprint, logger) - } else if code == WEB3_SUCCESS { - handle_status_with_success(scan_report, fingerprint, logger) - } else { - unreachable!( - "tx receipt for pending {:?}: status code other than 0 or 1 \ - shouldn't be possible, but was {}", - fingerprint.hash, code - ) - } - } - } - } - fn process_transactions_by_reported_state( &mut self, scan_report: PendingPayableScanReport, @@ -866,33 +833,7 @@ impl Scanner for ReceivableScanner { } fn finish_scan(&mut self, msg: ReceivedPayments, logger: &Logger) -> Option { - match msg.scan_result { - Ok(payments_and_start_block) => { - self.handle_new_received_payments(&payments_and_start_block, msg.timestamp, logger); - } - Err(e) => match e { - ReceivedPaymentsError::ExceededBlockScanLimit(max_block_count) => { - debug!(logger, "Writing max_block_count({})", max_block_count); - self.persistent_configuration - .set_max_block_count(Some(max_block_count)) - .map_or_else(|_| { - warning!(logger, "{:?} update max_block_count to {}. Scheduling next scan with that limit.", e, max_block_count); - }, - |e| { - warning!(logger, "Writing max_block_count failed: {:?}", e); - }, - ) - } - ReceivedPaymentsError::OtherRPCError(rpc_error) => { - warning!( - logger, - "Attempted to retrieve received payments but failed: {:?}", - rpc_error - ); - } - }, - } - + self.handle_new_received_payments(&msg.payments_and_start_block, msg.timestamp, logger); self.mark_as_ended(logger); msg.response_skeleton_opt .map(|response_skeleton| NodeToUiMessage { @@ -946,7 +887,6 @@ impl ReceivableScanner { new_start_block, e ), } - debug!(logger, "Updated start block to: {}", new_start_block) } else { let mut txn = self .receivable_dao @@ -1148,7 +1088,7 @@ mod tests { use crate::accountant::db_access_objects::utils::{from_time_t, to_time_t}; use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::msgs::QualifiedPayablesMessage; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PendingPayableMetadata; - use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::PendingPayableScanReport; + use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_none_status, handle_status_with_failure, PendingPayableScanReport}; use crate::accountant::scanners::test_utils::{ make_empty_payments_and_start_block, protect_payables_in_test, }; @@ -1174,7 +1114,7 @@ mod tests { use crate::database::rusqlite_wrappers::TransactionSafeWrapper; use crate::database::test_utils::transaction_wrapper_mock::TransactionInnerWrapperMockBuilder; use crate::db_config::mocks::ConfigDaoMock; - use crate::db_config::persistent_configuration::PersistentConfigError; + use crate::db_config::persistent_configuration::{PersistentConfigError}; use crate::sub_lib::accountant::{ DaoFactories, FinancialStatistics, PaymentThresholds, ScanIntervals, DEFAULT_PAYMENT_THRESHOLDS, @@ -1863,7 +1803,7 @@ mod tests { &format!("WARN: {test_name}: \ Deleting fingerprints for failed transactions 0x00000000000000000000000000000000000000000000000000000000000015b3, \ 0x0000000000000000000000000000000000000000000000000000000000003039", - )); + )); // we haven't supplied any result for mark_pending_payable() and so it's proved uncalled } @@ -2371,11 +2311,7 @@ mod tests { hash: H256, ) -> PendingPayableScanReport { init_test_logging(); - let tx_receipt = TransactionReceipt::default(); //status defaulted to None let when_sent = SystemTime::now().sub(Duration::from_secs(pending_payable_age_sec)); - let subject = PendingPayableScannerBuilder::new() - .when_pending_too_long_sec(when_pending_too_long_sec) - .build(); let fingerprint = PendingPayableFingerprint { rowid, timestamp: when_sent, @@ -2387,7 +2323,7 @@ mod tests { let logger = Logger::new(test_name); let scan_report = PendingPayableScanReport::default(); - subject.interpret_transaction_receipt(scan_report, &tx_receipt, fingerprint, &logger) + handle_none_status(scan_report, fingerprint, when_pending_too_long_sec, &logger) } fn assert_log_msg_and_elapsed_time_in_log_makes_sense( @@ -2451,7 +2387,7 @@ mod tests { 00000000000000000000000237 has exceeded the maximum pending time \\({}sec\\) with the age \ \\d+sec and the confirmation process is going to be aborted now at the final attempt 1; manual \ resolution is required from the user to complete the transaction" - ,test_name, DEFAULT_PENDING_TOO_LONG_SEC, ),elapsed_after,capture_regex) + , test_name, DEFAULT_PENDING_TOO_LONG_SEC, ), elapsed_after, capture_regex) } #[test] @@ -2479,7 +2415,7 @@ mod tests { } ); let capture_regex = r#"\s(\d+)ms"#; - assert_log_msg_and_elapsed_time_in_log_makes_sense (&format!( + assert_log_msg_and_elapsed_time_in_log_makes_sense(&format!( "INFO: {test_name}: Pending transaction 0x0000000000000000000000000000000000000000000000000\ 00000000000007b couldn't be confirmed at attempt 1 at \\d+ms after its sending"), elapsed_after_ms, capture_regex); } @@ -2515,29 +2451,10 @@ mod tests { ), elapsed_after_ms, capture_regex); } - #[test] - #[should_panic( - expected = "tx receipt for pending 0x000000000000000000000000000000000000000000000000000000000000007b: \ - status code other than 0 or 1 shouldn't be possible, but was 456" - )] - fn interpret_transaction_receipt_panics_at_undefined_status_code() { - let mut tx_receipt = TransactionReceipt::default(); - tx_receipt.status = Some(U64::from(456)); - let mut fingerprint = make_pending_payable_fingerprint(); - fingerprint.hash = make_tx_hash(0x7b); - let subject = PendingPayableScannerBuilder::new().build(); - let scan_report = PendingPayableScanReport::default(); - let logger = Logger::new("test"); - - let _ = - subject.interpret_transaction_receipt(scan_report, &tx_receipt, fingerprint, &logger); - } - #[test] fn interpret_transaction_receipt_when_transaction_status_is_a_failure() { init_test_logging(); let test_name = "interpret_transaction_receipt_when_transaction_status_is_a_failure"; - let subject = PendingPayableScannerBuilder::new().build(); let mut tx_receipt = TransactionReceipt::default(); tx_receipt.status = Some(U64::from(0)); //failure let hash = make_tx_hash(0xd7); @@ -2552,8 +2469,7 @@ mod tests { let logger = Logger::new(test_name); let scan_report = PendingPayableScanReport::default(); - let result = - subject.interpret_transaction_receipt(scan_report, &tx_receipt, fingerprint, &logger); + let result = handle_status_with_failure(scan_report, fingerprint, &logger); assert_eq!( result, @@ -3125,7 +3041,7 @@ mod tests { #[test] fn receivable_scanner_handles_no_new_payments_found() { init_test_logging(); - let test_name = "receivable_scanner_aborts_scan_if_no_payments_were_supplied"; + let test_name = "receivable_scanner_handles_no_new_payments_found"; let set_start_block_params_arc = Arc::new(Mutex::new(vec![])); let new_start_block = 4321; let persistent_config = PersistentConfigurationMock::new() @@ -3134,11 +3050,11 @@ mod tests { let mut subject = ReceivableScannerBuilder::new() .persistent_configuration(persistent_config) .build(); - let mut scan_result = make_empty_payments_and_start_block(); - scan_result.new_start_block = new_start_block; + let mut payments_and_start_block = make_empty_payments_and_start_block(); + payments_and_start_block.new_start_block = new_start_block; let msg = ReceivedPayments { timestamp: SystemTime::now(), - scan_result: Ok(scan_result), + payments_and_start_block, response_skeleton_opt: None, }; @@ -3165,11 +3081,11 @@ mod tests { let mut subject = ReceivableScannerBuilder::new() .persistent_configuration(persistent_config) .build(); - let mut scan_result = make_empty_payments_and_start_block(); - scan_result.new_start_block = 6709; + let mut payments_and_start_block = make_empty_payments_and_start_block(); + payments_and_start_block.new_start_block = 6709; let msg = ReceivedPayments { timestamp: now, - scan_result: Ok(scan_result), + payments_and_start_block, response_skeleton_opt: None, }; @@ -3218,12 +3134,12 @@ mod tests { wei_amount: 3_333_345, }, ]; - let mut scan_result = make_empty_payments_and_start_block(); - scan_result.new_start_block = 7890123; - scan_result.payments = receivables.clone(); + let mut payments_and_start_block = make_empty_payments_and_start_block(); + payments_and_start_block.new_start_block = 7890123; + payments_and_start_block.payments = receivables.clone(); let msg = ReceivedPayments { timestamp: now, - scan_result: Ok(scan_result), + payments_and_start_block, response_skeleton_opt: None, }; subject.mark_as_started(SystemTime::now()); @@ -3275,10 +3191,10 @@ mod tests { }]; let msg = ReceivedPayments { timestamp: now, - scan_result: Ok(PaymentsAndStartBlock { + payments_and_start_block: PaymentsAndStartBlock { payments: receivables, new_start_block: 7890123, - }), + }, response_skeleton_opt: None, }; // Not necessary, rather for preciseness @@ -3318,11 +3234,11 @@ mod tests { from: make_wallet("abc"), wei_amount: 45_780, }]; - let mut scan_result = make_empty_payments_and_start_block(); - scan_result.payments = receivables; + let mut payments_and_start_block = make_empty_payments_and_start_block(); + payments_and_start_block.payments = receivables; let msg = ReceivedPayments { timestamp: now, - scan_result: Ok(scan_result), + payments_and_start_block, response_skeleton_opt: None, }; // Not necessary, rather for preciseness diff --git a/node/src/accountant/scanners/scanners_utils.rs b/node/src/accountant/scanners/scanners_utils.rs index 429a36b05..1876ca58d 100644 --- a/node/src/accountant/scanners/scanners_utils.rs +++ b/node/src/accountant/scanners/scanners_utils.rs @@ -383,6 +383,7 @@ pub mod pending_payable_scanner_utils { scan_report } + //TODO: failures handling is going to need enhancement suggested by GH-693 pub fn handle_status_with_failure( mut scan_report: PendingPayableScanReport, fingerprint: PendingPayableFingerprint, diff --git a/node/src/actor_system_factory.rs b/node/src/actor_system_factory.rs index d85104e21..70f00c92b 100644 --- a/node/src/actor_system_factory.rs +++ b/node/src/actor_system_factory.rs @@ -308,7 +308,7 @@ impl ActorSystemFactoryToolsReal { AutomapChange::NewIp(new_public_ip) => { exit_process( 1, - format! ("IP change to {} reported from ISP. We can't handle that until GH-499. Going down...", new_public_ip).as_str() + format!("IP change to {} reported from ISP. We can't handle that until GH-499. Going down...", new_public_ip).as_str(), ); } AutomapChange::Error(e) => Self::handle_housekeeping_thread_error(e), @@ -635,8 +635,11 @@ where mod tests { use super::*; use crate::accountant::exportable_test_parts::test_accountant_is_constructed_with_upgraded_db_connection_recognizing_our_extra_sqlite_functions; - use crate::accountant::{ReceivedPayments, DEFAULT_PENDING_TOO_LONG_SEC}; + use crate::accountant::{ + PaymentsAndStartBlock, ReceivedPayments, DEFAULT_PENDING_TOO_LONG_SEC, + }; use crate::blockchain::blockchain_bridge::RetrieveTransactions; + use crate::blockchain::blockchain_interface::data_structures::BlockchainTransaction; use crate::bootstrapper::{Bootstrapper, RealUser}; use crate::db_config::persistent_configuration::PersistentConfigurationReal; use crate::node_test_utils::{ @@ -654,6 +657,7 @@ mod tests { use crate::sub_lib::peer_actors::StartMessage; use crate::sub_lib::stream_handler_pool::TransmitDataMsg; use crate::sub_lib::ui_gateway::UiGatewayConfig; + use crate::sub_lib::wallet::Wallet; use crate::test_utils::actor_system_factory::BannedCacheLoaderMock; use crate::test_utils::automap_mocks::{AutomapControlFactoryMock, AutomapControlMock}; use crate::test_utils::make_wallet; @@ -1257,7 +1261,7 @@ mod tests { earning_wallet: make_wallet("earning"), consuming_wallet_opt: Some(make_wallet("consuming")), data_directory: PathBuf::new(), - node_descriptor: NodeDescriptor::try_from ((main_cryptde(), "masq://polygon-mainnet:OHsC2CAm4rmfCkaFfiynwxflUgVTJRb2oY5mWxNCQkY@172.50.48.6:9342")).unwrap(), + node_descriptor: NodeDescriptor::try_from((main_cryptde(), "masq://polygon-mainnet:OHsC2CAm4rmfCkaFfiynwxflUgVTJRb2oY5mWxNCQkY@172.50.48.6:9342")).unwrap(), main_cryptde_null_opt: None, alias_cryptde_null_opt: None, mapping_protocol_opt: Some(AutomapProtocol::Igdp), @@ -1271,7 +1275,7 @@ mod tests { min_hops: MIN_HOPS_FOR_TEST, }, payment_thresholds_opt: Default::default(), - when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC + when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, }; let add_mapping_params_arc = Arc::new(Mutex::new(vec![])); let mut subject = make_subject_with_null_setter(); @@ -1358,7 +1362,7 @@ mod tests { let dispatcher_param = Parameters::get(parameters.dispatcher_params); assert_eq!( dispatcher_param.node_descriptor, - NodeDescriptor::try_from ((main_cryptde(), "masq://polygon-mainnet:OHsC2CAm4rmfCkaFfiynwxflUgVTJRb2oY5mWxNCQkY@172.50.48.6:9342")).unwrap() + NodeDescriptor::try_from((main_cryptde(), "masq://polygon-mainnet:OHsC2CAm4rmfCkaFfiynwxflUgVTJRb2oY5mWxNCQkY@172.50.48.6:9342")).unwrap() ); let blockchain_bridge_param = Parameters::get(parameters.blockchain_bridge_params); assert_eq!( @@ -1570,7 +1574,7 @@ mod tests { min_hops: MIN_HOPS_FOR_TEST, }, payment_thresholds_opt: Default::default(), - when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC + when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, }; let system = System::new("MASQNode"); let mut subject = make_subject_with_null_setter(); @@ -2063,14 +2067,24 @@ mod tests { .unwrap(); blockchain_bridge_addr .try_send(RetrieveTransactions { - recipient: wallet, + recipient: wallet.clone(), response_skeleton_opt: None, }) .unwrap(); assert_eq!(system.run(), 0); let recording = accountant_recording.lock().unwrap(); let received_payments_message = recording.get_record::(0); - assert!(received_payments_message.scan_result.is_ok()); + assert_eq!( + received_payments_message.payments_and_start_block, + PaymentsAndStartBlock { + payments: vec![BlockchainTransaction { + block_number: 2000, + from: Wallet::new("0x0000000000006561726e696e675f77616c6c6574"), + wei_amount: 996000000 + }], + new_start_block: 1000000000 + } + ); } #[test] diff --git a/node/src/blockchain/batch_web3.rs b/node/src/blockchain/batch_web3.rs deleted file mode 100644 index 0487be8de..000000000 --- a/node/src/blockchain/batch_web3.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub trait BatchWeb3 { - fn submit_batch(&self) {} -} diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index ae89f1b79..e32e5203b 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -4,7 +4,7 @@ use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::msgs::{ BlockchainAgentWithContextMessage, QualifiedPayablesMessage, }; use crate::accountant::{ - PaymentsAndStartBlock, ReceivedPayments, ReceivedPaymentsError, ResponseSkeleton, ScanError, + PaymentsAndStartBlock, ReceivedPayments, ResponseSkeleton, ScanError, SentPayables, SkeletonOptHolder, }; use crate::accountant::{ReportTransactionReceipts, RequestTransactionReceipts}; @@ -16,7 +16,6 @@ use crate::blockchain::blockchain_interface::data_structures::errors::{ use crate::blockchain::blockchain_interface::data_structures::ProcessedPayableFallible; use crate::blockchain::blockchain_interface::BlockchainInterface; use crate::blockchain::blockchain_interface_initializer::BlockchainInterfaceInitializer; -use crate::blockchain::blockchain_interface_utils::calculate_fallback_start_block_number; use crate::database::db_initializer::{DbInitializationConfig, DbInitializer, DbInitializerReal}; use crate::db_config::config_dao::ConfigDaoReal; use crate::db_config::persistent_configuration::{ @@ -42,6 +41,7 @@ use masq_lib::ui_gateway::NodeFromUiMessage; use regex::Regex; use std::path::Path; use std::string::ToString; +use std::sync::{Arc, Mutex}; use std::time::SystemTime; use ethabi::Hash; use web3::types::{BlockNumber, H256}; @@ -55,7 +55,7 @@ pub const DEFAULT_BLOCKCHAIN_SERVICE_URL: &str = "https://0.0.0.0"; pub struct BlockchainBridge { blockchain_interface: Box, logger: Logger, - persistent_config: Box, + persistent_config_arc: Arc>, sent_payable_subs_opt: Option>, payable_payments_setup_subs_opt: Option>, received_payments_subs_opt: Option>, @@ -182,12 +182,12 @@ impl Handler for BlockchainBridge { impl BlockchainBridge { pub fn new( blockchain_interface: Box, - persistent_config: Box, + persistent_config: Arc>, crashable: bool, ) -> BlockchainBridge { BlockchainBridge { blockchain_interface, - persistent_config, + persistent_config_arc: persistent_config, sent_payable_subs_opt: None, payable_payments_setup_subs_opt: None, received_payments_subs_opt: None, @@ -203,13 +203,13 @@ impl BlockchainBridge { pub fn initialize_persistent_configuration( data_directory: &Path, - ) -> Box { + ) -> Arc> { let config_dao = Box::new(ConfigDaoReal::new( DbInitializerReal::default() .initialize(data_directory, DbInitializationConfig::panic_on_migration()) .unwrap_or_else(|err| db_connection_launch_panic(err, data_directory)), )); - Box::new(PersistentConfigurationReal::new(config_dao)) + Arc::new(Mutex::new(PersistentConfigurationReal::new(config_dao))) } pub fn initialize_blockchain_interface( @@ -249,7 +249,7 @@ impl BlockchainBridge { ) -> Box> { // TODO rewrite this into a batch call as soon as GH-629 gets into master let accountant_recipient = self.payable_payments_setup_subs_opt.clone(); - return Box::new( + Box::new( self.blockchain_interface .build_blockchain_agent(incoming_message.consuming_wallet) .map_err(|e| format!("Blockchain agent build error: {:?}", e)) @@ -265,7 +265,7 @@ impl BlockchainBridge { .expect("Accountant is dead"); Ok(()) }), - ); + ) } fn handle_outbound_payments_instructions( @@ -284,7 +284,7 @@ impl BlockchainBridge { }; let send_message_if_successful = send_message_if_failure.clone(); - return Box::new( + Box::new( self.process_payments(msg.agent, msg.affordable_accounts) .map_err(move |e: PayableTransactionError| { send_message_if_failure(SentPayables { @@ -300,61 +300,72 @@ impl BlockchainBridge { }); Ok(()) }), - ); + ) } fn handle_retrieve_transactions( &mut self, msg: RetrieveTransactions, ) -> Box> { - let start_block_nbr = match self.persistent_config.start_block() { - Ok (sb) => sb, - Err (e) => panic! ("Cannot retrieve start block from database; payments to you may not be processed: {:?}", e) + let (start_block_nbr, max_block_count) = { + let persistent_config_lock = self + .persistent_config_arc + .lock() + .expect("Unable to lock persistent config in BlockchainBridge"); + + let start_block_nbr = match persistent_config_lock.start_block() { + Ok(sb) => sb, + Err(e) => panic!("Cannot retrieve start block from database; payments to you may not be processed: {:?}", e) + }; + let max_block_count = match persistent_config_lock.max_block_count() { + Ok(Some(mbc)) => mbc, + _ => u64::MAX, }; - let max_block_count = match self.persistent_config.max_block_count() { - Ok(Some(mbc)) => mbc, - _ => u64::MAX, + (start_block_nbr, max_block_count) }; - let fallback_start_block_number = - calculate_fallback_start_block_number(start_block_nbr, max_block_count); + let logger = self.logger.clone(); + let fallback_next_start_block_number = + Self::calculate_fallback_start_block_number(start_block_nbr, max_block_count); let start_block = BlockNumber::Number(start_block_nbr.into()); - let received_payments_subs_ok_case = self + let received_payments_subs = self .received_payments_subs_opt .as_ref() .expect("Accountant is unbound") .clone(); - let received_payments_subs_error_case = received_payments_subs_ok_case.clone(); + let persistent_config_arc = self.persistent_config_arc.clone(); Box::new( self.blockchain_interface .retrieve_transactions( start_block, - fallback_start_block_number, + fallback_next_start_block_number, msg.recipient.address(), ) .map_err(move |e| { - let received_payments_error = - match BlockchainBridge::extract_max_block_count(e.clone()) { - Some(max_block_count) => { - ReceivedPaymentsError::ExceededBlockScanLimit(max_block_count) + if let Some(max_block_count) = + BlockchainBridge::extract_max_block_count(e.clone()) + { + match persistent_config_arc + .lock() + .expect("Unable to lock persistent config in BlockchainBridge") + .set_max_block_count(Some(max_block_count)) + { + Ok(()) => { + debug!( + logger, + "Updated max_block_count to {} in database.", max_block_count + ); } - None => ReceivedPaymentsError::OtherRPCError(format!( - "Attempted to retrieve received payments but failed: {:?}", - e - )), - }; - received_payments_subs_error_case - .try_send(ReceivedPayments { - timestamp: SystemTime::now(), - scan_result: Err(received_payments_error.clone()), - response_skeleton_opt: msg.response_skeleton_opt, - }) - .expect("Accountant is dead."); - format!( - "Error while retrieving transactions: {:?}", - received_payments_error - ) + Err(e) => { + panic!( + "Attempt to set new max block to {} failed due to: {:?}", + max_block_count, e + ) + } + } + } + format!("Error while retrieving transactions: {:?}", e) }) .and_then(move |transactions| { let payments_and_start_block = PaymentsAndStartBlock { @@ -362,10 +373,10 @@ impl BlockchainBridge { new_start_block: transactions.new_start_block, }; - received_payments_subs_ok_case + received_payments_subs .try_send(ReceivedPayments { timestamp: SystemTime::now(), - scan_result: Ok(payments_and_start_block), + payments_and_start_block, response_skeleton_opt: msg.response_skeleton_opt, }) .expect("Accountant is dead."); @@ -378,6 +389,7 @@ impl BlockchainBridge { &mut self, msg: RequestTransactionReceipts, ) -> Box> { + let logger = self.logger.clone(); let accountant_recipient = self .pending_payable_confirmation .report_transaction_receipts_sub_opt @@ -392,8 +404,7 @@ impl BlockchainBridge { Box::new( self.blockchain_interface - .lower_interface() - .get_transaction_receipt_batch(transaction_hashes) + .process_transaction_receipts(transaction_hashes) .map_err(move |e| e.to_string()) .and_then(move |transaction_receipts_results| { let length = transaction_receipts_results.len(); @@ -414,11 +425,12 @@ impl BlockchainBridge { }) .expect("Accountant is dead"); if length != transactions_found { - return Err(format!( + debug!( + logger, "Aborting scanning; {} transactions succeed and {} transactions failed", transactions_found, length - transactions_found - )); + ); }; Ok(()) }), @@ -435,7 +447,6 @@ impl BlockchainBridge { let scan_error_subs_opt = self.scan_error_subs_opt.clone(); let future = handler(self, msg).map_err(move |e| { warning!(logger, "{}", e); - // TODO: This ScanError needs to be removed, And added into OutboundPaymentsInstructions & QualifiedPayablesMessage scan_error_subs_opt .as_ref() .expect("Accountant not bound") @@ -450,6 +461,14 @@ impl BlockchainBridge { actix::spawn(future); } + fn calculate_fallback_start_block_number(start_block_number: u64, max_block_count: u64) -> u64 { + if max_block_count == u64::MAX { + start_block_number + 1u64 + } else { + start_block_number + max_block_count + } + } + fn process_payments( &self, agent: Box, @@ -459,15 +478,13 @@ impl BlockchainBridge { let new_fingerprints_recipient = self.new_fingerprints_recipient(); let logger = self.logger.clone(); let chain = self.blockchain_interface.get_chain(); - self.blockchain_interface - .lower_interface() - .submit_payables_in_batch( - logger, - chain, - agent.consuming_wallet().clone(), - new_fingerprints_recipient, - affordable_accounts, - ) + self.blockchain_interface.submit_payables_in_batch( + logger, + chain, + agent, + new_fingerprints_recipient, + affordable_accounts, + ) } fn new_fingerprints_recipient(&self) -> Recipient { @@ -519,21 +536,28 @@ impl SubsFactory for BlockchainBridgeSub mod tests { use super::*; use crate::accountant::db_access_objects::payable_dao::PayableAccount; + use crate::accountant::db_access_objects::pending_payable_dao::PendingPayable; use crate::accountant::db_access_objects::utils::from_time_t; + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::test_utils::BlockchainAgentMock; use crate::accountant::scanners::test_utils::{ make_empty_payments_and_start_block, protect_payables_in_test, }; use crate::accountant::test_utils::{make_payable_account, make_pending_payable_fingerprint}; + use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError::TransactionID; use crate::blockchain::blockchain_interface::data_structures::errors::{ BlockchainAgentBuildError, PayableTransactionError, }; + use crate::blockchain::blockchain_interface::data_structures::ProcessedPayableFallible::Correct; use crate::blockchain::blockchain_interface::data_structures::{ BlockchainTransaction, RetrievedBlockchainTransactions, }; - use crate::blockchain::test_utils::{make_tx_hash, make_blockchain_interface_web3, BlockchainInterfaceMock, ReceiptResponseBuilder}; + use crate::blockchain::test_utils::{ + make_blockchain_interface_web3, make_tx_hash, ReceiptResponseBuilder, + }; use crate::db_config::persistent_configuration::PersistentConfigError; use crate::match_every_type_id; use crate::node_test_utils::check_timestamp; + use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMock; use crate::test_utils::recorder::{ make_accountant_subs_from_recorder, make_recorder, peer_actors_builder, @@ -541,28 +565,27 @@ mod tests { use crate::test_utils::recorder_stop_conditions::StopCondition; use crate::test_utils::recorder_stop_conditions::StopConditions; use crate::test_utils::unshared_test_utils::arbitrary_id_stamp::ArbitraryIdStamp; - use crate::test_utils::unshared_test_utils::{assert_on_initialization_with_panic_on_migration, prove_that_crash_request_handler_is_hooked_up, AssertionsMessage, configure_default_persistent_config, ZERO}; + use crate::test_utils::unshared_test_utils::{ + assert_on_initialization_with_panic_on_migration, configure_default_persistent_config, + prove_that_crash_request_handler_is_hooked_up, AssertionsMessage, ZERO, + }; use crate::test_utils::{make_paying_wallet, make_wallet}; use actix::System; - use ethereum_types::{U64}; + use ethereum_types::U64; use masq_lib::messages::ScanType; use masq_lib::test_utils::logging::init_test_logging; use masq_lib::test_utils::logging::TestLogHandler; - use masq_lib::test_utils::utils::{ensure_node_home_directory_exists, LogObject, TEST_DEFAULT_CHAIN}; + use masq_lib::test_utils::mock_blockchain_client_server::MBCSBuilder; + use masq_lib::test_utils::utils::{ + ensure_node_home_directory_exists, LogObject, TEST_DEFAULT_CHAIN, + }; use masq_lib::utils::find_free_port; use std::any::TypeId; use std::path::Path; use std::str::FromStr; use std::sync::{Arc, Mutex}; use std::time::{Duration, SystemTime}; - use web3::types::{BlockNumber, TransactionReceipt, H160}; - use masq_lib::test_utils::mock_blockchain_client_server::MBCSBuilder; - use crate::accountant::db_access_objects::pending_payable_dao::PendingPayable; - use crate::accountant::ReceivedPaymentsError::OtherRPCError; - use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::test_utils::BlockchainAgentMock; - use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError::{GasPriceQueryFailed, TransactionID}; - use crate::blockchain::blockchain_interface::data_structures::ProcessedPayableFallible::Correct; - use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; + use web3::types::{TransactionReceipt, H160}; impl Handler> for BlockchainBridge { type Result = (); @@ -583,7 +606,7 @@ mod tests { } fn stub_bi() -> Box { - Box::new(make_blockchain_interface_web3(None)) + Box::new(make_blockchain_interface_web3(find_free_port())) } #[test] @@ -591,7 +614,7 @@ mod tests { init_test_logging(); let subject = BlockchainBridge::new( stub_bi(), - Box::new(configure_default_persistent_config(ZERO)), + Arc::new(Mutex::new(configure_default_persistent_config(ZERO))), false, ); let system = System::new("blockchain_bridge_receives_bind_message"); @@ -646,7 +669,7 @@ mod tests { fn qualified_payables_msg_is_handled_and_new_msg_with_an_added_blockchain_agent_returns_to_accountant( ) { let system = System::new( - "qualified_payables_msg_is_handled_and_new_msg_with_an_added_blockchain_agent_returns_to_accountant", + "qualified_payables_msg_is_handled_and_new_msg_with_an_added_blockchain_agent_returns_to_accountant", ); let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port) @@ -656,11 +679,10 @@ mod tests { "0x000000000000000000000000000000000000000000000000000000000000FFFF".to_string(), 0, ) - .response("0x23".to_string(), 1) .start(); let (accountant, _, accountant_recording_arc) = make_recorder(); let accountant_recipient = accountant.start().recipient(); - let blockchain_interface = make_blockchain_interface_web3(Some(port)); + let blockchain_interface = make_blockchain_interface_web3(port); let consuming_wallet = make_paying_wallet(b"somewallet"); let persistent_configuration = PersistentConfigurationMock::default(); let wallet_1 = make_wallet("booga"); @@ -685,7 +707,7 @@ mod tests { ]; let mut subject = BlockchainBridge::new( Box::new(blockchain_interface), - Box::new(persistent_configuration), + Arc::new(Mutex::new(persistent_configuration)), false, ); subject.payable_payments_setup_subs_opt = Some(accountant_recipient); @@ -720,17 +742,11 @@ mod tests { .consuming_wallet(), &consuming_wallet ); - assert_eq!( - blockchain_agent_with_context_msg_actual - .agent - .pending_transaction_id(), - 35.into() - ); assert_eq!( blockchain_agent_with_context_msg_actual .agent .agreed_fee_per_computation_unit(), - 10 + 9395240960 ); assert_eq!( blockchain_agent_with_context_msg_actual @@ -742,7 +758,7 @@ mod tests { blockchain_agent_with_context_msg_actual .agent .estimated_transaction_fee_total(1), - 733280 + 688_934_229_114_880 ); assert_eq!( blockchain_agent_with_context_msg_actual.response_skeleton_opt, @@ -759,22 +775,18 @@ mod tests { let system = System::new("qualified_payables_msg_is_handled_but_fails_on_build_blockchain_agent"); let port = find_free_port(); - // build blockchain agent fails by not providing the fourth response. + // build blockchain agent fails by not providing the third response. let _blockchain_client_server = MBCSBuilder::new(port) .response("0x23".to_string(), 1) .response("0x23".to_string(), 1) - .response( - "0x000000000000000000000000000000000000000000000000000000000000FFFF".to_string(), - 0, - ) .start(); let (accountant, _, accountant_recording_arc) = make_recorder(); let accountant_recipient = accountant.start().recipient(); - let blockchain_interface = make_blockchain_interface_web3(Some(port)); + let blockchain_interface = make_blockchain_interface_web3(port); let consuming_wallet = make_paying_wallet(b"somewallet"); let mut subject = BlockchainBridge::new( Box::new(blockchain_interface), - Box::new(PersistentConfigurationMock::default()), + Arc::new(Mutex::new(PersistentConfigurationMock::default())), false, ); subject.payable_payments_setup_subs_opt = Some(accountant_recipient); @@ -798,15 +810,18 @@ mod tests { let accountant_recording = accountant_recording_arc.lock().unwrap(); assert_eq!(accountant_recording.len(), 0); - let transaction_id_error = BlockchainAgentBuildError::TransactionID( + let service_fee_balance_error = BlockchainAgentBuildError::ServiceFeeBalance( consuming_wallet.address(), BlockchainError::QueryFailed( - "Transport error: Error(IncompleteMessage) for wallet 0xc4e2…3ac6".to_string(), + "Api error: Transport error: Error(IncompleteMessage)".to_string(), ), ); assert_eq!( error_msg, - format!("Blockchain agent build error: {:?}", transaction_id_error) + format!( + "Blockchain agent build error: {:?}", + service_fee_balance_error + ) ) } @@ -819,7 +834,6 @@ mod tests { let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port) .response("0x20".to_string(), 1) - .response("0x7B".to_string(), 1) .begin_batch() .response("rpc result".to_string(), 1) .end_batch() @@ -830,11 +844,11 @@ mod tests { .start(); let wallet_account = make_wallet("blah"); let consuming_wallet = make_paying_wallet(b"consuming_wallet"); - let blockchain_interface = make_blockchain_interface_web3(Some(port)); + let blockchain_interface = make_blockchain_interface_web3(port); let persistent_configuration_mock = PersistentConfigurationMock::default(); let subject = BlockchainBridge::new( Box::new(blockchain_interface), - Box::new(persistent_configuration_mock), + Arc::new(Mutex::new(persistent_configuration_mock)), false, ); let addr = subject.start(); @@ -850,6 +864,7 @@ mod tests { let agent_id_stamp = ArbitraryIdStamp::new(); let agent = BlockchainAgentMock::default() .set_arbitrary_id_stamp(agent_id_stamp) + .agreed_fee_per_computation_unit_result(123) .consuming_wallet_result(consuming_wallet); send_bind_message!(subject_subs, peer_actors); @@ -878,7 +893,7 @@ mod tests { payment_procedure_result: Ok(vec![Correct(PendingPayable { recipient_wallet: accounts[0].wallet.clone(), hash: H256::from_str( - "43d39b06f417183f925e1726d25c147cdb947dea3d437f898655b7dcb4d29fef" + "36e9d7cdd657181317dd461192d537d9944c57a51ee950607de5a618b00e57a1" ) .unwrap() })]), @@ -894,7 +909,7 @@ mod tests { pending_payable_fingerprint_seeds_msg.hashes_and_balances, vec![HashAndAmount { hash: H256::from_str( - "43d39b06f417183f925e1726d25c147cdb947dea3d437f898655b7dcb4d29fef" + "36e9d7cdd657181317dd461192d537d9944c57a51ee950607de5a618b00e57a1" ) .unwrap(), amount: accounts[0].balance_wei @@ -912,18 +927,17 @@ mod tests { // To make submit_batch failed we didn't provide any responses for batch calls let _blockchain_client_server = MBCSBuilder::new(port) .response("0x20".to_string(), 1) - .response("0x7B".to_string(), 1) .start(); let (accountant, _, accountant_recording_arc) = make_recorder(); let accountant_addr = accountant .system_stop_conditions(match_every_type_id!(SentPayables)) .start(); let wallet_account = make_wallet("blah"); - let blockchain_interface = make_blockchain_interface_web3(Some(port)); + let blockchain_interface = make_blockchain_interface_web3(port); let persistent_configuration_mock = PersistentConfigurationMock::default(); let subject = BlockchainBridge::new( Box::new(blockchain_interface), - Box::new(persistent_configuration_mock), + Arc::new(Mutex::new(persistent_configuration_mock)), false, ); let addr = subject.start(); @@ -937,7 +951,9 @@ mod tests { pending_payable_opt: None, }]; let consuming_wallet = make_paying_wallet(b"consuming_wallet"); - let agent = BlockchainAgentMock::default().consuming_wallet_result(consuming_wallet); + let agent = BlockchainAgentMock::default() + .consuming_wallet_result(consuming_wallet) + .agreed_fee_per_computation_unit_result(123); send_bind_message!(subject_subs, peer_actors); let _ = addr @@ -968,7 +984,7 @@ mod tests { pending_payable_fingerprint_seeds_msg.hashes_and_balances, vec![HashAndAmount { hash: H256::from_str( - "43d39b06f417183f925e1726d25c147cdb947dea3d437f898655b7dcb4d29fef" + "36e9d7cdd657181317dd461192d537d9944c57a51ee950607de5a618b00e57a1" ) .unwrap(), amount: accounts[0].balance_wei @@ -983,7 +999,7 @@ mod tests { context_id: 4321 }), msg: format!( - "ReportAccountsPayable: Sending phase: \"Transport error: Error(IncompleteMessage)\". Signed and hashed transactions: 0x43d39b06f417183f925e1726d25c147cdb947dea3d437f898655b7dcb4d29fef" + "ReportAccountsPayable: Sending phase: \"Transport error: Error(IncompleteMessage)\". Signed and hashed transactions: 0x36e9d7cdd657181317dd461192d537d9944c57a51ee950607de5a618b00e57a1" ) } ); @@ -995,25 +1011,26 @@ mod tests { let test_name = "process_payments_works"; let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port) - .response("0x01".to_string(), 1) .response("0x01".to_string(), 1) .begin_batch() .response("rpc_result".to_string(), 7) .response("rpc_result_2".to_string(), 7) .end_batch() .start(); - let blockchain_interface_web3 = make_blockchain_interface_web3(Some(port)); + let blockchain_interface_web3 = make_blockchain_interface_web3(port); let consuming_wallet = make_paying_wallet(b"consuming_wallet"); let accounts_1 = make_payable_account(1); let accounts_2 = make_payable_account(2); let accounts = vec![accounts_1.clone(), accounts_2.clone()]; let system = System::new(test_name); - let agent = BlockchainAgentMock::default().consuming_wallet_result(consuming_wallet); + let agent = BlockchainAgentMock::default() + .consuming_wallet_result(consuming_wallet) + .agreed_fee_per_computation_unit_result(1); let msg = OutboundPaymentsInstructions::new(accounts, Box::new(agent), None); let persistent_config = PersistentConfigurationMock::new(); let mut subject = BlockchainBridge::new( Box::new(blockchain_interface_web3), - Box::new(persistent_config), + Arc::new(Mutex::new(persistent_config)), false, ); let (accountant, _, accountant_recording) = make_recorder(); @@ -1033,7 +1050,7 @@ mod tests { Correct(PendingPayable { recipient_wallet: accounts_1.wallet, hash: H256::from_str( - "35f42b260f090a559e8b456718d9c91a9da0f234ed0a129b9d5c4813b6615af4" + "cc73f3d5fe9fc3dac28b510ddeb157b0f8030b201e809014967396cdf365488a" ) .unwrap() }) @@ -1043,7 +1060,7 @@ mod tests { Correct(PendingPayable { recipient_wallet: accounts_2.wallet, hash: H256::from_str( - "7f3221109e4f1de8ba1f7cd358aab340ecca872a1456cb1b4f59ca33d3e22ee3" + "891d9ffa838aedc0bb2f6f7e9737128ce98bb33d07b4c8aa5645871e20d6cd13" ) .unwrap() }) @@ -1059,15 +1076,17 @@ mod tests { let _blockchain_client_server = MBCSBuilder::new(port) .response("trash transaction id".to_string(), 1) .start(); - let blockchain_interface_web3 = make_blockchain_interface_web3(Some(port)); + let blockchain_interface_web3 = make_blockchain_interface_web3(port); let consuming_wallet = make_paying_wallet(b"consuming_wallet"); let system = System::new(test_name); - let agent = BlockchainAgentMock::default().consuming_wallet_result(consuming_wallet); + let agent = BlockchainAgentMock::default() + .consuming_wallet_result(consuming_wallet) + .agreed_fee_per_computation_unit_result(123); let msg = OutboundPaymentsInstructions::new(vec![], Box::new(agent), None); let persistent_config = configure_default_persistent_config(ZERO); let mut subject = BlockchainBridge::new( Box::new(blockchain_interface_web3), - Box::new(persistent_config), + Arc::new(Mutex::new(persistent_config)), false, ); let (accountant, _, accountant_recording) = make_recorder(); @@ -1092,47 +1111,6 @@ mod tests { assert_eq!(recording.len(), 0); } - #[test] - fn process_payments_fails_on_missing_gas_price() { - let test_name = "process_payments_fails_on_missing_gas_price"; - let port = find_free_port(); - let _blockchain_client_server = MBCSBuilder::new(port) - .response("0x20".to_string(), 0) - .response("Trash Gas Price".to_string(), 0) - .start(); - let blockchain_interface_web3 = make_blockchain_interface_web3(Some(port)); - let consuming_wallet = make_paying_wallet(b"consuming_wallet"); - let system = System::new(test_name); - let agent = BlockchainAgentMock::default().consuming_wallet_result(consuming_wallet); - let msg = OutboundPaymentsInstructions::new(vec![], Box::new(agent), None); - let persistent_config = PersistentConfigurationMock::new(); - let mut subject = BlockchainBridge::new( - Box::new(blockchain_interface_web3), - Box::new(persistent_config), - false, - ); - let (accountant, _, accountant_recording) = make_recorder(); - subject - .pending_payable_confirmation - .new_pp_fingerprints_sub_opt = Some(accountant.start().recipient()); - - let result = subject - .process_payments(msg.agent, msg.affordable_accounts) - .wait(); - - System::current().stop(); - system.run(); - let error_result = result.unwrap_err(); - assert_eq!( - error_result, - GasPriceQueryFailed(BlockchainError::QueryFailed( - "Decoder error: Error(\"0x prefix is missing\", line: 0, column: 0)".to_string() - )) - ); - let recording = accountant_recording.lock().unwrap(); - assert_eq!(recording.len(), 0); - } - fn assert_sending_error(error: &PayableTransactionError, error_msg: &str) { if let PayableTransactionError::Sending { msg, .. } = error { assert!( @@ -1162,6 +1140,7 @@ mod tests { process_error: None, }; let first_response = ReceiptResponseBuilder::default() + .status(U64::from(1)) .transaction_hash(hash_1) .build(); let port = find_free_port(); @@ -1172,10 +1151,10 @@ mod tests { .raw_response(r#"{ "jsonrpc": "2.0", "id": 1, "result": null }"#.to_string()) .end_batch() .start(); - let blockchain_interface = make_blockchain_interface_web3(Some(port)); + let blockchain_interface = make_blockchain_interface_web3(port); let subject = BlockchainBridge::new( Box::new(blockchain_interface), - Box::new(PersistentConfigurationMock::default()), + Arc::new(Mutex::new(PersistentConfigurationMock::default())), false, ); let addr = subject.start(); @@ -1198,12 +1177,12 @@ mod tests { let system = System::new("transaction receipts"); system.run(); let accountant_recording = accountant_recording_arc.lock().unwrap(); - assert_eq!(accountant_recording.len(), 2); + assert_eq!(accountant_recording.len(), 1); let report_transaction_receipt_message = accountant_recording.get_record::(0); - let scan_error_message = accountant_recording.get_record::(1); let mut expected_receipt = TransactionReceipt::default(); expected_receipt.transaction_hash = hash_1; + expected_receipt.status = Some(U64::from(1)); assert_eq!( report_transaction_receipt_message, &ReportTransactionReceipts { @@ -1223,18 +1202,6 @@ mod tests { }), } ); - assert_eq!( - scan_error_message, - &ScanError { - scan_type: ScanType::PendingPayables, - response_skeleton_opt: Some(ResponseSkeleton { - client_id: 1234, - context_id: 4321 - }), - msg: "Aborting scanning; 1 transactions succeed and 1 transactions failed" - .to_string(), - } - ) } #[test] @@ -1251,13 +1218,13 @@ mod tests { .start(); let scan_error_recipient: Recipient = accountant_addr.clone().recipient(); let received_payments_subs: Recipient = accountant_addr.recipient(); - let blockchain_interface = make_blockchain_interface_web3(Some(port)); + let blockchain_interface = make_blockchain_interface_web3(port); let persistent_config = PersistentConfigurationMock::new() .max_block_count_result(Ok(Some(100_000))) .start_block_result(Ok(5)); // no set_start_block_result: set_start_block() must not be called let mut subject = BlockchainBridge::new( Box::new(blockchain_interface), - Box::new(persistent_config), + Arc::new(Mutex::new(persistent_config)), false, ); subject.scan_error_subs_opt = Some(scan_error_recipient); @@ -1273,23 +1240,18 @@ mod tests { system.run(); let recording = accountant_recording_arc.lock().unwrap(); - let message = recording.get_record::(0); - assert_eq!( - message.scan_result, - Err(ReceivedPaymentsError::OtherRPCError("Attempted to retrieve received payments but failed: QueryFailed(\"Transport error: Error(IncompleteMessage)\")".to_string())) - ); - let message_2 = recording.get_record::(1); + let scan_error = recording.get_record::(0); assert_eq!( - message_2, - &ScanError { - scan_type: ScanType::Receivables, - response_skeleton_opt: None, - msg: "Error while retrieving transactions: OtherRPCError(\"Attempted to retrieve received payments but failed: QueryFailed(\\\"Transport error: Error(IncompleteMessage)\\\")\")".to_string() - } + scan_error, + &ScanError { + scan_type: ScanType::Receivables, + response_skeleton_opt: None, + msg: "Error while retrieving transactions: QueryFailed(\"Transport error: Error(IncompleteMessage)\")".to_string() + } ); - assert_eq!(recording.len(), 2); + assert_eq!(recording.len(), 1); TestLogHandler::new().exists_log_containing( - "WARN: BlockchainBridge: Error while retrieving transactions: OtherRPCError(\"Attempted to retrieve received payments but failed: QueryFailed(\\\"Transport error: Error(IncompleteMessage)\\\")\")", + "WARN: BlockchainBridge: Error while retrieving transactions: QueryFailed(\"Transport error: Error(IncompleteMessage)\")", ); } @@ -1302,6 +1264,7 @@ mod tests { let contract_address = H160::from_low_u64_be(887766); let tx_receipt_response = ReceiptResponseBuilder::default() .block_number(block_number) + .status(U64::from(1)) .contract_address(contract_address) .build(); let _blockchain_client_server = MBCSBuilder::new(port) @@ -1357,11 +1320,12 @@ mod tests { let mut transaction_receipt = TransactionReceipt::default(); transaction_receipt.block_number = Some(block_number); transaction_receipt.contract_address = Some(contract_address); - let blockchain_interface = make_blockchain_interface_web3(Some(port)); + transaction_receipt.status = Some(U64::from(1)); + let blockchain_interface = make_blockchain_interface_web3(port); let system = System::new("test_transaction_receipts"); let mut subject = BlockchainBridge::new( Box::new(blockchain_interface), - Box::new(PersistentConfigurationMock::default()), + Arc::new(Mutex::new(PersistentConfigurationMock::default())), false, ); subject @@ -1386,7 +1350,7 @@ mod tests { assert_eq!(system.run(), 0); let accountant_recording = accountant_recording_arc.lock().unwrap(); - assert_eq!(accountant_recording.len(), 2); + assert_eq!(accountant_recording.len(), 1); let report_receipts_msg = accountant_recording.get_record::(0); assert_eq!( *report_receipts_msg, @@ -1403,62 +1367,7 @@ mod tests { }), } ); - let scan_error_msg = accountant_recording.get_record::(1); - assert_eq!( - *scan_error_msg, - ScanError { - scan_type: ScanType::PendingPayables, - response_skeleton_opt: Some(ResponseSkeleton { - client_id: 1234, - context_id: 4321 - }), - msg: "Aborting scanning; 1 transactions succeed and 3 transactions failed" - .to_string() - } - ); - TestLogHandler::new().exists_log_containing("WARN: BlockchainBridge: Aborting scanning; 1 transactions succeed and 3 transactions failed"); - } - - #[test] - fn blockchain_bridge_can_return_report_transaction_receipts_with_an_empty_vector() { - let (accountant, _, accountant_recording) = make_recorder(); - let recipient = accountant.start().recipient(); - let transaction_receipt_response = ReceiptResponseBuilder::default().build(); - let port = find_free_port(); - let _blockchain_client_server = MBCSBuilder::new(port) - .begin_batch() - .raw_response(transaction_receipt_response) - .end_batch() - .start(); - let blockchain_interface = make_blockchain_interface_web3(Some(port)); - let mut subject = BlockchainBridge::new( - Box::new(blockchain_interface), - Box::new(PersistentConfigurationMock::default()), - false, - ); - subject - .pending_payable_confirmation - .report_transaction_receipts_sub_opt = Some(recipient); - let msg = RequestTransactionReceipts { - pending_payable: vec![], - response_skeleton_opt: None, - }; - let system = System::new( - "blockchain_bridge_can_return_report_transaction_receipts_with_an_empty_vector", - ); - - let _ = subject.handle_request_transaction_receipts(msg).wait(); - - System::current().stop(); - system.run(); - let recording = accountant_recording.lock().unwrap(); - assert_eq!( - recording.get_record::(0), - &ReportTransactionReceipts { - fingerprints_with_receipts: vec![], - response_skeleton_opt: None - } - ) + TestLogHandler::new().exists_log_containing("DEBUG: BlockchainBridge: Aborting scanning; 1 transactions succeed and 3 transactions failed"); } #[test] @@ -1490,10 +1399,10 @@ mod tests { }; let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port).start(); - let blockchain_interface = make_blockchain_interface_web3(Some(port)); + let blockchain_interface = make_blockchain_interface_web3(port); let mut subject = BlockchainBridge::new( Box::new(blockchain_interface), - Box::new(PersistentConfigurationMock::default()), + Arc::new(Mutex::new(PersistentConfigurationMock::default())), false, ); subject @@ -1530,38 +1439,55 @@ mod tests { #[test] fn handle_retrieve_transactions_uses_latest_block_number_upon_get_block_number_error() { init_test_logging(); - let retrieve_transactions_params_arc = Arc::new(Mutex::new(vec![])); let system = System::new( "handle_retrieve_transactions_uses_latest_block_number_upon_get_block_number_error", ); + let port = find_free_port(); + let _blockchain_client_server = MBCSBuilder::new(port) + .response("0xC8".to_string(), 0) + .raw_response(r#"{ + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "address": "0x06012c8cf97bead5deae237070f9587f8e7a266d", + "blockHash": "0x7c5a35e9cb3e8ae0e221ab470abae9d446c3a5626ce6689fc777dcffcab52c70", + "blockNumber": "0x5c29fb", + "data": "0x0000000000000000000000000000002a", + "logIndex": "0x1d", + "removed": false, + "topics": [ + "0x241ea03ca20251805084d27d4440371c34a0b85ff108f6bb5611248f73818b80", + "0x000000000000000000000000000000000000000066697273745f77616c6c6574" + ], + "transactionHash": "0x3dc91b98249fa9f2c5c37486a2427a3a7825be240c1c84961dfb3063d9c04d50", + "transactionIndex": "0x1d" + }, + { + "address": "0x06012c8cf97bead5deae237070f9587f8e7a266d", + "blockHash": "0x7c5a35e9cb3e8ae0e221ab470abae9d446c3a5626ce6689fc777dcffcab52c70", + "blockNumber": "0x5c29fc", + "data": "0x00000000000000000000000000000037", + "logIndex": "0x57", + "removed": false, + "topics": [ + "0x241ea03ca20251805084d27d4440371c34a0b85ff108f6bb5611248f73818b80", + "0x000000000000000000000000000000000000007365636f6e645f77616c6c6574" + ], + "transactionHash": "0x788b1442414cb9c9a36dba2abe250763161a6f6395788a2e808f1b34e92beec1", + "transactionIndex": "0x54" + } + ] + }"#.to_string()) + .start(); let (accountant, _, accountant_recording_arc) = make_recorder(); let earning_wallet = make_wallet("somewallet"); - let amount = 42; - let amount2 = 55; - let expected_transactions = RetrievedBlockchainTransactions { - new_start_block: 8675309u64, - transactions: vec![ - BlockchainTransaction { - block_number: 7, - from: earning_wallet.clone(), - wei_amount: amount, - }, - BlockchainTransaction { - block_number: 9, - from: earning_wallet.clone(), - wei_amount: amount2, - }, - ], - }; - let blockchain_interface_mock = BlockchainInterfaceMock::default() - .retrieve_transactions_params(&retrieve_transactions_params_arc) - .retrieve_transactions_result(Ok(expected_transactions.clone())); let persistent_config = PersistentConfigurationMock::new() .max_block_count_result(Ok(Some(10000u64))) - .start_block_result(Ok(6)); + .start_block_result(Ok(100)); let mut subject = BlockchainBridge::new( - Box::new(blockchain_interface_mock), - Box::new(persistent_config), + Box::new(make_blockchain_interface_web3(port)), + Arc::new(Mutex::new(persistent_config)), false, ); subject.received_payments_subs_opt = Some(accountant.start().recipient()); @@ -1582,27 +1508,33 @@ mod tests { System::current().stop(); system.run(); let after = SystemTime::now(); - let retrieve_transactions_params = retrieve_transactions_params_arc.lock().unwrap(); - assert_eq!( - *retrieve_transactions_params, - vec![( - BlockNumber::Number(6u64.into()), - 10006u64, - earning_wallet.address() - )] - ); + let expected_transactions = RetrievedBlockchainTransactions { + new_start_block: 6040060u64, + transactions: vec![ + BlockchainTransaction { + block_number: 6040059, + from: make_wallet("first_wallet"), // Points to topics of 1 + wei_amount: 42, // Its points to the field data + }, + BlockchainTransaction { + block_number: 6040060, + from: make_wallet("second_wallet"), // Points to topics of 1 + wei_amount: 55, // Its points to the field data + }, + ], + }; + let mut payments_and_start_block = make_empty_payments_and_start_block(); + payments_and_start_block.payments = expected_transactions.transactions; + payments_and_start_block.new_start_block = expected_transactions.new_start_block; let accountant_received_payment = accountant_recording_arc.lock().unwrap(); assert_eq!(accountant_received_payment.len(), 1); let received_payments = accountant_received_payment.get_record::(0); check_timestamp(before, received_payments.timestamp, after); - let mut scan_result = make_empty_payments_and_start_block(); - scan_result.payments = expected_transactions.transactions; - scan_result.new_start_block = 8675309u64; assert_eq!( received_payments, &ReceivedPayments { timestamp: received_payments.timestamp, - scan_result: Ok(scan_result), + payments_and_start_block, response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, context_id: 4321 @@ -1652,20 +1584,20 @@ mod tests { let earning_wallet = make_wallet("earning_wallet"); let amount = 996000000; let expected_transactions = RetrievedBlockchainTransactions { - new_start_block: 1000000001, + new_start_block: 1000000000, transactions: vec![BlockchainTransaction { block_number: 2000, from: earning_wallet.clone(), wei_amount: amount, }], }; - let blockchain_interface = make_blockchain_interface_web3(Some(port)); + let blockchain_interface = make_blockchain_interface_web3(port); let persistent_config = PersistentConfigurationMock::new() .start_block_result(Ok(6)) .max_block_count_result(Err(PersistentConfigError::NotPresent)); let subject = BlockchainBridge::new( Box::new(blockchain_interface), - Box::new(persistent_config), + Arc::new(Mutex::new(persistent_config)), false, ); let addr = subject.start(); @@ -1694,10 +1626,10 @@ mod tests { received_payments_message, &ReceivedPayments { timestamp: received_payments_message.timestamp, - scan_result: Ok(PaymentsAndStartBlock { + payments_and_start_block: PaymentsAndStartBlock { payments: expected_transactions.transactions, new_start_block: expected_transactions.new_start_block, - }), + }, response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, context_id: 4321 @@ -1735,17 +1667,18 @@ mod tests { .response(expected_response_logs, 1) .start(); let (accountant, _, accountant_recording_arc) = make_recorder(); - let accountant_addr = - accountant.system_stop_conditions(match_every_type_id!(ReceivedPayments)); + let accountant_addr = accountant.system_stop_conditions(match_every_type_id!(ScanError)); let earning_wallet = make_wallet("earning_wallet"); - let mut blockchain_interface = make_blockchain_interface_web3(Some(port)); + let mut blockchain_interface = make_blockchain_interface_web3(port); blockchain_interface.logger = logger; let persistent_config = PersistentConfigurationMock::new() .start_block_result(Ok(6)) - .max_block_count_result(Err(PersistentConfigError::NotPresent)); + .max_block_count_result(Err(PersistentConfigError::DatabaseError( + "my tummy hurts".to_string(), + ))); let subject = BlockchainBridge::new( Box::new(blockchain_interface), - Box::new(persistent_config), + Arc::new(Mutex::new(persistent_config)), false, ); let addr = subject.start(); @@ -1759,30 +1692,22 @@ mod tests { context_id: 4321, }), }; - let before = SystemTime::now(); let _ = addr.try_send(retrieve_transactions).unwrap(); system.run(); - let after = SystemTime::now(); - let accountant_recording = accountant_recording_arc.lock().unwrap(); - assert_eq!(accountant_recording.len(), 2); - let received_payments_error_message = - accountant_recording.get_record::(0); - check_timestamp(before, received_payments_error_message.timestamp, after); + assert_eq!(accountant_recording.len(), 1); + let scan_error_msg = accountant_recording.get_record::(0); assert_eq!( - received_payments_error_message, - &ReceivedPayments { - timestamp: received_payments_error_message.timestamp, - scan_result: Err(OtherRPCError( - "Attempted to retrieve received payments but failed: InvalidResponse" - .to_string() - )), + scan_error_msg, + &ScanError { + scan_type: ScanType::Receivables, response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, context_id: 4321 }), + msg: "Error while retrieving transactions: InvalidResponse".to_string(), } ); TestLogHandler::new().exists_log_containing(&format!( @@ -1790,6 +1715,110 @@ mod tests { )); } + #[test] + fn handle_retrieve_transactions_receives_query_failed_and_updates_max_block() { + init_test_logging(); + let test_name = "handle_retrieve_transactions_receives_query_failed_and_updates_max_block"; + let system = System::new(test_name); + let port = find_free_port(); + let _blockchain_client_server = MBCSBuilder::new(port) + .response("0x3B9ACA00".to_string(), 0) + .err_response(-32005, "Blockheight too far in the past. Check params passed to eth_getLogs or eth_call requests.Range of blocks allowed for your plan: 1000", 0) + .start(); + let (accountant, _, accountant_recording_arc) = make_recorder(); + let accountant = accountant.system_stop_conditions(match_every_type_id!(ScanError)); + let earning_wallet = make_wallet("earning_wallet"); + let blockchain_interface = make_blockchain_interface_web3(port); + let persistent_config = PersistentConfigurationMock::new() + .start_block_result(Ok(6)) + .max_block_count_result(Err(PersistentConfigError::DatabaseError( + "my tummy hurts".to_string(), + ))) + .set_max_block_count_result(Ok(())); + let mut subject = BlockchainBridge::new( + Box::new(blockchain_interface), + Arc::new(Mutex::new(persistent_config)), + false, + ); + subject.logger = Logger::new(test_name); + let addr = subject.start(); + let subject_subs = BlockchainBridge::make_subs_from(&addr); + let peer_actors = peer_actors_builder().accountant(accountant).build(); + send_bind_message!(subject_subs, peer_actors); + let retrieve_transactions = RetrieveTransactions { + recipient: earning_wallet.clone(), + response_skeleton_opt: Some(ResponseSkeleton { + client_id: 1234, + context_id: 4321, + }), + }; + + let _ = addr.try_send(retrieve_transactions).unwrap(); + + system.run(); + let accountant_recording = accountant_recording_arc.lock().unwrap(); + assert_eq!(accountant_recording.len(), 1); + let scan_error_msg = accountant_recording.get_record::(0); + assert_eq!( + scan_error_msg, + &ScanError { + scan_type: ScanType::Receivables, + response_skeleton_opt: Some(ResponseSkeleton { + client_id: 1234, + context_id: 4321 + }), + msg: "Error while retrieving transactions: 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(), + } + ); + TestLogHandler::new().exists_log_containing(&format!( + "DEBUG: {test_name}: Updated max_block_count to 1000 in database" + )); + } + + #[test] + #[should_panic( + expected = "Attempt to set new max block to 1000 failed due to: DatabaseError(\"my brain hurst\")" + )] + fn handle_retrieve_transactions_receives_query_failed_and_failed_update_max_block() { + let system = System::new("test"); + let port = find_free_port(); + let _blockchain_client_server = MBCSBuilder::new(port) + .response("0x3B9ACA00".to_string(), 0) + .err_response(-32005, "Blockheight too far in the past. Check params passed to eth_getLogs or eth_call requests.Range of blocks allowed for your plan: 1000", 0) + .start(); + let (accountant, _, _) = make_recorder(); + let earning_wallet = make_wallet("earning_wallet"); + let blockchain_interface = make_blockchain_interface_web3(port); + let persistent_config = PersistentConfigurationMock::new() + .start_block_result(Ok(6)) + .max_block_count_result(Err(PersistentConfigError::DatabaseError( + "my tummy hurts".to_string(), + ))) + .set_max_block_count_result(Err(PersistentConfigError::DatabaseError( + "my brain hurst".to_string(), + ))); + let subject = BlockchainBridge::new( + Box::new(blockchain_interface), + Arc::new(Mutex::new(persistent_config)), + false, + ); + let addr = subject.start(); + let subject_subs = BlockchainBridge::make_subs_from(&addr); + let peer_actors = peer_actors_builder().accountant(accountant).build(); + send_bind_message!(subject_subs, peer_actors); + let retrieve_transactions = RetrieveTransactions { + recipient: earning_wallet.clone(), + response_skeleton_opt: Some(ResponseSkeleton { + client_id: 1234, + context_id: 4321, + }), + }; + + let _ = addr.try_send(retrieve_transactions).unwrap(); + + system.run(); + } + #[test] #[should_panic( expected = "Cannot retrieve start block from database; payments to you may not be processed: TransactionError" @@ -1798,8 +1827,8 @@ mod tests { let persistent_config = PersistentConfigurationMock::new() .start_block_result(Err(PersistentConfigError::TransactionError)); let mut subject = BlockchainBridge::new( - Box::new(BlockchainInterfaceMock::default()), - Box::new(persistent_config), + Box::new(make_blockchain_interface_web3(find_free_port())), + Arc::new(Mutex::new(persistent_config)), false, ); let retrieve_transactions = RetrieveTransactions { @@ -1810,8 +1839,47 @@ mod tests { let _ = subject.handle_retrieve_transactions(retrieve_transactions); } + // TODO: GH-555: Remove system_stop_conditions while also confirming the ScanError msg wasn't sent. #[test] fn handle_scan_future_handles_success() { + let port = find_free_port(); + let _blockchain_client_server = MBCSBuilder::new(port) + .response("0xC8".to_string(), 0) + .raw_response(r#"{ + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "address": "0x06012c8cf97bead5deae237070f9587f8e7a266d", + "blockHash": "0x7c5a35e9cb3e8ae0e221ab470abae9d446c3a5626ce6689fc777dcffcab52c70", + "blockNumber": "0x5c29fb", + "data": "0x0000000000000000000000000000002a", + "logIndex": "0x1d", + "removed": false, + "topics": [ + "0x241ea03ca20251805084d27d4440371c34a0b85ff108f6bb5611248f73818b80", + "0x000000000000000000000000000000000000000066697273745f77616c6c6574" + ], + "transactionHash": "0x3dc91b98249fa9f2c5c37486a2427a3a7825be240c1c84961dfb3063d9c04d50", + "transactionIndex": "0x1d" + }, + { + "address": "0x06012c8cf97bead5deae237070f9587f8e7a266d", + "blockHash": "0x7c5a35e9cb3e8ae0e221ab470abae9d446c3a5626ce6689fc777dcffcab52c70", + "blockNumber": "0x5c29fc", + "data": "0x00000000000000000000000000000037", + "logIndex": "0x57", + "removed": false, + "topics": [ + "0x241ea03ca20251805084d27d4440371c34a0b85ff108f6bb5611248f73818b80", + "0x000000000000000000000000000000000000007365636f6e645f77616c6c6574" + ], + "transactionHash": "0x788b1442414cb9c9a36dba2abe250763161a6f6395788a2e808f1b34e92beec1", + "transactionIndex": "0x54" + } + ] + }"#.to_string()) + .start(); let (accountant, _, accountant_recording_arc) = make_recorder(); let start_block = 2000; let wallet = make_wallet("somewallet"); @@ -1825,21 +1893,9 @@ mod tests { context_id: 4321, }), }; - let blockchain_transaction = BlockchainTransaction { - block_number: start_block, - from: wallet, - wei_amount: 20_000, - }; - let retrieved_blockchain_transactions = RetrievedBlockchainTransactions { - new_start_block: start_block, - transactions: vec![blockchain_transaction], - }; let mut subject = BlockchainBridge::new( - Box::new( - BlockchainInterfaceMock::default() - .retrieve_transactions_result(Ok(retrieved_blockchain_transactions)), - ), - Box::new(persistent_config), + Box::new(make_blockchain_interface_web3(port)), + Arc::new(Mutex::new(persistent_config)), false, ); let system = System::new("test"); @@ -1878,18 +1934,19 @@ mod tests { fn assert_handle_scan_future_handles_failure(msg: RetrieveTransactions) { init_test_logging(); + let port = find_free_port(); + let _blockchain_client_server = MBCSBuilder::new(port) + .response("0xC8".to_string(), 0) + .err_response(-32005, "My tummy hurts", 0) + .start(); let (accountant, _, accountant_recording_arc) = make_recorder(); let start_block = 2000; let persistent_config = PersistentConfigurationMock::default() .start_block_result(Ok(start_block)) .max_block_count_result(Ok(None)); let mut subject = BlockchainBridge::new( - Box::new( - BlockchainInterfaceMock::default().retrieve_transactions_result(Err( - BlockchainError::QueryFailed("My tummy hurts".to_string()), - )), - ), - Box::new(persistent_config), + Box::new(make_blockchain_interface_web3(port)), + Arc::new(Mutex::new(persistent_config)), false, ); let system = System::new("test"); @@ -1907,17 +1964,18 @@ mod tests { system.run(); let accountant_recording = accountant_recording_arc.lock().unwrap(); - let message = accountant_recording.get_record::(1); + let message = accountant_recording.get_record::(0); assert_eq!( message, &ScanError { scan_type: ScanType::Receivables, response_skeleton_opt: msg.response_skeleton_opt, - msg: "Error while retrieving transactions: OtherRPCError(\"Attempted to retrieve received payments but failed: QueryFailed(\\\"My tummy hurts\\\")\")".to_string() + msg: "Error while retrieving transactions: QueryFailed(\"RPC error: Error { code: ServerError(-32005), message: \\\"My tummy hurts\\\", data: None }\")" + .to_string() } ); - assert_eq!(accountant_recording.len(), 2); - TestLogHandler::new().exists_log_containing("WARN: BlockchainBridge: Error while retrieving transactions: OtherRPCError(\"Attempted to retrieve received payments but failed: QueryFailed(\\\"My tummy hurts\\\")\")"); + assert_eq!(accountant_recording.len(), 1); + TestLogHandler::new().exists_log_containing("WARN: BlockchainBridge: Error while retrieving transactions: QueryFailed(\"RPC error: Error { code: ServerError(-32005), message: \\\"My tummy hurts\\\", data: None }\")"); } #[test] @@ -1927,8 +1985,8 @@ mod tests { fn blockchain_bridge_can_be_crashed_properly_but_not_improperly() { let crashable = true; let subject = BlockchainBridge::new( - Box::new(BlockchainInterfaceMock::default()), - Box::new(PersistentConfigurationMock::default()), + Box::new(make_blockchain_interface_web3(find_free_port())), + Arc::new(Mutex::new(PersistentConfigurationMock::default())), crashable, ); @@ -2034,6 +2092,18 @@ mod tests { assert_on_initialization_with_panic_on_migration(&data_dir, &act); } + + #[test] + fn calculate_fallback_start_block_number_works() { + assert_eq!( + BlockchainBridge::calculate_fallback_start_block_number(10_000, u64::MAX), + 10_000 + 1 + ); + assert_eq!( + BlockchainBridge::calculate_fallback_start_block_number(5_000, 10_000), + 5_000 + 10_000 + ); + } } #[cfg(test)] diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/batch_payable_tools.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/batch_payable_tools.rs deleted file mode 100644 index 8b1378917..000000000 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/batch_payable_tools.rs +++ /dev/null @@ -1 +0,0 @@ - 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 cfe79e092..0049df8cd 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,34 +1,24 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::db_access_objects::payable_dao::PayableAccount; -use crate::blockchain::blockchain_bridge::PendingPayableFingerprintSeeds; 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::{ - BlockchainError, PayableTransactionError, -}; -use crate::blockchain::blockchain_interface::data_structures::ProcessedPayableFallible; use crate::blockchain::blockchain_interface::lower_level_interface::LowBlockchainInt; -use crate::blockchain::blockchain_interface_utils::{ - convert_wei_to_gwei, send_payables_within_batch, -}; -use crate::sub_lib::wallet::Wallet; -use actix::Recipient; use ethereum_types::{H256, U256, U64}; use futures::Future; -use masq_lib::blockchains::chains::Chain; -use masq_lib::logger::Logger; +use serde_json::Value; use web3::contract::{Contract, Options}; use web3::transports::{Batch, Http}; use web3::types::{Address, BlockNumber, Filter, Log, TransactionReceipt}; -use web3::Web3; +use web3::{Error, Web3}; #[derive(Debug, PartialEq, Clone)] #[allow(clippy::large_enum_variant)] pub enum TransactionReceiptResult { NotPresent, Found(TransactionReceipt), - Error(String), + TransactionFailed(TransactionReceipt), // RemoteFailure + Error(String), // LocalFailure } pub struct LowBlockchainIntWeb3 { @@ -92,22 +82,10 @@ impl LowBlockchainInt for LowBlockchainIntWeb3 { ) } - fn get_transaction_receipt( - &self, - hash: H256, - ) -> Box, Error = BlockchainError>> { - Box::new( - self.web3 - .eth() - .transaction_receipt(hash) - .map_err(|e| QueryFailed(e.to_string())), - ) - } - - fn get_transaction_receipt_batch( + fn get_transaction_receipt_in_batch( &self, hash_vec: Vec, - ) -> Box, Error = BlockchainError>> { + ) -> Box>, Error = BlockchainError>> { let _ = hash_vec.into_iter().map(|hash| { self.web3_batch.eth().transaction_receipt(hash); }); @@ -115,32 +93,12 @@ impl LowBlockchainInt for LowBlockchainIntWeb3 { self.web3_batch .transport() .submit_batch() - .map_err(|e| QueryFailed(e.to_string())) - .and_then(move |batch_response| { - Ok(batch_response - .into_iter() - .map(|response| match response { - Ok(result) => { - match serde_json::from_value::(result) { - Ok(receipt) => TransactionReceiptResult::Found(receipt), - Err(e) => { - if e.to_string().contains("invalid type: null") { - TransactionReceiptResult::NotPresent - } else { - TransactionReceiptResult::Error(e.to_string()) - } - } - } - } - Err(e) => TransactionReceiptResult::Error(e.to_string()), - }) - .collect::>()) - }), + .map_err(|e| QueryFailed(e.to_string())), ) } - fn get_contract(&self) -> Contract { - self.contract.clone() + fn get_contract_address(&self) -> Address { + self.contract.address() } fn get_transaction_logs( @@ -155,40 +113,8 @@ impl LowBlockchainInt for LowBlockchainIntWeb3 { ) } - fn submit_payables_in_batch( - &self, - logger: Logger, - chain: Chain, - consuming_wallet: Wallet, - fingerprints_recipient: Recipient, - affordable_accounts: Vec, - ) -> Box, Error = PayableTransactionError>> - { - let web3_batch = self.web3_batch.clone(); - let get_transaction_id = self.get_transaction_id(consuming_wallet.address()); - let get_gas_price = self.get_gas_price(); - - Box::new( - get_transaction_id - .map_err(PayableTransactionError::TransactionID) - .and_then(move |pending_nonce| { - get_gas_price - .map_err(PayableTransactionError::GasPriceQueryFailed) - .and_then(move |gas_price_wei| { - let gas_price = convert_wei_to_gwei(gas_price_wei); - send_payables_within_batch( - logger, - chain, - web3_batch, - consuming_wallet, - gas_price, - pending_nonce, - fingerprints_recipient, - affordable_accounts, - ) - }) - }), - ) + fn get_web3_batch(&self) -> Web3> { + self.web3_batch.clone() } } @@ -209,19 +135,18 @@ 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::test_utils::make_blockchain_interface_web3; use crate::sub_lib::wallet::Wallet; - use masq_lib::utils::find_free_port; - use std::str::FromStr; + use crate::test_utils::make_wallet; use ethereum_types::{H256, U64}; use futures::Future; - use web3::types::{BlockNumber, Bytes, FilterBuilder, H2048, Log, TransactionReceipt, U256}; use masq_lib::test_utils::mock_blockchain_client_server::MBCSBuilder; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::TRANSACTION_LITERAL; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::TransactionReceiptResult; - use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainError::QueryFailed; - use crate::blockchain::test_utils::{make_blockchain_interface_web3, make_tx_hash, ReceiptResponseBuilder}; - use crate::test_utils::{assert_string_contains, make_wallet}; + use masq_lib::utils::find_free_port; + use std::str::FromStr; + use web3::types::{BlockNumber, Bytes, FilterBuilder, Log, U256}; #[test] fn get_transaction_fee_balance_works() { @@ -230,7 +155,7 @@ mod tests { .response("0x23".to_string(), 1) .start(); let wallet = &Wallet::from_str("0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc").unwrap(); - let subject = make_blockchain_interface_web3(Some(port)); + let subject = make_blockchain_interface_web3(port); let result = subject .lower_interface() @@ -247,7 +172,7 @@ mod tests { let _blockchain_client_server = MBCSBuilder::new(port) .response("0xFFFQ".to_string(), 0) .start(); - let subject = make_blockchain_interface_web3(Some(port)); + let subject = make_blockchain_interface_web3(port); let result = subject .lower_interface() @@ -272,7 +197,7 @@ mod tests { let _blockchain_client_server = MBCSBuilder::new(port) .response("0x01".to_string(), 1) .start(); - let subject = make_blockchain_interface_web3(Some(port)); + let subject = make_blockchain_interface_web3(port); let result = subject.lower_interface().get_gas_price().wait().unwrap(); @@ -283,7 +208,7 @@ mod tests { fn get_gas_price_returns_error() { let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port).start(); - let subject = make_blockchain_interface_web3(Some(port)); + let subject = make_blockchain_interface_web3(port); let error = subject .lower_interface() @@ -303,7 +228,7 @@ mod tests { let _blockchain_client_server = MBCSBuilder::new(port) .response("0x23".to_string(), 1) .start(); - let subject = make_blockchain_interface_web3(Some(port)); + let subject = make_blockchain_interface_web3(port); let result = subject.lower_interface().get_block_number().wait(); @@ -316,7 +241,7 @@ mod tests { let _blockchain_client_server = MBCSBuilder::new(port) .response("trash".to_string(), 1) .start(); - let subject = make_blockchain_interface_web3(Some(port)); + let subject = make_blockchain_interface_web3(port); let error = subject .lower_interface() @@ -338,7 +263,7 @@ mod tests { let _blockchain_client_server = MBCSBuilder::new(port) .response("0x23".to_string(), 1) .start(); - let subject = make_blockchain_interface_web3(Some(port)); + let subject = make_blockchain_interface_web3(port); let wallet = &Wallet::from_str("0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc").unwrap(); let result = subject @@ -355,7 +280,7 @@ mod tests { let _blockchain_client_server = MBCSBuilder::new(port) .response("0xFFFQ".to_string(), 0) .start(); - let subject = make_blockchain_interface_web3(Some(port)); + let subject = make_blockchain_interface_web3(port); let result = subject .lower_interface() @@ -383,7 +308,7 @@ mod tests { 0, ) .start(); - let subject = make_blockchain_interface_web3(Some(port)); + let subject = make_blockchain_interface_web3(port); let result = subject .lower_interface() @@ -408,7 +333,7 @@ mod tests { ) .start(); let expected_err_msg = "Invalid hex"; - let subject = make_blockchain_interface_web3(Some(port)); + let subject = make_blockchain_interface_web3(port); let result = subject .lower_interface() @@ -431,164 +356,11 @@ mod tests { ) } - #[test] - fn transaction_receipt_works() { - let port = find_free_port(); - let tx_hash = - H256::from_str("a128f9ca1e705cc20a936a24a7fa1df73bad6e0aaf58e8e6ffcc154a7cff6e0e") - .unwrap(); - let block_hash = - H256::from_str("6d0abccae617442c26104c2bc63d1bc05e1e002e555aec4ab62a46e826b18f18") - .unwrap(); - let block_number = U64::from_str("b0328d").unwrap(); - let cumulative_gas_used = U256::from_str("60ef").unwrap(); - let gas_used = U256::from_str("60ef").unwrap(); - let status = U64::from(0); - let tx_receipt_response = ReceiptResponseBuilder::default() - .transaction_hash(tx_hash) - .block_hash(block_hash) - .block_number(block_number) - .cumulative_gas_used(cumulative_gas_used) - .gas_used(gas_used) - .status(status) - .build(); - let _blockchain_client_server = MBCSBuilder::new(port) - .raw_response(tx_receipt_response) - .start(); - let subject = make_blockchain_interface_web3(Some(port)); - - let result = subject - .lower_interface() - .get_transaction_receipt(tx_hash) - .wait(); - - let expected_receipt = TransactionReceipt { - transaction_hash: tx_hash, - transaction_index: Default::default(), - block_hash: Some(block_hash), - block_number: Some(block_number), - cumulative_gas_used, - gas_used: Some(gas_used), - contract_address: None, - logs: vec![], - status: Some(status), - root: None, - logs_bloom: Default::default(), - }; - assert_eq!(result, Ok(Some(expected_receipt))); - } - - #[test] - fn get_transaction_receipt_handles_errors() { - let port = find_free_port(); - let subject = make_blockchain_interface_web3(Some(port)); - let tx_hash = make_tx_hash(4564546); - - let actual_error = subject - .lower_interface() - .get_transaction_receipt(tx_hash) - .wait() - .unwrap_err(); - let error_message = if let BlockchainError::QueryFailed(em) = actual_error { - em - } else { - panic!("Expected BlockchainError::QueryFailed(msg)"); - }; - assert_string_contains( - error_message.as_str(), - "Transport error: Error(Connect, Os { code: ", - ); - assert_string_contains( - error_message.as_str(), - ", kind: ConnectionRefused, message: ", - ); - } - - #[test] - fn transaction_receipt_batch_works() { - let port = find_free_port(); - let tx_hash_1 = - H256::from_str("a128f9ca1e705cc20a936a24a7fa1df73bad6e0aaf58e8e6ffcc154a7cff6e0e") - .unwrap(); - let tx_hash_2 = - H256::from_str("a128f9ca1e705cc20a936a24a7fa1df73bad6e0aaf58e8e6ffcc154a7cff6e0f") - .unwrap(); - let tx_hash_3 = - H256::from_str("a128f9ca1e705cc20a936a24a7fa1df73bad6e0aaf58e8e6ffcc154a7cff6e0a") - .unwrap(); - let tx_hash_4 = - H256::from_str("a128f9ca1e705cc20a936a24a7fa1df73bad6e0aaf58e8e6ffcc154a7cff6e0b") - .unwrap(); - let tx_hash_vec = vec![tx_hash_1, tx_hash_2, tx_hash_3, tx_hash_4]; - let block_hash = - H256::from_str("6d0abccae617442c26104c2bc63d1bc05e1e002e555aec4ab62a46e826b18f18") - .unwrap(); - let block_number = U64::from_str("b0328d").unwrap(); - let cumulative_gas_used = U256::from_str("60ef").unwrap(); - let gas_used = U256::from_str("60ef").unwrap(); - let status = U64::from(0); - let logs_bloom = H2048::from_str("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000").unwrap(); - let tx_receipt_response = ReceiptResponseBuilder::default() - .transaction_hash(tx_hash_1) - .block_hash(block_hash) - .block_number(block_number) - .cumulative_gas_used(cumulative_gas_used) - .gas_used(gas_used) - .status(status) - .logs_bloom(logs_bloom) - .build(); - let _blockchain_client_server = MBCSBuilder::new(port) - .begin_batch() - .err_response( - 429, - "The requests per second (RPS) of your requests are higher than your plan allows." - .to_string(), - 7, - ) - .raw_response(r#"{ "jsonrpc": "2.0", "id": 1, "result": null }"#.to_string()) - .raw_response(tx_receipt_response) - .response("trash".to_string(), 0) - .end_batch() - .start(); - let subject = make_blockchain_interface_web3(Some(port)); - - let result = subject - .lower_interface() - .get_transaction_receipt_batch(tx_hash_vec) - .wait() - .unwrap(); - - assert_eq!(result[0], TransactionReceiptResult::Error("RPC error: Error { code: ServerError(429), message: \"The requests per second (RPS) of your requests are higher than your plan allows.\", data: None }".to_string())); - assert_eq!(result[1], TransactionReceiptResult::NotPresent); - assert_eq!( - result[2], - TransactionReceiptResult::Found(TransactionReceipt { - transaction_hash: tx_hash_1, - transaction_index: Default::default(), - block_hash: Some(block_hash), - block_number: Some(block_number), - cumulative_gas_used, - gas_used: Some(gas_used), - contract_address: None, - logs: vec![], - status: Some(status), - root: None, - logs_bloom - }) - ); - assert_eq!( - result[3], - TransactionReceiptResult::Error( - "invalid type: string \"trash\", expected struct Receipt".to_string() - ) - ); - } - #[test] fn transaction_receipt_batch_fails_on_submit_batch() { let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port).start(); - let subject = make_blockchain_interface_web3(Some(port)); + let subject = make_blockchain_interface_web3(port); let tx_hash_1 = H256::from_str("a128f9ca1e705cc20a936a24a7fa1df73bad6e0aaf58e8e6ffcc154a7cff6e0e") .unwrap(); @@ -599,7 +371,7 @@ mod tests { let result = subject .lower_interface() - .get_transaction_receipt_batch(tx_hash_vec) + .get_transaction_receipt_in_batch(tx_hash_vec) .wait() .unwrap_err(); @@ -646,7 +418,7 @@ mod tests { ] }"#.to_string()) .start(); - let subject = make_blockchain_interface_web3(Some(port)); + let subject = make_blockchain_interface_web3(port); let contract_address = subject.chain.rec().contract; let start_block = BlockNumber::Number(U64::from(100)); let response_block_number = BlockNumber::Number(U64::from(200)); @@ -728,7 +500,7 @@ mod tests { ] }"#.to_string()) .start(); - let subject = make_blockchain_interface_web3(Some(port)); + let subject = make_blockchain_interface_web3(port); let contract_address = subject.chain.rec().contract; let start_block = BlockNumber::Number(U64::from(100)); let response_block_number = BlockNumber::Number(U64::from(200)); 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 930ed9ba3..2909cce3a 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs @@ -1,10 +1,9 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -mod batch_payable_tools; pub mod lower_level_interface_web3; use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; -use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainError; -use crate::blockchain::blockchain_interface::data_structures::BlockchainTransaction; +use crate::blockchain::blockchain_interface::data_structures::errors::{BlockchainError, 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; use crate::blockchain::blockchain_interface::{BlockchainAgentBuildError, BlockchainInterface}; @@ -15,11 +14,14 @@ use masq_lib::blockchains::chains::Chain; use masq_lib::logger::Logger; use std::convert::{From, TryInto}; use std::fmt::Debug; +use actix::Recipient; use ethereum_types::U64; use web3::transports::{EventLoopHandle, Http}; -use web3::types::{Address, BlockNumber, Log, H256, U256, FilterBuilder}; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::LowBlockchainIntWeb3; -use crate::blockchain::blockchain_interface_utils::{create_blockchain_agent_web3, BlockchainAgentFutureResult}; +use web3::types::{Address, BlockNumber, Log, H256, U256, FilterBuilder, TransactionReceipt}; +use crate::accountant::db_access_objects::payable_dao::PayableAccount; +use crate::blockchain::blockchain_bridge::PendingPayableFingerprintSeeds; +use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{LowBlockchainIntWeb3, TransactionReceiptResult}; +use crate::blockchain::blockchain_interface_utils::{dynamically_create_blockchain_agent_web3, send_payables_within_batch, BlockchainAgentFutureResult}; const CONTRACT_ABI: &str = indoc!( r#"[{ @@ -57,7 +59,7 @@ restart the Node with a value for blockchain-service-url"; pub struct BlockchainInterfaceWeb3 { pub logger: Logger, chain: Chain, - gas_limit_const_part: u64, + gas_limit_const_part: u128, // This must not be dropped for Web3 requests to be completed _event_loop_handle: EventLoopHandle, transport: Http, @@ -79,6 +81,13 @@ impl BlockchainInterface for BlockchainInterfaceWeb3 { self.chain } + fn lower_interface(&self) -> Box { + Box::new(LowBlockchainIntWeb3::new( + self.transport.clone(), + self.contract_address(), + )) + } + fn retrieve_transactions( &self, start_block: BlockNumber, @@ -87,9 +96,9 @@ impl BlockchainInterface for BlockchainInterfaceWeb3 { ) -> Box> { let lower_level_interface = self.lower_interface(); let logger = self.logger.clone(); - let contract_address = lower_level_interface.get_contract().address(); + let contract_address = lower_level_interface.get_contract_address(); let num_chain_id = self.chain.rec().num_chain_id; - return Box::new( + Box::new( lower_level_interface.get_block_number().then(move |response_block_number_result| { let response_block_number = match response_block_number_result { Ok(block_number) => { @@ -123,7 +132,8 @@ impl BlockchainInterface for BlockchainInterfaceWeb3 { .build(); lower_level_interface.get_transaction_logs(filter) .then(move |logs| { - debug!(logger, "Transaction retrieval completed: {:?}", logs); + trace!(logger, "Transaction logs retrieval completed: {:?}", logs); + future::result::( match logs { Ok(logs) => { @@ -133,18 +143,18 @@ impl BlockchainInterface for BlockchainInterfaceWeb3 { }, ) }) - }, + }, ) - ); + ) } fn build_blockchain_agent( &self, - // TODO: Change wallet to address in the future consuming_wallet: Wallet, ) -> Box, Error = BlockchainAgentBuildError>> { let wallet_address = consuming_wallet.address(); let gas_limit_const_part = self.gas_limit_const_part; + // TODO: Would it be better to wrap these 3 calls into a single batch call? let get_gas_price = self.lower_interface().get_gas_price(); let get_transaction_fee_balance = self .lower_interface() @@ -152,7 +162,6 @@ impl BlockchainInterface for BlockchainInterfaceWeb3 { let get_service_fee_balance = self .lower_interface() .get_service_fee_balance(wallet_address); - let get_transaction_id = self.lower_interface().get_transaction_id(wallet_address); Box::new( get_gas_price @@ -168,38 +177,95 @@ impl BlockchainInterface for BlockchainInterfaceWeb3 { BlockchainAgentBuildError::ServiceFeeBalance(wallet_address, e) }) .and_then(move |masq_token_balance| { - get_transaction_id - .map_err(move |e| { - BlockchainAgentBuildError::TransactionID( - wallet_address, - e, - ) - }) - .and_then(move |pending_transaction_id| { - let blockchain_agent_future_result = - BlockchainAgentFutureResult { - gas_price_wei, - transaction_fee_balance, - masq_token_balance, - pending_transaction_id, - }; - Ok(create_blockchain_agent_web3( - gas_limit_const_part, - blockchain_agent_future_result, - consuming_wallet, - )) - }) + let blockchain_agent_future_result = + BlockchainAgentFutureResult { + gas_price_wei, + transaction_fee_balance, + masq_token_balance, + }; + Ok(dynamically_create_blockchain_agent_web3( + gas_limit_const_part, + blockchain_agent_future_result, + consuming_wallet, + )) }) }) }), ) } - fn lower_interface(&self) -> Box { - Box::new(LowBlockchainIntWeb3::new( - self.transport.clone(), - self.contract_address(), - )) + fn process_transaction_receipts( + &self, + transaction_hashes: Vec, + ) -> Box, Error = BlockchainError>> { + Box::new( + self.lower_interface() + .get_transaction_receipt_in_batch(transaction_hashes) + .map_err(|e| e) + .and_then(move |batch_response| { + Ok(batch_response + .into_iter() + .map(|response| match response { + Ok(result) => { + match serde_json::from_value::(result) { + Ok(receipt) => match receipt.status { + None => TransactionReceiptResult::NotPresent, + Some(status) => { + if status == U64::from(1) { + TransactionReceiptResult::Found(receipt) + } else { + TransactionReceiptResult::TransactionFailed(receipt) + } + } + }, + Err(e) => { + if e.to_string().contains("invalid type: null") { + TransactionReceiptResult::NotPresent + } else { + TransactionReceiptResult::Error(e.to_string()) + } + } + } + } + Err(e) => TransactionReceiptResult::Error(e.to_string()), + }) + .collect::>()) + }), + ) + } + + fn submit_payables_in_batch( + &self, + logger: Logger, + chain: Chain, + agent: Box, + fingerprints_recipient: Recipient, + affordable_accounts: Vec, + ) -> Box, Error = PayableTransactionError>> + { + let consuming_wallet = agent.consuming_wallet().clone(); + let web3_batch = self.lower_interface().get_web3_batch(); + let get_transaction_id = self + .lower_interface() + .get_transaction_id(consuming_wallet.address()); + let gas_price_wei = agent.agreed_fee_per_computation_unit(); + + Box::new( + get_transaction_id + .map_err(PayableTransactionError::TransactionID) + .and_then(move |pending_nonce| { + send_payables_within_batch( + &logger, + chain, + &web3_batch, + consuming_wallet, + gas_price_wei, + pending_nonce, + fingerprints_recipient, + affordable_accounts, + ) + }), + ) } } @@ -222,7 +288,7 @@ impl BlockchainInterfaceWeb3 { } } - pub fn web3_gas_limit_const_part(chain: Chain) -> u64 { + pub fn web3_gas_limit_const_part(chain: Chain) -> u128 { match chain { Chain::EthMainnet | Chain::EthRopsten | Chain::Dev => 55_000, Chain::PolyMainnet | Chain::PolyAmoy => 70_000, @@ -298,7 +364,7 @@ impl BlockchainInterfaceWeb3 { ); Ok(RetrievedBlockchainTransactions { - new_start_block: 1u64 + transaction_max_block_number, + new_start_block: transaction_max_block_number, transactions, }) } @@ -309,7 +375,6 @@ impl BlockchainInterfaceWeb3 { mod tests { use super::*; use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::agent_web3::WEB3_MAXIMAL_GAS_LIMIT_MARGIN; - use crate::blockchain::bip32::Bip32EncryptionKeyProvider; use crate::blockchain::blockchain_interface::blockchain_interface_web3::{ BlockchainInterfaceWeb3, CONTRACT_ABI, REQUESTS_IN_PARALLEL, TRANSACTION_LITERAL, TRANSFER_METHOD_ID, @@ -320,12 +385,13 @@ mod tests { BlockchainAgentBuildError, BlockchainError, BlockchainInterface, RetrievedBlockchainTransactions, }; - use crate::blockchain::blockchain_interface_utils::calculate_fallback_start_block_number; - use crate::blockchain::test_utils::{all_chains, make_blockchain_interface_web3}; + use crate::blockchain::test_utils::{ + all_chains, make_blockchain_interface_web3, ReceiptResponseBuilder, + }; use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; use crate::sub_lib::wallet::Wallet; use crate::test_utils::make_paying_wallet; - use crate::test_utils::{make_wallet, TestRawTransaction}; + use crate::test_utils::make_wallet; use ethsign_crypto::Keccak256; use futures::Future; use indoc::indoc; @@ -334,12 +400,10 @@ mod tests { use masq_lib::test_utils::mock_blockchain_client_server::MBCSBuilder; use masq_lib::test_utils::utils::TEST_DEFAULT_CHAIN; use masq_lib::utils::find_free_port; - use serde_derive::Deserialize; use std::net::Ipv4Addr; use std::str::FromStr; use web3::transports::Http; - use web3::types::{BlockNumber, Bytes, TransactionParameters, H256, U256}; - use web3::Web3; + use web3::types::{BlockNumber, H2048, H256, U256}; #[test] fn constants_are_correct() { @@ -373,12 +437,16 @@ mod tests { assert_eq!(TRANSACTION_LITERAL, transaction_literal_expected); assert_eq!(TRANSFER_METHOD_ID, [0xa9, 0x05, 0x9c, 0xbb]); assert_eq!(REQUESTS_IN_PARALLEL, 1); + assert_eq!( + TRANSFER_METHOD_ID, + "transfer(address,uint256)".keccak256()[0..4], + ); } #[test] fn blockchain_interface_web3_can_return_contract() { all_chains().iter().for_each(|chain| { - let mut subject = make_blockchain_interface_web3(None); + let mut subject = make_blockchain_interface_web3(find_free_port()); subject.chain = *chain; assert_eq!(subject.contract_address(), chain.rec().contract) @@ -431,7 +499,7 @@ mod tests { }"#.to_string() ) .start(); - let subject = make_blockchain_interface_web3(Some(port)); + let subject = make_blockchain_interface_web3(port); let end_block_nbr = 1024u64; let result = subject @@ -446,7 +514,7 @@ mod tests { assert_eq!( result, RetrievedBlockchainTransactions { - new_start_block: 0x4be664, + new_start_block: 0x4be663, transactions: vec![ BlockchainTransaction { block_number: 0x4be663, @@ -463,6 +531,24 @@ mod tests { ] } ) + + // TODO: GH-543: Improve MBCS so we can confirm the calls we make are the correct ones. + // Example of older code + // let requests = test_server.requests_so_far(); + // let bodies: Vec = requests + // .into_iter() + // .map(|request| serde_json::from_slice(&request.body()).unwrap()) + // .map(|b: Value| serde_json::to_string(&b).unwrap()) + // .collect(); + // let expected_body_prefix = r#"[{"id":0,"jsonrpc":"2.0","method":"eth_blockNumber","params":[]},{"id":1,"jsonrpc":"2.0","method":"eth_getLogs","params":[{"address":"0x384dec25e03f94931767ce4c3556168468ba24c3","fromBlock":"0x2a","toBlock":"0x400","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",null,"0x000000000000000000000000"#; + // let expected_body_suffix = r#""]}]}]"#; + // let expected_body = format!( + // "{}{}{}", + // expected_body_prefix, + // &to[2..], + // expected_body_suffix + // ); + // assert_eq!(bodies, vec!(expected_body)); } #[test] @@ -474,7 +560,7 @@ mod tests { .response("0x178def".to_string(), 2) .response(empty_transactions_result, 2) .start(); - let subject = make_blockchain_interface_web3(Some(port)); + let subject = make_blockchain_interface_web3(port); let end_block_nbr = 1024u64; let result = subject @@ -488,31 +574,36 @@ mod tests { assert_eq!( result, Ok(RetrievedBlockchainTransactions { - new_start_block: 1543664, + new_start_block: 1543663, transactions: vec![] }) ); + + // TODO: GH-543: Improve MBCS so we can confirm the calls we make are the correct ones. + // Example of older code + // let requests = test_server.requests_so_far(); + // let bodies: Vec = requests + // .into_iter() + // .map(|request| serde_json::from_slice(&request.body()).unwrap()) + // .map(|b: Value| serde_json::to_string(&b).unwrap()) + // .collect(); + // let expected_body_prefix = r#"[{"id":0,"jsonrpc":"2.0","method":"eth_blockNumber","params":[]},{"id":1,"jsonrpc":"2.0","method":"eth_getLogs","params":[{"address":"0x384dec25e03f94931767ce4c3556168468ba24c3","fromBlock":"0x2a","toBlock":"0x400","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",null,"0x000000000000000000000000"#; + // let expected_body_suffix = r#""]}]}]"#; + // let expected_body = format!( + // "{}{}{}", + // expected_body_prefix, + // &to[2..], + // expected_body_suffix + // ); + // assert_eq!(bodies, vec!(expected_body)); } #[test] #[should_panic(expected = "No address for an uninitialized wallet!")] - fn blockchain_interface_web3_retrieve_transactions_returns_an_error_if_the_to_address_is_invalid( - ) { - let port = find_free_port(); - let subject = make_blockchain_interface_web3(Some(port)); + fn retrieving_address_of_uninitialised_wallet_panics() { + let subject = Wallet::new("0x3f69f9efd4f2592fd70beecd9dce71c472fc"); - let result = subject - .retrieve_transactions( - BlockNumber::Number(42u64.into()), - 555u64, - Wallet::new("0x3f69f9efd4f2592fd70beecd9dce71c472fc").address(), - ) - .wait(); - - assert_eq!( - result.expect_err("Expected an Err, got Ok"), - BlockchainError::InvalidAddress - ); + subject.address(); } #[test] @@ -523,7 +614,7 @@ mod tests { .response("0x178def", 1) .raw_response(r#"{"jsonrpc":"2.0","id":3,"result":[{"address":"0xcd6c588e005032dd882cd43bf53a32129be81302","blockHash":"0x1a24b9169cbaec3f6effa1f600b70c7ab9e8e86db44062b49132a4415d26732a","blockNumber":"0x4be663","data":"0x0000000000000000000000000000000000000000000000056bc75e2d63100000","logIndex":"0x0","removed":false,"topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"],"transactionHash":"0x955cec6ac4f832911ab894ce16aa22c3003f46deff3f7165b32700d2f5ff0681","transactionIndex":"0x0"}]}"#.to_string()) .start(); - let subject = make_blockchain_interface_web3(Some(port)); + let subject = make_blockchain_interface_web3(port); let result = subject .retrieve_transactions( @@ -549,7 +640,7 @@ mod tests { .response("0x178def", 1) .raw_response(r#"{"jsonrpc":"2.0","id":3,"result":[{"address":"0xcd6c588e005032dd882cd43bf53a32129be81302","blockHash":"0x1a24b9169cbaec3f6effa1f600b70c7ab9e8e86db44062b49132a4415d26732a","blockNumber":"0x4be663","data":"0x0000000000000000000000000000000000000000000000056bc75e2d6310000001","logIndex":"0x0","removed":false,"topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000003f69f9efd4f2592fd70be8c32ecd9dce71c472fc","0x000000000000000000000000adc1853c7859369639eb414b6342b36288fe6092"],"transactionHash":"0x955cec6ac4f832911ab894ce16aa22c3003f46deff3f7165b32700d2f5ff0681","transactionIndex":"0x0"}]}"#.to_string()) .start(); - let subject = make_blockchain_interface_web3(Some(port)); + let subject = make_blockchain_interface_web3(port); let result = subject .retrieve_transactions( @@ -569,7 +660,7 @@ mod tests { ) { let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port) - .response("0x178def", 1) + .response("0x400", 1) .raw_response(r#"{"jsonrpc":"2.0","id":2,"result":[{"address":"0xcd6c588e005032dd882cd43bf53a32129be81302","blockHash":"0x1a24b9169cbaec3f6effa1f600b70c7ab9e8e86db44062b49132a4415d26732a","data":"0x0000000000000000000000000000000000000000000000000010000000000000","logIndex":"0x0","removed":false,"topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000003f69f9efd4f2592fd70be8c32ecd9dce71c472fc","0x000000000000000000000000adc1853c7859369639eb414b6342b36288fe6092"],"transactionHash":"0x955cec6ac4f832911ab894ce16aa22c3003f46deff3f7165b32700d2f5ff0681","transactionIndex":"0x0"}]}"#.to_string()) .start(); init_test_logging(); @@ -596,7 +687,7 @@ mod tests { assert_eq!( result, Ok(RetrievedBlockchainTransactions { - new_start_block: 1543664, + new_start_block: end_block_nbr, transactions: vec![] }) ); @@ -614,10 +705,11 @@ mod tests { .response("trash", 1) .raw_response(r#"{"jsonrpc":"2.0","id":2,"result":[{"address":"0xcd6c588e005032dd882cd43bf53a32129be81302","blockHash":"0x1a24b9169cbaec3f6effa1f600b70c7ab9e8e86db44062b49132a4415d26732a","data":"0x0000000000000000000000000000000000000000000000000010000000000000","logIndex":"0x0","removed":false,"topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000003f69f9efd4f2592fd70be8c32ecd9dce71c472fc","0x000000000000000000000000adc1853c7859369639eb414b6342b36288fe6092"],"transactionHash":"0x955cec6ac4f832911ab894ce16aa22c3003f46deff3f7165b32700d2f5ff0681","transactionIndex":"0x0"}]}"#.to_string()) .start(); - let subject = make_blockchain_interface_web3(Some(port)); + let subject = make_blockchain_interface_web3(port); let start_block_nbr = 42u64; let start_block = BlockNumber::Number(start_block_nbr.into()); - let fallback_number = calculate_fallback_start_block_number(start_block_nbr, u64::MAX); + // let fallback_number = BlockchainBridge::calculate_fallback_start_block_number(start_block_nbr, u64::MAX); + let fallback_number = start_block_nbr + 1; let result = subject .retrieve_transactions( @@ -630,11 +722,10 @@ mod tests { .wait(); let expected_fallback_start_block = start_block_nbr + 1u64; - assert_eq!( result, Ok(RetrievedBlockchainTransactions { - new_start_block: 1 + expected_fallback_start_block, + new_start_block: expected_fallback_start_block, transactions: vec![] }) ); @@ -653,60 +744,41 @@ mod tests { "0x000000000000000000000000000000000000000000000000000000000000FFFF".to_string(), // 65535 0, ) - // transaction_id - .response("0x23".to_string(), 1) .start(); let chain = Chain::PolyMainnet; let wallet = make_wallet("abc"); - let subject = make_blockchain_interface_web3(Some(port)); - let transaction_fee_balance = U256::from(65_520); - let masq_balance = U256::from(65_535); - let transaction_id = U256::from(35); + let subject = make_blockchain_interface_web3(port); let result = subject .build_blockchain_agent(wallet.clone()) .wait() .unwrap(); - let expected_gas_price_gwei = 2; + let expected_transaction_fee_balance = U256::from(65_520); + let expected_masq_balance = U256::from(65_535); + let expected_gas_price_wei = 1_000_000_000; assert_eq!(result.consuming_wallet(), &wallet); - assert_eq!(result.pending_transaction_id(), transaction_id); assert_eq!( result.consuming_wallet_balances(), ConsumingWalletBalances { - transaction_fee_balance_in_minor_units: transaction_fee_balance, - masq_token_balance_in_minor_units: masq_balance + transaction_fee_balance_in_minor_units: expected_transaction_fee_balance, + masq_token_balance_in_minor_units: expected_masq_balance } ); assert_eq!( result.agreed_fee_per_computation_unit(), - expected_gas_price_gwei + expected_gas_price_wei ); let expected_fee_estimation = (3 * (BlockchainInterfaceWeb3::web3_gas_limit_const_part(chain) + WEB3_MAXIMAL_GAS_LIMIT_MARGIN) - * expected_gas_price_gwei) as u128; + * expected_gas_price_wei) as u128; assert_eq!( result.estimated_transaction_fee_total(3), expected_fee_estimation ) } - #[test] - fn build_of_the_blockchain_agent_fails_on_fetching_gas_price() { - let port = find_free_port(); - let _blockchain_client_server = MBCSBuilder::new(port).start(); - let wallet = make_wallet("abc"); - let subject = make_blockchain_interface_web3(Some(port)); - - let err = subject.build_blockchain_agent(wallet).wait().err().unwrap(); - - let expected_err = BlockchainAgentBuildError::GasPrice(QueryFailed( - "Transport error: Error(IncompleteMessage)".to_string(), - )); - assert_eq!(err, expected_err) - } - fn build_of_the_blockchain_agent_fails_on_blockchain_interface_error( port: u16, expected_err_factory: F, @@ -714,7 +786,7 @@ mod tests { F: FnOnce(&Wallet) -> BlockchainAgentBuildError, { let wallet = make_wallet("bcd"); - let subject = make_blockchain_interface_web3(Some(port)); + let subject = make_blockchain_interface_web3(port); let result = subject.build_blockchain_agent(wallet.clone()).wait(); let err = match result { Err(e) => e, @@ -724,6 +796,21 @@ mod tests { assert_eq!(err, expected_err) } + #[test] + fn build_of_the_blockchain_agent_fails_on_fetching_gas_price() { + let port = find_free_port(); + let _blockchain_client_server = MBCSBuilder::new(port).start(); + let wallet = make_wallet("abc"); + let subject = make_blockchain_interface_web3(port); + + let err = subject.build_blockchain_agent(wallet).wait().err().unwrap(); + + let expected_err = BlockchainAgentBuildError::GasPrice(QueryFailed( + "Transport error: Error(IncompleteMessage)".to_string(), + )); + assert_eq!(err, expected_err) + } + #[test] fn build_of_the_blockchain_agent_fails_on_transaction_fee_balance() { let port = find_free_port(); @@ -768,29 +855,114 @@ mod tests { } #[test] - fn build_of_the_blockchain_agent_fails_on_transaction_id() { + fn process_transaction_receipts_works() { let port = find_free_port(); + let tx_hash_1 = + H256::from_str("a128f9ca1e705cc20a936a24a7fa1df73bad6e0aaf58e8e6ffcc154a7cff6e0e") + .unwrap(); + let tx_hash_2 = + H256::from_str("a128f9ca1e705cc20a936a24a7fa1df73bad6e0aaf58e8e6ffcc154a7cff6e0f") + .unwrap(); + let tx_hash_3 = + H256::from_str("a128f9ca1e705cc20a936a24a7fa1df73bad6e0aaf58e8e6ffcc154a7cff6e0a") + .unwrap(); + let tx_hash_4 = + H256::from_str("a128f9ca1e705cc20a936a24a7fa1df73bad6e0aaf58e8e6ffcc154a7cff6e0b") + .unwrap(); + let tx_hash_5 = + H256::from_str("a128f9ca1e705cc20a936a24a7fa1df73bad6e0aaf58e8e6ffcc154a7cff6e0c") + .unwrap(); + let tx_hash_6 = + H256::from_str("a128f9ca1e705cc20a936a24a7fa1df73bad6e0aaf58e8e6ffcc154a7cff6e0d") + .unwrap(); + let tx_hash_vec = vec![ + tx_hash_1, tx_hash_2, tx_hash_3, tx_hash_4, tx_hash_5, tx_hash_6, + ]; + let block_hash = + H256::from_str("6d0abccae617442c26104c2bc63d1bc05e1e002e555aec4ab62a46e826b18f18") + .unwrap(); + let block_number = U64::from_str("b0328d").unwrap(); + let cumulative_gas_used = U256::from_str("60ef").unwrap(); + let gas_used = U256::from_str("60ef").unwrap(); + let status = U64::from(1); + let status_failed = U64::from(0); + let tx_receipt_response_not_present = ReceiptResponseBuilder::default() + .transaction_hash(tx_hash_4) + .build(); + let tx_receipt_response_failed = ReceiptResponseBuilder::default() + .transaction_hash(tx_hash_5) + .status(status_failed) + .build(); + let tx_receipt_response_success = ReceiptResponseBuilder::default() + .transaction_hash(tx_hash_6) + .block_hash(block_hash) + .block_number(block_number) + .cumulative_gas_used(cumulative_gas_used) + .gas_used(gas_used) + .status(status) + .build(); let _blockchain_client_server = MBCSBuilder::new(port) - .response("0x3B9ACA00".to_string(), 0) - .response("0xFFF0".to_string(), 0) - .response( - "0x000000000000000000000000000000000000000000000000000000000000FFFF".to_string(), - 0, + .begin_batch() + .err_response( + 429, + "The requests per second (RPS) of your requests are higher than your plan allows." + .to_string(), + 7, ) + .raw_response(r#"{ "jsonrpc": "2.0", "id": 1, "result": null }"#.to_string()) + .response("trash".to_string(), 0) + .raw_response(tx_receipt_response_not_present) + .raw_response(tx_receipt_response_failed) + .raw_response(tx_receipt_response_success) + .end_batch() .start(); + let subject = make_blockchain_interface_web3(port); - let expected_err_factory = |wallet: &Wallet| { - BlockchainAgentBuildError::TransactionID( - wallet.address(), - BlockchainError::QueryFailed( - "Transport error: Error(IncompleteMessage) for wallet 0x0000…6364".to_string(), - ), - ) - }; + let result = subject + .process_transaction_receipts(tx_hash_vec) + .wait() + .unwrap(); - build_of_the_blockchain_agent_fails_on_blockchain_interface_error( - port, - expected_err_factory, + assert_eq!(result[0], TransactionReceiptResult::Error("RPC error: Error { code: ServerError(429), message: \"The requests per second (RPS) of your requests are higher than your plan allows.\", data: None }".to_string())); + assert_eq!(result[1], TransactionReceiptResult::NotPresent); + assert_eq!( + result[2], + TransactionReceiptResult::Error( + "invalid type: string \"trash\", expected struct Receipt".to_string() + ) + ); + assert_eq!(result[3], TransactionReceiptResult::NotPresent); + assert_eq!( + result[4], + TransactionReceiptResult::TransactionFailed(TransactionReceipt { + transaction_hash: tx_hash_5, + transaction_index: Default::default(), + block_hash: None, + block_number: None, + cumulative_gas_used: U256::from(0), + gas_used: None, + contract_address: None, + logs: vec![], + status: Some(status_failed), + root: None, + logs_bloom: H2048::default() + }) + ); + assert_eq!( + result[5], + TransactionReceiptResult::Found(TransactionReceipt { + transaction_hash: tx_hash_6, + transaction_index: Default::default(), + block_hash: Some(block_hash), + block_number: Some(block_number), + cumulative_gas_used, + gas_used: Some(gas_used), + contract_address: None, + logs: vec![], + status: Some(status), + root: None, + logs_bloom: H2048::default() + }) ); } @@ -812,169 +984,4 @@ mod tests { assert_eq!(Subject::web3_gas_limit_const_part(Chain::PolyAmoy), 70_000); assert_eq!(Subject::web3_gas_limit_const_part(Chain::Dev), 55_000); } - - //an adapted test from old times when we had our own signing method - //I don't have data for the new chains so I omit them in this kind of tests - #[test] - fn signs_various_transactions_for_eth_mainnet() { - let signatures = &[ - &[ - 248, 108, 9, 133, 4, 168, 23, 200, 0, 130, 82, 8, 148, 53, 53, 53, 53, 53, 53, 53, - 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 136, 13, 224, 182, 179, 167, - 100, 0, 0, 128, 37, 160, 40, 239, 97, 52, 11, 217, 57, 188, 33, 149, 254, 83, 117, - 103, 134, 96, 3, 225, 161, 93, 60, 113, 255, 99, 225, 89, 6, 32, 170, 99, 98, 118, - 160, 103, 203, 233, 216, 153, 127, 118, 26, 236, 183, 3, 48, 75, 56, 0, 204, 245, - 85, 201, 243, 220, 100, 33, 75, 41, 127, 177, 150, 106, 59, 109, 131, - ][..], - &[ - 248, 106, 128, 134, 213, 86, 152, 55, 36, 49, 131, 30, 132, 128, 148, 240, 16, 159, - 200, 223, 40, 48, 39, 182, 40, 92, 200, 137, 245, 170, 98, 78, 172, 31, 85, 132, - 59, 154, 202, 0, 128, 37, 160, 9, 235, 182, 202, 5, 122, 5, 53, 214, 24, 100, 98, - 188, 11, 70, 91, 86, 28, 148, 162, 149, 189, 176, 98, 31, 193, 146, 8, 171, 20, - 154, 156, 160, 68, 15, 253, 119, 92, 233, 26, 131, 58, 180, 16, 119, 114, 4, 213, - 52, 26, 111, 159, 169, 18, 22, 166, 243, 238, 44, 5, 31, 234, 106, 4, 40, - ][..], - &[ - 248, 117, 128, 134, 9, 24, 78, 114, 160, 0, 130, 39, 16, 128, 128, 164, 127, 116, - 101, 115, 116, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 96, 0, 87, 38, 160, 122, 155, 12, 58, 133, 108, 183, 145, 181, - 210, 141, 44, 236, 17, 96, 40, 55, 87, 204, 250, 142, 83, 122, 168, 250, 5, 113, - 172, 203, 5, 12, 181, 160, 9, 100, 95, 141, 167, 178, 53, 101, 115, 131, 83, 172, - 199, 242, 208, 96, 246, 121, 25, 18, 211, 89, 60, 94, 165, 169, 71, 3, 176, 157, - 167, 50, - ][..], - ]; - assert_signature(Chain::EthMainnet, signatures) - } - - // Adapted test from old times when we had our own signing method. - // Don't have data for new chains, so I omit them in this kind of tests - #[test] - fn signs_various_transactions_for_ropsten() { - let signatures = &[ - &[ - 248, 108, 9, 133, 4, 168, 23, 200, 0, 130, 82, 8, 148, 53, 53, 53, 53, 53, 53, 53, - 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 136, 13, 224, 182, 179, 167, - 100, 0, 0, 128, 41, 160, 8, 220, 80, 201, 100, 41, 178, 35, 151, 227, 210, 85, 27, - 41, 27, 82, 217, 176, 64, 92, 205, 10, 195, 169, 66, 91, 213, 199, 124, 52, 3, 192, - 160, 94, 220, 102, 179, 128, 78, 150, 78, 230, 117, 10, 10, 32, 108, 241, 50, 19, - 148, 198, 6, 147, 110, 175, 70, 157, 72, 31, 216, 193, 229, 151, 115, - ][..], - &[ - 248, 106, 128, 134, 213, 86, 152, 55, 36, 49, 131, 30, 132, 128, 148, 240, 16, 159, - 200, 223, 40, 48, 39, 182, 40, 92, 200, 137, 245, 170, 98, 78, 172, 31, 85, 132, - 59, 154, 202, 0, 128, 41, 160, 186, 65, 161, 205, 173, 93, 185, 43, 220, 161, 63, - 65, 19, 229, 65, 186, 247, 197, 132, 141, 184, 196, 6, 117, 225, 181, 8, 81, 198, - 102, 150, 198, 160, 112, 126, 42, 201, 234, 236, 168, 183, 30, 214, 145, 115, 201, - 45, 191, 46, 3, 113, 53, 80, 203, 164, 210, 112, 42, 182, 136, 223, 125, 232, 21, - 205, - ][..], - &[ - 248, 117, 128, 134, 9, 24, 78, 114, 160, 0, 130, 39, 16, 128, 128, 164, 127, 116, - 101, 115, 116, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 96, 0, 87, 41, 160, 146, 204, 57, 32, 218, 236, 59, 94, 106, 72, - 174, 211, 223, 160, 122, 186, 126, 44, 200, 41, 222, 117, 117, 177, 189, 78, 203, - 8, 172, 155, 219, 66, 160, 83, 82, 37, 6, 243, 61, 188, 102, 176, 132, 102, 74, - 111, 180, 105, 33, 122, 106, 109, 73, 180, 65, 10, 117, 175, 190, 19, 196, 17, 128, - 193, 75, - ][..], - ]; - assert_signature(Chain::EthRopsten, signatures) - } - - #[derive(Deserialize)] - struct Signing { - signed: Vec, - private_key: H256, - } - - fn assert_signature(chain: Chain, slice_of_slices: &[&[u8]]) { - let first_part_tx_1 = r#"[{"nonce": "0x9", "gasPrice": "0x4a817c800", "gasLimit": "0x5208", "to": "0x3535353535353535353535353535353535353535", "value": "0xde0b6b3a7640000", "data": []}, {"private_key": "0x4646464646464646464646464646464646464646464646464646464646464646", "signed": "#; - let first_part_tx_2 = r#"[{"nonce": "0x0", "gasPrice": "0xd55698372431", "gasLimit": "0x1e8480", "to": "0xF0109fC8DF283027b6285cc889F5aA624EaC1F55", "value": "0x3b9aca00", "data": []}, {"private_key": "0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318", "signed": "#; - let first_part_tx_3 = r#"[{"nonce": "0x00", "gasPrice": "0x09184e72a000", "gasLimit": "0x2710", "to": null, "value": "0x00", "data": [127,116,101,115,116,50,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,87]}, {"private_key": "0xe331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109", "signed": "#; - fn compose(first_part: &str, slice: &[u8]) -> String { - let third_part_jrc = "}]"; - format!("{}{:?}{}", first_part, slice, third_part_jrc) - } - let all_transactions = format!( - "[{}]", - vec![first_part_tx_1, first_part_tx_2, first_part_tx_3] - .iter() - .zip(slice_of_slices.iter()) - .zip(0usize..2) - .fold(String::new(), |so_far, actual| [ - so_far, - compose(actual.0 .0, actual.0 .1) - ] - .join(if actual.1 == 0 { "" } else { ", " })) - ); - let txs: Vec<(TestRawTransaction, Signing)> = - serde_json::from_str(&all_transactions).unwrap(); - let constant_parts = &[ - &[ - 248u8, 108, 9, 133, 4, 168, 23, 200, 0, 130, 82, 8, 148, 53, 53, 53, 53, 53, 53, - 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 136, 13, 224, 182, 179, - 167, 100, 0, 0, 128, - ][..], - &[ - 248, 106, 128, 134, 213, 86, 152, 55, 36, 49, 131, 30, 132, 128, 148, 240, 16, 159, - 200, 223, 40, 48, 39, 182, 40, 92, 200, 137, 245, 170, 98, 78, 172, 31, 85, 132, - 59, 154, 202, 0, 128, - ][..], - &[ - 248, 117, 128, 134, 9, 24, 78, 114, 160, 0, 130, 39, 16, 128, 128, 164, 127, 116, - 101, 115, 116, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 96, 0, 87, - ][..], - ]; - - let subject = make_blockchain_interface_web3(None); - let lengths_of_constant_parts: Vec = - constant_parts.iter().map(|part| part.len()).collect(); - for (((tx, signed), length), constant_part) in txs - .iter() - .zip(lengths_of_constant_parts) - .zip(constant_parts) - { - let secret = Wallet::from( - Bip32EncryptionKeyProvider::from_raw_secret(&signed.private_key.0.as_ref()) - .unwrap(), - ) - .prepare_secp256k1_secret() - .unwrap(); - let tx_params = from_raw_transaction_to_transaction_parameters(tx, chain); - let web3 = Web3::new(subject.transport.clone()); - let sign = web3 - .accounts() - .sign_transaction(tx_params, &secret) - .wait() - .unwrap(); - let signed_data_bytes = sign.raw_transaction.0; - assert_eq!(signed_data_bytes, signed.signed); - assert_eq!(signed_data_bytes[..length], **constant_part) - } - } - - fn from_raw_transaction_to_transaction_parameters( - raw_transaction: &TestRawTransaction, - chain: Chain, - ) -> TransactionParameters { - TransactionParameters { - nonce: Some(raw_transaction.nonce), - to: raw_transaction.to, - gas: raw_transaction.gas_limit, - gas_price: Some(raw_transaction.gas_price), - value: raw_transaction.value, - data: Bytes(raw_transaction.data.clone()), - chain_id: Some(chain.rec().num_chain_id), - } - } - - #[test] - fn hash_the_smart_contract_transfer_function_signature() { - assert_eq!( - "transfer(address,uint256)".keccak256()[0..4], - TRANSFER_METHOD_ID, - ); - } } diff --git a/node/src/blockchain/blockchain_interface/data_structures/errors.rs b/node/src/blockchain/blockchain_interface/data_structures/errors.rs index 8b4e1f914..3084accfb 100644 --- a/node/src/blockchain/blockchain_interface/data_structures/errors.rs +++ b/node/src/blockchain/blockchain_interface/data_structures/errors.rs @@ -81,7 +81,6 @@ pub enum BlockchainAgentBuildError { GasPrice(BlockchainError), TransactionFeeBalance(Address, BlockchainError), ServiceFeeBalance(Address, BlockchainError), - TransactionID(Address, BlockchainError), UninitializedBlockchainInterface, } @@ -99,10 +98,6 @@ impl Display for BlockchainAgentBuildError { "masq balance for our earning wallet {:#x} due to {}", address, blockchain_e )), - Self::TransactionID(address, blockchain_e) => Either::Left(format!( - "transaction id for our earning wallet {:#x} due to {}", - address, blockchain_e - )), Self::UninitializedBlockchainInterface => { Either::Right(BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED.to_string()) } @@ -227,7 +222,6 @@ mod tests { wallet.address(), BlockchainError::InvalidAddress, ), - BlockchainAgentBuildError::TransactionID(wallet.address(), BlockchainError::InvalidUrl), BlockchainAgentBuildError::UninitializedBlockchainInterface, ]; @@ -246,8 +240,6 @@ mod tests { wallet 0x0000000000000000000000000000000000616263 due to: Blockchain error: Invalid response", "Blockchain agent construction failed at fetching masq balance for our earning wallet \ 0x0000000000000000000000000000000000616263 due to Blockchain error: Invalid address", - "Blockchain agent construction failed at fetching transaction id for our earning wallet \ - 0x0000000000000000000000000000000000616263 due to Blockchain error: Invalid url", BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED ]) ) diff --git a/node/src/blockchain/blockchain_interface/lower_level_interface.rs b/node/src/blockchain/blockchain_interface/lower_level_interface.rs index 58b75f5f8..6e33d5c00 100644 --- a/node/src/blockchain/blockchain_interface/lower_level_interface.rs +++ b/node/src/blockchain/blockchain_interface/lower_level_interface.rs @@ -1,24 +1,18 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use actix::Recipient; -use crate::blockchain::blockchain_interface::data_structures::errors::{BlockchainError, PayableTransactionError}; -use crate::sub_lib::wallet::Wallet; +use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainError; use ethereum_types::{H256, U64}; use futures::Future; -use web3::contract::Contract; -use web3::transports::Http; -use web3::types::{Address, Filter, Log, TransactionReceipt, U256}; -use masq_lib::blockchains::chains::Chain; -use masq_lib::logger::Logger; -use crate::accountant::db_access_objects::payable_dao::PayableAccount; -use crate::blockchain::blockchain_bridge::PendingPayableFingerprintSeeds; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::TransactionReceiptResult; -use crate::blockchain::blockchain_interface::data_structures::ProcessedPayableFallible; +use serde_json::Value; +use web3::transports::{Batch, Http}; +use web3::types::{Address, Filter, Log, U256}; +use web3::{Error, Web3}; pub trait LowBlockchainInt { // TODO: GH-495 The data structures in this trait are not generic, will need associated_type_defaults to implement it. // see issue #29661 for more information + // TODO: Address can be a wrapper type fn get_transaction_fee_balance( &self, address: Address, @@ -38,29 +32,17 @@ pub trait LowBlockchainInt { address: Address, ) -> Box>; - fn get_transaction_receipt( - &self, - hash: H256, - ) -> Box, Error = BlockchainError>>; - - fn get_transaction_receipt_batch( + fn get_transaction_receipt_in_batch( &self, hash_vec: Vec, - ) -> Box, Error = BlockchainError>>; + ) -> Box>, Error = BlockchainError>>; - fn get_contract(&self) -> Contract; + fn get_contract_address(&self) -> Address; fn get_transaction_logs( &self, filter: Filter, ) -> Box, Error = BlockchainError>>; - fn submit_payables_in_batch( - &self, - logger: Logger, - chain: Chain, - consuming_wallet: Wallet, - fingerprints_recipient: Recipient, - affordable_accounts: Vec, - ) -> Box, Error = PayableTransactionError>>; + 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 fe6490c22..fda6157ab 100644 --- a/node/src/blockchain/blockchain_interface/mod.rs +++ b/node/src/blockchain/blockchain_interface/mod.rs @@ -3,24 +3,30 @@ pub mod blockchain_interface_web3; pub mod data_structures; pub mod lower_level_interface; -pub mod test_utils; +use actix::Recipient; +use ethereum_types::H256; use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; -use crate::blockchain::blockchain_interface::data_structures::errors::{ - BlockchainAgentBuildError, BlockchainError, -}; -use crate::blockchain::blockchain_interface::data_structures::RetrievedBlockchainTransactions; +use crate::blockchain::blockchain_interface::data_structures::errors::{BlockchainAgentBuildError, BlockchainError, 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; use futures::Future; use masq_lib::blockchains::chains::Chain; use web3::types::{Address, BlockNumber}; +use masq_lib::logger::Logger; +use crate::accountant::db_access_objects::payable_dao::PayableAccount; +use crate::blockchain::blockchain_bridge::PendingPayableFingerprintSeeds; +use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::TransactionReceiptResult; pub trait BlockchainInterface { fn contract_address(&self) -> Address; fn get_chain(&self) -> Chain; + // Initially this lower_interface wasn't wrapped with a box, but under the card GH-744 this design was used to solve lifetime issues + // with the futures. + // The downside to this method is we cant store persistent values, instead its being initialised where ever it being used. fn lower_interface(&self) -> Box; fn retrieve_transactions( @@ -35,5 +41,19 @@ pub trait BlockchainInterface { consuming_wallet: Wallet, ) -> Box, Error = BlockchainAgentBuildError>>; + fn process_transaction_receipts( + &self, + transaction_hashes: Vec, + ) -> Box, Error = BlockchainError>>; + + fn submit_payables_in_batch( + &self, + logger: Logger, + chain: Chain, + agent: Box, + fingerprints_recipient: Recipient, + affordable_accounts: Vec, + ) -> Box, Error = PayableTransactionError>>; + as_any_ref_in_trait!(); } diff --git a/node/src/blockchain/blockchain_interface/test_utils.rs b/node/src/blockchain/blockchain_interface/test_utils.rs deleted file mode 100644 index 824a63a4a..000000000 --- a/node/src/blockchain/blockchain_interface/test_utils.rs +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. - -#![cfg(test)] -use crate::blockchain::blockchain_interface::lower_level_interface::{ - LowBlockchainInt, -}; -use crate::sub_lib::wallet::Wallet; -use std::cell::RefCell; -use std::sync::{Arc, Mutex}; -use actix::Recipient; -use ethereum_types::{H256, U256, U64}; -use futures::Future; -use web3::contract::Contract; -use web3::transports::Http; -use web3::types::{Address, Filter, Log, TransactionReceipt}; -use masq_lib::blockchains::chains::Chain; -use masq_lib::logger::Logger; -use crate::accountant::db_access_objects::payable_dao::PayableAccount; -use crate::blockchain::blockchain_bridge::PendingPayableFingerprintSeeds; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::TransactionReceiptResult; -use crate::blockchain::blockchain_interface::data_structures::errors::{BlockchainError, PayableTransactionError}; -use crate::blockchain::blockchain_interface::data_structures::ProcessedPayableFallible; - -#[derive(Default)] -pub struct LowBlockchainIntMock { - get_transaction_fee_balance_params: Arc>>, - get_transaction_fee_balance_results: RefCell>>, - get_masq_balance_params: Arc>>, - get_masq_balance_results: RefCell>>, - get_block_number_results: RefCell>>, - get_transaction_id_params: Arc>>, - get_transaction_id_results: RefCell>>, -} - -impl LowBlockchainInt for LowBlockchainIntMock { - fn get_transaction_fee_balance( - &self, - _address: Address, - ) -> Box> { - unimplemented!("not needed so far") - } - - fn get_service_fee_balance( - &self, - _address: Address, - ) -> Box> { - unimplemented!("not needed so far") - } - - fn get_gas_price(&self) -> Box> { - unimplemented!("not needed so far") - } - - fn get_block_number(&self) -> Box> { - unimplemented!("not needed so far") - } - - fn get_transaction_id( - &self, - _address: Address, - ) -> Box> { - unimplemented!("not needed so far") - } - - fn get_transaction_receipt( - &self, - _hash: H256, - ) -> Box, Error = BlockchainError>> { - unimplemented!("not needed so far") - } - - fn get_transaction_receipt_batch( - &self, - _hash_vec: Vec, - ) -> Box, Error = BlockchainError>> { - unimplemented!("not needed so far") - } - - fn get_contract(&self) -> Contract { - unimplemented!("not needed so far") - } - - fn get_transaction_logs( - &self, - _filter: Filter, - ) -> Box, Error = BlockchainError>> { - unimplemented!("not needed so far") - } - - fn submit_payables_in_batch( - &self, - _logger: Logger, - _chain: Chain, - _consuming_wallet: Wallet, - _fingerprints_recipient: Recipient, - _affordable_accounts: Vec, - ) -> Box, Error = PayableTransactionError>> - { - unimplemented!("not needed so far") - } -} - -impl LowBlockchainIntMock { - pub fn get_transaction_fee_balance_params(mut self, params: &Arc>>) -> Self { - self.get_transaction_fee_balance_params = params.clone(); - self - } - - pub fn get_transaction_fee_balance_result(self, result: Result) -> Self { - self.get_transaction_fee_balance_results - .borrow_mut() - .push(result); - self - } - - pub fn get_masq_balance_params(mut self, params: &Arc>>) -> Self { - self.get_masq_balance_params = params.clone(); - self - } - - pub fn get_masq_balance_result(self, result: Result) -> Self { - self.get_masq_balance_results.borrow_mut().push(result); - self - } - - pub fn get_block_number_result(self, result: Result) -> Self { - self.get_block_number_results.borrow_mut().push(result); - self - } - - pub fn get_transaction_id_params(mut self, params: &Arc>>) -> Self { - self.get_transaction_id_params = params.clone(); - self - } - - pub fn get_transaction_id_result(self, result: Result) -> Self { - self.get_transaction_id_results.borrow_mut().push(result); - self - } -} diff --git a/node/src/blockchain/blockchain_interface_initializer.rs b/node/src/blockchain/blockchain_interface_initializer.rs index f7d002497..06fbf491b 100644 --- a/node/src/blockchain/blockchain_interface_initializer.rs +++ b/node/src/blockchain/blockchain_interface_initializer.rs @@ -63,7 +63,7 @@ mod tests { fn initialize_web3_interface_works() { let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port) - .response("0x3B9ACA00".to_string(), 0) + .response("0x3B9ACA00".to_string(), 0) // gas_price = 10000000000 .response("0xFF40".to_string(), 0) .response( "0x000000000000000000000000000000000000000000000000000000000000FFFF".to_string(), @@ -84,7 +84,10 @@ mod tests { .unwrap(); assert_eq!(blockchain_agent.consuming_wallet(), &wallet); - assert_eq!(blockchain_agent.agreed_fee_per_computation_unit(), 2); + assert_eq!( + blockchain_agent.agreed_fee_per_computation_unit(), + 1_000_000_000 + ); } #[test] diff --git a/node/src/blockchain/blockchain_interface_utils.rs b/node/src/blockchain/blockchain_interface_utils.rs index 44ab853ff..be94f8a2c 100644 --- a/node/src/blockchain/blockchain_interface_utils.rs +++ b/node/src/blockchain/blockchain_interface_utils.rs @@ -1,4 +1,7 @@ -// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. +// Copyright (c) 2024, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +// TODO: GH-744: At the end of the review rename this file to: web3_blockchain_interface_utils.rs +// Or we should move this file into blockchain_interface_web3 use crate::accountant::db_access_objects::payable_dao::PayableAccount; use crate::accountant::db_access_objects::pending_payable_dao::PendingPayable; @@ -6,7 +9,7 @@ use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::agent_w use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; use crate::blockchain::blockchain_bridge::PendingPayableFingerprintSeeds; use crate::blockchain::blockchain_interface::blockchain_interface_web3::{ - to_wei, BlockchainInterfaceWeb3, HashAndAmount, TRANSFER_METHOD_ID, + BlockchainInterfaceWeb3, HashAndAmount, TRANSFER_METHOD_ID, }; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; use crate::blockchain::blockchain_interface::data_structures::{ @@ -24,18 +27,15 @@ use std::iter::once; use std::time::SystemTime; use thousands::Separable; use web3::transports::{Batch, Http}; -use web3::types::{Bytes, SignedTransaction, TransactionParameters, H256, U256}; +use web3::types::{Bytes, SignedTransaction, TransactionParameters, U256}; use web3::Error as Web3Error; use web3::Web3; -const GWEI_UNIT: u64 = 1_000_000_000; // 1 Gwei = 1e9 Wei - #[derive(Debug)] pub struct BlockchainAgentFutureResult { pub gas_price_wei: U256, pub transaction_fee_balance: U256, pub masq_token_balance: U256, - pub pending_transaction_id: U256, } pub fn advance_used_nonce(current_nonce: U256) -> U256 { current_nonce @@ -70,7 +70,7 @@ pub fn merged_output_data( .map( |((rpc_result, hash_and_amount), account)| match rpc_result { Ok(_rpc_result) => { - // TODO: This rpc_result should be validated + // TODO: GH-547: This rpc_result should be validated ProcessedPayableFallible::Correct(PendingPayable { recipient_wallet: account.wallet.clone(), hash: hash_and_amount.hash, @@ -86,7 +86,11 @@ pub fn merged_output_data( .collect() } -pub fn transmission_log(chain: Chain, accounts: &[PayableAccount], gas_price: u64) -> String { +pub fn transmission_log( + chain: Chain, + accounts: &[PayableAccount], + gas_price_in_wei: u128, +) -> String { let chain_name = chain .rec() .literal_identifier @@ -99,11 +103,11 @@ pub fn transmission_log(chain: Chain, accounts: &[PayableAccount], gas_price: u6 Paying to creditors...\n\ Transactions in the batch:\n\ \n\ - gas price: {} gwei\n\ + gas price: {} wei\n\ chain: {}\n\ \n\ [wallet address] [payment in wei]\n", - gas_price, chain_name + gas_price_in_wei, chain_name )); let body = accounts.iter().map(|account| { format!( @@ -125,44 +129,42 @@ pub fn sign_transaction_data(amount: u128, recipient_wallet: Wallet) -> [u8; 68] pub fn gas_limit(data: [u8; 68], chain: Chain) -> U256 { let base_gas_limit = BlockchainInterfaceWeb3::web3_gas_limit_const_part(chain); - let gas_limit = ethereum_types::U256::try_from(data.iter().fold(base_gas_limit, |acc, v| { + ethereum_types::U256::try_from(data.iter().fold(base_gas_limit, |acc, v| { acc + if v == &0u8 { 4 } else { 68 } })) - .expect("Internal error"); - gas_limit + .expect("Internal error") } pub fn sign_transaction( chain: Chain, - web3_batch: Web3>, + web3_batch: &Web3>, recipient_wallet: Wallet, consuming_wallet: Wallet, amount: u128, nonce: U256, - gas_price_in_gwei: u64, + gas_price_in_wei: u128, ) -> SignedTransaction { let data = sign_transaction_data(amount, recipient_wallet); let gas_limit = gas_limit(data, chain); - let gas_price_in_wei = to_wei(gas_price_in_gwei); - // If you flip gas_price or nonce to None this function will start making RPC calls (Do it at your own risk). + // Warning: If you set gas_price or nonce to None in transaction_parameters, sign_transaction will start making RPC calls which we don't want (Do it at your own risk). let transaction_parameters = TransactionParameters { nonce: Some(nonce), to: Some(chain.rec().contract), gas: gas_limit, - gas_price: Some(gas_price_in_wei), + gas_price: Some(U256::from(gas_price_in_wei)), value: ethereum_types::U256::zero(), data: Bytes(data.to_vec()), chain_id: Some(chain.rec().num_chain_id), }; let key = consuming_wallet .prepare_secp256k1_secret() - .expect("Consuming wallet doesnt contain a secret key"); + .expect("Consuming wallet doesn't contain a secret key"); sign_transaction_locally(web3_batch, transaction_parameters, &key) } pub fn sign_transaction_locally( - web3_batch: Web3>, + web3_batch: &Web3>, transaction_parameters: TransactionParameters, key: &SecretKey, ) -> SignedTransaction { @@ -170,10 +172,10 @@ pub fn sign_transaction_locally( || transaction_parameters.chain_id.is_none() || transaction_parameters.gas_price.is_none() { - panic!("Signing should be done locally"); + panic!("We don't want to fetch any values while signing"); } - // This wait call doesn't actually make any RPC call and signing is done locally. + // This wait call doesn't actually make any RPC call as long as nonce, chain_id & gas_price are set. web3_batch .accounts() .sign_transaction(transaction_parameters, key) @@ -181,67 +183,47 @@ pub fn sign_transaction_locally( .expect("Web call wasn't allowed") } -pub fn handle_new_transaction( +pub fn sign_and_append_payment( chain: Chain, - web3_batch: Web3>, - recipient_wallet: Wallet, + web3_batch: &Web3>, + recipient: &PayableAccount, consuming_wallet: Wallet, - amount: u128, nonce: U256, - gas_price: u64, -) -> H256 { + gas_price_in_wei: u128, +) -> HashAndAmount { let signed_tx = sign_transaction( chain, - web3_batch.clone(), - recipient_wallet, + web3_batch, + recipient.wallet.clone(), consuming_wallet, - amount, + recipient.balance_wei, nonce, - gas_price, + gas_price_in_wei, ); append_signed_transaction_to_batch(web3_batch, signed_tx.raw_transaction); - signed_tx.transaction_hash + + HashAndAmount { + hash: signed_tx.transaction_hash, + amount: recipient.balance_wei, + } } -pub fn append_signed_transaction_to_batch(web3_batch: Web3>, raw_transaction: Bytes) { +pub fn append_signed_transaction_to_batch(web3_batch: &Web3>, raw_transaction: Bytes) { // This function only prepares a raw transaction for a batch call and doesn't actually send it right here. web3_batch.eth().send_raw_transaction(raw_transaction); } -pub fn sign_and_append_payment( - chain: Chain, - web3_batch: Web3>, - consuming_wallet: Wallet, - nonce: U256, - gas_price: u64, - account: PayableAccount, -) -> HashAndAmount { - let hash = handle_new_transaction( - chain, - web3_batch, - account.wallet.clone(), - consuming_wallet, - account.balance_wei, - nonce, - gas_price, - ); - HashAndAmount { - hash, - amount: account.balance_wei, - } -} - pub fn sign_and_append_multiple_payments( - logger: Logger, + logger: &Logger, chain: Chain, - web3_batch: Web3>, + web3_batch: &Web3>, consuming_wallet: Wallet, - gas_price: u64, + gas_price_in_wei: u128, mut pending_nonce: U256, - accounts: Vec, + accounts: &[PayableAccount], ) -> Vec { let mut hash_and_amount_list = vec![]; - accounts.into_iter().for_each(|payable| { + accounts.iter().for_each(|payable| { debug!( logger, "Preparing payable future of {} wei to {} with nonce {}", @@ -252,11 +234,11 @@ pub fn sign_and_append_multiple_payments( let hash_and_amount = sign_and_append_payment( chain, - web3_batch.clone(), + web3_batch, + payable, consuming_wallet.clone(), pending_nonce, - gas_price, - payable, + gas_price_in_wei, ); pending_nonce = advance_used_nonce(pending_nonce); @@ -267,11 +249,11 @@ pub fn sign_and_append_multiple_payments( #[allow(clippy::too_many_arguments)] pub fn send_payables_within_batch( - logger: Logger, + logger: &Logger, chain: Chain, - web3_batch: Web3>, + web3_batch: &Web3>, consuming_wallet: Wallet, - gas_price_in_gwei: u64, + gas_price_in_wei: u128, pending_nonce: U256, new_fingerprints_recipient: Recipient, accounts: Vec, @@ -283,17 +265,17 @@ pub fn send_payables_within_batch( consuming_wallet, chain.rec().contract, chain.rec().num_chain_id, - gas_price_in_gwei + gas_price_in_wei ); let hashes_and_paid_amounts = sign_and_append_multiple_payments( - logger.clone(), + logger, chain, - web3_batch.clone(), + web3_batch, consuming_wallet, - gas_price_in_gwei, + gas_price_in_wei, pending_nonce, - accounts.clone(), + &accounts, ); let timestamp = SystemTime::now(); @@ -311,10 +293,10 @@ pub fn send_payables_within_batch( info!( logger, "{}", - transmission_log(chain, &accounts, gas_price_in_gwei) + transmission_log(chain, &accounts, gas_price_in_wei) ); - return Box::new( + Box::new( web3_batch .transport() .submit_batch() @@ -326,28 +308,16 @@ pub fn send_payables_within_batch( accounts, )) }), - ); -} - -pub fn calculate_fallback_start_block_number(start_block_number: u64, max_block_count: u64) -> u64 { - if max_block_count == u64::MAX { - start_block_number + 1u64 - } else { - start_block_number + max_block_count - } + ) } -pub fn convert_wei_to_gwei(wei: U256) -> u64 { - (wei / U256::from(GWEI_UNIT)).as_u64() + 1 -} - -pub fn create_blockchain_agent_web3( - gas_limit_const_part: u64, +pub fn dynamically_create_blockchain_agent_web3( + gas_limit_const_part: u128, blockchain_agent_future_result: BlockchainAgentFutureResult, wallet: Wallet, ) -> Box { Box::new(BlockchainAgentWeb3::new( - convert_wei_to_gwei(blockchain_agent_future_result.gas_price_wei), + blockchain_agent_future_result.gas_price_wei.as_u128(), gas_limit_const_part, wallet, ConsumingWalletBalances { @@ -355,7 +325,6 @@ pub fn create_blockchain_agent_web3( .transaction_fee_balance, masq_token_balance_in_minor_units: blockchain_agent_future_result.masq_token_balance, }, - blockchain_agent_future_result.pending_transaction_id, )) } @@ -382,10 +351,11 @@ mod tests { use crate::sub_lib::wallet::Wallet; use crate::test_utils::make_paying_wallet; use crate::test_utils::make_wallet; - use crate::test_utils::recorder::{make_recorder, Recorder}; + use crate::test_utils::recorder::make_recorder; use crate::test_utils::unshared_test_utils::decode_hex; use actix::{Actor, System}; use ethabi::Address; + use ethereum_types::H256; use jsonrpc_core::ErrorCode::ServerError; use jsonrpc_core::{Error, ErrorCode}; use masq_lib::constants::{DEFAULT_CHAIN, DEFAULT_GAS_PRICE}; @@ -401,63 +371,7 @@ mod tests { use web3::Error::Rpc; #[test] - fn calculate_fallback_start_block_number_works() { - assert_eq!( - calculate_fallback_start_block_number(10_000, u64::MAX), - 10_000 + 1 - ); - assert_eq!( - calculate_fallback_start_block_number(5_000, 10_000), - 5_000 + 10_000 - ); - } - - #[test] - fn append_signed_transaction_to_batch_works() { - let port = find_free_port(); - let _blockchain_client_server = MBCSBuilder::new(port) - .begin_batch() - .response( - "0x8290c22bd9b4d61bc57222698799edd7bbc8df5214be44e239a95f679249c59c".to_string(), - 7, - ) - .end_batch() - .start(); - let (_event_loop_handle, transport) = Http::with_max_parallel( - &format!("http://{}:{}", &Ipv4Addr::LOCALHOST, port), - REQUESTS_IN_PARALLEL, - ) - .unwrap(); - let web3_batch = Web3::new(Batch::new(transport)); - let pending_nonce = 1; - let chain = TEST_DEFAULT_CHAIN; - let gas_price = DEFAULT_GAS_PRICE; - let consuming_wallet = make_paying_wallet(b"paying_wallet"); - let account = make_payable_account(1); - let signed_transaction = sign_transaction( - chain, - web3_batch.clone(), - account.wallet, - consuming_wallet, - account.balance_wei, - pending_nonce.into(), - gas_price, - ); - - append_signed_transaction_to_batch(web3_batch.clone(), signed_transaction.raw_transaction); - - let mut batch_result = web3_batch.eth().transport().submit_batch().wait().unwrap(); - let result = batch_result.pop().unwrap().unwrap(); - assert_eq!( - result, - Value::String( - "0x8290c22bd9b4d61bc57222698799edd7bbc8df5214be44e239a95f679249c59c".to_string() - ) - ); - } - - #[test] - fn handle_new_transaction_works() { + fn sign_and_append_payment_works() { let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port) .begin_batch() @@ -478,22 +392,26 @@ mod tests { let consuming_wallet = make_paying_wallet(b"paying_wallet"); let account = make_payable_account(1); let web3_batch = Web3::new(Batch::new(transport)); - let result = handle_new_transaction( + + let result = sign_and_append_payment( chain, - web3_batch.clone(), - account.wallet, + &web3_batch, + &account, consuming_wallet, - account.balance_wei, pending_nonce.into(), - gas_price, + (gas_price * 1_000_000_000) as u128, ); let mut batch_result = web3_batch.eth().transport().submit_batch().wait().unwrap(); - assert_eq!( result, - H256::from_str("94881436a9c89f48b01651ff491c69e97089daf71ab8cfb240243d7ecf9b38b2") - .unwrap() + HashAndAmount { + hash: H256::from_str( + "94881436a9c89f48b01651ff491c69e97089daf71ab8cfb240243d7ecf9b38b2" + ) + .unwrap(), + amount: account.balance_wei + } ); assert_eq!( batch_result.pop().unwrap().unwrap(), @@ -503,41 +421,6 @@ mod tests { ); } - #[test] - fn sign_and_append_payment_works() { - let port = find_free_port(); - let (_event_loop_handle, transport) = Http::with_max_parallel( - &format!("http://{}:{}", &Ipv4Addr::LOCALHOST, port), - REQUESTS_IN_PARALLEL, - ) - .unwrap(); - let web3_batch = Web3::new(Batch::new(transport)); - let pending_nonce = 1; - let chain = DEFAULT_CHAIN; - let gas_price = DEFAULT_GAS_PRICE; - let consuming_wallet = make_paying_wallet(b"paying_wallet"); - let account = make_payable_account(1); - let amount = account.balance_wei; - - let result = sign_and_append_payment( - chain, - web3_batch, - consuming_wallet, - pending_nonce.into(), - gas_price, - account, - ); - - let expected_hash_and_amount = HashAndAmount { - hash: H256::from_str( - "94881436a9c89f48b01651ff491c69e97089daf71ab8cfb240243d7ecf9b38b2", - ) - .unwrap(), - amount, - }; - assert_eq!(result, expected_hash_and_amount); - } - #[test] fn send_and_append_multiple_payments_works() { let port = find_free_port(); @@ -557,13 +440,13 @@ mod tests { let accounts = vec![account_1, account_2]; let result = sign_and_append_multiple_payments( - logger, + &logger, chain, - web3_batch, + &web3_batch, consuming_wallet, - gas_price, + (gas_price * 1_000_000_000) as u128, pending_nonce.into(), - accounts, + &accounts, ); assert_eq!( @@ -624,7 +507,7 @@ mod tests { "INFO: transmission_log_just_works: Paying to creditors...\n\ Transactions in the batch:\n\ \n\ - gas price: 120 gwei\n\ + gas price: 120 wei\n\ chain: ropsten\n\ \n\ [wallet address] [payment in wei]\n\ @@ -691,123 +574,35 @@ mod tests { ) } - #[test] - fn send_payables_within_batch_fails_on_submit_batch_call() { - let port = find_free_port(); - let (_event_loop_handle, transport) = Http::with_max_parallel( - &format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port), - REQUESTS_IN_PARALLEL, - ) - .unwrap(); - let consuming_wallet_secret_raw_bytes = b"okay-wallet"; - let recipient_wallet = make_wallet("blah123"); - let unimportant_recipient = Recorder::new().start().recipient(); - let account = make_payable_account_with_wallet_and_balance_and_timestamp_opt( - recipient_wallet.clone(), - 5000, - None, - ); - let consuming_wallet = make_paying_wallet(consuming_wallet_secret_raw_bytes); - let gas_price = 123; - let nonce = U256::from(1); - let os_code = transport_error_code(); - let os_msg = transport_error_message(); - - let result = send_payables_within_batch( - Logger::new("test"), - TEST_DEFAULT_CHAIN, - Web3::new(Batch::new(transport)), - consuming_wallet, - gas_price, - nonce, - unimportant_recipient, - vec![account], - ) - .wait(); - - assert_eq!( - result, - Err( - Sending { - msg: format!("Transport error: Error(Connect, Os {{ code: {}, kind: ConnectionRefused, message: {:?} }})", os_code, os_msg).to_string(), - hashes: vec![H256::from_str("424c0231591a9879d82f25e0d81e09f39499b2bfd56b3aba708491995e35b4ac").unwrap()] - } - ) - ); - } - - #[test] - fn advance_used_nonce_works() { - let initial_nonce = U256::from(55); - - let result = advance_used_nonce(initial_nonce); - - assert_eq!(result, U256::from(56)) - } - - #[test] - #[should_panic( - expected = "Consuming wallet doesnt contain a secret key: Signature(\"Cannot sign with non-keypair wallet: Address(0x000000000000000000006261645f77616c6c6574).\")" - )] - fn sign_transaction_panics_on_signing_itself() { - let port = find_free_port(); - let (_event_loop_handle, transport) = Http::with_max_parallel( - &format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port), - REQUESTS_IN_PARALLEL, - ) - .unwrap(); - let recipient_wallet = make_wallet("unlucky man"); - let consuming_wallet = make_wallet("bad_wallet"); - let gas_price = 123; - let nonce = U256::from(1); - - sign_transaction( - Chain::PolyAmoy, - Web3::new(Batch::new(transport)), - recipient_wallet, - consuming_wallet, - 444444, - nonce, - gas_price, - ); - } - - #[test] - fn send_payables_within_batch_works() { + fn execute_send_payables_test( + test_name: &str, + accounts: Vec, + expected_result: Result, PayableTransactionError>, + port: u16, + ) { init_test_logging(); - let test_name = "send_payables_within_batch_works"; - let port = find_free_port(); let (_event_loop_handle, transport) = Http::with_max_parallel( &format!("http://{}:{}", &Ipv4Addr::LOCALHOST, port), REQUESTS_IN_PARALLEL, ) .unwrap(); - let _blockchain_client_server = MBCSBuilder::new(port) - .begin_batch() - .response("rpc_result".to_string(), 7) - .response("rpc_result_2".to_string(), 7) - .end_batch() - .start(); + let gas_price = 1_000_000_000; + let pending_nonce: U256 = 1.into(); let web3_batch = Web3::new(Batch::new(transport)); let (accountant, _, accountant_recording) = make_recorder(); let logger = Logger::new(test_name); let chain = DEFAULT_CHAIN; let consuming_wallet = make_paying_wallet(b"consuming_wallet"); - let gas_price = 1u64; - let pending_nonce: U256 = 1.into(); let new_fingerprints_recipient = accountant.start().recipient(); - let accounts_1 = make_payable_account(1); - let accounts_2 = make_payable_account(2); - let accounts = vec![accounts_1.clone(), accounts_2.clone()]; let system = System::new(test_name); let timestamp_before = SystemTime::now(); let result = send_payables_within_batch( - logger, + &logger, chain, - web3_batch, + &web3_batch, consuming_wallet.clone(), - gas_price.clone(), + gas_price, pending_nonce, new_fingerprints_recipient, accounts.clone(), @@ -816,53 +611,14 @@ mod tests { System::current().stop(); system.run(); - let tlh = TestLogHandler::new(); let timestamp_after = SystemTime::now(); - let recording_result = accountant_recording.lock().unwrap(); - let processed_payments = result.unwrap(); - let message = recording_result.get_record::(0); - assert_eq!(recording_result.len(), 1); - assert!(timestamp_before <= message.batch_wide_timestamp); - assert!(timestamp_after >= message.batch_wide_timestamp); - assert_eq!( - message.hashes_and_balances, - vec![ - HashAndAmount { - hash: H256::from_str( - "35f42b260f090a559e8b456718d9c91a9da0f234ed0a129b9d5c4813b6615af4" - ) - .unwrap(), - amount: accounts_1.balance_wei - }, - HashAndAmount { - hash: H256::from_str( - "7f3221109e4f1de8ba1f7cd358aab340ecca872a1456cb1b4f59ca33d3e22ee3" - ) - .unwrap(), - amount: accounts_2.balance_wei - }, - ] - ); - assert_eq!( - processed_payments[0], - ProcessedPayableFallible::Correct(PendingPayable { - recipient_wallet: accounts_1.wallet, - hash: H256::from_str( - "35f42b260f090a559e8b456718d9c91a9da0f234ed0a129b9d5c4813b6615af4" - ) - .unwrap() - }) - ); - assert_eq!( - processed_payments[1], - ProcessedPayableFallible::Correct(PendingPayable { - recipient_wallet: accounts_2.wallet, - hash: H256::from_str( - "7f3221109e4f1de8ba1f7cd358aab340ecca872a1456cb1b4f59ca33d3e22ee3" - ) - .unwrap() - }) - ); + let accountant_recording_result = accountant_recording.lock().unwrap(); + let ppfs_message = + accountant_recording_result.get_record::(0); + assert_eq!(accountant_recording_result.len(), 1); + assert!(timestamp_before <= ppfs_message.batch_wide_timestamp); + assert!(timestamp_after >= ppfs_message.batch_wide_timestamp); + let tlh = TestLogHandler::new(); tlh.exists_log_containing( &format!("DEBUG: {test_name}: Common attributes of payables to be transacted: sender wallet: {}, contract: {:?}, chain_id: {}, gas_price: {}", consuming_wallet, @@ -875,18 +631,91 @@ mod tests { "INFO: {test_name}: {}", transmission_log(chain, &accounts, gas_price) )); + assert_eq!(result, expected_result); + } + + #[test] + fn send_payables_within_batch_works() { + let accounts = vec![make_payable_account(1), make_payable_account(2)]; + let expected_result = Ok(vec![ + Correct(PendingPayable { + recipient_wallet: accounts[0].wallet.clone(), + hash: H256::from_str( + "35f42b260f090a559e8b456718d9c91a9da0f234ed0a129b9d5c4813b6615af4", + ) + .unwrap(), + }), + Correct(PendingPayable { + recipient_wallet: accounts[1].wallet.clone(), + hash: H256::from_str( + "7f3221109e4f1de8ba1f7cd358aab340ecca872a1456cb1b4f59ca33d3e22ee3", + ) + .unwrap(), + }), + ]); + + let port = find_free_port(); + let _blockchain_client_server = MBCSBuilder::new(port) + .begin_batch() + .response("rpc_result".to_string(), 7) + .response("rpc_result_2".to_string(), 8) + .end_batch() + .start(); + execute_send_payables_test( + "send_payables_within_batch_works", + accounts, + expected_result, + port, + ); + } + + #[test] + fn send_payables_within_batch_fails_on_submit_batch_call() { + let accounts = vec![make_payable_account(1), make_payable_account(2)]; + let os_code = transport_error_code(); + let os_msg = transport_error_message(); + let expected_result = Err(Sending { + msg: format!("Transport error: Error(Connect, Os {{ code: {}, kind: ConnectionRefused, message: {:?} }})", os_code, os_msg).to_string(), + hashes: vec![ + H256::from_str("35f42b260f090a559e8b456718d9c91a9da0f234ed0a129b9d5c4813b6615af4").unwrap(), + H256::from_str("7f3221109e4f1de8ba1f7cd358aab340ecca872a1456cb1b4f59ca33d3e22ee3").unwrap() + ], + }); + + let port = find_free_port(); + execute_send_payables_test( + "send_payables_within_batch_fails_on_submit_batch_call", + accounts, + expected_result, + port, + ); } #[test] fn send_payables_within_batch_all_payments_fail() { - init_test_logging(); - let test_name = "send_payables_within_batch_all_payments_fail"; + let accounts = vec![make_payable_account(1), make_payable_account(2)]; + let expected_result = Ok(vec![ + Failed(RpcPayableFailure { + rpc_error: Rpc(Error { + code: ServerError(429), + message: "The requests per second (RPS) of your requests are higher than your plan allows.".to_string(), + data: None, + }), + recipient_wallet: accounts[0].wallet.clone(), + hash: H256::from_str("35f42b260f090a559e8b456718d9c91a9da0f234ed0a129b9d5c4813b6615af4").unwrap(), + }), + Failed(RpcPayableFailure { + rpc_error: Rpc(Error { + code: ServerError(429), + message: "The requests per second (RPS) of your requests are higher than your plan allows.".to_string(), + data: None, + }), + recipient_wallet: accounts[1].wallet.clone(), + hash: H256::from_str("7f3221109e4f1de8ba1f7cd358aab340ecca872a1456cb1b4f59ca33d3e22ee3").unwrap(), + }), + ]); + let port = find_free_port(); - let (_event_loop_handle, transport) = Http::with_max_parallel( - &format!("http://{}:{}", &Ipv4Addr::LOCALHOST, port), - REQUESTS_IN_PARALLEL, - ) - .unwrap(); let _blockchain_client_server = MBCSBuilder::new(port) .begin_batch() .err_response( @@ -899,107 +728,38 @@ mod tests { 429, "The requests per second (RPS) of your requests are higher than your plan allows." .to_string(), - 7, + 8, ) .end_batch() .start(); - let web3_batch = Web3::new(Batch::new(transport)); - let (accountant, _, accountant_recording) = make_recorder(); - let logger = Logger::new(test_name); - let chain = DEFAULT_CHAIN; - let consuming_wallet = make_paying_wallet(b"consuming_wallet"); - let gas_price = 1u64; - let pending_nonce: U256 = 1.into(); - let new_fingerprints_recipient = accountant.start().recipient(); - let accounts_1 = make_payable_account(1); - let accounts_2 = make_payable_account(2); - let accounts = vec![accounts_1.clone(), accounts_2.clone()]; - let system = System::new(test_name); - let timestamp_before = SystemTime::now(); - - let result = send_payables_within_batch( - logger, - chain, - web3_batch, - consuming_wallet.clone(), - gas_price.clone(), - pending_nonce, - new_fingerprints_recipient, - accounts.clone(), - ) - .wait(); - - System::current().stop(); - system.run(); - let tlh = TestLogHandler::new(); - let timestamp_after = SystemTime::now(); - let recording_result = accountant_recording.lock().unwrap(); - let processed_payments = result.unwrap(); - let message = recording_result.get_record::(0); - assert_eq!(recording_result.len(), 1); - assert!(timestamp_before <= message.batch_wide_timestamp); - assert!(timestamp_after >= message.batch_wide_timestamp); - assert_eq!( - message.hashes_and_balances, - vec![ - HashAndAmount { - hash: H256::from_str( - "35f42b260f090a559e8b456718d9c91a9da0f234ed0a129b9d5c4813b6615af4" - ) - .unwrap(), - amount: accounts_1.balance_wei - }, - HashAndAmount { - hash: H256::from_str( - "7f3221109e4f1de8ba1f7cd358aab340ecca872a1456cb1b4f59ca33d3e22ee3" - ) - .unwrap(), - amount: accounts_2.balance_wei - }, - ] - ); - assert_eq!(processed_payments[0], Failed(RpcPayableFailure{ - rpc_error: Rpc(Error { - code: ServerError(429), - message: "The requests per second (RPS) of your requests are higher than your plan allows.".to_string(), - data: None, - }), - recipient_wallet: accounts_1.wallet, - hash: H256::from_str("35f42b260f090a559e8b456718d9c91a9da0f234ed0a129b9d5c4813b6615af4").unwrap(), - })); - assert_eq!(processed_payments[1], Failed(RpcPayableFailure{ - rpc_error: Rpc(Error { - code: ServerError(429), - message: "The requests per second (RPS) of your requests are higher than your plan allows.".to_string(), - data: None, - }), - recipient_wallet: accounts_2.wallet, - hash: H256::from_str("7f3221109e4f1de8ba1f7cd358aab340ecca872a1456cb1b4f59ca33d3e22ee3").unwrap(), - })); - tlh.exists_log_containing( - &format!("DEBUG: {test_name}: Common attributes of payables to be transacted: sender wallet: {}, contract: {:?}, chain_id: {}, gas_price: {}", - consuming_wallet, - chain.rec().contract, - chain.rec().num_chain_id, - gas_price - ) + execute_send_payables_test( + "send_payables_within_batch_all_payments_fail", + accounts, + expected_result, + port, ); - tlh.exists_log_containing(&format!( - "INFO: {test_name}: {}", - transmission_log(chain, &accounts, gas_price) - )); } #[test] fn send_payables_within_batch_one_payment_works_the_other_fails() { - init_test_logging(); - let test_name = "send_payables_within_batch_one_payment_works_the_other_fails"; + let accounts = vec![make_payable_account(1), make_payable_account(2)]; + let expected_result = Ok(vec![ + Correct(PendingPayable { + recipient_wallet: accounts[0].wallet.clone(), + hash: H256::from_str("35f42b260f090a559e8b456718d9c91a9da0f234ed0a129b9d5c4813b6615af4").unwrap(), + }), + Failed(RpcPayableFailure { + rpc_error: Rpc(Error { + code: ServerError(429), + message: "The requests per second (RPS) of your requests are higher than your plan allows.".to_string(), + data: None, + }), + recipient_wallet: accounts[1].wallet.clone(), + hash: H256::from_str("7f3221109e4f1de8ba1f7cd358aab340ecca872a1456cb1b4f59ca33d3e22ee3").unwrap(), + }), + ]); + let port = find_free_port(); - let (_event_loop_handle, transport) = Http::with_max_parallel( - &format!("http://{}:{}", &Ipv4Addr::LOCALHOST, port), - REQUESTS_IN_PARALLEL, - ) - .unwrap(); let _blockchain_client_server = MBCSBuilder::new(port) .begin_batch() .response("rpc_result".to_string(), 7) @@ -1011,93 +771,48 @@ mod tests { ) .end_batch() .start(); - let web3_batch = Web3::new(Batch::new(transport.clone())); + execute_send_payables_test( + "send_payables_within_batch_one_payment_works_the_other_fails", + accounts, + expected_result, + port, + ); + } - let (accountant, _, accountant_recording) = make_recorder(); - let logger = Logger::new(test_name); - let chain = DEFAULT_CHAIN; - let consuming_wallet = make_paying_wallet(b"consuming_wallet"); - let gas_price = 1u64; - let pending_nonce: U256 = 1.into(); - let new_fingerprints_recipient = accountant.start().recipient(); - let accounts_1 = make_payable_account(1); - let accounts_2 = make_payable_account(2); - let accounts = vec![accounts_1.clone(), accounts_2.clone()]; - let system = System::new(test_name); - let timestamp_before = SystemTime::now(); + #[test] + fn advance_used_nonce_works() { + let initial_nonce = U256::from(55); - let result = send_payables_within_batch( - logger, - chain, - web3_batch, - consuming_wallet.clone(), - gas_price.clone(), - pending_nonce, - new_fingerprints_recipient, - accounts.clone(), + let result = advance_used_nonce(initial_nonce); + + assert_eq!(result, U256::from(56)) + } + + #[test] + #[should_panic( + expected = "Consuming wallet doesn't contain a secret key: Signature(\"Cannot sign with non-keypair wallet: Address(0x000000000000000000006261645f77616c6c6574).\")" + )] + fn sign_transaction_panics_due_to_lack_of_secret_key() { + let port = find_free_port(); + let (_event_loop_handle, transport) = Http::with_max_parallel( + &format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port), + REQUESTS_IN_PARALLEL, ) - .wait(); + .unwrap(); + let recipient_wallet = make_wallet("unlucky man"); + let consuming_wallet = make_wallet("bad_wallet"); + let gas_price = 123_000_000_000; + let nonce = U256::from(1); - System::current().stop(); - system.run(); - let tlh = TestLogHandler::new(); - let timestamp_after = SystemTime::now(); - let recording_result = accountant_recording.lock().unwrap(); - let processed_payments = result.unwrap(); - let message = recording_result.get_record::(0); - assert_eq!(recording_result.len(), 1); - assert!(timestamp_before <= message.batch_wide_timestamp); - assert!(timestamp_after >= message.batch_wide_timestamp); - assert_eq!( - message.hashes_and_balances, - vec![ - HashAndAmount { - hash: H256::from_str( - "35f42b260f090a559e8b456718d9c91a9da0f234ed0a129b9d5c4813b6615af4" - ) - .unwrap(), - amount: accounts_1.balance_wei - }, - HashAndAmount { - hash: H256::from_str( - "7f3221109e4f1de8ba1f7cd358aab340ecca872a1456cb1b4f59ca33d3e22ee3" - ) - .unwrap(), - amount: accounts_2.balance_wei - }, - ] - ); - assert_eq!( - processed_payments[0], - ProcessedPayableFallible::Correct(PendingPayable { - recipient_wallet: accounts_1.wallet, - hash: H256::from_str( - "35f42b260f090a559e8b456718d9c91a9da0f234ed0a129b9d5c4813b6615af4" - ) - .unwrap() - }) - ); - assert_eq!(processed_payments[1], ProcessedPayableFallible::Failed(RpcPayableFailure{ - rpc_error: Rpc(Error { - code: ServerError(429), - message: "The requests per second (RPS) of your requests are higher than your plan allows.".to_string(), - data: None, - }), - recipient_wallet: accounts_2.wallet, - hash: H256::from_str("7f3221109e4f1de8ba1f7cd358aab340ecca872a1456cb1b4f59ca33d3e22ee3").unwrap(), - })); - tlh.exists_log_containing( - &format!("DEBUG: {test_name}: Common attributes of payables to be transacted: sender wallet: {}, contract: {:?}, chain_id: {}, gas_price: {}", - consuming_wallet, - chain.rec().contract, - chain.rec().num_chain_id, - gas_price - ) + sign_transaction( + Chain::PolyAmoy, + &Web3::new(Batch::new(transport)), + recipient_wallet, + consuming_wallet, + 444444, + nonce, + gas_price, ); - tlh.exists_log_containing(&format!( - "INFO: {test_name}: {}", - transmission_log(chain, &accounts, gas_price) - )); } #[test] @@ -1111,7 +826,7 @@ mod tests { let web3 = Web3::new(transport.clone()); let chain = DEFAULT_CHAIN; let amount = 11_222_333_444; - let gas_price_in_gwei = 123000000000_u64; + let gas_price_in_wei = 123_000_000_000_000_000_000; let nonce = U256::from(5); let recipient_wallet = make_wallet("recipient_wallet"); let consuming_wallet = make_paying_wallet(b"consuming_wallet"); @@ -1121,19 +836,19 @@ mod tests { nonce: Some(nonce), to: Some(chain.rec().contract), gas: gas_limit(data, chain), - gas_price: Some(to_wei(gas_price_in_gwei)), + gas_price: Some(U256::from(gas_price_in_wei)), value: U256::zero(), data: Bytes(data.to_vec()), chain_id: Some(chain.rec().num_chain_id), }; let result = sign_transaction( chain, - Web3::new(Batch::new(transport)), + &Web3::new(Batch::new(transport)), recipient_wallet, consuming_wallet, amount, nonce, - gas_price_in_gwei, + gas_price_in_wei, ); let expected_tx_result = web3 @@ -1146,36 +861,7 @@ mod tests { } #[test] - #[should_panic( - expected = "Consuming wallet doesnt contain a secret key: Signature(\"Cannot sign with non-keypair wallet: Address(0x00000000636f6e73756d696e675f77616c6c6574).\")" - )] - fn sign_transaction_panics_on_bad_consuming_wallet() { - let port = find_free_port(); - let (_event_loop_handle, transport) = Http::with_max_parallel( - &format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port), - REQUESTS_IN_PARALLEL, - ) - .unwrap(); - let chain = DEFAULT_CHAIN; - let amount = 11_222_333_444; - let gas_price_in_gwei = 123000000000_u64; - let nonce = U256::from(5); - let recipient_wallet = make_wallet("recipient_wallet"); - let consuming_wallet = make_wallet("consuming_wallet"); - - let _result = sign_transaction( - chain, - Web3::new(Batch::new(transport)), - recipient_wallet, - consuming_wallet, - amount, - nonce, - gas_price_in_gwei, - ); - } - - #[test] - #[should_panic(expected = "Signing should be done locally")] + #[should_panic(expected = "We don't want to fetch any values while signing")] fn sign_transaction_locally_panics_on_signed_transaction() { let port = find_free_port(); let (_event_loop_handle, transport) = Http::with_max_parallel( @@ -1202,51 +888,15 @@ mod tests { }; let key = consuming_wallet .prepare_secp256k1_secret() - .expect("Consuming wallet doesnt contain a secret key"); + .expect("Consuming wallet doesn't contain a secret key"); let _result = sign_transaction_locally( - Web3::new(Batch::new(transport)), + &Web3::new(Batch::new(transport)), transaction_parameters, &key, ); } - #[test] - fn sign_and_append_payment_just_works() { - let port = find_free_port(); - let (_event_loop_handle, transport) = Http::with_max_parallel( - &format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port), - REQUESTS_IN_PARALLEL, - ) - .unwrap(); - let consuming_wallet = make_paying_wallet(b"consuming_wallet"); - let system = System::new("test"); - let account = make_payable_account_with_wallet_and_balance_and_timestamp_opt( - make_wallet("blah123"), - 9000, - None, - ); - let gas_price = 123; - let nonce = U256::from(1); - - let result = sign_and_append_payment( - TEST_DEFAULT_CHAIN, - Web3::new(Batch::new(transport)), - consuming_wallet, - nonce, - gas_price, - account, - ); - - System::current().stop(); - system.run(); - let expected_hash = - H256::from_str("8d278f82f42ee4f3b9eef2e099cccc91ff117e80c28d6369fec38ec50f5bd2c2") - .unwrap(); - assert_eq!(result.hash, expected_hash); - assert_eq!(result.amount, 9000); - } - //with a real confirmation through a transaction sent with this data to the network #[test] fn web3_interface_signing_a_transaction_works_for_polygon_amoy() { @@ -1377,12 +1027,12 @@ mod tests { let signed_transaction = sign_transaction( chain, - Web3::new(Batch::new(transport)), + &Web3::new(Batch::new(transport)), payable_account.wallet, consuming_wallet, payable_account.balance_wei, nonce_correct_type, - gas_price, + (gas_price * 1_000_000_000) as u128, ); let byte_set_to_compare = signed_transaction.raw_transaction.0; diff --git a/node/src/blockchain/mod.rs b/node/src/blockchain/mod.rs index 5415cd56d..20435b48b 100644 --- a/node/src/blockchain/mod.rs +++ b/node/src/blockchain/mod.rs @@ -4,10 +4,8 @@ pub mod bip39; pub mod blockchain_bridge; pub mod blockchain_interface; pub mod blockchain_interface_initializer; +mod blockchain_interface_utils; pub mod payer; pub mod signature; - -mod batch_web3; -mod blockchain_interface_utils; #[cfg(test)] pub mod test_utils; diff --git a/node/src/blockchain/test_utils.rs b/node/src/blockchain/test_utils.rs index 4a36ca87c..4124e283a 100644 --- a/node/src/blockchain/test_utils.rs +++ b/node/src/blockchain/test_utils.rs @@ -2,45 +2,21 @@ #![cfg(test)] -use crate::accountant::db_access_objects::payable_dao::PayableAccount; -use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; -use crate::blockchain::blockchain_bridge::PendingPayableFingerprintSeeds; use crate::blockchain::blockchain_interface::blockchain_interface_web3::{ BlockchainInterfaceWeb3, REQUESTS_IN_PARALLEL, }; -use crate::blockchain::blockchain_interface::data_structures::errors::{ - BlockchainAgentBuildError, BlockchainError, PayableTransactionError, -}; -use crate::blockchain::blockchain_interface::data_structures::{ - ProcessedPayableFallible, RetrievedBlockchainTransactions, -}; -use crate::blockchain::blockchain_interface::lower_level_interface::LowBlockchainInt; -// use crate::blockchain::blockchain_interface::test_utils::LowBlockchainIntMock; -use crate::blockchain::blockchain_interface::test_utils::LowBlockchainIntMock; -use crate::blockchain::blockchain_interface::BlockchainInterface; -use crate::set_arbitrary_id_stamp_in_mock_impl; -use crate::sub_lib::wallet::Wallet; -use crate::test_utils::unshared_test_utils::arbitrary_id_stamp::ArbitraryIdStamp; -use actix::Recipient; use bip39::{Language, Mnemonic, Seed}; use ethabi::Hash; use ethereum_types::{BigEndianHash, H160, H256, U64}; -use futures::future::result; -use futures::Future; use lazy_static::lazy_static; use masq_lib::blockchains::chains::Chain; -use masq_lib::utils::{find_free_port, to_string}; +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::sync::{Arc, Mutex}; -use web3::transports::{Batch, EventLoopHandle, Http}; -use web3::types::{ - Address, BlockNumber, Index, Log, SignedTransaction, TransactionReceipt, H2048, U256, -}; -use web3::Web3; +use web3::transports::{EventLoopHandle, Http}; +use web3::types::{Index, Log, SignedTransaction, TransactionReceipt, H2048, U256}; lazy_static! { static ref BIG_MEANINGLESS_PHRASE: Vec<&'static str> = vec![ @@ -63,8 +39,7 @@ pub fn make_meaningless_seed() -> Seed { Seed::new(&mnemonic, "passphrase") } -pub fn make_blockchain_interface_web3(port_opt: Option) -> BlockchainInterfaceWeb3 { - let port = port_opt.unwrap_or_else(|| find_free_port()); +pub fn make_blockchain_interface_web3(port: u16) -> BlockchainInterfaceWeb3 { let chain = Chain::PolyMainnet; let (event_loop_handle, transport) = Http::with_max_parallel( &format!("http://{}:{}", &Ipv4Addr::LOCALHOST, port), @@ -186,175 +161,13 @@ impl ReceiptResponseBuilder { let rpc_response = RpcResponse { json_rpc: "2.0".to_string(), - id: 0, + id: 1, result: transaction_receipt, }; serde_json::to_string(&rpc_response).unwrap() } } -#[derive(Default)] -pub struct BlockchainInterfaceMock { - retrieve_transactions_parameters: Arc>>, - retrieve_transactions_results: - RefCell>>, - build_blockchain_agent_params: Arc>>, - build_blockchain_agent_results: - RefCell, BlockchainAgentBuildError>>>, - send_batch_of_payables_params: Arc< - Mutex< - Vec<( - ArbitraryIdStamp, - Recipient, - Vec, - )>, - >, - >, - send_batch_of_payables_results: - RefCell, PayableTransactionError>>>, - get_transaction_receipt_params: Arc>>, - get_transaction_receipt_results: - RefCell, BlockchainError>>>, - lower_interface_result: Option>, - arbitrary_id_stamp_opt: Option, - get_chain_results: RefCell>, - get_batch_web3_results: RefCell>>>, -} - -impl BlockchainInterface for BlockchainInterfaceMock { - fn contract_address(&self) -> Address { - unimplemented!("not needed so far") - } - - fn get_chain(&self) -> Chain { - unimplemented!("not needed so far") - } - - fn retrieve_transactions( - &self, - start_block: BlockNumber, - fallback_start_block_number: u64, - recipient: Address, - ) -> Box> { - self.retrieve_transactions_parameters.lock().unwrap().push(( - start_block, - fallback_start_block_number, - recipient, - )); - Box::new(result( - self.retrieve_transactions_results.borrow_mut().remove(0), - )) - } - - fn build_blockchain_agent( - &self, - _consuming_wallet: Wallet, - ) -> Box, Error = BlockchainAgentBuildError>> { - unimplemented!("not needed so far") - } - - fn lower_interface(&self) -> Box { - unimplemented!("not needed so far") - } -} - -impl BlockchainInterfaceMock { - pub fn retrieve_transactions_params( - mut self, - params: &Arc>>, - ) -> Self { - self.retrieve_transactions_parameters = params.clone(); - self - } - - pub fn retrieve_transactions_result( - self, - result: Result, - ) -> Self { - self.retrieve_transactions_results.borrow_mut().push(result); - self - } - - pub fn build_blockchain_agent_params( - mut self, - params: &Arc>>, - ) -> Self { - self.build_blockchain_agent_params = params.clone(); - self - } - - pub fn build_blockchain_agent_result( - self, - result: Result, BlockchainAgentBuildError>, - ) -> Self { - self.build_blockchain_agent_results - .borrow_mut() - .push(result); - self - } - - pub fn send_batch_of_payables_params( - mut self, - params: &Arc< - Mutex< - Vec<( - ArbitraryIdStamp, - Recipient, - Vec, - )>, - >, - >, - ) -> Self { - self.send_batch_of_payables_params = params.clone(); - self - } - - pub fn send_batch_of_payables_result( - self, - result: Result, PayableTransactionError>, - ) -> Self { - self.send_batch_of_payables_results - .borrow_mut() - .push(result); - self - } - - pub fn get_chain_result(self, result: Chain) -> Self { - self.get_chain_results.borrow_mut().push(result); - self - } - - pub fn get_batch_web3_result(self, result: Web3>) -> Self { - self.get_batch_web3_results.borrow_mut().push(result); - self - } - - pub fn get_transaction_receipt_params(mut self, params: &Arc>>) -> Self { - self.get_transaction_receipt_params = params.clone(); - self - } - - pub fn get_transaction_receipt_result( - self, - result: Result, BlockchainError>, - ) -> Self { - self.get_transaction_receipt_results - .borrow_mut() - .push(result); - self - } - - pub fn lower_interface_results( - mut self, - aggregated_results: Box, - ) -> Self { - self.lower_interface_result = Some(aggregated_results); - self - } - - set_arbitrary_id_stamp_in_mock_impl!(); -} - pub fn make_fake_event_loop_handle() -> EventLoopHandle { Http::with_max_parallel("http://86.75.30.9", REQUESTS_IN_PARALLEL) .unwrap() diff --git a/node/src/neighborhood/mod.rs b/node/src/neighborhood/mod.rs index 09d007ab7..e46e2abce 100644 --- a/node/src/neighborhood/mod.rs +++ b/node/src/neighborhood/mod.rs @@ -507,13 +507,6 @@ impl Neighborhood { } fn handle_route_query_message(&mut self, msg: RouteQueryMessage) -> Option { - if let Some(ref url) = msg.hostname_opt { - if url.contains("0.0.0.0") { - error!(self.logger, "Request to wildcard IP detected 0.0.0.0. Most likely because Blockchain Service URL is not set"); - return None; - } - } - let debug_msg_opt = self.logger.debug_enabled().then(|| format!("{:?}", msg)); let route_result = if self.mode == NeighborhoodModeLight::ZeroHop { Ok(self.zero_hop_route_response()) @@ -846,7 +839,7 @@ impl Neighborhood { Err(PersistentConfigError::DatabaseError(msg)) if &msg == "database is locked" => { - warning! ( + warning!( self.logger, "Could not persist immediate-neighbor changes: database locked - skipping" ) @@ -2597,9 +2590,9 @@ mod tests { system.run(); // If this never halts, it's because the Neighborhood isn't properly killing its actor let tlh = TestLogHandler::new(); - tlh.exists_log_containing ("WARN: Neighborhood: Node at 3.4.5.6 refused Debut: No neighbors for Introduction or Pass"); - tlh.exists_log_containing ("WARN: Neighborhood: Node at 4.5.6.7 refused Debut: Node owner manually rejected your Debut"); - tlh.exists_log_containing ("ERROR: Neighborhood: None of the Nodes listed in the --neighbors parameter could accept your Debut; shutting down"); + tlh.exists_log_containing("WARN: Neighborhood: Node at 3.4.5.6 refused Debut: No neighbors for Introduction or Pass"); + tlh.exists_log_containing("WARN: Neighborhood: Node at 4.5.6.7 refused Debut: Node owner manually rejected your Debut"); + tlh.exists_log_containing("ERROR: Neighborhood: None of the Nodes listed in the --neighbors parameter could accept your Debut; shutting down"); } #[test] @@ -2634,31 +2627,6 @@ mod tests { assert_eq!(result, None); } - #[test] - fn route_query_responds_with_none_when_wildcard_ip_is_requested() { - init_test_logging(); - let test_name = "route_query_responds_with_none_when_wildcard_ip_is_requested"; - let system = System::new(test_name); - let mut subject = make_standard_subject(); - subject.logger = Logger::new(test_name); - let addr: Addr = subject.start(); - let sub: Recipient = addr.recipient::(); - - let future = sub.send(RouteQueryMessage::data_indefinite_route_request( - Some("0.0.0.0".to_string()), - 430, - )); - - System::current().stop_with_code(0); - system.run(); - let result = future.wait().unwrap(); - assert_eq!(result, None); - TestLogHandler::new().exists_log_containing(&format!( - "ERROR: {}: Request to wildcard IP detected 0.0.0.0. Most likely because Blockchain Service URL is not set", - test_name - )); - } - #[test] fn route_query_works_when_node_is_set_for_one_hop_and_no_consuming_wallet() { let cryptde = main_cryptde(); @@ -3419,7 +3387,7 @@ mod tests { assert_eq!( new_undesirability, 1_000_000 // existing undesirability - + rate_pack.routing_charge (1_000) as i64 // charge to route packet + + rate_pack.routing_charge(1_000) as i64 // charge to route packet ); } @@ -3442,7 +3410,7 @@ mod tests { assert_eq!( new_undesirability, 1_000_000 // existing undesirability - + rate_pack.exit_charge (1_000) as i64 // charge to exit request + + rate_pack.exit_charge(1_000) as i64 // charge to exit request ); } @@ -3470,8 +3438,8 @@ mod tests { assert_eq!( new_undesirability, 1_000_000 // existing undesirability - + rate_pack.exit_charge (1_000) as i64 // charge to exit request - + UNREACHABLE_HOST_PENALTY // because host is blacklisted + + rate_pack.exit_charge(1_000) as i64 // charge to exit request + + UNREACHABLE_HOST_PENALTY // because host is blacklisted ); TestLogHandler::new().exists_log_containing( "TRACE: Neighborhood: Node with PubKey 0x02030405 \ @@ -3519,8 +3487,8 @@ mod tests { let rate_pack = node_record.rate_pack(); assert_eq!( initial_undesirability, - rate_pack.exit_charge (1_000) as i64 // charge to exit response - + rate_pack.routing_charge (1_000) as i64 // charge to route response + rate_pack.exit_charge(1_000) as i64 // charge to exit response + + rate_pack.routing_charge(1_000) as i64 // charge to route response ); } @@ -3543,7 +3511,7 @@ mod tests { assert_eq!( new_undesirability, 1_000_000 // existing undesirability - + rate_pack.routing_charge (1_000) as i64 // charge to route response + + rate_pack.routing_charge(1_000) as i64 // charge to route response ); } @@ -5065,7 +5033,7 @@ mod tests { }, earning_wallet.clone(), consuming_wallet.clone(), - "neighborhood_sends_node_query_response_with_none_when_key_query_matches_no_configured_data" + "neighborhood_sends_node_query_response_with_none_when_key_query_matches_no_configured_data", ), ); let addr: Addr = subject.start(); @@ -5127,7 +5095,7 @@ mod tests { }, earning_wallet.clone(), consuming_wallet.clone(), - "neighborhood_sends_node_query_response_with_result_when_key_query_matches_configured_data" + "neighborhood_sends_node_query_response_with_result_when_key_query_matches_configured_data", ), ); subject @@ -5194,7 +5162,7 @@ mod tests { }, earning_wallet.clone(), consuming_wallet.clone(), - "neighborhood_sends_node_query_response_with_none_when_ip_address_query_matches_no_configured_data" + "neighborhood_sends_node_query_response_with_none_when_ip_address_query_matches_no_configured_data", ), ); let addr: Addr = subject.start(); @@ -5258,7 +5226,7 @@ mod tests { }, node_record.earning_wallet(), None, - "neighborhood_sends_node_query_response_with_result_when_ip_address_query_matches_configured_data" + "neighborhood_sends_node_query_response_with_result_when_ip_address_query_matches_configured_data", ); let mut subject = Neighborhood::new(cryptde, &config); subject diff --git a/node/src/proxy_server/mod.rs b/node/src/proxy_server/mod.rs index 058c7c12f..4d221a8f7 100644 --- a/node/src/proxy_server/mod.rs +++ b/node/src/proxy_server/mod.rs @@ -50,8 +50,9 @@ use masq_lib::ui_gateway::NodeFromUiMessage; use masq_lib::utils::MutabilityConflictHelper; use regex::Regex; use std::collections::HashMap; -use std::net::SocketAddr; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::rc::Rc; +use std::str::FromStr; use std::time::{Duration, SystemTime}; use tokio::prelude::Future; @@ -1066,7 +1067,14 @@ impl IBCDHelper for IBCDHelperReal { let stream_key = proxy.find_or_generate_stream_key(&msg); let timestamp = msg.timestamp; let payload = match proxy.make_payload(msg, &stream_key) { - Ok(payload) => payload, + Ok(payload) => { + if let Some(hostname) = &payload.target_hostname { + if let Err(e) = Hostname::new(hostname).is_valid() { + return Err(format!("Request to wildcard IP detected - {} (Most likely because Blockchain Service URL is not set)", e)); + } + } + payload + } Err(e) => return Err(e), }; @@ -1213,7 +1221,6 @@ struct Hostname { } impl Hostname { - #[allow(dead_code)] fn new(raw_url: &str) -> Self { let regex = Regex::new( r"^((http[s]?|ftp):/)?/?([^:/\s]+)((/\w+)*/)([\w\-.]+[^#?\s]+)(.*)?(#[\w\-]+)?$", @@ -1228,6 +1235,44 @@ impl Hostname { }; Self { hostname } } + + fn is_valid(&self) -> Result<(), String> { + match IpAddr::from_str(&self.hostname) { + Ok(ip_addr) => match ip_addr { + IpAddr::V4(ipv4addr) => Self::validate_ipv4(ipv4addr), + IpAddr::V6(ipv6addr) => Self::validate_ipv6(ipv6addr), + }, + Err(_) => Self::validate_raw_string(&self.hostname), + } + } + + fn validate_ipv4(addr: Ipv4Addr) -> Result<(), String> { + if addr.octets() == [0, 0, 0, 0] { + Err("0.0.0.0".to_string()) + } else if addr.octets() == [127, 0, 0, 1] { + Err("127.0.0.1".to_string()) + } else { + Ok(()) + } + } + + fn validate_ipv6(addr: Ipv6Addr) -> Result<(), String> { + if addr.segments() == [0, 0, 0, 0, 0, 0, 0, 0] { + Err("::".to_string()) + } else if addr.segments() == [0, 0, 0, 0, 0, 0, 0, 1] { + Err("::1".to_string()) + } else { + Ok(()) + } + } + + fn validate_raw_string(name: &str) -> Result<(), String> { + if name == "localhost" { + Err("localhost".to_string()) + } else { + Ok(()) + } + } } #[cfg(test)] @@ -2535,6 +2580,58 @@ mod tests { ); } + #[test] + fn proxy_server_sends_a_message_with_error_when_quad_zeros_are_detected() { + init_test_logging(); + let test_name = "proxy_server_sends_a_message_with_error_when_quad_zeros_are_detected"; + let cryptde = main_cryptde(); + let http_request = b"GET /index.html HTTP/1.1\r\nHost: 0.0.0.0\r\n\r\n"; + let (proxy_server_mock, _, _) = make_recorder(); + let route_query_response = None; + let (neighborhood_mock, _, _) = make_recorder(); + let neighborhood_mock = + neighborhood_mock.route_query_response(route_query_response.clone()); + let socket_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); + let stream_key = StreamKey::make_meaningless_stream_key(); + let expected_data = http_request.to_vec(); + let msg_from_dispatcher = InboundClientData { + timestamp: SystemTime::now(), + peer_addr: socket_addr.clone(), + reception_port: Some(HTTP_PORT), + sequence_number: Some(0), + last_data: true, + is_clandestine: false, + data: expected_data.clone(), + }; + let stream_key_factory = StreamKeyFactoryMock::new().make_result(stream_key); + let system = System::new(test_name); + let mut subject = ProxyServer::new( + cryptde, + alias_cryptde(), + true, + Some(STANDARD_CONSUMING_WALLET_BALANCE), + false, + ); + subject.stream_key_factory = Box::new(stream_key_factory); + subject.logger = Logger::new(test_name); + let subject_addr: Addr = subject.start(); + let mut peer_actors = peer_actors_builder() + .proxy_server(proxy_server_mock) + .neighborhood(neighborhood_mock) + .build(); + // Get the dns_retry_result recipient so we can partially mock it... + let dns_retry_result_recipient = peer_actors.proxy_server.route_result_sub; + peer_actors.proxy_server.route_result_sub = dns_retry_result_recipient; //Partial mocking + subject_addr.try_send(BindMessage { peer_actors }).unwrap(); + + subject_addr.try_send(msg_from_dispatcher).unwrap(); + + System::current().stop(); + system.run(); + + TestLogHandler::new().exists_log_containing(&format!("ERROR: {test_name}: Request to wildcard IP detected - 0.0.0.0 (Most likely because Blockchain Service URL is not set)")); + } + #[test] fn proxy_server_uses_existing_route() { let main_cryptde = main_cryptde(); @@ -5919,6 +6016,40 @@ mod tests { assert_eq!(expected_result, clean_hostname); } + #[test] + fn hostname_is_valid_works() { + // IPv4 + assert_eq!( + Hostname::new("0.0.0.0").is_valid(), + Err("0.0.0.0".to_string()) + ); + assert_eq!( + Hostname::new("127.0.0.1").is_valid(), + Err("127.0.0.1".to_string()) + ); + assert_eq!(Hostname::new("192.168.1.158").is_valid(), Ok(())); + // IPv6 + assert_eq!( + Hostname::new("0:0:0:0:0:0:0:0").is_valid(), + Err("::".to_string()) + ); + assert_eq!( + Hostname::new("0:0:0:0:0:0:0:1").is_valid(), + Err("::1".to_string()) + ); + assert_eq!( + Hostname::new("2001:0db8:85a3:0000:0000:8a2e:0370:7334").is_valid(), + Ok(()) + ); + // Hostname + assert_eq!( + Hostname::new("localhost").is_valid(), + Err("localhost".to_string()) + ); + assert_eq!(Hostname::new("example.com").is_valid(), Ok(())); + assert_eq!(Hostname::new("https://example.com").is_valid(), Ok(())); + } + #[test] #[should_panic( expected = "ProxyServer should never get ShutdownStreamMsg about clandestine stream" diff --git a/node/src/sub_lib/blockchain_bridge.rs b/node/src/sub_lib/blockchain_bridge.rs index 6d43ea47b..a6fc4dd3c 100644 --- a/node/src/sub_lib/blockchain_bridge.rs +++ b/node/src/sub_lib/blockchain_bridge.rs @@ -85,10 +85,12 @@ impl ConsumingWalletBalances { mod tests { use crate::actor_system_factory::SubsFactory; use crate::blockchain::blockchain_bridge::{BlockchainBridge, BlockchainBridgeSubsFactoryReal}; - use crate::blockchain::test_utils::BlockchainInterfaceMock; + use crate::blockchain::test_utils::make_blockchain_interface_web3; use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMock; use crate::test_utils::recorder::{make_blockchain_bridge_subs_from_recorder, Recorder}; use actix::Actor; + use masq_lib::utils::find_free_port; + use std::sync::{Arc, Mutex}; #[test] fn blockchain_bridge_subs_debug() { @@ -102,11 +104,11 @@ mod tests { #[test] fn blockchain_bridge_subs_factory_produces_proper_subs() { let subject = BlockchainBridgeSubsFactoryReal {}; - let blockchain_interface = BlockchainInterfaceMock::default(); + let blockchain_interface = make_blockchain_interface_web3(find_free_port()); let persistent_config = PersistentConfigurationMock::new(); let accountant = BlockchainBridge::new( Box::new(blockchain_interface), - Box::new(persistent_config), + Arc::new(Mutex::new(persistent_config)), false, ); let addr = accountant.start(); diff --git a/node/src/sub_lib/wallet.rs b/node/src/sub_lib/wallet.rs index feb0667ec..4663e4938 100644 --- a/node/src/sub_lib/wallet.rs +++ b/node/src/sub_lib/wallet.rs @@ -128,7 +128,7 @@ impl Wallet { WalletKind::Address(address) => H160(address.0), WalletKind::PublicKey(public) => H160(*public.address()), WalletKind::SecretKey(key_provider) => key_provider.address(), - WalletKind::Uninitialized => panic!("No address for an uninitialized wallet!"), + WalletKind::Uninitialized => panic!("No address for an uninitialized wallet!"), // TODO: If we can get rid of it, it'll be awesome! } } diff --git a/node/src/test_utils/persistent_configuration_mock.rs b/node/src/test_utils/persistent_configuration_mock.rs index 00c50e7c2..c1e63bb7a 100644 --- a/node/src/test_utils/persistent_configuration_mock.rs +++ b/node/src/test_utils/persistent_configuration_mock.rs @@ -678,8 +678,8 @@ impl PersistentConfigurationMock { set_arbitrary_id_stamp_in_mock_impl!(); - // TODO: Review this, maybe we should return an error instead of panic? - // Also unsure why we have the else if clause. + // result_from allows a tester to push only a single value that can then be called multiple times. + // as opposed to pushing the same value for every call. fn result_from(results: &RefCell>) -> T { let mut borrowed = results.borrow_mut(); if borrowed.is_empty() { diff --git a/node/tests/utils.rs b/node/tests/utils.rs index e5199b3c6..adeec8c7e 100644 --- a/node/tests/utils.rs +++ b/node/tests/utils.rs @@ -485,7 +485,10 @@ impl MASQNode { "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC", ) .pair("--log-level", "trace") - .pair("--blockchain-service-url", "https://example.com") + .pair( + "--blockchain-service-url", + "https://nonexistentblockchainservice.com", + ) .args }