diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..54960b032 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,106 @@ +# Basic `dependabot.yml` file with +# minimum configuration for two package managers + +version: 2 +updates: + # Enable version updates for node + - package-ecosystem: "cargo" + directory: "/node" + # Check the cargo registry for updates every day (weekdays) + schedule: + interval: "daily" + time: '14:00' + timezone: 'Etc/UTC' + allow: + - dependency-name: "*" + dependency-type: "direct" + ignore: + - dependency-name: "automap" + - dependency-name: "masq_lib" + - dependency-name: "actix" + - dependency-name: "tokio" + - dependency-name: "clap" + - dependency-name: "sodiumoxide" + - dependency-name: "web3" + - dependency-name: "websocket" + groups: + patch: + update-types: + - "patch" + minor: + update-types: + - "minor" + target-branch: 'master' + open-pull-requests-limit: 2 + + # Enable version updates for masq + - package-ecosystem: "cargo" + directory: "/masq" + # Check the cargo registry for updates every day (weekdays) + schedule: + interval: "daily" + time: '14:00' + timezone: 'Etc/UTC' + allow: + - dependency-name: "*" + dependency-type: "direct" + ignore: + - dependency-name: "masq_lib" + - dependency-name: "clap" + - dependency-name: "websocket" + groups: + patch: + update-types: + - "patch" + minor: + update-types: + - "minor" + target-branch: 'master' + open-pull-requests-limit: 2 + + # Enable version updates for masq_lib + - package-ecosystem: "cargo" + directory: "/masq_lib" + # Check the cargo registry for updates every day (weekdays) + schedule: + interval: "daily" + time: '14:00' + timezone: 'Etc/UTC' + allow: + - dependency-name: "*" + dependency-type: "direct" + ignore: + - dependency-name: "clap" + - dependency-name: "websocket" + groups: + patch: + update-types: + - "patch" + minor: + update-types: + - "minor" + target-branch: 'master' + open-pull-requests-limit: 2 + + # Enable version updates for automap + - package-ecosystem: "cargo" + directory: "/automap" + # Check the cargo registry for updates every day (weekdays) + schedule: + interval: "daily" + time: '14:00' + timezone: 'Etc/UTC' + allow: + - dependency-name: "*" + dependency-type: "direct" + ignore: + - dependency-name: "masq_lib" + groups: + patch: + update-types: + - "patch" + minor: + update-types: + - "minor" + target-branch: 'master' + open-pull-requests-limit: 2 diff --git a/.github/workflows/ci-matrix.yml b/.github/workflows/ci-matrix.yml index 3b7c44c7a..193cc2107 100644 --- a/.github/workflows/ci-matrix.yml +++ b/.github/workflows/ci-matrix.yml @@ -4,7 +4,7 @@ on: pull_request: types: [opened, reopened, synchronize] branches: - - master + - GH-784 jobs: build: @@ -14,7 +14,7 @@ jobs: matrix: target: - { name: linux, os: ubuntu-22.04 } - - { name: macos, os: macos-14 } + - { name: macos, os: macos-13 } - { name: windows, os: windows-2022 } name: Build node on ${{ matrix.target.os }} @@ -23,6 +23,12 @@ jobs: - uses: actions/checkout@v3 with: ref: ${{ github.event.pull_request.head.sha }} + + - name: Print source branch name + run: | + echo "Source branch: ${{ github.head_ref }}" + shell: bash + - name: Stable with rustfmt and clippy uses: actions-rs/toolchain@v1 with: @@ -40,14 +46,16 @@ jobs: ~/.cargo/git/db/ target/ key: ${{ runner.os }}-cargo + - name: Build ${{ matrix.target.os }} run: | ./ci/all.sh ./ci/multinode_integration_test.sh ./ci/collect_results.sh shell: bash + - name: Publish ${{ matrix.target.os }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Node-${{ matrix.target.name }} path: results @@ -67,7 +75,8 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha }} - - uses: actions/download-artifact@v3 + - name: Download artifacts + uses: actions/download-artifact@v4 - name: Display structure of downloaded files run: ls -R @@ -82,7 +91,7 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_REGION: 'us-west-2' - DEST_DIR: 'Node/${{ github.event.workflow_run.head_branch }}/Node-${{ matrix.os }}' + DEST_DIR: 'Node/${{ github.head_ref }}/Node-${{ matrix.os }}' SOURCE_DIR: 'Node-${{ matrix.os }}/generated/bin/' - if: startsWith(github.head_ref, 'v') @@ -105,4 +114,4 @@ jobs: PATHS: "/Node*" AWS_REGION: "us-west-2" AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} \ No newline at end of file + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} diff --git a/.idea/modules.xml b/.idea/modules.xml index c32b128d9..861973a36 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -10,6 +10,7 @@ + \ No newline at end of file diff --git a/README.md b/README.md index f739378e5..84fb5ac44 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@ # MASQ Node ![masq-logo](images/masq-logo-sm.png) +
+ ![ci-matrix](https://github.com/MASQ-Project/Node/workflows/ci-matrix/badge.svg) +[![GitHub Release](https://img.shields.io/github/v/release/MASQ-Project/Node?display_name=release&color=green)](https://github.com/MASQ-Project/Node/releases/latest) +[![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/masq) + +
MASQ Node combines the benefits of VPN and Tor technology, to create superior next-generation privacy software, where users are rewarded for supporting an uncensored, global Web. Users gain privacy and anonymity, while helping promote @@ -33,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 @@ -43,7 +48,7 @@ A [Knowledge Base](https://docs.masq.ai/masq) and testing resources are being re levels of technical ability. There you can find further information, guides and configuration examples for running MASQ Node from: -- MASQ app - v0.2 (formerly called "GEMINI") +- [MASQ Browser](https://masqbrowser.com) - CLI - Docker image @@ -66,7 +71,7 @@ To help navigate the codebase, here are the README.md links for all documented c ### Downloading Official Releases -We haven't set up any official releases yet; but when we do, instructions will appear here. +Releases will appear on our GitHub page - click on the badge above for the latest stable beta build, or go to our [Releases page](https://github.com/MASQ-Project/Node/releases/latest) ### Downloading the Latest Build @@ -446,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/USER-INTERFACE-INTERFACE.md b/USER-INTERFACE-INTERFACE.md index c53e420cc..c7059929a 100644 --- a/USER-INTERFACE-INTERFACE.md +++ b/USER-INTERFACE-INTERFACE.md @@ -512,7 +512,7 @@ There are following three connection stages: 1. NotConnected: No external neighbor is connected to us. 2. ConnectedToNeighbor: External node(s) are connected to us. -3. ThreeHopsRouteFound: You can relay data over the network. +3. RouteFound: You can relay data over the network. The Node can only be on one of these connection stages during any moment of the Node's lifetime. @@ -1095,6 +1095,18 @@ even for parameters whose values are natively of other types. ##### Description: If the value of the respective parameter was successfully changed, this is a simple acknowledgment that the change is complete. +The following commands can be configured using the `setConfiguration`: + + +| Name | Parameter | Possible Values | +|------------------|-----------------|------------------| +| Gas Price | `--gas-price` | > 0 | +| Start Block | `--start-block` | > 0 | +| Min Hops | `--min-hops` | [1, 6] | + + +Note: The descriptions for the above commands can be found [here](#permitted-names). + #### `setup` ##### Direction: Request ##### Correspondent: Daemon @@ -1131,10 +1143,11 @@ be cleared. * `db-password` - Password to unlock the sensitive values in the database. * `dns-servers` - Comma-separated list of DNS servers to use. * `earning-wallet` - Wallet into which earnings should be deposited. -* `gas-price` - Transaction fee to offer on the blockchain. +* `gas-price` - The fee per unit of computational effort in blockchain transactions, measured in gwei. * `ip` - The public IP address of the Node. * `log-level` - The lowest level of logs that should be recorded. `off`, `error`, `warn`, `info`, `debug`, `trace` * `mapping-protocol` - The management protocol to try first with the router. `pcp`, `pmp`, `igdp` +* `min-hops`: The minimum number of hops required for the package to reach the Exit Node. * `neighborhood-mode` - `zero-hop`, `originate-only`, `consume-only`, `standard` * `neighbors` - Comma-separated list of Node descriptors for neighbors to contact on startup * `real-user` - Non-Windows platforms only, only where required: :: diff --git a/automap/Cargo.lock b/automap/Cargo.lock index c970dc200..e1f3984db 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", @@ -464,6 +464,27 @@ dependencies = [ "subtle 1.0.0", ] +[[package]] +name = "csv" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + [[package]] name = "cxx" version = "1.0.94" @@ -901,6 +922,15 @@ dependencies = [ "libc", ] +[[package]] +name = "ip_country" +version = "0.1.0" +dependencies = [ + "csv", + "itertools 0.13.0", + "lazy_static", +] + [[package]] name = "ipconfig" version = "0.1.9" @@ -923,6 +953,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.6" @@ -1051,7 +1090,7 @@ dependencies = [ [[package]] name = "masq_lib" -version = "0.8.0" +version = "0.8.2" dependencies = [ "actix", "clap", @@ -1059,7 +1098,8 @@ dependencies = [ "crossbeam-channel 0.5.8", "dirs", "ethereum-types", - "itertools", + "ip_country", + "itertools 0.10.5", "lazy_static", "log 0.4.17", "nix", @@ -1067,6 +1107,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", + "test_utilities", "time 0.3.20", "tiny-hderive", "toml", @@ -1927,6 +1968,10 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "test_utilities" +version = "0.1.0" + [[package]] name = "textwrap" version = "0.11.0" 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/ci/all.sh b/ci/all.sh index 2f381c45c..6591cd392 100755 --- a/ci/all.sh +++ b/ci/all.sh @@ -8,7 +8,7 @@ ci/format.sh export RUST_BACKTRACE=1 # Remove these two lines to slow down the build -which sccache || cargo install sccache || echo "Skipping sccache installation" # Should do significant work only once +which sccache || cargo install --version 0.4.1 sccache || echo "Skipping sccache installation" # Should do significant work only once #export CARGO_TARGET_DIR="$CI_DIR/../cargo-cache" export SCCACHE_DIR="$HOME/.cargo/sccache" #export RUSTC_WRAPPER="$HOME/.cargo/bin/sccache" diff --git a/dns_utility/Cargo.lock b/dns_utility/Cargo.lock index 52a3ec6b3..7c7d11ac5 100644 --- a/dns_utility/Cargo.lock +++ b/dns_utility/Cargo.lock @@ -399,6 +399,27 @@ dependencies = [ "subtle 1.0.0", ] +[[package]] +name = "csv" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + [[package]] name = "digest" version = "0.8.1" @@ -430,7 +451,7 @@ dependencies = [ [[package]] name = "dns_utility" -version = "0.8.0" +version = "0.8.2" dependencies = [ "core-foundation", "ipconfig 0.2.2", @@ -716,6 +737,15 @@ dependencies = [ "libc", ] +[[package]] +name = "ip_country" +version = "0.1.0" +dependencies = [ + "csv", + "itertools 0.13.0", + "lazy_static", +] + [[package]] name = "ipconfig" version = "0.1.9" @@ -750,6 +780,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.6" @@ -854,7 +893,7 @@ dependencies = [ [[package]] name = "masq_lib" -version = "0.8.0" +version = "0.8.2" dependencies = [ "actix", "clap", @@ -862,7 +901,8 @@ dependencies = [ "crossbeam-channel 0.5.8", "dirs", "ethereum-types", - "itertools", + "ip_country", + "itertools 0.10.5", "lazy_static", "log 0.4.17", "nix", @@ -870,6 +910,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", + "test_utilities", "time 0.3.20", "tiny-hderive", "toml", @@ -1653,6 +1694,10 @@ dependencies = [ "libc", ] +[[package]] +name = "test_utilities" +version = "0.1.0" + [[package]] name = "textwrap" version = "0.11.0" 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/ip_country/Cargo.toml b/ip_country/Cargo.toml index 7ba43fb60..0e57d7cac 100644 --- a/ip_country/Cargo.toml +++ b/ip_country/Cargo.toml @@ -13,8 +13,9 @@ workspace = "../node" csv = "1.3.0" itertools = "0.13.0" lazy_static = "1.4.0" -masq_lib = { path = "../masq_lib" } +[dev-dependencies] +test_utilities = { path = "../test_utilities"} [[bin]] name = "ip_country" diff --git a/ip_country/src/country_finder.rs b/ip_country/src/country_finder.rs index 270ecd76c..114c89421 100644 --- a/ip_country/src/country_finder.rs +++ b/ip_country/src/country_finder.rs @@ -54,9 +54,7 @@ impl CountryCodeFinder { #[cfg(test)] mod tests { use super::*; - use crate::country_block_serde::{ - CountryBlockDeserializer, Ipv4CountryBlockDeserializer, Ipv6CountryBlockDeserializer, - }; + use crate::country_block_serde::{Ipv4CountryBlockDeserializer, Ipv6CountryBlockDeserializer}; use crate::dbip_country; use std::str::FromStr; use std::time::SystemTime; diff --git a/ip_country/src/ip_country.rs b/ip_country/src/ip_country.rs index abf461aba..f4a7cbe13 100644 --- a/ip_country/src/ip_country.rs +++ b/ip_country/src/ip_country.rs @@ -133,8 +133,8 @@ fn write_value( #[cfg(test)] mod tests { use super::*; - use masq_lib::test_utils::fake_stream_holder::{ByteArrayReader, ByteArrayWriter}; use std::io::{Error, ErrorKind}; + use test_utilities::byte_array_reader_writer::{ByteArrayReader, ByteArrayWriter}; static PROPER_TEST_DATA: &str = "0.0.0.0,0.255.255.255,ZZ 1.0.0.0,1.0.0.255,AU diff --git a/masq/Cargo.toml b/masq/Cargo.toml index 87a20ad67..0a6484895 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" @@ -28,6 +28,7 @@ nix = "0.23.0" [dev-dependencies] atty = "0.2.14" +test_utilities = { path = "../test_utilities" } [lib] name = "masq_cli_lib" diff --git a/masq/src/command_context.rs b/masq/src/command_context.rs index 41308e5bb..e648da218 100644 --- a/masq/src/command_context.rs +++ b/masq/src/command_context.rs @@ -156,12 +156,12 @@ mod tests { use crate::test_utils::mocks::TRANSACT_TIMEOUT_MILLIS_FOR_TESTS; use masq_lib::messages::{FromMessageBody, UiCrashRequest, UiSetupRequest}; use masq_lib::messages::{ToMessageBody, UiShutdownRequest, UiShutdownResponse}; - use masq_lib::test_utils::fake_stream_holder::{ByteArrayReader, ByteArrayWriter}; use masq_lib::test_utils::mock_websockets_server::MockWebSocketsServer; use masq_lib::ui_gateway::MessageBody; use masq_lib::ui_gateway::MessagePath::Conversation; use masq_lib::ui_traffic_converter::{TrafficConversionError, UnmarshalError}; use masq_lib::utils::{find_free_port, running_test}; + use test_utilities::byte_array_reader_writer::{ByteArrayReader, ByteArrayWriter}; #[test] fn constant_has_correct_values() { diff --git a/masq/src/command_factory.rs b/masq/src/command_factory.rs index 21222c454..6ec658903 100644 --- a/masq/src/command_factory.rs +++ b/masq/src/command_factory.rs @@ -8,6 +8,7 @@ use crate::commands::configuration_command::ConfigurationCommand; use crate::commands::connection_status_command::ConnectionStatusCommand; use crate::commands::crash_command::CrashCommand; use crate::commands::descriptor_command::DescriptorCommand; +use crate::commands::exit_location_command::SetExitLocationCommand; use crate::commands::financials_command::FinancialsCommand; use crate::commands::generate_wallets_command::GenerateWalletsCommand; use crate::commands::recover_wallets_command::RecoverWalletsCommand; @@ -52,6 +53,10 @@ impl CommandFactory for CommandFactoryReal { Err(msg) => return Err(CommandSyntax(msg)), }, "descriptor" => Box::new(DescriptorCommand::new()), + "exit-location" => match SetExitLocationCommand::new(pieces) { + Ok(command) => Box::new(command), + Err(msg) => return Err(CommandSyntax(msg)), + }, "financials" => match FinancialsCommand::new(pieces) { Ok(command) => Box::new(command), Err(msg) => return Err(CommandSyntax(msg)), @@ -103,6 +108,7 @@ impl CommandFactoryReal { mod tests { use super::*; use crate::command_factory::CommandFactoryError::UnrecognizedSubcommand; + use masq_lib::messages::CountryCodes; #[test] fn complains_about_unrecognized_subcommand() { @@ -258,6 +264,34 @@ mod tests { ); } + #[test] + fn factory_produces_exit_location() { + let subject = CommandFactoryReal::new(); + + let command = subject + .make(&[ + "exit-location".to_string(), + "--country-codes".to_string(), + "CZ".to_string(), + ]) + .unwrap(); + + assert_eq!( + command + .as_any() + .downcast_ref::() + .unwrap(), + &SetExitLocationCommand { + exit_locations: vec![CountryCodes { + country_codes: vec!["CZ".to_string()], + priority: 1 + }], + fallback_routing: false, + show_countries: false, + } + ); + } + #[test] fn complains_about_set_configuration_command_with_no_parameters() { let subject = CommandFactoryReal::new(); diff --git a/masq/src/commands/configuration_command.rs b/masq/src/commands/configuration_command.rs index 8701f1617..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\ @@ -410,7 +413,7 @@ mod tests { blockchain_service_url_opt: Some("https://infura.io/ID".to_string()), current_schema_version: "schema version".to_string(), clandestine_port: 1234, - chain_name: "mumbai".to_string(), + chain_name: "amoy".to_string(), gas_price: 2345, max_block_count_opt: Some(100_000), neighborhood_mode: "zero-hop".to_string(), @@ -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, @@ -467,7 +470,7 @@ mod tests { "\ |NAME VALUE\n\ |Blockchain service URL: https://infura.io/ID\n\ -|Chain: mumbai\n\ +|Chain: amoy\n\ |Clandestine port: 1234\n\ |Consuming wallet private key: [?]\n\ |Current schema version: schema version\n\ @@ -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/exit_location_command.rs b/masq/src/commands/exit_location_command.rs new file mode 100644 index 000000000..552bab768 --- /dev/null +++ b/masq/src/commands/exit_location_command.rs @@ -0,0 +1,274 @@ +use crate::command_context::CommandContext; +use crate::commands::commands_common::{ + transaction, Command, CommandError, STANDARD_COMMAND_TIMEOUT_MILLIS, +}; +use clap::{App, Arg, SubCommand}; +use masq_lib::as_any_ref_in_trait_impl; +use masq_lib::messages::{CountryCodes, UiSetExitLocationRequest, UiSetExitLocationResponse}; +use masq_lib::shared_schema::common_validators; + +const EXIT_LOCATION_ABOUT: &str = + "If you activate exit-location preferences, all exit Nodes in countries you don't specify will be prohibited: \n\ + that is, if there is no exit Node available in any of your preferred countries, you'll get an error. However, \ + if you just want to make a suggestion, and you don't mind Nodes in other countries being used if nothing is available \ + in your preferred countries, you can specify --fallback-routing, and you'll get no error unless there are no exit Nodes \ + available anywhere.\n\n\ + Here are some example commands:\n\ + masq> exit-location // disable exit-location preferences\n\ + masq> exit-location --fallback-routing // disable exit-location preferences\n\ + masq> exit-location --country-codes \"CZ,PL|SK\" --fallback-routing \n\t// fallback-routing is ON, \"CZ\" and \"PL\" countries have same priority \"1\", \"SK\" has priority \"2\"\n\ + masq> exit-location --country-codes \"CZ|SK\" \n\t// fallback-routing is OFF, \"CZ\" and \"SK\" countries have different priority\n"; + +// TODO update following help when GH-469 is done with `To obtain codes, you can use the 'country-codes-list' (469 card command) command.` +const COUNTRY_CODES_HELP: &str = "Establish a set of countries that your Node should try to use for exit Nodes. You should choose from the countries that host the \ + Nodes in your Neighborhood. List the countries in order of preference, separated by vertical pipes (|). If your level of preference \ + for a group of countries is the same, separate those countries by commas (,).\n\ + You can specify country codes as follows:\n\n\ + masq> exit-location --country-codes \"CZ,PL|SK\" \n\t// \"CZ\" and \"PL\" countries have same priority \"1\", \"SK\" has priority \"2\" \n\ + masq> exit-location --country-codes \"CZ|SK\" \n\t// \"CZ\" and \"SK\" countries have different priority\n\n"; + +const FALLBACK_ROUTING_HELP: &str = "If you just want to make a suggestion, and you don't mind Nodes in other countries being used if nothing is available \ + in your preferred countries, you can specify --fallback-routing, and you'll get no error unless there are no exit Nodes \ + available anywhere. \n Here are some examples: \n\n\ + masq> exit-location --country-codes \"CZ\" --fallback-routing \n\t// Set exit-location for \"CZ\" country with fallback-routing on \n\ + masq> exit-location --country-codes \"CZ\" \n\t// Set exit-location for \"CZ\" country with fallback-routing off \n\n"; + +pub fn exit_location_subcommand() -> App<'static, 'static> { + SubCommand::with_name("exit-location").about(EXIT_LOCATION_ABOUT) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct SetExitLocationCommand { + pub exit_locations: Vec, + pub fallback_routing: bool, + pub show_countries: bool, +} + +impl SetExitLocationCommand { + pub fn new(pieces: &[String]) -> Result { + match set_exit_location_subcommand().get_matches_from_safe(pieces) { + Ok(matches) => { + let exit_locations = match matches.is_present("country-codes") { + true => matches + .values_of("country-codes") + .expect("Expected Country Codes") + .into_iter() + .enumerate() + .map(|(index, code)| CountryCodes::from((code.to_string(), index))) + .collect(), + false => vec![], + }; + let fallback_routing = !matches!( + ( + matches.is_present("fallback-routing"), + matches.is_present("country-codes") + ), + (false, true) + ); + let show_countries = matches.is_present("show-countries"); + Ok(SetExitLocationCommand { + exit_locations, + fallback_routing, + show_countries, + }) + } + + Err(e) => Err(format!("SetExitLocationCommand {}", e)), + } + } +} + +impl Command for SetExitLocationCommand { + fn execute(&self, context: &mut dyn CommandContext) -> Result<(), CommandError> { + let input = UiSetExitLocationRequest { + exit_locations: self.exit_locations.clone(), + fallback_routing: self.fallback_routing, + show_countries: self.show_countries, + }; + + let _: UiSetExitLocationResponse = + transaction(input, context, STANDARD_COMMAND_TIMEOUT_MILLIS)?; + Ok(()) + } + + as_any_ref_in_trait_impl!(); +} + +pub fn set_exit_location_subcommand() -> App<'static, 'static> { + SubCommand::with_name("exit-location") + .about(EXIT_LOCATION_ABOUT) + .arg( + Arg::with_name("country-codes") + .long("country-codes") + .value_name("COUNTRY-CODES") + .value_delimiter("|") + .validator(common_validators::validate_exit_locations) + .help(COUNTRY_CODES_HELP) + .required(false), + ) + .arg( + Arg::with_name("fallback-routing") + .long("fallback-routing") + .value_name("FALLBACK-ROUTING") + .help(FALLBACK_ROUTING_HELP) + .takes_value(false) + .required(false), + ) + .arg( + Arg::with_name("show-countries") + .long("show-countries") + .value_name("SHOW-COUNTRIES") + .takes_value(false) + .required(false), + ) +} + +#[cfg(test)] +pub mod tests { + use crate::commands::commands_common::{Command, STANDARD_COMMAND_TIMEOUT_MILLIS}; + use crate::commands::exit_location_command::SetExitLocationCommand; + use crate::test_utils::mocks::CommandContextMock; + use masq_lib::messages::{ + CountryCodes, ToMessageBody, UiSetExitLocationRequest, UiSetExitLocationResponse, + }; + use std::sync::{Arc, Mutex}; + + #[test] + fn can_deserialize_ui_set_exit_location() { + let transact_params_arc = Arc::new(Mutex::new(vec![])); + let mut context = CommandContextMock::new() + .transact_params(&transact_params_arc) + .transact_result(Ok(UiSetExitLocationResponse {}.tmb(0))); + let stderr_arc = context.stderr_arc(); + let subject = SetExitLocationCommand::new(&[ + "exit-location".to_string(), + "--country-codes".to_string(), + "CZ,SK|AT,DE|PL".to_string(), + "--fallback-routing".to_string(), + ]) + .unwrap(); + + let result = subject.execute(&mut context); + + assert_eq!(result, Ok(())); + let expected_request = UiSetExitLocationRequest { + fallback_routing: true, + exit_locations: vec![ + CountryCodes { + country_codes: vec!["CZ".to_string(), "SK".to_string()], + priority: 1, + }, + CountryCodes { + country_codes: vec!["AT".to_string(), "DE".to_string()], + priority: 2, + }, + CountryCodes { + country_codes: vec!["PL".to_string()], + priority: 3, + }, + ], + show_countries: false, + }; + let transact_params = transact_params_arc.lock().unwrap(); + let expected_message_body = expected_request.tmb(0); + assert_eq!( + transact_params.as_slice(), + &[(expected_message_body, STANDARD_COMMAND_TIMEOUT_MILLIS)] + ); + let stderr = stderr_arc.lock().unwrap(); + assert_eq!(&stderr.get_string(), ""); + } + + #[test] + fn absence_of_fallback_routing_produces_fallback_routing_false() { + let transact_params_arc = Arc::new(Mutex::new(vec![])); + let mut context = CommandContextMock::new() + .transact_params(&transact_params_arc) + .transact_result(Ok(UiSetExitLocationResponse {}.tmb(0))); + let stderr_arc = context.stderr_arc(); + let subject = SetExitLocationCommand::new(&[ + "exit-location".to_string(), + "--country-codes".to_string(), + "CZ".to_string(), + ]) + .unwrap(); + + let result = subject.execute(&mut context); + + assert_eq!(result, Ok(())); + let expected_request = UiSetExitLocationRequest { + fallback_routing: false, + exit_locations: vec![CountryCodes { + country_codes: vec!["CZ".to_string()], + priority: 1, + }], + show_countries: false, + }; + let transact_params = transact_params_arc.lock().unwrap(); + let expected_message_body = expected_request.tmb(0); + assert_eq!( + transact_params.as_slice(), + &[(expected_message_body, STANDARD_COMMAND_TIMEOUT_MILLIS)] + ); + let stderr = stderr_arc.lock().unwrap(); + assert_eq!(&stderr.get_string(), ""); + } + + #[test] + fn providing_no_arguments_cause_exit_location_reset_request() { + let transact_params_arc = Arc::new(Mutex::new(vec![])); + let mut context = CommandContextMock::new() + .transact_params(&transact_params_arc) + .transact_result(Ok(UiSetExitLocationResponse {}.tmb(0))); + let stderr_arc = context.stderr_arc(); + let subject = SetExitLocationCommand::new(&["exit-location".to_string()]).unwrap(); + + let result = subject.execute(&mut context); + + assert_eq!(result, Ok(())); + let expected_request = UiSetExitLocationRequest { + fallback_routing: true, + exit_locations: vec![], + show_countries: false, + }; + let transact_params = transact_params_arc.lock().unwrap(); + let expected_message_body = expected_request.tmb(0); + assert_eq!( + transact_params.as_slice(), + &[(expected_message_body, STANDARD_COMMAND_TIMEOUT_MILLIS)] + ); + let stderr = stderr_arc.lock().unwrap(); + assert_eq!(&stderr.get_string(), ""); + } + + #[test] + fn providing_show_countries_cause_request_for_list_of_countries() { + let transact_params_arc = Arc::new(Mutex::new(vec![])); + let mut context = CommandContextMock::new() + .transact_params(&transact_params_arc) + .transact_result(Ok(UiSetExitLocationResponse {}.tmb(0))); + let stderr_arc = context.stderr_arc(); + let subject = SetExitLocationCommand::new(&[ + "exit-location".to_string(), + "--show-countries".to_string(), + ]) + .unwrap(); + + let result = subject.execute(&mut context); + + assert_eq!(result, Ok(())); + let expected_request = UiSetExitLocationRequest { + fallback_routing: true, + exit_locations: vec![], + show_countries: true, + }; + let transact_params = transact_params_arc.lock().unwrap(); + let expected_message_body = expected_request.tmb(0); + assert_eq!( + transact_params.as_slice(), + &[(expected_message_body, STANDARD_COMMAND_TIMEOUT_MILLIS)] + ); + let stderr = stderr_arc.lock().unwrap(); + assert_eq!(&stderr.get_string(), ""); + } +} diff --git a/masq/src/commands/mod.rs b/masq/src/commands/mod.rs index 44026ebf8..143dff8e1 100644 --- a/masq/src/commands/mod.rs +++ b/masq/src/commands/mod.rs @@ -7,6 +7,7 @@ pub mod configuration_command; pub mod connection_status_command; pub mod crash_command; pub mod descriptor_command; +pub mod exit_location_command; pub mod financials_command; pub mod generate_wallets_command; pub mod recover_wallets_command; diff --git a/masq/src/commands/set_configuration_command.rs b/masq/src/commands/set_configuration_command.rs index 99d979f07..385a48bc9 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) @@ -84,6 +93,8 @@ pub fn set_configuration_subcommand() -> App<'static, 'static> { .args(&["gas-price", "min-hops", "start-block"]) .required(true), ) + //TODO here is the place we want to place function to set country_code for ExitService, + // this function will be used in shared_app to setup the country_code for ExitService on startup } #[cfg(test)] @@ -103,7 +114,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 +133,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/src/commands/setup_command.rs b/masq/src/commands/setup_command.rs index 92f605247..eaeede3c8 100644 --- a/masq/src/commands/setup_command.rs +++ b/masq/src/commands/setup_command.rs @@ -391,10 +391,10 @@ ip No sir, I don't like it.\n\ status_data_dir: UiSetupResponseValueStatus::Default, }, SetupCommandData { - chain_str: Some("polygon-mumbai".to_owned()), + chain_str: Some("polygon-amoy".to_owned()), data_directory: None, - chain_name_expected: Some("polygon-mumbai"), - data_directory_expected: Some("/home/cooga/.local/MASQ/polygon-mumbai"), + chain_name_expected: Some("polygon-amoy"), + data_directory_expected: Some("/home/cooga/.local/MASQ/polygon-amoy"), note_expected: true, status_chain: UiSetupResponseValueStatus::Set, status_data_dir: UiSetupResponseValueStatus::Default, @@ -409,10 +409,10 @@ ip No sir, I don't like it.\n\ status_data_dir: UiSetupResponseValueStatus::Set, }, SetupCommandData { - chain_str: Some("polygon-mumbai".to_owned()), - data_directory: Some("booga/polygon-mumbai".to_owned()), - chain_name_expected: Some("polygon-mumbai"), - data_directory_expected: Some("booga/polygon-mumbai/polygon-mumbai"), + chain_str: Some("polygon-amoy".to_owned()), + data_directory: Some("booga/polygon-amoy".to_owned()), + chain_name_expected: Some("polygon-amoy"), + data_directory_expected: Some("booga/polygon-amoy/polygon-amoy"), note_expected: true, status_chain: UiSetupResponseValueStatus::Set, status_data_dir: UiSetupResponseValueStatus::Set, diff --git a/masq/src/interactive_mode.rs b/masq/src/interactive_mode.rs index a9141ffa5..026ed4aef 100644 --- a/masq/src/interactive_mode.rs +++ b/masq/src/interactive_mode.rs @@ -168,10 +168,11 @@ mod tests { CommandFactoryMock, CommandProcessorMock, TerminalActiveMock, TerminalPassiveMock, }; use crossbeam_channel::bounded; - use masq_lib::test_utils::fake_stream_holder::{ByteArrayWriter, FakeStreamHolder}; + use masq_lib::test_utils::fake_stream_holder::FakeStreamHolder; use std::sync::{Arc, Mutex}; use std::thread; use std::time::{Duration, Instant}; + use test_utilities::byte_array_reader_writer::ByteArrayWriter; #[test] fn interactive_mode_works_for_unrecognized_command() { diff --git a/masq/src/notifications/connection_change_notification.rs b/masq/src/notifications/connection_change_notification.rs index 9ea7d5911..0480660fb 100644 --- a/masq/src/notifications/connection_change_notification.rs +++ b/masq/src/notifications/connection_change_notification.rs @@ -34,9 +34,9 @@ impl ConnectionChangeNotification { mod tests { use super::*; use crate::test_utils::mocks::TerminalPassiveMock; - use masq_lib::test_utils::fake_stream_holder::ByteArrayWriter; use masq_lib::utils::running_test; use std::sync::Arc; + use test_utilities::byte_array_reader_writer::ByteArrayWriter; #[test] fn broadcasts_connected_to_neighbor() { diff --git a/masq/src/notifications/crashed_notification.rs b/masq/src/notifications/crashed_notification.rs index 36e0ff4f4..66dfc773c 100644 --- a/masq/src/notifications/crashed_notification.rs +++ b/masq/src/notifications/crashed_notification.rs @@ -63,9 +63,9 @@ impl CrashNotifier { mod tests { use super::*; use crate::test_utils::mocks::TerminalPassiveMock; - use masq_lib::test_utils::fake_stream_holder::ByteArrayWriter; use masq_lib::utils::running_test; use std::sync::Arc; + use test_utilities::byte_array_reader_writer::ByteArrayWriter; #[test] pub fn handles_child_wait_failure() { diff --git a/masq/src/schema.rs b/masq/src/schema.rs index c03a3ea39..287fd9468 100644 --- a/masq/src/schema.rs +++ b/masq/src/schema.rs @@ -8,6 +8,7 @@ use crate::commands::configuration_command::configuration_subcommand; use crate::commands::connection_status_command::connection_status_subcommand; use crate::commands::crash_command::crash_subcommand; use crate::commands::descriptor_command::descriptor_subcommand; +use crate::commands::exit_location_command::exit_location_subcommand; use crate::commands::financials_command::args_validation::financials_subcommand; use crate::commands::generate_wallets_command::generate_wallets_subcommand; use crate::commands::recover_wallets_command::recover_wallets_subcommand; @@ -67,6 +68,7 @@ pub fn app() -> App<'static, 'static> { .subcommand(configuration_subcommand()) .subcommand(connection_status_subcommand()) .subcommand(descriptor_subcommand()) + .subcommand(exit_location_subcommand()) .subcommand(financials_subcommand()) .subcommand(generate_wallets_subcommand()) .subcommand(recover_wallets_subcommand()) diff --git a/masq/src/terminal/integration_test_utils.rs b/masq/src/terminal/integration_test_utils.rs index 7f42f72f5..526e19a66 100644 --- a/masq/src/terminal/integration_test_utils.rs +++ b/masq/src/terminal/integration_test_utils.rs @@ -187,9 +187,9 @@ mod tests { use crate::terminal::terminal_interface::TerminalWrapper; use crate::test_utils::mocks::StdoutBlender; use crossbeam_channel::{bounded, unbounded}; - use masq_lib::test_utils::fake_stream_holder::ByteArrayReader; use std::thread; use std::time::Duration; + use test_utilities::byte_array_reader_writer::ByteArrayReader; #[test] fn constants_have_correct_values() { diff --git a/masq/src/test_utils/mocks.rs b/masq/src/test_utils/mocks.rs index 955fa578a..9f70b7e5b 100644 --- a/masq/src/test_utils/mocks.rs +++ b/masq/src/test_utils/mocks.rs @@ -15,7 +15,6 @@ use linefeed::memory::MemoryTerminal; use linefeed::{Interface, ReadResult, Signal}; use masq_lib::command::StdStreams; use masq_lib::constants::DEFAULT_UI_PORT; -use masq_lib::test_utils::fake_stream_holder::{ByteArrayWriter, ByteArrayWriterInner}; use masq_lib::ui_gateway::MessageBody; use std::cell::RefCell; use std::fmt::Arguments; @@ -23,6 +22,7 @@ use std::io::{Read, Write}; use std::sync::{Arc, Mutex}; use std::time::Duration; use std::{io, thread}; +use test_utilities::byte_array_reader_writer::{ByteArrayWriter, ByteArrayWriterInner}; pub const TRANSACT_TIMEOUT_MILLIS_FOR_TESTS: u64 = DEFAULT_TRANSACT_TIMEOUT_MILLIS; diff --git a/masq_lib/Cargo.toml b/masq_lib/Cargo.toml index f87de81a1..e845574a8 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" @@ -15,12 +15,14 @@ crossbeam-channel = "0.5.1" dirs = "4.0.0" ethereum-types = "0.9.0" itertools = "0.10.1" +ip_country = { path = "../ip_country"} lazy_static = "1.4.0" log = "0.4.8" regex = "1.5.4" serde = "1.0.133" serde_derive = "1.0.133" serde_json = "1.0.74" +test_utilities = { path = "../test_utilities"} time = {version = "0.3.11", features = [ "formatting" ]} tiny-hderive = "0.3.0" toml = "0.5.8" diff --git a/masq_lib/src/blockchains/blockchain_records.rs b/masq_lib/src/blockchains/blockchain_records.rs index f9b4bcab0..cc1198afa 100644 --- a/masq_lib/src/blockchains/blockchain_records.rs +++ b/masq_lib/src/blockchains/blockchain_records.rs @@ -2,16 +2,17 @@ use crate::blockchains::chains::Chain; use crate::constants::{ - DEV_CHAIN_FULL_IDENTIFIER, ETH_MAINNET_CONTRACT_CREATION_BLOCK, ETH_MAINNET_FULL_IDENTIFIER, - ETH_ROPSTEN_FULL_IDENTIFIER, MULTINODE_TESTNET_CONTRACT_CREATION_BLOCK, - MUMBAI_TESTNET_CONTRACT_CREATION_BLOCK, POLYGON_MAINNET_CONTRACT_CREATION_BLOCK, - POLYGON_MAINNET_FULL_IDENTIFIER, POLYGON_MUMBAI_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}; -//chains are ordered by their significance for the community of users (the order reflects in some error or help messages) -pub const CHAINS: [BlockchainRecord; 5] = [ +pub const CHAINS: [BlockchainRecord; 7] = [ BlockchainRecord { self_id: Chain::PolyMainnet, num_chain_id: 137, @@ -27,18 +28,32 @@ pub const CHAINS: [BlockchainRecord; 5] = [ contract_creation_block: ETH_MAINNET_CONTRACT_CREATION_BLOCK, }, BlockchainRecord { - self_id: Chain::PolyMumbai, - num_chain_id: 80001, - literal_identifier: POLYGON_MUMBAI_FULL_IDENTIFIER, - contract: MUMBAI_TESTNET_CONTRACT_ADDRESS, - contract_creation_block: MUMBAI_TESTNET_CONTRACT_CREATION_BLOCK, + 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: 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, @@ -58,12 +73,28 @@ pub struct BlockchainRecord { pub contract_creation_block: u64, } +// $tMASQ (Amoy) +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, @@ -80,20 +111,11 @@ const POLYGON_MAINNET_CONTRACT_ADDRESS: Address = H160([ 0xfB, 0xe9, 0xDd, 0x35, ]); -// $tMASQ (Mumbai) -#[allow(clippy::mixed_case_hex_literals)] -const MUMBAI_TESTNET_CONTRACT_ADDRESS: Address = H160([ - 0x9B, 0x27, 0x03, 0x4a, 0xca, 0xBd, 0x44, 0x22, 0x3f, 0xB2, 0x3d, 0x62, 0x8B, 0xa4, 0x84, 0x98, - 0x67, 0xcE, 0x1D, 0xB2, -]); - #[cfg(test)] mod tests { use super::*; use crate::blockchains::chains::chain_from_chain_identifier_opt; - use crate::constants::{ - MUMBAI_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; @@ -101,10 +123,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::PolyMumbai, 80001), + 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,9 +142,11 @@ mod tests { fn from_str_works() { let test_array = [ assert_from_str(Chain::PolyMainnet), - assert_from_str(Chain::PolyMumbai), + 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) @@ -140,18 +166,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::PolyMumbai), - 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] @@ -180,8 +211,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, } ); } @@ -203,17 +234,49 @@ mod tests { } #[test] - fn mumbai_record_is_properly_declared() { - let examined_chain = Chain::PolyMumbai; + fn amoy_record_is_properly_declared() { + let examined_chain = Chain::PolyAmoy; + let chain_record = return_examined(examined_chain); + assert_eq!( + chain_record, + &BlockchainRecord { + num_chain_id: 80002, + self_id: examined_chain, + literal_identifier: "polygon-amoy", + 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: 80001, + num_chain_id: 8453, self_id: examined_chain, - literal_identifier: "polygon-mumbai", - contract: MUMBAI_TESTNET_CONTRACT_ADDRESS, - contract_creation_block: MUMBAI_TESTNET_CONTRACT_CREATION_BLOCK, + 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, } ); } @@ -229,7 +292,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, } ); } @@ -243,9 +306,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-mumbai", Some(Chain::PolyMumbai)), + 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 ac3fbbac0..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_MAINNET_FULL_IDENTIFIER, POLYGON_MUMBAI_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}; @@ -12,7 +13,9 @@ pub enum Chain { EthMainnet, EthRopsten, PolyMainnet, - PolyMumbai, + PolyAmoy, + BaseMainnet, + BaseSepolia, Dev, } @@ -28,8 +31,12 @@ impl From<&str> for Chain { Chain::PolyMainnet } else if str == ETH_MAINNET_FULL_IDENTIFIER { Chain::EthMainnet - } else if str == POLYGON_MUMBAI_FULL_IDENTIFIER { - Chain::PolyMumbai + } 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 { Chain::EthRopsten } else if str == DEV_CHAIN_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 7bdcbe1e1..03b3a4c88 100644 --- a/masq_lib/src/constants.rs +++ b/masq_lib/src/constants.rs @@ -5,7 +5,7 @@ use crate::data_version::DataVersion; use const_format::concatcp; pub const DEFAULT_CHAIN: Chain = Chain::PolyMainnet; -pub const CURRENT_SCHEMA_VERSION: usize = 9; +pub const CURRENT_SCHEMA_VERSION: usize = 10; pub const HIGHEST_RANDOM_CLANDESTINE_PORT: u16 = 9999; pub const HTTP_PORT: u16 = 80; @@ -24,11 +24,15 @@ pub const WALLET_ADDRESS_LENGTH: usize = 42; pub const MASQ_TOTAL_SUPPLY: u64 = 37_500_000; pub const WEIS_IN_GWEI: i128 = 1_000_000_000; +pub const DEFAULT_MAX_BLOCK_COUNT: u64 = 100_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 MULTINODE_TESTNET_CONTRACT_CREATION_BLOCK: u64 = 0; +pub const ETH_ROPSTEN_CONTRACT_CREATION_BLOCK: u64 = 8_688_171; pub const POLYGON_MAINNET_CONTRACT_CREATION_BLOCK: u64 = 14_863_650; -pub const MUMBAI_TESTNET_CONTRACT_CREATION_BLOCK: u64 = 24_638_838; +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 //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -71,6 +75,7 @@ pub const UNMARSHAL_ERROR: u64 = UI_NODE_COMMUNICATION_PREFIX | 4; pub const SETUP_ERROR: u64 = UI_NODE_COMMUNICATION_PREFIX | 5; pub const TIMEOUT_ERROR: u64 = UI_NODE_COMMUNICATION_PREFIX | 6; pub const SCAN_ERROR: u64 = UI_NODE_COMMUNICATION_PREFIX | 7; +pub const EXIT_COUNTRY_ERROR: u64 = UI_NODE_COMMUNICATION_PREFIX | 8; //accountant pub const ACCOUNTANT_PREFIX: u64 = 0x0040_0000_0000_0000; @@ -87,15 +92,18 @@ pub const CENTRAL_DELIMITER: char = '@'; pub const CHAIN_IDENTIFIER_DELIMITER: char = ':'; //chains -const MAINNET: &str = "mainnet"; 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_MUMBAI_FULL_IDENTIFIER: &str = concatcp!(POLYGON_FAMILY, LINK, "mumbai"); -pub const DEV_CHAIN_FULL_IDENTIFIER: &str = "dev"; +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)] mod tests { @@ -118,10 +126,12 @@ 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!(MULTINODE_TESTNET_CONTRACT_CREATION_BLOCK, 0); + assert_eq!(ETH_ROPSTEN_CONTRACT_CREATION_BLOCK, 8_688_171); assert_eq!(POLYGON_MAINNET_CONTRACT_CREATION_BLOCK, 14_863_650); - assert_eq!(MUMBAI_TESTNET_CONTRACT_CREATION_BLOCK, 24_638_838); + 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); assert_eq!(CONFIGURATOR_WRITE_ERROR, CONFIGURATOR_PREFIX | 2); @@ -157,15 +167,17 @@ mod tests { assert_eq!(VALUE_EXCEEDS_ALLOWED_LIMIT, ACCOUNTANT_PREFIX | 3); assert_eq!(CENTRAL_DELIMITER, '@'); assert_eq!(CHAIN_IDENTIFIER_DELIMITER, ':'); - assert_eq!(MAINNET, "mainnet"); 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_MUMBAI_FULL_IDENTIFIER, "polygon-mumbai"); - assert_eq!(DEV_CHAIN_FULL_IDENTIFIER, "dev"); + 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, DataVersion { major: 0, minor: 1 } diff --git a/masq_lib/src/messages.rs b/masq_lib/src/messages.rs index 59522171e..5821d62a3 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 @@ -846,6 +846,44 @@ pub struct UiWalletAddressesResponse { } conversation_message!(UiWalletAddressesResponse, "walletAddresses"); +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct CountryCodes { + #[serde(rename = "countryCodes")] + pub country_codes: Vec, + #[serde(rename = "priority")] + pub priority: usize, +} + +impl From<(String, usize)> for CountryCodes { + fn from((item, priority): (String, usize)) -> Self { + CountryCodes { + country_codes: item + .split(',') + .into_iter() + .map(|x| x.to_string()) + .collect::>(), + priority: priority + 1, + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct UiSetExitLocationRequest { + #[serde(rename = "fallbackRouting")] + pub fallback_routing: bool, + #[serde(rename = "exitLocations")] + pub exit_locations: Vec, + #[serde(rename = "showCountries")] + pub show_countries: bool, +} + +conversation_message!(UiSetExitLocationRequest, "exitLocation"); + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct UiSetExitLocationResponse {} + +conversation_message!(UiSetExitLocationResponse, "exitLocation"); + #[cfg(test)] mod tests { use super::*; diff --git a/masq_lib/src/shared_schema.rs b/masq_lib/src/shared_schema.rs index fc7e8ced2..9bd948e8d 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_MAINNET_FULL_IDENTIFIER, POLYGON_MUMBAI_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://polygon-mumbai:A6PGHT3rRjaeFpD_rFi3qGEXAVPq7bJDfEUZpZaIyq8@14.10.50.6:10504\n\ - masq://eth-ropsten:OHsC2CAm4rmfCkaFfiynwxflUgVTJRb2oY5mWxNCQkY@150.60.42.72:6642/4789/5254\n\n\ + masq://base-mainnet:ZjPLnb9RrgsRM1D9edqH8jx9DkbPZSWqqFqLnmdKhsk@112.55.78.0:7878\n\ + masq://polygon-amoy:A6PGHT3rRjaeFpD_rFi3qGEXAVPq7bJDfEUZpZaIyq8@14.10.50.6:10504\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,7 +259,9 @@ pub fn official_chain_names() -> &'static [&'static str] { &[ POLYGON_MAINNET_FULL_IDENTIFIER, ETH_MAINNET_FULL_IDENTIFIER, - POLYGON_MUMBAI_FULL_IDENTIFIER, + BASE_MAINNET_FULL_IDENTIFIER, + BASE_SEPOLIA_FULL_IDENTIFIER, + POLYGON_AMOY_FULL_IDENTIFIER, ETH_ROPSTEN_FULL_IDENTIFIER, DEV_CHAIN_FULL_IDENTIFIER, ] @@ -388,7 +393,6 @@ pub fn shared_app(head: App<'static, 'static>) -> App<'static, 'static> { .case_insensitive(true) .hidden(true), ) - .arg(data_directory_arg(DATA_DIRECTORY_HELP)) .arg(db_password_arg(DB_PASSWORD_HELP)) .arg( Arg::with_name("dns-servers") @@ -484,6 +488,7 @@ pub fn shared_app(head: App<'static, 'static>) -> App<'static, 'static> { pub mod common_validators { use crate::constants::LOWEST_USABLE_INSECURE_PORT; + use ip_country_lib::countries::INDEX_BY_ISO3166; use regex::Regex; use std::net::IpAddr; use std::str::FromStr; @@ -520,6 +525,44 @@ pub mod common_validators { } } + pub fn validate_country_code(country_code: &str) -> Result<(), String> { + match INDEX_BY_ISO3166.contains_key(country_code) { + true => Ok(()), + false => Err(format!( + "'{}' is not a valid ISO3166 country code", + country_code + )), + } + } + + pub fn validate_exit_locations(exit_location: String) -> Result<(), String> { + validate_pipe_separated_values(exit_location, |country: String| { + let mut collect_fails = "".to_string(); + country.split(',').into_iter().for_each(|country_code| { + match validate_country_code(country_code) { + Ok(_) => (), + Err(e) => collect_fails.push_str(&e), + } + }); + match collect_fails.is_empty() { + true => Ok(()), + false => Err(collect_fails), + } + }) + } + + pub fn validate_separate_u64_values(values: String) -> Result<(), String> { + validate_pipe_separated_values(values, |segment: String| { + segment + .parse::() + .map_err(|_| { + "Supply nonnegative numeric values separated by vertical bars like 111|222|333|..." + .to_string() + }) + .map(|_| ()) + }) + } + pub fn validate_private_key(key: String) -> Result<(), String> { if Regex::new("^[0-9a-fA-F]{64}$") .expect("Failed to compile regular expression") @@ -606,16 +649,24 @@ pub mod common_validators { } } - pub fn validate_separate_u64_values(values_with_delimiters: String) -> Result<(), String> { - values_with_delimiters.split('|').try_for_each(|segment| { - segment - .parse::() - .map_err(|_| { - "Supply positive numeric values separated by vertical bars like 111|222|333|..." - .to_string() - }) - .map(|_| ()) - }) + fn validate_pipe_separated_values( + values_with_delimiters: String, + closure: fn(String) -> Result<(), String>, + ) -> Result<(), String> { + let mut error_collection = vec![]; + values_with_delimiters + .split('|') + .into_iter() + .for_each(|segment| { + match closure(segment.to_string()) { + Ok(_) => (), + Err(msg) => error_collection.push(msg), + }; + }); + match error_collection.is_empty() { + true => Ok(()), + false => Err(error_collection.into_iter().collect::()), + } } } @@ -670,11 +721,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 +734,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 +809,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://polygon-mumbai:A6PGHT3rRjaeFpD_rFi3qGEXAVPq7bJDfEUZpZaIyq8@14.10.50.6:10504\n\ - masq://eth-ropsten:OHsC2CAm4rmfCkaFfiynwxflUgVTJRb2oY5mWxNCQkY@150.60.42.72:6642/4789/5254\n\n\ + masq://base-mainnet:ZjPLnb9RrgsRM1D9edqH8jx9DkbPZSWqqFqLnmdKhsk@112.55.78.0:7878\n\ + masq://polygon-amoy:A6PGHT3rRjaeFpD_rFi3qGEXAVPq7bJDfEUZpZaIyq8@14.10.50.6:10504\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\ @@ -925,6 +978,23 @@ mod tests { ) } + #[test] + fn validate_exit_key_fails_on_not_valid_country_code() { + let result = common_validators::validate_exit_locations(String::from("CZ|SK,RR")); + + assert_eq!( + result, + Err("'RR' is not a valid ISO3166 country code".to_string()) + ); + } + + #[test] + fn validate_exit_key_success() { + let result = common_validators::validate_exit_locations(String::from("CZ|SK")); + + assert_eq!(result, Ok(())); + } + #[test] fn validate_private_key_requires_a_key_that_is_64_characters_long() { let result = common_validators::validate_private_key(String::from("42")); @@ -1075,7 +1145,7 @@ mod tests { assert_eq!( result, Err(String::from( - "Supply positive numeric values separated by vertical bars like 111|222|333|..." + "Supply nonnegative numeric values separated by vertical bars like 111|222|333|..." )) ) } @@ -1087,7 +1157,7 @@ mod tests { assert_eq!( result, Err(String::from( - "Supply positive numeric values separated by vertical bars like 111|222|333|..." + "Supply nonnegative numeric values separated by vertical bars like 111|222|333|..." )) ) } @@ -1099,7 +1169,7 @@ mod tests { assert_eq!( result, Err(String::from( - "Supply positive numeric values separated by vertical bars like 111|222|333|..." + "Supply nonnegative numeric values separated by vertical bars like 111|222|333|..." )) ) } @@ -1141,12 +1211,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::PolyMumbai); - 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/fake_stream_holder.rs b/masq_lib/src/test_utils/fake_stream_holder.rs index d971ffa7a..d57ed3c3c 100644 --- a/masq_lib/src/test_utils/fake_stream_holder.rs +++ b/masq_lib/src/test_utils/fake_stream_holder.rs @@ -1,136 +1,7 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::command::StdStreams; -use std::cmp::min; -use std::io; -use std::io::Read; -use std::io::Write; -use std::io::{BufRead, Error}; -use std::sync::{Arc, Mutex}; - -pub struct ByteArrayWriter { - inner_arc: Arc>, -} - -pub struct ByteArrayWriterInner { - byte_array: Vec, - next_error: Option, -} - -impl ByteArrayWriterInner { - pub fn get_bytes(&self) -> Vec { - self.byte_array.clone() - } - pub fn get_string(&self) -> String { - String::from_utf8(self.get_bytes()).unwrap() - } -} - -impl Default for ByteArrayWriter { - fn default() -> Self { - ByteArrayWriter { - inner_arc: Arc::new(Mutex::new(ByteArrayWriterInner { - byte_array: vec![], - next_error: None, - })), - } - } -} - -impl ByteArrayWriter { - pub fn new() -> ByteArrayWriter { - Self::default() - } - - pub fn inner_arc(&self) -> Arc> { - self.inner_arc.clone() - } - - pub fn get_bytes(&self) -> Vec { - self.inner_arc.lock().unwrap().byte_array.clone() - } - pub fn get_string(&self) -> String { - String::from_utf8(self.get_bytes()).unwrap() - } - - pub fn reject_next_write(&mut self, error: Error) { - self.inner_arc().lock().unwrap().next_error = Some(error); - } -} - -impl Write for ByteArrayWriter { - fn write(&mut self, buf: &[u8]) -> io::Result { - let mut inner = self.inner_arc.lock().unwrap(); - if let Some(next_error) = inner.next_error.take() { - Err(next_error) - } else { - for byte in buf { - inner.byte_array.push(*byte) - } - Ok(buf.len()) - } - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -pub struct ByteArrayReader { - byte_array: Vec, - position: usize, - next_error: Option, -} - -impl ByteArrayReader { - pub fn new(byte_array: &[u8]) -> ByteArrayReader { - ByteArrayReader { - byte_array: byte_array.to_vec(), - position: 0, - next_error: None, - } - } - - pub fn reject_next_read(mut self, error: Error) -> ByteArrayReader { - self.next_error = Some(error); - self - } -} - -impl Read for ByteArrayReader { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - match self.next_error.take() { - Some(error) => Err(error), - None => { - let to_copy = min(buf.len(), self.byte_array.len() - self.position); - #[allow(clippy::needless_range_loop)] - for idx in 0..to_copy { - buf[idx] = self.byte_array[self.position + idx] - } - self.position += to_copy; - Ok(to_copy) - } - } - } -} - -impl BufRead for ByteArrayReader { - fn fill_buf(&mut self) -> io::Result<&[u8]> { - match self.next_error.take() { - Some(error) => Err(error), - None => Ok(&self.byte_array[self.position..]), - } - } - - fn consume(&mut self, amt: usize) { - let result = self.position + amt; - self.position = if result < self.byte_array.len() { - result - } else { - self.byte_array.len() - } - } -} +use test_utilities::byte_array_reader_writer::{ByteArrayReader, ByteArrayWriter}; pub struct FakeStreamHolder { pub stdin: ByteArrayReader, diff --git a/masq_lib/src/test_utils/logging.rs b/masq_lib/src/test_utils/logging.rs index ec60ea72f..92566a753 100644 --- a/masq_lib/src/test_utils/logging.rs +++ b/masq_lib/src/test_utils/logging.rs @@ -1,6 +1,5 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::logger::real_format_function; -use crate::test_utils::fake_stream_holder::ByteArrayWriter; use crate::test_utils::utils::to_millis; use lazy_static::lazy_static; use log::set_logger; @@ -13,6 +12,7 @@ use std::sync::{Arc, Mutex, MutexGuard}; use std::thread; use std::time::Duration; use std::time::Instant; +use test_utilities::byte_array_reader_writer::ByteArrayWriter; use time::OffsetDateTime; lazy_static! { diff --git a/masq_lib/src/test_utils/utils.rs b/masq_lib/src/test_utils/utils.rs index ac12bd6ab..6d1e12609 100644 --- a/masq_lib/src/test_utils/utils.rs +++ b/masq_lib/src/test_utils/utils.rs @@ -35,8 +35,16 @@ pub fn ensure_node_home_directory_exists(module: &str, name: &str) -> PathBuf { } pub fn is_running_under_github_actions() -> bool { - if let Ok(value) = std::env::var("GITHUB_ACTIONS") { - &value == "true" + is_env_variable_set("GITHUB_ACTIONS", "true") +} + +pub fn is_test_generated_data_allowed_to_escape_project_dir() -> bool { + is_env_variable_set("ALLOW_TEST_DATA_ESCAPE_PROJECT_DIR", "true") +} + +fn is_env_variable_set(var_name: &str, searched_value: &str) -> bool { + if let Ok(value) = std::env::var(var_name) { + value == searched_value } else { false } diff --git a/masq_lib/src/utils.rs b/masq_lib/src/utils.rs index 1083af2b9..8d563ef37 100644 --- a/masq_lib/src/utils.rs +++ b/masq_lib/src/utils.rs @@ -49,14 +49,14 @@ fn compute_data_directory_help() -> String { let polygon_mainnet_dir = Path::new(&data_dir.to_str().unwrap()) .join("MASQ") .join("polygon-mainnet"); - let polygon_mumbai_dir = Path::new(&data_dir.to_str().unwrap()) + let polygon_amoy_dir = Path::new(&data_dir.to_str().unwrap()) .join("MASQ") - .join("polygon-mumbai"); + .join("polygon-amoy"); format!("Directory in which the Node will store its persistent state, including at least its database \ and by default its configuration file as well. By default, your data-directory is located in \ your application directory, under your home directory e.g.: '{}'.\n\n\ In case you change your chain to a different one, the data-directory path is automatically changed \ - to end with the name of your chain: e.g.: if you choose polygon-mumbai, then data-directory is \ + to end with the name of your chain: e.g.: if you choose polygon-amoy, then data-directory is \ automatically changed to: '{}'.\n\n\ You can specify your own data-directory to the Daemon in two different ways: \n\n\ 1. If you provide a path without the chain name on the end, the Daemon will automatically change \ @@ -65,7 +65,7 @@ fn compute_data_directory_help() -> String { 2. If you provide your data directory with the corresponding chain name on the end, eg: {}/masq_home/polygon-mainnet, \ there will be no change until you set the chain parameter to a different value.", polygon_mainnet_dir.to_string_lossy().to_string().as_str(), - polygon_mumbai_dir.to_string_lossy().to_string().as_str(), + polygon_amoy_dir.to_string_lossy().to_string().as_str(), &home_dir.to_string_lossy().to_string().as_str(), &home_dir.to_string_lossy().to_string().as_str(), home_dir.to_string_lossy().to_string().as_str() diff --git a/multinode_integration_tests/Cargo.toml b/multinode_integration_tests/Cargo.toml index bf50f472f..775ee0306 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/blockchain/Dockerfile b/multinode_integration_tests/docker/blockchain/Dockerfile index 027eb7a27..7ff65ea16 100644 --- a/multinode_integration_tests/docker/blockchain/Dockerfile +++ b/multinode_integration_tests/docker/blockchain/Dockerfile @@ -1,8 +1,8 @@ # Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -FROM trufflesuite/ganache-cli:v6.7.0 +FROM trufflesuite/ganache-cli:v6.12.2 ADD ./entrypoint.sh /app/ EXPOSE 18545 -ENTRYPOINT /app/entrypoint.sh +ENTRYPOINT ["/app/entrypoint.sh"] diff --git a/multinode_integration_tests/docker/blockchain/entrypoint.sh b/multinode_integration_tests/docker/blockchain/entrypoint.sh index f9d6cc220..c184cbb50 100755 --- a/multinode_integration_tests/docker/blockchain/entrypoint.sh +++ b/multinode_integration_tests/docker/blockchain/entrypoint.sh @@ -1,3 +1,8 @@ #!/bin/sh -node /app/ganache-core.docker.cli.js -p 18545 --networkId 2 --verbose --mnemonic "timber cage wide hawk phone shaft pattern movie army dizzy hen tackle lamp absent write kind term toddler sphere ripple idle dragon curious hold" +node /app/ganache-core.docker.cli.js \ + -h 0.0.0.0 \ + -p 18545 \ + --networkId 2 \ + --verbose \ + --mnemonic "timber cage wide hawk phone shaft pattern movie army dizzy hen tackle lamp absent write kind term toddler sphere ripple idle dragon curious hold" 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/masq_node_cluster.rs b/multinode_integration_tests/src/masq_node_cluster.rs index 86a94af54..aff6f1d37 100644 --- a/multinode_integration_tests/src/masq_node_cluster.rs +++ b/multinode_integration_tests/src/masq_node_cluster.rs @@ -337,6 +337,43 @@ impl MASQNodeCluster { )), } } + + #[allow(dead_code)] + fn interconnect_world_network(network_one: &str, network_two: &str) -> Result<(), String> { + let mut command = Command::new( + "docker", + Command::strings(vec!["network", "connect", network_one, network_two]), + ); + match command.wait_for_exit() { + 0 => Ok(()), + _ => Err(format!( + "Could not connect network {} to {}: {}", + network_one, + network_two, + command.stderr_as_string() + )), + } + } + + #[allow(dead_code)] + fn create_world_network(ipv4addr: Ipv4Addr, name: &str) -> Result<(), String> { + let mut command = Command::new( + "docker", + Command::strings(vec![ + "network", + "create", + format!("--subnet={}/16", ipv4addr).as_str(), + name, + ]), + ); + match command.wait_for_exit() { + 0 => Ok(()), + _ => Err(format!( + "Could not create network integration_net: {}", + command.stderr_as_string() + )), + } + } } pub struct DockerHostSocketAddr { diff --git a/multinode_integration_tests/src/mock_blockchain_client_server.rs b/multinode_integration_tests/src/mock_blockchain_client_server.rs index 24031b2cc..a40543808 100644 --- a/multinode_integration_tests/src/mock_blockchain_client_server.rs +++ b/multinode_integration_tests/src/mock_blockchain_client_server.rs @@ -241,8 +241,10 @@ impl MockBlockchainClientServer { let mut requests = requests_arc.lock().unwrap(); requests.push(body); } - let response = responses.remove(0); - Self::send_body(conn_state, response); + if !responses.is_empty() { + let response = responses.remove(0); + Self::send_body(conn_state, response); + } let _ = notifier_tx.send(()); // receiver doesn't exist if test didn't set it up } None => (), @@ -437,7 +439,7 @@ mod tests { .response("Thank you and good night", 40) .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!( @@ -567,7 +569,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(), @@ -600,7 +602,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() ]) } @@ -704,6 +706,6 @@ mod tests { body.len(), body ) - .into_bytes() + .into_bytes() } } diff --git a/multinode_integration_tests/src/neighborhood_constructor.rs b/multinode_integration_tests/src/neighborhood_constructor.rs index ba3fb650b..53a9d611e 100644 --- a/multinode_integration_tests/src/neighborhood_constructor.rs +++ b/multinode_integration_tests/src/neighborhood_constructor.rs @@ -259,8 +259,8 @@ fn from_masq_node_to_node_record(masq_node: &dyn MASQNode) -> NodeRecord { last_update: time_t_timestamp(), node_addr_opt: agr.node_addr_opt.clone(), unreachable_hosts: Default::default(), - node_distrust_score: 0, node_location_opt: None, + country_undesirability: 0u32, }, signed_gossip: agr.signed_gossip.clone(), signature: agr.signature, diff --git a/multinode_integration_tests/src/utils.rs b/multinode_integration_tests/src/utils.rs index 6c4868e89..5c522b364 100644 --- a/multinode_integration_tests/src/utils.rs +++ b/multinode_integration_tests/src/utils.rs @@ -3,7 +3,6 @@ use crate::command::Command; use crate::masq_node::{MASQNode, MASQNodeUtils}; use crate::masq_real_node::MASQRealNode; -use ip_country_lib::country_finder::COUNTRY_CODE_FINDER; use masq_lib::test_utils::utils::TEST_DEFAULT_MULTINODE_CHAIN; use masq_lib::utils::NeighborhoodModeLight; use node_lib::accountant::db_access_objects::payable_dao::{PayableDao, PayableDaoReal}; @@ -147,7 +146,7 @@ impl From<&dyn MASQNode> for AccessibleGossipRecord { signature: CryptData::new(b""), }; let ip_addr = masq_node.node_addr().ip_addr(); - let country_code = get_node_location(Some(ip_addr), &COUNTRY_CODE_FINDER); + let country_code = get_node_location(Some(ip_addr)); if let Some(cc) = country_code { agr.inner.country_code_opt = Some(cc.country_code) }; diff --git a/multinode_integration_tests/tests/blockchain_interaction_test.rs b/multinode_integration_tests/tests/blockchain_interaction_test.rs index bb7c00578..a675dcd76 100644 --- a/multinode_integration_tests/tests/blockchain_interaction_test.rs +++ b/multinode_integration_tests/tests/blockchain_interaction_test.rs @@ -5,7 +5,6 @@ use std::path::PathBuf; use std::time::{Duration, SystemTime}; use log::Level; -use regex::escape; use serde_derive::Serialize; use masq_lib::messages::{FromMessageBody, ScanType, ToMessageBody, UiScanRequest, UiScanResponse}; @@ -161,19 +160,15 @@ fn blockchain_bridge_starts_properly_on_bootstrap() { let mut cluster = MASQNodeCluster::start().unwrap(); let private_key = "0011223300112233001122330011223300112233001122330011223300112233"; let subject = cluster.start_real_node( - NodeStartupConfigBuilder::zero_hop() + NodeStartupConfigBuilder::standard() .consuming_wallet_info(ConsumingWalletInfo::PrivateKey(private_key.to_string())) .chain(cluster.chain) .build(), ); - let escaped_pattern = escape(&format!( - "DEBUG: BlockchainBridge: Received BindMessage; consuming wallet address {}", - subject.consuming_wallet().unwrap() - )); MASQNodeUtils::wrote_log_containing( subject.name(), - &escaped_pattern, + "DEBUG: BlockchainBridge: Received BindMessage", Duration::from_millis(1000), ) } diff --git a/multinode_integration_tests/tests/data_routing_test.rs b/multinode_integration_tests/tests/data_routing_test.rs index 0b3fa9d21..cdefcd354 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 / 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: www.testingmcafeesites.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(); @@ -330,7 +330,8 @@ fn multiple_stream_zero_hop_test() { assert_eq!( index_of( &another_response, - &b"neverssl.com will never use SSL (also known as TLS)"[..], + &b"This is an index url which gives an overview of the different test urls available." + [..], ) .is_some(), true, diff --git a/multinode_integration_tests/tests/verify_bill_payment.rs b/multinode_integration_tests/tests/verify_bill_payment.rs index 5e9b50347..1240deb58 100644 --- a/multinode_integration_tests/tests/verify_bill_payment.rs +++ b/multinode_integration_tests/tests/verify_bill_payment.rs @@ -38,10 +38,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", }; @@ -64,7 +61,7 @@ fn verify_bill_payment() { assert_balances( &contract_owner_wallet, &blockchain_interface, - "99998043204000000000", + "99998381140000000000", "472000000000000000000000000", ); let payment_thresholds = PaymentThresholds { @@ -189,7 +186,7 @@ fn verify_bill_payment() { assert_balances( &contract_owner_wallet, &blockchain_interface, - "99998043204000000000", + "99998381140000000000", "472000000000000000000000000", ); @@ -235,7 +232,7 @@ fn verify_bill_payment() { assert_balances( &contract_owner_wallet, &blockchain_interface, - "99997886466000000000", + "99998223682000000000", "471999999700000000000000000", ); @@ -330,7 +327,7 @@ fn assert_balances( assert_eq!( format!("{}", eth_balance), String::from(expected_eth_balance), - "Actual EthBalance {} doesn't much with expected {}", + "Actual EthBalance {} doesn't match with expected {}", eth_balance, expected_eth_balance ); diff --git a/node/Cargo.lock b/node/Cargo.lock index 5d4b6605d..16e936902 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", @@ -1565,7 +1565,7 @@ dependencies = [ "csv", "itertools 0.13.0", "lazy_static", - "masq_lib", + "test_utilities", ] [[package]] @@ -1842,7 +1842,7 @@ dependencies = [ [[package]] name = "masq" -version = "0.8.0" +version = "0.8.2" dependencies = [ "atty", "clap", @@ -1855,6 +1855,7 @@ dependencies = [ "nix 0.23.1", "num", "regex", + "test_utilities", "thousands", "time 0.3.11", "websocket", @@ -1862,7 +1863,7 @@ dependencies = [ [[package]] name = "masq_lib" -version = "0.8.0" +version = "0.8.2" dependencies = [ "actix", "clap", @@ -1870,6 +1871,7 @@ dependencies = [ "crossbeam-channel 0.5.1", "dirs 4.0.0", "ethereum-types", + "ip_country", "itertools 0.10.3", "lazy_static", "log 0.4.18", @@ -1878,6 +1880,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", + "test_utilities", "time 0.3.11", "tiny-hderive", "toml", @@ -2039,7 +2042,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", @@ -2133,7 +2136,7 @@ dependencies = [ [[package]] name = "node" -version = "0.8.0" +version = "0.8.2" dependencies = [ "actix", "automap", @@ -2188,6 +2191,7 @@ dependencies = [ "sodiumoxide", "sysinfo", "system-configuration", + "test_utilities", "thousands", "time 0.3.11", "tiny-bip39", @@ -3654,6 +3658,10 @@ dependencies = [ "phf_codegen", ] +[[package]] +name = "test_utilities" +version = "0.1.0" + [[package]] name = "textwrap" version = "0.11.0" diff --git a/node/Cargo.toml b/node/Cargo.toml index 345daa234..552761f32 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." @@ -86,6 +86,7 @@ native-tls = {version = "0.2.8", features = ["vendored"]} simple-server = "0.4.0" serial_test_derive = "0.5.1" serial_test = "0.5.1" +test_utilities = { path = "../test_utilities"} trust-dns-proto = "0.8.0" [[bin]] diff --git a/node/docs/Blockchain-Service.md b/node/docs/Blockchain-Service.md index 4f6e407cb..eb930736f 100644 --- a/node/docs/Blockchain-Service.md +++ b/node/docs/Blockchain-Service.md @@ -2,7 +2,7 @@ ### What is a blockchain service URL? -A blockchain service url is a URL that MASQ Node uses to interact with various blockchains, either the Mumbai and Ropsten +A blockchain service url is a URL that MASQ Node uses to interact with various blockchains, either the Amoy and Ropsten testnets or Polygon and Ethereum mainnets. On mainnets, MASQ uses the MASQ token; on testnets, it uses a test token. #### MASQ Node software supports connections to the following blockchains: @@ -11,7 +11,7 @@ testnets or Polygon and Ethereum mainnets. On mainnets, MASQ uses the MASQ token - [x] Ethereum mainnet **Polygon** -- [x] Mumbai testnet +- [x] Amoy testnet - [x] Polygon mainnet *in beta testing* To be fully functional MASQ Node needs communication to the blockchain for access to: @@ -29,27 +29,27 @@ There are two general types of Blockchain Services MASQ Node currently supports. ### 1. Sign up for a free [Infura.io](https://infura.io/register) account. Follow the instructions here [Infura.io/docs](https://infura.io/docs) to create a "Project" -Choose one of the following options for the network of your choice (if you are testing it is likely the Mumbai testnet): +Choose one of the following options for the network of your choice (if you are testing it is likely the Amoy testnet): -* Enter your Infura.io url `https://polygon-mumbai.infura.io/v3/` in the blockchain service url field of the GUI. +* Enter your Infura.io url `https://polygon-amoy.infura.io/v3/` in the blockchain service url field of the GUI. * For the `masq` command-line interface, use the `setup` command: - > `masq> setup --blockchain-service-url https://polygon-mumbai.infura.io/v3/` + > `masq> setup --blockchain-service-url https://polygon-amoy.infura.io/v3/` * Edit your config.toml file and include the entry - > `blockservice-service-url = "https://polygon-mumbai.infura.io/v3/"` + > `blockservice-service-url = "https://polygon-amoy.infura.io/v3/"` * Or define an environment variable * Windows - > `set MASQ_BLOCKCHAIN_SERVICE_URL = https://polygon-mumbai.infura.io/v3/` + > `set MASQ_BLOCKCHAIN_SERVICE_URL = https://polygon-amoy.infura.io/v3/` * Linux or macOS - > `export MASQ_BLOCKCHAIN_SERVICE_URL = https://polygon-mumbai.infura.io/v3/` + > `export MASQ_BLOCKCHAIN_SERVICE_URL = https://polygon-amoy.infura.io/v3/` Change `` with the PROJECT ID from your Infura.io Project Dashboard. Change the URL to mainnet when ready to spend and earn real MASQ. diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index bd444ef8f..e76b15a0d 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -25,7 +25,7 @@ use crate::accountant::financials::visibility_restricted_module::{ use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::msgs::{ BlockchainAgentWithContextMessage, QualifiedPayablesMessage, }; -use crate::accountant::scanners::{ScanSchedulers, Scanners}; +use crate::accountant::scanners::{BeginScanError, ScanSchedulers, Scanners}; use crate::blockchain::blockchain_bridge::{ PendingPayableFingerprint, PendingPayableFingerprintSeeds, RetrieveTransactions, }; @@ -43,6 +43,7 @@ use crate::sub_lib::accountant::ReportRoutingServiceProvidedMessage; use crate::sub_lib::accountant::ReportServicesConsumedMessage; use crate::sub_lib::accountant::{MessageIdGenerator, MessageIdGeneratorReal}; use crate::sub_lib::blockchain_bridge::OutboundPaymentsInstructions; +use crate::sub_lib::neighborhood::{ConfigChange, ConfigChangeMsg}; use crate::sub_lib::peer_actors::{BindMessage, StartMessage}; use crate::sub_lib::utils::{handle_ui_crash_request, NODE_MAILBOX_CAPACITY}; use crate::sub_lib::wallet::Wallet; @@ -83,7 +84,7 @@ pub const DEFAULT_PENDING_TOO_LONG_SEC: u64 = 21_600; //6 hours pub struct Accountant { suppress_initial_scans: bool, consuming_wallet_opt: Option, - earning_wallet: Rc, + earning_wallet: Wallet, payable_dao: Box, receivable_dao: Box, pending_payable_dao: Box, @@ -164,6 +165,14 @@ impl Handler for Accountant { } } +impl Handler for Accountant { + type Result = (); + + fn handle(&mut self, msg: ConfigChangeMsg, _ctx: &mut Self::Context) -> Self::Result { + self.handle_config_change_msg(msg); + } +} + impl Handler for Accountant { type Result = (); @@ -408,7 +417,7 @@ impl Accountant { pub fn new(config: BootstrapperConfig, dao_factories: DaoFactories) -> Accountant { let payment_thresholds = config.payment_thresholds_opt.expectv("Payment thresholds"); let scan_intervals = config.scan_intervals_opt.expectv("Scan Intervals"); - let earning_wallet = Rc::new(config.earning_wallet); + let earning_wallet = config.earning_wallet.clone(); let financial_statistics = Rc::new(RefCell::new(FinancialStatistics::default())); let payable_dao = dao_factories.payable_dao_factory.make(); let pending_payable_dao = dao_factories.pending_payable_dao_factory.make(); @@ -416,7 +425,6 @@ impl Accountant { let scanners = Scanners::new( dao_factories, Rc::new(payment_thresholds), - Rc::clone(&earning_wallet), config.when_pending_too_long_sec, Rc::clone(&financial_statistics), ); @@ -424,7 +432,7 @@ impl Accountant { Accountant { suppress_initial_scans: config.suppress_initial_scans, consuming_wallet_opt: config.consuming_wallet_opt.clone(), - earning_wallet: Rc::clone(&earning_wallet), + earning_wallet, payable_dao, receivable_dao, pending_payable_dao, @@ -447,6 +455,7 @@ impl Accountant { pub fn make_subs_from(addr: &Addr) -> AccountantSubs { AccountantSubs { bind: recipient!(addr, BindMessage), + config_change_msg_sub: recipient!(addr, ConfigChangeMsg), start: recipient!(addr, StartMessage), report_routing_service_provided: recipient!(addr, ReportRoutingServiceProvidedMessage), report_exit_service_provided: recipient!(addr, ReportExitServiceProvidedMessage), @@ -563,6 +572,27 @@ impl Accountant { info!(self.logger, "Accountant bound"); } + fn handle_config_change_msg(&mut self, msg: ConfigChangeMsg) { + if let ConfigChange::UpdateWallets(wallet_pair) = msg.change { + if self.earning_wallet != wallet_pair.earning_wallet { + info!( + self.logger, + "Earning Wallet has been updated: {}", wallet_pair.earning_wallet + ); + self.earning_wallet = wallet_pair.earning_wallet; + } + if self.consuming_wallet_opt != Some(wallet_pair.consuming_wallet.clone()) { + info!( + self.logger, + "Consuming Wallet has been updated: {}", wallet_pair.consuming_wallet + ); + self.consuming_wallet_opt = Some(wallet_pair.consuming_wallet); + } + } else { + trace!(self.logger, "Ignored irrelevant message: {:?}", msg); + } + } + fn schedule_next_scan(&self, scan_type: ScanType, ctx: &mut Context) { self.scan_schedulers .schedulers @@ -806,11 +836,17 @@ impl Accountant { &mut self, response_skeleton_opt: Option, ) { - match self.scanners.payable.begin_scan( - SystemTime::now(), - response_skeleton_opt, - &self.logger, - ) { + let result = match self.consuming_wallet_opt.clone() { + Some(consuming_wallet) => self.scanners.payable.begin_scan( + consuming_wallet, + SystemTime::now(), + response_skeleton_opt, + &self.logger, + ), + None => Err(BeginScanError::NoConsumingWalletFound), + }; + + match result { Ok(scan_message) => { self.qualified_payables_sub_opt .as_ref() @@ -830,11 +866,17 @@ impl Accountant { &mut self, response_skeleton_opt: Option, ) { - match self.scanners.pending_payable.begin_scan( - SystemTime::now(), - response_skeleton_opt, - &self.logger, - ) { + let result = match self.consuming_wallet_opt.clone() { + Some(consuming_wallet) => self.scanners.pending_payable.begin_scan( + consuming_wallet, // This argument is not used and is therefore irrelevant + SystemTime::now(), + response_skeleton_opt, + &self.logger, + ), + None => Err(BeginScanError::NoConsumingWalletFound), + }; + + match result { Ok(scan_message) => self .request_transaction_receipts_subs_opt .as_ref() @@ -854,6 +896,7 @@ impl Accountant { response_skeleton_opt: Option, ) { match self.scanners.receivable.begin_scan( + self.earning_wallet.clone(), SystemTime::now(), response_skeleton_opt, &self.logger, @@ -1013,6 +1056,7 @@ mod tests { use crate::blockchain::test_utils::{make_tx_hash, BlockchainInterfaceMock}; 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::{ @@ -1020,6 +1064,8 @@ mod tests { DEFAULT_EARNING_WALLET, DEFAULT_PAYMENT_THRESHOLDS, }; use crate::sub_lib::blockchain_bridge::OutboundPaymentsInstructions; + use crate::sub_lib::neighborhood::ConfigChange; + use crate::sub_lib::neighborhood::{Hops, WalletPair}; use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMock; use crate::test_utils::recorder::make_recorder; use crate::test_utils::recorder::peer_actors_builder; @@ -1196,7 +1242,7 @@ mod tests { default_scan_intervals.receivable_scan_interval, ); assert_eq!(result.consuming_wallet_opt, None); - assert_eq!(*result.earning_wallet, *DEFAULT_EARNING_WALLET); + assert_eq!(result.earning_wallet, *DEFAULT_EARNING_WALLET); assert_eq!(result.suppress_initial_scans, false); result .message_id_generator @@ -1208,6 +1254,68 @@ mod tests { assert_eq!(financial_statistics.total_paid_payable_wei, 0); } + #[test] + fn accountant_handles_config_change_msg() { + assert_handling_of_config_change_msg( + ConfigChangeMsg { + change: ConfigChange::UpdateWallets(WalletPair { + consuming_wallet: make_paying_wallet(b"new_consuming_wallet"), + earning_wallet: make_wallet("new_earning_wallet"), + }), + }, + |subject: &Accountant| { + assert_eq!( + subject.consuming_wallet_opt, + Some(make_paying_wallet(b"new_consuming_wallet")) + ); + assert_eq!(subject.earning_wallet, make_wallet("new_earning_wallet")); + let _ = TestLogHandler::new().assert_logs_contain_in_order( + vec![ + "INFO: ConfigChange: Earning Wallet has been updated: 0x00006e65775f6561726e696e675f77616c6c6574", + "INFO: ConfigChange: Consuming Wallet has been updated: 0xfa133bbf90bce093fa2e7caa6da68054af66793e", + ] + ); + }, + ); + assert_handling_of_config_change_msg( + ConfigChangeMsg { + change: ConfigChange::UpdatePassword("new password".to_string()), + }, + |_subject: &Accountant| { + let _ = TestLogHandler::new().exists_log_containing( + "TRACE: ConfigChange: Ignored irrelevant message: \ + ConfigChangeMsg { change: UpdatePassword(\"new password\") }", + ); + }, + ); + assert_handling_of_config_change_msg( + ConfigChangeMsg { + change: ConfigChange::UpdateMinHops(Hops::FourHops), + }, + |_subject: &Accountant| { + let _ = TestLogHandler::new().exists_log_containing( + "TRACE: ConfigChange: Ignored irrelevant message: \ + ConfigChangeMsg { change: UpdateMinHops(FourHops) }", + ); + }, + ); + } + + fn assert_handling_of_config_change_msg(msg: ConfigChangeMsg, assertions: A) + where + A: FnOnce(&Accountant), + { + init_test_logging(); + let mut subject = AccountantBuilder::default() + .bootstrapper_config(make_bc_with_defaults()) + .build(); + subject.logger = Logger::new("ConfigChange"); + + subject.handle_config_change_msg(msg); + + assertions(&subject); + } + #[test] fn scan_receivables_request() { let mut config = bc_from_earning_wallet(make_wallet("earning_wallet")); @@ -1266,7 +1374,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(); @@ -1300,6 +1412,7 @@ mod tests { #[test] fn scan_payables_request() { let config = bc_from_earning_wallet(make_wallet("some_wallet_address")); + let consuming_wallet = make_paying_wallet(b"consuming"); let payable_account = PayableAccount { wallet: make_wallet("wallet"), balance_wei: gwei_to_wei(DEFAULT_PAYMENT_THRESHOLDS.debt_threshold_gwei + 1), @@ -1312,6 +1425,7 @@ mod tests { PayableDaoMock::new().non_pending_payables_result(vec![payable_account.clone()]); let subject = AccountantBuilder::default() .bootstrapper_config(config) + .consuming_wallet(consuming_wallet.clone()) .payable_daos(vec![ForPayableScanner(payable_dao)]) .build(); let (blockchain_bridge, _, blockchain_bridge_recording_arc) = make_recorder(); @@ -1338,6 +1452,7 @@ mod tests { blockchain_bridge_recording.get_record::(0), &QualifiedPayablesMessage { protected_qualified_payables: protect_payables_in_test(vec![payable_account]), + consuming_wallet, response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, context_id: 4321, @@ -1617,6 +1732,7 @@ mod tests { let pending_payable_dao = PendingPayableDaoMock::default() .return_all_errorless_fingerprints_result(vec![fingerprint.clone()]); let subject = AccountantBuilder::default() + .consuming_wallet(make_paying_wallet(b"consuming")) .bootstrapper_config(config) .pending_payable_daos(vec![ForPendingPayableScanner(pending_payable_dao)]) .build(); @@ -1675,6 +1791,7 @@ mod tests { .return_all_errorless_fingerprints_result(vec![fingerprint]); let subject = AccountantBuilder::default() .bootstrapper_config(config) + .consuming_wallet(make_paying_wallet(b"consuming")) .logger(Logger::new(test_name)) .pending_payable_daos(vec![ForPendingPayableScanner(pending_payable_dao)]) .build(); @@ -1802,8 +1919,10 @@ mod tests { let system = System::new( "accountant_sends_initial_payable_payments_msg_when_qualified_payable_found", ); + let consuming_wallet = make_paying_wallet(b"consuming"); let mut subject = AccountantBuilder::default() .bootstrapper_config(bc_from_earning_wallet(make_wallet("some_wallet_address"))) + .consuming_wallet(consuming_wallet.clone()) .payable_daos(vec![ForPayableScanner(payable_dao)]) .build(); subject.scanners.pending_payable = Box::new(NullScanner::new()); @@ -1826,6 +1945,7 @@ mod tests { message, &QualifiedPayablesMessage { protected_qualified_payables: protect_payables_in_test(qualified_payables), + consuming_wallet, response_skeleton_opt: None, } ); @@ -1876,6 +1996,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"); @@ -1899,6 +2020,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() @@ -1913,7 +2036,7 @@ mod tests { .try_send(ReceivedPayments { timestamp: now, payments: vec![expected_receivable_1.clone(), expected_receivable_2.clone()], - new_start_block: 123456789, + new_start_block: 123456789u64, response_skeleton_opt: None, }) .expect("unexpected actix error"); @@ -2019,7 +2142,8 @@ mod tests { response_skeleton_opt: None, })) .stop_the_system_after_last_msg(); - let mut config = make_bc_with_defaults(); + let earning_wallet = make_wallet("earning"); + let mut config = bc_from_earning_wallet(earning_wallet.clone()); config.scan_intervals_opt = Some(ScanIntervals { payable_scan_interval: Duration::from_secs(100), receivable_scan_interval: Duration::from_millis(99), @@ -2048,13 +2172,38 @@ mod tests { send_start_message!(subject_subs); + let time_before = SystemTime::now(); system.run(); - let begin_scan_params = begin_scan_params_arc.lock().unwrap(); + let time_after = SystemTime::now(); let notify_later_receivable_params = notify_later_receivable_params_arc.lock().unwrap(); TestLogHandler::new().exists_log_containing(&format!( "DEBUG: {test_name}: There was nothing to process during Receivables scan." )); - assert_eq!(begin_scan_params.len(), 2); + let mut begin_scan_params = begin_scan_params_arc.lock().unwrap(); + let ( + first_attempt_wallet, + first_attempt_timestamp, + first_attempt_response_skeleton_opt, + first_attempt_logger, + ) = begin_scan_params.remove(0); + let ( + second_attempt_wallet, + second_attempt_timestamp, + second_attempt_response_skeleton_opt, + second_attempt_logger, + ) = begin_scan_params.remove(0); + assert_eq!(first_attempt_wallet, second_attempt_wallet); + assert_eq!(second_attempt_wallet, earning_wallet); + assert!(time_before <= first_attempt_timestamp); + assert!(first_attempt_timestamp <= second_attempt_timestamp); + assert!(second_attempt_timestamp <= time_after); + assert_eq!(first_attempt_response_skeleton_opt, None); + assert_eq!(second_attempt_response_skeleton_opt, None); + debug!(first_attempt_logger, "first attempt"); + debug!(second_attempt_logger, "second attempt"); + let tlh = TestLogHandler::new(); + tlh.exists_log_containing(&format!("DEBUG: {test_name}: first attempt")); + tlh.exists_log_containing(&format!("DEBUG: {test_name}: second attempt")); assert_eq!( *notify_later_receivable_params, vec![ @@ -2082,6 +2231,7 @@ mod tests { let notify_later_pending_payable_params_arc = Arc::new(Mutex::new(vec![])); let system = System::new(test_name); SystemKillerActor::new(Duration::from_secs(10)).start(); // a safety net for GitHub Actions + let consuming_wallet = make_paying_wallet(b"consuming"); let pending_payable_scanner = ScannerMock::new() .begin_scan_params(&begin_scan_params_arc) .begin_scan_result(Err(BeginScanError::NothingToProcess)) @@ -2097,6 +2247,7 @@ mod tests { pending_payable_scan_interval: Duration::from_millis(98), }); let mut subject = AccountantBuilder::default() + .consuming_wallet(consuming_wallet.clone()) .bootstrapper_config(config) .logger(Logger::new(test_name)) .build(); @@ -2119,14 +2270,39 @@ mod tests { send_start_message!(subject_subs); + let time_before = SystemTime::now(); system.run(); - let begin_scan_params = begin_scan_params_arc.lock().unwrap(); + let time_after = SystemTime::now(); let notify_later_pending_payable_params = notify_later_pending_payable_params_arc.lock().unwrap(); TestLogHandler::new().exists_log_containing(&format!( "DEBUG: {test_name}: There was nothing to process during PendingPayables scan." )); - assert_eq!(begin_scan_params.len(), 2); + let mut begin_scan_params = begin_scan_params_arc.lock().unwrap(); + let ( + first_attempt_wallet, + first_attempt_timestamp, + first_attempt_response_skeleton_opt, + first_attempt_logger, + ) = begin_scan_params.remove(0); + let ( + second_attempt_wallet, + second_attempt_timestamp, + second_attempt_response_skeleton_opt, + second_attempt_logger, + ) = begin_scan_params.remove(0); + assert_eq!(first_attempt_wallet, second_attempt_wallet); + assert_eq!(second_attempt_wallet, consuming_wallet); + assert!(time_before <= first_attempt_timestamp); + assert!(first_attempt_timestamp <= second_attempt_timestamp); + assert!(second_attempt_timestamp <= time_after); + assert_eq!(first_attempt_response_skeleton_opt, None); + assert_eq!(second_attempt_response_skeleton_opt, None); + debug!(first_attempt_logger, "first attempt"); + debug!(second_attempt_logger, "second attempt"); + let tlh = TestLogHandler::new(); + tlh.exists_log_containing(&format!("DEBUG: {test_name}: first attempt")); + tlh.exists_log_containing(&format!("DEBUG: {test_name}: second attempt")); assert_eq!( *notify_later_pending_payable_params, vec![ @@ -2154,6 +2330,7 @@ mod tests { let notify_later_payables_params_arc = Arc::new(Mutex::new(vec![])); let system = System::new(test_name); SystemKillerActor::new(Duration::from_secs(10)).start(); // a safety net for GitHub Actions + let consuming_wallet = make_paying_wallet(b"consuming"); let payable_scanner = ScannerMock::new() .begin_scan_params(&begin_scan_params_arc) .begin_scan_result(Err(BeginScanError::NothingToProcess)) @@ -2161,6 +2338,7 @@ mod tests { protected_qualified_payables: protect_payables_in_test(vec![make_payable_account( 123, )]), + consuming_wallet: consuming_wallet.clone(), response_skeleton_opt: None, })) .stop_the_system_after_last_msg(); @@ -2172,6 +2350,7 @@ mod tests { }); let mut subject = AccountantBuilder::default() .bootstrapper_config(config) + .consuming_wallet(consuming_wallet.clone()) .logger(Logger::new(test_name)) .build(); subject.scanners.payable = Box::new(payable_scanner); @@ -2193,14 +2372,39 @@ mod tests { send_start_message!(subject_subs); + let time_before = SystemTime::now(); system.run(); + let time_after = SystemTime::now(); //the second attempt is the one where the queue is empty and System::current.stop() ends the cycle - let begin_scan_params = begin_scan_params_arc.lock().unwrap(); let notify_later_payables_params = notify_later_payables_params_arc.lock().unwrap(); TestLogHandler::new().exists_log_containing(&format!( "DEBUG: {test_name}: There was nothing to process during Payables scan." )); - assert_eq!(begin_scan_params.len(), 2); + let mut begin_scan_params = begin_scan_params_arc.lock().unwrap(); + let ( + first_attempt_wallet, + first_attempt_timestamp, + first_attempt_response_skeleton_opt, + first_attempt_logger, + ) = begin_scan_params.remove(0); + let ( + second_attempt_wallet, + second_attempt_timestamp, + second_attempt_response_skeleton_opt, + second_attempt_logger, + ) = begin_scan_params.remove(0); + assert_eq!(first_attempt_wallet, second_attempt_wallet); + assert_eq!(second_attempt_wallet, consuming_wallet); + assert!(time_before <= first_attempt_timestamp); + assert!(first_attempt_timestamp <= second_attempt_timestamp); + assert!(second_attempt_timestamp <= time_after); + assert_eq!(first_attempt_response_skeleton_opt, None); + assert_eq!(second_attempt_response_skeleton_opt, None); + debug!(first_attempt_logger, "first attempt"); + debug!(second_attempt_logger, "second attempt"); + let tlh = TestLogHandler::new(); + tlh.exists_log_containing(&format!("DEBUG: {test_name}: first attempt")); + tlh.exists_log_containing(&format!("DEBUG: {test_name}: second attempt")); assert_eq!( *notify_later_payables_params, vec![ @@ -2220,6 +2424,40 @@ mod tests { ) } + #[test] + fn payable_scan_is_not_initiated_if_consuming_wallet_is_not_found() { + init_test_logging(); + let test_name = "payable_scan_is_not_initiated_if_consuming_wallet_is_not_found"; + let mut subject = AccountantBuilder::default().build(); + subject.consuming_wallet_opt = None; + subject.logger = Logger::new(test_name); + + subject.handle_request_of_scan_for_payable(None); + + let has_scan_started = subject.scanners.payable.scan_started_at().is_some(); + assert_eq!(has_scan_started, false); + TestLogHandler::new().exists_log_containing(&format!( + "DEBUG: {test_name}: Cannot initiate Payables scan because no consuming wallet was found." + )); + } + + #[test] + fn pending_payable_scan_is_not_initiated_if_consuming_wallet_is_not_found() { + init_test_logging(); + let test_name = "pending_payable_scan_is_not_initiated_if_consuming_wallet_is_not_found"; + let mut subject = AccountantBuilder::default().build(); + subject.consuming_wallet_opt = None; + subject.logger = Logger::new(test_name); + + subject.handle_request_of_scan_for_pending_payable(None); + + let has_scan_started = subject.scanners.pending_payable.scan_started_at().is_some(); + assert_eq!(has_scan_started, false); + TestLogHandler::new().exists_log_containing(&format!( + "DEBUG: {test_name}: Cannot initiate PendingPayables scan because no consuming wallet was found." + )); + } + #[test] fn start_message_triggers_no_scans_in_suppress_mode() { init_test_logging(); @@ -2254,6 +2492,7 @@ mod tests { #[test] fn scan_for_payables_message_does_not_trigger_payment_for_balances_below_the_curve() { init_test_logging(); + let consuming_wallet = make_paying_wallet(b"consuming wallet"); let payment_thresholds = PaymentThresholds { threshold_interval_sec: 2_592_000, debt_threshold_gwei: 1_000_000_000, @@ -2315,10 +2554,12 @@ mod tests { .build(); subject.outbound_payments_instructions_sub_opt = Some(outbound_payments_instructions_sub); - let _result = subject - .scanners - .payable - .begin_scan(SystemTime::now(), None, &subject.logger); + let _result = subject.scanners.payable.begin_scan( + consuming_wallet, + SystemTime::now(), + None, + &subject.logger, + ); System::current().stop(); system.run(); @@ -2330,6 +2571,7 @@ mod tests { fn scan_for_payable_message_triggers_payment_for_balances_over_the_curve() { init_test_logging(); let mut config = bc_from_earning_wallet(make_wallet("mine")); + let consuming_wallet = make_paying_wallet(b"consuming"); config.scan_intervals_opt = Some(ScanIntervals { pending_payable_scan_interval: Duration::from_secs(50_000), payable_scan_interval: Duration::from_secs(50_000), @@ -2376,6 +2618,7 @@ mod tests { .build(); let mut subject = AccountantBuilder::default() .bootstrapper_config(config) + .consuming_wallet(consuming_wallet.clone()) .payable_daos(vec![ForPayableScanner(payable_dao)]) .build(); subject.scanners.pending_payable = Box::new(NullScanner::new()); @@ -2393,6 +2636,7 @@ mod tests { message, &QualifiedPayablesMessage { protected_qualified_payables: protect_payables_in_test(qualified_payables), + consuming_wallet, response_skeleton_opt: None, } ); @@ -2426,6 +2670,7 @@ mod tests { let config = bc_from_earning_wallet(make_wallet("mine")); let system = System::new(test_name); let mut subject = AccountantBuilder::default() + .consuming_wallet(make_paying_wallet(b"consuming")) .logger(Logger::new(test_name)) .payable_daos(vec![ForPayableScanner(payable_dao)]) .bootstrapper_config(config) @@ -2515,6 +2760,7 @@ mod tests { let config = bc_from_earning_wallet(make_wallet("mine")); let system = System::new("pending payable scan"); let mut subject = AccountantBuilder::default() + .consuming_wallet(make_paying_wallet(b"consuming")) .pending_payable_daos(vec![ForPendingPayableScanner(pending_payable_dao)]) .bootstrapper_config(config) .build(); @@ -3247,7 +3493,6 @@ mod tests { Box::new(blockchain_interface), Box::new(persistent_config), false, - Some(consuming_wallet.clone()), ); let account_1 = PayableAccount { wallet: wallet_account_1.clone(), @@ -3355,10 +3600,12 @@ mod tests { .delete_fingerprints_result(Ok(())); pending_payable_dao_for_pending_payable_scanner .have_return_all_errorless_fingerprints_shut_down_the_system = true; + let consuming_wallet_for_accountant = consuming_wallet.clone(); let accountant_addr = Arbiter::builder() .stop_system_on_panic(true) .start(move |_| { let mut subject = AccountantBuilder::default() + .consuming_wallet(consuming_wallet_for_accountant) .bootstrapper_config(bootstrapper_config) .payable_daos(vec![ ForPayableScanner(payable_dao_for_payable_scanner), @@ -4535,6 +4782,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/msgs.rs b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/msgs.rs index d4cd40be4..41a1b3940 100644 --- a/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/msgs.rs +++ b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/msgs.rs @@ -2,6 +2,7 @@ use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; use crate::accountant::{ResponseSkeleton, SkeletonOptHolder}; +use crate::sub_lib::wallet::Wallet; use actix::Message; use masq_lib::type_obfuscation::Obfuscated; use std::fmt::Debug; @@ -9,16 +10,19 @@ use std::fmt::Debug; #[derive(Debug, Message, PartialEq, Eq, Clone)] pub struct QualifiedPayablesMessage { pub protected_qualified_payables: Obfuscated, + pub consuming_wallet: Wallet, pub response_skeleton_opt: Option, } impl QualifiedPayablesMessage { pub(in crate::accountant) fn new( protected_qualified_payables: Obfuscated, + consuming_wallet: Wallet, response_skeleton_opt: Option, ) -> Self { Self { protected_qualified_payables, + consuming_wallet, response_skeleton_opt, } } diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index f1341ed10..fc5f5ce91 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -68,7 +68,6 @@ impl Scanners { pub fn new( dao_factories: DaoFactories, payment_thresholds: Rc, - earning_wallet: Rc, when_pending_too_long_sec: u64, financial_statistics: Rc>, ) -> Self { @@ -94,7 +93,6 @@ impl Scanners { dao_factories.banned_dao_factory.make(), Box::new(persistent_configuration), Rc::clone(&payment_thresholds), - earning_wallet, financial_statistics, )); @@ -113,6 +111,7 @@ where { fn begin_scan( &mut self, + wallet: Wallet, timestamp: SystemTime, response_skeleton_opt: Option, logger: &Logger, @@ -193,6 +192,7 @@ pub struct PayableScanner { impl Scanner for PayableScanner { fn begin_scan( &mut self, + consuming_wallet: Wallet, timestamp: SystemTime, response_skeleton_opt: Option, logger: &Logger, @@ -225,8 +225,11 @@ impl Scanner for PayableScanner { qualified_payables.len() ); let protected_payables = self.protect_payables(qualified_payables); - let outgoing_msg = - QualifiedPayablesMessage::new(protected_payables, response_skeleton_opt); + let outgoing_msg = QualifiedPayablesMessage::new( + protected_payables, + consuming_wallet, + response_skeleton_opt, + ); Ok(outgoing_msg) } } @@ -567,6 +570,7 @@ pub struct PendingPayableScanner { impl Scanner for PendingPayableScanner { fn begin_scan( &mut self, + _irrelevant_wallet: Wallet, timestamp: SystemTime, response_skeleton_opt: Option, logger: &Logger, @@ -825,13 +829,13 @@ pub struct ReceivableScanner { pub receivable_dao: Box, pub banned_dao: Box, pub persistent_configuration: Box, - pub earning_wallet: Rc, pub financial_statistics: Rc>, } impl Scanner for ReceivableScanner { fn begin_scan( &mut self, + earning_wallet: Wallet, timestamp: SystemTime, response_skeleton_opt: Option, logger: &Logger, @@ -840,14 +844,11 @@ impl Scanner for ReceivableScanner { return Err(BeginScanError::ScanAlreadyRunning(timestamp)); } self.mark_as_started(timestamp); - info!( - logger, - "Scanning for receivables to {}", self.earning_wallet - ); + info!(logger, "Scanning for receivables to {}", earning_wallet); self.scan_for_delinquencies(timestamp, logger); Ok(RetrieveTransactions { - recipient: self.earning_wallet.as_ref().clone(), + recipient: earning_wallet, response_skeleton_opt, }) } @@ -861,7 +862,7 @@ impl Scanner for ReceivableScanner { match self .persistent_configuration - .set_start_block(msg.new_start_block) + .set_start_block(Some(msg.new_start_block)) { Ok(()) => debug!(logger, "Start block updated to {}", msg.new_start_block), Err(e) => panic!( @@ -893,12 +894,10 @@ impl ReceivableScanner { banned_dao: Box, persistent_configuration: Box, payment_thresholds: Rc, - earning_wallet: Rc, financial_statistics: Rc>, ) -> Self { Self { common: ScannerCommon::new(payment_thresholds), - earning_wallet, receivable_dao, banned_dao, persistent_configuration, @@ -915,7 +914,7 @@ impl ReceivableScanner { let new_start_block = 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!( @@ -985,6 +984,7 @@ impl ReceivableScanner { #[derive(Debug, PartialEq, Eq)] pub enum BeginScanError { NothingToProcess, + NoConsumingWalletFound, ScanAlreadyRunning(SystemTime), CalledFromNullScanner, // Exclusive for tests } @@ -1007,6 +1007,10 @@ impl BeginScanError { scan_type, BeginScanError::timestamp_as_string(timestamp) )), + BeginScanError::NoConsumingWalletFound => Some(format!( + "Cannot initiate {:?} scan because no consuming wallet was found.", + scan_type + )), BeginScanError::CalledFromNullScanner => match cfg!(test) { true => None, false => panic!("Null Scanner shouldn't be running inside production code."), @@ -1134,9 +1138,9 @@ mod tests { DaoFactories, FinancialStatistics, PaymentThresholds, ScanIntervals, DEFAULT_PAYMENT_THRESHOLDS, }; - use crate::test_utils::make_wallet; use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMock; use crate::test_utils::unshared_test_utils::arbitrary_id_stamp::ArbitraryIdStamp; + use crate::test_utils::{make_paying_wallet, make_wallet}; use actix::{Message, System}; use ethereum_types::U64; use masq_lib::logger::Logger; @@ -1175,7 +1179,6 @@ mod tests { total_paid_payable_wei: 1, total_paid_receivable_wei: 2, }; - let earning_wallet = make_wallet("unique_wallet"); let payment_thresholds = make_custom_payment_thresholds(); let payment_thresholds_rc = Rc::new(payment_thresholds); let initial_rc_count = Rc::strong_count(&payment_thresholds_rc); @@ -1189,7 +1192,6 @@ mod tests { config_dao_factory: Box::new(config_dao_factory), }, Rc::clone(&payment_thresholds_rc), - Rc::new(earning_wallet.clone()), when_pending_too_long_sec, Rc::new(RefCell::new(financial_statistics.clone())), ); @@ -1234,10 +1236,6 @@ mod tests { *receivable_scanner.financial_statistics.borrow(), financial_statistics ); - assert_eq!( - receivable_scanner.earning_wallet.address(), - earning_wallet.address() - ); assert_eq!( receivable_scanner.common.payment_thresholds.as_ref(), &payment_thresholds @@ -1245,7 +1243,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!( @@ -1274,6 +1272,7 @@ mod tests { fn payable_scanner_can_initiate_a_scan() { init_test_logging(); let test_name = "payable_scanner_can_initiate_a_scan"; + let consuming_wallet = make_paying_wallet(b"consuming wallet"); let now = SystemTime::now(); let (qualified_payable_accounts, _, all_non_pending_payables) = make_payables(now, &PaymentThresholds::default()); @@ -1283,7 +1282,8 @@ mod tests { .payable_dao(payable_dao) .build(); - let result = subject.begin_scan(now, None, &Logger::new(test_name)); + let result = + subject.begin_scan(consuming_wallet.clone(), now, None, &Logger::new(test_name)); let timestamp = subject.scan_started_at(); assert_eq!(timestamp, Some(now)); @@ -1293,6 +1293,7 @@ mod tests { protected_qualified_payables: protect_payables_in_test( qualified_payable_accounts.clone() ), + consuming_wallet, response_skeleton_opt: None, }) ); @@ -1307,6 +1308,7 @@ mod tests { #[test] fn payable_scanner_throws_error_when_a_scan_is_already_running() { + let consuming_wallet = make_paying_wallet(b"consuming wallet"); let now = SystemTime::now(); let (_, _, all_non_pending_payables) = make_payables(now, &PaymentThresholds::default()); let payable_dao = @@ -1314,9 +1316,14 @@ mod tests { let mut subject = PayableScannerBuilder::new() .payable_dao(payable_dao) .build(); - let _result = subject.begin_scan(now, None, &Logger::new("test")); + let _result = subject.begin_scan(consuming_wallet.clone(), now, None, &Logger::new("test")); - let run_again_result = subject.begin_scan(SystemTime::now(), None, &Logger::new("test")); + let run_again_result = subject.begin_scan( + consuming_wallet, + SystemTime::now(), + None, + &Logger::new("test"), + ); let is_scan_running = subject.scan_started_at().is_some(); assert_eq!(is_scan_running, true); @@ -1328,6 +1335,7 @@ mod tests { #[test] fn payable_scanner_throws_error_in_case_no_qualified_payable_is_found() { + let consuming_wallet = make_paying_wallet(b"consuming wallet"); let now = SystemTime::now(); let (_, unqualified_payable_accounts, _) = make_payables(now, &PaymentThresholds::default()); @@ -1337,7 +1345,7 @@ mod tests { .payable_dao(payable_dao) .build(); - let result = subject.begin_scan(now, None, &Logger::new("test")); + let result = subject.begin_scan(consuming_wallet, now, None, &Logger::new("test")); let is_scan_running = subject.scan_started_at().is_some(); assert_eq!(is_scan_running, false); @@ -1623,7 +1631,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) @@ -1875,7 +1883,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)) } @@ -2210,6 +2218,7 @@ mod tests { fn pending_payable_scanner_can_initiate_a_scan() { init_test_logging(); let test_name = "pending_payable_scanner_can_initiate_a_scan"; + let consuming_wallet = make_paying_wallet(b"consuming wallet"); let now = SystemTime::now(); let payable_fingerprint_1 = PendingPayableFingerprint { rowid: 555, @@ -2234,7 +2243,12 @@ mod tests { .pending_payable_dao(pending_payable_dao) .build(); - let result = pending_payable_scanner.begin_scan(now, None, &Logger::new(test_name)); + let result = pending_payable_scanner.begin_scan( + consuming_wallet, + now, + None, + &Logger::new(test_name), + ); let no_of_pending_payables = fingerprints.len(); let is_scan_running = pending_payable_scanner.scan_started_at().is_some(); @@ -2257,6 +2271,7 @@ mod tests { #[test] fn pending_payable_scanner_throws_error_in_case_scan_is_already_running() { let now = SystemTime::now(); + let consuming_wallet = make_paying_wallet(b"consuming"); let pending_payable_dao = PendingPayableDaoMock::new() .return_all_errorless_fingerprints_result(vec![PendingPayableFingerprint { rowid: 1234, @@ -2270,9 +2285,9 @@ mod tests { .pending_payable_dao(pending_payable_dao) .build(); let logger = Logger::new("test"); - let _ = subject.begin_scan(now, None, &logger); + let _ = subject.begin_scan(consuming_wallet.clone(), now, None, &logger); - let result = subject.begin_scan(SystemTime::now(), None, &logger); + let result = subject.begin_scan(consuming_wallet, SystemTime::now(), None, &logger); let is_scan_running = subject.scan_started_at().is_some(); assert_eq!(is_scan_running, true); @@ -2282,13 +2297,15 @@ mod tests { #[test] fn pending_payable_scanner_throws_an_error_when_no_fingerprint_is_found() { let now = SystemTime::now(); + let consuming_wallet = make_paying_wallet(b"consuming_wallet"); let pending_payable_dao = PendingPayableDaoMock::new().return_all_errorless_fingerprints_result(vec![]); let mut pending_payable_scanner = PendingPayableScannerBuilder::new() .pending_payable_dao(pending_payable_dao) .build(); - let result = pending_payable_scanner.begin_scan(now, None, &Logger::new("test")); + let result = + pending_payable_scanner.begin_scan(consuming_wallet, now, None, &Logger::new("test")); let is_scan_running = pending_payable_scanner.scan_started_at().is_some(); assert_eq!(result, Err(BeginScanError::NothingToProcess)); @@ -2924,10 +2941,14 @@ mod tests { let earning_wallet = make_wallet("earning"); let mut receivable_scanner = ReceivableScannerBuilder::new() .receivable_dao(receivable_dao) - .earning_wallet(earning_wallet.clone()) .build(); - let result = receivable_scanner.begin_scan(now, None, &Logger::new(test_name)); + let result = receivable_scanner.begin_scan( + earning_wallet.clone(), + now, + None, + &Logger::new(test_name), + ); let is_scan_running = receivable_scanner.scan_started_at().is_some(); assert_eq!(is_scan_running, true); @@ -2952,11 +2973,16 @@ mod tests { let earning_wallet = make_wallet("earning"); let mut receivable_scanner = ReceivableScannerBuilder::new() .receivable_dao(receivable_dao) - .earning_wallet(earning_wallet) .build(); - let _ = receivable_scanner.begin_scan(now, None, &Logger::new("test")); + let _ = + receivable_scanner.begin_scan(earning_wallet.clone(), now, None, &Logger::new("test")); - let result = receivable_scanner.begin_scan(SystemTime::now(), None, &Logger::new("test")); + let result = receivable_scanner.begin_scan( + earning_wallet, + SystemTime::now(), + None, + &Logger::new("test"), + ); let is_scan_running = receivable_scanner.scan_started_at().is_some(); assert_eq!(is_scan_running, true); @@ -2989,12 +3015,11 @@ mod tests { .receivable_dao(receivable_dao) .banned_dao(banned_dao) .payment_thresholds(payment_thresholds) - .earning_wallet(earning_wallet.clone()) .build(); let logger = Logger::new("DELINQUENCY_TEST"); let now = SystemTime::now(); - let result = receivable_scanner.begin_scan(now, None, &logger); + let result = receivable_scanner.begin_scan(earning_wallet.clone(), now, None, &logger); assert_eq!( result, @@ -3045,6 +3070,7 @@ mod tests { 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() @@ -3061,7 +3087,7 @@ mod tests { 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." )); @@ -3074,16 +3100,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 new_start_block = 6709u64; + 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 msg = ReceivedPayments { timestamp: now, payments: vec![], - new_start_block: 6709, + new_start_block, response_skeleton_opt: None, }; // Not necessary, rather for preciseness @@ -3107,6 +3138,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() @@ -3153,7 +3185,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![()]); @@ -3171,9 +3203,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) @@ -3215,8 +3249,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) diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs index 38c7c56ec..6de6968ac 100644 --- a/node/src/accountant/test_utils.rs +++ b/node/src/accountant/test_utils.rs @@ -95,25 +95,27 @@ pub fn make_payable_account_with_wallet_and_balance_and_timestamp_opt( } pub struct AccountantBuilder { - config: Option, - logger: Option, - payable_dao_factory: Option, - receivable_dao_factory: Option, - pending_payable_dao_factory: Option, - banned_dao_factory: Option, - config_dao_factory: Option, + config_opt: Option, + consuming_wallet_opt: Option, + logger_opt: Option, + payable_dao_factory_opt: Option, + receivable_dao_factory_opt: Option, + pending_payable_dao_factory_opt: Option, + banned_dao_factory_opt: Option, + config_dao_factory_opt: Option, } impl Default for AccountantBuilder { fn default() -> Self { Self { - config: None, - logger: None, - payable_dao_factory: None, - receivable_dao_factory: None, - pending_payable_dao_factory: None, - banned_dao_factory: None, - config_dao_factory: None, + config_opt: None, + consuming_wallet_opt: None, + logger_opt: None, + payable_dao_factory_opt: None, + receivable_dao_factory_opt: None, + pending_payable_dao_factory_opt: None, + banned_dao_factory_opt: None, + config_dao_factory_opt: None, } } } @@ -262,12 +264,17 @@ const RECEIVABLE_DAOS_ACCOUNTANT_INITIALIZATION_ORDER: [DestinationMarker; 2] = impl AccountantBuilder { pub fn bootstrapper_config(mut self, config: BootstrapperConfig) -> Self { - self.config = Some(config); + self.config_opt = Some(config); + self + } + + pub fn consuming_wallet(mut self, consuming_wallet: Wallet) -> Self { + self.consuming_wallet_opt = Some(consuming_wallet); self } pub fn logger(mut self, logger: Logger) -> Self { - self.logger = Some(logger); + self.logger_opt = Some(logger); self } @@ -278,7 +285,7 @@ impl AccountantBuilder { create_or_update_factory!( specially_configured_daos, PENDING_PAYABLE_DAOS_ACCOUNTANT_INITIALIZATION_ORDER, - pending_payable_dao_factory, + pending_payable_dao_factory_opt, PendingPayableDaoFactoryMock, PendingPayableDao, self @@ -292,7 +299,7 @@ impl AccountantBuilder { create_or_update_factory!( specially_configured_daos, PAYABLE_DAOS_ACCOUNTANT_INITIALIZATION_ORDER, - payable_dao_factory, + payable_dao_factory_opt, PayableDaoFactoryMock, PayableDao, self @@ -306,7 +313,7 @@ impl AccountantBuilder { create_or_update_factory!( specially_configured_daos, RECEIVABLE_DAOS_ACCOUNTANT_INITIALIZATION_ORDER, - receivable_dao_factory, + receivable_dao_factory_opt, ReceivableDaoFactoryMock, ReceivableDao, self @@ -315,46 +322,47 @@ impl AccountantBuilder { //TODO this method seems to be never used? pub fn banned_dao(mut self, banned_dao: BannedDaoMock) -> Self { - match self.banned_dao_factory { + match self.banned_dao_factory_opt { None => { - self.banned_dao_factory = Some(BannedDaoFactoryMock::new().make_result(banned_dao)) + self.banned_dao_factory_opt = + Some(BannedDaoFactoryMock::new().make_result(banned_dao)) } Some(banned_dao_factory) => { - self.banned_dao_factory = Some(banned_dao_factory.make_result(banned_dao)) + self.banned_dao_factory_opt = Some(banned_dao_factory.make_result(banned_dao)) } } self } pub fn config_dao(mut self, config_dao: ConfigDaoMock) -> Self { - self.config_dao_factory = Some(ConfigDaoFactoryMock::new().make_result(config_dao)); + self.config_dao_factory_opt = Some(ConfigDaoFactoryMock::new().make_result(config_dao)); self } pub fn build(self) -> Accountant { - let config = self.config.unwrap_or(make_bc_with_defaults()); - let payable_dao_factory = self.payable_dao_factory.unwrap_or( + let config = self.config_opt.unwrap_or(make_bc_with_defaults()); + let payable_dao_factory = self.payable_dao_factory_opt.unwrap_or( PayableDaoFactoryMock::new() .make_result(PayableDaoMock::new()) .make_result(PayableDaoMock::new()) .make_result(PayableDaoMock::new()), ); - let receivable_dao_factory = self.receivable_dao_factory.unwrap_or( + let receivable_dao_factory = self.receivable_dao_factory_opt.unwrap_or( ReceivableDaoFactoryMock::new() .make_result(ReceivableDaoMock::new()) .make_result(ReceivableDaoMock::new()), ); - let pending_payable_dao_factory = self.pending_payable_dao_factory.unwrap_or( + let pending_payable_dao_factory = self.pending_payable_dao_factory_opt.unwrap_or( PendingPayableDaoFactoryMock::new() .make_result(PendingPayableDaoMock::new()) .make_result(PendingPayableDaoMock::new()) .make_result(PendingPayableDaoMock::new()), ); let banned_dao_factory = self - .banned_dao_factory + .banned_dao_factory_opt .unwrap_or(BannedDaoFactoryMock::new().make_result(BannedDaoMock::new())); let config_dao_factory = self - .config_dao_factory + .config_dao_factory_opt .unwrap_or(ConfigDaoFactoryMock::new().make_result(ConfigDaoMock::new())); let mut accountant = Accountant::new( config, @@ -366,9 +374,12 @@ impl AccountantBuilder { config_dao_factory: Box::new(config_dao_factory), }, ); - if let Some(logger) = self.logger { + if let Some(logger) = self.logger_opt { accountant.logger = logger; } + if let Some(consuming_wallet) = self.consuming_wallet_opt { + accountant.consuming_wallet_opt = Some(consuming_wallet); + } accountant } @@ -1176,7 +1187,6 @@ pub struct ReceivableScannerBuilder { banned_dao: BannedDaoMock, persistent_configuration: PersistentConfigurationMock, payment_thresholds: PaymentThresholds, - earning_wallet: Wallet, financial_statistics: FinancialStatistics, } @@ -1187,7 +1197,6 @@ impl ReceivableScannerBuilder { banned_dao: BannedDaoMock::new(), persistent_configuration: PersistentConfigurationMock::new(), payment_thresholds: PaymentThresholds::default(), - earning_wallet: make_wallet("earning_default"), financial_statistics: FinancialStatistics::default(), } } @@ -1215,18 +1224,12 @@ impl ReceivableScannerBuilder { self } - pub fn earning_wallet(mut self, earning_wallet: Wallet) -> Self { - self.earning_wallet = earning_wallet; - self - } - pub fn build(self) -> ReceivableScanner { ReceivableScanner::new( Box::new(self.receivable_dao), Box::new(self.banned_dao), Box::new(self.persistent_configuration), Rc::new(self.payment_thresholds), - Rc::new(self.earning_wallet), Rc::new(RefCell::new(self.financial_statistics)), ) } @@ -1538,6 +1541,7 @@ where { fn begin_scan( &mut self, + _wallet_opt: Wallet, _timestamp: SystemTime, _response_skeleton_opt: Option, _logger: &Logger, @@ -1579,7 +1583,7 @@ impl NullScanner { } pub struct ScannerMock { - begin_scan_params: Arc>>, + begin_scan_params: Arc, Logger)>>>, begin_scan_results: RefCell>>, end_scan_params: Arc>>, end_scan_results: RefCell>>, @@ -1594,11 +1598,17 @@ where { fn begin_scan( &mut self, - _timestamp: SystemTime, - _response_skeleton_opt: Option, - _logger: &Logger, + wallet: Wallet, + timestamp: SystemTime, + response_skeleton_opt: Option, + logger: &Logger, ) -> Result { - self.begin_scan_params.lock().unwrap().push(()); + self.begin_scan_params.lock().unwrap().push(( + wallet, + timestamp, + response_skeleton_opt, + logger.clone(), + )); if self.is_allowed_to_stop_the_system() && self.is_last_message() { System::current().stop(); } @@ -1643,7 +1653,10 @@ impl ScannerMock { } } - pub fn begin_scan_params(mut self, params: &Arc>>) -> Self { + pub fn begin_scan_params( + mut self, + params: &Arc, Logger)>>>, + ) -> Self { self.begin_scan_params = params.clone(); self } diff --git a/node/src/actor_system_factory.rs b/node/src/actor_system_factory.rs index 0b175a797..2a03d024c 100644 --- a/node/src/actor_system_factory.rs +++ b/node/src/actor_system_factory.rs @@ -506,7 +506,6 @@ impl ActorFactory for ActorFactoryReal { .blockchain_service_url_opt .clone(); let crashable = is_crashable(config); - let wallet_opt = config.consuming_wallet_opt.clone(); let data_directory = config.data_directory.clone(); let chain = config.blockchain_bridge_config.chain; let arbiter = Arbiter::builder().stop_system_on_panic(true); @@ -517,12 +516,7 @@ impl ActorFactory for ActorFactoryReal { ); let persistent_config = BlockchainBridge::initialize_persistent_configuration(&data_directory); - BlockchainBridge::new( - blockchain_interface, - persistent_config, - crashable, - wallet_opt, - ) + BlockchainBridge::new(blockchain_interface, persistent_config, crashable) }); subs_factory.make(&addr) } @@ -1179,7 +1173,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }, payment_thresholds_opt: Some(PaymentThresholds::default()), when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, @@ -1255,7 +1248,7 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), + }, payment_thresholds_opt: Default::default(), when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC @@ -1401,7 +1394,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }; let make_params_arc = Arc::new(Mutex::new(vec![])); let mut subject = make_subject_with_null_setter(); @@ -1556,7 +1548,7 @@ mod tests { neighborhood_config: NeighborhoodConfig { mode: NeighborhoodMode::ConsumeOnly(vec![]), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), + }, payment_thresholds_opt: Default::default(), when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC @@ -1746,7 +1738,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }, node_descriptor: Default::default(), payment_thresholds_opt: Default::default(), diff --git a/node/src/apps.rs b/node/src/apps.rs index 9aed6369b..2a38c7e47 100644 --- a/node/src/apps.rs +++ b/node/src/apps.rs @@ -6,7 +6,7 @@ use lazy_static::lazy_static; use masq_lib::constants::{HIGHEST_USABLE_PORT, LOWEST_USABLE_INSECURE_PORT}; use masq_lib::shared_schema::{ chain_arg, data_directory_arg, db_password_arg, real_user_arg, shared_app, ui_port_arg, - DB_PASSWORD_HELP, + DATA_DIRECTORY_HELP, DB_PASSWORD_HELP, }; use masq_lib::utils::DATA_DIRECTORY_DAEMON_HELP; @@ -36,7 +36,9 @@ pub fn app_daemon() -> App<'static, 'static> { } pub fn app_node() -> App<'static, 'static> { - shared_app(app_head().after_help(NODE_HELP_TEXT)).arg(ui_port_arg(&DAEMON_UI_PORT_HELP)) + shared_app(app_head().after_help(NODE_HELP_TEXT)) + .arg(data_directory_arg(DATA_DIRECTORY_HELP)) + .arg(ui_port_arg(&DAEMON_UI_PORT_HELP)) } pub fn app_config_dumper() -> App<'static, 'static> { @@ -107,9 +109,9 @@ mod tests { let polygon_mainnet_dir = Path::new(&data_dir.to_str().unwrap()) .join("MASQ") .join("polygon-mainnet"); - let polygon_mumbai_dir = Path::new(&data_dir.to_str().unwrap()) + let polygon_amoy_dir = Path::new(&data_dir.to_str().unwrap()) .join("MASQ") - .join("polygon-mumbai"); + .join("polygon-amoy"); assert_eq!( DATA_DIRECTORY_DAEMON_HELP.as_str(), @@ -117,7 +119,7 @@ mod tests { and by default its configuration file as well. By default, your data-directory is located in \ your application directory, under your home directory e.g.: '{}'.\n\n\ In case you change your chain to a different one, the data-directory path is automatically changed \ - to end with the name of your chain: e.g.: if you choose polygon-mumbai, then data-directory is \ + to end with the name of your chain: e.g.: if you choose polygon-amoy, then data-directory is \ automatically changed to: '{}'.\n\n\ You can specify your own data-directory to the Daemon in two different ways: \n\n\ 1. If you provide a path without the chain name on the end, the Daemon will automatically change \ @@ -126,7 +128,7 @@ mod tests { 2. If you provide your data directory with the corresponding chain name on the end, eg: {}/masq_home/polygon-mainnet, \ there will be no change until you set the chain parameter to a different value.", polygon_mainnet_dir.to_string_lossy().to_string().as_str(), - polygon_mumbai_dir.to_string_lossy().to_string().as_str(), + polygon_amoy_dir.to_string_lossy().to_string().as_str(), &home_dir.to_string_lossy().to_string().as_str(), &home_dir.to_string_lossy().to_string().as_str(), home_dir.to_string_lossy().to_string().as_str() diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index 93e4d9bb1..67d018e27 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -33,6 +33,7 @@ use actix::Message; use actix::{Addr, Recipient}; use itertools::Itertools; use masq_lib::blockchains::chains::Chain; +use masq_lib::constants::DEFAULT_MAX_BLOCK_COUNT; use masq_lib::logger::Logger; use masq_lib::messages::ScanType; use masq_lib::ui_gateway::NodeFromUiMessage; @@ -45,7 +46,6 @@ use web3::types::{BlockNumber, TransactionReceipt, H256}; pub const CRASH_KEY: &str = "BLOCKCHAINBRIDGE"; pub struct BlockchainBridge { - consuming_wallet_opt: Option, blockchain_interface: Box, logger: Logger, persistent_config: Box, @@ -81,16 +81,8 @@ impl Handler for BlockchainBridge { self.sent_payable_subs_opt = Some(msg.peer_actors.accountant.report_sent_payments); self.received_payments_subs_opt = Some(msg.peer_actors.accountant.report_inbound_payments); self.scan_error_subs_opt = Some(msg.peer_actors.accountant.scan_errors); - match self.consuming_wallet_opt.as_ref() { - Some(wallet) => debug!( - self.logger, - "Received BindMessage; consuming wallet address {}", wallet - ), - None => debug!( - self.logger, - "Received BindMessage; no consuming wallet address specified" - ), - } + // There's a multinode integration test looking for this message + debug!(self.logger, "Received BindMessage"); } } @@ -185,10 +177,8 @@ impl BlockchainBridge { blockchain_interface: Box, persistent_config: Box, crashable: bool, - consuming_wallet_opt: Option, ) -> BlockchainBridge { BlockchainBridge { - consuming_wallet_opt, blockchain_interface, persistent_config, sent_payable_subs_opt: None, @@ -244,18 +234,9 @@ impl BlockchainBridge { &mut self, incoming_message: QualifiedPayablesMessage, ) -> Result<(), String> { - let consuming_wallet = if let Some(wallet) = self.consuming_wallet_opt.as_ref() { - wallet - } else { - return Err( - "Cannot inspect available balances for payables while consuming wallet is missing" - .to_string(), - ); - }; - let agent = self .blockchain_interface - .build_blockchain_agent(consuming_wallet, &*self.persistent_config) + .build_blockchain_agent(&incoming_message.consuming_wallet, &*self.persistent_config) .map_err(to_string)?; let outgoing_message = BlockchainAgentWithContextMessage::new( @@ -301,66 +282,99 @@ impl BlockchainBridge { fn handle_retrieve_transactions(&mut self, msg: RetrieveTransactions) -> Result<(), String> { 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) + 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, + _ => DEFAULT_MAX_BLOCK_COUNT, }; + let use_unlimited_block_count_range = u64::MAX == max_block_count; + let use_latest_block = u64::MAX == start_block_nbr; let end_block = match self .blockchain_interface .lower_interface() .get_block_number() { Ok(eb) => { - if u64::MAX == max_block_count { + if use_unlimited_block_count_range || use_latest_block { BlockNumber::Number(eb) } else { BlockNumber::Number(eb.as_u64().min(start_block_nbr + max_block_count).into()) } } Err(e) => { - info!( - self.logger, - "Using 'latest' block number instead of a literal number. {:?}", e - ); - if max_block_count == u64::MAX { + if use_unlimited_block_count_range || use_latest_block { + debug!( + self.logger, + "Using 'latest' block number instead of a literal number. {:?}", e + ); BlockNumber::Latest } else { + debug!( + self.logger, + "Using '{}' ending block number. {:?}", + start_block_nbr + max_block_count, + e + ); BlockNumber::Number((start_block_nbr + max_block_count).into()) } } }; - let start_block = BlockNumber::Number(start_block_nbr.into()); + let start_block = if use_latest_block { + end_block + } else { + BlockNumber::Number(start_block_nbr.into()) + }; let retrieved_transactions = self.blockchain_interface .retrieve_transactions(start_block, end_block, &msg.recipient); match retrieved_transactions { Ok(transactions) => { - if transactions.transactions.is_empty() { - debug!(self.logger, "No new receivable detected"); + if let BlockNumber::Number(new_start_block_number) = transactions.new_start_block { + if transactions.transactions.is_empty() { + debug!(self.logger, "No new receivable detected"); + } + self.received_payments_subs_opt + .as_ref() + .expect("Accountant is unbound") + .try_send(ReceivedPayments { + timestamp: SystemTime::now(), + payments: transactions.transactions, + new_start_block: new_start_block_number.as_u64(), + response_skeleton_opt: msg.response_skeleton_opt, + }) + .expect("Accountant is dead."); } - self.received_payments_subs_opt - .as_ref() - .expect("Accountant is unbound") - .try_send(ReceivedPayments { - timestamp: SystemTime::now(), - payments: transactions.transactions, - new_start_block: transactions.new_start_block, - response_skeleton_opt: msg.response_skeleton_opt, - }) - .expect("Accountant is dead."); Ok(()) } Err(e) => { if let Some(max_block_count) = self.extract_max_block_count(e.clone()) { - debug!(self.logger, "Writing max_block_count({})", max_block_count); + debug!(self.logger, "Writing max_block_count({})", &max_block_count); self.persistent_config .set_max_block_count(Some(max_block_count)) - .unwrap_or_else(|_| panic!("Writing max_block_count failed: {:?}", e)); + .map_or_else( + |_| { + warning!(self.logger, "{} update max_block_count to {}. Scheduling next scan with that limit.", e, &max_block_count); + Err(format!("{} updated max_block_count to {}. Scheduling next scan with that limit.", e, &max_block_count)) + }, + |e| { + warning!(self.logger, "Writing max_block_count failed: {:?}", e); + Err(format!("Writing max_block_count failed: {:?}", e)) + }, + ) + } else { + warning!( + self.logger, + "Attempted to retrieve received payments but failed: {:?}", + e + ); + Err(format!( + "Attempted to retrieve received payments but failed: {:?}", + e + )) } - Err(format!("Could not retrieve Transactions: {:?}", e)) } } } @@ -458,7 +472,7 @@ impl BlockchainBridge { pub fn extract_max_block_count(&self, 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()) { @@ -503,7 +517,6 @@ mod tests { 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_pending_payable_fingerprint; - use crate::blockchain::bip32::Bip32EncryptionKeyProvider; use crate::blockchain::blockchain_interface::blockchain_interface_null::BlockchainInterfaceNull; use crate::blockchain::blockchain_interface::data_structures::errors::{ BlockchainAgentBuildError, PayableTransactionError, @@ -523,8 +536,8 @@ mod tests { 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, configure_default_persistent_config, - prove_that_crash_request_handler_is_hooked_up, AssertionsMessage, ZERO, + assert_on_initialization_with_panic_on_migration, + prove_that_crash_request_handler_is_hooked_up, AssertionsMessage, }; use crate::test_utils::{make_paying_wallet, make_wallet}; use actix::System; @@ -534,7 +547,6 @@ mod tests { 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, TEST_DEFAULT_CHAIN}; - use rustc_hex::FromHex; use std::any::TypeId; use std::path::Path; use std::sync::{Arc, Mutex}; @@ -558,40 +570,6 @@ mod tests { assert_eq!(CRASH_KEY, "BLOCKCHAINBRIDGE"); } - fn stub_bi() -> Box { - Box::new(BlockchainInterfaceMock::default()) - } - - #[test] - fn blockchain_bridge_receives_bind_message_with_consuming_private_key() { - init_test_logging(); - let secret: Vec = "cc46befe8d169b89db447bd725fc2368b12542113555302598430cb5d5c74ea9" - .from_hex() - .unwrap(); - let consuming_wallet = - Wallet::from(Bip32EncryptionKeyProvider::from_raw_secret(&secret).unwrap()); - let subject = BlockchainBridge::new( - stub_bi(), - Box::new(configure_default_persistent_config(ZERO)), - false, - Some(consuming_wallet.clone()), - ); - let system = System::new("blockchain_bridge_receives_bind_message"); - let addr = subject.start(); - - addr.try_send(BindMessage { - peer_actors: peer_actors_builder().build(), - }) - .unwrap(); - - System::current().stop(); - system.run(); - TestLogHandler::new().exists_log_containing(&format!( - "DEBUG: BlockchainBridge: Received BindMessage; consuming wallet address {}", - consuming_wallet - )); - } - #[test] fn blockchain_interface_null_as_result_of_missing_blockchain_service_url() { let result = BlockchainBridge::initialize_blockchain_interface(None, TEST_DEFAULT_CHAIN); @@ -602,30 +580,6 @@ mod tests { .unwrap(); } - #[test] - fn blockchain_bridge_receives_bind_message_without_consuming_private_key() { - init_test_logging(); - let subject = BlockchainBridge::new( - stub_bi(), - Box::new(PersistentConfigurationMock::default()), - false, - None, - ); - let system = System::new("blockchain_bridge_receives_bind_message"); - let addr = subject.start(); - - addr.try_send(BindMessage { - peer_actors: peer_actors_builder().build(), - }) - .unwrap(); - - System::current().stop(); - system.run(); - TestLogHandler::new().exists_log_containing( - "DEBUG: BlockchainBridge: Received BindMessage; no consuming wallet address specified", - ); - } - #[test] fn qualified_payables_msg_is_handled_and_new_msg_with_an_added_blockchain_agent_returns_to_accountant( ) { @@ -667,7 +621,6 @@ mod tests { Box::new(blockchain_interface), Box::new(persistent_configuration), false, - Some(consuming_wallet.clone()), ); let addr = subject.start(); let subject_subs = BlockchainBridge::make_subs_from(&addr); @@ -675,6 +628,7 @@ mod tests { let qualified_payables = protect_payables_in_test(qualified_payables.clone()); let qualified_payables_msg = QualifiedPayablesMessage { protected_qualified_payables: qualified_payables.clone(), + consuming_wallet: consuming_wallet.clone(), response_skeleton_opt: Some(ResponseSkeleton { client_id: 11122, context_id: 444, @@ -728,7 +682,6 @@ mod tests { Box::new(blockchain_interface), Box::new(persistent_configuration), false, - Some(consuming_wallet), ); subject.logger = Logger::new(test_name); subject.scan_error_subs_opt = Some(scan_error_recipient); @@ -739,6 +692,7 @@ mod tests { last_paid_timestamp: SystemTime::now(), pending_payable_opt: None, }]), + consuming_wallet, response_skeleton_opt: Some(ResponseSkeleton { client_id: 11, context_id: 2323, @@ -772,37 +726,6 @@ mod tests { .exists_log_containing(&format!("WARN: {test_name}: {expected_error_msg}")); } - #[test] - fn handle_qualified_payable_msg_fails_at_missing_consuming_wallet() { - let blockchain_interface = BlockchainInterfaceMock::default(); - let persistent_configuration = PersistentConfigurationMock::default(); - let mut subject = BlockchainBridge::new( - Box::new(blockchain_interface), - Box::new(persistent_configuration), - false, - None, - ); - let request = QualifiedPayablesMessage { - protected_qualified_payables: protect_payables_in_test(vec![PayableAccount { - wallet: make_wallet("blah"), - balance_wei: 4254, - last_paid_timestamp: SystemTime::now(), - pending_payable_opt: None, - }]), - response_skeleton_opt: None, - }; - - let result = subject.handle_qualified_payable_msg(request); - - assert_eq!( - result, - Err( - "Cannot inspect available balances for payables while consuming wallet is missing" - .to_string() - ) - ) - } - #[test] fn handle_outbound_payments_instructions_sees_payments_happen_and_sends_payment_results_back_to_accountant( ) { @@ -828,12 +751,10 @@ mod tests { hash: H256::from("someothertransactionhash".keccak256()), }), ])); - let consuming_wallet = make_paying_wallet(b"somewallet"); let subject = BlockchainBridge::new( Box::new(blockchain_interface_mock), Box::new(PersistentConfigurationMock::default()), false, - Some(consuming_wallet.clone()), ); let addr = subject.start(); let subject_subs = BlockchainBridge::make_subs_from(&addr); @@ -917,12 +838,10 @@ mod tests { let blockchain_interface_mock = BlockchainInterfaceMock::default() .send_batch_of_payables_result(expected_error.clone()); let persistent_configuration_mock = PersistentConfigurationMock::default(); - let consuming_wallet = make_paying_wallet(b"somewallet"); let subject = BlockchainBridge::new( Box::new(blockchain_interface_mock), Box::new(persistent_configuration_mock), false, - Some(consuming_wallet), ); let addr = subject.start(); let subject_subs = BlockchainBridge::make_subs_from(&addr); @@ -988,13 +907,11 @@ mod tests { msg: "failure from chronic exhaustion".to_string(), hashes: vec![transaction_hash], })); - let consuming_wallet = make_wallet("somewallet"); let persistent_configuration_mock = PersistentConfigurationMock::new(); let mut subject = BlockchainBridge::new( Box::new(blockchain_interface_mock), Box::new(persistent_configuration_mock), false, - Some(consuming_wallet.clone()), ); let checked_accounts = vec![PayableAccount { wallet: make_wallet("blah"), @@ -1043,7 +960,6 @@ mod tests { Box::new(blockchain_interface_mock), Box::new(PersistentConfigurationMock::default()), false, - None, ); let addr = subject.start(); let subject_subs = BlockchainBridge::make_subs_from(&addr); @@ -1104,13 +1020,12 @@ mod tests { ))) .lower_interface_results(Box::new(lower_interface)); 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 + .max_block_count_result(Ok(Some(DEFAULT_MAX_BLOCK_COUNT))) + .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), false, - None, ); subject.scan_error_subs_opt = Some(scan_error_recipient); let msg = RetrieveTransactions { @@ -1130,13 +1045,13 @@ mod tests { &ScanError { scan_type: ScanType::Receivables, response_skeleton_opt: None, - msg: "Could not retrieve Transactions: QueryFailed(\"we have no luck\")" - .to_string() + msg: "Attempted to retrieve received payments but failed: QueryFailed(\"we have no luck\")".to_string() } ); assert_eq!(recording.len(), 1); TestLogHandler::new().exists_log_containing( - "WARN: BlockchainBridge: Could not retrieve Transactions: QueryFailed(\"we have no luck\")", + "WARN: BlockchainBridge: Attempted to retrieve \ + received payments but failed: QueryFailed(\"we have no luck\")", ); } @@ -1197,7 +1112,6 @@ mod tests { Box::new(blockchain_interface_mock), Box::new(PersistentConfigurationMock::default()), false, - None, ); subject .pending_payable_confirmation @@ -1260,7 +1174,6 @@ mod tests { Box::new(BlockchainInterfaceMock::default()), Box::new(PersistentConfigurationMock::default()), false, - Some(Wallet::new("mine")), ); subject .pending_payable_confirmation @@ -1321,7 +1234,6 @@ mod tests { Box::new(blockchain_interface_mock), Box::new(PersistentConfigurationMock::default()), false, - None, ); subject .pending_payable_confirmation @@ -1366,18 +1278,19 @@ mod tests { } #[test] - fn handle_retrieve_transactions_uses_latest_block_number_upon_get_block_number_error() { + fn handle_retrieve_transactions_uses_default_max_block_count_for_ending_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", + "handle_retrieve_transactions_uses_default_max_block_count_for_ending_block_number_upon_get_block_number_error", ); 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, + new_start_block: BlockNumber::Number(8675309u64.into()), transactions: vec![ BlockchainTransaction { block_number: 7, @@ -1400,13 +1313,12 @@ mod tests { .retrieve_transactions_result(Ok(expected_transactions.clone())) .lower_interface_results(Box::new(lower_interface)); let persistent_config = PersistentConfigurationMock::new() - .max_block_count_result(Ok(Some(10000u64))) - .start_block_result(Ok(6)); + .max_block_count_result(Ok(Some(DEFAULT_MAX_BLOCK_COUNT))) + .start_block_result(Ok(Some(6))); let subject = BlockchainBridge::new( Box::new(blockchain_interface_mock), Box::new(persistent_config), false, - Some(make_wallet("consuming")), ); let addr = subject.start(); let subject_subs = BlockchainBridge::make_subs_from(&addr); @@ -1431,7 +1343,7 @@ mod tests { *retrieve_transactions_params, vec![( BlockNumber::Number(6u64.into()), - BlockNumber::Number(10006u64.into()), + BlockNumber::Number((DEFAULT_MAX_BLOCK_COUNT + 6u64).into()), earning_wallet )] ); @@ -1451,7 +1363,176 @@ mod tests { }), } ); - TestLogHandler::new().exists_log_containing("INFO: BlockchainBridge: Using 'latest' block number instead of a literal number. QueryFailed(\"Failed to read the latest block number\")"); + TestLogHandler::new().exists_log_containing("DEBUG: BlockchainBridge: Using '100006' ending block number. QueryFailed(\"Failed to read the latest block number\")"); + } + + #[test] + fn handle_retrieve_transactions_when_start_block_number_starts_undefined_in_a_brand_new_database( + ) { + let retrieve_transactions_params_arc = Arc::new(Mutex::new(vec![])); + let system = System::new( + "handle_retrieve_transactions_when_start_block_number_starts_undefined_in_a_brand_new_database", + ); + 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: BlockNumber::Number(8675309u64.into()), + transactions: vec![ + BlockchainTransaction { + block_number: 8675308u64, + from: earning_wallet.clone(), + wei_amount: amount, + }, + BlockchainTransaction { + block_number: 8675309u64, + from: earning_wallet.clone(), + wei_amount: amount2, + }, + ], + }; + let lower_interface = LowBlockchainIntMock::default().get_block_number_result( + LatestBlockNumber::Err(BlockchainError::QueryFailed( + "\"Failed to read the latest block number\"".to_string(), + )), + ); + let blockchain_interface_mock = BlockchainInterfaceMock::default() + .retrieve_transactions_params(&retrieve_transactions_params_arc) + .retrieve_transactions_result(Ok(expected_transactions.clone())) + .lower_interface_results(Box::new(lower_interface)); + let persistent_config = PersistentConfigurationMock::new() + .max_block_count_result(Ok(Some(DEFAULT_MAX_BLOCK_COUNT))) + .start_block_result(Ok(None)); + let subject = BlockchainBridge::new( + Box::new(blockchain_interface_mock), + Box::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 before = SystemTime::now(); + + let _ = addr.try_send(retrieve_transactions).unwrap(); + + System::current().stop(); + system.run(); + let after = SystemTime::now(); + let retrieve_transactions_params = retrieve_transactions_params_arc.lock().unwrap(); + assert_eq!( + *retrieve_transactions_params, + vec![(BlockNumber::Latest, BlockNumber::Latest, earning_wallet)] + ); + 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!( + received_payments, + &ReceivedPayments { + timestamp: received_payments.timestamp, + payments: expected_transactions.transactions, + new_start_block: 8675309u64, + response_skeleton_opt: Some(ResponseSkeleton { + client_id: 1234, + context_id: 4321 + }), + } + ); + } + + #[test] + fn handle_retrieve_transactions_when_get_block_number_fails_uses_latest_for_start_and_end_block( + ) { + let retrieve_transactions_params_arc = Arc::new(Mutex::new(vec![])); + let earning_wallet = make_wallet("somewallet"); + let amount = 42; + let amount2 = 55; + let expected_transactions = RetrievedBlockchainTransactions { + new_start_block: BlockNumber::Number(98765u64.into()), + transactions: vec![ + BlockchainTransaction { + block_number: 77, + from: earning_wallet.clone(), + wei_amount: amount, + }, + BlockchainTransaction { + block_number: 99, + from: earning_wallet.clone(), + wei_amount: amount2, + }, + ], + }; + + let system = System::new( + "handle_retrieve_transactions_when_get_block_number_fails_uses_latest_for_start_and_end_block", + ); + let (accountant, _, accountant_recording_arc) = make_recorder(); + let persistent_config = PersistentConfigurationMock::new() + .max_block_count_result(Ok(Some(DEFAULT_MAX_BLOCK_COUNT))) + .start_block_result(Ok(None)); + let latest_block_number = LatestBlockNumber::Err(BlockchainError::QueryFailed( + "Failed to read from block chain service".to_string(), + )); + let lower_interface = + LowBlockchainIntMock::default().get_block_number_result(latest_block_number); + let blockchain_interface = BlockchainInterfaceMock::default() + .retrieve_transactions_params(&retrieve_transactions_params_arc) + .retrieve_transactions_result(Ok(expected_transactions.clone())) + .lower_interface_results(Box::new(lower_interface)); + let subject = BlockchainBridge::new( + Box::new(blockchain_interface), + Box::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 before = SystemTime::now(); + + let _ = addr.try_send(retrieve_transactions).unwrap(); + + System::current().stop(); + system.run(); + let after = SystemTime::now(); + let retrieve_transactions_params = retrieve_transactions_params_arc.lock().unwrap(); + assert_eq!( + *retrieve_transactions_params, + vec![(BlockNumber::Latest, BlockNumber::Latest, earning_wallet)] + ); + 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!( + received_payments, + &ReceivedPayments { + timestamp: received_payments.timestamp, + payments: expected_transactions.transactions, + new_start_block: 98765, + response_skeleton_opt: Some(ResponseSkeleton { + client_id: 1234, + context_id: 4321 + }), + } + ); } #[test] @@ -1464,7 +1545,7 @@ mod tests { let amount = 42; let amount2 = 55; let expected_transactions = RetrievedBlockchainTransactions { - new_start_block: 9876, + new_start_block: BlockNumber::Number(9876.into()), transactions: vec![ BlockchainTransaction { block_number: 7, @@ -1487,12 +1568,11 @@ mod tests { .lower_interface_results(Box::new(lower_interface)); let persistent_config = PersistentConfigurationMock::new() .max_block_count_result(Ok(Some(10000u64))) - .start_block_result(Ok(6)); + .start_block_result(Ok(Some(6))); let subject = BlockchainBridge::new( Box::new(blockchain_interface_mock), Box::new(persistent_config), false, - Some(make_wallet("consuming")), ); let addr = subject.start(); let subject_subs = BlockchainBridge::make_subs_from(&addr); @@ -1546,13 +1626,13 @@ mod tests { LowBlockchainIntMock::default().get_block_number_result(Ok(0u64.into())); let blockchain_interface_mock = BlockchainInterfaceMock::default() .retrieve_transactions_result(Ok(RetrievedBlockchainTransactions { - new_start_block: 7, + new_start_block: BlockNumber::Number(7.into()), transactions: vec![], })) .lower_interface_results(Box::new(lower_interface)); let persistent_config = PersistentConfigurationMock::new() .max_block_count_result(Ok(Some(10000u64))) - .start_block_result(Ok(6)); + .start_block_result(Ok(Some(6))); let (accountant, _, accountant_recording_arc) = make_recorder(); let system = System::new( "processing_of_received_payments_continues_even_if_no_payments_are_detected", @@ -1561,7 +1641,6 @@ mod tests { Box::new(blockchain_interface_mock), Box::new(persistent_config), false, - None, //not needed in this test ); let addr = subject.start(); let subject_subs = BlockchainBridge::make_subs_from(&addr); @@ -1615,7 +1694,6 @@ mod tests { Box::new(blockchain_interface), Box::new(persistent_config), false, - None, //not needed in this test ); let retrieve_transactions = RetrieveTransactions { recipient: make_wallet("somewallet"), @@ -1625,38 +1703,6 @@ mod tests { let _ = subject.handle_retrieve_transactions(retrieve_transactions); } - #[test] - #[should_panic(expected = "Writing max_block_count failed: QueryFailed")] - fn handle_retrieve_transactions_panics_if_writing_max_block_count_failed() { - let retrieve_transactions_params_arc = Arc::new(Mutex::new(vec![])); - let earning_wallet = make_wallet("somewallet"); - let latest_block_number = LatestBlockNumber::Ok(1024u64.into()); - let lower_interface = - LowBlockchainIntMock::default().get_block_number_result(latest_block_number); - let blockchain_interface = - BlockchainInterfaceMock::default() - .retrieve_transactions_params(&retrieve_transactions_params_arc) - .retrieve_transactions_result(Err(BlockchainError::QueryFailed("RPC error: Error { code: ServerError(-32005), message: \"eth_getLogs is limited to 1024 block range. Please check the parameter requirements at https://docs.blockpi.io/documentations/api-reference\", data: None }".to_string()))) - .lower_interface_results(Box::new(lower_interface)); - let persistent_config = PersistentConfigurationMock::new() - .max_block_count_result(Ok(Some(10000u64))) - .start_block_result(Ok(6)) - .set_max_block_count_result(Err(PersistentConfigError::TransactionError)); - let mut subject = BlockchainBridge::new( - Box::new(blockchain_interface), - Box::new(persistent_config), - false, - Some(make_wallet("consuming")), - ); - - let retrieve_transactions = RetrieveTransactions { - recipient: earning_wallet, - response_skeleton_opt: None, - }; - - let _ = subject.handle_retrieve_transactions(retrieve_transactions); - } - fn success_handler( _bcb: &mut BlockchainBridge, _msg: RetrieveTransactions, @@ -1678,7 +1724,6 @@ mod tests { Box::new(BlockchainInterfaceMock::default()), Box::new(PersistentConfigurationMock::new()), false, - None, //not needed in this test ); let system = System::new("test"); subject.scan_error_subs_opt = Some(accountant.start().recipient()); @@ -1710,7 +1755,6 @@ mod tests { Box::new(BlockchainInterfaceMock::default()), Box::new(PersistentConfigurationMock::new()), false, - None, //not needed in this test ); let system = System::new("test"); subject.scan_error_subs_opt = Some(accountant.start().recipient()); @@ -1749,7 +1793,6 @@ mod tests { Box::new(BlockchainInterfaceMock::default()), Box::new(PersistentConfigurationMock::new()), false, - None, //not needed in this test ); let system = System::new("test"); subject.scan_error_subs_opt = Some(accountant.start().recipient()); @@ -1794,7 +1837,6 @@ mod tests { Box::new(BlockchainInterfaceMock::default()), Box::new(PersistentConfigurationMock::default()), crashable, - None, ); prove_that_crash_request_handler_is_hooked_up(subject, CRASH_KEY); @@ -1807,7 +1849,6 @@ mod tests { Box::new(BlockchainInterfaceMock::default()), Box::new(PersistentConfigurationMock::default()), false, - None, ); let max_block_count = subject.extract_max_block_count(result); @@ -1821,14 +1862,13 @@ mod tests { Box::new(BlockchainInterfaceMock::default()), Box::new(PersistentConfigurationMock::default()), false, - None, ); let max_block_count = subject.extract_max_block_count(result); assert_eq!(Some(100000u64), max_block_count); } /* - POKT (Polygon mainnet and mumbai) + POKT (Polygon mainnet and amoy) {"jsonrpc":"2.0","id":7,"error":{"message":"You cannot query logs for more than 100000 blocks at once.","code":-32064}} */ /* @@ -1842,7 +1882,6 @@ mod tests { Box::new(BlockchainInterfaceMock::default()), Box::new(PersistentConfigurationMock::default()), false, - None, ); let max_block_count = subject.extract_max_block_count(result); @@ -1860,7 +1899,6 @@ mod tests { Box::new(BlockchainInterfaceMock::default()), Box::new(PersistentConfigurationMock::default()), false, - None, ); let max_block_count = subject.extract_max_block_count(result); @@ -1878,7 +1916,6 @@ mod tests { Box::new(BlockchainInterfaceMock::default()), Box::new(PersistentConfigurationMock::default()), false, - None, ); let max_block_count = subject.extract_max_block_count(result); @@ -1898,13 +1935,25 @@ mod tests { Box::new(BlockchainInterfaceMock::default()), Box::new(PersistentConfigurationMock::default()), false, - None, ); let max_block_count = subject.extract_max_block_count(result); 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 subject = BlockchainBridge::new( + Box::new(BlockchainInterfaceMock::default()), + Box::new(PersistentConfigurationMock::default()), + false, + ); + let max_block_count = subject.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( @@ -1914,7 +1963,6 @@ mod tests { Box::new(BlockchainInterfaceMock::default()), Box::new(PersistentConfigurationMock::default()), false, - None, ); let max_block_count = subject.extract_max_block_count(result); 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 083f75822..a4425a82f 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs @@ -119,12 +119,12 @@ where .build(); let fallback_start_block_number = match end_block { - BlockNumber::Number(eb) => eb.as_u64(), + BlockNumber::Number(eb) => Some(eb.as_u64()), _ => { if let BlockNumber::Number(start_block_number) = start_block { - start_block_number.as_u64() + 1u64 + Some(start_block_number.as_u64() + 1u64) } else { - panic!("start_block of Latest, Earliest, and Pending are not supported"); + None } } }; @@ -134,15 +134,15 @@ where let logger = self.logger.clone(); match self.web3_batch.transport().submit_batch().wait() { Ok(_) => { - let response_block_number = match block_request.wait() { + let response_block_number_opt = match block_request.wait() { Ok(block_nbr) => { debug!(logger, "Latest block number: {}", block_nbr.as_u64()); - block_nbr.as_u64() + Some(block_nbr.as_u64()) } Err(_) => { debug!( logger, - "Using fallback block number: {}", fallback_start_block_number + "Using fallback block number: {:?}", fallback_start_block_number ); fallback_start_block_number } @@ -178,16 +178,18 @@ where // was not successful. let transaction_max_block_number = self .find_largest_transaction_block_number( - response_block_number, + response_block_number_opt, &transactions, ); debug!( logger, - "Discovered transaction max block nbr: {}", + "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 + .map(|nsb| BlockNumber::Number((1u64 + nsb).into())) + .unwrap_or(BlockNumber::Latest), transactions, }) } @@ -579,7 +581,9 @@ where fn web3_gas_limit_const_part(chain: Chain) -> u64 { match chain { Chain::EthMainnet | Chain::EthRopsten | Chain::Dev => 55_000, - Chain::PolyMainnet | Chain::PolyMumbai => 70_000, + Chain::PolyMainnet | Chain::PolyAmoy | Chain::BaseMainnet | Chain::BaseSepolia => { + 70_000 + } } } @@ -601,15 +605,18 @@ where fn find_largest_transaction_block_number( &self, - 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() } } } @@ -676,6 +683,7 @@ mod tests { BlockchainTransaction, RpcPayablesFailure, }; use indoc::indoc; + use sodiumoxide::hex; use std::str::FromStr; use std::sync::{Arc, Mutex}; use std::time::SystemTime; @@ -830,7 +838,7 @@ mod tests { assert_eq!( result, RetrievedBlockchainTransactions { - new_start_block: 0x4be664, + new_start_block: BlockNumber::Number(0x4be664.into()), transactions: vec![ BlockchainTransaction { block_number: 0x4be663, @@ -892,7 +900,7 @@ mod tests { assert_eq!( result, RetrievedBlockchainTransactions { - new_start_block: 1 + end_block_nbr, + new_start_block: BlockNumber::Number((1 + end_block_nbr).into()), transactions: vec![] } ); @@ -1001,7 +1009,7 @@ mod tests { assert_eq!( result, Ok(RetrievedBlockchainTransactions { - new_start_block: 1 + end_block_nbr, + new_start_block: BlockNumber::Number((1 + end_block_nbr).into()), transactions: vec![] }) ); @@ -1043,7 +1051,39 @@ mod tests { assert_eq!( result, Ok(RetrievedBlockchainTransactions { - new_start_block: 1 + expected_fallback_start_block, + new_start_block: BlockNumber::Number((1 + expected_fallback_start_block).into()), + transactions: vec![] + }) + ); + } + + #[test] + fn blockchain_interface_retrieve_transactions_start_and_end_blocks_can_be_latest() { + let port = find_free_port(); + let contains_error_causing_to_pick_fallback_value = br#"[{"jsonrpc":"2.0","id":1,"result":"error"},{"jsonrpc":"2.0","id":2,"result":[{"address":"0xcd6c588e005032dd882cd43bf53a32129be81302","blockHash":"0x1a24b9169cbaec3f6effa1f600b70c7ab9e8e86db44062b49132a4415d26732a","data":"0x0000000000000000000000000000000000000000000000000010000000000000","logIndex":"0x0","removed":false,"topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000003f69f9efd4f2592fd70be8c32ecd9dce71c472fc","0x000000000000000000000000adc1853c7859369639eb414b6342b36288fe6092"],"transactionHash":"0x955cec6ac4f832911ab894ce16aa22c3003f46deff3f7165b32700d2f5ff0681","transactionIndex":"0x0"}]}]"#; + let _test_server = TestServer::start( + port, + vec![contains_error_causing_to_pick_fallback_value.to_vec()], + ); + let (event_loop_handle, transport) = Http::with_max_parallel( + &format!("http://{}:{}", &Ipv4Addr::LOCALHOST, port), + REQUESTS_IN_PARALLEL, + ) + .unwrap(); + let chain = TEST_DEFAULT_CHAIN; + let subject = BlockchainInterfaceWeb3::new(transport, event_loop_handle, chain); + + let result = subject.retrieve_transactions( + BlockNumber::Latest, + BlockNumber::Latest, + &make_wallet("earning-wallet"), + ); + + let expected_new_start_block = BlockNumber::Latest; + assert_eq!( + result, + Ok(RetrievedBlockchainTransactions { + new_start_block: expected_new_start_block, transactions: vec![] }) ); @@ -1107,7 +1147,7 @@ mod tests { #[test] fn build_of_the_blockchain_agent_fails_on_fetching_gas_price() { - let chain = Chain::PolyMumbai; + let chain = Chain::PolyAmoy; let wallet = make_wallet("abc"); let persistent_config = PersistentConfigurationMock::new().gas_price_result(Err( PersistentConfigError::UninterpretableValue("booga".to_string()), @@ -1218,7 +1258,7 @@ mod tests { //exercising also the layer of web3 functions, but the transport layer is mocked init_test_logging(); let send_batch_params_arc = Arc::new(Mutex::new(vec![])); - //we compute the hashes ourselves during the batch preparation and so we don't care about + //we compute the hashes ourselves during the batch preparation, and so we don't care about //the same ones coming back with the response; we use the returned OKs as indicators of success only. //Any eventual rpc errors brought back are processed as well... let expected_batch_responses = vec![ @@ -1281,9 +1321,14 @@ mod tests { Call::MethodCall(MethodCall { jsonrpc: Some(V2), method: "eth_sendRawTransaction".to_string(), - params: Params::Array(vec![Value::String("0xf8a906851bf08eb00082db6894384dec25e03f94931767ce4c3556168468ba24c380b844a9059cbb000\ - 00000000000000000000000000000000000000000000000000000773132330000000000000000000000000000000000000000000000000c7d713b49da00002aa060b9f375c06f56\ - 41951606643d76ef999d32ae02f6b6cd62c9275ebdaa36a390a0199c3d8644c428efd5e0e0698c031172ac6873037d90dcca36a1fbf2e67960ff".to_string())]), + params: Params::Array(vec![Value::String( + "0xf8a906851bf08eb00082db6894384de\ + c25e03f94931767ce4c3556168468ba24c380b844a9059cbb000000000000000000000000000000000000000000\ + 00000000000000773132330000000000000000000000000000000000000000000000000c7d713b49da00002aa06\ + 0b9f375c06f5641951606643d76ef999d32ae02f6b6cd62c9275ebdaa36a390a0199c3d8644c428efd5e0e0698c\ + 031172ac6873037d90dcca36a1fbf2e67960ff" + .to_string() + )]), id: Id::Num(1) }) ), @@ -1292,9 +1337,14 @@ mod tests { Call::MethodCall(MethodCall { jsonrpc: Some(V2), method: "eth_sendRawTransaction".to_string(), - params: Params::Array(vec![Value::String("0xf8a907851bf08eb00082dae894384dec25e03f94931767ce4c3556168468ba24c380b844a9059cbb000\ - 000000000000000000000000000000000000000000000000000007735353500000000000000000000000000000000000000000000000000000000075bcd1529a00e61352bb2ac9b\ - 32b411206250f219b35cdc85db679f3e2416daac4f730a12f1a02c2ad62759d86942f3af2b8915ecfbaa58268010e00d32c18a49a9fc3b9bd20a".to_string())]), + params: Params::Array(vec![Value::String( + "0xf8a907851bf08eb00082dae894384de\ + c25e03f94931767ce4c3556168468ba24c380b844a9059cbb000000000000000000000000000000000000000000\ + 000000000000007735353500000000000000000000000000000000000000000000000000000000075bcd1529a00\ + e61352bb2ac9b32b411206250f219b35cdc85db679f3e2416daac4f730a12f1a02c2ad62759d86942f3af2b8915\ + ecfbaa58268010e00d32c18a49a9fc3b9bd20a" + .to_string() + )]), id: Id::Num(1) }) ), @@ -1303,9 +1353,14 @@ mod tests { Call::MethodCall(MethodCall { jsonrpc: Some(V2), method: "eth_sendRawTransaction".to_string(), - params: Params::Array(vec![Value::String("0xf8a908851bf08eb00082db6894384dec25e03f94931767ce4c3556168468ba24c380b844a9059cbb000\ - 0000000000000000000000000000000000000000000000000000077393837000000000000000000000000000000000000000000000000007680cd2f2d34002aa02d300cc8ba7b63\ - b0147727c824a54a7db9ec083273be52a32bdca72657a3e310a042a17224b35e7036d84976a23fbe8b1a488b2bcabed1e4a2b0b03f0c9bbc38e9".to_string())]), + params: Params::Array(vec![Value::String( + "0xf8a908851bf08eb00082db6894384de\ + c25e03f94931767ce4c3556168468ba24c380b844a9059cbb000000000000000000000000000000000000000000\ + 0000000000000077393837000000000000000000000000000000000000000000000000007680cd2f2d34002aa02\ + d300cc8ba7b63b0147727c824a54a7db9ec083273be52a32bdca72657a3e310a042a17224b35e7036d84976a23f\ + be8b1a488b2bcabed1e4a2b0b03f0c9bbc38e9" + .to_string() + )]), id: Id::Num(1) }) ) @@ -1613,8 +1668,9 @@ mod tests { Subject::web3_gas_limit_const_part(Chain::PolyMainnet), 70_000 ); + assert_eq!(Subject::web3_gas_limit_const_part(Chain::PolyAmoy), 70_000); assert_eq!( - Subject::web3_gas_limit_const_part(Chain::PolyMumbai), + Subject::web3_gas_limit_const_part(Chain::BaseSepolia), 70_000 ); assert_eq!(Subject::web3_gas_limit_const_part(Chain::Dev), 55_000); @@ -1683,7 +1739,7 @@ mod tests { #[test] fn signing_error_terminates_iteration_over_accounts_and_propagates_it_all_way_up_and_out() { let transport = TestTransport::default(); - let chain = Chain::PolyMumbai; + let chain = Chain::PolyAmoy; let batch_payable_tools = BatchPayableToolsMock::::default() .sign_transaction_result(Err(Web3Error::Signing( secp256k1secrets::Error::InvalidSecretKey, @@ -1749,7 +1805,7 @@ mod tests { .batch_wide_timestamp_result(SystemTime::now()) .submit_batch_result(Err(Web3Error::Transport("Transaction crashed".to_string()))); let consuming_wallet_secret_raw_bytes = b"okay-wallet"; - let chain = Chain::PolyMumbai; + let chain = Chain::PolyAmoy; let mut subject = BlockchainInterfaceWeb3::new(transport, make_fake_event_loop_handle(), chain); subject.batch_payable_tools = Box::new(batch_payable_tools); @@ -1781,7 +1837,7 @@ mod tests { secp256k1secrets::Error::InvalidSecretKey, ))); let consuming_wallet_secret_raw_bytes = b"okay-wallet"; - let chain = Chain::PolyMumbai; + let chain = Chain::PolyAmoy; let mut subject = BlockchainInterfaceWeb3::new(transport, make_fake_event_loop_handle(), chain); subject.batch_payable_tools = Box::new(batch_payable_tools); @@ -1801,10 +1857,6 @@ mod tests { ); } - const TEST_PAYMENT_AMOUNT: u128 = 1_000_000_000_000; - const TEST_GAS_PRICE_ETH: u64 = 110; - const TEST_GAS_PRICE_POLYGON: u64 = 50; - fn test_consuming_wallet_with_secret() -> Wallet { let key_pair = Bip32EncryptionKeyProvider::from_raw_secret( &decode_hex("97923d8fd8de4a00f912bfb77ef483141dec551bd73ea59343ef5c4aac965d04") @@ -1832,12 +1884,16 @@ mod tests { let recipient_wallet = test_recipient_wallet(); let nonce_correct_type = U256::from(nonce); let gas_price = match chain { - Chain::EthMainnet | Chain::EthRopsten | Chain::Dev => TEST_GAS_PRICE_ETH, - Chain::PolyMainnet | Chain::PolyMumbai => TEST_GAS_PRICE_POLYGON, + Chain::EthMainnet | Chain::EthRopsten | Chain::Dev => 110, + Chain::PolyMainnet | Chain::PolyAmoy => 55, + // It performs on even cheaper fees, but we're + // limited by the units here + Chain::BaseMainnet | Chain::BaseSepolia => 1, }; + let payment_size_wei = gwei_to_wei(1_000_u64); let payable_account = make_payable_account_with_wallet_and_balance_and_timestamp_opt( recipient_wallet, - TEST_PAYMENT_AMOUNT, + payment_size_wei, None, ); @@ -1852,77 +1908,106 @@ mod tests { .unwrap(); let byte_set_to_compare = signed_transaction.raw_transaction.0; - assert_eq!(byte_set_to_compare.as_slice(), template) + assert_eq!( + byte_set_to_compare, + template, + "Actual signed transaction {} does not match {} as expected", + hex::encode(byte_set_to_compare.clone()), + hex::encode(template.to_vec()) + ) } - //with a real confirmation through a transaction sent with this data to the network + // Transaction with this input was verified on the test network #[test] - fn web3_interface_signing_a_transaction_works_for_polygon_mumbai() { - let chain = Chain::PolyMumbai; - let nonce = 5; - // signed_transaction_data changed after we changed the contract address of polygon matic - let signed_transaction_data = "f8ad05850ba43b740083011980949b27034acabd44223fb23d628ba4849867ce1db280b844a9059cbb0000000000000000000000007788df76bbd9a0c7c3e5bf0f77bb28c60a167a7b000000000000000000000000000000000000000000000000000000e8d4a5100083027126a09fdbbd7064d3b7240f5422b2164aaa13d62f0946a683d82ee26f97f242570d90a077b49dbb408c20d73e0666ba0a77ac888bf7a9cb14824a5f35c97217b9bc0a5a"; + fn web3_interface_signing_a_transaction_works_for_polygon_amoy() { + let chain = Chain::PolyAmoy; + let nonce = 4; + let signed_transaction_data = "\ + f8ad04850cce4166008301198094d98c3ebd6b7f9b7cda2449ecac00d1e5f47a819380b844a9059cbb000000000\ + 0000000000000007788df76bbd9a0c7c3e5bf0f77bb28c60a167a7b000000000000000000000000000000000000\ + 000000000000000000e8d4a5100083027127a0ddd78a41c42b7a409c281292f7c6aedefab8b461d87371fe402b4\ + b0804a092f2a04b1b599ac2c1ff07bb3d40d3698c454691c3b70d99f1e5d840c852e968c96a10"; + let in_bytes = decode_hex(signed_transaction_data).unwrap(); + + assert_that_signed_transactions_agrees_with_template(chain, nonce, &in_bytes) + } + #[test] + fn web3_interface_signing_a_transaction_works_for_base_sepolia() { + let chain = Chain::BaseSepolia; + let nonce = 2; + let signed_transaction_data = "\ + f8ac02843b9aca008301198094898e1ce720084a902bc37dd822ed6d6a5f027e1080b844a9059cbb00000000000\ + 00000000000007788df76bbd9a0c7c3e5bf0f77bb28c60a167a7b00000000000000000000000000000000000000\ + 0000000000000000e8d4a510008302948ca07b57223b566ade08ec817770c8b9ae94373edbefc13372c3463cf7b\ + 6ce542231a020991f2ff180a12cbc2745465a4e710da294b890901a3887519b191c3a69cd4f"; let in_bytes = decode_hex(signed_transaction_data).unwrap(); assert_that_signed_transactions_agrees_with_template(chain, nonce, &in_bytes) } - //with a real confirmation through a transaction sent with this data to the network + // Transaction with this input was verified on the test network #[test] fn web3_interface_signing_a_transaction_works_for_eth_ropsten() { let chain = Chain::EthRopsten; - let nonce = 1; //must stay like this! - let signed_transaction_data = "f8a90185199c82cc0082dee894384dec25e03f94931767ce4c3556168468ba24c380b844a9059cbb0000000000000000000000007788df76bbd9a0c7c3e5bf0f77bb28c60a167a7b000000000000000000000000000000000000000000000000000000e8d4a510002aa0635fbb3652e1c3063afac6ffdf47220e0431825015aef7daff9251694e449bfca00b2ed6d556bd030ac75291bf58817da15a891cd027a4c261bb80b51f33b78adf"; + let nonce = 1; + let signed_transaction_data = "\ + f8a90185199c82cc0082dee894384dec25e03f94931767ce4c3556168468ba24c380b844a9059cbb00000000000\ + 00000000000007788df76bbd9a0c7c3e5bf0f77bb28c60a167a7b00000000000000000000000000000000000000\ + 0000000000000000e8d4a510002aa0635fbb3652e1c3063afac6ffdf47220e0431825015aef7daff9251694e449\ + bfca00b2ed6d556bd030ac75291bf58817da15a891cd027a4c261bb80b51f33b78adf"; let in_bytes = decode_hex(signed_transaction_data).unwrap(); assert_that_signed_transactions_agrees_with_template(chain, nonce, &in_bytes) } - //not confirmed on the real network + // Unconfirmed on the real network #[test] fn web3_interface_signing_a_transaction_for_polygon_mainnet() { let chain = Chain::PolyMainnet; let nonce = 10; - //generated locally - let signed_transaction_data = [ - 248, 172, 10, 133, 11, 164, 59, 116, 0, 131, 1, 25, 128, 148, 238, 154, 53, 47, 106, - 172, 74, 241, 165, 185, 244, 103, 246, 169, 62, 15, 251, 233, 221, 53, 128, 184, 68, - 169, 5, 156, 187, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 119, 136, 223, 118, 187, 217, - 160, 199, 195, 229, 191, 15, 119, 187, 40, 198, 10, 22, 122, 123, 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, 232, 212, 165, 16, 0, 130, - 1, 53, 160, 7, 203, 40, 44, 202, 233, 15, 5, 64, 218, 199, 239, 94, 126, 152, 2, 108, - 30, 157, 75, 124, 129, 117, 27, 109, 163, 132, 27, 11, 123, 137, 10, 160, 18, 170, 130, - 198, 73, 190, 158, 235, 0, 77, 118, 213, 244, 229, 225, 143, 156, 214, 219, 204, 193, - 155, 199, 164, 162, 31, 134, 51, 139, 130, 152, 104, - ]; + let signed_transaction_data = "f8ac0a850cce4166008301198094ee9a352f6aac4af1a5b9f467f6a\ + 93e0ffbe9dd3580b844a9059cbb0000000000000000000000007788df76bbd9a0c7c3e5bf0f77bb28c60a167a7b\ + 000000000000000000000000000000000000000000000000000000e8d4a51000820135a0c89f4dca80c3437a23c\ + c1a41ab59fd5206b0c0e1293d975242e8482c44838c75a075429a84b761db83d648dc4298480f6b2cedc110c134\ + 065ed8955e66c7504469"; + let in_bytes = decode_hex(signed_transaction_data).unwrap(); - assert_that_signed_transactions_agrees_with_template(chain, nonce, &signed_transaction_data) + assert_that_signed_transactions_agrees_with_template(chain, nonce, &in_bytes) } - //not confirmed on the real network + // Unconfirmed on the real network #[test] fn web3_interface_signing_a_transaction_for_eth_mainnet() { let chain = Chain::EthMainnet; let nonce = 10; - //generated locally - let signed_transaction_data = [ - 248, 169, 10, 133, 25, 156, 130, 204, 0, 130, 222, 232, 148, 6, 243, 195, 35, 240, 35, - 140, 114, 191, 53, 1, 16, 113, 242, 181, 183, 244, 58, 5, 76, 128, 184, 68, 169, 5, - 156, 187, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 119, 136, 223, 118, 187, 217, 160, 199, - 195, 229, 191, 15, 119, 187, 40, 198, 10, 22, 122, 123, 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, 232, 212, 165, 16, 0, 38, 160, 199, - 155, 76, 106, 39, 227, 3, 151, 90, 117, 245, 211, 86, 98, 187, 117, 120, 103, 165, 131, - 99, 72, 36, 211, 10, 224, 252, 104, 51, 200, 230, 158, 160, 84, 18, 140, 248, 119, 22, - 193, 14, 148, 253, 48, 59, 185, 11, 38, 152, 103, 150, 120, 60, 74, 56, 159, 206, 22, - 15, 73, 173, 153, 11, 76, 74, - ]; + let signed_transaction_data = "f8a90a85199c82cc0082dee89406f3c323f0238c72bf35011071f2b\ + 5b7f43a054c80b844a9059cbb0000000000000000000000007788df76bbd9a0c7c3e5bf0f77bb28c60a167a7b00\ + 0000000000000000000000000000000000000000000000000000e8d4a5100026a0c79b4c6a27e303975a75f5d35\ + 662bb757867a583634824d30ae0fc6833c8e69ea054128cf87716c10e94fd303bb90b26986796783c4a389fce16\ + 0f49ad990b4c4a"; + let in_bytes = decode_hex(signed_transaction_data).unwrap(); + + assert_that_signed_transactions_agrees_with_template(chain, nonce, &in_bytes) + } + + // Unconfirmed on the real network + #[test] + fn web3_interface_signing_a_transaction_for_base_mainnet() { + let chain = Chain::BaseMainnet; + let nonce = 124; + let signed_transaction_data = "f8ab7c843b9aca00830119809445d9c101a3870ca5024582fd788f4\ + e1e8f7971c380b844a9059cbb0000000000000000000000007788df76bbd9a0c7c3e5bf0f77bb28c60a167a7b00\ + 0000000000000000000000000000000000000000000000000000e8d4a5100082422da0587b5f8401225d5cf6267\ + 6f51f376f085805851e2e59c5253eb2834612295bdba05b6963872bac7eeafb38191079e8c8df919c193839022b\ + d57b91ace5a8638034"; + let in_bytes = decode_hex(signed_transaction_data).unwrap(); - assert_that_signed_transactions_agrees_with_template(chain, nonce, &signed_transaction_data) + assert_that_signed_transactions_agrees_with_template(chain, nonce, &in_bytes) } - //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 + // 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_eth_mainnet() { let signatures = &[ @@ -1955,8 +2040,8 @@ mod tests { assert_signature(Chain::EthMainnet, signatures) } - //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 + // 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 = &[ diff --git a/node/src/blockchain/blockchain_interface/data_structures/mod.rs b/node/src/blockchain/blockchain_interface/data_structures/mod.rs index d1d785aae..d8b86d5d9 100644 --- a/node/src/blockchain/blockchain_interface/data_structures/mod.rs +++ b/node/src/blockchain/blockchain_interface/data_structures/mod.rs @@ -3,7 +3,7 @@ pub mod errors; use crate::accountant::db_access_objects::pending_payable_dao::PendingPayable; use crate::sub_lib::wallet::Wallet; -use web3::types::H256; +use web3::types::{BlockNumber, H256}; use web3::Error; #[derive(Clone, Debug, Eq, PartialEq)] @@ -13,9 +13,9 @@ pub struct BlockchainTransaction { pub wei_amount: u128, } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq)] pub struct RetrievedBlockchainTransactions { - pub new_start_block: u64, + pub new_start_block: BlockNumber, pub transactions: Vec, } diff --git a/node/src/blockchain/test_utils.rs b/node/src/blockchain/test_utils.rs index 4a8f4ecd6..16f7d21da 100644 --- a/node/src/blockchain/test_utils.rs +++ b/node/src/blockchain/test_utils.rs @@ -331,7 +331,7 @@ pub fn all_chains() -> [Chain; 4] { [ Chain::EthMainnet, Chain::PolyMainnet, - Chain::PolyMumbai, + Chain::PolyAmoy, Chain::Dev, ] } diff --git a/node/src/bootstrapper.rs b/node/src/bootstrapper.rs index f1ece6d54..fccdf83c4 100644 --- a/node/src/bootstrapper.rs +++ b/node/src/bootstrapper.rs @@ -400,7 +400,6 @@ impl BootstrapperConfig { neighborhood_config: NeighborhoodConfig { mode: NeighborhoodMode::ZeroHop, min_hops: DEFAULT_MIN_HOPS, - country: "ZZ".to_string(), }, when_pending_too_long_sec: DEFAULT_PENDING_TOO_LONG_SEC, } @@ -1156,7 +1155,7 @@ mod tests { "--real-user", "123:456:/home/booga", "--chain", - "polygon-mumbai", + "polygon-amoy", ])) .unwrap(); @@ -1240,7 +1239,6 @@ mod tests { let neighborhood_config = NeighborhoodConfig { mode: NeighborhoodMode::OriginateOnly(vec![], rate_pack(9)), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }; let earning_wallet = make_wallet("earning wallet"); let consuming_wallet_opt = Some(make_wallet("consuming wallet")); @@ -1861,7 +1859,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }; config.data_directory = data_dir.clone(); config.clandestine_port_opt = Some(port); @@ -1932,7 +1929,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }; config.data_directory = data_dir.clone(); config.clandestine_port_opt = None; @@ -1982,7 +1978,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }; let listener_handler = ListenerHandlerNull::new(vec![]); let mut subject = BootstrapperBuilder::new() @@ -2020,7 +2015,6 @@ mod tests { cryptde, ))]), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }; let listener_handler = ListenerHandlerNull::new(vec![]); let mut subject = BootstrapperBuilder::new() @@ -2051,7 +2045,6 @@ mod tests { config.neighborhood_config = NeighborhoodConfig { mode: NeighborhoodMode::ZeroHop, min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }; let listener_handler = ListenerHandlerNull::new(vec![]); let mut subject = BootstrapperBuilder::new() @@ -2083,7 +2076,6 @@ mod tests { config.neighborhood_config = NeighborhoodConfig { mode: NeighborhoodMode::Standard(NodeAddr::default(), vec![], DEFAULT_RATE_PACK), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }; let mut subject = BootstrapperBuilder::new().config(config).build(); subject.set_up_clandestine_port(); diff --git a/node/src/daemon/mod.rs b/node/src/daemon/mod.rs index 78b8491e9..f16bcbc7f 100644 --- a/node/src/daemon/mod.rs +++ b/node/src/daemon/mod.rs @@ -161,7 +161,7 @@ impl Daemon { node_process_id: None, node_ui_port: None, verifier_tools: Box::new(VerifierToolsReal::new()), - setup_reporter: Box::new(SetupReporterReal::new(Box::new(DirsWrapperReal {}))), + setup_reporter: Box::new(SetupReporterReal::new(Box::new(DirsWrapperReal::default()))), logger: Logger::new("Daemon"), } } diff --git a/node/src/daemon/setup_reporter.rs b/node/src/daemon/setup_reporter.rs index d882ee773..69970c297 100644 --- a/node/src/daemon/setup_reporter.rs +++ b/node/src/daemon/setup_reporter.rs @@ -26,7 +26,7 @@ use crate::sub_lib::neighborhood::NodeDescriptor; use crate::sub_lib::neighborhood::{NeighborhoodMode as NeighborhoodModeEnum, DEFAULT_RATE_PACK}; use crate::sub_lib::utils::make_new_multi_config; use crate::test_utils::main_cryptde; -use clap::value_t; +use clap::{value_t, App}; use itertools::Itertools; use masq_lib::blockchains::chains::Chain as BlockChain; use masq_lib::constants::DEFAULT_CHAIN; @@ -36,8 +36,10 @@ use masq_lib::messages::{UiSetupRequestValue, UiSetupResponseValue, UiSetupRespo use masq_lib::multi_config::{ CommandLineVcl, ConfigFileVcl, EnvironmentVcl, MultiConfig, VirtualCommandLine, }; -use masq_lib::shared_schema::{shared_app, ConfiguratorError}; -use masq_lib::utils::{add_chain_specific_directory, to_string, ExpectValue}; +use masq_lib::shared_schema::{data_directory_arg, shared_app, ConfiguratorError}; +use masq_lib::utils::{ + add_chain_specific_directory, to_string, ExpectValue, DATA_DIRECTORY_DAEMON_HELP, +}; use std::collections::HashMap; use std::fmt::Display; use std::net::{IpAddr, Ipv4Addr}; @@ -67,6 +69,10 @@ pub fn setup_cluster_from(input: Vec<(&str, &str, UiSetupResponseValueStatus)>) .collect::() } +fn daemon_shared_app() -> App<'static, 'static> { + shared_app(app_head()).arg(data_directory_arg(DATA_DIRECTORY_DAEMON_HELP.as_str())) +} + pub trait SetupReporter { fn get_modified_setup( &self, @@ -222,7 +228,7 @@ impl SetupReporterReal { } pub fn get_default_params() -> SetupCluster { - let schema = shared_app(app_head()); + let schema = daemon_shared_app(); schema .p .opts @@ -492,7 +498,7 @@ impl SetupReporterReal { environment: bool, config_file: bool, ) -> Result, ConfiguratorError> { - let app = shared_app(app_head()); + let app = daemon_shared_app(); let mut vcls: Vec> = vec![]; if let Some(command_line) = command_line_opt.clone() { vcls.push(Box::new(CommandLineVcl::new(command_line))); @@ -742,7 +748,7 @@ impl ValueRetriever for DataDirectory { } impl std::default::Default for DataDirectory { fn default() -> Self { - Self::new(&DirsWrapperReal) + Self::new(&DirsWrapperReal::default()) } } impl DataDirectory { @@ -1136,7 +1142,7 @@ impl ValueRetriever for RealUser { } impl std::default::Default for RealUser { fn default() -> Self { - Self::new(&DirsWrapperReal {}) + Self::new(&DirsWrapperReal::default()) } } impl RealUser { @@ -1226,9 +1232,8 @@ mod tests { }; use crate::test_utils::{assert_string_contains, rate_pack}; use core::option::Option; - use dirs::home_dir; use masq_lib::blockchains::chains::Chain as Blockchain; - use masq_lib::blockchains::chains::Chain::PolyMumbai; + use masq_lib::blockchains::chains::Chain::PolyAmoy; use masq_lib::constants::{DEFAULT_CHAIN, DEFAULT_GAS_PRICE}; use masq_lib::messages::UiSetupResponseValueStatus::{Blank, Configured, Required, Set}; use masq_lib::test_utils::environment_guard::{ClapGuard, EnvironmentGuard}; @@ -1239,6 +1244,7 @@ mod tests { use std::convert::TryFrom; #[cfg(not(target_os = "windows"))] use std::default::Default; + use std::env::current_dir; use std::fs::{create_dir_all, File}; use std::io::Write; use std::net::IpAddr; @@ -1378,7 +1384,7 @@ mod tests { .into_iter() .map(|(name, value)| UiSetupRequestValue::new(name, value)) .collect_vec(); - let dirs_wrapper = Box::new(DirsWrapperReal); + let dirs_wrapper = Box::new(DirsWrapperReal::default()); let subject = SetupReporterReal::new(dirs_wrapper); let result = subject @@ -1436,7 +1442,7 @@ mod tests { ( "real-user", &RealUser::new(None, None, None) - .populate(&DirsWrapperReal {}) + .populate(&DirsWrapperReal::default()) .to_string(), Default, ), @@ -1496,7 +1502,7 @@ mod tests { ("scan-intervals","150|150|150",Set), ("scans", "off", Set), ]); - let dirs_wrapper = Box::new(DirsWrapperReal); + let dirs_wrapper = Box::new(DirsWrapperReal::default()); let subject = SetupReporterReal::new(dirs_wrapper); let result = subject.get_modified_setup(existing_setup, vec![]).unwrap(); @@ -1568,7 +1574,7 @@ mod tests { ].into_iter() .map (|(name, value)| UiSetupRequestValue::new(name, value)) .collect_vec(); - let dirs_wrapper = Box::new(DirsWrapperReal); + let dirs_wrapper = Box::new(DirsWrapperReal::default()); let subject = SetupReporterReal::new(dirs_wrapper); let result = subject @@ -1643,7 +1649,7 @@ mod tests { ("MASQ_SCAN_INTERVALS","133|133|111") ].into_iter() .for_each (|(name, value)| std::env::set_var (name, value)); - let dirs_wrapper = Box::new(DirsWrapperReal); + let dirs_wrapper = Box::new(DirsWrapperReal::default()); let params = vec![]; let subject = SetupReporterReal::new(dirs_wrapper); @@ -1956,7 +1962,7 @@ mod tests { ("scan-intervals", "111|111|111", Set), ("scans", "off", Set), ]); - let dirs_wrapper = Box::new(DirsWrapperReal); + let dirs_wrapper = Box::new(DirsWrapperReal::default()); let subject = SetupReporterReal::new(dirs_wrapper); let result = subject.get_modified_setup(existing_setup, params).unwrap(); @@ -2046,21 +2052,16 @@ mod tests { } #[test] - fn get_modified_setup_tilde_in_config_file_path() { + fn get_modified_setup_handles_tilde_in_config_file_and_data_directory_path() { let _guard = EnvironmentGuard::new(); let base_dir = ensure_node_home_directory_exists( "setup_reporter", - "get_modified_setup_tilde_in_data_directory", + "get_modified_setup_handles_tilde_in_config_file_and_data_directory_path", ); let data_dir = base_dir.join("data_dir"); - std::fs::create_dir_all(home_dir().expect("expect home dir").join("masqhome")).unwrap(); - let mut config_file = File::create( - home_dir() - .expect("expect home dir") - .join("masqhome") - .join("config.toml"), - ) - .unwrap(); + std::fs::create_dir_all(base_dir.join("masqhome")).unwrap(); + let config_file_path = base_dir.join("masqhome").join("config.toml"); + let mut config_file = File::create(&config_file_path).unwrap(); config_file .write_all(b"blockchain-service-url = \"https://www.mainnet.com\"\n") .unwrap(); @@ -2082,12 +2083,11 @@ mod tests { .collect_vec(); let expected_config_file_data = "https://www.mainnet.com"; - let dirs_wrapper = Box::new( - DirsWrapperMock::new() - .data_dir_result(Some(data_dir)) - .home_dir_result(Some(base_dir)), - ); - let subject = SetupReporterReal::new(dirs_wrapper); + let dirs_wrapper = DirsWrapperMock { + data_dir_result: Some(PathBuf::from(current_dir().unwrap().join(&data_dir))), + home_dir_result: Some(PathBuf::from(current_dir().unwrap().join(&base_dir))), + }; + let subject = SetupReporterReal::new(Box::new(dirs_wrapper)); let result = subject .get_modified_setup(existing_setup, incoming_setup) @@ -2264,14 +2264,10 @@ mod tests { let current_data_dir = base_dir .join("data_dir") .join("MASQ") - .join(BlockChain::PolyMumbai.rec().literal_identifier); //not a default + .join(BlockChain::PolyAmoy.rec().literal_identifier); //not a default let existing_setup = setup_cluster_from(vec![ ("blockchain-service-url", "", Required), - ( - "chain", - BlockChain::PolyMumbai.rec().literal_identifier, - Set, - ), + ("chain", BlockChain::PolyAmoy.rec().literal_identifier, Set), ("clandestine-port", "7788", Default), ("config-file", "config.toml", Default), ("consuming-private-key", "", Blank), @@ -2310,7 +2306,7 @@ mod tests { .get_modified_setup(existing_setup, incoming_setup) .unwrap_err(); - let expected_chain = PolyMumbai.rec().literal_identifier; + let expected_chain = PolyAmoy.rec().literal_identifier; let actual_chain = &resulting_setup_cluster.get("chain").unwrap().value; assert_eq!(actual_chain, expected_chain); let actual_data_directory = @@ -2338,7 +2334,7 @@ mod tests { ( "real-user", &crate::bootstrapper::RealUser::new(None, None, None) - .populate(&DirsWrapperReal {}) + .populate(&DirsWrapperReal::default()) .to_string(), Default, ), @@ -2347,7 +2343,7 @@ mod tests { .into_iter() .map(|(name, value)| UiSetupRequestValue::new(name, value)) .collect_vec(); - let dirs_wrapper = Box::new(DirsWrapperReal); + let dirs_wrapper = Box::new(DirsWrapperReal::default()); let subject = SetupReporterReal::new(dirs_wrapper); let _ = subject @@ -2375,7 +2371,7 @@ mod tests { ), ]); let incoming_setup = vec![UiSetupRequestValue::clear("neighbors")]; - let dirs_wrapper = Box::new(DirsWrapperReal); + let dirs_wrapper = Box::new(DirsWrapperReal::default()); let subject = SetupReporterReal::new(dirs_wrapper); let result = subject @@ -2487,7 +2483,7 @@ mod tests { let setup = setup_cluster_from(vec![]); let (real_user_opt, data_directory_opt, chain) = - SetupReporterReal::calculate_fundamentals(&DirsWrapperReal {}, &setup).unwrap(); + SetupReporterReal::calculate_fundamentals(&DirsWrapperReal::default(), &setup).unwrap(); assert_eq!( real_user_opt, @@ -2518,7 +2514,7 @@ mod tests { ]); let (real_user_opt, data_directory_opt, chain) = - SetupReporterReal::calculate_fundamentals(&DirsWrapperReal {}, &setup).unwrap(); + SetupReporterReal::calculate_fundamentals(&DirsWrapperReal::default(), &setup).unwrap(); assert_eq!( real_user_opt, @@ -2549,7 +2545,7 @@ mod tests { ]); let (real_user_opt, data_directory_opt, chain) = - SetupReporterReal::calculate_fundamentals(&DirsWrapperReal {}, &setup).unwrap(); + SetupReporterReal::calculate_fundamentals(&DirsWrapperReal::default(), &setup).unwrap(); assert_eq!( real_user_opt, @@ -2576,7 +2572,7 @@ mod tests { ]); let (real_user_opt, data_directory_opt, chain) = - SetupReporterReal::calculate_fundamentals(&DirsWrapperReal {}, &setup).unwrap(); + SetupReporterReal::calculate_fundamentals(&DirsWrapperReal::default(), &setup).unwrap(); assert_eq!( real_user_opt, @@ -2599,12 +2595,13 @@ mod tests { let setup = setup_cluster_from(vec![]); let (real_user_opt, data_directory_opt, chain) = - SetupReporterReal::calculate_fundamentals(&DirsWrapperReal {}, &setup).unwrap(); + SetupReporterReal::calculate_fundamentals(&DirsWrapperReal::default(), &setup).unwrap(); assert_eq!( real_user_opt, Some( - crate::bootstrapper::RealUser::new(None, None, None).populate(&DirsWrapperReal {}) + crate::bootstrapper::RealUser::new(None, None, None) + .populate(&DirsWrapperReal::default()) ) ); assert_eq!(data_directory_opt, None); @@ -2618,7 +2615,7 @@ mod tests { "setup_reporter", "blanking_a_parameter_with_a_default_produces_that_default", ); - let dirs_wrapper = Box::new(DirsWrapperReal); + let dirs_wrapper = Box::new(DirsWrapperReal::default()); let subject = SetupReporterReal::new(dirs_wrapper); let result = subject @@ -2690,7 +2687,7 @@ mod tests { .into_iter() .map(|uisrv| (uisrv.name.clone(), uisrv)) .collect(); - let subject = SetupReporterReal::new(Box::new(DirsWrapperReal {})); + let subject = SetupReporterReal::new(Box::new(DirsWrapperReal::default())); let result = subject .calculate_configured_setup(&setup, &data_directory) @@ -2712,6 +2709,7 @@ mod tests { #[test] fn config_file_not_specified_but_exists() { + let _guard = EnvironmentGuard::new(); let data_directory = ensure_node_home_directory_exists( "setup_reporter", "config_file_not_specified_but_exists", @@ -2736,7 +2734,7 @@ mod tests { .map(|uisrv| (uisrv.name.clone(), uisrv)) .collect(); - let (result, _) = SetupReporterReal::new(Box::new(DirsWrapperReal {})) + let (result, _) = SetupReporterReal::new(Box::new(DirsWrapperReal::default())) .calculate_configured_setup(&setup, &*data_directory); assert_eq!(result.get("gas-price").unwrap().value, "10".to_string()); @@ -2744,6 +2742,7 @@ mod tests { #[test] fn config_file_has_relative_directory_that_exists_in_data_directory() { + let _guard = EnvironmentGuard::new(); let data_directory = ensure_node_home_directory_exists( "setup_reporter", "config_file_has_relative_directory_that_exists_in_data_directory", @@ -2757,7 +2756,7 @@ mod tests { } let setup = vec![ //no config-file setting - UiSetupResponseValue::new("chain", "polygon-mumbai", Set), + UiSetupResponseValue::new("chain", "polygon-amoy", Set), UiSetupResponseValue::new("neighborhood-mode", "zero-hop", Set), UiSetupResponseValue::new("config-file", "booga/special.toml", Set), UiSetupResponseValue::new( @@ -2769,7 +2768,7 @@ mod tests { .into_iter() .map(|uisrv| (uisrv.name.clone(), uisrv)) .collect(); - let subject = SetupReporterReal::new(Box::new(DirsWrapperReal {})); + let subject = SetupReporterReal::new(Box::new(DirsWrapperReal::default())); let result = subject .calculate_configured_setup(&setup, &data_directory) .0; @@ -2795,7 +2794,7 @@ mod tests { .into_iter() .map(|uisrv| (uisrv.name.clone(), uisrv)) .collect(); - let subject = SetupReporterReal::new(Box::new(DirsWrapperReal {})); + let subject = SetupReporterReal::new(Box::new(DirsWrapperReal::default())); let result = subject .calculate_configured_setup(&setup, &data_directory) @@ -2833,7 +2832,7 @@ mod tests { .into_iter() .map(|uisrv| (uisrv.name.clone(), uisrv)) .collect(); - let subject = SetupReporterReal::new(Box::new(DirsWrapperReal {})); + let subject = SetupReporterReal::new(Box::new(DirsWrapperReal::default())); let result = subject.calculate_configured_setup(&setup, &data_dir).0; @@ -2848,7 +2847,7 @@ mod tests { ); let config_file_dir = config_file_dir.canonicalize().unwrap(); let config_file_path = config_file_dir.join("nonexistent.toml"); - let wrapper = DirsWrapperReal {}; + let wrapper = DirsWrapperReal::default(); let data_directory = wrapper .data_dir() .unwrap() @@ -2866,7 +2865,7 @@ mod tests { .into_iter() .map(|uisrv| (uisrv.name.clone(), uisrv)) .collect(); - let subject = SetupReporterReal::new(Box::new(DirsWrapperReal {})); + let subject = SetupReporterReal::new(Box::new(DirsWrapperReal::default())); let result = subject .calculate_configured_setup(&setup, &data_directory) @@ -2919,11 +2918,14 @@ mod tests { #[test] fn data_directory_computed_default() { - let real_user = RealUser::new(None, None, None).populate(&DirsWrapperReal {}); - let expected = - data_directory_from_context(&DirsWrapperReal {}, &real_user, Blockchain::EthMainnet) - .to_string_lossy() - .to_string(); + let real_user = RealUser::new(None, None, None).populate(&DirsWrapperReal::default()); + let expected = data_directory_from_context( + &DirsWrapperReal::default(), + &real_user, + Blockchain::EthMainnet, + ) + .to_string_lossy() + .to_string(); let mut config = BootstrapperConfig::new(); config.real_user = real_user; config.blockchain_bridge_config.chain = Blockchain::from("eth-mainnet"); @@ -3317,7 +3319,7 @@ mod tests { result, Some(( RealUser::new(None, None, None) - .populate(&DirsWrapperReal {}) + .populate(&DirsWrapperReal::default()) .to_string(), Default )) @@ -3699,7 +3701,7 @@ mod tests { "data-directory", &masqhome.to_str().unwrap(), )]; - let dirs_wrapper = Box::new(DirsWrapperReal); + let dirs_wrapper = Box::new(DirsWrapperReal::default()); let subject = SetupReporterReal::new(dirs_wrapper); let result = subject.get_modified_setup(existing_setup, incoming_setup); @@ -3716,10 +3718,10 @@ mod tests { let _guard = EnvironmentGuard::new(); let existing_setup = setup_cluster_from(vec![("real-user", "1111:1111:/home/booga", Default)]); - let incoming_setup = vec![UiSetupRequestValue::new("chain", "polygon-mumbai")]; + let incoming_setup = vec![UiSetupRequestValue::new("chain", "polygon-amoy")]; let home_directory = Path::new("/home/booga"); let data_directory = home_directory.join("data"); - let expected = data_directory.join("MASQ").join("polygon-mumbai"); + let expected = data_directory.join("MASQ").join("polygon-amoy"); let dirs_wrapper = Box::new( DirsWrapperMock::new() .data_dir_result(Some(data_directory)) diff --git a/node/src/database/config_dumper.rs b/node/src/database/config_dumper.rs index 891eac1ad..17e24899e 100644 --- a/node/src/database/config_dumper.rs +++ b/node/src/database/config_dumper.rs @@ -198,7 +198,7 @@ mod tests { .opt("--dump-config") .into(); let subject = DumpConfigRunnerReal { - dirs_wrapper: Box::new(DirsWrapperReal), + dirs_wrapper: Box::new(DirsWrapperReal::default()), }; let caught_panic = catch_unwind(AssertUnwindSafe(|| { @@ -239,7 +239,7 @@ mod tests { .opt("--dump-config") .into(); let subject = DumpConfigRunnerReal { - dirs_wrapper: Box::new(DirsWrapperReal), + dirs_wrapper: Box::new(DirsWrapperReal::default()), }; let result = subject.go(&mut holder.streams(), args_vec.as_slice()); @@ -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(), @@ -471,7 +467,7 @@ mod tests { .opt("--dump-config") .into(); let subject = DumpConfigRunnerReal { - dirs_wrapper: Box::new(DirsWrapperReal), + dirs_wrapper: Box::new(DirsWrapperReal::default()), }; let result = subject.go(&mut holder.streams(), args_vec.as_slice()); @@ -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(); @@ -579,7 +571,7 @@ mod tests { .opt("--dump-config") .into(); let subject = DumpConfigRunnerReal { - dirs_wrapper: Box::new(DirsWrapperReal), + dirs_wrapper: Box::new(DirsWrapperReal::default()), }; let result = subject.go(&mut holder.streams(), args_vec.as_slice()); @@ -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..3619cc1b4 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", @@ -658,7 +652,7 @@ mod tests { #[test] fn constants_have_correct_values() { assert_eq!(DATABASE_FILE, "node-data.db"); - assert_eq!(CURRENT_SCHEMA_VERSION, 9); + assert_eq!(CURRENT_SCHEMA_VERSION, 10); } #[test] @@ -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/database/db_migrations/db_migrator.rs b/node/src/database/db_migrations/db_migrator.rs index 746af3e26..7d1ec4f8c 100644 --- a/node/src/database/db_migrations/db_migrator.rs +++ b/node/src/database/db_migrations/db_migrator.rs @@ -10,6 +10,7 @@ use crate::database::db_migrations::migrations::migration_5_to_6::Migrate_5_to_6 use crate::database::db_migrations::migrations::migration_6_to_7::Migrate_6_to_7; use crate::database::db_migrations::migrations::migration_7_to_8::Migrate_7_to_8; use crate::database::db_migrations::migrations::migration_8_to_9::Migrate_8_to_9; +use crate::database::db_migrations::migrations::migration_9_to_10::Migrate_9_to_10; use crate::database::db_migrations::migrator_utils::{ DBMigDeclarator, DBMigrationUtilities, DBMigrationUtilitiesReal, DBMigratorInnerConfiguration, }; @@ -78,6 +79,7 @@ impl DbMigratorReal { &Migrate_6_to_7, &Migrate_7_to_8, &Migrate_8_to_9, + &Migrate_9_to_10, ] } diff --git a/node/src/database/db_migrations/migrations/migration_8_to_9.rs b/node/src/database/db_migrations/migrations/migration_8_to_9.rs index c5928edb6..4bf95e955 100644 --- a/node/src/database/db_migrations/migrations/migration_8_to_9.rs +++ b/node/src/database/db_migrations/migrations/migration_8_to_9.rs @@ -43,27 +43,28 @@ mod tests { let _ = bring_db_0_back_to_life_and_return_connection(&db_path); let subject = DbInitializerReal::default(); + let result = subject.initialize_to_version( + &dir_path, + 8, + DbInitializationConfig::create_or_migrate(make_external_data()), + ); + + assert!(result.is_ok()); + let result = subject.initialize_to_version( &dir_path, 9, DbInitializationConfig::create_or_migrate(make_external_data()), ); + let connection = result.unwrap(); let (mp_value, mp_encrypted) = retrieve_config_row(connection.as_ref(), "max_block_count"); let (cs_value, cs_encrypted) = retrieve_config_row(connection.as_ref(), "schema_version"); assert_eq!(mp_value, None); assert_eq!(mp_encrypted, false); - assert_eq!(cs_value, Some("9".to_string())); + assert_eq!(cs_value, Some(9.to_string())); assert_eq!(cs_encrypted, false); TestLogHandler::new().assert_logs_contain_in_order(vec![ - "DbMigrator: Database successfully migrated from version 0 to 1", - "DbMigrator: Database successfully migrated from version 1 to 2", - "DbMigrator: Database successfully migrated from version 2 to 3", - "DbMigrator: Database successfully migrated from version 3 to 4", - "DbMigrator: Database successfully migrated from version 4 to 5", - "DbMigrator: Database successfully migrated from version 5 to 6", - "DbMigrator: Database successfully migrated from version 6 to 7", - "DbMigrator: Database successfully migrated from version 7 to 8", "DbMigrator: Database successfully migrated from version 8 to 9", ]); } diff --git a/node/src/database/db_migrations/migrations/migration_9_to_10.rs b/node/src/database/db_migrations/migrations/migration_9_to_10.rs new file mode 100644 index 000000000..7622ef01f --- /dev/null +++ b/node/src/database/db_migrations/migrations/migration_9_to_10.rs @@ -0,0 +1,71 @@ +use crate::database::db_migrations::db_migrator::DatabaseMigration; +use crate::database::db_migrations::migrator_utils::DBMigDeclarator; + +#[allow(non_camel_case_types)] +pub struct Migrate_9_to_10; + +impl DatabaseMigration for Migrate_9_to_10 { + fn migrate<'a>( + &self, + declaration_utils: Box, + ) -> rusqlite::Result<()> { + declaration_utils.execute_upon_transaction(&[ + &"INSERT INTO config (name, value, encrypted) VALUES ('max_block_count', 100000, 0) ON CONFLICT(name) DO UPDATE SET value = 100000 WHERE name = 'max_block_count'" + ]) + } + + fn old_version(&self) -> usize { + 9 + } +} + +#[cfg(test)] +mod tests { + use crate::database::db_initializer::{ + DbInitializationConfig, DbInitializer, DbInitializerReal, DATABASE_FILE, + }; + use crate::test_utils::database_utils::{ + bring_db_0_back_to_life_and_return_connection, make_external_data, retrieve_config_row, + }; + use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; + use masq_lib::test_utils::utils::ensure_node_home_directory_exists; + use std::fs::create_dir_all; + + #[test] + fn migration_from_9_to_10_is_properly_set() { + init_test_logging(); + let dir_path = ensure_node_home_directory_exists( + "db_migrations", + "migration_from_9_to_10_is_properly_set", + ); + create_dir_all(&dir_path).unwrap(); + let db_path = dir_path.join(DATABASE_FILE); + let _ = bring_db_0_back_to_life_and_return_connection(&db_path); + let subject = DbInitializerReal::default(); + + let result = subject.initialize_to_version( + &dir_path, + 9, + DbInitializationConfig::create_or_migrate(make_external_data()), + ); + + assert!(result.is_ok()); + + let result = subject.initialize_to_version( + &dir_path, + 10, + DbInitializationConfig::create_or_migrate(make_external_data()), + ); + + let connection = result.unwrap(); + let (mp_value, mp_encrypted) = retrieve_config_row(connection.as_ref(), "max_block_count"); + let (cs_value, cs_encrypted) = retrieve_config_row(connection.as_ref(), "schema_version"); + assert_eq!(mp_value, Some(100_000u64.to_string())); + assert_eq!(mp_encrypted, false); + assert_eq!(cs_value, Some(10.to_string())); + assert_eq!(cs_encrypted, false); + TestLogHandler::new().assert_logs_contain_in_order(vec![ + "DbMigrator: Database successfully migrated from version 9 to 10", + ]); + } +} diff --git a/node/src/database/db_migrations/migrations/mod.rs b/node/src/database/db_migrations/migrations/mod.rs index 68b10ca9b..bcdb14176 100644 --- a/node/src/database/db_migrations/migrations/mod.rs +++ b/node/src/database/db_migrations/migrations/mod.rs @@ -9,3 +9,4 @@ pub mod migration_5_to_6; pub mod migration_6_to_7; pub mod migration_7_to_8; pub mod migration_8_to_9; +pub mod migration_9_to_10; 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/lib.rs b/node/src/lib.rs index 4d072736a..ac4767ea1 100644 --- a/node/src/lib.rs +++ b/node/src/lib.rs @@ -45,6 +45,6 @@ mod stream_messages; mod stream_reader; mod stream_writer_sorted; mod stream_writer_unsorted; -pub mod test_utils; //TODO we should make some effort for collections of testing utils to be really test conditioned. +pub mod test_utils; pub mod tls_discriminator_factory; pub mod ui_gateway; diff --git a/node/src/neighborhood/dot_graph.rs b/node/src/neighborhood/dot_graph.rs index 345afe077..78de9f339 100644 --- a/node/src/neighborhood/dot_graph.rs +++ b/node/src/neighborhood/dot_graph.rs @@ -12,6 +12,7 @@ pub struct NodeRenderableInner { pub version: u32, pub accepts_connections: bool, pub routes_data: bool, + pub country_code: String, } pub struct NodeRenderable { @@ -45,10 +46,11 @@ impl NodeRenderable { fn render_label(&self) -> String { let inner_string = match &self.inner { Some(inner) => format!( - "{}{} v{}\\n", + "{}{} v{} {}\\n", if inner.accepts_connections { "A" } else { "a" }, if inner.routes_data { "R" } else { "r" }, inner.version, + inner.country_code ), None => String::new(), }; @@ -107,6 +109,7 @@ mod tests { version: 1, accepts_connections: true, routes_data: true, + country_code: "ZZ".to_string(), }), public_key: public_key.clone(), node_addr: None, @@ -120,7 +123,7 @@ mod tests { assert_string_contains( &result, &format!( - "\"{}\" [label=\"AR v1\\n{}\"];", + "\"{}\" [label=\"AR v1 ZZ\\n{}\"];", public_key_64, public_key_trunc ), ); @@ -135,6 +138,7 @@ mod tests { version: 1, accepts_connections: false, routes_data: false, + country_code: "ZZ".to_string(), }), public_key: public_key.clone(), node_addr: None, @@ -148,7 +152,7 @@ mod tests { assert_string_contains( &result, &format!( - "\"{}\" [label=\"ar v1\\n{}\"];", + "\"{}\" [label=\"ar v1 ZZ\\n{}\"];", public_key_64, public_key_64 ), ); diff --git a/node/src/neighborhood/gossip.rs b/node/src/neighborhood/gossip.rs index 1c156b4b6..117750871 100644 --- a/node/src/neighborhood/gossip.rs +++ b/node/src/neighborhood/gossip.rs @@ -378,11 +378,16 @@ impl Gossip_0v1 { to: k.clone(), }) }); + let country_code = match &nri.country_code_opt { + Some(cc) => cc.clone(), + None => "ZZ".to_string(), + }; node_renderables.push(NodeRenderable { inner: Some(NodeRenderableInner { version: nri.version, accepts_connections: nri.accepts_connections, routes_data: nri.routes_data, + country_code, }), public_key: nri.public_key.clone(), node_addr: addr.clone(), @@ -716,14 +721,14 @@ Length: 4 (0x4) bytes let result = gossip.to_dot_graph(&source_node, &target_node); assert_string_contains(&result, "digraph db { "); - assert_string_contains(&result, "\"AwQFBg\" [label=\"AR v0\\nAwQFBg\"]; "); + assert_string_contains(&result, "\"AwQFBg\" [label=\"AR v0 FR\\nAwQFBg\"]; "); assert_string_contains( &result, - "\"QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo\" [label=\"AR v1\\nQUJDREVG\\n1.2.3.4:1234\"] [style=filled]; ", + "\"QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo\" [label=\"AR v1 AU\\nQUJDREVG\\n1.2.3.4:1234\"] [style=filled]; ", ); assert_string_contains( &result, - "\"WllYV1ZVVFNSUVBPTk1MS0pJSEdGRURDQkE\" [label=\"AR v0\\nWllYV1ZV\\n2.3.4.5:2345\"] [shape=box]; ", + "\"WllYV1ZVVFNSUVBPTk1MS0pJSEdGRURDQkE\" [label=\"AR v0 FR\\nWllYV1ZV\\n2.3.4.5:2345\"] [shape=box]; ", ); assert_string_contains( &result, diff --git a/node/src/neighborhood/gossip_acceptor.rs b/node/src/neighborhood/gossip_acceptor.rs index fa5009ae7..a7d4574db 100644 --- a/node/src/neighborhood/gossip_acceptor.rs +++ b/node/src/neighborhood/gossip_acceptor.rs @@ -3,7 +3,7 @@ use crate::neighborhood::gossip::{GossipBuilder, GossipNodeRecord, Gossip_0v1}; use crate::neighborhood::neighborhood_database::{NeighborhoodDatabase, NeighborhoodDatabaseError}; use crate::neighborhood::node_record::NodeRecord; -use crate::neighborhood::AccessibleGossipRecord; +use crate::neighborhood::{AccessibleGossipRecord, UserExitPreferences}; use crate::sub_lib::cryptde::{CryptDE, PublicKey}; use crate::sub_lib::neighborhood::{ ConnectionProgressEvent, ConnectionProgressMessage, GossipFailure_0v1, NeighborhoodMetadata, @@ -130,7 +130,7 @@ impl GossipHandler for DebutHandler { database: &mut NeighborhoodDatabase, mut agrs: Vec, gossip_source: SocketAddr, - _neighborhood_metadata: NeighborhoodMetadata, + neighborhood_metadata: NeighborhoodMetadata, ) -> GossipAcceptanceResult { let source_agr = { let mut agr = agrs.remove(0); // empty Gossip shouldn't get here @@ -165,7 +165,13 @@ impl GossipHandler for DebutHandler { source_node_addr, ); } - if let Ok(result) = self.try_accept_debut(cryptde, database, &source_agr, gossip_source) { + if let Ok(result) = self.try_accept_debut( + cryptde, + database, + &source_agr, + gossip_source, + neighborhood_metadata.user_exit_preferences_opt, + ) { return result; } debug!(self.logger, "Seeking neighbor for Pass"); @@ -266,13 +272,22 @@ impl DebutHandler { database: &mut NeighborhoodDatabase, debuting_agr: &AccessibleGossipRecord, gossip_source: SocketAddr, + user_exit_preferences_opt: Option, ) -> Result { if database.gossip_target_degree(database.root().public_key()) >= MAX_DEGREE { debug!(self.logger, "Neighbor count already at maximum"); return Err(()); } let debut_node_addr_opt = debuting_agr.node_addr_opt.clone(); - let debuting_node = NodeRecord::from(debuting_agr); + let mut debuting_node = NodeRecord::from(debuting_agr); + match user_exit_preferences_opt { + Some(user_exit_preferences) => { + //TODO 788 check if country code is present in Neighborhood DB and if yes, perform assign country code to exit_countries without duplication + user_exit_preferences.assign_nodes_country_undesirability(&mut debuting_node); + } + None => (), + } + // TODO 468 make debuting_node mut and add country_undesirability to its metadata let debut_node_key = database .add_node(debuting_node) .expect("Debuting Node suddenly appeared in database"); @@ -685,7 +700,13 @@ impl GossipHandler for IntroductionHandler { .as_ref() .expect("IP Address not found for the Node Addr.") .ip_addr(); - match self.update_database(database, cryptde, introducer) { + // TODO 468 pass the NeighborhoodMetadata into update_database + match self.update_database( + database, + cryptde, + introducer, + neighborhood_metadata.user_exit_preferences_opt, + ) { Ok(_) => (), Err(e) => { return GossipAcceptanceResult::Ban(format!( @@ -845,6 +866,7 @@ impl IntroductionHandler { database: &mut NeighborhoodDatabase, cryptde: &dyn CryptDE, introducer: AccessibleGossipRecord, + user_exit_preferences_opt: Option, ) -> Result { let introducer_key = introducer.inner.public_key.clone(); match database.node_by_key_mut(&introducer_key) { @@ -869,7 +891,14 @@ impl IntroductionHandler { } } None => { - let new_introducer = NodeRecord::from(introducer); + let mut new_introducer = NodeRecord::from(introducer); + //TODO 468 add country undesirability + match user_exit_preferences_opt { + //TODO 788 check if country code is present in Neighborhood DB and if yes, perform assign country code to exit_countries without duplication + Some(user_exit_preferences) => user_exit_preferences + .assign_nodes_country_undesirability(&mut new_introducer), + None => (), + } debug!( self.logger, "Adding introducer {} to database", introducer_key @@ -984,6 +1013,7 @@ impl GossipHandler for StandardGossipHandler { database, &filtered_agrs, gossip_source, + neighborhood_metadata.user_exit_preferences_opt.as_ref(), ); db_changed = self.identify_and_update_obsolete_nodes(database, filtered_agrs) || db_changed; db_changed = @@ -1095,6 +1125,7 @@ impl StandardGossipHandler { database: &mut NeighborhoodDatabase, agrs: &[AccessibleGossipRecord], gossip_source: SocketAddr, + user_exit_preferences_opt: Option<&UserExitPreferences>, ) -> bool { let all_keys = database .keys() @@ -1112,7 +1143,15 @@ impl StandardGossipHandler { } }) .for_each(|agr| { - let node_record = NodeRecord::from(agr); + let mut node_record = NodeRecord::from(agr); + // TODO modify for country undesirability in node_record (make it mut) + match user_exit_preferences_opt { + Some(user_exit_preferences) => { + //TODO 788 check if country code is present in Neighborhood DB and if yes, perform assign country code to exit_countries without duplication + user_exit_preferences.assign_nodes_country_undesirability(&mut node_record) + } + None => (), + } trace!( self.logger, "Discovered new Node {:?}: {:?}", @@ -1369,8 +1408,14 @@ mod tests { use crate::neighborhood::gossip_producer::GossipProducer; use crate::neighborhood::gossip_producer::GossipProducerReal; use crate::neighborhood::node_record::NodeRecord; + use crate::neighborhood::{ + ExitPreference, UserExitPreferences, COUNTRY_UNDESIRABILITY_FACTOR, + UNREACHABLE_COUNTRY_PENALTY, + }; use crate::sub_lib::cryptde_null::CryptDENull; - use crate::sub_lib::neighborhood::{ConnectionProgressEvent, ConnectionProgressMessage}; + use crate::sub_lib::neighborhood::{ + ConnectionProgressEvent, ConnectionProgressMessage, ExitLocation, + }; use crate::sub_lib::utils::time_t_timestamp; use crate::test_utils::neighborhood_test_utils::{ db_from_node, gossip_about_nodes_from_database, linearly_connect_nodes, @@ -1407,6 +1452,7 @@ mod tests { connection_progress_peers: vec![], cpm_recipient: make_cpm_recipient().0, db_patch_size: DB_PATCH_SIZE_FOR_TEST, + user_exit_preferences_opt: None, } } @@ -1677,6 +1723,7 @@ mod tests { dest_db.add_arbitrary_half_neighbor(root_node.public_key(), half_debuted_node.public_key()); let logger = Logger::new("Debut test"); let subject = DebutHandler::new(logger); + let neighborhood_metadata = make_default_neighborhood_metadata(); let counter_debut = subject .try_accept_debut( @@ -1684,6 +1731,7 @@ mod tests { &mut dest_db, &AccessibleGossipRecord::from(&new_debutant), SocketAddr::new(IpAddr::V4(Ipv4Addr::new(4, 5, 6, 7)), 4567), + neighborhood_metadata.user_exit_preferences_opt, ) .unwrap(); @@ -2020,6 +2068,16 @@ mod tests { let cryptde = CryptDENull::from(dest_db.root().public_key(), TEST_DEFAULT_CHAIN); let subject = IntroductionHandler::new(Logger::new("test")); let agrs: Vec = gossip.try_into().unwrap(); + let mut neighborhood_metadata = make_default_neighborhood_metadata(); + neighborhood_metadata.user_exit_preferences_opt = Some(UserExitPreferences { + exit_countries: vec!["FR".to_string()], + location_preference: ExitPreference::ExitCountryNoFallback, + locations_opt: Some(vec![ExitLocation { + country_codes: vec!["FR".to_string()], + priority: 2, + }]), + db_countries: vec!["FR".to_string()], + }); let qualifies_result = subject.qualifies(&dest_db, &agrs, gossip_source); let handle_result = subject.handle( @@ -2027,7 +2085,7 @@ mod tests { &mut dest_db, agrs.clone(), gossip_source, - make_default_neighborhood_metadata(), + neighborhood_metadata, ); assert_eq!(Qualification::Matched, qualifies_result); @@ -2047,6 +2105,7 @@ mod tests { dest_db.node_by_key_mut(&agrs[0].inner.public_key).unwrap(); let mut expected_introducer = NodeRecord::from(&agrs[0]); expected_introducer.metadata.last_update = result_introducer.metadata.last_update; + expected_introducer.metadata.country_undesirability = COUNTRY_UNDESIRABILITY_FACTOR; expected_introducer.resign(); assert_eq!(result_introducer, &expected_introducer); assert_eq!( @@ -2407,6 +2466,15 @@ mod tests { let gossip_source: SocketAddr = src_root.node_addr_opt().unwrap().into(); let (cpm_recipient, recording_arc) = make_cpm_recipient(); let mut neighborhood_metadata = make_default_neighborhood_metadata(); + neighborhood_metadata.user_exit_preferences_opt = Some(UserExitPreferences { + exit_countries: vec!["FR".to_string()], + location_preference: ExitPreference::ExitCountryWithFallback, + locations_opt: Some(vec![ExitLocation { + country_codes: vec!["FR".to_string()], + priority: 1, + }]), + db_countries: vec!["FR".to_string()], + }); neighborhood_metadata.cpm_recipient = cpm_recipient; let system = System::new("test"); @@ -2419,6 +2487,22 @@ mod tests { neighborhood_metadata, ); + assert_eq!( + dest_db + .node_by_key(node_a.public_key()) + .unwrap() + .metadata + .country_undesirability, + 0u32 + ); + assert_eq!( + dest_db + .node_by_key(node_b.public_key()) + .unwrap() + .metadata + .country_undesirability, + UNREACHABLE_COUNTRY_PENALTY + ); assert_eq!(Qualification::Matched, qualifies_result); assert_eq!(GossipAcceptanceResult::Accepted, handle_result); assert_eq!( @@ -2979,12 +3063,22 @@ mod tests { let (gossip, mut debut_node, gossip_source) = make_debut(2345, Mode::Standard); let subject = make_subject(&root_node_cryptde); let before = time_t_timestamp(); + let mut neighborhood_metadata = make_default_neighborhood_metadata(); + neighborhood_metadata.user_exit_preferences_opt = Some(UserExitPreferences { + exit_countries: vec!["CZ".to_string()], + location_preference: ExitPreference::ExitCountryWithFallback, + locations_opt: Some(vec![ExitLocation { + country_codes: vec!["CZ".to_string()], + priority: 1, + }]), + db_countries: vec!["CZ".to_string()], + }); let result = subject.handle( &mut dest_db, gossip.try_into().unwrap(), gossip_source, - make_default_neighborhood_metadata(), + neighborhood_metadata, ); let after = time_t_timestamp(); @@ -3034,6 +3128,11 @@ Length: 24 (0x18) bytes let reference_node = dest_db.node_by_key_mut(debut_node.public_key()).unwrap(); debut_node.metadata.last_update = reference_node.metadata.last_update; debut_node.resign(); + assert_eq!( + reference_node.metadata.country_undesirability, + UNREACHABLE_COUNTRY_PENALTY + ); + reference_node.metadata.country_undesirability = 0u32; assert_node_records_eq(reference_node, &debut_node, before, after); } diff --git a/node/src/neighborhood/mod.rs b/node/src/neighborhood/mod.rs index f7dc08404..889491a1e 100644 --- a/node/src/neighborhood/mod.rs +++ b/node/src/neighborhood/mod.rs @@ -9,11 +9,6 @@ pub mod node_location; pub mod node_record; pub mod overall_connection_status; -use std::collections::HashSet; -use std::convert::TryFrom; -use std::net::{IpAddr, SocketAddr}; -use std::path::PathBuf; - use actix::Context; use actix::Handler; use actix::MessageResult; @@ -23,10 +18,17 @@ use actix::{Addr, AsyncContext}; use itertools::Itertools; use masq_lib::messages::{ FromMessageBody, ToMessageBody, UiConnectionStage, UiConnectionStatusRequest, + UiSetExitLocationRequest, UiSetExitLocationResponse, }; use masq_lib::messages::{UiConnectionStatusResponse, UiShutdownRequest}; -use masq_lib::ui_gateway::{MessageTarget, NodeFromUiMessage, NodeToUiMessage}; +use masq_lib::ui_gateway::{MessageBody, MessageTarget, NodeFromUiMessage, NodeToUiMessage}; use masq_lib::utils::{exit_process, ExpectValue, NeighborhoodModeLight}; +use std::collections::{HashMap, HashSet}; +use std::convert::TryFrom; +use std::fmt::Debug; +use std::net::{IpAddr, SocketAddr}; +use std::path::PathBuf; +use std::string::ToString; use crate::bootstrapper::BootstrapperConfig; use crate::database::db_initializer::DbInitializationConfig; @@ -42,20 +44,19 @@ use crate::neighborhood::overall_connection_status::{ OverallConnectionStage, OverallConnectionStatus, }; use crate::stream_messages::RemovedStreamType; -use crate::sub_lib::configurator::NewPasswordMessage; use crate::sub_lib::cryptde::PublicKey; use crate::sub_lib::cryptde::{CryptDE, CryptData, PlainData}; use crate::sub_lib::dispatcher::{Component, StreamShutdownMsg}; use crate::sub_lib::hopper::{ExpiredCoresPackage, NoLookupIncipientCoresPackage}; use crate::sub_lib::hopper::{IncipientCoresPackage, MessageType}; -use crate::sub_lib::neighborhood::RouteQueryResponse; -use crate::sub_lib::neighborhood::UpdateNodeRecordMetadataMessage; use crate::sub_lib::neighborhood::{AskAboutDebutGossipMessage, NodeDescriptor}; -use crate::sub_lib::neighborhood::{ConfigurationChange, RemoveNeighborMessage}; -use crate::sub_lib::neighborhood::{ConfigurationChangeMessage, RouteQueryMessage}; +use crate::sub_lib::neighborhood::{ConfigChange, RemoveNeighborMessage}; +use crate::sub_lib::neighborhood::{ConfigChangeMsg, RouteQueryMessage}; use crate::sub_lib::neighborhood::{ConnectionProgressEvent, ExpectedServices}; use crate::sub_lib::neighborhood::{ConnectionProgressMessage, ExpectedService}; use crate::sub_lib::neighborhood::{DispatcherNodeQueryMessage, GossipFailure_0v1}; +use crate::sub_lib::neighborhood::{ExitLocation, UpdateNodeRecordMetadataMessage}; +use crate::sub_lib::neighborhood::{ExitLocationSet, RouteQueryResponse}; use crate::sub_lib::neighborhood::{Hops, NeighborhoodMetadata, NodeQueryResponseMetadata}; use crate::sub_lib::neighborhood::{NRMetadataChange, NodeQueryMessage}; use crate::sub_lib::neighborhood::{NeighborhoodSubs, NeighborhoodTools}; @@ -74,15 +75,84 @@ use gossip_acceptor::GossipAcceptorReal; use gossip_producer::GossipProducer; use gossip_producer::GossipProducerReal; use masq_lib::blockchains::chains::Chain; +use masq_lib::constants::EXIT_COUNTRY_ERROR; use masq_lib::crash_point::CrashPoint; use masq_lib::logger::Logger; +use masq_lib::ui_gateway::MessagePath::Conversation; use neighborhood_database::NeighborhoodDatabase; use node_record::NodeRecord; pub const CRASH_KEY: &str = "NEIGHBORHOOD"; pub const DEFAULT_MIN_HOPS: Hops = Hops::ThreeHops; pub const UNREACHABLE_HOST_PENALTY: i64 = 100_000_000; +pub const UNREACHABLE_COUNTRY_PENALTY: u32 = 100_000_000; +pub const COUNTRY_UNDESIRABILITY_FACTOR: u32 = 1_000; pub const RESPONSE_UNDESIRABILITY_FACTOR: usize = 1_000; // assumed response length is request * this +pub const ZZ_COUNTRY_CODE_STRING: &str = "ZZ"; + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct ExitLocationsRoutes<'a> { + routes: Vec<(Vec<&'a PublicKey>, i64)>, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum ExitPreference { + Nothing, + ExitCountryWithFallback, + ExitCountryNoFallback, +} + +#[derive(Clone, Debug)] +pub struct UserExitPreferences { + exit_countries: Vec, //if we cross number of countries used in one workflow, we want to change this member to HashSet + location_preference: ExitPreference, + locations_opt: Option>, + db_countries: Vec, +} + +impl UserExitPreferences { + fn new() -> UserExitPreferences { + UserExitPreferences { + exit_countries: vec![], + location_preference: ExitPreference::Nothing, + locations_opt: None, + db_countries: vec![], + } + } + + pub fn assign_nodes_country_undesirability(&self, node_record: &mut NodeRecord) { + let country_code = node_record + .inner + .country_code_opt + .clone() + .unwrap_or_else(|| ZZ_COUNTRY_CODE_STRING.to_string()); + match &self.locations_opt { + Some(exit_locations_by_priority) => { + for exit_location in exit_locations_by_priority { + if exit_location.country_codes.contains(&country_code) + && country_code != ZZ_COUNTRY_CODE_STRING + { + node_record.metadata.country_undesirability = + Self::calculate_country_undesirability( + (exit_location.priority - 1) as u32, + ); + } + if (self.location_preference == ExitPreference::ExitCountryWithFallback + && !self.exit_countries.contains(&country_code)) + || country_code == ZZ_COUNTRY_CODE_STRING + { + node_record.metadata.country_undesirability = UNREACHABLE_COUNTRY_PENALTY; + } + } + } + None => (), + } + } + + fn calculate_country_undesirability(priority: u32) -> u32 { + COUNTRY_UNDESIRABILITY_FACTOR * priority + } +} pub struct Neighborhood { cryptde: &'static dyn CryptDE, @@ -106,6 +176,7 @@ pub struct Neighborhood { db_password_opt: Option, logger: Logger, tools: NeighborhoodTools, + user_exit_preferences: UserExitPreferences, } impl Actor for Neighborhood { @@ -117,10 +188,7 @@ impl Handler for Neighborhood { fn handle(&mut self, msg: BindMessage, ctx: &mut Self::Context) -> Self::Result { ctx.set_mailbox_capacity(NODE_MAILBOX_CAPACITY); - self.hopper_opt = Some(msg.peer_actors.hopper.from_hopper_client); - self.hopper_no_lookup_opt = Some(msg.peer_actors.hopper.from_hopper_client_no_lookup); - self.connected_signal_opt = Some(msg.peer_actors.accountant.start); - self.node_to_ui_recipient_opt = Some(msg.peer_actors.ui_gateway.node_to_ui_message_sub); + self.handle_bind_message(msg); } } @@ -140,35 +208,11 @@ impl Handler for Neighborhood { } } -impl Handler for Neighborhood { +impl Handler for Neighborhood { type Result = (); - fn handle( - &mut self, - msg: ConfigurationChangeMessage, - _ctx: &mut Self::Context, - ) -> Self::Result { - match msg.change { - ConfigurationChange::UpdateConsumingWallet(new_wallet) => { - self.consuming_wallet_opt = Some(new_wallet) - } - ConfigurationChange::UpdateMinHops(new_min_hops) => { - self.set_min_hops_and_patch_size(new_min_hops); - if self.overall_connection_status.can_make_routes() { - let node_to_ui_recipient = self - .node_to_ui_recipient_opt - .as_ref() - .expect("UI gateway is dead"); - self.overall_connection_status - .update_ocs_stage_and_send_message_to_ui( - OverallConnectionStage::ConnectedToNeighbor, - node_to_ui_recipient, - &self.logger, - ); - } - self.search_for_a_new_route(); - } - } + fn handle(&mut self, msg: ConfigChangeMsg, _ctx: &mut Self::Context) -> Self::Result { + self.handle_config_change_msg(msg); } } @@ -366,15 +410,6 @@ impl Handler for Neighborhood { } } -// GH-728 -impl Handler for Neighborhood { - type Result = (); - - fn handle(&mut self, msg: NewPasswordMessage, _ctx: &mut Self::Context) -> Self::Result { - self.handle_new_password(msg.new_password); - } -} - impl Handler for Neighborhood { type Result = (); @@ -388,7 +423,9 @@ impl Handler for Neighborhood { fn handle(&mut self, msg: NodeFromUiMessage, _ctx: &mut Self::Context) -> Self::Result { let client_id = msg.client_id; - if let Ok((_, context_id)) = UiConnectionStatusRequest::fmb(msg.body.clone()) { + if let Ok((message, context_id)) = UiSetExitLocationRequest::fmb(msg.body.clone()) { + self.handle_exit_location_message(message, client_id, context_id); + } else if let Ok((_, context_id)) = UiConnectionStatusRequest::fmb(msg.body.clone()) { self.handle_connection_status_message(client_id, context_id); } else if let Ok((body, _)) = UiShutdownRequest::fmb(msg.body.clone()) { self.handle_shutdown_order(client_id, body); @@ -495,6 +532,7 @@ impl Neighborhood { db_password_opt: config.db_password_opt.clone(), logger: Logger::new("Neighborhood"), tools: NeighborhoodTools::default(), + user_exit_preferences: UserExitPreferences::new(), } } @@ -513,10 +551,9 @@ impl Neighborhood { .recipient::>(), dispatcher_node_query: addr.clone().recipient::(), remove_neighbor: addr.clone().recipient::(), - configuration_change_msg_sub: addr.clone().recipient::(), + config_change_msg_sub: addr.clone().recipient::(), stream_shutdown_sub: addr.clone().recipient::(), from_ui_message_sub: addr.clone().recipient::(), - new_password_sub: addr.clone().recipient::(), // GH-728 connection_progress_sub: addr.clone().recipient::(), } } @@ -591,6 +628,40 @@ impl Neighborhood { } } + fn handle_config_change_msg(&mut self, msg: ConfigChangeMsg) { + match msg.change { + ConfigChange::UpdateWallets(wallet_pair) => { + if self.consuming_wallet_opt != Some(wallet_pair.consuming_wallet.clone()) { + info!( + self.logger, + "Consuming Wallet has been updated: {}", wallet_pair.consuming_wallet + ); + self.consuming_wallet_opt = Some(wallet_pair.consuming_wallet); + } + } + ConfigChange::UpdateMinHops(new_min_hops) => { + self.set_min_hops_and_patch_size(new_min_hops); + if self.overall_connection_status.can_make_routes() { + let node_to_ui_recipient = self + .node_to_ui_recipient_opt + .as_ref() + .expect("UI gateway is dead"); + self.overall_connection_status + .update_ocs_stage_and_send_message_to_ui( + OverallConnectionStage::ConnectedToNeighbor, + node_to_ui_recipient, + &self.logger, + ); + } + self.search_for_a_new_route(); + } + ConfigChange::UpdatePassword(new_password) => { + info!(self.logger, "DB Password has been updated."); + self.db_password_opt = Some(new_password); + } + } + } + fn validate_or_replace_min_hops_value(&mut self) { if let Some(persistent_config) = self.persistent_config_opt.as_ref() { let value_in_db = persistent_config @@ -793,6 +864,7 @@ impl Neighborhood { connection_progress_peers: self.overall_connection_status.get_peer_addrs(), cpm_recipient, db_patch_size: self.db_patch_size, + user_exit_preferences_opt: Some(self.user_exit_preferences.clone()), }; let acceptance_result = self.gossip_acceptor.handle( &mut self.neighborhood_database, @@ -801,8 +873,12 @@ impl Neighborhood { neighborhood_metadata, ); match acceptance_result { - GossipAcceptanceResult::Accepted => self.gossip_to_neighbors(), + GossipAcceptanceResult::Accepted => { + self.user_exit_preferences.db_countries = self.init_db_countries(); + self.gossip_to_neighbors() + } GossipAcceptanceResult::Reply(next_debut, target_key, target_node_addr) => { + self.user_exit_preferences.db_countries = self.init_db_countries(); self.handle_gossip_reply(next_debut, &target_key, &target_node_addr) } GossipAcceptanceResult::Failed(failure, target_key, target_node_addr) => { @@ -813,8 +889,9 @@ impl Neighborhood { self.handle_gossip_ignored(ignored_node_name, gossip_record_count) } GossipAcceptanceResult::Ban(reason) => { - warning!(self.logger, "Malefactor detected at {}, but malefactor bans not yet implemented; ignoring: {}", gossip_source, reason - ); + // TODO in case we introduce Ban machinery we need to reinitialize the db_countries here as well - in that case, we need to make new process to subtract + // result in init_db_countries guts by one for particular country + warning!(self.logger, "Malefactor detected at {}, but malefactor bans not yet implemented; ignoring: {}", gossip_source, reason); self.handle_gossip_ignored(ignored_node_name, gossip_record_count); } } @@ -888,6 +965,7 @@ impl Neighborhood { return_component_opt: Some(Component::ProxyServer), payload_size: 10000, hostname_opt: None, + target_country_opt: None, }; if self.handle_route_query_message(msg).is_some() { debug!( @@ -1205,6 +1283,59 @@ impl Neighborhood { } } + fn validate_fallback_country_exit_codes(&self, last_node: &PublicKey) -> bool { + let last_cc = match self.neighborhood_database.node_by_key(last_node) { + Some(nr) => match nr.clone().inner.country_code_opt { + Some(cc) => cc, + None => "ZZ".to_string(), + }, + None => "ZZ".to_string(), + }; + if self.user_exit_preferences.exit_countries.contains(&last_cc) { + return true; + } + if self.user_exit_preferences.exit_countries.is_empty() { + return true; + } + for country in &self.user_exit_preferences.exit_countries { + if country == &last_cc { + return true; + } + if self.user_exit_preferences.db_countries.contains(country) && country != &last_cc { + return false; + } + } + true + } + + fn validate_last_node_country_code( + &self, + first_node_key: &PublicKey, + research_neighborhood: bool, + direction: RouteDirection, + ) -> bool { + if self.user_exit_preferences.location_preference == ExitPreference::Nothing + || (self.user_exit_preferences.location_preference + == ExitPreference::ExitCountryWithFallback + && self.validate_fallback_country_exit_codes(first_node_key)) + || research_neighborhood + || direction == RouteDirection::Back + { + true // Zero- and single-hop routes are not subject to exit-too-close restrictions + } else { + match self.neighborhood_database.node_by_key(first_node_key) { + Some(node_record) => match &node_record.inner.country_code_opt { + Some(country_code) => self + .user_exit_preferences + .exit_countries + .contains(country_code), + _ => false, + }, + _ => false, + } + } + } + fn compute_undesirability( node_record: &NodeRecord, payload_size: u64, @@ -1215,6 +1346,7 @@ impl Neighborhood { UndesirabilityType::Relay => node_record.inner.rate_pack.routing_charge(payload_size), UndesirabilityType::ExitRequest(_) => { node_record.inner.rate_pack.exit_charge(payload_size) + + node_record.metadata.country_undesirability as u64 } UndesirabilityType::ExitAndRouteResponse => { node_record.inner.rate_pack.exit_charge(payload_size) @@ -1259,6 +1391,47 @@ impl Neighborhood { return_route_id } + pub fn find_exit_location<'a>( + &'a self, + source: &'a PublicKey, + minimum_hops: usize, + payload_size: usize, + ) -> (HashMap, Vec<&'a PublicKey>) { + let mut minimum_undesirability = i64::MAX; + let initial_undesirability = 0; + let research_exits: &mut Vec<&'a PublicKey> = &mut vec![]; + let mut prefix = Vec::with_capacity(10); + prefix.push(source); + let over_routes = self.routing_engine( + &mut prefix, + initial_undesirability, + None, + minimum_hops, + payload_size, + RouteDirection::Over, + &mut minimum_undesirability, + None, + true, + research_exits, + ); + let mut result_exit: HashMap = HashMap::new(); + over_routes.into_iter().for_each(|segment| { + if !segment.nodes.is_empty() { + let exit_node = segment.nodes[segment.nodes.len() - 1]; + result_exit + .entry(exit_node.clone()) + .and_modify(|e| { + e.routes + .push((segment.nodes.clone(), segment.undesirability)) + }) + .or_insert(ExitLocationsRoutes { + routes: vec![(segment.nodes.clone(), segment.undesirability)], + }); + } + }); + (result_exit, research_exits.to_vec()) + } + // Interface to main routing engine. Supply source key, target key--if any--in target_opt, // minimum hops, size of payload in bytes, the route direction, and the hostname if you know it. // @@ -1266,6 +1439,7 @@ impl Neighborhood { // target in hops_remaining or more hops with no cycles, or from the origin hops_remaining hops // out into the MASQ Network. No round trips; if you want a round trip, call this method twice. // If the return value is None, no qualifying route was found. + #[allow(clippy::too_many_arguments)] fn find_best_route_segment<'a>( &'a self, source: &'a PublicKey, @@ -1278,9 +1452,11 @@ impl Neighborhood { let mut minimum_undesirability = i64::MAX; let initial_undesirability = self.compute_initial_undesirability(source, payload_size as u64, direction); + let mut prefix = Vec::with_capacity(10); + prefix.push(source); let result = self .routing_engine( - vec![source], + &mut vec![source], initial_undesirability, target_opt, minimum_hops, @@ -1288,6 +1464,8 @@ impl Neighborhood { direction, &mut minimum_undesirability, hostname_opt, + false, + &mut vec![], ) .into_iter() .filter_map(|cr| match cr.undesirability <= minimum_undesirability { @@ -1302,7 +1480,7 @@ impl Neighborhood { #[allow(clippy::too_many_arguments)] fn routing_engine<'a>( &'a self, - prefix: Vec<&'a PublicKey>, + prefix: &mut Vec<&'a PublicKey>, undesirability: i64, target_opt: Option<&'a PublicKey>, hops_remaining: usize, @@ -1310,10 +1488,13 @@ impl Neighborhood { direction: RouteDirection, minimum_undesirability: &mut i64, hostname_opt: Option<&str>, + research_neighborhood: bool, + research_exits: &mut Vec<&'a PublicKey>, ) -> Vec> { - if undesirability > *minimum_undesirability { + if undesirability > *minimum_undesirability && !research_neighborhood { return vec![]; } + //TODO when target node is present ignore all country_codes selection - write test for route back and ignore the country code let first_node_key = prefix.first().expect("Empty prefix"); let previous_node = self .neighborhood_database @@ -1328,58 +1509,121 @@ impl Neighborhood { previous_node.public_key(), ) { - if undesirability < *minimum_undesirability { - *minimum_undesirability = undesirability; + if !research_neighborhood + && self.validate_last_node_country_code( + previous_node.public_key(), + research_neighborhood, + direction, + ) + { + if undesirability < *minimum_undesirability { + *minimum_undesirability = undesirability; + } + vec![ComputedRouteSegment::new(prefix.clone(), undesirability)] + } else if research_neighborhood && research_exits.contains(&prefix[prefix.len() - 1]) { + vec![] + } else { + if research_neighborhood { + research_exits.push(prefix[prefix.len() - 1]); + } + self.routing_guts( + prefix, + undesirability, + target_opt, + hops_remaining, + payload_size, + direction, + minimum_undesirability, + hostname_opt, + research_neighborhood, + research_exits, + previous_node, + ) } - vec![ComputedRouteSegment::new(prefix, undesirability)] - } else if (hops_remaining == 0) && target_opt.is_none() { + } else if ((hops_remaining == 0) && target_opt.is_none() && !research_neighborhood) + && (self.user_exit_preferences.location_preference == ExitPreference::Nothing + || self.user_exit_preferences.exit_countries.is_empty()) + { + // in case we do not investigate neighborhood for country codes, or we do not looking for particular country exit: // don't continue a targetless search past the minimum hop count vec![] } else { - // Go through all the neighbors and compute shorter routes through all the ones we're not already using. - previous_node - .full_neighbors(&self.neighborhood_database) - .iter() - .filter(|node_record| !prefix.contains(&node_record.public_key())) - .filter(|node_record| { - node_record.routes_data() - || Self::is_orig_node_on_back_leg(**node_record, target_opt, direction) - }) - .flat_map(|node_record| { - let mut new_prefix = prefix.clone(); - new_prefix.push(node_record.public_key()); - - let new_hops_remaining = if hops_remaining == 0 { - 0 - } else { - hops_remaining - 1 - }; - - let new_undesirability = self.compute_new_undesirability( - node_record, - undesirability, - target_opt, - new_hops_remaining, - payload_size as u64, - direction, - hostname_opt, - ); - - self.routing_engine( - new_prefix.clone(), - new_undesirability, - target_opt, - new_hops_remaining, - payload_size, - direction, - minimum_undesirability, - hostname_opt, - ) - }) - .collect() + self.routing_guts( + prefix, + undesirability, + target_opt, + hops_remaining, + payload_size, + direction, + minimum_undesirability, + hostname_opt, + research_neighborhood, + research_exits, + previous_node, + ) } } + #[allow(clippy::too_many_arguments)] + fn routing_guts<'a>( + &'a self, + prefix: &mut [&'a PublicKey], + undesirability: i64, + target_opt: Option<&'a PublicKey>, + hops_remaining: usize, + payload_size: usize, + direction: RouteDirection, + minimum_undesirability: &mut i64, + hostname_opt: Option<&str>, + research_neighborhood: bool, + research_exits: &mut Vec<&'a PublicKey>, + previous_node: &NodeRecord, + ) -> Vec { + // Go through all the neighbors and compute shorter routes through all the ones we're not already using. + previous_node + .full_neighbors(&self.neighborhood_database) + .iter() + .filter(|node_record| !prefix.contains(&node_record.public_key())) + .filter(|node_record| { + node_record.routes_data() + || Self::is_orig_node_on_back_leg(**node_record, target_opt, direction) + }) + .flat_map(|node_record| { + let mut new_prefix = prefix.to_owned(); + new_prefix.push(node_record.public_key()); + + let new_hops_remaining = if hops_remaining == 0 { + 0 + } else { + hops_remaining - 1 + }; + + let new_undesirability = self.compute_new_undesirability( + node_record, + undesirability, + target_opt, + new_hops_remaining, + payload_size as u64, + direction, + hostname_opt, + ); + + self.routing_engine( + &mut new_prefix, + new_undesirability, + target_opt, + new_hops_remaining, + payload_size, + direction, + minimum_undesirability, + hostname_opt, + research_neighborhood, + research_exits, + ) + }) + .collect() + } + fn send_ask_about_debut_gossip_message( &mut self, ctx: &mut Context, @@ -1448,6 +1692,211 @@ impl Neighborhood { undesirability + node_undesirability } + fn handle_exit_location_message( + &mut self, + message: UiSetExitLocationRequest, + client_id: u64, + context_id: u64, + ) { + let (exit_locations_by_priority, missing_locations) = + self.extract_exit_locations_from_message(&message); + self.user_exit_preferences.location_preference = match ( + message.fallback_routing, + exit_locations_by_priority.is_empty(), + ) { + (true, true) => ExitPreference::Nothing, + (true, false) => ExitPreference::ExitCountryWithFallback, + (false, false) => ExitPreference::ExitCountryNoFallback, + (false, true) => ExitPreference::Nothing, + }; + let fallback_status = match self.user_exit_preferences.location_preference { + ExitPreference::Nothing => "Fallback Routing is set.", + ExitPreference::ExitCountryWithFallback => "Fallback Routing is set.", + ExitPreference::ExitCountryNoFallback => "Fallback Routing NOT set.", + }; + self.set_exit_locations_opt(&exit_locations_by_priority); + match self.neighborhood_database.keys().len() > 1 { + true => { + self.set_country_undesirability_and_exit_countries(&exit_locations_by_priority); + let location_set = ExitLocationSet { + locations: exit_locations_by_priority, + }; + let exit_location_status = match location_set.locations.is_empty() { + false => "Exit location set: ", + true => "Exit location unset.", + }; + info!( + self.logger, + "{} {}{}", fallback_status, exit_location_status, location_set + ); + if !missing_locations.is_empty() { + warning!( + self.logger, + "Exit Location: following desired countries are missing in Neighborhood {:?}", &missing_locations + ); + } + } + false => info!( + self.logger, + "Neighborhood is empty, no exit Nodes are available.", + ), + } + let message = self.create_exit_location_response( + client_id, + context_id, + missing_locations, + message.show_countries, + ); + self.node_to_ui_recipient_opt + .as_ref() + .expect("UI Gateway is unbound") + .try_send(message) + .expect("UiGateway is dead"); + } + + fn create_exit_location_response( + &self, + client_id: u64, + context_id: u64, + missing_locations: Vec, + show_countries: bool, + ) -> NodeToUiMessage { + let errmessage = match show_countries { + true => match &missing_locations.is_empty() { + true => { + format!( + "Exit Countries: {:?}", + self.user_exit_preferences.db_countries + ) + } + false => { + format!( + "Exit Countries: {:?}\nExit Location: following desired countries are missing in Neighborhood {:?}", + self.user_exit_preferences.db_countries, + missing_locations + ) + } + }, + false => match &missing_locations.is_empty() { + true => "".to_string(), + false => { + format!( + "Exit Location: following desired countries are missing in Neighborhood {:?}", + missing_locations + ) + } + }, + }; + match &errmessage.is_empty() { + false => NodeToUiMessage { + target: MessageTarget::ClientId(client_id), + body: MessageBody { + opcode: "exit_location".to_string(), + path: Conversation(context_id), + payload: Err((EXIT_COUNTRY_ERROR, errmessage)), + }, + }, + true => NodeToUiMessage { + target: MessageTarget::ClientId(client_id), + body: UiSetExitLocationResponse {}.tmb(context_id), + }, + } + } + + fn set_exit_locations_opt(&mut self, exit_locations_by_priority: &[ExitLocation]) { + self.user_exit_preferences.locations_opt = + match self.user_exit_preferences.exit_countries.is_empty() { + false => Some(exit_locations_by_priority.to_owned()), + true => match self.user_exit_preferences.location_preference { + ExitPreference::ExitCountryNoFallback => None, + _ => Some(exit_locations_by_priority.to_owned()), + }, + }; + } + + fn set_country_undesirability_and_exit_countries( + &mut self, + exit_locations_by_priority: &Vec, + ) { + let nodes = self.neighborhood_database.nodes_mut(); + match !&exit_locations_by_priority.is_empty() { + true => { + for node_record in nodes { + self.user_exit_preferences + .assign_nodes_country_undesirability(node_record) + } + } + false => { + self.user_exit_preferences.exit_countries = vec![]; + for node_record in nodes { + node_record.metadata.country_undesirability = 0u32; + } + } + } + } + + fn extract_exit_locations_from_message( + &mut self, + message: &UiSetExitLocationRequest, + ) -> (Vec, Vec) { + self.user_exit_preferences.db_countries = self.init_db_countries(); + let mut countries_lack_in_neighborhood = vec![]; + ( + message + .to_owned() + .exit_locations + .into_iter() + .map(|cc| { + for code in &cc.country_codes { + if self.user_exit_preferences.db_countries.contains(code) + || self.user_exit_preferences.location_preference + == ExitPreference::ExitCountryWithFallback + { + self.user_exit_preferences.exit_countries.push(code.clone()); + if self.user_exit_preferences.location_preference + == ExitPreference::ExitCountryWithFallback + { + countries_lack_in_neighborhood.push(code.clone()); + } + } else { + countries_lack_in_neighborhood.push(code.clone()); + } + } + ExitLocation { + country_codes: cc.country_codes, + priority: cc.priority, + } + }) + .collect(), + countries_lack_in_neighborhood, + ) + } + + fn init_db_countries(&mut self) -> Vec { + let root_key = self.neighborhood_database.root_key(); + let min_hops = self.min_hops as usize; + let (exit_locations_db, exit_nodes) = self + .find_exit_location(root_key, min_hops, 0usize) + .to_owned(); + let mut db_countries = vec![]; + match (exit_locations_db.is_empty(), exit_nodes.is_empty()) { + (true, true) => {} + _ => { + for pub_key in exit_nodes { + let node_opt = self.neighborhood_database.node_by_key(pub_key); + if let Some(node_record) = node_opt { + if let Some(cc) = &node_record.inner.country_code_opt { + db_countries.push(cc.clone()) + } + } + } + } + } + db_countries.sort(); + db_countries.dedup(); + db_countries + } + fn handle_gossip_reply( &self, gossip: Gossip_0v1, @@ -1610,9 +2059,11 @@ impl Neighborhood { debug!(self.logger, "The value of min_hops ({}-hop -> {}-hop) and db_patch_size ({} -> {}) has been changed", prev_min_hops, self.min_hops, prev_db_patch_size, self.db_patch_size); } - // GH-728 - fn handle_new_password(&mut self, new_password: String) { - self.db_password_opt = Some(new_password); + fn handle_bind_message(&mut self, msg: BindMessage) { + self.hopper_opt = Some(msg.peer_actors.hopper.from_hopper_client); + self.hopper_no_lookup_opt = Some(msg.peer_actors.hopper.from_hopper_client_no_lookup); + self.connected_signal_opt = Some(msg.peer_actors.accountant.start); + self.node_to_ui_recipient_opt = Some(msg.peer_actors.ui_gateway.node_to_ui_message_sub); } } @@ -1636,6 +2087,7 @@ enum UndesirabilityType<'hostname> { ExitAndRouteResponse, } +#[derive(Debug)] struct ComputedRouteSegment<'a> { pub nodes: Vec<&'a PublicKey>, pub undesirability: i64, @@ -1659,7 +2111,7 @@ mod tests { use std::any::TypeId; use std::cell::RefCell; use std::convert::TryInto; - use std::net::SocketAddr; + use std::net::{IpAddr, SocketAddr}; use std::path::Path; use std::str::FromStr; use std::sync::{Arc, Mutex}; @@ -1669,7 +2121,9 @@ mod tests { use tokio::prelude::Future; use masq_lib::constants::{DEFAULT_CHAIN, TLS_PORT}; - use masq_lib::messages::{ToMessageBody, UiConnectionChangeBroadcast, UiConnectionStage}; + use masq_lib::messages::{ + CountryCodes, ToMessageBody, UiConnectionChangeBroadcast, UiConnectionStage, + }; use masq_lib::test_utils::utils::{ensure_node_home_directory_exists, TEST_DEFAULT_CHAIN}; use masq_lib::ui_gateway::MessageBody; use masq_lib::ui_gateway::MessagePath::Conversation; @@ -1687,8 +2141,8 @@ mod tests { use crate::sub_lib::hop::LiveHop; use crate::sub_lib::hopper::MessageType; use crate::sub_lib::neighborhood::{ - AskAboutDebutGossipMessage, ConfigurationChange, ConfigurationChangeMessage, - ExpectedServices, NeighborhoodMode, + AskAboutDebutGossipMessage, ConfigChange, ConfigChangeMsg, ExpectedServices, + NeighborhoodMode, WalletPair, }; use crate::sub_lib::neighborhood::{NeighborhoodConfig, DEFAULT_RATE_PACK}; use crate::sub_lib::neighborhood::{NeighborhoodMetadata, RatePack}; @@ -1728,6 +2182,13 @@ mod tests { }; use crate::test_utils::unshared_test_utils::notify_handlers::NotifyLaterHandleMock; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; + use masq_lib::ui_gateway::MessageTarget::ClientId; + + impl NeighborhoodDatabase { + pub fn set_root_key(&mut self, key: &PublicKey) { + self.this_node = key.clone(); + } + } impl Handler> for Neighborhood { type Result = (); @@ -1755,12 +2216,7 @@ mod tests { vec![make_node_descriptor(make_ip(2))], rate_pack(100), ); - let country = "ZZ".to_string(); - let neighborhood_config = NeighborhoodConfig { - mode, - min_hops, - country, - }; + let neighborhood_config = NeighborhoodConfig { mode, min_hops }; let subject = Neighborhood::new( main_cryptde(), @@ -1777,6 +2233,60 @@ mod tests { assert_eq!(subject.db_patch_size, expected_db_patch_size); } + // #[test] + // fn test_wtih_debut_allways_have_half_neighborship_in_handle_gossip() { + // let mut subject = make_standard_subject(); + // subject.min_hops = Hops::OneHop; + // let root_node_key = subject.neighborhood_database.root_key(); + // let first_neighbor = make_node_record(1111, true); + // let mut debut_db = subject.neighborhood_database.clone(); + // debut_db.add_node(first_neighbor.clone()).unwrap(); + // debut_db.this_node = first_neighbor.public_key().clone(); + // debut_db.remove_node(&root_node_key.clone()); + // let resinging = debut_db.node_by_key_mut(first_neighbor.public_key()).unwrap(); + // resinging.resign(); + // let debut = GossipBuilder::new(&debut_db).node(first_neighbor.public_key(), true).build(); + // + // let peer_actors = peer_actors_builder().build(); + // subject.handle_bind_message(BindMessage { peer_actors }); + // + // + // subject.handle_gossip( + // debut, + // SocketAddr::from_str("1.1.1.1:1111").unwrap(), + // make_cpm_recipient().0, + // ); + // + // print!("db after: {:?}\n", subject.neighborhood_database.to_dot_graph()); + // println!("db_countries: {:?}", subject.user_exit_preferences.db_countries); + // } + + #[test] + fn init_db_countries_works_properly() { + let mut subject = make_standard_subject(); + subject.min_hops = Hops::OneHop; + let root_node = subject.neighborhood_database.root().clone(); + let mut first_neighbor = make_node_record(1111, true); + first_neighbor.inner.country_code_opt = Some("CZ".to_string()); + subject + .neighborhood_database + .add_node(first_neighbor.clone()) + .unwrap(); + subject + .neighborhood_database + .add_arbitrary_full_neighbor(root_node.public_key(), first_neighbor.public_key()); + + let filled_db_countries = subject.init_db_countries(); + + subject + .neighborhood_database + .remove_arbitrary_half_neighbor(root_node.public_key(), first_neighbor.public_key()); + let emptied_db_countries = subject.init_db_countries(); + + assert_eq!(filled_db_countries, &["CZ".to_string()]); + assert!(emptied_db_countries.is_empty()); + } + #[test] #[should_panic( expected = "Neighbor masq://eth-ropsten:AQIDBA@1.2.3.4:1234 is not on the mainnet blockchain" @@ -1792,7 +2302,6 @@ mod tests { )) .unwrap()]), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }, earning_wallet.clone(), None, @@ -1818,7 +2327,6 @@ mod tests { )) .unwrap()]), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }, earning_wallet.clone(), None, @@ -1840,7 +2348,6 @@ mod tests { NeighborhoodConfig { mode: NeighborhoodMode::ZeroHop, min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }, earning_wallet.clone(), None, @@ -1870,7 +2377,6 @@ mod tests { DEFAULT_RATE_PACK.clone(), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }, earning_wallet.clone(), None, @@ -1910,7 +2416,6 @@ mod tests { NeighborhoodConfig { mode: NeighborhoodMode::ZeroHop, min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }, earning_wallet.clone(), consuming_wallet.clone(), @@ -1963,7 +2468,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }, earning_wallet.clone(), consuming_wallet.clone(), @@ -2044,7 +2548,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }; let bootstrap_config = bc_from_nc_plus(neighborhood_config, make_wallet("earning"), None, "test"); @@ -2514,7 +3017,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }; let mut subject = Neighborhood::new( main_cryptde(), @@ -2592,7 +3094,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }, earning_wallet.clone(), None, @@ -2636,7 +3137,9 @@ mod tests { let addr: Addr = subject.start(); let sub: Recipient = addr.recipient::(); - let future = sub.send(RouteQueryMessage::data_indefinite_route_request(None, 400)); + let future = sub.send(RouteQueryMessage::data_indefinite_route_request( + None, None, 400, + )); System::current().stop_with_code(0); system.run(); @@ -2652,7 +3155,9 @@ mod tests { let addr: Addr = subject.start(); let sub: Recipient = addr.recipient::(); - let future = sub.send(RouteQueryMessage::data_indefinite_route_request(None, 430)); + let future = sub.send(RouteQueryMessage::data_indefinite_route_request( + None, None, 430, + )); System::current().stop_with_code(0); system.run(); @@ -2692,7 +3197,7 @@ mod tests { } let addr: Addr = subject.start(); let sub: Recipient = addr.recipient::(); - let msg = RouteQueryMessage::data_indefinite_route_request(None, 54000); + let msg = RouteQueryMessage::data_indefinite_route_request(None, None, 54000); let future = sub.send(msg); @@ -2752,7 +3257,7 @@ mod tests { subject.min_hops = Hops::TwoHops; let addr: Addr = subject.start(); let sub: Recipient = addr.recipient::(); - let msg = RouteQueryMessage::data_indefinite_route_request(None, 20000); + let msg = RouteQueryMessage::data_indefinite_route_request(None, None, 20000); let future = sub.send(msg); @@ -2772,7 +3277,7 @@ mod tests { let sub: Recipient = addr.recipient::(); let future = sub.send(RouteQueryMessage::data_indefinite_route_request( - None, 12345, + None, None, 12345, )); System::current().stop_with_code(0); @@ -2868,7 +3373,9 @@ mod tests { let addr: Addr = subject.start(); let sub: Recipient = addr.recipient::(); - let data_route = sub.send(RouteQueryMessage::data_indefinite_route_request(None, 5000)); + let data_route = sub.send(RouteQueryMessage::data_indefinite_route_request( + None, None, 5000, + )); System::current().stop_with_code(0); system.run(); @@ -2963,8 +3470,12 @@ mod tests { let addr: Addr = subject.start(); let sub: Recipient = addr.recipient::(); - let data_route_0 = sub.send(RouteQueryMessage::data_indefinite_route_request(None, 2000)); - let data_route_1 = sub.send(RouteQueryMessage::data_indefinite_route_request(None, 3000)); + let data_route_0 = sub.send(RouteQueryMessage::data_indefinite_route_request( + None, None, 2000, + )); + let data_route_1 = sub.send(RouteQueryMessage::data_indefinite_route_request( + None, None, 3000, + )); System::current().stop_with_code(0); system.run(); @@ -2986,52 +3497,60 @@ mod tests { } #[test] - fn can_update_consuming_wallet_with_configuration_change_msg() { - let cryptde = main_cryptde(); - let system = System::new("can_update_consuming_wallet"); - let (o, r, e, mut subject) = make_o_r_e_subject(); - subject.min_hops = Hops::TwoHops; - let addr: Addr = subject.start(); - let configuration_change_msg_sub = addr.clone().recipient::(); - let route_sub = addr.recipient::(); - let expected_new_wallet = make_paying_wallet(b"new consuming wallet"); - let expected_before_route = Route::round_trip( - segment(&[&o, &r, &e], &Component::ProxyClient), - segment(&[&e, &r, &o], &Component::ProxyServer), - cryptde, - Some(make_paying_wallet(b"consuming")), - 0, - Some(TEST_DEFAULT_CHAIN.rec().contract), - ) - .unwrap(); - let expected_after_route = Route::round_trip( - segment(&[&o, &r, &e], &Component::ProxyClient), - segment(&[&e, &r, &o], &Component::ProxyServer), - cryptde, - Some(expected_new_wallet.clone()), - 1, - Some(TEST_DEFAULT_CHAIN.rec().contract), - ) - .unwrap(); + fn neighborhood_handles_config_change_msg() { + assert_handling_of_config_change_msg( + ConfigChangeMsg { + change: ConfigChange::UpdateWallets(WalletPair { + consuming_wallet: make_paying_wallet(b"new_consuming_wallet"), + earning_wallet: make_wallet("new_earning_wallet"), + }), + }, + |subject: &Neighborhood| { + assert_eq!( + subject.consuming_wallet_opt, + Some(make_paying_wallet(b"new_consuming_wallet")) + ); + let _ = TestLogHandler::new().exists_log_containing("INFO: ConfigChange: Consuming Wallet has been updated: 0xfa133bbf90bce093fa2e7caa6da68054af66793e"); + }, + ); + assert_handling_of_config_change_msg( + ConfigChangeMsg { + change: ConfigChange::UpdatePassword("new password".to_string()), + }, + |subject: &Neighborhood| { + assert_eq!(subject.db_password_opt, Some("new password".to_string())); - let route_request_1 = - route_sub.send(RouteQueryMessage::data_indefinite_route_request(None, 1000)); - configuration_change_msg_sub - .try_send(ConfigurationChangeMessage { - change: ConfigurationChange::UpdateConsumingWallet(expected_new_wallet), - }) - .unwrap(); - let route_request_2 = - route_sub.send(RouteQueryMessage::data_indefinite_route_request(None, 2000)); + let _ = TestLogHandler::new() + .exists_log_containing("INFO: ConfigChange: DB Password has been updated."); + }, + ); + assert_handling_of_config_change_msg( + ConfigChangeMsg { + change: ConfigChange::UpdateMinHops(Hops::FourHops), + }, + |subject: &Neighborhood| { + let expected_db_patch_size = Neighborhood::calculate_db_patch_size(Hops::FourHops); + assert_eq!(subject.min_hops, Hops::FourHops); + assert_eq!(subject.db_patch_size, expected_db_patch_size); + assert_eq!( + subject.overall_connection_status.stage, + OverallConnectionStage::NotConnected + ); + }, + ) + } - System::current().stop(); - system.run(); + fn assert_handling_of_config_change_msg(msg: ConfigChangeMsg, assertions: A) + where + A: FnOnce(&Neighborhood), + { + init_test_logging(); + let mut subject = make_standard_subject(); + subject.logger = Logger::new("ConfigChange"); - let route_1 = route_request_1.wait().unwrap().unwrap().route; - let route_2 = route_request_2.wait().unwrap().unwrap().route; + subject.handle_config_change_msg(msg); - assert_eq!(route_1, expected_before_route); - assert_eq!(route_2, expected_after_route); + assertions(&subject); } #[test] @@ -3065,9 +3584,587 @@ mod tests { } #[test] - fn min_hops_can_be_changed_during_runtime_using_configuration_change_msg() { + fn exit_location_with_multiple_countries_and_priorities_can_be_changed_using_exit_location_msg() + { + init_test_logging(); + let test_name = "exit_location_with_multiple_countries_and_priorities_can_be_changed_using_exit_location_msg"; + let request = UiSetExitLocationRequest { + fallback_routing: true, + exit_locations: vec![ + CountryCodes { + country_codes: vec!["CZ".to_string(), "SK".to_string()], + priority: 1, + }, + CountryCodes { + country_codes: vec!["AT".to_string(), "DE".to_string()], + priority: 2, + }, + CountryCodes { + country_codes: vec!["PL".to_string()], + priority: 3, + }, + ], + show_countries: false, + }; + let message = NodeFromUiMessage { + client_id: 0, + body: request.tmb(0), + }; + let system = System::new(test_name); + let (ui_gateway, _, _) = make_recorder(); + let mut subject = make_standard_subject(); + subject.logger = Logger::new(test_name); + let cz = &mut make_node_record(3456, true); + cz.inner.country_code_opt = Some("CZ".to_string()); + let us = &mut make_node_record(4567, true); + us.inner.country_code_opt = Some("US".to_string()); + let sk = &mut make_node_record(5678, true); + sk.inner.country_code_opt = Some("SK".to_string()); + let de = &mut make_node_record(7777, true); + de.inner.country_code_opt = Some("DE".to_string()); + let at = &mut make_node_record(1325, true); + at.inner.country_code_opt = Some("AT".to_string()); + let pl = &mut make_node_record(2543, true); + pl.inner.country_code_opt = Some("PL".to_string()); + let db = &mut subject.neighborhood_database.clone(); + db.add_node(cz.clone()).unwrap(); + db.add_node(de.clone()).unwrap(); + db.add_node(us.clone()).unwrap(); + db.add_node(sk.clone()).unwrap(); + db.add_node(at.clone()).unwrap(); + db.add_node(pl.clone()).unwrap(); + let mut dual_edge = |a: &NodeRecord, b: &NodeRecord| { + db.add_arbitrary_full_neighbor(a.public_key(), b.public_key()); + }; + dual_edge(&subject.neighborhood_database.root(), cz); + dual_edge(cz, de); + dual_edge(cz, us); + dual_edge(us, sk); + dual_edge(us, at); + dual_edge(at, pl); + subject.neighborhood_database = db.clone(); + let subject_addr = subject.start(); + let peer_actors = peer_actors_builder().ui_gateway(ui_gateway).build(); + let cz_public_key = cz.inner.public_key.clone(); + let us_public_key = us.inner.public_key.clone(); + let sk_public_key = sk.inner.public_key.clone(); + let de_public_key = de.inner.public_key.clone(); + let at_public_key = at.inner.public_key.clone(); + let pl_public_key = pl.inner.public_key.clone(); + let assertion_msg = AssertionsMessage { + assertions: Box::new(move |neighborhood: &mut Neighborhood| { + assert_eq!( + neighborhood.user_exit_preferences.exit_countries, + vec!["SK".to_string(), "AT".to_string(), "PL".to_string(),] + ); + assert_eq!( + neighborhood.user_exit_preferences.location_preference, + ExitPreference::ExitCountryWithFallback + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&cz_public_key) + .unwrap() + .metadata + .country_undesirability, + UNREACHABLE_COUNTRY_PENALTY, + "cz We expecting {}, country is too close to be exit", + UNREACHABLE_COUNTRY_PENALTY + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&us_public_key) + .unwrap() + .metadata + .country_undesirability, + UNREACHABLE_COUNTRY_PENALTY, + "us We expecting {}, country is considered for exit location in fallback", + UNREACHABLE_COUNTRY_PENALTY + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&sk_public_key) + .unwrap() + .metadata + .country_undesirability, + 0u32, + "sk We expecting 0, country is with Priority: 1" + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&de_public_key) + .unwrap() + .metadata + .country_undesirability, + UNREACHABLE_COUNTRY_PENALTY, + "de We expecting {}, country is too close to be exit", + UNREACHABLE_COUNTRY_PENALTY + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&at_public_key) + .unwrap() + .metadata + .country_undesirability, + 1 * COUNTRY_UNDESIRABILITY_FACTOR, + "at We expecting {}, country is with Priority: 2", + 1 * COUNTRY_UNDESIRABILITY_FACTOR + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&pl_public_key) + .unwrap() + .metadata + .country_undesirability, + 2 * COUNTRY_UNDESIRABILITY_FACTOR, + "pl We expecting {}, country is with Priority: 3", + 2 * COUNTRY_UNDESIRABILITY_FACTOR + ); + }), + }; + subject_addr.try_send(BindMessage { peer_actors }).unwrap(); + + subject_addr.try_send(message).unwrap(); + subject_addr.try_send(assertion_msg).unwrap(); + + System::current().stop(); + system.run(); + + TestLogHandler::new().assert_logs_contain_in_order(vec![ + &format!( + "INFO: {}: Fallback Routing is set. Exit location set:", + test_name + ), + &"Country Codes: [\"CZ\", \"SK\"] - Priority: 1; Country Codes: [\"AT\", \"DE\"] - Priority: 2; Country Codes: [\"PL\"] - Priority: 3;" + ]); + } + + #[test] + fn no_exit_location_is_set_if_desired_country_codes_not_present_in_neighborhood() { init_test_logging(); - let test_name = "min_hops_can_be_changed_during_runtime_using_configuration_change_msg"; + let test_name = "exit_location_with_multiple_countries_and_priorities_can_be_changed_using_exit_location_msg"; + let request = UiSetExitLocationRequest { + fallback_routing: true, + exit_locations: vec![CountryCodes { + country_codes: vec!["CZ".to_string(), "SK".to_string(), "IN".to_string()], + priority: 1, + }], + show_countries: true, + }; + let message = NodeFromUiMessage { + client_id: 0, + body: request.tmb(0), + }; + let system = System::new(test_name); + let (ui_gateway, _recorder, arc_recorder) = make_recorder(); + let mut subject = make_standard_subject(); + subject.min_hops = Hops::TwoHops; + subject.logger = Logger::new(test_name); + let es = &mut make_node_record(3456, true); + es.inner.country_code_opt = Some("ES".to_string()); + let us = &mut make_node_record(4567, true); + us.inner.country_code_opt = Some("US".to_string()); + let hu = &mut make_node_record(5678, true); + hu.inner.country_code_opt = Some("US".to_string()); + let de = &mut make_node_record(7777, true); + de.inner.country_code_opt = Some("DE".to_string()); + let at = &mut make_node_record(1325, true); + at.inner.country_code_opt = Some("AT".to_string()); + let pl = &mut make_node_record(2543, true); + pl.inner.country_code_opt = Some("PL".to_string()); + let db = &mut subject.neighborhood_database.clone(); + db.add_node(es.clone()).unwrap(); + db.add_node(de.clone()).unwrap(); + db.add_node(us.clone()).unwrap(); + db.add_node(hu.clone()).unwrap(); + db.add_node(at.clone()).unwrap(); + db.add_node(pl.clone()).unwrap(); + let mut dual_edge = |a: &NodeRecord, b: &NodeRecord| { + db.add_arbitrary_full_neighbor(a.public_key(), b.public_key()); + }; + dual_edge(&subject.neighborhood_database.root(), es); + dual_edge(es, de); + dual_edge(es, us); + dual_edge(us, hu); + dual_edge(us, at); + dual_edge(at, pl); + subject.neighborhood_database = db.clone(); + let subject_addr = subject.start(); + let peer_actors = peer_actors_builder().ui_gateway(ui_gateway).build(); + let es_public_key = es.inner.public_key.clone(); + let us_public_key = us.inner.public_key.clone(); + let hu_public_key = hu.inner.public_key.clone(); + let de_public_key = de.inner.public_key.clone(); + let at_public_key = at.inner.public_key.clone(); + let pl_public_key = pl.inner.public_key.clone(); + let assertion_msg = AssertionsMessage { + assertions: Box::new(move |neighborhood: &mut Neighborhood| { + assert!(neighborhood.user_exit_preferences.exit_countries.is_empty(),); + assert_eq!( + neighborhood.user_exit_preferences.locations_opt, + Some(vec![ExitLocation { + country_codes: vec!["CZ".to_string(), "SK".to_string(), "IN".to_string()], + priority: 1 + }]) + ); + assert_eq!( + neighborhood.user_exit_preferences.db_countries, + vec![ + "AT".to_string(), + "DE".to_string(), + "PL".to_string(), + "US".to_string() + ] + ); + assert_eq!( + neighborhood.user_exit_preferences.location_preference, + ExitPreference::ExitCountryWithFallback + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&es_public_key) + .unwrap() + .metadata + .country_undesirability, + UNREACHABLE_COUNTRY_PENALTY, + "es We expecting {}, country is too close to be exit", + UNREACHABLE_COUNTRY_PENALTY + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&us_public_key) + .unwrap() + .metadata + .country_undesirability, + UNREACHABLE_COUNTRY_PENALTY, + "us We expecting {}, country is considered for exit location in fallback", + UNREACHABLE_COUNTRY_PENALTY + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&hu_public_key) + .unwrap() + .metadata + .country_undesirability, + UNREACHABLE_COUNTRY_PENALTY, + "hu We expecting {}, country is too close to be exit", + UNREACHABLE_COUNTRY_PENALTY + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&de_public_key) + .unwrap() + .metadata + .country_undesirability, + UNREACHABLE_COUNTRY_PENALTY, + "de We expecting {}, country is too close to be exit", + UNREACHABLE_COUNTRY_PENALTY + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&at_public_key) + .unwrap() + .metadata + .country_undesirability, + UNREACHABLE_COUNTRY_PENALTY, + "at We expecting {}, country is considered for exit location in fallback", + UNREACHABLE_COUNTRY_PENALTY + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&pl_public_key) + .unwrap() + .metadata + .country_undesirability, + UNREACHABLE_COUNTRY_PENALTY, + "pl We expecting {}, country is too close to be exit", + UNREACHABLE_COUNTRY_PENALTY + ); + }), + }; + subject_addr.try_send(BindMessage { peer_actors }).unwrap(); + + subject_addr.try_send(message).unwrap(); + subject_addr.try_send(assertion_msg).unwrap(); + + System::current().stop(); + system.run(); + + //println!("recorder: {:#?}", &recorder.try_into().unwrap()); + let exit_location_recording = &arc_recorder.lock().unwrap(); + let exit_handler_response = exit_location_recording + .get_record::(0) + .body + .payload + .clone(); + let log_handler = TestLogHandler::new(); + assert_eq!( + exit_handler_response, + Err(( + 9223372036854775816, + "Exit Countries: [\"AT\", \"DE\", \"PL\", \"US\"]\nExit Location: following desired countries are missing in Neighborhood [\"CZ\", \"SK\", \"IN\"]".to_string(), + )) + ); + log_handler.assert_logs_contain_in_order(vec![ + &format!( + "INFO: {}: Fallback Routing is set. Exit location set:", + test_name + ), + &"Country Codes: [\"CZ\", \"SK\", \"IN\"] - Priority: 1;", + &format!( + "WARN: {}: Exit Location: following desired countries are missing in Neighborhood [\"CZ\", \"SK\", \"IN\"]", + test_name + ), + ]); + } + + #[test] + fn exit_location_is_set_and_unset_with_fallback_routing_using_exit_location_msg() { + init_test_logging(); + let test_name = + "exit_location_is_set_and_unset_with_fallback_routing_using_exit_location_msg"; + let request = UiSetExitLocationRequest { + fallback_routing: false, + exit_locations: vec![ + CountryCodes { + country_codes: vec!["CZ".to_string()], + priority: 1, + }, + CountryCodes { + country_codes: vec!["FR".to_string()], + priority: 2, + }, + ], + show_countries: false, + }; + let set_exit_location_message = NodeFromUiMessage { + client_id: 8765, + body: request.tmb(1234), + }; + let mut subject = make_standard_subject(); + let system = System::new(test_name); + let (ui_gateway, _, ui_gateway_recording_arc) = make_recorder(); + subject.logger = Logger::new(test_name); + let cz = &mut make_node_record(3456, true); + cz.inner.country_code_opt = Some("CZ".to_string()); + let r = &make_node_record(4567, false); + let fr = &mut make_node_record(5678, false); + fr.inner.country_code_opt = Some("FR".to_string()); + let t = &make_node_record(7777, false); + let db = &mut subject.neighborhood_database.clone(); + db.add_node(cz.clone()).unwrap(); + db.add_node(t.clone()).unwrap(); + db.add_node(r.clone()).unwrap(); + db.add_node(fr.clone()).unwrap(); + let mut dual_edge = |a: &NodeRecord, b: &NodeRecord| { + db.add_arbitrary_full_neighbor(a.public_key(), b.public_key()); + }; + dual_edge(&subject.neighborhood_database.root(), cz); + dual_edge(cz, t); + dual_edge(cz, r); + dual_edge(r, fr); + subject.neighborhood_database = db.clone(); + let subject_addr = subject.start(); + let peer_actors = peer_actors_builder().ui_gateway(ui_gateway).build(); + let cz_public_key = cz.inner.public_key.clone(); + let r_public_key = r.inner.public_key.clone(); + let fr_public_key = fr.inner.public_key.clone(); + let t_public_key = t.inner.public_key.clone(); + let assert_country_undesirability_populated = AssertionsMessage { + assertions: Box::new(move |neighborhood: &mut Neighborhood| { + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&cz_public_key) + .unwrap() + .metadata + .country_undesirability, + 0u32, + "CZ - We expecting zero, country is with Priority: 1" + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&r_public_key) + .unwrap() + .metadata + .country_undesirability, + 0u32, + "We expecting 0, country is not considered for exit location, so country_undesirability doesn't matter" + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&fr_public_key) + .unwrap() + .metadata + .country_undesirability, + 1 * COUNTRY_UNDESIRABILITY_FACTOR, + "FR - We expecting {}, country is with Priority: 2", + 1 * COUNTRY_UNDESIRABILITY_FACTOR + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&t_public_key) + .unwrap() + .metadata + .country_undesirability, + 0u32, + "We expecting 0, country is not considered for exit location, so country_undesirability doesn't matter" + ); + }), + }; + let assert_neighborhood_exit_location = AssertionsMessage { + assertions: Box::new(move |neighborhood: &mut Neighborhood| { + assert_eq!( + neighborhood.user_exit_preferences.exit_countries, + vec!["FR".to_string()] + ); + assert_eq!( + neighborhood.user_exit_preferences.location_preference, + ExitPreference::ExitCountryNoFallback + ); + }), + }; + let request_2 = UiSetExitLocationRequest { + fallback_routing: true, + exit_locations: vec![], + show_countries: false, + }; + let clear_exit_location_message = NodeFromUiMessage { + client_id: 6543, + body: request_2.tmb(7894), + }; + let cz_public_key_2 = cz.inner.public_key.clone(); + let r_public_key_2 = r.inner.public_key.clone(); + let fr_public_key_2 = fr.inner.public_key.clone(); + let t_public_key_2 = t.inner.public_key.clone(); + let assert_country_undesirability_and_exit_preference_cleared = AssertionsMessage { + assertions: Box::new(move |neighborhood: &mut Neighborhood| { + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&cz_public_key_2) + .unwrap() + .metadata + .country_undesirability, + 0u32, + "We expecting zero, exit_location was unset" + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&r_public_key_2) + .unwrap() + .metadata + .country_undesirability, + 0u32, + "We expecting zero, exit_location was unset" + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&fr_public_key_2) + .unwrap() + .metadata + .country_undesirability, + 0u32, + "We expecting zero, exit_location was unset" + ); + assert_eq!( + neighborhood + .neighborhood_database + .node_by_key(&t_public_key_2) + .unwrap() + .metadata + .country_undesirability, + 0u32, + "We expecting zero, exit_location was unset" + ); + assert_eq!( + neighborhood.user_exit_preferences.exit_countries.is_empty(), + true + ); + assert_eq!( + neighborhood.user_exit_preferences.location_preference, + ExitPreference::Nothing + ) + }), + }; + + subject_addr.try_send(BindMessage { peer_actors }).unwrap(); + subject_addr.try_send(set_exit_location_message).unwrap(); + subject_addr + .try_send(assert_country_undesirability_populated) + .unwrap(); + subject_addr + .try_send(assert_neighborhood_exit_location) + .unwrap(); + subject_addr.try_send(clear_exit_location_message).unwrap(); + subject_addr + .try_send(assert_country_undesirability_and_exit_preference_cleared) + .unwrap(); + + System::current().stop(); + system.run(); + let ui_gateway_recording = ui_gateway_recording_arc.lock().unwrap(); + let record_one: &NodeToUiMessage = ui_gateway_recording.get_record(0); + let record_two: &NodeToUiMessage = ui_gateway_recording.get_record(1); + + assert_eq!(ui_gateway_recording.len(), 2); + assert_eq!( + record_one, + &NodeToUiMessage { + target: ClientId(8765), + body: MessageBody { + opcode: "exit_location".to_string(), + path: Conversation(1234), + payload: Err( + (9223372036854775816, "Exit Location: following desired countries are missing in Neighborhood [\"CZ\"]".to_string()) + ) + } + } + ); + assert_eq!( + record_two, + &NodeToUiMessage { + target: MessageTarget::ClientId(6543), + body: UiSetExitLocationResponse {}.tmb(7894), + } + ); + TestLogHandler::new().assert_logs_contain_in_order(vec![ + &format!( + "INFO: {}: Fallback Routing NOT set. Exit location set: Country Codes: [\"CZ\"] - Priority: 1; Country Codes: [\"FR\"] - Priority: 2;", + test_name + ), + &format!( + "WARN: {}: Exit Location: following desired countries are missing in Neighborhood [\"CZ\"]", + test_name + ), + &format!( + "INFO: {}: Fallback Routing is set. Exit location unset.", + test_name + ), + ]); + } + + #[test] + fn min_hops_change_triggers_node_to_ui_broadcast_message() { + init_test_logging(); + let test_name = "min_hops_change_triggers_node_to_ui_broadcast_message"; let new_min_hops = Hops::FourHops; let system = System::new(test_name); let (ui_gateway, _, ui_gateway_recording) = make_recorder(); @@ -3080,8 +4177,8 @@ mod tests { subject_addr.try_send(BindMessage { peer_actors }).unwrap(); subject_addr - .try_send(ConfigurationChangeMessage { - change: ConfigurationChange::UpdateMinHops(new_min_hops), + .try_send(ConfigChangeMsg { + change: ConfigChange::UpdateMinHops(new_min_hops), }) .unwrap(); @@ -3139,8 +4236,8 @@ mod tests { subject_addr.try_send(BindMessage { peer_actors }).unwrap(); subject_addr - .try_send(ConfigurationChangeMessage { - change: ConfigurationChange::UpdateMinHops(new_min_hops), + .try_send(ConfigChangeMsg { + change: ConfigChange::UpdateMinHops(new_min_hops), }) .unwrap(); @@ -3189,36 +4286,154 @@ mod tests { ); } - #[test] - fn compose_route_query_response_returns_an_error_when_the_neighbor_is_none() { - let mut subject = make_standard_subject(); + #[test] + fn compose_route_query_response_returns_an_error_when_the_neighbor_is_none() { + let mut subject = make_standard_subject(); + + let result: Result = subject.compose_route_query_response( + RouteSegment::new(vec![&PublicKey::new(&[3, 3, 8])], Component::ProxyClient), + RouteSegment::new(vec![&PublicKey::new(&[8, 3, 3])], Component::ProxyServer), + ); + assert!(result.is_err()); + let error_expectation: String = result.expect_err("Expected an Err but got:"); + assert_eq!( + error_expectation, + "Cannot make multi_hop with unknown neighbor" + ); + assert_eq!(subject.next_return_route_id, 0); + } + + #[test] + fn calculate_expected_service_returns_error_when_given_empty_segment() { + let mut subject = make_standard_subject(); + let a = &make_node_record(3456, true); + let db = &mut subject.neighborhood_database; + db.add_node(a.clone()).unwrap(); + + let result = subject.calculate_expected_service(a.public_key(), None, None); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err(), + "cannot calculate expected service, no keys provided in route segment" + ); + } + + /* + Database: + + A---B---C---D---E + | | | | | + F---G---H---I---J + | | | | | + K---L---M---N---O + | | | | | + P---Q---R---S---T + | | | | | + U---V---W---X---Y + + All these Nodes are standard-mode. L is the root Node. + */ + #[test] + fn find_exit_location_test() { + let mut subject = make_standard_subject(); + let db = &mut subject.neighborhood_database; + let mut generator = 1000; + let mut make_node = |db: &mut NeighborhoodDatabase| { + let node = &db.add_node(make_node_record(generator, true)).unwrap(); + generator += 1; + node.clone() + }; + let mut make_row = |db: &mut NeighborhoodDatabase| { + let n1 = make_node(db); + let n2 = make_node(db); + let n3 = make_node(db); + let n4 = make_node(db); + let n5 = make_node(db); + db.add_arbitrary_full_neighbor(&n1, &n2); + db.add_arbitrary_full_neighbor(&n2, &n3); + db.add_arbitrary_full_neighbor(&n3, &n4); + db.add_arbitrary_full_neighbor(&n4, &n5); + (n1, n2, n3, n4, n5) + }; + let join_rows = |db: &mut NeighborhoodDatabase, first_row, second_row| { + let (f1, f2, f3, f4, f5) = first_row; + let (s1, s2, s3, s4, s5) = second_row; + db.add_arbitrary_full_neighbor(f1, s1); + db.add_arbitrary_full_neighbor(f2, s2); + db.add_arbitrary_full_neighbor(f3, s3); + db.add_arbitrary_full_neighbor(f4, s4); + db.add_arbitrary_full_neighbor(f5, s5); + }; + let designate_root_node = |db: &mut NeighborhoodDatabase, key: &PublicKey| { + let root_node_key = db.root_key().clone(); + db.set_root_key(key); + db.remove_node(&root_node_key); + }; + let (a, b, c, d, e) = make_row(db); + let (f, g, h, i, j) = make_row(db); + let (k, l, m, n, o) = make_row(db); + let (p, q, r, s, t) = make_row(db); + let (u, v, w, x, y) = make_row(db); + join_rows(db, (&a, &b, &c, &d, &e), (&f, &g, &h, &i, &j)); + join_rows(db, (&f, &g, &h, &i, &j), (&k, &l, &m, &n, &o)); + join_rows(db, (&k, &l, &m, &n, &o), (&p, &q, &r, &s, &t)); + join_rows(db, (&p, &q, &r, &s, &t), (&u, &v, &w, &x, &y)); + designate_root_node(db, &l); + + let (_routes, mut exit_nodes) = subject.find_exit_location(&l, 3, 10_000); - let result: Result = subject.compose_route_query_response( - RouteSegment::new(vec![&PublicKey::new(&[3, 3, 8])], Component::ProxyClient), - RouteSegment::new(vec![&PublicKey::new(&[8, 3, 3])], Component::ProxyServer), - ); - assert!(result.is_err()); - let error_expectation: String = result.expect_err("Expected an Err but got:"); - assert_eq!( - error_expectation, - "Cannot make multi_hop with unknown neighbor" - ); - assert_eq!(subject.next_return_route_id, 0); + let total_exit_nodes = exit_nodes.len(); + exit_nodes.sort(); + exit_nodes.dedup(); + let dedup_len = exit_nodes.len(); + assert_eq!(total_exit_nodes, dedup_len); + assert_eq!(total_exit_nodes, 20); } #[test] - fn calculate_expected_service_returns_error_when_given_empty_segment() { + fn find_exit_locations_in_row_structure_test() { let mut subject = make_standard_subject(); - let a = &make_node_record(3456, true); let db = &mut subject.neighborhood_database; - db.add_node(a.clone()).unwrap(); + let mut generator = 1000; + let mut make_node = |db: &mut NeighborhoodDatabase| { + let node = &db.add_node(make_node_record(generator, true)).unwrap(); + generator += 1; + node.clone() + }; + let n1 = make_node(db); + let n2 = make_node(db); + let n3 = make_node(db); + let n4 = make_node(db); + let n5 = make_node(db); + let f1 = make_node(db); + let f2 = make_node(db); + let f3 = make_node(db); + let f4 = make_node(db); + let f5 = make_node(db); + db.add_arbitrary_full_neighbor(&n1, &n2); + db.add_arbitrary_full_neighbor(&n2, &n3); + db.add_arbitrary_full_neighbor(&n3, &n4); + db.add_arbitrary_full_neighbor(&n4, &n5); + db.add_arbitrary_full_neighbor(&n5, &f1); + db.add_arbitrary_full_neighbor(&f1, &f2); + db.add_arbitrary_full_neighbor(&f2, &f3); + db.add_arbitrary_full_neighbor(&f3, &f4); + db.add_arbitrary_full_neighbor(&f4, &f5); + let designate_root_node = |db: &mut NeighborhoodDatabase, key: &PublicKey| { + let root_node_key = db.root_key().clone(); + db.set_root_key(key); + db.remove_node(&root_node_key); + }; + designate_root_node(db, &n1); - let result = subject.calculate_expected_service(a.public_key(), None, None); - assert!(result.is_err()); - assert_eq!( - result.unwrap_err(), - "cannot calculate expected service, no keys provided in route segment" - ); + let (_routes, mut exit_nodes) = subject.find_exit_location(&n1, 3, 10_000); + + let total_exit_nodes = exit_nodes.len(); + exit_nodes.sort(); + exit_nodes.dedup(); + let dedup_len = exit_nodes.len(); + assert_eq!(total_exit_nodes, dedup_len); + assert_eq!(total_exit_nodes, 7); } /* @@ -3304,6 +4519,13 @@ mod tests { fn route_optimization_test() { let mut subject = make_standard_subject(); let db = &mut subject.neighborhood_database; + let (recipient, _) = make_node_to_ui_recipient(); + subject.node_to_ui_recipient_opt = Some(recipient); + let message = UiSetExitLocationRequest { + fallback_routing: true, + exit_locations: vec![], + show_countries: false, + }; let mut generator = 1000; let mut make_node = |db: &mut NeighborhoodDatabase| { let node = &db.add_node(make_node_record(generator, true)).unwrap(); @@ -3331,11 +4553,9 @@ mod tests { db.add_arbitrary_full_neighbor(f4, s4); db.add_arbitrary_full_neighbor(f5, s5); }; - let designate_root_node = |db: &mut NeighborhoodDatabase, key| { - let root_node_key = db.root().public_key().clone(); - let node = db.node_by_key(key).unwrap().clone(); - db.root_mut().inner = node.inner.clone(); - db.root_mut().metadata = node.metadata.clone(); + let designate_root_node = |db: &mut NeighborhoodDatabase, key: &PublicKey| { + let root_node_key = db.root_key().clone(); + db.set_root_key(key); db.remove_node(&root_node_key); }; let (a, b, c, d, e) = make_row(db); @@ -3348,6 +4568,7 @@ mod tests { join_rows(db, (&k, &l, &m, &n, &o), (&p, &q, &r, &s, &t)); join_rows(db, (&p, &q, &r, &s, &t), (&u, &v, &w, &x, &y)); designate_root_node(db, &l); + subject.handle_exit_location_message(message, 0, 0); let before = Instant::now(); // All the target-designated routes from L to N @@ -3365,6 +4586,115 @@ mod tests { ); } + /* Complex testing of country_undesirability on large network with aim to find fallback routing and non fallback routing mechanisms + + Database: + + A---B---C---D---E + | | | | | + F---G---H---I---J + | | | | | + K---L---M---N---O + | | | | | + P---Q---R---S---T + | | | | | + U---V---W---X---Y + + All these Nodes are standard-mode. L is the root Node. + + */ + #[test] + fn route_optimization_country_codes() { + let mut subject = make_standard_subject(); + let db = &mut subject.neighborhood_database; + let (recipient, _) = make_node_to_ui_recipient(); + subject.node_to_ui_recipient_opt = Some(recipient); + let message = UiSetExitLocationRequest { + fallback_routing: false, + exit_locations: vec![CountryCodes { + country_codes: vec!["CZ".to_string()], + priority: 1, + }], + show_countries: false, + }; + println!("db {:?}", db.root()); + let mut generator = 1000; + let mut make_node = |db: &mut NeighborhoodDatabase| { + let node = &db.add_node(make_node_record(generator, true)).unwrap(); + generator += 1; + node.clone() + }; + let mut make_row = |db: &mut NeighborhoodDatabase| { + let n1 = make_node(db); + let n2 = make_node(db); + let n3 = make_node(db); + let n4 = make_node(db); + let n5 = make_node(db); + db.add_arbitrary_full_neighbor(&n1, &n2); + db.add_arbitrary_full_neighbor(&n2, &n3); + db.add_arbitrary_full_neighbor(&n3, &n4); + db.add_arbitrary_full_neighbor(&n4, &n5); + (n1, n2, n3, n4, n5) + }; + let join_rows = |db: &mut NeighborhoodDatabase, first_row, second_row| { + let (f1, f2, f3, f4, f5) = first_row; + let (s1, s2, s3, s4, s5) = second_row; + db.add_arbitrary_full_neighbor(f1, s1); + db.add_arbitrary_full_neighbor(f2, s2); + db.add_arbitrary_full_neighbor(f3, s3); + db.add_arbitrary_full_neighbor(f4, s4); + db.add_arbitrary_full_neighbor(f5, s5); + }; + let designate_root_node = |db: &mut NeighborhoodDatabase, key: &PublicKey| { + let root_node_key = db.root_key().clone(); + db.set_root_key(key); + db.remove_node(&root_node_key); + }; + let (a, b, c, d, e) = make_row(db); + let (f, g, h, i, j) = make_row(db); + let (k, l, m, n, o) = make_row(db); + let (p, q, r, s, t) = make_row(db); + let (u, v, w, x, y) = make_row(db); + + join_rows(db, (&a, &b, &c, &d, &e), (&f, &g, &h, &i, &j)); + join_rows(db, (&f, &g, &h, &i, &j), (&k, &l, &m, &n, &o)); + join_rows(db, (&k, &l, &m, &n, &o), (&p, &q, &r, &s, &t)); + join_rows(db, (&p, &q, &r, &s, &t), (&u, &v, &w, &x, &y)); + + let mut checkdb = db.clone(); + designate_root_node(db, &l); + + db.node_by_key_mut(&c).unwrap().inner.country_code_opt = Some("CZ".to_string()); + checkdb.node_by_key_mut(&c).unwrap().inner.country_code_opt = Some("CZ".to_string()); + db.node_by_key_mut(&t).unwrap().inner.country_code_opt = Some("CZ".to_string()); + checkdb.node_by_key_mut(&t).unwrap().inner.country_code_opt = Some("CZ".to_string()); + + subject.handle_exit_location_message(message, 0, 0); + let before = Instant::now(); + + let route_cz = + subject.find_best_route_segment(&l, None, 3, 10000, RouteDirection::Over, None); + + let after = Instant::now(); + let exit_node = checkdb.node_by_key( + &route_cz + .as_ref() + .unwrap() + .get(route_cz.as_ref().unwrap().len() - 1) + .unwrap(), + ); + assert_eq!( + exit_node.unwrap().inner.country_code_opt, + Some("CZ".to_string()) + ); + let interval = after.duration_since(before); + assert!( + interval.as_millis() <= 100, + "Should have calculated route in <=100ms, but was {}ms", + interval.as_millis() + ); + } + /* Database: @@ -3373,6 +4703,160 @@ mod tests { Test is written from the standpoint of P. Node q is non-routing. */ + #[test] + fn find_best_segment_traces_unreachable_country_code_exit_node() { + init_test_logging(); + let mut subject = make_standard_subject(); + let (recipient, _) = make_node_to_ui_recipient(); + subject.node_to_ui_recipient_opt = Some(recipient); + subject.user_exit_preferences.location_preference = ExitPreference::ExitCountryWithFallback; + let message = UiSetExitLocationRequest { + fallback_routing: false, + exit_locations: vec![CountryCodes { + country_codes: vec!["CZ".to_string()], + priority: 1, + }], + show_countries: false, + }; + let db = &mut subject.neighborhood_database; + let p = &db.root_mut().public_key().clone(); + let a = &db.add_node(make_node_record(2345, true)).unwrap(); + let b = &db.add_node(make_node_record(5678, true)).unwrap(); + let c = &db.add_node(make_node_record(1234, true)).unwrap(); + db.add_arbitrary_full_neighbor(p, c); + db.add_arbitrary_full_neighbor(c, b); + db.add_arbitrary_full_neighbor(c, a); + subject.handle_exit_location_message(message, 0, 0); + + let route_cz = + subject.find_best_route_segment(p, None, 2, 10000, RouteDirection::Over, None); + + assert_eq!(route_cz, None); + } + + #[test] + fn route_for_au_country_code_is_constructed_with_fallback_routing() { + let mut subject = make_standard_subject(); + //let db = &mut subject.neighborhood_database; + let p = &subject + .neighborhood_database + .root_mut() + .public_key() + .clone(); + let a = &subject + .neighborhood_database + .add_node(make_node_record(2345, true)) + .unwrap(); + let b = &subject + .neighborhood_database + .add_node(make_node_record(5678, true)) + .unwrap(); + let c = &subject + .neighborhood_database + .add_node(make_node_record(1234, true)) + .unwrap(); + subject + .neighborhood_database + .add_arbitrary_full_neighbor(p, b); + subject + .neighborhood_database + .add_arbitrary_full_neighbor(b, c); + subject + .neighborhood_database + .add_arbitrary_full_neighbor(b, a); + subject + .neighborhood_database + .add_arbitrary_full_neighbor(a, c); + let cdb = subject.neighborhood_database.clone(); + let (recipient, _) = make_node_to_ui_recipient(); + subject.node_to_ui_recipient_opt = Some(recipient); + let message = UiSetExitLocationRequest { + fallback_routing: true, + exit_locations: vec![CountryCodes { + country_codes: vec!["AU".to_string()], + priority: 1, + }], + show_countries: false, + }; + subject.handle_exit_location_message(message, 0, 0); + + let route_au = + subject.find_best_route_segment(p, None, 2, 10000, RouteDirection::Over, None); + + let exit_node = cdb.node_by_key( + &route_au + .as_ref() + .unwrap() + .get(route_au.as_ref().unwrap().len() - 1) + .unwrap(), + ); + assert_eq!( + exit_node.unwrap().inner.country_code_opt, + Some("AU".to_string()) + ); + } + + #[test] + fn route_for_fr_country_code_is_constructed_without_fallback_routing() { + let mut subject = make_standard_subject(); + let p = &subject + .neighborhood_database + .root_mut() + .public_key() + .clone(); + let a = &subject + .neighborhood_database + .add_node(make_node_record(2345, true)) + .unwrap(); + let b = &subject + .neighborhood_database + .add_node(make_node_record(5678, true)) + .unwrap(); + let c = &subject + .neighborhood_database + .add_node(make_node_record(1234, true)) + .unwrap(); + subject + .neighborhood_database + .add_arbitrary_full_neighbor(p, b); + subject + .neighborhood_database + .add_arbitrary_full_neighbor(b, c); + subject + .neighborhood_database + .add_arbitrary_full_neighbor(b, a); + subject + .neighborhood_database + .add_arbitrary_full_neighbor(a, c); + let cdb = subject.neighborhood_database.clone(); + let (recipient, _) = make_node_to_ui_recipient(); + subject.node_to_ui_recipient_opt = Some(recipient); + let message = UiSetExitLocationRequest { + fallback_routing: false, + exit_locations: vec![CountryCodes { + country_codes: vec!["FR".to_string()], + priority: 1, + }], + show_countries: false, + }; + subject.handle_exit_location_message(message, 0, 0); + + let route_fr = + subject.find_best_route_segment(p, None, 2, 10000, RouteDirection::Over, None); + + let exit_node = cdb.node_by_key( + &route_fr + .as_ref() + .unwrap() + .get(route_fr.as_ref().unwrap().len() - 1) + .unwrap(), + ); + assert_eq!( + exit_node.unwrap().inner.country_code_opt, + Some("FR".to_string()) + ); + } + #[test] fn cant_route_through_non_routing_node() { let mut subject = make_standard_subject(); @@ -3575,7 +5059,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }, earning_wallet.clone(), consuming_wallet.clone(), @@ -3976,7 +5459,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }; let bootstrap_config = bc_from_nc_plus(neighborhood_config, make_wallet("earning"), None, "test"); @@ -4373,6 +5855,59 @@ mod tests { .exists_log_containing("INFO: Neighborhood: Changed public IP from 1.2.3.4 to 4.3.2.1"); } + #[test] + fn handle_gossip_produces_new_entry_in_db_countries() { + init_test_logging(); + let subject_node = make_global_cryptde_node_record(5555, true); // 9e7p7un06eHs6frl5A + let first_neighbor = make_node_record(1050, true); + let mut subject = neighborhood_from_nodes(&subject_node, Some(&first_neighbor)); + let second_neighbor = make_node_record(1234, false); + let new_neighbor = make_node_record(2345, false); + let first_key = subject + .neighborhood_database + .add_node(first_neighbor) + .unwrap(); + let second_key = subject + .neighborhood_database + .add_node(second_neighbor) + .unwrap(); + subject + .neighborhood_database + .add_arbitrary_full_neighbor(subject_node.public_key(), &first_key); + subject + .neighborhood_database + .add_arbitrary_full_neighbor(&first_key, &second_key); + subject.user_exit_preferences.db_countries = subject.init_db_countries(); + let assertion_db_countries = subject.user_exit_preferences.db_countries.clone(); + let peer_actors = peer_actors_builder().build(); + subject.handle_bind_message(BindMessage { peer_actors }); + + let mut neighbor_db = subject.neighborhood_database.clone(); + neighbor_db.add_node(new_neighbor.clone()).unwrap(); + neighbor_db.this_node = first_key.clone(); + neighbor_db.add_arbitrary_full_neighbor(&second_key, new_neighbor.public_key()); + let mut new_second_neighbor = neighbor_db.node_by_key_mut(&second_key).unwrap(); + new_second_neighbor.inner.version = 2; + new_second_neighbor.resign(); + let gossip = GossipBuilder::new(&neighbor_db) + .node(&first_key, true) + .node(&second_key, false) + .node(new_neighbor.public_key(), false) + .build(); + + subject.handle_gossip( + gossip, + SocketAddr::from_str("1.0.5.0:1050").unwrap(), + make_cpm_recipient().0, + ); + + assert!(assertion_db_countries.is_empty()); + assert_eq!( + &subject.user_exit_preferences.db_countries, + &["FR".to_string()] + ) + } + #[test] fn neighborhood_sends_from_gossip_producer_when_acceptance_introductions_are_not_provided() { init_test_logging(); @@ -4722,7 +6257,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }, this_node_inside.earning_wallet(), None, @@ -4741,12 +6275,12 @@ mod tests { }); let tlh = TestLogHandler::new(); tlh.await_log_containing( - &format!("\"BAYFBw\" [label=\"AR v0\\nBAYFBw\\n4.6.5.7:4657\"];"), + &format!("\"BAYFBw\" [label=\"AR v0 US\\nBAYFBw\\n4.6.5.7:4657\"];"), 5000, ); tlh.exists_log_containing("Received Gossip: digraph db { "); - tlh.exists_log_containing("\"AQMCBA\" [label=\"AR v0\\nAQMCBA\"];"); + tlh.exists_log_containing("\"AQMCBA\" [label=\"AR v0 AU\\nAQMCBA\"];"); tlh.exists_log_containing(&format!( "\"{}\" [label=\"{}\"] [shape=none];", cryptde.public_key(), @@ -4786,7 +6320,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }, NodeRecord::earning_wallet_from_key(&cryptde.public_key()), NodeRecord::consuming_wallet_from_key(&cryptde.public_key()), @@ -4844,7 +6377,6 @@ mod tests { rate_pack(100), ), min_hops: min_hops_in_neighborhood, - country: "ZZ".to_string(), }, make_wallet("earning"), None, @@ -4888,7 +6420,6 @@ mod tests { rate_pack(100), ), min_hops: min_hops_in_neighborhood, - country: "ZZ".to_string(), }, make_wallet("earning"), None, @@ -4982,6 +6513,7 @@ mod tests { return_component_opt: None, payload_size: 10000, hostname_opt: None, + target_country_opt: None, }; let unsuccessful_three_hop_route = addr.send(three_hop_route_request); let asserted_node_record = a.clone(); @@ -5079,7 +6611,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string() }, earning_wallet.clone(), consuming_wallet.clone(), @@ -5142,7 +6673,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string() }, earning_wallet.clone(), consuming_wallet.clone(), @@ -5210,7 +6740,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string() }, earning_wallet.clone(), consuming_wallet.clone(), @@ -5275,7 +6804,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string() }, node_record.earning_wallet(), None, @@ -5352,6 +6880,7 @@ mod tests { return_component_opt: Some(Component::ProxyServer), payload_size: 10000, hostname_opt: None, + target_country_opt: None, }); assert_eq!( @@ -5397,6 +6926,7 @@ mod tests { return_component_opt: Some(Component::ProxyServer), payload_size: 10000, hostname_opt: None, + target_country_opt: None, }); let next_door_neighbor_cryptde = @@ -5451,6 +6981,7 @@ mod tests { return_component_opt: Some(Component::ProxyServer), payload_size: 10000, hostname_opt: None, + target_country_opt: None, }); let assert_hops = |cryptdes: Vec, route: &[CryptData]| { @@ -5552,6 +7083,7 @@ mod tests { return_component_opt: Some(Component::ProxyServer), payload_size, hostname_opt: None, + target_country_opt: None, }) .unwrap(); @@ -5788,7 +7320,6 @@ mod tests { NeighborhoodConfig { mode: NeighborhoodMode::ZeroHop, min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }, make_wallet("earning"), None, @@ -5894,54 +7425,6 @@ mod tests { ) } - #[test] - fn new_password_message_works() { - let system = System::new("test"); - let mut subject = make_standard_subject(); - let root_node_record = subject.neighborhood_database.root().clone(); - let set_past_neighbors_params_arc = Arc::new(Mutex::new(vec![])); - let persistent_config = PersistentConfigurationMock::new() - .set_past_neighbors_params(&set_past_neighbors_params_arc) - .set_past_neighbors_result(Ok(())); - subject.persistent_config_opt = Some(Box::new(persistent_config)); - let subject_addr = subject.start(); - let peer_actors = peer_actors_builder().build(); - subject_addr.try_send(BindMessage { peer_actors }).unwrap(); - - // GH-728 - subject_addr - .try_send(NewPasswordMessage { - new_password: "borkety-bork".to_string(), - }) - .unwrap(); - - let mut db = db_from_node(&root_node_record); - let new_neighbor = make_node_record(1324, true); - db.add_node(new_neighbor.clone()).unwrap(); - db.add_arbitrary_half_neighbor(new_neighbor.public_key(), root_node_record.public_key()); - db.node_by_key_mut(root_node_record.public_key()) - .unwrap() - .resign(); - db.node_by_key_mut(new_neighbor.public_key()) - .unwrap() - .resign(); - let gossip = GossipBuilder::new(&db) - .node(new_neighbor.public_key(), true) - .build(); - let cores_package = ExpiredCoresPackage { - immediate_neighbor: new_neighbor.node_addr_opt().unwrap().into(), - paying_wallet: None, - remaining_route: make_meaningless_route(), - payload: gossip, - payload_len: 0, - }; - subject_addr.try_send(cores_package).unwrap(); - System::current().stop(); - system.run(); - let set_past_neighbors_params = set_past_neighbors_params_arc.lock().unwrap(); - assert_eq!(set_past_neighbors_params[0].1, "borkety-bork"); - } - #[test] #[should_panic( expected = "panic message (processed with: node_lib::sub_lib::utils::crash_request_analyzer)" @@ -6159,7 +7642,6 @@ mod tests { rate_pack(100), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }; let bootstrap_config = bc_from_nc_plus(neighborhood_config, make_wallet("earning"), None, test_name); @@ -6184,7 +7666,6 @@ mod tests { NeighborhoodConfig { mode: NeighborhoodMode::ConsumeOnly(vec![make_node_descriptor(make_ip(1))]), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }, make_wallet("earning"), None, diff --git a/node/src/neighborhood/neighborhood_database.rs b/node/src/neighborhood/neighborhood_database.rs index acd6ee99c..4fd76bce7 100644 --- a/node/src/neighborhood/neighborhood_database.rs +++ b/node/src/neighborhood/neighborhood_database.rs @@ -25,7 +25,7 @@ pub const ISOLATED_NODE_GRACE_PERIOD_SECS: u32 = 30; #[derive(Clone)] pub struct NeighborhoodDatabase { - this_node: PublicKey, + pub this_node: PublicKey, by_public_key: HashMap, by_ip_addr: HashMap, logger: Logger, @@ -77,6 +77,10 @@ impl NeighborhoodDatabase { self.node_by_key(&self.this_node).expect("Internal error") } + pub fn root_key(&self) -> &PublicKey { + &self.this_node + } + pub fn root_mut(&mut self) -> &mut NodeRecord { let root_key = &self.this_node.clone(); self.node_by_key_mut(root_key).expect("Internal error") @@ -94,6 +98,13 @@ impl NeighborhoodDatabase { self.by_public_key.get_mut(public_key) } + pub fn nodes_mut(&mut self) -> Vec<&mut NodeRecord> { + self.by_public_key + .iter_mut() + .map(|(_key, node_record)| node_record) + .collect() + } + pub fn node_by_ip(&self, ip_addr: &IpAddr) -> Option<&NodeRecord> { match self.by_ip_addr.get(ip_addr) { Some(key) => self.node_by_key(key), @@ -288,11 +299,16 @@ impl NeighborhoodDatabase { to: k.clone(), }) }); + let country_code = match &nr.inner.country_code_opt { + Some(cc) => cc.clone(), + None => "ZZ".to_string(), + }; node_renderables.push(NodeRenderable { inner: Some(NodeRenderableInner { version: nr.version(), accepts_connections: nr.accepts_connections(), routes_data: nr.routes_data(), + country_code, }), public_key: public_key.clone(), node_addr: nr.node_addr_opt(), @@ -370,7 +386,9 @@ mod tests { use crate::sub_lib::cryptde_null::CryptDENull; use crate::sub_lib::utils::time_t_timestamp; use crate::test_utils::assert_string_contains; - use crate::test_utils::neighborhood_test_utils::{db_from_node, make_node_record}; + use crate::test_utils::neighborhood_test_utils::{ + db_from_node, make_node_record, make_segmented_ip, make_segments, + }; use masq_lib::constants::DEFAULT_CHAIN; use masq_lib::test_utils::utils::TEST_DEFAULT_CHAIN; use std::iter::FromIterator; @@ -561,6 +579,50 @@ mod tests { ); } + #[test] + fn nodes_mut_works() { + let root_node = make_node_record(1234, true); + let node_a = make_node_record(2345, false); + let node_b = make_node_record(3456, true); + let mut subject = NeighborhoodDatabase::new( + root_node.public_key(), + (&root_node).into(), + Wallet::from_str("0x0000000000000000000000000000000000004444").unwrap(), + &CryptDENull::from(root_node.public_key(), TEST_DEFAULT_CHAIN), + ); + subject.add_node(node_a.clone()).unwrap(); + subject.add_node(node_b.clone()).unwrap(); + let mut iterator: u16 = 7890; + let mut keys_nums: Vec<(PublicKey, u16)> = vec![]; + + let mutable_nodes = subject.nodes_mut(); + for node in mutable_nodes { + let (seg1, seg2, seg3, seg4) = make_segments(iterator); + node.metadata.node_addr_opt = Some(NodeAddr::new( + &make_segmented_ip(seg1, seg2, seg3, seg4), + &[iterator], + )); + keys_nums.push((node.inner.public_key.clone(), iterator)); + iterator += 1; + } + + for (pub_key, num) in keys_nums { + let (seg1, seg2, seg3, seg4) = make_segments(num); + assert_eq!( + &subject + .node_by_key(&pub_key) + .unwrap() + .clone() + .metadata + .node_addr_opt, + &Some(NodeAddr::new( + &make_segmented_ip(seg1, seg2, seg3, seg4), + &[num] + )) + ); + } + } + #[test] fn add_half_neighbor_works() { let this_node = make_node_record(1234, true); @@ -792,19 +854,19 @@ mod tests { assert_eq!(result.matches("->").count(), 8); assert_string_contains( &result, - "\"AQIDBA\" [label=\"AR v1\\nAQIDBA\\n1.2.3.4:1234\"] [style=filled];", + "\"AQIDBA\" [label=\"AR v1 AU\\nAQIDBA\\n1.2.3.4:1234\"] [style=filled];", ); assert_string_contains( &result, - "\"AgMEBQ\" [label=\"AR v0\\nAgMEBQ\\n2.3.4.5:2345\"];", + "\"AgMEBQ\" [label=\"AR v0 FR\\nAgMEBQ\\n2.3.4.5:2345\"];", ); assert_string_contains( &result, - "\"AwQFBg\" [label=\"AR v0\\nAwQFBg\\n3.4.5.6:3456\"];", + "\"AwQFBg\" [label=\"AR v0 FR\\nAwQFBg\\n3.4.5.6:3456\"];", ); assert_string_contains( &result, - "\"BAUGBw\" [label=\"AR v0\\nBAUGBw\\n4.5.6.7:4567\"];", + "\"BAUGBw\" [label=\"AR v0 US\\nBAUGBw\\n4.5.6.7:4567\"];", ); assert_string_contains(&result, "\"AQIDBA\" -> \"AgMEBQ\";"); assert_string_contains(&result, "\"AgMEBQ\" -> \"AQIDBA\";"); diff --git a/node/src/neighborhood/node_record.rs b/node/src/neighborhood/node_record.rs index 2a944f138..a84349e8f 100644 --- a/node/src/neighborhood/node_record.rs +++ b/node/src/neighborhood/node_record.rs @@ -347,7 +347,7 @@ pub struct NodeRecordMetadata { pub node_addr_opt: Option, pub unreachable_hosts: HashSet, pub node_location_opt: Option, - pub node_distrust_score: u32, + pub country_undesirability: u32, //TODO introduce various scores for latency, reliability and so } @@ -358,7 +358,7 @@ impl NodeRecordMetadata { node_addr_opt: None, unreachable_hosts: Default::default(), node_location_opt: None, - node_distrust_score: Default::default(), + country_undesirability: 0u32, } } } 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 037736a2c..19b0b958a 100644 --- a/node/src/node_configurator/configurator.rs +++ b/node/src/node_configurator/configurator.rs @@ -26,10 +26,8 @@ use crate::db_config::config_dao::ConfigDaoReal; use crate::db_config::persistent_configuration::{ PersistentConfigError, PersistentConfiguration, PersistentConfigurationReal, }; -use crate::sub_lib::configurator::NewPasswordMessage; -use crate::sub_lib::neighborhood::ConfigurationChange::UpdateMinHops; -use crate::sub_lib::neighborhood::{ConfigurationChangeMessage, Hops}; -use crate::sub_lib::peer_actors::BindMessage; +use crate::sub_lib::neighborhood::{ConfigChange, ConfigChangeMsg, Hops, WalletPair}; +use crate::sub_lib::peer_actors::{BindMessage, ConfigChangeSubs}; use crate::sub_lib::utils::{db_connection_launch_panic, handle_ui_crash_request}; use crate::sub_lib::wallet::Wallet; use crate::test_utils::main_cryptde; @@ -48,9 +46,8 @@ pub const CRASH_KEY: &str = "CONFIGURATOR"; pub struct Configurator { persistent_config: Box, - new_password_subs: Option>>, // GH-728 node_to_ui_sub_opt: Option>, - configuration_change_msg_sub_opt: Option>, + config_change_subs_opt: Option, crashable: bool, logger: Logger, } @@ -64,9 +61,7 @@ impl Handler for Configurator { fn handle(&mut self, msg: BindMessage, _ctx: &mut Self::Context) -> Self::Result { self.node_to_ui_sub_opt = Some(msg.peer_actors.ui_gateway.node_to_ui_message_sub.clone()); - self.new_password_subs = Some(vec![msg.peer_actors.neighborhood.new_password_sub]); // GH-728 - self.configuration_change_msg_sub_opt = - Some(msg.peer_actors.neighborhood.configuration_change_msg_sub); + self.config_change_subs_opt = Some(msg.peer_actors.config_change_subs()); } } @@ -113,9 +108,8 @@ impl Configurator { Box::new(PersistentConfigurationReal::new(Box::new(config_dao))); Configurator { persistent_config, - new_password_subs: None, // GH-728 node_to_ui_sub_opt: None, - configuration_change_msg_sub_opt: None, + config_change_subs_opt: None, crashable, logger: Logger::new("Configurator"), } @@ -154,7 +148,7 @@ impl Configurator { { Ok(_) => { let broadcast = UiNewPasswordBroadcast {}.tmb(0); - self.send_password_changes(msg.new_password.clone()); + self.send_new_password_to_subs(msg.new_password); self.send_to_ui_gateway(MessageTarget::AllExcept(client_id), broadcast); UiChangePasswordResponse {}.tmb(context_id) } @@ -242,9 +236,13 @@ impl Configurator { msg: UiGenerateWalletsRequest, context_id: u64, ) -> MessageBody { + let db_password = msg.db_password.clone(); match Self::unfriendly_handle_generate_wallets(msg, context_id, &mut self.persistent_config) { - Ok(message_body) => message_body, + Ok(message_body) => { + self.send_updated_wallets_to_subs(&db_password); + message_body + } Err((code, msg)) => MessageBody { opcode: "generateWallets".to_string(), path: MessagePath::Conversation(context_id), @@ -258,9 +256,13 @@ impl Configurator { msg: UiRecoverWalletsRequest, context_id: u64, ) -> MessageBody { + let db_password = msg.db_password.clone(); match Self::unfriendly_handle_recover_wallets(msg, context_id, &mut self.persistent_config) { - Ok(message_body) => message_body, + Ok(message_body) => { + self.send_updated_wallets_to_subs(&db_password); + message_body + } Err((code, msg)) => MessageBody { opcode: "recoverWallets".to_string(), path: MessagePath::Conversation(context_id), @@ -549,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!( @@ -647,7 +650,7 @@ impl Configurator { exit_byte_rate, exit_service_rate, }, - start_block, + start_block_opt, scan_intervals: UiScanIntervals { pending_payable_sec, payable_sec, @@ -701,23 +704,15 @@ impl Configurator { msg: UiSetConfigurationRequest, context_id: u64, ) -> MessageBody { - let configuration_change_msg_sub_opt = self.configuration_change_msg_sub_opt.clone(); - let logger = &self.logger; debug!( - logger, + self.logger, "A request from UI received: {:?} from context id: {}", msg, context_id ); - match Self::unfriendly_handle_set_configuration( - msg, - context_id, - &mut self.persistent_config, - configuration_change_msg_sub_opt, - logger, - ) { - Ok(message_body) => message_body, + match self.unfriendly_handle_set_configuration(msg, context_id) { + Ok(response) => response, Err((code, msg)) => { error!( - logger, + self.logger, "{}", format!("The UiSetConfigurationRequest failed with an error {code}: {msg}") ); @@ -731,34 +726,24 @@ impl Configurator { } fn unfriendly_handle_set_configuration( + &mut self, msg: UiSetConfigurationRequest, context_id: u64, - persistent_config: &mut Box, - configuration_change_msg_sub_opt: Option>, - logger: &Logger, ) -> Result { let password: Option = None; //prepared for an upgrade with parameters requiring the password match password { - None => { - if "gas-price" == &msg.name { - Self::set_gas_price(msg.value, persistent_config)?; - } else if "start-block" == &msg.name { - Self::set_start_block(msg.value, persistent_config)?; - } else if "min-hops" == &msg.name { - Self::set_min_hops( - msg.value, - persistent_config, - configuration_change_msg_sub_opt, - logger, - )?; - } else { + None => match msg.name.as_str() { + "gas-price" => self.set_gas_price(msg.value)?, + "min-hops" => self.set_min_hops(msg.value)?, + "start-block" => self.set_start_block(msg.value)?, + _ => { return Err(( UNRECOGNIZED_PARAMETER, format!("This parameter name is not known: {}", &msg.name), - )); + )) } - } + }, Some(_password) => { unimplemented!(); } @@ -767,61 +752,50 @@ impl Configurator { Ok(UiSetConfigurationResponse {}.tmb(context_id)) } - fn set_gas_price( - string_price: String, - config: &mut Box, - ) -> Result<(), (u64, String)> { + fn set_gas_price(&mut self, string_price: String) -> Result<(), (u64, String)> { let price_number = match string_price.parse::() { Ok(num) => num, Err(e) => return Err((NON_PARSABLE_VALUE, format!("gas price: {:?}", e))), }; - match config.set_gas_price(price_number) { + match self.persistent_config.set_gas_price(price_number) { Ok(_) => Ok(()), Err(e) => Err((CONFIGURATOR_WRITE_ERROR, format!("gas price: {:?}", e))), } } - fn set_min_hops( - string_number: String, - config: &mut Box, - configuration_change_msg_sub_opt: Option>, - logger: &Logger, - ) -> Result<(), (u64, String)> { - let min_hops = match Hops::from_str(&string_number) { + fn set_min_hops(&mut self, min_hops_value: String) -> Result<(), (u64, String)> { + let min_hops = match Hops::from_str(&min_hops_value) { Ok(min_hops) => min_hops, Err(e) => { return Err((NON_PARSABLE_VALUE, format!("min hops: {:?}", e))); } }; - match config.set_min_hops(min_hops) { + match self.persistent_config.set_min_hops(min_hops) { Ok(_) => { debug!( - logger, + self.logger, "The value of min-hops has been changed to {}-hop inside the database", min_hops ); - configuration_change_msg_sub_opt - .as_ref() - .expect("Configurator is unbound") - .try_send(ConfigurationChangeMessage { - change: UpdateMinHops(min_hops), - }) - .expect("Neighborhood is dead"); + self.send_config_change_msg(ConfigChangeMsg { + change: ConfigChange::UpdateMinHops(min_hops), + }); Ok(()) } Err(e) => Err((CONFIGURATOR_WRITE_ERROR, format!("min hops: {:?}", e))), } } - fn set_start_block( - string_number: String, - config: &mut Box, - ) -> Result<(), (u64, String)> { - let block_number = match string_number.parse::() { - Ok(num) => num, - Err(e) => return Err((NON_PARSABLE_VALUE, format!("start block: {:?}", e))), + fn set_start_block(&mut self, string_number: String) -> Result<(), (u64, String)> { + 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 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))), } @@ -836,17 +810,43 @@ impl Configurator { .expect("UiGateway is dead"); } - fn send_password_changes(&self, new_password: String) { - // GH-728 - let msg = NewPasswordMessage { new_password }; - self.new_password_subs + fn send_config_change_msg(&self, msg: ConfigChangeMsg) { + self.config_change_subs_opt .as_ref() - .expect("Configurator is unbound") + .expect("ConfigChangeSubs are uninitialized") .iter() - .for_each(|sub| { - sub.try_send(msg.clone()) - .expect("New password recipient is dead") + .for_each(|recipient| { + recipient + .try_send(msg.clone()) + .expect("ConfigChangeMsg recipient is dead") + }) + } + + fn send_new_password_to_subs(&self, new_password: String) { + let msg = ConfigChangeMsg { + change: ConfigChange::UpdatePassword(new_password), + }; + self.send_config_change_msg(msg); + } + + fn send_updated_wallets_to_subs(&self, db_password: &str) { + let consuming_wallet_result_opt = self + .persistent_config + .as_ref() + .consuming_wallet(db_password); + let earning_wallet_result_opt = self.persistent_config.as_ref().earning_wallet(); + if let (Ok(Some(new_consuming_wallet)), Ok(Some(new_earning_wallet))) = + (consuming_wallet_result_opt, earning_wallet_result_opt) + { + self.send_config_change_msg(ConfigChangeMsg { + change: ConfigChange::UpdateWallets(WalletPair { + consuming_wallet: new_consuming_wallet, + earning_wallet: new_earning_wallet, + }), }); + } else { + panic!("Unable to retrieve wallets from persistent configuration") + }; } fn call_handler MessageBody>( @@ -876,10 +876,9 @@ impl Configurator { mod tests { use actix::System; use masq_lib::messages::{ - ToMessageBody, UiChangePasswordResponse, UiCheckPasswordRequest, UiCheckPasswordResponse, - UiGenerateSeedSpec, UiGenerateWalletsResponse, UiNewPasswordBroadcast, UiPaymentThresholds, - UiRatePack, UiRecoverSeedSpec, UiScanIntervals, UiStartOrder, UiWalletAddressesRequest, - UiWalletAddressesResponse, + ToMessageBody, UiCheckPasswordRequest, UiCheckPasswordResponse, UiGenerateSeedSpec, + UiGenerateWalletsResponse, UiPaymentThresholds, UiRatePack, UiRecoverSeedSpec, + UiScanIntervals, UiStartOrder, UiWalletAddressesRequest, UiWalletAddressesResponse, }; use masq_lib::ui_gateway::{MessagePath, MessageTarget}; use std::path::Path; @@ -900,16 +899,16 @@ mod tests { use crate::blockchain::test_utils::make_meaningless_phrase_words; use crate::database::db_initializer::{DbInitializer, DbInitializerReal}; use crate::sub_lib::accountant::{PaymentThresholds, ScanIntervals}; - use crate::sub_lib::configurator::NewPasswordMessage; use crate::sub_lib::cryptde::PublicKey as PK; use crate::sub_lib::cryptde::{CryptDE, PlainData}; - use crate::sub_lib::neighborhood::{ConfigurationChange, NodeDescriptor, RatePack}; + use crate::sub_lib::neighborhood::{ConfigChange, NodeDescriptor, RatePack}; use crate::sub_lib::node_addr::NodeAddr; use crate::sub_lib::wallet::Wallet; 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, ZERO, }; + use crate::test_utils::{make_paying_wallet, make_wallet}; use bip39::{Language, Mnemonic}; use masq_lib::blockchains::chains::Chain; use masq_lib::constants::MISSING_DATA; @@ -932,16 +931,11 @@ mod tests { .initialize(&data_dir, DbInitializationConfig::test_default()) .unwrap(), ))); - let (recorder, _, _) = make_recorder(); - let recorder_addr = recorder.start(); - let (neighborhood, _, _) = make_recorder(); - let neighborhood_addr = neighborhood.start(); - + let peer_actors = peer_actors_builder().build(); let mut subject = Configurator::new(data_dir, false); + subject.config_change_subs_opt = Some(peer_actors.config_change_subs()); + subject.node_to_ui_sub_opt = Some(peer_actors.ui_gateway.node_to_ui_message_sub); - subject.node_to_ui_sub_opt = Some(recorder_addr.recipient()); - subject.configuration_change_msg_sub_opt = Some(neighborhood_addr.recipient()); - subject.new_password_subs = Some(vec![]); // GH-728 let _ = subject.handle_change_password( UiChangePasswordRequest { old_password_opt: None, @@ -950,6 +944,7 @@ mod tests { 0, 0, ); + assert_eq!( verifier.check_password(Some("password".to_string())), Ok(true) @@ -1055,19 +1050,19 @@ mod tests { } #[test] - fn change_password_works() { - let system = System::new("test"); - let change_password_params_arc = Arc::new(Mutex::new(vec![])); + fn the_password_is_synchronised_among_other_actors_when_modified() { + let system = System::new("the_password_is_synchronised_among_other_actors_when_modified"); + let new_password = "omae wa mou shindeiru"; let persistent_config = PersistentConfigurationMock::new() - .change_password_params(&change_password_params_arc) + .check_password_result(Ok(true)) .change_password_result(Ok(())); let subject = make_subject(Some(persistent_config)); let subject_addr = subject.start(); - let (ui_gateway, _, ui_gateway_recording_arc) = make_recorder(); let (neighborhood, _, neighborhood_recording_arc) = make_recorder(); + let (ui_gateway, _, ui_gateway_recording_arc) = make_recorder(); let peer_actors = peer_actors_builder() - .ui_gateway(ui_gateway) .neighborhood(neighborhood) + .ui_gateway(ui_gateway) .build(); subject_addr.try_send(BindMessage { peer_actors }).unwrap(); @@ -1075,8 +1070,8 @@ mod tests { .try_send(NodeFromUiMessage { client_id: 1234, body: UiChangePasswordRequest { - old_password_opt: Some("old_password".to_string()), - new_password: "new_password".to_string(), + old_password_opt: None, + new_password: new_password.to_string(), } .tmb(4321), }) @@ -1084,10 +1079,13 @@ mod tests { System::current().stop(); system.run(); - let change_password_params = change_password_params_arc.lock().unwrap(); + let neighborhood_recording = neighborhood_recording_arc.lock().unwrap(); + let expected_configuration_msg = ConfigChangeMsg { + change: ConfigChange::UpdatePassword(new_password.to_string()), + }; assert_eq!( - *change_password_params, - vec![(Some("old_password".to_string()), "new_password".to_string())] + neighborhood_recording.get_record::(0), + &expected_configuration_msg ); let ui_gateway_recording = ui_gateway_recording_arc.lock().unwrap(); assert_eq!( @@ -1104,15 +1102,84 @@ mod tests { body: UiChangePasswordResponse {}.tmb(4321) } ); + } + + #[test] + fn the_wallets_are_synchronised_among_other_actors_when_modified() { + assert_wallets_synchronisation_among_other_actors(NodeFromUiMessage { + client_id: 1234, + body: make_example_generate_wallets_request().tmb(4321), + }); + assert_wallets_synchronisation_among_other_actors(NodeFromUiMessage { + client_id: 1234, + body: make_example_recover_wallets_request_with_paths().tmb(4321), + }); + } + + fn assert_wallets_synchronisation_among_other_actors(msg: NodeFromUiMessage) { + let system = System::new("assert_wallets_synchronisation_among_other_actors"); + let consuming_wallet = make_paying_wallet(b"consuming"); + let earning_wallet = make_wallet("earning"); + let persistent_config = PersistentConfigurationMock::new() + .check_password_result(Ok(true)) + .set_wallet_info_result(Ok(())) + .consuming_wallet_result(Ok(Some(consuming_wallet.clone()))) + .earning_wallet_result(Ok(Some(earning_wallet.clone()))); + let subject = make_subject(Some(persistent_config)); + let subject_addr = subject.start(); + let (accountant, _, accountant_recording_arc) = make_recorder(); + let (neighborhood, _, neighborhood_recording_arc) = make_recorder(); + let peer_actors = peer_actors_builder() + .neighborhood(neighborhood) + .accountant(accountant) + .build(); + subject_addr.try_send(BindMessage { peer_actors }).unwrap(); + + subject_addr.try_send(msg).unwrap(); + + System::current().stop(); + system.run(); + let accountant_recording = accountant_recording_arc.lock().unwrap(); let neighborhood_recording = neighborhood_recording_arc.lock().unwrap(); - // GH-728 + let expected_configuration_msg = ConfigChangeMsg { + change: ConfigChange::UpdateWallets(WalletPair { + consuming_wallet, + earning_wallet, + }), + }; assert_eq!( - neighborhood_recording.get_record::(0), - &NewPasswordMessage { - new_password: "new_password".to_string() - } + accountant_recording.get_record::(0), + &expected_configuration_msg + ); + assert_eq!( + neighborhood_recording.get_record::(0), + &expected_configuration_msg ); - assert_eq!(neighborhood_recording.len(), 1); + } + + #[test] + #[should_panic(expected = "Unable to retrieve wallets from persistent configuration")] + fn panics_if_consuming_wallet_can_not_be_retrieved_before_sending_to_subs() { + let persistent_config = PersistentConfigurationMock::new() + .check_password_result(Ok(true)) + .set_wallet_info_result(Ok(())) + .consuming_wallet_result(Ok(None)) + .earning_wallet_result(Ok(Some(make_wallet("earning")))); + let subject = make_subject(Some(persistent_config)); + let subject_addr = subject.start(); + let peer_actors = peer_actors_builder().build(); + subject_addr.try_send(BindMessage { peer_actors }).unwrap(); + + subject_addr + .try_send(NodeFromUiMessage { + client_id: 1234, + body: make_example_generate_wallets_request().tmb(4321), + }) + .unwrap(); + + let system = System::new("test"); + System::current().stop(); + system.run(); } #[test] @@ -1338,11 +1405,15 @@ mod tests { let check_password_params_arc = Arc::new(Mutex::new(vec![])); let set_wallet_info_params_arc = Arc::new(Mutex::new(vec![])); let (ui_gateway, _, ui_gateway_recording_arc) = make_recorder(); + let consuming_wallet_for_mock = make_paying_wallet(b"consuming"); + let earning_wallet_for_mock = make_wallet("earning"); let persistent_config = PersistentConfigurationMock::new() .check_password_params(&check_password_params_arc) .check_password_result(Ok(true)) .set_wallet_info_params(&set_wallet_info_params_arc) - .set_wallet_info_result(Ok(())); + .set_wallet_info_result(Ok(())) + .consuming_wallet_result(Ok(Some(consuming_wallet_for_mock))) + .earning_wallet_result(Ok(Some(earning_wallet_for_mock))); let subject = make_subject(Some(persistent_config)); let subject_addr = subject.start(); let peer_actors = peer_actors_builder().ui_gateway(ui_gateway).build(); @@ -1405,7 +1476,6 @@ mod tests { ); let check_password_params = check_password_params_arc.lock().unwrap(); assert_eq!(*check_password_params, vec![Some("password".to_string())]); - let set_wallet_info_params = set_wallet_info_params_arc.lock().unwrap(); assert_eq!( *set_wallet_info_params, @@ -1604,7 +1674,9 @@ mod tests { .check_password_params(&check_password_params_arc) .check_password_result(Ok(true)) .set_wallet_info_params(&set_wallet_info_params_arc) - .set_wallet_info_result(Ok(())); + .set_wallet_info_result(Ok(())) + .consuming_wallet_result(Ok(Some(make_paying_wallet(b"consuming")))) + .earning_wallet_result(Ok(Some(make_wallet("earning")))); let subject = make_subject(Some(persistent_config)); let subject_addr = subject.start(); let peer_actors = peer_actors_builder().ui_gateway(ui_gateway).build(); @@ -1671,14 +1743,22 @@ mod tests { ); } + fn make_config_change_subs() -> ConfigChangeSubs { + let peer_actors = peer_actors_builder().build(); + peer_actors.config_change_subs() + } + #[test] fn handle_recover_wallets_works_with_earning_wallet_derivation_path() { let set_wallet_info_params_arc = Arc::new(Mutex::new(vec![])); let persistent_config = PersistentConfigurationMock::new() .check_password_result(Ok(true)) .set_wallet_info_params(&set_wallet_info_params_arc) - .set_wallet_info_result(Ok(())); + .set_wallet_info_result(Ok(())) + .consuming_wallet_result(Ok(Some(make_paying_wallet(b"consuming")))) + .earning_wallet_result(Ok(Some(make_wallet("earning")))); let mut subject = make_subject(Some(persistent_config)); + subject.config_change_subs_opt = Some(make_config_change_subs()); let mut request = make_example_recover_wallets_request_with_paths(); request.earning_derivation_path_opt = Some(derivation_path(0, 5)); @@ -1730,8 +1810,11 @@ mod tests { let persistent_config = PersistentConfigurationMock::new() .check_password_result(Ok(true)) .set_wallet_info_params(&set_wallet_info_params_arc) - .set_wallet_info_result(Ok(())); + .set_wallet_info_result(Ok(())) + .consuming_wallet_result(Ok(Some(make_paying_wallet(b"consuming")))) + .earning_wallet_result(Ok(Some(make_wallet("earning")))); let mut subject = make_subject(Some(persistent_config)); + subject.config_change_subs_opt = Some(make_config_change_subs()); let mut request = make_example_recover_wallets_request_with_paths(); request .seed_spec_opt @@ -2040,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] @@ -2193,11 +2319,10 @@ mod tests { .set_min_hops_result(Ok(())); let system = System::new("handle_set_configuration_works_for_min_hops"); let (neighborhood, _, neighborhood_recording_arc) = make_recorder(); - let neighborhood_addr = neighborhood.start(); + let peer_actors = peer_actors_builder().neighborhood(neighborhood).build(); let mut subject = make_subject(Some(persistent_config)); subject.logger = Logger::new(test_name); - subject.configuration_change_msg_sub_opt = - Some(neighborhood_addr.recipient::()); + subject.config_change_subs_opt = Some(peer_actors.config_change_subs()); let result = subject.handle_set_configuration( UiSetConfigurationRequest { @@ -2210,8 +2335,7 @@ mod tests { System::current().stop(); system.run(); let neighborhood_recording = neighborhood_recording_arc.lock().unwrap(); - let message_to_neighborhood = - neighborhood_recording.get_record::(0); + let message_to_neighborhood = neighborhood_recording.get_record::(0); let set_min_hops_params = set_min_hops_params_arc.lock().unwrap(); let min_hops_in_db = set_min_hops_params.get(0).unwrap(); assert_eq!( @@ -2224,8 +2348,8 @@ mod tests { ); assert_eq!( message_to_neighborhood, - &ConfigurationChangeMessage { - change: ConfigurationChange::UpdateMinHops(new_min_hops) + &ConfigChangeMsg { + change: ConfigChange::UpdateMinHops(new_min_hops) } ); assert_eq!(*min_hops_in_db, new_min_hops); @@ -2272,15 +2396,12 @@ mod tests { let test_name = "handle_set_configuration_handles_failure_on_min_hops_database_issue"; let persistent_config = PersistentConfigurationMock::new() .set_min_hops_result(Err(PersistentConfigError::TransactionError)); - let system = - System::new("handle_set_configuration_handles_failure_on_min_hops_database_issue"); + let system = System::new(test_name); let (neighborhood, _, neighborhood_recording_arc) = make_recorder(); - let configuration_change_msg_sub = neighborhood - .start() - .recipient::(); + let peer_actors = peer_actors_builder().neighborhood(neighborhood).build(); let mut subject = make_subject(Some(persistent_config)); - subject.configuration_change_msg_sub_opt = Some(configuration_change_msg_sub); subject.logger = Logger::new(test_name); + subject.config_change_subs_opt = Some(peer_actors.config_change_subs()); let result = subject.handle_set_configuration( UiSetConfigurationRequest { @@ -2425,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)); @@ -2468,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, @@ -2556,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)); @@ -2600,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, @@ -2627,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)) @@ -2693,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, @@ -2716,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(), ))); @@ -2766,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))) @@ -2874,9 +2994,8 @@ mod tests { fn from(persistent_config: Box) -> Self { Configurator { persistent_config, - new_password_subs: None, // GH-728 node_to_ui_sub_opt: None, - configuration_change_msg_sub_opt: None, + config_change_subs_opt: None, crashable: false, logger: Logger::new("Configurator"), } diff --git a/node/src/node_configurator/mod.rs b/node/src/node_configurator/mod.rs index a45a2969a..e75683f2e 100644 --- a/node/src/node_configurator/mod.rs +++ b/node/src/node_configurator/mod.rs @@ -119,15 +119,16 @@ fn get_data_directory_from_mc( } } -fn replace_tilde(config_path: PathBuf) -> PathBuf { +fn replace_tilde(config_path: PathBuf, dirs_wrapper: &dyn DirsWrapper) -> PathBuf { match config_path.starts_with("~") { true => PathBuf::from( config_path.display().to_string().replacen( '~', - home_dir() - .expect("expected users home_dir") + dirs_wrapper + .home_dir() + .expect("expected users home dir") .to_str() - .expect("expected str home_dir"), + .expect("expected home dir"), 1, ), ), @@ -166,12 +167,13 @@ fn get_config_file_from_mc( multi_config: &MultiConfig, data_directory: &Path, data_directory_def: bool, + dirs_wrapper: &dyn DirsWrapper, ) -> FieldPair { let mut panic: bool = false; let config_file = value_m!(multi_config, "config-file", PathBuf); match config_file { Some(config_path) => { - let config_path = replace_tilde(config_path); + let config_path = replace_tilde(config_path, dirs_wrapper); let config_path = replace_dots(config_path); let config_path = replace_relative_path(config_path, data_directory_def, data_directory, &mut panic); @@ -216,6 +218,7 @@ fn config_file_data_dir_real_user_chain_from_mc( &multi_config, &initialization_data.data_directory.item, initialization_data.data_directory.user_specified, + dirs_wrapper, ); initialization_data } @@ -305,7 +308,7 @@ pub trait DirsWrapper: Send { fn dup(&self) -> Box; // because implementing Clone for traits is problematic. } -pub struct DirsWrapperReal; +pub struct DirsWrapperReal {} impl DirsWrapper for DirsWrapperReal { fn data_dir(&self) -> Option { @@ -315,7 +318,19 @@ impl DirsWrapper for DirsWrapperReal { home_dir() } fn dup(&self) -> Box { - Box::new(DirsWrapperReal) + Box::new(DirsWrapperReal::default()) + } +} + +impl DirsWrapperReal { + pub fn new() -> Self { + Self {} + } +} + +impl Default for DirsWrapperReal { + fn default() -> Self { + DirsWrapperReal::new() } } @@ -348,7 +363,7 @@ mod tests { "/nonexistent_home/nonexistent_alice".to_string(), )), ); - let chain_name = "polygon-mumbai"; + let chain_name = "polygon-amoy"; let result = data_directory_from_context(&dirs_wrapper, &real_user, Chain::from(chain_name)); @@ -356,7 +371,7 @@ mod tests { assert_eq!( result, PathBuf::from( - "/nonexistent_home/nonexistent_alice/.local/share/MASQ/polygon-mumbai".to_string() + "/nonexistent_home/nonexistent_alice/.local/share/MASQ/polygon-amoy".to_string() ) ) } @@ -377,7 +392,8 @@ mod tests { let args_vec: Vec = args.into(); let app = determine_config_file_path_app(); let user_specific_data = - determine_user_specific_data(&DirsWrapperReal {}, &app, args_vec.as_slice()).unwrap(); + determine_user_specific_data(&DirsWrapperReal::default(), &app, args_vec.as_slice()) + .unwrap(); assert_eq!( &format!( @@ -418,7 +434,8 @@ mod tests { std::env::set_var("MASQ_CONFIG_FILE", "booga.toml"); let app = determine_config_file_path_app(); let user_specific_data = - determine_user_specific_data(&DirsWrapperReal {}, &app, args_vec.as_slice()).unwrap(); + determine_user_specific_data(&DirsWrapperReal::default(), &app, args_vec.as_slice()) + .unwrap(); assert_eq!( format!( "{}", @@ -454,7 +471,8 @@ mod tests { let app = determine_config_file_path_app(); let user_specific_data = - determine_user_specific_data(&DirsWrapperReal {}, &app, args_vec.as_slice()).unwrap(); + determine_user_specific_data(&DirsWrapperReal::default(), &app, args_vec.as_slice()) + .unwrap(); assert_eq!( "/tmp/booga.toml", @@ -474,7 +492,7 @@ mod tests { let args_vec: Vec = args.into(); let user_specific_data = determine_user_specific_data( - &DirsWrapperReal {}, + &DirsWrapperReal::default(), &determine_config_file_path_app(), args_vec.as_slice(), ) @@ -498,7 +516,7 @@ mod tests { let args_vec: Vec = args.into(); let user_specific_data = determine_user_specific_data( - &DirsWrapperReal {}, + &DirsWrapperReal::default(), &determine_config_file_path_app(), args_vec.as_slice(), ) @@ -521,7 +539,7 @@ mod tests { let args_vec: Vec = args.into(); let user_specific_data = determine_user_specific_data( - &DirsWrapperReal {}, + &DirsWrapperReal::default(), &determine_config_file_path_app(), args_vec.as_slice(), ) @@ -545,7 +563,7 @@ mod tests { let args_vec: Vec = args.into(); let user_specific_data = determine_user_specific_data( - &DirsWrapperReal {}, + &DirsWrapperReal::default(), &determine_config_file_path_app(), args_vec.as_slice(), ) diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index 49cb35703..79e65aff5 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -63,7 +63,7 @@ impl Default for NodeConfiguratorStandardPrivileged { impl NodeConfiguratorStandardPrivileged { pub fn new() -> Self { Self { - dirs_wrapper: Box::new(DirsWrapperReal {}), + dirs_wrapper: Box::new(DirsWrapperReal::default()), } } } @@ -376,7 +376,6 @@ mod tests { make_pre_populated_mocked_directory_wrapper, make_simplified_multi_config, }; use crate::test_utils::{assert_string_contains, main_cryptde, ArgsBuilder}; - use dirs::home_dir; use masq_lib::blockchains::chains::Chain; use masq_lib::constants::DEFAULT_CHAIN; use masq_lib::multi_config::VirtualCommandLine; @@ -597,8 +596,12 @@ mod tests { ) .unwrap(); - privileged_parse_args(&DirsWrapperReal {}, &multi_config, &mut bootstrapper_config) - .unwrap(); + privileged_parse_args( + &DirsWrapperReal::default(), + &multi_config, + &mut bootstrapper_config, + ) + .unwrap(); let node_parse_args_configurator = UnprivilegedParseArgsConfigurationDaoNull {}; node_parse_args_configurator .unprivileged_parse_args( @@ -660,13 +663,13 @@ mod tests { "ABCDEF01ABCDEF01ABCDEF01ABCDEF01ABCDEF01ABCDEF01ABCDEF01ABCDEF01", ) .param("--real-user", "999:999:/home/booga") - .param("--chain", "polygon-mumbai"); + .param("--chain", "polygon-amoy"); let mut config = BootstrapperConfig::new(); let vcls: Vec> = vec![Box::new(CommandLineVcl::new(args.into()))]; let multi_config = make_new_multi_config(&app_node(), vcls).unwrap(); - privileged_parse_args(&DirsWrapperReal {}, &multi_config, &mut config).unwrap(); + privileged_parse_args(&DirsWrapperReal::default(), &multi_config, &mut config).unwrap(); assert_eq!( value_m!(multi_config, "config-file", PathBuf), @@ -685,7 +688,6 @@ mod tests { NeighborhoodConfig { mode: NeighborhoodMode::ZeroHop, // not populated on the privileged side min_hops: Hops::ThreeHops, - country: "ZZ".to_string() } ); assert_eq!( @@ -712,7 +714,7 @@ mod tests { vec![Box::new(CommandLineVcl::new(args.into()))]; let multi_config = make_new_multi_config(&app_node(), vcls).unwrap(); - privileged_parse_args(&DirsWrapperReal {}, &multi_config, &mut config).unwrap(); + privileged_parse_args(&DirsWrapperReal::default(), &multi_config, &mut config).unwrap(); assert_eq!(None, value_m!(multi_config, "config-file", PathBuf)); assert_eq!( @@ -724,7 +726,7 @@ mod tests { assert!(config.main_cryptde_null_opt.is_none()); assert_eq!( config.real_user, - RealUser::new(None, None, None).populate(&DirsWrapperReal {}) + RealUser::new(None, None, None).populate(&DirsWrapperReal::default()) ); } @@ -740,7 +742,7 @@ mod tests { vec![Box::new(CommandLineVcl::new(args.into()))]; let multi_config = make_new_multi_config(&app_node(), vcls).unwrap(); - privileged_parse_args(&DirsWrapperReal {}, &multi_config, &mut config).unwrap(); + privileged_parse_args(&DirsWrapperReal::default(), &multi_config, &mut config).unwrap(); #[cfg(target_os = "linux")] assert_eq!( @@ -766,7 +768,7 @@ mod tests { vec![Box::new(CommandLineVcl::new(args.into()))]; let multi_config = make_new_multi_config(&app_node(), vcls).unwrap(); - privileged_parse_args(&DirsWrapperReal {}, &multi_config, &mut config).unwrap(); + privileged_parse_args(&DirsWrapperReal::default(), &multi_config, &mut config).unwrap(); assert_eq!(None, value_m!(multi_config, "config-file", PathBuf)); assert_eq!( @@ -778,7 +780,7 @@ mod tests { assert!(config.main_cryptde_null_opt.is_none()); assert_eq!( config.real_user, - RealUser::new(None, None, None).populate(&DirsWrapperReal {}) + RealUser::new(None, None, None).populate(&DirsWrapperReal::default()) ); } @@ -790,7 +792,7 @@ mod tests { let vcl = Box::new(CommandLineVcl::new(args.into())); let multi_config = make_new_multi_config(&app_node(), vec![vcl]).unwrap(); - privileged_parse_args(&DirsWrapperReal {}, &multi_config, &mut config).unwrap(); + privileged_parse_args(&DirsWrapperReal::default(), &multi_config, &mut config).unwrap(); assert_eq!(config.crash_point, CrashPoint::None); } @@ -803,7 +805,7 @@ mod tests { let vcl = Box::new(CommandLineVcl::new(args.into())); let multi_config = make_new_multi_config(&app_node(), vec![vcl]).unwrap(); - privileged_parse_args(&DirsWrapperReal {}, &multi_config, &mut config).unwrap(); + privileged_parse_args(&DirsWrapperReal::default(), &multi_config, &mut config).unwrap(); assert_eq!(config.crash_point, CrashPoint::Panic); } @@ -1021,8 +1023,8 @@ mod tests { ); let home_dir = canonicalize(home_dir).unwrap(); let data_dir = &home_dir.join("data_dir"); - let config_file_relative = File::create(home_dir.join("config.toml")).unwrap(); - fill_up_config_file(config_file_relative); + let config_file = File::create(home_dir.join("config.toml")).unwrap(); + fill_up_config_file(config_file); let env_vec_array = vec![ ( "MASQ_CONFIG_FILE", @@ -1072,14 +1074,16 @@ mod tests { } #[test] - fn server_initializer_collected_params_handle_tilde_in_path_config_file_from_commandline_and_real_user_from_config_file( - ) { + fn tilde_in_config_file_path_from_commandline_and_args_uploaded_from_config_file() { running_test(); let _guard = EnvironmentGuard::new(); let _clap_guard = ClapGuard::new(); - let home_dir = home_dir().expect("expectexd home dir"); - let data_dir = &home_dir.join("masqhome"); - let _create_data_dir = create_dir_all(data_dir); + let home_dir = ensure_node_home_directory_exists( + "node_configurator_standard", + "tilde_in_config_file_path_from_commandline_and_args_uploaded_from_config_file", + ); + let data_dir = home_dir.join("masqhome"); + let _dir = create_dir_all(&data_dir); let config_file_relative = File::create(data_dir.join("config.toml")).unwrap(); fill_up_config_file(config_file_relative); let env_vec_array = vec![ @@ -1102,16 +1106,30 @@ mod tests { .param("--config-file", "~\\masqhome\\config.toml") .param("--data-directory", "~\\masqhome"); let args_vec: Vec = args.into(); - let dir_wrapper = DirsWrapperMock::new() - .home_dir_result(Some(home_dir.to_path_buf())) - .data_dir_result(Some(data_dir.to_path_buf())); + let dir_wrapper = DirsWrapperMock { + data_dir_result: Some(PathBuf::from(current_dir().unwrap().join(&data_dir))), + home_dir_result: Some(PathBuf::from(current_dir().unwrap().join(&home_dir))), + }; let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); let multiconfig = result.unwrap(); assert_eq!( value_m!(multiconfig, "data-directory", String).unwrap(), - data_dir.to_string_lossy().to_string() + current_dir() + .unwrap() + .join(&data_dir) + .to_string_lossy() + .to_string() + ); + assert_eq!( + value_m!(multiconfig, "config-file", String).unwrap(), + current_dir() + .unwrap() + .join(data_dir) + .join(PathBuf::from("config.toml")) + .to_string_lossy() + .to_string() ); #[cfg(not(target_os = "windows"))] { @@ -1120,13 +1138,6 @@ mod tests { "9999:9999:booga" ); } - assert_eq!( - value_m!(multiconfig, "config-file", String).unwrap(), - data_dir - .join(PathBuf::from("config.toml")) - .to_string_lossy() - .to_string() - ); assert_eq!( value_m!(multiconfig, "blockchain-service-url", String).unwrap(), "https://www.mainnet1.com" @@ -1136,12 +1147,11 @@ mod tests { } #[test] - fn server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_data_directory( - ) { + fn config_file_from_env_and_real_user_from_config_file_with_data_directory_from_command_line() { running_test(); let _guard = EnvironmentGuard::new(); let _clap_guard = ClapGuard::new(); - let home_dir = ensure_node_home_directory_exists( "node_configurator_standard","server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_data_directory"); + let home_dir = ensure_node_home_directory_exists( "node_configurator_standard","config_file_from_env_and_real_user_from_config_file_with_data_directory_from_command_line"); let data_dir = &home_dir.join("data_dir"); create_dir_all(home_dir.join("config")).expect("expected directory for config"); let config_file_relative = File::create(&home_dir.join("config/config.toml")).unwrap(); @@ -1186,7 +1196,6 @@ mod tests { let _guard = EnvironmentGuard::new(); let _clap_guard = ClapGuard::new(); let home_dir = ensure_node_home_directory_exists( "node_configurator_standard","server_initializer_collected_params_fails_on_naked_dir_config_file_without_data_directory"); - let data_dir = &home_dir.join("data_dir"); vec![("MASQ_CONFIG_FILE", "config/config.toml")] .into_iter() @@ -1211,9 +1220,7 @@ mod tests { ); let data_dir = &home_dir.join("data_dir"); let config_file = File::create(&home_dir.join("booga.toml")).unwrap(); - let current_directory = current_dir().unwrap(); fill_up_config_file(config_file); - let env_vec_array = vec![ ("MASQ_CONFIG_FILE", "booga.toml"), ("MASQ_CLANDESTINE_PORT", "8888"), @@ -1230,7 +1237,10 @@ mod tests { .home_dir_result(Some(home_dir.clone())) .data_dir_result(Some(data_dir.to_path_buf())); let args = ArgsBuilder::new() - .param("--data-directory", current_directory.join(Path::new("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vcls_properly/home")).to_string_lossy().to_string().as_str()) + .param( + "--data-directory", + home_dir.to_string_lossy().to_string().as_str(), + ) .param("--clandestine-port", "1111") .param("--real-user", "1001:1001:cooga"); let args_vec: Vec = args.into(); @@ -1250,22 +1260,17 @@ mod tests { value_m!(multiconfig, "ip", String).unwrap(), "6.6.6.6".to_string() ); + assert_eq!( + value_m!(multiconfig, "config-file", String).unwrap(), + home_dir.join("booga.toml").as_os_str().to_str().unwrap() + ); #[cfg(not(target_os = "windows"))] { - assert_eq!( - value_m!(multiconfig, "config-file", String).unwrap(), - current_directory.join("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vcls_properly/home/booga.toml").to_string_lossy().to_string() - ); assert_eq!( value_m!(multiconfig, "real-user", String).unwrap(), "1001:1001:cooga".to_string() ); } - #[cfg(target_os = "windows")] - assert_eq!( - value_m!(multiconfig, "config-file", String).unwrap(), - current_directory.join("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vcls_properly/home\\booga.toml").to_string_lossy().to_string() - ); } #[test] @@ -1612,30 +1617,30 @@ mod tests { running_test(); let home_dir = Path::new("/home/cooga"); let home_dir_poly_main = home_dir.join(".local").join("MASQ").join("polygon-mainnet"); - let home_dir_poly_mumbai = home_dir.join(".local").join("MASQ").join("polygon-mumbai"); + let home_dir_poly_amoy = home_dir.join(".local").join("MASQ").join("polygon-amoy"); vec![ (None, None, Some(home_dir_poly_main.to_str().unwrap())), ( - Some("polygon-mumbai"), + Some("polygon-amoy"), None, - Some(home_dir_poly_mumbai.to_str().unwrap()), + Some(home_dir_poly_amoy.to_str().unwrap()), ), (None, Some("/cooga"), Some("/cooga")), - (Some("polygon-mumbai"), Some("/cooga"), Some("/cooga")), + (Some("polygon-amoy"), Some("/cooga"), Some("/cooga")), ( None, - Some("/cooga/polygon-mumbai"), - Some("/cooga/polygon-mumbai"), + Some("/cooga/polygon-amoy"), + Some("/cooga/polygon-amoy"), ), ( None, - Some("/cooga/polygon-mumbai/polygon-mainnet"), - Some("/cooga/polygon-mumbai/polygon-mainnet"), + Some("/cooga/polygon-amoy/polygon-mainnet"), + Some("/cooga/polygon-amoy/polygon-mainnet"), ), ( - Some("polygon-mumbai"), - Some("/cooga/polygon-mumbai"), - Some("/cooga/polygon-mumbai"), + Some("polygon-amoy"), + Some("/cooga/polygon-amoy"), + Some("/cooga/polygon-amoy"), ), ] .iter() diff --git a/node/src/node_configurator/unprivileged_parse_args_configuration.rs b/node/src/node_configurator/unprivileged_parse_args_configuration.rs index c05cb971c..91b0ca81f 100644 --- a/node/src/node_configurator/unprivileged_parse_args_configuration.rs +++ b/node/src/node_configurator/unprivileged_parse_args_configuration.rs @@ -222,11 +222,7 @@ pub fn make_neighborhood_config( }; match make_neighborhood_mode(multi_config, neighbor_configs, persistent_config) { - Ok(mode) => Ok(NeighborhoodConfig { - mode, - min_hops, - country: "ZZ".to_string(), - }), + Ok(mode) => Ok(NeighborhoodConfig { mode, min_hops }), Err(e) => Err(e), } } @@ -722,7 +718,7 @@ mod tests { DEFAULT_RATE_PACK ), min_hops: Hops::OneHop, - country: "ZZ".to_string() + }) ); } @@ -858,7 +854,7 @@ mod tests { Ok(NeighborhoodConfig { mode: NeighborhoodMode::Standard(node_addr, _, _), min_hops: Hops::ThreeHops, - country: _, + .. }) => node_addr, x => panic!("Wasn't expecting {:?}", x), }; diff --git a/node/src/privilege_drop.rs b/node/src/privilege_drop.rs index 1eba9cf88..2b4d8e198 100644 --- a/node/src/privilege_drop.rs +++ b/node/src/privilege_drop.rs @@ -190,7 +190,9 @@ mod tests { let mut subject = PrivilegeDropperReal::new(); subject.id_wrapper = Box::new(id_wrapper); - subject.drop_privileges(&RealUser::new(None, None, None).populate(&DirsWrapperReal {})); + subject.drop_privileges( + &RealUser::new(None, None, None).populate(&DirsWrapperReal::default()), + ); } #[cfg(not(target_os = "windows"))] @@ -283,7 +285,9 @@ mod tests { let mut subject = PrivilegeDropperReal::new(); subject.id_wrapper = Box::new(id_wrapper); - subject.drop_privileges(&RealUser::new(None, None, None).populate(&DirsWrapperReal {})); + subject.drop_privileges( + &RealUser::new(None, None, None).populate(&DirsWrapperReal::default()), + ); let setuid_params = setuid_params_arc.lock().unwrap(); assert!(setuid_params.is_empty()); diff --git a/node/src/proxy_client/stream_establisher.rs b/node/src/proxy_client/stream_establisher.rs index f25d7a9fe..4d6fdad24 100644 --- a/node/src/proxy_client/stream_establisher.rs +++ b/node/src/proxy_client/stream_establisher.rs @@ -1,5 +1,6 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. +use crate::proxy_client::stream_handler_pool::StreamSenders; use crate::proxy_client::stream_reader::StreamReader; use crate::proxy_client::stream_writer::StreamWriter; use crate::sub_lib::channel_wrappers::FuturesChannelFactory; @@ -14,7 +15,7 @@ use crate::sub_lib::stream_connector::StreamConnectorReal; use crate::sub_lib::stream_key::StreamKey; use crate::sub_lib::tokio_wrappers::ReadHalfWrapper; use actix::Recipient; -use crossbeam_channel::Sender; +use crossbeam_channel::{unbounded, Receiver, Sender}; use masq_lib::logger::Logger; use std::io; use std::net::IpAddr; @@ -22,7 +23,7 @@ use std::net::SocketAddr; pub struct StreamEstablisher { pub cryptde: &'static dyn CryptDE, - pub stream_adder_tx: Sender<(StreamKey, Box>)>, + pub stream_adder_tx: Sender<(StreamKey, StreamSenders)>, pub stream_killer_tx: Sender<(StreamKey, u64)>, pub stream_connector: Box, pub proxy_client_sub: Recipient, @@ -57,11 +58,13 @@ impl StreamEstablisher { payload.target_port, &self.logger, )?; + let (shutdown_signal_tx, shutdown_signal_rx) = unbounded(); self.spawn_stream_reader( &payload.clone(), connection_info.reader, connection_info.peer_addr, + shutdown_signal_rx, ); let (tx_to_write, rx_to_write) = self.channel_factory.make(connection_info.peer_addr); @@ -73,8 +76,13 @@ impl StreamEstablisher { ); tokio::spawn(stream_writer); + let stream_senders = StreamSenders { + writer_data: tx_to_write.clone(), + reader_shutdown_tx: shutdown_signal_tx, + }; + self.stream_adder_tx - .send((payload.stream_key, tx_to_write.clone())) + .send((payload.stream_key, stream_senders)) .expect("StreamHandlerPool died"); Ok(tx_to_write) } @@ -84,12 +92,14 @@ impl StreamEstablisher { payload: &ClientRequestPayload_0v1, read_stream: Box, peer_addr: SocketAddr, + shutdown_signal: Receiver<()>, ) { let stream_reader = StreamReader::new( payload.stream_key, self.proxy_client_sub.clone(), read_stream, self.stream_killer_tx.clone(), + shutdown_signal, peer_addr, ); debug!(self.logger, "Spawning StreamReader for {}", peer_addr); @@ -103,7 +113,7 @@ pub trait StreamEstablisherFactory: Send { pub struct StreamEstablisherFactoryReal { pub cryptde: &'static dyn CryptDE, - pub stream_adder_tx: Sender<(StreamKey, Box>)>, + pub stream_adder_tx: Sender<(StreamKey, StreamSenders)>, pub stream_killer_tx: Sender<(StreamKey, u64)>, pub proxy_client_subs: ProxyClientSubs, pub logger: Logger, @@ -191,6 +201,7 @@ mod tests { }, read_stream, SocketAddr::from_str("1.2.3.4:5678").unwrap(), + unbounded().1, ); proxy_client_awaiter.await_message_count(1); diff --git a/node/src/proxy_client/stream_handler_pool.rs b/node/src/proxy_client/stream_handler_pool.rs index 0c7f1f011..5ecbe9794 100644 --- a/node/src/proxy_client/stream_handler_pool.rs +++ b/node/src/proxy_client/stream_handler_pool.rs @@ -14,7 +14,7 @@ use crate::sub_lib::sequence_buffer::SequencedPacket; use crate::sub_lib::stream_key::StreamKey; use crate::sub_lib::wallet::Wallet; use actix::Recipient; -use crossbeam_channel::{unbounded, Receiver}; +use crossbeam_channel::{unbounded, Receiver, Sender}; use futures::future; use futures::future::Future; use masq_lib::logger::Logger; @@ -29,20 +29,28 @@ use tokio::prelude::future::{err, ok}; use trust_dns_resolver::error::ResolveError; use trust_dns_resolver::lookup_ip::LookupIp; +// TODO: This should be renamed to differentiate it from the other StreamHandlerPool, +// which, unlike this, is an actor. pub trait StreamHandlerPool { fn process_package(&self, payload: ClientRequestPayload_0v1, paying_wallet_opt: Option); } +#[derive(Debug)] +pub struct StreamSenders { + pub writer_data: Box>, + pub reader_shutdown_tx: Sender<()>, +} + pub struct StreamHandlerPoolReal { inner: Arc>, - stream_adder_rx: Receiver<(StreamKey, Box>)>, + stream_adder_rx: Receiver<(StreamKey, StreamSenders)>, stream_killer_rx: Receiver<(StreamKey, u64)>, } struct StreamHandlerPoolRealInner { accountant_sub: Recipient, proxy_client_subs: ProxyClientSubs, - stream_writer_channels: HashMap>>, + stream_writer_channels: HashMap, resolver: Box, logger: Logger, establisher_factory: Box, @@ -148,6 +156,30 @@ impl StreamHandlerPoolReal { }; } + fn send_shutdown_signal_to_stream_reader( + reader_shutdown_tx: Sender<()>, + stream_key: &StreamKey, + logger: &Logger, + ) { + match reader_shutdown_tx.try_send(()) { + Ok(()) => { + debug!( + logger, + "A shutdown signal was sent to the StreamReader for stream key {:?}.", + stream_key + ); + } + Err(_e) => { + debug!( + logger, + "Unable to send a shutdown signal to the StreamReader for \ + stream key {:?}. The channel is already gone.", + stream_key + ); + } + } + } + fn clean_up_bad_stream( inner_arc: Arc>, stream_key: &StreamKey, @@ -159,12 +191,17 @@ impl StreamHandlerPoolReal { inner.logger, "Couldn't process request from CORES package: {}", error ); - if let Some(sender_wrapper) = inner.stream_writer_channels.remove(stream_key) { + if let Some(stream_senders) = inner.stream_writer_channels.remove(stream_key) { debug!( inner.logger, "Removing stream writer for {}", - sender_wrapper.peer_addr() + stream_senders.writer_data.peer_addr() ); + Self::send_shutdown_signal_to_stream_reader( + stream_senders.reader_shutdown_tx, + stream_key, + &inner.logger, + ) } Self::send_terminating_package( stream_key, @@ -185,20 +222,6 @@ impl StreamHandlerPoolReal { Self::perform_write(payload.sequenced_packet, sender_wrapper.clone()).and_then(move |_| { let mut inner = inner_arc.lock().expect("Stream handler pool is poisoned"); - if last_data { - match inner.stream_writer_channels.remove(&stream_key) { - Some(channel) => debug!( - inner.logger, - "Removing StreamWriter {:?} to {}", - stream_key, - channel.peer_addr() - ), - None => debug!( - inner.logger, - "Trying to remove StreamWriter {:?}, but it's already gone", stream_key - ), - } - } if payload_size > 0 { match paying_wallet_opt { Some(wallet) => inner @@ -218,6 +241,30 @@ impl StreamHandlerPoolReal { ), } } + if last_data { + match inner.stream_writer_channels.remove(&stream_key) { + Some(stream_senders) => { + debug!( + inner.logger, + "Removing StreamWriter and Shutting down StreamReader for {:?} to {}", + stream_key, + stream_senders.writer_data.peer_addr() + ); + Self::send_shutdown_signal_to_stream_reader( + stream_senders.reader_shutdown_tx, + &stream_key, + &inner.logger, + ) + } + None => { + debug!( + inner.logger, + "Trying to remove StreamWriter and StreamReader for stream key {:?}, but it's already gone", stream_key + ) + } + } + } + Ok(()) }) } @@ -385,7 +432,8 @@ impl StreamHandlerPoolReal { ) -> Option>> { let inner = inner_arc.lock().expect("Stream handler pool is poisoned"); let sender_wrapper_opt = inner.stream_writer_channels.get(stream_key); - sender_wrapper_opt.map(|sender_wrapper_box_ref| sender_wrapper_box_ref.as_ref().clone()) + sender_wrapper_opt + .map(|sender_wrapper_box_ref| sender_wrapper_box_ref.writer_data.as_ref().clone()) } fn make_logger_copy(inner_arc: &Arc>) -> Logger { @@ -430,7 +478,7 @@ impl StreamHandlerPoolReal { let mut inner = self.inner.lock().expect("Stream handler pool is poisoned"); while let Ok((stream_key, sequence_number)) = self.stream_killer_rx.try_recv() { match inner.stream_writer_channels.remove(&stream_key) { - Some(writer_channel) => { + Some(stream_senders) => { inner .proxy_client_subs .inbound_server_data @@ -438,14 +486,20 @@ impl StreamHandlerPoolReal { stream_key, last_data: true, sequence_number, - source: writer_channel.peer_addr(), + source: stream_senders.writer_data.peer_addr(), data: vec![], }) .expect("ProxyClient is dead"); + Self::send_shutdown_signal_to_stream_reader( + stream_senders.reader_shutdown_tx, + &stream_key, + &inner.logger, + ); debug!( inner.logger, - "Killed StreamWriter to {} and sent server-drop report", - writer_channel.peer_addr() + "Killed StreamWriter and StreamReader for the stream key {:?} to {} and sent server-drop report", + stream_key, + stream_senders.writer_data.peer_addr() ) } None => debug!( @@ -461,16 +515,16 @@ impl StreamHandlerPoolReal { loop { match self.stream_adder_rx.try_recv() { Err(_) => break, - Ok((stream_key, stream_writer_channel)) => { + Ok((stream_key, stream_senders)) => { debug!( inner.logger, "Persisting StreamWriter to {} under key {:?}", - stream_writer_channel.peer_addr(), + stream_senders.writer_data.peer_addr(), stream_key ); inner .stream_writer_channels - .insert(stream_key, stream_writer_channel) + .insert(stream_key, stream_senders) } }; } @@ -519,7 +573,7 @@ mod tests { use crate::proxy_client::local_test_utils::make_send_error; use crate::proxy_client::local_test_utils::ResolverWrapperMock; use crate::proxy_client::stream_establisher::StreamEstablisher; - use crate::sub_lib::channel_wrappers::FuturesChannelFactoryReal; + use crate::sub_lib::channel_wrappers::{FuturesChannelFactoryReal, SenderWrapperReal}; use crate::sub_lib::cryptde::PublicKey; use crate::sub_lib::hopper::ExpiredCoresPackage; use crate::sub_lib::hopper::MessageType; @@ -658,8 +712,9 @@ mod tests { originator_public_key: PublicKey::new(&b"men's souls"[..]), }; let write_parameters = Arc::new(Mutex::new(vec![])); + let peer_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); let tx_to_write = Box::new( - SenderWrapperMock::new(SocketAddr::from_str("1.2.3.4:5678").unwrap()) + SenderWrapperMock::new(peer_addr) .unbounded_send_result(Ok(())) .unbounded_send_params(&write_parameters), ); @@ -682,12 +737,13 @@ mod tests { 100, 200, ); - subject - .inner - .lock() - .unwrap() - .stream_writer_channels - .insert(stream_key, tx_to_write); + subject.inner.lock().unwrap().stream_writer_channels.insert( + stream_key, + StreamSenders { + writer_data: tx_to_write, + reader_shutdown_tx: unbounded().0, + }, + ); run_process_package_in_actix(subject, package); }); @@ -703,9 +759,11 @@ mod tests { #[test] fn write_failure_for_nonexistent_stream_generates_termination_message() { init_test_logging(); + let test_name = "write_failure_for_nonexistent_stream_generates_termination_message"; let cryptde = main_cryptde(); let (proxy_client, proxy_client_awaiter, proxy_client_recording_arc) = make_recorder(); let originator_key = PublicKey::new(&b"men's souls"[..]); + let (reader_shutdown_tx, reader_shutdown_rx) = unbounded(); thread::spawn(move || { let client_request_payload = ClientRequestPayload_0v1 { stream_key: StreamKey::make_meaningless_stream_key(), @@ -727,13 +785,11 @@ mod tests { 0, ); let peer_actors = peer_actors_builder().proxy_client(proxy_client).build(); - let resolver = ResolverWrapperMock::new() - .lookup_ip_success(vec![IpAddr::from_str("2.3.4.5").unwrap()]); - - let tx_to_write = SenderWrapperMock::new(SocketAddr::from_str("2.3.4.5:80").unwrap()) - .unbounded_send_result(make_send_error( - client_request_payload.sequenced_packet.clone(), - )); + let peer_addr = SocketAddr::from_str("2.3.4.5:80").unwrap(); + let resolver = ResolverWrapperMock::new().lookup_ip_success(vec![peer_addr.ip()]); + let tx_to_write = SenderWrapperMock::new(peer_addr).unbounded_send_result( + make_send_error(client_request_payload.sequenced_packet.clone()), + ); let subject = StreamHandlerPoolReal::new( Box::new(resolver), @@ -743,17 +799,24 @@ mod tests { 100, 200, ); - subject - .inner - .lock() - .unwrap() - .stream_writer_channels - .insert(client_request_payload.stream_key, Box::new(tx_to_write)); + { + let mut inner = subject.inner.lock().unwrap(); + inner.stream_writer_channels.insert( + client_request_payload.stream_key, + StreamSenders { + writer_data: Box::new(tx_to_write), + reader_shutdown_tx, + }, + ); + inner.logger = Logger::new(test_name); + } run_process_package_in_actix(subject, package); }); proxy_client_awaiter.await_message_count(1); let proxy_client_recording = proxy_client_recording_arc.lock().unwrap(); + let received = reader_shutdown_rx.try_recv(); + assert_eq!(received, Ok(())); assert_eq!( proxy_client_recording.get_record::(0), &InboundServerData { @@ -764,13 +827,15 @@ mod tests { data: vec![], } ); + TestLogHandler::new().exists_log_containing(&format!( + "DEBUG: {test_name}: A shutdown signal was sent to the StreamReader \ + for stream key AAAAAAAAAAAAAAAAAAAAAAAAAAA." + )); } #[test] fn when_hostname_is_ip_establish_stream_without_dns_lookup() { let cryptde = main_cryptde(); - let lookup_ip_parameters = Arc::new(Mutex::new(vec![])); - let expected_lookup_ip_parameters = lookup_ip_parameters.clone(); let write_parameters = Arc::new(Mutex::new(vec![])); let expected_write_parameters = write_parameters.clone(); let (proxy_client, proxy_client_awaiter, proxy_client_recording_arc) = make_recorder(); @@ -795,12 +860,6 @@ mod tests { client_request_payload.into(), 0, ); - let resolver = ResolverWrapperMock::new() - .lookup_ip_parameters(&lookup_ip_parameters) - .lookup_ip_success(vec![ - IpAddr::from_str("2.3.4.5").unwrap(), - IpAddr::from_str("3.4.5.6").unwrap(), - ]); let peer_addr = SocketAddr::from_str("3.4.5.6:80").unwrap(); let first_read_result = b"HTTP/1.1 200 OK\r\n\r\n"; let reader = ReadHalfWrapperMock { @@ -818,7 +877,7 @@ mod tests { shutdown_results: Arc::new(Mutex::new(vec![])), }; let mut subject = StreamHandlerPoolReal::new( - Box::new(resolver), + Box::new(ResolverWrapperMock::new()), cryptde, peer_actors.accountant.report_exit_service_provided.clone(), peer_actors.proxy_client_opt.unwrap().clone(), @@ -854,10 +913,6 @@ mod tests { }); proxy_client_awaiter.await_message_count(1); - assert_eq!( - expected_lookup_ip_parameters.lock().unwrap().deref(), - &(vec![] as Vec) - ); assert_eq!( expected_write_parameters.lock().unwrap().remove(0), b"These are the times".to_vec() @@ -875,6 +930,126 @@ mod tests { ); } + #[test] + fn stream_handler_pool_sends_shutdown_signal_when_last_data_is_true() { + init_test_logging(); + let test_name = "stream_handler_pool_sends_shutdown_signal_when_last_data_is_true"; + let (shutdown_tx, shutdown_rx) = unbounded(); + thread::spawn(move || { + let stream_key = StreamKey::make_meaningful_stream_key("I should die"); + let client_request_payload = ClientRequestPayload_0v1 { + stream_key, + sequenced_packet: SequencedPacket { + data: b"I'm gonna kill you stream key".to_vec(), + sequence_number: 0, + last_data: true, + }, + target_hostname: Some(String::from("3.4.5.6:80")), + target_port: HTTP_PORT, + protocol: ProxyProtocol::HTTP, + originator_public_key: PublicKey::new(&b"brutal death"[..]), + }; + let package = ExpiredCoresPackage::new( + SocketAddr::from_str("1.2.3.4:1234").unwrap(), + Some(make_wallet("consuming")), + make_meaningless_route(), + client_request_payload.into(), + 0, + ); + let peer_addr = SocketAddr::from_str("3.4.5.6:80").unwrap(); + let peer_actors = peer_actors_builder().build(); + let subject = StreamHandlerPoolReal::new( + Box::new(ResolverWrapperMock::new()), + main_cryptde(), + peer_actors.accountant.report_exit_service_provided.clone(), + peer_actors.proxy_client_opt.unwrap().clone(), + 100, + 200, + ); + { + let mut inner = subject.inner.lock().unwrap(); + inner.logger = Logger::new(test_name); + inner.stream_writer_channels.insert( + stream_key, + StreamSenders { + writer_data: Box::new(SenderWrapperMock::new(peer_addr)), + reader_shutdown_tx: shutdown_tx, + }, + ); + } + + run_process_package_in_actix(subject, package); + }); + let received = shutdown_rx.recv(); + assert_eq!(received, Ok(())); + TestLogHandler::new().await_log_containing( + &format!( + "DEBUG: {test_name}: Removing StreamWriter and Shutting down StreamReader \ + for oUHoHuDKHjeWq+BJzBIqHpPFBQw to 3.4.5.6:80" + ), + 500, + ); + } + + #[test] + fn stream_handler_pool_logs_when_shutdown_channel_is_broken() { + init_test_logging(); + let test_name = "stream_handler_pool_logs_when_shutdown_channel_is_broken"; + let broken_shutdown_channel_tx = unbounded().0; + thread::spawn(move || { + let stream_key = StreamKey::make_meaningful_stream_key("I should die"); + let client_request_payload = ClientRequestPayload_0v1 { + stream_key, + sequenced_packet: SequencedPacket { + data: b"I'm gonna kill you stream key".to_vec(), + sequence_number: 0, + last_data: true, + }, + target_hostname: Some(String::from("3.4.5.6:80")), + target_port: HTTP_PORT, + protocol: ProxyProtocol::HTTP, + originator_public_key: PublicKey::new(&b"brutal death"[..]), + }; + let package = ExpiredCoresPackage::new( + SocketAddr::from_str("1.2.3.4:1234").unwrap(), + Some(make_wallet("consuming")), + make_meaningless_route(), + client_request_payload.into(), + 0, + ); + let peer_addr = SocketAddr::from_str("3.4.5.6:80").unwrap(); + let peer_actors = peer_actors_builder().build(); + let subject = StreamHandlerPoolReal::new( + Box::new(ResolverWrapperMock::new()), + main_cryptde(), + peer_actors.accountant.report_exit_service_provided.clone(), + peer_actors.proxy_client_opt.unwrap().clone(), + 100, + 200, + ); + { + let mut inner = subject.inner.lock().unwrap(); + inner.logger = Logger::new(test_name); + inner.stream_writer_channels.insert( + stream_key, + StreamSenders { + writer_data: Box::new(SenderWrapperMock::new(peer_addr)), + reader_shutdown_tx: broken_shutdown_channel_tx, + }, + ); + } + + run_process_package_in_actix(subject, package); + }); + TestLogHandler::new().await_log_containing( + &format!( + "DEBUG: {test_name}: Unable to send a shutdown signal to the StreamReader \ + for stream key oUHoHuDKHjeWq+BJzBIqHpPFBQw. The channel is already gone." + ), + 500, + ); + } + #[test] fn ip_is_parsed_even_without_port() { let cryptde = main_cryptde(); @@ -1610,12 +1785,13 @@ mod tests { 100, 200, ); - subject - .inner - .lock() - .unwrap() - .stream_writer_channels - .insert(stream_key, Box::new(sender_wrapper)); + subject.inner.lock().unwrap().stream_writer_channels.insert( + stream_key, + StreamSenders { + writer_data: Box::new(sender_wrapper), + reader_shutdown_tx: unbounded().0, + }, + ); run_process_package_in_actix(subject, package); }); @@ -1709,11 +1885,16 @@ mod tests { subject.stream_killer_rx = stream_killer_rx; let stream_key = StreamKey::make_meaningless_stream_key(); let peer_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); + let (shutdown_tx, shutdown_rx) = unbounded(); { let mut inner = subject.inner.lock().unwrap(); - inner - .stream_writer_channels - .insert(stream_key, Box::new(SenderWrapperMock::new(peer_addr))); + inner.stream_writer_channels.insert( + stream_key, + StreamSenders { + writer_data: Box::new(SenderWrapperMock::new(peer_addr)), + reader_shutdown_tx: shutdown_tx, + }, + ); } stream_killer_tx.send((stream_key, 47)).unwrap(); @@ -1723,6 +1904,8 @@ mod tests { system.run(); let proxy_client_recording = proxy_client_recording_arc.lock().unwrap(); let report = proxy_client_recording.get_record::(0); + let shutdown_signal_received = shutdown_rx.recv(); + assert_eq!(shutdown_signal_received, Ok(())); assert_eq!( report, &InboundServerData { @@ -1735,6 +1918,49 @@ mod tests { ); } + #[test] + fn clean_up_dead_streams_logs_when_the_stream_reader_is_down() { + init_test_logging(); + let test_name = "clean_up_dead_streams_logs_when_the_shutdown_channel_is_down"; + let system = System::new(test_name); + let peer_actors = peer_actors_builder().build(); + let mut subject = StreamHandlerPoolReal::new( + Box::new(ResolverWrapperMock::new()), + main_cryptde(), + peer_actors.accountant.report_exit_service_provided, + peer_actors.proxy_client_opt.unwrap(), + 0, + 0, + ); + let (stream_killer_tx, stream_killer_rx) = unbounded(); + subject.stream_killer_rx = stream_killer_rx; + let stream_key = StreamKey::make_meaningful_stream_key("I'll be gone well before then."); + let peer_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); + let broken_shutdown_channel_tx = unbounded().0; + { + let mut inner = subject.inner.lock().unwrap(); + inner.logger = Logger::new(test_name); + inner.stream_writer_channels.insert( + stream_key, + StreamSenders { + writer_data: Box::new(SenderWrapperMock::new(peer_addr)), + reader_shutdown_tx: broken_shutdown_channel_tx, + }, + ); + } + stream_killer_tx.send((stream_key, 47)).unwrap(); + + subject.clean_up_dead_streams(); + + System::current().stop_with_code(0); + system.run(); + TestLogHandler::new().exists_log_containing(&format!( + "DEBUG: {test_name}: Unable to send a shutdown signal \ + to the StreamReader for stream key cv9IZ5fizc4kZmR+0d+OQGXr3bw. \ + The channel is already gone." + )); + } + #[test] fn clean_up_dead_streams_does_not_send_server_drop_report_if_dead_stream_is_gone_already() { let system = System::new("test"); @@ -1760,4 +1986,77 @@ mod tests { let proxy_client_recording = proxy_client_recording_arc.lock().unwrap(); assert_eq!(proxy_client_recording.len(), 0); } + + #[test] + fn add_new_streams_works() { + init_test_logging(); + let test_name = "add_new_streams_works"; + let (stream_adder_tx, stream_adder_rx) = unbounded(); + let peer_actors = peer_actors_builder().build(); + let mut subject = StreamHandlerPoolReal::new( + Box::new(ResolverWrapperMock::new()), + main_cryptde(), + peer_actors.accountant.report_exit_service_provided, + peer_actors.proxy_client_opt.unwrap(), + 0, + 0, + ); + subject.stream_adder_rx = stream_adder_rx; + { + subject.inner.lock().unwrap().logger = Logger::new(test_name); + } + let first_stream_key = StreamKey::make_meaningful_stream_key("first_stream_key"); + let (first_writer_data_tx, _first_writer_data_rx) = futures::sync::mpsc::unbounded(); + let (first_shutdown_tx, _first_shutdown_rx) = unbounded(); + let first_stream_senders = StreamSenders { + writer_data: Box::new(SenderWrapperReal::new( + SocketAddr::from_str("1.2.3.4:5678").unwrap(), + first_writer_data_tx, + )), + reader_shutdown_tx: first_shutdown_tx, + }; + let (second_writer_data_tx, _second_writer_data_rx) = futures::sync::mpsc::unbounded(); + let (second_shutdown_tx, _second_shutdown_rx) = unbounded(); + let second_stream_key = StreamKey::make_meaningful_stream_key("second_stream_key"); + let second_stream_senders = StreamSenders { + writer_data: Box::new(SenderWrapperReal::new( + SocketAddr::from_str("2.3.4.5:6789").unwrap(), + second_writer_data_tx, + )), + reader_shutdown_tx: second_shutdown_tx, + }; + stream_adder_tx + .send((first_stream_key.clone(), first_stream_senders)) + .unwrap(); + stream_adder_tx + .send((second_stream_key.clone(), second_stream_senders)) + .unwrap(); + + subject.add_new_streams(); + + let mut inner = subject.inner.lock().unwrap(); + let actual_first_stream_senders = inner + .stream_writer_channels + .remove(&first_stream_key) + .unwrap(); + let actual_second_stream_senders = inner + .stream_writer_channels + .remove(&second_stream_key) + .unwrap(); + assert_eq!( + actual_first_stream_senders.writer_data.peer_addr(), + SocketAddr::from_str("1.2.3.4:5678").unwrap() + ); + assert_eq!( + actual_second_stream_senders.writer_data.peer_addr(), + SocketAddr::from_str("2.3.4.5:6789").unwrap() + ); + let tlh = TestLogHandler::new(); + tlh.exists_log_containing(&format!( + "DEBUG: {test_name}: Persisting StreamWriter to 1.2.3.4:5678 under key gY2vJ+OwPuItsBcFhbilDI61LGo" + )); + tlh.exists_log_containing(&format!( + "DEBUG: {test_name}: Persisting StreamWriter to 2.3.4.5:6789 under key 1Kbv+3/MIN4/1hLQXLeNPgdDM58" + )); + } } diff --git a/node/src/proxy_client/stream_reader.rs b/node/src/proxy_client/stream_reader.rs index 3f3d82477..992b58dbf 100644 --- a/node/src/proxy_client/stream_reader.rs +++ b/node/src/proxy_client/stream_reader.rs @@ -3,10 +3,9 @@ use crate::sub_lib::proxy_client::InboundServerData; use crate::sub_lib::sequencer::Sequencer; use crate::sub_lib::stream_key::StreamKey; use crate::sub_lib::tokio_wrappers::ReadHalfWrapper; -use crate::sub_lib::utils; use crate::sub_lib::utils::indicates_dead_stream; use actix::Recipient; -use crossbeam_channel::Sender; +use crossbeam_channel::{Receiver, Sender}; use masq_lib::logger::Logger; use std::net::SocketAddr; use tokio::prelude::Async; @@ -17,6 +16,7 @@ pub struct StreamReader { proxy_client_sub: Recipient, stream: Box, stream_killer: Sender<(StreamKey, u64)>, + shutdown_signal: Receiver<()>, peer_addr: SocketAddr, logger: Logger, sequencer: Sequencer, @@ -29,6 +29,13 @@ impl Future for StreamReader { fn poll(&mut self) -> Result::Item>, ::Error> { let mut buf: [u8; 16384] = [0; 16384]; loop { + if self.shutdown_signal.try_recv().is_ok() { + info!( + self.logger, + "Shutting down for stream: {:?}", self.stream_key + ); + return Ok(Async::Ready(())); + } match self.stream.poll_read(&mut buf) { Ok(Async::NotReady) => return Ok(Async::NotReady), Ok(Async::Ready(0)) => { @@ -44,10 +51,9 @@ impl Future for StreamReader { if self.logger.trace_enabled() { trace!( self.logger, - "Read {}-byte chunk from {}: {}", + "Read {}-byte chunk from {}", len, - self.peer_addr, - utils::to_string(&Vec::from(&buf[0..len])) + self.peer_addr ); } let stream_key = self.stream_key; @@ -82,15 +88,19 @@ impl StreamReader { proxy_client_sub: Recipient, stream: Box, stream_killer: Sender<(StreamKey, u64)>, + shutdown_signal: Receiver<()>, peer_addr: SocketAddr, ) -> StreamReader { + let logger = Logger::new(&format!("StreamReader for {:?}/{}", stream_key, peer_addr)[..]); + debug!(logger, "Initialised StreamReader"); StreamReader { stream_key, proxy_client_sub, stream, stream_killer, + shutdown_signal, peer_addr, - logger: Logger::new(&format!("StreamReader for {:?}/{}", stream_key, peer_addr)[..]), + logger, sequencer: Sequencer::new(), } } @@ -120,7 +130,7 @@ mod tests { use crate::test_utils::recorder::make_recorder; use crate::test_utils::recorder::peer_actors_builder; use crate::test_utils::tokio_wrapper_mocks::ReadHalfWrapperMock; - use actix::System; + use actix::{Actor, System}; use crossbeam_channel::unbounded; use masq_lib::test_utils::logging::init_test_logging; use masq_lib::test_utils::logging::TestLogHandler; @@ -175,6 +185,7 @@ mod tests { proxy_client_sub, stream, stream_killer, + shutdown_signal: unbounded().1, peer_addr: SocketAddr::from_str("8.7.4.3:50").unwrap(), logger: Logger::new("test"), sequencer: Sequencer::new(), @@ -253,15 +264,12 @@ mod tests { }); let proxy_client_sub = rx.recv().unwrap(); let (stream_killer, stream_killer_params) = unbounded(); - let mut subject = StreamReader { - stream_key: StreamKey::make_meaningless_stream_key(), - proxy_client_sub, - stream: Box::new(stream), - stream_killer, - peer_addr: SocketAddr::from_str("5.7.9.0:95").unwrap(), - logger: Logger::new("test"), - sequencer: Sequencer::new(), - }; + let peer_addr = SocketAddr::from_str("5.7.9.0:95").unwrap(); + let mut subject = make_subject(); + subject.proxy_client_sub = proxy_client_sub; + subject.stream = Box::new(stream); + subject.stream_killer = stream_killer; + subject.peer_addr = peer_addr; let result = subject.poll(); @@ -274,7 +282,7 @@ mod tests { stream_key: StreamKey::make_meaningless_stream_key(), last_data: false, sequence_number: 0, - source: SocketAddr::from_str("5.7.9.0:95").unwrap(), + source: peer_addr, data: b"HTTP/1.1 200".to_vec() } ); @@ -284,7 +292,7 @@ mod tests { stream_key: StreamKey::make_meaningless_stream_key(), last_data: false, sequence_number: 1, - source: SocketAddr::from_str("5.7.9.0:95").unwrap(), + source: peer_addr, data: b" OK\r\n\r\nHTTP/1.1 40".to_vec() } ); @@ -294,7 +302,7 @@ mod tests { stream_key: StreamKey::make_meaningless_stream_key(), last_data: false, sequence_number: 2, - source: SocketAddr::from_str("5.7.9.0:95").unwrap(), + source: peer_addr, data: b"4 File not found\r\n\r\nHTTP/1.1 503 Server error\r\n\r\n".to_vec() } ); @@ -312,49 +320,53 @@ mod tests { #[test] fn receiving_0_bytes_kills_stream() { init_test_logging(); + let test_name = "receiving_0_bytes_kills_stream"; let stream_key = StreamKey::make_meaningless_stream_key(); let (stream_killer, kill_stream_params) = unbounded(); let mut stream = ReadHalfWrapperMock::new(); stream.poll_read_results = vec![(vec![], Ok(Async::Ready(0)))]; - - let system = System::new("receiving_0_bytes_sends_empty_cores_response_and_kills_stream"); - let peer_actors = peer_actors_builder().build(); + let peer_addr = SocketAddr::from_str("5.3.4.3:654").unwrap(); + let system = System::new(test_name); let mut sequencer = Sequencer::new(); sequencer.next_sequence_number(); sequencer.next_sequence_number(); let mut subject = StreamReader { stream_key, - proxy_client_sub: peer_actors.proxy_client_opt.unwrap().inbound_server_data, + proxy_client_sub: make_recorder().0.start().recipient(), stream: Box::new(stream), stream_killer, - peer_addr: SocketAddr::from_str("5.3.4.3:654").unwrap(), - logger: Logger::new("test"), + shutdown_signal: unbounded().1, + peer_addr, + logger: Logger::new(test_name), sequencer, }; - System::current().stop_with_code(0); + System::current().stop(); system.run(); let result = subject.poll(); assert_eq!(result, Ok(Async::Ready(()))); assert_eq!(kill_stream_params.try_recv().unwrap(), (stream_key, 2)); - TestLogHandler::new() - .exists_log_containing("Stream from 5.3.4.3:654 was closed: (0-byte read)"); + TestLogHandler::new().exists_log_containing(&format!( + "DEBUG: {test_name}: Stream from {peer_addr} was closed: (0-byte read)" + )); } #[test] fn non_dead_stream_read_errors_log_but_do_not_shut_down() { init_test_logging(); + let test_name = "non_dead_stream_read_errors_log_but_do_not_shut_down"; let (proxy_client, proxy_client_awaiter, proxy_client_recording_arc) = make_recorder(); let stream_key = StreamKey::make_meaningless_stream_key(); let (stream_killer, _) = unbounded(); let mut stream = ReadHalfWrapperMock::new(); + let http_response = b"HTTP/1.1 200 OK\r\n\r\n"; stream.poll_read_results = vec![ (vec![], Err(Error::from(ErrorKind::Other))), ( - Vec::from(&b"HTTP/1.1 200 OK\r\n\r\n"[..]), - Ok(Async::Ready(b"HTTP/1.1 200 OK\r\n\r\n".len())), + http_response.to_vec(), + Ok(Async::Ready(http_response.len())), ), (vec![], Err(Error::from(ErrorKind::BrokenPipe))), ]; @@ -371,13 +383,15 @@ mod tests { }); let proxy_client_sub = rx.recv().unwrap(); + let peer_addr = SocketAddr::from_str("6.5.4.1:8325").unwrap(); let mut subject = StreamReader { stream_key, proxy_client_sub, stream: Box::new(stream), stream_killer, - peer_addr: SocketAddr::from_str("6.5.4.1:8325").unwrap(), - logger: Logger::new("test"), + shutdown_signal: unbounded().1, + peer_addr, + logger: Logger::new(test_name), sequencer: Sequencer::new(), }; @@ -386,18 +400,50 @@ mod tests { assert_eq!(result, Err(())); proxy_client_awaiter.await_message_count(1); TestLogHandler::new().exists_log_containing( - "WARN: test: Continuing after read error on stream from 6.5.4.1:8325: other error", + &format!("WARN: {test_name}: Continuing after read error on stream from {peer_addr}: other error"), ); let proxy_client_recording = proxy_client_recording_arc.lock().unwrap(); assert_eq!( proxy_client_recording.get_record::(0), &InboundServerData { - stream_key: StreamKey::make_meaningless_stream_key(), + stream_key, last_data: false, sequence_number: 0, - source: SocketAddr::from_str("6.5.4.1:8325").unwrap(), - data: b"HTTP/1.1 200 OK\r\n\r\n".to_vec() + source: peer_addr, + data: http_response.to_vec() } ); } + + #[test] + fn stream_reader_shuts_down_when_it_receives_the_shutdown_signal() { + init_test_logging(); + let test_name = "stream_reader_shuts_down_when_it_receives_the_shutdown_signal"; + let (shutdown_tx, shutdown_rx) = unbounded(); + let mut subject = make_subject(); + subject.shutdown_signal = shutdown_rx; + subject.logger = Logger::new(test_name); + shutdown_tx.send(()).unwrap(); + + let result = subject.poll(); + + assert_eq!(result, Ok(Async::Ready(()))); + TestLogHandler::new().exists_log_containing(&format!( + "INFO: {test_name}: Shutting down for stream: {:?}", + subject.stream_key + )); + } + + pub fn make_subject() -> StreamReader { + StreamReader { + stream_key: StreamKey::make_meaningless_stream_key(), + proxy_client_sub: make_recorder().0.start().recipient(), + stream: Box::new(ReadHalfWrapperMock::new()), + stream_killer: unbounded().0, + shutdown_signal: unbounded().1, + peer_addr: SocketAddr::from_str("9.8.7.6:5432").unwrap(), + logger: Logger::new("test"), + sequencer: Sequencer::new(), + } + } } diff --git a/node/src/proxy_server/mod.rs b/node/src/proxy_server/mod.rs index 058c7c12f..7ecccc632 100644 --- a/node/src/proxy_server/mod.rs +++ b/node/src/proxy_server/mod.rs @@ -29,8 +29,8 @@ use crate::sub_lib::neighborhood::{ExpectedServices, RatePack}; use crate::sub_lib::neighborhood::{NRMetadataChange, RouteQueryMessage}; use crate::sub_lib::peer_actors::BindMessage; use crate::sub_lib::proxy_client::{ClientResponsePayload_0v1, DnsResolveFailure_0v1}; -use crate::sub_lib::proxy_server::AddReturnRouteMessage; use crate::sub_lib::proxy_server::ProxyServerSubs; +use crate::sub_lib::proxy_server::{AddReturnRouteMessage, StreamKeyPurge}; use crate::sub_lib::proxy_server::{ AddRouteResultMessage, ClientRequestPayload_0v1, ProxyProtocol, }; @@ -38,13 +38,13 @@ use crate::sub_lib::route::Route; use crate::sub_lib::stream_handler_pool::TransmitDataMsg; use crate::sub_lib::stream_key::StreamKey; use crate::sub_lib::ttl_hashmap::TtlHashMap; -use crate::sub_lib::utils::{handle_ui_crash_request, NODE_MAILBOX_CAPACITY}; +use crate::sub_lib::utils::{handle_ui_crash_request, MessageScheduler, NODE_MAILBOX_CAPACITY}; use crate::sub_lib::wallet::Wallet; -use actix::Addr; use actix::Context; use actix::Handler; use actix::Recipient; use actix::{Actor, MailboxError}; +use actix::{Addr, AsyncContext}; use masq_lib::logger::Logger; use masq_lib::ui_gateway::NodeFromUiMessage; use masq_lib::utils::MutabilityConflictHelper; @@ -58,6 +58,8 @@ use tokio::prelude::Future; pub const CRASH_KEY: &str = "PROXYSERVER"; pub const RETURN_ROUTE_TTL: Duration = Duration::from_secs(120); +pub const STREAM_KEY_PURGE_DELAY: Duration = Duration::from_secs(30); + struct ProxyServerOutSubs { dispatcher: Recipient, hopper: Recipient, @@ -67,6 +69,7 @@ struct ProxyServerOutSubs { add_return_route: Recipient, stream_shutdown_sub: Recipient, route_result_sub: Recipient, + schedule_stream_key_purge: Recipient>, } pub struct ProxyServer { @@ -77,6 +80,7 @@ pub struct ProxyServer { tunneled_hosts: HashMap, dns_failure_retries: HashMap, stream_key_routes: HashMap, + stream_key_ttl: HashMap, is_decentralized: bool, consuming_wallet_balance: Option, main_cryptde: &'static dyn CryptDE, @@ -86,6 +90,7 @@ pub struct ProxyServer { route_ids_to_return_routes: TtlHashMap, browser_proxy_sequence_offset: bool, inbound_client_data_helper_opt: Option>, + stream_key_purge_delay: Duration, } impl Actor for ProxyServer { @@ -106,6 +111,7 @@ impl Handler for ProxyServer { add_return_route: msg.peer_actors.proxy_server.add_return_route, stream_shutdown_sub: msg.peer_actors.proxy_server.stream_shutdown_sub, route_result_sub: msg.peer_actors.proxy_server.route_result_sub, + schedule_stream_key_purge: msg.peer_actors.proxy_server.schedule_stream_key_purge, }; self.subs = Some(subs); } @@ -220,6 +226,25 @@ impl Handler for ProxyServer { } } +impl Handler for ProxyServer { + type Result = (); + + fn handle(&mut self, msg: StreamKeyPurge, _ctx: &mut Self::Context) -> Self::Result { + self.purge_stream_key(&msg.stream_key, "scheduled message"); + } +} + +impl Handler> for ProxyServer +where + ProxyServer: Handler, +{ + type Result = (); + + fn handle(&mut self, msg: MessageScheduler, ctx: &mut Self::Context) -> Self::Result { + ctx.notify_later(msg.scheduled_msg, msg.delay); + } +} + impl ProxyServer { pub fn new( main_cryptde: &'static dyn CryptDE, @@ -236,6 +261,7 @@ impl ProxyServer { tunneled_hosts: HashMap::new(), dns_failure_retries: HashMap::new(), stream_key_routes: HashMap::new(), + stream_key_ttl: HashMap::new(), is_decentralized, consuming_wallet_balance, main_cryptde, @@ -245,6 +271,7 @@ impl ProxyServer { route_ids_to_return_routes: TtlHashMap::new(RETURN_ROUTE_TTL), browser_proxy_sequence_offset: false, inbound_client_data_helper_opt: Some(Box::new(IBCDHelperReal::new())), + stream_key_purge_delay: STREAM_KEY_PURGE_DELAY, } } @@ -258,6 +285,7 @@ impl ProxyServer { stream_shutdown_sub: recipient!(addr, StreamShutdownMsg), node_from_ui: recipient!(addr, NodeFromUiMessage), route_result_sub: recipient!(addr, AddRouteResultMessage), + schedule_stream_key_purge: recipient!(addr, MessageScheduler), } } @@ -298,12 +326,7 @@ impl ProxyServer { } fn retire_stream_key(&mut self, stream_key: &StreamKey) { - warning!( - self.logger, - "Retiring stream key {}: DnsResolveFailure", - stream_key - ); - self.purge_stream_key(stream_key); + self.purge_stream_key(stream_key, "DNS resolution failure"); } fn send_dns_failure_response_to_the_browser( @@ -410,6 +433,48 @@ impl ProxyServer { } } + fn schedule_stream_key_purge(&mut self, stream_key: StreamKey) { + let host_info = match self.tunneled_hosts.get(&stream_key) { + None => String::from(""), + Some(hostname) => format!(", which was tunneling to the host {:?}", hostname), + }; + debug!( + self.logger, + "Client closed stream referenced by stream key {:?}{}. It will be purged after {:?}.", + &stream_key, + host_info, + self.stream_key_purge_delay + ); + self.stream_key_ttl.insert(stream_key, SystemTime::now()); + self.subs + .as_ref() + .expect("ProxyServer Subs Unbound") + .schedule_stream_key_purge + .try_send(MessageScheduler { + scheduled_msg: StreamKeyPurge { stream_key }, + delay: self.stream_key_purge_delay, + }) + .expect("ProxyServer is dead"); + } + + fn log_straggling_packet( + &self, + stream_key: &StreamKey, + packet_len: usize, + old_timestamp: &SystemTime, + ) { + let duration_since = SystemTime::now() + .duration_since(*old_timestamp) + .expect("time calculation error"); + warning!( + self.logger, + "Straggling packet of length {} received for a stream key {:?} after a delay of {:?}", + packet_len, + stream_key, + duration_since + ); + } + fn handle_client_response_payload( &mut self, msg: ExpiredCoresPackage, @@ -437,7 +502,8 @@ impl ProxyServer { response.sequenced_packet.data.len(), payload_data_len, ); - match self.remove_dns_failure_retry(&response.stream_key) { + let stream_key = response.stream_key; + match self.remove_dns_failure_retry(&stream_key) { Ok(_) => { debug!(self.logger, "Successful attempt of DNS resolution, removing DNS retry entry for stream key: {}", &response.stream_key) } @@ -449,10 +515,12 @@ impl ProxyServer { ) } } - match self.keys_and_addrs.a_to_b(&response.stream_key) { + if let Some(old_timestamp) = self.stream_key_ttl.get(&stream_key) { + self.log_straggling_packet(&stream_key, payload_data_len, old_timestamp) + } + match self.keys_and_addrs.a_to_b(&stream_key) { Some(socket_addr) => { let last_data = response.sequenced_packet.last_data; - let stream_key = response.stream_key; let sequence_number = Some( response.sequenced_packet.sequence_number + self.browser_proxy_sequence_offset as u64, @@ -469,11 +537,7 @@ impl ProxyServer { }) .expect("Dispatcher is dead"); if last_data { - debug!( - self.logger, - "Retiring stream key {}: no more data", &stream_key - ); - self.purge_stream_key(&stream_key); + self.purge_stream_key(&stream_key, "last data received from the exit node"); } } None => { @@ -551,6 +615,7 @@ impl ProxyServer { } Some(sk) => sk, }; + self.schedule_stream_key_purge(stream_key); if msg.report_to_counterpart { debug!( self.logger, @@ -570,12 +635,6 @@ impl ProxyServer { { error!(self.logger, "{}", e) }; - } else { - debug!( - self.logger, - "Retiring stream key {}: StreamShutdownMsg for peer {}", &stream_key, msg.peer_addr - ); - self.purge_stream_key(&stream_key); } } @@ -602,10 +661,15 @@ impl ProxyServer { } } - fn purge_stream_key(&mut self, stream_key: &StreamKey) { + fn purge_stream_key(&mut self, stream_key: &StreamKey, reason: &str) { + debug!( + self.logger, + "Retiring stream key {} due to {}", &stream_key, reason + ); let _ = self.keys_and_addrs.remove_a(stream_key); let _ = self.stream_key_routes.remove(stream_key); let _ = self.tunneled_hosts.remove(stream_key); + let _ = self.stream_key_ttl.remove(stream_key); } fn make_payload( @@ -1123,6 +1187,7 @@ impl IBCDHelper for IBCDHelperReal { neighborhood_sub .send(RouteQueryMessage::data_indefinite_route_request( hostname_opt, + None, payload_size, )) .then(move |route_result| { @@ -1363,6 +1428,7 @@ mod tests { fn constants_have_correct_values() { assert_eq!(CRASH_KEY, "PROXYSERVER"); assert_eq!(RETURN_ROUTE_TTL, Duration::from_secs(120)); + assert_eq!(STREAM_KEY_PURGE_DELAY, Duration::from_secs(30)); } const STANDARD_CONSUMING_WALLET_BALANCE: i64 = 0; @@ -1379,6 +1445,7 @@ mod tests { add_return_route: recipient!(addr, AddReturnRouteMessage), stream_shutdown_sub: recipient!(addr, StreamShutdownMsg), route_result_sub: recipient!(addr, AddRouteResultMessage), + schedule_stream_key_purge: recipient!(addr, MessageScheduler), } } @@ -1581,7 +1648,11 @@ mod tests { let record = recording.get_record::(0); assert_eq!( record, - &RouteQueryMessage::data_indefinite_route_request(Some("nowhere.com".to_string()), 47) + &RouteQueryMessage::data_indefinite_route_request( + Some("nowhere.com".to_string()), + None, + 47 + ) ); let recording = proxy_server_recording_arc.lock().unwrap(); assert_eq!(recording.len(), 0); @@ -1703,6 +1774,7 @@ mod tests { neighborhood_record, &RouteQueryMessage::data_indefinite_route_request( Some("realdomain.nu".to_string()), + None, 12 ) ); @@ -2095,7 +2167,8 @@ mod tests { target_component: Component::ProxyClient, return_component_opt: Some(Component::ProxyServer), payload_size: 47, - hostname_opt: Some("nowhere.com".to_string()) + hostname_opt: Some("nowhere.com".to_string()), + target_country_opt: None, } ); let dispatcher_recording = dispatcher_log_arc.lock().unwrap(); @@ -2174,7 +2247,8 @@ mod tests { target_component: Component::ProxyClient, return_component_opt: Some(Component::ProxyServer), payload_size: 16, - hostname_opt: None + hostname_opt: None, + target_country_opt: None, } ); let dispatcher_recording = dispatcher_log_arc.lock().unwrap(); @@ -2402,7 +2476,11 @@ mod tests { let record = recording.get_record::(0); assert_eq!( record, - &RouteQueryMessage::data_indefinite_route_request(Some("nowhere.com".to_string()), 47) + &RouteQueryMessage::data_indefinite_route_request( + Some("nowhere.com".to_string()), + None, + 47 + ) ); } @@ -3006,7 +3084,11 @@ mod tests { let record = recording.get_record::(0); assert_eq!( record, - &RouteQueryMessage::data_indefinite_route_request(Some("nowhere.com".to_string()), 47) + &RouteQueryMessage::data_indefinite_route_request( + Some("nowhere.com".to_string()), + None, + 47 + ) ); TestLogHandler::new().exists_log_containing(&format!( "WARN: {test_name}: No route found for hostname: Some(\"nowhere.com\") - stream key {stream_key} - retries left: 3 - AddRouteResultMessage Error: Failed to find route to nowhere.com" @@ -3182,7 +3264,11 @@ mod tests { let record = recording.get_record::(0); assert_eq!( record, - &RouteQueryMessage::data_indefinite_route_request(Some("nowhere.com".to_string()), 47) + &RouteQueryMessage::data_indefinite_route_request( + Some("nowhere.com".to_string()), + None, + 47 + ) ); TestLogHandler::new().exists_log_containing(&format!( "WARN: {test_name}: No route found for hostname: Some(\"nowhere.com\") - stream key {stream_key} - retries left: 3 - AddRouteResultMessage Error: Failed to find route to nowhere.com" @@ -3534,7 +3620,8 @@ mod tests { #[test] fn proxy_server_receives_terminal_response_from_hopper() { init_test_logging(); - let system = System::new("proxy_server_receives_response_from_hopper"); + let test_name = "proxy_server_receives_terminal_response_from_hopper"; + let system = System::new(test_name); let (dispatcher, _, dispatcher_recording_arc) = make_recorder(); let cryptde = main_cryptde(); let mut subject = ProxyServer::new( @@ -3544,8 +3631,9 @@ mod tests { Some(STANDARD_CONSUMING_WALLET_BALANCE), false, ); + subject.logger = Logger::new(test_name); let socket_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); - let stream_key = StreamKey::make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningful_stream_key(test_name); subject .keys_and_addrs .insert(stream_key.clone(), socket_addr.clone()); @@ -3579,17 +3667,42 @@ mod tests { let peer_actors = peer_actors_builder().dispatcher(dispatcher).build(); subject_addr.try_send(BindMessage { peer_actors }).unwrap(); - subject_addr.try_send(first_expired_cores_package).unwrap(); - subject_addr.try_send(second_expired_cores_package).unwrap(); // should generate log because stream key is now unknown + subject_addr.try_send(first_expired_cores_package).unwrap(); // This will purge the stream key records + subject_addr.try_send(second_expired_cores_package).unwrap(); // This will be discarded System::current().stop(); system.run(); let dispatcher_recording = dispatcher_recording_arc.lock().unwrap(); - let record = dispatcher_recording.get_record::(0); - assert_eq!(record.endpoint, Endpoint::Socket(socket_addr)); - assert_eq!(record.last_data, true); - assert_eq!(record.data, b"16 bytes of data".to_vec()); - TestLogHandler::new().exists_log_containing(&format!("WARN: ProxyServer: Discarding 16-byte packet 12345678 from an unrecognized stream key: {:?}", stream_key)); + let transmit_data_msg = dispatcher_recording.get_record::(0); + assert_eq!(transmit_data_msg.endpoint, Endpoint::Socket(socket_addr)); + assert_eq!(transmit_data_msg.last_data, true); + assert_eq!(transmit_data_msg.data, b"16 bytes of data".to_vec()); + let tlh = TestLogHandler::new(); + tlh.exists_log_containing(&format!( + "DEBUG: {test_name}: Retiring stream key {:?} due to last data received from the exit node", + stream_key + )); + tlh.exists_log_containing(&format!( + "WARN: {test_name}: Discarding 16-byte packet 12345678 from an unrecognized stream key: {:?}", + stream_key + )); + } + + #[test] + #[should_panic(expected = "time calculation error")] + fn log_straggling_packet_panics_if_timestamp_is_wrong() { + let subject = ProxyServer::new( + main_cryptde(), + alias_cryptde(), + true, + Some(STANDARD_CONSUMING_WALLET_BALANCE), + false, + ); + let stream_key = StreamKey::make_meaningless_stream_key(); + let timestamp = SystemTime::now() + .checked_add(Duration::from_secs(10)) + .unwrap(); + let _ = subject.log_straggling_packet(&stream_key, 10, ×tamp); } #[test] @@ -3651,6 +3764,234 @@ mod tests { assert!(subject.tunneled_hosts.is_empty()); } + #[test] + fn proxy_server_schedules_stream_key_purge_once_shutdown_order_is_received_for_stream() { + let common_msg = StreamShutdownMsg { + peer_addr: SocketAddr::from_str("1.2.3.4:5678").unwrap(), + stream_type: RemovedStreamType::NonClandestine(NonClandestineAttributes { + reception_port: 0, + sequence_number: 0, + }), + report_to_counterpart: true, + }; + assert_stream_is_purged_with_a_delay(StreamShutdownMsg { + report_to_counterpart: true, + ..common_msg.clone() + }); + assert_stream_is_purged_with_a_delay(StreamShutdownMsg { + report_to_counterpart: false, + ..common_msg + }); + } + + fn assert_stream_is_purged_with_a_delay(msg: StreamShutdownMsg) { + /* + +------------------------------------------------------------------+ + | (0ms) | + | Stream shutdown is ordered | + +------------------------------------------------------------------+ + | + v + +------------------------------------------------------------------+ + | (400ms) (stream_key_purge_delay_in_millis - offset_in_millis) | + | Pre-purge assertion message finds records | + +------------------------------------------------------------------+ + | + v + +------------------------------------------------------------------+ + | (500ms) (stream_key_purge_delay_in_millis) | + | Stream is purged | + +------------------------------------------------------------------+ + | + v + +------------------------------------------------------------------+ + | (600ms) (stream_key_purge_delay_in_millis + offset_in_millis) | + | Post-purge assertion message finds no records | + +------------------------------------------------------------------+ + */ + + init_test_logging(); + let test_name = + "proxy_server_schedules_stream_key_purge_once_shutdown_order_is_received_for_stream"; + let cryptde = main_cryptde(); + let stream_key_purge_delay_in_millis = 500; + let offset_in_millis = 100; + let mut subject = ProxyServer::new( + cryptde, + alias_cryptde(), + true, + Some(STANDARD_CONSUMING_WALLET_BALANCE), + false, + ); + subject.stream_key_purge_delay = Duration::from_millis(stream_key_purge_delay_in_millis); + subject.logger = Logger::new(&test_name); + subject.subs = Some(make_proxy_server_out_subs()); + let stream_key = StreamKey::make_meaningful_stream_key(&test_name); + subject + .keys_and_addrs + .insert(stream_key.clone(), msg.peer_addr.clone()); + subject.stream_key_routes.insert( + stream_key.clone(), + RouteQueryResponse { + route: Route { hops: vec![] }, + expected_services: ExpectedServices::RoundTrip(vec![], vec![], 1234), + }, + ); + subject + .tunneled_hosts + .insert(stream_key.clone(), "hostname".to_string()); + subject.route_ids_to_return_routes.insert( + 1234, + AddReturnRouteMessage { + return_route_id: 1234, + expected_services: vec![], + protocol: ProxyProtocol::HTTP, + hostname_opt: None, + }, + ); + let proxy_server_addr = subject.start(); + let schedule_stream_key_purge_sub = proxy_server_addr.clone().recipient(); + let mut peer_actors = peer_actors_builder().build(); + peer_actors.proxy_server.schedule_stream_key_purge = schedule_stream_key_purge_sub; + let system = System::new(test_name); + let bind_msg = BindMessage { peer_actors }; + proxy_server_addr.try_send(bind_msg).unwrap(); + let time_before_sending_package = SystemTime::now(); + + proxy_server_addr.try_send(msg).unwrap(); + + let time_after_sending_package = time_before_sending_package + .checked_add(Duration::from_secs(1)) + .unwrap(); + let pre_purge_assertions = AssertionsMessage { + assertions: Box::new(move |proxy_server: &mut ProxyServer| { + let purge_timestamp = proxy_server + .stream_key_ttl + .get(&stream_key) + .unwrap() + .clone(); + assert!( + time_before_sending_package <= purge_timestamp + && purge_timestamp <= time_after_sending_package + ); + assert!(!proxy_server.keys_and_addrs.is_empty()); + assert!(!proxy_server.stream_key_routes.is_empty()); + assert!(!proxy_server.tunneled_hosts.is_empty()); + TestLogHandler::new().exists_log_containing(&format!( + "DEBUG: {test_name}: Client closed stream referenced by stream key {:?}, \ + which was tunneling to the host \"hostname\". \ + It will be purged after {stream_key_purge_delay_in_millis}ms.", + stream_key + )); + }), + }; + proxy_server_addr + .try_send(MessageScheduler { + scheduled_msg: pre_purge_assertions, + delay: Duration::from_millis(stream_key_purge_delay_in_millis - offset_in_millis), // 400ms + }) + .unwrap(); + let post_purge_assertions = AssertionsMessage { + assertions: Box::new(move |proxy_server: &mut ProxyServer| { + assert!(proxy_server.keys_and_addrs.is_empty()); + assert!(proxy_server.stream_key_routes.is_empty()); + assert!(proxy_server.tunneled_hosts.is_empty()); + assert!(proxy_server.stream_key_ttl.is_empty()); + TestLogHandler::new().exists_log_containing(&format!( + "DEBUG: {test_name}: Retiring stream key {:?}", + stream_key + )); + System::current().stop(); + }), + }; + proxy_server_addr + .try_send(MessageScheduler { + scheduled_msg: post_purge_assertions, + delay: Duration::from_millis(stream_key_purge_delay_in_millis + offset_in_millis), // 600ms + }) + .unwrap(); + system.run(); + } + + #[test] + fn straggling_packets_are_logged() { + init_test_logging(); + let test_name = "straggling_packets_are_logged"; + let cryptde = main_cryptde(); + let mut subject = ProxyServer::new( + cryptde, + alias_cryptde(), + true, + Some(STANDARD_CONSUMING_WALLET_BALANCE), + false, + ); + subject.logger = Logger::new(test_name); + subject.subs = Some(make_proxy_server_out_subs()); + let stream_key = StreamKey::make_meaningful_stream_key(test_name); + let socket_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); + subject + .keys_and_addrs + .insert(stream_key.clone(), socket_addr.clone()); + subject.stream_key_routes.insert( + stream_key.clone(), + RouteQueryResponse { + route: Route { hops: vec![] }, + expected_services: ExpectedServices::RoundTrip(vec![], vec![], 1234), + }, + ); + subject + .tunneled_hosts + .insert(stream_key.clone(), "hostname".to_string()); + subject.route_ids_to_return_routes.insert( + 1234, + AddReturnRouteMessage { + return_route_id: 1234, + expected_services: vec![], + protocol: ProxyProtocol::HTTP, + hostname_opt: None, + }, + ); + let proxy_server_addr = subject.start(); + let schedule_stream_key_purge_sub = proxy_server_addr.clone().recipient(); + let mut peer_actors = peer_actors_builder().build(); + peer_actors.proxy_server.schedule_stream_key_purge = schedule_stream_key_purge_sub; + + let system = System::new(test_name); + let bind_msg = BindMessage { peer_actors }; + proxy_server_addr.try_send(bind_msg).unwrap(); + let stream_shutdown_msg = StreamShutdownMsg { + peer_addr: socket_addr, + stream_type: RemovedStreamType::NonClandestine(NonClandestineAttributes { + reception_port: 0, + sequence_number: 0, + }), + report_to_counterpart: true, + }; + let client_response_payload = ClientResponsePayload_0v1 { + stream_key: stream_key.clone(), + sequenced_packet: SequencedPacket::new(vec![], 1, true), + }; + let expired_cores_package: ExpiredCoresPackage = + ExpiredCoresPackage::new( + SocketAddr::from_str("1.2.3.4:1234").unwrap(), + Some(make_wallet("irrelevant")), + return_route_with_id(cryptde, 1234), + client_response_payload.into(), + 0, + ); + proxy_server_addr.try_send(stream_shutdown_msg).unwrap(); + + proxy_server_addr.try_send(expired_cores_package).unwrap(); + + System::current().stop(); + system.run(); + TestLogHandler::new().exists_log_containing(&format!( + "WARN: {test_name}: Straggling packet of length 0 received for a \ + stream key {:?} after a delay of", + stream_key + )); + } + #[test] fn proxy_server_receives_nonterminal_response_from_hopper() { let system = System::new("proxy_server_receives_nonterminal_response_from_hopper"); @@ -4981,7 +5322,7 @@ mod tests { let make_params = make_params_arc.lock().unwrap(); assert_eq!(make_params.len(), 3); TestLogHandler::new().exists_log_containing(&format!( - "WARN: {test_name}: Retiring stream key {stream_key_clone}: DnsResolveFailure" + "DEBUG: {test_name}: Retiring stream key {stream_key_clone} due to DNS resolution failure" )); } @@ -5394,7 +5735,10 @@ mod tests { #[test] fn handle_stream_shutdown_msg_reports_to_counterpart_without_tunnel_when_necessary() { - let system = System::new("test"); + init_test_logging(); + let test_name = + "handle_stream_shutdown_msg_reports_to_counterpart_without_tunnel_when_necessary"; + let system = System::new(test_name); let mut subject = ProxyServer::new( main_cryptde(), alias_cryptde(), @@ -5451,6 +5795,7 @@ mod tests { ), }, ); + subject.logger = Logger::new(test_name); let subject_addr = subject.start(); let (hopper, _, hopper_recording_arc) = make_recorder(); let (proxy_server, _, proxy_server_recording_arc) = make_recorder(); @@ -5505,72 +5850,18 @@ mod tests { report_to_counterpart: false } ); - } - - #[test] - fn handle_stream_shutdown_msg_does_not_report_to_counterpart_when_unnecessary() { - let mut subject = ProxyServer::new(main_cryptde(), alias_cryptde(), true, None, false); - let unaffected_socket_addr = SocketAddr::from_str("2.3.4.5:6789").unwrap(); - let unaffected_stream_key = StreamKey::make_meaningful_stream_key("unaffected"); - let affected_socket_addr = SocketAddr::from_str("3.4.5.6:7890").unwrap(); - let affected_stream_key = StreamKey::make_meaningful_stream_key("affected"); - subject - .keys_and_addrs - .insert(unaffected_stream_key, unaffected_socket_addr); - subject - .keys_and_addrs - .insert(affected_stream_key, affected_socket_addr); - subject.stream_key_routes.insert( - unaffected_stream_key, - RouteQueryResponse { - route: Route { hops: vec![] }, - expected_services: ExpectedServices::RoundTrip(vec![], vec![], 1234), - }, - ); - subject.stream_key_routes.insert( - affected_stream_key, - RouteQueryResponse { - route: Route { hops: vec![] }, - expected_services: ExpectedServices::RoundTrip(vec![], vec![], 1234), - }, - ); - subject - .tunneled_hosts - .insert(unaffected_stream_key, "blah".to_string()); - subject - .tunneled_hosts - .insert(affected_stream_key, "blah".to_string()); - - subject.handle_stream_shutdown_msg(StreamShutdownMsg { - peer_addr: affected_socket_addr, - stream_type: RemovedStreamType::NonClandestine(NonClandestineAttributes { - reception_port: HTTP_PORT, - sequence_number: 1234, - }), - report_to_counterpart: false, - }); - - // Subject is unbound but didn't panic; therefore, no attempt to send to Hopper: perfect! - assert!(subject - .keys_and_addrs - .a_to_b(&unaffected_stream_key) - .is_some()); - assert!(subject - .stream_key_routes - .contains_key(&unaffected_stream_key)); - assert!(subject.tunneled_hosts.contains_key(&unaffected_stream_key)); - assert!(subject - .keys_and_addrs - .a_to_b(&affected_stream_key) - .is_none()); - assert!(!subject.stream_key_routes.contains_key(&affected_stream_key)); - assert!(!subject.tunneled_hosts.contains_key(&affected_stream_key)); + TestLogHandler::new().exists_log_containing(&format!( + "DEBUG: {test_name}: Client closed stream referenced by stream key {:?}. \ + It will be purged after {:?}.", + &affected_stream_key, STREAM_KEY_PURGE_DELAY + )); } #[test] fn handle_stream_shutdown_msg_logs_errors_from_handling_normal_client_data() { init_test_logging(); let mut subject = ProxyServer::new(main_cryptde(), alias_cryptde(), true, Some(0), false); + subject.subs = Some(make_proxy_server_out_subs()); let helper = IBCDHelperMock::default() .handle_normal_client_data_result(Err("Our help is not welcome".to_string())); subject.inbound_client_data_helper_opt = Some(Box::new(helper)); @@ -5595,6 +5886,7 @@ mod tests { fn stream_shutdown_msg_populates_correct_inbound_client_data_msg() { let help_to_handle_normal_client_data_params_arc = Arc::new(Mutex::new(vec![])); let mut subject = ProxyServer::new(main_cryptde(), alias_cryptde(), true, Some(0), false); + subject.subs = Some(make_proxy_server_out_subs()); let icd_helper = IBCDHelperMock::default() .handle_normal_client_data_params(&help_to_handle_normal_client_data_params_arc) .handle_normal_client_data_result(Ok(())); diff --git a/node/src/run_modes_factories.rs b/node/src/run_modes_factories.rs index 61a3294ba..c3c009438 100644 --- a/node/src/run_modes_factories.rs +++ b/node/src/run_modes_factories.rs @@ -82,7 +82,7 @@ pub trait DaemonInitializer { impl DumpConfigRunnerFactory for DumpConfigRunnerFactoryReal { fn make(&self) -> Box { Box::new(DumpConfigRunnerReal { - dirs_wrapper: Box::new(DirsWrapperReal), + dirs_wrapper: Box::new(DirsWrapperReal::default()), }) } } @@ -111,7 +111,7 @@ impl DaemonInitializerFactory for DaemonInitializerFactoryReal { impl Default for DIClusteredParams { fn default() -> Self { Self { - dirs_wrapper: Box::new(DirsWrapperReal), + dirs_wrapper: Box::new(DirsWrapperReal::default()), logger_initializer_wrapper: Box::new(LoggerInitializerWrapperReal), channel_factory: Box::new(ChannelFactoryReal::new()), recipients_factory: Box::new(RecipientsFactoryReal::new()), diff --git a/node/src/server_initializer.rs b/node/src/server_initializer.rs index 048e8d6fa..54e8b0b36 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(())) } } @@ -94,7 +97,7 @@ impl Default for ServerInitializerReal { dns_socket_server: Box::new(DnsSocketServer::new()), bootstrapper: Box::new(Bootstrapper::new(Box::new(LoggerInitializerWrapperReal {}))), privilege_dropper: Box::new(PrivilegeDropperReal::new()), - dirs_wrapper: Box::new(DirsWrapperReal), + dirs_wrapper: Box::new(DirsWrapperReal::default()), } } } @@ -394,14 +397,13 @@ pub mod tests { use masq_lib::crash_point::CrashPoint; use masq_lib::multi_config::MultiConfig; use masq_lib::shared_schema::{ConfiguratorError, ParamError}; - use masq_lib::test_utils::fake_stream_holder::{ - ByteArrayReader, ByteArrayWriter, FakeStreamHolder, - }; + use masq_lib::test_utils::fake_stream_holder::FakeStreamHolder; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; use masq_lib::utils::slice_of_strs_to_vec_of_strings; use std::cell::RefCell; use std::ops::Not; use std::sync::{Arc, Mutex}; + use test_utilities::byte_array_reader_writer::{ByteArrayReader, ByteArrayWriter}; impl ConfiguredByPrivilege for CrashTestDummy { fn initialize_as_privileged( @@ -726,7 +728,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 +843,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 +892,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/stream_handler_pool.rs b/node/src/stream_handler_pool.rs index 3d5512892..db9bdd4e3 100644 --- a/node/src/stream_handler_pool.rs +++ b/node/src/stream_handler_pool.rs @@ -105,6 +105,7 @@ impl Display for StreamWriterKey { } } +// TODO: To avoid confusion with ProxyClient's StreamHandlerPool, rename this one or the other for easy identification. // It is used to store streams for both neighbors and browser. pub struct StreamHandlerPool { stream_writers: HashMap>>>, @@ -374,8 +375,31 @@ impl StreamHandlerPool { stream_writer_key ); let report_to_counterpart = match self.stream_writers.remove(&stream_writer_key) { - None | Some(None) => false, - Some(Some(_sender_wrapper)) => true, + None => { + trace!( + self.logger, + "While handling RemoveStreamMsg: Stream Writers did not contain any entry for key {}", + stream_writer_key + ); + false + } + Some(None) => { + error!( + self.logger, + "An unpopulated entry in stream_writers was found for a {:?} stream ({:?}) from \ + a client. This shouldn't be possible. Investigate!", + msg.stream_type, stream_writer_key + ); + false + } + Some(Some(_sender_wrapper)) => { + trace!( + self.logger, + "While handling RemoveStreamMsg: Stream Writers contained an entry for key {}, also found stream writer; removing", + stream_writer_key + ); + true + } }; let stream_shutdown_msg = StreamShutdownMsg { peer_addr: msg.peer_addr, @@ -1270,10 +1294,13 @@ mod tests { #[test] fn handle_remove_stream_msg_handles_stream_waiting_for_connect_scenario() { + init_test_logging(); + let test_name = "handle_remove_stream_msg_handles_stream_waiting_for_connect_scenario"; let (recorder, _, recording_arc) = make_recorder(); - let system = System::new("test"); + let system = System::new(test_name); let sub = recorder.start().recipient::(); let mut subject = StreamHandlerPool::new(vec![], false); + subject.logger = Logger::new(test_name); let peer_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); let local_addr = SocketAddr::from_str("127.0.0.1:0").unwrap(); let sw_key = StreamWriterKey::from(peer_addr); @@ -1299,6 +1326,11 @@ mod tests { report_to_counterpart: false } ); + TestLogHandler::new().exists_log_containing(&format!( + "ERROR: {}: An unpopulated entry in stream_writers was found for a \ + Clandestine stream ({:?}) from a client. This shouldn't be possible. Investigate!", + test_name, sw_key + )); } #[test] diff --git a/node/src/sub_lib/accountant.rs b/node/src/sub_lib/accountant.rs index cf87c0803..4b005f713 100644 --- a/node/src/sub_lib/accountant.rs +++ b/node/src/sub_lib/accountant.rs @@ -11,6 +11,7 @@ use crate::accountant::{ use crate::actor_system_factory::SubsFactory; use crate::blockchain::blockchain_bridge::PendingPayableFingerprintSeeds; use crate::db_config::config_dao::ConfigDaoFactory; +use crate::sub_lib::neighborhood::ConfigChangeMsg; use crate::sub_lib::peer_actors::{BindMessage, StartMessage}; use crate::sub_lib::wallet::Wallet; use actix::Recipient; @@ -92,6 +93,7 @@ impl Default for ScanIntervals { #[derive(Clone, PartialEq, Eq)] pub struct AccountantSubs { pub bind: Recipient, + pub config_change_msg_sub: Recipient, pub start: Recipient, pub report_routing_service_provided: Recipient, pub report_exit_service_provided: Recipient, diff --git a/node/src/sub_lib/blockchain_bridge.rs b/node/src/sub_lib/blockchain_bridge.rs index ff006fdaa..6d43ea47b 100644 --- a/node/src/sub_lib/blockchain_bridge.rs +++ b/node/src/sub_lib/blockchain_bridge.rs @@ -108,7 +108,6 @@ mod tests { Box::new(blockchain_interface), Box::new(persistent_config), false, - None, ); let addr = accountant.start(); diff --git a/node/src/sub_lib/configurator.rs b/node/src/sub_lib/configurator.rs index 3f92a7535..c5612be3d 100644 --- a/node/src/sub_lib/configurator.rs +++ b/node/src/sub_lib/configurator.rs @@ -6,12 +6,6 @@ use masq_lib::ui_gateway::NodeFromUiMessage; use std::fmt; use std::fmt::{Debug, Formatter}; -// GH-728 -#[derive(Debug, actix::Message, Clone, PartialEq, Eq)] -pub struct NewPasswordMessage { - pub new_password: String, -} - #[derive(Clone, PartialEq, Eq)] pub struct ConfiguratorSubs { pub bind: Recipient, diff --git a/node/src/sub_lib/neighborhood.rs b/node/src/sub_lib/neighborhood.rs index f2f49afec..02f22f4b2 100644 --- a/node/src/sub_lib/neighborhood.rs +++ b/node/src/sub_lib/neighborhood.rs @@ -3,8 +3,7 @@ use crate::neighborhood::gossip::Gossip_0v1; use crate::neighborhood::node_record::NodeRecord; use crate::neighborhood::overall_connection_status::ConnectionProgress; -use crate::neighborhood::Neighborhood; -use crate::sub_lib::configurator::NewPasswordMessage; +use crate::neighborhood::{Neighborhood, UserExitPreferences}; use crate::sub_lib::cryptde::{CryptDE, PublicKey}; use crate::sub_lib::cryptde_real::CryptDEReal; use crate::sub_lib::dispatcher::{Component, StreamShutdownMsg}; @@ -380,6 +379,39 @@ pub enum Hops { SixHops = 6, } +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct ExitLocation { + pub country_codes: Vec, + pub priority: usize, +} + +pub struct ExitLocationSet { + pub locations: Vec, +} + +impl Display for ExitLocation { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Country Codes: {:?}, Priority: {};", + self.country_codes, self.priority + ) + } +} + +impl Display for ExitLocationSet { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + for exit_location in self.locations.iter() { + write!( + f, + "Country Codes: {:?} - Priority: {}; ", + exit_location.country_codes, exit_location.priority + )?; + } + Ok(()) + } +} + impl FromStr for Hops { type Err = String; @@ -406,7 +438,6 @@ impl Display for Hops { pub struct NeighborhoodConfig { pub mode: NeighborhoodMode, pub min_hops: Hops, - pub country: String, } lazy_static! { @@ -424,10 +455,9 @@ pub struct NeighborhoodSubs { pub gossip_failure: Recipient>, pub dispatcher_node_query: Recipient, pub remove_neighbor: Recipient, - pub configuration_change_msg_sub: Recipient, + pub config_change_msg_sub: Recipient, pub stream_shutdown_sub: Recipient, pub from_ui_message_sub: Recipient, - pub new_password_sub: Recipient, // GH-728 pub connection_progress_sub: Recipient, } @@ -478,6 +508,7 @@ pub struct RouteQueryMessage { pub return_component_opt: Option, pub payload_size: usize, pub hostname_opt: Option, + pub target_country_opt: Option, } impl Message for RouteQueryMessage { @@ -487,6 +518,7 @@ impl Message for RouteQueryMessage { impl RouteQueryMessage { pub fn data_indefinite_route_request( hostname_opt: Option, + target_country_opt: Option, payload_size: usize, ) -> RouteQueryMessage { RouteQueryMessage { @@ -495,6 +527,7 @@ impl RouteQueryMessage { return_component_opt: Some(Component::ProxyServer), payload_size, hostname_opt, + target_country_opt, } } } @@ -557,14 +590,21 @@ pub enum NRMetadataChange { } #[derive(Clone, Debug, Message, PartialEq, Eq)] -pub struct ConfigurationChangeMessage { - pub change: ConfigurationChange, +pub struct ConfigChangeMsg { + pub change: ConfigChange, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct WalletPair { + pub consuming_wallet: Wallet, + pub earning_wallet: Wallet, } #[derive(Clone, Debug, PartialEq, Eq)] -pub enum ConfigurationChange { - UpdateConsumingWallet(Wallet), +pub enum ConfigChange { UpdateMinHops(Hops), + UpdatePassword(String), + UpdateWallets(WalletPair), } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -595,6 +635,7 @@ pub struct NeighborhoodMetadata { pub connection_progress_peers: Vec, pub cpm_recipient: Recipient, pub db_patch_size: u8, + pub user_exit_preferences_opt: Option, } pub struct NeighborhoodTools { @@ -671,10 +712,9 @@ mod tests { gossip_failure: recipient!(recorder, ExpiredCoresPackage), dispatcher_node_query: recipient!(recorder, DispatcherNodeQueryMessage), remove_neighbor: recipient!(recorder, RemoveNeighborMessage), - configuration_change_msg_sub: recipient!(recorder, ConfigurationChangeMessage), + config_change_msg_sub: recipient!(recorder, ConfigChangeMsg), stream_shutdown_sub: recipient!(recorder, StreamShutdownMsg), from_ui_message_sub: recipient!(recorder, NodeFromUiMessage), - new_password_sub: recipient!(recorder, NewPasswordMessage), // GH-728 connection_progress_sub: recipient!(recorder, ConnectionProgressMessage), }; @@ -718,12 +758,12 @@ mod tests { } #[test] - fn parse_works_for_mumbai() { - let descriptor = "masq://polygon-mumbai:as45cs5c5@1.2.3.4:4444"; + fn parse_works_for_amoy() { + let descriptor = "masq://polygon-amoy:as45cs5c5@1.2.3.4:4444"; let result = NodeDescriptor::parse_url(descriptor).unwrap(); - assert_eq!(result, (Chain::PolyMumbai, "as45cs5c5", "1.2.3.4:4444")) + assert_eq!(result, (Chain::PolyAmoy, "as45cs5c5", "1.2.3.4:4444")) } #[test] @@ -750,7 +790,7 @@ mod tests { assert_eq!( result, Err( - "Chain identifier 'bitcoin' is not valid; possible values are 'polygon-mainnet', 'eth-mainnet', 'polygon-mumbai', '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() ) ); @@ -849,7 +889,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-mumbai', '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] @@ -927,7 +967,7 @@ mod tests { node_addr_opt: Some(NodeAddr::new( &IpAddr::from_str("1.2.3.4").unwrap(), &[1234, 2345, 3456], - )), + )) }, ) } @@ -941,7 +981,7 @@ mod tests { NodeDescriptor { encryption_public_key: PublicKey::new(b"GoodKey"), blockchain: Chain::EthMainnet, - node_addr_opt: None, + node_addr_opt: None }, ) } @@ -1049,7 +1089,7 @@ mod tests { #[test] fn data_indefinite_route_request() { - let result = RouteQueryMessage::data_indefinite_route_request(None, 7500); + let result = RouteQueryMessage::data_indefinite_route_request(None, None, 7500); assert_eq!( result, @@ -1058,7 +1098,8 @@ mod tests { target_component: Component::ProxyClient, return_component_opt: Some(Component::ProxyServer), payload_size: 7500, - hostname_opt: None + hostname_opt: None, + target_country_opt: None, } ); } diff --git a/node/src/sub_lib/peer_actors.rs b/node/src/sub_lib/peer_actors.rs index 50142d485..3a51be868 100644 --- a/node/src/sub_lib/peer_actors.rs +++ b/node/src/sub_lib/peer_actors.rs @@ -4,11 +4,11 @@ use crate::sub_lib::blockchain_bridge::BlockchainBridgeSubs; use crate::sub_lib::configurator::ConfiguratorSubs; use crate::sub_lib::dispatcher::DispatcherSubs; use crate::sub_lib::hopper::HopperSubs; -use crate::sub_lib::neighborhood::NeighborhoodSubs; +use crate::sub_lib::neighborhood::{ConfigChangeMsg, NeighborhoodSubs}; use crate::sub_lib::proxy_client::ProxyClientSubs; use crate::sub_lib::proxy_server::ProxyServerSubs; use crate::sub_lib::ui_gateway::UiGatewaySubs; -use actix::Message; +use actix::{Message, Recipient}; use std::fmt; use std::fmt::Debug; use std::fmt::Formatter; @@ -33,6 +33,16 @@ impl Debug for PeerActors { } } +pub type ConfigChangeSubs = Vec>; +impl PeerActors { + pub fn config_change_subs(&self) -> ConfigChangeSubs { + vec![ + self.accountant.config_change_msg_sub.clone(), + self.neighborhood.config_change_msg_sub.clone(), + ] + } +} + #[derive(Debug, Message, Clone, PartialEq, Eq)] pub struct BindMessage { pub peer_actors: PeerActors, diff --git a/node/src/sub_lib/proxy_server.rs b/node/src/sub_lib/proxy_server.rs index 78c61ee72..c3042859f 100644 --- a/node/src/sub_lib/proxy_server.rs +++ b/node/src/sub_lib/proxy_server.rs @@ -9,6 +9,7 @@ use crate::sub_lib::peer_actors::BindMessage; use crate::sub_lib::proxy_client::{ClientResponsePayload_0v1, DnsResolveFailure_0v1}; use crate::sub_lib::sequence_buffer::SequencedPacket; use crate::sub_lib::stream_key::StreamKey; +use crate::sub_lib::utils::MessageScheduler; use crate::sub_lib::versioned_data::VersionedData; use actix::Message; use actix::Recipient; @@ -68,6 +69,11 @@ pub struct AddRouteResultMessage { pub result: Result, } +#[derive(Message, Debug, PartialEq, Eq)] +pub struct StreamKeyPurge { + pub stream_key: StreamKey, +} + #[derive(Clone, PartialEq, Eq)] pub struct ProxyServerSubs { // ProxyServer will handle these messages: @@ -79,6 +85,7 @@ pub struct ProxyServerSubs { pub stream_shutdown_sub: Recipient, pub node_from_ui: Recipient, pub route_result_sub: Recipient, + pub schedule_stream_key_purge: Recipient>, } impl Debug for ProxyServerSubs { @@ -110,6 +117,7 @@ mod tests { stream_shutdown_sub: recipient!(recorder, StreamShutdownMsg), node_from_ui: recipient!(recorder, NodeFromUiMessage), route_result_sub: recipient!(recorder, AddRouteResultMessage), + schedule_stream_key_purge: recipient!(recorder, MessageScheduler), }; assert_eq!(format!("{:?}", subject), "ProxyServerSubs"); diff --git a/node/src/sub_lib/utils.rs b/node/src/sub_lib/utils.rs index f6de206d7..d68d721bb 100644 --- a/node/src/sub_lib/utils.rs +++ b/node/src/sub_lib/utils.rs @@ -245,7 +245,7 @@ pub fn db_connection_launch_panic(err: InitializationError, data_directory: &Pat ) } -#[derive(Message, Clone, PartialEq, Eq)] +#[derive(Message, Debug, Clone, PartialEq, Eq)] pub struct MessageScheduler { pub scheduled_msg: M, pub delay: Duration, 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/mod.rs b/node/src/test_utils/mod.rs index 60a9b7741..edeee2851 100644 --- a/node/src/test_utils/mod.rs +++ b/node/src/test_utils/mod.rs @@ -52,11 +52,13 @@ use std::collections::btree_set::BTreeSet; use std::collections::HashSet; use std::convert::From; use std::fmt::Debug; + use std::hash::Hash; use std::io::ErrorKind; use std::io::Read; use std::iter::repeat; use std::net::{Shutdown, TcpStream}; + use std::str::FromStr; use std::sync::{Arc, Mutex}; use std::thread; @@ -516,7 +518,7 @@ pub fn assert_eq_debug(a: T, b: T) { assert_eq!(a_str, b_str); } -//must stay without cfg(test) -- used in another crate +// Must stay without cfg(test) -- used in another crate #[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)] pub struct TestRawTransaction { pub nonce: U256, @@ -1221,18 +1223,17 @@ pub mod unshared_test_utils { #[cfg(test)] mod tests { - use std::borrow::BorrowMut; - use std::iter; - use std::sync::{Arc, Mutex}; - use std::thread; - use std::time::Duration; - use crate::sub_lib::cryptde::CryptData; use crate::sub_lib::hop::LiveHop; use crate::sub_lib::neighborhood::ExpectedService; use crate::test_utils::unshared_test_utils::arbitrary_id_stamp::{ ArbitraryIdStamp, FirstTraitMock, SecondTraitMock, TestSubject, }; + use std::borrow::BorrowMut; + use std::iter; + use std::sync::{Arc, Mutex}; + use std::thread; + use std::time::Duration; use super::*; diff --git a/node/src/test_utils/neighborhood_test_utils.rs b/node/src/test_utils/neighborhood_test_utils.rs index 72545797a..983fd7932 100644 --- a/node/src/test_utils/neighborhood_test_utils.rs +++ b/node/src/test_utils/neighborhood_test_utils.rs @@ -66,13 +66,22 @@ impl From<(&NeighborhoodDatabase, &PublicKey, bool)> for AccessibleGossipRecord } } -pub fn make_node_record(n: u16, has_ip: bool) -> NodeRecord { +pub fn make_segments(n: u16) -> (u8, u8, u8, u8) { let seg1 = ((n / 1000) % 10) as u8; let seg2 = ((n / 100) % 10) as u8; let seg3 = ((n / 10) % 10) as u8; let seg4 = (n % 10) as u8; + (seg1, seg2, seg3, seg4) +} + +pub fn make_segmented_ip(seg1: u8, seg2: u8, seg3: u8, seg4: u8) -> IpAddr { + IpAddr::V4(Ipv4Addr::new(seg1, seg2, seg3, seg4)) +} + +pub fn make_node_record(n: u16, has_ip: bool) -> NodeRecord { + let (seg1, seg2, seg3, seg4) = make_segments(n); let key = PublicKey::new(&[seg1, seg2, seg3, seg4]); - let ip_addr = IpAddr::V4(Ipv4Addr::new(seg1, seg2, seg3, seg4)); + let ip_addr = make_segmented_ip(seg1, seg2, seg3, seg4); let node_addr = NodeAddr::new(&ip_addr, &[n % 10000]); let (_ip, country_code, free_world_bit) = pick_country_code_record(n % 6); let location_opt = match country_code.is_empty() { @@ -148,12 +157,10 @@ pub fn neighborhood_from_nodes( *root.rate_pack(), ), min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }, None => NeighborhoodConfig { mode: NeighborhoodMode::ZeroHop, min_hops: MIN_HOPS_FOR_TEST, - country: "ZZ".to_string(), }, }; config.earning_wallet = root.earning_wallet(); diff --git a/node/src/test_utils/persistent_configuration_mock.rs b/node/src/test_utils/persistent_configuration_mock.rs index 7b7ace61d..d50613392 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 diff --git a/node/src/test_utils/recorder.rs b/node/src/test_utils/recorder.rs index 9c114716f..da1aed944 100644 --- a/node/src/test_utils/recorder.rs +++ b/node/src/test_utils/recorder.rs @@ -20,14 +20,13 @@ use crate::sub_lib::accountant::ReportRoutingServiceProvidedMessage; use crate::sub_lib::accountant::ReportServicesConsumedMessage; use crate::sub_lib::blockchain_bridge::BlockchainBridgeSubs; use crate::sub_lib::blockchain_bridge::OutboundPaymentsInstructions; -use crate::sub_lib::configurator::NewPasswordMessage; use crate::sub_lib::dispatcher::InboundClientData; use crate::sub_lib::dispatcher::{DispatcherSubs, StreamShutdownMsg}; use crate::sub_lib::hopper::IncipientCoresPackage; use crate::sub_lib::hopper::{ExpiredCoresPackage, NoLookupIncipientCoresPackage}; use crate::sub_lib::hopper::{HopperSubs, MessageType}; use crate::sub_lib::neighborhood::NeighborhoodSubs; -use crate::sub_lib::neighborhood::{ConfigurationChangeMessage, ConnectionProgressMessage}; +use crate::sub_lib::neighborhood::{ConfigChangeMsg, ConnectionProgressMessage}; use crate::sub_lib::configurator::ConfiguratorSubs; use crate::sub_lib::neighborhood::NodeQueryResponseMetadata; @@ -40,7 +39,9 @@ use crate::sub_lib::peer_actors::PeerActors; use crate::sub_lib::peer_actors::{BindMessage, NewPublicIp, StartMessage}; use crate::sub_lib::proxy_client::{ClientResponsePayload_0v1, InboundServerData}; use crate::sub_lib::proxy_client::{DnsResolveFailure_0v1, ProxyClientSubs}; -use crate::sub_lib::proxy_server::{AddReturnRouteMessage, ClientRequestPayload_0v1}; +use crate::sub_lib::proxy_server::{ + AddReturnRouteMessage, ClientRequestPayload_0v1, StreamKeyPurge, +}; use crate::sub_lib::proxy_server::{AddRouteResultMessage, ProxyServerSubs}; use crate::sub_lib::stream_handler_pool::DispatcherNodeQueryResponse; use crate::sub_lib::stream_handler_pool::TransmitDataMsg; @@ -127,7 +128,7 @@ recorder_message_handler_t_m_p!(AddRouteResultMessage); recorder_message_handler_t_p!(AddStreamMsg); recorder_message_handler_t_m_p!(BindMessage); recorder_message_handler_t_p!(BlockchainAgentWithContextMessage); -recorder_message_handler_t_m_p!(ConfigurationChangeMessage); +recorder_message_handler_t_m_p!(ConfigChangeMsg); recorder_message_handler_t_m_p!(ConnectionProgressMessage); recorder_message_handler_t_m_p!(CrashNotification); recorder_message_handler_t_m_p!(DaemonBindMessage); @@ -143,7 +144,6 @@ recorder_message_handler_t_m_p!(ExpiredCoresPackage); recorder_message_handler_t_m_p!(InboundClientData); recorder_message_handler_t_m_p!(InboundServerData); recorder_message_handler_t_m_p!(IncipientCoresPackage); -recorder_message_handler_t_m_p!(NewPasswordMessage); // GH-728 recorder_message_handler_t_m_p!(NewPublicIp); recorder_message_handler_t_m_p!(NodeFromUiMessage); recorder_message_handler_t_m_p!(NodeToUiMessage); @@ -400,6 +400,7 @@ pub fn make_proxy_server_subs_from_recorder(addr: &Addr) -> ProxyServe stream_shutdown_sub: recipient!(addr, StreamShutdownMsg), node_from_ui: recipient!(addr, NodeFromUiMessage), route_result_sub: recipient!(addr, AddRouteResultMessage), + schedule_stream_key_purge: recipient!(addr, MessageScheduler), } } @@ -445,10 +446,9 @@ pub fn make_neighborhood_subs_from_recorder(addr: &Addr) -> Neighborho gossip_failure: recipient!(addr, ExpiredCoresPackage), dispatcher_node_query: recipient!(addr, DispatcherNodeQueryMessage), remove_neighbor: recipient!(addr, RemoveNeighborMessage), - configuration_change_msg_sub: recipient!(addr, ConfigurationChangeMessage), + config_change_msg_sub: recipient!(addr, ConfigChangeMsg), stream_shutdown_sub: recipient!(addr, StreamShutdownMsg), from_ui_message_sub: recipient!(addr, NodeFromUiMessage), - new_password_sub: recipient!(addr, NewPasswordMessage), // GH-728 connection_progress_sub: recipient!(addr, ConnectionProgressMessage), } } @@ -456,6 +456,7 @@ pub fn make_neighborhood_subs_from_recorder(addr: &Addr) -> Neighborho pub fn make_accountant_subs_from_recorder(addr: &Addr) -> AccountantSubs { AccountantSubs { bind: recipient!(addr, BindMessage), + config_change_msg_sub: recipient!(addr, ConfigChangeMsg), start: recipient!(addr, StartMessage), report_routing_service_provided: recipient!(addr, ReportRoutingServiceProvidedMessage), report_exit_service_provided: recipient!(addr, ReportExitServiceProvidedMessage), diff --git a/node/tests/connection_shutdown_test.rs b/node/tests/connection_shutdown_test.rs new file mode 100644 index 000000000..24cd9d25c --- /dev/null +++ b/node/tests/connection_shutdown_test.rs @@ -0,0 +1,81 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +pub mod utils; + +use crossbeam_channel::{unbounded, Sender}; +use masq_lib::utils::find_free_port; +use std::io::{Read, Write}; +use std::net::{IpAddr, TcpListener, TcpStream}; +use std::net::{Shutdown, SocketAddr}; +use std::str::FromStr; +use std::time::Duration; +use std::{io, thread}; + +// 'node' below must not be named '_' alone or disappear, or the MASQNode will be immediately reclaimed. +#[test] +fn proxy_client_stream_reader_dies_when_client_stream_is_killed_integration() { + let _node = utils::MASQNode::start_standard( + "proxy_client_stream_reader_dies_when_client_stream_is_killed_integration", + None, + true, + true, + false, + true, + ); + let (server_write_error_tx, server_write_error_rx) = unbounded(); + let server_port = find_free_port(); + let join_handle = thread::spawn(move || { + endless_write_server(server_port, server_write_error_tx); + }); + let mut browser_stream = + TcpStream::connect(SocketAddr::from_str("127.0.0.1:80").unwrap()).unwrap(); + browser_stream + .set_read_timeout(Some(Duration::from_millis(1000))) + .unwrap(); + let request = format!("GET / HTTP/1.1\r\nHost: 127.0.0.1:{server_port}\r\n\r\n"); + browser_stream.write(request.as_bytes()).unwrap(); + let mut buf = [0u8; 16384]; + // We want to make sure the Server is sending before we shutdown the stream + browser_stream.read(&mut buf).unwrap(); + + browser_stream.shutdown(Shutdown::Write).unwrap(); + + let write_error = server_write_error_rx + .recv_timeout(Duration::from_secs(60)) + .unwrap(); + if cfg!(target_os = "macos") { + assert_eq!(write_error.kind(), io::ErrorKind::BrokenPipe); + } else { + assert_eq!(write_error.kind(), io::ErrorKind::ConnectionReset); + } + + join_handle.join().unwrap(); +} + +fn endless_write_server(port: u16, write_error_tx: Sender) { + let listener = TcpListener::bind(SocketAddr::new( + IpAddr::from_str("127.0.0.1").unwrap(), + port, + )) + .unwrap(); + let mut buf = [0u8; 16_384]; + let (mut stream, _) = listener.accept().unwrap(); + stream + .set_write_timeout(Some(Duration::from_secs(1))) + .unwrap(); + let _ = stream.read(&mut buf).unwrap(); + stream + .write("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n".as_bytes()) + .unwrap(); + let msg = "Chancellor on brink of second bailout for banks"; + let msg_len = msg.len(); + let chunk_body = format!("{msg_len}\r\n{msg}\r\n"); + loop { + if let Err(e) = stream.write(chunk_body.as_bytes()) { + write_error_tx.send(e).unwrap(); + break; + } + + thread::sleep(Duration::from_millis(250)); + } +} diff --git a/node/tests/contract_test.rs b/node/tests/contract_test.rs index 6002f392b..42d6fce37 100644 --- a/node/tests/contract_test.rs +++ b/node/tests/contract_test.rs @@ -105,14 +105,13 @@ where } #[test] -fn masq_erc20_contract_exists_on_polygon_mumbai_integration() { +fn masq_erc20_contract_exists_on_polygon_amoy_integration() { let blockchain_urls = vec![ - "https://rpc-mumbai.polygon.technology", - "https://matic-mumbai.chainstacklabs.com", - "https://rpc-mumbai.maticvigil.com", - "https://matic-testnet-archive-rpc.bwarelabs.com", + "https://rpc-amoy.polygon.technology", + "https://rpc.ankr.com/polygon_amoy", + "https://80002.rpc.thirdweb.com", ]; - let chain = Chain::PolyMumbai; + let chain = Chain::PolyAmoy; let assertion_body = |url, chain| assert_contract_existence(url, chain, "tMASQ", 18); assert_contract(blockchain_urls, &chain, assertion_body) @@ -141,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/dump_configuration_test.rs b/node/tests/dump_configuration_test.rs index 044eb2043..62a045b0d 100644 --- a/node/tests/dump_configuration_test.rs +++ b/node/tests/dump_configuration_test.rs @@ -22,7 +22,7 @@ fn dump_configuration_with_an_existing_database_integration() { Some( CommandConfig::new() .pair("--ui-port", &port.to_string()) - .pair("--chain", "polygon-mumbai"), + .pair("--chain", "polygon-amoy"), ), true, true, @@ -38,7 +38,7 @@ fn dump_configuration_with_an_existing_database_integration() { let mut node = utils::MASQNode::run_dump_config( test_name, - Some(CommandConfig::new().pair("--chain", "polygon-mumbai")), + Some(CommandConfig::new().pair("--chain", "polygon-amoy")), false, true, true, diff --git a/node/tests/node_exits_from_future_panic_test.rs b/node/tests/node_exits_from_future_panic_test.rs index 1419dc0fe..dc3ec25ab 100644 --- a/node/tests/node_exits_from_future_panic_test.rs +++ b/node/tests/node_exits_from_future_panic_test.rs @@ -67,7 +67,7 @@ const STAT_FORMAT_PARAM_NAME: &str = "-f"; fn node_logfile_does_not_belong_to_root_integration() { let mut node = MASQNode::start_standard( "node_logfile_does_not_belong_to_root_integration", - Some(CommandConfig::new().pair("--chain", "polygon-mumbai")), + Some(CommandConfig::new().pair("--chain", "polygon-amoy")), true, true, false, 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." diff --git a/test_utilities/Cargo.toml b/test_utilities/Cargo.toml new file mode 100644 index 000000000..93191e691 --- /dev/null +++ b/test_utilities/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "test_utilities" +version = "0.1.0" +edition = "2021" +authors = ["Dan Wiebe ", "MASQ"] +license = "GPL-3.0-only" +description = "Testing utilities Code common to Node and masq; also, temporarily, to dns_utility" +workspace = "../node" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] \ No newline at end of file diff --git a/test_utilities/src/byte_array_reader_writer.rs b/test_utilities/src/byte_array_reader_writer.rs new file mode 100644 index 000000000..fe611f781 --- /dev/null +++ b/test_utilities/src/byte_array_reader_writer.rs @@ -0,0 +1,132 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use std::cmp::min; +use std::io; +use std::io::Read; +use std::io::Write; +use std::io::{BufRead, Error}; +use std::sync::{Arc, Mutex}; + +pub struct ByteArrayWriter { + inner_arc: Arc>, +} + +pub struct ByteArrayWriterInner { + byte_array: Vec, + next_error: Option, +} + +impl ByteArrayWriterInner { + pub fn get_bytes(&self) -> Vec { + self.byte_array.clone() + } + pub fn get_string(&self) -> String { + String::from_utf8(self.get_bytes()).unwrap() + } +} + +impl Default for ByteArrayWriter { + fn default() -> Self { + ByteArrayWriter { + inner_arc: Arc::new(Mutex::new(ByteArrayWriterInner { + byte_array: vec![], + next_error: None, + })), + } + } +} + +impl ByteArrayWriter { + pub fn new() -> ByteArrayWriter { + Self::default() + } + + pub fn inner_arc(&self) -> Arc> { + self.inner_arc.clone() + } + + pub fn get_bytes(&self) -> Vec { + self.inner_arc.lock().unwrap().byte_array.clone() + } + pub fn get_string(&self) -> String { + String::from_utf8(self.get_bytes()).unwrap() + } + + pub fn reject_next_write(&mut self, error: Error) { + self.inner_arc().lock().unwrap().next_error = Some(error); + } +} + +impl Write for ByteArrayWriter { + fn write(&mut self, buf: &[u8]) -> io::Result { + let mut inner = self.inner_arc.lock().unwrap(); + if let Some(next_error) = inner.next_error.take() { + Err(next_error) + } else { + for byte in buf { + inner.byte_array.push(*byte) + } + Ok(buf.len()) + } + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +pub struct ByteArrayReader { + byte_array: Vec, + position: usize, + next_error: Option, +} + +impl ByteArrayReader { + pub fn new(byte_array: &[u8]) -> ByteArrayReader { + ByteArrayReader { + byte_array: byte_array.to_vec(), + position: 0, + next_error: None, + } + } + + pub fn reject_next_read(mut self, error: Error) -> ByteArrayReader { + self.next_error = Some(error); + self + } +} + +impl Read for ByteArrayReader { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + match self.next_error.take() { + Some(error) => Err(error), + None => { + let to_copy = min(buf.len(), self.byte_array.len() - self.position); + #[allow(clippy::needless_range_loop)] + for idx in 0..to_copy { + buf[idx] = self.byte_array[self.position + idx] + } + self.position += to_copy; + Ok(to_copy) + } + } + } +} + +impl BufRead for ByteArrayReader { + fn fill_buf(&mut self) -> io::Result<&[u8]> { + match self.next_error.take() { + Some(error) => Err(error), + None => Ok(&self.byte_array[self.position..]), + } + } + + fn consume(&mut self, amt: usize) { + let result = self.position + amt; + self.position = if result < self.byte_array.len() { + result + } else { + self.byte_array.len() + } + } +} diff --git a/test_utilities/src/lib.rs b/test_utilities/src/lib.rs new file mode 100644 index 000000000..552c3b7d1 --- /dev/null +++ b/test_utilities/src/lib.rs @@ -0,0 +1,3 @@ +// Copyright (c) 2024, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +pub mod byte_array_reader_writer;