diff --git a/.github/workflows/ci-matrix.yml b/.github/workflows/ci-matrix.yml index 71a537d48..845add861 100644 --- a/.github/workflows/ci-matrix.yml +++ b/.github/workflows/ci-matrix.yml @@ -14,7 +14,7 @@ jobs: matrix: target: - { name: linux, os: ubuntu-22.04 } - - { name: macos, os: macos-12 } + - { name: macos, os: macos-13 } - { name: windows, os: windows-2022 } name: Build node on ${{ matrix.target.os }} diff --git a/README.md b/README.md index 404cd7573..84fb5ac44 100644 --- a/README.md +++ b/README.md @@ -39,8 +39,7 @@ stage - MASQ Network and it's developers are not responsible for any activity, o ## Source The MASQ project was forked from Substratum's Node project in order to carry on development after Substratum ceased operations in October of 2019. In 2021, Substratum's Node repositories were removed from GitHub, so the fork link -with MASQ was broken, but all credit for the original idea, the original design, and the first two years of MASQ's -development belongs to Substratum. +with MASQ was broken, but all credit for the original idea and the original design belongs to Substratum (and properly attributed through GPLv3 license) ## Running the MASQ Node @@ -452,6 +451,4 @@ recommend using a 64-bit version to build. We do plan to release binaries that will run on 32-bit Windows, but they will likely be built on 64-bit Windows. -Copyright (c) 2019-2022, MASQ Network - -Copyright (c) 2017-2019, Substratum LLC and/or its affiliates. All rights reserved. +Copyright (c) 2019-2024, MASQ Network diff --git a/automap/Cargo.lock b/automap/Cargo.lock index c970dc200..73d051ebb 100644 --- a/automap/Cargo.lock +++ b/automap/Cargo.lock @@ -137,7 +137,7 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "automap" -version = "0.8.0" +version = "0.8.2" dependencies = [ "crossbeam-channel 0.5.8", "flexi_logger", @@ -1051,7 +1051,7 @@ dependencies = [ [[package]] name = "masq_lib" -version = "0.8.0" +version = "0.8.2" dependencies = [ "actix", "clap", diff --git a/automap/Cargo.toml b/automap/Cargo.toml index c89c47ced..21c1acf91 100644 --- a/automap/Cargo.toml +++ b/automap/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "automap" -version = "0.8.0" +version = "0.8.2" authors = ["Dan Wiebe ", "MASQ"] license = "GPL-3.0-only" description = "Library full of code to make routers map ports through firewalls" diff --git a/dns_utility/Cargo.lock b/dns_utility/Cargo.lock index 52a3ec6b3..59f5a3bda 100644 --- a/dns_utility/Cargo.lock +++ b/dns_utility/Cargo.lock @@ -430,7 +430,7 @@ dependencies = [ [[package]] name = "dns_utility" -version = "0.8.0" +version = "0.8.2" dependencies = [ "core-foundation", "ipconfig 0.2.2", @@ -854,7 +854,7 @@ dependencies = [ [[package]] name = "masq_lib" -version = "0.8.0" +version = "0.8.2" dependencies = [ "actix", "clap", diff --git a/dns_utility/Cargo.toml b/dns_utility/Cargo.toml index f8ce5620f..50f358db8 100644 --- a/dns_utility/Cargo.toml +++ b/dns_utility/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dns_utility" -version = "0.8.0" +version = "0.8.2" license = "GPL-3.0-only" authors = ["Dan Wiebe ", "MASQ"] copyright = "Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved." diff --git a/masq/Cargo.toml b/masq/Cargo.toml index 87a20ad67..9a4fca0c1 100644 --- a/masq/Cargo.toml +++ b/masq/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "masq" -version = "0.8.0" +version = "0.8.2" authors = ["Dan Wiebe ", "MASQ"] license = "GPL-3.0-only" description = "Reference implementation of user interface for MASQ Node" diff --git a/masq/src/commands/configuration_command.rs b/masq/src/commands/configuration_command.rs index f9e72d5d4..62d2b4650 100644 --- a/masq/src/commands/configuration_command.rs +++ b/masq/src/commands/configuration_command.rs @@ -131,7 +131,10 @@ impl ConfigurationCommand { dump_parameter_line( stream, "Start block:", - &configuration.start_block.to_string(), + &configuration + .start_block_opt + .map(|m| m.separate_with_commas()) + .unwrap_or_else(|| "[Latest]".to_string()), ); Self::dump_value_list(stream, "Past neighbors:", &configuration.past_neighbors); let payment_thresholds = Self::preprocess_combined_parameters({ @@ -333,7 +336,7 @@ mod tests { exit_byte_rate: 129000000, exit_service_rate: 160000000, }, - start_block: 3456, + start_block_opt: None, scan_intervals: UiScanIntervals { pending_payable_sec: 150500, payable_sec: 155000, @@ -378,7 +381,7 @@ mod tests { |Max block count: [Unlimited]\n\ |Neighborhood mode: standard\n\ |Port mapping protocol: PCP\n\ -|Start block: 3456\n\ +|Start block: [Latest]\n\ |Past neighbors: neighbor 1\n\ | neighbor 2\n\ |Payment thresholds: \n\ @@ -433,7 +436,7 @@ mod tests { exit_byte_rate: 20, exit_service_rate: 30, }, - start_block: 3456, + start_block_opt: Some(1234567890u64), scan_intervals: UiScanIntervals { pending_payable_sec: 1000, payable_sec: 1000, @@ -476,7 +479,7 @@ mod tests { |Max block count: 100,000\n\ |Neighborhood mode: zero-hop\n\ |Port mapping protocol: PCP\n\ -|Start block: 3456\n\ +|Start block: 1,234,567,890\n\ |Past neighbors: [?]\n\ |Payment thresholds: \n\ | Debt threshold: 2,500 gwei\n\ diff --git a/masq/src/commands/set_configuration_command.rs b/masq/src/commands/set_configuration_command.rs index 99d979f07..6f822f1f3 100644 --- a/masq/src/commands/set_configuration_command.rs +++ b/masq/src/commands/set_configuration_command.rs @@ -7,6 +7,7 @@ use masq_lib::shared_schema::gas_price_arg; use masq_lib::shared_schema::min_hops_arg; use masq_lib::short_writeln; use masq_lib::utils::ExpectValue; +use std::num::IntErrorKind; #[derive(Debug, PartialEq, Eq)] pub struct SetConfigurationCommand { @@ -35,9 +36,17 @@ impl SetConfigurationCommand { } fn validate_start_block(start_block: String) -> Result<(), String> { - match start_block.parse::() { - Ok(_) => Ok(()), - _ => Err(start_block), + if "latest".eq_ignore_ascii_case(&start_block) || "none".eq_ignore_ascii_case(&start_block) { + Ok(()) + } else { + match start_block.parse::() { + Ok(_) => Ok(()), + Err(e) if e.kind() == &IntErrorKind::PosOverflow => Err( + format!("Unable to parse '{}' into a starting block number or provide 'none' or 'latest' for the latest block number: digits exceed {}.", + start_block, u64::MAX), + ), + Err(e) => Err(format!("Unable to parse '{}' into a starting block number or provide 'none' or 'latest' for the latest block number: {}.", start_block, e)) + } } } @@ -59,7 +68,7 @@ impl Command for SetConfigurationCommand { const SET_CONFIGURATION_ABOUT: &str = "Sets Node configuration parameters being enabled for this operation when the Node is running."; const START_BLOCK_HELP: &str = - "Ordinal number of the Ethereum block where scanning for transactions will start."; + "Ordinal number of the Ethereum block where scanning for transactions will start. Use 'latest' or 'none' for Latest block."; pub fn set_configurationify<'a>(shared_schema_arg: Arg<'a, 'a>) -> Arg<'a, 'a> { shared_schema_arg.takes_value(true).min_values(1) @@ -103,7 +112,7 @@ mod tests { ); assert_eq!( START_BLOCK_HELP, - "Ordinal number of the Ethereum block where scanning for transactions will start." + "Ordinal number of the Ethereum block where scanning for transactions will start. Use 'latest' or 'none' for Latest block." ); } @@ -122,10 +131,28 @@ mod tests { assert!(result.contains("cannot be used with one or more of the other specified arguments")); } + #[test] + fn validate_start_block_catches_invalid_values() { + assert_eq!(validate_start_block("abc".to_string()), Err("Unable to parse 'abc' into a starting block number or provide 'none' or 'latest' for the latest block number: invalid digit found in string.".to_string())); + assert_eq!(validate_start_block("918446744073709551615".to_string()), Err("Unable to parse '918446744073709551615' into a starting block number or provide 'none' or 'latest' for the latest block number: digits exceed 18446744073709551615.".to_string())); + assert_eq!(validate_start_block("123,456,789".to_string()), Err("Unable to parse '123,456,789' into a starting block number or provide 'none' or 'latest' for the latest block number: invalid digit found in string.".to_string())); + assert_eq!(validate_start_block("123'456'789".to_string()), Err("Unable to parse '123'456'789' into a starting block number or provide 'none' or 'latest' for the latest block number: invalid digit found in string.".to_string())); + } #[test] fn validate_start_block_works() { - assert!(validate_start_block("abc".to_string()).is_err()); - assert!(validate_start_block("1566".to_string()).is_ok()); + assert_eq!( + validate_start_block("18446744073709551615".to_string()), + Ok(()) + ); + assert_eq!(validate_start_block("1566".to_string()), Ok(())); + assert_eq!(validate_start_block("none".to_string()), Ok(())); + assert_eq!(validate_start_block("None".to_string()), Ok(())); + assert_eq!(validate_start_block("NONE".to_string()), Ok(())); + assert_eq!(validate_start_block("nOnE".to_string()), Ok(())); + assert_eq!(validate_start_block("latest".to_string()), Ok(())); + assert_eq!(validate_start_block("LATEST".to_string()), Ok(())); + assert_eq!(validate_start_block("LaTeST".to_string()), Ok(())); + assert_eq!(validate_start_block("lATEst".to_string()), Ok(())); } #[test] 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/Cargo.toml b/masq_lib/Cargo.toml index f87de81a1..af1fd5d15 100644 --- a/masq_lib/Cargo.toml +++ b/masq_lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "masq_lib" -version = "0.8.0" +version = "0.8.2" authors = ["Dan Wiebe ", "MASQ"] license = "GPL-3.0-only" description = "Code common to Node and masq; also, temporarily, to dns_utility" diff --git a/masq_lib/src/blockchains/blockchain_records.rs b/masq_lib/src/blockchains/blockchain_records.rs index 5227195f7..67a8870e2 100644 --- a/masq_lib/src/blockchains/blockchain_records.rs +++ b/masq_lib/src/blockchains/blockchain_records.rs @@ -2,15 +2,17 @@ use crate::blockchains::chains::Chain; use crate::constants::{ - AMOY_TESTNET_CONTRACT_CREATION_BLOCK, DEV_CHAIN_FULL_IDENTIFIER, - ETH_MAINNET_CONTRACT_CREATION_BLOCK, ETH_MAINNET_FULL_IDENTIFIER, ETH_ROPSTEN_FULL_IDENTIFIER, - MULTINODE_TESTNET_CONTRACT_CREATION_BLOCK, POLYGON_AMOY_FULL_IDENTIFIER, - POLYGON_MAINNET_CONTRACT_CREATION_BLOCK, POLYGON_MAINNET_FULL_IDENTIFIER, - ROPSTEN_TESTNET_CONTRACT_CREATION_BLOCK, + BASE_MAINNET_CONTRACT_CREATION_BLOCK, BASE_MAINNET_FULL_IDENTIFIER, + BASE_SEPOLIA_CONTRACT_CREATION_BLOCK, BASE_SEPOLIA_FULL_IDENTIFIER, DEV_CHAIN_FULL_IDENTIFIER, + ETH_MAINNET_CONTRACT_CREATION_BLOCK, ETH_MAINNET_FULL_IDENTIFIER, + ETH_ROPSTEN_CONTRACT_CREATION_BLOCK, ETH_ROPSTEN_FULL_IDENTIFIER, + MULTINODE_TESTNET_CONTRACT_CREATION_BLOCK, POLYGON_AMOY_CONTRACT_CREATION_BLOCK, + POLYGON_AMOY_FULL_IDENTIFIER, POLYGON_MAINNET_CONTRACT_CREATION_BLOCK, + POLYGON_MAINNET_FULL_IDENTIFIER, }; use ethereum_types::{Address, H160}; -pub const CHAINS: [BlockchainRecord; 5] = [ +pub const CHAINS: [BlockchainRecord; 7] = [ BlockchainRecord { self_id: Chain::PolyMainnet, num_chain_id: 137, @@ -25,19 +27,33 @@ pub const CHAINS: [BlockchainRecord; 5] = [ contract: ETH_MAINNET_CONTRACT_ADDRESS, contract_creation_block: ETH_MAINNET_CONTRACT_CREATION_BLOCK, }, + BlockchainRecord { + self_id: Chain::BaseMainnet, + num_chain_id: 8453, + literal_identifier: BASE_MAINNET_FULL_IDENTIFIER, + contract: BASE_MAINNET_CONTRACT_ADDRESS, + contract_creation_block: BASE_MAINNET_CONTRACT_CREATION_BLOCK, + }, + BlockchainRecord { + self_id: Chain::BaseSepolia, + num_chain_id: 84532, + literal_identifier: BASE_SEPOLIA_FULL_IDENTIFIER, + contract: BASE_SEPOLIA_TESTNET_CONTRACT_ADDRESS, + contract_creation_block: BASE_SEPOLIA_CONTRACT_CREATION_BLOCK, + }, BlockchainRecord { self_id: Chain::PolyAmoy, num_chain_id: 80002, literal_identifier: POLYGON_AMOY_FULL_IDENTIFIER, - contract: AMOY_TESTNET_CONTRACT_ADDRESS, - contract_creation_block: AMOY_TESTNET_CONTRACT_CREATION_BLOCK, + contract: POLYGON_AMOY_TESTNET_CONTRACT_ADDRESS, + contract_creation_block: POLYGON_AMOY_CONTRACT_CREATION_BLOCK, }, BlockchainRecord { self_id: Chain::EthRopsten, num_chain_id: 3, literal_identifier: ETH_ROPSTEN_FULL_IDENTIFIER, - contract: ROPSTEN_TESTNET_CONTRACT_ADDRESS, - contract_creation_block: ROPSTEN_TESTNET_CONTRACT_CREATION_BLOCK, + contract: ETH_ROPSTEN_TESTNET_CONTRACT_ADDRESS, + contract_creation_block: ETH_ROPSTEN_CONTRACT_CREATION_BLOCK, }, BlockchainRecord { self_id: Chain::Dev, @@ -68,17 +84,27 @@ const ETH_MAINNET_CONTRACT_ADDRESS: Address = H160([ ]); // $tMASQ (Amoy) -const AMOY_TESTNET_CONTRACT_ADDRESS: Address = H160([ +const POLYGON_AMOY_TESTNET_CONTRACT_ADDRESS: Address = H160([ 0xd9, 0x8c, 0x3e, 0xbd, 0x6b, 0x7f, 0x9b, 0x7c, 0xda, 0x24, 0x49, 0xec, 0xac, 0x00, 0xd1, 0xe5, 0xf4, 0x7a, 0x81, 0x93, ]); // SHRD (Ropsten) -const ROPSTEN_TESTNET_CONTRACT_ADDRESS: Address = H160([ +const ETH_ROPSTEN_TESTNET_CONTRACT_ADDRESS: Address = H160([ 0x38, 0x4d, 0xec, 0x25, 0xe0, 0x3f, 0x94, 0x93, 0x17, 0x67, 0xce, 0x4c, 0x35, 0x56, 0x16, 0x84, 0x68, 0xba, 0x24, 0xc3, ]); +const BASE_MAINNET_CONTRACT_ADDRESS: Address = H160([ + 0x45, 0xD9, 0xC1, 0x01, 0xa3, 0x87, 0x0C, 0xa5, 0x02, 0x45, 0x82, 0xfd, 0x78, 0x8F, 0x4E, 0x1e, + 0x8F, 0x79, 0x71, 0xc3, +]); + +const BASE_SEPOLIA_TESTNET_CONTRACT_ADDRESS: Address = H160([ + 0x89, 0x8e, 0x1c, 0xe7, 0x20, 0x08, 0x4A, 0x90, 0x2b, 0xc3, 0x7d, 0xd8, 0x22, 0xed, 0x6d, 0x6a, + 0x5f, 0x02, 0x7e, 0x10, +]); + const MULTINODE_TESTNET_CONTRACT_ADDRESS: Address = H160([ 0x59, 0x88, 0x2e, 0x4a, 0x8f, 0x5d, 0x24, 0x64, 0x3d, 0x4d, 0xda, 0x42, 0x29, 0x22, 0xa8, 0x70, 0xf1, 0xb3, 0xe6, 0x64, @@ -88,9 +114,7 @@ const MULTINODE_TESTNET_CONTRACT_ADDRESS: Address = H160([ mod tests { use super::*; use crate::blockchains::chains::chain_from_chain_identifier_opt; - use crate::constants::{ - AMOY_TESTNET_CONTRACT_CREATION_BLOCK, POLYGON_MAINNET_CONTRACT_CREATION_BLOCK, - }; + use crate::constants::BASE_MAINNET_CONTRACT_CREATION_BLOCK; use std::collections::HashSet; use std::iter::FromIterator; @@ -98,10 +122,12 @@ mod tests { fn record_returns_correct_blockchain_record() { let test_array = [ assert_returns_correct_record(Chain::EthMainnet, 1), - assert_returns_correct_record(Chain::Dev, 2), assert_returns_correct_record(Chain::EthRopsten, 3), assert_returns_correct_record(Chain::PolyMainnet, 137), assert_returns_correct_record(Chain::PolyAmoy, 80002), + assert_returns_correct_record(Chain::BaseMainnet, 8453), + assert_returns_correct_record(Chain::BaseSepolia, 84532), + assert_returns_correct_record(Chain::Dev, 2), ]; assert_exhaustive(&test_array) } @@ -118,6 +144,8 @@ mod tests { assert_from_str(Chain::PolyAmoy), assert_from_str(Chain::EthMainnet), assert_from_str(Chain::EthRopsten), + assert_from_str(Chain::BaseMainnet), + assert_from_str(Chain::BaseSepolia), assert_from_str(Chain::Dev), ]; assert_exhaustive(&test_array) @@ -137,18 +165,23 @@ mod tests { #[test] fn chains_are_ordered_by_their_significance_for_users() { let test_array = [ - assert_chain_significance(0, Chain::PolyMainnet), - assert_chain_significance(1, Chain::EthMainnet), - assert_chain_significance(2, Chain::PolyAmoy), - assert_chain_significance(3, Chain::EthRopsten), - assert_chain_significance(4, Chain::Dev), + Chain::PolyMainnet, + Chain::EthMainnet, + Chain::BaseMainnet, + Chain::BaseSepolia, + Chain::PolyAmoy, + Chain::EthRopsten, + Chain::Dev, ]; + test_array + .iter() + .enumerate() + .for_each(assert_chain_significance); assert_exhaustive(&test_array) } - fn assert_chain_significance(idx: usize, chain: Chain) -> Chain { - assert_eq!(CHAINS[idx].self_id, chain, "Error at index {}", idx); - chain + fn assert_chain_significance((idx, chain): (usize, &Chain)) { + assert_eq!(CHAINS[idx].self_id, *chain, "Error at index {}", idx); } #[test] @@ -177,8 +210,8 @@ mod tests { num_chain_id: 3, self_id: examined_chain, literal_identifier: "eth-ropsten", - contract: ROPSTEN_TESTNET_CONTRACT_ADDRESS, - contract_creation_block: ROPSTEN_TESTNET_CONTRACT_CREATION_BLOCK, + contract: ETH_ROPSTEN_TESTNET_CONTRACT_ADDRESS, + contract_creation_block: ETH_ROPSTEN_CONTRACT_CREATION_BLOCK, } ); } @@ -209,8 +242,40 @@ mod tests { num_chain_id: 80002, self_id: examined_chain, literal_identifier: "polygon-amoy", - contract: AMOY_TESTNET_CONTRACT_ADDRESS, - contract_creation_block: AMOY_TESTNET_CONTRACT_CREATION_BLOCK, + contract: POLYGON_AMOY_TESTNET_CONTRACT_ADDRESS, + contract_creation_block: POLYGON_AMOY_CONTRACT_CREATION_BLOCK, + } + ); + } + + #[test] + fn base_mainnet_record_is_properly_declared() { + let examined_chain = Chain::BaseMainnet; + let chain_record = return_examined(examined_chain); + assert_eq!( + chain_record, + &BlockchainRecord { + num_chain_id: 8453, + self_id: examined_chain, + literal_identifier: "base-mainnet", + contract: BASE_MAINNET_CONTRACT_ADDRESS, + contract_creation_block: BASE_MAINNET_CONTRACT_CREATION_BLOCK, + } + ); + } + + #[test] + fn base_sepolia_record_is_properly_declared() { + let examined_chain = Chain::BaseSepolia; + let chain_record = return_examined(examined_chain); + assert_eq!( + chain_record, + &BlockchainRecord { + num_chain_id: 84532, + self_id: examined_chain, + literal_identifier: "base-sepolia", + contract: BASE_SEPOLIA_TESTNET_CONTRACT_ADDRESS, + contract_creation_block: BASE_SEPOLIA_CONTRACT_CREATION_BLOCK, } ); } @@ -226,7 +291,7 @@ mod tests { self_id: examined_chain, literal_identifier: "dev", contract: MULTINODE_TESTNET_CONTRACT_ADDRESS, - contract_creation_block: 0, + contract_creation_block: MULTINODE_TESTNET_CONTRACT_CREATION_BLOCK, } ); } @@ -240,9 +305,11 @@ mod tests { let test_array = [ assert_chain_from_chain_identifier_opt("eth-mainnet", Some(Chain::EthMainnet)), assert_chain_from_chain_identifier_opt("eth-ropsten", Some(Chain::EthRopsten)), - assert_chain_from_chain_identifier_opt("dev", Some(Chain::Dev)), assert_chain_from_chain_identifier_opt("polygon-mainnet", Some(Chain::PolyMainnet)), assert_chain_from_chain_identifier_opt("polygon-amoy", Some(Chain::PolyAmoy)), + assert_chain_from_chain_identifier_opt("base-mainnet", Some(Chain::BaseMainnet)), + assert_chain_from_chain_identifier_opt("base-sepolia", Some(Chain::BaseSepolia)), + assert_chain_from_chain_identifier_opt("dev", Some(Chain::Dev)), ]; assert_exhaustive(&test_array) } diff --git a/masq_lib/src/blockchains/chains.rs b/masq_lib/src/blockchains/chains.rs index 433d5524c..b7733b842 100644 --- a/masq_lib/src/blockchains/chains.rs +++ b/masq_lib/src/blockchains/chains.rs @@ -2,8 +2,9 @@ use crate::blockchains::blockchain_records::{BlockchainRecord, CHAINS}; use crate::constants::{ - DEFAULT_CHAIN, DEV_CHAIN_FULL_IDENTIFIER, ETH_MAINNET_FULL_IDENTIFIER, - ETH_ROPSTEN_FULL_IDENTIFIER, POLYGON_AMOY_FULL_IDENTIFIER, POLYGON_MAINNET_FULL_IDENTIFIER, + BASE_MAINNET_FULL_IDENTIFIER, BASE_SEPOLIA_FULL_IDENTIFIER, DEFAULT_CHAIN, + DEV_CHAIN_FULL_IDENTIFIER, ETH_MAINNET_FULL_IDENTIFIER, ETH_ROPSTEN_FULL_IDENTIFIER, + POLYGON_AMOY_FULL_IDENTIFIER, POLYGON_MAINNET_FULL_IDENTIFIER, }; use serde_derive::{Deserialize, Serialize}; @@ -13,6 +14,8 @@ pub enum Chain { EthRopsten, PolyMainnet, PolyAmoy, + BaseMainnet, + BaseSepolia, Dev, } @@ -28,6 +31,10 @@ impl From<&str> for Chain { Chain::PolyMainnet } else if str == ETH_MAINNET_FULL_IDENTIFIER { Chain::EthMainnet + } else if str == BASE_MAINNET_FULL_IDENTIFIER { + Chain::BaseMainnet + } else if str == BASE_SEPOLIA_FULL_IDENTIFIER { + Chain::BaseSepolia } else if str == POLYGON_AMOY_FULL_IDENTIFIER { Chain::PolyAmoy } else if str == ETH_ROPSTEN_FULL_IDENTIFIER { @@ -56,7 +63,7 @@ impl Chain { } fn mainnets() -> &'static [Chain] { - &[Chain::PolyMainnet, Chain::EthMainnet] + &[Chain::PolyMainnet, Chain::BaseMainnet, Chain::EthMainnet] } } diff --git a/masq_lib/src/constants.rs b/masq_lib/src/constants.rs index 1a0542837..9cfdc90c6 100644 --- a/masq_lib/src/constants.rs +++ b/masq_lib/src/constants.rs @@ -25,9 +25,11 @@ pub const MASQ_TOTAL_SUPPLY: u64 = 37_500_000; pub const WEIS_IN_GWEI: i128 = 1_000_000_000; pub const ETH_MAINNET_CONTRACT_CREATION_BLOCK: u64 = 11_170_708; -pub const ROPSTEN_TESTNET_CONTRACT_CREATION_BLOCK: u64 = 8_688_171; +pub const ETH_ROPSTEN_CONTRACT_CREATION_BLOCK: u64 = 8_688_171; pub const POLYGON_MAINNET_CONTRACT_CREATION_BLOCK: u64 = 14_863_650; -pub const AMOY_TESTNET_CONTRACT_CREATION_BLOCK: u64 = 5_323_366; +pub const POLYGON_AMOY_CONTRACT_CREATION_BLOCK: u64 = 5_323_366; +pub const BASE_MAINNET_CONTRACT_CREATION_BLOCK: u64 = 19_711_235; +pub const BASE_SEPOLIA_CONTRACT_CREATION_BLOCK: u64 = 14_732_730; pub const MULTINODE_TESTNET_CONTRACT_CREATION_BLOCK: u64 = 0; //Migration versions @@ -89,12 +91,15 @@ pub const CHAIN_IDENTIFIER_DELIMITER: char = ':'; //chains const POLYGON_FAMILY: &str = "polygon"; const ETH_FAMILY: &str = "eth"; +const BASE_FAMILY: &str = "base"; const MAINNET: &str = "mainnet"; const LINK: char = '-'; pub const POLYGON_MAINNET_FULL_IDENTIFIER: &str = concatcp!(POLYGON_FAMILY, LINK, MAINNET); pub const POLYGON_AMOY_FULL_IDENTIFIER: &str = concatcp!(POLYGON_FAMILY, LINK, "amoy"); pub const ETH_MAINNET_FULL_IDENTIFIER: &str = concatcp!(ETH_FAMILY, LINK, MAINNET); pub const ETH_ROPSTEN_FULL_IDENTIFIER: &str = concatcp!(ETH_FAMILY, LINK, "ropsten"); +pub const BASE_MAINNET_FULL_IDENTIFIER: &str = concatcp!(BASE_FAMILY, LINK, MAINNET); +pub const BASE_SEPOLIA_FULL_IDENTIFIER: &str = concatcp!(BASE_FAMILY, LINK, "sepolia"); pub const DEV_CHAIN_FULL_IDENTIFIER: &str = "dev"; #[cfg(test)] @@ -118,9 +123,11 @@ mod tests { assert_eq!(MASQ_TOTAL_SUPPLY, 37_500_000); assert_eq!(WEIS_IN_GWEI, 1_000_000_000); assert_eq!(ETH_MAINNET_CONTRACT_CREATION_BLOCK, 11_170_708); - assert_eq!(ROPSTEN_TESTNET_CONTRACT_CREATION_BLOCK, 8_688_171); + assert_eq!(ETH_ROPSTEN_CONTRACT_CREATION_BLOCK, 8_688_171); assert_eq!(POLYGON_MAINNET_CONTRACT_CREATION_BLOCK, 14_863_650); - assert_eq!(AMOY_TESTNET_CONTRACT_CREATION_BLOCK, 5_323_366); + assert_eq!(POLYGON_AMOY_CONTRACT_CREATION_BLOCK, 5_323_366); + assert_eq!(BASE_MAINNET_CONTRACT_CREATION_BLOCK, 19_711_235); + assert_eq!(BASE_SEPOLIA_CONTRACT_CREATION_BLOCK, 14_732_730); assert_eq!(MULTINODE_TESTNET_CONTRACT_CREATION_BLOCK, 0); assert_eq!(CONFIGURATOR_PREFIX, 0x0001_0000_0000_0000); assert_eq!(CONFIGURATOR_READ_ERROR, CONFIGURATOR_PREFIX | 1); @@ -159,12 +166,14 @@ mod tests { assert_eq!(CHAIN_IDENTIFIER_DELIMITER, ':'); assert_eq!(POLYGON_FAMILY, "polygon"); assert_eq!(ETH_FAMILY, "eth"); + assert_eq!(BASE_FAMILY, "base"); assert_eq!(MAINNET, "mainnet"); assert_eq!(LINK, '-'); assert_eq!(POLYGON_MAINNET_FULL_IDENTIFIER, "polygon-mainnet"); assert_eq!(POLYGON_AMOY_FULL_IDENTIFIER, "polygon-amoy"); assert_eq!(ETH_MAINNET_FULL_IDENTIFIER, "eth-mainnet"); assert_eq!(ETH_ROPSTEN_FULL_IDENTIFIER, "eth-ropsten"); + assert_eq!(BASE_SEPOLIA_FULL_IDENTIFIER, "base-sepolia"); assert_eq!(DEV_CHAIN_FULL_IDENTIFIER, "dev"); assert_eq!( CLIENT_REQUEST_PAYLOAD_CURRENT_VERSION, diff --git a/masq_lib/src/messages.rs b/masq_lib/src/messages.rs index 59522171e..45842e419 100644 --- a/masq_lib/src/messages.rs +++ b/masq_lib/src/messages.rs @@ -492,7 +492,7 @@ pub struct UiConfigurationResponse { #[serde(rename = "portMappingProtocol")] pub port_mapping_protocol_opt: Option, #[serde(rename = "startBlock")] - pub start_block: u64, + pub start_block_opt: Option, #[serde(rename = "consumingWalletPrivateKeyOpt")] pub consuming_wallet_private_key_opt: Option, // This item is calculated from the private key, not stored in the database, so that diff --git a/masq_lib/src/shared_schema.rs b/masq_lib/src/shared_schema.rs index cc3e34b54..276108d2e 100644 --- a/masq_lib/src/shared_schema.rs +++ b/masq_lib/src/shared_schema.rs @@ -1,9 +1,10 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::constants::{ - DEFAULT_GAS_PRICE, DEFAULT_UI_PORT, DEV_CHAIN_FULL_IDENTIFIER, ETH_MAINNET_FULL_IDENTIFIER, - ETH_ROPSTEN_FULL_IDENTIFIER, HIGHEST_USABLE_PORT, LOWEST_USABLE_INSECURE_PORT, - POLYGON_AMOY_FULL_IDENTIFIER, POLYGON_MAINNET_FULL_IDENTIFIER, + BASE_MAINNET_FULL_IDENTIFIER, BASE_SEPOLIA_FULL_IDENTIFIER, DEFAULT_GAS_PRICE, DEFAULT_UI_PORT, + DEV_CHAIN_FULL_IDENTIFIER, ETH_MAINNET_FULL_IDENTIFIER, ETH_ROPSTEN_FULL_IDENTIFIER, + HIGHEST_USABLE_PORT, LOWEST_USABLE_INSECURE_PORT, POLYGON_AMOY_FULL_IDENTIFIER, + POLYGON_MAINNET_FULL_IDENTIFIER, }; use crate::crash_point::CrashPoint; use clap::{App, Arg}; @@ -13,6 +14,7 @@ pub const BLOCKCHAIN_SERVICE_HELP: &str = "The Ethereum client you wish to use to provide Blockchain \ exit services from your MASQ Node (e.g. http://localhost:8545, \ https://ropsten.infura.io/v3/YOUR-PROJECT-ID, https://mainnet.infura.io/v3/YOUR-PROJECT-ID), \ + https://base-mainnet.g.alchemy.com/v2/d66UL0lPrltmweEqVsv3opBSVI3wkL8I, \ https://polygon-mainnet.infura.io/v3/YOUR-PROJECT-ID"; pub const CHAIN_HELP: &str = "The blockchain network MASQ Node will configure itself to use. You must ensure the \ @@ -64,8 +66,9 @@ pub const NEIGHBORS_HELP: &str = "One or more Node descriptors for running Nodes on startup. A Node descriptor looks similar to one of these:\n\n\ masq://polygon-mainnet:d2U3Dv1BqtS5t_Zz3mt9_sCl7AgxUlnkB4jOMElylrU@172.50.48.6:9342\n\ masq://eth-mainnet:gBviQbjOS3e5ReFQCvIhUM3i02d1zPleo1iXg_EN6zQ@86.75.30.9:5542\n\ + masq://base-mainnet:ZjPLnb9RrgsRM1D9edqH8jx9DkbPZSWqqFqLnmdKhsk@112.55.78.0:7878\n\ masq://polygon-amoy:A6PGHT3rRjaeFpD_rFi3qGEXAVPq7bJDfEUZpZaIyq8@14.10.50.6:10504\n\ - masq://eth-ropsten:OHsC2CAm4rmfCkaFfiynwxflUgVTJRb2oY5mWxNCQkY@150.60.42.72:6642/4789/5254\n\n\ + masq://base-sepolia:OHsC2CAm4rmfCkaFfiynwxflUgVTJRb2oY5mWxNCQkY@150.60.42.72:6642/4789/5254\n\n\ Notice each of the different chain identifiers in the masq protocol prefix - they determine a family of chains \ and also the network the descriptor belongs to (mainnet or a testnet). See also the last descriptor which shows \ a configuration with multiple clandestine ports.\n\n\ @@ -256,6 +259,8 @@ pub fn official_chain_names() -> &'static [&'static str] { &[ POLYGON_MAINNET_FULL_IDENTIFIER, ETH_MAINNET_FULL_IDENTIFIER, + BASE_MAINNET_FULL_IDENTIFIER, + BASE_SEPOLIA_FULL_IDENTIFIER, POLYGON_AMOY_FULL_IDENTIFIER, ETH_ROPSTEN_FULL_IDENTIFIER, DEV_CHAIN_FULL_IDENTIFIER, @@ -670,11 +675,11 @@ impl ConfiguratorError { #[cfg(test)] mod tests { - use super::*; use crate::blockchains::chains::Chain; use crate::shared_schema::common_validators::validate_non_zero_u16; use crate::shared_schema::{common_validators, official_chain_names}; + use std::collections::HashSet; #[test] fn constants_have_correct_values() { @@ -683,6 +688,7 @@ mod tests { "The Ethereum client you wish to use to provide Blockchain \ exit services from your MASQ Node (e.g. http://localhost:8545, \ https://ropsten.infura.io/v3/YOUR-PROJECT-ID, https://mainnet.infura.io/v3/YOUR-PROJECT-ID), \ + https://base-mainnet.g.alchemy.com/v2/d66UL0lPrltmweEqVsv3opBSVI3wkL8I, \ https://polygon-mainnet.infura.io/v3/YOUR-PROJECT-ID" ); assert_eq!( @@ -757,8 +763,9 @@ mod tests { on startup. A Node descriptor looks similar to one of these:\n\n\ masq://polygon-mainnet:d2U3Dv1BqtS5t_Zz3mt9_sCl7AgxUlnkB4jOMElylrU@172.50.48.6:9342\n\ masq://eth-mainnet:gBviQbjOS3e5ReFQCvIhUM3i02d1zPleo1iXg_EN6zQ@86.75.30.9:5542\n\ + masq://base-mainnet:ZjPLnb9RrgsRM1D9edqH8jx9DkbPZSWqqFqLnmdKhsk@112.55.78.0:7878\n\ masq://polygon-amoy:A6PGHT3rRjaeFpD_rFi3qGEXAVPq7bJDfEUZpZaIyq8@14.10.50.6:10504\n\ - masq://eth-ropsten:OHsC2CAm4rmfCkaFfiynwxflUgVTJRb2oY5mWxNCQkY@150.60.42.72:6642/4789/5254\n\n\ + masq://base-sepolia:OHsC2CAm4rmfCkaFfiynwxflUgVTJRb2oY5mWxNCQkY@150.60.42.72:6642/4789/5254\n\n\ Notice each of the different chain identifiers in the masq protocol prefix - they determine a family of chains \ and also the network the descriptor belongs to (mainnet or a testnet). See also the last descriptor which shows \ a configuration with multiple clandestine ports.\n\n\ @@ -1141,12 +1148,34 @@ mod tests { #[test] fn official_chain_names_are_reliable() { - let mut iterator = official_chain_names().iter(); - assert_eq!(Chain::from(*iterator.next().unwrap()), Chain::PolyMainnet); - assert_eq!(Chain::from(*iterator.next().unwrap()), Chain::EthMainnet); - assert_eq!(Chain::from(*iterator.next().unwrap()), Chain::PolyAmoy); - assert_eq!(Chain::from(*iterator.next().unwrap()), Chain::EthRopsten); - assert_eq!(Chain::from(*iterator.next().unwrap()), Chain::Dev); - assert_eq!(iterator.next(), None) + let expected_supported_chains = [ + Chain::PolyMainnet, + Chain::EthMainnet, + Chain::BaseMainnet, + Chain::BaseSepolia, + Chain::PolyAmoy, + Chain::EthRopsten, + Chain::Dev, + ] + .into_iter() + .collect::>(); + + let chain_names_recognizable_by_clap = official_chain_names(); + + let chains_from_clap = chain_names_recognizable_by_clap + .into_iter() + .map(|chain_name| Chain::from(*chain_name)) + .collect::>(); + let differences = chains_from_clap + .symmetric_difference(&expected_supported_chains) + .collect::>(); + assert!( + differences.is_empty(), + "There are differences in the Clap schema in the collection of supported chains, \ + between the expected values {:?} and actual {:?}, specifically {:?}", + expected_supported_chains, + chains_from_clap, + differences + ); } } 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..424a4433d 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 } @@ -65,7 +65,7 @@ impl MBCSBuilder { self.store_response_string(raw_string) } - pub fn response(self, result: R, id: u64) -> Self + pub fn ok_response(self, result: R, id: u64) -> Self where R: Serialize, { @@ -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/Cargo.toml b/multinode_integration_tests/Cargo.toml index edf7a03f4..18bb5a16d 100644 --- a/multinode_integration_tests/Cargo.toml +++ b/multinode_integration_tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "multinode_integration_tests" -version = "0.8.0" +version = "0.8.2" authors = ["Dan Wiebe ", "MASQ"] license = "GPL-3.0-only" description = "" diff --git a/multinode_integration_tests/docker/Dockerfile b/multinode_integration_tests/docker/Dockerfile index 9adb3b09b..e33d2ee53 100644 --- a/multinode_integration_tests/docker/Dockerfile +++ b/multinode_integration_tests/docker/Dockerfile @@ -2,7 +2,8 @@ #FROM debian:stable-slim #FROM debian:buster-slim #FROM debian:bullseye-slim -FROM debian:bookworm-slim +#FROM debian:bookworm-slim +FROM debian:trixie-slim RUN apt-get update && \ apt-get install -y libc6 && \ diff --git a/multinode_integration_tests/src/main.rs b/multinode_integration_tests/src/main.rs index d78421672..8f705fed9 100644 --- a/multinode_integration_tests/src/main.rs +++ b/multinode_integration_tests/src/main.rs @@ -1,11 +1,10 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use self::sub_lib::utils::indicates_dead_stream; use masq_lib::command::{Command, StdStreams}; use masq_lib::constants::{HIGHEST_USABLE_PORT, LOWEST_USABLE_INSECURE_PORT}; -use node_lib::sub_lib; use node_lib::sub_lib::framer::Framer; use node_lib::sub_lib::node_addr::NodeAddr; +use node_lib::sub_lib::utils::indicates_dead_stream; use node_lib::test_utils::data_hunk::DataHunk; use node_lib::test_utils::data_hunk_framer::DataHunkFramer; use std::borrow::BorrowMut; @@ -14,10 +13,9 @@ use std::env; use std::io; use std::io::Read; use std::io::Write; -use std::net::Shutdown; -use std::net::SocketAddr; use std::net::TcpListener; use std::net::TcpStream; +use std::net::{Shutdown, SocketAddr}; use std::process; use std::str::FromStr; use std::sync::{Arc, Mutex, MutexGuard}; @@ -223,10 +221,10 @@ impl MockNode { } fn usage(stderr: &mut dyn Write) -> u8 { - writeln! (stderr, "Usage: MockNode ://... where is the address MockNode is running on and is between {} and {}", - LOWEST_USABLE_INSECURE_PORT, - HIGHEST_USABLE_PORT, - ).unwrap (); + writeln!(stderr, "Usage: MockNode ://... where is the address MockNode is running on and is between {} and {}", + LOWEST_USABLE_INSECURE_PORT, + HIGHEST_USABLE_PORT, + ).unwrap(); 1 } @@ -369,7 +367,7 @@ mod tests { assert_eq!(result, 1); let stderr = holder.stderr; - assert_eq! (stderr.get_string (), String::from ("Usage: MockNode ://... where is the address MockNode is running on and is between 1025 and 65535\n\n")); + assert_eq!(stderr.get_string(), String::from("Usage: MockNode ://... where is the address MockNode is running on and is between 1025 and 65535\n\n")); } #[test] diff --git a/multinode_integration_tests/src/mock_blockchain_client_server.rs b/multinode_integration_tests/src/mock_blockchain_client_server.rs index 6eef0e839..2f0d7a9c5 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 { @@ -28,8 +29,8 @@ mod tests { let _cluster = MASQNodeCluster::start(); let port = find_free_port(); let _subject = MockBlockchainClientServer::builder(port) - .response("Thank you and good night", 40) - .run_on_docker() + .ok_response("Thank you and good night", 40) + .run_in_docker() .start(); let mut client = connect(port); let chunks = vec![ @@ -59,12 +60,12 @@ mod tests { let _cluster = MASQNodeCluster::start(); let port = find_free_port(); let _subject = MockBlockchainClientServer::builder(port) - .response("Welcome, and thanks for coming!", 39) - .response("Thank you and good night", 40) - .run_on_docker() + .ok_response("Welcome, and thanks for coming!", 39) + .ok_response("Thank you and good night", 40) + .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(); + 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(); let (_, body) = receive_response(&mut client); assert_eq!( @@ -84,8 +85,8 @@ mod tests { let _cluster = MASQNodeCluster::start(); let port = find_free_port(); let _subject = MockBlockchainClientServer::builder(port) - .response("irrelevant".to_string(), 42) - .run_on_docker() + .ok_response("irrelevant".to_string(), 42) + .run_in_docker() .start(); let mut client = connect(port); let request = b"POST /biddle HTTP/1.1\r\n\r\nbody"; @@ -101,8 +102,8 @@ mod tests { let _cluster = MASQNodeCluster::start(); let port = find_free_port(); let _subject = MockBlockchainClientServer::builder(port) - .response("irrelevant".to_string(), 42) - .run_on_docker() + .ok_response("irrelevant".to_string(), 42) + .run_in_docker() .start(); let mut client = connect(port); let request = b"GET /booga\r\nContent-Length: 4\r\n\r\nbody"; @@ -118,8 +119,8 @@ mod tests { let _cluster = MASQNodeCluster::start(); let port = find_free_port(); let _subject = MockBlockchainClientServer::builder(port) - .response("irrelevant".to_string(), 42) - .run_on_docker() + .ok_response("irrelevant".to_string(), 42) + .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"; @@ -137,10 +138,10 @@ mod tests { let subject = MockBlockchainClientServer::builder(port) .notifier(notifier) .begin_batch() - .response(1234u64, 40) + .ok_response(1234u64, 40) .error(1234, "My tummy hurts", None as Option<()>) .end_batch() - .response( + .ok_response( Person { name: "Billy".to_string(), age: 15, @@ -155,7 +156,7 @@ mod tests { age: 37, }), ) - .run_on_docker() + .run_in_docker() .start(); let mut client = connect(port); @@ -198,7 +199,7 @@ mod tests { assert_eq!(notified.try_recv().is_err(), true); let requests = subject.requests(); - assert_eq! (requests, vec! [ + assert_eq!(requests, vec![ "POST /biddle HTTP/1.1\r\nContent-Type: application-json\r\nContent-Length: 82\r\n\r\n{\"jsonrpc\": \"2.0\", \"method\": \"first\", \"params\": [\"biddle\", \"de\", \"bee\"], \"id\": 40}".to_string(), "POST /biddle HTTP/1.1\r\nContent-Type: application-json\r\nContent-Length: 48\r\n\r\n{\"jsonrpc\": \"2.0\", \"method\": \"second\", \"id\": 42}".to_string(), "POST /biddle HTTP/1.1\r\nContent-Type: application-json\r\nContent-Length: 47\r\n\r\n{\"jsonrpc\": \"2.0\", \"method\": \"third\", \"id\": 42}".to_string(), @@ -210,14 +211,14 @@ mod tests { let _cluster = MASQNodeCluster::start(); let port = find_free_port(); let subject = MockBlockchainClientServer::builder(port) - .response( + .ok_response( Person { name: "Billy".to_string(), age: 15, }, 42, ) - .run_on_docker() + .run_in_docker() .start(); let mut client = connect(port); let request = @@ -232,7 +233,7 @@ mod tests { r#"{"jsonrpc": "2.0", "result": {"name":"Billy","age":15}, "id": 42}"# ); let requests = subject.requests(); - assert_eq! (requests, vec! [ + assert_eq!(requests, vec![ "POST / HTTP/1.1\r\ncontent-type: application/json\r\nuser-agent: web3.rs\r\nhost: 172.18.0.1:32768\r\ncontent-length: 308\r\n\r\n{\"jsonrpc\":\"2.0\",\"method\":\"eth_getLogs\",\"params\":[{\"address\":\"0x59882e4a8f5d24643d4dda422922a870f1b3e664\",\"fromBlock\":\"0x3e8\",\"toBlock\":\"latest\",\"topics\":[\"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef\",null,\"0x00000000000000000000000027d9a2ac83b493f88ce9b4532edcf74e95b9788d\"]}],\"id\":0}".to_string() ]) } @@ -336,6 +337,6 @@ mod tests { body.len(), body ) - .into_bytes() + .into_bytes() } } diff --git a/multinode_integration_tests/tests/blockchain_interaction_test.rs b/multinode_integration_tests/tests/blockchain_interaction_test.rs index 7f86d0e10..05bc1f8b5 100644 --- a/multinode_integration_tests/tests/blockchain_interaction_test.rs +++ b/multinode_integration_tests/tests/blockchain_interaction_test.rs @@ -32,8 +32,8 @@ fn debtors_are_credited_once_but_not_twice() { // Create and initialize mock blockchain client: prepare a receivable at block 2000 eprintln!("Setting up mock blockchain client"); let blockchain_client_server = MBCSBuilder::new(mbcs_port) - .response("0x5DC", 1) // eth_blockNumber 1500 - .response( + .ok_response("0x5DC", 1) // eth_blockNumber 1500 + .ok_response( vec![LogObject { removed: false, log_index: Some("0x20".to_string()), @@ -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() diff --git a/multinode_integration_tests/tests/bookkeeping_test.rs b/multinode_integration_tests/tests/bookkeeping_test.rs index 41642a724..cbed01e84 100644 --- a/multinode_integration_tests/tests/bookkeeping_test.rs +++ b/multinode_integration_tests/tests/bookkeeping_test.rs @@ -42,7 +42,7 @@ fn provided_and_consumed_services_are_recorded_in_databases() { let payables = non_pending_payables(&originating_node); // Waiting until the serving nodes have finished accruing their receivables - thread::sleep(Duration::from_secs(7)); + thread::sleep(Duration::from_secs(10)); // get all receivables from all other nodes let receivable_balances = non_originating_nodes diff --git a/multinode_integration_tests/tests/communication_failure_test.rs b/multinode_integration_tests/tests/communication_failure_test.rs index c71b4b4e6..b123d30cc 100644 --- a/multinode_integration_tests/tests/communication_failure_test.rs +++ b/multinode_integration_tests/tests/communication_failure_test.rs @@ -294,7 +294,7 @@ fn dns_resolution_failure_for_wildcard_ip_with_real_nodes() { thread::sleep(Duration::from_millis(1000)); let mut client = originating_node.make_client(8080, STANDARD_CLIENT_TIMEOUT_MILLIS); - client.send_chunk(b"GET / HTTP/1.1\r\nHost: www.xvideos.com\r\n\r\n"); + client.send_chunk(b"GET / HTTP/1.1\r\nHost: www.adomainthatdoesntexist.com\r\n\r\n"); let response = client.wait_for_chunk(); assert_eq!( @@ -304,7 +304,7 @@ fn dns_resolution_failure_for_wildcard_ip_with_real_nodes() { String::from_utf8(response.clone()).unwrap() ); assert_eq!( - index_of(&response, &b"

DNS Failure, We have tried multiple Exit Nodes and all have failed to resolve this address www.xvideos.com

"[..]).is_some(), + index_of(&response, &b"

DNS Failure, We have tried multiple Exit Nodes and all have failed to resolve this address www.adomainthatdoesntexsit.com

"[..]).is_some(), true, "Actual response:\n{}", String::from_utf8(response).unwrap() diff --git a/multinode_integration_tests/tests/data_routing_test.rs b/multinode_integration_tests/tests/data_routing_test.rs index 0b3fa9d21..0c4cef279 100644 --- a/multinode_integration_tests/tests/data_routing_test.rs +++ b/multinode_integration_tests/tests/data_routing_test.rs @@ -316,7 +316,7 @@ fn multiple_stream_zero_hop_test() { let mut another_client = zero_hop_node.make_client(8080, STANDARD_CLIENT_TIMEOUT_MILLIS); one_client.send_chunk(b"GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"); - another_client.send_chunk(b"GET /online/ HTTP/1.1\r\nHost: whatever.neverssl.com\r\n\r\n"); + another_client.send_chunk(b"GET /online/ HTTP/1.1\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Language: cs-CZ,cs;q=0.9,en;q=0.8,sk;q=0.7\r\nCache-Control: max-age=0\r\nConnection: keep-alive\r\nHost: whatever.neverssl.com\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36\r\n\r\n"); let one_response = one_client.wait_for_chunk(); let another_response = another_client.wait_for_chunk(); diff --git a/multinode_integration_tests/tests/verify_bill_payment.rs b/multinode_integration_tests/tests/verify_bill_payment.rs index 2fb133818..e649deba3 100644 --- a/multinode_integration_tests/tests/verify_bill_payment.rs +++ b/multinode_integration_tests/tests/verify_bill_payment.rs @@ -39,10 +39,7 @@ use web3::Web3; #[test] fn verify_bill_payment() { - let mut cluster = match MASQNodeCluster::start() { - Ok(cluster) => cluster, - Err(e) => panic!("{}", e), - }; + let mut cluster = MASQNodeCluster::start().unwrap(); let blockchain_server = BlockchainServer { name: "ganache-cli", }; @@ -236,7 +233,7 @@ fn verify_bill_payment() { assert_balances( &contract_owner_wallet, &blockchain_interface, - "99995074522000000000", + "99995231980000000000", "471999999700000000000000000", ); diff --git a/node/Cargo.lock b/node/Cargo.lock index 04806d093..63e2b2029 100644 --- a/node/Cargo.lock +++ b/node/Cargo.lock @@ -182,7 +182,7 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "automap" -version = "0.8.0" +version = "0.8.2" dependencies = [ "crossbeam-channel 0.5.1", "flexi_logger 0.17.1", @@ -1802,7 +1802,7 @@ dependencies = [ [[package]] name = "masq" -version = "0.8.0" +version = "0.8.2" dependencies = [ "atty", "clap", @@ -1822,7 +1822,7 @@ dependencies = [ [[package]] name = "masq_lib" -version = "0.8.0" +version = "0.8.2" dependencies = [ "actix", "clap", @@ -1999,7 +1999,7 @@ dependencies = [ [[package]] name = "multinode_integration_tests" -version = "0.8.0" +version = "0.8.2" dependencies = [ "base64 0.13.0", "crossbeam-channel 0.5.1", @@ -2092,7 +2092,7 @@ dependencies = [ [[package]] name = "node" -version = "0.8.0" +version = "0.8.2" dependencies = [ "actix", "automap", diff --git a/node/Cargo.toml b/node/Cargo.toml index 4ae89971d..7d01fd728 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "node" -version = "0.8.0" +version = "0.8.2" license = "GPL-3.0-only" authors = ["Dan Wiebe ", "MASQ"] description = "MASQ Node is the foundation of MASQ Network, an open-source network that allows anyone to allocate spare computing resources to make the internet a free and fair place for the entire world." diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index 1e7a16a81..a1510e8bd 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -115,12 +115,6 @@ pub struct ResponseSkeleton { pub context_id: u64, } -#[derive(Debug, PartialEq, Eq)] -pub struct PaymentsAndStartBlock { - pub payments: Vec, - pub new_start_block: u64, -} - #[derive(Debug, PartialEq, Eq, Clone)] pub enum ReceivedPaymentsError { ExceededBlockScanLimit(u64), @@ -137,7 +131,8 @@ 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 new_start_block: u64, + pub transactions: Vec, pub response_skeleton_opt: Option, } @@ -374,7 +369,7 @@ impl SkeletonOptHolder for RequestTransactionReceipts { } } -#[derive(Debug, PartialEq, Message, Clone)] +#[derive(Debug, PartialEq, Eq, Message, Clone)] pub struct ReportTransactionReceipts { pub fingerprints_with_receipts: Vec<(TransactionReceiptResult, PendingPayableFingerprint)>, pub response_skeleton_opt: Option, @@ -501,9 +496,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 +506,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 +530,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, @@ -1053,9 +1048,7 @@ mod tests { use crate::accountant::db_access_objects::utils::{from_time_t, to_time_t, CustomQuery}; use crate::accountant::payment_adjuster::Adjustment; 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::scanners::test_utils::protect_payables_in_test; use crate::accountant::scanners::BeginScanError; use crate::accountant::test_utils::DaoWithDestination::{ ForAccountantBody, ForPayableScanner, ForPendingPayableScanner, ForReceivableScanner, @@ -1076,6 +1069,7 @@ mod tests { }; use crate::database::rusqlite_wrappers::TransactionSafeWrapper; use crate::database::test_utils::transaction_wrapper_mock::TransactionInnerWrapperMockBuilder; + use crate::db_config::config_dao::ConfigDaoRecord; use crate::db_config::mocks::ConfigDaoMock; use crate::match_every_type_id; use crate::sub_lib::accountant::{ @@ -1125,7 +1119,7 @@ mod tests { use std::sync::Mutex; use std::time::Duration; use std::vec; - use web3::types::TransactionReceipt; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, TxReceipt, TxStatus}; impl Handler> for Accountant { type Result = (); @@ -1395,7 +1389,11 @@ mod tests { config.suppress_initial_scans = true; let subject = AccountantBuilder::default() .bootstrapper_config(config) - .config_dao(ConfigDaoMock::new().set_result(Ok(()))) + .config_dao( + ConfigDaoMock::new() + .get_result(Ok(ConfigDaoRecord::new("start_block", None, false))) + .set_result(Ok(())), + ) .build(); let (ui_gateway, _, ui_gateway_recording_arc) = make_recorder(); let subject_addr = subject.start(); @@ -1404,11 +1402,12 @@ 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()), + new_start_block: 0, response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, context_id: 4321, }), + transactions: vec![], }; subject_addr.try_send(received_payments).unwrap(); @@ -1597,6 +1596,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 +1712,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 @@ -2007,6 +2014,7 @@ mod tests { ) { let more_money_received_params_arc = Arc::new(Mutex::new(vec![])); let commit_params_arc = Arc::new(Mutex::new(vec![])); + let get_params_arc = Arc::new(Mutex::new(vec![])); let set_by_guest_transaction_params_arc = Arc::new(Mutex::new(vec![])); let now = SystemTime::now(); let earning_wallet = make_wallet("earner3000"); @@ -2030,6 +2038,8 @@ mod tests { .more_money_received_params(&more_money_received_params_arc) .more_money_received_result(wrapped_transaction); let config_dao = ConfigDaoMock::new() + .get_params(&get_params_arc) + .get_result(Ok(ConfigDaoRecord::new("start_block", None, false))) .set_by_guest_transaction_params(&set_by_guest_transaction_params_arc) .set_by_guest_transaction_result(Ok(())); let accountant = AccountantBuilder::default() @@ -2039,14 +2049,12 @@ 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; subject .try_send(ReceivedPayments { timestamp: now, - scan_result: Ok(scan_result), + new_start_block: 123456789, response_skeleton_opt: None, + transactions: vec![expected_receivable_1.clone(), expected_receivable_2.clone()], }) .expect("unexpected actix error"); @@ -3431,27 +3439,23 @@ 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 - .response("0x3B9ACA00".to_string(), 0) // 1000000000 + .ok_response("0x3B9ACA00".to_string(), 0) // 1000000000 // Blockchain Agent transaction fee balance - .response("0xFFF0".to_string(), 0) // 65520 + .ok_response("0xFFF0".to_string(), 0) // 65520 // Blockchain Agent masq balance - .response( + .ok_response( "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) + .ok_response("0xFFF0".to_string(), 1) .begin_batch() .raw_response( ReceiptResponseBuilder::default() @@ -3506,11 +3510,12 @@ mod tests { ReceiptResponseBuilder::default() .transaction_hash(pending_tx_hash_2) .status(U64::from(1)) + .block_number(U64::from(1234)) + .block_hash(Default::default()) .build(), ) .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 +3543,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 +3551,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 +3633,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 +3709,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 +3734,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 +3772,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", ); } @@ -3798,9 +3799,13 @@ mod tests { .build(); let subject_addr = subject.start(); let transaction_hash_1 = make_tx_hash(4545); - let mut transaction_receipt_1 = TransactionReceipt::default(); - transaction_receipt_1.transaction_hash = transaction_hash_1; - transaction_receipt_1.status = Some(U64::from(1)); //success + let transaction_receipt_1 = TxReceipt { + transaction_hash: transaction_hash_1, + status: TxStatus::Succeeded(TransactionBlock { + block_hash: Default::default(), + block_number: U64::from(100), + }), + }; let fingerprint_1 = PendingPayableFingerprint { rowid: 5, timestamp: from_time_t(200_000_000), @@ -3810,9 +3815,13 @@ mod tests { process_error: None, }; let transaction_hash_2 = make_tx_hash(3333333); - let mut transaction_receipt_2 = TransactionReceipt::default(); - transaction_receipt_2.transaction_hash = transaction_hash_2; - transaction_receipt_2.status = Some(U64::from(1)); //success + let transaction_receipt_2 = TxReceipt { + transaction_hash: transaction_hash_2, + status: TxStatus::Succeeded(TransactionBlock { + block_hash: Default::default(), + block_number: U64::from(200), + }), + }; let fingerprint_2 = PendingPayableFingerprint { rowid: 10, timestamp: from_time_t(199_780_000), @@ -3824,11 +3833,11 @@ mod tests { let msg = ReportTransactionReceipts { fingerprints_with_receipts: vec![ ( - TransactionReceiptResult::Found(transaction_receipt_1), + TransactionReceiptResult::RpcResponse(transaction_receipt_1), fingerprint_1.clone(), ), ( - TransactionReceiptResult::Found(transaction_receipt_2), + TransactionReceiptResult::RpcResponse(transaction_receipt_2), fingerprint_2.clone(), ), ], @@ -3864,7 +3873,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 +3881,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 +4742,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] @@ -4852,6 +4859,7 @@ mod tests { let factory = Accountant::dao_factory(data_dir); factory.make(); }; + assert_on_initialization_with_panic_on_migration(&data_dir, &act); } } 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..e95673002 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 @@ -5,7 +5,9 @@ use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockch use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; use crate::sub_lib::wallet::Wallet; use ethereum_types::U256; +use masq_lib::blockchains::chains::Chain; use masq_lib::logger::Logger; +use masq_lib::test_utils::utils::TEST_DEFAULT_CHAIN; #[derive(Clone)] pub struct BlockchainAgentNull { @@ -27,7 +29,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,9 +39,9 @@ impl BlockchainAgent for BlockchainAgentNull { &self.wallet } - fn pending_transaction_id(&self) -> U256 { - self.log_function_call("pending_transaction_id()"); - U256::zero() + fn get_chain(&self) -> Chain { + self.log_function_call("get_chain()"); + TEST_DEFAULT_CHAIN } #[cfg(test)] @@ -83,6 +85,7 @@ mod tests { use masq_lib::logger::Logger; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; + use masq_lib::test_utils::utils::TEST_DEFAULT_CHAIN; use web3::types::U256; fn blockchain_agent_null_constructor_works(constructor: C) @@ -178,15 +181,15 @@ mod tests { } #[test] - fn null_agent_pending_transaction_id() { + fn null_agent_get_chain() { init_test_logging(); - let test_name = "null_agent_pending_transaction_id"; + let test_name = "null_agent_get_chain"; let mut subject = BlockchainAgentNull::new(); subject.logger = Logger::new(test_name); - let result = subject.pending_transaction_id(); + let result = subject.get_chain(); - assert_eq!(result, U256::zero()); - assert_error_log(test_name, "pending_transaction_id"); + assert_eq!(result, TEST_DEFAULT_CHAIN); + assert_error_log(test_name, "get_chain") } } 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..725e14f00 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,22 @@ 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; +use masq_lib::blockchains::chains::Chain; -#[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, + chain: Chain, } 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 +26,38 @@ 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 + fn get_chain(&self) -> Chain { + self.chain } } // 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, + chain: Chain, ) -> 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, + chain, } } } @@ -68,15 +68,14 @@ mod tests { BlockchainAgentWeb3, WEB3_MAXIMAL_GAS_LIMIT_MARGIN, }; use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; - use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; use crate::test_utils::make_wallet; - + use masq_lib::test_utils::utils::TEST_DEFAULT_CHAIN; use web3::types::U256; #[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 +87,13 @@ 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, + TEST_DEFAULT_CHAIN, ); assert_eq!(subject.agreed_fee_per_computation_unit(), gas_price_gwei); @@ -104,7 +102,7 @@ mod tests { subject.consuming_wallet_balances(), consuming_wallet_balances ); - assert_eq!(subject.pending_transaction_id(), pending_transaction_id) + assert_eq!(subject.get_chain(), TEST_DEFAULT_CHAIN); } #[test] @@ -114,13 +112,12 @@ 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, + TEST_DEFAULT_CHAIN, ); 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..2f2af4015 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,7 @@ use crate::arbitrary_id_stamp_in_trait; use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; use crate::sub_lib::wallet::Wallet; -use web3::types::U256; +use masq_lib::blockchains::chains::Chain; // Table of chains by // @@ -24,9 +24,10 @@ 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; + + fn get_chain(&self) -> Chain; #[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..d3ab97284 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,16 +7,27 @@ 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 masq_lib::blockchains::chains::Chain; 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, + get_chain_result_opt: Option, +} + +impl Default for BlockchainAgentMock { + fn default() -> Self { + BlockchainAgentMock { + consuming_wallet_balances_results: RefCell::new(vec![]), + agreed_fee_per_computation_unit_results: RefCell::new(vec![]), + consuming_wallet_result_opt: None, + arbitrary_id_stamp_opt: None, + get_chain_result_opt: None, + } + } } impl BlockchainAgent for BlockchainAgentMock { @@ -28,7 +39,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,8 +49,8 @@ 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 get_chain(&self) -> Chain { + self.get_chain_result_opt.unwrap() } fn dup(&self) -> Box { @@ -57,7 +68,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,10 +80,8 @@ impl BlockchainAgentMock { self } - pub fn pending_transaction_id_result(self, result: U256) -> Self { - self.pending_transaction_id_results - .borrow_mut() - .push(result); + pub fn get_chain_result(mut self, get_chain_result: Chain) -> Self { + self.get_chain_result_opt = Some(get_chain_result); self } diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index 4a37d3385..572d36400 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -17,12 +17,9 @@ use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{ separate_errors, separate_rowids_and_hashes, PayableThresholdsGauge, 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, - PendingPayableScanReport, -}; +use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_none_receipt, 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::PendingPayableId; use crate::accountant::{ comma_joined_stringifiable, gwei_to_wei, Accountant, ReceivedPayments, ReportTransactionReceipts, RequestTransactionReceipts, ResponseSkeleton, ScanForPayables, @@ -51,11 +48,11 @@ 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}; -use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::TransactionReceiptResult; +use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionReceiptResult, TxStatus}; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; use crate::db_config::persistent_configuration::{PersistentConfiguration, PersistentConfigurationReal}; @@ -497,7 +494,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,84 +651,34 @@ impl PendingPayableScanner { msg: ReportTransactionReceipts, logger: &Logger, ) -> PendingPayableScanReport { - fn handle_none_receipt( - mut scan_report: PendingPayableScanReport, - payable: PendingPayableFingerprint, - error_msg: String, - logger: &Logger, - ) -> PendingPayableScanReport { - debug!(logger, - "Interpreting a receipt for transaction {:?} but {}; attempt {}, {}ms since sending", - payable.hash, error_msg, payable.attempt,elapsed_in_ms(payable.timestamp) - ); - - scan_report - .still_pending - .push(PendingPayableId::new(payable.rowid, payable.hash)); - scan_report - } - let scan_report = PendingPayableScanReport::default(); 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::NotPresent => handle_none_receipt( - scan_report_so_far, - fingerprint, - "none was given".to_string(), - logger, - ), - TransactionReceiptResult::Error(e) => handle_none_receipt( + TransactionReceiptResult::RpcResponse(tx_receipt) => match tx_receipt.status { + TxStatus::Pending => handle_none_receipt( + scan_report_so_far, + fingerprint, + "none was given", + logger, + ), + TxStatus::Failed => { + handle_status_with_failure(scan_report_so_far, fingerprint, logger) + } + TxStatus::Succeeded(_) => { + handle_status_with_success(scan_report_so_far, fingerprint, logger) + } + }, + TransactionReceiptResult::LocalError(e) => handle_none_receipt( scan_report_so_far, fingerprint, - format!("failed due to {}", e), + &format!("failed due to {}", e), logger, ), }, ) } - 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 +813,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, logger); self.mark_as_ended(logger); msg.response_skeleton_opt .map(|response_skeleton| NodeToUiMessage { @@ -926,19 +847,18 @@ impl ReceivableScanner { fn handle_new_received_payments( &mut self, - payments_and_start_block: &PaymentsAndStartBlock, - timestamp: SystemTime, + received_payments_msg: &ReceivedPayments, logger: &Logger, ) { - if payments_and_start_block.payments.is_empty() { + if received_payments_msg.transactions.is_empty() { info!( logger, "No newly received payments were detected during the scanning process." ); - let new_start_block = payments_and_start_block.new_start_block; + let new_start_block = received_payments_msg.new_start_block; match self .persistent_configuration - .set_start_block(new_start_block) + .set_start_block(Some(new_start_block)) { Ok(()) => debug!(logger, "Start block updated to {}", new_start_block), Err(e) => panic!( @@ -946,16 +866,15 @@ impl ReceivableScanner { new_start_block, e ), } - debug!(logger, "Updated start block to: {}", new_start_block) } else { - let mut txn = self - .receivable_dao - .as_mut() - .more_money_received(timestamp, &payments_and_start_block.payments); - let new_start_block = payments_and_start_block.new_start_block; + let mut txn = self.receivable_dao.as_mut().more_money_received( + received_payments_msg.timestamp, + &received_payments_msg.transactions, + ); + let new_start_block = received_payments_msg.new_start_block; match self .persistent_configuration - .set_start_block_from_txn(new_start_block, &mut txn) + .set_start_block_from_txn(Some(new_start_block), &mut txn) { Ok(()) => (), Err(e) => panic!( @@ -971,8 +890,8 @@ impl ReceivableScanner { Err(e) => panic!("Commit of received transactions failed: {:?}", e), } - let total_newly_paid_receivable = payments_and_start_block - .payments + let total_newly_paid_receivable = received_payments_msg + .transactions .iter() .fold(0, |so_far, now| so_far + now.wei_amount); @@ -1148,10 +1067,8 @@ 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::test_utils::{ - make_empty_payments_and_start_block, protect_payables_in_test, - }; + use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{handle_none_status, handle_status_with_failure, PendingPayableScanReport}; + use crate::accountant::scanners::test_utils::protect_payables_in_test; use crate::accountant::scanners::{ BeginScanError, PayableScanner, PendingPayableScanner, ReceivableScanner, ScanSchedulers, Scanner, ScannerCommon, Scanners, @@ -1164,7 +1081,7 @@ mod tests { PendingPayableDaoMock, PendingPayableScannerBuilder, ReceivableDaoFactoryMock, ReceivableDaoMock, ReceivableScannerBuilder, }; - use crate::accountant::{gwei_to_wei, PendingPayableId, ReceivedPayments, ReportTransactionReceipts, RequestTransactionReceipts, SentPayables, DEFAULT_PENDING_TOO_LONG_SEC, PaymentsAndStartBlock}; + use crate::accountant::{gwei_to_wei, PendingPayableId, ReceivedPayments, ReportTransactionReceipts, RequestTransactionReceipts, SentPayables, DEFAULT_PENDING_TOO_LONG_SEC}; use crate::blockchain::blockchain_bridge::{PendingPayableFingerprint, RetrieveTransactions}; use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; use crate::blockchain::blockchain_interface::data_structures::{ @@ -1174,7 +1091,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, @@ -1198,7 +1115,7 @@ mod tests { use std::time::{Duration, SystemTime}; use web3::types::{TransactionReceipt, H256}; use web3::Error; - use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::TransactionReceiptResult; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, TransactionReceiptResult, TxReceipt, TxStatus}; #[test] fn scanners_struct_can_be_constructed_with_the_respective_scanners() { @@ -1285,7 +1202,7 @@ mod tests { assert_eq!(receivable_scanner.common.initiated_at_opt.is_some(), false); receivable_scanner .persistent_configuration - .set_start_block(136890) + .set_start_block(Some(136890)) .unwrap(); let set_params = set_params_arc.lock().unwrap(); assert_eq!( @@ -1673,7 +1590,7 @@ mod tests { .iter() .map(|ppayable| ppayable.hash) .collect::>(); - // Not in an ascending order + // Not in ascending order let rowids_and_hashes_from_fingerprints = vec![(hash_1, 3), (hash_3, 5), (hash_2, 6)] .iter() .map(|(hash, _id)| *hash) @@ -1863,7 +1780,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 } @@ -1931,7 +1848,7 @@ mod tests { 00000000000000000000000000000000000000000315 failed due to RecordDeletion(\"Gosh, I overslept \ without an alarm set\")"); let log_handler = TestLogHandler::new(); - // There is a possible situation when we stumble over missing fingerprints and so we log it. + // There is a possible situation when we stumble over missing fingerprints, so we log it. // Here we don't and so any ERROR log shouldn't turn up log_handler.exists_no_log_containing(&format!("ERROR: {}", test_name)) } @@ -2371,11 +2288,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 +2300,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 +2364,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 +2392,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 +2428,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 +2446,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, @@ -2587,7 +2480,10 @@ mod tests { }; let msg = ReportTransactionReceipts { fingerprints_with_receipts: vec![( - TransactionReceiptResult::NotPresent, + TransactionReceiptResult::RpcResponse(TxReceipt { + transaction_hash: hash, + status: TxStatus::Pending, + }), fingerprint.clone(), )], response_skeleton_opt: None, @@ -2907,9 +2803,13 @@ mod tests { .pending_payable_dao(pending_payable_dao) .build(); let transaction_hash_1 = make_tx_hash(4545); - let mut transaction_receipt_1 = TransactionReceipt::default(); - transaction_receipt_1.transaction_hash = transaction_hash_1; - transaction_receipt_1.status = Some(U64::from(1)); //success + let transaction_receipt_1 = TxReceipt { + transaction_hash: transaction_hash_1, + status: TxStatus::Succeeded(TransactionBlock { + block_hash: Default::default(), + block_number: U64::from(1234), + }), + }; let fingerprint_1 = PendingPayableFingerprint { rowid: 5, timestamp: from_time_t(200_000_000), @@ -2919,9 +2819,13 @@ mod tests { process_error: None, }; let transaction_hash_2 = make_tx_hash(1234); - let mut transaction_receipt_2 = TransactionReceipt::default(); - transaction_receipt_2.transaction_hash = transaction_hash_2; - transaction_receipt_2.status = Some(U64::from(1)); //success + let transaction_receipt_2 = TxReceipt { + transaction_hash: transaction_hash_2, + status: TxStatus::Succeeded(TransactionBlock { + block_hash: Default::default(), + block_number: U64::from(2345), + }), + }; let fingerprint_2 = PendingPayableFingerprint { rowid: 10, timestamp: from_time_t(199_780_000), @@ -2933,11 +2837,11 @@ mod tests { let msg = ReportTransactionReceipts { fingerprints_with_receipts: vec![ ( - TransactionReceiptResult::Found(transaction_receipt_1), + TransactionReceiptResult::RpcResponse(transaction_receipt_1), fingerprint_1.clone(), ), ( - TransactionReceiptResult::Found(transaction_receipt_2), + TransactionReceiptResult::RpcResponse(transaction_receipt_2), fingerprint_2.clone(), ), ], @@ -3125,28 +3029,28 @@ 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() + .start_block_result(Ok(None)) .set_start_block_params(&set_start_block_params_arc) .set_start_block_result(Ok(())); 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 msg = ReceivedPayments { timestamp: SystemTime::now(), - scan_result: Ok(scan_result), + new_start_block, response_skeleton_opt: None, + transactions: vec![], }; let message_opt = subject.finish_scan(msg, &Logger::new(test_name)); assert_eq!(message_opt, None); let set_start_block_params = set_start_block_params_arc.lock().unwrap(); - assert_eq!(*set_start_block_params, vec![4321]); + assert_eq!(*set_start_block_params, vec![Some(4321)]); TestLogHandler::new().exists_log_containing(&format!( "INFO: {test_name}: No newly received payments were detected during the scanning process." )); @@ -3159,18 +3063,21 @@ mod tests { init_test_logging(); let test_name = "no_transactions_received_but_start_block_setting_fails"; let now = SystemTime::now(); - let persistent_config = PersistentConfigurationMock::new().set_start_block_result(Err( - PersistentConfigError::UninterpretableValue("Illiterate database manager".to_string()), - )); + let set_start_block_params_arc = Arc::new(Mutex::new(vec![])); + let persistent_config = PersistentConfigurationMock::new() + .start_block_result(Ok(None)) + .set_start_block_params(&set_start_block_params_arc) + .set_start_block_result(Err(PersistentConfigError::UninterpretableValue( + "Illiterate database manager".to_string(), + ))); 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 msg = ReceivedPayments { timestamp: now, - scan_result: Ok(scan_result), + new_start_block: 6709, response_skeleton_opt: None, + transactions: vec![], }; // Not necessary, rather for preciseness @@ -3194,6 +3101,7 @@ mod tests { .set_arbitrary_id_stamp(transaction_id); let transaction = TransactionSafeWrapper::new_with_builder(txn_inner_builder); let persistent_config = PersistentConfigurationMock::new() + .start_block_result(Ok(None)) .set_start_block_from_txn_params(&set_start_block_from_txn_params_arc) .set_start_block_from_txn_result(Ok(())); let receivable_dao = ReceivableDaoMock::new() @@ -3218,13 +3126,11 @@ 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 msg = ReceivedPayments { timestamp: now, - scan_result: Ok(scan_result), + new_start_block: 7890123, response_skeleton_opt: None, + transactions: receivables.clone(), }; subject.mark_as_started(SystemTime::now()); @@ -3242,7 +3148,7 @@ mod tests { let set_by_guest_transaction_params = set_start_block_from_txn_params_arc.lock().unwrap(); assert_eq!( *set_by_guest_transaction_params, - vec![(7890123, transaction_id)] + vec![(Some(7890123u64), transaction_id)] ); let commit_params = commit_params_arc.lock().unwrap(); assert_eq!(*commit_params, vec![()]); @@ -3260,9 +3166,11 @@ mod tests { let now = SystemTime::now(); let txn_inner_builder = TransactionInnerWrapperMockBuilder::default(); let transaction = TransactionSafeWrapper::new_with_builder(txn_inner_builder); - let persistent_config = PersistentConfigurationMock::new().set_start_block_from_txn_result( - Err(PersistentConfigError::DatabaseError("Fatigue".to_string())), - ); + let persistent_config = PersistentConfigurationMock::new() + .start_block_result(Ok(None)) + .set_start_block_from_txn_result(Err(PersistentConfigError::DatabaseError( + "Fatigue".to_string(), + ))); let receivable_dao = ReceivableDaoMock::new().more_money_received_result(transaction); let mut subject = ReceivableScannerBuilder::new() .receivable_dao(receivable_dao) @@ -3275,11 +3183,9 @@ mod tests { }]; let msg = ReceivedPayments { timestamp: now, - scan_result: Ok(PaymentsAndStartBlock { - payments: receivables, - new_start_block: 7890123, - }), + new_start_block: 7890123, response_skeleton_opt: None, + transactions: receivables, }; // Not necessary, rather for preciseness subject.mark_as_started(SystemTime::now()); @@ -3306,8 +3212,9 @@ mod tests { let txn_inner_builder = TransactionInnerWrapperMockBuilder::default().commit_result(commit_err); let transaction = TransactionSafeWrapper::new_with_builder(txn_inner_builder); - let persistent_config = - PersistentConfigurationMock::new().set_start_block_from_txn_result(Ok(())); + let persistent_config = PersistentConfigurationMock::new() + .start_block_result(Ok(None)) + .set_start_block_from_txn_result(Ok(())); let receivable_dao = ReceivableDaoMock::new().more_money_received_result(transaction); let mut subject = ReceivableScannerBuilder::new() .receivable_dao(receivable_dao) @@ -3318,12 +3225,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 msg = ReceivedPayments { timestamp: now, - scan_result: Ok(scan_result), + new_start_block: 0, response_skeleton_opt: None, + transactions: receivables, }; // Not necessary, rather for preciseness subject.mark_as_started(SystemTime::now()); diff --git a/node/src/accountant/scanners/scanners_utils.rs b/node/src/accountant/scanners/scanners_utils.rs index 429a36b05..30b3a3d2d 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, @@ -399,6 +400,27 @@ pub mod pending_payable_scanner_utils { scan_report.failures.push(fingerprint.into()); scan_report } + + pub fn handle_none_receipt( + mut scan_report: PendingPayableScanReport, + payable: PendingPayableFingerprint, + error_msg: &str, + logger: &Logger, + ) -> PendingPayableScanReport { + debug!( + logger, + "Interpreting a receipt for transaction {:?} but {}; attempt {}, {}ms since sending", + payable.hash, + error_msg, + payable.attempt, + elapsed_in_ms(payable.timestamp) + ); + + scan_report + .still_pending + .push(PendingPayableId::new(payable.rowid, payable.hash)); + scan_report + } } pub mod receivable_scanner_utils { diff --git a/node/src/accountant/scanners/test_utils.rs b/node/src/accountant/scanners/test_utils.rs index c32c10d81..c43d6f71b 100644 --- a/node/src/accountant/scanners/test_utils.rs +++ b/node/src/accountant/scanners/test_utils.rs @@ -3,16 +3,8 @@ #![cfg(test)] use crate::accountant::db_access_objects::payable_dao::PayableAccount; -use crate::accountant::PaymentsAndStartBlock; use masq_lib::type_obfuscation::Obfuscated; pub fn protect_payables_in_test(payables: Vec) -> Obfuscated { Obfuscated::obfuscate_vector(payables) } - -pub fn make_empty_payments_and_start_block() -> PaymentsAndStartBlock { - PaymentsAndStartBlock { - payments: vec![], - new_start_block: 0, - } -} diff --git a/node/src/actor_system_factory.rs b/node/src/actor_system_factory.rs index d85104e21..0cb035f61 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,10 +635,8 @@ 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::blockchain::blockchain_bridge::RetrieveTransactions; + use crate::accountant::DEFAULT_PENDING_TOO_LONG_SEC; use crate::bootstrapper::{Bootstrapper, RealUser}; - use crate::db_config::persistent_configuration::PersistentConfigurationReal; use crate::node_test_utils::{ make_stream_handler_pool_subs_from_recorder, start_recorder_refcell_opt, }; @@ -663,11 +661,9 @@ mod tests { make_accountant_subs_from_recorder, make_blockchain_bridge_subs_from_recorder, make_configurator_subs_from_recorder, make_hopper_subs_from_recorder, make_neighborhood_subs_from_recorder, make_proxy_client_subs_from_recorder, - make_proxy_server_subs_from_recorder, make_ui_gateway_subs_from_recorder, - peer_actors_builder, Recording, + make_proxy_server_subs_from_recorder, make_ui_gateway_subs_from_recorder, Recording, }; use crate::test_utils::recorder::{make_recorder, Recorder}; - use crate::test_utils::recorder_stop_conditions::{StopCondition, StopConditions}; use crate::test_utils::unshared_test_utils::arbitrary_id_stamp::ArbitraryIdStamp; use crate::test_utils::unshared_test_utils::system_killer_actor::SystemKillerActor; use crate::test_utils::unshared_test_utils::{ @@ -675,30 +671,24 @@ mod tests { }; use crate::test_utils::{alias_cryptde, rate_pack}; use crate::test_utils::{main_cryptde, make_cryptde_pair}; - use crate::{ - hopper, match_every_type_id, proxy_client, proxy_server, stream_handler_pool, ui_gateway, - }; + use crate::{hopper, proxy_client, proxy_server, stream_handler_pool, ui_gateway}; use actix::{Actor, Arbiter, System}; use automap_lib::control_layer::automap_control::AutomapChange; #[cfg(all(test, not(feature = "no_test_share")))] use automap_lib::mocks::{ parameterizable_automap_control, TransactorMock, PUBLIC_IP, ROUTER_IP, }; - use core::any::TypeId; - use crossbeam_channel::{bounded, unbounded}; + use crossbeam_channel::unbounded; use log::LevelFilter; use masq_lib::constants::DEFAULT_CHAIN; use masq_lib::crash_point::CrashPoint; #[cfg(feature = "log_recipient_test")] use masq_lib::logger::INITIALIZATION_COUNTER; use masq_lib::messages::{ToMessageBody, UiCrashRequest, UiDescriptorRequest}; - 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::test_utils::utils::{ensure_node_home_directory_exists, TEST_DEFAULT_CHAIN}; use masq_lib::ui_gateway::NodeFromUiMessage; + use masq_lib::utils::running_test; use masq_lib::utils::AutomapProtocol::Igdp; - use masq_lib::utils::{find_free_port, running_test}; use std::cell::RefCell; use std::collections::HashMap; use std::convert::TryFrom; @@ -1257,7 +1247,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 +1261,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 +1348,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 +1560,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(); @@ -1997,82 +1987,6 @@ mod tests { ) } - #[test] - fn blockchain_bridge_is_constructed_with_correctly_functioning_connections() { - let test_name = "blockchain_bridge_is_constructed_with_correctly_functioning_connections"; - let data_dir = ensure_node_home_directory_exists("actor_system_factory", test_name); - let port = find_free_port(); - let _blockchain_client_server = MBCSBuilder::new(port) - .response("0x3B9ACA00".to_string(), 0) - .response( - vec![LogObject { - removed: false, - log_index: Some("0x20".to_string()), - transaction_index: Some("0x30".to_string()), - transaction_hash: Some( - "0x2222222222222222222222222222222222222222222222222222222222222222" - .to_string(), - ), - block_hash: Some( - "0x1111111111111111111111111111111111111111111111111111111111111111" - .to_string(), - ), - block_number: Some("0x7D0".to_string()), // 2000 decimal - address: "0x3333333333333333333333333333333333333334".to_string(), - data: "0x000000000000000000000000000000000000000000000000000000003b5dc100" - .to_string(), - topics: vec![ - "0xddf252ad1be2c89b69c2b0680000000000006561726e696e675f77616c6c6574" - .to_string(), - "0xddf252ad1be2c89b69c2b0690000000000006561726e696e675f77616c6c6574" - .to_string(), - ], - }], - 1, - ) - .start(); - let server_url = format!("http://{}:{}", &Ipv4Addr::LOCALHOST, port); - let _persistent_config = { - let conn = DbInitializerReal::default() - .initialize(&data_dir, DbInitializationConfig::test_default()) - .unwrap(); - PersistentConfigurationReal::from(conn) - }; - let wallet = make_wallet("abc"); - let mut bootstrapper_config = BootstrapperConfig::new(); - bootstrapper_config - .blockchain_bridge_config - .blockchain_service_url_opt = Some(server_url); - bootstrapper_config.blockchain_bridge_config.chain = TEST_DEFAULT_CHAIN; - bootstrapper_config.data_directory = data_dir.clone(); - let system = System::new(test_name); - let (accountant, _, accountant_recording) = make_recorder(); - let accountant = accountant.system_stop_conditions(match_every_type_id!(ReceivedPayments)); - let peer_actors = peer_actors_builder().accountant(accountant).build(); - let (tx, blockchain_bridge_addr_rx) = bounded(1); - let address_leaker = SubsFactoryTestAddrLeaker { address_leaker: tx }; - - ActorFactoryReal::new() - .make_and_start_blockchain_bridge(&bootstrapper_config, &address_leaker); - - let blockchain_bridge_addr = blockchain_bridge_addr_rx.try_recv().unwrap(); - blockchain_bridge_addr - .try_send(BindMessage { - peer_actors: peer_actors, - }) - .unwrap(); - blockchain_bridge_addr - .try_send(RetrieveTransactions { - recipient: wallet, - 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()); - } - #[test] fn load_banned_cache_implements_panic_on_migration() { let data_dir = ensure_node_home_directory_exists( 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..b204d9ccf 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, + 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,12 +41,13 @@ 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}; +use web3::types::H256; 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_interface::blockchain_interface_web3::lower_level_interface_web3::TransactionReceiptResult; +use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionReceiptResult, TxStatus}; pub const CRASH_KEY: &str = "BLOCKCHAINBRIDGE"; pub const DEFAULT_BLOCKCHAIN_SERVICE_URL: &str = "https://0.0.0.0"; @@ -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,73 +300,78 @@ 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, 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(Some(sb)) => sb, + Ok(None) => u64::MAX, + Err(e) => panic!("Cannot retrieve start block from database; payments to you may not be processed: {:?}", e) }; - let max_block_count = match self.persistent_config.max_block_count() { - Ok(Some(mbc)) => mbc, - _ => u64::MAX, + let max_block_count = match persistent_config_lock.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 start_block = BlockNumber::Number(start_block_nbr.into()); - let received_payments_subs_ok_case = self + let logger = self.logger.clone(); + let fallback_next_start_block_number = + Self::calculate_fallback_start_block_number(start_block, max_block_count); + 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("Mutex with persistent configuration in BlockchainBridge was poisoned") + .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 { - payments: transactions.transactions, - new_start_block: transactions.new_start_block, - }; - - received_payments_subs_ok_case + .and_then(move |retrieved_blockchain_transactions| { + received_payments_subs .try_send(ReceivedPayments { timestamp: SystemTime::now(), - scan_result: Ok(payments_and_start_block), + new_start_block: retrieved_blockchain_transactions.new_start_block, response_skeleton_opt: msg.response_skeleton_opt, + transactions: retrieved_blockchain_transactions.transactions, }) .expect("Accountant is dead."); Ok(()) @@ -374,10 +379,37 @@ impl BlockchainBridge { ) } + fn log_status_of_tx_receipts( + logger: &Logger, + transaction_receipts_results: &[TransactionReceiptResult], + ) { + logger.debug(|| { + let (successful_count, failed_count, pending_count) = + transaction_receipts_results.iter().fold( + (0, 0, 0), + |(success, fail, pending), transaction_receipt| match transaction_receipt { + TransactionReceiptResult::RpcResponse(tx_receipt) => { + match tx_receipt.status { + TxStatus::Failed => (success, fail + 1, pending), + TxStatus::Pending => (success, fail, pending + 1), + TxStatus::Succeeded(_) => (success + 1, fail, pending), + } + } + TransactionReceiptResult::LocalError(_) => (success, fail, pending + 1), + }, + ); + format!( + "Scan results: Successful: {}, Pending: {}, Failed: {}", + successful_count, pending_count, failed_count + ) + }); + } + fn handle_request_transaction_receipts( &mut self, msg: RequestTransactionReceipts, ) -> Box> { + let logger = self.logger.clone(); let accountant_recipient = self .pending_payable_confirmation .report_transaction_receipts_sub_opt @@ -392,34 +424,23 @@ 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(); - let mut transactions_found = 0; - for transaction_receipt in &transaction_receipts_results { - if let TransactionReceiptResult::Found(_) = transaction_receipt { - transactions_found += 1; - } - } + Self::log_status_of_tx_receipts(&logger, &transaction_receipts_results); + let pairs = transaction_receipts_results .into_iter() .zip(msg.pending_payable.into_iter()) .collect_vec(); + accountant_recipient .try_send(ReportTransactionReceipts { fingerprints_with_receipts: pairs, response_skeleton_opt: msg.response_skeleton_opt, }) .expect("Accountant is dead"); - if length != transactions_found { - return Err(format!( - "Aborting scanning; {} transactions succeed and {} transactions failed", - transactions_found, - length - transactions_found - )); - }; + Ok(()) }), ) @@ -435,7 +456,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 +470,18 @@ 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 { + if start_block_number == u64::MAX { + start_block_number + } else { + start_block_number + 1u64 + } + } else { + start_block_number + max_block_count + } + } + fn process_payments( &self, agent: Box, @@ -458,16 +490,12 @@ 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, + agent, + new_fingerprints_recipient, + affordable_accounts, + ) } fn new_fingerprints_recipient(&self) -> Recipient { @@ -479,7 +507,7 @@ impl BlockchainBridge { pub fn extract_max_block_count(error: BlockchainError) -> Option { let regex_result = - Regex::new(r".* (max: |allowed for your plan: |is limited to |block range limit \()(?P\d+).*") + Regex::new(r".* (max: |allowed for your plan: |is limited to |block range limit \(|exceeds max block range )(?P\d+).*") .expect("Invalid regex"); let max_block_count = match error { BlockchainError::QueryFailed(msg) => match regex_result.captures(msg.as_str()) { @@ -519,21 +547,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::test_utils::{ - make_empty_payments_and_start_block, protect_payables_in_test, - }; + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::agent_web3::WEB3_MAXIMAL_GAS_LIMIT_MARGIN; + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::test_utils::BlockchainAgentMock; + use crate::accountant::scanners::test_utils::protect_payables_in_test; use crate::accountant::test_utils::{make_payable_account, make_pending_payable_fingerprint}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::BlockchainInterfaceWeb3; + use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError::TransactionID; use crate::blockchain::blockchain_interface::data_structures::errors::{ 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 +576,28 @@ 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}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, TxReceipt}; impl Handler> for BlockchainBridge { type Result = (); @@ -583,7 +618,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 +626,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,21 +681,20 @@ 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) - .response("0x230000000".to_string(), 1) - .response("0x23".to_string(), 1) - .response( + .ok_response("0x230000000".to_string(), 1) // 9395240960 + .ok_response("0x23".to_string(), 1) + .ok_response( "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 +719,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,29 +754,28 @@ 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 + 0x230000000 ); assert_eq!( blockchain_agent_with_context_msg_actual .agent .consuming_wallet_balances(), - ConsumingWalletBalances::new(35.into(), 65535.into()) + ConsumingWalletBalances::new( + 35.into(), + 0x000000000000000000000000000000000000000000000000000000000000FFFF.into() + ) ); + let gas_limit_const_part = + BlockchainInterfaceWeb3::web3_gas_limit_const_part(Chain::PolyMainnet); assert_eq!( blockchain_agent_with_context_msg_actual .agent .estimated_transaction_fee_total(1), - 733280 + (1 * 0x230000000 * (gas_limit_const_part + WEB3_MAXIMAL_GAS_LIMIT_MARGIN)) ); assert_eq!( blockchain_agent_with_context_msg_actual.response_skeleton_opt, @@ -759,22 +792,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, - ) + .ok_response("0x23".to_string(), 1) + .ok_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 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 +827,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 + ) ) } @@ -818,10 +850,9 @@ mod tests { ); let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port) - .response("0x20".to_string(), 1) - .response("0x7B".to_string(), 1) + .ok_response("0x20".to_string(), 1) .begin_batch() - .response("rpc result".to_string(), 1) + .ok_response("rpc result".to_string(), 1) .end_batch() .start(); let (accountant, _, accountant_recording_arc) = make_recorder(); @@ -830,11 +861,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,7 +881,9 @@ mod tests { let agent_id_stamp = ArbitraryIdStamp::new(); let agent = BlockchainAgentMock::default() .set_arbitrary_id_stamp(agent_id_stamp) - .consuming_wallet_result(consuming_wallet); + .agreed_fee_per_computation_unit_result(123) + .consuming_wallet_result(consuming_wallet) + .get_chain_result(Chain::PolyMainnet); send_bind_message!(subject_subs, peer_actors); @@ -878,7 +911,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 +927,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 @@ -911,19 +944,18 @@ mod tests { let port = find_free_port(); // 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) + .ok_response("0x20".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 +969,10 @@ 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) + .get_chain_result(Chain::PolyMainnet); send_bind_message!(subject_subs, peer_actors); let _ = addr @@ -968,7 +1003,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 +1018,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 +1030,27 @@ 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) + .ok_response("0x01".to_string(), 1) .begin_batch() - .response("rpc_result".to_string(), 7) - .response("rpc_result_2".to_string(), 7) + .ok_response("rpc_result".to_string(), 7) + .ok_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) + .get_chain_result(Chain::PolyMainnet); 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 +1070,7 @@ mod tests { Correct(PendingPayable { recipient_wallet: accounts_1.wallet, hash: H256::from_str( - "35f42b260f090a559e8b456718d9c91a9da0f234ed0a129b9d5c4813b6615af4" + "cc73f3d5fe9fc3dac28b510ddeb157b0f8030b201e809014967396cdf365488a" ) .unwrap() }) @@ -1043,7 +1080,7 @@ mod tests { Correct(PendingPayable { recipient_wallet: accounts_2.wallet, hash: H256::from_str( - "7f3221109e4f1de8ba1f7cd358aab340ecca872a1456cb1b4f59ca33d3e22ee3" + "891d9ffa838aedc0bb2f6f7e9737128ce98bb33d07b4c8aa5645871e20d6cd13" ) .unwrap() }) @@ -1057,17 +1094,20 @@ mod tests { let test_name = "process_payments_fails_on_get_transaction_count"; let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port) - .response("trash transaction id".to_string(), 1) + .ok_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() + .get_chain_result(TEST_DEFAULT_CHAIN) + .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 +1132,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 +1161,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 +1172,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,22 +1198,25 @@ 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 { fingerprints_with_receipts: vec![ ( - TransactionReceiptResult::Found(expected_receipt), + TransactionReceiptResult::RpcResponse(expected_receipt.into()), pending_payable_fingerprint_1 ), ( - TransactionReceiptResult::NotPresent, + TransactionReceiptResult::RpcResponse(TxReceipt { + transaction_hash: hash_2, + status: TxStatus::Pending + }), pending_payable_fingerprint_2 ), ], @@ -1223,18 +1226,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] @@ -1243,7 +1234,7 @@ mod tests { let port = find_free_port(); // We have intentionally left out responses to cause this error let _blockchain_client_server = MBCSBuilder::new(port) - .response("0x3B9ACA00".to_string(), 0) + .ok_response("0x3B9ACA00".to_string(), 0) .start(); let (accountant, _, accountant_recording_arc) = make_recorder(); let accountant_addr = accountant @@ -1251,13 +1242,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 + .start_block_result(Ok(Some(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 +1264,18 @@ mod tests { system.run(); let recording = accountant_recording_arc.lock().unwrap(); - let message = recording.get_record::(0); + let scan_error = 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); - 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 +1288,8 @@ mod tests { let contract_address = H160::from_low_u64_be(887766); let tx_receipt_response = ReceiptResponseBuilder::default() .block_number(block_number) + .block_hash(Default::default()) + .status(U64::from(1)) .contract_address(contract_address) .build(); let _blockchain_client_server = MBCSBuilder::new(port) @@ -1354,14 +1342,18 @@ mod tests { amount: 7879, process_error: None, }; - 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)); + let transaction_receipt = TxReceipt { + transaction_hash: Default::default(), + status: TxStatus::Succeeded(TransactionBlock { + block_hash: Default::default(), + block_number, + }), + }; + 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,16 +1378,16 @@ 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, ReportTransactionReceipts { fingerprints_with_receipts: vec![ - (TransactionReceiptResult::NotPresent, fingerprint_1), - (TransactionReceiptResult::Found(transaction_receipt), fingerprint_2), - (TransactionReceiptResult::NotPresent, fingerprint_3), - (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()), fingerprint_4) + (TransactionReceiptResult::RpcResponse(TxReceipt{ transaction_hash: hash_1, status: TxStatus::Pending }), fingerprint_1), + (TransactionReceiptResult::RpcResponse(transaction_receipt), fingerprint_2), + (TransactionReceiptResult::RpcResponse(TxReceipt{ transaction_hash: hash_3, status: TxStatus::Pending }), fingerprint_3), + (TransactionReceiptResult::LocalError("RPC error: Error { code: ServerError(429), message: \"The requests per second (RPS) of your requests are higher than your plan allows.\", data: None }".to_string()), fingerprint_4) ], response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, @@ -1403,62 +1395,9 @@ 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", + TestLogHandler::new().exists_log_containing( + "DEBUG: BlockchainBridge: Scan results: Successful: 1, Pending: 3, Failed: 0", ); - - 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 - } - ) } #[test] @@ -1490,10 +1429,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 +1469,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) + .ok_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(Some(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 +1538,129 @@ mod tests { System::current().stop(); system.run(); let after = SystemTime::now(); - let retrieve_transactions_params = retrieve_transactions_params_arc.lock().unwrap(); + let expected_transactions = RetrievedBlockchainTransactions { + new_start_block: 6040060u64 + 1, + transactions: vec![ + BlockchainTransaction { + block_number: 6040059, + // Wallet represented in the RPC response by the first 'topic' as: 0x241ea03ca20251805084d27d4440371c34a0b85ff108f6bb5611248f73818b80 + from: make_wallet("first_wallet"), + // Paid amount read out from the field 'data' in the RPC + wei_amount: 42, + }, + BlockchainTransaction { + block_number: 6040060, + // Wallet represented in the RPC response by the first 'topic' as: 0x241ea03ca20251805084d27d4440371c34a0b85ff108f6bb5611248f73818b80 + from: make_wallet("second_wallet"), + // Paid amount read out from the field 'data' in the RPC + wei_amount: 55, + }, + ], + }; + 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); assert_eq!( - *retrieve_transactions_params, - vec![( - BlockNumber::Number(6u64.into()), - 10006u64, - earning_wallet.address() - )] + received_payments, + &ReceivedPayments { + timestamp: received_payments.timestamp, + new_start_block: expected_transactions.new_start_block, + response_skeleton_opt: Some(ResponseSkeleton { + client_id: 1234, + context_id: 4321 + }), + transactions: expected_transactions.transactions, + } + ); + } + + #[test] + fn handle_retrieve_transactions_when_start_block_number_starts_undefined_in_a_brand_new_database( + ) { + let system = System::new( + "handle_retrieve_transactions_when_start_block_number_starts_undefined_in_a_brand_new_database", ); + let port = find_free_port(); + let _blockchain_client_server = MBCSBuilder::new(port) + .ok_response("0x845FED".to_string(), 0) + .ok_response( + vec![LogObject { + removed: false, + log_index: Some("0x20".to_string()), + transaction_index: Some("0x30".to_string()), + transaction_hash: Some( + "0x2222222222222222222222222222222222222222222222222222222222222222" + .to_string(), + ), + block_hash: Some( + "0x1111111111111111111111111111111111111111111111111111111111111111" + .to_string(), + ), + block_number: Some("0x845FEC".to_string()), + address: "0x3333333333333333333333333333333333333334".to_string(), + data: "0x000000000000000000000000000000000000000000000000000000003b5dc100" + .to_string(), + topics: vec![ + "0xddf252ad1be2c89b69c2b06800000000000000000000736f6d6577616c6c6574" + .to_string(), + "0xddf252ad1be2c89b69c2b06900000000000000000000736f6d6577616c6c6574" + .to_string(), + ], + }], + 1, + ) + .start(); + let (accountant, _, accountant_recording_arc) = make_recorder(); + let accountant_addr = + accountant.system_stop_conditions(match_every_type_id!(ReceivedPayments)); + let some_wallet = make_wallet("somewallet"); + let recipient_wallet = make_wallet("recipient_wallet"); + let amount = 996000000; + let expected_transactions = RetrievedBlockchainTransactions { + new_start_block: 8675309u64, + transactions: vec![BlockchainTransaction { + block_number: 8675308u64, + from: some_wallet.clone(), + wei_amount: amount, + }], + }; + let blockchain_interface = make_blockchain_interface_web3(port); + let persistent_config = PersistentConfigurationMock::new() + .max_block_count_result(Ok(None)) + .start_block_result(Ok(None)); + 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_addr).build(); + send_bind_message!(subject_subs, peer_actors); + let retrieve_transactions = RetrieveTransactions { + recipient: recipient_wallet.clone(), + response_skeleton_opt: Some(ResponseSkeleton { + client_id: 1234, + context_id: 4321, + }), + }; + let before = SystemTime::now(); + + let _ = addr.try_send(retrieve_transactions).unwrap(); + + system.run(); + let after = SystemTime::now(); 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), + new_start_block: 8675309u64 + 1, + transactions: expected_transactions.transactions, response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, context_id: 4321 @@ -1617,8 +1675,8 @@ mod tests { System::new("handle_retrieve_transactions_sends_received_payments_back_to_accountant"); let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port) - .response("0x3B9ACA00".to_string(), 0) - .response( + .ok_response("0x3B9ACA00".to_string(), 0) + .ok_response( vec![LogObject { removed: false, log_index: Some("0x20".to_string()), @@ -1652,20 +1710,20 @@ mod tests { let earning_wallet = make_wallet("earning_wallet"); let amount = 996000000; let expected_transactions = RetrievedBlockchainTransactions { - new_start_block: 1000000001, + new_start_block: (0x3B9ACA00 + 1), 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)) + .start_block_result(Ok(Some(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(); @@ -1685,7 +1743,6 @@ mod tests { system.run(); let after = SystemTime::now(); - let accountant_recording = accountant_recording_arc.lock().unwrap(); assert_eq!(accountant_recording.len(), 1); let received_payments_message = accountant_recording.get_record::(0); @@ -1694,14 +1751,12 @@ mod tests { received_payments_message, &ReceivedPayments { timestamp: received_payments_message.timestamp, - scan_result: Ok(PaymentsAndStartBlock { - payments: expected_transactions.transactions, - new_start_block: expected_transactions.new_start_block, - }), + new_start_block: expected_transactions.new_start_block, response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, context_id: 4321 }), + transactions: expected_transactions.transactions, } ); } @@ -1731,21 +1786,22 @@ mod tests { ], }]; let _blockchain_client_server = MBCSBuilder::new(port) - .response("0x3B9ACA00".to_string(), 0) - .response(expected_response_logs, 1) + .ok_response("0x3B9ACA00".to_string(), 0) + .ok_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)); + .start_block_result(Ok(Some(6))) + .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 +1815,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 +1838,114 @@ 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) + .ok_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 set_max_block_count_params_arc = Arc::new(Mutex::new(vec![])); + let persistent_config = PersistentConfigurationMock::new() + .start_block_result(Ok(Some(6))) + .max_block_count_result(Err(PersistentConfigError::DatabaseError( + "my tummy hurts".to_string(), + ))) + .set_max_block_count_result(Ok(())) + .set_max_block_count_params(&set_max_block_count_params_arc); + 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(), + } + ); + let max_block_count_params = set_max_block_count_params_arc.lock().unwrap(); + assert_eq!(*max_block_count_params, vec![Some(1000)]); + 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) + .ok_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(Some(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 +1954,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,10 +1966,49 @@ 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) + .ok_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 start_block = Some(2000); let wallet = make_wallet("somewallet"); let persistent_config = PersistentConfigurationMock::default() .start_block_result(Ok(start_block)) @@ -1825,21 +2020,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 +2061,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) + .ok_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 start_block = Some(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 +2091,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 +2112,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, ); @@ -2010,6 +2195,15 @@ mod tests { assert_eq!(None, max_block_count); } + #[test] + fn extract_max_block_range_for_nodies_error_response() { + let result = BlockchainError::QueryFailed("RPC error: Error { code: InvalidParams, message: \"query exceeds max block range 100000\", data: None }".to_string()); + + let max_block_count = BlockchainBridge::extract_max_block_count(result); + + assert_eq!(Some(100000), max_block_count); + } + #[test] fn extract_max_block_range_for_expected_batch_got_single_error_response() { let result = BlockchainError::QueryFailed( @@ -2034,6 +2228,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..58a8865eb 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,60 @@ // 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)] +#[derive(Debug, PartialEq, Eq, Clone)] pub enum TransactionReceiptResult { - NotPresent, - Found(TransactionReceipt), - Error(String), + RpcResponse(TxReceipt), + LocalError(String), +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum TxStatus { + Failed, + Pending, + Succeeded(TransactionBlock), +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct TxReceipt { + pub transaction_hash: H256, + pub status: TxStatus, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct TransactionBlock { + pub block_hash: H256, + pub block_number: U64, +} + +impl From for TxReceipt { + fn from(receipt: TransactionReceipt) -> Self { + let status = match (receipt.status, receipt.block_hash, receipt.block_number) { + (Some(status), Some(block_hash), Some(block_number)) if status == U64::from(1) => { + TxStatus::Succeeded(TransactionBlock { + block_hash, + block_number, + }) + } + (Some(status), _, _) if status == U64::from(0) => TxStatus::Failed, + _ => TxStatus::Pending, + }; + + TxReceipt { + transaction_hash: receipt.transaction_hash, + status, + } + } } pub struct LowBlockchainIntWeb3 { @@ -92,22 +118,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 +129,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 +149,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,28 +171,28 @@ 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, TransactionReceipt, U256}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TxReceipt, TxStatus}; #[test] fn get_transaction_fee_balance_works() { let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port) - .response("0x23".to_string(), 1) + .ok_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() @@ -245,9 +207,9 @@ mod tests { ) { let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port) - .response("0xFFFQ".to_string(), 0) + .ok_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() @@ -270,9 +232,9 @@ mod tests { fn get_gas_price_works() { let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port) - .response("0x01".to_string(), 1) + .ok_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 +245,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() @@ -301,9 +263,9 @@ mod tests { fn get_block_number_works() { let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port) - .response("0x23".to_string(), 1) + .ok_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(); @@ -314,9 +276,9 @@ mod tests { fn get_block_number_returns_an_error() { let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port) - .response("trash".to_string(), 1) + .ok_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() @@ -336,9 +298,9 @@ mod tests { fn get_transaction_id_works() { let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port) - .response("0x23".to_string(), 1) + .ok_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 @@ -353,9 +315,9 @@ mod tests { fn get_transaction_id_returns_an_error_for_unintelligible_response() { let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port) - .response("0xFFFQ".to_string(), 0) + .ok_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() @@ -378,12 +340,12 @@ mod tests { fn get_token_balance_can_retrieve_token_balance_of_a_wallet() { let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port) - .response( + .ok_response( "0x000000000000000000000000000000000000000000000000000000000000FFFF".to_string(), 0, ) .start(); - let subject = make_blockchain_interface_web3(Some(port)); + let subject = make_blockchain_interface_web3(port); let result = subject .lower_interface() @@ -402,13 +364,13 @@ mod tests { fn get_token_balance_returns_error_for_unintelligible_response_to_token_balance() { let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port) - .response( + .ok_response( "0x000000000000000000000000000000000000000000000000000000000000FFFQ".to_string(), 0, ) .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,184 +393,6 @@ 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 tx_hash_1 = - H256::from_str("a128f9ca1e705cc20a936a24a7fa1df73bad6e0aaf58e8e6ffcc154a7cff6e0e") - .unwrap(); - let tx_hash_2 = - H256::from_str("a128f9ca1e705cc20a936a24a7fa1df73bad6e0aaf58e8e6ffcc154a7cff6e0f") - .unwrap(); - let tx_hash_vec = vec![tx_hash_1, tx_hash_2]; - - let result = subject - .lower_interface() - .get_transaction_receipt_batch(tx_hash_vec) - .wait() - .unwrap_err(); - - assert_eq!( - result, - BlockchainError::QueryFailed("Transport error: Error(IncompleteMessage)".to_string()) - ); - } - #[test] fn get_transaction_logs_works() { let port = find_free_port(); @@ -646,7 +430,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 +512,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)); @@ -759,4 +543,93 @@ mod tests { ) ); } + + #[test] + fn transaction_receipt_can_be_converted_to_successful_transaction() { + let tx_receipt: TxReceipt = create_tx_receipt( + Some(U64::from(1)), + Some(H256::from_low_u64_be(0x1234)), + Some(U64::from(10)), + H256::from_low_u64_be(0x5678), + ); + + assert_eq!(tx_receipt.transaction_hash, H256::from_low_u64_be(0x5678)); + match tx_receipt.status { + TxStatus::Succeeded(ref block) => { + assert_eq!(block.block_hash, H256::from_low_u64_be(0x1234)); + assert_eq!(block.block_number, U64::from(10)); + } + _ => panic!("Expected status to be Succeeded"), + } + } + + #[test] + fn transaction_receipt_can_be_converted_to_failed_transaction() { + let tx_receipt: TxReceipt = create_tx_receipt( + Some(U64::from(0)), + None, + None, + H256::from_low_u64_be(0x5678), + ); + + assert_eq!(tx_receipt.transaction_hash, H256::from_low_u64_be(0x5678)); + assert_eq!(tx_receipt.status, TxStatus::Failed); + } + + #[test] + fn transaction_receipt_can_be_converted_to_pending_transaction_no_status() { + let tx_receipt: TxReceipt = + create_tx_receipt(None, None, None, H256::from_low_u64_be(0x5678)); + + assert_eq!(tx_receipt.transaction_hash, H256::from_low_u64_be(0x5678)); + assert_eq!(tx_receipt.status, TxStatus::Pending); + } + + #[test] + fn transaction_receipt_can_be_converted_to_pending_transaction_no_block_info() { + let tx_receipt: TxReceipt = create_tx_receipt( + Some(U64::from(1)), + None, + None, + H256::from_low_u64_be(0x5678), + ); + + assert_eq!(tx_receipt.transaction_hash, H256::from_low_u64_be(0x5678)); + assert_eq!(tx_receipt.status, TxStatus::Pending); + } + + #[test] + fn transaction_receipt_can_be_converted_to_pending_transaction_no_status_and_block_info() { + let tx_receipt: TxReceipt = create_tx_receipt( + Some(U64::from(1)), + Some(H256::from_low_u64_be(0x1234)), + None, + H256::from_low_u64_be(0x5678), + ); + + assert_eq!(tx_receipt.transaction_hash, H256::from_low_u64_be(0x5678)); + assert_eq!(tx_receipt.status, TxStatus::Pending); + } + + fn create_tx_receipt( + status: Option, + block_hash: Option, + block_number: Option, + transaction_hash: H256, + ) -> TxReceipt { + let receipt = TransactionReceipt { + status, + root: None, + block_hash, + block_number, + cumulative_gas_used: Default::default(), + gas_used: None, + contract_address: None, + transaction_hash, + transaction_index: Default::default(), + logs: vec![], + logs_bloom: Default::default(), + }; + receipt.into() + } } diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs index 930ed9ba3..bea78e155 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,11 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -mod batch_payable_tools; pub mod lower_level_interface_web3; +mod utils; + 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 +16,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, TxReceipt, TxStatus}; +use crate::blockchain::blockchain_interface::blockchain_interface_web3::utils::{create_blockchain_agent_web3, send_payables_within_batch, BlockchainAgentFutureResult}; const CONTRACT_ABI: &str = indoc!( r#"[{ @@ -57,7 +61,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,17 +83,24 @@ 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, + start_block: u64, fallback_start_block_number: u64, recipient: Address, ) -> 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) => { @@ -112,7 +123,7 @@ impl BlockchainInterface for BlockchainInterfaceWeb3 { ); let filter = FilterBuilder::default() .address(vec![contract_address]) - .from_block(start_block) + .from_block(BlockNumber::Number(U64::from(start_block))) .to_block(BlockNumber::Number(U64::from(response_block_number))) .topics( Some(vec![TRANSACTION_LITERAL]), @@ -123,7 +134,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 +145,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 +164,7 @@ 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); + let chain = self.chain; Box::new( get_gas_price @@ -168,38 +180,92 @@ 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(create_blockchain_agent_web3( + gas_limit_const_part, + blockchain_agent_future_result, + consuming_wallet, + chain, + )) }) }) }), ) } - 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.clone()) + .and_then(move |batch_response| { + Ok(batch_response + .into_iter() + .zip(transaction_hashes) + .map(|(response, hash)| match response { + Ok(result) => { + match serde_json::from_value::(result) { + Ok(receipt) => { + TransactionReceiptResult::RpcResponse(receipt.into()) + } + Err(e) => { + if e.to_string().contains("invalid type: null") { + TransactionReceiptResult::RpcResponse(TxReceipt { + transaction_hash: hash, + status: TxStatus::Pending, + }) + } else { + TransactionReceiptResult::LocalError(e.to_string()) + } + } + } + } + Err(e) => TransactionReceiptResult::LocalError(e.to_string()), + }) + .collect::>()) + }), + ) + } + + fn submit_payables_in_batch( + &self, + logger: Logger, + 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(); + let chain = agent.get_chain(); + + 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,10 +288,12 @@ 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, + Chain::PolyMainnet | Chain::PolyAmoy | Chain::BaseMainnet | Chain::BaseSepolia => { + 70_000 + } } } @@ -246,15 +314,18 @@ impl BlockchainInterfaceWeb3 { } fn find_largest_transaction_block_number( - response_block_number: u64, + response_block_number: Option, transactions: &[BlockchainTransaction], - ) -> u64 { + ) -> Option { if transactions.is_empty() { response_block_number } else { transactions .iter() - .fold(response_block_number, |a, b| a.max(b.block_number)) + .fold(response_block_number.unwrap_or(0u64), |a, b| { + a.max(b.block_number) + }) + .into() } } @@ -290,15 +361,18 @@ impl BlockchainInterfaceWeb3 { // Get the largest transaction block number, unless there are no // transactions, in which case use end_block, unless get_latest_block() // was not successful. - let transaction_max_block_number = - Self::find_largest_transaction_block_number(response_block_number, &transactions); + let transaction_max_block_number = Self::find_largest_transaction_block_number( + Some(response_block_number), + &transactions, + ) + .unwrap(); debug!( logger, "Discovered transaction max block nbr: {}", transaction_max_block_number ); Ok(RetrievedBlockchainTransactions { - new_start_block: 1u64 + transaction_max_block_number, + new_start_block: transaction_max_block_number + 1, transactions, }) } @@ -309,7 +383,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,26 +393,25 @@ 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; use masq_lib::blockchains::chains::Chain; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; 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::{H256, U256}; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::{TransactionBlock, TxReceipt, TxStatus}; #[test] fn constants_are_correct() { @@ -373,12 +445,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) @@ -391,7 +467,7 @@ mod tests { let port = find_free_port(); #[rustfmt::skip] let _blockchain_client_server = MBCSBuilder::new(port) - .response("0x178def", 1) + .ok_response("0x178def", 1) .raw_response( r#"{ "jsonrpc":"2.0", @@ -431,12 +507,12 @@ 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 .retrieve_transactions( - BlockNumber::Number(42u64.into()), + 42u64, end_block_nbr, Wallet::from_str(&to).unwrap().address(), ) @@ -446,7 +522,7 @@ mod tests { assert_eq!( result, RetrievedBlockchainTransactions { - new_start_block: 0x4be664, + new_start_block: 0x4be663 + 1, transactions: vec![ BlockchainTransaction { block_number: 0x4be663, @@ -471,18 +547,14 @@ mod tests { let port = find_free_port(); let empty_transactions_result: Vec = vec![]; let _blockchain_client_server = MBCSBuilder::new(port) - .response("0x178def".to_string(), 2) - .response(empty_transactions_result, 2) + .ok_response("0x178def".to_string(), 2) + .ok_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 - .retrieve_transactions( - BlockNumber::Number(42u64.into()), - end_block_nbr, - to_wallet.address(), - ) + .retrieve_transactions(42u64, end_block_nbr, to_wallet.address()) .wait(); assert_eq!( @@ -496,23 +568,10 @@ mod tests { #[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)); - - let result = subject - .retrieve_transactions( - BlockNumber::Number(42u64.into()), - 555u64, - Wallet::new("0x3f69f9efd4f2592fd70beecd9dce71c472fc").address(), - ) - .wait(); + fn retrieving_address_of_uninitialised_wallet_panics() { + let subject = Wallet::new("0x3f69f9efd4f2592fd70beecd9dce71c472fc"); - assert_eq!( - result.expect_err("Expected an Err, got Ok"), - BlockchainError::InvalidAddress - ); + subject.address(); } #[test] @@ -520,14 +579,14 @@ mod tests { ) { let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port) - .response("0x178def", 1) + .ok_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( - BlockNumber::Number(42u64.into()), + 42u64, 555u64, Wallet::from_str("0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc") .unwrap() @@ -546,14 +605,14 @@ mod tests { ) { let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port) - .response("0x178def", 1) + .ok_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( - BlockNumber::Number(42u64.into()), + 42u64, 555u64, Wallet::from_str("0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc") .unwrap() @@ -569,7 +628,7 @@ mod tests { ) { let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port) - .response("0x178def", 1) + .ok_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(); @@ -579,13 +638,13 @@ mod tests { ) .unwrap(); - let end_block_nbr = 1024u64; + let end_block_nbr = 1025u64; let subject = BlockchainInterfaceWeb3::new(transport, event_loop_handle, TEST_DEFAULT_CHAIN); let result = subject .retrieve_transactions( - BlockNumber::Number(42u64.into()), + 42u64, end_block_nbr, Wallet::from_str("0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc") .unwrap() @@ -596,7 +655,7 @@ mod tests { assert_eq!( result, Ok(RetrievedBlockchainTransactions { - new_start_block: 1543664, + new_start_block: end_block_nbr, transactions: vec![] }) ); @@ -611,13 +670,12 @@ mod tests { ) { let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port) - .response("trash", 1) + .ok_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 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 subject = make_blockchain_interface_web3(port); + let start_block = 42u64; + let fallback_number = start_block; let result = subject .retrieve_transactions( @@ -629,12 +687,11 @@ mod tests { ) .wait(); - let expected_fallback_start_block = start_block_nbr + 1u64; - + let expected_start_block = fallback_number + 1u64; assert_eq!( result, Ok(RetrievedBlockchainTransactions { - new_start_block: 1 + expected_fallback_start_block, + new_start_block: expected_start_block, transactions: vec![] }) ); @@ -645,68 +702,49 @@ mod tests { let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port) // gas_price - .response("0x3B9ACA00".to_string(), 0) // 1000000000 + .ok_response("0x3B9ACA00".to_string(), 0) // 1000000000 // transaction_fee_balance - .response("0xFFF0".to_string(), 0) // 65520 + .ok_response("0xFFF0".to_string(), 0) // 65520 // masq_balance - .response( + .ok_response( "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 +752,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,11 +762,26 @@ 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(); let _blockchain_client_server = MBCSBuilder::new(port) - .response("0x3B9ACA00".to_string(), 0) + .ok_response("0x3B9ACA00".to_string(), 0) .start(); let expected_err_factory = |wallet: &Wallet| { BlockchainAgentBuildError::TransactionFeeBalance( @@ -749,8 +802,8 @@ mod tests { fn build_of_the_blockchain_agent_fails_on_masq_balance() { let port = find_free_port(); let _blockchain_client_server = MBCSBuilder::new(port) - .response("0x3B9ACA00".to_string(), 0) - .response("0xFFF0".to_string(), 0) + .ok_response("0x3B9ACA00".to_string(), 0) + .ok_response("0xFFF0".to_string(), 0) .start(); let expected_err_factory = |wallet: &Wallet| { BlockchainAgentBuildError::ServiceFeeBalance( @@ -768,29 +821,135 @@ 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()) + .ok_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(); + + assert_eq!(result[0], TransactionReceiptResult::LocalError("RPC error: Error { code: ServerError(429), message: \"The requests per second (RPS) of your requests are higher than your plan allows.\", data: None }".to_string())); + assert_eq!( + result[1], + TransactionReceiptResult::RpcResponse(TxReceipt { + transaction_hash: tx_hash_2, + status: TxStatus::Pending + }) + ); + assert_eq!( + result[2], + TransactionReceiptResult::LocalError( + "invalid type: string \"trash\", expected struct Receipt".to_string() ) - }; + ); + assert_eq!( + result[3], + TransactionReceiptResult::RpcResponse(TxReceipt { + transaction_hash: tx_hash_4, + status: TxStatus::Pending + }) + ); + assert_eq!( + result[4], + TransactionReceiptResult::RpcResponse(TxReceipt { + transaction_hash: tx_hash_5, + status: TxStatus::Failed, + }) + ); + assert_eq!( + result[5], + TransactionReceiptResult::RpcResponse(TxReceipt { + transaction_hash: tx_hash_6, + status: TxStatus::Succeeded(TransactionBlock { + block_hash, + block_number, + }), + }) + ); + } - build_of_the_blockchain_agent_fails_on_blockchain_interface_error( - port, - expected_err_factory, + #[test] + fn process_transaction_receipts_fails_on_submit_batch() { + let port = find_free_port(); + let _blockchain_client_server = MBCSBuilder::new(port).start(); + let subject = make_blockchain_interface_web3(port); + let tx_hash_1 = + H256::from_str("a128f9ca1e705cc20a936a24a7fa1df73bad6e0aaf58e8e6ffcc154a7cff6e0e") + .unwrap(); + let tx_hash_2 = + H256::from_str("a128f9ca1e705cc20a936a24a7fa1df73bad6e0aaf58e8e6ffcc154a7cff6e0f") + .unwrap(); + let tx_hash_vec = vec![tx_hash_1, tx_hash_2]; + + let error = subject + .process_transaction_receipts(tx_hash_vec) + .wait() + .unwrap_err(); + + assert_eq!( + error, + QueryFailed("Transport error: Error(IncompleteMessage)".to_string()) ); } @@ -810,171 +969,10 @@ mod tests { 70_000 ); 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, + Subject::web3_gas_limit_const_part(Chain::BaseSepolia), + 70_000 ); + assert_eq!(Subject::web3_gas_limit_const_part(Chain::Dev), 55_000); } } diff --git a/node/src/blockchain/blockchain_interface_utils.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs similarity index 60% rename from node/src/blockchain/blockchain_interface_utils.rs rename to node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs index 44ab853ff..2be7d5977 100644 --- a/node/src/blockchain/blockchain_interface_utils.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs @@ -1,4 +1,4 @@ -// 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. use crate::accountant::db_access_objects::payable_dao::PayableAccount; use crate::accountant::db_access_objects::pending_payable_dao::PendingPayable; @@ -6,7 +6,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 +24,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 +67,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 +83,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 +100,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 +126,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 +169,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 +180,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 +231,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 +246,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 +262,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 +290,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 +305,17 @@ 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, + gas_limit_const_part: u128, blockchain_agent_future_result: BlockchainAgentFutureResult, wallet: Wallet, + chain: Chain, ) -> 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 +323,7 @@ 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, + chain, )) } @@ -382,10 +350,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,67 +370,11 @@ 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() - .response( + .ok_response( "0x94881436a9c89f48b01651ff491c69e97089daf71ab8cfb240243d7ecf9b38b2".to_string(), 7, ) @@ -474,26 +387,30 @@ mod tests { .unwrap(); let pending_nonce = 1; let chain = DEFAULT_CHAIN; - let gas_price = DEFAULT_GAS_PRICE; + let gas_price_in_gwei = DEFAULT_GAS_PRICE; 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, + gwei_to_wei(gas_price_in_gwei), ); 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 +420,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(); @@ -549,7 +431,7 @@ mod tests { .unwrap(); let web3_batch = Web3::new(Batch::new(transport)); let chain = DEFAULT_CHAIN; - let gas_price = DEFAULT_GAS_PRICE; + let gas_price_in_gwei = DEFAULT_GAS_PRICE; let pending_nonce = 1; let consuming_wallet = make_paying_wallet(b"paying_wallet"); let account_1 = make_payable_account(1); @@ -557,13 +439,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, + gwei_to_wei(gas_price_in_gwei), pending_nonce.into(), - accounts, + &accounts, ); assert_eq!( @@ -624,7 +506,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 +573,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 +610,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 +630,71 @@ 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 port = find_free_port(); + let _blockchain_client_server = MBCSBuilder::new(port) + .begin_batch() + // TODO: GH-547: This rpc_result should be validated in production code. + .ok_response("irrelevant_ok_rpc_response".to_string(), 7) + .ok_response("irrelevant_ok_rpc_response_2".to_string(), 8) + .end_batch() + .start(); + 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(), + }), + ]); + + 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 port = find_free_port(); + 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() + ], + }); + + 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 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,110 +707,46 @@ 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, + 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(), }), - 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, + 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(), }), - 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 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) + .ok_response("rpc_result".to_string(), 7) .err_response( 429, "The requests per second (RPS) of your requests are higher than your plan allows." @@ -1011,93 +755,64 @@ mod tests { ) .end_batch() .start(); - let web3_batch = Web3::new(Batch::new(transport.clone())); + 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 (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(); + execute_send_payables_test( + "send_payables_within_batch_one_payment_works_the_other_fails", + accounts, + expected_result, + port, + ); + } - let result = send_payables_within_batch( - logger, - chain, - web3_batch, - consuming_wallet.clone(), - gas_price.clone(), - pending_nonce, - new_fingerprints_recipient, - accounts.clone(), + #[test] + fn advance_used_nonce_works() { + let initial_nonce = U256::from(55); + + let result = advance_used_nonce(initial_nonce); + + assert_eq!(result, U256::from(56)) + } + + #[test] + #[should_panic( + expected = "Consuming wallet doesn't contain a secret key: Signature(\"Cannot sign with non-keypair wallet: Address(0x000000000000000000006261645f77616c6c6574).\")" + )] + 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 * 10_u128.pow(18); 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() { @@ -1362,7 +1012,7 @@ mod tests { Wallet::from(address) }; let nonce_correct_type = U256::from(nonce); - let gas_price = match chain { + let gas_price_in_gwei = match chain { Chain::EthMainnet => TEST_GAS_PRICE_ETH, Chain::EthRopsten => TEST_GAS_PRICE_ETH, Chain::PolyMainnet => TEST_GAS_PRICE_POLYGON, @@ -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, + gwei_to_wei(gas_price_in_gwei), ); let byte_set_to_compare = signed_transaction.raw_transaction.0; 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/data_structures/mod.rs b/node/src/blockchain/blockchain_interface/data_structures/mod.rs index fa285da13..4894b3563 100644 --- a/node/src/blockchain/blockchain_interface/data_structures/mod.rs +++ b/node/src/blockchain/blockchain_interface/data_structures/mod.rs @@ -26,7 +26,7 @@ impl fmt::Display for BlockchainTransaction { } } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct RetrievedBlockchainTransactions { pub new_start_block: u64, pub transactions: Vec, diff --git a/node/src/blockchain/blockchain_interface/lower_level_interface.rs b/node/src/blockchain/blockchain_interface/lower_level_interface.rs index 58b75f5f8..c8653f985 100644 --- a/node/src/blockchain/blockchain_interface/lower_level_interface.rs +++ b/node/src/blockchain/blockchain_interface/lower_level_interface.rs @@ -1,19 +1,12 @@ // 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. @@ -38,29 +31,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..569620533 100644 --- a/node/src/blockchain/blockchain_interface/mod.rs +++ b/node/src/blockchain/blockchain_interface/mod.rs @@ -3,18 +3,21 @@ 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 web3::types::Address; +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; @@ -25,7 +28,7 @@ pub trait BlockchainInterface { fn retrieve_transactions( &self, - start_block: BlockNumber, + start_block: u64, fallback_start_block_number: u64, recipient: Address, ) -> Box>; @@ -35,5 +38,18 @@ 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, + 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..ee87519a0 100644 --- a/node/src/blockchain/blockchain_interface_initializer.rs +++ b/node/src/blockchain/blockchain_interface_initializer.rs @@ -63,13 +63,13 @@ 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("0xFF40".to_string(), 0) - .response( + .ok_response("0x3B9ACA00".to_string(), 0) // gas_price = 10000000000 + .ok_response("0xFF40".to_string(), 0) + .ok_response( "0x000000000000000000000000000000000000000000000000000000000000FFFF".to_string(), 0, ) - .response("0x23".to_string(), 1) + .ok_response("0x23".to_string(), 1) .start(); let wallet = make_wallet("123"); let chain = Chain::PolyMainnet; @@ -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/mod.rs b/node/src/blockchain/mod.rs index 5415cd56d..4c51e726e 100644 --- a/node/src/blockchain/mod.rs +++ b/node/src/blockchain/mod.rs @@ -6,8 +6,5 @@ pub mod blockchain_interface; pub mod blockchain_interface_initializer; 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/database/config_dumper.rs b/node/src/database/config_dumper.rs index 78f23ade7..17e24899e 100644 --- a/node/src/database/config_dumper.rs +++ b/node/src/database/config_dumper.rs @@ -353,11 +353,7 @@ mod tests { ); assert_value("neighborhoodMode", "zero-hop", &map); assert_value("schemaVersion", &CURRENT_SCHEMA_VERSION.to_string(), &map); - assert_value( - "startBlock", - &Chain::PolyMainnet.rec().contract_creation_block.to_string(), - &map, - ); + assert_null("startBlock", &map); assert_value( "exampleEncrypted", &dao.get("example_encrypted").unwrap().value_opt.unwrap(), @@ -503,11 +499,7 @@ mod tests { assert_value("pastNeighbors", "masq://polygon-mainnet:QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVowMTIzNDU@1.2.3.4:1234,masq://polygon-mainnet:QkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxMjM0NTY@2.3.4.5:2345", &map); assert_value("neighborhoodMode", "consume-only", &map); assert_value("schemaVersion", &CURRENT_SCHEMA_VERSION.to_string(), &map); - assert_value( - "startBlock", - &Chain::PolyMainnet.rec().contract_creation_block.to_string(), - &map, - ); + assert_null("startBlock", &map); let expected_ee_entry = dao.get("example_encrypted").unwrap().value_opt.unwrap(); let expected_ee_decrypted = Bip39::decrypt_bytes(&expected_ee_entry, "password").unwrap(); let expected_ee_string = encode_bytes(Some(expected_ee_decrypted)).unwrap().unwrap(); @@ -620,11 +612,7 @@ mod tests { ); assert_value("neighborhoodMode", "standard", &map); assert_value("schemaVersion", &CURRENT_SCHEMA_VERSION.to_string(), &map); - assert_value( - "startBlock", - &Chain::PolyMainnet.rec().contract_creation_block.to_string(), - &map, - ); + assert_null("startBlock", &map); assert_value( "exampleEncrypted", &dao.get("example_encrypted").unwrap().value_opt.unwrap(), @@ -679,6 +667,18 @@ mod tests { assert_eq!(actual_value, expected_value); } + fn assert_null(key: &str, map: &Map) { + assert!(map.contains_key(key)); + let value = map + .get(key) + .unwrap_or_else(|| panic!("record for {} is missing", key)); + assert!( + value.is_null(), + "Expecting {} to be null, but it wasn't", + value + ) + } + fn assert_encrypted_value( key: &str, expected_value: &str, diff --git a/node/src/database/db_initializer.rs b/node/src/database/db_initializer.rs index 8bfb9c1eb..bcb9a3a0a 100644 --- a/node/src/database/db_initializer.rs +++ b/node/src/database/db_initializer.rs @@ -205,13 +205,7 @@ impl DbInitializerReal { Self::set_config_value( conn, "start_block", - Some( - &external_params - .chain - .rec() - .contract_creation_block - .to_string(), - ), + None, false, &format!( "{} start block", @@ -967,15 +961,7 @@ mod tests { Some(&CURRENT_SCHEMA_VERSION.to_string()), false, ); - verify( - &mut config_vec, - "start_block", - Some(&format!( - "{}", - &TEST_DEFAULT_CHAIN.rec().contract_creation_block.to_string() - )), - false, - ); + verify(&mut config_vec, "start_block", None, false); assert_eq!(config_vec, vec![]); } diff --git a/node/src/db_config/config_dao.rs b/node/src/db_config/config_dao.rs index 23bd1fce5..36798dd05 100644 --- a/node/src/db_config/config_dao.rs +++ b/node/src/db_config/config_dao.rs @@ -180,7 +180,7 @@ mod tests { use crate::database::db_initializer::{DbInitializer, DbInitializerReal}; use crate::database::test_utils::ConnectionWrapperMock; use crate::test_utils::assert_contains; - use masq_lib::constants::{CURRENT_SCHEMA_VERSION, ROPSTEN_TESTNET_CONTRACT_CREATION_BLOCK}; + use masq_lib::constants::CURRENT_SCHEMA_VERSION; use masq_lib::test_utils::utils::ensure_node_home_directory_exists; use rusqlite::Connection; use std::path::Path; @@ -201,14 +201,7 @@ mod tests { false, ), ); - assert_contains( - &result, - &ConfigDaoRecord::new( - "start_block", - Some(&ROPSTEN_TESTNET_CONTRACT_CREATION_BLOCK.to_string()), - false, - ), - ); + assert_contains(&result, &ConfigDaoRecord::new("start_block", None, false)); assert_contains( &result, &ConfigDaoRecord::new("consuming_wallet_private_key", None, true), diff --git a/node/src/db_config/persistent_configuration.rs b/node/src/db_config/persistent_configuration.rs index da3fa1583..532048a34 100644 --- a/node/src/db_config/persistent_configuration.rs +++ b/node/src/db_config/persistent_configuration.rs @@ -113,7 +113,7 @@ pub trait PersistentConfiguration { fn mapping_protocol(&self) -> Result, PersistentConfigError>; fn set_mapping_protocol( &mut self, - value: Option, + value_opt: Option, ) -> Result<(), PersistentConfigError>; fn min_hops(&self) -> Result; fn set_min_hops(&mut self, value: Hops) -> Result<(), PersistentConfigError>; @@ -131,13 +131,13 @@ pub trait PersistentConfiguration { node_descriptors_opt: Option>, db_password: &str, ) -> Result<(), PersistentConfigError>; - fn start_block(&self) -> Result; - fn set_start_block(&mut self, value: u64) -> Result<(), PersistentConfigError>; + fn start_block(&self) -> Result, PersistentConfigError>; + fn set_start_block(&mut self, value_opt: Option) -> Result<(), PersistentConfigError>; fn max_block_count(&self) -> Result, PersistentConfigError>; - fn set_max_block_count(&mut self, value: Option) -> Result<(), PersistentConfigError>; + fn set_max_block_count(&mut self, value_opt: Option) -> Result<(), PersistentConfigError>; fn set_start_block_from_txn( &mut self, - value: u64, + value_opt: Option, transaction: &mut TransactionSafeWrapper, ) -> Result<(), PersistentConfigError>; fn set_wallet_info( @@ -335,9 +335,9 @@ impl PersistentConfiguration for PersistentConfigurationReal { fn set_mapping_protocol( &mut self, - value: Option, + value_opt: Option, ) -> Result<(), PersistentConfigError> { - Ok(self.dao.set("mapping_protocol", value.map(to_string))?) + Ok(self.dao.set("mapping_protocol", value_opt.map(to_string))?) } fn min_hops(&self) -> Result { @@ -406,28 +406,28 @@ impl PersistentConfiguration for PersistentConfigurationReal { )?) } - fn start_block(&self) -> Result { - self.simple_get_method(decode_u64, "start_block") + fn start_block(&self) -> Result, PersistentConfigError> { + Ok(decode_u64(self.get("start_block")?)?) } - fn set_start_block(&mut self, value: u64) -> Result<(), PersistentConfigError> { - self.simple_set_method("start_block", value) + fn set_start_block(&mut self, value_opt: Option) -> Result<(), PersistentConfigError> { + Ok(self.dao.set("start_block", encode_u64(value_opt)?)?) } fn max_block_count(&self) -> Result, PersistentConfigError> { Ok(decode_u64(self.get("max_block_count")?)?) } - fn set_max_block_count(&mut self, value: Option) -> Result<(), PersistentConfigError> { - Ok(self.dao.set("max_block_count", encode_u64(value)?)?) + fn set_max_block_count(&mut self, value_opt: Option) -> Result<(), PersistentConfigError> { + Ok(self.dao.set("max_block_count", encode_u64(value_opt)?)?) } fn set_start_block_from_txn( &mut self, - value: u64, + value_opt: Option, transaction: &mut TransactionSafeWrapper, ) -> Result<(), PersistentConfigError> { - self.simple_set_method_from_provided_txn("start_block", value, transaction) + self.simple_set_method_from_provided_txn("start_block", value_opt, transaction) } fn set_wallet_info( @@ -568,23 +568,14 @@ impl PersistentConfigurationReal { fn simple_set_method_from_provided_txn( &mut self, parameter_name: &str, - value: T, + value_opt: Option, txn: &mut TransactionSafeWrapper, ) -> Result<(), PersistentConfigError> { - Ok(self - .dao - .set_by_guest_transaction(txn, parameter_name, Some(value.to_string()))?) - } - - fn simple_get_method( - &self, - decoder: fn(Option) -> Result, TypedConfigLayerError>, - parameter: &str, - ) -> Result { - match decoder(self.get(parameter)?)? { - None => Self::missing_value_panic(parameter), - Some(value) => Ok(value), - } + Ok(self.dao.set_by_guest_transaction( + txn, + parameter_name, + value_opt.map(|v| v.to_string()), + )?) } fn combined_params_get_method<'a, T, C>( @@ -1503,12 +1494,11 @@ mod tests { let start_block = subject.start_block().unwrap(); - assert_eq!(start_block, 6); + assert_eq!(start_block, Some(6)); } #[test] - #[should_panic(expected = "ever-supplied value missing: start_block; database is corrupt!")] - fn start_block_does_not_tolerate_optional_output() { + fn start_block_can_be_none() { let config_dao = Box::new(ConfigDaoMock::new().get_result(Ok(ConfigDaoRecord::new( "start_block", None, @@ -1516,11 +1506,13 @@ mod tests { )))); let subject = PersistentConfigurationReal::new(config_dao); - let _ = subject.start_block(); + let start_block = subject.start_block(); + + assert_eq!(start_block, Ok(None)); } #[test] - fn set_start_block_success() { + fn set_start_block_success_with_some() { let set_params_arc = Arc::new(Mutex::new(vec![])); let config_dao = Box::new( ConfigDaoMock::new() @@ -1529,7 +1521,7 @@ mod tests { ); let mut subject = PersistentConfigurationReal::new(config_dao); - let result = subject.set_start_block(1234); + let result = subject.set_start_block(Some(1234)); assert_eq!(result, Ok(())); let set_params = set_params_arc.lock().unwrap(); @@ -1553,7 +1545,7 @@ mod tests { let mut txn = TransactionSafeWrapper::new_with_builder(txn_inner_builder); let mut subject = PersistentConfigurationReal::new(config_dao); - let result = subject.set_start_block_from_txn(1234, &mut txn); + let result = subject.set_start_block_from_txn(Some(1234), &mut txn); assert_eq!(result, Ok(())); let set_params = set_params_arc.lock().unwrap(); @@ -1563,6 +1555,23 @@ mod tests { ) } + #[test] + fn set_start_block_success_with_none() { + let set_params_arc = Arc::new(Mutex::new(vec![])); + let config_dao = Box::new( + ConfigDaoMock::new() + .set_params(&set_params_arc) + .set_result(Ok(())), + ); + let mut subject = PersistentConfigurationReal::new(config_dao); + + let result = subject.set_start_block(None); + + assert_eq!(result, Ok(())); + let set_params = set_params_arc.lock().unwrap(); + assert_eq!(*set_params, vec![("start_block".to_string(), None)]) + } + #[test] fn gas_price() { let config_dao = Box::new(ConfigDaoMock::new().get_result(Ok(ConfigDaoRecord::new( 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/neighborhood/overall_connection_status.rs b/node/src/neighborhood/overall_connection_status.rs index 18e5b2a13..abb533f8c 100644 --- a/node/src/neighborhood/overall_connection_status.rs +++ b/node/src/neighborhood/overall_connection_status.rs @@ -93,21 +93,28 @@ impl ConnectionProgress { } pub fn handle_pass_gossip(&mut self, logger: &Logger, new_pass_target: IpAddr) { - if self.connection_stage != ConnectionStage::TcpConnectionEstablished { - panic!( - "Can't update the stage from {:?} to {:?}", + let preliminary_msg = format!( + "Pass gossip received from Node with IP Address {:?} to a Node with IP Address {:?}", + self.current_peer_addr, new_pass_target, + ); + match self.connection_stage { + ConnectionStage::StageZero => { + error!( + logger, + "{preliminary_msg}. Requested to update the stage from StageZero to StageZero.", + ) + } + ConnectionStage::TcpConnectionEstablished => { + debug!( + logger, + "{preliminary_msg}. Updating the stage from TcpConnectionEstablished to StageZero.", + ) + } + _ => panic!( + "{preliminary_msg}. Can't update the stage from {:?} to StageZero", self.connection_stage, - ConnectionStage::StageZero - ) - }; - - debug!( - logger, - "Pass gossip received from Node with IP Address {:?} to a Node with IP Address {:?}. \ - Hence, updating the connection stage of the new Node to StageZero.", - self.current_peer_addr, - new_pass_target - ); + ), + } self.connection_stage = ConnectionStage::StageZero; self.current_peer_addr = new_pass_target; @@ -351,13 +358,13 @@ mod tests { #[test] fn connection_progress_handles_pass_gossip_correctly_and_performs_logging_in_order() { init_test_logging(); + let test_name = + "connection_progress_handles_pass_gossip_correctly_and_performs_logging_in_order"; let ip_addr = make_ip(1); let initial_node_descriptor = make_node_descriptor(ip_addr); let mut subject = ConnectionProgress::new(initial_node_descriptor.clone()); let pass_target = make_ip(2); - let logger = Logger::new( - "connection_progress_handles_pass_gossip_correctly_and_performs_logging_in_order", - ); + let logger = Logger::new(test_name); subject.update_stage(&logger, ConnectionStage::TcpConnectionEstablished); subject.handle_pass_gossip(&logger, pass_target); @@ -372,29 +379,57 @@ mod tests { ); TestLogHandler::new().assert_logs_contain_in_order(vec![ &format!( - "DEBUG: connection_progress_handles_pass_gossip_correctly_and\ - _performs_logging_in_order: The connection stage \ + "DEBUG: {test_name}: The connection stage \ for Node with IP address {:?} has been updated from {:?} to {:?}.", ip_addr, ConnectionStage::StageZero, ConnectionStage::TcpConnectionEstablished ), &format!( - "DEBUG: connection_progress_handles_pass_gossip_correctly_and_performs_logging\ - _in_order: Pass gossip received from Node with IP Address {:?} to a Node with \ - IP Address {:?}. Hence, updating the connection stage of the new Node to StageZero.", + "DEBUG: {test_name}: Pass gossip received from Node with IP Address {:?} to a Node with \ + IP Address {:?}. Updating the stage from TcpConnectionEstablished to StageZero.", ip_addr, pass_target ), ]); } #[test] - #[should_panic(expected = "Can't update the stage from StageZero to StageZero")] + fn connection_progress_logs_error_while_handling_pass_gossip_in_case_tcp_connection_is_not_established( + ) { + init_test_logging(); + let test_name = "connection_progress_logs_error_while_handling_pass_gossip_in_case_tcp_connection_is_not_established"; + let ip_addr = make_ip(1); + let initial_node_descriptor = make_node_descriptor(ip_addr); + let mut subject = ConnectionProgress::new(initial_node_descriptor.clone()); + let pass_target = make_ip(2); + + subject.handle_pass_gossip(&Logger::new(test_name), pass_target); + + assert_eq!( + subject, + ConnectionProgress { + initial_node_descriptor, + current_peer_addr: pass_target, + connection_stage: ConnectionStage::StageZero + } + ); + TestLogHandler::new().exists_log_containing(&format!( + "ERROR: {test_name}: Pass gossip received from Node with IP Address 1.1.1.1 to a Node \ + with IP Address 1.1.1.2. Requested to update the stage from StageZero to StageZero." + )); + } + + #[test] + #[should_panic( + expected = "Pass gossip received from Node with IP Address 1.1.1.1 to a Node \ + with IP Address 1.1.1.2. Can't update the stage from NeighborshipEstablished to StageZero" + )] fn connection_progress_panics_while_handling_pass_gossip_in_case_tcp_connection_is_not_established( ) { let ip_addr = make_ip(1); let initial_node_descriptor = make_node_descriptor(ip_addr); let mut subject = ConnectionProgress::new(initial_node_descriptor); + subject.connection_stage = ConnectionStage::NeighborshipEstablished; let pass_target = make_ip(2); subject.handle_pass_gossip(&Logger::new("test"), pass_target); diff --git a/node/src/node_configurator/configurator.rs b/node/src/node_configurator/configurator.rs index 30f0eed57..19b0b958a 100644 --- a/node/src/node_configurator/configurator.rs +++ b/node/src/node_configurator/configurator.rs @@ -551,7 +551,8 @@ impl Configurator { persistent_config.earning_wallet_address(), "earningWalletAddressOpt", )?; - let start_block = Self::value_required(persistent_config.start_block(), "startBlock")?; + let start_block_opt = + Self::value_not_required(persistent_config.start_block(), "startBlock")?; let max_block_count_opt = match persistent_config.max_block_count() { Ok(value) => value, Err(e) => panic!( @@ -649,7 +650,7 @@ impl Configurator { exit_byte_rate, exit_service_rate, }, - start_block, + start_block_opt, scan_intervals: UiScanIntervals { pending_payable_sec, payable_sec, @@ -786,11 +787,15 @@ impl Configurator { } fn set_start_block(&mut self, string_number: String) -> Result<(), (u64, String)> { - let block_number = match string_number.parse::() { - Ok(num) => num, - Err(e) => return Err((NON_PARSABLE_VALUE, format!("start block: {:?}", e))), + let block_number_opt = if "none".eq_ignore_ascii_case(&string_number) { + None + } else { + match string_number.parse::() { + Ok(num) => Some(num), + Err(e) => return Err((NON_PARSABLE_VALUE, format!("start block: {:?}", e))), + } }; - match self.persistent_config.set_start_block(block_number) { + match self.persistent_config.set_start_block(block_number_opt) { Ok(_) => Ok(()), Err(e) => Err((CONFIGURATOR_WRITE_ERROR, format!("start block: {:?}", e))), } @@ -2118,11 +2123,54 @@ mod tests { let (_, context_id) = UiSetConfigurationResponse::fmb(response.body.clone()).unwrap(); assert_eq!(context_id, 4444); let check_start_block_params = set_start_block_params_arc.lock().unwrap(); - assert_eq!(*check_start_block_params, vec![166666]); - TestLogHandler::new().exists_log_containing(&format!( - "DEBUG: {}: A request from UI received: {:?} from context id: {}", - test_name, msg, context_id - )); + assert_eq!(*check_start_block_params, vec![Some(166666)]); + } + + #[test] + fn handle_none_cases() { + vec!["none", "None", "nOnE", "NoNe", "NONE"] + .iter() + .for_each(|value| handle_set_configuration_accepts_none_to_unset_start_block(value)); + } + + fn handle_set_configuration_accepts_none_to_unset_start_block(cfg_value: &str) { + init_test_logging(); + let test_name = format!( + "handle_set_configuration_accepts_{}_to_unset_start_block", + &cfg_value + ); + let set_start_block_params_arc = Arc::new(Mutex::new(vec![])); + let (ui_gateway, _, ui_gateway_recording_arc) = make_recorder(); + let persistent_config = PersistentConfigurationMock::new() + .set_start_block_params(&set_start_block_params_arc) + .set_start_block_result(Ok(())); + let mut subject = make_subject(Some(persistent_config)); + subject.logger = Logger::new(test_name.as_str()); + let subject_addr = subject.start(); + let peer_actors = peer_actors_builder().ui_gateway(ui_gateway).build(); + subject_addr.try_send(BindMessage { peer_actors }).unwrap(); + let msg = UiSetConfigurationRequest { + name: "start-block".to_string(), + value: cfg_value.to_string(), + }; + let context_id = 4444; + + subject_addr + .try_send(NodeFromUiMessage { + client_id: 1234, + body: msg.clone().tmb(context_id), + }) + .unwrap(); + + let system = System::new("test"); + System::current().stop(); + system.run(); + let ui_gateway_recording = ui_gateway_recording_arc.lock().unwrap(); + let response = ui_gateway_recording.get_record::(0); + let (_, context_id) = UiSetConfigurationResponse::fmb(response.body.clone()).unwrap(); + assert_eq!(context_id, 4444); + let check_start_block_params = set_start_block_params_arc.lock().unwrap(); + assert_eq!(*check_start_block_params, vec![None]); } #[test] @@ -2498,7 +2546,7 @@ mod tests { .neighborhood_mode_result(Ok(NeighborhoodModeLight::Standard)) .past_neighbors_result(Ok(Some(vec![node_descriptor.clone()]))) .earning_wallet_address_result(Ok(Some(earning_wallet_address.clone()))) - .start_block_result(Ok(3456)); + .start_block_result(Ok(Some(3456))); let persistent_config = payment_thresholds_scan_intervals_rate_pack(persistent_config); let mut subject = make_subject(Some(persistent_config)); @@ -2541,7 +2589,7 @@ mod tests { exit_byte_rate: 10, exit_service_rate: 13 }, - start_block: 3456, + start_block_opt: Some(3456), scan_intervals: UiScanIntervals { pending_payable_sec: 122, payable_sec: 125, @@ -2629,8 +2677,7 @@ mod tests { .past_neighbors_params(&past_neighbors_params_arc) .past_neighbors_result(Ok(Some(vec![node_descriptor.clone()]))) .earning_wallet_address_result(Ok(Some(earning_wallet_address.clone()))) - .start_block_result(Ok(3456)) - .start_block_result(Ok(3456)); + .start_block_result(Ok(Some(3456))); let persistent_config = payment_thresholds_scan_intervals_rate_pack(persistent_config); let mut subject = make_subject(Some(persistent_config)); @@ -2673,7 +2720,7 @@ mod tests { exit_byte_rate: 10, exit_service_rate: 13 }, - start_block: 3456, + start_block_opt: Some(3456), scan_intervals: UiScanIntervals { pending_payable_sec: 122, payable_sec: 125, @@ -2700,7 +2747,7 @@ mod tests { .chain_name_result("ropsten".to_string()) .gas_price_result(Ok(2345)) .earning_wallet_address_result(Ok(None)) - .start_block_result(Ok(3456)) + .start_block_result(Ok(Some(3456))) .max_block_count_result(Ok(None)) .neighborhood_mode_result(Ok(NeighborhoodModeLight::ZeroHop)) .mapping_protocol_result(Ok(None)) @@ -2766,7 +2813,7 @@ mod tests { exit_byte_rate: 0, exit_service_rate: 0 }, - start_block: 3456, + start_block_opt: Some(3456), scan_intervals: UiScanIntervals { pending_payable_sec: 0, payable_sec: 0, @@ -2789,7 +2836,7 @@ mod tests { .chain_name_result("ropsten".to_string()) .gas_price_result(Ok(2345)) .earning_wallet_address_result(Ok(Some("4a5e43b54c6C56Ebf7".to_string()))) - .start_block_result(Ok(3456)) + .start_block_result(Ok(Some(3456))) .max_block_count_result(Err(PersistentConfigError::DatabaseError( "Corruption".to_string(), ))); @@ -2839,7 +2886,7 @@ mod tests { .earning_wallet_address_result(Ok(Some( "0x0123456789012345678901234567890123456789".to_string(), ))) - .start_block_result(Ok(3456)) + .start_block_result(Ok(Some(3456))) .max_block_count_result(Ok(Some(100000))) .neighborhood_mode_result(Ok(NeighborhoodModeLight::ConsumeOnly)) .mapping_protocol_result(Ok(Some(AutomapProtocol::Igdp))) diff --git a/node/src/proxy_server/mod.rs b/node/src/proxy_server/mod.rs index 058c7c12f..55c1c6efd 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).validate_hostname() { + 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 validate_hostname(&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,47 @@ 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 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 peer_actors = peer_actors_builder().build(); + 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 +6005,43 @@ mod tests { assert_eq!(expected_result, clean_hostname); } + #[test] + fn hostname_is_valid_works() { + // IPv4 + assert_eq!( + Hostname::new("0.0.0.0").validate_hostname(), + Err("0.0.0.0".to_string()) + ); + assert_eq!( + Hostname::new("127.0.0.1").validate_hostname(), + Err("127.0.0.1".to_string()) + ); + assert_eq!(Hostname::new("192.168.1.158").validate_hostname(), Ok(())); + // IPv6 + assert_eq!( + Hostname::new("0:0:0:0:0:0:0:0").validate_hostname(), + Err("::".to_string()) + ); + assert_eq!( + Hostname::new("0:0:0:0:0:0:0:1").validate_hostname(), + Err("::1".to_string()) + ); + assert_eq!( + Hostname::new("2001:0db8:85a3:0000:0000:8a2e:0370:7334").validate_hostname(), + Ok(()) + ); + // Hostname + assert_eq!( + Hostname::new("localhost").validate_hostname(), + Err("localhost".to_string()) + ); + assert_eq!(Hostname::new("example.com").validate_hostname(), Ok(())); + assert_eq!( + Hostname::new("https://example.com").validate_hostname(), + Ok(()) + ); + } + #[test] #[should_panic( expected = "ProxyServer should never get ShutdownStreamMsg about clandestine stream" diff --git a/node/src/server_initializer.rs b/node/src/server_initializer.rs index 2f2c5f320..ea288fdce 100644 --- a/node/src/server_initializer.rs +++ b/node/src/server_initializer.rs @@ -28,6 +28,7 @@ use time::OffsetDateTime; use tokio::prelude::{Async, Future}; pub struct ServerInitializerReal { + #[allow(dead_code)] dns_socket_server: Box>, bootstrapper: Box>, privilege_dropper: Box, @@ -42,12 +43,13 @@ impl ServerInitializer for ServerInitializerReal { let data_directory = value_m!(multi_config, "data-directory", String) .expect("ServerInitializer: Data directory not present in Multi Config"); + // TODO: GH-525: This card should bring back the commented out code for dns_socket_server let result: RunModeResult = Ok(()) - .combine_results( - self.dns_socket_server - .as_mut() - .initialize_as_privileged(&multi_config), - ) + // .combine_results( + // self.dns_socket_server + // .as_mut() + // .initialize_as_privileged(&multi_config), + // ) .combine_results( self.bootstrapper .as_mut() @@ -60,11 +62,11 @@ impl ServerInitializer for ServerInitializerReal { self.privilege_dropper.drop_privileges(&real_user); result - .combine_results( - self.dns_socket_server - .as_mut() - .initialize_as_unprivileged(&multi_config, streams), - ) + // .combine_results( + // self.dns_socket_server + // .as_mut() + // .initialize_as_unprivileged(&multi_config, streams), + // ) .combine_results( self.bootstrapper .as_mut() @@ -79,11 +81,12 @@ impl Future for ServerInitializerReal { type Error = (); fn poll(&mut self) -> Result::Item>, ::Error> { - try_ready!(self - .dns_socket_server - .as_mut() - .join(self.bootstrapper.as_mut()) - .poll()); + // try_ready!(self + // .dns_socket_server + // .as_mut() + // .join(self.bootstrapper.as_mut()) + // .poll()); + try_ready!(self.bootstrapper.as_mut().poll()); Ok(Async::Ready(())) } } @@ -726,7 +729,8 @@ pub mod tests { } #[test] - #[should_panic(expected = "EntryDnsServerMock was instructed to panic")] + // TODO: GH-525: It should panic + // #[should_panic(expected = "EntryDnsServerMock was instructed to panic")] fn server_initializer_dns_socket_server_panics() { let bootstrapper = CrashTestDummy::new(CrashPoint::None, BootstrapperConfig::new()); let privilege_dropper = PrivilegeDropperMock::new(); @@ -840,8 +844,8 @@ pub mod tests { [ bootstrapper_init_privileged_params_arc, bootstrapper_init_unprivileged_params_arc, - dns_socket_server_privileged_params_arc, - dns_socket_server_unprivileged_params_arc, + // dns_socket_server_privileged_params_arc, // TODO: GH-525: Fix me + // dns_socket_server_unprivileged_params_arc, ] .iter() .for_each(|arc_params| { @@ -889,12 +893,13 @@ pub mod tests { let result = subject.go(&mut holder.streams(), &args); + // TODO: GH-525: Fix me assert_eq!( result, Err(ConfiguratorError::new(vec![ - ParamError::new("dns-iap", "dns-iap-reason"), + // ParamError::new("dns-iap", "dns-iap-reason"), ParamError::new("boot-iap", "boot-iap-reason"), - ParamError::new("dns-iau", "dns-iau-reason"), + // ParamError::new("dns-iau", "dns-iau-reason"), ParamError::new("boot-iau", "boot-iau-reason") ])) ); 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/neighborhood.rs b/node/src/sub_lib/neighborhood.rs index dc4872273..d3acc5655 100644 --- a/node/src/sub_lib/neighborhood.rs +++ b/node/src/sub_lib/neighborhood.rs @@ -752,7 +752,7 @@ mod tests { assert_eq!( result, Err( - "Chain identifier 'bitcoin' is not valid; possible values are 'polygon-mainnet', 'eth-mainnet', 'polygon-amoy', 'eth-ropsten' while formatted as 'masq://:@'" + "Chain identifier 'bitcoin' is not valid; possible values are 'polygon-mainnet', 'eth-mainnet', 'base-mainnet', 'base-sepolia', 'polygon-amoy', 'eth-ropsten' while formatted as 'masq://:@'" .to_string() ) ); @@ -851,7 +851,7 @@ mod tests { let result = DescriptorParsingError::WrongChainIdentifier("blah").to_string(); - assert_eq!(result, "Chain identifier 'blah' is not valid; possible values are 'polygon-mainnet', 'eth-mainnet', 'polygon-amoy', 'eth-ropsten' while formatted as 'masq://:@'") + assert_eq!(result, "Chain identifier 'blah' is not valid; possible values are 'polygon-mainnet', 'eth-mainnet', 'base-mainnet', 'base-sepolia', 'polygon-amoy', 'eth-ropsten' while formatted as 'masq://:@'") } #[test] 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/database_utils.rs b/node/src/test_utils/database_utils.rs index 2005166c0..02ba441a4 100644 --- a/node/src/test_utils/database_utils.rs +++ b/node/src/test_utils/database_utils.rs @@ -196,7 +196,7 @@ fn contains_particular_list_of_key_words( found += 1 } }); - assert_eq!(found,1, "We found {} occurrences of the searched line in the tested sql although only a one is considered correct", found) + assert_eq!(found, 1, "We found {} occurrences of the searched line in the tested sql although only a one is considered correct", found) } fn prepare_expected_vectors_of_words_including_sorting( diff --git a/node/src/test_utils/persistent_configuration_mock.rs b/node/src/test_utils/persistent_configuration_mock.rs index 00c50e7c2..4906c835c 100644 --- a/node/src/test_utils/persistent_configuration_mock.rs +++ b/node/src/test_utils/persistent_configuration_mock.rs @@ -58,14 +58,14 @@ pub struct PersistentConfigurationMock { set_past_neighbors_params: Arc>, String)>>>, set_past_neighbors_results: RefCell>>, start_block_params: Arc>>, - start_block_results: RefCell>>, - set_start_block_params: Arc>>, + start_block_results: RefCell, PersistentConfigError>>>, + set_start_block_params: Arc>>>, set_start_block_results: RefCell>>, max_block_count_params: Arc>>, max_block_count_results: RefCell, PersistentConfigError>>>, set_max_block_count_params: Arc>>>, set_max_block_count_results: RefCell>>, - set_start_block_from_txn_params: Arc>>, + set_start_block_from_txn_params: Arc, ArbitraryIdStamp)>>>, set_start_block_from_txn_results: RefCell>>, payment_thresholds_results: RefCell>>, set_payment_thresholds_params: Arc>>, @@ -230,12 +230,12 @@ impl PersistentConfiguration for PersistentConfigurationMock { self.set_past_neighbors_results.borrow_mut().remove(0) } - fn start_block(&self) -> Result { + fn start_block(&self) -> Result, PersistentConfigError> { self.start_block_params.lock().unwrap().push(()); Self::result_from(&self.start_block_results) } - fn set_start_block(&mut self, value: u64) -> Result<(), PersistentConfigError> { + fn set_start_block(&mut self, value: Option) -> Result<(), PersistentConfigError> { self.set_start_block_params.lock().unwrap().push(value); Self::result_from(&self.set_start_block_results) } @@ -252,7 +252,7 @@ impl PersistentConfiguration for PersistentConfigurationMock { fn set_start_block_from_txn( &mut self, - value: u64, + value: Option, transaction: &mut TransactionSafeWrapper, ) -> Result<(), PersistentConfigError> { self.set_start_block_from_txn_params @@ -546,12 +546,12 @@ impl PersistentConfigurationMock { self } - pub fn start_block_result(self, result: Result) -> Self { + pub fn start_block_result(self, result: Result, PersistentConfigError>) -> Self { self.start_block_results.borrow_mut().push(result); self } - pub fn set_start_block_params(mut self, params: &Arc>>) -> Self { + pub fn set_start_block_params(mut self, params: &Arc>>>) -> Self { self.set_start_block_params = params.clone(); self } @@ -586,7 +586,7 @@ impl PersistentConfigurationMock { pub fn set_start_block_from_txn_params( mut self, - params: &Arc>>, + params: &Arc, ArbitraryIdStamp)>>>, ) -> Self { self.set_start_block_from_txn_params = params.clone(); self @@ -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/contract_test.rs b/node/tests/contract_test.rs index 8e9e74395..42d6fce37 100644 --- a/node/tests/contract_test.rs +++ b/node/tests/contract_test.rs @@ -140,6 +140,32 @@ fn masq_erc20_contract_exists_on_ethereum_mainnet_integration() { assert_contract(blockchain_urls, &chain, assertion_body) } +#[test] +fn masq_erc20_contract_exists_on_base_mainnet_integration() { + let blockchain_urls = vec![ + "https://base-rpc.publicnode.com", + "https://base.drpc.org", + "https://base-pokt.nodies.app", + ]; + let chain = Chain::BaseMainnet; + + let assertion_body = |url, chain| assert_contract_existence(url, chain, "MASQ", 18); + assert_contract(blockchain_urls, &chain, assertion_body) +} + +#[test] +fn masq_erc20_contract_exists_on_base_sepolia_integration() { + let blockchain_urls = vec![ + "https://rpc.ankr.com/base_sepolia", + "https://base-sepolia-rpc.publicnode.com", + "https://base-sepolia.public.blastapi.io", + ]; + let chain = Chain::BaseSepolia; + + let assertion_body = |url, chain| assert_contract_existence(url, chain, "tMASQ", 18); + assert_contract(blockchain_urls, &chain, assertion_body) +} + fn assert_total_supply( blockchain_service_url: &str, chain: &Chain, diff --git a/node/tests/dns_round_trip_test.rs b/node/tests/dns_round_trip_test.rs index c3c82c297..7d32e0892 100644 --- a/node/tests/dns_round_trip_test.rs +++ b/node/tests/dns_round_trip_test.rs @@ -9,6 +9,8 @@ use trust_dns::op::{OpCode, ResponseCode}; use trust_dns::rr::{DNSClass, RecordType}; #[test] +// TODO This ignore should be lifted by GH-525 +#[ignore] #[serial(port53)] fn handles_two_consecutive_ipv4_dns_requests_integration() { let _node = utils::MASQNode::start_standard( @@ -25,6 +27,8 @@ fn handles_two_consecutive_ipv4_dns_requests_integration() { } #[test] +// TODO This ignore should be lifted by GH-525 +#[ignore] #[serial(port53)] fn handles_consecutive_heterogeneous_dns_requests_integration() { let _node = utils::MASQNode::start_standard( 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 } diff --git a/port_exposer/Cargo.lock b/port_exposer/Cargo.lock index 1f2db3a9d..210c0de54 100644 --- a/port_exposer/Cargo.lock +++ b/port_exposer/Cargo.lock @@ -20,7 +20,7 @@ checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" [[package]] name = "port_exposer" -version = "0.8.0" +version = "0.8.2" dependencies = [ "default-net", ] diff --git a/port_exposer/Cargo.toml b/port_exposer/Cargo.toml index 042deb104..a5eab68f0 100644 --- a/port_exposer/Cargo.toml +++ b/port_exposer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "port_exposer" -version = "0.8.0" +version = "0.8.2" authors = ["Dan Wiebe ", "MASQ"] license = "GPL-3.0-only" copyright = "Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved."