diff --git a/.github/workflows/ci-matrix.yml b/.github/workflows/ci-matrix.yml
index 5285afcbd..b444643e8 100644
--- a/.github/workflows/ci-matrix.yml
+++ b/.github/workflows/ci-matrix.yml
@@ -14,7 +14,7 @@ jobs:
matrix:
target:
- { name: linux, os: ubuntu-22.04 }
- - { name: macos, os: macos-13 }
+ - { name: macos, os: macos-latest }
- { name: windows, os: windows-2022 }
name: Build node on ${{ matrix.target.os }}
diff --git a/.gitignore b/.gitignore
index 88a2ab5f9..5416fd7bc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@
**/*.rs.bk
.idea/azure/
.idea/inspectionProfiles/Project_Default.xml
+.idea/copilot*
### Node
node_modules
@@ -57,8 +58,10 @@ Temporary Items
**/.idea/jsLibraryMappings.xml
**/.idea/markdown-navigator.xml
**/.idea/markdown-navigator/**
+**/idea/db-forest-config.xml
**/.idea/aws.xml
**/.idea/scopes/*.xml
+**/.idea/copilot*
# Sensitive or high-churn files:
**/.idea/**/dataSources/
diff --git a/.idea/SweepConfig.xml b/.idea/SweepConfig.xml
new file mode 100644
index 000000000..bfb1652a3
--- /dev/null
+++ b/.idea/SweepConfig.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SWEEP.md b/SWEEP.md
new file mode 100644
index 000000000..485f2958b
--- /dev/null
+++ b/SWEEP.md
@@ -0,0 +1,549 @@
+# SWEEP.md - MASQ Node Development Guide
+
+This file contains essential information for AI assistants and developers working on the MASQ Node project, including coding standards, common commands, project structure, and development workflows.
+
+## Table of Contents
+- [Project Overview](#project-overview)
+- [Common Terminal Commands](#common-terminal-commands)
+- [Project Structure](#project-structure)
+- [Development Workflow](#development-workflow)
+- [Code Style and Standards](#code-style-and-standards)
+- [Testing Guidelines](#testing-guidelines)
+- [Build and CI/CD](#build-and-cicd)
+- [Important Notes](#important-notes)
+
+---
+
+## Project Overview
+
+**MASQ Node** is the foundation of the MASQ Network, an open-source decentralized mesh-network (dMN) written primarily in Rust. It combines the benefits of VPN and Tor technology to create next-generation privacy software.
+
+### Key Technologies
+- **Language**: Rust (2021 edition)
+- **Build System**: Cargo
+- **Testing**: Unit tests, integration tests, multinode integration tests (Docker-based)
+- **CI/CD**: GitHub Actions
+- **Platforms**: Linux, macOS, Windows (64-bit)
+
+### Repository Structure
+The project is organized as a Cargo workspace with multiple crates:
+- `node/` - Main MASQ Node implementation
+- `masq/` - Command-line user interface
+- `masq_lib/` - Shared library code
+- `dns_utility/` - DNS configuration utility - deprecated
+- `automap/` - Automatic public-IP detection and configuration
+- `port_exposer/` - Port exposure utilities for testing only
+- `ip_country/` - IP geolocation functionality
+- `multinode_integration_tests/` - Docker-based integration tests employing multiple simultaneous Nodes
+
+---
+
+## Common Terminal Commands
+
+### Building and Testing
+
+#### Run All Tests and Checks (Full CI Pipeline)
+```bash
+ci/all.sh
+```
+This runs formatting, linting, unit tests, and integration tests for all components. **You will be prompted for your password** during zero-hop integration tests (requires sudo).
+
+#### Run Multinode Integration Tests (Linux only)
+```bash
+ci/multinode_integration_test.sh
+```
+Note: These tests only run on Linux and require Docker.
+
+#### Format Code
+```bash
+ci/format.sh
+```
+Formats all Rust code using `rustfmt`. This is required before submitting PRs.
+
+#### Run Linting (Clippy)
+```bash
+cargo clippy -- -D warnings -Anon-snake-case
+```
+Or for a specific component:
+```bash
+cd node && ci/lint.sh
+```
+
+#### Run Unit Tests
+```bash
+# For the node component
+cd node && ci/unit_tests.sh
+
+# Or manually
+cargo test --release --lib --no-fail-fast --features masq_lib/log_recipient_test -- --nocapture --skip _integration
+```
+
+#### Run Integration Tests
+```bash
+cd node && ci/integration_tests.sh
+```
+Note: Integration tests require sudo privileges on Linux and macOS.
+
+#### Build Release Version
+```bash
+cargo build --release
+```
+
+#### Build Debug Version
+```bash
+cargo build
+```
+
+### Version Management
+
+#### Bump Version
+```bash
+cd ci && ./bump_version.sh
+```
+Example:
+```bash
+cd ci && ./bump_version.sh 6.9.1
+```
+This updates version numbers in all `Cargo.toml` files and corresponding `Cargo.lock` files.
+
+### Running MASQ Node
+
+#### Start MASQ Daemon (Linux/macOS)
+```bash
+sudo nohup ./MASQNode --initialization &
+```
+
+#### Start MASQ Daemon (Windows)
+```bash
+start /b MASQNode --initialization
+```
+
+#### Start MASQ CLI
+```bash
+./masq
+```
+
+#### Run MASQ Command (Non-interactive)
+```bash
+./masq setup --log-level debug --clandestine-port 1234
+```
+
+#### Shutdown MASQ Node
+```bash
+./masq shutdown
+```
+
+#### Revert DNS Configuration
+```bash
+sudo ./dns_utility revert
+```
+
+### Git Workflow
+
+#### Update Master Branch
+```bash
+git checkout master
+git pull
+```
+
+#### Create Feature Branch
+```bash
+git checkout -b GH-
+git push -u origin HEAD
+```
+
+#### Merge Master into Feature Branch
+```bash
+git checkout master
+git pull
+git checkout - # Returns to previous branch
+git merge master
+```
+
+### Docker Commands (for Multinode Tests)
+
+#### View Docker Logs
+```bash
+multinode_integration_tests/docker_logs.sh
+```
+
+#### Dump Docker State
+```bash
+multinode_integration_tests/docker_dump.sh
+```
+
+#### Build Docker Images
+```bash
+multinode_integration_tests/docker/build.sh
+```
+
+---
+
+## Project Structure
+
+### Main Components
+
+#### Node (`node/`)
+The core MASQ Node implementation containing:
+- `src/accountant/` - Payment and accounting logic
+- `src/blockchain/` - Blockchain interaction
+- `src/entry_dns/` - Tiny DNS server that returns 'localhost' for any hostname - deprecated
+- `src/hopper/` - Message routing
+- `src/neighborhood/` - Network topology management
+- `src/proxy_client/` - Exit-Node proxy
+- `src/proxy_server/` - Originating-Node proxy
+- `src/ui_gateway/` - User interface communication
+- `src/sub_lib/` - Code shared among accountant, blockchain, hopper, etc.
+- `src/test_utils/` - Testing utilities
+
+Each component has its own README.md with detailed documentation.
+
+#### MASQ CLI (`masq/`)
+Command-line interface for controlling the MASQ Daemon and Node.
+
+#### DNS Utility (`dns_utility/`) - deprecated
+The standard way to add intercept processing to your network data flow is to configure your system network stack to
+use an HTTP and/or HTTPS proxy. However, early in the history of Substratum, Justin Tabb made a promise that Node
+would be "zero-configuration." This utility, and the code in Node that it supports, were intended to keep that promise.
+Essentially, Node contains a tiny DNS server that always returns 'localhost' for any hostname; therefore, when your
+browser or other application performs DNS resolution, it will be fooled into routing its traffic through Node, which
+is running on `localhost`. This utility wrangles your system DNS settings to point at that tiny DNS server, if you're
+going to run Node, or back at your real DNS server if you're not. Since Node is now part of MASQ and no longer part of
+Substratum and Justin Tabb is a part of history, the Node now operates as an HTTPS proxy and its `entry_dns` server is
+no longer used. However, the code is still available and active and usable, so this utility remains available as well,
+although its use is deprecated.
+
+#### Multinode Integration Tests (`multinode_integration_tests/`)
+Docker-based integration tests that simulate multi-node networks.
+
+### Configuration Files
+
+#### Cargo.toml
+Each component has its own `Cargo.toml` defining dependencies and metadata.
+
+#### config.toml
+Runtime configuration file (located in data directory by default).
+- Can be specified via `--config-file` parameter or `MASQ_CONFIG_FILE` environment variable
+- Uses TOML format with scalar settings
+- Example: `clandestine-port = 1234`
+
+---
+
+## Development Workflow
+
+### Setting Up Development Environment
+
+1. **Install Rust toolchain**:
+ ```bash
+ rustup component add rustfmt
+ rustup component add clippy
+ ```
+
+2. **Install Docker** (for multinode tests on Linux)
+
+3. **Clone repository and test**:
+ ```bash
+ git clone
+ cd Node
+ ci/all.sh
+ ```
+
+### Working on an Issue
+
+1. **Select issue** from the [MASQ Node Card Wall](https://github.com/orgs/MASQ-Project/projects/1)
+2. **Update master branch**: `git checkout master && git pull`
+3. **Create feature branch**: `git checkout -b GH-`
+4. **Complete the work** (test-driven development required)
+5. **Merge in master regularly**: `git merge master`
+6. **Run full test suite**: `ci/all.sh`
+7. **Run multinode tests** (Linux only): `ci/multinode_integration_test.sh`
+8. **Push changes**: `git push`
+9. **Open pull request** on GitHub
+10. **Watch GitHub Actions build**
+11. **Address reviewer comments**
+12. **Wait for QA approval**
+
+### Commit Guidelines
+
+- Commit frequently (commits will be squashed before merging)
+- Commit when tests go green
+- Commit before trying risky changes
+- Write descriptive commit messages for your own reference
+
+---
+
+## Code Style and Standards
+
+### Rust Standards
+
+#### Formatting
+- Use `rustfmt` for all code formatting
+- Run `ci/format.sh` before committing (non-auto-formatted code will fail CI)
+- To skip formatting on specific code: `#[cfg_attr(rustfmt, rustfmt_skip)]`
+
+#### Linting
+- All code must pass `clippy` with `-D warnings`
+- Non-snake-case warnings are allowed: `-Anon-snake-case`
+- Run `cargo clippy -- -D warnings -Anon-snake-case`
+
+#### Compiler Flags
+```bash
+export RUSTFLAGS="-D warnings -Anon-snake-case"
+```
+
+#### Error Handling
+- Use `Result` types for fallible operations
+- Provide descriptive error messages
+- Handle errors appropriately (no `unwrap()` in production code; use `expect()` only, and that sparingly)
+- **Important:** Nothing that comes into the system from the network must ever be allowed to cause a panic. All the
+standard anti-injection rules apply, but whenever something from the outside generates an error, the error must be
+logged and execution must continue. (Sometimes additional action is appropriate, such as banning an evil Node; but
+logging the error is the bare minimum.) `eprintln!()` is not logging.
+
+#### Testing
+- **Test-Driven Development (TDD) is required**
+- All new code must have corresponding tests
+- Tests must pass before code review
+- Use descriptive test names
+- Test both success and failure cases
+
+### Naming Conventions
+- Use snake_case for functions and variables
+- Use PascalCase for types and traits
+- Use SCREAMING_SNAKE_CASE for constants
+- All zero-hop integration tests must have names _suffixed_ with `_integration`; otherwise they'll run as unit tests
+rather than integration tests.
+
+### Documentation
+- Document public APIs with doc comments (`///`)
+- Include examples in doc comments where helpful
+- Keep README.md files updated for each component
+
+---
+
+## Testing Guidelines
+
+### Test Types
+
+#### Unit Tests
+- Located in the same file as the code being tested (in `#[cfg(test)]` modules)
+- Test individual functions and methods in isolation
+- Run with: `cargo test --lib`
+- Should not require sudo or network access
+
+#### Integration Tests
+- Test interactions between components
+- Starts up a real Node, always in `--neighborhood-mode zero-hop`
+- Will require sudo privileges, because a Node has to start with root privileges to open low ports
+- Run with: `ci/integration_tests.sh`
+
+#### Multinode Integration Tests
+- Docker-based tests simulating multi-node networks
+- **Linux only** (do not run on macOS or Windows)
+- Require Docker installation
+- Run with: `ci/multinode_integration_test.sh`
+
+### Test Execution
+
+#### Run All Tests
+```bash
+ci/all.sh
+```
+
+#### Run Specific Test
+```bash
+cargo test test_name -- --nocapture
+```
+
+#### Run Tests with Backtrace
+```bash
+RUST_BACKTRACE=full cargo test
+```
+
+#### Run Tests in Release Mode
+```bash
+cargo test --release
+```
+
+### Test Features
+- Use `--no-fail-fast` to run all tests even if some fail
+- Use `--nocapture` to see println! output
+
+---
+
+## Build and CI/CD
+
+### Local Build Process
+
+The `ci/all.sh` script performs the following:
+1. Format check and auto-format (`ci/format.sh`)
+2. Install git hooks (`install-hooks.sh`)
+3. Start sccache server (for faster compilation)
+4. Build and test each component:
+ - masq_lib
+ - node
+ - dns_utility
+ - masq (CLI)
+ - automap
+ - ip_country
+
+### GitHub Actions
+
+- Builds run automatically on pull requests
+- Unit and single-Node integration tests run on Linux, macOS, and Windows
+- Multinode integration tests run on Linux only
+- All builds must pass before merging
+
+### Build Artifacts
+
+After successful builds, artifacts are available in:
+- `/target/release/` and `/target/debug/` - Executable binaries
+ - `MASQNode` (or `MASQNode.exe`) - Node and Daemon
+ - `masq` - Command-line interface
+ - `dns_utility` - DNS configuration tool
+ - `automap` - Firewall penetration test utility: requires special remote setup
+
+### Caching
+
+The project uses `sccache` for faster compilation:
+```bash
+export SCCACHE_DIR="$HOME/.cargo/sccache"
+SCCACHE_IDLE_TIMEOUT=0 sccache --start-server
+```
+
+---
+
+## Important Notes
+
+### Platform-Specific Considerations
+
+#### Linux
+- Full support for all features
+- Multinode integration tests available
+- Requires sudo for integration tests
+- May need to free port 53: `sudo ci/free-port-53.sh`
+
+#### macOS
+- Full support except multinode integration tests
+- Requires sudo for integration tests
+- May need to adjust file descriptor limits in GitHub Actions
+
+#### Windows
+- Use Git Bash for running scripts
+- Multinode integration tests not supported
+- Some services may need to be stopped (ICS, W3SVC)
+- 32-bit Windows not reliably supported (use 64-bit)
+- Run as Administrator
+
+### Security Considerations
+
+- **MASQ Node is currently in beta** - not clandestine yet
+- Do not use for sensitive traffic
+- Traffic cannot be decrypted by attackers but MASQ traffic is identifiable
+- Database encryption uses a user-provided password
+- Password is never stored on disk
+- Forgetting the password means losing all the encrypted content in the database and starting over
+
+### Configuration Priority
+
+Configuration sources in order of priority (highest to lowest):
+1. `masq` UI commands
+2. Environment variables (prefixed with `MASQ_`)
+3. Configuration file (`config.toml`)
+4. Defaults
+
+Example:
+- CLI, non-interactive mode: `masq setup --clandestine-port 1234`
+- Environment: `MASQ_CLANDESTINE_PORT=1234`
+- Config file: `clandestine-port = "1234"`
+
+### Daemon vs Node
+
+- **Daemon**: Runs with admin privileges, starts at boot, cannot access network or communicate with Node
+- **Node**: Starts with admin privileges, drops privileges after network configuration, handles all network traffic
+- UI connects to Daemon first, then is redirected to Node when it first sends the Daemon a Node command
+
+### Error Handling in Browser
+
+#### HTTP Errors
+- MASQ Node can impersonate the remote server for HTTP errors
+- Errors are clearly marked as coming from MASQ Node rather than the remote server
+
+#### TLS Errors
+- In-band error reporting severely limited by TLS protocol; only available for ClientHello
+- Friendliest errors are written to the log
+
+### Development Tools
+
+#### Recommended IDE
+- JetBrains IntelliJ IDEA with Rust plugin (used by MASQ team)
+- Other options: VS Code with rust-analyzer, etc.
+
+#### Required Tools
+- Rust toolchain (rustc, cargo, rustfmt, clippy)
+- Git
+- Docker (for multinode tests on Linux)
+- sudo access (for integration tests)
+
+### Useful Links
+
+- [MASQ Node Repository](https://github.com/MASQ-Project/Node)
+- [MASQ Node Card Wall](https://github.com/orgs/MASQ-Project/projects/1)
+- [GitHub Actions Build Site](https://github.com/MASQ-Project/Node/actions)
+- [Knowledge Base](https://docs.masq.ai/masq)
+- [Discord Channel](https://discord.gg/masq)
+- [Latest Release](https://github.com/MASQ-Project/Node/releases/latest)
+
+---
+
+## Quick Reference
+
+### Most Common Commands
+
+```bash
+# Full test suite
+ci/all.sh
+
+# Format code
+ci/format.sh
+
+# Lint code
+cargo clippy -- -D warnings -Anon-snake-case
+
+# Run unit tests
+cargo test --release --lib --no-fail-fast
+
+# Build release
+cargo build --release
+
+# Start daemon (Linux/macOS)
+sudo nohup ./MASQNode --initialization &
+
+# Start CLI
+./masq
+
+# Shutdown node
+./masq shutdown
+```
+
+### Environment Variables
+
+```bash
+export RUST_BACKTRACE=full # Full backtraces on panic
+export RUSTFLAGS="-D warnings -Anon-snake-case" # Compiler flags
+export SCCACHE_DIR="$HOME/.cargo/sccache" # Cache directory
+```
+
+### File Locations
+
+- Binaries: `/target/release/` or `/target/debug/`
+- Config file: `/config.toml`
+- Database: `/`
+- Logs: Configured via `--log-level` parameter
+
+---
+
+**Last Updated**: 2026
+**Project**: MASQ Node
+**License**: GPL-3.0-only
+**Copyright**: (c) 2026, MASQ Network
diff --git a/automap/Cargo.lock b/automap/Cargo.lock
index 82258e87b..409a66cff 100644
--- a/automap/Cargo.lock
+++ b/automap/Cargo.lock
@@ -137,7 +137,7 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "automap"
-version = "0.9.0"
+version = "0.9.1"
dependencies = [
"crossbeam-channel 0.5.8",
"flexi_logger",
@@ -942,7 +942,7 @@ dependencies = [
[[package]]
name = "ip_country"
-version = "0.1.0"
+version = "0.9.1"
dependencies = [
"csv",
"ipnetwork",
@@ -1116,7 +1116,7 @@ dependencies = [
[[package]]
name = "masq_lib"
-version = "0.9.0"
+version = "0.9.1"
dependencies = [
"actix",
"clap",
@@ -2043,7 +2043,7 @@ dependencies = [
[[package]]
name = "test_utilities"
-version = "0.1.0"
+version = "0.9.1"
[[package]]
name = "textwrap"
diff --git a/automap/Cargo.toml b/automap/Cargo.toml
index b379c8a55..f4fa870de 100644
--- a/automap/Cargo.toml
+++ b/automap/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "automap"
-version = "0.9.0"
+version = "0.9.1"
authors = ["Dan Wiebe ", "MASQ"]
license = "GPL-3.0-only"
description = "Library full of code to make routers map ports through firewalls"
diff --git a/dns_utility/Cargo.lock b/dns_utility/Cargo.lock
index 32431ab07..7f87dbdb9 100644
--- a/dns_utility/Cargo.lock
+++ b/dns_utility/Cargo.lock
@@ -457,7 +457,7 @@ dependencies = [
[[package]]
name = "dns_utility"
-version = "0.9.0"
+version = "0.9.1"
dependencies = [
"core-foundation",
"ipconfig 0.2.2",
@@ -757,7 +757,7 @@ dependencies = [
[[package]]
name = "ip_country"
-version = "0.1.0"
+version = "0.9.1"
dependencies = [
"csv",
"ipnetwork",
@@ -919,7 +919,7 @@ dependencies = [
[[package]]
name = "masq_lib"
-version = "0.9.0"
+version = "0.9.1"
dependencies = [
"actix",
"clap",
@@ -1775,7 +1775,7 @@ dependencies = [
[[package]]
name = "test_utilities"
-version = "0.1.0"
+version = "0.9.1"
[[package]]
name = "textwrap"
diff --git a/dns_utility/Cargo.toml b/dns_utility/Cargo.toml
index c21f8a9ca..beabe18f5 100644
--- a/dns_utility/Cargo.toml
+++ b/dns_utility/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "dns_utility"
-version = "0.9.0"
+version = "0.9.1"
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 e974fcf41..ce74c5f48 100644
--- a/ip_country/Cargo.toml
+++ b/ip_country/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "ip_country"
-version = "0.1.0"
+version = "0.9.1"
edition = "2021"
license = "GPL-3.0-only"
authors = ["Dan Wiebe ", "MASQ"]
diff --git a/masq/Cargo.toml b/masq/Cargo.toml
index 0bef7d9ca..878b4e172 100644
--- a/masq/Cargo.toml
+++ b/masq/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "masq"
-version = "0.9.0"
+version = "0.9.1"
authors = ["Dan Wiebe ", "MASQ"]
license = "GPL-3.0-only"
description = "Reference implementation of user interface for MASQ Node"
diff --git a/masq/src/commands/configuration_command.rs b/masq/src/commands/configuration_command.rs
index 62d2b4650..2442270a6 100644
--- a/masq/src/commands/configuration_command.rs
+++ b/masq/src/commands/configuration_command.rs
@@ -166,8 +166,8 @@ impl ConfigurationCommand {
let scan_intervals = Self::preprocess_combined_parameters({
let s_i = &configuration.scan_intervals;
&[
- ("Pending payable:", &s_i.pending_payable_sec, "s"),
("Payable:", &s_i.payable_sec, "s"),
+ ("Pending payable:", &s_i.pending_payable_sec, "s"),
("Receivable:", &s_i.receivable_sec, "s"),
]
});
@@ -397,8 +397,8 @@ mod tests {
| Exit byte rate: 129,000,000 wei\n\
| Exit service rate: 160,000,000 wei\n\
|Scan intervals: \n\
-| Pending payable: 150,500 s\n\
| Payable: 155,000 s\n\
+| Pending payable: 150,500 s\n\
| Receivable: 250,666 s\n"
)
.replace('|', "")
@@ -494,8 +494,8 @@ mod tests {
| Exit byte rate: 20 wei\n\
| Exit service rate: 30 wei\n\
|Scan intervals: \n\
-| Pending payable: 1,000 s\n\
| Payable: 1,000 s\n\
+| Pending payable: 1,000 s\n\
| Receivable: 1,000 s\n",
)
.replace('|', "")
diff --git a/masq_lib/Cargo.toml b/masq_lib/Cargo.toml
index ca2ce815b..94ceadb17 100644
--- a/masq_lib/Cargo.toml
+++ b/masq_lib/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "masq_lib"
-version = "0.9.0"
+version = "0.9.1"
authors = ["Dan Wiebe ", "MASQ"]
license = "GPL-3.0-only"
description = "Code common to Node and masq; also, temporarily, to dns_utility"
diff --git a/masq_lib/src/constants.rs b/masq_lib/src/constants.rs
index 20e332f4a..1cc14f357 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::BaseMainnet;
-pub const CURRENT_SCHEMA_VERSION: usize = 12;
+pub const CURRENT_SCHEMA_VERSION: usize = 13;
pub const HIGHEST_RANDOM_CLANDESTINE_PORT: u16 = 9999;
pub const HTTP_PORT: u16 = 80;
@@ -83,7 +83,8 @@ pub const VALUE_EXCEEDS_ALLOWED_LIMIT: u64 = ACCOUNTANT_PREFIX | 3;
pub const MASQ_TOTAL_SUPPLY: u64 = 37_500_000;
pub const DEFAULT_GAS_PRICE: u64 = 1; //TODO ?? Really
-pub const DEFAULT_GAS_PRICE_MARGIN: u64 = 30;
+pub const DEFAULT_GAS_PRICE_RETRY_PERCENTAGE: u64 = 30;
+pub const DEFAULT_GAS_PRICE_RETRY_CONSTANT: u128 = 5_000;
pub const DEFAULT_MAX_BLOCK_COUNT: u64 = 100_000;
//chains
@@ -142,7 +143,8 @@ mod tests {
assert_eq!(CURRENT_LOGFILE_NAME, "MASQNode_rCURRENT.log");
assert_eq!(MASQ_PROMPT, "masq> ");
assert_eq!(DEFAULT_GAS_PRICE, 1);
- assert_eq!(DEFAULT_GAS_PRICE_MARGIN, 30);
+ assert_eq!(DEFAULT_GAS_PRICE_RETRY_PERCENTAGE, 30);
+ assert_eq!(DEFAULT_GAS_PRICE_RETRY_CONSTANT, 5_000);
assert_eq!(WALLET_ADDRESS_LENGTH, 42);
assert_eq!(MASQ_TOTAL_SUPPLY, 37_500_000);
assert_eq!(WEIS_IN_GWEI, 1_000_000_000);
diff --git a/masq_lib/src/logger.rs b/masq_lib/src/logger.rs
index b5fe282c8..5a94127b0 100644
--- a/masq_lib/src/logger.rs
+++ b/masq_lib/src/logger.rs
@@ -116,6 +116,10 @@ impl Logger {
}
}
+ pub fn name(&self) -> &str {
+ &self.name
+ }
+
pub fn trace(&self, log_function: F)
where
F: FnOnce() -> String,
diff --git a/masq_lib/src/shared_schema.rs b/masq_lib/src/shared_schema.rs
index a273205ff..b99a79717 100644
--- a/masq_lib/src/shared_schema.rs
+++ b/masq_lib/src/shared_schema.rs
@@ -7,7 +7,7 @@ use crate::constants::{
POLYGON_MAINNET_FULL_IDENTIFIER,
};
use crate::crash_point::CrashPoint;
-use clap::{App, Arg};
+use clap::{arg_enum, App, Arg};
use lazy_static::lazy_static;
pub const BLOCKCHAIN_SERVICE_HELP: &str =
@@ -81,6 +81,17 @@ pub const NEIGHBORS_HELP: &str = "One or more Node descriptors for running Nodes
if you don't specify a neighbor, your Node will start without being connected to any MASQ \
Network, although other Nodes will be able to connect to yours if they know your Node's descriptor. \
--neighbors is meaningless in --neighborhood-mode zero-hop.";
+pub const NEW_PUBLIC_KEY_HELP: &str = "Whenever you start it, the Node will try to use the same public key \
+ it used last time. That's '--new-public-key off'. If you want it to select a new public key when it \
+ starts, then specify '--new-public-key on', and you'll get a different one this time...which it will \
+ reuse next time unless you specify '--new-public-key on' again.\n\n\
+ You should be careful about restarting your Node with the same public key too quickly. If your new \
+ Node tries to join the Network before the Network has forgotten your old Node, every Node you try \
+ to connect to will ignore you.\n\n\
+ There are some conditions under which the Node cannot use the same public key it used last time: \
+ for example, if there was no last time, or if you don't specify a `--db-password`. Normally, in \
+ these situations, the Node will select a new public key and store it for future use; but if you \
+ explicitly demand the old public key with `--new-public-key off`, the Node will refuse to start.";
// generated valid encoded keys for future needs
// UJNoZW5p/PDVqEjpr3b+8jZ/93yPG8i5dOAgE1bhK+A
@@ -225,6 +236,14 @@ lazy_static! {
DEFAULT_GAS_PRICE);
}
+arg_enum! {
+ #[derive(Debug, PartialEq, Eq, Clone, Copy)]
+ pub enum OnOff {
+ On,
+ Off,
+ }
+}
+
// These Args are needed in more than one clap schema. To avoid code duplication, they're defined here and referred
// to from multiple places.
pub fn chain_arg<'a>() -> Arg<'a, 'a> {
@@ -466,13 +485,23 @@ pub fn shared_app(head: App<'static, 'static>) -> App<'static, 'static> {
.min_values(0)
.help(NEIGHBORS_HELP),
)
+ .arg(
+ Arg::with_name("new-public-key")
+ .long("new-public-key")
+ .value_name("NEW-PUBLIC-KEY")
+ .takes_value(true)
+ .possible_values(&OnOff::variants())
+ .case_insensitive(true)
+ .help(NEW_PUBLIC_KEY_HELP),
+ )
.arg(real_user_arg())
.arg(
Arg::with_name("scans")
.long("scans")
.value_name("SCANS")
.takes_value(true)
- .possible_values(&["on", "off"])
+ .possible_values(&OnOff::variants())
+ .case_insensitive(true)
.help(SCANS_HELP),
)
.arg(common_parameter_with_separate_u64_values(
diff --git a/multinode_integration_tests/Cargo.toml b/multinode_integration_tests/Cargo.toml
index 6a9d22533..d3070e920 100644
--- a/multinode_integration_tests/Cargo.toml
+++ b/multinode_integration_tests/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "multinode_integration_tests"
-version = "0.9.0"
+version = "0.9.1"
authors = ["Dan Wiebe ", "MASQ"]
license = "GPL-3.0-only"
description = ""
diff --git a/multinode_integration_tests/src/masq_node_cluster.rs b/multinode_integration_tests/src/masq_node_cluster.rs
index 86a94af54..e233f6674 100644
--- a/multinode_integration_tests/src/masq_node_cluster.rs
+++ b/multinode_integration_tests/src/masq_node_cluster.rs
@@ -14,6 +14,7 @@ use std::collections::HashMap;
use std::collections::HashSet;
use std::env;
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, ToSocketAddrs};
+use std::time::Duration;
pub struct MASQNodeCluster {
startup_configs: HashMap<(String, usize), NodeStartupConfig>,
@@ -306,6 +307,42 @@ impl MASQNodeCluster {
}
fn create_network() -> Result<(), String> {
+ let mut errors = vec![];
+ let mut retries_remaining = 2;
+ let interval = Duration::from_millis(250);
+ while retries_remaining >= 0 {
+ match MASQNodeCluster::create_network_attempt() {
+ Ok(s) => return Ok(s),
+ Err(err_msg) => {
+ retries_remaining -= 1;
+ errors.push(err_msg);
+ std::thread::sleep(interval);
+ }
+ }
+ }
+ Err(format!(
+ "Errors trying to create Docker network:\n {}\n",
+ errors.join("\n ")
+ ))
+ }
+
+ fn create_network_attempt() -> Result<(), String> {
+ let mut command = Command::new(
+ "docker",
+ Command::strings(vec!["network", "rm", "integration_net"]),
+ );
+ match command.stdout_or_stderr() {
+ Ok(_) => println!("Removed existing integration_net network"),
+ Err(msg) if msg.contains("network integration_net not found") => {
+ println!("No existing integration_net network to remove: cool!")
+ }
+ Err(msg) => {
+ return Err(format!(
+ "Error removing existing integration_net network: {}",
+ msg
+ ))
+ }
+ }
let mut command = Command::new(
"docker",
Command::strings(vec![
diff --git a/multinode_integration_tests/tests/bookkeeping_test.rs b/multinode_integration_tests/tests/bookkeeping_test.rs
index de716a3e3..c2845fc72 100644
--- a/multinode_integration_tests/tests/bookkeeping_test.rs
+++ b/multinode_integration_tests/tests/bookkeeping_test.rs
@@ -1,4 +1,5 @@
// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved.
+use itertools::Itertools;
use multinode_integration_tests_lib::masq_node::{MASQNode, NodeReference};
use multinode_integration_tests_lib::masq_node_cluster::MASQNodeCluster;
use multinode_integration_tests_lib::masq_real_node::{
@@ -30,54 +31,72 @@ fn provided_and_consumed_services_are_recorded_in_databases() {
let mut client = originating_node.make_client(8080, STANDARD_CLIENT_TIMEOUT_MILLIS);
client.set_timeout(Duration::from_secs(10));
- let request = "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n".as_bytes();
+ let request = "GET /index.html HTTP/1.1\r\nHost: www.testingmcafeesites.com\r\n\r\n".as_bytes();
client.send_chunk(request);
let response = String::from_utf8(client.wait_for_chunk()).unwrap();
assert!(
- response.contains("Example Domain "),
- "Not from www.example.com:\n{}",
+ response.contains("URL for testing. "),
+ "Not from www.testingmcafeesites.com:\n{}",
response
);
+ // Waiting until everybody has finished generating payables and receivables
+ thread::sleep(Duration::from_secs(10));
+
// get all payables from originating node
let payables = retrieve_payables(&originating_node);
- // Waiting until the serving nodes have finished accruing their receivables
- thread::sleep(Duration::from_secs(10));
-
// get all receivables from all other nodes
- let receivable_balances = non_originating_nodes
+ let receivable_nodes = non_originating_nodes
.iter()
.flat_map(|node| {
receivables(node)
.into_iter()
.map(move |receivable_account| {
- (node.earning_wallet(), receivable_account.balance_wei)
+ (
+ node.earning_wallet(),
+ (node.name().to_string(), receivable_account.balance_wei),
+ )
})
})
- .collect::>();
+ .collect::>();
// check that each payable has a receivable
assert_eq!(
payables.len(),
- receivable_balances.len(),
+ receivable_nodes.len(),
"Lengths of payables and receivables should match.\nPayables: {:?}\nReceivables: {:?}",
payables,
- receivable_balances
+ receivable_nodes
);
assert!(
- receivable_balances.len() >= 3, // minimum service list: route, route, exit.
+ receivable_nodes.len() >= 3, // minimum service list: route, route, exit.
"not enough receivables found {:?}",
- receivable_balances
+ receivable_nodes
);
- payables.iter().for_each(|payable| {
- assert_eq!(
- payable.balance_wei,
- *receivable_balances.get(&payable.wallet).unwrap() as u128,
- );
- });
+ let messages = payables
+ .iter()
+ .flat_map(|payable| {
+ let payable_balance = payable.balance_wei;
+ let (non_originating_node_name, receivable_balance) =
+ receivable_nodes.get(&payable.wallet).unwrap().clone();
+ if payable_balance != receivable_balance as u128 {
+ Some(format!(
+ "Payable for {} ({}) does not match receivable for {} ({})",
+ originating_node.name(),
+ payable_balance,
+ non_originating_node_name,
+ receivable_balance
+ ))
+ } else {
+ None
+ }
+ })
+ .collect_vec();
+
+ assert!(messages.is_empty(), "{:#?}", messages);
}
fn retrieve_payables(node: &MASQRealNode) -> Vec {
diff --git a/multinode_integration_tests/tests/connection_progress_test.rs b/multinode_integration_tests/tests/connection_progress_test.rs
index 336cef2c2..fa307fdf5 100644
--- a/multinode_integration_tests/tests/connection_progress_test.rs
+++ b/multinode_integration_tests/tests/connection_progress_test.rs
@@ -57,7 +57,7 @@ fn connection_progress_is_properly_broadcast() {
.collect::>();
let message_body =
- ui_client.wait_for_specific_broadcast(vec!["connectionChange"], Duration::from_secs(5));
+ ui_client.wait_for_specific_broadcast(vec!["connectionChange"], Duration::from_secs(10));
let (ccb, _) = UiConnectionChangeBroadcast::fmb(message_body).unwrap();
if ccb.stage == UiConnectionStage::ConnectedToNeighbor {
let message_body =
diff --git a/multinode_integration_tests/tests/connection_termination_test.rs b/multinode_integration_tests/tests/connection_termination_test.rs
index 2657661b8..287f2f120 100644
--- a/multinode_integration_tests/tests/connection_termination_test.rs
+++ b/multinode_integration_tests/tests/connection_termination_test.rs
@@ -1,6 +1,7 @@
// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved.
use masq_lib::blockchains::chains::Chain;
+use masq_lib::constants::HTTP_PORT;
use masq_lib::test_utils::utils::TEST_DEFAULT_MULTINODE_CHAIN;
use masq_lib::utils::find_free_port;
use multinode_integration_tests_lib::masq_mock_node::MASQMockNode;
@@ -86,13 +87,12 @@ fn reported_server_drop() {
let (_, _, lcp) = mock_node
.wait_for_package(&masquerader, Duration::from_secs(2))
.unwrap();
- let (stream_key, return_route_id) =
- context_from_request_lcp(lcp, real_node.main_cryptde_null().unwrap(), &exit_cryptde);
+ let stream_key = stream_key_from_request_lcp(lcp, &exit_cryptde);
mock_node
.transmit_package(
mock_node.port_list()[0],
- create_server_drop_report(&mock_node, &real_node, stream_key, return_route_id),
+ create_server_drop_report(&mock_node, &real_node, stream_key),
&masquerader,
real_node.main_public_key(),
real_node.socket_addr(PortSelector::First),
@@ -115,7 +115,7 @@ fn actual_server_drop() {
let server_port = find_free_port();
let mut server = real_node.make_server(server_port);
let masquerader = JsonMasquerader::new();
- let (stream_key, return_route_id) = arbitrary_context();
+ let stream_key = arbitrary_stream_key();
let index: u64 = 0;
request_server_payload(
index,
@@ -125,7 +125,6 @@ fn actual_server_drop() {
&mut server,
&masquerader,
stream_key,
- return_route_id,
);
let index: u64 = 1;
request_server_payload(
@@ -136,7 +135,6 @@ fn actual_server_drop() {
&mut server,
&masquerader,
stream_key,
- return_route_id,
);
server.shutdown();
@@ -174,7 +172,6 @@ fn request_server_payload(
server: &mut MASQNodeServer,
masquerader: &JsonMasquerader,
stream_key: StreamKey,
- return_route_id: u32,
) {
mock_node
.transmit_package(
@@ -184,7 +181,6 @@ fn request_server_payload(
&mock_node,
&real_node,
stream_key,
- return_route_id,
&server,
cluster.chain,
),
@@ -212,7 +208,7 @@ fn reported_client_drop() {
let server_port = find_free_port();
let mut server = real_node.make_server(server_port);
let masquerader = JsonMasquerader::new();
- let (stream_key, return_route_id) = arbitrary_context();
+ let stream_key = arbitrary_stream_key();
let index: u64 = 0;
mock_node
.transmit_package(
@@ -222,7 +218,6 @@ fn reported_client_drop() {
&mock_node,
&real_node,
stream_key,
- return_route_id,
&server,
cluster.chain,
),
@@ -240,7 +235,7 @@ fn reported_client_drop() {
mock_node
.transmit_package(
mock_node.port_list()[0],
- create_client_drop_report(&mock_node, &real_node, stream_key, return_route_id),
+ create_client_drop_report(&mock_node, &real_node, stream_key),
&masquerader,
real_node.main_public_key(),
real_node.socket_addr(PortSelector::First),
@@ -322,11 +317,7 @@ fn full_neighbor(one: &mut NodeRecord, another: &mut NodeRecord) {
.unwrap();
}
-fn context_from_request_lcp(
- lcp: LiveCoresPackage,
- originating_cryptde: &dyn CryptDE,
- exit_cryptde: &dyn CryptDE,
-) -> (StreamKey, u32) {
+fn stream_key_from_request_lcp(lcp: LiveCoresPackage, exit_cryptde: &dyn CryptDE) -> StreamKey {
let payload = match decodex::(exit_cryptde, &lcp.payload).unwrap() {
MessageType::ClientRequest(vd) => vd
.extract(&node_lib::sub_lib::migrations::client_request_payload::MIGRATIONS)
@@ -334,15 +325,11 @@ fn context_from_request_lcp(
mt => panic!("Unexpected: {:?}", mt),
};
let stream_key = payload.stream_key;
- let return_route_id = decodex::(originating_cryptde, &lcp.route.hops[6]).unwrap();
- (stream_key, return_route_id)
+ stream_key
}
-fn arbitrary_context() -> (StreamKey, u32) {
- (
- StreamKey::make_meaningful_stream_key("arbitrary_context"),
- 12345678,
- )
+fn arbitrary_stream_key() -> StreamKey {
+ StreamKey::make_meaningful_stream_key("arbitrary_context")
}
fn create_request_icp(
@@ -350,12 +337,12 @@ fn create_request_icp(
originating_node: &MASQMockNode,
exit_node: &MASQRealNode,
stream_key: StreamKey,
- return_route_id: u32,
server: &MASQNodeServer,
chain: Chain,
) -> IncipientCoresPackage {
+ let originating_main_cryptde = originating_node.main_cryptde_null().unwrap();
IncipientCoresPackage::new(
- originating_node.main_cryptde_null().unwrap(),
+ originating_main_cryptde,
Route::round_trip(
RouteSegment::new(
vec![
@@ -371,9 +358,8 @@ fn create_request_icp(
],
Component::ProxyServer,
),
- originating_node.main_cryptde_null().unwrap(),
+ originating_main_cryptde,
originating_node.consuming_wallet(),
- return_route_id,
Some(chain.rec().contract),
)
.unwrap(),
@@ -382,7 +368,7 @@ fn create_request_icp(
&ClientRequestPayload_0v1 {
stream_key,
sequenced_packet: SequencedPacket::new(Vec::from(HTTP_REQUEST), index, false),
- target_hostname: Some(format!("{}", server.local_addr().ip())),
+ target_hostname: format!("{}", server.local_addr().ip()),
target_port: server.local_addr().port(),
protocol: ProxyProtocol::HTTP,
originator_public_key: originating_node.main_public_key().clone(),
@@ -400,8 +386,9 @@ fn create_meaningless_icp(
let socket_addr = SocketAddr::from_str("3.2.1.0:7654").unwrap();
let stream_key =
StreamKey::make_meaningful_stream_key("Chancellor on brink of second bailout for banks");
+ let main_cryptde = originating_node.main_cryptde_null().unwrap();
IncipientCoresPackage::new(
- originating_node.main_cryptde_null().unwrap(),
+ main_cryptde,
Route::round_trip(
RouteSegment::new(
vec![
@@ -417,9 +404,8 @@ fn create_meaningless_icp(
],
Component::ProxyServer,
),
- originating_node.main_cryptde_null().unwrap(),
+ main_cryptde,
originating_node.consuming_wallet(),
- 1357,
Some(TEST_DEFAULT_MULTINODE_CHAIN.rec().contract),
)
.unwrap(),
@@ -428,7 +414,7 @@ fn create_meaningless_icp(
&ClientRequestPayload_0v1 {
stream_key,
sequenced_packet: SequencedPacket::new(Vec::from(HTTP_REQUEST), 0, false),
- target_hostname: Some(format!("nowhere.com")),
+ target_hostname: "nowhere.com".to_string(),
target_port: socket_addr.port(),
protocol: ProxyProtocol::HTTP,
originator_public_key: originating_node.main_public_key().clone(),
@@ -443,8 +429,9 @@ fn create_server_drop_report(
exit_node: &MASQMockNode,
originating_node: &MASQRealNode,
stream_key: StreamKey,
- return_route_id: u32,
) -> IncipientCoresPackage {
+ let exit_main_cryptde = exit_node.main_cryptde_null().unwrap();
+ let originating_main_cryptde = originating_node.main_cryptde_null().unwrap();
let mut route = Route::round_trip(
RouteSegment::new(
vec![
@@ -460,15 +447,12 @@ fn create_server_drop_report(
],
Component::ProxyServer,
),
- originating_node.main_cryptde_null().unwrap(),
+ originating_main_cryptde,
originating_node.consuming_wallet(),
- return_route_id,
Some(TEST_DEFAULT_MULTINODE_CHAIN.rec().contract),
)
.unwrap();
- route
- .shift(originating_node.main_cryptde_null().unwrap())
- .unwrap();
+ route.shift(originating_main_cryptde).unwrap();
let payload = MessageType::ClientResponse(VersionedData::new(
&node_lib::sub_lib::migrations::client_response_payload::MIGRATIONS,
&ClientResponsePayload_0v1 {
@@ -478,7 +462,7 @@ fn create_server_drop_report(
));
IncipientCoresPackage::new(
- exit_node.main_cryptde_null().unwrap(),
+ exit_main_cryptde,
route,
payload,
originating_node.alias_public_key(),
@@ -490,8 +474,8 @@ fn create_client_drop_report(
originating_node: &MASQMockNode,
exit_node: &MASQRealNode,
stream_key: StreamKey,
- return_route_id: u32,
) -> IncipientCoresPackage {
+ let originating_main_cryptde = originating_node.main_cryptde_null().unwrap();
let route = Route::round_trip(
RouteSegment::new(
vec![
@@ -507,9 +491,8 @@ fn create_client_drop_report(
],
Component::ProxyServer,
),
- originating_node.main_cryptde_null().unwrap(),
+ originating_main_cryptde,
originating_node.consuming_wallet(),
- return_route_id,
Some(TEST_DEFAULT_MULTINODE_CHAIN.rec().contract),
)
.unwrap();
@@ -518,15 +501,15 @@ fn create_client_drop_report(
&ClientRequestPayload_0v1 {
stream_key,
sequenced_packet: SequencedPacket::new(vec![], 1, true),
- target_hostname: Some(String::from("doesnt.matter.com")),
- target_port: 80,
+ target_hostname: String::from("doesnt.matter.com"),
+ target_port: HTTP_PORT,
protocol: ProxyProtocol::HTTP,
originator_public_key: originating_node.main_public_key().clone(),
},
));
IncipientCoresPackage::new(
- originating_node.main_cryptde_null().unwrap(),
+ originating_main_cryptde,
route,
payload,
exit_node.main_public_key(),
diff --git a/multinode_integration_tests/tests/data_routing_test.rs b/multinode_integration_tests/tests/data_routing_test.rs
index 98dd460a1..5d3a0076e 100644
--- a/multinode_integration_tests/tests/data_routing_test.rs
+++ b/multinode_integration_tests/tests/data_routing_test.rs
@@ -58,11 +58,11 @@ fn http_end_to_end_routing_test() {
thread::sleep(Duration::from_millis(500));
let mut client = last_node.make_client(8080, 5000);
- client.send_chunk(b"GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n");
+ client.send_chunk(b"GET /index.html HTTP/1.1\r\nHost: www.testingmcafeesites.com\r\n\r\n");
let response = client.wait_for_chunk();
assert_eq!(
- index_of(&response, &b"Example Domain "[..]).is_some(),
+ index_of(&response, &b"URL for testing. "[..]).is_some(),
true,
"Actual response:\n{}",
String::from_utf8(response).unwrap()
@@ -89,7 +89,7 @@ fn http_end_to_end_routing_test_with_consume_and_originate_only_nodes() {
.chain(cluster.chain)
.build(),
);
- let _potential_exit_nodes = vec![0, 1, 2, 3, 4]
+ let _potential_exit_nodes = vec![3, 4, 5, 6, 7]
.into_iter()
.map(|_| {
cluster.start_real_node(
@@ -101,17 +101,19 @@ fn http_end_to_end_routing_test_with_consume_and_originate_only_nodes() {
})
.collect_vec();
- thread::sleep(Duration::from_millis(1000));
+ thread::sleep(Duration::from_secs(5));
let mut client = originating_node.make_client(8080, STANDARD_CLIENT_TIMEOUT_MILLIS);
- client.send_chunk(b"GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n");
- let response = client.wait_for_chunk();
+ client.set_timeout(Duration::from_secs(10));
+ let request = "GET /index.html HTTP/1.1\r\nHost: www.testingmcafeesites.com\r\n\r\n".as_bytes();
- assert_eq!(
- index_of(&response, &b"Example Domain "[..]).is_some(),
- true,
- "Actual response:\n{}",
- String::from_utf8(response).unwrap()
+ client.send_chunk(request);
+ let response = String::from_utf8(client.wait_for_chunk()).unwrap();
+
+ assert!(
+ response.contains("URL for testing. "),
+ "Not from www.testingmcafeesites.com:\n{}",
+ response
);
}
@@ -235,11 +237,11 @@ fn http_routing_failure_produces_internal_error_response() {
let mut client = originating_node.make_client(8080, STANDARD_CLIENT_TIMEOUT_MILLIS);
- client.send_chunk(b"GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n");
+ client.send_chunk(b"GET /index.html HTTP/1.1\r\nHost: www.testingmcafeesites.com\r\n\r\n");
let response = client.wait_for_chunk();
let expected_response =
- ServerImpersonatorHttp {}.route_query_failure_response("www.example.com");
+ ServerImpersonatorHttp {}.route_query_failure_response("www.testingmcafeesites.com");
assert_eq!(
&expected_response,
@@ -315,27 +317,56 @@ fn multiple_stream_zero_hop_test() {
let mut one_client = zero_hop_node.make_client(8080, STANDARD_CLIENT_TIMEOUT_MILLIS);
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 / 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");
+ one_client.send_chunk(b"GET /testcat_ed.html 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");
+ another_client.send_chunk(b"GET /testcat_cm.html 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();
- assert_eq!(
- index_of(&one_response, &b"Example Domain "[..]).is_some(),
- true,
- "Actual response:\n{}",
- String::from_utf8(one_response).unwrap()
- );
- assert_eq!(
- index_of(
- &another_response,
- &b"This is an index url which gives an overview of the different test urls available."
- [..],
- )
- .is_some(),
- true,
- "Actual response:\n{}",
- String::from_utf8(another_response).unwrap()
- );
+ {
+ assert_eq!(
+ index_of(
+ &one_response,
+ &b"Education/Reference website with a low risk reputation score"[..]
+ )
+ .is_some(),
+ true,
+ "Actual response:\n{}",
+ String::from_utf8(one_response).unwrap()
+ );
+ assert_eq!(
+ index_of(
+ &one_response,
+ &b"This page simply displays this text without any specific content on it, it is just for testing purpose."
+ [..],
+ )
+ .is_some(),
+ true,
+ "Actual response:\n{}",
+ String::from_utf8(one_response).unwrap()
+ );
+ }
+ {
+ assert_eq!(
+ index_of(
+ &another_response,
+ &b"Public Information website with a low risk reputation score"[..]
+ )
+ .is_some(),
+ true,
+ "Actual response:\n{}",
+ String::from_utf8(another_response).unwrap()
+ );
+ assert_eq!(
+ index_of(
+ &another_response,
+ &b"This page simply displays this text without any specific content on it, it is just for testing purpose."
+ [..],
+ )
+ .is_some(),
+ true,
+ "Actual response:\n{}",
+ String::from_utf8(another_response).unwrap()
+ );
+ }
}
diff --git a/multinode_integration_tests/tests/min_hops_tests.rs b/multinode_integration_tests/tests/min_hops_tests.rs
index ed0ef34f4..18efabd4f 100644
--- a/multinode_integration_tests/tests/min_hops_tests.rs
+++ b/multinode_integration_tests/tests/min_hops_tests.rs
@@ -30,7 +30,7 @@ fn assert_http_end_to_end_routing(min_hops: Hops) {
let first_node = cluster.start_real_node(config);
// For 1-hop route, 3 nodes are necessary if we use last node as the originating node
- let nodes_count = (min_hops as usize) + 2;
+ let nodes_count = (min_hops as usize) * 2 + 1;
let nodes = (0..nodes_count)
.map(|i| {
cluster.start_real_node(
@@ -43,15 +43,15 @@ fn assert_http_end_to_end_routing(min_hops: Hops) {
})
.collect::>();
- thread::sleep(Duration::from_millis(500 * (nodes.len() as u64)));
+ thread::sleep(Duration::from_millis(4000 * (nodes.len() as u64)));
let last_node = nodes.last().unwrap();
let mut client = last_node.make_client(8080, 5000);
- client.send_chunk(b"GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n");
+ client.send_chunk(b"GET /index.html HTTP/1.1\r\nHost: www.testingmcafeesites.com\r\n\r\n");
let response = client.wait_for_chunk();
assert_eq!(
- index_of(&response, &b"Example Domain "[..]).is_some(),
+ index_of(&response, &b"URL for testing. "[..]).is_some(),
true,
"Actual response:\n{}",
String::from_utf8(response).unwrap()
@@ -84,14 +84,14 @@ fn min_hops_can_be_changed_during_runtime() {
thread::sleep(Duration::from_millis(1000));
let mut client = first_node.make_client(8080, 5000);
- client.send_chunk(b"GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n");
+ client.send_chunk(b"GET /index.html HTTP/1.1\r\nHost: www.testingmcafeesites.com\r\n\r\n");
let response = client.wait_for_chunk();
// Client shutdown is necessary to re-initialize stream keys for old requests
client.shutdown();
assert_eq!(
- index_of(&response, &b"Example Domain "[..]).is_some(),
+ index_of(&response, &b"URL for testing. "[..]).is_some(),
true,
"Actual response:\n{}",
String::from_utf8(response).unwrap()
@@ -108,12 +108,12 @@ fn min_hops_can_be_changed_during_runtime() {
assert!(response.payload.is_ok());
let mut client = first_node.make_client(8080, 5000);
- client.send_chunk(b"GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n");
+ client.send_chunk(b"GET /index.html HTTP/1.1\r\nHost: www.testingmcafeesites.com\r\n\r\n");
let response = client.wait_for_chunk();
assert_eq!(
index_of(
&response,
- &b"Subtitle: Can't find a route to www.example.com "[..]
+ &b"Subtitle: Can't find a route to www.testingmcafeesites.com "[..]
)
.is_some(),
true,
diff --git a/multinode_integration_tests/tests/neighbor_selection_test.rs b/multinode_integration_tests/tests/neighbor_selection_test.rs
index 225713ba5..0ff393467 100644
--- a/multinode_integration_tests/tests/neighbor_selection_test.rs
+++ b/multinode_integration_tests/tests/neighbor_selection_test.rs
@@ -21,7 +21,7 @@ use std::convert::TryInto;
use std::time::Duration;
#[test]
-fn debut_target_does_not_introduce_known_neighbors() {
+fn if_debuter_already_knows_all_of_recipients_neighbors_recipient_redebuts() {
let mut cluster = MASQNodeCluster::start().unwrap();
let one_common_neighbor = make_node_record(1234, true);
let another_common_neighbor = make_node_record(2435, true);
@@ -74,11 +74,7 @@ fn debut_target_does_not_introduce_known_neighbors() {
let standard_gossip = Standard::from(&agrs);
assert_eq!(
standard_gossip.key_set(),
- vec_to_set(vec![
- subject_real_node.main_public_key().clone(),
- one_common_neighbor.public_key().clone(),
- another_common_neighbor.public_key().clone(),
- ])
+ vec_to_set(vec![subject_real_node.main_public_key().clone(),])
);
}
diff --git a/multinode_integration_tests/tests/self_test.rs b/multinode_integration_tests/tests/self_test.rs
index 973affaf0..c19db4c33 100644
--- a/multinode_integration_tests/tests/self_test.rs
+++ b/multinode_integration_tests/tests/self_test.rs
@@ -16,6 +16,7 @@ use node_lib::sub_lib::dispatcher::Component;
use node_lib::sub_lib::hopper::IncipientCoresPackage;
use node_lib::sub_lib::route::Route;
use node_lib::sub_lib::route::RouteSegment;
+use node_lib::sub_lib::stream_key::StreamKey;
use node_lib::test_utils::{make_meaningless_message_type, make_paying_wallet};
use std::collections::HashSet;
use std::io::ErrorKind;
@@ -68,6 +69,7 @@ fn server_relays_cores_package() {
let masquerader = JsonMasquerader::new();
let server = MASQCoresServer::new(cluster.chain);
let cryptde = server.main_cryptde();
+ let stream_key = StreamKey::make_meaningless_stream_key();
let mut client = MASQCoresClient::new(server.local_addr(), cryptde);
let mut route = Route::one_way(
RouteSegment::new(
@@ -82,7 +84,7 @@ fn server_relays_cores_package() {
let incipient = IncipientCoresPackage::new(
cryptde,
route.clone(),
- make_meaningless_message_type(),
+ make_meaningless_message_type(stream_key),
&cryptde.public_key(),
)
.unwrap();
@@ -99,7 +101,7 @@ fn server_relays_cores_package() {
route.shift(cryptde).unwrap();
assert_eq!(expired.remaining_route, route);
- assert_eq!(expired.payload, make_meaningless_message_type());
+ assert_eq!(expired.payload, make_meaningless_message_type(stream_key));
}
#[test]
@@ -111,6 +113,7 @@ fn one_mock_node_talks_to_another() {
let mock_node_1 = cluster.get_mock_node_by_name("mock_node_1").unwrap();
let mock_node_2 = cluster.get_mock_node_by_name("mock_node_2").unwrap();
let cryptde = CryptDENull::new(TEST_DEFAULT_CHAIN);
+ let stream_key = StreamKey::make_meaningless_stream_key();
let route = Route::one_way(
RouteSegment::new(
vec![
@@ -127,7 +130,7 @@ fn one_mock_node_talks_to_another() {
let incipient_cores_package = IncipientCoresPackage::new(
&cryptde,
route,
- make_meaningless_message_type(),
+ make_meaningless_message_type(stream_key),
&mock_node_2.main_public_key(),
)
.unwrap();
@@ -156,7 +159,7 @@ fn one_mock_node_talks_to_another() {
assert_eq!(package_to, mock_node_2.socket_addr(PortSelector::First));
assert_eq!(
expired_cores_package.payload,
- make_meaningless_message_type()
+ make_meaningless_message_type(stream_key)
);
}
diff --git a/multinode_integration_tests/tests/verify_bill_payment.rs b/multinode_integration_tests/tests/verify_bill_payment.rs
index d421f82b2..e5fddc67f 100644
--- a/multinode_integration_tests/tests/verify_bill_payment.rs
+++ b/multinode_integration_tests/tests/verify_bill_payment.rs
@@ -465,7 +465,7 @@ fn verify_pending_payables() {
}
MASQNodeUtils::assert_node_wrote_log_containing(
real_consuming_node.name(),
- "Found 3 pending payables and 0 unfinalized failures to process",
+ "Found 3 pending payables and 0 suspected failures to process",
Duration::from_secs(5),
);
MASQNodeUtils::assert_node_wrote_log_containing(
diff --git a/node/.gitignore b/node/.gitignore
index 4bdd95441..341f40dbf 100644
--- a/node/.gitignore
+++ b/node/.gitignore
@@ -73,6 +73,9 @@ cmake-build-debug/
# IntelliJ
out/
+# Local IDE database configuration
+.idea/db-forest-config.xml
+
# mpeltonen/sbt-idea plugin
.idea_modules/
diff --git a/node/Cargo.lock b/node/Cargo.lock
index c825fda4b..443b6a132 100644
--- a/node/Cargo.lock
+++ b/node/Cargo.lock
@@ -182,7 +182,7 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "automap"
-version = "0.9.0"
+version = "0.9.1"
dependencies = [
"crossbeam-channel 0.5.1",
"flexi_logger 0.17.1",
@@ -1578,7 +1578,7 @@ dependencies = [
[[package]]
name = "ip_country"
-version = "0.1.0"
+version = "0.9.1"
dependencies = [
"csv",
"ipnetwork",
@@ -1868,7 +1868,7 @@ dependencies = [
[[package]]
name = "masq"
-version = "0.9.0"
+version = "0.9.1"
dependencies = [
"atty",
"clap",
@@ -1889,7 +1889,7 @@ dependencies = [
[[package]]
name = "masq_lib"
-version = "0.9.0"
+version = "0.9.1"
dependencies = [
"actix",
"clap",
@@ -2082,7 +2082,7 @@ dependencies = [
[[package]]
name = "multinode_integration_tests"
-version = "0.9.0"
+version = "0.9.1"
dependencies = [
"base64 0.13.0",
"crossbeam-channel 0.5.1",
@@ -2176,7 +2176,7 @@ dependencies = [
[[package]]
name = "node"
-version = "0.9.0"
+version = "0.9.1"
dependencies = [
"actix",
"automap",
@@ -3750,7 +3750,7 @@ dependencies = [
[[package]]
name = "test_utilities"
-version = "0.1.0"
+version = "0.9.1"
[[package]]
name = "textwrap"
diff --git a/node/Cargo.toml b/node/Cargo.toml
index 80d75b5ef..cbcb8ac2d 100644
--- a/node/Cargo.toml
+++ b/node/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "node"
-version = "0.9.0"
+version = "0.9.1"
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."
@@ -15,7 +15,7 @@ automap = { path = "../automap"}
backtrace = "0.3.57"
base64 = "0.13.0"
bytes = "0.4.12"
-time = {version = "0.3.11", features = [ "macros", "parsing" ]}
+time = { version = "0.3.11", features = ["macros", "local-offset", "parsing"] }
clap = "2.33.3"
crossbeam-channel = "0.5.1"
dirs = "4.0.0"
diff --git a/node/src/accountant/db_access_objects/failed_payable_dao.rs b/node/src/accountant/db_access_objects/failed_payable_dao.rs
index 2b83fdfab..be875807e 100644
--- a/node/src/accountant/db_access_objects/failed_payable_dao.rs
+++ b/node/src/accountant/db_access_objects/failed_payable_dao.rs
@@ -163,7 +163,7 @@ impl Display for FailureRetrieveCondition {
)
}
FailureRetrieveCondition::EveryRecheckRequiredRecord => {
- write!(f, "WHERE status LIKE 'RecheckRequired%'")
+ write!(f, "WHERE status LIKE '%RecheckRequired%'")
}
}
}
@@ -657,9 +657,14 @@ mod tests {
.to_string(),
"WHERE status = '{\"RecheckRequired\":\"Waiting\"}'"
);
+ assert_eq!(
+ FailureRetrieveCondition::ByReceiverAddresses(BTreeSet::from([make_address(1), make_address(2)]))
+ .to_string(),
+ "WHERE receiver_address IN ('0x0000000000000000000003000000000003000000', '0x0000000000000000000006000000000006000000')"
+ );
assert_eq!(
FailureRetrieveCondition::EveryRecheckRequiredRecord.to_string(),
- "WHERE status LIKE 'RecheckRequired%'"
+ "WHERE status LIKE '%RecheckRequired%'"
);
}
@@ -739,19 +744,6 @@ mod tests {
);
}
- #[test]
- fn retrieve_condition_display_works() {
- assert_eq!(
- FailureRetrieveCondition::ByStatus(RetryRequired).to_string(),
- "WHERE status = '\"RetryRequired\"'"
- );
- assert_eq!(
- FailureRetrieveCondition::ByReceiverAddresses(BTreeSet::from([make_address(1), make_address(2)]))
- .to_string(),
- "WHERE receiver_address IN ('0x0000000000000000000003000000000003000000', '0x0000000000000000000006000000000006000000')"
- )
- }
-
#[test]
fn can_retrieve_all_txs_ordered_by_timestamp_and_nonce() {
let home_dir = ensure_node_home_directory_exists(
@@ -904,6 +896,63 @@ mod tests {
assert!(!result.contains(&tx4));
}
+ #[test]
+ fn can_retrieve_every_recheck_required() {
+ let home_dir = ensure_node_home_directory_exists(
+ "failed_payable_dao",
+ "can_retrieve_every_recheck_required",
+ );
+ let wrapped_conn = DbInitializerReal::default()
+ .initialize(&home_dir, DbInitializationConfig::test_default())
+ .unwrap();
+ let subject = FailedPayableDaoReal::new(wrapped_conn);
+ let address1 = make_address(1);
+ let address2 = make_address(2);
+ let address3 = make_address(3);
+ let address4 = make_address(4);
+ let tx1 = FailedTxBuilder::default()
+ .hash(make_tx_hash(1))
+ .receiver_address(address1)
+ .nonce(1)
+ .status(RetryRequired)
+ .build();
+ let tx2 = FailedTxBuilder::default()
+ .hash(make_tx_hash(2))
+ .receiver_address(address2)
+ .nonce(2)
+ .status(RecheckRequired(ValidationStatus::Waiting))
+ .build();
+ let tx3 = FailedTxBuilder::default()
+ .hash(make_tx_hash(3))
+ .receiver_address(address3)
+ .nonce(3)
+ .status(Concluded)
+ .build();
+ let tx4 = FailedTxBuilder::default()
+ .hash(make_tx_hash(4))
+ .receiver_address(address4)
+ .nonce(4)
+ .status(RecheckRequired(ValidationStatus::Reattempting(
+ PreviousAttempts::new(
+ BlockchainErrorKind::AppRpc(AppRpcErrorKind::Remote(
+ RemoteErrorKind::Unreachable,
+ )),
+ &SimpleClockReal::default(),
+ ),
+ )))
+ .build();
+ subject
+ .insert_new_records(&BTreeSet::from([tx1, tx2.clone(), tx3, tx4.clone()]))
+ .unwrap();
+
+ let result =
+ subject.retrieve_txs(Some(FailureRetrieveCondition::EveryRecheckRequiredRecord));
+
+ assert_eq!(result.len(), 2);
+ assert!(result.contains(&tx2));
+ assert!(result.contains(&tx4));
+ }
+
#[test]
fn update_statuses_works() {
let home_dir =
diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs
index 153158dd4..a82d39887 100644
--- a/node/src/accountant/mod.rs
+++ b/node/src/accountant/mod.rs
@@ -30,7 +30,7 @@ use crate::accountant::scanners::pending_payable_scanner::utils::{
PendingPayableScanResult, TxHashByTable,
};
use crate::accountant::scanners::scan_schedulers::{
- PayableSequenceScanner, ScanReschedulingAfterEarlyStop, ScanSchedulers,
+ ScanReschedulingAfterEarlyStop, ScanSchedulers, UnableToStartScanner,
};
use crate::accountant::scanners::{Scanners, StartScanError};
use crate::blockchain::blockchain_bridge::{
@@ -312,11 +312,25 @@ impl Handler for Accountant {
impl Handler for Accountant {
type Result = ();
- fn handle(&mut self, msg: ScanForReceivables, ctx: &mut Self::Context) -> Self::Result {
+ fn handle(&mut self, msg: ScanForReceivables, _ctx: &mut Self::Context) -> Self::Result {
// By now we know it is an automatic scan. The ReceivableScanner is independent of other
// scanners and rescheduled regularly, just here.
- self.handle_request_of_scan_for_receivable(msg.response_skeleton_opt);
- self.scan_schedulers.receivable.schedule(ctx, &self.logger);
+ let scheduling_hint = self.handle_request_of_scan_for_receivable(msg.response_skeleton_opt);
+
+ match scheduling_hint {
+ ScanReschedulingAfterEarlyStop::Schedule(other_scan_type) => unreachable!(
+ "Early stopped receivable scan was suggested to be followed up by the scan \
+ for {:?}, which is not supported though",
+ other_scan_type
+ ),
+ ScanReschedulingAfterEarlyStop::DoNotSchedule => {
+ trace!(
+ self.logger,
+ "No early rescheduling, as the receivable scan did find results, or this \
+ is the NullScanner"
+ )
+ }
+ }
}
}
@@ -348,8 +362,8 @@ impl Handler for Accountant {
if let Some(node_to_ui_msg) = ui_msg_opt {
info!(
self.logger,
- "Re-running the pending payable scan is recommended, as some \
- parts did not finish last time."
+ "Re-running the pending payable scan is recommended, as some parts \
+ did not finish last time."
);
self.ui_message_sub_opt
.as_ref()
@@ -401,13 +415,19 @@ impl Handler for Accountant {
impl Handler for Accountant {
type Result = ();
- fn handle(&mut self, msg: ReceivedPayments, _ctx: &mut Self::Context) -> Self::Result {
- if let Some(node_to_ui_msg) = self.scanners.finish_receivable_scan(msg, &self.logger) {
- self.ui_message_sub_opt
- .as_ref()
- .expect("UIGateway is not bound")
- .try_send(node_to_ui_msg)
- .expect("UIGateway is dead");
+ fn handle(&mut self, msg: ReceivedPayments, ctx: &mut Self::Context) -> Self::Result {
+ match self.scanners.finish_receivable_scan(msg, &self.logger) {
+ None => self.scan_schedulers.receivable.schedule(ctx, &self.logger),
+ Some(node_to_ui_msg) => {
+ self.ui_message_sub_opt
+ .as_ref()
+ .expect("UIGateway is not bound")
+ .try_send(node_to_ui_msg)
+ .expect("UIGateway is dead");
+ // Externally triggered scans are not allowed to provoke an unwinding scan sequence
+ // with intervals. The only exception is the PendingPayableScanner that is always
+ // followed by the retry-payable scanner in a tight tandem.
+ }
}
}
}
@@ -417,27 +437,34 @@ impl Handler for Accountant {
fn handle(&mut self, scan_error: ScanError, ctx: &mut Self::Context) -> Self::Result {
error!(self.logger, "Received ScanError: {:?}", scan_error);
+
self.scanners
.acknowledge_scan_error(&scan_error, &self.logger);
match scan_error.response_skeleton_opt {
- None => match scan_error.scan_type {
- DetailedScanType::NewPayables => self
- .scan_schedulers
- .payable
- .schedule_new_payable_scan(ctx, &self.logger),
- DetailedScanType::RetryPayables => self
- .scan_schedulers
- .payable
- .schedule_retry_payable_scan(ctx, None, &self.logger),
- DetailedScanType::PendingPayables => self
- .scan_schedulers
- .pending_payable
- .schedule(ctx, &self.logger),
- DetailedScanType::Receivables => {
- self.scan_schedulers.receivable.schedule(ctx, &self.logger)
+ None => {
+ debug!(
+ self.logger,
+ "Trying to restore the scan train after a crash"
+ );
+ match scan_error.scan_type {
+ DetailedScanType::NewPayables => self
+ .scan_schedulers
+ .payable
+ .schedule_new_payable_scan(ctx, &self.logger),
+ DetailedScanType::RetryPayables => self
+ .scan_schedulers
+ .payable
+ .schedule_retry_payable_scan(ctx, None, &self.logger),
+ DetailedScanType::PendingPayables => self
+ .scan_schedulers
+ .pending_payable
+ .schedule(ctx, &self.logger),
+ DetailedScanType::Receivables => {
+ self.scan_schedulers.receivable.schedule(ctx, &self.logger)
+ }
}
- },
+ }
Some(response_skeleton) => {
let error_msg = NodeToUiMessage {
target: ClientId(response_skeleton.client_id),
@@ -613,12 +640,11 @@ impl Accountant {
&self,
service_rate: u64,
byte_rate: u64,
+ total_charge: u128,
timestamp: SystemTime,
payload_size: usize,
wallet: &Wallet,
) {
- let byte_charge = byte_rate as u128 * (payload_size as u128);
- let total_charge = service_rate as u128 + byte_charge;
if !self.our_wallet(wallet) {
match self.receivable_dao
.as_ref()
@@ -647,12 +673,11 @@ impl Accountant {
&self,
service_rate: u64,
byte_rate: u64,
+ total_charge: u128,
timestamp: SystemTime,
payload_size: usize,
wallet: &Wallet,
) {
- let byte_charge = byte_rate as u128 * (payload_size as u128);
- let total_charge = service_rate as u128 + byte_charge;
if !self.our_wallet(wallet) {
match self.payable_dao
.as_ref()
@@ -732,15 +757,18 @@ impl Accountant {
&mut self,
msg: ReportRoutingServiceProvidedMessage,
) {
+ let total_charge = Self::total_charge(msg.byte_rate, msg.service_rate, msg.payload_size);
trace!(
self.logger,
- "Charging routing of {} bytes to wallet {}",
+ "Charging {} wei for routing of {} bytes to wallet {}",
+ total_charge,
msg.payload_size,
msg.paying_wallet
);
self.record_service_provided(
msg.service_rate,
msg.byte_rate,
+ total_charge,
msg.timestamp,
msg.payload_size,
&msg.paying_wallet,
@@ -751,9 +779,11 @@ impl Accountant {
&mut self,
msg: ReportExitServiceProvidedMessage,
) {
+ let total_charge = Self::total_charge(msg.byte_rate, msg.service_rate, msg.payload_size);
trace!(
self.logger,
- "Charging exit service for {} bytes to wallet {} at {} per service and {} per byte",
+ "Charging {} wei for exit service for {} bytes to wallet {} at {} per service and {} per byte",
+ total_charge,
msg.payload_size,
msg.paying_wallet,
msg.service_rate,
@@ -762,6 +792,7 @@ impl Accountant {
self.record_service_provided(
msg.service_rate,
msg.byte_rate,
+ total_charge,
msg.timestamp,
msg.payload_size,
&msg.paying_wallet,
@@ -778,31 +809,45 @@ impl Accountant {
fn handle_report_services_consumed_message(&mut self, msg: ReportServicesConsumedMessage) {
let msg_id = self.msg_id();
+ let total_charge = Self::total_charge(
+ msg.exit.byte_rate,
+ msg.exit.service_rate,
+ msg.exit.payload_size,
+ );
trace!(
self.logger,
- "MsgId {}: Accruing debt to {} for consuming {} exited bytes",
+ "MsgId {}: Accruing {} wei of debt to {} for consuming {} exited bytes",
msg_id,
+ total_charge,
msg.exit.earning_wallet,
msg.exit.payload_size
);
self.record_service_consumed(
msg.exit.service_rate,
msg.exit.byte_rate,
+ total_charge,
msg.timestamp,
msg.exit.payload_size,
&msg.exit.earning_wallet,
);
msg.routing.iter().for_each(|routing_service| {
+ let total_charge = Self::total_charge(
+ routing_service.byte_rate,
+ routing_service.service_rate,
+ msg.routing_payload_size,
+ );
trace!(
self.logger,
- "MsgId {}: Accruing debt to {} for consuming {} routed bytes",
+ "MsgId {}: Accruing {} wei of debt to {} for consuming {} routed bytes",
msg_id,
+ total_charge,
routing_service.earning_wallet,
msg.routing_payload_size
);
self.record_service_consumed(
routing_service.service_rate,
routing_service.byte_rate,
+ total_charge,
msg.timestamp,
msg.routing_payload_size,
&routing_service.earning_wallet,
@@ -975,7 +1020,7 @@ impl Accountant {
None => Err(StartScanError::NoConsumingWalletFound),
};
- self.scan_schedulers.payable.reset_scan_timer();
+ self.scan_schedulers.payable.reset_scan_timer(&self.logger);
match result {
Ok(scan_message) => {
@@ -986,8 +1031,8 @@ impl Accountant {
.expect("BlockchainBridge is dead");
ScanReschedulingAfterEarlyStop::DoNotSchedule
}
- Err(e) => self.handle_start_scan_error_and_prevent_scan_stall_point(
- PayableSequenceScanner::NewPayables,
+ Err(e) => self.handle_start_scan_error(
+ UnableToStartScanner::NewPayables,
e,
response_skeleton_opt,
),
@@ -1018,10 +1063,9 @@ impl Accountant {
.expect("BlockchainBridge is dead");
}
Err(e) => {
- // It is thrown away and there is no rescheduling downstream because every error
- // happening here on the start resolves into a panic by the current design
- let _ = self.handle_start_scan_error_and_prevent_scan_stall_point(
- PayableSequenceScanner::RetryPayables,
+ // Any error here panics by design, so the return value is unreachable/ignored.
+ let _ = self.handle_start_scan_error(
+ UnableToStartScanner::RetryPayables,
e,
response_skeleton_opt,
);
@@ -1056,8 +1100,8 @@ impl Accountant {
}
Err(e) => {
let initial_pending_payable_scan = self.scanners.initial_pending_payable_scan();
- self.handle_start_scan_error_and_prevent_scan_stall_point(
- PayableSequenceScanner::PendingPayables {
+ self.handle_start_scan_error(
+ UnableToStartScanner::PendingPayables {
initial_pending_payable_scan,
},
e,
@@ -1073,9 +1117,9 @@ impl Accountant {
hint
}
- fn handle_start_scan_error_and_prevent_scan_stall_point(
+ fn handle_start_scan_error(
&self,
- scanner: PayableSequenceScanner,
+ scanner: UnableToStartScanner,
e: StartScanError,
response_skeleton_opt: Option,
) -> ScanReschedulingAfterEarlyStop {
@@ -1102,7 +1146,7 @@ impl Accountant {
fn handle_request_of_scan_for_receivable(
&mut self,
response_skeleton_opt: Option,
- ) {
+ ) -> ScanReschedulingAfterEarlyStop {
let result: Result =
self.scanners.start_receivable_scan_guarded(
&self.earning_wallet,
@@ -1113,29 +1157,22 @@ impl Accountant {
);
match result {
- Ok(scan_message) => self
- .retrieve_transactions_sub_opt
- .as_ref()
- .expect("BlockchainBridge is unbound")
- .try_send(scan_message)
- .expect("BlockchainBridge is dead"),
- Err(e) => {
- e.log_error(
- &self.logger,
- ScanType::Receivables,
- response_skeleton_opt.is_some(),
- );
-
- if let Some(skeleton) = response_skeleton_opt {
- self.ui_message_sub_opt
- .as_ref()
- .expect("UiGateway is unbound")
- .try_send(NodeToUiMessage {
- target: MessageTarget::ClientId(skeleton.client_id),
- body: UiScanResponse {}.tmb(skeleton.context_id),
- })
- .expect("UiGateway is dead");
- };
+ Ok(scan_message) => {
+ self.retrieve_transactions_sub_opt
+ .as_ref()
+ .expect("BlockchainBridge is unbound")
+ .try_send(scan_message)
+ .expect("BlockchainBridge is dead");
+ ScanReschedulingAfterEarlyStop::DoNotSchedule
+ }
+ Err(e) =>
+ // Any error here panics by design, so the return value is unreachable/ignored.
+ {
+ self.handle_start_scan_error(
+ UnableToStartScanner::Receivables,
+ e,
+ response_skeleton_opt,
+ )
}
}
}
@@ -1205,6 +1242,11 @@ impl Accountant {
}
}
+ fn total_charge(byte_rate: u64, service_rate: u64, payload_size: usize) -> u128 {
+ let byte_charge = byte_rate as u128 * (payload_size as u128);
+ service_rate as u128 + byte_charge
+ }
+
fn financial_statistics(&self) -> Ref<'_, FinancialStatistics> {
self.financial_statistics.borrow()
}
@@ -2037,8 +2079,8 @@ mod tests {
let system = System::new("test");
subject.scan_schedulers.automatic_scans_enabled = false;
// Making sure we would kill the test if any sort of scan was scheduled
- subject.scan_schedulers.payable.retry_payable_notify =
- Box::new(NotifyHandleMock::default().panic_on_schedule_attempt());
+ subject.scan_schedulers.payable.retry_payable_notify_later =
+ Box::new(NotifyLaterHandleMock::default().panic_on_schedule_attempt());
subject.scan_schedulers.payable.new_payable_notify_later =
Box::new(NotifyLaterHandleMock::default().panic_on_schedule_attempt());
subject.scan_schedulers.payable.new_payable_notify =
@@ -2274,6 +2316,7 @@ mod tests {
])
.build();
subject.scan_schedulers.automatic_scans_enabled = false;
+ subject.scan_schedulers.payable.retry_payable_scan_interval = Duration::from_millis(1);
let (blockchain_bridge, _, blockchain_bridge_recording_arc) = make_recorder();
let (ui_gateway, _, ui_gateway_recording_arc) = make_recorder();
let ui_gateway =
@@ -2281,7 +2324,7 @@ mod tests {
let (peer_actors, peer_addresses) = peer_actors_builder()
.blockchain_bridge(blockchain_bridge)
.ui_gateway(ui_gateway)
- .build_and_provide_addresses();
+ .build_with_addresses();
let subject_addr = subject.start();
let system = System::new("test");
let response_skeleton_opt = Some(ResponseSkeleton {
@@ -2715,6 +2758,40 @@ mod tests {
);
}
+ #[test]
+ #[should_panic(
+ expected = "internal error: entered unreachable code: Early stopped receivable scan \
+ was suggested to be followed up by the scan for PendingPayables, which is not supported though"
+ )]
+ fn start_scan_error_in_receivables_and_unexpected_instruction_from_early_stop_scan_rescheduling(
+ ) {
+ let mut subject = AccountantBuilder::default().build();
+ let reschedule_on_error_resolver = RescheduleScanOnErrorResolverMock::default()
+ .resolve_rescheduling_on_error_result(ScanReschedulingAfterEarlyStop::Schedule(
+ ScanType::PendingPayables,
+ ));
+ let receivable_scanner = ScannerMock::default()
+ .scan_started_at_result(None)
+ .start_scan_result(Err(StartScanError::NoConsumingWalletFound));
+ subject
+ .scanners
+ .replace_scanner(ScannerReplacement::Receivable(ReplacementType::Mock(
+ receivable_scanner,
+ )));
+ subject.scan_schedulers.reschedule_on_error_resolver =
+ Box::new(reschedule_on_error_resolver);
+ let system = System::new("test");
+ let subject_addr = subject.start();
+
+ subject_addr
+ .try_send(ScanForReceivables {
+ response_skeleton_opt: None,
+ })
+ .unwrap();
+
+ system.run();
+ }
+
#[test]
fn received_payments_with_response_skeleton_sends_response_to_ui_gateway() {
let mut config = bc_from_earning_wallet(make_wallet("earning_wallet"));
@@ -2724,7 +2801,7 @@ mod tests {
receivable_scan_interval: Duration::from_millis(10_000),
});
config.automatic_scans_enabled = false;
- let subject = AccountantBuilder::default()
+ let mut subject = AccountantBuilder::default()
.bootstrapper_config(config)
.config_dao(
ConfigDaoMock::new()
@@ -2732,6 +2809,9 @@ mod tests {
.set_result(Ok(())),
)
.build();
+ // Another scan must not be scheduled
+ subject.scan_schedulers.receivable.handle =
+ Box::new(NotifyLaterHandleMock::default().panic_on_schedule_attempt());
let (ui_gateway, _, ui_gateway_recording_arc) = make_recorder();
let subject_addr = subject.start();
let system = System::new("test");
@@ -2838,11 +2918,13 @@ mod tests {
let test_name = "accountant_scans_after_startup_and_does_not_detect_any_pending_payables";
let scan_params = ScanParams::default();
let notify_and_notify_later_params = NotifyAndNotifyLaterParams::default();
- let time_until_next_scan_params_arc = Arc::new(Mutex::new(vec![]));
+ let compute_time_to_next_scan_params_arc = Arc::new(Mutex::new(vec![]));
let earning_wallet = make_wallet("earning");
let consuming_wallet = make_wallet("consuming");
let system = System::new(test_name);
- let _ = SystemKillerActor::new(Duration::from_secs(10)).start();
+ let (blockchain_bridge, _, _) = make_recorder();
+ let blockchain_bridge = blockchain_bridge
+ .system_stop_conditions(match_lazily_every_type_id!(RetrieveTransactions));
let config = bc_from_wallets(consuming_wallet.clone(), earning_wallet.clone());
let payable_scanner = ScannerMock::new()
.scan_started_at_result(None)
@@ -2855,25 +2937,31 @@ mod tests {
let receivable_scanner = ScannerMock::new()
.scan_started_at_result(None)
.start_scan_params(&scan_params.receivable_start_scan)
- .start_scan_result(Err(StartScanError::NothingToProcess));
- let (subject, new_payable_expected_computed_interval, receivable_scan_interval) =
+ .start_scan_result(Ok(RetrieveTransactions {
+ recipient: earning_wallet.clone(),
+ response_skeleton_opt: None,
+ }));
+ let (subject, new_payable_expected_computed_interval) =
configure_accountant_for_startup_with_preexisting_pending_payables(
test_name,
¬ify_and_notify_later_params,
- &time_until_next_scan_params_arc,
+ &compute_time_to_next_scan_params_arc,
config,
pending_payable_scanner,
receivable_scanner,
payable_scanner,
);
- let peer_actors = peer_actors_builder().build();
+ let peer_actors = peer_actors_builder()
+ .blockchain_bridge(blockchain_bridge)
+ .build();
let subject_addr: Addr = subject.start();
let subject_subs = Accountant::make_subs_from(&subject_addr);
send_bind_message!(subject_subs, peer_actors);
send_start_message!(subject_subs);
- // The system is stopped by the NotifyLaterHandleMock for the Receivable scanner
+ // The system is stopped by the time the RetrieveTransactions msg arrives at the mocked
+ // BlockchainBridge.
let before = SystemTime::now();
system.run();
let after = SystemTime::now();
@@ -2888,15 +2976,13 @@ mod tests {
assert_payable_scanner_for_no_pending_payable_found(
&scan_params.payable_start_scan,
¬ify_and_notify_later_params,
- time_until_next_scan_params_arc,
+ compute_time_to_next_scan_params_arc,
new_payable_expected_computed_interval,
);
assert_receivable_scanner(
test_name,
earning_wallet,
&scan_params.receivable_start_scan,
- ¬ify_and_notify_later_params.receivables_notify_later,
- receivable_scan_interval,
);
// The test lays down evidences that the NewPayableScanner couldn't run before
// the PendingPayableScanner, which is an intention.
@@ -2954,8 +3040,11 @@ mod tests {
let receivable_scanner = ScannerMock::new()
.scan_started_at_result(None)
.start_scan_params(&scan_params.receivable_start_scan)
- .start_scan_result(Err(StartScanError::NothingToProcess));
- let (subject, expected_pending_payable_notify_later_interval, receivable_scan_interval) =
+ .start_scan_result(Ok(RetrieveTransactions {
+ recipient: earning_wallet.clone(),
+ response_skeleton_opt: None,
+ }));
+ let (subject, expected_pending_payable_notify_later_interval) =
configure_accountant_for_startup_with_no_preexisting_pending_payables(
test_name,
¬ify_and_notify_later_params,
@@ -2964,7 +3053,7 @@ mod tests {
pending_payable_scanner,
receivable_scanner,
);
- let (peer_actors, addresses) = peer_actors_builder().build_and_provide_addresses();
+ let (peer_actors, addresses) = peer_actors_builder().build_with_addresses();
let subject_addr: Addr = subject.start();
let subject_subs = Accountant::make_subs_from(&subject_addr);
let expected_tx_receipts_msg = TxReceiptsMessage {
@@ -3031,8 +3120,6 @@ mod tests {
test_name,
earning_wallet,
&scan_params.receivable_start_scan,
- ¬ify_and_notify_later_params.receivables_notify_later,
- receivable_scan_interval,
);
// Since the assertions proved that the pending payable scanner had run multiple times
// before the new payable scanner started or was scheduled, the front position definitely
@@ -3055,15 +3142,14 @@ mod tests {
struct NotifyAndNotifyLaterParams {
new_payables_notify_later: Arc>>,
new_payables_notify: Arc>>,
- retry_payables_notify: Arc>>,
+ retry_payables_notify_later: Arc>>,
pending_payables_notify_later: Arc>>,
- receivables_notify_later: Arc>>,
}
fn configure_accountant_for_startup_with_preexisting_pending_payables(
test_name: &str,
notify_and_notify_later_params: &NotifyAndNotifyLaterParams,
- time_until_next_scan_params_arc: &Arc>>,
+ compute_time_to_next_scan_params_arc: &Arc>>,
config: BootstrapperConfig,
pending_payable_scanner: ScannerMock<
RequestTransactionReceipts,
@@ -3076,7 +3162,7 @@ mod tests {
Option,
>,
payable_scanner: ScannerMock,
- ) -> (Accountant, Duration, Duration) {
+ ) -> (Accountant, Duration) {
let mut subject = make_subject_and_inject_scanners(
test_name,
config,
@@ -3086,7 +3172,6 @@ mod tests {
);
let new_payable_expected_computed_interval = Duration::from_secs(3600);
// Important that this is made short because the test relies on it with the system stop.
- let receivable_scan_interval = Duration::from_millis(50);
subject.scan_schedulers.pending_payable.handle = Box::new(
NotifyLaterHandleMock::default()
.notify_later_params(¬ify_and_notify_later_params.pending_payables_notify_later),
@@ -3095,30 +3180,21 @@ mod tests {
NotifyLaterHandleMock::default()
.notify_later_params(¬ify_and_notify_later_params.new_payables_notify_later),
);
- subject.scan_schedulers.payable.retry_payable_notify = Box::new(
- NotifyHandleMock::default()
- .notify_params(¬ify_and_notify_later_params.retry_payables_notify),
+ subject.scan_schedulers.payable.retry_payable_notify_later = Box::new(
+ NotifyLaterHandleMock::default()
+ .notify_later_params(¬ify_and_notify_later_params.retry_payables_notify_later),
);
subject.scan_schedulers.payable.new_payable_notify = Box::new(
NotifyHandleMock::default()
.notify_params(¬ify_and_notify_later_params.new_payables_notify),
);
- let receivable_notify_later_handle_mock = NotifyLaterHandleMock::default()
- .notify_later_params(¬ify_and_notify_later_params.receivables_notify_later)
- .stop_system_on_count_received(1);
- subject.scan_schedulers.receivable.handle = Box::new(receivable_notify_later_handle_mock);
- subject.scan_schedulers.receivable.interval = receivable_scan_interval;
let interval_computer = NewPayableScanIntervalComputerMock::default()
- .time_until_next_scan_params(&time_until_next_scan_params_arc)
- .time_until_next_scan_result(ScanTiming::WaitFor(
+ .compute_time_to_next_scan_params(&compute_time_to_next_scan_params_arc)
+ .compute_time_to_next_scan_result(ScanTiming::WaitFor(
new_payable_expected_computed_interval,
));
subject.scan_schedulers.payable.interval_computer = Box::new(interval_computer);
- (
- subject,
- new_payable_expected_computed_interval,
- receivable_scan_interval,
- )
+ (subject, new_payable_expected_computed_interval)
}
fn configure_accountant_for_startup_with_no_preexisting_pending_payables(
@@ -3136,7 +3212,7 @@ mod tests {
ReceivedPayments,
Option,
>,
- ) -> (Accountant, Duration, Duration) {
+ ) -> (Accountant, Duration) {
let mut subject = make_subject_and_inject_scanners(
test_name,
config,
@@ -3144,37 +3220,30 @@ mod tests {
receivable_scanner,
payable_scanner,
);
+ let retry_payable_scan_interval = Duration::from_millis(1);
let pending_payable_scan_interval = Duration::from_secs(3600);
- let receivable_scan_interval = Duration::from_secs(3600);
let pending_payable_notify_later_handle_mock = NotifyLaterHandleMock::default()
.notify_later_params(¬ify_and_notify_later_params.pending_payables_notify_later)
// This should stop the system
.stop_system_on_count_received(1);
subject.scan_schedulers.pending_payable.handle =
Box::new(pending_payable_notify_later_handle_mock);
+ subject.scan_schedulers.payable.retry_payable_scan_interval = retry_payable_scan_interval;
subject.scan_schedulers.pending_payable.interval = pending_payable_scan_interval;
subject.scan_schedulers.payable.new_payable_notify_later = Box::new(
NotifyLaterHandleMock::default()
.notify_later_params(¬ify_and_notify_later_params.new_payables_notify_later),
);
- subject.scan_schedulers.payable.retry_payable_notify = Box::new(
- NotifyHandleMock::default()
- .notify_params(¬ify_and_notify_later_params.retry_payables_notify)
+ subject.scan_schedulers.payable.retry_payable_notify_later = Box::new(
+ NotifyLaterHandleMock::default()
+ .notify_later_params(¬ify_and_notify_later_params.retry_payables_notify_later)
.capture_msg_and_let_it_fly_on(),
);
subject.scan_schedulers.payable.new_payable_notify = Box::new(
NotifyHandleMock::default()
.notify_params(¬ify_and_notify_later_params.new_payables_notify),
);
- let receivable_notify_later_handle_mock = NotifyLaterHandleMock::default()
- .notify_later_params(¬ify_and_notify_later_params.receivables_notify_later);
- subject.scan_schedulers.receivable.interval = receivable_scan_interval;
- subject.scan_schedulers.receivable.handle = Box::new(receivable_notify_later_handle_mock);
- (
- subject,
- pending_payable_scan_interval,
- receivable_scan_interval,
- )
+ (subject, pending_payable_scan_interval)
}
fn make_subject_and_inject_scanners(
@@ -3333,7 +3402,7 @@ mod tests {
Mutex, Logger, String)>>,
>,
notify_and_notify_later_params: &NotifyAndNotifyLaterParams,
- time_until_next_scan_until_next_new_payable_scan_params_arc: Arc>>,
+ time_until_next_new_payable_scan_params_arc: Arc>>,
new_payable_expected_computed_interval: Duration,
) {
// Note that there is no functionality from the payable scanner actually running.
@@ -3351,14 +3420,9 @@ mod tests {
new_payable_expected_computed_interval
)]
);
- let time_until_next_scan_until_next_new_payable_scan_params =
- time_until_next_scan_until_next_new_payable_scan_params_arc
- .lock()
- .unwrap();
- assert_eq!(
- *time_until_next_scan_until_next_new_payable_scan_params,
- vec![()]
- );
+ let time_until_next_new_payable_scan_params =
+ time_until_next_new_payable_scan_params_arc.lock().unwrap();
+ assert_eq!(*time_until_next_new_payable_scan_params, vec![()]);
let payable_scanner_start_scan = payable_scanner_start_scan_arc.lock().unwrap();
assert!(
payable_scanner_start_scan.is_empty(),
@@ -3374,7 +3438,7 @@ mod tests {
scan_for_new_payables_notify_params
);
let scan_for_retry_payables_notify_params = notify_and_notify_later_params
- .retry_payables_notify
+ .retry_payables_notify_later
.lock()
.unwrap();
assert!(
@@ -3460,14 +3524,17 @@ mod tests {
scan_for_new_payables_notify_params
);
let scan_for_retry_payables_notify_params = notify_and_notify_later_params
- .retry_payables_notify
+ .retry_payables_notify_later
.lock()
.unwrap();
assert_eq!(
*scan_for_retry_payables_notify_params,
- vec![ScanForRetryPayables {
- response_skeleton_opt: None
- }],
+ vec![(
+ ScanForRetryPayables {
+ response_skeleton_opt: None
+ },
+ Duration::from_millis(1)
+ )],
);
}
@@ -3477,16 +3544,8 @@ mod tests {
receivable_start_scan_params_arc: &Arc<
Mutex, Logger, String)>>,
>,
- scan_for_receivables_notify_later_params_arc: &Arc<
- Mutex>,
- >,
- receivable_scan_interval: Duration,
) {
assert_receivable_scan_ran(test_name, receivable_start_scan_params_arc, earning_wallet);
- assert_another_receivable_scan_scheduled(
- scan_for_receivables_notify_later_params_arc,
- receivable_scan_interval,
- )
}
fn assert_receivable_scan_ran(
@@ -3514,25 +3573,6 @@ mod tests {
assert_using_the_same_logger(&r_logger, test_name, Some("r"));
}
- fn assert_another_receivable_scan_scheduled(
- scan_for_receivables_notify_later_params_arc: &Arc<
- Mutex>,
- >,
- receivable_scan_interval: Duration,
- ) {
- let scan_for_receivables_notify_later_params =
- scan_for_receivables_notify_later_params_arc.lock().unwrap();
- assert_eq!(
- *scan_for_receivables_notify_later_params,
- vec![(
- ScanForReceivables {
- response_skeleton_opt: None
- },
- receivable_scan_interval
- )]
- );
- }
-
#[test]
fn initial_pending_payable_scan_if_some_payables_found() {
let sent_payable_dao =
@@ -3629,23 +3669,31 @@ mod tests {
let start_scan_params_arc = Arc::new(Mutex::new(vec![]));
let notify_later_receivable_params_arc = Arc::new(Mutex::new(vec![]));
let system = System::new(test_name);
+ let (blockchain_bridge, _, blockchain_bridge_recording_arc) = make_recorder();
+ let blockchain_bridge = blockchain_bridge.system_stop_conditions(
+ match_lazily_every_type_id!(RetrieveTransactions, RetrieveTransactions),
+ );
SystemKillerActor::new(Duration::from_secs(10)).start(); // a safety net for GitHub Actions
+ let recipient = make_wallet("some_recipient");
let receivable_scanner = ScannerMock::new()
.scan_started_at_result(None)
.scan_started_at_result(None)
.start_scan_params(&start_scan_params_arc)
- .start_scan_result(Err(StartScanError::NothingToProcess))
.start_scan_result(Ok(RetrieveTransactions {
- recipient: make_wallet("some_recipient"),
+ recipient: recipient.clone(),
response_skeleton_opt: None,
}))
- .stop_the_system_after_last_msg();
+ .start_scan_result(Ok(RetrieveTransactions {
+ recipient: recipient.clone(),
+ response_skeleton_opt: None,
+ }))
+ .finish_scan_result(None);
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),
pending_payable_scan_interval: Duration::from_secs(10),
- receivable_scan_interval: Duration::from_millis(99),
+ receivable_scan_interval: Duration::from_millis(15),
});
let mut subject = AccountantBuilder::default()
.bootstrapper_config(config)
@@ -3663,8 +3711,25 @@ mod tests {
);
let subject_addr = subject.start();
let subject_subs = Accountant::make_subs_from(&subject_addr);
- let peer_actors = peer_actors_builder().build();
+ let (peer_actors, peer_actors_addrs) = peer_actors_builder()
+ .blockchain_bridge(blockchain_bridge)
+ .build_with_addresses();
send_bind_message!(subject_subs, peer_actors);
+ let counter_msg = ReceivedPayments {
+ timestamp: SystemTime::now(),
+ new_start_block: BlockMarker::Value(1234),
+ transactions: vec![],
+ response_skeleton_opt: None,
+ };
+ let counter_msg_setup = setup_for_counter_msg_triggered_via_type_id!(
+ RetrieveTransactions,
+ counter_msg,
+ subject_addr
+ );
+ peer_actors_addrs
+ .blockchain_bridge_addr
+ .try_send(SetUpCounterMsgs::new(vec![counter_msg_setup]))
+ .unwrap();
subject_addr
.try_send(ScanForReceivables {
@@ -3702,37 +3767,37 @@ mod tests {
assert!(start_scan_params.is_empty());
debug!(
first_attempt_logger,
- "first attempt verifying receivable scanner"
+ "first attempt receivable scanner logger verification"
);
debug!(
second_attempt_logger,
- "second attempt verifying receivable scanner"
+ "second attempt receivable scanner logger verification"
);
+ let blockchain_bridge_recording = blockchain_bridge_recording_arc.lock().unwrap();
+ let first_msg_towards_bb =
+ blockchain_bridge_recording.get_record::(0);
+ let expected_msg = RetrieveTransactions {
+ recipient,
+ response_skeleton_opt: None,
+ };
+ assert_eq!(first_msg_towards_bb, &expected_msg);
+ let second_msg_towards_bb =
+ blockchain_bridge_recording.get_record::(1);
+ assert_eq!(second_msg_towards_bb, &expected_msg);
assert_eq!(
*notify_later_receivable_params,
- vec![
- (
- ScanForReceivables {
- response_skeleton_opt: None
- },
- Duration::from_millis(99)
- ),
- (
- ScanForReceivables {
- response_skeleton_opt: None
- },
- Duration::from_millis(99)
- ),
- ]
+ vec![(
+ ScanForReceivables {
+ response_skeleton_opt: None
+ },
+ Duration::from_millis(15)
+ ),]
);
tlh.exists_log_containing(&format!(
- "DEBUG: {test_name}: There was nothing to process during Receivables scan."
- ));
- tlh.exists_log_containing(&format!(
- "DEBUG: {test_name}: first attempt verifying receivable scanner",
+ "DEBUG: {test_name}: first attempt receivable scanner logger verification",
));
tlh.exists_log_containing(&format!(
- "DEBUG: {test_name}: second attempt verifying receivable scanner",
+ "DEBUG: {test_name}: second attempt receivable scanner logger verification",
));
}
@@ -4207,7 +4272,8 @@ mod tests {
expected = "internal error: entered unreachable code: Early stopped new payable scan \
was suggested to be followed up by the scan for Receivables, which is not supported though"
)]
- fn start_scan_error_in_new_payables_and_unexpected_reaction_by_receivable_scan_scheduling() {
+ fn start_scan_error_in_new_payables_and_unexpected_instruction_from_early_stop_scan_rescheduling(
+ ) {
let mut subject = AccountantBuilder::default().build();
let reschedule_on_error_resolver = RescheduleScanOnErrorResolverMock::default()
.resolve_rescheduling_on_error_result(ScanReschedulingAfterEarlyStop::Schedule(
@@ -4427,32 +4493,6 @@ mod tests {
);
}
- #[test]
- #[should_panic(
- expected = "internal error: entered unreachable code: Early stopped pending payable scan \
- was suggested to be followed up by the scan for Receivables, which is not supported though"
- )]
- fn start_scan_error_in_pending_payables_and_unexpected_reaction_by_receivable_scan_scheduling()
- {
- let mut subject = AccountantBuilder::default().build();
- let reschedule_on_error_resolver = RescheduleScanOnErrorResolverMock::default()
- .resolve_rescheduling_on_error_result(ScanReschedulingAfterEarlyStop::Schedule(
- ScanType::Receivables,
- ));
- subject.scan_schedulers.reschedule_on_error_resolver =
- Box::new(reschedule_on_error_resolver);
- let system = System::new("test");
- let subject_addr = subject.start();
-
- subject_addr
- .try_send(ScanForPendingPayables {
- response_skeleton_opt: None,
- })
- .unwrap();
-
- system.run();
- }
-
#[test]
fn report_routing_service_provided_message_is_received() {
init_test_logging();
@@ -4494,6 +4534,10 @@ mod tests {
more_money_receivable_parameters[0],
(now, make_wallet("booga"), (1 * 42) + (1234 * 24))
);
+ TestLogHandler::new().exists_log_containing(&format!(
+ "TRACE: Accountant: Charging 29658 wei for routing of 1234 bytes to wallet {}",
+ paying_wallet
+ ));
}
#[test]
@@ -4627,6 +4671,10 @@ mod tests {
more_money_receivable_parameters[0],
(now, make_wallet("booga"), (1 * 42) + (1234 * 24))
);
+ TestLogHandler::new().exists_log_containing(&format!(
+ "TRACE: Accountant: Charging 29658 wei for exit service for 1234 bytes to wallet {}",
+ paying_wallet
+ ));
}
#[test]
@@ -4793,6 +4841,19 @@ mod tests {
)
]
);
+ let test_log_handler = TestLogHandler::new();
+ test_log_handler.exists_log_containing(&format!(
+ "TRACE: Accountant: MsgId 123: Accruing 36120 wei of debt to {} for consuming 1200 exited bytes",
+ earning_wallet_exit
+ ));
+ test_log_handler.exists_log_containing(&format!(
+ "TRACE: Accountant: MsgId 123: Accruing 82986 wei of debt to {} for consuming 3456 routed bytes",
+ earning_wallet_routing_1
+ ));
+ test_log_handler.exists_log_containing(&format!(
+ "TRACE: Accountant: MsgId 123: Accruing 114100 wei of debt to {} for consuming 3456 routed bytes",
+ earning_wallet_routing_2
+ ));
}
fn assert_that_we_do_not_charge_our_own_wallet_for_consumed_services(
@@ -4984,7 +5045,14 @@ mod tests {
.receivable_daos(vec![ForAccountantBody(receivable_dao)])
.build();
- let _ = subject.record_service_provided(i64::MAX as u64, 1, SystemTime::now(), 2, &wallet);
+ let _ = subject.record_service_provided(
+ i64::MAX as u64,
+ 1,
+ 1000,
+ SystemTime::now(),
+ 2,
+ &wallet,
+ );
}
#[test]
@@ -4997,7 +5065,7 @@ mod tests {
.receivable_daos(vec![ForAccountantBody(receivable_dao)])
.build();
- subject.record_service_provided(i64::MAX as u64, 1, SystemTime::now(), 2, &wallet);
+ subject.record_service_provided(i64::MAX as u64, 1, 1000, SystemTime::now(), 2, &wallet);
TestLogHandler::new().exists_log_containing(&format!(
"ERROR: Accountant: Overflow error recording service provided for {}: service rate {}, byte rate 1, payload size 2. Skipping",
@@ -5017,7 +5085,14 @@ mod tests {
.build();
let service_rate = i64::MAX as u64;
- subject.record_service_consumed(service_rate, 1, SystemTime::now(), 2, &wallet);
+ subject.record_service_consumed(
+ service_rate,
+ 1,
+ 9223372036854775809,
+ SystemTime::now(),
+ 2,
+ &wallet,
+ );
TestLogHandler::new().exists_log_containing(&format!(
"ERROR: Accountant: Overflow error recording consumed services from {}: total charge {}, service rate {}, byte rate 1, payload size 2. Skipping",
@@ -5042,7 +5117,14 @@ mod tests {
.payable_daos(vec![ForAccountantBody(payable_dao)])
.build();
- let _ = subject.record_service_consumed(i64::MAX as u64, 1, SystemTime::now(), 2, &wallet);
+ let _ = subject.record_service_consumed(
+ i64::MAX as u64,
+ 1,
+ 1000,
+ SystemTime::now(),
+ 2,
+ &wallet,
+ );
}
#[test]
@@ -5090,8 +5172,8 @@ mod tests {
Box::new(NotifyHandleMock::default().panic_on_schedule_attempt());
subject.scan_schedulers.payable.new_payable_notify_later =
Box::new(NotifyLaterHandleMock::default().panic_on_schedule_attempt());
- subject.scan_schedulers.payable.retry_payable_notify =
- Box::new(NotifyHandleMock::default().panic_on_schedule_attempt());
+ subject.scan_schedulers.payable.retry_payable_notify_later =
+ Box::new(NotifyLaterHandleMock::default().panic_on_schedule_attempt());
let expected_tx = TxBuilder::default().hash(expected_hash.clone()).build();
let sent_payable = SentPayables {
payment_procedure_result: Ok(BatchResults {
@@ -5149,8 +5231,8 @@ mod tests {
Box::new(NotifyHandleMock::default().panic_on_schedule_attempt());
subject.scan_schedulers.payable.new_payable_notify_later =
Box::new(NotifyLaterHandleMock::default().panic_on_schedule_attempt());
- subject.scan_schedulers.payable.retry_payable_notify =
- Box::new(NotifyHandleMock::default().panic_on_schedule_attempt());
+ subject.scan_schedulers.payable.retry_payable_notify_later =
+ Box::new(NotifyLaterHandleMock::default().panic_on_schedule_attempt());
let expected_tx = TxBuilder::default().hash(expected_hash.clone()).build();
let sent_payable = SentPayables {
payment_procedure_result: Ok(BatchResults {
@@ -5184,7 +5266,7 @@ mod tests {
init_test_logging();
let test_name = "retry_payable_scan_is_requested_to_be_repeated";
let finish_scan_params_arc = Arc::new(Mutex::new(vec![]));
- let retry_payable_notify_params_arc = Arc::new(Mutex::new(vec![]));
+ let retry_payable_notify_later_params_arc = Arc::new(Mutex::new(vec![]));
let system = System::new(test_name);
let consuming_wallet = make_paying_wallet(b"paying wallet");
let mut subject = AccountantBuilder::default()
@@ -5201,8 +5283,10 @@ mod tests {
result: NextScanToRun::RetryPayableScan,
}),
)));
- subject.scan_schedulers.payable.retry_payable_notify =
- Box::new(NotifyHandleMock::default().notify_params(&retry_payable_notify_params_arc));
+ subject.scan_schedulers.payable.retry_payable_notify_later = Box::new(
+ NotifyLaterHandleMock::default()
+ .notify_later_params(&retry_payable_notify_later_params_arc),
+ );
subject.scan_schedulers.payable.new_payable_notify =
Box::new(NotifyHandleMock::default().panic_on_schedule_attempt());
subject.scan_schedulers.payable.new_payable_notify_later =
@@ -5228,9 +5312,10 @@ mod tests {
let (actual_sent_payable, logger) = finish_scan_params.remove(0);
assert_eq!(actual_sent_payable, sent_payable,);
assert_using_the_same_logger(&logger, test_name, None);
- let mut payable_notify_params = retry_payable_notify_params_arc.lock().unwrap();
- let scheduled_msg = payable_notify_params.remove(0);
+ let mut payable_notify_params = retry_payable_notify_later_params_arc.lock().unwrap();
+ let (scheduled_msg, duration) = payable_notify_params.remove(0);
assert_eq!(scheduled_msg, ScanForRetryPayables::default());
+ assert_eq!(duration, Duration::from_secs(5 * 60));
assert!(
payable_notify_params.is_empty(),
"Should be empty but {:?}",
@@ -5245,7 +5330,7 @@ mod tests {
let test_name =
"accountant_in_automatic_mode_schedules_tx_retry_as_some_pending_payables_have_not_completed";
let finish_scan_params_arc = Arc::new(Mutex::new(vec![]));
- let retry_payable_notify_params_arc = Arc::new(Mutex::new(vec![]));
+ let retry_payable_notify_later_params_arc = Arc::new(Mutex::new(vec![]));
let mut subject = AccountantBuilder::default()
.logger(Logger::new(test_name))
.build();
@@ -5263,8 +5348,10 @@ mod tests {
Box::new(NotifyLaterHandleMock::default().panic_on_schedule_attempt());
subject.scan_schedulers.pending_payable.handle =
Box::new(NotifyLaterHandleMock::default().panic_on_schedule_attempt());
- subject.scan_schedulers.payable.retry_payable_notify =
- Box::new(NotifyHandleMock::default().notify_params(&retry_payable_notify_params_arc));
+ subject.scan_schedulers.payable.retry_payable_notify_later = Box::new(
+ NotifyLaterHandleMock::default()
+ .notify_later_params(&retry_payable_notify_later_params_arc),
+ );
let system = System::new(test_name);
let (mut msg, _) = make_tx_receipts_msg(vec![
SeedsToMakeUpPayableWithStatus {
@@ -5286,12 +5373,15 @@ mod tests {
let mut finish_scan_params = finish_scan_params_arc.lock().unwrap();
let (msg_actual, logger) = finish_scan_params.remove(0);
assert_eq!(msg_actual, msg);
- let retry_payable_notify_params = retry_payable_notify_params_arc.lock().unwrap();
+ let retry_payable_notify_params = retry_payable_notify_later_params_arc.lock().unwrap();
assert_eq!(
*retry_payable_notify_params,
- vec![ScanForRetryPayables {
- response_skeleton_opt: None
- }]
+ vec![(
+ ScanForRetryPayables {
+ response_skeleton_opt: None
+ },
+ Duration::from_secs(5 * 60)
+ )]
);
assert_using_the_same_logger(&logger, test_name, None)
}
@@ -5314,8 +5404,8 @@ mod tests {
.replace_scanner(ScannerReplacement::PendingPayable(ReplacementType::Mock(
pending_payable_scanner,
)));
- subject.scan_schedulers.payable.retry_payable_notify =
- Box::new(NotifyHandleMock::default().panic_on_schedule_attempt());
+ subject.scan_schedulers.payable.retry_payable_notify_later =
+ Box::new(NotifyLaterHandleMock::default().panic_on_schedule_attempt());
subject.scan_schedulers.payable.new_payable_notify =
Box::new(NotifyHandleMock::default().panic_on_schedule_attempt());
subject.scan_schedulers.payable.new_payable_notify_later =
@@ -5380,8 +5470,8 @@ mod tests {
.replace_scanner(ScannerReplacement::PendingPayable(ReplacementType::Mock(
pending_payable_scanner,
)));
- subject.scan_schedulers.payable.retry_payable_notify =
- Box::new(NotifyHandleMock::default().panic_on_schedule_attempt());
+ subject.scan_schedulers.payable.retry_payable_notify_later =
+ Box::new(NotifyLaterHandleMock::default().panic_on_schedule_attempt());
subject.scan_schedulers.payable.new_payable_notify =
Box::new(NotifyHandleMock::default().panic_on_schedule_attempt());
subject.scan_schedulers.payable.new_payable_notify_later =
@@ -5443,8 +5533,9 @@ mod tests {
.replace_scanner(ScannerReplacement::PendingPayable(ReplacementType::Mock(
pending_payable_scanner,
)));
- subject.scan_schedulers.payable.retry_payable_notify =
- Box::new(NotifyHandleMock::default().notify_params(&retry_payable_notify_params_arc));
+ subject.scan_schedulers.payable.retry_payable_notify_later = Box::new(
+ NotifyLaterHandleMock::default().notify_later_params(&retry_payable_notify_params_arc),
+ );
subject.scan_schedulers.payable.new_payable_notify =
Box::new(NotifyHandleMock::default().panic_on_schedule_attempt());
subject.scan_schedulers.payable.new_payable_notify_later =
@@ -5468,9 +5559,12 @@ mod tests {
let retry_payable_notify_params = retry_payable_notify_params_arc.lock().unwrap();
assert_eq!(
*retry_payable_notify_params,
- vec![ScanForRetryPayables {
- response_skeleton_opt: Some(response_skeleton)
- }]
+ vec![(
+ ScanForRetryPayables {
+ response_skeleton_opt: Some(response_skeleton)
+ },
+ Duration::from_secs(5 * 60)
+ )]
);
assert_using_the_same_logger(&logger, test_name, None)
}
@@ -5481,7 +5575,7 @@ mod tests {
let test_name =
"accountant_confirms_all_pending_txs_and_schedules_new_payable_scanner_timely";
let finish_scan_params_arc = Arc::new(Mutex::new(vec![]));
- let time_until_next_scan_params_arc = Arc::new(Mutex::new(vec![]));
+ let compute_time_to_next_scan_params_arc = Arc::new(Mutex::new(vec![]));
let new_payable_notify_later_arc = Arc::new(Mutex::new(vec![]));
let new_payable_notify_arc = Arc::new(Mutex::new(vec![]));
let system = System::new("new_payable_scanner_timely");
@@ -5498,9 +5592,9 @@ mod tests {
)));
let expected_computed_interval = Duration::from_secs(3);
let interval_computer = NewPayableScanIntervalComputerMock::default()
- .time_until_next_scan_params(&time_until_next_scan_params_arc)
+ .compute_time_to_next_scan_params(&compute_time_to_next_scan_params_arc)
// This determines the test
- .time_until_next_scan_result(ScanTiming::WaitFor(expected_computed_interval));
+ .compute_time_to_next_scan_result(ScanTiming::WaitFor(expected_computed_interval));
subject.scan_schedulers.payable.interval_computer = Box::new(interval_computer);
subject.scan_schedulers.payable.new_payable_notify_later = Box::new(
NotifyLaterHandleMock::default().notify_later_params(&new_payable_notify_later_arc),
@@ -5558,7 +5652,7 @@ mod tests {
let test_name =
"accountant_confirms_payable_txs_and_schedules_the_delayed_new_payable_scanner_asap";
let finish_scan_params_arc = Arc::new(Mutex::new(vec![]));
- let time_until_next_scan_params_arc = Arc::new(Mutex::new(vec![]));
+ let compute_time_to_next_scan_params_arc = Arc::new(Mutex::new(vec![]));
let new_payable_notify_later_arc = Arc::new(Mutex::new(vec![]));
let new_payable_notify_arc = Arc::new(Mutex::new(vec![]));
let mut subject = AccountantBuilder::default()
@@ -5573,9 +5667,9 @@ mod tests {
pending_payable_scanner,
)));
let interval_computer = NewPayableScanIntervalComputerMock::default()
- .time_until_next_scan_params(&time_until_next_scan_params_arc)
+ .compute_time_to_next_scan_params(&compute_time_to_next_scan_params_arc)
// This determines the test
- .time_until_next_scan_result(ScanTiming::ReadyNow);
+ .compute_time_to_next_scan_result(ScanTiming::ReadyNow);
subject.scan_schedulers.payable.interval_computer = Box::new(interval_computer);
subject.scan_schedulers.payable.new_payable_notify_later = Box::new(
NotifyLaterHandleMock::default().notify_later_params(&new_payable_notify_later_arc),
@@ -5610,8 +5704,8 @@ mod tests {
"Should be empty but {:?}",
finish_scan_params
);
- let time_until_next_scan_params = time_until_next_scan_params_arc.lock().unwrap();
- assert_eq!(*time_until_next_scan_params, vec![()]);
+ let compute_time_to_next_scan_params = compute_time_to_next_scan_params_arc.lock().unwrap();
+ assert_eq!(*compute_time_to_next_scan_params, vec![()]);
let new_payable_notify_later = new_payable_notify_later_arc.lock().unwrap();
assert!(
new_payable_notify_later.is_empty(),
@@ -5662,7 +5756,7 @@ mod tests {
}),
}]);
let left_side_bound = if let ScanTiming::WaitFor(interval) =
- assertion_interval_computer.time_until_next_scan()
+ assertion_interval_computer.compute_time_to_next_scan()
{
interval
} else {
@@ -5676,7 +5770,7 @@ mod tests {
let new_payable_notify_later = new_payable_notify_later_arc.lock().unwrap();
let (_, actual_interval) = new_payable_notify_later[0];
let right_side_bound = if let ScanTiming::WaitFor(interval) =
- assertion_interval_computer.time_until_next_scan()
+ assertion_interval_computer.compute_time_to_next_scan()
{
interval
} else {
@@ -5854,8 +5948,9 @@ mod tests {
// Setup
let notify_later_params_arc = Arc::new(Mutex::new(vec![]));
scan_schedulers.payable.interval_computer = Box::new(
- NewPayableScanIntervalComputerMock::default()
- .time_until_next_scan_result(ScanTiming::WaitFor(Duration::from_secs(152))),
+ NewPayableScanIntervalComputerMock::default().compute_time_to_next_scan_result(
+ ScanTiming::WaitFor(Duration::from_secs(152)),
+ ),
);
scan_schedulers.payable.new_payable_notify_later = Box::new(
NotifyLaterHandleMock::default().notify_later_params(¬ify_later_params_arc),
@@ -5887,28 +5982,32 @@ mod tests {
Box::new(
|_scanners: &mut Scanners, scan_schedulers: &mut ScanSchedulers| {
// Setup
- let notify_params_arc = Arc::new(Mutex::new(vec![]));
- scan_schedulers.payable.retry_payable_notify =
- Box::new(NotifyHandleMock::default().notify_params(¬ify_params_arc));
+ let notify_later_params_arc = Arc::new(Mutex::new(vec![]));
+ scan_schedulers.payable.retry_payable_notify_later = Box::new(
+ NotifyLaterHandleMock::default().notify_later_params(¬ify_later_params_arc),
+ );
// Assertions
Box::new(move |response_skeleton_opt| {
- let notify_params = notify_params_arc.lock().unwrap();
+ let notify_later_params = notify_later_params_arc.lock().unwrap();
match response_skeleton_opt {
None => {
// Response skeleton must be None
assert_eq!(
- *notify_params,
- vec![ScanForRetryPayables {
- response_skeleton_opt: None
- }]
+ *notify_later_params,
+ vec![(
+ ScanForRetryPayables {
+ response_skeleton_opt: None
+ },
+ Duration::from_secs(5 * 60)
+ )]
)
}
Some(_) => {
assert!(
- notify_params.is_empty(),
+ notify_later_params.is_empty(),
"Should be empty but contained {:?}",
- notify_params
+ notify_later_params
)
}
}
diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs
index 787b9a8b2..d14814a5b 100644
--- a/node/src/accountant/scanners/mod.rs
+++ b/node/src/accountant/scanners/mod.rs
@@ -122,7 +122,10 @@ impl Scanners {
});
}
- Self::start_correct_payable_scanner::(
+ <(dyn MultistageDualPayableScanner) as StartableScanner<
+ ScanForNewPayables,
+ InitialTemplatesMessage,
+ >>::start_scan(
&mut *self.payable,
wallet,
timestamp,
@@ -150,7 +153,10 @@ impl Scanners {
)
}
- Self::start_correct_payable_scanner::(
+ <(dyn MultistageDualPayableScanner) as StartableScanner<
+ ScanForRetryPayables,
+ InitialTemplatesMessage,
+ >>::start_scan(
&mut *self.payable,
wallet,
timestamp,
@@ -168,10 +174,14 @@ impl Scanners {
automatic_scans_enabled: bool,
) -> Result {
let triggered_manually = response_skeleton_opt.is_some();
- self.check_general_conditions_for_pending_payable_scan(
- triggered_manually,
- automatic_scans_enabled,
- )?;
+ if triggered_manually && automatic_scans_enabled {
+ return Err(StartScanError::ManualTriggerError(
+ ManulTriggerError::AutomaticScanConflict,
+ ));
+ }
+
+ self.check_pending_payable_existence(triggered_manually)?;
+
match (
self.pending_payable.scan_started_at(),
self.payable.scan_started_at(),
@@ -256,6 +266,7 @@ impl Scanners {
}
pub fn acknowledge_scan_error(&mut self, error: &ScanError, logger: &Logger) {
+ debug!(logger, "Acknowledging a scan that couldn't finish");
match error.scan_type {
DetailedScanType::NewPayables | DetailedScanType::RetryPayables => {
self.payable.mark_as_ended(logger)
@@ -298,41 +309,14 @@ impl Scanners {
self.initial_pending_payable_scan = false
}
- // This is a helper function reducing a boilerplate of complex trait resolving where
- // the compiler requires to specify which trigger message distinguishes the scan to run.
- // The payable scanner offers two modes through doubled implementations of StartableScanner
- // which uses the trigger message type as the only distinction between them.
- fn start_correct_payable_scanner<'a, TriggerMessage>(
- scanner: &'a mut (dyn MultistageDualPayableScanner + 'a),
- wallet: &Wallet,
- timestamp: SystemTime,
- response_skeleton_opt: Option,
- logger: &Logger,
- ) -> Result
- where
- TriggerMessage: Message,
- (dyn MultistageDualPayableScanner + 'a):
- StartableScanner,
- {
- <(dyn MultistageDualPayableScanner + 'a) as StartableScanner<
- TriggerMessage,
- InitialTemplatesMessage,
- >>::start_scan(scanner, wallet, timestamp, response_skeleton_opt, logger)
- }
-
- fn check_general_conditions_for_pending_payable_scan(
+ fn check_pending_payable_existence(
&mut self,
triggered_manually: bool,
- automatic_scans_enabled: bool,
) -> Result<(), StartScanError> {
- if triggered_manually && automatic_scans_enabled {
- return Err(StartScanError::ManualTriggerError(
- ManulTriggerError::AutomaticScanConflict,
- ));
- }
if self.initial_pending_payable_scan {
return Ok(());
}
+
if triggered_manually && !self.aware_of_unresolved_pending_payable {
return Err(StartScanError::ManualTriggerError(
ManulTriggerError::UnnecessaryRequest {
@@ -794,11 +778,11 @@ mod tests {
false
);
let dumped_records = pending_payable_scanner
- .yet_unproven_failed_payables
+ .suspected_failed_payables
.dump_cache();
assert!(
dumped_records.is_empty(),
- "There should be no yet unproven failures but found {:?}.",
+ "There should be no suspected failures but found {:?}.",
dumped_records
);
assert_eq!(
@@ -1199,7 +1183,9 @@ mod tests {
);
TestLogHandler::new().assert_logs_match_in_order(vec![
&format!("INFO: {test_name}: Scanning for pending payable"),
- &format!("DEBUG: {test_name}: Found 1 pending payables and 1 unfinalized failures to process"),
+ &format!(
+ "DEBUG: {test_name}: Found 1 pending payables and 1 suspected failures to process"
+ ),
])
}
@@ -1340,8 +1326,8 @@ mod tests {
#[test]
#[should_panic(
- expected = "internal error: entered unreachable code: Automatic pending payable \
- scan should never start if there are no pending payables to process."
+ expected = "internal error: entered unreachable code: Automatic pending payable scan should \
+ never start if there are no pending payables to process."
)]
fn pending_payable_scanner_bumps_into_zero_pending_payable_awareness_in_the_automatic_mode() {
let consuming_wallet = make_paying_wallet(b"consuming");
@@ -1360,11 +1346,12 @@ mod tests {
}
#[test]
- fn check_general_conditions_for_pending_payable_scan_if_it_is_initial_pending_payable_scan() {
+ fn check_pending_payable_existence_for_initial_pending_payable_scan_and_zero_awareness() {
let mut subject = make_dull_subject();
+ subject.aware_of_unresolved_pending_payable = false;
subject.initial_pending_payable_scan = true;
- let result = subject.check_general_conditions_for_pending_payable_scan(false, true);
+ let result = subject.check_pending_payable_existence(false);
assert_eq!(result, Ok(()));
assert_eq!(subject.initial_pending_payable_scan, true);
diff --git a/node/src/accountant/scanners/payable_scanner/mod.rs b/node/src/accountant/scanners/payable_scanner/mod.rs
index b82d2c46e..4c9ac9804 100644
--- a/node/src/accountant/scanners/payable_scanner/mod.rs
+++ b/node/src/accountant/scanners/payable_scanner/mod.rs
@@ -251,6 +251,10 @@ impl PayableScanner {
self.insert_records_in_sent_payables(&batch_results.sent_txs);
}
if failed > 0 {
+ debug!(
+ logger,
+ "Recording failed txs: {:?}", batch_results.failed_txs
+ );
self.insert_records_in_failed_payables(&batch_results.failed_txs);
}
}
diff --git a/node/src/accountant/scanners/payable_scanner/start_scan.rs b/node/src/accountant/scanners/payable_scanner/start_scan.rs
index 35cbd3ab2..2684407f7 100644
--- a/node/src/accountant/scanners/payable_scanner/start_scan.rs
+++ b/node/src/accountant/scanners/payable_scanner/start_scan.rs
@@ -66,7 +66,7 @@ impl StartableScanner for Payable
info!(logger, "Scanning for retry payables");
let failed_txs = self.get_txs_to_retry();
let amount_from_payables = self.find_amount_from_payables(&failed_txs);
- let retry_tx_templates = RetryTxTemplates::new(&failed_txs, &amount_from_payables);
+ let retry_tx_templates = RetryTxTemplates::new(&failed_txs, &amount_from_payables, logger);
Ok(InitialTemplatesMessage {
initial_templates: Either::Right(retry_tx_templates),
@@ -89,7 +89,7 @@ mod tests {
use crate::accountant::scanners::payable_scanner::tx_templates::initial::retry::{
RetryTxTemplate, RetryTxTemplates,
};
- use crate::accountant::scanners::Scanners;
+ use crate::accountant::scanners::payable_scanner::MultistageDualPayableScanner;
use crate::accountant::test_utils::{
make_payable_account, FailedPayableDaoMock, PayableDaoMock,
};
@@ -144,7 +144,10 @@ mod tests {
.payable_dao(payable_dao)
.build();
- let result = Scanners::start_correct_payable_scanner::(
+ let result = <(dyn MultistageDualPayableScanner) as StartableScanner<
+ ScanForRetryPayables,
+ InitialTemplatesMessage,
+ >>::start_scan(
&mut subject,
&consuming_wallet,
timestamp,
@@ -157,11 +160,8 @@ mod tests {
let retrieve_payables_params = retrieve_payables_params_arc.lock().unwrap();
let expected_tx_templates = {
let mut tx_template_1 = RetryTxTemplate::from(&failed_tx_1);
- tx_template_1.base.amount_in_wei =
- tx_template_1.base.amount_in_wei + payable_account_1.balance_wei;
-
+ tx_template_1.base.amount_in_wei = payable_account_1.balance_wei;
let tx_template_2 = RetryTxTemplate::from(&failed_tx_2);
-
RetryTxTemplates(vec![tx_template_1, tx_template_2])
};
assert_eq!(
diff --git a/node/src/accountant/scanners/payable_scanner/tx_templates/initial/retry.rs b/node/src/accountant/scanners/payable_scanner/tx_templates/initial/retry.rs
index 9990635cd..8157a373b 100644
--- a/node/src/accountant/scanners/payable_scanner/tx_templates/initial/retry.rs
+++ b/node/src/accountant/scanners/payable_scanner/tx_templates/initial/retry.rs
@@ -1,6 +1,7 @@
// Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved.
use crate::accountant::db_access_objects::failed_payable_dao::FailedTx;
use crate::accountant::scanners::payable_scanner::tx_templates::BaseTxTemplate;
+use masq_lib::logger::Logger;
use std::collections::{BTreeSet, HashMap};
use std::ops::{Deref, DerefMut};
use web3::types::Address;
@@ -13,11 +14,25 @@ pub struct RetryTxTemplate {
}
impl RetryTxTemplate {
- pub fn new(failed_tx: &FailedTx, payable_scan_amount_opt: Option) -> Self {
+ pub fn new(
+ failed_tx: &FailedTx,
+ updated_payable_balance_opt: Option,
+ logger: &Logger,
+ ) -> Self {
let mut retry_template = RetryTxTemplate::from(failed_tx);
- if let Some(payable_scan_amount) = payable_scan_amount_opt {
- retry_template.base.amount_in_wei += payable_scan_amount;
+ debug!(logger, "Tx to retry {:?}", failed_tx);
+
+ if let Some(updated_payable_balance) = updated_payable_balance_opt {
+ debug!(
+ logger,
+ "Updating the pay for {:?} from former {} to latest accounted balance {} of minor",
+ failed_tx.receiver_address,
+ failed_tx.amount_minor,
+ updated_payable_balance
+ );
+
+ retry_template.base.amount_in_wei = updated_payable_balance;
}
retry_template
@@ -44,6 +59,7 @@ impl RetryTxTemplates {
pub fn new(
txs_to_retry: &BTreeSet,
amounts_from_payables: &HashMap,
+ logger: &Logger,
) -> Self {
Self(
txs_to_retry
@@ -52,7 +68,7 @@ impl RetryTxTemplates {
let payable_scan_amount_opt = amounts_from_payables
.get(&tx_to_retry.receiver_address)
.copied();
- RetryTxTemplate::new(tx_to_retry, payable_scan_amount_opt)
+ RetryTxTemplate::new(tx_to_retry, payable_scan_amount_opt, logger)
})
.collect(),
)
@@ -98,6 +114,49 @@ mod tests {
};
use crate::accountant::scanners::payable_scanner::tx_templates::BaseTxTemplate;
use crate::blockchain::test_utils::{make_address, make_tx_hash};
+ use masq_lib::logger::Logger;
+
+ #[test]
+ fn retry_tx_template_constructor_works() {
+ let receiver_address = make_address(42);
+ let amount_in_wei = 1_000_000;
+ let gas_price = 20_000_000_000;
+ let nonce = 123;
+ let tx_hash = make_tx_hash(789);
+ let failed_tx = FailedTx {
+ hash: tx_hash,
+ receiver_address,
+ amount_minor: amount_in_wei,
+ gas_price_minor: gas_price,
+ nonce,
+ timestamp: 1234567,
+ reason: FailureReason::PendingTooLong,
+ status: FailureStatus::RetryRequired,
+ };
+ let logger = Logger::new("test");
+ let fetched_balance_from_payable_table_opt_1 = None;
+ let fetched_balance_from_payable_table_opt_2 = Some(1_234_567);
+
+ let result_1 = RetryTxTemplate::new(
+ &failed_tx,
+ fetched_balance_from_payable_table_opt_1,
+ &logger,
+ );
+ let result_2 = RetryTxTemplate::new(
+ &failed_tx,
+ fetched_balance_from_payable_table_opt_2,
+ &logger,
+ );
+
+ let assert = |result: RetryTxTemplate, expected_amount_in_wei: u128| {
+ assert_eq!(result.base.receiver_address, receiver_address);
+ assert_eq!(result.base.amount_in_wei, expected_amount_in_wei);
+ assert_eq!(result.prev_gas_price_wei, gas_price);
+ assert_eq!(result.prev_nonce, nonce);
+ };
+ assert(result_1, amount_in_wei);
+ assert(result_2, fetched_balance_from_payable_table_opt_2.unwrap());
+ }
#[test]
fn retry_tx_template_can_be_created_from_failed_tx() {
diff --git a/node/src/accountant/scanners/payable_scanner/tx_templates/priced/new.rs b/node/src/accountant/scanners/payable_scanner/tx_templates/priced/new.rs
index 6de54e4c9..200b9602b 100644
--- a/node/src/accountant/scanners/payable_scanner/tx_templates/priced/new.rs
+++ b/node/src/accountant/scanners/payable_scanner/tx_templates/priced/new.rs
@@ -4,7 +4,7 @@ use crate::accountant::scanners::payable_scanner::tx_templates::initial::new::{
NewTxTemplate, NewTxTemplates,
};
use crate::accountant::scanners::payable_scanner::tx_templates::BaseTxTemplate;
-use crate::blockchain::blockchain_bridge::increase_gas_price_by_margin;
+use crate::blockchain::blockchain_bridge::increase_by_percentage;
use masq_lib::logger::Logger;
use std::ops::Deref;
use thousands::Separable;
@@ -63,7 +63,7 @@ impl PricedNewTxTemplates {
ceil: u128,
logger: &Logger,
) -> Self {
- let computed_gas_price_wei = increase_gas_price_by_margin(latest_gas_price_wei);
+ let computed_gas_price_wei = increase_by_percentage(latest_gas_price_wei);
let safe_gas_price_wei = if computed_gas_price_wei > ceil {
warning!(
diff --git a/node/src/accountant/scanners/payable_scanner/tx_templates/priced/retry.rs b/node/src/accountant/scanners/payable_scanner/tx_templates/priced/retry.rs
index 48e41f4b9..3477b206a 100644
--- a/node/src/accountant/scanners/payable_scanner/tx_templates/priced/retry.rs
+++ b/node/src/accountant/scanners/payable_scanner/tx_templates/priced/retry.rs
@@ -4,7 +4,8 @@ use crate::accountant::scanners::payable_scanner::tx_templates::initial::retry::
RetryTxTemplate, RetryTxTemplates,
};
use crate::accountant::scanners::payable_scanner::tx_templates::BaseTxTemplate;
-use crate::blockchain::blockchain_bridge::increase_gas_price_by_margin;
+use crate::blockchain::blockchain_bridge::increase_by_percentage;
+use masq_lib::constants::DEFAULT_GAS_PRICE_RETRY_CONSTANT;
use masq_lib::logger::Logger;
use std::ops::{Deref, DerefMut};
use thousands::Separable;
@@ -34,7 +35,7 @@ impl PricedRetryTxTemplate {
) -> PricedRetryTxTemplate {
let receiver = retry_tx_template.base.receiver_address;
let computed_gas_price_wei =
- Self::compute_gas_price(retry_tx_template.prev_gas_price_wei, latest_gas_price_wei);
+ Self::compute_gas_price(latest_gas_price_wei, retry_tx_template.prev_gas_price_wei);
let safe_gas_price_wei = if computed_gas_price_wei > ceil {
log_builder.push(receiver, computed_gas_price_wei);
@@ -46,10 +47,12 @@ impl PricedRetryTxTemplate {
PricedRetryTxTemplate::new(retry_tx_template, safe_gas_price_wei)
}
- fn compute_gas_price(latest_gas_price_wei: u128, prev_gas_price_wei: u128) -> u128 {
- let gas_price_wei = latest_gas_price_wei.max(prev_gas_price_wei);
-
- increase_gas_price_by_margin(gas_price_wei)
+ pub fn compute_gas_price(latest_gas_price_wei: u128, prev_gas_price_wei: u128) -> u128 {
+ if latest_gas_price_wei >= prev_gas_price_wei {
+ increase_by_percentage(latest_gas_price_wei)
+ } else {
+ prev_gas_price_wei + DEFAULT_GAS_PRICE_RETRY_CONSTANT
+ }
}
}
@@ -167,3 +170,44 @@ impl RetryLogBuilder {
}
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn compute_gas_price_increases_by_percentage_if_latest_is_higher() {
+ let latest_gas_price_wei = 101;
+ let prev_gas_price_wei = 100;
+
+ let computed_gas_price =
+ PricedRetryTxTemplate::compute_gas_price(latest_gas_price_wei, prev_gas_price_wei);
+
+ let expected_gas_price = increase_by_percentage(latest_gas_price_wei);
+ assert_eq!(computed_gas_price, expected_gas_price);
+ }
+
+ #[test]
+ fn compute_gas_price_increases_by_percentage_if_latest_is_equal() {
+ let latest_gas_price_wei = 100;
+ let prev_gas_price_wei = 100;
+
+ let computed_gas_price =
+ PricedRetryTxTemplate::compute_gas_price(latest_gas_price_wei, prev_gas_price_wei);
+
+ let expected_gas_price = increase_by_percentage(latest_gas_price_wei);
+ assert_eq!(computed_gas_price, expected_gas_price);
+ }
+
+ #[test]
+ fn compute_gas_price_increments_previous_by_constant_if_latest_is_lower() {
+ let latest_gas_price_wei = 99;
+ let prev_gas_price_wei = 100;
+
+ let computed_gas_price =
+ PricedRetryTxTemplate::compute_gas_price(latest_gas_price_wei, prev_gas_price_wei);
+
+ let expected_gas_price = prev_gas_price_wei + DEFAULT_GAS_PRICE_RETRY_CONSTANT;
+ assert_eq!(computed_gas_price, expected_gas_price);
+ }
+}
diff --git a/node/src/accountant/scanners/pending_payable_scanner/mod.rs b/node/src/accountant/scanners/pending_payable_scanner/mod.rs
index 499ee0179..4fe12add1 100644
--- a/node/src/accountant/scanners/pending_payable_scanner/mod.rs
+++ b/node/src/accountant/scanners/pending_payable_scanner/mod.rs
@@ -65,7 +65,7 @@ pub struct PendingPayableScanner {
pub failed_payable_dao: Box,
pub financial_statistics: Rc>,
pub current_sent_payables: Box>,
- pub yet_unproven_failed_payables: Box>,
+ pub suspected_failed_payables: Box>,
pub clock: Box,
}
@@ -119,6 +119,8 @@ impl Scanner for PendingPayableScan
let retry_opt = scan_report.requires_payments_retry();
+ debug!(logger, "Payment retry requirement: {:?}", retry_opt);
+
self.process_txs_by_state(scan_report, logger);
self.mark_as_ended(logger);
@@ -136,7 +138,7 @@ impl Scanner for PendingPayableScan
impl CachesEmptiableScanner for PendingPayableScanner {
fn empty_caches(&mut self, logger: &Logger) {
self.current_sent_payables.ensure_empty_cache(logger);
- self.yet_unproven_failed_payables.ensure_empty_cache(logger);
+ self.suspected_failed_payables.ensure_empty_cache(logger);
}
}
@@ -155,14 +157,16 @@ impl PendingPayableScanner {
failed_payable_dao,
financial_statistics,
current_sent_payables: Box::new(CurrentPendingPayables::default()),
- yet_unproven_failed_payables: Box::new(RecheckRequiringFailures::default()),
+ suspected_failed_payables: Box::new(RecheckRequiringFailures::default()),
clock: Box::new(SimpleClockReal::default()),
}
}
fn harvest_tables(&mut self, logger: &Logger) -> Result, StartScanError> {
+ debug!(logger, "Harvesting sent_payable and failed_payable tables");
+
let pending_tx_hashes_opt = self.harvest_pending_payables();
- let failure_hashes_opt = self.harvest_unproven_failures();
+ let failure_hashes_opt = self.harvest_suspected_failures();
if Self::is_there_nothing_to_process(
pending_tx_hashes_opt.as_ref(),
@@ -199,7 +203,7 @@ impl PendingPayableScanner {
Some(pending_tx_hashes)
}
- fn harvest_unproven_failures(&mut self) -> Option> {
+ fn harvest_suspected_failures(&mut self) -> Option> {
let failures = self
.failed_payable_dao
.retrieve_txs(Some(FailureRetrieveCondition::EveryRecheckRequiredRecord))
@@ -211,7 +215,7 @@ impl PendingPayableScanner {
}
let failure_hashes = Self::wrap_hashes(&failures, TxHashByTable::FailedPayable);
- self.yet_unproven_failed_payables.load_cache(failures);
+ self.suspected_failed_payables.load_cache(failures);
Some(failure_hashes)
}
@@ -250,8 +254,8 @@ impl PendingPayableScanner {
fn emptiness_check(&self, msg: &TxReceiptsMessage) {
if msg.results.is_empty() {
panic!(
- "We should never receive an empty list of results. \
- Even receipts that could not be retrieved can be interpreted"
+ "We should never receive an empty list of results. Even receipts that could \
+ not be retrieved can be interpreted"
)
}
}
@@ -325,7 +329,7 @@ impl PendingPayableScanner {
};
self.current_sent_payables.ensure_empty_cache(logger);
- self.yet_unproven_failed_payables.ensure_empty_cache(logger);
+ self.suspected_failed_payables.ensure_empty_cache(logger);
cases
}
@@ -350,10 +354,7 @@ impl PendingPayableScanner {
}
}
TxHashByTable::FailedPayable(tx_hash) => {
- match self
- .yet_unproven_failed_payables
- .get_record_by_hash(tx_hash)
- {
+ match self.suspected_failed_payables.get_record_by_hash(tx_hash) {
Some(failed_tx) => {
cases.push(TxCaseToBeInterpreted::new(
TxByTable::FailedPayable(failed_tx),
@@ -378,10 +379,10 @@ impl PendingPayableScanner {
panic!(
"Looking up '{:?}' in the cache, the record could not be found. Dumping \
- the remaining values. Pending payables: {:?}. Unproven failures: {:?}.",
+ the remaining values. Pending payables: {:?}. Suspected failures: {:?}.",
missing_entry,
rearrange(self.current_sent_payables.dump_cache()),
- rearrange(self.yet_unproven_failed_payables.dump_cache()),
+ rearrange(self.suspected_failed_payables.dump_cache()),
)
}
@@ -396,14 +397,18 @@ impl PendingPayableScanner {
logger: &Logger,
) {
self.handle_tx_failure_reclaims(confirmed_txs.reclaims, logger);
- self.handle_normal_confirmations(confirmed_txs.normal_confirmations, logger);
+ self.handle_standard_confirmations(confirmed_txs.standard_confirmations, logger);
}
fn handle_tx_failure_reclaims(&mut self, reclaimed: Vec, logger: &Logger) {
if reclaimed.is_empty() {
+ debug!(logger, "No failure reclaim to process");
+
return;
}
+ debug!(logger, "Processing failure reclaims: {:?}", reclaimed);
+
let hashes_and_blocks = Self::collect_and_sort_hashes_and_blocks(&reclaimed);
self.replace_sent_tx_records(&reclaimed, &hashes_and_blocks, logger);
@@ -495,11 +500,19 @@ impl PendingPayableScanner {
}
}
- fn handle_normal_confirmations(&mut self, confirmed_txs: Vec, logger: &Logger) {
+ fn handle_standard_confirmations(&mut self, confirmed_txs: Vec, logger: &Logger) {
if confirmed_txs.is_empty() {
+ debug!(logger, "No standard tx confirmations to process");
return;
}
+ debug!(
+ logger,
+ "Processing {} standard tx confirmations",
+ confirmed_txs.len()
+ );
+ trace!(logger, "{:?}", confirmed_txs);
+
self.confirm_transactions(&confirmed_txs);
self.update_tx_blocks(&confirmed_txs, logger);
@@ -616,7 +629,7 @@ impl PendingPayableScanner {
});
self.add_new_failures(grouped_failures.new_failures, logger);
- self.finalize_unproven_failures(grouped_failures.rechecks_completed, logger);
+ self.finalize_suspected_failures(grouped_failures.rechecks_completed, logger);
}
fn add_new_failures(&self, new_failures: Vec, logger: &Logger) {
@@ -632,9 +645,12 @@ impl PendingPayableScanner {
}
if new_failures.is_empty() {
+ debug!(logger, "No reverted txs to process");
return;
}
+ debug!(logger, "Processing reverted txs {:?}", new_failures);
+
let new_failures_btree_set: BTreeSet = new_failures.iter().cloned().collect();
if let Err(e) = self
@@ -665,7 +681,7 @@ impl PendingPayableScanner {
}
}
- fn finalize_unproven_failures(&self, rechecks_completed: Vec, logger: &Logger) {
+ fn finalize_suspected_failures(&self, rechecks_completed: Vec, logger: &Logger) {
fn prepare_hashmap(rechecks_completed: &[TxHash]) -> HashMap {
rechecks_completed
.iter()
@@ -674,9 +690,17 @@ impl PendingPayableScanner {
}
if rechecks_completed.is_empty() {
+ debug!(logger, "No recheck-requiring failures to finalize");
return;
}
+ debug!(
+ logger,
+ "Finalizing {} double-checked failures",
+ rechecks_completed.len()
+ );
+ trace!(logger, "{:?}", rechecks_completed);
+
match self
.failed_payable_dao
.update_statuses(&prepare_hashmap(&rechecks_completed))
@@ -830,7 +854,7 @@ impl PendingPayableScanner {
debug!(
logger,
- "Found {} pending payables and {} unfinalized failures to process",
+ "Found {} pending payables and {} suspected failures to process",
resolve_optional_vec(pending_tx_hashes_opt),
resolve_optional_vec(failure_hashes_opt)
);
@@ -905,7 +929,7 @@ mod tests {
.build();
let logger = Logger::new("start_scan_fills_in_caches_and_returns_msg");
let pending_payable_cache_before = subject.current_sent_payables.dump_cache();
- let failed_payable_cache_before = subject.yet_unproven_failed_payables.dump_cache();
+ let failed_payable_cache_before = subject.suspected_failed_payables.dump_cache();
let result = subject.start_scan(&make_wallet("blah"), SystemTime::now(), None, &logger);
@@ -932,7 +956,7 @@ mod tests {
failed_payable_cache_before
);
let pending_payable_cache_after = subject.current_sent_payables.dump_cache();
- let failed_payable_cache_after = subject.yet_unproven_failed_payables.dump_cache();
+ let failed_payable_cache_after = subject.suspected_failed_payables.dump_cache();
assert_eq!(
pending_payable_cache_after,
hashmap!(sent_tx_hash_1 => sent_tx_1, sent_tx_hash_2 => sent_tx_2)
@@ -1040,7 +1064,7 @@ mod tests {
failed_payable_cache.load_cache(vec![failed_tx_1, failed_tx_2]);
let mut subject = PendingPayableScannerBuilder::new().build();
subject.current_sent_payables = Box::new(pending_payable_cache);
- subject.yet_unproven_failed_payables = Box::new(failed_payable_cache);
+ subject.suspected_failed_payables = Box::new(failed_payable_cache);
let logger = Logger::new("test");
let msg = TxReceiptsMessage {
results: btreemap![TxHashByTable::SentPayable(sent_tx_hash_1) => Ok(
@@ -1061,7 +1085,7 @@ mod tests {
values. Pending payables: [SentTx { hash: 0x0000000000000000000000000000000000000000000000\
000000000000000890, receiver_address: 0x0000000000000000000558000000000558000000, \
amount_minor: 43237380096, timestamp: 29942784, gas_price_minor: 94818816, nonce: 456, \
- status: Pending(Waiting) }]. Unproven failures: [].";
+ status: Pending(Waiting) }]. Suspected failures: [].";
assert_eq!(panic_msg, expected);
}
@@ -1080,7 +1104,7 @@ mod tests {
failed_payable_cache.load_cache(vec![failed_tx_1]);
let mut subject = PendingPayableScannerBuilder::new().build();
subject.current_sent_payables = Box::new(pending_payable_cache);
- subject.yet_unproven_failed_payables = Box::new(failed_payable_cache);
+ subject.suspected_failed_payables = Box::new(failed_payable_cache);
let logger = Logger::new("test");
let msg = TxReceiptsMessage {
results: btreemap![TxHashByTable::SentPayable(sent_tx_hash_1) => Ok(StatusReadFromReceiptCheck::Pending),
@@ -1103,7 +1127,7 @@ mod tests {
Pending(Waiting) }, SentTx { hash: 0x0000000000000000000000000000000000000000000000000000000\
000000315, receiver_address: 0x000000000000000000093f00000000093f000000, amount_minor: \
387532395441, timestamp: 89643024, gas_price_minor: 491169069, nonce: 789, status: \
- Pending(Waiting) }]. Unproven failures: [].";
+ Pending(Waiting) }]. Suspected failures: [].";
assert_eq!(panic_msg, expected);
}
@@ -1720,7 +1744,7 @@ mod tests {
subject.handle_confirmed_transactions(
DetectedConfirmations {
- normal_confirmations: vec![],
+ standard_confirmations: vec![],
reclaims: vec![sent_tx_1.clone(), sent_tx_2.clone()],
},
&logger,
@@ -1785,7 +1809,7 @@ mod tests {
subject.handle_confirmed_transactions(
DetectedConfirmations {
- normal_confirmations: vec![],
+ standard_confirmations: vec![],
reclaims: vec![sent_tx_1.clone(), sent_tx_2.clone()],
},
&Logger::new("test"),
@@ -1832,7 +1856,7 @@ mod tests {
subject.handle_confirmed_transactions(
DetectedConfirmations {
- normal_confirmations: vec![],
+ standard_confirmations: vec![],
reclaims: vec![sent_tx_1.clone(), sent_tx_2.clone()],
},
&Logger::new("test"),
@@ -1854,7 +1878,7 @@ mod tests {
subject.handle_confirmed_transactions(
DetectedConfirmations {
- normal_confirmations: vec![],
+ standard_confirmations: vec![],
reclaims: vec![sent_tx.clone()],
},
&Logger::new("test"),
@@ -1862,9 +1886,9 @@ mod tests {
}
#[test]
- fn handles_normal_confirmations_alone() {
+ fn handles_standard_confirmations_alone() {
init_test_logging();
- let test_name = "handles_normal_confirmations_alone";
+ let test_name = "handles_standard_confirmations_alone";
let transactions_confirmed_params_arc = Arc::new(Mutex::new(vec![]));
let confirm_tx_params_arc = Arc::new(Mutex::new(vec![]));
let payable_dao = PayableDaoMock::default()
@@ -1905,7 +1929,7 @@ mod tests {
subject.handle_confirmed_transactions(
DetectedConfirmations {
- normal_confirmations: vec![sent_tx_1.clone(), sent_tx_2.clone()],
+ standard_confirmations: vec![sent_tx_1.clone(), sent_tx_2.clone()],
reclaims: vec![],
},
&logger,
@@ -1981,7 +2005,7 @@ mod tests {
subject.handle_confirmed_transactions(
DetectedConfirmations {
- normal_confirmations: vec![sent_tx_1.clone()],
+ standard_confirmations: vec![sent_tx_1.clone()],
reclaims: vec![sent_tx_2.clone()],
},
&logger,
@@ -2045,7 +2069,7 @@ mod tests {
subject.handle_confirmed_transactions(
DetectedConfirmations {
- normal_confirmations: vec![sent_tx_1, sent_tx_2],
+ standard_confirmations: vec![sent_tx_1, sent_tx_2],
reclaims: vec![],
},
&Logger::new("test"),
@@ -2071,7 +2095,7 @@ mod tests {
subject.handle_confirmed_transactions(
DetectedConfirmations {
- normal_confirmations: vec![sent_tx],
+ standard_confirmations: vec![sent_tx],
reclaims: vec![],
},
&Logger::new("test"),
@@ -2153,7 +2177,7 @@ mod tests {
subject.handle_confirmed_transactions(
DetectedConfirmations {
- normal_confirmations: vec![sent_tx_1, sent_tx_2],
+ standard_confirmations: vec![sent_tx_1, sent_tx_2],
reclaims: vec![sent_tx_3],
},
&Logger::new(test_name),
diff --git a/node/src/accountant/scanners/pending_payable_scanner/tx_receipt_interpreter.rs b/node/src/accountant/scanners/pending_payable_scanner/tx_receipt_interpreter.rs
index fc16c5713..d04d458b4 100644
--- a/node/src/accountant/scanners/pending_payable_scanner/tx_receipt_interpreter.rs
+++ b/node/src/accountant/scanners/pending_payable_scanner/tx_receipt_interpreter.rs
@@ -31,6 +31,7 @@ impl TxReceiptInterpreter {
pending_payable_scanner: &PendingPayableScanner,
logger: &Logger,
) -> ReceiptScanReport {
+ debug!(logger, "Composing receipt scan report");
let scan_report = ReceiptScanReport::default();
tx_cases
.into_iter()
@@ -162,7 +163,7 @@ impl TxReceiptInterpreter {
scan_report
}
- //TODO: failures handling might need enhancement suggested by GH-693
+ //TODO: if wanted, address GH-693 for more detailed failures
fn handle_reverted_tx(
mut scan_report: ReceiptScanReport,
tx: TxByTable,
@@ -185,7 +186,7 @@ impl TxReceiptInterpreter {
failed_tx.reason,
);
- scan_report.register_finalization_of_unproven_failure(failed_tx.hash);
+ scan_report.register_finalization_of_suspected_failure(failed_tx.hash);
}
}
scan_report
@@ -279,7 +280,7 @@ mod tests {
ReceiptScanReport {
failures: DetectedFailures::default(),
confirmations: DetectedConfirmations {
- normal_confirmations: vec![updated_tx],
+ standard_confirmations: vec![updated_tx],
reclaims: vec![]
}
}
@@ -327,7 +328,7 @@ mod tests {
ReceiptScanReport {
failures: DetectedFailures::default(),
confirmations: DetectedConfirmations {
- normal_confirmations: vec![],
+ standard_confirmations: vec![],
reclaims: vec![sent_tx]
}
}
diff --git a/node/src/accountant/scanners/pending_payable_scanner/utils.rs b/node/src/accountant/scanners/pending_payable_scanner/utils.rs
index 7a1d18eaa..2b8052496 100644
--- a/node/src/accountant/scanners/pending_payable_scanner/utils.rs
+++ b/node/src/accountant/scanners/pending_payable_scanner/utils.rs
@@ -38,7 +38,9 @@ impl ReceiptScanReport {
confirmation_type: ConfirmationType,
) {
match confirmation_type {
- ConfirmationType::Normal => self.confirmations.normal_confirmations.push(confirmed_tx),
+ ConfirmationType::Normal => {
+ self.confirmations.standard_confirmations.push(confirmed_tx)
+ }
ConfirmationType::Reclaim => self.confirmations.reclaims.push(confirmed_tx),
}
}
@@ -49,7 +51,7 @@ impl ReceiptScanReport {
.push(PresortedTxFailure::NewEntry(failed_tx));
}
- pub(super) fn register_finalization_of_unproven_failure(&mut self, tx_hash: TxHash) {
+ pub(super) fn register_finalization_of_suspected_failure(&mut self, tx_hash: TxHash) {
self.failures
.tx_failures
.push(PresortedTxFailure::RecheckCompleted(tx_hash));
@@ -62,13 +64,13 @@ impl ReceiptScanReport {
#[derive(Debug, Default, PartialEq, Eq, Clone)]
pub struct DetectedConfirmations {
- pub normal_confirmations: Vec,
+ pub standard_confirmations: Vec,
pub reclaims: Vec,
}
impl DetectedConfirmations {
pub(super) fn is_empty(&self) -> bool {
- self.normal_confirmations.is_empty() && self.reclaims.is_empty()
+ self.standard_confirmations.is_empty() && self.reclaims.is_empty()
}
}
@@ -410,7 +412,7 @@ mod tests {
#[test]
fn detected_confirmations_is_empty_works() {
let subject = DetectedConfirmations {
- normal_confirmations: vec![],
+ standard_confirmations: vec![],
reclaims: vec![],
};
@@ -462,19 +464,19 @@ mod tests {
];
let detected_confirmations_feeding = vec![
DetectedConfirmations {
- normal_confirmations: vec![],
+ standard_confirmations: vec![],
reclaims: vec![],
},
DetectedConfirmations {
- normal_confirmations: vec![make_sent_tx(456)],
+ standard_confirmations: vec![make_sent_tx(456)],
reclaims: vec![make_sent_tx(999)],
},
DetectedConfirmations {
- normal_confirmations: vec![make_sent_tx(777)],
+ standard_confirmations: vec![make_sent_tx(777)],
reclaims: vec![],
},
DetectedConfirmations {
- normal_confirmations: vec![],
+ standard_confirmations: vec![],
reclaims: vec![make_sent_tx(999)],
},
];
@@ -550,19 +552,19 @@ mod tests {
];
let detected_confirmations_feeding = vec![
DetectedConfirmations {
- normal_confirmations: vec![],
+ standard_confirmations: vec![],
reclaims: vec![],
},
DetectedConfirmations {
- normal_confirmations: vec![make_sent_tx(777)],
+ standard_confirmations: vec![make_sent_tx(777)],
reclaims: vec![make_sent_tx(999)],
},
DetectedConfirmations {
- normal_confirmations: vec![make_sent_tx(777)],
+ standard_confirmations: vec![make_sent_tx(777)],
reclaims: vec![],
},
DetectedConfirmations {
- normal_confirmations: vec![],
+ standard_confirmations: vec![],
reclaims: vec![make_sent_tx(999)],
},
];
@@ -594,15 +596,15 @@ mod tests {
fn requires_payments_retry_says_no() {
let detected_confirmations_feeding = vec![
DetectedConfirmations {
- normal_confirmations: vec![make_sent_tx(777)],
+ standard_confirmations: vec![make_sent_tx(777)],
reclaims: vec![make_sent_tx(999)],
},
DetectedConfirmations {
- normal_confirmations: vec![make_sent_tx(777)],
+ standard_confirmations: vec![make_sent_tx(777)],
reclaims: vec![],
},
DetectedConfirmations {
- normal_confirmations: vec![],
+ standard_confirmations: vec![],
reclaims: vec![make_sent_tx(999)],
},
];
@@ -638,7 +640,7 @@ mod tests {
tx_receipt_rpc_failures: vec![],
},
confirmations: DetectedConfirmations {
- normal_confirmations: vec![],
+ standard_confirmations: vec![],
reclaims: vec![],
},
};
diff --git a/node/src/accountant/scanners/scan_schedulers.rs b/node/src/accountant/scanners/scan_schedulers.rs
index dedfee1e5..5de151c73 100644
--- a/node/src/accountant/scanners/scan_schedulers.rs
+++ b/node/src/accountant/scanners/scan_schedulers.rs
@@ -14,6 +14,7 @@ use masq_lib::logger::Logger;
use masq_lib::messages::ScanType;
use masq_lib::simple_clock::{SimpleClock, SimpleClockReal};
use std::fmt::{Debug, Display, Formatter};
+use std::ops::Div;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
pub struct ScanSchedulers {
@@ -50,28 +51,31 @@ pub enum ScanReschedulingAfterEarlyStop {
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub enum PayableSequenceScanner {
+pub enum UnableToStartScanner {
NewPayables,
RetryPayables,
PendingPayables { initial_pending_payable_scan: bool },
+ Receivables,
}
-impl Display for PayableSequenceScanner {
+impl Display for UnableToStartScanner {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
- PayableSequenceScanner::NewPayables => write!(f, "NewPayables"),
- PayableSequenceScanner::RetryPayables => write!(f, "RetryPayables"),
- PayableSequenceScanner::PendingPayables { .. } => write!(f, "PendingPayables"),
+ UnableToStartScanner::NewPayables => write!(f, "NewPayables"),
+ UnableToStartScanner::RetryPayables => write!(f, "RetryPayables"),
+ UnableToStartScanner::PendingPayables { .. } => write!(f, "PendingPayables"),
+ UnableToStartScanner::Receivables => write!(f, "Receivables"),
}
}
}
-impl From for ScanType {
- fn from(scanner: PayableSequenceScanner) -> Self {
+impl From for ScanType {
+ fn from(scanner: UnableToStartScanner) -> Self {
match scanner {
- PayableSequenceScanner::NewPayables => ScanType::Payables,
- PayableSequenceScanner::RetryPayables => ScanType::Payables,
- PayableSequenceScanner::PendingPayables { .. } => ScanType::PendingPayables,
+ UnableToStartScanner::NewPayables => ScanType::Payables,
+ UnableToStartScanner::RetryPayables => ScanType::Payables,
+ UnableToStartScanner::PendingPayables { .. } => ScanType::PendingPayables,
+ UnableToStartScanner::Receivables => ScanType::Receivables,
}
}
}
@@ -80,23 +84,25 @@ pub struct PayableScanScheduler {
pub new_payable_notify_later: Box>,
pub interval_computer: Box,
pub new_payable_notify: Box>,
- pub retry_payable_notify: Box>,
+ pub retry_payable_notify_later: Box>,
+ pub retry_payable_scan_interval: Duration,
}
impl PayableScanScheduler {
- fn new(new_payable_interval: Duration) -> Self {
+ fn new(payable_scan_interval: Duration) -> Self {
Self {
new_payable_notify_later: Box::new(NotifyLaterHandleReal::default()),
interval_computer: Box::new(NewPayableScanIntervalComputerReal::new(
- new_payable_interval,
+ payable_scan_interval,
)),
new_payable_notify: Box::new(NotifyHandleReal::default()),
- retry_payable_notify: Box::new(NotifyHandleReal::default()),
+ retry_payable_notify_later: Box::new(NotifyLaterHandleReal::default()),
+ retry_payable_scan_interval: payable_scan_interval.div(2),
}
}
pub fn schedule_new_payable_scan(&self, ctx: &mut Context, logger: &Logger) {
- if let ScanTiming::WaitFor(interval) = self.interval_computer.time_until_next_scan() {
+ if let ScanTiming::WaitFor(interval) = self.interval_computer.compute_time_to_next_scan() {
debug!(
logger,
"Scheduling a new-payable scan in {}ms",
@@ -122,7 +128,8 @@ impl PayableScanScheduler {
}
}
- pub fn reset_scan_timer(&mut self) {
+ pub fn reset_scan_timer(&mut self, logger: &Logger) {
+ debug!(logger, "NewPayableScanIntervalComputer timer reset");
self.interval_computer.reset_last_scan_timestamp();
}
@@ -136,18 +143,20 @@ impl PayableScanScheduler {
logger: &Logger,
) {
debug!(logger, "Scheduling a retry-payable scan asap");
+ let delay = self.retry_payable_scan_interval;
- self.retry_payable_notify.notify(
+ let _ = self.retry_payable_notify_later.notify_later(
ScanForRetryPayables {
response_skeleton_opt,
},
+ delay,
ctx,
- )
+ );
}
}
pub trait NewPayableScanIntervalComputer {
- fn time_until_next_scan(&self) -> ScanTiming;
+ fn compute_time_to_next_scan(&self) -> ScanTiming;
fn reset_last_scan_timestamp(&mut self);
@@ -161,7 +170,7 @@ pub struct NewPayableScanIntervalComputerReal {
}
impl NewPayableScanIntervalComputer for NewPayableScanIntervalComputerReal {
- fn time_until_next_scan(&self) -> ScanTiming {
+ fn compute_time_to_next_scan(&self) -> ScanTiming {
let current_time = self.clock.now();
let time_since_last_scan = current_time
.duration_since(self.last_scan_timestamp)
@@ -244,7 +253,7 @@ where
pub trait RescheduleScanOnErrorResolver {
fn resolve_rescheduling_on_error(
&self,
- scanner: PayableSequenceScanner,
+ scanner: UnableToStartScanner,
error: &StartScanError,
is_externally_triggered: bool,
logger: &Logger,
@@ -257,25 +266,28 @@ pub struct RescheduleScanOnErrorResolverReal {}
impl RescheduleScanOnErrorResolver for RescheduleScanOnErrorResolverReal {
fn resolve_rescheduling_on_error(
&self,
- scanner: PayableSequenceScanner,
+ scanner: UnableToStartScanner,
error: &StartScanError,
is_externally_triggered: bool,
logger: &Logger,
) -> ScanReschedulingAfterEarlyStop {
let reschedule_hint = match scanner {
- PayableSequenceScanner::NewPayables => {
+ UnableToStartScanner::NewPayables => {
Self::resolve_new_payables(error, is_externally_triggered)
}
- PayableSequenceScanner::RetryPayables => {
+ UnableToStartScanner::RetryPayables => {
Self::resolve_retry_payables(error, is_externally_triggered)
}
- PayableSequenceScanner::PendingPayables {
+ UnableToStartScanner::PendingPayables {
initial_pending_payable_scan,
} => Self::resolve_pending_payables(
error,
initial_pending_payable_scan,
is_externally_triggered,
),
+ UnableToStartScanner::Receivables => {
+ Self::resolve_receivables(error, is_externally_triggered)
+ }
};
Self::log_rescheduling(scanner, is_externally_triggered, logger, &reschedule_hint);
@@ -290,14 +302,18 @@ impl RescheduleScanOnErrorResolverReal {
is_externally_triggered: bool,
) -> ScanReschedulingAfterEarlyStop {
if is_externally_triggered {
- ScanReschedulingAfterEarlyStop::DoNotSchedule
- } else if matches!(err, StartScanError::ScanAlreadyRunning { .. }) {
- unreachable!(
- "an automatic scan of NewPayableScanner should never interfere with itself {:?}",
- err
- )
- } else {
- ScanReschedulingAfterEarlyStop::Schedule(ScanType::Payables)
+ return ScanReschedulingAfterEarlyStop::DoNotSchedule;
+ }
+
+ match err {
+ StartScanError::CalledFromNullScanner => ScanReschedulingAfterEarlyStop::DoNotSchedule,
+ StartScanError::ScanAlreadyRunning { .. } => {
+ unreachable!(
+ "an automatic scan of NewPayableScanner should never interfere with itself {:?}",
+ err
+ )
+ }
+ _ => ScanReschedulingAfterEarlyStop::Schedule(ScanType::Payables),
}
}
@@ -324,18 +340,26 @@ impl RescheduleScanOnErrorResolverReal {
is_externally_triggered: bool,
) -> ScanReschedulingAfterEarlyStop {
if is_externally_triggered {
- ScanReschedulingAfterEarlyStop::DoNotSchedule
- } else if err == &StartScanError::NothingToProcess {
- if initial_pending_payable_scan {
+ return ScanReschedulingAfterEarlyStop::DoNotSchedule;
+ }
+
+ match err {
+ StartScanError::NothingToProcess => {
+ if !initial_pending_payable_scan {
+ unreachable!(
+ "the automatic pending payable scan should always be requested only in need, \
+ which contradicts the current StartScanError::NothingToProcess"
+ )
+ }
ScanReschedulingAfterEarlyStop::Schedule(ScanType::Payables)
- } else {
- unreachable!(
- "the automatic pending payable scan should always be requested only in need, \
- which contradicts the current StartScanError::NothingToProcess"
- )
}
- } else if err == &StartScanError::NoConsumingWalletFound {
- if initial_pending_payable_scan {
+ StartScanError::NoConsumingWalletFound => {
+ if !initial_pending_payable_scan {
+ unreachable!(
+ "PendingPayableScanner called later than the initial attempt, but \
+ the consuming wallet is still missing; this should not be possible"
+ )
+ }
// Cannot deduce there are strayed pending payables from the previous Node's run
// (StartScanError::NoConsumingWalletFound is thrown before
// StartScanError::NothingToProcess can be evaluated); but may be cautious and
@@ -344,22 +368,34 @@ impl RescheduleScanOnErrorResolverReal {
// TODO Correctly, a check-point during the bootstrap, not allowing to come
// this far, should be the solution. Part of the issue mentioned in GH-799
ScanReschedulingAfterEarlyStop::Schedule(ScanType::PendingPayables)
- } else {
- unreachable!(
- "PendingPayableScanner called later than the initial attempt, but \
- the consuming wallet is still missing; this should not be possible"
- )
}
- } else {
- unreachable!(
+ StartScanError::CalledFromNullScanner => ScanReschedulingAfterEarlyStop::DoNotSchedule,
+ _ => unreachable!(
"{:?} should be impossible with PendingPayableScanner in automatic mode",
err
- )
+ ),
+ }
+ }
+
+ fn resolve_receivables(
+ err: &StartScanError,
+ is_externally_triggered: bool,
+ ) -> ScanReschedulingAfterEarlyStop {
+ if is_externally_triggered {
+ return ScanReschedulingAfterEarlyStop::DoNotSchedule;
+ }
+
+ match err {
+ StartScanError::CalledFromNullScanner => ScanReschedulingAfterEarlyStop::DoNotSchedule,
+ _ => unreachable!(
+ "{:?} should be impossible with ReceivableScanner in automatic mode",
+ err
+ ),
}
}
fn log_rescheduling(
- scanner: PayableSequenceScanner,
+ scanner: UnableToStartScanner,
is_externally_triggered: bool,
logger: &Logger,
reschedule_hint: &ScanReschedulingAfterEarlyStop,
@@ -383,8 +419,8 @@ impl RescheduleScanOnErrorResolverReal {
#[cfg(test)]
mod tests {
use crate::accountant::scanners::scan_schedulers::{
- NewPayableScanIntervalComputer, NewPayableScanIntervalComputerReal, PayableSequenceScanner,
- ScanReschedulingAfterEarlyStop, ScanSchedulers, ScanTiming,
+ NewPayableScanIntervalComputer, NewPayableScanIntervalComputerReal,
+ ScanReschedulingAfterEarlyStop, ScanSchedulers, ScanTiming, UnableToStartScanner,
};
use crate::accountant::scanners::test_utils::NewPayableScanIntervalComputerMock;
use crate::accountant::scanners::{ManulTriggerError, StartScanError};
@@ -463,7 +499,7 @@ mod tests {
subject.scan_interval = standard_interval;
subject.last_scan_timestamp = past_instant;
- let result = subject.time_until_next_scan();
+ let result = subject.compute_time_to_next_scan();
assert_eq!(
result,
@@ -498,7 +534,7 @@ mod tests {
subject.scan_interval = standard_interval;
subject.last_scan_timestamp = past_instant;
- let result = subject.time_until_next_scan();
+ let result = subject.compute_time_to_next_scan();
assert_eq!(
result,
@@ -518,7 +554,7 @@ mod tests {
subject.scan_interval = Duration::from_secs(180);
subject.clock = Box::new(SimpleClockMock::default().now_result(now));
- let result = subject.time_until_next_scan();
+ let result = subject.compute_time_to_next_scan();
assert_eq!(
result,
@@ -564,7 +600,7 @@ mod tests {
subject.clock = Box::new(SimpleClockMock::default().now_result(now));
subject.last_scan_timestamp = now.checked_add(Duration::from_secs(1)).unwrap();
- let _ = subject.time_until_next_scan();
+ let _ = subject.compute_time_to_next_scan();
}
#[test]
@@ -612,7 +648,7 @@ mod tests {
.reset_last_scan_timestamp_params(&reset_last_scan_timestamp_params_arc),
);
- subject.payable.reset_scan_timer();
+ subject.payable.reset_scan_timer(&Logger::new("test"));
let reset_last_scan_timestamp_params = reset_last_scan_timestamp_params_arc.lock().unwrap();
assert_eq!(*reset_last_scan_timestamp_params, vec![()])
@@ -642,7 +678,6 @@ mod tests {
StartScanError::CalledFromNullScanner
];
-
let mut check_vec = candidates
.iter()
.fold(vec![],|mut acc, current|{
@@ -703,14 +738,14 @@ mod tests {
test_what_if_externally_triggered(
&format!("{}(initial_pending_payable_scan = false)", test_name),
&subject,
- PayableSequenceScanner::PendingPayables {
+ UnableToStartScanner::PendingPayables {
initial_pending_payable_scan: false,
},
);
test_what_if_externally_triggered(
&format!("{}(initial_pending_payable_scan = true)", test_name),
&subject,
- PayableSequenceScanner::PendingPayables {
+ UnableToStartScanner::PendingPayables {
initial_pending_payable_scan: true,
},
);
@@ -719,7 +754,7 @@ mod tests {
fn test_what_if_externally_triggered(
test_name: &str,
subject: &ScanSchedulers,
- scanner: PayableSequenceScanner,
+ scanner: UnableToStartScanner,
) {
init_test_logging();
let logger = Logger::new(test_name);
@@ -749,17 +784,16 @@ mod tests {
}
#[test]
- fn resolve_error_for_pending_payables_if_nothing_to_process_and_initial_pending_payable_scan_true(
- ) {
+ fn resolve_error_if_nothing_to_process_and_initial_pending_payable_scan_true() {
init_test_logging();
let subject = ScanSchedulers::new(*TEST_SCAN_INTERVALS, true);
- let test_name = "resolve_error_for_pending_payables_if_nothing_to_process_and_initial_pending_payable_scan_true";
+ let test_name = "resolve_error_if_nothing_to_process_and_initial_pending_payable_scan_true";
let logger = Logger::new(test_name);
let result = subject
.reschedule_on_error_resolver
.resolve_rescheduling_on_error(
- PayableSequenceScanner::PendingPayables {
+ UnableToStartScanner::PendingPayables {
initial_pending_payable_scan: true,
},
&StartScanError::NothingToProcess,
@@ -792,7 +826,7 @@ mod tests {
let _ = subject
.reschedule_on_error_resolver
.resolve_rescheduling_on_error(
- PayableSequenceScanner::PendingPayables {
+ UnableToStartScanner::PendingPayables {
initial_pending_payable_scan: false,
},
&StartScanError::NothingToProcess,
@@ -802,12 +836,13 @@ mod tests {
}
#[test]
- fn resolve_error_for_pending_p_if_no_consuming_wallet_found_in_initial_pending_payable_scan() {
+ fn resolve_error_if_no_consuming_wallet_found_in_initial_pending_payable_scan() {
init_test_logging();
- let test_name = "resolve_error_for_pending_p_if_no_consuming_wallet_found_in_initial_pending_payable_scan";
+ let test_name =
+ "resolve_error_if_no_consuming_wallet_found_in_initial_pending_payable_scan";
let logger = Logger::new(test_name);
let subject = ScanSchedulers::new(*TEST_SCAN_INTERVALS, true);
- let scanner = PayableSequenceScanner::PendingPayables {
+ let scanner = UnableToStartScanner::PendingPayables {
initial_pending_payable_scan: true,
};
@@ -839,9 +874,10 @@ mod tests {
than the initial attempt, but the consuming wallet is still missing; this should not be \
possible"
)]
- fn pending_p_scan_attempt_if_no_consuming_wallet_found_mustnt_happen_if_not_initial_scan() {
+ fn pending_payable_scan_attempt_if_no_consuming_wallet_found_mustnt_happen_if_not_initial_scan()
+ {
let subject = ScanSchedulers::new(*TEST_SCAN_INTERVALS, true);
- let scanner = PayableSequenceScanner::PendingPayables {
+ let scanner = UnableToStartScanner::PendingPayables {
initial_pending_payable_scan: false,
};
@@ -855,6 +891,20 @@ mod tests {
);
}
+ #[test]
+ fn resolve_error_for_pending_payables_if_null_scanner_is_used_in_automatic_mode() {
+ test_resolve_error_if_null_scanner_is_used_in_automatic_mode(
+ UnableToStartScanner::PendingPayables {
+ initial_pending_payable_scan: true,
+ },
+ );
+ test_resolve_error_if_null_scanner_is_used_in_automatic_mode(
+ UnableToStartScanner::PendingPayables {
+ initial_pending_payable_scan: false,
+ },
+ );
+ }
+
#[test]
fn resolve_error_for_pending_payables_forbidden_states() {
fn test_forbidden_states(
@@ -867,7 +917,7 @@ mod tests {
subject
.reschedule_on_error_resolver
.resolve_rescheduling_on_error(
- PayableSequenceScanner::PendingPayables {
+ UnableToStartScanner::PendingPayables {
initial_pending_payable_scan,
},
*error,
@@ -894,6 +944,7 @@ mod tests {
let inputs = ListOfStartScanErrors::default().eliminate_already_tested_variants(vec![
StartScanError::NothingToProcess,
StartScanError::NoConsumingWalletFound,
+ StartScanError::CalledFromNullScanner,
]);
let subject = ScanSchedulers::new(*TEST_SCAN_INTERVALS, true);
@@ -910,7 +961,7 @@ mod tests {
test_what_if_externally_triggered(
test_name,
&subject,
- PayableSequenceScanner::RetryPayables {},
+ UnableToStartScanner::RetryPayables {},
);
}
@@ -923,7 +974,7 @@ mod tests {
subject
.reschedule_on_error_resolver
.resolve_rescheduling_on_error(
- PayableSequenceScanner::RetryPayables,
+ UnableToStartScanner::RetryPayables,
error,
false,
&Logger::new("test"),
@@ -946,15 +997,14 @@ mod tests {
}
#[test]
- fn resolve_rescheduling_on_error_works_for_new_payables_if_externally_triggered() {
- let test_name =
- "resolve_rescheduling_on_error_works_for_new_payables_if_externally_triggered";
+ fn resolve_rescheduling_on_error_for_new_payables_if_externally_triggered() {
+ let test_name = "resolve_rescheduling_on_error_for_new_payables_if_externally_triggered";
let subject = ScanSchedulers::new(*TEST_SCAN_INTERVALS, true);
test_what_if_externally_triggered(
test_name,
&subject,
- PayableSequenceScanner::NewPayables {},
+ UnableToStartScanner::NewPayables {},
);
}
@@ -963,13 +1013,13 @@ mod tests {
expected = "internal error: entered unreachable code: an automatic scan of NewPayableScanner \
should never interfere with itself ScanAlreadyRunning { cross_scan_cause_opt: None, started_at:"
)]
- fn resolve_hint_for_new_payables_if_scan_is_already_running_error_and_is_automatic_scan() {
+ fn resolve_hint_for_new_payables_error_if_scan_is_already_running_in_automatic_scan() {
let subject = ScanSchedulers::new(*TEST_SCAN_INTERVALS, true);
let _ = subject
.reschedule_on_error_resolver
.resolve_rescheduling_on_error(
- PayableSequenceScanner::NewPayables,
+ UnableToStartScanner::NewPayables,
&StartScanError::ScanAlreadyRunning {
cross_scan_cause_opt: None,
started_at: SystemTime::now(),
@@ -979,10 +1029,18 @@ mod tests {
);
}
+ #[test]
+ fn resolve_error_for_new_payables_if_null_scanner_is_used_in_automatic_mode() {
+ test_resolve_error_if_null_scanner_is_used_in_automatic_mode(
+ UnableToStartScanner::NewPayables,
+ );
+ }
+
#[test]
fn resolve_new_payables_with_error_cases_resulting_in_future_rescheduling() {
let test_name = "resolve_new_payables_with_error_cases_resulting_in_future_rescheduling";
let inputs = ListOfStartScanErrors::default().eliminate_already_tested_variants(vec![
+ StartScanError::CalledFromNullScanner,
StartScanError::ScanAlreadyRunning {
cross_scan_cause_opt: None,
started_at: SystemTime::now(),
@@ -996,7 +1054,7 @@ mod tests {
let result = subject
.reschedule_on_error_resolver
.resolve_rescheduling_on_error(
- PayableSequenceScanner::NewPayables,
+ UnableToStartScanner::NewPayables,
*error,
false,
&logger,
@@ -1015,27 +1073,100 @@ mod tests {
})
}
+ #[test]
+ fn resolve_rescheduling_on_error_for_receivables_if_externally_triggered() {
+ let test_name = "resolve_rescheduling_on_error_for_receivables_if_externally_triggered";
+ let subject = ScanSchedulers::new(*TEST_SCAN_INTERVALS, true);
+
+ test_what_if_externally_triggered(test_name, &subject, UnableToStartScanner::Receivables);
+ }
+
+ #[test]
+ fn resolve_error_for_receivables_if_null_scanner_is_used_in_automatic_mode() {
+ test_resolve_error_if_null_scanner_is_used_in_automatic_mode(
+ UnableToStartScanner::Receivables,
+ );
+ }
+
+ #[test]
+ fn resolve_error_for_receivables_all_fatal_cases_in_automatic_mode() {
+ let inputs = ListOfStartScanErrors::default()
+ .eliminate_already_tested_variants(vec![StartScanError::CalledFromNullScanner]);
+ let subject = ScanSchedulers::new(*TEST_SCAN_INTERVALS, true);
+
+ inputs.errors.iter().for_each(|error| {
+ let panic = catch_unwind(AssertUnwindSafe(|| {
+ subject
+ .reschedule_on_error_resolver
+ .resolve_rescheduling_on_error(
+ UnableToStartScanner::Receivables,
+ *error,
+ false,
+ &Logger::new("test"),
+ )
+ }))
+ .unwrap_err();
+
+ let panic_msg = panic.downcast_ref::().unwrap();
+ let expected_msg = format!(
+ "internal error: entered unreachable code: {:?} should be impossible with \
+ ReceivableScanner in automatic mode",
+ error
+ );
+ assert_eq!(
+ panic_msg, &expected_msg,
+ "We expected '{}' but got '{}'",
+ expected_msg, panic_msg
+ )
+ })
+ }
+
#[test]
fn conversion_between_hintable_scanner_and_scan_type_works() {
assert_eq!(
- ScanType::from(PayableSequenceScanner::NewPayables),
+ ScanType::from(UnableToStartScanner::NewPayables),
ScanType::Payables
);
assert_eq!(
- ScanType::from(PayableSequenceScanner::RetryPayables),
+ ScanType::from(UnableToStartScanner::RetryPayables),
ScanType::Payables
);
assert_eq!(
- ScanType::from(PayableSequenceScanner::PendingPayables {
+ ScanType::from(UnableToStartScanner::PendingPayables {
initial_pending_payable_scan: false
}),
ScanType::PendingPayables
);
assert_eq!(
- ScanType::from(PayableSequenceScanner::PendingPayables {
+ ScanType::from(UnableToStartScanner::PendingPayables {
initial_pending_payable_scan: true
}),
ScanType::PendingPayables
);
+ assert_eq!(
+ ScanType::from(UnableToStartScanner::Receivables),
+ ScanType::Receivables
+ )
+ }
+
+ fn test_resolve_error_if_null_scanner_is_used_in_automatic_mode(scanner: UnableToStartScanner) {
+ let subject = ScanSchedulers::new(*TEST_SCAN_INTERVALS, true);
+
+ let result = subject
+ .reschedule_on_error_resolver
+ .resolve_rescheduling_on_error(
+ scanner,
+ &StartScanError::CalledFromNullScanner,
+ false,
+ &Logger::new("test"),
+ );
+
+ assert_eq!(
+ result,
+ ScanReschedulingAfterEarlyStop::DoNotSchedule,
+ "We expected DoNotSchedule but got {:?} for {:?}",
+ result,
+ scanner
+ );
}
}
diff --git a/node/src/accountant/scanners/test_utils.rs b/node/src/accountant/scanners/test_utils.rs
index 08325dedc..031e1bb7d 100644
--- a/node/src/accountant/scanners/test_utils.rs
+++ b/node/src/accountant/scanners/test_utils.rs
@@ -18,8 +18,8 @@ use crate::accountant::scanners::pending_payable_scanner::{
CachesEmptiableScanner, ExtendedPendingPayablePrivateScanner,
};
use crate::accountant::scanners::scan_schedulers::{
- NewPayableScanIntervalComputer, PayableSequenceScanner, RescheduleScanOnErrorResolver,
- ScanReschedulingAfterEarlyStop, ScanTiming,
+ NewPayableScanIntervalComputer, RescheduleScanOnErrorResolver, ScanReschedulingAfterEarlyStop,
+ ScanTiming, UnableToStartScanner,
};
use crate::accountant::scanners::{
PendingPayableScanner, PrivateScanner, RealScannerMarker, ReceivableScanner, Scanner,
@@ -336,15 +336,20 @@ impl ScannerMockMarker for ScannerMock>>,
- time_until_next_scan_results: RefCell>,
+ compute_time_to_next_scan_params: Arc>>,
+ compute_time_to_next_scan_results: RefCell>,
reset_last_scan_timestamp_params: Arc>>,
}
impl NewPayableScanIntervalComputer for NewPayableScanIntervalComputerMock {
- fn time_until_next_scan(&self) -> ScanTiming {
- self.time_until_next_scan_params.lock().unwrap().push(());
- self.time_until_next_scan_results.borrow_mut().remove(0)
+ fn compute_time_to_next_scan(&self) -> ScanTiming {
+ self.compute_time_to_next_scan_params
+ .lock()
+ .unwrap()
+ .push(());
+ self.compute_time_to_next_scan_results
+ .borrow_mut()
+ .remove(0)
}
fn reset_last_scan_timestamp(&mut self) {
@@ -358,13 +363,15 @@ impl NewPayableScanIntervalComputer for NewPayableScanIntervalComputerMock {
}
impl NewPayableScanIntervalComputerMock {
- pub fn time_until_next_scan_params(mut self, params: &Arc>>) -> Self {
- self.time_until_next_scan_params = params.clone();
+ pub fn compute_time_to_next_scan_params(mut self, params: &Arc>>) -> Self {
+ self.compute_time_to_next_scan_params = params.clone();
self
}
- pub fn time_until_next_scan_result(self, result: ScanTiming) -> Self {
- self.time_until_next_scan_results.borrow_mut().push(result);
+ pub fn compute_time_to_next_scan_result(self, result: ScanTiming) -> Self {
+ self.compute_time_to_next_scan_results
+ .borrow_mut()
+ .push(result);
self
}
@@ -470,14 +477,14 @@ pub fn assert_timestamps_from_str(examined_str: &str, expected_timestamps: Vec>>,
+ Arc>>,
resolve_rescheduling_on_error_results: RefCell>,
}
impl RescheduleScanOnErrorResolver for RescheduleScanOnErrorResolverMock {
fn resolve_rescheduling_on_error(
&self,
- scanner: PayableSequenceScanner,
+ scanner: UnableToStartScanner,
error: &StartScanError,
is_externally_triggered: bool,
logger: &Logger,
@@ -500,7 +507,7 @@ impl RescheduleScanOnErrorResolver for RescheduleScanOnErrorResolverMock {
impl RescheduleScanOnErrorResolverMock {
pub fn resolve_rescheduling_on_error_params(
mut self,
- params: &Arc>>,
+ params: &Arc>>,
) -> Self {
self.resolve_rescheduling_on_error_params = params.clone();
self
diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs
index b460363ee..9e3e06575 100644
--- a/node/src/accountant/test_utils.rs
+++ b/node/src/accountant/test_utils.rs
@@ -1281,7 +1281,7 @@ pub struct PendingPayableScannerBuilder {
payment_thresholds: PaymentThresholds,
financial_statistics: FinancialStatistics,
current_sent_payables: Box>,
- yet_unproven_failed_payables: Box>,
+ suspected_failed_payables: Box>,
clock: Box,
}
@@ -1294,7 +1294,7 @@ impl PendingPayableScannerBuilder {
payment_thresholds: PaymentThresholds::default(),
financial_statistics: FinancialStatistics::default(),
current_sent_payables: Box::new(PendingPayableCacheMock::default()),
- yet_unproven_failed_payables: Box::new(PendingPayableCacheMock::default()),
+ suspected_failed_payables: Box::new(PendingPayableCacheMock::default()),
clock: Box::new(SimpleClockMock::default()),
}
}
@@ -1323,7 +1323,7 @@ impl PendingPayableScannerBuilder {
mut self,
failures: Box>,
) -> Self {
- self.yet_unproven_failed_payables = failures;
+ self.suspected_failed_payables = failures;
self
}
@@ -1341,7 +1341,7 @@ impl PendingPayableScannerBuilder {
Rc::new(RefCell::new(self.financial_statistics)),
);
scanner.current_sent_payables = self.current_sent_payables;
- scanner.yet_unproven_failed_payables = self.yet_unproven_failed_payables;
+ scanner.suspected_failed_payables = self.suspected_failed_payables;
scanner.clock = self.clock;
scanner
}
diff --git a/node/src/actor_system_factory.rs b/node/src/actor_system_factory.rs
index 6b811cb09..8d750a647 100644
--- a/node/src/actor_system_factory.rs
+++ b/node/src/actor_system_factory.rs
@@ -1142,6 +1142,7 @@ mod tests {
data_directory: PathBuf::new(),
node_descriptor: NodeDescriptor::default(),
mapping_protocol_opt: None,
+ new_public_key_opt: None,
real_user: RealUser::null(),
neighborhood_config: NeighborhoodConfig {
mode: NeighborhoodMode::Standard(
@@ -1213,6 +1214,7 @@ mod tests {
cryptde_pair: CRYPTDE_PAIR.clone(),
mapping_protocol_opt: Some(Igdp),
real_user: RealUser::null(),
+ new_public_key_opt: None,
neighborhood_config: NeighborhoodConfig {
mode: NeighborhoodMode::Standard(
NodeAddr::new(&IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), &[1234, 2345]),
@@ -1509,6 +1511,7 @@ mod tests {
cryptde_pair: CRYPTDE_PAIR.clone(),
mapping_protocol_opt: None,
real_user: RealUser::null(),
+ new_public_key_opt: None,
neighborhood_config: NeighborhoodConfig {
mode: NeighborhoodMode::ConsumeOnly(vec![]),
min_hops: MIN_HOPS_FOR_TEST,
@@ -1692,6 +1695,7 @@ mod tests {
cryptde_pair: CRYPTDE_PAIR.clone(),
mapping_protocol_opt: None,
real_user: RealUser::null(),
+ new_public_key_opt: None,
neighborhood_config: NeighborhoodConfig {
mode: NeighborhoodMode::Standard(
NodeAddr::new(&IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)), &[]),
diff --git a/node/src/blockchain/blockchain_agent/agent_web3.rs b/node/src/blockchain/blockchain_agent/agent_web3.rs
index 66df08d57..4446642cd 100644
--- a/node/src/blockchain/blockchain_agent/agent_web3.rs
+++ b/node/src/blockchain/blockchain_agent/agent_web3.rs
@@ -118,11 +118,13 @@ mod tests {
BlockchainAgentWeb3, WEB3_MAXIMAL_GAS_LIMIT_MARGIN,
};
use crate::blockchain::blockchain_agent::BlockchainAgent;
- use crate::blockchain::blockchain_bridge::increase_gas_price_by_margin;
+ use crate::blockchain::blockchain_bridge::increase_by_percentage;
use crate::test_utils::make_wallet;
use itertools::{Either, Itertools};
use masq_lib::blockchains::chains::Chain;
- use masq_lib::constants::DEFAULT_GAS_PRICE_MARGIN;
+ use masq_lib::constants::{
+ DEFAULT_GAS_PRICE_RETRY_CONSTANT, DEFAULT_GAS_PRICE_RETRY_PERCENTAGE,
+ };
use masq_lib::logger::Logger;
use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler};
use masq_lib::test_utils::utils::TEST_DEFAULT_CHAIN;
@@ -155,7 +157,7 @@ mod tests {
let result = subject.price_qualified_payables(Either::Left(new_tx_templates.clone()));
- let gas_price_with_margin_wei = increase_gas_price_by_margin(rpc_gas_price_wei);
+ let gas_price_with_margin_wei = increase_by_percentage(rpc_gas_price_wei);
let expected_result = Either::Left(PricedNewTxTemplates::new(
new_tx_templates,
gas_price_with_margin_wei,
@@ -170,30 +172,32 @@ mod tests {
let test_name = "returns_correct_priced_qualified_payables_for_retry_payable_scan";
let consuming_wallet = make_wallet("efg");
let consuming_wallet_balances = make_zeroed_consuming_wallet_balances();
- let rpc_gas_price_wei = 444_555_666;
+ let latest_gas_price_wei = 444_555_666;
let chain = TEST_DEFAULT_CHAIN;
+ let prev_gas_prices = vec![
+ latest_gas_price_wei - 1,
+ latest_gas_price_wei,
+ latest_gas_price_wei + 1,
+ latest_gas_price_wei - 123_456,
+ latest_gas_price_wei + 456_789,
+ ];
let retry_tx_templates: Vec = {
- vec![
- rpc_gas_price_wei - 1,
- rpc_gas_price_wei,
- rpc_gas_price_wei + 1,
- rpc_gas_price_wei - 123_456,
- rpc_gas_price_wei + 456_789,
- ]
- .into_iter()
- .enumerate()
- .map(|(idx, prev_gas_price_wei)| {
- let account = make_payable_account((idx as u64 + 1) * 3_000);
- RetryTxTemplate {
- base: BaseTxTemplate::from(&account),
- prev_gas_price_wei,
- prev_nonce: idx as u64,
- }
- })
- .collect_vec()
+ prev_gas_prices
+ .iter()
+ .copied()
+ .enumerate()
+ .map(|(idx, prev_gas_price_wei)| {
+ let account = make_payable_account((idx as u64 + 1) * 3_000);
+ RetryTxTemplate {
+ base: BaseTxTemplate::from(&account),
+ prev_gas_price_wei,
+ prev_nonce: idx as u64,
+ }
+ })
+ .collect_vec()
};
let mut subject = BlockchainAgentWeb3::new(
- rpc_gas_price_wei,
+ latest_gas_price_wei,
77_777,
consuming_wallet,
consuming_wallet_balances,
@@ -205,23 +209,27 @@ mod tests {
.price_qualified_payables(Either::Right(RetryTxTemplates(retry_tx_templates.clone())));
let expected_result = {
- let price_wei_for_accounts_from_1_to_5 = vec![
- increase_gas_price_by_margin(rpc_gas_price_wei),
- increase_gas_price_by_margin(rpc_gas_price_wei),
- increase_gas_price_by_margin(rpc_gas_price_wei + 1),
- increase_gas_price_by_margin(rpc_gas_price_wei),
- increase_gas_price_by_margin(rpc_gas_price_wei + 456_789),
- ];
- if price_wei_for_accounts_from_1_to_5.len() != retry_tx_templates.len() {
+ let computed_gas_prices = prev_gas_prices
+ .into_iter()
+ .map(|prev_gas_price_wei| {
+ PricedRetryTxTemplate::compute_gas_price(
+ latest_gas_price_wei,
+ prev_gas_price_wei,
+ )
+ })
+ .collect::>();
+ if computed_gas_prices.len() != retry_tx_templates.len() {
panic!("Corrupted test")
}
-
Either::Right(PricedRetryTxTemplates(
retry_tx_templates
.iter()
- .zip(price_wei_for_accounts_from_1_to_5.into_iter())
- .map(|(retry_tx_template, increased_gas_price)| {
- PricedRetryTxTemplate::new(retry_tx_template.clone(), increased_gas_price)
+ .zip(computed_gas_prices.into_iter())
+ .map(|(retry_tx_template, computed_gas_price_wei)| {
+ PricedRetryTxTemplate::new(
+ retry_tx_template.clone(),
+ computed_gas_price_wei,
+ )
})
.collect_vec(),
))
@@ -238,9 +246,10 @@ mod tests {
// This should be the value that would surplus the ceiling just slightly if the margin is
// applied.
// Adding just 1 didn't work, therefore 2
- let rpc_gas_price_wei =
- ((ceiling_gas_price_wei * 100) / (DEFAULT_GAS_PRICE_MARGIN as u128 + 100)) + 2;
- let check_value_wei = increase_gas_price_by_margin(rpc_gas_price_wei);
+ let rpc_gas_price_wei = ((ceiling_gas_price_wei * 100)
+ / (DEFAULT_GAS_PRICE_RETRY_PERCENTAGE as u128 + 100))
+ + 2;
+ let check_value_wei = increase_by_percentage(rpc_gas_price_wei);
test_gas_price_must_not_break_through_ceiling_value_in_the_new_payable_mode(
test_name,
@@ -340,8 +349,8 @@ mod tests {
// applied.
// Adding just 1 didn't work, therefore 2
let rpc_gas_price_wei =
- (ceiling_gas_price_wei * 100) / (DEFAULT_GAS_PRICE_MARGIN as u128 + 100) + 2;
- let check_value_wei = increase_gas_price_by_margin(rpc_gas_price_wei);
+ (ceiling_gas_price_wei * 100) / (DEFAULT_GAS_PRICE_RETRY_PERCENTAGE as u128 + 100) + 2;
+ let check_value_wei = increase_by_percentage(rpc_gas_price_wei);
let template_1 = RetryTxTemplateBuilder::new()
.payable_account(&account_1)
.prev_gas_price_wei(rpc_gas_price_wei - 1)
@@ -382,18 +391,17 @@ mod tests {
let account_1 = make_payable_account(12);
let account_2 = make_payable_account(34);
let ceiling_gas_price_wei = chain.rec().gas_price_safe_ceiling_minor;
- // This should be the value that would surplus the ceiling just slightly if the margin is applied
- let border_gas_price_wei =
- (ceiling_gas_price_wei * 100) / (DEFAULT_GAS_PRICE_MARGIN as u128 + 100) + 2;
- let rpc_gas_price_wei = border_gas_price_wei - 1;
- let check_value_wei = increase_gas_price_by_margin(border_gas_price_wei);
+ // Once the gas price is computed from latest and prev gas price values, it'll break the ceiling
+ let prev_gas_price_wei = ceiling_gas_price_wei + 1 - DEFAULT_GAS_PRICE_RETRY_CONSTANT;
+ let latest_gas_price_wei = prev_gas_price_wei - 1;
+ let check_value_wei = prev_gas_price_wei + DEFAULT_GAS_PRICE_RETRY_CONSTANT;
let template_1 = RetryTxTemplateBuilder::new()
.payable_account(&account_1)
- .prev_gas_price_wei(border_gas_price_wei)
+ .prev_gas_price_wei(prev_gas_price_wei)
.build();
let template_2 = RetryTxTemplateBuilder::new()
.payable_account(&account_2)
- .prev_gas_price_wei(border_gas_price_wei)
+ .prev_gas_price_wei(prev_gas_price_wei)
.build();
let retry_tx_templates = vec![template_1, template_2];
let expected_log_msg = format!(
@@ -406,7 +414,7 @@ mod tests {
test_gas_price_must_not_break_through_ceiling_value_in_the_retry_payable_mode(
test_name,
chain,
- rpc_gas_price_wei,
+ latest_gas_price_wei,
Either::Right(RetryTxTemplates(retry_tx_templates)),
&expected_log_msg,
);
@@ -466,8 +474,8 @@ mod tests {
let expected_log_msg = format!(
"The computed gas price(s) in wei is above the ceil value of 50,000,000,000 wei computed by this Node.\n\
Transaction(s) to following receivers are affected:\n\
- 0x00000000000000000000000077616c6c65743132 with gas price 64,999,999,998\n\
- 0x00000000000000000000000077616c6c65743334 with gas price 64,999,999,997"
+ 0x00000000000000000000000077616c6c65743132 with gas price 50,000,004,999\n\
+ 0x00000000000000000000000077616c6c65743334 with gas price 50,000,004,998"
);
test_gas_price_must_not_break_through_ceiling_value_in_the_retry_payable_mode(
@@ -602,8 +610,7 @@ mod tests {
assert_eq!(
result,
- (2 * (77_777 + WEB3_MAXIMAL_GAS_LIMIT_MARGIN))
- * increase_gas_price_by_margin(444_555_666)
+ (2 * (77_777 + WEB3_MAXIMAL_GAS_LIMIT_MARGIN)) * increase_by_percentage(444_555_666)
);
}
@@ -611,30 +618,32 @@ mod tests {
fn estimate_transaction_fee_total_works_for_retry_txs() {
let consuming_wallet = make_wallet("efg");
let consuming_wallet_balances = make_zeroed_consuming_wallet_balances();
- let rpc_gas_price_wei = 444_555_666;
+ let latest_gas_price_wei = 444_555_666;
let chain = TEST_DEFAULT_CHAIN;
+ let prev_gas_prices = vec![
+ latest_gas_price_wei - 1,
+ latest_gas_price_wei,
+ latest_gas_price_wei + 1,
+ latest_gas_price_wei - 123_456,
+ latest_gas_price_wei + 456_789,
+ ];
let retry_tx_templates: Vec = {
- vec![
- rpc_gas_price_wei - 1,
- rpc_gas_price_wei,
- rpc_gas_price_wei + 1,
- rpc_gas_price_wei - 123_456,
- rpc_gas_price_wei + 456_789,
- ]
- .into_iter()
- .enumerate()
- .map(|(idx, prev_gas_price_wei)| {
- let account = make_payable_account((idx as u64 + 1) * 3_000);
- RetryTxTemplate {
- base: BaseTxTemplate::from(&account),
- prev_gas_price_wei,
- prev_nonce: idx as u64,
- }
- })
- .collect()
+ prev_gas_prices
+ .iter()
+ .copied()
+ .enumerate()
+ .map(|(idx, prev_gas_price_wei)| {
+ let account = make_payable_account((idx as u64 + 1) * 3_000);
+ RetryTxTemplate {
+ base: BaseTxTemplate::from(&account),
+ prev_gas_price_wei,
+ prev_nonce: idx as u64,
+ }
+ })
+ .collect()
};
let subject = BlockchainAgentWeb3::new(
- rpc_gas_price_wei,
+ latest_gas_price_wei,
77_777,
consuming_wallet,
consuming_wallet_balances,
@@ -645,15 +654,11 @@ mod tests {
let result = subject.estimate_transaction_fee_total(&priced_qualified_payables);
- let gas_prices_for_accounts_from_1_to_5 = vec![
- increase_gas_price_by_margin(rpc_gas_price_wei),
- increase_gas_price_by_margin(rpc_gas_price_wei),
- increase_gas_price_by_margin(rpc_gas_price_wei + 1),
- increase_gas_price_by_margin(rpc_gas_price_wei),
- increase_gas_price_by_margin(rpc_gas_price_wei + 456_789),
- ];
- let expected_result = gas_prices_for_accounts_from_1_to_5
+ let expected_result = prev_gas_prices
.into_iter()
+ .map(|prev_gas_price_wei| {
+ PricedRetryTxTemplate::compute_gas_price(latest_gas_price_wei, prev_gas_price_wei)
+ })
.sum::()
* (77_777 + WEB3_MAXIMAL_GAS_LIMIT_MARGIN);
assert_eq!(result, expected_result)
diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs
index 183f58659..0d13ead5d 100644
--- a/node/src/blockchain/blockchain_bridge.rs
+++ b/node/src/blockchain/blockchain_bridge.rs
@@ -39,7 +39,7 @@ use actix::{Addr, Recipient};
use futures::Future;
use itertools::{Either, Itertools};
use masq_lib::blockchains::chains::Chain;
-use masq_lib::constants::DEFAULT_GAS_PRICE_MARGIN;
+use masq_lib::constants::DEFAULT_GAS_PRICE_RETRY_PERCENTAGE;
use masq_lib::logger::Logger;
use masq_lib::ui_gateway::NodeFromUiMessage;
use regex::Regex;
@@ -312,28 +312,32 @@ impl BlockchainBridge {
Box::new(
self.process_payments(msg.agent, msg.priced_templates)
- .map_err(move |e: LocalPayableError| {
- sent_payable_subs_err
- .try_send(SentPayables {
- payment_procedure_result: Self::payment_procedure_result_from_error(
- e.clone(),
- ),
- payable_scan_type,
- response_skeleton_opt: skeleton_opt,
- })
- .expect("Accountant is dead");
-
- format!("ReportAccountsPayable: {}", e)
- })
- .and_then(move |batch_results| {
- sent_payable_subs_success
- .try_send(SentPayables {
- payment_procedure_result: Ok(batch_results),
- payable_scan_type,
- response_skeleton_opt: skeleton_opt,
- })
- .expect("Accountant is dead");
-
+ .then(move |result| {
+ match result {
+ Ok(batch_results) => {
+ sent_payable_subs_success
+ .try_send(SentPayables {
+ payment_procedure_result: Ok(batch_results),
+ payable_scan_type,
+ response_skeleton_opt: skeleton_opt,
+ })
+ .expect("Accountant is dead");
+ }
+ Err(e) => {
+ sent_payable_subs_err
+ .try_send(SentPayables {
+ payment_procedure_result:
+ Self::payment_procedure_result_from_error(e),
+ payable_scan_type,
+ response_skeleton_opt: skeleton_opt,
+ })
+ .expect("Accountant is dead");
+ }
+ }
+ // TODO Temporary workaround: prevents the Accountant from receiving two messages
+ // describing the same error. Duplicate notifications could previously trigger
+ // a panic in the scanners, because the substitution call for a given scanner
+ // was executed twice and tripped the guard that enforces a single concurrent scan.
Ok(())
}),
)
@@ -509,8 +513,8 @@ impl BlockchainBridge {
pub fn extract_max_block_count(error: BlockchainInterfaceError) -> Option {
let regex_result =
- Regex::new(r".* (max: |allowed for your plan: |is limited to |block range limit \(|exceeds max block range )(?P\d+).*")
- .expect("Invalid regex");
+ Regex::new(r".* (max: |allowed for your plan: |is limited to |block range limit \(|exceeds max block range |maximum allowed is )(?P\d+).*")
+ .expect("Invalid regex");
let max_block_count = match error {
BlockchainInterfaceError::QueryFailed(msg) => match regex_result.captures(msg.as_str())
{
@@ -538,8 +542,8 @@ struct PendingTxInfo {
when_sent: SystemTime,
}
-pub fn increase_gas_price_by_margin(gas_price: u128) -> u128 {
- (gas_price * (100 + DEFAULT_GAS_PRICE_MARGIN as u128)) / 100
+pub fn increase_by_percentage(gas_price: u128) -> u128 {
+ (gas_price * (100 + DEFAULT_GAS_PRICE_RETRY_PERCENTAGE as u128)) / 100
}
pub struct BlockchainBridgeSubsFactoryReal {}
@@ -771,7 +775,7 @@ mod tests {
let accountant_received_payment = accountant_recording_arc.lock().unwrap();
let blockchain_agent_with_context_msg_actual: &PricedTemplatesMessage =
accountant_received_payment.get_record(0);
- let computed_gas_price_wei = increase_gas_price_by_margin(0x230000000);
+ let computed_gas_price_wei = increase_by_percentage(0x230000000);
let expected_tx_templates = tx_templates
.iter()
.map(|tx_template| PricedNewTxTemplate {
@@ -1021,7 +1025,7 @@ mod tests {
// let pending_payable_fingerprint_seeds_msg =
// accountant_recording.get_record::(0);
let sent_payables_msg = accountant_recording.get_record::(0);
- let scan_error_msg = accountant_recording.get_record::(1);
+ // let scan_error_msg = accountant_recording.get_record::(1);
let batch_results = sent_payables_msg.clone().payment_procedure_result.unwrap();
let failed_tx = FailedTx {
hash: H256::from_str(
@@ -1048,22 +1052,23 @@ mod tests {
// amount: account.balance_wei
// }]
// );
- assert_eq!(scan_error_msg.scan_type, DetailedScanType::NewPayables);
- assert_eq!(
- scan_error_msg.response_skeleton_opt,
- Some(ResponseSkeleton {
- client_id: 1234,
- context_id: 4321
- })
- );
- assert!(scan_error_msg
- .msg
- .contains("ReportAccountsPayable: Sending error: \"Transport error: Error(IncompleteMessage)\". Signed and hashed transactions:"), "This string didn't contain the expected: {}", scan_error_msg.msg);
- assert!(scan_error_msg.msg.contains(
- "FailedTx { hash: 0x81d20df32920161727cd20e375e53c2f9df40fd80256a236fb39e444c999fb6c,"
- ));
- assert!(scan_error_msg.msg.contains("FailedTx { hash: 0x81d20df32920161727cd20e375e53c2f9df40fd80256a236fb39e444c999fb6c, receiver_address: 0x00000000000000000000000000000000626c6168, amount_minor: 111420204, timestamp:"), "This string didn't contain the expected: {}", scan_error_msg.msg);
- assert_eq!(accountant_recording.len(), 2);
+ // assert_eq!(scan_error_msg.scan_type, DetailedScanType::NewPayables);
+ // assert_eq!(
+ // scan_error_msg.response_skeleton_opt,
+ // Some(ResponseSkeleton {
+ // client_id: 1234,
+ // context_id: 4321
+ // })
+ // );
+ // assert!(scan_error_msg
+ // .msg
+ // .contains("ReportAccountsPayable: Sending error: \"Transport error: Error(IncompleteMessage)\". Signed and hashed transactions:"), "This string didn't contain the expected: {}", scan_error_msg.msg);
+ // assert!(scan_error_msg.msg.contains(
+ // "FailedTx { hash: 0x81d20df32920161727cd20e375e53c2f9df40fd80256a236fb39e444c999fb6c,"
+ // ));
+ // assert!(scan_error_msg.msg.contains("FailedTx { hash: 0x81d20df32920161727cd20e375e53c2f9df40fd80256a236fb39e444c999fb6c, receiver_address: 0x00000000000000000000000000000000626c6168, amount_minor: 111420204, timestamp:"), "This string didn't contain the expected: {}", scan_error_msg.msg);
+ //assert_eq!(accountant_recording.len(), 2);
+ assert_eq!(accountant_recording.len(), 1);
}
#[test]
@@ -1534,7 +1539,7 @@ mod tests {
system.run();
let after = SystemTime::now();
let expected_transactions = RetrievedBlockchainTransactions {
- new_start_block: BlockMarker::Value(42 + 9_000_000 + 1),
+ new_start_block: BlockMarker::Value(42 + 9_000_000),
transactions: vec![
BlockchainTransaction {
block_number: 6040059,
@@ -1735,7 +1740,7 @@ mod tests {
let received_payments_message = accountant_recording.get_record::(0);
check_timestamp(before, received_payments_message.timestamp, after);
let expected_transactions = RetrievedBlockchainTransactions {
- new_start_block: BlockMarker::Value(6 + 5000 + 1),
+ new_start_block: BlockMarker::Value(6 + 5000),
transactions: vec![BlockchainTransaction {
block_number: 2000,
from: earning_wallet.clone(),
@@ -2198,6 +2203,15 @@ mod tests {
assert_eq!(Some(100000), max_block_count);
}
+ #[test]
+ fn extract_max_block_range_for_nodies_error_response_v2() {
+ let result = BlockchainInterfaceError::QueryFailed("RPC error: Error { code: ServerError(-32001), message: \"Block range too large: maximum allowed is 20000 blocks\", data: None }".to_string());
+
+ let max_block_count = BlockchainBridge::extract_max_block_count(result);
+
+ assert_eq!(Some(20000), max_block_count);
+ }
+
#[test]
fn extract_max_block_range_for_expected_batch_got_single_error_response() {
let result = BlockchainInterfaceError::QueryFailed(
@@ -2225,7 +2239,7 @@ mod tests {
#[test]
fn increase_gas_price_by_margin_works() {
- assert_eq!(increase_gas_price_by_margin(1_000_000_000), 1_300_000_000);
- assert_eq!(increase_gas_price_by_margin(9_000_000_000), 11_700_000_000);
+ assert_eq!(increase_by_percentage(1_000_000_000), 1_300_000_000);
+ assert_eq!(increase_by_percentage(9_000_000_000), 11_700_000_000);
}
}
diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs
index 7a4d6ddfb..e2c6f4dcc 100644
--- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs
+++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs
@@ -9,7 +9,7 @@ use futures::Future;
use serde_json::Value;
use web3::contract::{Contract, Options};
use web3::transports::{Batch, Http};
-use web3::types::{Address, BlockNumber, Filter, Log};
+use web3::types::{Address, Filter, Log};
use web3::{Error, Web3};
pub struct LowBlockchainIntWeb3 {
@@ -68,7 +68,7 @@ impl LowBlockchainInt for LowBlockchainIntWeb3 {
Box::new(
self.web3
.eth()
- .transaction_count(address, Some(BlockNumber::Pending))
+ .transaction_count(address, None)
.map_err(move |e| QueryFailed(format!("{} for wallet {}", e, address))),
)
}
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 9249c6ee0..f7e779cc3 100644
--- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs
+++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs
@@ -279,6 +279,8 @@ impl BlockchainInterface for BlockchainInterfaceWeb3 {
get_transaction_id
.map_err(LocalPayableError::TransactionID)
.and_then(move |latest_nonce| {
+ debug!(logger, "Latest nonce: {:?}", latest_nonce);
+
let templates =
SignableTxTemplates::new(priced_templates, latest_nonce.as_u64());
@@ -394,7 +396,9 @@ impl BlockchainInterfaceWeb3 {
) -> BlockMarker {
let locally_determined_end_block_marker = match (start_block_marker, scan_range) {
(BlockMarker::Value(start_block), BlockScanRange::Range(scan_range_number)) => {
- BlockMarker::Value(start_block + scan_range_number)
+ // Subtract 1 because the range is inclusive: [start_block, end_block]
+ // Example: If max range is 20000, we need start_block to start_block+20000-1 (ending up with 20000 blocks total)
+ BlockMarker::Value(start_block + scan_range_number - 1)
}
(_, _) => BlockMarker::Uninitialized,
};
@@ -469,7 +473,6 @@ mod tests {
use super::*;
use crate::accountant::scanners::pending_payable_scanner::utils::TxHashByTable;
use crate::accountant::test_utils::make_payable_account;
- use crate::blockchain::blockchain_bridge::increase_gas_price_by_margin;
use crate::blockchain::blockchain_interface::blockchain_interface_web3::{
BlockchainInterfaceWeb3, CONTRACT_ABI, REQUESTS_IN_PARALLEL, TRANSACTION_LITERAL,
TRANSFER_METHOD_ID,
@@ -501,10 +504,12 @@ mod tests {
use itertools::Either;
use web3::transports::Http;
use web3::types::{H256, U256};
+ use masq_lib::constants::DEFAULT_GAS_PRICE_RETRY_CONSTANT;
use crate::accountant::scanners::payable_scanner::tx_templates::initial::new::NewTxTemplates;
use crate::accountant::scanners::payable_scanner::tx_templates::initial::retry::RetryTxTemplates;
use crate::accountant::scanners::payable_scanner::tx_templates::priced::retry::PricedRetryTxTemplate;
use crate::accountant::scanners::payable_scanner::tx_templates::test_utils::RetryTxTemplateBuilder;
+ use crate::blockchain::blockchain_bridge::increase_by_percentage;
#[test]
fn constants_are_correct() {
@@ -560,8 +565,8 @@ mod tests {
let start_block_marker = BlockMarker::Value(42);
let scan_range = BlockScanRange::Range(1000);
let block_response = "0x7d0"; // 2_000
- let expected_new_start_block = BlockMarker::Value(42 + 1000 + 1);
- let expected_log = "from start block: Number(42) to end block: Number(1042)";
+ let expected_new_start_block = BlockMarker::Value(42 + 1000);
+ let expected_log = "from start block: Number(42) to end block: Number(1041)";
assert_on_retrieves_transactions(
start_block_marker,
scan_range,
@@ -884,7 +889,7 @@ mod tests {
let gas_price_wei_from_rpc_u128_wei =
u128::from_str_radix(&gas_price_wei_from_rpc_hex[2..], 16).unwrap();
let gas_price_wei_from_rpc_u128_wei_with_margin =
- increase_gas_price_by_margin(gas_price_wei_from_rpc_u128_wei);
+ increase_by_percentage(gas_price_wei_from_rpc_u128_wei);
let expected_result = Either::Left(PricedNewTxTemplates::new(
tx_templates.clone(),
gas_price_wei_from_rpc_u128_wei_with_margin,
@@ -902,32 +907,32 @@ mod tests {
#[test]
fn blockchain_interface_web3_can_introduce_blockchain_agent_in_the_retry_payables_mode() {
let gas_price_wei = "0x3B9ACA00"; // 1000000000
- let gas_price_from_rpc = u128::from_str_radix(&gas_price_wei[2..], 16).unwrap();
+ let latest_gas_price_wei = u128::from_str_radix(&gas_price_wei[2..], 16).unwrap();
let retry_1 = RetryTxTemplateBuilder::default()
.payable_account(&make_payable_account(12))
- .prev_gas_price_wei(gas_price_from_rpc - 1)
+ .prev_gas_price_wei(latest_gas_price_wei - 1)
.build();
let retry_2 = RetryTxTemplateBuilder::default()
.payable_account(&make_payable_account(34))
- .prev_gas_price_wei(gas_price_from_rpc)
+ .prev_gas_price_wei(latest_gas_price_wei)
.build();
let retry_3 = RetryTxTemplateBuilder::default()
.payable_account(&make_payable_account(56))
- .prev_gas_price_wei(gas_price_from_rpc + 1)
+ .prev_gas_price_wei(latest_gas_price_wei + 1)
.build();
let retry_tx_templates =
RetryTxTemplates(vec![retry_1.clone(), retry_2.clone(), retry_3.clone()]);
let expected_retry_tx_templates = PricedRetryTxTemplates(vec![
- PricedRetryTxTemplate::new(retry_1, increase_gas_price_by_margin(gas_price_from_rpc)),
- PricedRetryTxTemplate::new(retry_2, increase_gas_price_by_margin(gas_price_from_rpc)),
+ PricedRetryTxTemplate::new(retry_1, increase_by_percentage(latest_gas_price_wei)),
+ PricedRetryTxTemplate::new(retry_2, increase_by_percentage(latest_gas_price_wei)),
PricedRetryTxTemplate::new(
retry_3,
- increase_gas_price_by_margin(gas_price_from_rpc + 1),
+ (latest_gas_price_wei + 1) + DEFAULT_GAS_PRICE_RETRY_CONSTANT,
),
]);
- let expected_estimated_transaction_fee_total = 285_979_200_073_328;
+ let expected_estimated_transaction_fee_total = 263_981_166_713_328;
test_blockchain_interface_web3_can_introduce_blockchain_agent(
Either::Right(retry_tx_templates),
@@ -1270,7 +1275,7 @@ mod tests {
Err(BlockchainInterfaceError::InvalidResponse),
&logger
),
- BlockMarker::Value(150)
+ BlockMarker::Value(149)
);
assert_eq!(
Subject::calculate_end_block_marker(
@@ -1288,7 +1293,7 @@ mod tests {
Ok(120.into()),
&logger
),
- BlockMarker::Value(50 + 10)
+ BlockMarker::Value(59)
);
}
diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs
index 16cab8c2d..fa893b19f 100644
--- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs
+++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/utils.rs
@@ -282,11 +282,16 @@ pub fn send_payables_within_batch(
transmission_log(chain, &signable_tx_templates)
);
+ let logger_clone = logger.clone();
+
Box::new(
web3_batch
.transport()
.submit_batch()
- .map_err(move |e| return_sending_error(&sent_txs_for_err, &e))
+ .map_err(move |e| {
+ warning!(logger_clone, "Failed to submit batch to Web3 client: {}", e);
+ return_sending_error(&sent_txs_for_err, &e)
+ })
.and_then(move |batch_responses| Ok(return_batch_results(sent_txs, batch_responses))),
)
}
@@ -727,6 +732,7 @@ mod tests {
#[test]
fn send_payables_within_batch_fails_on_submit_batch_call() {
+ let test_name = "send_payables_within_batch_fails_on_submit_batch_call";
let port = find_free_port();
let (_event_loop_handle, transport) = Http::with_max_parallel(
&format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port),
@@ -779,12 +785,15 @@ mod tests {
failed_txs,
});
- test_send_payables_within_batch(
- "send_payables_within_batch_fails_on_submit_batch_call",
- signable_tx_templates,
- expected_result,
- port,
- );
+ test_send_payables_within_batch(test_name, signable_tx_templates, expected_result, port);
+
+ let os_specific_code = transport_error_code();
+ let os_specific_msg = transport_error_message();
+ TestLogHandler::new().exists_log_containing(&format!(
+ "WARN: {test_name}: Failed to submit batch to Web3 client: Transport error: \
+ Error(Connect, Os {{ code: {}, kind: ConnectionRefused, message: \"{}\" }}",
+ os_specific_code, os_specific_msg
+ ));
}
#[test]
diff --git a/node/src/blockchain/blockchain_interface_initializer.rs b/node/src/blockchain/blockchain_interface_initializer.rs
index 04838f312..01b97a3b8 100644
--- a/node/src/blockchain/blockchain_interface_initializer.rs
+++ b/node/src/blockchain/blockchain_interface_initializer.rs
@@ -47,7 +47,7 @@ mod tests {
use crate::accountant::scanners::payable_scanner::tx_templates::initial::new::NewTxTemplates;
use crate::accountant::scanners::payable_scanner::tx_templates::priced::new::PricedNewTxTemplates;
use crate::accountant::test_utils::make_payable_account;
- use crate::blockchain::blockchain_bridge::increase_gas_price_by_margin;
+ use crate::blockchain::blockchain_bridge::increase_by_percentage;
use crate::blockchain::blockchain_interface_initializer::BlockchainInterfaceInitializer;
use crate::test_utils::make_wallet;
use futures::Future;
@@ -88,7 +88,7 @@ mod tests {
.unwrap();
assert_eq!(blockchain_agent.consuming_wallet(), &payable_wallet);
let result = blockchain_agent.price_qualified_payables(Either::Left(tx_templates.clone()));
- let gas_price_with_margin = increase_gas_price_by_margin(1_000_000_000);
+ let gas_price_with_margin = increase_by_percentage(1_000_000_000);
let expected_result = Either::Left(PricedNewTxTemplates::new(
tx_templates,
gas_price_with_margin,
diff --git a/node/src/bootstrapper.rs b/node/src/bootstrapper.rs
index d0b92755c..7c3891a8a 100644
--- a/node/src/bootstrapper.rs
+++ b/node/src/bootstrapper.rs
@@ -361,6 +361,7 @@ pub struct BootstrapperConfig {
pub node_descriptor: NodeDescriptor,
pub cryptde_pair: CryptDEPair,
pub mapping_protocol_opt: Option,
+ pub new_public_key_opt: Option,
pub real_user: RealUser,
pub payment_thresholds_opt: Option,
@@ -406,6 +407,7 @@ impl BootstrapperConfig {
Box::new(CryptDEReal::disabled()),
),
mapping_protocol_opt: None,
+ new_public_key_opt: None,
real_user: RealUser::new(None, None, None),
payment_thresholds_opt: Default::default(),
@@ -748,7 +750,7 @@ mod tests {
use tokio::prelude::Async;
lazy_static! {
- static ref CRYPTDE_PAIR: CryptDEPair = CryptDEPair::null();
+ static ref BS_CRYPTDE_PAIR: CryptDEPair = CryptDEPair::null();
pub static ref INITIALIZATION: Mutex = Mutex::new(false);
}
@@ -1535,13 +1537,13 @@ mod tests {
);
let cryptde_ref = {
let descriptor = Bootstrapper::make_local_descriptor(
- CRYPTDE_PAIR.main.as_ref(),
+ BS_CRYPTDE_PAIR.main.as_ref(),
Some(node_addr),
TEST_DEFAULT_CHAIN,
);
- Bootstrapper::report_local_descriptor(CRYPTDE_PAIR.main.as_ref(), &descriptor);
+ Bootstrapper::report_local_descriptor(BS_CRYPTDE_PAIR.main.as_ref(), &descriptor);
- CRYPTDE_PAIR.main.as_ref()
+ BS_CRYPTDE_PAIR.main.as_ref()
};
let expected_descriptor = format!(
"masq://base-sepolia:{}@2.3.4.5:3456/4567",
@@ -1577,13 +1579,13 @@ mod tests {
init_test_logging();
let cryptdes = {
let descriptor = Bootstrapper::make_local_descriptor(
- CRYPTDE_PAIR.main.as_ref(),
+ BS_CRYPTDE_PAIR.main.as_ref(),
None,
TEST_DEFAULT_CHAIN,
);
- Bootstrapper::report_local_descriptor(CRYPTDE_PAIR.main.as_ref(), &descriptor);
+ Bootstrapper::report_local_descriptor(BS_CRYPTDE_PAIR.main.as_ref(), &descriptor);
- CRYPTDE_PAIR.clone()
+ BS_CRYPTDE_PAIR.clone()
};
let expected_descriptor = format!(
"masq://base-sepolia:{}@:",
diff --git a/node/src/daemon/setup_reporter.rs b/node/src/daemon/setup_reporter.rs
index dd1e2182a..f0d31f2be 100644
--- a/node/src/daemon/setup_reporter.rs
+++ b/node/src/daemon/setup_reporter.rs
@@ -1041,6 +1041,26 @@ impl ValueRetriever for Neighbors {
}
}
+struct NewPublicKey {}
+impl ValueRetriever for NewPublicKey {
+ fn value_name(&self) -> &'static str {
+ "new-public-key"
+ }
+
+ fn computed_default(
+ &self,
+ _bootstrapper_config: &BootstrapperConfig,
+ _persistent_config: &dyn PersistentConfiguration,
+ _db_password_opt: &Option,
+ ) -> Option<(String, UiSetupResponseValueStatus)> {
+ Some(("".to_string(), Blank))
+ }
+
+ fn is_required(&self, _params: &SetupCluster) -> bool {
+ false
+ }
+}
+
struct PaymentThresholds {}
impl ValueRetriever for PaymentThresholds {
fn value_name(&self) -> &'static str {
@@ -1216,6 +1236,7 @@ fn value_retrievers(dirs_wrapper: &dyn DirsWrapper) -> Vec,
) -> rusqlite::Result<()> {
- let sql_statement_for_sent_payable = "create table if not exists sent_payable (
- rowid integer primary key,
- tx_hash text not null,
- receiver_address text not null,
- amount_high_b integer not null,
- amount_low_b integer not null,
- timestamp integer not null,
- gas_price_wei_high_b integer not null,
- gas_price_wei_low_b integer not null,
- nonce integer not null,
- status text not null
- )";
-
- let sql_statement_for_failed_payable = "create table if not exists failed_payable (
- rowid integer primary key,
- tx_hash text not null,
- receiver_address text not null,
- amount_high_b integer not null,
- amount_low_b integer not null,
- timestamp integer not null,
- gas_price_wei_high_b integer not null,
- gas_price_wei_low_b integer not null,
- nonce integer not null,
- reason text not null,
- status text not null
- )";
-
- let sql_statement_for_pending_payable = "drop table pending_payable";
-
- declaration_utils.execute_upon_transaction(&[
- &sql_statement_for_sent_payable,
- &sql_statement_for_failed_payable,
- &sql_statement_for_pending_payable,
- ])
+ declaration_utils.execute_upon_transaction(&[&format!(
+ "INSERT INTO config (name, value, encrypted) VALUES ('rate_pack_limits', '{}', 0)",
+ DEFAULT_RATE_PACK_LIMITS.rate_pack_limits_parameter()
+ )])
}
fn old_version(&self) -> usize {
@@ -56,17 +28,14 @@ mod tests {
use crate::database::db_initializer::{
DbInitializationConfig, DbInitializer, DbInitializerReal, DATABASE_FILE,
};
- use crate::database::test_utils::{
- SQL_ATTRIBUTES_FOR_CREATING_FAILED_PAYABLE, SQL_ATTRIBUTES_FOR_CREATING_SENT_PAYABLE,
+ use crate::test_utils::database_utils::{
+ bring_db_0_back_to_life_and_return_connection, make_external_data, retrieve_config_row,
};
- use crate::test_utils::database_utils::{assert_create_table_stm_contains_all_parts, assert_table_does_not_exist, assert_table_exists, bring_db_0_back_to_life_and_return_connection, make_external_data};
- 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_11_to_12_is_applied_correctly() {
- init_test_logging();
+ fn migration_from_11_to_12_is_properly_set() {
let dir_path = ensure_node_home_directory_exists(
"db_migrations",
"migration_from_11_to_12_is_properly_set",
@@ -75,37 +44,34 @@ mod tests {
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 _prev_connection = subject
- .initialize_to_version(
- &dir_path,
- 11,
- DbInitializationConfig::create_or_migrate(make_external_data()),
- )
- .unwrap();
- let connection = subject
- .initialize_to_version(
- &dir_path,
- 12,
- DbInitializationConfig::create_or_migrate(make_external_data()),
- )
- .unwrap();
+ let result = subject.initialize_to_version(
+ &dir_path,
+ 11,
+ DbInitializationConfig::create_or_migrate(make_external_data()),
+ );
+
+ assert!(result.is_ok());
- assert_table_exists(connection.as_ref(), "sent_payable");
- assert_table_exists(connection.as_ref(), "failed_payable");
- assert_create_table_stm_contains_all_parts(
- &*connection,
- "sent_payable",
- SQL_ATTRIBUTES_FOR_CREATING_SENT_PAYABLE,
+ let result = subject.initialize_to_version(
+ &dir_path,
+ 12,
+ DbInitializationConfig::create_or_migrate(make_external_data()),
);
- assert_create_table_stm_contains_all_parts(
- &*connection,
- "failed_payable",
- SQL_ATTRIBUTES_FOR_CREATING_FAILED_PAYABLE,
+
+ assert!(result.is_ok());
+ let connection = result.unwrap();
+ let (lc_value, lc_encrypted) = retrieve_config_row(connection.as_ref(), "rate_pack_limits");
+ let (cs_value, cs_encrypted) = retrieve_config_row(connection.as_ref(), "schema_version");
+ assert_eq!(
+ lc_value,
+ Some(
+ "100-100000000000000|100-100000000000000|100-100000000000000|100-100000000000000"
+ .to_string()
+ )
);
- assert_table_does_not_exist(connection.as_ref(), "pending_payable");
- TestLogHandler::new().assert_logs_contain_in_order(vec![
- "DbMigrator: Database successfully migrated from version 11 to 12",
- ]);
+ assert_eq!(lc_encrypted, false);
+ assert_eq!(cs_value, Some("12".to_string()));
+ assert_eq!(cs_encrypted, false)
}
}
diff --git a/node/src/database/db_migrations/migrations/migration_12_to_13.rs b/node/src/database/db_migrations/migrations/migration_12_to_13.rs
new file mode 100644
index 000000000..3a83cb11c
--- /dev/null
+++ b/node/src/database/db_migrations/migrations/migration_12_to_13.rs
@@ -0,0 +1,111 @@
+// Copyright (c) 2025, MASQ (https://masq.ai) and/or its affiliates. All rights reserved.
+use crate::database::db_migrations::db_migrator::DatabaseMigration;
+use crate::database::db_migrations::migrator_utils::DBMigDeclarator;
+
+#[allow(non_camel_case_types)]
+pub struct Migrate_12_to_13;
+
+impl DatabaseMigration for Migrate_12_to_13 {
+ fn migrate<'a>(
+ &self,
+ declaration_utils: Box,
+ ) -> rusqlite::Result<()> {
+ let sql_statement_for_sent_payable = "create table if not exists sent_payable (
+ rowid integer primary key,
+ tx_hash text not null,
+ receiver_address text not null,
+ amount_high_b integer not null,
+ amount_low_b integer not null,
+ timestamp integer not null,
+ gas_price_wei_high_b integer not null,
+ gas_price_wei_low_b integer not null,
+ nonce integer not null,
+ status text not null
+ )";
+
+ let sql_statement_for_failed_payable = "create table if not exists failed_payable (
+ rowid integer primary key,
+ tx_hash text not null,
+ receiver_address text not null,
+ amount_high_b integer not null,
+ amount_low_b integer not null,
+ timestamp integer not null,
+ gas_price_wei_high_b integer not null,
+ gas_price_wei_low_b integer not null,
+ nonce integer not null,
+ reason text not null,
+ status text not null
+ )";
+
+ let sql_statement_for_pending_payable = "drop table pending_payable";
+
+ declaration_utils.execute_upon_transaction(&[
+ &sql_statement_for_sent_payable,
+ &sql_statement_for_failed_payable,
+ &sql_statement_for_pending_payable,
+ ])
+ }
+
+ fn old_version(&self) -> usize {
+ 12
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::database::db_initializer::{
+ DbInitializationConfig, DbInitializer, DbInitializerReal, DATABASE_FILE,
+ };
+ use crate::database::test_utils::{
+ SQL_ATTRIBUTES_FOR_CREATING_FAILED_PAYABLE, SQL_ATTRIBUTES_FOR_CREATING_SENT_PAYABLE,
+ };
+ use crate::test_utils::database_utils::{assert_create_table_stm_contains_all_parts, assert_table_does_not_exist, assert_table_exists, bring_db_0_back_to_life_and_return_connection, make_external_data};
+ 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_12_to_13_is_applied_correctly() {
+ init_test_logging();
+ let dir_path = ensure_node_home_directory_exists(
+ "db_migrations",
+ "migration_from_12_to_13_is_applied_correctly",
+ );
+ 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 _prev_connection = subject
+ .initialize_to_version(
+ &dir_path,
+ 12,
+ DbInitializationConfig::create_or_migrate(make_external_data()),
+ )
+ .unwrap();
+
+ let connection = subject
+ .initialize_to_version(
+ &dir_path,
+ 13,
+ DbInitializationConfig::create_or_migrate(make_external_data()),
+ )
+ .unwrap();
+
+ assert_table_exists(connection.as_ref(), "sent_payable");
+ assert_table_exists(connection.as_ref(), "failed_payable");
+ assert_create_table_stm_contains_all_parts(
+ &*connection,
+ "sent_payable",
+ SQL_ATTRIBUTES_FOR_CREATING_SENT_PAYABLE,
+ );
+ assert_create_table_stm_contains_all_parts(
+ &*connection,
+ "failed_payable",
+ SQL_ATTRIBUTES_FOR_CREATING_FAILED_PAYABLE,
+ );
+ assert_table_does_not_exist(connection.as_ref(), "pending_payable");
+ TestLogHandler::new().assert_logs_contain_in_order(vec![
+ "DbMigrator: Database successfully migrated from version 12 to 13",
+ ]);
+ }
+}
diff --git a/node/src/database/db_migrations/migrations/mod.rs b/node/src/database/db_migrations/migrations/mod.rs
index 1abcf911c..44d4dac82 100644
--- a/node/src/database/db_migrations/migrations/mod.rs
+++ b/node/src/database/db_migrations/migrations/mod.rs
@@ -14,3 +14,5 @@ pub mod migration_9_to_10;
pub mod migration_10_to_11;
#[rustfmt::skip]
pub mod migration_11_to_12;
+#[rustfmt::skip]
+pub mod migration_12_to_13;
diff --git a/node/src/db_config/config_dao_null.rs b/node/src/db_config/config_dao_null.rs
index 1c988caea..8ee6716d2 100644
--- a/node/src/db_config/config_dao_null.rs
+++ b/node/src/db_config/config_dao_null.rs
@@ -6,7 +6,7 @@ use crate::db_config::config_dao::{ConfigDao, ConfigDaoError, ConfigDaoRecord};
use crate::neighborhood::DEFAULT_MIN_HOPS;
use crate::sub_lib::accountant;
use crate::sub_lib::accountant::DEFAULT_PAYMENT_THRESHOLDS;
-use crate::sub_lib::neighborhood::DEFAULT_RATE_PACK;
+use crate::sub_lib::neighborhood::{DEFAULT_RATE_PACK, DEFAULT_RATE_PACK_LIMITS};
use itertools::Itertools;
use masq_lib::blockchains::chains::Chain;
use masq_lib::constants::{CURRENT_SCHEMA_VERSION, DEFAULT_GAS_PRICE};
@@ -138,6 +138,13 @@ impl Default for ConfigDaoNull {
"rate_pack".to_string(),
(Some(DEFAULT_RATE_PACK.to_string()), false),
);
+ data.insert(
+ "rate_pack_limits".to_string(),
+ (
+ Some(DEFAULT_RATE_PACK_LIMITS.rate_pack_limits_parameter()),
+ false,
+ ),
+ );
data.insert(
"scan_intervals".to_string(),
(
diff --git a/node/src/db_config/persistent_configuration.rs b/node/src/db_config/persistent_configuration.rs
index 8567d7807..602593ab7 100644
--- a/node/src/db_config/persistent_configuration.rs
+++ b/node/src/db_config/persistent_configuration.rs
@@ -3,6 +3,7 @@
use crate::arbitrary_id_stamp_in_trait;
use crate::blockchain::bip32::Bip32EncryptionKeyProvider;
use crate::blockchain::bip39::{Bip39, Bip39Error};
+use crate::database::db_initializer::{DbInitializationConfig, DbInitializer, DbInitializerReal};
use crate::database::rusqlite_wrappers::{ConnectionWrapper, TransactionSafeWrapper};
use crate::db_config::config_dao::{ConfigDao, ConfigDaoError, ConfigDaoReal, ConfigDaoRecord};
use crate::db_config::secure_config_layer::{SecureConfigLayer, SecureConfigLayerError};
@@ -14,19 +15,28 @@ use crate::sub_lib::accountant::{PaymentThresholds, ScanIntervals};
use crate::sub_lib::cryptde::{CryptDE, PlainData};
use crate::sub_lib::cryptde_null::CryptDENull;
use crate::sub_lib::cryptde_real::CryptDEReal;
-use crate::sub_lib::neighborhood::{Hops, NodeDescriptor, RatePack};
+use crate::sub_lib::neighborhood::{Hops, NodeDescriptor, RatePack, RatePackLimits};
+use crate::sub_lib::utils::db_connection_launch_panic;
use crate::sub_lib::wallet::Wallet;
+use lazy_static::lazy_static;
use masq_lib::blockchains::chains::Chain;
use masq_lib::constants::{HIGHEST_USABLE_PORT, LOWEST_USABLE_INSECURE_PORT};
use masq_lib::shared_schema::{ConfiguratorError, ParamError};
use masq_lib::utils::NeighborhoodModeLight;
use masq_lib::utils::{to_string, AutomapProtocol};
+use regex::{Captures, Regex};
use rustc_hex::{FromHex, ToHex};
use std::fmt::Display;
use std::net::{Ipv4Addr, SocketAddrV4, TcpListener};
+use std::path::PathBuf;
use std::str::FromStr;
use websocket::url::Url;
+lazy_static! {
+ static ref RATE_PACK_LIMIT_FORMAT: Regex =
+ Regex::new(r"^(\d{1,19})-(\d{1,19})\|(\d{1,19})-(\d{1,19})\|(\d{1,19})-(\d{1,19})\|(\d{1,19})-(\d{1,19})$").unwrap();
+}
+
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum PersistentConfigError {
NotPresent,
@@ -159,6 +169,7 @@ pub trait PersistentConfiguration {
fn payment_thresholds(&self) -> Result;
fn set_payment_thresholds(&mut self, curves: String) -> Result<(), PersistentConfigError>;
fn rate_pack(&self) -> Result;
+ fn rate_pack_limits(&self) -> Result;
fn set_rate_pack(&mut self, rate_pack: String) -> Result<(), PersistentConfigError>;
fn scan_intervals(&self) -> Result;
fn set_scan_intervals(&mut self, intervals: String) -> Result<(), PersistentConfigError>;
@@ -570,6 +581,44 @@ impl PersistentConfiguration for PersistentConfigurationReal {
self.simple_set_method("rate_pack", rate_pack)
}
+ fn rate_pack_limits(&self) -> Result {
+ let limits_string = self
+ .get("rate_pack_limits")
+ .expect(
+ "Required value rate_pack_limits missing from CONFIG table: database is corrupt!",
+ )
+ .expect(
+ "Required value rate_pack_limits is NULL in CONFIG table: database is corrupt!",
+ );
+ let captures = RATE_PACK_LIMIT_FORMAT.captures(limits_string.as_str())
+ .unwrap_or_else(|| panic!("Syntax error in rate_pack_limits value '{}': should be -|-|-|- where L is low, H is high, R is routing, E is exit, BR is byte rate, and SR is service rate. All numbers should be in wei.", limits_string));
+ let candidate = RatePackLimits::new(
+ Self::extract_candidate(&captures, 1),
+ Self::extract_candidate(&captures, 2),
+ );
+ Self::check_rate_pack_limit_order(
+ candidate.lo.routing_byte_rate,
+ candidate.hi.routing_byte_rate,
+ "routing_byte_rate",
+ );
+ Self::check_rate_pack_limit_order(
+ candidate.lo.routing_service_rate,
+ candidate.hi.routing_service_rate,
+ "routing_service_rate",
+ );
+ Self::check_rate_pack_limit_order(
+ candidate.lo.exit_byte_rate,
+ candidate.hi.exit_byte_rate,
+ "exit_byte_rate",
+ );
+ Self::check_rate_pack_limit_order(
+ candidate.lo.exit_service_rate,
+ candidate.hi.exit_service_rate,
+ "exit_service_rate",
+ );
+ Ok(candidate)
+ }
+
fn scan_intervals(&self) -> Result {
self.combined_params_get_method(|str: &str| ScanIntervals::try_from(str), "scan_intervals")
}
@@ -671,6 +720,239 @@ impl PersistentConfigurationReal {
parameter_name
)
}
+
+ fn extract_candidate(captures: &Captures, start_index: usize) -> RatePack {
+ RatePack {
+ routing_byte_rate: Self::parse_capture(captures, start_index),
+ routing_service_rate: Self::parse_capture(captures, start_index + 2),
+ exit_byte_rate: Self::parse_capture(captures, start_index + 4),
+ exit_service_rate: Self::parse_capture(captures, start_index + 6),
+ }
+ }
+
+ fn parse_capture(captures: &Captures, index: usize) -> u64 {
+ u64::from_str(
+ captures
+ .get(index)
+ .expect("Internal error: regex needs eight captures")
+ .as_str(),
+ )
+ .expect("Internal error: regex must require u64")
+ }
+
+ fn check_rate_pack_limit_order(low: u64, high: u64, field_name: &str) {
+ if low >= high {
+ panic!(
+ "Rate pack limits should have low limits less than high limits, but {} limits are {}-{}",
+ field_name, low, high
+ );
+ }
+ }
+}
+
+pub struct PersistentConfigurationInvalid {}
+
+impl PersistentConfiguration for PersistentConfigurationInvalid {
+ fn blockchain_service_url(&self) -> Result, PersistentConfigError> {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn set_blockchain_service_url(&mut self, _url: &str) -> Result<(), PersistentConfigError> {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn current_schema_version(&self) -> String {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn chain_name(&self) -> String {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn check_password(
+ &self,
+ _db_password_opt: Option,
+ ) -> Result {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn change_password(
+ &mut self,
+ _old_password_opt: Option,
+ _new_password: &str,
+ ) -> Result<(), PersistentConfigError> {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn consuming_wallet(
+ &self,
+ _db_password: &str,
+ ) -> Result, PersistentConfigError> {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn consuming_wallet_private_key(
+ &self,
+ _db_password: &str,
+ ) -> Result , PersistentConfigError> {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn clandestine_port(&self) -> Result {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn set_clandestine_port(&mut self, _port: u16) -> Result<(), PersistentConfigError> {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn cryptde(
+ &self,
+ _db_password: &str,
+ ) -> Result>, PersistentConfigError> {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn set_cryptde(
+ &mut self,
+ _cryptde: &dyn CryptDE,
+ _db_password: &str,
+ ) -> Result<(), PersistentConfigError> {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn earning_wallet(&self) -> Result , PersistentConfigError> {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn earning_wallet_address(&self) -> Result , PersistentConfigError> {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn gas_price(&self) -> Result {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn set_gas_price(&mut self, _gas_price: u64) -> Result<(), PersistentConfigError> {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn mapping_protocol(&self) -> Result, PersistentConfigError> {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn set_mapping_protocol(
+ &mut self,
+ _value_opt: Option,
+ ) -> Result<(), PersistentConfigError> {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn min_hops(&self) -> Result {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn set_min_hops(&mut self, _value: Hops) -> Result<(), PersistentConfigError> {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn neighborhood_mode(&self) -> Result {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn set_neighborhood_mode(
+ &mut self,
+ _value: NeighborhoodModeLight,
+ ) -> Result<(), PersistentConfigError> {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn past_neighbors(
+ &self,
+ _db_password: &str,
+ ) -> Result>, PersistentConfigError> {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn set_past_neighbors(
+ &mut self,
+ _node_descriptors_opt: Option>,
+ _db_password: &str,
+ ) -> Result<(), PersistentConfigError> {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn start_block(&self) -> Result, PersistentConfigError> {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn set_start_block(&mut self, _value_opt: Option) -> Result<(), PersistentConfigError> {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn max_block_count(&self) -> Result, PersistentConfigError> {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn set_max_block_count(
+ &mut self,
+ _value_opt: Option,
+ ) -> Result<(), PersistentConfigError> {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn set_start_block_from_txn(
+ &mut self,
+ _value_opt: Option,
+ _transaction: &mut TransactionSafeWrapper,
+ ) -> Result<(), PersistentConfigError> {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn set_wallet_info(
+ &mut self,
+ _consuming_wallet_private_key: &str,
+ _earning_wallet_address: &str,
+ _db_password: &str,
+ ) -> Result<(), PersistentConfigError> {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn payment_thresholds(&self) -> Result {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn set_payment_thresholds(&mut self, _curves: String) -> Result<(), PersistentConfigError> {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn rate_pack(&self) -> Result {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn set_rate_pack(&mut self, _rate_pack: String) -> Result<(), PersistentConfigError> {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn rate_pack_limits(&self) -> Result {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn scan_intervals(&self) -> Result {
+ PersistentConfigurationInvalid::invalid()
+ }
+ fn set_scan_intervals(&mut self, _intervals: String) -> Result<(), PersistentConfigError> {
+ PersistentConfigurationInvalid::invalid()
+ }
+ arbitrary_id_stamp_in_trait!();
+}
+
+impl PersistentConfigurationInvalid {
+ pub fn new() -> Self {
+ Self {}
+ }
+
+ fn invalid() -> ! {
+ panic!("PersistentConfiguration is uninitialized")
+ }
+}
+
+impl Default for PersistentConfigurationInvalid {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+pub trait PersistentConfigurationFactory {
+ fn make(&self) -> Box;
+}
+
+pub struct PersistentConfigurationFactoryReal {
+ data_directory: PathBuf,
+}
+
+impl PersistentConfigurationFactory for PersistentConfigurationFactoryReal {
+ fn make(&self) -> Box {
+ let db_initializer: &dyn DbInitializer = &DbInitializerReal::default();
+ let conn = db_initializer
+ .initialize(
+ self.data_directory.as_path(),
+ DbInitializationConfig::panic_on_migration(),
+ )
+ .unwrap_or_else(|err| db_connection_launch_panic(err, &self.data_directory));
+ Box::new(PersistentConfigurationReal::from(conn))
+ }
+}
+
+impl PersistentConfigurationFactoryReal {
+ pub fn new(data_directory: PathBuf) -> Self {
+ Self { data_directory }
+ }
}
#[cfg(test)]
@@ -2284,6 +2566,166 @@ mod tests {
getter_method_plain_data_does_not_tolerate_none_value!("rate_pack");
}
+ #[test]
+ fn rate_pack_limits_works() {
+ persistent_config_plain_data_assertions_for_simple_get_method!(
+ "rate_pack_limits",
+ "100-200|300-400|500000000000000000-600000000000000000|700-800",
+ RatePackLimits::new(
+ RatePack {
+ routing_byte_rate: 100,
+ routing_service_rate: 300,
+ exit_byte_rate: 500_000_000_000_000_000,
+ exit_service_rate: 700,
+ },
+ RatePack {
+ routing_byte_rate: 200,
+ routing_service_rate: 400,
+ exit_byte_rate: 600_000_000_000_000_000,
+ exit_service_rate: 800,
+ }
+ )
+ );
+ }
+
+ #[test]
+ #[should_panic(
+ expected = "Required value rate_pack_limits is NULL in CONFIG table: database is corrupt!"
+ )]
+ fn rate_pack_limits_panics_at_none_value() {
+ getter_method_plain_data_does_not_tolerate_none_value!("rate_pack_limits");
+ }
+
+ #[test]
+ #[should_panic(
+ expected = "Syntax error in rate_pack_limits value 'Booga!': should be -|-|-|- where L is low, H is high, R is routing, E is exit, BR is byte rate, and SR is service rate. All numbers should be in wei."
+ )]
+ fn rate_pack_limits_panics_at_syntax_error_in_value() {
+ persistent_config_plain_data_assertions_for_simple_get_method!(
+ "rate_pack_limits",
+ "Booga!",
+ // Irrelevant but necessary
+ RatePackLimits::new(
+ RatePack {
+ routing_byte_rate: 0,
+ routing_service_rate: 0,
+ exit_byte_rate: 0,
+ exit_service_rate: 0,
+ },
+ RatePack {
+ routing_byte_rate: 0,
+ routing_service_rate: 0,
+ exit_byte_rate: 0,
+ exit_service_rate: 0,
+ }
+ )
+ );
+ }
+
+ #[test]
+ #[should_panic(
+ expected = "Rate pack limits should have low limits less than high limits, but routing_byte_rate limits are 1-1"
+ )]
+ fn rate_pack_limits_panics_when_routing_byte_rate_limits_are_reversed() {
+ persistent_config_plain_data_assertions_for_simple_get_method!(
+ "rate_pack_limits",
+ "1-1|0-1|0-1|0-1",
+ // Irrelevant but necessary
+ RatePackLimits::new(
+ RatePack {
+ routing_byte_rate: 0,
+ routing_service_rate: 0,
+ exit_byte_rate: 0,
+ exit_service_rate: 0,
+ },
+ RatePack {
+ routing_byte_rate: 0,
+ routing_service_rate: 0,
+ exit_byte_rate: 0,
+ exit_service_rate: 0,
+ }
+ )
+ );
+ }
+
+ #[test]
+ #[should_panic(
+ expected = "Rate pack limits should have low limits less than high limits, but routing_service_rate limits are 1-1"
+ )]
+ fn rate_pack_limits_panics_when_routing_service_rate_limits_are_reversed() {
+ persistent_config_plain_data_assertions_for_simple_get_method!(
+ "rate_pack_limits",
+ "0-1|1-1|0-1|0-1",
+ // Irrelevant but necessary
+ RatePackLimits::new(
+ RatePack {
+ routing_byte_rate: 0,
+ routing_service_rate: 0,
+ exit_byte_rate: 0,
+ exit_service_rate: 0,
+ },
+ RatePack {
+ routing_byte_rate: 0,
+ routing_service_rate: 0,
+ exit_byte_rate: 0,
+ exit_service_rate: 0,
+ }
+ )
+ );
+ }
+
+ #[test]
+ #[should_panic(
+ expected = "Rate pack limits should have low limits less than high limits, but exit_byte_rate limits are 1-1"
+ )]
+ fn rate_pack_limits_panics_when_exit_byte_rate_limits_are_reversed() {
+ persistent_config_plain_data_assertions_for_simple_get_method!(
+ "rate_pack_limits",
+ "0-1|0-1|1-1|0-1",
+ // Irrelevant but necessary
+ RatePackLimits::new(
+ RatePack {
+ routing_byte_rate: 0,
+ routing_service_rate: 0,
+ exit_byte_rate: 0,
+ exit_service_rate: 0,
+ },
+ RatePack {
+ routing_byte_rate: 0,
+ routing_service_rate: 0,
+ exit_byte_rate: 0,
+ exit_service_rate: 0,
+ }
+ )
+ );
+ }
+
+ #[test]
+ #[should_panic(
+ expected = "Rate pack limits should have low limits less than high limits, but exit_service_rate limits are 1-1"
+ )]
+ fn rate_pack_limits_panics_when_exit_service_rate_limits_are_reversed() {
+ persistent_config_plain_data_assertions_for_simple_get_method!(
+ "rate_pack_limits",
+ "0-1|0-1|0-1|1-1",
+ // Irrelevant but necessary
+ RatePackLimits::new(
+ RatePack {
+ routing_byte_rate: 0,
+ routing_service_rate: 0,
+ exit_byte_rate: 0,
+ exit_service_rate: 0,
+ },
+ RatePack {
+ routing_byte_rate: 0,
+ routing_service_rate: 0,
+ exit_byte_rate: 0,
+ exit_service_rate: 0,
+ }
+ )
+ );
+ }
+
#[test]
fn scan_intervals_get_method_works() {
persistent_config_plain_data_assertions_for_simple_get_method!(
diff --git a/node/src/dispatcher.rs b/node/src/dispatcher.rs
index ac698e043..4b01cd593 100644
--- a/node/src/dispatcher.rs
+++ b/node/src/dispatcher.rs
@@ -271,13 +271,13 @@ mod tests {
let recording_arc = proxy_server.get_recording();
let awaiter = proxy_server.get_awaiter();
let client_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap();
- let reception_port = Some(8080);
+ let reception_port_opt = Some(8080);
let data: Vec = vec![9, 10, 11];
let ibcd_in = InboundClientData {
timestamp: SystemTime::now(),
client_addr,
- reception_port,
- sequence_number: Some(0),
+ reception_port_opt,
+ sequence_number_opt: Some(0),
last_data: false,
is_clandestine: false,
data: data.clone(),
@@ -310,15 +310,15 @@ mod tests {
let subject_addr = subject.start();
let (hopper, hopper_awaiter, hopper_recording_arc) = make_recorder();
let client_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap();
- let reception_port = Some(8080);
+ let reception_port_opt = Some(8080);
let data: Vec = vec![9, 10, 11];
let ibcd_in = InboundClientData {
timestamp: SystemTime::now(),
client_addr,
- reception_port,
+ reception_port_opt,
last_data: false,
is_clandestine: true,
- sequence_number: None,
+ sequence_number_opt: None,
data: data.clone(),
};
let mut peer_actors = peer_actors_builder().hopper(hopper).build();
@@ -350,15 +350,15 @@ mod tests {
let subject_addr = subject.start();
let subject_ibcd = subject_addr.recipient::();
let client_addr = SocketAddr::from_str("1.2.3.4:8765").unwrap();
- let reception_port = Some(1234);
+ let reception_port_opt = Some(1234);
let data: Vec = vec![9, 10, 11];
let ibcd_in = InboundClientData {
timestamp: SystemTime::now(),
client_addr,
- reception_port,
+ reception_port_opt,
last_data: false,
is_clandestine: false,
- sequence_number: Some(0),
+ sequence_number_opt: Some(0),
data: data.clone(),
};
@@ -376,15 +376,15 @@ mod tests {
let subject_addr = subject.start();
let subject_ibcd = subject_addr.recipient::();
let client_addr = SocketAddr::from_str("1.2.3.4:8765").unwrap();
- let reception_port = Some(1234);
+ let reception_port_opt = Some(1234);
let data: Vec = vec![9, 10, 11];
let ibcd_in = InboundClientData {
timestamp: SystemTime::now(),
client_addr,
- reception_port,
+ reception_port_opt,
last_data: false,
is_clandestine: true,
- sequence_number: None,
+ sequence_number_opt: None,
data: data.clone(),
};
@@ -406,7 +406,7 @@ mod tests {
let obcd = TransmitDataMsg {
endpoint: Endpoint::Socket(socket_addr),
last_data: false,
- sequence_number: Some(0),
+ sequence_number_opt: Some(0),
data: data.clone(),
};
@@ -430,7 +430,7 @@ mod tests {
let obcd = TransmitDataMsg {
endpoint: Endpoint::Socket(socket_addr),
last_data: false,
- sequence_number: None,
+ sequence_number_opt: None,
data: data.clone(),
};
let mut peer_actors = peer_actors_builder().build();
diff --git a/node/src/hopper/consuming_service.rs b/node/src/hopper/consuming_service.rs
index 7d5660a8b..0eb34cb52 100644
--- a/node/src/hopper/consuming_service.rs
+++ b/node/src/hopper/consuming_service.rs
@@ -100,10 +100,10 @@ impl ConsumingService {
let ibcd = InboundClientData {
timestamp: SystemTime::now(),
client_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0),
- reception_port: None,
+ reception_port_opt: None,
last_data: false,
is_clandestine: true,
- sequence_number: None,
+ sequence_number_opt: None,
data: encrypted_package.into(),
};
debug!(
@@ -119,7 +119,7 @@ impl ConsumingService {
endpoint: next_stop,
last_data: false, // Hopper-to-Hopper clandestine streams are never remotely killed
data: encrypted_package.into(),
- sequence_number: None,
+ sequence_number_opt: None,
};
debug!(
@@ -143,6 +143,7 @@ mod tests {
use crate::sub_lib::node_addr::NodeAddr;
use crate::sub_lib::route::Route;
use crate::sub_lib::route::RouteSegment;
+ use crate::sub_lib::stream_key::StreamKey;
use crate::test_utils::recorder::make_recorder;
use crate::test_utils::recorder::peer_actors_builder;
use crate::test_utils::{make_meaningless_message_type, make_paying_wallet};
@@ -168,7 +169,7 @@ mod tests {
CRYPTDE_PAIR.main.as_ref(),
&target_key,
&target_node_addr,
- make_meaningless_message_type(),
+ make_meaningless_message_type(StreamKey::make_meaningless_stream_key()),
)
.unwrap();
let system = System::new("");
@@ -192,7 +193,7 @@ mod tests {
&TransmitDataMsg {
endpoint: Endpoint::Socket(SocketAddr::from_str("1.2.1.2:1212").unwrap()),
last_data: false,
- sequence_number: None,
+ sequence_number_opt: None,
data: encodex(CRYPTDE_PAIR.main.as_ref(), &target_key, &lcp)
.unwrap()
.into(),
@@ -242,7 +243,7 @@ mod tests {
Some(TEST_DEFAULT_CHAIN.rec().contract),
)
.unwrap();
- let payload = make_meaningless_message_type();
+ let payload = make_meaningless_message_type(StreamKey::make_meaningless_stream_key());
let incipient_cores_package =
IncipientCoresPackage::new(cryptde, route.clone(), payload, &destination_key).unwrap();
let system = System::new("converts_incipient_message_to_live_and_sends_to_dispatcher");
@@ -266,7 +267,7 @@ mod tests {
TransmitDataMsg {
endpoint: Endpoint::Key(destination_key.clone()),
last_data: false,
- sequence_number: None,
+ sequence_number_opt: None,
data: expected_lcp_enc.into(),
},
*record,
@@ -289,7 +290,7 @@ mod tests {
Some(TEST_DEFAULT_CHAIN.rec().contract),
)
.unwrap();
- let payload = make_meaningless_message_type();
+ let payload = make_meaningless_message_type(StreamKey::make_meaningless_stream_key());
let incipient_cores_package =
IncipientCoresPackage::new(cryptde, route.clone(), payload, &destination_key).unwrap();
let system = System::new("consume_sends_zero_hop_incipient_directly_to_hopper");
@@ -317,10 +318,10 @@ mod tests {
InboundClientData {
timestamp: record.timestamp,
client_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0),
- reception_port: None,
+ reception_port_opt: None,
last_data: false,
is_clandestine: true,
- sequence_number: None,
+ sequence_number_opt: None,
data: expected_lcp_enc.into(),
},
);
@@ -340,7 +341,7 @@ mod tests {
IncipientCoresPackage::new(
CRYPTDE_PAIR.main.as_ref(),
Route { hops: vec![] },
- make_meaningless_message_type(),
+ make_meaningless_message_type(StreamKey::make_meaningless_stream_key()),
&PublicKey::new(&[1, 2]),
)
.unwrap(),
diff --git a/node/src/hopper/live_cores_package.rs b/node/src/hopper/live_cores_package.rs
index 2b05b27e8..cac27bebc 100644
--- a/node/src/hopper/live_cores_package.rs
+++ b/node/src/hopper/live_cores_package.rs
@@ -100,6 +100,7 @@ mod tests {
use crate::sub_lib::node_addr::NodeAddr;
use crate::sub_lib::route::RouteSegment;
use crate::sub_lib::route::{Route, RouteError};
+ use crate::sub_lib::stream_key::StreamKey;
use crate::test_utils::{
make_meaningless_message_type, make_meaningless_route, make_paying_wallet,
};
@@ -141,7 +142,9 @@ mod tests {
let relay_key = PublicKey::new(&[1, 2]);
let relay_cryptde = CryptDENull::from(&relay_key, TEST_DEFAULT_CHAIN);
let cryptde = CRYPTDE_PAIR.main.as_ref();
- let serialized_payload = serde_cbor::ser::to_vec(&make_meaningless_message_type()).unwrap();
+ let stream_key = StreamKey::make_meaningless_stream_key();
+ let serialized_payload =
+ serde_cbor::ser::to_vec(&make_meaningless_message_type(stream_key)).unwrap();
let encrypted_payload = cryptde
.encode(&destination_key, &PlainData::new(&serialized_payload))
.unwrap();
@@ -204,7 +207,7 @@ mod tests {
let key34 = PublicKey::new(&[3, 4]);
let node_addr34 = NodeAddr::new(&IpAddr::from_str("3.4.3.4").unwrap(), &[1234]);
let mut route = Route::single_hop(&key34, cryptde).unwrap();
- let payload = make_meaningless_message_type();
+ let payload = make_meaningless_message_type(StreamKey::make_meaningless_stream_key());
let incipient =
NoLookupIncipientCoresPackage::new(cryptde, &key34, &node_addr34, payload.clone())
@@ -228,7 +231,7 @@ mod tests {
cryptde,
&blank_key,
&node_addr34,
- make_meaningless_message_type(),
+ make_meaningless_message_type(StreamKey::make_meaningless_stream_key()),
);
assert_eq!(
@@ -254,7 +257,7 @@ mod tests {
Some(contract_address),
)
.unwrap();
- let payload = make_meaningless_message_type();
+ let payload = make_meaningless_message_type(StreamKey::make_meaningless_stream_key());
let incipient =
IncipientCoresPackage::new(cryptde, route.clone(), payload.clone(), &key56).unwrap();
@@ -280,7 +283,7 @@ mod tests {
let incipient = IncipientCoresPackage::new(
cryptde,
Route { hops: vec![] },
- make_meaningless_message_type(),
+ make_meaningless_message_type(StreamKey::make_meaningless_stream_key()),
&PublicKey::new(&[3, 4]),
)
.unwrap();
@@ -297,7 +300,8 @@ mod tests {
#[test]
fn expired_cores_package_can_be_constructed_from_live_cores_package() {
let immediate_neighbor_ip = SocketAddr::from_str("1.2.3.4:1234").unwrap();
- let payload = make_meaningless_message_type();
+ let stream_key = StreamKey::make_meaningless_stream_key();
+ let payload = make_meaningless_message_type(stream_key);
let first_stop_key = PublicKey::new(&[3, 4]);
let first_stop_cryptde = CryptDENull::from(&first_stop_key, TEST_DEFAULT_CHAIN);
let relay_key = PublicKey::new(&[1, 2]);
@@ -316,7 +320,6 @@ mod tests {
),
cryptde,
Some(paying_wallet.clone()),
- 1234,
Some(contract_address),
)
.unwrap();
@@ -375,11 +378,6 @@ mod tests {
Component::ProxyServer,
)
);
- assert_eq!(
- route.hops[0],
- crate::test_utils::encrypt_return_route_id(1234, cryptde),
- );
- route.hops.remove(0);
assert_eq!(
&route.hops[0].as_slice()[..8],
&[52, 52, 52, 52, 52, 52, 52, 52]
diff --git a/node/src/hopper/mod.rs b/node/src/hopper/mod.rs
index 588016440..b2f3d6dc0 100644
--- a/node/src/hopper/mod.rs
+++ b/node/src/hopper/mod.rs
@@ -149,6 +149,7 @@ mod tests {
use crate::sub_lib::hopper::IncipientCoresPackage;
use crate::sub_lib::route::Route;
use crate::sub_lib::route::RouteSegment;
+ use crate::sub_lib::stream_key::StreamKey;
use crate::test_utils::unshared_test_utils::prove_that_crash_request_handler_is_hooked_up;
use crate::test_utils::{
make_meaningless_message_type, make_paying_wallet, route_to_proxy_client,
@@ -175,8 +176,10 @@ mod tests {
fn panics_if_routing_service_is_unbound() {
let main_cryptde = CRYPTDE_PAIR.main.as_ref();
let client_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap();
- let route = route_to_proxy_client(&main_cryptde.public_key(), main_cryptde);
- let serialized_payload = serde_cbor::ser::to_vec(&make_meaningless_message_type()).unwrap();
+ let route = route_to_proxy_client(&main_cryptde.public_key(), main_cryptde, false);
+ let stream_key = StreamKey::make_meaningless_stream_key();
+ let serialized_payload =
+ serde_cbor::ser::to_vec(&make_meaningless_message_type(stream_key)).unwrap();
let data = main_cryptde
.encode(
&main_cryptde.public_key(),
@@ -193,10 +196,10 @@ mod tests {
let inbound_client_data = InboundClientData {
timestamp: SystemTime::now(),
client_addr,
- reception_port: None,
+ reception_port_opt: None,
last_data: false,
is_clandestine: false,
- sequence_number: None,
+ sequence_number_opt: None,
data: encrypted_package,
};
let system = System::new("panics_if_routing_service_is_unbound");
@@ -234,7 +237,7 @@ mod tests {
let incipient_package = IncipientCoresPackage::new(
main_cryptde,
route,
- make_meaningless_message_type(),
+ make_meaningless_message_type(StreamKey::make_meaningless_stream_key()),
&main_cryptde.public_key(),
)
.unwrap();
diff --git a/node/src/hopper/routing_service.rs b/node/src/hopper/routing_service.rs
index 1ad3ba578..e2f637820 100644
--- a/node/src/hopper/routing_service.rs
+++ b/node/src/hopper/routing_service.rs
@@ -186,10 +186,10 @@ impl RoutingService {
let inbound_client_data = InboundClientData {
timestamp: ibcd_but_data.timestamp,
client_addr: ibcd_but_data.client_addr,
- reception_port: ibcd_but_data.reception_port,
+ reception_port_opt: ibcd_but_data.reception_port_opt,
last_data: ibcd_but_data.last_data,
is_clandestine: ibcd_but_data.is_clandestine,
- sequence_number: ibcd_but_data.sequence_number,
+ sequence_number_opt: ibcd_but_data.sequence_number_opt,
data: payload.into(),
};
self.routing_service_subs
@@ -495,7 +495,7 @@ impl RoutingService {
endpoint: Endpoint::Key(next_hop.public_key),
last_data,
data: next_live_package_enc.into(),
- sequence_number: None,
+ sequence_number_opt: None,
})
}
}
@@ -542,16 +542,18 @@ mod tests {
#[test]
fn dns_resolution_failures_are_reported_to_the_proxy_server() {
- let cryptde_pair = CRYPTDE_PAIR.clone();
- let route =
- route_to_proxy_server(&cryptde_pair.main.public_key(), cryptde_pair.main.as_ref());
+ let route = route_to_proxy_server(
+ &CRYPTDE_PAIR.main.public_key(),
+ CRYPTDE_PAIR.main.as_ref(),
+ false,
+ );
let stream_key = StreamKey::make_meaningless_stream_key();
let dns_resolve_failure = DnsResolveFailure_0v1::new(stream_key);
let lcp = LiveCoresPackage::new(
route,
encodex(
- cryptde_pair.alias.as_ref(),
- &cryptde_pair.alias.public_key(),
+ CRYPTDE_PAIR.alias.as_ref(),
+ &CRYPTDE_PAIR.alias.public_key(),
&MessageType::DnsResolveFailed(VersionedData::new(
&crate::sub_lib::migrations::dns_resolve_failure::MIGRATIONS,
&dns_resolve_failure.clone(),
@@ -560,16 +562,16 @@ mod tests {
.unwrap(),
);
let data_enc = encodex(
- cryptde_pair.main.as_ref(),
- &cryptde_pair.main.public_key(),
+ CRYPTDE_PAIR.main.as_ref(),
+ &CRYPTDE_PAIR.main.public_key(),
&lcp,
)
.unwrap();
let inbound_client_data = InboundClientData {
timestamp: SystemTime::now(),
client_addr: SocketAddr::from_str("1.2.3.4:5678").unwrap(),
- reception_port: None,
- sequence_number: None,
+ reception_port_opt: None,
+ sequence_number_opt: None,
last_data: false,
is_clandestine: false,
data: data_enc.into(),
@@ -579,7 +581,7 @@ mod tests {
let system = System::new("dns_resolution_failures_are_reported_to_the_proxy_server");
let peer_actors = peer_actors_builder().proxy_server(proxy_server).build();
let subject = RoutingService::new(
- cryptde_pair,
+ CRYPTDE_PAIR.clone(),
RoutingServiceSubs {
proxy_client_subs_opt: peer_actors.proxy_client_opt,
proxy_server_subs: peer_actors.proxy_server,
@@ -607,36 +609,38 @@ mod tests {
fn logs_and_ignores_message_that_cannot_be_deserialized() {
init_test_logging();
let test_name = "logs_and_ignores_message_that_cannot_be_deserialized";
- let cryptde_pair = CRYPTDE_PAIR.clone();
- let route =
- route_from_proxy_client(&cryptde_pair.main.public_key(), cryptde_pair.main.as_ref());
+ let route = route_from_proxy_client(
+ &CRYPTDE_PAIR.main.public_key(),
+ CRYPTDE_PAIR.main.as_ref(),
+ false,
+ );
let lcp = LiveCoresPackage::new(
route,
encodex(
- cryptde_pair.main.as_ref(),
- &cryptde_pair.main.public_key(),
+ CRYPTDE_PAIR.main.as_ref(),
+ &CRYPTDE_PAIR.main.public_key(),
&[42u8],
)
.unwrap(),
);
let data_enc = encodex(
- cryptde_pair.main.as_ref(),
- &cryptde_pair.main.public_key(),
+ CRYPTDE_PAIR.main.as_ref(),
+ &CRYPTDE_PAIR.main.public_key(),
&lcp,
)
.unwrap();
let inbound_client_data = InboundClientData {
timestamp: SystemTime::now(),
client_addr: SocketAddr::from_str("1.2.3.4:5678").unwrap(),
- reception_port: None,
- sequence_number: None,
+ reception_port_opt: None,
+ sequence_number_opt: None,
last_data: false,
is_clandestine: false,
data: data_enc.into(),
};
let peer_actors = peer_actors_builder().build();
let mut subject = RoutingService::new(
- cryptde_pair,
+ CRYPTDE_PAIR.clone(),
RoutingServiceSubs {
proxy_client_subs_opt: peer_actors.proxy_client_opt,
proxy_server_subs: peer_actors.proxy_server,
@@ -664,7 +668,7 @@ mod tests {
let test_name = "logs_and_ignores_message_that_cannot_be_decrypted";
let main_cryptde = CryptDEReal::new(TEST_DEFAULT_CHAIN);
let rogue_cryptde = CryptDEReal::new(TEST_DEFAULT_CHAIN);
- let route = route_from_proxy_client(main_cryptde.public_key(), &main_cryptde);
+ let route = route_from_proxy_client(main_cryptde.public_key(), &main_cryptde, false);
let lcp = LiveCoresPackage::new(
route,
encodex(&rogue_cryptde, rogue_cryptde.public_key(), &[42u8]).unwrap(),
@@ -673,8 +677,8 @@ mod tests {
let inbound_client_data = InboundClientData {
timestamp: SystemTime::now(),
client_addr: SocketAddr::from_str("1.2.3.4:5678").unwrap(),
- reception_port: None,
- sequence_number: None,
+ reception_port_opt: None,
+ sequence_number_opt: None,
last_data: false,
is_clandestine: false,
data: data_enc.into(),
@@ -711,7 +715,7 @@ mod tests {
fn logs_and_ignores_message_that_had_invalid_destination() {
init_test_logging();
let main_cryptde = CRYPTDE_PAIR.main.as_ref();
- let route = route_from_proxy_client(&main_cryptde.public_key(), main_cryptde);
+ let route = route_from_proxy_client(&main_cryptde.public_key(), main_cryptde, false);
let payload = GossipBuilder::empty();
let lcp = LiveCoresPackage::new(
route,
@@ -726,8 +730,8 @@ mod tests {
let inbound_client_data = InboundClientData {
timestamp: SystemTime::now(),
client_addr: SocketAddr::from_str("1.2.3.4:5678").unwrap(),
- reception_port: None,
- sequence_number: None,
+ reception_port_opt: None,
+ sequence_number_opt: None,
last_data: false,
is_clandestine: false,
data: data_enc.into(),
@@ -757,7 +761,7 @@ mod tests {
BAN_CACHE.clear();
let main_cryptde = CRYPTDE_PAIR.main.as_ref();
let (component, _, component_recording_arc) = make_recorder();
- let route = route_to_proxy_client(&main_cryptde.public_key(), main_cryptde);
+ let route = route_to_proxy_client(&main_cryptde.public_key(), main_cryptde, false);
let payload = make_request_payload(0, main_cryptde);
let lcp = LiveCoresPackage::new(
route,
@@ -776,8 +780,8 @@ mod tests {
let inbound_client_data = InboundClientData {
timestamp: SystemTime::now(),
client_addr: SocketAddr::from_str("1.2.3.4:5678").unwrap(),
- reception_port: None,
- sequence_number: None,
+ reception_port_opt: None,
+ sequence_number_opt: None,
last_data: true,
is_clandestine: false,
data: data_enc.into(),
@@ -828,7 +832,7 @@ mod tests {
BAN_CACHE.clear();
let test_name = "complains_about_live_message_for_nonexistent_proxy_client";
let main_cryptde = CRYPTDE_PAIR.main.as_ref();
- let route = route_to_proxy_client(&main_cryptde.public_key(), main_cryptde);
+ let route = route_to_proxy_client(&main_cryptde.public_key(), main_cryptde, false);
let payload = make_request_payload(0, main_cryptde);
let lcp = LiveCoresPackage::new(
route,
@@ -846,8 +850,8 @@ mod tests {
let inbound_client_data = InboundClientData {
timestamp: SystemTime::now(),
client_addr: SocketAddr::from_str("1.2.3.4:5678").unwrap(),
- reception_port: None,
- sequence_number: None,
+ reception_port_opt: None,
+ sequence_number_opt: None,
last_data: true,
is_clandestine: false,
data: data_enc.into(),
@@ -887,7 +891,7 @@ mod tests {
let main_cryptde = CRYPTDE_PAIR.main.as_ref();
let alias_cryptde = CRYPTDE_PAIR.alias.as_ref();
let (proxy_server, _, proxy_server_recording_arc) = make_recorder();
- let route = route_to_proxy_server(&main_cryptde.public_key(), main_cryptde);
+ let route = route_to_proxy_server(&main_cryptde.public_key(), main_cryptde, false);
let payload = make_response_payload(0);
let lcp = LiveCoresPackage::new(
route,
@@ -903,10 +907,10 @@ mod tests {
let inbound_client_data = InboundClientData {
timestamp: SystemTime::now(),
client_addr: SocketAddr::from_str("1.3.2.4:5678").unwrap(),
- reception_port: None,
+ reception_port_opt: None,
last_data: false,
is_clandestine: true,
- sequence_number: None,
+ sequence_number_opt: None,
data: lcp_enc.into(),
};
@@ -980,10 +984,10 @@ mod tests {
let inbound_client_data = InboundClientData {
timestamp: SystemTime::now(),
client_addr: SocketAddr::from_str("1.3.2.4:5678").unwrap(),
- reception_port: None,
+ reception_port_opt: None,
last_data: false,
is_clandestine: true,
- sequence_number: None,
+ sequence_number_opt: None,
data: data_enc.into(),
};
@@ -1053,10 +1057,10 @@ mod tests {
let inbound_client_data = InboundClientData {
timestamp: SystemTime::now(),
client_addr: SocketAddr::from_str("1.3.2.4:5678").unwrap(),
- reception_port: None,
+ reception_port_opt: None,
last_data: false,
is_clandestine: true,
- sequence_number: None,
+ sequence_number_opt: None,
data: data_enc.into(),
};
@@ -1129,10 +1133,10 @@ mod tests {
let inbound_client_data = InboundClientData {
timestamp: SystemTime::now(),
client_addr: SocketAddr::from_str("1.2.3.4:5678").unwrap(),
- reception_port: None,
+ reception_port_opt: None,
last_data: true,
is_clandestine: true,
- sequence_number: None,
+ sequence_number_opt: None,
data: data_enc.into(),
};
@@ -1172,7 +1176,7 @@ mod tests {
TransmitDataMsg {
endpoint: Endpoint::Key(next_key.clone()),
last_data: true,
- sequence_number: None,
+ sequence_number_opt: None,
data: expected_lcp_enc.into(),
}
);
@@ -1221,10 +1225,10 @@ mod tests {
let inbound_client_data = InboundClientData {
timestamp: SystemTime::now(),
client_addr: SocketAddr::from_str("1.2.3.4:5678").unwrap(),
- reception_port: None,
+ reception_port_opt: None,
last_data: true,
is_clandestine: true,
- sequence_number: None,
+ sequence_number_opt: None,
data: data_enc.into(),
};
@@ -1264,10 +1268,10 @@ mod tests {
InboundClientData {
timestamp: record.timestamp,
client_addr: SocketAddr::from_str("1.2.3.4:5678").unwrap(),
- reception_port: None,
+ reception_port_opt: None,
last_data: true,
is_clandestine: true,
- sequence_number: None,
+ sequence_number_opt: None,
data: expected_lcp_enc.into()
}
);
@@ -1284,7 +1288,7 @@ mod tests {
let origin_key = PublicKey::new(&[1, 2]);
let origin_cryptde = CryptDENull::from(&origin_key, TEST_DEFAULT_CHAIN);
let destination_key = PublicKey::new(&[3, 4]);
- let payload = make_meaningless_message_type();
+ let payload = make_meaningless_message_type(StreamKey::make_meaningless_stream_key());
let route = Route::one_way(
RouteSegment::new(
vec![&origin_key, &main_cryptde.public_key(), &destination_key],
@@ -1305,10 +1309,10 @@ mod tests {
let inbound_client_data = InboundClientData {
timestamp: SystemTime::now(),
client_addr: SocketAddr::from_str("1.2.3.4:5678").unwrap(),
- reception_port: None,
+ reception_port_opt: None,
last_data: true,
is_clandestine: true,
- sequence_number: None,
+ sequence_number_opt: None,
data: data_enc.into(),
};
let system = System::new(test_name);
@@ -1402,10 +1406,10 @@ mod tests {
let inbound_client_data = InboundClientData {
timestamp: SystemTime::now(),
client_addr: SocketAddr::from_str("1.2.3.4:5678").unwrap(),
- reception_port: None,
+ reception_port_opt: None,
last_data: true,
is_clandestine: true,
- sequence_number: None,
+ sequence_number_opt: None,
data: data_enc.into(),
};
let system = System::new(test_name);
@@ -1579,10 +1583,10 @@ mod tests {
let inbound_client_data = InboundClientData {
timestamp: SystemTime::now(),
client_addr: SocketAddr::from_str("1.2.3.4:5678").unwrap(),
- reception_port: None,
+ reception_port_opt: None,
last_data: true,
is_clandestine: true,
- sequence_number: None,
+ sequence_number_opt: None,
data: data_enc.into(),
};
let system = System::new("test");
@@ -1652,10 +1656,10 @@ mod tests {
let inbound_client_data = InboundClientData {
timestamp: SystemTime::now(),
client_addr: SocketAddr::from_str("1.2.3.4:5678").unwrap(),
- reception_port: None,
+ reception_port_opt: None,
last_data: true,
is_clandestine: true,
- sequence_number: None,
+ sequence_number_opt: None,
data: data_enc.into(),
};
let system = System::new("test");
@@ -1699,10 +1703,10 @@ mod tests {
let inbound_client_data = InboundClientData {
timestamp: SystemTime::now(),
client_addr: SocketAddr::from_str("1.2.3.4:5678").unwrap(),
- reception_port: None,
+ reception_port_opt: None,
last_data: true,
is_clandestine: true,
- sequence_number: None,
+ sequence_number_opt: None,
data: vec![],
};
let system = System::new("consume_logs_error_when_given_bad_input_data");
@@ -1758,10 +1762,10 @@ mod tests {
let inbound_client_data = InboundClientData {
timestamp: SystemTime::now(),
client_addr: SocketAddr::from_str("1.2.3.4:5678").unwrap(),
- reception_port: None,
+ reception_port_opt: None,
last_data: true,
is_clandestine: true,
- sequence_number: None,
+ sequence_number_opt: None,
data: data_enc.into(),
};
let system = System::new("consume_logs_error_when_given_bad_input_data");
@@ -1828,10 +1832,10 @@ mod tests {
let ibcd = InboundClientData {
timestamp: SystemTime::now(),
client_addr: SocketAddr::from_str("1.2.3.4:5678").unwrap(),
- reception_port: None,
+ reception_port_opt: None,
last_data: true,
is_clandestine: true,
- sequence_number: None,
+ sequence_number_opt: None,
data: vec![],
};
@@ -1893,7 +1897,7 @@ mod tests {
&ClientRequestPayload_0v1 {
stream_key: StreamKey::make_meaningless_stream_key(),
sequenced_packet: SequencedPacket::new(vec![1, 2, 3, 4], 1234, false),
- target_hostname: Some("hostname".to_string()),
+ target_hostname: "hostname".to_string(),
target_port: 1234,
protocol: ProxyProtocol::TLS,
originator_public_key: PublicKey::new(b"1234"),
diff --git a/node/src/neighborhood/dot_graph.rs b/node/src/neighborhood/dot_graph.rs
index 78de9f339..e6ae6f056 100644
--- a/node/src/neighborhood/dot_graph.rs
+++ b/node/src/neighborhood/dot_graph.rs
@@ -18,7 +18,7 @@ pub struct NodeRenderableInner {
pub struct NodeRenderable {
pub inner: Option,
pub public_key: PublicKey,
- pub node_addr: Option,
+ pub node_addr_opt: Option,
pub known_source: bool,
pub known_target: bool,
pub is_present: bool,
@@ -60,7 +60,7 @@ impl NodeRenderable {
} else {
&public_key_str
};
- let node_addr_string = match self.node_addr {
+ let node_addr_string = match self.node_addr_opt {
None => String::new(),
Some(ref na) => format!("\\n{}", na),
};
@@ -112,7 +112,7 @@ mod tests {
country_code: "ZZ".to_string(),
}),
public_key: public_key.clone(),
- node_addr: None,
+ node_addr_opt: None,
known_source: false,
known_target: false,
is_present: true,
@@ -141,7 +141,7 @@ mod tests {
country_code: "ZZ".to_string(),
}),
public_key: public_key.clone(),
- node_addr: None,
+ node_addr_opt: None,
known_source: false,
known_target: false,
is_present: true,
diff --git a/node/src/neighborhood/gossip.rs b/node/src/neighborhood/gossip.rs
index 15daeaf91..8381b1d80 100644
--- a/node/src/neighborhood/gossip.rs
+++ b/node/src/neighborhood/gossip.rs
@@ -14,16 +14,20 @@ use serde_cbor::Value;
use serde_derive::{Deserialize, Serialize};
use std::collections::HashSet;
use std::convert::{TryFrom, TryInto};
-use std::fmt::Debug;
use std::fmt::Error;
use std::fmt::Formatter;
use std::fmt::Write as _;
+use std::fmt::{Debug, Display};
use std::net::{IpAddr, SocketAddr};
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GossipNodeRecord {
pub signed_data: PlainData,
pub signature: CryptData,
+ // This field should probably not exist. If the Gossip source is our next-door neighbor,
+ // we can get its IP address from the network stack, which is more reliable than an
+ // unsigned data field. If the Gossip source is not our next-door neighbor, we should
+ // not know its IP address anyway.
pub node_addr_opt: Option,
}
@@ -389,7 +393,7 @@ impl Gossip_0v1 {
country_code,
}),
public_key: nri.public_key.clone(),
- node_addr: addr.clone(),
+ node_addr_opt: addr.clone(),
known_source: nri.public_key == source.public_key,
known_target: nri.public_key == target.public_key,
is_present: true,
@@ -399,7 +403,7 @@ impl Gossip_0v1 {
node_renderables.push(NodeRenderable {
inner: None,
public_key: k.clone(),
- node_addr: None,
+ node_addr_opt: None,
known_source: false,
known_target: false,
is_present: false,
@@ -472,6 +476,28 @@ pub struct AccessibleGossipRecord {
pub inner: NodeRecordInner_0v1,
}
+impl Display for AccessibleGossipRecord {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ match self.node_addr_opt {
+ Some(ref addr) => {
+ write!(
+ f,
+ "{} {} {} {} {} {} {} {}",
+ self.inner.behavior_string(),
+ self.inner.version_string(),
+ self.inner.country_code_string(),
+ self.inner.public_key_string(),
+ addr.ip_addr(),
+ self.inner.wallet_string(),
+ self.inner.rate_pack_string(),
+ self.inner.neighbors_string()
+ )
+ }
+ None => write!(f, "{}", self.inner),
+ }
+ }
+}
+
impl AccessibleGossipRecord {
pub fn regenerate_signed_gossip(&mut self, cryptde: &dyn CryptDE) {
let (signed_gossip, signature) = regenerate_signed_gossip(&self.inner, cryptde);
@@ -605,6 +631,52 @@ mod tests {
assert_eq!(gossip.node_records.remove(0).node_addr_opt, None)
}
+ #[test]
+ fn accessible_gossip_record_to_string_without_ip() {
+ let mut node_record = make_node_record(1234, true);
+ node_record.inner.country_code_opt = Some("AD".to_string());
+ let db = db_from_node(&node_record);
+ let subject = AccessibleGossipRecord::from((&db, node_record.public_key(), false));
+
+ let result = subject.to_string();
+
+ assert_eq!(
+ result,
+ "AR v0 AD AQIDBA 0x546900db8d6e0937497133d1ae6fdf5f4b75bcd0 1235|1434|1237|1634 []"
+ );
+ }
+
+ #[test]
+ fn accessible_gossip_record_to_string_with_ip() {
+ let mut node_record = make_node_record(1234, true);
+ node_record.inner.country_code_opt = Some("AD".to_string());
+ let db = db_from_node(&node_record);
+ let subject = AccessibleGossipRecord::from((&db, node_record.public_key(), true));
+
+ let result = subject.to_string();
+
+ assert_eq!(
+ result,
+ "AR v0 AD AQIDBA 1.2.3.4 0x546900db8d6e0937497133d1ae6fdf5f4b75bcd0 1235|1434|1237|1634 []"
+ );
+ }
+
+ #[test]
+ fn accessible_gossip_record_to_string_with_ip_and_really_big_version() {
+ let mut node_record = make_node_record(1234, true);
+ node_record.inner.country_code_opt = Some("AD".to_string());
+ let db = db_from_node(&node_record);
+ let mut subject = AccessibleGossipRecord::from((&db, node_record.public_key(), true));
+ subject.inner.version = 4_000_000_000;
+
+ let result = subject.to_string();
+
+ assert_eq!(
+ result,
+ "AR v4000000000 AD AQIDBA 1.2.3.4 0x546900db8d6e0937497133d1ae6fdf5f4b75bcd0 1235|1434|1237|1634 []"
+ );
+ }
+
#[test]
fn gossip_node_record_keeps_all_half_neighbors() {
let mut this_node = make_node_record(1234, true);
diff --git a/node/src/neighborhood/gossip_acceptor.rs b/node/src/neighborhood/gossip_acceptor.rs
index f99eba27d..4699a7e88 100644
--- a/node/src/neighborhood/gossip_acceptor.rs
+++ b/node/src/neighborhood/gossip_acceptor.rs
@@ -1,17 +1,22 @@
// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved.
+use crate::db_config::persistent_configuration::PersistentConfiguration;
use crate::neighborhood::gossip::{
AccessibleGossipRecord, GossipBuilder, GossipNodeRecord, Gossip_0v1,
};
+use crate::neighborhood::malefactor::Malefactor;
use crate::neighborhood::neighborhood_database::{NeighborhoodDatabase, NeighborhoodDatabaseError};
use crate::neighborhood::node_record::NodeRecord;
use crate::neighborhood::UserExitPreferences;
use crate::sub_lib::cryptde::{CryptDE, PublicKey};
use crate::sub_lib::neighborhood::{
ConnectionProgressEvent, ConnectionProgressMessage, GossipFailure_0v1, NeighborhoodMetadata,
+ RatePackLimits,
};
use crate::sub_lib::node_addr::NodeAddr;
+use itertools::Itertools;
use masq_lib::logger::Logger;
+use std::any::Any;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::net::{IpAddr, SocketAddr};
@@ -26,6 +31,7 @@ pub const MAX_DEGREE: usize = 5;
const PASS_GOSSIP_EXPIRED_TIME: Duration = Duration::from_secs(60);
#[derive(Clone, PartialEq, Eq, Debug)]
+#[allow(clippy::large_enum_variant)]
pub enum GossipAcceptanceResult {
// The incoming Gossip produced database changes. Generate standard Gossip and broadcast.
Accepted,
@@ -33,17 +39,16 @@ pub enum GossipAcceptanceResult {
Reply(Gossip_0v1, PublicKey, NodeAddr),
// The incoming Gossip was proper, and we tried to accept it, but couldn't.
Failed(GossipFailure_0v1, PublicKey, NodeAddr),
- // The incoming Gossip contained nothing we didn't know. Don't send out any Gossip because of it.
- Ignored,
// Gossip was ignored because it was evil: ban the sender of the Gossip as a malefactor.
- Ban(String),
+ Ban(Malefactor),
}
#[derive(Clone, PartialEq, Eq, Debug)]
+#[allow(clippy::large_enum_variant)]
enum Qualification {
Matched,
- Unmatched,
- Malformed(String),
+ Unmatched(String),
+ Malformed(Malefactor),
}
trait NamedType {
@@ -64,10 +69,13 @@ trait GossipHandler: NamedType + Send /* Send because lazily-written tests requi
agrs: Vec,
gossip_source: SocketAddr,
neighborhood_metadata: NeighborhoodMetadata,
- ) -> GossipAcceptanceResult;
+ ) -> Vec;
+
+ fn as_any(&self) -> &dyn Any;
}
struct DebutHandler {
+ rate_pack_limits: RatePackLimits,
logger: Logger,
}
@@ -88,34 +96,23 @@ impl GossipHandler for DebutHandler {
gossip_source: SocketAddr,
) -> Qualification {
if agrs.len() != 1 {
- return Qualification::Unmatched;
+ return Qualification::Unmatched(format!(
+ "Debut has 1 Node record, not {}",
+ agrs.len()
+ ));
}
if database.node_by_key(&agrs[0].inner.public_key).is_some() {
- return Qualification::Unmatched;
+ return Qualification::Unmatched("Node record already in database".to_string());
}
- //TODO: Create optimization card to drive in the following logic:
- // Imagine a brand-new network, consisting only of Node A.
- // When Node B debuts, Node A cannot respond with an Introduction,
- // since there's nobody to introduce. Therefore, Node A must
- // respond with a single-Node Gossip that will currently be
- // interpreted as a Debut by Node B, resulting in another
- // single-Node Gossip from B to A. This last Gossip isn't a
- // problem, because Node A will ignore it since B is already
- // in its database. However, there is a possible optimization:
- // drive in the code below, and Node B will no longer interpret
- // Node A's acceptance Gossip as another Debut, because it will
- // see that Node A already has Node B as a neighbor. This means
- // Node A's original response will be interpreted as Standard
- // Gossip.
- // if agrs[0].inner.neighbors.contains(database.root_key()) {
- // return Qualification::Unmatched;
- // }
match &agrs[0].node_addr_opt {
None => {
if agrs[0].inner.accepts_connections {
- Qualification::Malformed(format!(
- "Debut from {} for {} contained no NodeAddr",
- gossip_source, agrs[0].inner.public_key
+ Qualification::Malformed(Malefactor::new(
+ Some(agrs[0].inner.public_key.clone()),
+ Some(gossip_source.ip()),
+ Some(agrs[0].inner.earning_wallet.clone()),
+ None,
+ "Debut contained no NodeAddr".to_string(),
))
} else {
Qualification::Matched
@@ -124,19 +121,25 @@ impl GossipHandler for DebutHandler {
Some(node_addr) => {
if agrs[0].inner.accepts_connections {
if node_addr.ports().is_empty() {
- Qualification::Malformed(format!(
- "Debut from {} for {} contained NodeAddr with no ports",
- gossip_source, agrs[0].inner.public_key
+ Qualification::Malformed(Malefactor::new(
+ Some(agrs[0].inner.public_key.clone()),
+ Some(gossip_source.ip()),
+ Some(agrs[0].inner.earning_wallet.clone()),
+ None,
+ "Debut contained NodeAddr with no ports".to_string(),
))
} else if node_addr.ip_addr() == gossip_source.ip() {
Qualification::Matched
} else {
- Qualification::Unmatched
+ Qualification::Unmatched("Node record is not Gossip source".to_string())
}
} else {
- Qualification::Malformed(format!(
- "Debut from {} for {} does not accept connections, yet contained NodeAddr",
- gossip_source, agrs[0].inner.public_key
+ Qualification::Malformed(Malefactor::new(
+ Some(agrs[0].inner.public_key.clone()),
+ Some(gossip_source.ip()),
+ Some(agrs[0].inner.earning_wallet.clone()),
+ None,
+ "Debut does not accept connections, yet contained NodeAddr".to_string(),
))
}
}
@@ -150,7 +153,7 @@ impl GossipHandler for DebutHandler {
mut agrs: Vec,
gossip_source: SocketAddr,
neighborhood_metadata: NeighborhoodMetadata,
- ) -> GossipAcceptanceResult {
+ ) -> Vec {
let source_agr = {
let mut agr = agrs.remove(0); // empty Gossip shouldn't get here
if agr.node_addr_opt.is_none() {
@@ -158,6 +161,28 @@ impl GossipHandler for DebutHandler {
}
agr
};
+ if let Err(message) = GossipAcceptorReal::validate_incoming_agr(
+ &source_agr,
+ format!(
+ "Debut from {} at {}",
+ source_agr.inner.public_key, gossip_source
+ ),
+ &self.rate_pack_limits,
+ &self.logger,
+ ) {
+ return vec![GossipAcceptanceResult::Ban(Malefactor::new(
+ Some(source_agr.inner.public_key.clone()),
+ Some(
+ source_agr
+ .node_addr_opt
+ .expect("IP address disappeared")
+ .ip_addr(),
+ ),
+ Some(source_agr.inner.earning_wallet),
+ None,
+ message,
+ ))];
+ }
let source_key = source_agr.inner.public_key.clone();
let source_node_addr = source_agr
.node_addr_opt
@@ -178,11 +203,11 @@ impl GossipHandler for DebutHandler {
preferred_key,
preferred_ip,
);
- return GossipAcceptanceResult::Reply(
+ return vec![GossipAcceptanceResult::Reply(
Self::make_pass_gossip(database, preferred_key),
source_key,
source_node_addr,
- );
+ )];
}
if let Ok(result) = self.try_accept_debut(
cryptde,
@@ -191,7 +216,7 @@ impl GossipHandler for DebutHandler {
gossip_source,
neighborhood_metadata.user_exit_preferences_opt,
) {
- return result;
+ return vec![result];
}
debug!(self.logger, "Seeking neighbor for Pass");
let lcn_key = match Self::find_least_connected_half_neighbor_excluding(
@@ -203,14 +228,14 @@ impl GossipHandler for DebutHandler {
"Neighbor count at maximum, but no non-common neighbors. DebutHandler is reluctantly ignoring debut from {} at {}",
source_key, source_node_addr
);
- return GossipAcceptanceResult::Failed(
+ return vec![GossipAcceptanceResult::Failed(
GossipFailure_0v1::NoSuitableNeighbors,
(&source_agr.inner.public_key).clone(),
(&source_agr
.node_addr_opt
.expect("Debuter's NodeAddr disappeared"))
.clone(),
- );
+ )];
}
Some(key) => key,
};
@@ -230,17 +255,29 @@ impl GossipHandler for DebutHandler {
lcn_key,
lcn_ip_str
);
- GossipAcceptanceResult::Reply(
+ vec![GossipAcceptanceResult::Reply(
Self::make_pass_gossip(database, lcn_key),
source_key,
source_node_addr,
- )
+ )]
+ }
+
+ fn as_any(&self) -> &dyn Any {
+ self
}
}
+enum IntroductionAttempt {
+ Success(Gossip_0v1, PublicKey, NodeAddr),
+ Failure,
+}
+
impl DebutHandler {
- fn new(logger: Logger) -> DebutHandler {
- DebutHandler { logger }
+ fn new(rate_pack_limits: &RatePackLimits, logger: Logger) -> DebutHandler {
+ DebutHandler {
+ rate_pack_limits: rate_pack_limits.clone(),
+ logger,
+ }
}
fn find_more_appropriate_neighbor<'b>(
@@ -253,10 +290,8 @@ impl DebutHandler {
let qualified_neighbors: Vec<&PublicKey> = neighbor_vec
.into_iter()
.filter(|k| {
- database
- .node_by_key(*k)
- .expect("Node disappeared")
- .accepts_connections()
+ let node = database.node_by_key(*k).expect("Node disappeared");
+ node.accepts_connections() && node.routes_data()
})
.skip_while(|k| database.gossip_target_degree(*k) <= 2)
.collect();
@@ -321,50 +356,93 @@ impl DebutHandler {
root_mut.increment_version();
root_mut.regenerate_signed_gossip(cryptde);
trace!(self.logger, "Current database: {}", database.to_dot_graph());
- if Self::should_not_make_another_introduction(debuting_agr) {
+ if Self::should_not_make_another_introduction(database, debuting_agr) {
let ip_addr_str = match &debuting_agr.node_addr_opt {
Some(node_addr) => node_addr.ip_addr().to_string(),
None => "?.?.?.?".to_string(),
};
- debug!(self.logger, "Node {} at {} is responding to first introduction: sending standard Gossip instead of further introduction",
- debuting_agr.inner.public_key,
- ip_addr_str);
- Ok(GossipAcceptanceResult::Accepted)
- } else {
- match self.make_introduction(database, debuting_agr, gossip_source) {
- Some((introduction, target_key, target_node_addr)) => {
- Ok(GossipAcceptanceResult::Reply(
- introduction,
- target_key,
- target_node_addr,
- ))
+ if Self::counts_us_among_neighbors(debuting_agr, database) {
+ debug!(
+ self.logger,
+ "Can't send introduction to Node {} at {}, but it already knows us; sending Standard Gossip",
+ &debut_node_key,
+ gossip_source,
+ );
+ Ok(GossipAcceptanceResult::Accepted)
+ } else {
+ match GossipAcceptorReal::make_debut_triple(database, debuting_agr) {
+ Ok((redebut_gossip, public_key, node_addr)) => {
+ debug!(
+ self.logger,
+ "Can't send introduction to Node {} at {}; sending redebut instead",
+ debuting_agr.inner.public_key,
+ ip_addr_str
+ );
+ Ok(GossipAcceptanceResult::Reply(
+ redebut_gossip,
+ public_key,
+ node_addr,
+ ))
+ }
+ Err(e) => {
+ debug!(
+ self.logger,
+ "Can't send either introduction or redebut to Node {} at {} ({}); sending standard Gossip instead",
+ debuting_agr.inner.public_key,
+ ip_addr_str,
+ e
+ );
+ Ok(GossipAcceptanceResult::Accepted)
+ }
}
- None => {
- debug!(
- self.logger,
- "DebutHandler has no one to introduce, but is debuting back to {} at {}",
- &debut_node_key,
- gossip_source,
- );
+ }
+ } else {
+ match self.try_to_make_introduction(database, debuting_agr, gossip_source) {
+ IntroductionAttempt::Success(
+ introduction,
+ target_key,
+ target_node_addr,
+ ) => Ok(GossipAcceptanceResult::Reply(
+ introduction,
+ target_key,
+ target_node_addr,
+ )),
+ IntroductionAttempt::Failure => {
trace!(
self.logger,
"DebutHandler database state: {}",
&database.to_dot_graph(),
);
- let debut_gossip = Self::create_debut_gossip_response(
- cryptde,
- database,
- debut_node_key,
- );
- Ok(GossipAcceptanceResult::Reply(
- debut_gossip,
- debuting_agr.inner.public_key.clone(),
- debuting_agr
- .node_addr_opt
- .as_ref()
- .expect("Debut gossip always has an IP")
- .clone(),
- ))
+ if Self::counts_us_among_neighbors(debuting_agr, database) {
+ debug!(
+ self.logger,
+ "DebutHandler has no one to introduce and debutant already has us as a neighbor; accepting {} at {} and sending Standard Gossip",
+ &debut_node_key,
+ gossip_source,
+ );
+ Ok(GossipAcceptanceResult::Accepted)
+ } else {
+ debug!(
+ self.logger,
+ "DebutHandler has no one to introduce, but debutant does not have us as a neighbor; debuting back to {} at {}",
+ &debut_node_key,
+ gossip_source,
+ );
+ let debut_gossip = Self::create_second_node_gossip_response(
+ cryptde,
+ database,
+ debut_node_key,
+ );
+ Ok(GossipAcceptanceResult::Reply(
+ debut_gossip,
+ debuting_agr.inner.public_key.clone(),
+ debuting_agr
+ .node_addr_opt
+ .as_ref()
+ .expect("Debut gossip always has an IP")
+ .clone(),
+ ))
+ }
}
}
}
@@ -373,12 +451,23 @@ impl DebutHandler {
}
}
- fn create_debut_gossip_response(
+ fn counts_us_among_neighbors(
+ debuting_agr: &AccessibleGossipRecord,
+ database: &NeighborhoodDatabase,
+ ) -> bool {
+ let our_key = database.root().public_key();
+ debuting_agr.inner.neighbors.iter().any(|pk| pk == our_key)
+ }
+
+ fn create_second_node_gossip_response(
cryptde: &dyn CryptDE,
database: &NeighborhoodDatabase,
debut_node_key: PublicKey,
) -> Gossip_0v1 {
let mut root_node = database.root().clone();
+ // Several "second" Nodes may debut before the real second Node is fully established. If
+ // they do, we want to make sure each of them is isolated from the others and thinks that
+ // it is the real second Node.
root_node.clear_half_neighbors();
root_node
.add_half_neighbor_key(debut_node_key.clone())
@@ -395,12 +484,12 @@ impl DebutHandler {
}
}
- fn make_introduction(
+ fn try_to_make_introduction(
&self,
database: &NeighborhoodDatabase,
debuting_agr: &AccessibleGossipRecord,
gossip_source: SocketAddr,
- ) -> Option<(Gossip_0v1, PublicKey, NodeAddr)> {
+ ) -> IntroductionAttempt {
if let Some(lcn_key) =
Self::find_least_connected_full_neighbor_excluding(database, debuting_agr)
{
@@ -424,16 +513,16 @@ impl DebutHandler {
&debuting_agr.inner.public_key,
debut_node_addr
);
- Some((
+ IntroductionAttempt::Success(
GossipBuilder::new(database)
.node(database.root().public_key(), true)
.node(lcn_key, true)
.build(),
debuting_agr.inner.public_key.clone(),
debut_node_addr,
- ))
+ )
} else {
- None
+ IntroductionAttempt::Failure
}
}
@@ -458,10 +547,8 @@ impl DebutHandler {
.full_neighbor_keys(database)
.into_iter()
.filter(|k| {
- database
- .node_by_key(*k)
- .expect("Node disappeared")
- .accepts_connections()
+ let candidate = database.node_by_key(*k).expect("Node disappeared");
+ candidate.accepts_connections() && candidate.routes_data()
})
.collect(),
database,
@@ -515,8 +602,20 @@ impl DebutHandler {
keys
}
- fn should_not_make_another_introduction(debuting_agr: &AccessibleGossipRecord) -> bool {
- !debuting_agr.inner.neighbors.is_empty()
+ fn should_not_make_another_introduction(
+ db: &NeighborhoodDatabase,
+ debuting_agr: &AccessibleGossipRecord,
+ ) -> bool {
+ let routing_neighbors =
+ debuting_agr
+ .inner
+ .neighbors
+ .iter()
+ .filter(|pk| match db.node_by_key(pk) {
+ Some(node) => node.inner.routes_data,
+ None => false,
+ });
+ routing_neighbors.count() > 0
}
fn make_pass_gossip(database: &NeighborhoodDatabase, pass_target: &PublicKey) -> Gossip_0v1 {
@@ -539,7 +638,8 @@ impl NamedType for PassHandler {
}
impl GossipHandler for PassHandler {
- // A Pass must contain a single AGR representing the pass target; it must provide its IP address;
+ // A Pass must contain a single AGR representing the pass target; the pass target must
+ // accept connections; it must provide its IP address;
// it must specify at least one port; and it must _not_ be sourced by the pass target.
fn qualifies(
&self,
@@ -548,23 +648,23 @@ impl GossipHandler for PassHandler {
gossip_source: SocketAddr,
) -> Qualification {
if agrs.len() != 1 {
- return Qualification::Unmatched;
+ return Qualification::Unmatched(format!("Pass has 1 Node record, not {}", agrs.len()));
}
match &agrs[0].node_addr_opt {
- None => Qualification::Malformed(format!(
- "Pass from {} to {} did not contain NodeAddr",
- gossip_source, agrs[0].inner.public_key
- )),
+ None => Qualification::Unmatched("Pass Node record always has a NodeAddr".to_string()),
Some(node_addr) => {
if node_addr.ports().is_empty() {
- Qualification::Malformed(format!(
- "Pass from {} to {} at {} contained NodeAddr with no ports",
- gossip_source,
- agrs[0].inner.public_key,
- node_addr.ip_addr()
+ Qualification::Malformed(Malefactor::new(
+ Some(agrs[0].inner.public_key.clone()),
+ Some(gossip_source.ip()),
+ Some(agrs[0].inner.earning_wallet.clone()),
+ None,
+ "Pass contained NodeAddr with no ports".to_string(),
))
} else if node_addr.ip_addr() == gossip_source.ip() {
- Qualification::Unmatched
+ Qualification::Unmatched(
+ "Pass Node record must not be Gossip source".to_string(),
+ )
} else {
Qualification::Matched
}
@@ -579,7 +679,7 @@ impl GossipHandler for PassHandler {
agrs: Vec,
_gossip_source: SocketAddr,
neighborhood_metadata: NeighborhoodMetadata,
- ) -> GossipAcceptanceResult {
+ ) -> Vec {
let pass_agr = &agrs[0]; // empty Gossip shouldn't get here
let pass_target_node_addr: NodeAddr = pass_agr
.node_addr_opt
@@ -608,21 +708,21 @@ impl GossipHandler for PassHandler {
};
let mut hash_map = self.previous_pass_targets.borrow_mut();
- let gossip_acceptance_result = match hash_map.get_mut(&pass_target_ip_addr) {
+ let gossip_acceptance_results = match hash_map.get_mut(&pass_target_ip_addr) {
None => match neighborhood_metadata
.connection_progress_peers
.contains(&pass_target_ip_addr)
{
true => {
send_cpm(ConnectionProgressEvent::PassLoopFound);
- GossipAcceptanceResult::Ignored
+ vec![]
}
false => {
hash_map.insert(pass_target_ip_addr, SystemTime::now());
send_cpm(ConnectionProgressEvent::PassGossipReceived(
pass_target_ip_addr,
));
- gossip_acceptance_reply()
+ vec![gossip_acceptance_reply()]
}
},
Some(timestamp) => {
@@ -632,16 +732,20 @@ impl GossipHandler for PassHandler {
*timestamp = SystemTime::now();
if duration_since <= PASS_GOSSIP_EXPIRED_TIME {
send_cpm(ConnectionProgressEvent::PassLoopFound);
- GossipAcceptanceResult::Ignored
+ vec![]
} else {
send_cpm(ConnectionProgressEvent::PassGossipReceived(
pass_target_ip_addr,
));
- gossip_acceptance_reply()
+ vec![gossip_acceptance_reply()]
}
}
};
- gossip_acceptance_result
+ gossip_acceptance_results
+ }
+
+ fn as_any(&self) -> &dyn Any {
+ self
}
}
@@ -654,6 +758,7 @@ impl PassHandler {
}
struct IntroductionHandler {
+ rate_pack_limits: RatePackLimits,
logger: Logger,
}
@@ -666,8 +771,8 @@ impl NamedType for IntroductionHandler {
impl GossipHandler for IntroductionHandler {
// An Introduction must contain two AGRs, one representing the introducer and one representing
// the introducee. Both records must provide their IP addresses. One of the IP addresses must
- // match the gossip_source. The other record's IP address must not match the gossip_source. The
- // record whose IP address does not match the gossip source must not already be in the database.
+ // match the gossip_source. The other record's IP address must not match the gossip_source.
+ // Neither of the two records can be targeted by a half neighborship from this Node.
fn qualifies(
&self,
database: &NeighborhoodDatabase,
@@ -683,13 +788,16 @@ impl GossipHandler for IntroductionHandler {
Ok(true) => (&agrs[0], &agrs[1]),
Ok(false) => (&agrs[1], &agrs[0]),
};
- if let Some(qual) = Self::verify_introducer(introducer, database.root()) {
+ if let Some(qual) = Self::verify_introducer(gossip_source, introducer, database.root()) {
return qual;
};
if let Some(qual) = Self::verify_introducee(database, introducer, introducee, gossip_source)
{
return qual;
};
+ if let Some(qual) = Self::verify_both(database, introducer, introducee) {
+ return qual;
+ };
Qualification::Matched
}
@@ -700,61 +808,87 @@ impl GossipHandler for IntroductionHandler {
agrs: Vec,
gossip_source: SocketAddr,
neighborhood_metadata: NeighborhoodMetadata,
- ) -> GossipAcceptanceResult {
+ ) -> Vec {
if database.root().full_neighbor_keys(database).len() >= MAX_DEGREE {
- GossipAcceptanceResult::Ignored
- } else {
- let (introducer, introducee) = Self::identify_players(agrs, gossip_source)
- .expect("Introduction not properly qualified");
- let introducer_key = introducer.inner.public_key.clone();
- let introducer_ip_addr = introducer
- .node_addr_opt
- .as_ref()
- .expect("IP Address not found for the Node Addr.")
- .ip_addr();
- let introducee_ip_addr = introducee
- .node_addr_opt
- .as_ref()
- .expect("IP Address not found for the Node Addr.")
- .ip_addr();
- match self.update_database(
- database,
+ return vec![];
+ }
+ let (introducer, introducee) = Self::identify_players(agrs, gossip_source)
+ .expect("Introduction not properly qualified");
+ let introducer_ban_message_opt =
+ self.make_introducer_ban_message_if_appropriate(gossip_source, &introducer);
+ let (introducer_ban_message_opt, introducer_updated) = self
+ .update_introducer_if_appropriate(
cryptde,
- introducer,
- neighborhood_metadata.user_exit_preferences_opt,
- ) {
- Ok(_) => (),
- Err(e) => {
- return GossipAcceptanceResult::Ban(format!(
- "Introducer {} tried changing immutable characteristic: {}",
- introducer_key, e
- ));
- }
- }
+ database,
+ &neighborhood_metadata,
+ &introducer,
+ introducer_ban_message_opt,
+ );
+ let introducee_ban_message_opt =
+ self.make_introducee_ban_message_if_appropriate(&introducee);
+ let mut results = vec![];
+ if let Some(ref introducer_ban_message) = introducer_ban_message_opt {
+ let ban_result = Self::make_ban_result(&introducer, introducer_ban_message);
+ results.push(ban_result);
+ }
+ if let Some(ref introducee_ban_message) = introducee_ban_message_opt {
+ let ban_result = Self::make_ban_result(&introducee, introducee_ban_message);
+ results.push(ban_result);
+ }
+ if introducer_ban_message_opt.is_some() {
+ // If the introducer is banned, we're stopping right here
+ return results;
+ }
+ if introducer_updated {
+ // If we found out something new about the introducer, gossip it around
+ results.push(GossipAcceptanceResult::Accepted);
+ }
+ if introducee_ban_message_opt.is_none() {
+ // If the introducee is good, we'll want to debut to it
+ let (debut, target_key, target_node_addr) =
+ GossipAcceptorReal::make_debut_triple(database, &introducee)
+ .expect("Introduction not properly qualified");
+ let introducee_ip_addr = target_node_addr.ip_addr();
+ results.push(GossipAcceptanceResult::Reply(
+ debut,
+ target_key,
+ target_node_addr,
+ ));
let connection_progress_message = ConnectionProgressMessage {
- peer_addr: introducer_ip_addr,
+ peer_addr: introducer
+ .node_addr_opt
+ .as_ref()
+ .expect("Introducer IP address disappeared")
+ .ip_addr(),
event: ConnectionProgressEvent::IntroductionGossipReceived(introducee_ip_addr),
};
neighborhood_metadata
.cpm_recipient
.try_send(connection_progress_message)
.expect("Neighborhood is dead");
- let (debut, target_key, target_node_addr) =
- GossipAcceptorReal::make_debut_triple(database, &introducee)
- .expect("Introduction not properly qualified");
- GossipAcceptanceResult::Reply(debut, target_key, target_node_addr)
}
+ results
+ }
+
+ fn as_any(&self) -> &dyn Any {
+ self
}
}
impl IntroductionHandler {
- fn new(logger: Logger) -> IntroductionHandler {
- IntroductionHandler { logger }
+ fn new(rate_pack_limits: &RatePackLimits, logger: Logger) -> IntroductionHandler {
+ IntroductionHandler {
+ rate_pack_limits: rate_pack_limits.clone(),
+ logger,
+ }
}
fn verify_size(agrs: &[AccessibleGossipRecord]) -> Option {
if agrs.len() != 2 {
- return Some(Qualification::Unmatched);
+ return Some(Qualification::Unmatched(format!(
+ "Introduction has 2 Node records, not {}",
+ agrs.len()
+ )));
}
None
}
@@ -765,12 +899,20 @@ impl IntroductionHandler {
) -> Result {
let first_agr = &agrs_ref[0];
let first_ip = match first_agr.node_addr_opt.as_ref() {
- None => return Err(Qualification::Unmatched),
+ None => {
+ return Err(Qualification::Unmatched(
+ "Both Node records in Introduction must have NodeAddr".to_string(),
+ ))
+ }
Some(node_addr) => node_addr.ip_addr(),
};
let second_agr = &agrs_ref[1];
let second_ip = match second_agr.node_addr_opt.as_ref() {
- None => return Err(Qualification::Unmatched),
+ None => {
+ return Err(Qualification::Unmatched(
+ "Both Node records in Introduction must have NodeAddr".to_string(),
+ ))
+ }
Some(node_addr) => node_addr.ip_addr(),
};
if first_ip == gossip_source.ip() {
@@ -778,13 +920,12 @@ impl IntroductionHandler {
} else if second_ip == gossip_source.ip() {
Ok(false)
} else {
- Err(Qualification::Malformed(format!(
- "In Introduction, neither {} from {} nor {} from {} claims the source IP {}",
- first_agr.inner.public_key,
- first_ip,
- second_agr.inner.public_key,
- second_ip,
- gossip_source.ip()
+ Err(Qualification::Malformed(Malefactor::new(
+ None,
+ Some(gossip_source.ip()),
+ None,
+ None,
+ "In Introduction, neither Node record claims the source IP".to_string(),
)))
}
}
@@ -807,28 +948,40 @@ impl IntroductionHandler {
}
fn verify_introducer(
- agr: &AccessibleGossipRecord,
+ gossip_source: SocketAddr,
+ introducer: &AccessibleGossipRecord,
root_node: &NodeRecord,
) -> Option {
- if &agr.inner.public_key == root_node.public_key() {
- return Some(Qualification::Malformed(format!(
- "Introducer {} claims local Node's public key",
- agr.inner.public_key
+ if &introducer.inner.public_key == root_node.public_key() {
+ return Some(Qualification::Malformed(Malefactor::new(
+ Some(introducer.inner.public_key.clone()),
+ Some(gossip_source.ip()),
+ Some(introducer.inner.earning_wallet.clone()),
+ None,
+ "Introducer claims local Node's public key".to_string(),
)));
}
- let introducer_node_addr = agr.node_addr_opt.as_ref().expect("NodeAddr disappeared");
+ let introducer_node_addr = introducer
+ .node_addr_opt
+ .as_ref()
+ .expect("NodeAddr disappeared");
if introducer_node_addr.ports().is_empty() {
- return Some(Qualification::Malformed(format!(
- "Introducer {} from {} has no ports",
- &agr.inner.public_key,
- introducer_node_addr.ip_addr()
+ return Some(Qualification::Malformed(Malefactor::new(
+ Some(introducer.inner.public_key.clone()),
+ Some(introducer_node_addr.ip_addr()),
+ Some(introducer.inner.earning_wallet.clone()),
+ None,
+ "Introducer has no ports".to_string(),
)));
}
if let Some(root_node_addr) = root_node.node_addr_opt() {
if introducer_node_addr.ip_addr() == root_node_addr.ip_addr() {
- return Some(Qualification::Malformed(format!(
- "Introducer {} claims to be at local Node's IP address",
- agr.inner.public_key
+ return Some(Qualification::Malformed(Malefactor::new(
+ Some(introducer.inner.public_key.clone()),
+ Some(introducer_node_addr.ip_addr()),
+ Some(introducer.inner.earning_wallet.clone()),
+ None,
+ "Introducer claims to be at local Node's IP address".to_string(),
)));
}
}
@@ -836,28 +989,26 @@ impl IntroductionHandler {
}
fn verify_introducee(
- database: &NeighborhoodDatabase,
+ _db: &NeighborhoodDatabase,
introducer: &AccessibleGossipRecord,
introducee: &AccessibleGossipRecord,
gossip_source: SocketAddr,
) -> Option {
- if database.node_by_key(&introducee.inner.public_key).is_some() {
- return Some(Qualification::Unmatched);
- }
let introducee_node_addr = match introducee.node_addr_opt.as_ref() {
- None => return Some(Qualification::Unmatched),
+ None => {
+ return Some(Qualification::Unmatched(
+ "Introducee has no NodeAddr".to_string(),
+ ))
+ }
Some(node_addr) => node_addr,
};
if introducee_node_addr.ports().is_empty() {
- return Some(Qualification::Malformed(format!(
- "Introducer {} from {} introduced {} from {} with no ports",
- &introducer.inner.public_key,
- match &introducer.node_addr_opt {
- Some(node_addr) => node_addr.ip_addr().to_string(),
- None => "?.?.?.?".to_string(),
- },
- &introducee.inner.public_key,
- introducee_node_addr.ip_addr()
+ return Some(Qualification::Malformed(Malefactor::new(
+ Some(introducer.inner.public_key.clone()),
+ introducer.node_addr_opt.as_ref().map(|na| na.ip_addr()),
+ Some(introducer.inner.earning_wallet.clone()),
+ None,
+ "Introducer introduced introducee with no ports".to_string(),
)));
}
if introducee
@@ -867,16 +1018,34 @@ impl IntroductionHandler {
.ip_addr()
== gossip_source.ip()
{
- return Some(Qualification::Malformed(format!(
- "Introducer {} and introducee {} both claim {}",
- introducer.inner.public_key,
- introducee.inner.public_key,
- gossip_source.ip()
+ return Some(Qualification::Malformed(Malefactor::new(
+ Some(introducer.inner.public_key.clone()),
+ Some(gossip_source.ip()),
+ Some(introducer.inner.earning_wallet.clone()),
+ None,
+ "Introducer claims the same IP address as its introducee".to_string(),
)));
}
None
}
+ fn verify_both(
+ db: &NeighborhoodDatabase,
+ introducer: &AccessibleGossipRecord,
+ introducee: &AccessibleGossipRecord,
+ ) -> Option {
+ let half_neighbors = db.root().half_neighbor_keys();
+ if half_neighbors.contains(&introducee.inner.public_key)
+ && half_neighbors.contains(&introducer.inner.public_key)
+ {
+ Some(Qualification::Unmatched(
+ "Introducer and introducee are already our half-neighbors".to_string(),
+ ))
+ } else {
+ None
+ }
+ }
+
fn update_database(
&self,
database: &mut NeighborhoodDatabase,
@@ -932,9 +1101,97 @@ impl IntroductionHandler {
trace!(self.logger, "Current database: {}", database.to_dot_graph());
Ok(true)
}
+
+ fn make_introducer_ban_message_if_appropriate(
+ &self,
+ gossip_source: SocketAddr,
+ introducer: &AccessibleGossipRecord,
+ ) -> Option {
+ GossipAcceptorReal::validate_incoming_agr(
+ introducer,
+ format!(
+ "Introducer {} from {}",
+ introducer.inner.public_key, gossip_source
+ ),
+ &self.rate_pack_limits,
+ &self.logger,
+ )
+ .err()
+ }
+
+ fn make_introducee_ban_message_if_appropriate(
+ &self,
+ introducee: &AccessibleGossipRecord,
+ ) -> Option {
+ GossipAcceptorReal::validate_incoming_agr(
+ introducee,
+ format!(
+ "Introducee {} at {}",
+ introducee.inner.public_key,
+ introducee
+ .node_addr_opt
+ .as_ref()
+ .expect("NodeAddr disappeared")
+ .ip_addr(),
+ ),
+ &self.rate_pack_limits,
+ &self.logger,
+ )
+ .err()
+ }
+
+ fn update_introducer_if_appropriate(
+ &self,
+ cryptde: &dyn CryptDE,
+ database: &mut NeighborhoodDatabase,
+ neighborhood_metadata: &NeighborhoodMetadata,
+ introducer: &AccessibleGossipRecord,
+ introducer_ban_message_opt: Option,
+ ) -> (Option, bool) {
+ if introducer_ban_message_opt.is_none() {
+ match self.update_database(
+ database,
+ cryptde,
+ introducer.clone(),
+ neighborhood_metadata.user_exit_preferences_opt.clone(),
+ ) {
+ Ok(updated) => (None, updated),
+ Err(e) => {
+ let message = format!(
+ "Introducer {} tried changing immutable characteristic: {}",
+ introducer.inner.public_key, e
+ );
+ (Some(message), false)
+ }
+ }
+ } else {
+ (introducer_ban_message_opt, false)
+ }
+ }
+
+ fn make_ban_result(
+ agr: &AccessibleGossipRecord,
+ agr_ban_message: &str,
+ ) -> GossipAcceptanceResult {
+ let agr_key = agr.inner.public_key.clone();
+ let agr_wallet = agr.inner.earning_wallet.clone();
+ let agr_ip_address = agr
+ .node_addr_opt
+ .as_ref()
+ .expect("AGR IP address disappeared")
+ .ip_addr();
+ GossipAcceptanceResult::Ban(Malefactor::new(
+ Some(agr_key),
+ Some(agr_ip_address),
+ Some(agr_wallet),
+ None,
+ agr_ban_message.to_string(),
+ ))
+ }
}
struct StandardGossipHandler {
+ rate_pack_limits: RatePackLimits,
logger: Logger,
}
@@ -947,6 +1204,7 @@ impl NamedType for StandardGossipHandler {
impl GossipHandler for StandardGossipHandler {
// Standard Gossip must not be a Debut, Pass, or Introduction. There must be no record in the
// Gossip describing the local Node (although there may be records that reference the local Node as a neighbor).
+ // There must be no Node in the Gossip that claims to reside at this Node's IP address.
fn qualifies(
&self,
database: &NeighborhoodDatabase,
@@ -954,37 +1212,49 @@ impl GossipHandler for StandardGossipHandler {
gossip_source: SocketAddr,
) -> Qualification {
// must-not-be-debut-pass-or-introduction is assured by StandardGossipHandler's placement in the gossip_handlers list
- let agrs_next_door = agrs
+ // Check to make sure no record claims this Node's IP address
+ let agrs_with_ips = agrs
.iter()
.filter(|agr| agr.node_addr_opt.is_some())
.collect::>();
let root_node = database.root();
if root_node.accepts_connections() {
- if let Some(impostor) = agrs_next_door.iter().find(|agr| {
- Self::ip_of(agr)
+ if let Some(impostor) = agrs_with_ips.iter().find(|agr_with_ip| {
+ Self::ip_of(agr_with_ip)
== root_node
.node_addr_opt()
.expect("Root Node that accepts connections must have NodeAddr")
.ip_addr()
}) {
- return Qualification::Malformed(
- format!("Standard Gossip from {} contains a record claiming that {} has this Node's IP address",
- gossip_source,
- impostor.inner.public_key));
+ return Qualification::Malformed(Malefactor::new(
+ None,
+ Some(gossip_source.ip()),
+ None,
+ None,
+ format!(
+ "Standard Gossip contains a record claiming that {} resides at this Node's IP address",
+ impostor.inner.public_key
+ )
+ ));
}
}
- if agrs
+ // Check to make sure no record claims this Node's public key
+ if let Some(impostor) = agrs
.iter()
- .any(|agr| &agr.inner.public_key == root_node.public_key())
+ .find(|agr| &agr.inner.public_key == root_node.public_key())
{
- return Qualification::Malformed(format!(
- "Standard Gossip from {} contains a record with this Node's public key",
- gossip_source
+ return Qualification::Malformed(Malefactor::new(
+ Some(impostor.inner.public_key.clone()),
+ None,
+ Some(impostor.inner.earning_wallet.clone()),
+ None,
+ "Standard Gossip contains a record claiming this Node's public key".to_string(),
));
}
+ // Check for duplicate IP addresses in the Gossip
let init_addr_set: HashSet = HashSet::new();
let init_dup_set: HashSet = HashSet::new();
- let dup_set = agrs_next_door
+ let dup_set = agrs_with_ips
.into_iter()
.fold((init_addr_set, init_dup_set), |so_far, agr| {
let (addr_set, dup_set) = so_far;
@@ -996,15 +1266,20 @@ impl GossipHandler for StandardGossipHandler {
}
})
.1;
-
if dup_set.is_empty() {
Qualification::Matched
} else {
let dup_vec = dup_set.into_iter().take(1).collect::>();
let first_dup_ip = dup_vec.first().expect("Duplicate IP address disappeared");
- Qualification::Malformed(format!(
- "Standard Gossip from {} contains multiple records claiming to be from {}",
- gossip_source, first_dup_ip
+ Qualification::Malformed(Malefactor::new(
+ None,
+ Some(gossip_source.ip()),
+ None,
+ None,
+ format!(
+ "Standard Gossip contains multiple records claiming to be from {}",
+ first_dup_ip
+ ),
))
}
}
@@ -1016,27 +1291,72 @@ impl GossipHandler for StandardGossipHandler {
agrs: Vec,
gossip_source: SocketAddr,
neighborhood_metadata: NeighborhoodMetadata,
- ) -> GossipAcceptanceResult {
+ ) -> Vec {
+ // TODO:
+ // We should look up the source Node in the database by gossip_source and validate all
+ // its immutable data (public key, earning wallet, maybe others). If any of that has changed,
+ // another Malefactor ban.
let initial_neighborship_status =
StandardGossipHandler::check_full_neighbor(database, gossip_source.ip());
-
+ let gossip_source_agr = match agrs.iter().find(|agr| {
+ if agr.inner.accepts_connections {
+ // If it accepts connections, it'll know its IP address, and we can match on gossip_source
+ agr.node_addr_opt.as_ref().map(|na| na.ip_addr()) == Some(gossip_source.ip())
+ } else {
+ // If it doesn't, we'll know its IP address but it won't, so we'll look it up by IP
+ // and check public keys.
+ if let Some(db_node) = database.node_by_ip(&gossip_source.ip()) {
+ db_node.public_key() == &agr.inner.public_key
+ } else {
+ false
+ }
+ }
+ }) {
+ Some(agr) => agr.clone(),
+ None => {
+ let message = format!(
+ "Node at {} sent Standard gossip without a record describing itself",
+ gossip_source.ip()
+ );
+ warning!(self.logger, "{}", message);
+ return vec![GossipAcceptanceResult::Ban(Malefactor::new(
+ None,
+ Some(gossip_source.ip()),
+ None,
+ None,
+ message,
+ ))];
+ }
+ };
let patch = self.compute_patch(&agrs, database.root(), neighborhood_metadata.db_patch_size);
- let filtered_agrs = self.filter_agrs_by_patch(agrs, patch);
+ let in_patch_agrs = agrs
+ .into_iter()
+ .filter(|agr| self.contained_by_patch(agr, &patch))
+ .collect_vec();
+
+ let (worthy_agrs, malefactor_bans) =
+ self.extract_malefactors(in_patch_agrs, database, &gossip_source_agr, gossip_source);
+ let mut db_changed = false;
+
+ let (new_agrs, obsolete_agrs) =
+ self.identify_non_introductory_new_and_obsolete_nodes(database, worthy_agrs);
- let mut db_changed = self.identify_and_add_non_introductory_new_nodes(
+ db_changed |= !new_agrs.is_empty();
+ self.add_new_nodes(
database,
- &filtered_agrs,
- gossip_source,
+ new_agrs,
neighborhood_metadata.user_exit_preferences_opt.as_ref(),
);
- db_changed = self.identify_and_update_obsolete_nodes(database, filtered_agrs) || db_changed;
- db_changed =
- self.add_src_node_as_half_neighbor(cryptde, database, gossip_source) || db_changed;
+
+ db_changed |= !obsolete_agrs.is_empty();
+ self.update_obsolete_nodes(database, obsolete_agrs);
+
+ db_changed |= self.add_src_node_as_half_neighbor(cryptde, database, gossip_source);
let final_neighborship_status =
StandardGossipHandler::check_full_neighbor(database, gossip_source.ip());
// If no Nodes need updating, return ::Ignored and don't change the database.
// Otherwise, return ::Accepted.
- if db_changed {
+ let mut response = if db_changed {
trace!(self.logger, "Current database: {}", database.to_dot_graph());
if (initial_neighborship_status, final_neighborship_status) == (false, true) {
// Received Reply for Acceptance of Debut Gossip (false, true)
@@ -1049,20 +1369,29 @@ impl GossipHandler for StandardGossipHandler {
.try_send(cpm)
.unwrap_or_else(|e| panic!("Neighborhood is dead: {}", e));
}
- GossipAcceptanceResult::Accepted
+ vec![GossipAcceptanceResult::Accepted]
} else {
debug!(
self.logger,
"Gossip contained nothing new: StandardGossipHandler is ignoring it"
);
- GossipAcceptanceResult::Ignored
- }
+ vec![]
+ };
+ response.extend(malefactor_bans);
+ response
+ }
+
+ fn as_any(&self) -> &dyn Any {
+ self
}
}
impl StandardGossipHandler {
- fn new(logger: Logger) -> StandardGossipHandler {
- StandardGossipHandler { logger }
+ fn new(rate_pack_limits: &RatePackLimits, logger: Logger) -> StandardGossipHandler {
+ StandardGossipHandler {
+ rate_pack_limits: rate_pack_limits.clone(),
+ logger,
+ }
}
fn compute_patch(
@@ -1109,7 +1438,7 @@ impl StandardGossipHandler {
patch.remove(current_node_key);
trace!(
self.logger,
- "While computing patch no AGR record found for public key {:?}",
+ "While computing patch no Node record found for public key {:?}",
current_node_key
);
return;
@@ -1124,78 +1453,168 @@ impl StandardGossipHandler {
}
}
- fn filter_agrs_by_patch(
+ fn contained_by_patch(&self, agr: &AccessibleGossipRecord, patch: &HashSet) -> bool {
+ patch.contains(&agr.inner.public_key)
+ }
+
+ fn extract_malefactors(
&self,
agrs: Vec,
- patch: HashSet,
- ) -> Vec {
- agrs.into_iter()
- .filter(|agr| patch.contains(&agr.inner.public_key))
- .collect::>()
+ database: &NeighborhoodDatabase,
+ gossip_source_agr: &AccessibleGossipRecord,
+ // If our gossip_source_agr doesn't accept connections, it won't have an IP address; so
+ // we accept that separately.
+ gossip_source: SocketAddr,
+ ) -> (Vec, Vec) {
+ let gossip_source_ip = gossip_source.ip();
+ // TODO: This would be more consistent with identify_non_introductory_new_and_obsolete_nodes
+ // below if it used a for loop with mutation.
+ let (valid_agrs, bans) = agrs.into_iter().fold((vec![], vec![]), |so_far, agr| {
+ let (mut valid_agrs, mut bans) = so_far;
+ match GossipAcceptorReal::validate_incoming_agr(
+ &agr,
+ format!(
+ "Node {} from Standard gossip received from {}",
+ agr.inner.public_key, gossip_source_ip,
+ ),
+ &self.rate_pack_limits,
+ &self.logger,
+ ) {
+ Ok(_) => valid_agrs.push(agr),
+ Err(ban_message) => {
+ bans.push(GossipAcceptanceResult::Ban(Malefactor::new(
+ Some(agr.inner.public_key.clone()),
+ agr.node_addr_opt.map(|na| na.ip_addr()),
+ Some(agr.inner.earning_wallet.clone()),
+ None,
+ ban_message,
+ )));
+ }
+ }
+ (valid_agrs, bans)
+ });
+ let next_door_neighbor_keys = database
+ .root()
+ .inner
+ .neighbors
+ .iter()
+ .collect::>();
+ let (valid_agrs, bans) =
+ valid_agrs.into_iter().fold((vec![], bans), |so_far, agr| {
+ let (mut valid_agrs, mut bans) = so_far;
+ if &agr.inner.public_key == database.root_key() {
+ let ip_addr_opt = agr.node_addr_opt.map(|na| na.ip_addr());
+ bans.push(GossipAcceptanceResult::Ban(Malefactor::new(
+ Some(agr.inner.public_key.clone()),
+ ip_addr_opt,
+ Some(agr.inner.earning_wallet.clone()),
+ None,
+ format!(
+ "Node {} at {} sent Standard gossip that contained a record claiming our own public key",
+ agr.inner.public_key,
+ match ip_addr_opt {
+ Some(ip) => ip.to_string(),
+ None => "?.?.?.?".to_string(),
+ }
+ )
+ )));
+ }
+ else if
+ // the AGR has an IP address
+ agr.node_addr_opt.is_some() &&
+ // the AGR's IP address doesn't match the gossip source's IP address
+ (agr.node_addr_opt.as_ref().expect("NodeAddr disappeared").ip_addr() != gossip_source_ip) &&
+ // the AGR's public key isn't a next-door neighbor
+ !next_door_neighbor_keys.contains(&&agr.inner.public_key)
+ {
+ bans.push(GossipAcceptanceResult::Ban(Malefactor::new(
+ Some(gossip_source_agr.inner.public_key.clone()),
+ Some(gossip_source_ip),
+ Some(gossip_source_agr.inner.earning_wallet.clone()),
+ None,
+ format!(
+ "Node {} at {:?} sent Standard gossip that contained an IP address for victim Node {} that we should not have known",
+ gossip_source_agr.inner.public_key,
+ gossip_source_ip,
+ agr.inner.public_key,
+ ),
+ )));
+ }
+ else {
+ valid_agrs.push(agr)
+ }
+ (valid_agrs, bans)
+ });
+ (valid_agrs, bans)
}
- fn identify_and_add_non_introductory_new_nodes(
+ fn identify_non_introductory_new_and_obsolete_nodes(
&self,
database: &mut NeighborhoodDatabase,
- agrs: &[AccessibleGossipRecord],
- gossip_source: SocketAddr,
- user_exit_preferences_opt: Option<&UserExitPreferences>,
- ) -> bool {
+ agrs: Vec,
+ ) -> (Vec, Vec) {
let all_keys = database
.keys()
.into_iter()
.cloned()
.collect::>();
- agrs.iter()
- .filter(|agr| !all_keys.contains(&agr.inner.public_key))
- // TODO: A node that tells us the IP Address of the node that isn't in our database should be malefactor banned
- .filter(|agr| match &agr.node_addr_opt {
- None => true,
- Some(node_addr) => {
- let socket_addrs: Vec = node_addr.clone().into();
- socket_addrs.contains(&gossip_source)
- }
- })
- .for_each(|agr| {
- let mut node_record = NodeRecord::from(agr);
- match user_exit_preferences_opt {
- Some(user_exit_preferences) => {
- user_exit_preferences.assign_nodes_country_undesirability(&mut node_record)
- }
- None => (),
+ let mut new_nodes = vec![];
+ let mut obsolete_nodes = vec![];
+ for agr in agrs {
+ if !all_keys.contains(&agr.inner.public_key) {
+ new_nodes.push(agr);
+ } else if let Some(existing_node) = database.node_by_key(&agr.inner.public_key) {
+ if agr.inner.version > existing_node.version() {
+ obsolete_nodes.push(agr);
}
- trace!(
- self.logger,
- "Discovered new Node {:?}: {:?}",
- node_record.public_key(),
- node_record.full_neighbor_keys(database)
- );
- database
- .add_node(node_record)
- .expect("List of new Nodes contained existing Nodes");
- });
- database.keys().len() != all_keys.len()
+ }
+ }
+ (new_nodes, obsolete_nodes)
}
- fn identify_and_update_obsolete_nodes(
+ fn add_new_nodes(
&self,
database: &mut NeighborhoodDatabase,
agrs: Vec,
- ) -> bool {
- agrs.into_iter().fold(false, |b, agr| {
- match database.node_by_key(&agr.inner.public_key) {
- Some(existing_node) if agr.inner.version > existing_node.version() => {
- trace!(
- self.logger,
- "Updating Node {:?} from v{} to v{}",
- existing_node.public_key(),
- existing_node.version(),
- agr.inner.version
- );
- self.update_database_record(database, agr) || b
+ user_exit_preferences_opt: Option<&UserExitPreferences>,
+ ) {
+ agrs.into_iter().for_each(|agr| {
+ let mut node_record = NodeRecord::from(agr);
+ match user_exit_preferences_opt {
+ Some(user_exit_preferences) => {
+ user_exit_preferences.assign_nodes_country_undesirability(&mut node_record)
}
- _ => b,
+ None => (),
}
+ trace!(
+ self.logger,
+ "Discovered new Node {:?}: {:?}",
+ node_record.public_key(),
+ node_record.full_neighbor_keys(database)
+ );
+ database
+ .add_node(node_record)
+ .expect("List of new Nodes contained existing Nodes");
+ });
+ }
+
+ fn update_obsolete_nodes(
+ &self,
+ database: &mut NeighborhoodDatabase,
+ agrs: Vec,
+ ) {
+ agrs.into_iter().for_each(|agr| {
+ let existing_node = database
+ .node_by_key(&agr.inner.public_key)
+ .expect("Node magically disappeared from neighborhood database");
+ trace!(
+ self.logger,
+ "Updating Node {:?} from v{} to v{}",
+ existing_node.public_key(),
+ existing_node.version(),
+ agr.inner.version
+ );
+ self.update_database_record(database, agr);
})
}
@@ -1287,10 +1706,15 @@ impl GossipHandler for RejectHandler {
agrs: &[AccessibleGossipRecord],
gossip_source: SocketAddr,
) -> Qualification {
- Qualification::Malformed(format!(
- "Gossip with {} records from {} is unclassifiable by any qualifier",
- agrs.len(),
- gossip_source
+ Qualification::Malformed(Malefactor::new(
+ None,
+ Some(gossip_source.ip()),
+ None,
+ None,
+ format!(
+ "Gossip with {} records is unclassifiable by any qualifier",
+ agrs.len(),
+ ),
))
}
@@ -1301,9 +1725,13 @@ impl GossipHandler for RejectHandler {
_agrs: Vec,
_gossip_source: SocketAddr,
_neighborhood_metadata: NeighborhoodMetadata,
- ) -> GossipAcceptanceResult {
+ ) -> Vec {
panic!("Should never be called")
}
+
+ fn as_any(&self) -> &dyn Any {
+ self
+ }
}
impl RejectHandler {
@@ -1319,7 +1747,7 @@ pub trait GossipAcceptor: Send /* Send because lazily-written tests require it *
agrs: Vec,
gossip_source: SocketAddr,
neighborhood_metadata: NeighborhoodMetadata,
- ) -> GossipAcceptanceResult;
+ ) -> Vec;
}
pub struct GossipAcceptorReal {
@@ -1335,12 +1763,24 @@ impl GossipAcceptor for GossipAcceptorReal {
agrs: Vec,
gossip_source: SocketAddr,
neighborhood_metadata: NeighborhoodMetadata,
- ) -> GossipAcceptanceResult {
+ ) -> Vec {
let (qualification, handler_ref) = self
.gossip_handlers
.iter()
.map(|h| (h.qualifies(database, &agrs, gossip_source), h.as_ref()))
- .find(|pair| !matches!(pair, (Qualification::Unmatched, _)))
+ .map(|(qualification, handler_ref)| {
+ if let Qualification::Unmatched(msg) = &qualification {
+ trace!(
+ self.logger,
+ "Gossip from {} did not qualify for {}: {}",
+ gossip_source,
+ handler_ref.type_name(),
+ msg
+ );
+ }
+ (qualification, handler_ref)
+ })
+ .find(|pair| !matches!(pair, (Qualification::Unmatched(_), _)))
.expect("gossip_handlers should intercept everything");
match qualification {
Qualification::Matched => {
@@ -1357,23 +1797,32 @@ impl GossipAcceptor for GossipAcceptorReal {
neighborhood_metadata,
)
}
- Qualification::Unmatched => {
+ Qualification::Unmatched(_) => {
panic!("Nothing in gossip_handlers returned Matched or Malformed")
}
- Qualification::Malformed(reason) => GossipAcceptanceResult::Ban(reason),
+ Qualification::Malformed(malefactor) => {
+ vec![GossipAcceptanceResult::Ban(malefactor)]
+ }
}
}
}
impl GossipAcceptorReal {
- pub fn new(cryptde: Box) -> GossipAcceptorReal {
+ pub fn new(
+ cryptde: Box,
+ persistent_config: &dyn PersistentConfiguration,
+ ) -> GossipAcceptorReal {
+ let rate_pack_limits = &persistent_config
+ .rate_pack_limits()
+ .expect("RatePackLimits should be set");
+
let logger = Logger::new("GossipAcceptor");
GossipAcceptorReal {
gossip_handlers: vec![
- Box::new(DebutHandler::new(logger.clone())),
+ Box::new(DebutHandler::new(rate_pack_limits, logger.clone())),
Box::new(PassHandler::new()),
- Box::new(IntroductionHandler::new(logger.clone())),
- Box::new(StandardGossipHandler::new(logger.clone())),
+ Box::new(IntroductionHandler::new(rate_pack_limits, logger.clone())),
+ Box::new(StandardGossipHandler::new(rate_pack_limits, logger.clone())),
Box::new(RejectHandler::new()),
],
cryptde,
@@ -1412,6 +1861,58 @@ impl GossipAcceptorReal {
debut_target_node_addr.clone(),
))
}
+
+ fn validate_incoming_agr(
+ agr: &AccessibleGossipRecord,
+ agr_description: String,
+ rate_pack_limits: &RatePackLimits,
+ logger: &Logger,
+ ) -> Result<(), String> {
+ if agr.inner.routes_data {
+ return match rate_pack_limits.analyze(&agr.inner.rate_pack) {
+ Ok(_) => Ok(()),
+ Err(e) => {
+ let message = format!(
+ "{} rejected due to rate pack limit violation: {:?}",
+ agr_description, e
+ );
+ warning!(logger, "{}", message.as_str());
+ Err(message)
+ }
+ };
+ }
+ Ok(())
+ }
+}
+
+pub struct GossipAcceptorInvalid {}
+
+impl GossipAcceptor for GossipAcceptorInvalid {
+ fn handle(
+ &self,
+ _database: &mut NeighborhoodDatabase,
+ _agrs: Vec,
+ _gossip_source: SocketAddr,
+ _neighborhood_metadata: NeighborhoodMetadata,
+ ) -> Vec {
+ Self::invalid()
+ }
+}
+
+impl GossipAcceptorInvalid {
+ pub fn new() -> Self {
+ Self {}
+ }
+
+ fn invalid() -> ! {
+ panic!("GossipAcceptor was never initialized");
+ }
+}
+
+impl Default for GossipAcceptorInvalid {
+ fn default() -> Self {
+ Self::new()
+ }
}
#[cfg(test)]
@@ -1426,13 +1927,18 @@ mod tests {
UNREACHABLE_COUNTRY_PENALTY,
};
use crate::sub_lib::cryptde_null::CryptDENull;
- use crate::sub_lib::neighborhood::{ConnectionProgressEvent, ConnectionProgressMessage};
+ use crate::sub_lib::neighborhood::{
+ ConnectionProgressEvent, ConnectionProgressMessage, RatePack, DEFAULT_RATE_PACK,
+ DEFAULT_RATE_PACK_LIMITS, ZERO_RATE_PACK,
+ };
use crate::sub_lib::utils::time_t_timestamp;
+ use crate::sub_lib::wallet::Wallet;
use crate::test_utils::neighborhood_test_utils::{
db_from_node, gossip_about_nodes_from_database, linearly_connect_nodes,
make_meaningless_db, make_node_record, make_node_record_cc, make_node_record_f,
make_node_records, public_keys_from_node_records, DB_PATCH_SIZE_FOR_TEST,
};
+ use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMock;
use crate::test_utils::unshared_test_utils::make_cpm_recipient;
use crate::test_utils::{assert_contains, vec_to_set};
use actix::System;
@@ -1442,6 +1948,7 @@ mod tests {
use masq_lib::messages::ExitLocation;
use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler};
use masq_lib::test_utils::utils::TEST_DEFAULT_CHAIN;
+ use masq_lib::utils::NeighborhoodModeLight;
use std::convert::TryInto;
use std::net::Ipv4Addr;
use std::ops::{Add, Sub};
@@ -1449,21 +1956,35 @@ mod tests {
use std::time::Duration;
lazy_static! {
- static ref CRYPTDE_PAIR: CryptDEPair = CryptDEPair::null();
+ static ref GA_CRYPTDE_PAIR: CryptDEPair = CryptDEPair::null();
}
- #[test]
- fn constants_have_correct_values() {
- assert_eq!(MAX_DEGREE, 5);
- assert_eq!(PASS_GOSSIP_EXPIRED_TIME, Duration::from_secs(60));
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
+ struct Mode {
+ pub accepts_connections: bool,
+ pub routes_data: bool,
}
- #[derive(Clone, Copy, Debug, PartialEq, Eq)]
- enum Mode {
- Standard,
- OriginateOnly,
- // GossipAcceptor doesn't care about ConsumeOnly; that's routing, not Gossip
- // ZeroHop is decentralized and should never appear in GossipAcceptor tests
+ impl Mode {
+ pub fn new(accepts_connections: bool, routes_data: bool) -> Self {
+ Self {
+ accepts_connections,
+ routes_data,
+ }
+ }
+ }
+
+ impl From for Mode {
+ fn from(neighborhood_mode: NeighborhoodModeLight) -> Self {
+ match neighborhood_mode {
+ NeighborhoodModeLight::Standard => Mode::new(true, true),
+ NeighborhoodModeLight::OriginateOnly => Mode::new(false, true),
+ NeighborhoodModeLight::ConsumeOnly => Mode::new(false, false),
+ NeighborhoodModeLight::ZeroHop => {
+ unimplemented!("ZeroHop should not be used in GossipAcceptor tests")
+ }
+ }
+ }
}
fn make_default_neighborhood_metadata() -> NeighborhoodMetadata {
@@ -1475,16 +1996,84 @@ mod tests {
}
}
+ impl RatePackLimits {
+ pub fn test_default() -> RatePackLimits {
+ Self {
+ lo: RatePack::new(u64::MIN, u64::MIN, u64::MIN, u64::MIN),
+ hi: RatePack::new(u64::MAX, u64::MAX, u64::MAX, u64::MAX),
+ }
+ }
+ }
+
+ #[test]
+ fn constants_have_correct_values() {
+ assert_eq!(MAX_DEGREE, 5);
+ assert_eq!(PASS_GOSSIP_EXPIRED_TIME, Duration::from_secs(60));
+ }
+
+ #[test]
+ fn gossip_acceptor_constructor_creates_handlers_properly() {
+ let subject = GossipAcceptorReal::new(
+ GA_CRYPTDE_PAIR.main.dup(),
+ &PersistentConfigurationMock::new()
+ .rate_pack_limits_result(Ok(RatePackLimits::test_default())),
+ );
+
+ let debut_handler: &DebutHandler = subject.gossip_handlers[0]
+ .as_any()
+ .downcast_ref::()
+ .unwrap();
+ assert_eq!(
+ debut_handler.rate_pack_limits,
+ RatePackLimits::test_default()
+ );
+ assert_eq!(debut_handler.logger.name(), "GossipAcceptor");
+
+ let _: &PassHandler = subject.gossip_handlers[1]
+ .as_any()
+ .downcast_ref::()
+ .unwrap();
+ // The fact that the downcast didn't panic is assertion enough
+
+ let introduction_handler: &IntroductionHandler = subject.gossip_handlers[2]
+ .as_any()
+ .downcast_ref::()
+ .unwrap();
+ assert_eq!(
+ introduction_handler.rate_pack_limits,
+ RatePackLimits::test_default()
+ );
+ assert_eq!(introduction_handler.logger.name(), "GossipAcceptor");
+
+ let standard_gossip_handler: &StandardGossipHandler = subject.gossip_handlers[3]
+ .as_any()
+ .downcast_ref::()
+ .unwrap();
+ assert_eq!(
+ standard_gossip_handler.rate_pack_limits,
+ RatePackLimits::test_default()
+ );
+ assert_eq!(standard_gossip_handler.logger.name(), "GossipAcceptor");
+
+ let _: &RejectHandler = subject.gossip_handlers[4]
+ .as_any()
+ .downcast_ref::()
+ .unwrap();
+ // The fact that the downcast didn't panic is assertion enough
+ assert_eq!(subject.gossip_handlers.len(), 5);
+ }
+
#[test]
fn proper_debut_of_accepting_node_with_populated_database_is_identified_and_handled() {
- let (gossip, new_node, gossip_source_opt) = make_debut(2345, Mode::Standard);
+ let (gossip, new_node, gossip_source_opt) =
+ make_debut(2345, NeighborhoodModeLight::Standard.into());
let root_node = make_node_record(1234, true);
let mut db = db_from_node(&root_node);
let neighbor_key = &db.add_node(make_node_record(3456, true)).unwrap();
db.add_arbitrary_full_neighbor(root_node.public_key(), neighbor_key);
let cryptde = CryptDENull::from(db.root().public_key(), TEST_DEFAULT_CHAIN);
let agrs_vec: Vec = gossip.try_into().unwrap();
- let subject = DebutHandler::new(Logger::new("test"));
+ let subject = DebutHandler::new(&RatePackLimits::test_default(), Logger::new("test"));
let qualifies_result =
subject.qualifies(&db, &agrs_vec.as_slice(), gossip_source_opt.clone());
@@ -1503,88 +2092,56 @@ mod tests {
.build();
assert_eq!(
handle_result,
- GossipAcceptanceResult::Reply(
+ vec![GossipAcceptanceResult::Reply(
introduction,
new_node.public_key().clone(),
new_node.node_addr_opt().unwrap(),
- ),
+ )],
);
}
#[test]
fn proper_debut_of_non_accepting_node_with_populated_database_is_identified_and_handled() {
- let (gossip, new_node, gossip_source) = make_debut(2345, Mode::OriginateOnly);
+ let (gossip, new_node, gossip_source) =
+ make_debut(2345, NeighborhoodModeLight::OriginateOnly.into());
let root_node = make_node_record(1234, true);
- let mut db = db_from_node(&root_node);
- let neighbor_key = &db.add_node(make_node_record(3456, true)).unwrap();
- db.add_arbitrary_full_neighbor(root_node.public_key(), neighbor_key);
- let cryptde = CryptDENull::from(db.root().public_key(), TEST_DEFAULT_CHAIN);
+ let mut dest_db = db_from_node(&root_node);
+ let neighbor_key = &dest_db.add_node(make_node_record(3456, true)).unwrap();
+ dest_db.add_arbitrary_full_neighbor(root_node.public_key(), neighbor_key);
+ let cryptde = CryptDENull::from(dest_db.root().public_key(), TEST_DEFAULT_CHAIN);
let agrs_vec: Vec = gossip.try_into().unwrap();
- let subject = DebutHandler::new(Logger::new("test"));
+ let subject = DebutHandler::new(&RatePackLimits::test_default(), Logger::new("test"));
- let qualifies_result = subject.qualifies(&db, agrs_vec.as_slice(), gossip_source.clone());
+ let qualifies_result =
+ subject.qualifies(&dest_db, agrs_vec.as_slice(), gossip_source.clone());
let handle_result = subject.handle(
&cryptde,
- &mut db,
+ &mut dest_db,
agrs_vec,
gossip_source,
make_default_neighborhood_metadata(),
);
assert_eq!(Qualification::Matched, qualifies_result);
- let introduction = GossipBuilder::new(&db)
- .node(db.root().public_key(), true)
+ let introduction = GossipBuilder::new(&dest_db)
+ .node(dest_db.root().public_key(), true)
.node(neighbor_key, true)
.build();
assert_eq!(
handle_result,
- GossipAcceptanceResult::Reply(
+ vec![GossipAcceptanceResult::Reply(
introduction,
new_node.public_key().clone(),
NodeAddr::from(&gossip_source),
- ),
+ )],
);
- }
-
- #[test]
- fn proper_debut_of_node_cant_produce_introduction_because_of_common_neighbor() {
- let src_root = make_node_record(1234, true);
- let mut src_db = db_from_node(&src_root);
- let cryptde = CryptDENull::from(src_db.root().public_key(), TEST_DEFAULT_CHAIN);
- let dest_root = make_node_record(2345, true);
- let mut dest_db = db_from_node(&dest_root);
- let one_common_neighbor = make_node_record(3456, true);
- let another_common_neighbor = make_node_record(4567, true);
- src_db.add_node(one_common_neighbor.clone()).unwrap();
- src_db.add_arbitrary_full_neighbor(src_root.public_key(), one_common_neighbor.public_key());
- src_db.add_node(another_common_neighbor.clone()).unwrap();
- src_db.add_arbitrary_full_neighbor(
- src_root.public_key(),
- another_common_neighbor.public_key(),
- );
- dest_db.add_node(one_common_neighbor.clone()).unwrap();
- dest_db
- .add_arbitrary_full_neighbor(dest_root.public_key(), one_common_neighbor.public_key());
- dest_db.add_node(another_common_neighbor.clone()).unwrap();
- dest_db.add_arbitrary_full_neighbor(
- dest_root.public_key(),
- another_common_neighbor.public_key(),
- );
- let gossip = GossipBuilder::new(&src_db)
- .node(src_db.root().public_key(), true)
- .build();
- let agrs_vec: Vec = gossip.try_into().unwrap();
- let subject = DebutHandler::new(Logger::new("test"));
-
- let result = subject.handle(
- &cryptde,
- &mut dest_db,
- agrs_vec,
- src_root.node_addr_opt().unwrap().into(),
- make_default_neighborhood_metadata(),
+ // Version of debuting Node in destination database should have NodeAddr
+ // even though it doesn't accept connections
+ let debuted_node = dest_db.node_by_key(&new_node.public_key()).unwrap();
+ assert_eq!(
+ debuted_node.metadata.node_addr_opt,
+ Some(NodeAddr::from(&gossip_source))
);
-
- assert_eq!(result, GossipAcceptanceResult::Accepted);
}
#[test]
@@ -1606,7 +2163,7 @@ mod tests {
.node(src_db.root().public_key(), true)
.build();
let agrs_vec: Vec = gossip.try_into().unwrap();
- let subject = DebutHandler::new(Logger::new("test"));
+ let subject = DebutHandler::new(&RatePackLimits::test_default(), Logger::new("test"));
let result = subject.handle(
&cryptde,
@@ -1618,18 +2175,19 @@ mod tests {
assert_eq!(
result,
- GossipAcceptanceResult::Failed(
+ vec![GossipAcceptanceResult::Failed(
GossipFailure_0v1::NoSuitableNeighbors,
src_root.public_key().clone(),
src_root.node_addr_opt().unwrap(),
- )
+ )]
);
}
#[test]
fn debut_with_node_addr_not_accepting_connections_is_rejected() {
- let (mut gossip, _j, gossip_source) = make_debut(2345, Mode::OriginateOnly);
- let subject = DebutHandler::new(Logger::new("test"));
+ let (mut gossip, _j, gossip_source) =
+ make_debut(2345, NeighborhoodModeLight::OriginateOnly.into());
+ let subject = DebutHandler::new(&RatePackLimits::test_default(), Logger::new("test"));
gossip.node_records[0].node_addr_opt = Some(NodeAddr::new(
&IpAddr::from_str("1.2.3.4").unwrap(),
&[1234],
@@ -1640,65 +2198,83 @@ mod tests {
assert_eq!(
result,
- Qualification::Malformed(
- "Debut from 200.200.200.200:2000 for AgMEBQ does not accept connections, yet contained NodeAddr".to_string()
- ),
+ Qualification::Malformed(Malefactor::new(
+ Some(agrs_vec[0].inner.public_key.clone()),
+ Some(gossip_source.ip()),
+ Some(agrs_vec[0].inner.earning_wallet.clone()),
+ None,
+ "Debut does not accept connections, yet contained NodeAddr".to_string()
+ )),
);
}
#[test]
fn debut_without_node_addr_accepting_connections_is_rejected() {
- let (mut gossip, _j, gossip_source) = make_debut(2345, Mode::Standard);
+ let (mut gossip, _j, gossip_source) =
+ make_debut(2345, NeighborhoodModeLight::Standard.into());
gossip.node_records[0].node_addr_opt = None;
- let subject = DebutHandler::new(Logger::new("test"));
+ let subject = DebutHandler::new(&RatePackLimits::test_default(), Logger::new("test"));
let agrs_vec: Vec = gossip.try_into().unwrap();
let result = subject.qualifies(&make_meaningless_db(), agrs_vec.as_slice(), gossip_source);
assert_eq!(
result,
- Qualification::Malformed(
- "Debut from 2.3.4.5:2345 for AgMEBQ contained no NodeAddr".to_string()
- ),
+ Qualification::Malformed(Malefactor::new(
+ Some(agrs_vec[0].inner.public_key.clone()),
+ Some(gossip_source.ip()),
+ Some(agrs_vec[0].inner.earning_wallet.clone()),
+ None,
+ "Debut contained no NodeAddr".to_string()
+ )),
);
}
#[test]
fn debut_without_node_addr_ports_accepting_connections_is_rejected() {
- let (mut gossip, _, gossip_source) = make_debut(2345, Mode::Standard);
+ let (mut gossip, _, gossip_source) =
+ make_debut(2345, NeighborhoodModeLight::Standard.into());
gossip.node_records[0].node_addr_opt =
Some(NodeAddr::new(&IpAddr::from_str("1.2.3.4").unwrap(), &[]));
- let subject = DebutHandler::new(Logger::new("test"));
+ let subject = DebutHandler::new(&RatePackLimits::test_default(), Logger::new("test"));
let agrs_vec: Vec = gossip.try_into().unwrap();
let result = subject.qualifies(&make_meaningless_db(), agrs_vec.as_slice(), gossip_source);
assert_eq!(
result,
- Qualification::Malformed(
- "Debut from 2.3.4.5:2345 for AgMEBQ contained NodeAddr with no ports".to_string()
- ),
+ Qualification::Malformed(Malefactor::new(
+ Some(agrs_vec[0].inner.public_key.clone()),
+ Some(gossip_source.ip()),
+ Some(agrs_vec[0].inner.earning_wallet.clone()),
+ None,
+ "Debut contained NodeAddr with no ports".to_string()
+ )),
);
}
#[test]
fn apparent_debut_with_node_already_in_database_is_unmatched() {
- let (gossip, new_node, gossip_source) = make_debut(2345, Mode::Standard);
+ let (gossip, new_node, gossip_source) =
+ make_debut(2345, NeighborhoodModeLight::Standard.into());
let root_node = make_node_record(1234, true);
let mut db = db_from_node(&root_node);
let neighbor_key = &db.add_node(make_node_record(3456, true)).unwrap();
db.add_arbitrary_full_neighbor(root_node.public_key(), neighbor_key);
db.add_node(new_node.clone()).unwrap();
let agrs_vec: Vec = gossip.try_into().unwrap();
- let subject = DebutHandler::new(Logger::new("test"));
+ let subject = DebutHandler::new(&RatePackLimits::test_default(), Logger::new("test"));
let result = subject.qualifies(&db, agrs_vec.as_slice(), gossip_source);
- assert_eq!(result, Qualification::Unmatched);
+ assert_eq!(
+ result,
+ Qualification::Unmatched("Node record already in database".to_string())
+ );
}
#[test]
- fn debut_of_already_connected_node_produces_accepted_result_instead_of_introduction_to_prevent_overconnection(
+ fn debut_of_already_connected_node_produces_debut_back_instead_of_introduction_to_prevent_overconnection(
) {
let src_root = make_node_record(1234, true);
let mut src_db = db_from_node(&src_root);
@@ -1718,7 +2294,7 @@ mod tests {
.build()
.try_into()
.unwrap();
- let subject = DebutHandler::new(Logger::new("test"));
+ let subject = DebutHandler::new(&RatePackLimits::test_default(), Logger::new("test"));
let result = subject.handle(
&dest_cryptde,
@@ -1728,7 +2304,57 @@ mod tests {
make_default_neighborhood_metadata(),
);
- assert_eq!(result, GossipAcceptanceResult::Accepted);
+ let expected_debut_back_gossip = GossipBuilder::new(&dest_db)
+ .node(dest_root.public_key(), true)
+ .build();
+ assert_eq!(
+ result,
+ vec![GossipAcceptanceResult::Reply(
+ expected_debut_back_gossip,
+ src_root.public_key().clone(),
+ src_root.node_addr_opt().unwrap(),
+ )]
+ );
+ }
+
+ #[test]
+ fn debut_is_rejected_when_validation_fails() {
+ init_test_logging();
+ let test_name = "debut_is_rejected_when_validation_fails";
+ let root_node = make_node_record(1234, true);
+ let root_node_cryptde = CryptDENull::from(&root_node.public_key(), TEST_DEFAULT_CHAIN);
+ let mut src_db = db_from_node(&root_node);
+ let agrs_vec: Vec = GossipBuilder::new(&src_db)
+ .node(root_node.public_key(), true)
+ .build()
+ .try_into()
+ .unwrap();
+ let subject = DebutHandler::new(
+ &RatePackLimits::new(RatePack::new(0, 0, 0, 0), RatePack::new(0, 0, 0, 0)),
+ Logger::new(test_name),
+ );
+
+ let result = subject.handle(
+ &root_node_cryptde,
+ &mut src_db,
+ agrs_vec,
+ root_node.node_addr_opt().clone().unwrap().into(),
+ make_default_neighborhood_metadata(),
+ );
+
+ let message = r#"Debut from AQIDBA at 1.2.3.4:1234 rejected due to rate pack limit violation: ConfiguratorError { param_errors: [ParamError { parameter: "rate-pack", reason: "Value of routing_byte_rate (1235) is above the maximum allowed (0)" }, ParamError { parameter: "rate-pack", reason: "Value of routing_service_rate (1434) is above the maximum allowed (0)" }, ParamError { parameter: "rate-pack", reason: "Value of exit_byte_rate (1237) is above the maximum allowed (0)" }, ParamError { parameter: "rate-pack", reason: "Value of exit_service_rate (1634) is above the maximum allowed (0)" }] }"#.to_string();
+ assert_eq!(
+ result,
+ vec![GossipAcceptanceResult::Ban(Malefactor::new(
+ Some(root_node.public_key().clone()),
+ Some(root_node.node_addr_opt().unwrap().ip_addr()),
+ Some(root_node.earning_wallet()),
+ None,
+ message.clone()
+ ))]
+ );
+ TestLogHandler::new()
+ .exists_log_containing(format!("WARN: {}: {}", test_name, message).as_str());
}
#[test]
@@ -1744,7 +2370,7 @@ mod tests {
half_neighbor_debutant.public_key(),
);
let logger = Logger::new("Debut test");
- let subject = DebutHandler::new(logger);
+ let subject = DebutHandler::new(&RatePackLimits::test_default(), logger);
let neighborhood_metadata = make_default_neighborhood_metadata();
let counter_debut = subject
@@ -1767,9 +2393,135 @@ mod tests {
assert_eq!(dest_node_addr, &new_debutant.node_addr_opt().unwrap());
}
+ #[test]
+ fn if_we_have_neighbors_but_none_qualify_for_introduction_or_pass_and_does_not_half_neighbor_us_then_debut_back(
+ ) {
+ let test_name = "if_we_have_neighbors_but_none_qualify_for_introduction_or_pass_and_does_not_half_neighbor_us_then_debut_back";
+ let dest_root = make_node_record(1234, true);
+ let root_node_cryptde = CryptDENull::from(&dest_root.public_key(), TEST_DEFAULT_CHAIN);
+ let one_common_neighbor = make_node_record(4321, true); // can't introduce this one; debutant already knows it
+ let another_common_neighbor = make_node_record(5432, true); // can't introduce this one; debutant already knows it
+ let mut dest_db = db_from_node(&dest_root);
+ dest_db.add_node(one_common_neighbor.clone()).unwrap();
+ dest_db
+ .add_arbitrary_full_neighbor(dest_root.public_key(), one_common_neighbor.public_key());
+ dest_db.add_node(another_common_neighbor.clone()).unwrap();
+ dest_db.add_arbitrary_full_neighbor(
+ dest_root.public_key(),
+ another_common_neighbor.public_key(),
+ );
+ let original_version = dest_db.root().version();
+
+ let mut debutant = make_node_record(4567, true);
+ debutant
+ .inner
+ .neighbors
+ .insert(one_common_neighbor.public_key().clone());
+ debutant
+ .inner
+ .neighbors
+ .insert(another_common_neighbor.public_key().clone());
+ let logger = Logger::new(test_name);
+ let subject = DebutHandler::new(&RatePackLimits::test_default(), logger);
+ let neighborhood_metadata = make_default_neighborhood_metadata();
+
+ let result = subject
+ .try_accept_debut(
+ &root_node_cryptde,
+ &mut dest_db,
+ &AccessibleGossipRecord::from(&debutant),
+ SocketAddr::new(IpAddr::V4(Ipv4Addr::new(4, 5, 6, 7)), 4567),
+ neighborhood_metadata.user_exit_preferences_opt,
+ )
+ .unwrap();
+
+ assert_eq!(
+ result,
+ GossipAcceptanceResult::Reply(
+ GossipBuilder::new(&dest_db)
+ .node(dest_db.root().public_key(), true)
+ .build(),
+ debutant.public_key().clone(),
+ debutant
+ .node_addr_opt()
+ .expect("Debutant should have NodeAddr"),
+ )
+ );
+ assert!(dest_db
+ .root()
+ .half_neighbor_keys()
+ .contains(debutant.public_key()));
+ assert_eq!(dest_db.root().version(), original_version + 1);
+ root_node_cryptde.verify_signature(
+ dest_db.root().signed_gossip(),
+ dest_db.root().signature(),
+ dest_db.root().public_key(),
+ );
+ }
+
+ #[test]
+ fn if_we_have_neighbors_but_none_qualify_for_introduction_or_pass_and_half_neighbors_us_then_standard_gossip(
+ ) {
+ let test_name = "if_we_have_neighbors_but_none_qualify_for_introduction_or_pass_and_half_neighbors_us_then_standard_gossip";
+ let dest_root = make_node_record(1234, true);
+ let root_node_cryptde = CryptDENull::from(&dest_root.public_key(), TEST_DEFAULT_CHAIN);
+ let one_common_neighbor = make_node_record(4321, true); // can't introduce this one; debutant already knows it
+ let another_common_neighbor = make_node_record(5432, true); // can't introduce this one; debutant already knows it
+ let mut dest_db = db_from_node(&dest_root);
+ dest_db.add_node(one_common_neighbor.clone()).unwrap();
+ dest_db
+ .add_arbitrary_full_neighbor(dest_root.public_key(), one_common_neighbor.public_key());
+ dest_db.add_node(another_common_neighbor.clone()).unwrap();
+ dest_db.add_arbitrary_full_neighbor(
+ dest_root.public_key(),
+ another_common_neighbor.public_key(),
+ );
+ let original_version = dest_db.root().version();
+
+ let mut debutant = make_node_record(4567, true);
+ debutant
+ .inner
+ .neighbors
+ .insert(one_common_neighbor.public_key().clone());
+ debutant
+ .inner
+ .neighbors
+ .insert(another_common_neighbor.public_key().clone());
+ debutant
+ .inner
+ .neighbors
+ .insert(dest_root.public_key().clone());
+ let logger = Logger::new(test_name);
+ let subject = DebutHandler::new(&RatePackLimits::test_default(), logger);
+ let neighborhood_metadata = make_default_neighborhood_metadata();
+
+ let result = subject
+ .try_accept_debut(
+ &root_node_cryptde,
+ &mut dest_db,
+ &AccessibleGossipRecord::from(&debutant),
+ SocketAddr::new(IpAddr::V4(Ipv4Addr::new(4, 5, 6, 7)), 4567),
+ neighborhood_metadata.user_exit_preferences_opt,
+ )
+ .unwrap();
+
+ assert_eq!(result, GossipAcceptanceResult::Accepted);
+ assert!(dest_db
+ .root()
+ .half_neighbor_keys()
+ .contains(debutant.public_key()));
+ assert_eq!(dest_db.root().version(), original_version + 1);
+ root_node_cryptde.verify_signature(
+ dest_db.root().signed_gossip(),
+ dest_db.root().signature(),
+ dest_db.root().public_key(),
+ );
+ }
+
#[test]
fn proper_pass_is_identified_and_processed() {
- let (gossip, pass_target, gossip_source) = make_pass(2345);
+ let (gossip, pass_target, gossip_source) =
+ make_pass(2345, NeighborhoodModeLight::Standard.into());
let subject = PassHandler::new();
let mut dest_db = make_meaningless_db();
let cryptde = CryptDENull::from(dest_db.root().public_key(), TEST_DEFAULT_CHAIN);
@@ -1789,18 +2541,53 @@ mod tests {
.node(dest_db.root().public_key(), true)
.build();
assert_eq!(
- GossipAcceptanceResult::Reply(
+ handle_result,
+ vec![GossipAcceptanceResult::Reply(
debut,
pass_target.public_key().clone(),
pass_target.node_addr_opt().unwrap().clone(),
- ),
- handle_result
+ )],
+ );
+ }
+
+ #[test]
+ fn pass_with_node_that_does_not_accept_connections_is_rejected() {
+ let root_node = make_node_record(1234, true); // irrelevant
+ let mut db = db_from_node(&root_node); // irrelevant
+ let (gossip, _pass_target, gossip_source) =
+ make_pass(2345, NeighborhoodModeLight::OriginateOnly.into());
+ let agrs_vec: Vec = gossip.try_into().unwrap();
+ let subject = PassHandler::new();
+
+ let result = subject.qualifies(&mut db, agrs_vec.as_slice(), gossip_source);
+
+ assert_eq!(
+ result,
+ Qualification::Unmatched("Pass Node record always has a NodeAddr".to_string())
+ );
+ }
+
+ #[test]
+ fn pass_with_node_that_does_not_route_data_is_rejected() {
+ let root_node = make_node_record(1234, true); // irrelevant
+ let mut db = db_from_node(&root_node); // irrelevant
+ let (gossip, _pass_target, gossip_source) =
+ make_pass(2345, NeighborhoodModeLight::ConsumeOnly.into());
+ let agrs_vec: Vec = gossip.try_into().unwrap();
+ let subject = PassHandler::new();
+
+ let result = subject.qualifies(&mut db, agrs_vec.as_slice(), gossip_source);
+
+ assert_eq!(
+ result,
+ Qualification::Unmatched("Pass Node record always has a NodeAddr".to_string())
);
}
#[test]
fn pass_without_node_addr_is_rejected() {
- let (mut gossip, _, gossip_source) = make_pass(2345);
+ let (mut gossip, _, gossip_source) =
+ make_pass(2345, NeighborhoodModeLight::Standard.into());
gossip.node_records[0].node_addr_opt = None;
let subject = PassHandler::new();
let agrs_vec: Vec = gossip.try_into().unwrap();
@@ -1808,16 +2595,15 @@ mod tests {
let result = subject.qualifies(&make_meaningless_db(), agrs_vec.as_slice(), gossip_source);
assert_eq!(
- Qualification::Malformed(
- "Pass from 200.200.200.200:2000 to AgMEBQ did not contain NodeAddr".to_string()
- ),
- result
+ result,
+ Qualification::Unmatched("Pass Node record always has a NodeAddr".to_string()),
);
}
#[test]
fn pass_without_node_addr_ports_is_rejected() {
- let (mut gossip, _, gossip_source) = make_pass(2345);
+ let (mut gossip, _, gossip_source) =
+ make_pass(2345, NeighborhoodModeLight::Standard.into());
gossip.node_records[0].node_addr_opt =
Some(NodeAddr::new(&IpAddr::from_str("1.2.3.4").unwrap(), &[]));
let subject = PassHandler::new();
@@ -1826,38 +2612,103 @@ mod tests {
let result = subject.qualifies(&make_meaningless_db(), agrs_vec.as_slice(), gossip_source);
assert_eq!(
- Qualification::Malformed(
- "Pass from 200.200.200.200:2000 to AgMEBQ at 1.2.3.4 contained NodeAddr with no ports"
- .to_string()
- ),
- result
+ result,
+ Qualification::Malformed(Malefactor::new(
+ Some(agrs_vec[0].inner.public_key.clone()),
+ Some(gossip_source.ip()),
+ Some(agrs_vec[0].inner.earning_wallet.clone()),
+ None,
+ "Pass contained NodeAddr with no ports".to_string()
+ )),
);
}
#[test]
fn gossip_containing_other_than_two_records_is_not_an_introduction() {
- let (gossip, _, gossip_source) = make_debut(2345, Mode::Standard);
- let subject = IntroductionHandler::new(Logger::new("test"));
+ let (gossip, _, gossip_source) = make_debut(2345, NeighborhoodModeLight::Standard.into());
+ let subject =
+ IntroductionHandler::new(&RatePackLimits::test_default(), Logger::new("test"));
let agrs_vec: Vec = gossip.try_into().unwrap();
let result = subject.qualifies(&make_meaningless_db(), agrs_vec.as_slice(), gossip_source);
- assert_eq!(Qualification::Unmatched, result);
+ assert_eq!(
+ Qualification::Unmatched("Introduction has 2 Node records, not 1".to_string()),
+ result
+ );
}
#[test]
- fn introduction_where_introducee_is_in_the_database_is_unmatched() {
+ fn introduction_is_still_matched_even_if_receiver_has_half_neighborship_with_introducer() {
let (gossip, gossip_source) = make_introduction(2345, 3456);
- let not_introducee = make_node_record(3456, true);
+ let introducer_in_target_db = make_node_record(2345, true);
+ let introducee_in_target_db = make_node_record(3456, true);
let dest_root = make_node_record(7878, true);
let mut dest_db = db_from_node(&dest_root);
- dest_db.add_node(not_introducee.clone()).unwrap();
- let subject = IntroductionHandler::new(Logger::new("test"));
+ dest_db.add_node(introducer_in_target_db.clone()).unwrap();
+ dest_db
+ .add_half_neighbor(introducer_in_target_db.public_key())
+ .unwrap(); //disqualifying
+ dest_db.add_node(introducee_in_target_db.clone()).unwrap();
+ let subject =
+ IntroductionHandler::new(&RatePackLimits::test_default(), Logger::new("test"));
let agrs_vec: Vec