From 4c5351ba60fd63b595e4da64d77dad5e3a0a5ab8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 5 Apr 2024 18:34:31 +0100 Subject: [PATCH] Feature/one binary to rule them all (#4500) --- .../workflows/ci-build-upload-binaries.yml | 2 + Cargo.lock | 463 ++++++++++--- Cargo.toml | 7 +- clients/native/src/error.rs | 3 + clients/socks5/src/error.rs | 3 + common/bin-common/Cargo.toml | 1 - common/bin-common/src/logging/mod.rs | 5 +- common/client-core/Cargo.toml | 4 +- common/client-core/config-types/Cargo.toml | 26 + .../src/disk_persistence/mod.rs} | 40 +- .../src/disk_persistence/old/mod.rs | 9 + .../src/disk_persistence/old/v1.rs | 37 + .../src/disk_persistence/old/v2.rs} | 31 +- common/client-core/config-types/src/error.rs | 10 + common/client-core/config-types/src/lib.rs | 602 +++++++++++++++++ .../client-core/config-types/src/old/mod.rs | 15 + .../src/old/v1.rs} | 75 ++- .../src/old/v2.rs} | 139 ++-- .../src/old/v3.rs} | 147 ++-- .../src/old/v4.rs} | 143 ++-- .../src/old/v5.rs} | 126 ++-- .../client-core/gateways-storage/src/types.rs | 15 + .../src/client/key_manager/persistence.rs | 2 +- .../topology_control/geo_aware_provider.rs | 161 +---- .../src/config/disk_persistence/mod.rs | 43 -- .../config/disk_persistence/old_v1_1_20_2.rs | 33 - common/client-core/src/config/mod.rs | 605 +---------------- common/commands/Cargo.toml | 2 +- common/country-group/Cargo.toml | 15 + common/country-group/src/lib.rs | 158 +++++ common/crypto/src/asymmetric/identity/mod.rs | 3 + .../src/asymmetric/identity/serde_helpers.rs | 18 + common/crypto/src/asymmetric/mod.rs | 3 + common/dkg/src/bte/proof_chunking.rs | 2 +- common/http-api-common/Cargo.toml | 20 + .../http-api-common/src/lib.rs | 23 +- common/mixnode-common/Cargo.toml | 8 +- .../mixnode-common/src/verloc/measurement.rs | 266 +------- common/mixnode-common/src/verloc/mod.rs | 37 +- common/mixnode-common/src/verloc/sender.rs | 8 +- common/nymsphinx/addressing/src/lib.rs | 3 + common/task/src/manager.rs | 2 +- common/topology/src/gateway.rs | 4 +- explorer-api/Cargo.toml | 2 +- gateway/Cargo.toml | 16 +- gateway/src/commands/helpers.rs | 134 +--- gateway/src/commands/init.rs | 324 +-------- gateway/src/commands/mod.rs | 14 +- gateway/src/commands/node_details.rs | 2 +- gateway/src/commands/run.rs | 30 +- .../src/commands/setup_ip_packet_router.rs | 6 +- .../src/commands/setup_network_requester.rs | 16 +- gateway/src/commands/sign.rs | 11 +- gateway/src/commands/upgrade_helpers.rs | 12 +- gateway/src/config/mod.rs | 153 ++++- gateway/src/config/old_config_v1_1_29.rs | 3 +- gateway/src/config/old_config_v1_1_31.rs | 20 +- gateway/src/config/persistence/paths.rs | 46 +- gateway/src/error.rs | 35 +- gateway/src/helpers.rs | 231 +++++++ gateway/src/http/mod.rs | 31 +- gateway/src/lib.rs | 14 + gateway/src/main.rs | 10 +- .../connection_handler/authenticated.rs | 2 +- .../websocket/connection_handler/mod.rs | 2 +- gateway/src/node/helpers.rs | 148 +--- gateway/src/node/mod.rs | 167 +++-- gateway/src/node/storage/error.rs | 2 +- gateway/src/node/storage/mod.rs | 7 +- gateway/src/node/storage/models.rs | 8 +- gateway/src/support/config.rs | 14 - gateway/src/support/mod.rs | 4 - mixnode/Cargo.toml | 7 +- mixnode/src/commands/describe.rs | 4 +- mixnode/src/commands/init.rs | 130 +--- mixnode/src/commands/mod.rs | 38 +- mixnode/src/commands/node_details.rs | 2 +- mixnode/src/commands/run.rs | 12 +- mixnode/src/commands/sign.rs | 9 +- mixnode/src/commands/upgrade_helpers.rs | 8 +- mixnode/src/config/mod.rs | 85 ++- mixnode/src/config/old_config_v1_1_32.rs | 5 +- mixnode/src/config/persistence/paths.rs | 12 + mixnode/src/error.rs | 3 +- mixnode/src/lib.rs | 9 + mixnode/src/main.rs | 3 - mixnode/src/node/helpers.rs | 4 +- mixnode/src/node/http/legacy/description.rs | 2 +- mixnode/src/node/http/legacy/hardware.rs | 2 +- mixnode/src/node/http/legacy/state.rs | 11 +- mixnode/src/node/http/legacy/stats.rs | 15 +- mixnode/src/node/http/legacy/verloc.rs | 24 +- mixnode/src/node/http/mod.rs | 26 +- mixnode/src/node/mod.rs | 132 ++-- mixnode/src/node/node_description.rs | 29 +- mixnode/src/node/node_statistics.rs | 164 +---- nym-api/Cargo.toml | 2 +- nym-connect/desktop/Cargo.lock | 431 ++++++++---- nym-connect/desktop/src-tauri/src/error.rs | 3 + nym-node/Cargo.toml | 70 +- nym-node/build.rs | 36 + nym-node/nym-node-http-api/Cargo.toml | 50 ++ nym-node/nym-node-http-api/src/error.rs | 26 + nym-node/nym-node-http-api/src/lib.rs | 66 ++ .../src}/middleware/logging.rs | 0 .../src}/middleware/mod.rs | 0 .../http => nym-node-http-api/src}/mod.rs | 0 .../nym-node-http-api/src/router/api/mod.rs | 25 + .../api/v1/gateway/client_interfaces/mod.rs | 4 +- .../wireguard/client_registry.rs | 8 +- .../client_interfaces/wireguard/error.rs | 13 + .../client_interfaces/wireguard/mod.rs | 10 +- .../src}/router/api/v1/gateway/mod.rs | 2 +- .../src}/router/api/v1/gateway/root.rs | 2 +- .../src}/router/api/v1/health.rs | 4 +- .../router/api/v1/ip_packet_router/mod.rs | 0 .../router/api/v1/ip_packet_router/root.rs | 2 +- .../src/router/api/v1/metrics/mixing.rs | 33 + .../src/router/api/v1/metrics/mod.rs | 31 + .../src/router/api/v1/metrics/prometheus.rs | 45 ++ .../src/router/api/v1/metrics/verloc.rs | 32 + .../src}/router/api/v1/mixnode/mod.rs | 0 .../src}/router/api/v1/mixnode/root.rs | 2 +- .../src}/router/api/v1/mod.rs | 7 +- .../api/v1/network_requester/exit_policy.rs | 2 +- .../router/api/v1/network_requester/mod.rs | 2 +- .../router/api/v1/network_requester/root.rs | 2 +- .../router/api/v1/node/build_information.rs | 2 +- .../src/router/api/v1/node/description.rs | 31 + .../src/router/api/v1/node/hardware.rs | 41 ++ .../router/api/v1/node/host_information.rs | 2 +- .../src}/router/api/v1/node/mod.rs | 26 +- .../src}/router/api/v1/node/roles.rs | 2 +- .../src}/router/api/v1/openapi.rs | 41 +- .../src}/router/landing_page.rs | 0 .../src}/router/mod.rs | 87 ++- .../src}/router/types.rs | 0 .../nym-node-http-api/src/state/metrics.rs | 148 ++++ nym-node/nym-node-http-api/src/state/mod.rs | 46 ++ nym-node/nym-node-requests/Cargo.toml | 5 +- nym-node/nym-node-requests/src/api/mod.rs | 3 +- .../src/api/v1/metrics/mod.rs | 4 + .../src/api/v1/metrics/models.rs | 279 ++++++++ nym-node/nym-node-requests/src/api/v1/mod.rs | 1 + .../src/api/v1/node/models.rs | 97 ++- nym-node/nym-node-requests/src/lib.rs | 44 +- .../src/cli/commands/bonding_information.rs | 41 ++ nym-node/src/cli/commands/build_info.rs | 17 + nym-node/src/cli/commands/migrate.rs | 631 ++++++++++++++++++ nym-node/src/cli/commands/mod.rs | 9 + nym-node/src/cli/commands/node_details.rs | 29 + nym-node/src/cli/commands/run/args.rs | 148 ++++ nym-node/src/cli/commands/run/mod.rs | 77 +++ nym-node/src/cli/commands/sign.rs | 88 +++ nym-node/src/cli/helpers.rs | 434 ++++++++++++ nym-node/src/cli/mod.rs | 87 +++ nym-node/src/config/entry_gateway.rs | 83 +++ nym-node/src/config/exit_gateway.rs | 241 +++++++ nym-node/src/config/helpers.rs | 93 +++ nym-node/src/config/mixnode.rs | 214 ++++++ nym-node/src/config/mod.rs | 479 ++++++++++++- nym-node/src/config/persistence.rs | 368 +++++++++- nym-node/src/config/serde_helpers.rs | 29 - nym-node/src/config/template.rs | 252 +++++++ nym-node/src/config/upgrade_helpers.rs | 22 + nym-node/src/env.rs | 58 ++ nym-node/src/error.rs | 200 +++++- nym-node/src/http/state.rs | 38 -- nym-node/src/lib.rs | 7 +- nym-node/src/main.rs | 34 + nym-node/src/node/bonding_information.rs | 197 ++++++ nym-node/src/node/description.rs | 40 ++ nym-node/src/node/helpers.rs | 153 +++++ nym-node/src/node/http/mod.rs | 34 + nym-node/src/node/http/system_info.rs | 80 +++ nym-node/src/node/mod.rs | 589 ++++++++++++++++ nym-node/src/wireguard/types.rs | 4 +- nym-outfox/Cargo.toml | 2 +- nym-validator-rewarder/Cargo.toml | 2 +- nym-wallet/Cargo.lock | 9 +- sdk/rust/nym-sdk/src/mixnet/config.rs | 1 + sdk/rust/nym-sdk/src/mixnet/paths.rs | 2 +- .../ip-packet-router/src/config/mod.rs | 2 +- .../ip-packet-router/src/error.rs | 3 + .../ip-packet-router/src/ip_packet_router.rs | 8 +- .../network-requester/Cargo.toml | 2 +- .../network-requester/src/cli/mod.rs | 2 +- .../network-requester/src/config/mod.rs | 2 +- .../network-requester/src/error.rs | 3 + .../network-requester/src/lib.rs | 7 +- tools/nymvisor/Cargo.toml | 2 +- 191 files changed, 8988 insertions(+), 3123 deletions(-) create mode 100644 common/client-core/config-types/Cargo.toml rename common/client-core/{src/config/disk_persistence/keys_paths.rs => config-types/src/disk_persistence/mod.rs} (70%) create mode 100644 common/client-core/config-types/src/disk_persistence/old/mod.rs create mode 100644 common/client-core/config-types/src/disk_persistence/old/v1.rs rename common/client-core/{src/config/disk_persistence/old_v1_1_33.rs => config-types/src/disk_persistence/old/v2.rs} (78%) create mode 100644 common/client-core/config-types/src/error.rs create mode 100644 common/client-core/config-types/src/lib.rs create mode 100644 common/client-core/config-types/src/old/mod.rs rename common/client-core/{src/config/old_config_v1_1_13.rs => config-types/src/old/v1.rs} (80%) rename common/client-core/{src/config/old_config_v1_1_20.rs => config-types/src/old/v2.rs} (78%) rename common/client-core/{src/config/old_config_v1_1_20_2.rs => config-types/src/old/v3.rs} (77%) rename common/client-core/{src/config/old_config_v1_1_30.rs => config-types/src/old/v4.rs} (85%) rename common/client-core/{src/config/old_config_v1_1_33.rs => config-types/src/old/v5.rs} (87%) delete mode 100644 common/client-core/src/config/disk_persistence/mod.rs delete mode 100644 common/client-core/src/config/disk_persistence/old_v1_1_20_2.rs create mode 100644 common/country-group/Cargo.toml create mode 100644 common/country-group/src/lib.rs create mode 100644 common/crypto/src/asymmetric/identity/serde_helpers.rs create mode 100644 common/http-api-common/Cargo.toml rename nym-node/src/http/router/api/mod.rs => common/http-api-common/src/lib.rs (80%) create mode 100644 gateway/src/helpers.rs create mode 100644 gateway/src/lib.rs delete mode 100644 gateway/src/support/config.rs delete mode 100644 gateway/src/support/mod.rs create mode 100644 mixnode/src/lib.rs create mode 100644 nym-node/build.rs create mode 100644 nym-node/nym-node-http-api/Cargo.toml create mode 100644 nym-node/nym-node-http-api/src/error.rs create mode 100644 nym-node/nym-node-http-api/src/lib.rs rename nym-node/{src/http => nym-node-http-api/src}/middleware/logging.rs (100%) rename nym-node/{src/http => nym-node-http-api/src}/middleware/mod.rs (100%) rename nym-node/{src/http => nym-node-http-api/src}/mod.rs (100%) create mode 100644 nym-node/nym-node-http-api/src/router/api/mod.rs rename nym-node/{src/http => nym-node-http-api/src}/router/api/v1/gateway/client_interfaces/mod.rs (96%) rename nym-node/{src/http => nym-node-http-api/src}/router/api/v1/gateway/client_interfaces/wireguard/client_registry.rs (97%) create mode 100644 nym-node/nym-node-http-api/src/router/api/v1/gateway/client_interfaces/wireguard/error.rs rename nym-node/{src/http => nym-node-http-api/src}/router/api/v1/gateway/client_interfaces/wireguard/mod.rs (96%) rename nym-node/{src/http => nym-node-http-api/src}/router/api/v1/gateway/mod.rs (91%) rename nym-node/{src/http => nym-node-http-api/src}/router/api/v1/gateway/root.rs (93%) rename nym-node/{src/http => nym-node-http-api/src}/router/api/v1/health.rs (91%) rename nym-node/{src/http => nym-node-http-api/src}/router/api/v1/ip_packet_router/mod.rs (100%) rename nym-node/{src/http => nym-node-http-api/src}/router/api/v1/ip_packet_router/root.rs (94%) create mode 100644 nym-node/nym-node-http-api/src/router/api/v1/metrics/mixing.rs create mode 100644 nym-node/nym-node-http-api/src/router/api/v1/metrics/mod.rs create mode 100644 nym-node/nym-node-http-api/src/router/api/v1/metrics/prometheus.rs create mode 100644 nym-node/nym-node-http-api/src/router/api/v1/metrics/verloc.rs rename nym-node/{src/http => nym-node-http-api/src}/router/api/v1/mixnode/mod.rs (100%) rename nym-node/{src/http => nym-node-http-api/src}/router/api/v1/mixnode/root.rs (93%) rename nym-node/{src/http => nym-node-http-api/src}/router/api/v1/mod.rs (85%) rename nym-node/{src/http => nym-node-http-api/src}/router/api/v1/network_requester/exit_policy.rs (93%) rename nym-node/{src/http => nym-node-http-api/src}/router/api/v1/network_requester/mod.rs (93%) rename nym-node/{src/http => nym-node-http-api/src}/router/api/v1/network_requester/root.rs (94%) rename nym-node/{src/http => nym-node-http-api/src}/router/api/v1/node/build_information.rs (93%) create mode 100644 nym-node/nym-node-http-api/src/router/api/v1/node/description.rs create mode 100644 nym-node/nym-node-http-api/src/router/api/v1/node/hardware.rs rename nym-node/{src/http => nym-node-http-api/src}/router/api/v1/node/host_information.rs (93%) rename nym-node/{src/http => nym-node-http-api/src}/router/api/v1/node/mod.rs (59%) rename nym-node/{src/http => nym-node-http-api/src}/router/api/v1/node/roles.rs (92%) rename nym-node/{src/http => nym-node-http-api/src}/router/api/v1/openapi.rs (69%) rename nym-node/{src/http => nym-node-http-api/src}/router/landing_page.rs (100%) rename nym-node/{src/http => nym-node-http-api/src}/router/mod.rs (62%) rename nym-node/{src/http => nym-node-http-api/src}/router/types.rs (100%) create mode 100644 nym-node/nym-node-http-api/src/state/metrics.rs create mode 100644 nym-node/nym-node-http-api/src/state/mod.rs create mode 100644 nym-node/nym-node-requests/src/api/v1/metrics/mod.rs create mode 100644 nym-node/nym-node-requests/src/api/v1/metrics/models.rs create mode 100644 nym-node/src/cli/commands/bonding_information.rs create mode 100644 nym-node/src/cli/commands/build_info.rs create mode 100644 nym-node/src/cli/commands/migrate.rs create mode 100644 nym-node/src/cli/commands/mod.rs create mode 100644 nym-node/src/cli/commands/node_details.rs create mode 100644 nym-node/src/cli/commands/run/args.rs create mode 100644 nym-node/src/cli/commands/run/mod.rs create mode 100644 nym-node/src/cli/commands/sign.rs create mode 100644 nym-node/src/cli/helpers.rs create mode 100644 nym-node/src/cli/mod.rs create mode 100644 nym-node/src/config/entry_gateway.rs create mode 100644 nym-node/src/config/exit_gateway.rs create mode 100644 nym-node/src/config/helpers.rs create mode 100644 nym-node/src/config/mixnode.rs delete mode 100644 nym-node/src/config/serde_helpers.rs create mode 100644 nym-node/src/config/template.rs create mode 100644 nym-node/src/config/upgrade_helpers.rs create mode 100644 nym-node/src/env.rs delete mode 100644 nym-node/src/http/state.rs create mode 100644 nym-node/src/main.rs create mode 100644 nym-node/src/node/bonding_information.rs create mode 100644 nym-node/src/node/description.rs create mode 100644 nym-node/src/node/helpers.rs create mode 100644 nym-node/src/node/http/mod.rs create mode 100644 nym-node/src/node/http/system_info.rs create mode 100644 nym-node/src/node/mod.rs diff --git a/.github/workflows/ci-build-upload-binaries.yml b/.github/workflows/ci-build-upload-binaries.yml index a8ba53e9dd..5a0f325b45 100644 --- a/.github/workflows/ci-build-upload-binaries.yml +++ b/.github/workflows/ci-build-upload-binaries.yml @@ -112,6 +112,7 @@ jobs: target/release/nym-network-statistics target/release/nym-cli target/release/nymvisor + target/release/nym-node retention-days: 30 # If this was a pull_request or nightly, upload to build server @@ -130,6 +131,7 @@ jobs: cp target/release/nym-network-requester $OUTPUT_DIR cp target/release/nym-network-statistics $OUTPUT_DIR cp target/release/nymvisor $OUTPUT_DIR + cp target/release/nym-node $OUTPUT_DIR cp target/release/nym-cli $OUTPUT_DIR cp target/release/explorer-api $OUTPUT_DIR if [ ${{ github.event_name == 'workflow_dispatch' && inputs.enable_deb == true }} = true ]; then diff --git a/Cargo.lock b/Cargo.lock index a06f771ca2..3e44825198 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -437,7 +437,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.58", ] [[package]] @@ -448,7 +448,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.58", ] [[package]] @@ -547,6 +547,7 @@ dependencies = [ "bitflags 1.3.2", "bytes", "futures-util", + "headers", "http 0.2.9", "http-body 0.4.5", "hyper 0.14.27", @@ -772,7 +773,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57a0e8073e8baa88212fb5823574c02ebccb395136ba9a164ab89379ec6072f0" dependencies = [ - "block-padding", + "block-padding 0.2.1", "cipher 0.2.5", ] @@ -782,6 +783,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array 0.14.7", +] + [[package]] name = "bls12_381" version = "0.8.0" @@ -861,6 +871,38 @@ dependencies = [ "serde", ] +[[package]] +name = "camino" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver 1.0.22", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "cast" version = "0.3.0" @@ -893,6 +935,15 @@ dependencies = [ "subtle 2.4.1", ] +[[package]] +name = "celes" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39b9a21273925d7cc9e8a9a5f068122341336813c607014f5ef64f82b6acba58" +dependencies = [ + "serde", +] + [[package]] name = "cesu8" version = "1.1.0" @@ -1071,9 +1122,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.4.3" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ae8ba90b9d8b007efe66e55e48fb936272f5ca00349b5b0e89877520d35ea7" +checksum = "885e4d7d5af40bfb99ae6f9433e292feac98d452dcb3ec3d25dfe7552b77da8c" dependencies = [ "clap 4.4.7", ] @@ -1097,7 +1148,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.58", ] [[package]] @@ -1765,7 +1816,7 @@ checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.58", ] [[package]] @@ -1817,7 +1868,7 @@ dependencies = [ "cosmwasm-std", "cw2", "schemars", - "semver 1.0.20", + "semver 1.0.22", "serde", "thiserror", ] @@ -1832,7 +1883,7 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus", "schemars", - "semver 1.0.20", + "semver 1.0.22", "serde", "thiserror", ] @@ -1900,12 +1951,12 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.5" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc5d6b04b3fd0ba9926f945895de7d806260a2d7431ba82e7edaecb043c4c6b8" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" dependencies = [ - "darling_core 0.20.5", - "darling_macro 0.20.5", + "darling_core 0.20.3", + "darling_macro 0.20.3", ] [[package]] @@ -1938,16 +1989,16 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.5" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04e48a959bcd5c761246f5d090ebc2fbf7b9cd527a492b07a67510c108f1e7e3" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.38", + "syn 2.0.58", ] [[package]] @@ -1974,13 +2025,13 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.20.5" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1545d67a2149e1d93b7e5c7752dce5a7426eb5d1357ddcfd89336b94444f77" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ - "darling_core 0.20.5", + "darling_core 0.20.3", "quote", - "syn 2.0.38", + "syn 2.0.58", ] [[package]] @@ -2172,7 +2223,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.38", + "syn 2.0.58", ] [[package]] @@ -2254,7 +2305,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.58", ] [[package]] @@ -2635,7 +2686,7 @@ dependencies = [ "atomic 0.6.0", "pear", "serde", - "toml 0.8.2", + "toml 0.8.12", "uncased", "version_check", ] @@ -2822,7 +2873,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.58", ] [[package]] @@ -2978,7 +3029,7 @@ checksum = "ba330b70a5341d3bc730b8e205aaee97ddab5d9c448c4f51a7c2d924266fa8f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.58", ] [[package]] @@ -3150,6 +3201,30 @@ dependencies = [ "num-traits", ] +[[package]] +name = "headers" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" +dependencies = [ + "base64 0.21.4", + "bytes", + "headers-core", + "http 0.2.9", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http 0.2.9", +] + [[package]] name = "heck" version = "0.3.3" @@ -3476,7 +3551,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core", + "windows-core 0.51.1", ] [[package]] @@ -3628,6 +3703,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ + "block-padding 0.3.3", "generic-array 0.14.7", ] @@ -3960,9 +4036,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.149" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libm" @@ -5073,7 +5149,6 @@ dependencies = [ name = "nym-bin-common" version = "0.6.0" dependencies = [ - "atty", "clap 4.4.7", "clap_complete", "clap_complete_fig", @@ -5243,9 +5318,11 @@ dependencies = [ "hyper-util", "log", "nym-bandwidth-controller", + "nym-client-core-config-types", "nym-client-core-gateways-storage", "nym-client-core-surb-storage", "nym-config", + "nym-country-group", "nym-credential-storage", "nym-crypto", "nym-explorer-client", @@ -5281,6 +5358,21 @@ dependencies = [ "zeroize", ] +[[package]] +name = "nym-client-core-config-types" +version = "0.1.0" +dependencies = [ + "humantime-serde", + "nym-config", + "nym-country-group", + "nym-pemstore", + "nym-sphinx-addressing", + "nym-sphinx-params", + "serde", + "thiserror", + "url", +] + [[package]] name = "nym-client-core-gateways-storage" version = "0.1.0" @@ -5420,6 +5512,14 @@ dependencies = [ "thiserror", ] +[[package]] +name = "nym-country-group" +version = "0.1.0" +dependencies = [ + "serde", + "tracing", +] + [[package]] name = "nym-credential-storage" version = "0.1.0" @@ -5587,7 +5687,6 @@ version = "1.1.33" dependencies = [ "anyhow", "async-trait", - "atty", "bip39", "bs58 0.5.0", "clap 4.4.7", @@ -5598,7 +5697,6 @@ dependencies = [ "dotenvy", "futures", "humantime-serde", - "hyper 0.14.27", "ipnetwork 0.16.0", "log", "nym-api-requests", @@ -5613,7 +5711,7 @@ dependencies = [ "nym-mixnode-common", "nym-network-defaults", "nym-network-requester", - "nym-node", + "nym-node-http-api", "nym-pemstore", "nym-sphinx", "nym-statistics-common", @@ -5621,10 +5719,9 @@ dependencies = [ "nym-types", "nym-validator-client", "nym-wireguard", + "nym-wireguard-types", "once_cell", - "pretty_env_logger", "rand 0.7.3", - "rand 0.8.5", "serde", "serde_json", "sqlx", @@ -5635,7 +5732,6 @@ dependencies = [ "tokio-stream", "tokio-tungstenite", "tokio-util", - "tower", "url", "zeroize", ] @@ -5720,6 +5816,19 @@ dependencies = [ "wasmtimer", ] +[[package]] +name = "nym-http-api-common" +version = "0.1.0" +dependencies = [ + "axum", + "bytes", + "mime", + "serde", + "serde_json", + "serde_yaml", + "utoipa", +] + [[package]] name = "nym-id" version = "0.1.0" @@ -5884,10 +5993,11 @@ dependencies = [ "nym-config", "nym-contracts-common", "nym-crypto", + "nym-http-api-common", "nym-metrics", "nym-mixnet-client", "nym-mixnode-common", - "nym-node", + "nym-node-http-api", "nym-nonexhaustive-delayqueue", "nym-pemstore", "nym-sphinx", @@ -5900,8 +6010,9 @@ dependencies = [ "rand 0.7.3", "serde", "serde_json", - "sysinfo", + "sysinfo 0.27.8", "thiserror", + "time", "tokio", "tokio-util", "toml 0.5.11", @@ -5920,6 +6031,7 @@ dependencies = [ "nym-crypto", "nym-metrics", "nym-network-defaults", + "nym-node-http-api", "nym-sphinx-acknowledgements", "nym-sphinx-addressing", "nym-sphinx-forwarding", @@ -5931,6 +6043,7 @@ dependencies = [ "rand 0.8.5", "serde", "thiserror", + "time", "tokio", "tokio-util", "url", @@ -6054,26 +6167,64 @@ name = "nym-node" version = "0.1.0" dependencies = [ "anyhow", + "bip39", + "bs58 0.5.0", + "cargo_metadata", + "celes", + "clap 4.4.7", + "cupid", + "humantime-serde", + "ipnetwork 0.16.0", + "nym-bin-common", + "nym-client-core-config-types", + "nym-config", + "nym-crypto", + "nym-gateway", + "nym-ip-packet-router", + "nym-mixnode", + "nym-network-requester", + "nym-node-http-api", + "nym-pemstore", + "nym-sphinx-acknowledgements", + "nym-sphinx-addressing", + "nym-task", + "nym-types", + "nym-wireguard-types", + "rand 0.7.3", + "semver 1.0.22", + "serde", + "serde_json", + "sysinfo 0.30.7", + "thiserror", + "tokio", + "toml 0.8.12", + "tracing", + "url", + "zeroize", +] + +[[package]] +name = "nym-node-http-api" +version = "0.1.0" +dependencies = [ "axum", - "bytes", "colored", "dashmap", "fastrand 2.0.1", "hmac 0.12.1", "hyper 0.14.27", "ipnetwork 0.16.0", - "mime", - "nym-config", "nym-crypto", + "nym-http-api-common", + "nym-metrics", "nym-node-requests", "nym-task", "nym-wireguard", "nym-wireguard-types", "rand 0.7.3", - "serde", "serde_json", - "serde_yaml", "thiserror", + "time", "tokio", "tower", "tower-http", @@ -6089,6 +6240,8 @@ version = "0.1.0" dependencies = [ "async-trait", "base64 0.21.4", + "humantime 2.1.0", + "humantime-serde", "nym-bin-common", "nym-crypto", "nym-exit-policy", @@ -6098,6 +6251,7 @@ dependencies = [ "serde", "serde_json", "thiserror", + "time", "tokio", "utoipa", ] @@ -7184,7 +7338,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.38", + "syn 2.0.58", ] [[package]] @@ -7280,7 +7434,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.58", ] [[package]] @@ -7321,7 +7475,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.58", ] [[package]] @@ -7532,9 +7686,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] @@ -7547,7 +7701,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.58", "version_check", "yansi", ] @@ -7587,7 +7741,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.58", ] [[package]] @@ -7667,7 +7821,7 @@ dependencies = [ "itertools 0.11.0", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.58", ] [[package]] @@ -7696,9 +7850,9 @@ checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" [[package]] name = "psl" -version = "2.1.22" +version = "2.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74a6e6a56708be1cf5c4c4d1a0dc21d33b2dcaa24e731b7fa9c287ce4f916f" +checksum = "383703acfc34f7a00724846c14dc5ea4407c59e5aedcbbb18a1c0c1a23fe5013" dependencies = [ "psl-types", ] @@ -7762,9 +7916,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -8089,18 +8243,18 @@ checksum = "7f7473c2cfcf90008193dd0e3e16599455cb601a9fce322b5bb55de799664925" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.58", ] [[package]] name = "regex" -version = "1.10.3" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.6", + "regex-automata 0.4.3", "regex-syntax 0.8.2", ] @@ -8115,9 +8269,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", @@ -8298,7 +8452,7 @@ dependencies = [ "proc-macro2", "quote", "rocket_http", - "syn 2.0.38", + "syn 2.0.58", "unicode-xid", "version_check", ] @@ -8436,7 +8590,7 @@ dependencies = [ "quote", "rust-embed-utils", "shellexpand", - "syn 2.0.38", + "syn 2.0.58", "walkdir", ] @@ -8477,7 +8631,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.20", + "semver 1.0.22", ] [[package]] @@ -8795,9 +8949,12 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +dependencies = [ + "serde", +] [[package]] name = "semver-parser" @@ -8816,9 +8973,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.189" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] @@ -8854,13 +9011,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.189" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.58", ] [[package]] @@ -8882,7 +9039,7 @@ checksum = "e578a843d40b4189a4d66bba51d7684f57da5bd7c304c64e14bd63efbef49509" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.58", ] [[package]] @@ -8914,14 +9071,14 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.58", ] [[package]] name = "serde_spanned" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" dependencies = [ "serde", ] @@ -8940,9 +9097,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.6.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b0ed1662c5a68664f45b76d18deb0e234aff37207086803165c961eb695e981" +checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23" dependencies = [ "base64 0.21.4", "chrono", @@ -8957,14 +9114,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.6.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "568577ff0ef47b879f736cd66740e022f3672788cdf002a05a4e609ea5a6fb15" +checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788" dependencies = [ - "darling 0.20.5", + "darling 0.20.3", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.58", ] [[package]] @@ -9482,7 +9639,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.38", + "syn 2.0.58", ] [[package]] @@ -9553,9 +9710,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.38" +version = "2.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" dependencies = [ "proc-macro2", "quote", @@ -9595,6 +9752,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "sysinfo" +version = "0.30.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c385888ef380a852a16209afc8cfad22795dd8873d69c9a14d2e2088f118d18" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "windows 0.52.0", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -9724,7 +9896,7 @@ dependencies = [ "peg", "pin-project", "reqwest", - "semver 1.0.20", + "semver 1.0.22", "serde", "serde_bytes", "serde_json", @@ -9774,7 +9946,7 @@ checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.58", ] [[package]] @@ -9904,7 +10076,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.58", ] [[package]] @@ -10033,21 +10205,21 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.2" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.20.2", + "toml_edit 0.22.9", ] [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" dependencies = [ "serde", ] @@ -10062,20 +10234,20 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.5.17", ] [[package]] name = "toml_edit" -version = "0.20.2" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" dependencies = [ "indexmap 2.0.2", "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.6.5", ] [[package]] @@ -10183,7 +10355,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.58", ] [[package]] @@ -10376,7 +10548,7 @@ dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.58", "termcolor", ] @@ -10403,7 +10575,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals 0.28.0", - "syn 2.0.38", + "syn 2.0.58", ] [[package]] @@ -10655,7 +10827,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.38", + "syn 2.0.58", ] [[package]] @@ -10793,7 +10965,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.58", "wasm-bindgen-shared", ] @@ -10827,7 +10999,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.58", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -11278,10 +11450,20 @@ version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" dependencies = [ - "windows-core", + "windows-core 0.51.1", "windows-targets 0.48.5", ] +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core 0.52.0", + "windows-targets 0.52.4", +] + [[package]] name = "windows-core" version = "0.51.1" @@ -11291,6 +11473,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.4", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -11339,6 +11530,21 @@ dependencies = [ "windows_x86_64_msvc 0.48.5", ] +[[package]] +name = "windows-targets" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -11351,6 +11557,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -11363,6 +11575,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -11375,6 +11593,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -11387,6 +11611,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -11399,6 +11629,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -11411,6 +11647,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -11423,6 +11665,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + [[package]] name = "winnow" version = "0.5.17" @@ -11432,6 +11680,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.50.0" @@ -11590,7 +11847,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.58", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c0fbd003e2..1682869b0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ members = [ "common/bandwidth-controller", "common/bin-common", "common/client-core", + "common/client-core/config-types", "common/client-core/surb-storage", "common/client-core/gateways-storage", "common/client-libs/gateway-client", @@ -41,6 +42,7 @@ members = [ "common/cosmwasm-smart-contracts/name-service", "common/cosmwasm-smart-contracts/service-provider-directory", "common/cosmwasm-smart-contracts/vesting-contract", + "common/country-group", "common/credential-storage", "common/credentials", "common/credential-utils", @@ -50,6 +52,7 @@ members = [ "common/execute", "common/exit-policy", "common/http-api-client", + "common/http-api-common", "common/inclusion-probability", "common/ip-packet-requests", "common/ledger", @@ -104,6 +107,7 @@ members = [ "nym-browser-extension/storage", "nym-api/nym-api-requests", "nym-node", + "nym-node/nym-node-http-api", "nym-node/nym-node-requests", "nym-outfox", "nym-validator-rewarder", @@ -118,7 +122,6 @@ members = [ # "wasm/full-nym-wasm", "wasm/mix-fetch", "wasm/node-tester", - "common/nym-metrics", ] default-members = [ @@ -132,6 +135,7 @@ default-members = [ "tools/nymvisor", "explorer-api", "nym-validator-rewarder", + "nym-node" ] exclude = [ @@ -167,6 +171,7 @@ dotenvy = "0.15.6" futures = "0.3.28" generic-array = "0.14.7" getrandom = "0.2.10" +humantime-serde = "1.1.1" hyper = "0.14.27" k256 = "0.13" lazy_static = "1.4.0" diff --git a/clients/native/src/error.rs b/clients/native/src/error.rs index 33daa1247d..23e30121f5 100644 --- a/clients/native/src/error.rs +++ b/clients/native/src/error.rs @@ -23,6 +23,9 @@ pub enum ClientError { #[error("Attempted to start the client in invalid socket mode")] InvalidSocketMode, + #[error(transparent)] + ConfigUpgradeFailure(#[from] nym_client_core::config::ConfigUpgradeFailure), + #[error(transparent)] NymIdError(#[from] NymIdError), } diff --git a/clients/socks5/src/error.rs b/clients/socks5/src/error.rs index de40ccfefa..a2f4d8379a 100644 --- a/clients/socks5/src/error.rs +++ b/clients/socks5/src/error.rs @@ -23,6 +23,9 @@ pub enum Socks5ClientError { #[error(transparent)] ClientCoreError(#[from] ClientCoreError), + #[error(transparent)] + ConfigUpgradeFailure(#[from] nym_client_core::config::ConfigUpgradeFailure), + #[error(transparent)] NymIdError(#[from] NymIdError), } diff --git a/common/bin-common/Cargo.toml b/common/bin-common/Cargo.toml index 738e708737..36ee1785d9 100644 --- a/common/bin-common/Cargo.toml +++ b/common/bin-common/Cargo.toml @@ -8,7 +8,6 @@ license = { workspace = true } repository = { workspace = true } [dependencies] -atty = "0.2" const-str = "0.5.6" clap = { workspace = true, features = ["derive"] } clap_complete = "4.0" diff --git a/common/bin-common/src/logging/mod.rs b/common/bin-common/src/logging/mod.rs index a2f6f36d9e..250fef793f 100644 --- a/common/bin-common/src/logging/mod.rs +++ b/common/bin-common/src/logging/mod.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use serde::{Deserialize, Serialize}; +use std::io::IsTerminal; #[cfg(feature = "tracing")] pub use opentelemetry; @@ -14,7 +15,7 @@ pub use tracing_subscriber; #[cfg(feature = "tracing")] pub use tracing_tree; -#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[derive(Debug, Default, Copy, Clone, Deserialize, PartialEq, Eq, Serialize)] #[serde(deny_unknown_fields)] pub struct LoggingSettings { // well, we need to implement something here at some point... @@ -120,7 +121,7 @@ pub fn banner(crate_name: &str, crate_version: &str) -> String { } pub fn maybe_print_banner(crate_name: &str, crate_version: &str) { - if atty::is(atty::Stream::Stdout) { + if std::io::stdout().is_terminal() { println!("{}", banner(crate_name, crate_version)) } } diff --git a/common/client-core/Cargo.toml b/common/client-core/Cargo.toml index b5c8aa5e2e..464cd9d25a 100644 --- a/common/client-core/Cargo.toml +++ b/common/client-core/Cargo.toml @@ -15,7 +15,7 @@ bs58 = { workspace = true } cfg-if = "1.0.0" clap = { workspace = true, optional = true } futures = { workspace = true } -humantime-serde = "1.0" +humantime-serde = { workspace = true } log = { workspace = true } rand = { version = "0.7.3", features = ["wasm-bindgen"] } serde = { workspace = true, features = ["derive"] } @@ -34,6 +34,7 @@ zeroize = { workspace = true } nym-id = { path = "../nym-id" } nym-bandwidth-controller = { path = "../bandwidth-controller" } nym-config = { path = "../config" } +nym-country-group = { path = "../country-group" } nym-crypto = { path = "../crypto" } nym-explorer-client = { path = "../../explorer-api/explorer-client" } nym-gateway-client = { path = "../client-libs/gateway-client" } @@ -47,6 +48,7 @@ nym-validator-client = { path = "../client-libs/validator-client", default-featu nym-task = { path = "../task" } nym-credential-storage = { path = "../credential-storage" } nym-network-defaults = { path = "../network-defaults" } +nym-client-core-config-types = { path = "./config-types", features = ["disk-persistence"]} nym-client-core-surb-storage = { path = "./surb-storage" } nym-client-core-gateways-storage = { path = "./gateways-storage" } diff --git a/common/client-core/config-types/Cargo.toml b/common/client-core/config-types/Cargo.toml new file mode 100644 index 0000000000..c7d7f7fdf0 --- /dev/null +++ b/common/client-core/config-types/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "nym-client-core-config-types" +version = "0.1.0" +edition = "2021" +license.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +humantime-serde = { workspace = true } +serde = { workspace = true, features = ["derive"] } +thiserror.workspace = true +url = { workspace = true, features = ["serde"] } + +nym-config = { path = "../../config" } + +nym-country-group = { path = "../../country-group" } +nym-pemstore = { path = "../../pemstore", optional = true } + +# those are pulling so many deps T.T +nym-sphinx-params = { path = "../../nymsphinx/params" } +nym-sphinx-addressing = { path = "../../nymsphinx/addressing" } + + +[features] +disk-persistence = ["nym-pemstore"] \ No newline at end of file diff --git a/common/client-core/src/config/disk_persistence/keys_paths.rs b/common/client-core/config-types/src/disk_persistence/mod.rs similarity index 70% rename from common/client-core/src/config/disk_persistence/keys_paths.rs rename to common/client-core/config-types/src/disk_persistence/mod.rs index cdffebe1e2..b9acff307c 100644 --- a/common/client-core/src/config/disk_persistence/keys_paths.rs +++ b/common/client-core/config-types/src/disk_persistence/mod.rs @@ -1,15 +1,53 @@ -// Copyright 2021-2023 - Nym Technologies SA +// Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; +pub mod old; + +// preserve old structure for easier migration +pub use old::{old_v1_1_20_2, old_v1_1_33}; + +pub const DEFAULT_REPLY_SURB_DB_FILENAME: &str = "persistent_reply_store.sqlite"; +pub const DEFAULT_CREDENTIALS_DB_FILENAME: &str = "credentials_database.db"; +pub const DEFAULT_GATEWAYS_DETAILS_DB_FILENAME: &str = "gateways_registrations.sqlite"; + pub const DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME: &str = "private_identity.pem"; pub const DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME: &str = "public_identity.pem"; pub const DEFAULT_PRIVATE_ENCRYPTION_KEY_FILENAME: &str = "private_encryption.pem"; pub const DEFAULT_PUBLIC_ENCRYPTION_KEY_FILENAME: &str = "public_encryption.pem"; pub const DEFAULT_ACK_KEY_FILENAME: &str = "ack_key.pem"; +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct CommonClientPaths { + pub keys: ClientKeysPaths, + + /// Path to the file containing information about gateways used by this client, + /// i.e. details such as their public keys, owner addresses or the network information. + pub gateway_registrations: PathBuf, + + /// Path to the database containing bandwidth credentials of this client. + pub credentials_database: PathBuf, + + /// Path to the persistent store for received reply surbs, unused encryption keys and used sender tags. + pub reply_surb_database: PathBuf, +} + +impl CommonClientPaths { + pub fn new_base>(base_data_directory: P) -> Self { + let base_dir = base_data_directory.as_ref(); + + CommonClientPaths { + credentials_database: base_dir.join(DEFAULT_CREDENTIALS_DB_FILENAME), + reply_surb_database: base_dir.join(DEFAULT_REPLY_SURB_DB_FILENAME), + gateway_registrations: base_dir.join(DEFAULT_GATEWAYS_DETAILS_DB_FILENAME), + keys: ClientKeysPaths::new_base(base_data_directory), + } + } +} + #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] pub struct ClientKeysPaths { /// Path to file containing private identity key. diff --git a/common/client-core/config-types/src/disk_persistence/old/mod.rs b/common/client-core/config-types/src/disk_persistence/old/mod.rs new file mode 100644 index 0000000000..06f3ea2b7f --- /dev/null +++ b/common/client-core/config-types/src/disk_persistence/old/mod.rs @@ -0,0 +1,9 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod v1; +pub mod v2; + +// aliases for backwards compatibility +pub use v1 as old_v1_1_20_2; +pub use v2 as old_v1_1_33; diff --git a/common/client-core/config-types/src/disk_persistence/old/v1.rs b/common/client-core/config-types/src/disk_persistence/old/v1.rs new file mode 100644 index 0000000000..703ed87d7f --- /dev/null +++ b/common/client-core/config-types/src/disk_persistence/old/v1.rs @@ -0,0 +1,37 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::disk_persistence::old::v2::{ + ClientKeysPathsV2, CommonClientPathsV2, DEFAULT_GATEWAY_DETAILS_FILENAME, +}; +use crate::error::ConfigUpgradeFailure; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; + +// aliases for backwards compatibility +pub type CommonClientPathsV1_1_20_2 = CommonClientPathsV1; + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct CommonClientPathsV1 { + pub keys: ClientKeysPathsV2, + pub credentials_database: PathBuf, + pub reply_surb_database: PathBuf, +} + +impl CommonClientPathsV1 { + pub fn upgrade_default(self) -> Result { + let data_dir = self + .reply_surb_database + .parent() + .ok_or_else(|| ConfigUpgradeFailure { + current_version: "1.1.20-2".to_string(), + })?; + Ok(CommonClientPathsV2 { + keys: self.keys, + gateway_details: data_dir.join(DEFAULT_GATEWAY_DETAILS_FILENAME), + credentials_database: self.credentials_database, + reply_surb_database: self.reply_surb_database, + }) + } +} diff --git a/common/client-core/src/config/disk_persistence/old_v1_1_33.rs b/common/client-core/config-types/src/disk_persistence/old/v2.rs similarity index 78% rename from common/client-core/src/config/disk_persistence/old_v1_1_33.rs rename to common/client-core/config-types/src/disk_persistence/old/v2.rs index f453b5e01a..779c243ed1 100644 --- a/common/client-core/src/config/disk_persistence/old_v1_1_33.rs +++ b/common/client-core/config-types/src/disk_persistence/old/v2.rs @@ -1,16 +1,20 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::config::disk_persistence::keys_paths::ClientKeysPaths; -use crate::config::disk_persistence::{CommonClientPaths, DEFAULT_GATEWAYS_DETAILS_DB_FILENAME}; -use crate::error::ClientCoreError; +use crate::disk_persistence::ClientKeysPaths; +use crate::disk_persistence::{CommonClientPaths, DEFAULT_GATEWAYS_DETAILS_DB_FILENAME}; +use crate::error::ConfigUpgradeFailure; use serde::{Deserialize, Serialize}; use std::path::PathBuf; pub const DEFAULT_GATEWAY_DETAILS_FILENAME: &str = "gateway_details.json"; +// aliases for backwards compatibility +pub type CommonClientPathsV1_1_33 = CommonClientPathsV2; +pub type ClientKeysPathsV1_1_33 = ClientKeysPathsV2; + #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] -pub struct ClientKeysPathsV1_1_33 { +pub struct ClientKeysPathsV2 { /// Path to file containing private identity key. pub private_identity_key_file: PathBuf, @@ -32,7 +36,7 @@ pub struct ClientKeysPathsV1_1_33 { pub ack_key_file: PathBuf, } -impl ClientKeysPathsV1_1_33 { +impl ClientKeysPathsV2 { pub fn upgrade(self) -> ClientKeysPaths { ClientKeysPaths { private_identity_key_file: self.private_identity_key_file, @@ -46,8 +50,8 @@ impl ClientKeysPathsV1_1_33 { #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] #[serde(deny_unknown_fields)] -pub struct CommonClientPathsV1_1_33 { - pub keys: ClientKeysPathsV1_1_33, +pub struct CommonClientPathsV2 { + pub keys: ClientKeysPathsV2, /// Path to the file containing information about gateway used by this client, /// i.e. details such as its public key, owner address or the network information. @@ -60,15 +64,16 @@ pub struct CommonClientPathsV1_1_33 { pub reply_surb_database: PathBuf, } -impl CommonClientPathsV1_1_33 { +impl CommonClientPathsV2 { // note that during the upgrade process, the caller will need to extract the key and gateway details // manually and resave them in the new database - pub fn upgrade_default(self) -> Result { - let data_dir = self.gateway_details.parent().ok_or_else(|| { - ClientCoreError::ConfigFileUpgradeFailure { + pub fn upgrade_default(self) -> Result { + let data_dir = self + .gateway_details + .parent() + .ok_or_else(|| ConfigUpgradeFailure { current_version: "1.1.33".to_string(), - } - })?; + })?; Ok(CommonClientPaths { keys: self.keys.upgrade(), diff --git a/common/client-core/config-types/src/error.rs b/common/client-core/config-types/src/error.rs new file mode 100644 index 0000000000..bda078e1fa --- /dev/null +++ b/common/client-core/config-types/src/error.rs @@ -0,0 +1,10 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use thiserror::Error; + +#[derive(Debug, Error)] +#[error("unable to upgrade config file from `{current_version}`")] +pub struct ConfigUpgradeFailure { + pub current_version: String, +} diff --git a/common/client-core/config-types/src/lib.rs b/common/client-core/config-types/src/lib.rs new file mode 100644 index 0000000000..f68bb86c65 --- /dev/null +++ b/common/client-core/config-types/src/lib.rs @@ -0,0 +1,602 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_config::defaults::NymNetworkDetails; +use nym_sphinx_addressing::Recipient; +use nym_sphinx_params::{PacketSize, PacketType}; +use serde::{Deserialize, Serialize}; +use std::time::Duration; +use url::Url; + +#[cfg(feature = "disk-persistence")] +pub mod disk_persistence; +pub mod error; +pub mod old; + +pub use error::ConfigUpgradeFailure; + +// 'DEBUG' +const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 1.5; + +const DEFAULT_ACK_WAIT_ADDITION: Duration = Duration::from_millis(1_500); +const DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(200); +const DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(20); +const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(50); +const DEFAULT_TOPOLOGY_REFRESH_RATE: Duration = Duration::from_secs(5 * 60); // every 5min +const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: Duration = Duration::from_millis(5_000); +const DEFAULT_MAX_STARTUP_GATEWAY_WAITING_PERIOD: Duration = Duration::from_secs(70 * 60); // 70min -> full epoch (1h) + a bit of overhead + +// Set this to a high value for now, so that we don't risk sporadic timeouts that might cause +// bought bandwidth tokens to not have time to be spent; Once we remove the gateway from the +// bandwidth bridging protocol, we can come back to a smaller timeout value +const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5 * 60); + +const DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO: f64 = 0.70; + +// reply-surbs related: + +// define when to request +// clients/client-core/src/client/replies/reply_storage/surb_storage.rs +const DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 10; +const DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 200; + +// define how much to request at once +// clients/client-core/src/client/replies/reply_controller.rs +const DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 10; +const DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 100; + +const DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE: u32 = 500; + +const DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD: Duration = Duration::from_secs(10); +const DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD: Duration = Duration::from_secs(5 * 60); + +// 12 hours +const DEFAULT_MAXIMUM_REPLY_SURB_AGE: Duration = Duration::from_secs(12 * 60 * 60); + +// 24 hours +const DEFAULT_MAXIMUM_REPLY_KEY_AGE: Duration = Duration::from_secs(24 * 60 * 60); + +pub use nym_country_group::CountryGroup; + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Config { + pub client: Client, + + #[serde(default)] + pub debug: DebugConfig, +} + +impl Config { + pub fn new(id: S1, version: S2) -> Self + where + S1: Into, + S2: Into, + { + Config { + client: Client::new_default(id, version), + debug: Default::default(), + } + } + + pub fn from_client_config(client: Client, debug: DebugConfig) -> Self { + Config { client, debug } + } + + pub fn validate(&self) -> bool { + self.debug.validate() + } + + pub fn with_debug_config(mut self, debug: DebugConfig) -> Self { + self.debug = debug; + self + } + + pub fn with_disabled_credentials(mut self, disabled_credentials_mode: bool) -> Self { + self.client.disabled_credentials_mode = disabled_credentials_mode; + self + } + + pub fn with_custom_nyxd(mut self, urls: Vec) -> Self { + self.client.nyxd_urls = urls; + self + } + + pub fn set_custom_nyxd(&mut self, nyxd_urls: Vec) { + self.client.nyxd_urls = nyxd_urls; + } + + pub fn with_custom_nym_apis(mut self, nym_api_urls: Vec) -> Self { + self.client.nym_api_urls = nym_api_urls; + self + } + + pub fn set_custom_nym_apis(&mut self, nym_api_urls: Vec) { + self.client.nym_api_urls = nym_api_urls; + } + + pub fn with_high_default_traffic_volume(mut self, enabled: bool) -> Self { + if enabled { + self.set_high_default_traffic_volume(); + } + self + } + + pub fn with_packet_type(mut self, packet_type: PacketType) -> Self { + self.debug.traffic.packet_type = packet_type; + self + } + + pub fn set_high_default_traffic_volume(&mut self) { + self.debug.traffic.average_packet_delay = Duration::from_millis(10); + // basically don't really send cover messages + self.debug.cover_traffic.loop_cover_traffic_average_delay = + Duration::from_millis(2_000_000); + // 250 "real" messages / s + self.debug.traffic.message_sending_average_delay = Duration::from_millis(4); + } + + pub fn with_disabled_poisson_process(mut self, disabled: bool) -> Self { + if disabled { + self.set_no_poisson_process() + } + self + } + + pub fn set_no_poisson_process(&mut self) { + self.debug.traffic.disable_main_poisson_packet_distribution = true; + } + + pub fn with_disabled_cover_traffic(mut self, disabled: bool) -> Self { + if disabled { + self.set_no_cover_traffic() + } + self + } + + pub fn set_no_cover_traffic(&mut self) { + self.debug.cover_traffic.disable_loop_cover_traffic_stream = true; + self.debug.traffic.disable_main_poisson_packet_distribution = true; + } + + pub fn with_disabled_cover_traffic_with_keepalive(mut self, disabled: bool) -> Self { + if disabled { + self.set_no_cover_traffic_with_keepalive() + } + self + } + pub fn set_no_cover_traffic_with_keepalive(&mut self) { + self.debug.traffic.disable_main_poisson_packet_distribution = true; + self.debug.cover_traffic.loop_cover_traffic_average_delay = Duration::from_secs(5); + } + + pub fn with_disabled_topology_refresh(mut self, disable_topology_refresh: bool) -> Self { + self.debug.topology.disable_refreshing = disable_topology_refresh; + self + } + + pub fn with_topology_structure(mut self, topology_structure: TopologyStructure) -> Self { + self.set_topology_structure(topology_structure); + self + } + + pub fn set_topology_structure(&mut self, topology_structure: TopologyStructure) { + self.debug.topology.topology_structure = topology_structure; + } + + pub fn with_no_per_hop_delays(mut self, no_per_hop_delays: bool) -> Self { + if no_per_hop_delays { + self.set_no_per_hop_delays() + } + self + } + + pub fn set_no_per_hop_delays(&mut self) { + self.debug.traffic.average_packet_delay = Duration::ZERO; + self.debug.acknowledgements.average_ack_delay = Duration::ZERO; + } + + pub fn with_secondary_packet_size(mut self, secondary_packet_size: Option) -> Self { + self.set_secondary_packet_size(secondary_packet_size); + self + } + + pub fn set_secondary_packet_size(&mut self, secondary_packet_size: Option) { + self.debug.traffic.secondary_packet_size = secondary_packet_size; + } + + pub fn set_custom_version(&mut self, version: &str) { + self.client.version = version.to_string(); + } + + pub fn get_id(&self) -> String { + self.client.id.clone() + } + + pub fn get_disabled_credentials_mode(&self) -> bool { + self.client.disabled_credentials_mode + } + + pub fn get_validator_endpoints(&self) -> Vec { + self.client.nyxd_urls.clone() + } + + pub fn get_nym_api_endpoints(&self) -> Vec { + self.client.nym_api_urls.clone() + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +// note: the deny_unknown_fields is VITAL here to allow upgrades from v1.1.20_2 +#[serde(deny_unknown_fields)] +pub struct Client { + /// Version of the client for which this configuration was created. + pub version: String, + + /// ID specifies the human readable ID of this particular client. + pub id: String, + + /// Indicates whether this client is running in a disabled credentials mode, thus attempting + /// to claim bandwidth without presenting bandwidth credentials. + // TODO: this should be moved to `debug.gateway_connection` + #[serde(default)] + pub disabled_credentials_mode: bool, + + /// Addresses to nyxd validators via which the client can communicate with the chain. + #[serde(alias = "validator_urls")] + pub nyxd_urls: Vec, + + /// Addresses to APIs running on validator from which the client gets the view of the network. + #[serde(alias = "validator_api_urls")] + pub nym_api_urls: Vec, +} + +impl Client { + pub fn new_default(id: S1, version: S2) -> Self + where + S1: Into, + S2: Into, + { + let network = NymNetworkDetails::new_mainnet(); + let nyxd_urls = network + .endpoints + .iter() + .map(|validator| validator.nyxd_url()) + .collect(); + let nym_api_urls = network + .endpoints + .iter() + .filter_map(|validator| validator.api_url()) + .collect::>(); + + Client { + version: version.into(), + id: id.into(), + disabled_credentials_mode: true, + nyxd_urls, + nym_api_urls, + } + } + + pub fn new>( + id: S, + version: S, + disabled_credentials_mode: bool, + nyxd_urls: Vec, + nym_api_urls: Vec, + ) -> Self { + Client { + version: version.into(), + id: id.into(), + disabled_credentials_mode, + nyxd_urls, + nym_api_urls, + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default, deny_unknown_fields)] +pub struct Traffic { + /// The parameter of Poisson distribution determining how long, on average, + /// sent packet is going to be delayed at any given mix node. + /// So for a packet going through three mix nodes, on average, it will take three times this value + /// until the packet reaches its destination. + #[serde(with = "humantime_serde")] + pub average_packet_delay: Duration, + + /// The parameter of Poisson distribution determining how long, on average, + /// it is going to take another 'real traffic stream' message to be sent. + /// If no real packets are available and cover traffic is enabled, + /// a loop cover message is sent instead in order to preserve the rate. + #[serde(with = "humantime_serde")] + pub message_sending_average_delay: Duration, + + /// Controls whether the main packet stream constantly produces packets according to the predefined + /// poisson distribution. + pub disable_main_poisson_packet_distribution: bool, + + /// Specifies the packet size used for sent messages. + /// Do not override it unless you understand the consequences of that change. + pub primary_packet_size: PacketSize, + + /// Specifies the optional auxiliary packet size for optimizing message streams. + /// Note that its use decreases overall anonymity. + /// Do not set it it unless you understand the consequences of that change. + pub secondary_packet_size: Option, + + pub packet_type: PacketType, +} + +impl Traffic { + pub fn validate(&self) -> bool { + if let Some(secondary_packet_size) = self.secondary_packet_size { + if secondary_packet_size == PacketSize::AckPacket + || secondary_packet_size == self.primary_packet_size + { + return false; + } + } + true + } +} + +impl Default for Traffic { + fn default() -> Self { + Traffic { + average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY, + message_sending_average_delay: DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY, + disable_main_poisson_packet_distribution: false, + primary_packet_size: PacketSize::RegularPacket, + secondary_packet_size: None, + packet_type: PacketType::Mix, + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default, deny_unknown_fields)] +pub struct CoverTraffic { + /// The parameter of Poisson distribution determining how long, on average, + /// it is going to take for another loop cover traffic message to be sent. + #[serde(with = "humantime_serde")] + pub loop_cover_traffic_average_delay: Duration, + + /// Specifies the ratio of `primary_packet_size` to `secondary_packet_size` used in cover traffic. + /// Only applicable if `secondary_packet_size` is enabled. + pub cover_traffic_primary_size_ratio: f64, + + /// Controls whether the dedicated loop cover traffic stream should be enabled. + /// (and sending packets, on average, every [Self::loop_cover_traffic_average_delay]) + pub disable_loop_cover_traffic_stream: bool, +} + +impl Default for CoverTraffic { + fn default() -> Self { + CoverTraffic { + loop_cover_traffic_average_delay: DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY, + cover_traffic_primary_size_ratio: DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO, + disable_loop_cover_traffic_stream: false, + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default, deny_unknown_fields)] +pub struct GatewayConnection { + /// How long we're willing to wait for a response to a message sent to the gateway, + /// before giving up on it. + #[serde(with = "humantime_serde")] + pub gateway_response_timeout: Duration, +} + +impl Default for GatewayConnection { + fn default() -> Self { + GatewayConnection { + gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT, + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default, deny_unknown_fields)] +pub struct Acknowledgements { + /// The parameter of Poisson distribution determining how long, on average, + /// sent acknowledgement is going to be delayed at any given mix node. + /// So for an ack going through three mix nodes, on average, it will take three times this value + /// until the packet reaches its destination. + #[serde(with = "humantime_serde")] + pub average_ack_delay: Duration, + + /// Value multiplied with the expected round trip time of an acknowledgement packet before + /// it is assumed it was lost and retransmission of the data packet happens. + /// In an ideal network with 0 latency, this value would have been 1. + pub ack_wait_multiplier: f64, + + /// Value added to the expected round trip time of an acknowledgement packet before + /// it is assumed it was lost and retransmission of the data packet happens. + /// In an ideal network with 0 latency, this value would have been 0. + #[serde(with = "humantime_serde")] + pub ack_wait_addition: Duration, +} + +impl Default for Acknowledgements { + fn default() -> Self { + Acknowledgements { + average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY, + ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER, + ack_wait_addition: DEFAULT_ACK_WAIT_ADDITION, + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default, deny_unknown_fields)] +pub struct Topology { + /// The uniform delay every which clients are querying the directory server + /// to try to obtain a compatible network topology to send sphinx packets through. + #[serde(with = "humantime_serde")] + pub topology_refresh_rate: Duration, + + /// During topology refresh, test packets are sent through every single possible network + /// path. This timeout determines waiting period until it is decided that the packet + /// did not reach its destination. + #[serde(with = "humantime_serde")] + pub topology_resolution_timeout: Duration, + + /// Specifies whether the client should not refresh the network topology after obtaining + /// the first valid instance. + /// Supersedes `topology_refresh_rate_ms`. + pub disable_refreshing: bool, + + /// Defines how long the client is going to wait on startup for its gateway to come online, + /// before abandoning the procedure. + #[serde(with = "humantime_serde")] + pub max_startup_gateway_waiting_period: Duration, + + /// Specifies the mixnode topology to be used for sending packets. + pub topology_structure: TopologyStructure, +} + +#[allow(clippy::large_enum_variant)] +#[derive(Default, Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum TopologyStructure { + #[default] + NymApi, + GeoAware(GroupBy), +} + +#[allow(clippy::large_enum_variant)] +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum GroupBy { + CountryGroup(CountryGroup), + NymAddress(Recipient), +} + +impl std::fmt::Display for GroupBy { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + GroupBy::CountryGroup(group) => write!(f, "group: {group}"), + GroupBy::NymAddress(address) => write!(f, "address: {address}"), + } + } +} + +impl Default for Topology { + fn default() -> Self { + Topology { + topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE, + topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT, + disable_refreshing: false, + max_startup_gateway_waiting_period: DEFAULT_MAX_STARTUP_GATEWAY_WAITING_PERIOD, + topology_structure: TopologyStructure::default(), + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default, deny_unknown_fields)] +pub struct ReplySurbs { + /// Defines the minimum number of reply surbs the client wants to keep in its storage at all times. + /// It can only allow to go below that value if its to request additional reply surbs. + pub minimum_reply_surb_storage_threshold: usize, + + /// Defines the maximum number of reply surbs the client wants to keep in its storage at any times. + pub maximum_reply_surb_storage_threshold: usize, + + /// Defines the minimum number of reply surbs the client would request. + pub minimum_reply_surb_request_size: u32, + + /// Defines the maximum number of reply surbs the client would request. + pub maximum_reply_surb_request_size: u32, + + /// Defines the maximum number of reply surbs a remote party is allowed to request from this client at once. + pub maximum_allowed_reply_surb_request_size: u32, + + /// Defines maximum amount of time the client is going to wait for reply surbs before explicitly asking + /// for more even though in theory they wouldn't need to. + #[serde(with = "humantime_serde")] + pub maximum_reply_surb_rerequest_waiting_period: Duration, + + /// Defines maximum amount of time the client is going to wait for reply surbs before + /// deciding it's never going to get them and would drop all pending messages + #[serde(with = "humantime_serde")] + pub maximum_reply_surb_drop_waiting_period: Duration, + + /// Defines maximum amount of time given reply surb is going to be valid for. + /// This is going to be superseded by key rotation once implemented. + #[serde(with = "humantime_serde")] + pub maximum_reply_surb_age: Duration, + + /// Defines maximum amount of time given reply key is going to be valid for. + /// This is going to be superseded by key rotation once implemented. + #[serde(with = "humantime_serde")] + pub maximum_reply_key_age: Duration, + + /// Specifies the number of mixnet hops the packet should go through. If not specified, then + /// the default value is used. + pub surb_mix_hops: Option, +} + +impl Default for ReplySurbs { + fn default() -> Self { + ReplySurbs { + minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD, + maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD, + minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE, + maximum_reply_surb_request_size: DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE, + maximum_allowed_reply_surb_request_size: DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE, + maximum_reply_surb_rerequest_waiting_period: + DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD, + maximum_reply_surb_drop_waiting_period: DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD, + maximum_reply_surb_age: DEFAULT_MAXIMUM_REPLY_SURB_AGE, + maximum_reply_key_age: DEFAULT_MAXIMUM_REPLY_KEY_AGE, + surb_mix_hops: None, + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default, deny_unknown_fields)] +pub struct DebugConfig { + /// Defines all configuration options related to traffic streams. + pub traffic: Traffic, + + /// Defines all configuration options related to cover traffic stream(s). + pub cover_traffic: CoverTraffic, + + /// Defines all configuration options related to the gateway connection. + pub gateway_connection: GatewayConnection, + + /// Defines all configuration options related to acknowledgements, such as delays or wait timeouts. + pub acknowledgements: Acknowledgements, + + /// Defines all configuration options related topology, such as refresh rates or timeouts. + pub topology: Topology, + + /// Defines all configuration options related to reply SURBs. + pub reply_surbs: ReplySurbs, +} + +impl DebugConfig { + pub fn validate(&self) -> bool { + // no other sections have explicit requirements (yet) + self.traffic.validate() + } +} + +// it could be derived, sure, but I'd rather have an explicit implementation in case we had to change +// something manually at some point +#[allow(clippy::derivable_impls)] +impl Default for DebugConfig { + fn default() -> Self { + DebugConfig { + traffic: Default::default(), + cover_traffic: Default::default(), + gateway_connection: Default::default(), + acknowledgements: Default::default(), + topology: Default::default(), + reply_surbs: Default::default(), + } + } +} diff --git a/common/client-core/config-types/src/old/mod.rs b/common/client-core/config-types/src/old/mod.rs new file mode 100644 index 0000000000..80b49b41be --- /dev/null +++ b/common/client-core/config-types/src/old/mod.rs @@ -0,0 +1,15 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod v1; +pub mod v2; +pub mod v3; +pub mod v4; +pub mod v5; + +// aliases for backwards compatibility +pub use v1 as old_config_v1_1_13; +pub use v2 as old_config_v1_1_20; +pub use v3 as old_config_v1_1_20_2; +pub use v4 as old_config_v1_1_30; +pub use v5 as old_config_v1_1_33; diff --git a/common/client-core/src/config/old_config_v1_1_13.rs b/common/client-core/config-types/src/old/v1.rs similarity index 80% rename from common/client-core/src/config/old_config_v1_1_13.rs rename to common/client-core/config-types/src/old/v1.rs index df1246c88b..1c8c2380b3 100644 --- a/common/client-core/src/config/old_config_v1_1_13.rs +++ b/common/client-core/config-types/src/old/v1.rs @@ -1,23 +1,28 @@ -// Copyright 2023 - Nym Technologies SA +// Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::config::old_config_v1_1_20::{ - AcknowledgementsV1_1_20, ClientV1_1_20, ConfigV1_1_20, CoverTrafficV1_1_20, DebugConfigV1_1_20, - GatewayConnectionV1_1_20, LoggingV1_1_20, ReplySurbsV1_1_20, TopologyV1_1_20, TrafficV1_1_20, - DEFAULT_ACK_WAIT_ADDITION, DEFAULT_ACK_WAIT_MULTIPLIER, DEFAULT_AVERAGE_PACKET_DELAY, - DEFAULT_GATEWAY_RESPONSE_TIMEOUT, DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY, - DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE, DEFAULT_MAXIMUM_REPLY_KEY_AGE, - DEFAULT_MAXIMUM_REPLY_SURB_AGE, DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD, - DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE, DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD, +use crate::old::v2::{ + AcknowledgementsV2, ClientV2, ConfigV2, CoverTrafficV2, DebugConfigV2, GatewayConnectionV2, + LoggingV2, ReplySurbsV2, TopologyV2, TrafficV2, DEFAULT_ACK_WAIT_ADDITION, + DEFAULT_ACK_WAIT_MULTIPLIER, DEFAULT_AVERAGE_PACKET_DELAY, DEFAULT_GATEWAY_RESPONSE_TIMEOUT, + DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY, DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE, + DEFAULT_MAXIMUM_REPLY_KEY_AGE, DEFAULT_MAXIMUM_REPLY_SURB_AGE, + DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD, DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE, + DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD, DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD, DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY, DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE, DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD, DEFAULT_TOPOLOGY_REFRESH_RATE, DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT, }; -use nym_sphinx::params::PacketSize; +use nym_sphinx_params::PacketSize; use serde::{Deserialize, Serialize}; use std::marker::PhantomData; use std::time::Duration; +// aliases for backwards compatibility +pub type OldConfigV1_1_13 = ConfigV1; +pub type OldLoggingV1_1_13 = LoggingV1; +pub type OldDebugConfigV1_1_13 = DebugConfigV1; + #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "lowercase")] pub enum ExtendedPacketSize { @@ -38,28 +43,28 @@ impl From for PacketSize { #[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] -pub struct OldConfigV1_1_13 { - pub client: ClientV1_1_20, +pub struct ConfigV1 { + pub client: ClientV2, #[serde(default)] - pub logging: OldLoggingV1_1_13, + pub logging: LoggingV1, #[serde(default)] - pub debug: OldDebugConfigV1_1_13, + pub debug: DebugConfigV1, } #[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq, Serialize)] #[serde(deny_unknown_fields)] -pub struct OldLoggingV1_1_13 {} +pub struct LoggingV1 {} -impl From for LoggingV1_1_20 { - fn from(_value: OldLoggingV1_1_13) -> Self { - LoggingV1_1_20 {} +impl From for LoggingV2 { + fn from(_value: LoggingV1) -> Self { + LoggingV2 {} } } #[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] -pub struct OldDebugConfigV1_1_13 { +pub struct DebugConfigV1 { #[serde(with = "humantime_serde")] pub average_packet_delay: Duration, @@ -114,10 +119,10 @@ pub struct OldDebugConfigV1_1_13 { pub maximum_reply_key_age: Duration, } -impl From for DebugConfigV1_1_20 { - fn from(value: OldDebugConfigV1_1_13) -> Self { - DebugConfigV1_1_20 { - traffic: TrafficV1_1_20 { +impl From for DebugConfigV2 { + fn from(value: DebugConfigV1) -> Self { + DebugConfigV2 { + traffic: TrafficV2 { average_packet_delay: value.average_packet_delay, message_sending_average_delay: value.message_sending_average_delay, disable_main_poisson_packet_distribution: value @@ -125,25 +130,25 @@ impl From for DebugConfigV1_1_20 { primary_packet_size: PacketSize::RegularPacket, secondary_packet_size: value.use_extended_packet_size.map(Into::into), }, - cover_traffic: CoverTrafficV1_1_20 { + cover_traffic: CoverTrafficV2 { loop_cover_traffic_average_delay: value.loop_cover_traffic_average_delay, disable_loop_cover_traffic_stream: value.disable_loop_cover_traffic_stream, - ..CoverTrafficV1_1_20::default() + ..CoverTrafficV2::default() }, - gateway_connection: GatewayConnectionV1_1_20 { + gateway_connection: GatewayConnectionV2 { gateway_response_timeout: value.gateway_response_timeout, }, - acknowledgements: AcknowledgementsV1_1_20 { + acknowledgements: AcknowledgementsV2 { average_ack_delay: value.average_ack_delay, ack_wait_multiplier: value.ack_wait_multiplier, ack_wait_addition: value.ack_wait_addition, }, - topology: TopologyV1_1_20 { + topology: TopologyV2 { topology_refresh_rate: value.topology_refresh_rate, topology_resolution_timeout: value.topology_resolution_timeout, disable_refreshing: false, }, - reply_surbs: ReplySurbsV1_1_20 { + reply_surbs: ReplySurbsV2 { minimum_reply_surb_storage_threshold: value.minimum_reply_surb_storage_threshold, maximum_reply_surb_storage_threshold: value.maximum_reply_surb_storage_threshold, minimum_reply_surb_request_size: value.minimum_reply_surb_request_size, @@ -161,9 +166,9 @@ impl From for DebugConfigV1_1_20 { } } -impl Default for OldDebugConfigV1_1_13 { +impl Default for DebugConfigV1 { fn default() -> Self { - OldDebugConfigV1_1_13 { + DebugConfigV1 { average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY, average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY, ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER, @@ -190,10 +195,10 @@ impl Default for OldDebugConfigV1_1_13 { } } -impl From> for ConfigV1_1_20 { - fn from(value: OldConfigV1_1_13) -> Self { - ConfigV1_1_20 { - client: ClientV1_1_20 { +impl From> for ConfigV2 { + fn from(value: ConfigV1) -> Self { + ConfigV2 { + client: ClientV2 { version: value.client.version, id: value.client.id, disabled_credentials_mode: value.client.disabled_credentials_mode, diff --git a/common/client-core/src/config/old_config_v1_1_20.rs b/common/client-core/config-types/src/old/v2.rs similarity index 78% rename from common/client-core/src/config/old_config_v1_1_20.rs rename to common/client-core/config-types/src/old/v2.rs index b3a4a189d8..ff09de3f2f 100644 --- a/common/client-core/src/config/old_config_v1_1_20.rs +++ b/common/client-core/config-types/src/old/v2.rs @@ -1,12 +1,11 @@ -// Copyright 2023 - Nym Technologies SA +// Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::config::old_config_v1_1_20_2::{ - AcknowledgementsV1_1_20_2, CoverTrafficV1_1_20_2, DebugConfigV1_1_20_2, - GatewayConnectionV1_1_20_2, GatewayEndpointConfigV1_1_20_2, ReplySurbsV1_1_20_2, - TopologyV1_1_20_2, TrafficV1_1_20_2, +use crate::old::v3::{ + AcknowledgementsV3, CoverTrafficV3, DebugConfigV3, GatewayConnectionV3, + GatewayEndpointConfigV3, ReplySurbsV3, TopologyV3, TrafficV3, }; -use nym_sphinx::params::{PacketSize, PacketType}; +use nym_sphinx_params::{PacketSize, PacketType}; use serde::{Deserialize, Serialize}; use std::marker::PhantomData; use std::path::PathBuf; @@ -54,27 +53,41 @@ pub(crate) const DEFAULT_MAXIMUM_REPLY_SURB_AGE: Duration = Duration::from_secs( // 24 hours pub(crate) const DEFAULT_MAXIMUM_REPLY_KEY_AGE: Duration = Duration::from_secs(24 * 60 * 60); +// aliases for backwards compatibility +pub type ConfigV1_1_20 = ConfigV2; +pub type ClientV1_1_20 = ClientV2; +pub type LoggingV1_1_20 = LoggingV2; +pub type DebugConfigV1_1_20 = DebugConfigV2; +pub type GatewayEndpointConfigV1_1_20 = GatewayEndpointConfigV2; + +pub type TrafficV1_1_20 = TrafficV2; +pub type CoverTrafficV1_1_20 = CoverTrafficV2; +pub type GatewayConnectionV1_1_20 = GatewayConnectionV2; +pub type AcknowledgementsV1_1_20 = AcknowledgementsV2; +pub type TopologyV1_1_20 = TopologyV2; +pub type ReplySurbsV1_1_20 = ReplySurbsV2; + #[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] -pub struct ConfigV1_1_20 { - pub client: ClientV1_1_20, +pub struct ConfigV2 { + pub client: ClientV2, #[serde(default)] - pub logging: LoggingV1_1_20, + pub logging: LoggingV2, #[serde(default)] - pub debug: DebugConfigV1_1_20, + pub debug: DebugConfigV2, } #[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)] -pub struct GatewayEndpointConfigV1_1_20 { +pub struct GatewayEndpointConfigV2 { pub gateway_id: String, pub gateway_owner: String, pub gateway_listener: String, } -impl From for GatewayEndpointConfigV1_1_20_2 { - fn from(value: GatewayEndpointConfigV1_1_20) -> Self { - GatewayEndpointConfigV1_1_20_2 { +impl From for GatewayEndpointConfigV3 { + fn from(value: GatewayEndpointConfigV2) -> Self { + GatewayEndpointConfigV3 { gateway_id: value.gateway_id, gateway_owner: value.gateway_owner, gateway_listener: value.gateway_listener, @@ -83,7 +96,7 @@ impl From for GatewayEndpointConfigV1_1_20_2 { } #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] -pub struct ClientV1_1_20 { +pub struct ClientV2 { pub version: String, pub id: String, #[serde(default)] @@ -98,7 +111,7 @@ pub struct ClientV1_1_20 { pub public_encryption_key_file: PathBuf, pub gateway_shared_key_file: PathBuf, pub ack_key_file: PathBuf, - pub gateway_endpoint: GatewayEndpointConfigV1_1_20, + pub gateway_endpoint: GatewayEndpointConfigV2, pub database_path: PathBuf, #[serde(default)] pub reply_surb_database_path: PathBuf, @@ -110,11 +123,11 @@ pub struct ClientV1_1_20 { #[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq, Serialize)] #[serde(deny_unknown_fields)] -pub struct LoggingV1_1_20 {} +pub struct LoggingV2 {} #[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default)] -pub struct TrafficV1_1_20 { +pub struct TrafficV2 { #[serde(with = "humantime_serde")] pub average_packet_delay: Duration, #[serde(with = "humantime_serde")] @@ -124,9 +137,9 @@ pub struct TrafficV1_1_20 { pub secondary_packet_size: Option, } -impl From for TrafficV1_1_20_2 { - fn from(value: TrafficV1_1_20) -> Self { - TrafficV1_1_20_2 { +impl From for TrafficV3 { + fn from(value: TrafficV2) -> Self { + TrafficV3 { average_packet_delay: value.average_packet_delay, message_sending_average_delay: value.message_sending_average_delay, disable_main_poisson_packet_distribution: value @@ -138,9 +151,9 @@ impl From for TrafficV1_1_20_2 { } } -impl Default for TrafficV1_1_20 { +impl Default for TrafficV2 { fn default() -> Self { - TrafficV1_1_20 { + TrafficV2 { average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY, message_sending_average_delay: DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY, disable_main_poisson_packet_distribution: false, @@ -152,16 +165,16 @@ impl Default for TrafficV1_1_20 { #[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] -pub struct CoverTrafficV1_1_20 { +pub struct CoverTrafficV2 { #[serde(with = "humantime_serde")] pub loop_cover_traffic_average_delay: Duration, pub cover_traffic_primary_size_ratio: f64, pub disable_loop_cover_traffic_stream: bool, } -impl From for CoverTrafficV1_1_20_2 { - fn from(value: CoverTrafficV1_1_20) -> Self { - CoverTrafficV1_1_20_2 { +impl From for CoverTrafficV3 { + fn from(value: CoverTrafficV2) -> Self { + CoverTrafficV3 { loop_cover_traffic_average_delay: value.loop_cover_traffic_average_delay, cover_traffic_primary_size_ratio: value.cover_traffic_primary_size_ratio, disable_loop_cover_traffic_stream: value.disable_loop_cover_traffic_stream, @@ -169,9 +182,9 @@ impl From for CoverTrafficV1_1_20_2 { } } -impl Default for CoverTrafficV1_1_20 { +impl Default for CoverTrafficV2 { fn default() -> Self { - CoverTrafficV1_1_20 { + CoverTrafficV2 { loop_cover_traffic_average_delay: DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY, cover_traffic_primary_size_ratio: DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO, disable_loop_cover_traffic_stream: false, @@ -181,22 +194,22 @@ impl Default for CoverTrafficV1_1_20 { #[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] -pub struct GatewayConnectionV1_1_20 { +pub struct GatewayConnectionV2 { #[serde(with = "humantime_serde")] pub gateway_response_timeout: Duration, } -impl From for GatewayConnectionV1_1_20_2 { - fn from(value: GatewayConnectionV1_1_20) -> Self { - GatewayConnectionV1_1_20_2 { +impl From for GatewayConnectionV3 { + fn from(value: GatewayConnectionV2) -> Self { + GatewayConnectionV3 { gateway_response_timeout: value.gateway_response_timeout, } } } -impl Default for GatewayConnectionV1_1_20 { +impl Default for GatewayConnectionV2 { fn default() -> Self { - GatewayConnectionV1_1_20 { + GatewayConnectionV2 { gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT, } } @@ -204,7 +217,7 @@ impl Default for GatewayConnectionV1_1_20 { #[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] -pub struct AcknowledgementsV1_1_20 { +pub struct AcknowledgementsV2 { #[serde(with = "humantime_serde")] pub average_ack_delay: Duration, pub ack_wait_multiplier: f64, @@ -212,9 +225,9 @@ pub struct AcknowledgementsV1_1_20 { pub ack_wait_addition: Duration, } -impl From for AcknowledgementsV1_1_20_2 { - fn from(value: AcknowledgementsV1_1_20) -> Self { - AcknowledgementsV1_1_20_2 { +impl From for AcknowledgementsV3 { + fn from(value: AcknowledgementsV2) -> Self { + AcknowledgementsV3 { average_ack_delay: value.average_ack_delay, ack_wait_multiplier: value.ack_wait_multiplier, ack_wait_addition: value.ack_wait_addition, @@ -222,9 +235,9 @@ impl From for AcknowledgementsV1_1_20_2 { } } -impl Default for AcknowledgementsV1_1_20 { +impl Default for AcknowledgementsV2 { fn default() -> Self { - AcknowledgementsV1_1_20 { + AcknowledgementsV2 { average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY, ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER, ack_wait_addition: DEFAULT_ACK_WAIT_ADDITION, @@ -234,7 +247,7 @@ impl Default for AcknowledgementsV1_1_20 { #[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] -pub struct TopologyV1_1_20 { +pub struct TopologyV2 { #[serde(with = "humantime_serde")] pub topology_refresh_rate: Duration, #[serde(with = "humantime_serde")] @@ -242,9 +255,9 @@ pub struct TopologyV1_1_20 { pub disable_refreshing: bool, } -impl From for TopologyV1_1_20_2 { - fn from(value: TopologyV1_1_20) -> Self { - TopologyV1_1_20_2 { +impl From for TopologyV3 { + fn from(value: TopologyV2) -> Self { + TopologyV3 { topology_refresh_rate: value.topology_refresh_rate, topology_resolution_timeout: value.topology_resolution_timeout, disable_refreshing: value.disable_refreshing, @@ -252,9 +265,9 @@ impl From for TopologyV1_1_20_2 { } } -impl Default for TopologyV1_1_20 { +impl Default for TopologyV2 { fn default() -> Self { - TopologyV1_1_20 { + TopologyV2 { topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE, topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT, disable_refreshing: false, @@ -264,7 +277,7 @@ impl Default for TopologyV1_1_20 { #[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] -pub struct ReplySurbsV1_1_20 { +pub struct ReplySurbsV2 { pub minimum_reply_surb_storage_threshold: usize, pub maximum_reply_surb_storage_threshold: usize, pub minimum_reply_surb_request_size: u32, @@ -280,9 +293,9 @@ pub struct ReplySurbsV1_1_20 { pub maximum_reply_key_age: Duration, } -impl From for ReplySurbsV1_1_20_2 { - fn from(value: ReplySurbsV1_1_20) -> Self { - ReplySurbsV1_1_20_2 { +impl From for ReplySurbsV3 { + fn from(value: ReplySurbsV2) -> Self { + ReplySurbsV3 { minimum_reply_surb_storage_threshold: value.minimum_reply_surb_storage_threshold, maximum_reply_surb_storage_threshold: value.maximum_reply_surb_storage_threshold, minimum_reply_surb_request_size: value.minimum_reply_surb_request_size, @@ -297,9 +310,9 @@ impl From for ReplySurbsV1_1_20_2 { } } -impl Default for ReplySurbsV1_1_20 { +impl Default for ReplySurbsV2 { fn default() -> Self { - ReplySurbsV1_1_20 { + ReplySurbsV2 { minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD, maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD, minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE, @@ -316,18 +329,18 @@ impl Default for ReplySurbsV1_1_20 { #[derive(Debug, Default, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] -pub struct DebugConfigV1_1_20 { - pub traffic: TrafficV1_1_20, - pub cover_traffic: CoverTrafficV1_1_20, - pub gateway_connection: GatewayConnectionV1_1_20, - pub acknowledgements: AcknowledgementsV1_1_20, - pub topology: TopologyV1_1_20, - pub reply_surbs: ReplySurbsV1_1_20, +pub struct DebugConfigV2 { + pub traffic: TrafficV2, + pub cover_traffic: CoverTrafficV2, + pub gateway_connection: GatewayConnectionV2, + pub acknowledgements: AcknowledgementsV2, + pub topology: TopologyV2, + pub reply_surbs: ReplySurbsV2, } -impl From for DebugConfigV1_1_20_2 { - fn from(value: DebugConfigV1_1_20) -> Self { - DebugConfigV1_1_20_2 { +impl From for DebugConfigV3 { + fn from(value: DebugConfigV2) -> Self { + DebugConfigV3 { traffic: value.traffic.into(), cover_traffic: value.cover_traffic.into(), gateway_connection: value.gateway_connection.into(), diff --git a/common/client-core/src/config/old_config_v1_1_20_2.rs b/common/client-core/config-types/src/old/v3.rs similarity index 77% rename from common/client-core/src/config/old_config_v1_1_20_2.rs rename to common/client-core/config-types/src/old/v3.rs index 5fe039fc82..34d40cf913 100644 --- a/common/client-core/src/config/old_config_v1_1_20_2.rs +++ b/common/client-core/config-types/src/old/v3.rs @@ -1,12 +1,12 @@ -// Copyright 2023 - Nym Technologies SA +// Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::config::old_config_v1_1_30::{ - AcknowledgementsV1_1_30, ClientV1_1_30, ConfigV1_1_30, CoverTrafficV1_1_30, DebugConfigV1_1_30, - GatewayConnectionV1_1_30, ReplySurbsV1_1_30, TopologyV1_1_30, TrafficV1_1_30, +use crate::old::v4::{ + AcknowledgementsV4, ClientV4, ConfigV4, CoverTrafficV4, DebugConfigV4, GatewayConnectionV4, + ReplySurbsV4, TopologyV4, TrafficV4, }; -use crate::config::old_config_v1_1_33::OldGatewayEndpointConfigV1_1_33; -use nym_sphinx::params::{PacketSize, PacketType}; +use crate::old::v5::GatewayEndpointConfigV5; +use nym_sphinx_params::{PacketSize, PacketType}; use serde::{Deserialize, Serialize}; use std::time::Duration; use url::Url; @@ -50,18 +50,31 @@ const DEFAULT_MAXIMUM_REPLY_SURB_AGE: Duration = Duration::from_secs(12 * 60 * 6 // 24 hours const DEFAULT_MAXIMUM_REPLY_KEY_AGE: Duration = Duration::from_secs(24 * 60 * 60); +// aliases for backwards compatibility +pub type ConfigV1_1_20_2 = ConfigV3; +pub type ClientV1_1_20_2 = ClientV3; +pub type DebugConfigV1_1_20_2 = DebugConfigV3; +pub type GatewayEndpointConfigV1_1_20_2 = GatewayEndpointConfigV3; + +pub type TrafficV1_1_20_2 = TrafficV3; +pub type CoverTrafficV1_1_20_2 = CoverTrafficV3; +pub type GatewayConnectionV1_1_20_2 = GatewayConnectionV3; +pub type AcknowledgementsV1_1_20_2 = AcknowledgementsV3; +pub type TopologyV1_1_20_2 = TopologyV3; +pub type ReplySurbsV1_1_20_2 = ReplySurbsV3; + #[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] -pub struct ConfigV1_1_20_2 { - pub client: ClientV1_1_20_2, +pub struct ConfigV3 { + pub client: ClientV3, #[serde(default)] - pub debug: DebugConfigV1_1_20_2, + pub debug: DebugConfigV3, } -impl From for ConfigV1_1_30 { - fn from(value: ConfigV1_1_20_2) -> Self { - ConfigV1_1_30 { +impl From for ConfigV4 { + fn from(value: ConfigV3) -> Self { + ConfigV4 { client: value.client.into(), debug: value.debug.into(), } @@ -69,7 +82,7 @@ impl From for ConfigV1_1_30 { } #[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)] -pub struct GatewayEndpointConfigV1_1_20_2 { +pub struct GatewayEndpointConfigV3 { /// gateway_id specifies ID of the gateway to which the client should send messages. /// If initially omitted, a random gateway will be chosen from the available topology. pub gateway_id: String, @@ -81,9 +94,9 @@ pub struct GatewayEndpointConfigV1_1_20_2 { pub gateway_listener: String, } -impl From for OldGatewayEndpointConfigV1_1_33 { - fn from(value: GatewayEndpointConfigV1_1_20_2) -> Self { - OldGatewayEndpointConfigV1_1_33 { +impl From for GatewayEndpointConfigV5 { + fn from(value: GatewayEndpointConfigV3) -> Self { + GatewayEndpointConfigV5 { gateway_id: value.gateway_id, gateway_owner: value.gateway_owner, gateway_listener: value.gateway_listener, @@ -92,7 +105,7 @@ impl From for OldGatewayEndpointConfigV1_1_33 { } #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] -pub struct ClientV1_1_20_2 { +pub struct ClientV3 { pub version: String, pub id: String, @@ -105,12 +118,12 @@ pub struct ClientV1_1_20_2 { #[serde(alias = "validator_api_urls")] pub nym_api_urls: Vec, - pub gateway_endpoint: GatewayEndpointConfigV1_1_20_2, + pub gateway_endpoint: GatewayEndpointConfigV3, } -impl From for ClientV1_1_30 { - fn from(value: ClientV1_1_20_2) -> Self { - ClientV1_1_30 { +impl From for ClientV4 { + fn from(value: ClientV3) -> Self { + ClientV4 { version: value.version, id: value.id, disabled_credentials_mode: value.disabled_credentials_mode, @@ -122,7 +135,7 @@ impl From for ClientV1_1_30 { #[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default)] -pub struct TrafficV1_1_20_2 { +pub struct TrafficV3 { #[serde(with = "humantime_serde")] pub average_packet_delay: Duration, #[serde(with = "humantime_serde")] @@ -133,9 +146,9 @@ pub struct TrafficV1_1_20_2 { pub packet_type: PacketType, } -impl From for TrafficV1_1_30 { - fn from(value: TrafficV1_1_20_2) -> Self { - TrafficV1_1_30 { +impl From for TrafficV4 { + fn from(value: TrafficV3) -> Self { + TrafficV4 { average_packet_delay: value.average_packet_delay, message_sending_average_delay: value.message_sending_average_delay, disable_main_poisson_packet_distribution: value @@ -147,9 +160,9 @@ impl From for TrafficV1_1_30 { } } -impl Default for TrafficV1_1_20_2 { +impl Default for TrafficV3 { fn default() -> Self { - TrafficV1_1_20_2 { + TrafficV3 { average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY, message_sending_average_delay: DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY, disable_main_poisson_packet_distribution: false, @@ -162,16 +175,16 @@ impl Default for TrafficV1_1_20_2 { #[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] -pub struct CoverTrafficV1_1_20_2 { +pub struct CoverTrafficV3 { #[serde(with = "humantime_serde")] pub loop_cover_traffic_average_delay: Duration, pub cover_traffic_primary_size_ratio: f64, pub disable_loop_cover_traffic_stream: bool, } -impl From for CoverTrafficV1_1_30 { - fn from(value: CoverTrafficV1_1_20_2) -> Self { - CoverTrafficV1_1_30 { +impl From for CoverTrafficV4 { + fn from(value: CoverTrafficV3) -> Self { + CoverTrafficV4 { loop_cover_traffic_average_delay: value.loop_cover_traffic_average_delay, cover_traffic_primary_size_ratio: value.cover_traffic_primary_size_ratio, disable_loop_cover_traffic_stream: value.disable_loop_cover_traffic_stream, @@ -179,9 +192,9 @@ impl From for CoverTrafficV1_1_30 { } } -impl Default for CoverTrafficV1_1_20_2 { +impl Default for CoverTrafficV3 { fn default() -> Self { - CoverTrafficV1_1_20_2 { + CoverTrafficV3 { loop_cover_traffic_average_delay: DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY, cover_traffic_primary_size_ratio: DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO, disable_loop_cover_traffic_stream: false, @@ -191,22 +204,22 @@ impl Default for CoverTrafficV1_1_20_2 { #[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] -pub struct GatewayConnectionV1_1_20_2 { +pub struct GatewayConnectionV3 { #[serde(with = "humantime_serde")] pub gateway_response_timeout: Duration, } -impl From for GatewayConnectionV1_1_30 { - fn from(value: GatewayConnectionV1_1_20_2) -> Self { - GatewayConnectionV1_1_30 { +impl From for GatewayConnectionV4 { + fn from(value: GatewayConnectionV3) -> Self { + GatewayConnectionV4 { gateway_response_timeout: value.gateway_response_timeout, } } } -impl Default for GatewayConnectionV1_1_20_2 { +impl Default for GatewayConnectionV3 { fn default() -> Self { - GatewayConnectionV1_1_20_2 { + GatewayConnectionV3 { gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT, } } @@ -214,7 +227,7 @@ impl Default for GatewayConnectionV1_1_20_2 { #[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] -pub struct AcknowledgementsV1_1_20_2 { +pub struct AcknowledgementsV3 { #[serde(with = "humantime_serde")] pub average_ack_delay: Duration, pub ack_wait_multiplier: f64, @@ -222,9 +235,9 @@ pub struct AcknowledgementsV1_1_20_2 { pub ack_wait_addition: Duration, } -impl From for AcknowledgementsV1_1_30 { - fn from(value: AcknowledgementsV1_1_20_2) -> Self { - AcknowledgementsV1_1_30 { +impl From for AcknowledgementsV4 { + fn from(value: AcknowledgementsV3) -> Self { + AcknowledgementsV4 { average_ack_delay: value.average_ack_delay, ack_wait_multiplier: value.ack_wait_multiplier, ack_wait_addition: value.ack_wait_addition, @@ -232,9 +245,9 @@ impl From for AcknowledgementsV1_1_30 { } } -impl Default for AcknowledgementsV1_1_20_2 { +impl Default for AcknowledgementsV3 { fn default() -> Self { - AcknowledgementsV1_1_20_2 { + AcknowledgementsV3 { average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY, ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER, ack_wait_addition: DEFAULT_ACK_WAIT_ADDITION, @@ -244,7 +257,7 @@ impl Default for AcknowledgementsV1_1_20_2 { #[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] -pub struct TopologyV1_1_20_2 { +pub struct TopologyV3 { #[serde(with = "humantime_serde")] pub topology_refresh_rate: Duration, #[serde(with = "humantime_serde")] @@ -252,9 +265,9 @@ pub struct TopologyV1_1_20_2 { pub disable_refreshing: bool, } -impl Default for TopologyV1_1_20_2 { +impl Default for TopologyV3 { fn default() -> Self { - TopologyV1_1_20_2 { + TopologyV3 { topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE, topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT, disable_refreshing: false, @@ -262,9 +275,9 @@ impl Default for TopologyV1_1_20_2 { } } -impl From for TopologyV1_1_30 { - fn from(value: TopologyV1_1_20_2) -> Self { - TopologyV1_1_30 { +impl From for TopologyV4 { + fn from(value: TopologyV3) -> Self { + TopologyV4 { topology_refresh_rate: value.topology_refresh_rate, topology_resolution_timeout: value.topology_resolution_timeout, disable_refreshing: value.disable_refreshing, @@ -275,7 +288,7 @@ impl From for TopologyV1_1_30 { #[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] -pub struct ReplySurbsV1_1_20_2 { +pub struct ReplySurbsV3 { pub minimum_reply_surb_storage_threshold: usize, pub maximum_reply_surb_storage_threshold: usize, pub minimum_reply_surb_request_size: u32, @@ -291,9 +304,9 @@ pub struct ReplySurbsV1_1_20_2 { pub maximum_reply_key_age: Duration, } -impl Default for ReplySurbsV1_1_20_2 { +impl Default for ReplySurbsV3 { fn default() -> Self { - ReplySurbsV1_1_20_2 { + ReplySurbsV3 { minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD, maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD, minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE, @@ -308,9 +321,9 @@ impl Default for ReplySurbsV1_1_20_2 { } } -impl From for ReplySurbsV1_1_30 { - fn from(value: ReplySurbsV1_1_20_2) -> Self { - ReplySurbsV1_1_30 { +impl From for ReplySurbsV4 { + fn from(value: ReplySurbsV3) -> Self { + ReplySurbsV4 { minimum_reply_surb_storage_threshold: value.minimum_reply_surb_storage_threshold, maximum_reply_surb_storage_threshold: value.maximum_reply_surb_storage_threshold, minimum_reply_surb_request_size: value.minimum_reply_surb_request_size, @@ -327,18 +340,18 @@ impl From for ReplySurbsV1_1_30 { #[derive(Debug, Default, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] -pub struct DebugConfigV1_1_20_2 { - pub traffic: TrafficV1_1_20_2, - pub cover_traffic: CoverTrafficV1_1_20_2, - pub gateway_connection: GatewayConnectionV1_1_20_2, - pub acknowledgements: AcknowledgementsV1_1_20_2, - pub topology: TopologyV1_1_20_2, - pub reply_surbs: ReplySurbsV1_1_20_2, +pub struct DebugConfigV3 { + pub traffic: TrafficV3, + pub cover_traffic: CoverTrafficV3, + pub gateway_connection: GatewayConnectionV3, + pub acknowledgements: AcknowledgementsV3, + pub topology: TopologyV3, + pub reply_surbs: ReplySurbsV3, } -impl From for DebugConfigV1_1_30 { - fn from(value: DebugConfigV1_1_20_2) -> Self { - DebugConfigV1_1_30 { +impl From for DebugConfigV4 { + fn from(value: DebugConfigV3) -> Self { + DebugConfigV4 { traffic: value.traffic.into(), cover_traffic: value.cover_traffic.into(), gateway_connection: value.gateway_connection.into(), diff --git a/common/client-core/src/config/old_config_v1_1_30.rs b/common/client-core/config-types/src/old/v4.rs similarity index 85% rename from common/client-core/src/config/old_config_v1_1_30.rs rename to common/client-core/config-types/src/old/v4.rs index 17aeba6bfd..d293709c0c 100644 --- a/common/client-core/src/config/old_config_v1_1_30.rs +++ b/common/client-core/config-types/src/old/v4.rs @@ -1,16 +1,13 @@ -// Copyright 2023 - Nym Technologies SA +// Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::client::topology_control::geo_aware_provider::CountryGroup; -use crate::config::old_config_v1_1_33::{ - AcknowledgementsV1_1_33, ClientV1_1_33, ConfigV1_1_33, CoverTrafficV1_1_33, DebugConfigV1_1_33, - GatewayConnectionV1_1_33, GroupByV1_1_33, ReplySurbsV1_1_33, TopologyStructureV1_1_33, - TopologyV1_1_33, TrafficV1_1_33, -}; -use nym_sphinx::{ - addressing::clients::Recipient, - params::{PacketSize, PacketType}, +use crate::old::v5::{ + AcknowledgementsV5, ClientV5, ConfigV5, CoverTrafficV5, DebugConfigV5, GatewayConnectionV5, + GroupByV5, ReplySurbsV5, TopologyStructureV5, TopologyV5, TrafficV5, }; +use crate::CountryGroup; +use nym_sphinx_addressing::Recipient; +use nym_sphinx_params::{PacketSize, PacketType}; use serde::{Deserialize, Serialize}; use std::time::Duration; use url::Url; @@ -56,27 +53,39 @@ const DEFAULT_MAXIMUM_REPLY_SURB_AGE: Duration = Duration::from_secs(12 * 60 * 6 // 24 hours const DEFAULT_MAXIMUM_REPLY_KEY_AGE: Duration = Duration::from_secs(24 * 60 * 60); +// aliases for backwards compatibility +pub type ConfigV1_1_30 = ConfigV4; +pub type ClientV1_1_30 = ClientV4; +pub type DebugConfigV1_1_30 = DebugConfigV4; + +pub type TrafficV1_1_30 = TrafficV4; +pub type CoverTrafficV1_1_30 = CoverTrafficV4; +pub type GatewayConnectionV1_1_30 = GatewayConnectionV4; +pub type AcknowledgementsV1_1_30 = AcknowledgementsV4; +pub type TopologyV1_1_30 = TopologyV4; +pub type ReplySurbsV1_1_30 = ReplySurbsV4; + #[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] -pub struct ConfigV1_1_30 { - pub client: ClientV1_1_30, +pub struct ConfigV4 { + pub client: ClientV4, #[serde(default)] - pub debug: DebugConfigV1_1_30, + pub debug: DebugConfigV4, } -impl From for ConfigV1_1_33 { - fn from(value: ConfigV1_1_30) -> Self { - ConfigV1_1_33 { - client: ClientV1_1_33 { +impl From for ConfigV5 { + fn from(value: ConfigV4) -> Self { + ConfigV5 { + client: ClientV5 { version: value.client.version, id: value.client.id, disabled_credentials_mode: value.client.disabled_credentials_mode, nyxd_urls: value.client.nyxd_urls, nym_api_urls: value.client.nym_api_urls, }, - debug: DebugConfigV1_1_33 { - traffic: TrafficV1_1_33 { + debug: DebugConfigV5 { + traffic: TrafficV5 { average_packet_delay: value.debug.traffic.average_packet_delay, message_sending_average_delay: value .debug @@ -90,7 +99,7 @@ impl From for ConfigV1_1_33 { secondary_packet_size: value.debug.traffic.secondary_packet_size, packet_type: value.debug.traffic.packet_type, }, - cover_traffic: CoverTrafficV1_1_33 { + cover_traffic: CoverTrafficV5 { loop_cover_traffic_average_delay: value .debug .cover_traffic @@ -104,18 +113,18 @@ impl From for ConfigV1_1_33 { .cover_traffic .disable_loop_cover_traffic_stream, }, - gateway_connection: GatewayConnectionV1_1_33 { + gateway_connection: GatewayConnectionV5 { gateway_response_timeout: value .debug .gateway_connection .gateway_response_timeout, }, - acknowledgements: AcknowledgementsV1_1_33 { + acknowledgements: AcknowledgementsV5 { average_ack_delay: value.debug.acknowledgements.average_ack_delay, ack_wait_multiplier: value.debug.acknowledgements.ack_wait_multiplier, ack_wait_addition: value.debug.acknowledgements.ack_wait_addition, }, - topology: TopologyV1_1_33 { + topology: TopologyV5 { topology_refresh_rate: value.debug.topology.topology_refresh_rate, topology_resolution_timeout: value.debug.topology.topology_resolution_timeout, disable_refreshing: value.debug.topology.disable_refreshing, @@ -125,7 +134,7 @@ impl From for ConfigV1_1_33 { .max_startup_gateway_waiting_period, topology_structure: value.debug.topology.topology_structure.into(), }, - reply_surbs: ReplySurbsV1_1_33 { + reply_surbs: ReplySurbsV5 { minimum_reply_surb_storage_threshold: value .debug .reply_surbs @@ -169,7 +178,7 @@ impl From for ConfigV1_1_33 { #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] // note: the deny_unknown_fields is VITAL here to allow upgrades from v1.1.20_2 #[serde(deny_unknown_fields)] -pub struct ClientV1_1_30 { +pub struct ClientV4 { /// Version of the client for which this configuration was created. pub version: String, @@ -193,7 +202,7 @@ pub struct ClientV1_1_30 { #[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] -pub struct TrafficV1_1_30 { +pub struct TrafficV4 { /// The parameter of Poisson distribution determining how long, on average, /// sent packet is going to be delayed at any given mix node. /// So for a packet going through three mix nodes, on average, it will take three times this value @@ -224,9 +233,9 @@ pub struct TrafficV1_1_30 { pub packet_type: PacketType, } -impl Default for TrafficV1_1_30 { +impl Default for TrafficV4 { fn default() -> Self { - TrafficV1_1_30 { + TrafficV4 { average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY, message_sending_average_delay: DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY, disable_main_poisson_packet_distribution: false, @@ -239,7 +248,7 @@ impl Default for TrafficV1_1_30 { #[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] -pub struct CoverTrafficV1_1_30 { +pub struct CoverTrafficV4 { /// The parameter of Poisson distribution determining how long, on average, /// it is going to take for another loop cover traffic message to be sent. #[serde(with = "humantime_serde")] @@ -254,9 +263,9 @@ pub struct CoverTrafficV1_1_30 { pub disable_loop_cover_traffic_stream: bool, } -impl Default for CoverTrafficV1_1_30 { +impl Default for CoverTrafficV4 { fn default() -> Self { - CoverTrafficV1_1_30 { + CoverTrafficV4 { loop_cover_traffic_average_delay: DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY, cover_traffic_primary_size_ratio: DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO, disable_loop_cover_traffic_stream: false, @@ -266,16 +275,16 @@ impl Default for CoverTrafficV1_1_30 { #[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] -pub struct GatewayConnectionV1_1_30 { +pub struct GatewayConnectionV4 { /// How long we're willing to wait for a response to a message sent to the gateway, /// before giving up on it. #[serde(with = "humantime_serde")] pub gateway_response_timeout: Duration, } -impl Default for GatewayConnectionV1_1_30 { +impl Default for GatewayConnectionV4 { fn default() -> Self { - GatewayConnectionV1_1_30 { + GatewayConnectionV4 { gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT, } } @@ -283,7 +292,7 @@ impl Default for GatewayConnectionV1_1_30 { #[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] -pub struct AcknowledgementsV1_1_30 { +pub struct AcknowledgementsV4 { /// The parameter of Poisson distribution determining how long, on average, /// sent acknowledgement is going to be delayed at any given mix node. /// So for an ack going through three mix nodes, on average, it will take three times this value @@ -303,9 +312,9 @@ pub struct AcknowledgementsV1_1_30 { pub ack_wait_addition: Duration, } -impl Default for AcknowledgementsV1_1_30 { +impl Default for AcknowledgementsV4 { fn default() -> Self { - AcknowledgementsV1_1_30 { + AcknowledgementsV4 { average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY, ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER, ack_wait_addition: DEFAULT_ACK_WAIT_ADDITION, @@ -315,7 +324,7 @@ impl Default for AcknowledgementsV1_1_30 { #[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] -pub struct TopologyV1_1_30 { +pub struct TopologyV4 { /// The uniform delay every which clients are querying the directory server /// to try to obtain a compatible network topology to send sphinx packets through. #[serde(with = "humantime_serde")] @@ -338,23 +347,23 @@ pub struct TopologyV1_1_30 { pub max_startup_gateway_waiting_period: Duration, /// Specifies the mixnode topology to be used for sending packets. - pub topology_structure: TopologyStructureV1_1_30, + pub topology_structure: TopologyStructureV4, } #[allow(clippy::large_enum_variant)] #[derive(Default, Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] -pub enum TopologyStructureV1_1_30 { +pub enum TopologyStructureV4 { #[default] NymApi, - GeoAware(GroupByV1_1_30), + GeoAware(GroupByV4), } -impl From for TopologyStructureV1_1_33 { - fn from(value: TopologyStructureV1_1_30) -> Self { +impl From for TopologyStructureV5 { + fn from(value: TopologyStructureV4) -> Self { match value { - TopologyStructureV1_1_30::NymApi => TopologyStructureV1_1_33::NymApi, - TopologyStructureV1_1_30::GeoAware(group_by) => { - TopologyStructureV1_1_33::GeoAware(group_by.into()) + TopologyStructureV4::NymApi => TopologyStructureV5::NymApi, + TopologyStructureV4::GeoAware(group_by) => { + TopologyStructureV5::GeoAware(group_by.into()) } } } @@ -362,44 +371,44 @@ impl From for TopologyStructureV1_1_33 { #[allow(clippy::large_enum_variant)] #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] -pub enum GroupByV1_1_30 { +pub enum GroupByV4 { CountryGroup(CountryGroup), NymAddress(Recipient), } -impl From for GroupByV1_1_33 { - fn from(value: GroupByV1_1_30) -> Self { +impl From for GroupByV5 { + fn from(value: GroupByV4) -> Self { match value { - GroupByV1_1_30::CountryGroup(country) => GroupByV1_1_33::CountryGroup(country), - GroupByV1_1_30::NymAddress(addr) => GroupByV1_1_33::NymAddress(addr), + GroupByV4::CountryGroup(country) => GroupByV5::CountryGroup(country), + GroupByV4::NymAddress(addr) => GroupByV5::NymAddress(addr), } } } -impl std::fmt::Display for GroupByV1_1_30 { +impl std::fmt::Display for GroupByV4 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - GroupByV1_1_30::CountryGroup(group) => write!(f, "group: {}", group), - GroupByV1_1_30::NymAddress(address) => write!(f, "address: {}", address), + GroupByV4::CountryGroup(group) => write!(f, "group: {}", group), + GroupByV4::NymAddress(address) => write!(f, "address: {}", address), } } } -impl Default for TopologyV1_1_30 { +impl Default for TopologyV4 { fn default() -> Self { - TopologyV1_1_30 { + TopologyV4 { topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE, topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT, disable_refreshing: false, max_startup_gateway_waiting_period: DEFAULT_MAX_STARTUP_GATEWAY_WAITING_PERIOD, - topology_structure: TopologyStructureV1_1_30::default(), + topology_structure: TopologyStructureV4::default(), } } } #[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] -pub struct ReplySurbsV1_1_30 { +pub struct ReplySurbsV4 { /// Defines the minimum number of reply surbs the client wants to keep in its storage at all times. /// It can only allow to go below that value if its to request additional reply surbs. pub minimum_reply_surb_storage_threshold: usize, @@ -437,9 +446,9 @@ pub struct ReplySurbsV1_1_30 { pub maximum_reply_key_age: Duration, } -impl Default for ReplySurbsV1_1_30 { +impl Default for ReplySurbsV4 { fn default() -> Self { - ReplySurbsV1_1_30 { + ReplySurbsV4 { minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD, maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD, minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE, @@ -456,22 +465,22 @@ impl Default for ReplySurbsV1_1_30 { #[derive(Debug, Default, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] -pub struct DebugConfigV1_1_30 { +pub struct DebugConfigV4 { /// Defines all configuration options related to traffic streams. - pub traffic: TrafficV1_1_30, + pub traffic: TrafficV4, /// Defines all configuration options related to cover traffic stream(s). - pub cover_traffic: CoverTrafficV1_1_30, + pub cover_traffic: CoverTrafficV4, /// Defines all configuration options related to the gateway connection. - pub gateway_connection: GatewayConnectionV1_1_30, + pub gateway_connection: GatewayConnectionV4, /// Defines all configuration options related to acknowledgements, such as delays or wait timeouts. - pub acknowledgements: AcknowledgementsV1_1_30, + pub acknowledgements: AcknowledgementsV4, /// Defines all configuration options related topology, such as refresh rates or timeouts. - pub topology: TopologyV1_1_30, + pub topology: TopologyV4, /// Defines all configuration options related to reply SURBs. - pub reply_surbs: ReplySurbsV1_1_30, + pub reply_surbs: ReplySurbsV4, } diff --git a/common/client-core/src/config/old_config_v1_1_33.rs b/common/client-core/config-types/src/old/v5.rs similarity index 87% rename from common/client-core/src/config/old_config_v1_1_33.rs rename to common/client-core/config-types/src/old/v5.rs index 028b35fb52..34c3f8144e 100644 --- a/common/client-core/src/config/old_config_v1_1_33.rs +++ b/common/client-core/config-types/src/old/v5.rs @@ -1,15 +1,12 @@ -// Copyright 2023 - Nym Technologies SA +// Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::client::topology_control::geo_aware_provider::CountryGroup; -use crate::config::{ - Acknowledgements, Client, Config, CoverTraffic, DebugConfig, GatewayConnection, GroupBy, - ReplySurbs, Topology, TopologyStructure, Traffic, -}; -use nym_sphinx::{ - addressing::clients::Recipient, - params::{PacketSize, PacketType}, +use crate::{ + Acknowledgements, Client, Config, CountryGroup, CoverTraffic, DebugConfig, GatewayConnection, + GroupBy, ReplySurbs, Topology, TopologyStructure, Traffic, }; +use nym_sphinx_addressing::Recipient; +use nym_sphinx_params::{PacketSize, PacketType}; use serde::{Deserialize, Serialize}; use std::time::Duration; use url::Url; @@ -55,8 +52,21 @@ const DEFAULT_MAXIMUM_REPLY_SURB_AGE: Duration = Duration::from_secs(12 * 60 * 6 // 24 hours const DEFAULT_MAXIMUM_REPLY_KEY_AGE: Duration = Duration::from_secs(24 * 60 * 60); +// aliases for backwards compatibility +pub type ConfigV1_1_33 = ConfigV5; +pub type ClientV1_1_33 = ClientV5; +pub type DebugConfigV1_1_33 = DebugConfigV5; +pub type OldGatewayEndpointConfigV1_1_33 = GatewayEndpointConfigV5; + +pub type TrafficV1_1_33 = TrafficV5; +pub type CoverTrafficV1_1_33 = CoverTrafficV5; +pub type GatewayConnectionV1_1_33 = GatewayConnectionV5; +pub type AcknowledgementsV1_1_33 = AcknowledgementsV5; +pub type TopologyV1_1_33 = TopologyV5; +pub type ReplySurbsV1_1_33 = ReplySurbsV5; + #[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)] -pub struct OldGatewayEndpointConfigV1_1_33 { +pub struct GatewayEndpointConfigV5 { /// gateway_id specifies ID of the gateway to which the client should send messages. /// If initially omitted, a random gateway will be chosen from the available topology. pub gateway_id: String, @@ -70,15 +80,15 @@ pub struct OldGatewayEndpointConfigV1_1_33 { #[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] -pub struct ConfigV1_1_33 { - pub client: ClientV1_1_33, +pub struct ConfigV5 { + pub client: ClientV5, #[serde(default)] - pub debug: DebugConfigV1_1_33, + pub debug: DebugConfigV5, } -impl From for Config { - fn from(value: ConfigV1_1_33) -> Self { +impl From for Config { + fn from(value: ConfigV5) -> Self { Config { client: Client { version: value.client.version, @@ -178,7 +188,7 @@ impl From for Config { #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] // note: the deny_unknown_fields is VITAL here to allow upgrades from v1.1.20_2 #[serde(deny_unknown_fields)] -pub struct ClientV1_1_33 { +pub struct ClientV5 { /// Version of the client for which this configuration was created. pub version: String, @@ -202,7 +212,7 @@ pub struct ClientV1_1_33 { #[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] -pub struct TrafficV1_1_33 { +pub struct TrafficV5 { /// The parameter of Poisson distribution determining how long, on average, /// sent packet is going to be delayed at any given mix node. /// So for a packet going through three mix nodes, on average, it will take three times this value @@ -233,9 +243,9 @@ pub struct TrafficV1_1_33 { pub packet_type: PacketType, } -impl Default for TrafficV1_1_33 { +impl Default for TrafficV5 { fn default() -> Self { - TrafficV1_1_33 { + TrafficV5 { average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY, message_sending_average_delay: DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY, disable_main_poisson_packet_distribution: false, @@ -248,7 +258,7 @@ impl Default for TrafficV1_1_33 { #[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] -pub struct CoverTrafficV1_1_33 { +pub struct CoverTrafficV5 { /// The parameter of Poisson distribution determining how long, on average, /// it is going to take for another loop cover traffic message to be sent. #[serde(with = "humantime_serde")] @@ -263,9 +273,9 @@ pub struct CoverTrafficV1_1_33 { pub disable_loop_cover_traffic_stream: bool, } -impl Default for CoverTrafficV1_1_33 { +impl Default for CoverTrafficV5 { fn default() -> Self { - CoverTrafficV1_1_33 { + CoverTrafficV5 { loop_cover_traffic_average_delay: DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY, cover_traffic_primary_size_ratio: DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO, disable_loop_cover_traffic_stream: false, @@ -275,16 +285,16 @@ impl Default for CoverTrafficV1_1_33 { #[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] -pub struct GatewayConnectionV1_1_33 { +pub struct GatewayConnectionV5 { /// How long we're willing to wait for a response to a message sent to the gateway, /// before giving up on it. #[serde(with = "humantime_serde")] pub gateway_response_timeout: Duration, } -impl Default for GatewayConnectionV1_1_33 { +impl Default for GatewayConnectionV5 { fn default() -> Self { - GatewayConnectionV1_1_33 { + GatewayConnectionV5 { gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT, } } @@ -292,7 +302,7 @@ impl Default for GatewayConnectionV1_1_33 { #[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] -pub struct AcknowledgementsV1_1_33 { +pub struct AcknowledgementsV5 { /// The parameter of Poisson distribution determining how long, on average, /// sent acknowledgement is going to be delayed at any given mix node. /// So for an ack going through three mix nodes, on average, it will take three times this value @@ -312,9 +322,9 @@ pub struct AcknowledgementsV1_1_33 { pub ack_wait_addition: Duration, } -impl Default for AcknowledgementsV1_1_33 { +impl Default for AcknowledgementsV5 { fn default() -> Self { - AcknowledgementsV1_1_33 { + AcknowledgementsV5 { average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY, ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER, ack_wait_addition: DEFAULT_ACK_WAIT_ADDITION, @@ -324,7 +334,7 @@ impl Default for AcknowledgementsV1_1_33 { #[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] -pub struct TopologyV1_1_33 { +pub struct TopologyV5 { /// The uniform delay every which clients are querying the directory server /// to try to obtain a compatible network topology to send sphinx packets through. #[serde(with = "humantime_serde")] @@ -347,68 +357,66 @@ pub struct TopologyV1_1_33 { pub max_startup_gateway_waiting_period: Duration, /// Specifies the mixnode topology to be used for sending packets. - pub topology_structure: TopologyStructureV1_1_33, + pub topology_structure: TopologyStructureV5, } #[allow(clippy::large_enum_variant)] #[derive(Default, Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] -pub enum TopologyStructureV1_1_33 { +pub enum TopologyStructureV5 { #[default] NymApi, - GeoAware(GroupByV1_1_33), + GeoAware(GroupByV5), } -impl From for TopologyStructure { - fn from(value: TopologyStructureV1_1_33) -> Self { +impl From for TopologyStructure { + fn from(value: TopologyStructureV5) -> Self { match value { - TopologyStructureV1_1_33::NymApi => TopologyStructure::NymApi, - TopologyStructureV1_1_33::GeoAware(group_by) => { - TopologyStructure::GeoAware(group_by.into()) - } + TopologyStructureV5::NymApi => TopologyStructure::NymApi, + TopologyStructureV5::GeoAware(group_by) => TopologyStructure::GeoAware(group_by.into()), } } } #[allow(clippy::large_enum_variant)] #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] -pub enum GroupByV1_1_33 { +pub enum GroupByV5 { CountryGroup(CountryGroup), NymAddress(Recipient), } -impl From for GroupBy { - fn from(value: GroupByV1_1_33) -> Self { +impl From for GroupBy { + fn from(value: GroupByV5) -> Self { match value { - GroupByV1_1_33::CountryGroup(country) => GroupBy::CountryGroup(country), - GroupByV1_1_33::NymAddress(addr) => GroupBy::NymAddress(addr), + GroupByV5::CountryGroup(country) => GroupBy::CountryGroup(country), + GroupByV5::NymAddress(addr) => GroupBy::NymAddress(addr), } } } -impl std::fmt::Display for GroupByV1_1_33 { +impl std::fmt::Display for GroupByV5 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - GroupByV1_1_33::CountryGroup(group) => write!(f, "group: {}", group), - GroupByV1_1_33::NymAddress(address) => write!(f, "address: {}", address), + GroupByV5::CountryGroup(group) => write!(f, "group: {}", group), + GroupByV5::NymAddress(address) => write!(f, "address: {}", address), } } } -impl Default for TopologyV1_1_33 { +impl Default for TopologyV5 { fn default() -> Self { - TopologyV1_1_33 { + TopologyV5 { topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE, topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT, disable_refreshing: false, max_startup_gateway_waiting_period: DEFAULT_MAX_STARTUP_GATEWAY_WAITING_PERIOD, - topology_structure: TopologyStructureV1_1_33::default(), + topology_structure: TopologyStructureV5::default(), } } } #[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] -pub struct ReplySurbsV1_1_33 { +pub struct ReplySurbsV5 { /// Defines the minimum number of reply surbs the client wants to keep in its storage at all times. /// It can only allow to go below that value if its to request additional reply surbs. pub minimum_reply_surb_storage_threshold: usize, @@ -450,9 +458,9 @@ pub struct ReplySurbsV1_1_33 { pub surb_mix_hops: Option, } -impl Default for ReplySurbsV1_1_33 { +impl Default for ReplySurbsV5 { fn default() -> Self { - ReplySurbsV1_1_33 { + ReplySurbsV5 { minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD, maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD, minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE, @@ -470,22 +478,22 @@ impl Default for ReplySurbsV1_1_33 { #[derive(Debug, Default, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] -pub struct DebugConfigV1_1_33 { +pub struct DebugConfigV5 { /// Defines all configuration options related to traffic streams. - pub traffic: TrafficV1_1_33, + pub traffic: TrafficV5, /// Defines all configuration options related to cover traffic stream(s). - pub cover_traffic: CoverTrafficV1_1_33, + pub cover_traffic: CoverTrafficV5, /// Defines all configuration options related to the gateway connection. - pub gateway_connection: GatewayConnectionV1_1_33, + pub gateway_connection: GatewayConnectionV5, /// Defines all configuration options related to acknowledgements, such as delays or wait timeouts. - pub acknowledgements: AcknowledgementsV1_1_33, + pub acknowledgements: AcknowledgementsV5, /// Defines all configuration options related topology, such as refresh rates or timeouts. - pub topology: TopologyV1_1_33, + pub topology: TopologyV5, /// Defines all configuration options related to reply SURBs. - pub reply_surbs: ReplySurbsV1_1_33, + pub reply_surbs: ReplySurbsV5, } diff --git a/common/client-core/gateways-storage/src/types.rs b/common/client-core/gateways-storage/src/types.rs index 3755cb6127..8eb5c8753c 100644 --- a/common/client-core/gateways-storage/src/types.rs +++ b/common/client-core/gateways-storage/src/types.rs @@ -27,6 +27,12 @@ pub struct GatewayRegistration { pub registration_timestamp: OffsetDateTime, } +impl GatewayRegistration { + pub fn gateway_id(&self) -> identity::PublicKey { + self.details.gateway_id() + } +} + impl<'a> From<&'a GatewayRegistration> for RawRegisteredGateway { fn from(value: &'a GatewayRegistration) -> Self { RawRegisteredGateway { @@ -298,3 +304,12 @@ pub struct CustomGatewayDetails { pub gateway_id: identity::PublicKey, pub data: Option>, } + +impl CustomGatewayDetails { + pub fn new(gateway_id: identity::PublicKey) -> CustomGatewayDetails { + Self { + gateway_id, + data: None, + } + } +} diff --git a/common/client-core/src/client/key_manager/persistence.rs b/common/client-core/src/client/key_manager/persistence.rs index a4711e3d39..2bff78ee36 100644 --- a/common/client-core/src/client/key_manager/persistence.rs +++ b/common/client-core/src/client/key_manager/persistence.rs @@ -7,7 +7,7 @@ use std::error::Error; use tokio::sync::Mutex; #[cfg(not(target_arch = "wasm32"))] -use crate::config::disk_persistence::keys_paths::ClientKeysPaths; +use crate::config::disk_persistence::ClientKeysPaths; #[cfg(not(target_arch = "wasm32"))] use nym_crypto::asymmetric::{encryption, identity}; #[cfg(not(target_arch = "wasm32"))] diff --git a/common/client-core/src/client/topology_control/geo_aware_provider.rs b/common/client-core/src/client/topology_control/geo_aware_provider.rs index 71de0327b5..96d52fd8d5 100644 --- a/common/client-core/src/client/topology_control/geo_aware_provider.rs +++ b/common/client-core/src/client/topology_control/geo_aware_provider.rs @@ -1,6 +1,5 @@ -use std::{collections::HashMap, fmt}; - -use log::{debug, error, info}; +use crate::config::GroupBy; +use log::{debug, error}; use nym_explorer_client::{ExplorerClient, PrettyDetailedMixNodeBond}; use nym_network_defaults::var_names::EXPLORER_API; use nym_topology::{ @@ -10,11 +9,11 @@ use nym_topology::{ }; use nym_validator_client::client::MixId; use rand::{prelude::SliceRandom, thread_rng}; -use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use tap::TapOptional; use url::Url; -use crate::config::GroupBy; +pub use nym_country_group::CountryGroup; const MIN_NODES_PER_LAYER: usize = 1; @@ -38,158 +37,6 @@ fn create_explorer_client() -> Option { Some(client) } -#[derive(Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Debug)] -pub enum CountryGroup { - Europe, - NorthAmerica, - SouthAmerica, - Oceania, - Asia, - Africa, - Unknown, -} - -impl CountryGroup { - // We map contry codes into group, which initially are continent codes to a first approximation, - // but we do it manually to reserve the right to tweak this distribution for our purposes. - // NOTE: I did this quickly and it's not a complete list of all countries, but only those that - // were present in the network at the time. Please add more as needed. - fn new(country_code: &str) -> Self { - let country_code = country_code.to_uppercase(); - use CountryGroup::*; - match country_code.as_ref() { - // Europe - "AT" => Europe, - "BG" => Europe, - "CH" => Europe, - "CY" => Europe, - "CZ" => Europe, - "DE" => Europe, - "DK" => Europe, - "ES" => Europe, - "FI" => Europe, - "FR" => Europe, - "GB" => Europe, - "GR" => Europe, - "IE" => Europe, - "IT" => Europe, - "LT" => Europe, - "LU" => Europe, - "LV" => Europe, - "MD" => Europe, - "MT" => Europe, - "NL" => Europe, - "NO" => Europe, - "PL" => Europe, - "RO" => Europe, - "SE" => Europe, - "SK" => Europe, - "TR" => Europe, - "UA" => Europe, - - // North America - "CA" => NorthAmerica, - "MX" => NorthAmerica, - "US" => NorthAmerica, - - // South America - "AR" => SouthAmerica, - "BR" => SouthAmerica, - "CL" => SouthAmerica, - "CO" => SouthAmerica, - "CR" => SouthAmerica, - "GT" => SouthAmerica, - - // Oceania - "AU" => Oceania, - - // Asia - "AM" => Asia, - "BH" => Asia, - "CN" => Asia, - "GE" => Asia, - "HK" => Asia, - "ID" => Asia, - "IL" => Asia, - "IN" => Asia, - "JP" => Asia, - "KH" => Asia, - "KR" => Asia, - "KZ" => Asia, - "MY" => Asia, - "RU" => Asia, - "SG" => Asia, - "TH" => Asia, - "VN" => Asia, - - // Africa - "SC" => Africa, - "UG" => Africa, - "ZA" => Africa, - - // And group level codes work too - "EU" => Europe, - "NA" => NorthAmerica, - "SA" => SouthAmerica, - "OC" => Oceania, - "AS" => Asia, - "AF" => Africa, - - // And some aliases - "EUROPE" => Europe, - "NORTHAMERICA" => NorthAmerica, - "SOUTHAMERICA" => SouthAmerica, - "OCEANIA" => Oceania, - "ASIA" => Asia, - "AFRICA" => Africa, - - _ => { - info!("Unknown country code: {}", country_code); - Unknown - } - } - } -} - -impl fmt::Display for CountryGroup { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use CountryGroup::*; - match self { - Europe => write!(f, "EU"), - NorthAmerica => write!(f, "NA"), - SouthAmerica => write!(f, "SA"), - Oceania => write!(f, "OC"), - Asia => write!(f, "AS"), - Africa => write!(f, "AF"), - Unknown => write!(f, "Unknown"), - } - } -} - -impl std::str::FromStr for CountryGroup { - type Err = (); - - fn from_str(s: &str) -> Result { - let group = CountryGroup::new(s); - if group == CountryGroup::Unknown { - Err(()) - } else { - Ok(group) - } - } -} - -impl CountryGroup { - #[allow(unused)] - fn known(self) -> Option { - use CountryGroup::*; - match self { - Europe | NorthAmerica | SouthAmerica | Oceania | Asia | Africa => Some(self), - Unknown => None, - } - } -} - fn group_mixnodes_by_country_code( mixnodes: Vec, ) -> HashMap> { diff --git a/common/client-core/src/config/disk_persistence/mod.rs b/common/client-core/src/config/disk_persistence/mod.rs deleted file mode 100644 index c086696a4b..0000000000 --- a/common/client-core/src/config/disk_persistence/mod.rs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2021-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::config::disk_persistence::keys_paths::ClientKeysPaths; -use serde::{Deserialize, Serialize}; -use std::path::{Path, PathBuf}; - -pub mod keys_paths; -pub mod old_v1_1_20_2; -pub mod old_v1_1_33; - -pub const DEFAULT_REPLY_SURB_DB_FILENAME: &str = "persistent_reply_store.sqlite"; -pub const DEFAULT_CREDENTIALS_DB_FILENAME: &str = "credentials_database.db"; -pub const DEFAULT_GATEWAYS_DETAILS_DB_FILENAME: &str = "gateways_registrations.sqlite"; - -#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct CommonClientPaths { - pub keys: ClientKeysPaths, - - /// Path to the file containing information about gateways used by this client, - /// i.e. details such as their public keys, owner addresses or the network information. - pub gateway_registrations: PathBuf, - - /// Path to the database containing bandwidth credentials of this client. - pub credentials_database: PathBuf, - - /// Path to the persistent store for received reply surbs, unused encryption keys and used sender tags. - pub reply_surb_database: PathBuf, -} - -impl CommonClientPaths { - pub fn new_base>(base_data_directory: P) -> Self { - let base_dir = base_data_directory.as_ref(); - - CommonClientPaths { - credentials_database: base_dir.join(DEFAULT_CREDENTIALS_DB_FILENAME), - reply_surb_database: base_dir.join(DEFAULT_REPLY_SURB_DB_FILENAME), - gateway_registrations: base_dir.join(DEFAULT_GATEWAYS_DETAILS_DB_FILENAME), - keys: ClientKeysPaths::new_base(base_data_directory), - } - } -} diff --git a/common/client-core/src/config/disk_persistence/old_v1_1_20_2.rs b/common/client-core/src/config/disk_persistence/old_v1_1_20_2.rs deleted file mode 100644 index e1863bb567..0000000000 --- a/common/client-core/src/config/disk_persistence/old_v1_1_20_2.rs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::config::disk_persistence::old_v1_1_33::{ - ClientKeysPathsV1_1_33, CommonClientPathsV1_1_33, DEFAULT_GATEWAY_DETAILS_FILENAME, -}; -use crate::error::ClientCoreError; -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; - -#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct CommonClientPathsV1_1_20_2 { - pub keys: ClientKeysPathsV1_1_33, - pub credentials_database: PathBuf, - pub reply_surb_database: PathBuf, -} - -impl CommonClientPathsV1_1_20_2 { - pub fn upgrade_default(self) -> Result { - let data_dir = self.reply_surb_database.parent().ok_or_else(|| { - ClientCoreError::ConfigFileUpgradeFailure { - current_version: "1.1.20-2".to_string(), - } - })?; - Ok(CommonClientPathsV1_1_33 { - keys: self.keys, - gateway_details: data_dir.join(DEFAULT_GATEWAY_DETAILS_FILENAME), - credentials_database: self.credentials_database, - reply_surb_database: self.reply_surb_database, - }) - } -} diff --git a/common/client-core/src/config/mod.rs b/common/client-core/src/config/mod.rs index 2333c58df4..69c30c69ae 100644 --- a/common/client-core/src/config/mod.rs +++ b/common/client-core/src/config/mod.rs @@ -1,602 +1,9 @@ -// Copyright 2021-2023 - Nym Technologies SA +// Copyright 2021-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::client::topology_control::geo_aware_provider::CountryGroup; -use nym_config::defaults::NymNetworkDetails; -use nym_sphinx::{ - addressing::clients::Recipient, - params::{PacketSize, PacketType}, +pub use nym_client_core_config_types::disk_persistence; +pub use nym_client_core_config_types::old::{ + old_config_v1_1_13, old_config_v1_1_20, old_config_v1_1_20_2, old_config_v1_1_30, + old_config_v1_1_33, }; -use serde::{Deserialize, Serialize}; -use std::time::Duration; -use url::Url; - -pub mod disk_persistence; -pub mod old_config_v1_1_13; -pub mod old_config_v1_1_20; -pub mod old_config_v1_1_20_2; -pub mod old_config_v1_1_30; -pub mod old_config_v1_1_33; - -// 'DEBUG' -const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 1.5; - -const DEFAULT_ACK_WAIT_ADDITION: Duration = Duration::from_millis(1_500); -const DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(200); -const DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(20); -const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(50); -const DEFAULT_TOPOLOGY_REFRESH_RATE: Duration = Duration::from_secs(5 * 60); // every 5min -const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: Duration = Duration::from_millis(5_000); -const DEFAULT_MAX_STARTUP_GATEWAY_WAITING_PERIOD: Duration = Duration::from_secs(70 * 60); // 70min -> full epoch (1h) + a bit of overhead - -// Set this to a high value for now, so that we don't risk sporadic timeouts that might cause -// bought bandwidth tokens to not have time to be spent; Once we remove the gateway from the -// bandwidth bridging protocol, we can come back to a smaller timeout value -const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5 * 60); - -const DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO: f64 = 0.70; - -// reply-surbs related: - -// define when to request -// clients/client-core/src/client/replies/reply_storage/surb_storage.rs -const DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 10; -const DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 200; - -// define how much to request at once -// clients/client-core/src/client/replies/reply_controller.rs -const DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 10; -const DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 100; - -const DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE: u32 = 500; - -const DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD: Duration = Duration::from_secs(10); -const DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD: Duration = Duration::from_secs(5 * 60); - -// 12 hours -const DEFAULT_MAXIMUM_REPLY_SURB_AGE: Duration = Duration::from_secs(12 * 60 * 60); - -// 24 hours -const DEFAULT_MAXIMUM_REPLY_KEY_AGE: Duration = Duration::from_secs(24 * 60 * 60); - -#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct Config { - pub client: Client, - - #[serde(default)] - pub debug: DebugConfig, -} - -impl Config { - pub fn new(id: S1, version: S2) -> Self - where - S1: Into, - S2: Into, - { - Config { - client: Client::new_default(id, version), - debug: Default::default(), - } - } - - pub fn from_client_config(client: Client, debug: DebugConfig) -> Self { - Config { client, debug } - } - - pub fn validate(&self) -> bool { - self.debug.validate() - } - - pub fn with_debug_config(mut self, debug: DebugConfig) -> Self { - self.debug = debug; - self - } - - pub fn with_disabled_credentials(mut self, disabled_credentials_mode: bool) -> Self { - self.client.disabled_credentials_mode = disabled_credentials_mode; - self - } - - pub fn with_custom_nyxd(mut self, urls: Vec) -> Self { - self.client.nyxd_urls = urls; - self - } - - pub fn set_custom_nyxd(&mut self, nyxd_urls: Vec) { - self.client.nyxd_urls = nyxd_urls; - } - - pub fn with_custom_nym_apis(mut self, nym_api_urls: Vec) -> Self { - self.client.nym_api_urls = nym_api_urls; - self - } - - pub fn set_custom_nym_apis(&mut self, nym_api_urls: Vec) { - self.client.nym_api_urls = nym_api_urls; - } - - pub fn with_high_default_traffic_volume(mut self, enabled: bool) -> Self { - if enabled { - self.set_high_default_traffic_volume(); - } - self - } - - pub fn with_packet_type(mut self, packet_type: PacketType) -> Self { - self.debug.traffic.packet_type = packet_type; - self - } - - pub fn set_high_default_traffic_volume(&mut self) { - self.debug.traffic.average_packet_delay = Duration::from_millis(10); - // basically don't really send cover messages - self.debug.cover_traffic.loop_cover_traffic_average_delay = - Duration::from_millis(2_000_000); - // 250 "real" messages / s - self.debug.traffic.message_sending_average_delay = Duration::from_millis(4); - } - - pub fn with_disabled_poisson_process(mut self, disabled: bool) -> Self { - if disabled { - self.set_no_poisson_process() - } - self - } - - pub fn set_no_poisson_process(&mut self) { - self.debug.traffic.disable_main_poisson_packet_distribution = true; - } - - pub fn with_disabled_cover_traffic(mut self, disabled: bool) -> Self { - if disabled { - self.set_no_cover_traffic() - } - self - } - - pub fn set_no_cover_traffic(&mut self) { - self.debug.cover_traffic.disable_loop_cover_traffic_stream = true; - self.debug.traffic.disable_main_poisson_packet_distribution = true; - } - - pub fn with_disabled_cover_traffic_with_keepalive(mut self, disabled: bool) -> Self { - if disabled { - self.set_no_cover_traffic_with_keepalive() - } - self - } - pub fn set_no_cover_traffic_with_keepalive(&mut self) { - self.debug.traffic.disable_main_poisson_packet_distribution = true; - self.debug.cover_traffic.loop_cover_traffic_average_delay = Duration::from_secs(5); - } - - pub fn with_disabled_topology_refresh(mut self, disable_topology_refresh: bool) -> Self { - self.debug.topology.disable_refreshing = disable_topology_refresh; - self - } - - pub fn with_topology_structure(mut self, topology_structure: TopologyStructure) -> Self { - self.set_topology_structure(topology_structure); - self - } - - pub fn set_topology_structure(&mut self, topology_structure: TopologyStructure) { - self.debug.topology.topology_structure = topology_structure; - } - - pub fn with_no_per_hop_delays(mut self, no_per_hop_delays: bool) -> Self { - if no_per_hop_delays { - self.set_no_per_hop_delays() - } - self - } - - pub fn set_no_per_hop_delays(&mut self) { - self.debug.traffic.average_packet_delay = Duration::ZERO; - self.debug.acknowledgements.average_ack_delay = Duration::ZERO; - } - - pub fn with_secondary_packet_size(mut self, secondary_packet_size: Option) -> Self { - self.set_secondary_packet_size(secondary_packet_size); - self - } - - pub fn set_secondary_packet_size(&mut self, secondary_packet_size: Option) { - self.debug.traffic.secondary_packet_size = secondary_packet_size; - } - - pub fn set_custom_version(&mut self, version: &str) { - self.client.version = version.to_string(); - } - - pub fn get_id(&self) -> String { - self.client.id.clone() - } - - pub fn get_disabled_credentials_mode(&self) -> bool { - self.client.disabled_credentials_mode - } - - pub fn get_validator_endpoints(&self) -> Vec { - self.client.nyxd_urls.clone() - } - - pub fn get_nym_api_endpoints(&self) -> Vec { - self.client.nym_api_urls.clone() - } -} - -#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] -// note: the deny_unknown_fields is VITAL here to allow upgrades from v1.1.20_2 -#[serde(deny_unknown_fields)] -pub struct Client { - /// Version of the client for which this configuration was created. - pub version: String, - - /// ID specifies the human readable ID of this particular client. - pub id: String, - - /// Indicates whether this client is running in a disabled credentials mode, thus attempting - /// to claim bandwidth without presenting bandwidth credentials. - // TODO: this should be moved to `debug.gateway_connection` - #[serde(default)] - pub disabled_credentials_mode: bool, - - /// Addresses to nyxd validators via which the client can communicate with the chain. - #[serde(alias = "validator_urls")] - pub nyxd_urls: Vec, - - /// Addresses to APIs running on validator from which the client gets the view of the network. - #[serde(alias = "validator_api_urls")] - pub nym_api_urls: Vec, -} - -impl Client { - pub fn new_default(id: S1, version: S2) -> Self - where - S1: Into, - S2: Into, - { - let network = NymNetworkDetails::new_mainnet(); - let nyxd_urls = network - .endpoints - .iter() - .map(|validator| validator.nyxd_url()) - .collect(); - let nym_api_urls = network - .endpoints - .iter() - .filter_map(|validator| validator.api_url()) - .collect::>(); - - Client { - version: version.into(), - id: id.into(), - disabled_credentials_mode: true, - nyxd_urls, - nym_api_urls, - } - } - - pub fn new>( - id: S, - disabled_credentials_mode: bool, - nyxd_urls: Vec, - nym_api_urls: Vec, - ) -> Self { - Client { - version: env!("CARGO_PKG_VERSION").to_string(), - id: id.into(), - disabled_credentials_mode, - nyxd_urls, - nym_api_urls, - } - } -} - -#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] -#[serde(default, deny_unknown_fields)] -pub struct Traffic { - /// The parameter of Poisson distribution determining how long, on average, - /// sent packet is going to be delayed at any given mix node. - /// So for a packet going through three mix nodes, on average, it will take three times this value - /// until the packet reaches its destination. - #[serde(with = "humantime_serde")] - pub average_packet_delay: Duration, - - /// The parameter of Poisson distribution determining how long, on average, - /// it is going to take another 'real traffic stream' message to be sent. - /// If no real packets are available and cover traffic is enabled, - /// a loop cover message is sent instead in order to preserve the rate. - #[serde(with = "humantime_serde")] - pub message_sending_average_delay: Duration, - - /// Controls whether the main packet stream constantly produces packets according to the predefined - /// poisson distribution. - pub disable_main_poisson_packet_distribution: bool, - - /// Specifies the packet size used for sent messages. - /// Do not override it unless you understand the consequences of that change. - pub primary_packet_size: PacketSize, - - /// Specifies the optional auxiliary packet size for optimizing message streams. - /// Note that its use decreases overall anonymity. - /// Do not set it it unless you understand the consequences of that change. - pub secondary_packet_size: Option, - - pub packet_type: PacketType, -} - -impl Traffic { - pub fn validate(&self) -> bool { - if let Some(secondary_packet_size) = self.secondary_packet_size { - if secondary_packet_size == PacketSize::AckPacket - || secondary_packet_size == self.primary_packet_size - { - return false; - } - } - true - } -} - -impl Default for Traffic { - fn default() -> Self { - Traffic { - average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY, - message_sending_average_delay: DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY, - disable_main_poisson_packet_distribution: false, - primary_packet_size: PacketSize::RegularPacket, - secondary_packet_size: None, - packet_type: PacketType::Mix, - } - } -} - -#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] -#[serde(default, deny_unknown_fields)] -pub struct CoverTraffic { - /// The parameter of Poisson distribution determining how long, on average, - /// it is going to take for another loop cover traffic message to be sent. - #[serde(with = "humantime_serde")] - pub loop_cover_traffic_average_delay: Duration, - - /// Specifies the ratio of `primary_packet_size` to `secondary_packet_size` used in cover traffic. - /// Only applicable if `secondary_packet_size` is enabled. - pub cover_traffic_primary_size_ratio: f64, - - /// Controls whether the dedicated loop cover traffic stream should be enabled. - /// (and sending packets, on average, every [Self::loop_cover_traffic_average_delay]) - pub disable_loop_cover_traffic_stream: bool, -} - -impl Default for CoverTraffic { - fn default() -> Self { - CoverTraffic { - loop_cover_traffic_average_delay: DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY, - cover_traffic_primary_size_ratio: DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO, - disable_loop_cover_traffic_stream: false, - } - } -} - -#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] -#[serde(default, deny_unknown_fields)] -pub struct GatewayConnection { - /// How long we're willing to wait for a response to a message sent to the gateway, - /// before giving up on it. - #[serde(with = "humantime_serde")] - pub gateway_response_timeout: Duration, -} - -impl Default for GatewayConnection { - fn default() -> Self { - GatewayConnection { - gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT, - } - } -} - -#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] -#[serde(default, deny_unknown_fields)] -pub struct Acknowledgements { - /// The parameter of Poisson distribution determining how long, on average, - /// sent acknowledgement is going to be delayed at any given mix node. - /// So for an ack going through three mix nodes, on average, it will take three times this value - /// until the packet reaches its destination. - #[serde(with = "humantime_serde")] - pub average_ack_delay: Duration, - - /// Value multiplied with the expected round trip time of an acknowledgement packet before - /// it is assumed it was lost and retransmission of the data packet happens. - /// In an ideal network with 0 latency, this value would have been 1. - pub ack_wait_multiplier: f64, - - /// Value added to the expected round trip time of an acknowledgement packet before - /// it is assumed it was lost and retransmission of the data packet happens. - /// In an ideal network with 0 latency, this value would have been 0. - #[serde(with = "humantime_serde")] - pub ack_wait_addition: Duration, -} - -impl Default for Acknowledgements { - fn default() -> Self { - Acknowledgements { - average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY, - ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER, - ack_wait_addition: DEFAULT_ACK_WAIT_ADDITION, - } - } -} - -#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] -#[serde(default, deny_unknown_fields)] -pub struct Topology { - /// The uniform delay every which clients are querying the directory server - /// to try to obtain a compatible network topology to send sphinx packets through. - #[serde(with = "humantime_serde")] - pub topology_refresh_rate: Duration, - - /// During topology refresh, test packets are sent through every single possible network - /// path. This timeout determines waiting period until it is decided that the packet - /// did not reach its destination. - #[serde(with = "humantime_serde")] - pub topology_resolution_timeout: Duration, - - /// Specifies whether the client should not refresh the network topology after obtaining - /// the first valid instance. - /// Supersedes `topology_refresh_rate_ms`. - pub disable_refreshing: bool, - - /// Defines how long the client is going to wait on startup for its gateway to come online, - /// before abandoning the procedure. - #[serde(with = "humantime_serde")] - pub max_startup_gateway_waiting_period: Duration, - - /// Specifies the mixnode topology to be used for sending packets. - pub topology_structure: TopologyStructure, -} - -#[allow(clippy::large_enum_variant)] -#[derive(Default, Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] -pub enum TopologyStructure { - #[default] - NymApi, - GeoAware(GroupBy), -} - -#[allow(clippy::large_enum_variant)] -#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] -pub enum GroupBy { - CountryGroup(CountryGroup), - NymAddress(Recipient), -} - -impl std::fmt::Display for GroupBy { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - GroupBy::CountryGroup(group) => write!(f, "group: {}", group), - GroupBy::NymAddress(address) => write!(f, "address: {}", address), - } - } -} - -impl Default for Topology { - fn default() -> Self { - Topology { - topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE, - topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT, - disable_refreshing: false, - max_startup_gateway_waiting_period: DEFAULT_MAX_STARTUP_GATEWAY_WAITING_PERIOD, - topology_structure: TopologyStructure::default(), - } - } -} - -#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] -#[serde(default, deny_unknown_fields)] -pub struct ReplySurbs { - /// Defines the minimum number of reply surbs the client wants to keep in its storage at all times. - /// It can only allow to go below that value if its to request additional reply surbs. - pub minimum_reply_surb_storage_threshold: usize, - - /// Defines the maximum number of reply surbs the client wants to keep in its storage at any times. - pub maximum_reply_surb_storage_threshold: usize, - - /// Defines the minimum number of reply surbs the client would request. - pub minimum_reply_surb_request_size: u32, - - /// Defines the maximum number of reply surbs the client would request. - pub maximum_reply_surb_request_size: u32, - - /// Defines the maximum number of reply surbs a remote party is allowed to request from this client at once. - pub maximum_allowed_reply_surb_request_size: u32, - - /// Defines maximum amount of time the client is going to wait for reply surbs before explicitly asking - /// for more even though in theory they wouldn't need to. - #[serde(with = "humantime_serde")] - pub maximum_reply_surb_rerequest_waiting_period: Duration, - - /// Defines maximum amount of time the client is going to wait for reply surbs before - /// deciding it's never going to get them and would drop all pending messages - #[serde(with = "humantime_serde")] - pub maximum_reply_surb_drop_waiting_period: Duration, - - /// Defines maximum amount of time given reply surb is going to be valid for. - /// This is going to be superseded by key rotation once implemented. - #[serde(with = "humantime_serde")] - pub maximum_reply_surb_age: Duration, - - /// Defines maximum amount of time given reply key is going to be valid for. - /// This is going to be superseded by key rotation once implemented. - #[serde(with = "humantime_serde")] - pub maximum_reply_key_age: Duration, - - /// Specifies the number of mixnet hops the packet should go through. If not specified, then - /// the default value is used. - pub surb_mix_hops: Option, -} - -impl Default for ReplySurbs { - fn default() -> Self { - ReplySurbs { - minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD, - maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD, - minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE, - maximum_reply_surb_request_size: DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE, - maximum_allowed_reply_surb_request_size: DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE, - maximum_reply_surb_rerequest_waiting_period: - DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD, - maximum_reply_surb_drop_waiting_period: DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD, - maximum_reply_surb_age: DEFAULT_MAXIMUM_REPLY_SURB_AGE, - maximum_reply_key_age: DEFAULT_MAXIMUM_REPLY_KEY_AGE, - surb_mix_hops: None, - } - } -} - -#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] -#[serde(default, deny_unknown_fields)] -pub struct DebugConfig { - /// Defines all configuration options related to traffic streams. - pub traffic: Traffic, - - /// Defines all configuration options related to cover traffic stream(s). - pub cover_traffic: CoverTraffic, - - /// Defines all configuration options related to the gateway connection. - pub gateway_connection: GatewayConnection, - - /// Defines all configuration options related to acknowledgements, such as delays or wait timeouts. - pub acknowledgements: Acknowledgements, - - /// Defines all configuration options related topology, such as refresh rates or timeouts. - pub topology: Topology, - - /// Defines all configuration options related to reply SURBs. - pub reply_surbs: ReplySurbs, -} - -impl DebugConfig { - pub fn validate(&self) -> bool { - // no other sections have explicit requirements (yet) - self.traffic.validate() - } -} - -// it could be derived, sure, but I'd rather have an explicit implementation in case we had to change -// something manually at some point -#[allow(clippy::derivable_impls)] -impl Default for DebugConfig { - fn default() -> Self { - DebugConfig { - traffic: Default::default(), - cover_traffic: Default::default(), - gateway_connection: Default::default(), - acknowledgements: Default::default(), - topology: Default::default(), - reply_surbs: Default::default(), - } - } -} +pub use nym_client_core_config_types::*; diff --git a/common/commands/Cargo.toml b/common/commands/Cargo.toml index 6c96a3f93d..d977b49121 100644 --- a/common/commands/Cargo.toml +++ b/common/commands/Cargo.toml @@ -17,7 +17,7 @@ csv = "1.3.0" cw-utils = { workspace = true } futures = { workspace = true } handlebars = "3.0.1" -humantime-serde = "1.0" +humantime-serde = { workspace = true } inquire = "0.6.2" k256 = { workspace = true, features = ["ecdsa", "sha256"] } log = { workspace = true } diff --git a/common/country-group/Cargo.toml b/common/country-group/Cargo.toml new file mode 100644 index 0000000000..ec96820852 --- /dev/null +++ b/common/country-group/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "nym-country-group" +version = "0.1.0" +authors.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +edition.workspace = true +license.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde = { workspace = true, features = ["derive"] } +tracing.workspace = true diff --git a/common/country-group/src/lib.rs b/common/country-group/src/lib.rs new file mode 100644 index 0000000000..3e57ae7d21 --- /dev/null +++ b/common/country-group/src/lib.rs @@ -0,0 +1,158 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use serde::{Deserialize, Serialize}; +use std::fmt; +use tracing::info; + +#[derive(Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Debug)] +pub enum CountryGroup { + Europe, + NorthAmerica, + SouthAmerica, + Oceania, + Asia, + Africa, + Unknown, +} + +impl CountryGroup { + // We map country codes into group, which initially are continent codes to a first approximation, + // but we do it manually to reserve the right to tweak this distribution for our purposes. + // NOTE: I did this quickly, and it's not a complete list of all countries, but only those that + // were present in the network at the time. Please add more as needed. + pub fn new(country_code: &str) -> Self { + let country_code = country_code.to_uppercase(); + use CountryGroup::*; + match country_code.as_ref() { + // Europe + "AT" => Europe, + "BG" => Europe, + "CH" => Europe, + "CY" => Europe, + "CZ" => Europe, + "DE" => Europe, + "DK" => Europe, + "ES" => Europe, + "FI" => Europe, + "FR" => Europe, + "GB" => Europe, + "GR" => Europe, + "IE" => Europe, + "IT" => Europe, + "LT" => Europe, + "LU" => Europe, + "LV" => Europe, + "MD" => Europe, + "MT" => Europe, + "NL" => Europe, + "NO" => Europe, + "PL" => Europe, + "RO" => Europe, + "SE" => Europe, + "SK" => Europe, + "TR" => Europe, + "UA" => Europe, + + // North America + "CA" => NorthAmerica, + "MX" => NorthAmerica, + "US" => NorthAmerica, + + // South America + "AR" => SouthAmerica, + "BR" => SouthAmerica, + "CL" => SouthAmerica, + "CO" => SouthAmerica, + "CR" => SouthAmerica, + "GT" => SouthAmerica, + + // Oceania + "AU" => Oceania, + + // Asia + "AM" => Asia, + "BH" => Asia, + "CN" => Asia, + "GE" => Asia, + "HK" => Asia, + "ID" => Asia, + "IL" => Asia, + "IN" => Asia, + "JP" => Asia, + "KH" => Asia, + "KR" => Asia, + "KZ" => Asia, + "MY" => Asia, + "RU" => Asia, + "SG" => Asia, + "TH" => Asia, + "VN" => Asia, + + // Africa + "SC" => Africa, + "UG" => Africa, + "ZA" => Africa, + + // And group level codes work too + "EU" => Europe, + "NA" => NorthAmerica, + "SA" => SouthAmerica, + "OC" => Oceania, + "AS" => Asia, + "AF" => Africa, + + // And some aliases + "EUROPE" => Europe, + "NORTHAMERICA" => NorthAmerica, + "SOUTHAMERICA" => SouthAmerica, + "OCEANIA" => Oceania, + "ASIA" => Asia, + "AFRICA" => Africa, + + _ => { + info!("Unknown country code: {country_code}"); + Unknown + } + } + } +} + +impl fmt::Display for CountryGroup { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use CountryGroup::*; + match self { + Europe => write!(f, "EU"), + NorthAmerica => write!(f, "NA"), + SouthAmerica => write!(f, "SA"), + Oceania => write!(f, "OC"), + Asia => write!(f, "AS"), + Africa => write!(f, "AF"), + Unknown => write!(f, "Unknown"), + } + } +} + +impl std::str::FromStr for CountryGroup { + type Err = (); + + fn from_str(s: &str) -> Result { + let group = CountryGroup::new(s); + if group == CountryGroup::Unknown { + Err(()) + } else { + Ok(group) + } + } +} + +impl CountryGroup { + #[allow(unused)] + fn known(self) -> Option { + use CountryGroup::*; + match self { + Europe | NorthAmerica | SouthAmerica | Oceania | Asia | Africa => Some(self), + Unknown => None, + } + } +} diff --git a/common/crypto/src/asymmetric/identity/mod.rs b/common/crypto/src/asymmetric/identity/mod.rs index 7d1db5a8c1..a48fb78a82 100644 --- a/common/crypto/src/asymmetric/identity/mod.rs +++ b/common/crypto/src/asymmetric/identity/mod.rs @@ -10,6 +10,9 @@ use std::str::FromStr; use thiserror::Error; use zeroize::{Zeroize, ZeroizeOnDrop}; +#[cfg(feature = "serde")] +pub mod serde_helpers; + #[cfg(feature = "sphinx")] use nym_sphinx_types::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH}; diff --git a/common/crypto/src/asymmetric/identity/serde_helpers.rs b/common/crypto/src/asymmetric/identity/serde_helpers.rs new file mode 100644 index 0000000000..67f9dece70 --- /dev/null +++ b/common/crypto/src/asymmetric/identity/serde_helpers.rs @@ -0,0 +1,18 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use super::PublicKey; + +pub mod bs58_pubkey { + use super::*; + use serde_crate::{Deserialize, Deserializer, Serializer}; + + pub fn serialize(key: &PublicKey, serializer: S) -> Result { + serializer.serialize_str(&key.to_base58_string()) + } + + pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result { + let s = String::deserialize(deserializer)?; + PublicKey::from_base58_string(s).map_err(serde::de::Error::custom) + } +} diff --git a/common/crypto/src/asymmetric/mod.rs b/common/crypto/src/asymmetric/mod.rs index 6e8836809c..4f6b8eb4bf 100644 --- a/common/crypto/src/asymmetric/mod.rs +++ b/common/crypto/src/asymmetric/mod.rs @@ -3,3 +3,6 @@ pub mod encryption; pub mod identity; + +pub use encryption as x25519; +pub use identity as ed25519; diff --git a/common/dkg/src/bte/proof_chunking.rs b/common/dkg/src/bte/proof_chunking.rs index 27bc04894a..6829a53660 100644 --- a/common/dkg/src/bte/proof_chunking.rs +++ b/common/dkg/src/bte/proof_chunking.rs @@ -836,7 +836,7 @@ mod tests { let share3 = chunks3.clone().try_into().unwrap(); let shares = vec![share1, share2, share3]; - let chunks = &[chunks1, chunks2, chunks3]; + let chunks = [chunks1, chunks2, chunks3]; for (i, pk_i) in pks.iter().enumerate() { let mut ciphertext_chunk_i = Vec::with_capacity(NUM_CHUNKS); diff --git a/common/http-api-common/Cargo.toml b/common/http-api-common/Cargo.toml new file mode 100644 index 0000000000..5104b27d13 --- /dev/null +++ b/common/http-api-common/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "nym-http-api-common" +version = "0.1.0" +authors.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +edition.workspace = true +license.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +axum.workspace = true +bytes = "1.5.0" +mime = "0.3.17" +serde = { workspace = true, features = ["derive"] } +serde_json.workspace = true +serde_yaml = "0.9.25" +utoipa = { workspace = true } diff --git a/nym-node/src/http/router/api/mod.rs b/common/http-api-common/src/lib.rs similarity index 80% rename from nym-node/src/http/router/api/mod.rs rename to common/http-api-common/src/lib.rs index 5e8a4160ca..83b9c685d8 100644 --- a/nym-node/src/http/router/api/mod.rs +++ b/common/http-api-common/src/lib.rs @@ -1,32 +1,13 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only +// SPDX-License-Identifier: Apache-2.0 use axum::http::{header, HeaderValue, StatusCode}; use axum::response::{IntoResponse, Response}; -use axum::{Json, Router}; +use axum::Json; use bytes::{BufMut, BytesMut}; use serde::{Deserialize, Serialize}; use utoipa::{IntoParams, ToSchema}; -pub mod v1; - -use crate::http::api::v1::gateway::client_interfaces::wireguard::WireguardAppState; -use crate::http::state::AppState; -pub use nym_node_requests::api as api_requests; -use nym_node_requests::routes; - -#[derive(Debug, Clone)] -pub struct Config { - pub v1_config: v1::Config, -} - -pub(super) fn routes(config: Config, initial_wg_state: WireguardAppState) -> Router { - Router::new().nest( - routes::api::V1, - v1::routes(config.v1_config, initial_wg_state), - ) -} - #[derive(Debug, Clone, ToSchema)] pub enum FormattedResponse { Json(Json), diff --git a/common/mixnode-common/Cargo.toml b/common/mixnode-common/Cargo.toml index 0ee39302e3..a19fc70332 100644 --- a/common/mixnode-common/Cargo.toml +++ b/common/mixnode-common/Cargo.toml @@ -10,11 +10,11 @@ license.workspace = true [dependencies] bytes = "1.0" futures = { workspace = true } -humantime-serde = "1.0" +humantime-serde = { workspace = true } log = { workspace = true } rand = "0.8" -serde = { version = "1.0", features = ["derive"] } -tokio = { version = "1.24.1", features = [ +serde = { workspace = true, features = ["derive"] } +tokio = { workspace = true, features = [ "time", "macros", "rt", @@ -23,6 +23,7 @@ tokio = { version = "1.24.1", features = [ ] } tokio-util = { workspace = true, features = ["codec"] } url = { workspace = true } +time.workspace = true thiserror = { workspace = true } nym-crypto = { path = "../crypto" } @@ -37,3 +38,4 @@ nym-task = { path = "../task" } nym-validator-client = { path = "../client-libs/validator-client" } nym-bin-common = { path = "../bin-common" } nym-metrics = { path = "../nym-metrics" } +nym-node-http-api = { path = "../../nym-node/nym-node-http-api" } \ No newline at end of file diff --git a/common/mixnode-common/src/verloc/measurement.rs b/common/mixnode-common/src/verloc/measurement.rs index 0e7d312f84..cacf72080a 100644 --- a/common/mixnode-common/src/verloc/measurement.rs +++ b/common/mixnode-common/src/verloc/measurement.rs @@ -1,259 +1,35 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use nym_crypto::asymmetric::identity; -use serde::{Serialize, Serializer}; -use std::cmp::Ordering; -use std::fmt::{self, Display, Formatter}; -use std::sync::Arc; -use std::time::Duration; -use tokio::sync::RwLock; +use nym_node_http_api::state::metrics::{SharedVerlocStats, VerlocNodeResult}; +use std::mem; +use time::OffsetDateTime; -#[derive(Clone, Default)] -pub struct AtomicVerlocResult { - inner: Arc>, +pub(crate) trait VerlocStatsUpdateExt { + async fn start_new_measurements(&self, nodes_to_test: usize); + + async fn append_measurement_results(&self, new_data: Vec); + + async fn finish_measurements(&self); } -#[derive(Debug, Clone, Serialize, Default)] -pub struct VerlocResult { - total_tested: usize, - #[serde(with = "humantime_serde")] - run_started: Option, - #[serde(with = "humantime_serde")] - run_finished: Option, - results: Vec, -} - -impl AtomicVerlocResult { - pub(crate) fn new() -> Self { - AtomicVerlocResult { - inner: Arc::new(RwLock::new(VerlocResult { - total_tested: 0, - run_started: None, - run_finished: None, - results: Vec::new(), - })), - } +impl VerlocStatsUpdateExt for SharedVerlocStats { + async fn start_new_measurements(&self, nodes_to_test: usize) { + let mut guard = self.write().await; + guard.previous_run_data = mem::take(&mut guard.current_run_data); + guard.current_run_data.nodes_tested = nodes_to_test; } - pub(crate) async fn reset_results(&self, new_tested: usize) { - let mut write_permit = self.inner.write().await; - write_permit.total_tested = new_tested; - write_permit.run_started = Some(std::time::SystemTime::now()); - write_permit.run_finished = None; - write_permit.results = Vec::new() - } - - pub(crate) async fn append_results(&self, mut new_data: Vec) { - let mut write_permit = self.inner.write().await; - write_permit.results.append(&mut new_data); + async fn append_measurement_results(&self, mut new_data: Vec) { + let mut write_permit = self.write().await; + write_permit.current_run_data.results.append(&mut new_data); // make sure the data always stays in order. // TODO: considering the front of the results is guaranteed to be sorted, should perhaps // a non-default sorting algorithm be used? - write_permit.results.sort() + write_permit.current_run_data.results.sort() } - pub(crate) async fn finish_measurements(&self) { - self.inner.write().await.run_finished = Some(std::time::SystemTime::now()); - } - - // Considering that on every read we will need to clone data regardless, let's make our - // lives simpler and clone it here rather than deal with lifetime of the permit - pub async fn clone_data(&self) -> VerlocResult { - self.inner.read().await.clone() - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize)] -pub struct Verloc { - #[serde(serialize_with = "serialize_identity_as_string")] - pub identity: identity::PublicKey, - pub latest_measurement: Option, -} - -fn serialize_identity_as_string( - identity: &identity::PublicKey, - serializer: S, -) -> Result -where - S: Serializer, -{ - serializer.serialize_str(&identity.to_base58_string()) -} - -impl Display for Verloc { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if let Some(measurement) = self.latest_measurement { - write!(f, "{} - {}", self.identity, measurement) - } else { - write!(f, "{} - COULD NOT MEASURE", self.identity) - } - } -} - -impl PartialOrd for Verloc { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for Verloc { - fn cmp(&self, other: &Self) -> Ordering { - // if both have measurement, compare measurements - // then if only one have measurement, prefer that one - // completely ignore identity as it makes no sense to order by it - if let Some(self_measurement) = &self.latest_measurement { - if let Some(other_measurement) = &other.latest_measurement { - self_measurement.cmp(other_measurement) - } else { - Ordering::Less - } - } else if other.latest_measurement.is_some() { - Ordering::Greater - } else { - Ordering::Equal - } - } -} - -impl Verloc { - pub(crate) fn new( - identity: identity::PublicKey, - latest_measurement: Option, - ) -> Self { - Verloc { - identity, - latest_measurement, - } - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize)] -pub struct Measurement { - #[serde(serialize_with = "humantime_serde::serialize")] - pub minimum: Duration, - #[serde(serialize_with = "humantime_serde::serialize")] - pub mean: Duration, - #[serde(serialize_with = "humantime_serde::serialize")] - pub maximum: Duration, - #[serde(serialize_with = "humantime_serde::serialize")] - pub standard_deviation: Duration, -} - -impl Measurement { - pub(crate) fn new(raw_results: &[Duration]) -> Self { - let minimum = *raw_results.iter().min().expect("didn't get any results!"); - let maximum = *raw_results.iter().max().expect("didn't get any results!"); - - let mean = Self::duration_mean(raw_results); - let standard_deviation = Self::duration_standard_deviation(raw_results, mean); - - Measurement { - minimum, - mean, - maximum, - standard_deviation, - } - } - - fn duration_mean(data: &[Duration]) -> Duration { - let sum = data.iter().sum::(); - let count = data.len() as u32; - - sum / count - } - - fn duration_standard_deviation(data: &[Duration], mean: Duration) -> Duration { - let variance_micros = data - .iter() - .map(|&value| { - // make sure we don't underflow - let diff = if mean > value { - mean - value - } else { - value - mean - }; - // we don't need nanos precision - let diff_micros = diff.as_micros(); - diff_micros * diff_micros - }) - .sum::() - / data.len() as u128; - - // we shouldn't really overflow as our differences shouldn't be larger than couple seconds at the worst possible case scenario - let std_deviation_micros = (variance_micros as f64).sqrt() as u64; - Duration::from_micros(std_deviation_micros) - } -} - -impl Display for Measurement { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!( - f, - "rtt min/avg/max/mdev = {:?} / {:?} / {:?} / {:?}", - self.minimum, self.mean, self.maximum, self.standard_deviation - ) - } -} - -impl PartialOrd for Measurement { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for Measurement { - fn cmp(&self, other: &Self) -> Ordering { - // minimum value is most important, then look at standard deviation, then mean and finally maximum - let min_cmp = self.minimum.cmp(&other.minimum); - if min_cmp != Ordering::Equal { - return min_cmp; - } - let std_dev_cmp = self.standard_deviation.cmp(&other.standard_deviation); - if std_dev_cmp != Ordering::Equal { - return std_dev_cmp; - } - let std_dev_cmp = self.mean.cmp(&other.mean); - if std_dev_cmp != Ordering::Equal { - return std_dev_cmp; - } - self.maximum.cmp(&other.maximum) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn sorting_vec_of_verlocs() { - let some_identity = - identity::PublicKey::from_base58_string("Be9wH7xuXBRJAuV1pC7MALZv6a61RvWQ3SypsNarqTt") - .unwrap(); - let no_measurement = Verloc::new(some_identity, None); - let low_min = Verloc::new( - some_identity, - Some(Measurement { - minimum: Duration::from_millis(42), - mean: Duration::from_millis(43), - maximum: Duration::from_millis(44), - standard_deviation: Duration::from_millis(45), - }), - ); - let higher_min = Verloc::new( - some_identity, - Some(Measurement { - minimum: Duration::from_millis(420), - mean: Duration::from_millis(430), - maximum: Duration::from_millis(440), - standard_deviation: Duration::from_millis(450), - }), - ); - - let mut vec_verloc = vec![no_measurement, low_min, no_measurement, higher_min]; - vec_verloc.sort(); - - let expected_sorted = vec![low_min, higher_min, no_measurement, no_measurement]; - assert_eq!(expected_sorted, vec_verloc); + async fn finish_measurements(&self) { + self.write().await.current_run_data.run_finished = Some(OffsetDateTime::now_utc()) } } diff --git a/common/mixnode-common/src/verloc/mod.rs b/common/mixnode-common/src/verloc/mod.rs index 8fa66e3445..25c7bd8881 100644 --- a/common/mixnode-common/src/verloc/mod.rs +++ b/common/mixnode-common/src/verloc/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::verloc::listener::PacketListener; @@ -9,6 +9,7 @@ use log::*; use nym_bin_common::version_checker::{self, parse_version}; use nym_crypto::asymmetric::identity; use nym_network_defaults::mainnet::NYM_API; +use nym_node_http_api::state::metrics::{SharedVerlocStats, VerlocNodeResult}; use nym_task::TaskClient; use rand::seq::SliceRandom; use rand::thread_rng; @@ -20,7 +21,9 @@ use tokio::task::JoinHandle; use tokio::time::sleep; use url::Url; -pub use crate::verloc::measurement::{AtomicVerlocResult, Verloc, VerlocResult}; +use measurement::VerlocStatsUpdateExt; + +// pub use crate::verloc::measurement::{AtomicVerlocResult, Verloc, VerlocResult}; pub mod error; pub(crate) mod listener; @@ -179,7 +182,7 @@ pub struct VerlocMeasurer { // then it definitely cannot be constructed here and probably will need to be passed from outside, // as mixnodes/gateways would already be using an instance of said client. validator_client: nym_validator_client::NymApiClient, - results: AtomicVerlocResult, + state: SharedVerlocStats, } impl VerlocMeasurer { @@ -210,10 +213,14 @@ impl VerlocMeasurer { config.nym_api_urls[0].clone(), ), config, - results: AtomicVerlocResult::new(), + state: SharedVerlocStats::default(), } } + pub fn set_shared_state(&mut self, state: SharedVerlocStats) { + self.state = state; + } + fn use_next_nym_api(&mut self) { if self.config.nym_api_urls.len() == 1 { warn!("There's only a single validator API available - it won't be possible to use a different one"); @@ -225,10 +232,6 @@ impl VerlocMeasurer { .change_nym_api(self.config.nym_api_urls[self.currently_used_api].clone()) } - pub fn get_verloc_results_pointer(&self) -> AtomicVerlocResult { - self.results.clone() - } - fn start_listening(&self) -> JoinHandle<()> { let packet_listener = Arc::clone(&self.packet_listener); tokio::spawn(packet_listener.run()) @@ -270,7 +273,7 @@ impl VerlocMeasurer { while !shutdown_listener.is_shutdown() { tokio::select! { measurement_result = measurement_chunk.next() => { - let Some(result) = measurement_result else { + let Some(result) = measurement_result else { // if the stream has finished, it means we got everything we could have gotten break }; @@ -278,18 +281,16 @@ impl VerlocMeasurer { // if we receive JoinError it means the task failed to get executed, so either there's a bigger issue with tokio // or there was a panic inside the task itself. In either case, we should just terminate ourselves. let execution_result = result.expect("the measurement task panicked!"); + let identity = execution_result.1; + let measurement_result = match execution_result.0 { Err(err) => { - debug!( - "Failed to perform measurement for {} - {}", - execution_result.1.to_base58_string(), - err - ); + debug!("Failed to perform measurement for {identity}: {err}"); None } Ok(result) => Some(result), }; - chunk_results.push(Verloc::new(execution_result.1, measurement_result)); + chunk_results.push(VerlocNodeResult::new(identity, measurement_result)); }, _ = shutdown_listener.recv() => { trace!("Shutdown received while measuring"); @@ -299,7 +300,7 @@ impl VerlocMeasurer { } // update the results vector with chunks as they become available (by default every 50 nodes) - self.results.append_results(chunk_results).await; + self.state.append_measurement_results(chunk_results).await; } MeasurementOutcome::Done @@ -358,7 +359,7 @@ impl VerlocMeasurer { .collect::>(); // on start of each run remove old results - self.results.reset_results(tested_nodes.len()).await; + self.state.start_new_measurements(tested_nodes.len()).await; if let MeasurementOutcome::Shutdown = self.perform_measurement(tested_nodes).await { log::trace!("Shutting down after aborting measurements"); @@ -366,7 +367,7 @@ impl VerlocMeasurer { } // write current time to "run finished" field - self.results.finish_measurements().await; + self.state.finish_measurements().await; info!( "Finished performing verloc measurements. The next one will happen in {:?}", diff --git a/common/mixnode-common/src/verloc/sender.rs b/common/mixnode-common/src/verloc/sender.rs index 8940738bcf..1549af972f 100644 --- a/common/mixnode-common/src/verloc/sender.rs +++ b/common/mixnode-common/src/verloc/sender.rs @@ -1,11 +1,11 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::verloc::error::RttError; -use crate::verloc::measurement::Measurement; use crate::verloc::packet::{EchoPacket, ReplyPacket}; use log::*; use nym_crypto::asymmetric::identity; +use nym_node_http_api::state::metrics::VerlocMeasurement; use nym_task::TaskClient; use rand::{thread_rng, Rng}; use std::net::SocketAddr; @@ -82,7 +82,7 @@ impl PacketSender { pub(super) async fn send_packets_to_node( self: Arc, tested_node: TestedNode, - ) -> Result { + ) -> Result { let mut shutdown_listener = self.shutdown_listener.fork(tested_node.address.to_string()); shutdown_listener.mark_as_success(); @@ -208,6 +208,6 @@ impl PacketSender { sleep(self.delay_between_packets).await; } - Ok(Measurement::new(&results)) + Ok(VerlocMeasurement::new(&results)) } } diff --git a/common/nymsphinx/addressing/src/lib.rs b/common/nymsphinx/addressing/src/lib.rs index d30f0bde57..0d5e26b92a 100644 --- a/common/nymsphinx/addressing/src/lib.rs +++ b/common/nymsphinx/addressing/src/lib.rs @@ -3,3 +3,6 @@ pub mod clients; pub mod nodes; + +pub use clients::Recipient; +pub use nodes::NodeIdentity; diff --git a/common/task/src/manager.rs b/common/task/src/manager.rs index 5cd0254a79..523d22aae5 100644 --- a/common/task/src/manager.rs +++ b/common/task/src/manager.rs @@ -348,7 +348,7 @@ impl TaskClient { "unnamed-TaskClient".to_string() }; - log!(target: target, level, "{msg}") + log!(target: target, level, "{}", format!("[{target}] {msg}")) } #[must_use] diff --git a/common/topology/src/gateway.rs b/common/topology/src/gateway.rs index e8b61eedf6..60624796c6 100644 --- a/common/topology/src/gateway.rs +++ b/common/topology/src/gateway.rs @@ -205,10 +205,10 @@ impl<'a> TryFrom<&'a DescribedGateway> for Node { clients_ws_port: self_described.mixnet_websockets.ws_port, clients_wss_port: self_described.mixnet_websockets.wss_port, identity_key: identity::PublicKey::from_base58_string( - &self_described.host_information.keys.ed25519, + &self_described.host_information.keys.ed25519_identity, )?, sphinx_key: encryption::PublicKey::from_base58_string( - &self_described.host_information.keys.x25519, + &self_described.host_information.keys.x25519_sphinx, )?, version: self_described .build_information diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index f4c034c0d0..7eb2a26b58 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -10,7 +10,7 @@ license.workspace = true chrono = { version = "0.4.31", features = ["serde"] } clap = { workspace = true, features = ["cargo", "derive"] } dotenvy = { workspace = true } -humantime-serde = "1.0" +humantime-serde = { workspace = true } isocountry = "0.3.2" itertools = "0.10.3" log = { workspace = true } diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index 01d1aaf9c8..4ec9d74844 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -18,7 +18,6 @@ rust-version = "1.70" [dependencies] anyhow = { workspace = true } async-trait = { workspace = true } -atty = "0.2" bip39 = { workspace = true } bs58 = { workspace = true } clap = { workspace = true, features = ["cargo", "derive"] } @@ -27,11 +26,10 @@ dashmap = { workspace = true } dirs = "4.0" dotenvy = { workspace = true } futures = { workspace = true } -humantime-serde = "1.0.1" +humantime-serde = { workspace = true } ipnetwork = "0.16" log = { workspace = true } once_cell = "1.7.2" -pretty_env_logger = "0.4" rand = "0.7" serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } @@ -51,14 +49,13 @@ tokio = { workspace = true, features = [ "time", ] } tokio-stream = { version = "0.1.11", features = ["fs"] } -tokio-tungstenite = { version = "0.20.1" } +tokio-tungstenite = { vworkspace = true } tokio-util = { workspace = true, features = ["codec"] } url = { workspace = true, features = ["serde"] } time = { workspace = true } zeroize = { workspace = true } # internal -nym-node = { path = "../nym-node" } nym-api-requests = { path = "../nym-api/nym-api-requests" } nym-bin-common = { path = "../common/bin-common", features = ["output_format"] } @@ -71,6 +68,7 @@ nym-mixnet-client = { path = "../common/client-libs/mixnet-client" } nym-mixnode-common = { path = "../common/mixnode-common" } nym-network-defaults = { path = "../common/network-defaults" } nym-network-requester = { path = "../service-providers/network-requester" } +nym-node-http-api = { path = "../nym-node/nym-node-http-api" } nym-pemstore = { path = "../common/pemstore" } nym-sphinx = { path = "../common/nymsphinx" } nym-statistics-common = { path = "../common/statistics" } @@ -80,15 +78,13 @@ nym-validator-client = { path = "../common/client-libs/validator-client" } nym-ip-packet-router = { path = "../service-providers/ip-packet-router" } nym-wireguard = { path = "../common/wireguard", optional = true } +nym-wireguard-types = { path = "../common/wireguard-types", default-features = false } + defguard_wireguard_rs = { git = "https://github.com/neacsu/wireguard-rs.git", rev = "c2cd0c1119f699f4bc43f5e6ffd6fc242caa42ed", optional = true } -[dev-dependencies] -tower = "0.4.13" -rand = "0.8.5" -hyper = "0.14.27" [build-dependencies] -tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] } +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } sqlx = { workspace = true, features = [ "runtime-tokio-rustls", "sqlite", diff --git a/gateway/src/commands/helpers.rs b/gateway/src/commands/helpers.rs index 2bd3f3834c..ff487dc7c2 100644 --- a/gateway/src/commands/helpers.rs +++ b/gateway/src/commands/helpers.rs @@ -2,20 +2,23 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::commands::upgrade_helpers; -use crate::config::default_config_filepath; -use crate::config::persistence::paths::{ - default_ip_packet_router_data_dir, default_network_requester_data_dir, -}; -use crate::config::Config; -use crate::error::GatewayError; use log::{error, info}; -use nym_bin_common::version_checker; use nym_config::{save_formatted_config_to_file, OptionalSet}; use nym_crypto::asymmetric::identity; +use nym_gateway::config::default_config_filepath; +use nym_gateway::config::persistence::paths::{ + default_ip_packet_router_data_dir, default_network_requester_data_dir, +}; +use nym_gateway::config::Config; +use nym_gateway::error::GatewayError; +use nym_gateway::helpers::{ + override_ip_packet_router_config, override_network_requester_config, + OverrideIpPacketRouterConfig, OverrideNetworkRequesterConfig, +}; use nym_network_defaults::mainnet; use nym_network_defaults::var_names::NYXD; use nym_network_defaults::var_names::{BECH32_PREFIX, NYM_API, STATISTICS_SERVICE_DOMAIN_ADDRESS}; -use nym_network_requester::config::BaseClientConfig; + use nym_network_requester::{ generate_new_client_keys, set_active_gateway, setup_fs_gateways_storage, setup_gateway, GatewaySetup, OnDiskKeys, @@ -102,21 +105,11 @@ impl OverrideConfig { } } -#[derive(Default, Debug)] -pub(crate) struct OverrideNetworkRequesterConfig { - pub(crate) fastmode: bool, - pub(crate) no_cover: bool, - pub(crate) medium_toggle: bool, - - pub(crate) open_proxy: Option, - - pub(crate) enable_statistics: Option, - pub(crate) statistics_recipient: Option, -} - -#[derive(Default, Debug)] -pub(crate) struct OverrideIpPacketRouterConfig { - // TODO +pub(crate) fn try_override_config>( + config: Config, + override_args: O, +) -> Result { + override_args.into().do_override(config) } /// Ensures that a given bech32 address is valid @@ -136,30 +129,6 @@ pub(crate) fn ensure_correct_bech32_prefix(address: &AccountId) -> Result<(), Ga Ok(()) } -// this only checks compatibility between config the binary. It does not take into consideration -// network version. It might do so in the future. -pub(crate) fn ensure_config_version_compatibility(cfg: &Config) -> Result<(), GatewayError> { - let binary_version = env!("CARGO_PKG_VERSION"); - let config_version = &cfg.gateway.version; - - if binary_version == config_version { - Ok(()) - } else if version_checker::is_minor_version_compatible(binary_version, config_version) { - log::warn!( - "The gateway binary has different version than what is specified in config file! {binary_version} and {config_version}. \ - But, they are still semver compatible. However, consider running the `upgrade` command."); - Ok(()) - } else { - log::error!( - "The gateway binary has different version than what is specified in config file! {binary_version} and {config_version}. \ - And they are semver incompatible! - please run the `upgrade` command before attempting `run` again"); - Err(GatewayError::LocalVersionCheckFailure { - binary_version: binary_version.to_owned(), - config_version: config_version.to_owned(), - }) - } -} - pub(crate) fn try_load_current_config(id: &str) -> Result { upgrade_helpers::try_upgrade_config(id)?; @@ -183,77 +152,6 @@ fn make_ip_id(gateway_id: &str) -> String { format!("{gateway_id}-ip-packet-router") } -// NOTE: make sure this is in sync with service-providers/network-requester/src/cli/mod.rs::override_config -pub(crate) fn override_network_requester_config( - mut cfg: nym_network_requester::Config, - opts: Option, -) -> nym_network_requester::Config { - let Some(opts) = opts else { return cfg }; - - // as of 12.09.23 the below is true (not sure how this comment will rot in the future) - // medium_toggle: - // - sets secondary packet size to 16kb - // - disables poisson distribution of the main traffic stream - // - sets the cover traffic stream to 1 packet / 5s (on average) - // - disables per hop delay - // - // fastmode (to be renamed to `fast-poisson`): - // - sets average per hop delay to 10ms - // - sets the cover traffic stream to 1 packet / 2000s (on average); for all intents and purposes it disables the stream - // - sets the poisson distribution of the main traffic stream to 4ms, i.e. 250 packets / s on average - // - // no_cover: - // - disables poisson distribution of the main traffic stream - // - disables the secondary cover traffic stream - - // disable poisson rate in the BASE client if the NR option is enabled - if cfg.network_requester.disable_poisson_rate { - cfg.set_no_poisson_process(); - } - - // those should be enforced by `clap` when parsing the arguments - if opts.medium_toggle { - assert!(!opts.fastmode); - assert!(!opts.no_cover); - - cfg.set_medium_toggle(); - } - - cfg.with_base( - BaseClientConfig::with_high_default_traffic_volume, - opts.fastmode, - ) - .with_base(BaseClientConfig::with_disabled_cover_traffic, opts.no_cover) - .with_optional( - nym_network_requester::Config::with_open_proxy, - opts.open_proxy, - ) - .with_optional( - nym_network_requester::Config::with_enabled_statistics, - opts.enable_statistics, - ) - .with_optional( - nym_network_requester::Config::with_statistics_recipient, - opts.statistics_recipient, - ) -} - -// NOTE: make sure this is in sync with service-providers/ip-packet-router/src/cli/mod.rs::override_config -pub(crate) fn override_ip_packet_router_config( - mut cfg: nym_ip_packet_router::Config, - opts: Option, -) -> nym_ip_packet_router::Config { - let Some(_opts) = opts else { return cfg }; - - // disable poisson rate in the BASE client if the IPR option is enabled - if cfg.ip_packet_router.disable_poisson_rate { - info!("Disabling poisson rate for ip packet router"); - cfg.set_no_poisson_process(); - } - - cfg -} - pub(crate) async fn initialise_local_network_requester( gateway_config: &Config, opts: OverrideNetworkRequesterConfig, diff --git a/gateway/src/commands/init.rs b/gateway/src/commands/init.rs index b2be9129d4..4c43236b7b 100644 --- a/gateway/src/commands/init.rs +++ b/gateway/src/commands/init.rs @@ -1,326 +1,22 @@ -// Copyright 2020-2023 - Nym Technologies SA +// Copyright 2020-2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::commands::helpers::{ - initialise_local_ip_packet_router, initialise_local_network_requester, - OverrideNetworkRequesterConfig, -}; -use crate::config::{default_config_directory, default_config_filepath, default_data_directory}; -use crate::node::helpers::node_details; -use crate::{commands::helpers::OverrideConfig, config::Config, OutputFormat}; use anyhow::bail; use clap::Args; -use nym_crypto::asymmetric::{encryption, identity}; -use std::net::IpAddr; -use std::path::PathBuf; -use std::{fs, io}; - -use super::helpers::OverrideIpPacketRouterConfig; +use colored::Colorize; +#[allow(dead_code)] #[derive(Args, Clone, Debug)] pub struct Init { /// Id of the gateway we want to create config for #[clap(long)] - id: String, - - /// The listening address on which the gateway will be receiving sphinx packets and listening for client data - #[clap(long, alias = "host")] - listening_address: IpAddr, - - /// Comma separated list of public ip addresses that will announced to the nym-api and subsequently to the clients. - /// In nearly all circumstances, it's going to be identical to the address you're going to use for bonding. - #[clap(long, value_delimiter = ',')] - public_ips: Option>, - - /// Optional hostname associated with this gateway that will announced to the nym-api and subsequently to the clients - #[clap(long)] - hostname: Option, - - /// The port on which the gateway will be listening for sphinx packets - #[clap(long)] - mix_port: Option, - - /// The port on which the gateway will be listening for clients gateway-requests - #[clap(long)] - clients_port: Option, - - /// Path to sqlite database containing all gateway persistent data - #[clap(long)] - datastore: Option, - - /// Comma separated list of endpoints of nym APIs - #[clap(long, alias = "validator_apis", value_delimiter = ',')] - // the alias here is included for backwards compatibility (1.1.4 and before) - nym_apis: Option>, - - /// Comma separated list of endpoints of the validator - #[clap( - long, - alias = "validators", - alias = "nyxd_validators", - value_delimiter = ',', - hide = true - )] - // the alias here is included for backwards compatibility (1.1.4 and before) - nyxd_urls: Option>, - - /// Cosmos wallet mnemonic needed for double spending protection - #[clap(long)] - mnemonic: Option, - - /// Set this gateway to work only with coconut credentials; that would disallow clients to - /// bypass bandwidth credential requirement - #[clap(long, hide = true)] - only_coconut_credentials: Option, - - /// Enable/disable gateway anonymized statistics that get sent to a statistics aggregator server - #[clap(long)] - enabled_statistics: Option, - - /// URL where a statistics aggregator is running. The default value is a Nym aggregator server - #[clap(long)] - statistics_service_url: Option, - - /// Allows this gateway to run an embedded network requester for minimal network overhead - #[clap(long)] - with_network_requester: bool, - - /// Allows this gateway to run an embedded network requester for minimal network overhead - #[clap(long, hide = true)] - with_ip_packet_router: bool, - - // ##### NETWORK REQUESTER FLAGS ##### - /// Specifies whether this network requester should run in 'open-proxy' mode - #[clap(long, requires = "with_network_requester")] - open_proxy: Option, - - /// Enable service anonymized statistics that get sent to a statistics aggregator server - #[clap(long, requires = "with_network_requester")] - enable_statistics: Option, - - /// Mixnet client address where a statistics aggregator is running. The default value is a Nym - /// aggregator client - #[clap(long, requires = "with_network_requester")] - statistics_recipient: Option, - - /// Mostly debug-related option to increase default traffic rate so that you would not need to - /// modify config post init - #[clap( - long, - hide = true, - conflicts_with = "medium_toggle", - requires = "with_network_requester" - )] - fastmode: bool, - - /// Disable loop cover traffic and the Poisson rate limiter (for debugging only) - #[clap( - long, - hide = true, - conflicts_with = "medium_toggle", - requires = "with_network_requester" - )] - no_cover: bool, - - /// Enable medium mixnet traffic, for experiments only. - /// This includes things like disabling cover traffic, no per hop delays, etc. - #[clap( - long, - hide = true, - conflicts_with = "no_cover", - conflicts_with = "fastmode", - requires = "with_network_requester" - )] - medium_toggle: bool, - - #[clap(short, long, default_value_t = OutputFormat::default())] - output: OutputFormat, + id: Option, } -impl From for OverrideConfig { - fn from(init_config: Init) -> Self { - OverrideConfig { - listening_address: Some(init_config.listening_address), - public_ips: init_config.public_ips, - hostname: init_config.hostname, - mix_port: init_config.mix_port, - clients_port: init_config.clients_port, - datastore: init_config.datastore, - nym_apis: init_config.nym_apis, - mnemonic: init_config.mnemonic, - - enabled_statistics: init_config.enabled_statistics, - statistics_service_url: init_config.statistics_service_url, - - nyxd_urls: init_config.nyxd_urls, - only_coconut_credentials: init_config.only_coconut_credentials, - with_network_requester: Some(init_config.with_network_requester), - with_ip_packet_router: Some(init_config.with_ip_packet_router), - } - } -} - -impl<'a> From<&'a Init> for OverrideNetworkRequesterConfig { - fn from(value: &'a Init) -> Self { - OverrideNetworkRequesterConfig { - fastmode: value.fastmode, - no_cover: value.no_cover, - medium_toggle: value.medium_toggle, - open_proxy: value.open_proxy, - enable_statistics: value.enable_statistics, - statistics_recipient: value.statistics_recipient.clone(), - } - } -} - -impl From<&Init> for OverrideIpPacketRouterConfig { - fn from(_value: &Init) -> Self { - OverrideIpPacketRouterConfig {} - } -} - -fn init_paths(id: &str) -> io::Result<()> { - fs::create_dir_all(default_data_directory(id))?; - fs::create_dir_all(default_config_directory(id)) -} - -pub async fn execute(args: Init) -> anyhow::Result<()> { - eprintln!("Initialising gateway {}...", args.id); - let output = args.output; - - let already_init = if default_config_filepath(&args.id).exists() { - eprintln!( - "Gateway \"{}\" was already initialised before! Config information will be \ - overwritten (but keys will be kept)!", - args.id - ); - true - } else { - init_paths(&args.id)?; - false - }; - - // Initialising the config structure is just overriding a default constructed one - let fresh_config = Config::new(&args.id); - let nr_opts = (&args).into(); - let ip_opts = (&args).into(); - let mut config = OverrideConfig::from(args).do_override(fresh_config)?; - - // if gateway was already initialised, don't generate new keys, et al. - if !already_init { - let mut rng = rand::rngs::OsRng; - - let identity_keys = identity::KeyPair::new(&mut rng); - let sphinx_keys = encryption::KeyPair::new(&mut rng); - - if let Err(err) = nym_pemstore::store_keypair( - &identity_keys, - &nym_pemstore::KeyPairPath::new( - config.storage_paths.private_identity_key(), - config.storage_paths.public_identity_key(), - ), - ) { - bail!("failed to save the identity keys: {err}") - } - - if let Err(err) = nym_pemstore::store_keypair( - &sphinx_keys, - &nym_pemstore::KeyPairPath::new( - config.storage_paths.private_encryption_key(), - config.storage_paths.public_encryption_key(), - ), - ) { - bail!("failed to save the sphinx keys: {err}") - } - - if config.network_requester.enabled { - initialise_local_network_requester(&config, nr_opts, *identity_keys.public_key()) - .await?; - } - - if config.ip_packet_router.enabled { - initialise_local_ip_packet_router(&config, ip_opts, *identity_keys.public_key()) - .await?; - } - - eprintln!("Saved identity and mixnet sphinx keypairs"); - } - - let config_save_location = config.default_location(); - if let Err(err) = config.save_to_default_location() { - bail!("failed to save the config file: {err}") - } - config.save_path = Some(config_save_location.clone()); - - eprintln!( - "Saved configuration file to {}", - config_save_location.display() - ); - eprintln!("Gateway configuration completed.\n\n\n"); - - output.to_stdout(&node_details(&config).await?); - - Ok(()) -} - -#[cfg(test)] -mod tests { - use nym_network_defaults::var_names::BECH32_PREFIX; - - use crate::node::{storage::InMemStorage, Gateway}; - - use super::*; - - #[tokio::test] - async fn create_gateway_with_in_mem_storage() { - let args = Init { - id: "foo-id".to_string(), - listening_address: "1.1.1.1".parse().unwrap(), - public_ips: None, - hostname: None, - mix_port: Some(42), - clients_port: Some(43), - datastore: Some("/foo-datastore".parse().unwrap()), - nym_apis: None, - mnemonic: None, - statistics_service_url: None, - enabled_statistics: None, - nyxd_urls: None, - only_coconut_credentials: None, - output: Default::default(), - with_network_requester: false, - with_ip_packet_router: false, - open_proxy: None, - enable_statistics: None, - statistics_recipient: None, - fastmode: false, - no_cover: false, - medium_toggle: false, - }; - std::env::set_var(BECH32_PREFIX, "n"); - - let fresh_config = Config::new(&args.id); - let config = OverrideConfig::from(args) - .do_override(fresh_config) - .unwrap(); - - let (identity_keys, sphinx_keys) = { - let mut rng = rand::rngs::OsRng; - ( - identity::KeyPair::new(&mut rng), - encryption::KeyPair::new(&mut rng), - ) - }; - - // The test is really if this instantiates with InMemStorage without panics - let _gateway = Gateway::new_from_keys_and_storage( - config, - None, - None, - identity_keys, - sphinx_keys, - InMemStorage, - ) - .await; - } +pub async fn execute(_args: Init) -> anyhow::Result<()> { + bail!( + "standalone mixnode initialisation has been removed - please initialise a `nym-node` instead" + .red() + .bold() + ) } diff --git a/gateway/src/commands/mod.rs b/gateway/src/commands/mod.rs index 4f9d46d7df..3cd84a1c0a 100644 --- a/gateway/src/commands/mod.rs +++ b/gateway/src/commands/mod.rs @@ -4,7 +4,10 @@ use crate::Cli; use clap::CommandFactory; use clap::Subcommand; +use log::warn; use nym_bin_common::completions::{fig_generate, ArgShell}; +use std::io::IsTerminal; +use std::time::Duration; pub(crate) mod build_info; pub(crate) mod helpers; @@ -25,7 +28,7 @@ pub(crate) enum Commands { NodeDetails(node_details::NodeDetails), /// Starts the gateway - Run(run::Run), + Run(Box), /// Add network requester support to this gateway // essentially an option to include NR without having to setup fresh gateway @@ -52,10 +55,17 @@ pub(crate) enum Commands { pub(crate) async fn execute(args: Cli) -> anyhow::Result<()> { let bin_name = "nym-gateway"; + warn!("standalone gateways have been deprecated - please consider migrating it to a `nym-node` via `nym-node migrate gateway` command"); + if std::io::stdout().is_terminal() { + // if user is running it in terminal session, + // introduce the delay, so they'd notice the message + tokio::time::sleep(Duration::from_secs(1)).await + } + match args.command { Commands::Init(m) => init::execute(m).await?, Commands::NodeDetails(m) => node_details::execute(m).await?, - Commands::Run(m) => run::execute(m).await?, + Commands::Run(m) => run::execute(*m).await?, Commands::SetupNetworkRequester(m) => setup_network_requester::execute(m).await?, Commands::SetupIpPacketRouter(m) => setup_ip_packet_router::execute(m).await?, Commands::Sign(m) => sign::execute(m)?, diff --git a/gateway/src/commands/node_details.rs b/gateway/src/commands/node_details.rs index 72840a8a7a..6bf2f6f330 100644 --- a/gateway/src/commands/node_details.rs +++ b/gateway/src/commands/node_details.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::commands::helpers::try_load_current_config; -use crate::node::helpers::node_details; use clap::Args; use nym_bin_common::output_format::OutputFormat; +use nym_gateway::helpers::node_details; #[derive(Args, Clone)] pub struct NodeDetails { diff --git a/gateway/src/commands/run.rs b/gateway/src/commands/run.rs index bc52ff056e..547045ebb7 100644 --- a/gateway/src/commands/run.rs +++ b/gateway/src/commands/run.rs @@ -1,22 +1,17 @@ // Copyright 2020-2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::commands::helpers::{ - ensure_config_version_compatibility, OverrideConfig, OverrideNetworkRequesterConfig, -}; -use crate::node::helpers::node_details; -use crate::support::config::build_config; +use crate::commands::helpers::{try_load_current_config, try_override_config, OverrideConfig}; use anyhow::bail; use clap::Args; use log::warn; use nym_bin_common::output_format::OutputFormat; use nym_config::helpers::SPECIAL_ADDRESSES; -use nym_node::error::NymNodeError; +use nym_gateway::helpers::{OverrideIpPacketRouterConfig, OverrideNetworkRequesterConfig}; +use nym_gateway::GatewayError; use std::net::IpAddr; use std::path::PathBuf; -use super::helpers::OverrideIpPacketRouterConfig; - #[derive(Args, Clone)] pub struct Run { /// Id of the gateway we want to run @@ -27,12 +22,12 @@ pub struct Run { #[arg(long, alias = "host")] listening_address: Option, - /// Comma separated list of public ip addresses that will announced to the nym-api and subsequently to the clients. + /// Comma separated list of public ip addresses that will be announced to the nym-api and subsequently to the clients. /// In nearly all circumstances, it's going to be identical to the address you're going to use for bonding. #[arg(long, value_delimiter = ',')] public_ips: Option>, - /// Optional hostname associated with this gateway that will announced to the nym-api and subsequently to the clients + /// Optional hostname associated with this gateway that will be announced to the nym-api and subsequently to the clients #[arg(long)] hostname: Option, @@ -196,7 +191,7 @@ fn check_public_ips(ips: &[IpAddr], local: bool) -> anyhow::Result<()> { for ip in ips { if SPECIAL_ADDRESSES.contains(ip) { if !local { - return Err(NymNodeError::InvalidPublicIp { address: *ip }.into()); + return Err(GatewayError::InvalidPublicIp { address: *ip }.into()); } suspicious_ip.push(ip); } @@ -225,12 +220,12 @@ pub async fn execute(args: Run) -> anyhow::Result<()> { let nr_opts = (&args).into(); let ip_opts = (&args).into(); - let config = build_config(id, args)?; - ensure_config_version_compatibility(&config)?; + let mut config = try_load_current_config(&args.id)?; + config = try_override_config(config, args)?; let public_ips = &config.host.public_ips; if public_ips.is_empty() { - return Err(NymNodeError::NoPublicIps.into()); + return Err(GatewayError::NoPublicIps.into()); } check_public_ips(public_ips, local)?; if config.gateway.clients_wss_port.is_some() && config.host.hostname.is_none() { @@ -241,13 +236,14 @@ pub async fn execute(args: Run) -> anyhow::Result<()> { show_binding_warning(config.gateway.listening_address); } - let node_details = node_details(&config).await?; let gateway = - crate::node::create_gateway(config, Some(nr_opts), Some(ip_opts), custom_mixnet).await?; + nym_gateway::create_gateway(config, Some(nr_opts), Some(ip_opts), custom_mixnet).await?; + let node_details = gateway.node_details().await?; eprintln!( "\nTo bond your gateway you will need to install the Nym wallet, go to https://nymtech.net/get-involved and select the Download button.\n\ Select the correct version and install it to your machine. You will need to provide some of the following: \n "); output.to_stdout(&node_details); - gateway.run().await + gateway.run().await?; + Ok(()) } diff --git a/gateway/src/commands/setup_ip_packet_router.rs b/gateway/src/commands/setup_ip_packet_router.rs index a08f94dea3..5f22e794e4 100644 --- a/gateway/src/commands/setup_ip_packet_router.rs +++ b/gateway/src/commands/setup_ip_packet_router.rs @@ -1,12 +1,10 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::commands::helpers::{ - initialise_local_ip_packet_router, try_load_current_config, OverrideIpPacketRouterConfig, -}; -use crate::node::helpers::load_public_key; +use crate::commands::helpers::{initialise_local_ip_packet_router, try_load_current_config}; use clap::Args; use nym_bin_common::output_format::OutputFormat; +use nym_gateway::helpers::{load_public_key, OverrideIpPacketRouterConfig}; use std::path::PathBuf; #[derive(Args, Clone)] diff --git a/gateway/src/commands/setup_network_requester.rs b/gateway/src/commands/setup_network_requester.rs index b9368ce2e0..ef87889139 100644 --- a/gateway/src/commands/setup_network_requester.rs +++ b/gateway/src/commands/setup_network_requester.rs @@ -1,13 +1,14 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::commands::helpers::{ - initialise_local_network_requester, try_load_current_config, OverrideNetworkRequesterConfig, -}; -use crate::node::helpers::load_public_key; +use crate::commands::helpers::{initialise_local_network_requester, try_load_current_config}; use clap::Args; +use log::warn; use nym_bin_common::output_format::OutputFormat; +use nym_gateway::helpers::{load_public_key, OverrideNetworkRequesterConfig}; +use std::io::IsTerminal; use std::path::PathBuf; +use std::time::Duration; #[derive(Args, Clone)] pub struct CmdArgs { @@ -77,6 +78,13 @@ impl<'a> From<&'a CmdArgs> for OverrideNetworkRequesterConfig { } pub async fn execute(args: CmdArgs) -> anyhow::Result<()> { + warn!("standalone gateways have been deprecated - please consider migrating it to a `nym-node` via `nym-node migrate gateway` command"); + if std::io::stdout().is_terminal() { + // if user is running it in terminal session, + // introduce the delay, so they'd notice the message + tokio::time::sleep(Duration::from_secs(1)).await + } + let mut config = try_load_current_config(&args.id)?; let opts = (&args).into(); diff --git a/gateway/src/commands/sign.rs b/gateway/src/commands/sign.rs index 552315614c..4aa5e16d1f 100644 --- a/gateway/src/commands/sign.rs +++ b/gateway/src/commands/sign.rs @@ -2,15 +2,14 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::commands::helpers::{ - ensure_config_version_compatibility, ensure_correct_bech32_prefix, OverrideConfig, + ensure_correct_bech32_prefix, try_load_current_config, try_override_config, OverrideConfig, }; -use crate::error::GatewayError; -use crate::node::helpers::load_identity_keys; -use crate::support::config::build_config; use anyhow::{bail, Result}; use clap::{ArgGroup, Args}; use nym_bin_common::output_format::OutputFormat; use nym_crypto::asymmetric::identity; +use nym_gateway::error::GatewayError; +use nym_gateway::helpers::load_identity_keys; use nym_types::helpers::ConsoleSigningOutput; use nym_validator_client::nyxd; @@ -120,8 +119,8 @@ fn print_signed_contract_msg( } pub fn execute(args: Sign) -> anyhow::Result<()> { - let config = build_config(args.id.clone(), OverrideConfig::default())?; - ensure_config_version_compatibility(&config)?; + let mut config = try_load_current_config(&args.id)?; + config = try_override_config(config, OverrideConfig::default())?; let output = args.output; let signed_target = SignedTarget::try_from(args)?; diff --git a/gateway/src/commands/upgrade_helpers.rs b/gateway/src/commands/upgrade_helpers.rs index f93350caaa..2de3e6ec32 100644 --- a/gateway/src/commands/upgrade_helpers.rs +++ b/gateway/src/commands/upgrade_helpers.rs @@ -1,13 +1,13 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::config::old_config_v1_1_20::ConfigV1_1_20; -use crate::config::old_config_v1_1_28::ConfigV1_1_28; -use crate::config::old_config_v1_1_29::ConfigV1_1_29; -use crate::config::old_config_v1_1_31::ConfigV1_1_31; -use crate::config::{default_config_filepath, Config}; -use crate::error::GatewayError; use log::info; +use nym_gateway::config::old_config_v1_1_20::ConfigV1_1_20; +use nym_gateway::config::old_config_v1_1_28::ConfigV1_1_28; +use nym_gateway::config::old_config_v1_1_29::ConfigV1_1_29; +use nym_gateway::config::old_config_v1_1_31::ConfigV1_1_31; +use nym_gateway::config::{default_config_filepath, Config}; +use nym_gateway::error::GatewayError; fn try_upgrade_v1_1_20_config(id: &str) -> Result { use nym_config::legacy_helpers::nym_config::MigrationNymConfig; diff --git a/gateway/src/config/mod.rs b/gateway/src/config/mod.rs index b872a1dffd..5a345279d1 100644 --- a/gateway/src/config/mod.rs +++ b/gateway/src/config/mod.rs @@ -1,30 +1,31 @@ // Copyright 2020-2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::config::persistence::paths::GatewayPaths; use crate::config::template::CONFIG_TEMPLATE; use log::{debug, warn}; use nym_bin_common::logging::LoggingSettings; use nym_config::defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT}; use nym_config::helpers::inaddr_any; +use nym_config::serde_helpers::{de_maybe_port, de_maybe_stringified}; use nym_config::{ must_get_home, read_config_from_toml_file, save_formatted_config_to_file, NymConfigTemplate, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, NYM_DIR, }; -use nym_network_defaults::mainnet; -use nym_node::config; -use serde::{Deserialize, Deserializer, Serialize}; +use nym_network_defaults::{mainnet, DEFAULT_NYM_NODE_HTTP_PORT, WG_PORT}; +use serde::{Deserialize, Serialize}; use std::io; -use std::net::{IpAddr, SocketAddr}; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::path::{Path, PathBuf}; use std::time::Duration; use url::Url; use zeroize::{Zeroize, ZeroizeOnDrop}; -pub(crate) mod old_config_v1_1_20; -pub(crate) mod old_config_v1_1_28; -pub(crate) mod old_config_v1_1_29; -pub(crate) mod old_config_v1_1_31; +pub use crate::config::persistence::paths::{GatewayPaths, WireguardPaths}; + +pub mod old_config_v1_1_20; +pub mod old_config_v1_1_28; +pub mod old_config_v1_1_29; +pub mod old_config_v1_1_31; pub mod persistence; mod template; @@ -41,18 +42,6 @@ const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000; const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16; const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100; -fn de_maybe_port<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let port = u16::deserialize(deserializer)?; - if port == 0 { - Ok(None) - } else { - Ok(Some(port)) - } -} - /// Derive default path to gateway's config directory. /// It should get resolved to `$HOME/.nym/gateways//config` pub fn default_config_directory>(id: P) -> PathBuf { @@ -86,16 +75,16 @@ pub struct Config { #[serde(skip)] pub(crate) save_path: Option, - pub host: config::Host, + pub host: Host, #[serde(default)] - pub http: config::Http, + pub http: Http, pub gateway: Gateway, - #[serde(default)] // currently not really used for anything useful - pub wireguard: config::Wireguard, + #[serde(default)] + pub wireguard: Wireguard, pub storage_paths: GatewayPaths, @@ -122,7 +111,7 @@ impl Config { let default_gateway = Gateway::new_default(id.as_ref()); Config { save_path: None, - host: config::Host { + host: Host { // this is a very bad default! public_ips: vec![default_gateway.listening_address], hostname: None, @@ -138,6 +127,32 @@ impl Config { } } + #[allow(clippy::too_many_arguments)] + pub fn externally_loaded( + host: impl Into, + http: impl Into, + gateway: impl Into, + wireguard: impl Into, + storage_paths: impl Into, + network_requester: impl Into, + ip_packet_router: impl Into, + logging: impl Into, + debug: impl Into, + ) -> Self { + Config { + save_path: None, + host: host.into(), + http: http.into(), + gateway: gateway.into(), + wireguard: wireguard.into(), + storage_paths: storage_paths.into(), + network_requester: network_requester.into(), + ip_packet_router: ip_packet_router.into(), + logging: logging.into(), + debug: debug.into(), + } + } + // simple wrapper that reads config file and assigns path location fn read_from_path>(path: P) -> io::Result { let path = path.as_ref(); @@ -147,9 +162,6 @@ impl Config { Ok(loaded) } - // currently this is dead code, but once we allow loading configs from custom paths - // well, we will have to be using it - #[allow(dead_code)] pub fn read_from_toml_file>(path: P) -> io::Result { Self::read_from_path(path) } @@ -285,6 +297,87 @@ impl Config { } } +// TODO: this is very much a WIP. we need proper ssl certificate support here +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Host { + /// Ip address(es) of this host, such as 1.1.1.1 that external clients will use for connections. + pub public_ips: Vec, + + /// Optional hostname of this node, for example nymtech.net. + // TODO: this is temporary. to be replaced by pulling the data directly from the certs. + #[serde(deserialize_with = "de_maybe_stringified")] + pub hostname: Option, +} + +impl Host { + pub fn validate(&self) -> bool { + if self.public_ips.is_empty() { + return false; + } + + true + } +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Http { + /// Socket address this node will use for binding its http API. + /// default: `0.0.0.0:8000` + pub bind_address: SocketAddr, + + /// Path to assets directory of custom landing page of this node. + #[serde(deserialize_with = "de_maybe_stringified")] + pub landing_page_assets_path: Option, +} + +impl Default for Http { + fn default() -> Self { + Http { + bind_address: SocketAddr::new( + IpAddr::V4(Ipv4Addr::UNSPECIFIED), + DEFAULT_NYM_NODE_HTTP_PORT, + ), + landing_page_assets_path: None, + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Wireguard { + /// Specifies whether the wireguard service is enabled on this node. + pub enabled: bool, + + /// Socket address this node will use for binding its wireguard interface. + /// default: `0.0.0.0:51822` + pub bind_address: SocketAddr, + + /// Port announced to external clients wishing to connect to the wireguard interface. + /// Useful in the instances where the node is behind a proxy. + pub announced_port: u16, + + /// The prefix denoting the maximum number of the clients that can be connected via Wireguard. + /// The maximum value for IPv4 is 32 and for IPv6 is 128 + pub private_network_prefix: u8, + + /// Paths for wireguard keys, client registries, etc. + pub storage_paths: WireguardPaths, +} + +impl Default for Wireguard { + fn default() -> Self { + Wireguard { + enabled: false, + bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), WG_PORT), + announced_port: WG_PORT, + private_network_prefix: 16, + storage_paths: WireguardPaths {}, + } + } +} + // we only really care about the mnemonic being zeroized #[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Zeroize, ZeroizeOnDrop)] pub struct Gateway { @@ -413,9 +506,11 @@ pub struct Debug { /// Delay between each subsequent presence data being sent. #[serde(with = "humantime_serde")] + // DEAD FIELD pub presence_sending_delay: Duration, /// Length of filenames for new client messages. + // DEAD FIELD pub stored_messages_filename_length: u16, /// Number of messages from offline client that can be pulled at once from the storage. diff --git a/gateway/src/config/old_config_v1_1_29.rs b/gateway/src/config/old_config_v1_1_29.rs index 48ca3d14fb..e3fd44a8c9 100644 --- a/gateway/src/config/old_config_v1_1_29.rs +++ b/gateway/src/config/old_config_v1_1_29.rs @@ -1,6 +1,7 @@ // Copyright 2020-2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use crate::config::Host; use nym_config::{ must_get_home, read_config_from_toml_file, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, NYM_DIR, }; @@ -113,7 +114,7 @@ impl From for ConfigV1_1_31 { save_path: value.save_path, // \/ ADDED - host: nym_node::config::Host { + host: Host { // this is a very bad default! public_ips: vec![value.gateway.listening_address], hostname: None, diff --git a/gateway/src/config/old_config_v1_1_31.rs b/gateway/src/config/old_config_v1_1_31.rs index 18841a4993..b0797a9eaf 100644 --- a/gateway/src/config/old_config_v1_1_31.rs +++ b/gateway/src/config/old_config_v1_1_31.rs @@ -1,11 +1,12 @@ // Copyright 2020-2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::config::persistence::paths::GatewayPaths; +use crate::config::persistence::paths::{GatewayPaths, WireguardPaths}; use nym_bin_common::logging::LoggingSettings; use nym_config::{ must_get_home, read_config_from_toml_file, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, NYM_DIR, }; +use nym_network_defaults::WG_PORT; use serde::{Deserialize, Deserializer, Serialize}; use std::io; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; @@ -14,7 +15,7 @@ use std::time::Duration; use url::Url; use super::persistence::paths::KeysPaths; -use super::{Config, Debug, Gateway, NetworkRequester}; +use super::{Config, Debug, Gateway, Host, Http, NetworkRequester, Wireguard}; const DEFAULT_GATEWAYS_DIR: &str = "gateways"; @@ -76,10 +77,10 @@ pub struct ConfigV1_1_31 { #[serde(skip)] pub(crate) save_path: Option, - pub host: nym_node::config::Host, + pub host: Host, #[serde(default)] - pub http: nym_node::config::Http, + pub http: Http, pub gateway: GatewayV1_1_31, @@ -124,12 +125,12 @@ impl From for Config { nyxd_urls: value.gateway.nyxd_urls, cosmos_mnemonic: value.gateway.cosmos_mnemonic, }, - wireguard: nym_node::config::Wireguard { + wireguard: Wireguard { enabled: value.wireguard.enabled, bind_address: value.wireguard.bind_address, announced_port: value.wireguard.announced_port, private_network_prefix: Default::default(), - storage_paths: nym_node::config::persistence::WireguardPaths { + storage_paths: WireguardPaths { // no fields (yet) }, }, @@ -241,11 +242,8 @@ impl Default for WireguardV1_1_31 { fn default() -> Self { Self { enabled: false, - bind_address: SocketAddr::new( - IpAddr::V4(Ipv4Addr::UNSPECIFIED), - nym_node::config::DEFAULT_WIREGUARD_PORT, - ), - announced_port: nym_node::config::DEFAULT_WIREGUARD_PORT, + bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), WG_PORT), + announced_port: WG_PORT, storage_paths: WireguardPathsV1_1_31 {}, } } diff --git a/gateway/src/config/persistence/paths.rs b/gateway/src/config/persistence/paths.rs index b4b79241c6..1a35263ef0 100644 --- a/gateway/src/config/persistence/paths.rs +++ b/gateway/src/config/persistence/paths.rs @@ -2,7 +2,8 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::config::{default_config_directory, default_data_directory}; -use serde::{Deserialize, Deserializer, Serialize}; +use nym_config::serde_helpers::de_maybe_stringified; +use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; pub const DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME: &str = "private_identity.pem"; @@ -28,19 +29,6 @@ pub fn default_ip_packet_router_data_dir>(id: P) -> PathBuf { default_data_directory(id).join(DEFAULT_IP_PACKET_ROUTER_DATA_DIR) } -/// makes sure that an empty path is converted into a `None` as opposed to `Some("")` -fn de_maybe_path<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let path = PathBuf::deserialize(deserializer)?; - if path.as_os_str().is_empty() { - Ok(None) - } else { - Ok(Some(path)) - } -} - #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] #[serde(deny_unknown_fields)] pub struct GatewayPaths { @@ -52,13 +40,13 @@ pub struct GatewayPaths { pub clients_storage: PathBuf, /// Path to the configuration of the embedded network requester. - #[serde(deserialize_with = "de_maybe_path")] + #[serde(deserialize_with = "de_maybe_stringified")] pub network_requester_config: Option, // pub node_description: PathBuf, // pub cosmos_bip39_mnemonic: PathBuf, /// Path to the configuration of the embedded ip packet router. - #[serde(deserialize_with = "de_maybe_path")] + #[serde(deserialize_with = "de_maybe_stringified")] pub ip_packet_router_config: Option, } @@ -73,6 +61,20 @@ impl GatewayPaths { } } + pub fn new_empty() -> Self { + GatewayPaths { + keys: KeysPaths { + private_identity_key_file: Default::default(), + public_identity_key_file: Default::default(), + private_sphinx_key_file: Default::default(), + public_sphinx_key_file: Default::default(), + }, + clients_storage: Default::default(), + network_requester_config: None, + ip_packet_router_config: None, + } + } + #[must_use] pub fn with_network_requester_config>(mut self, path: P) -> Self { self.network_requester_config = Some(path.as_ref().into()); @@ -167,3 +169,15 @@ impl KeysPaths { &self.public_sphinx_key_file } } + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct WireguardPaths { + // pub keys: +} + +impl WireguardPaths { + pub fn new_empty() -> Self { + WireguardPaths {} + } +} diff --git a/gateway/src/error.rs b/gateway/src/error.rs index 61f75981ef..87037d5e35 100644 --- a/gateway/src/error.rs +++ b/gateway/src/error.rs @@ -5,14 +5,17 @@ use crate::node::storage::error::StorageError; use nym_ip_packet_router::error::IpPacketRouterError; use nym_network_requester::error::{ClientCoreError, NetworkRequesterError}; use nym_validator_client::nyxd::error::NyxdError; -use nym_validator_client::nyxd::AccountId; +use nym_validator_client::nyxd::{AccountId, Coin}; use nym_validator_client::ValidatorClientError; use std::io; +use std::net::IpAddr; use std::path::PathBuf; use thiserror::Error; +pub use crate::node::client_handling::websocket::connection_handler::authenticated::RequestHandlingError; + #[derive(Debug, Error)] -pub(crate) enum GatewayError { +pub enum GatewayError { #[error("failed to load {keys} keys from '{}' (private key) and '{}' (public key): {err}", .paths.private_key_path.display(), .paths.public_key_path.display())] KeyPairLoadFailure { keys: String, @@ -89,6 +92,9 @@ pub(crate) enum GatewayError { actual_prefix: String, }, + #[error("this node has insufficient balance to run as zk-nym entry node since it won't be capable of redeeming received credentials. it's account ({account}) has a balance of only {balance}")] + InsufficientNodeBalance { account: AccountId, balance: Coin }, + #[error("storage failure: {source}")] StorageError { #[from] @@ -131,15 +137,36 @@ pub(crate) enum GatewayError { source: NyxdError, }, - // TODO: in the future this should work the other way, i.e. NymNode depending on Gateway errors #[error(transparent)] - NymNodeError(#[from] nym_node::error::NymNodeError), + ClientRequestFailure { + #[from] + source: RequestHandlingError, + }, + + #[error("failed to catch an interrupt: {source}")] + ShutdownFailure { + #[source] + source: Box, + }, + + #[error("this node hasn't set any valid public addresses to announce. Please modify [host.public_ips] section of your config")] + NoPublicIps, + + #[error("this node attempted to announce an invalid public address: {address}. Please modify [host.public_ips] section of your config. Alternatively, if you wanted to use it in the local setting, run the node with the '--local' flag.")] + InvalidPublicIp { address: IpAddr }, + + #[error(transparent)] + NymNodeHttpError(#[from] nym_node_http_api::NymNodeHttpError), #[error("there was an issue with wireguard IP network: {source}")] IpNetworkError { #[from] source: ipnetwork::IpNetworkError, }, + + #[cfg(all(feature = "wireguard", target_os = "linux"))] + #[error("failed to remove wireguard interface: {0}")] + WireguardInterfaceError(#[from] defguard_wireguard_rs::error::WireguardInterfaceError), } impl From for GatewayError { diff --git a/gateway/src/helpers.rs b/gateway/src/helpers.rs new file mode 100644 index 0000000000..f117889473 --- /dev/null +++ b/gateway/src/helpers.rs @@ -0,0 +1,231 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::config::Config; +use crate::node::helpers::load_keypair; +use crate::GatewayError; +use nym_config::OptionalSet; +use nym_crypto::asymmetric::{encryption, identity}; +use nym_ip_packet_router::config::BaseClientConfig; +use nym_pemstore::traits::PemStorableKey; +use nym_pemstore::KeyPairPath; +use nym_sphinx::addressing::clients::Recipient; +use nym_types::gateway::{ + GatewayIpPacketRouterDetails, GatewayNetworkRequesterDetails, GatewayNodeDetailsResponse, +}; +use std::path::Path; + +pub use crate::node::helpers::{load_ip_packet_router_config, load_network_requester_config}; + +fn display_maybe_path>(path: Option

) -> String { + path.as_ref() + .map(|p| p.as_ref().display().to_string()) + .unwrap_or_default() +} + +fn display_path>(path: P) -> String { + path.as_ref().display().to_string() +} + +#[derive(Default)] +pub struct OverrideNetworkRequesterConfig { + pub fastmode: bool, + pub no_cover: bool, + pub medium_toggle: bool, + + pub open_proxy: Option, + pub enable_statistics: Option, + pub statistics_recipient: Option, +} + +#[derive(Default)] +pub struct OverrideIpPacketRouterConfig { + // TODO +} + +// NOTE: make sure this is in sync with service-providers/network-requester/src/cli/mod.rs::override_config +pub fn override_network_requester_config( + mut cfg: nym_network_requester::Config, + opts: Option, +) -> nym_network_requester::Config { + let Some(opts) = opts else { return cfg }; + + // as of 12.09.23 the below is true (not sure how this comment will rot in the future) + // medium_toggle: + // - sets secondary packet size to 16kb + // - disables poisson distribution of the main traffic stream + // - sets the cover traffic stream to 1 packet / 5s (on average) + // - disables per hop delay + // + // fastmode (to be renamed to `fast-poisson`): + // - sets average per hop delay to 10ms + // - sets the cover traffic stream to 1 packet / 2000s (on average); for all intents and purposes it disables the stream + // - sets the poisson distribution of the main traffic stream to 4ms, i.e. 250 packets / s on average + // + // no_cover: + // - disables poisson distribution of the main traffic stream + // - disables the secondary cover traffic stream + + // disable poisson rate in the BASE client if the NR option is enabled + if cfg.network_requester.disable_poisson_rate { + cfg.set_no_poisson_process(); + } + + // those should be enforced by `clap` when parsing the arguments + if opts.medium_toggle { + assert!(!opts.fastmode); + assert!(!opts.no_cover); + + cfg.set_medium_toggle(); + } + + cfg.with_base( + BaseClientConfig::with_high_default_traffic_volume, + opts.fastmode, + ) + .with_base(BaseClientConfig::with_disabled_cover_traffic, opts.no_cover) + .with_optional( + nym_network_requester::Config::with_open_proxy, + opts.open_proxy, + ) + .with_optional( + nym_network_requester::Config::with_enabled_statistics, + opts.enable_statistics, + ) + .with_optional( + nym_network_requester::Config::with_statistics_recipient, + opts.statistics_recipient, + ) +} + +// NOTE: make sure this is in sync with service-providers/ip-packet-router/src/cli/mod.rs::override_config +pub fn override_ip_packet_router_config( + mut cfg: nym_ip_packet_router::Config, + opts: Option, +) -> nym_ip_packet_router::Config { + let Some(_opts) = opts else { return cfg }; + + // disable poisson rate in the BASE client if the IPR option is enabled + if cfg.ip_packet_router.disable_poisson_rate { + log::info!("Disabling poisson rate for ip packet router"); + cfg.set_no_poisson_process(); + } + + cfg +} + +pub fn load_public_key(path: P, name: S) -> Result +where + T: PemStorableKey, + P: AsRef, + S: Into, +{ + nym_pemstore::load_key(path.as_ref()).map_err(|err| GatewayError::PublicKeyLoadFailure { + key: name.into(), + path: path.as_ref().to_path_buf(), + err, + }) +} + +/// Loads identity keys stored on disk +pub fn load_identity_keys(config: &Config) -> Result { + let identity_paths = KeyPairPath::new( + config.storage_paths.keys.private_identity_key(), + config.storage_paths.keys.public_identity_key(), + ); + load_keypair(identity_paths, "gateway identity") +} + +pub async fn node_details(config: &Config) -> Result { + let gateway_identity_public_key: identity::PublicKey = load_public_key( + &config.storage_paths.keys.public_identity_key_file, + "gateway identity", + )?; + + let gateway_sphinx_public_key: encryption::PublicKey = load_public_key( + &config.storage_paths.keys.public_sphinx_key_file, + "gateway sphinx", + )?; + + let network_requester = + if let Some(nr_cfg_path) = &config.storage_paths.network_requester_config { + let cfg = load_network_requester_config(&config.gateway.id, nr_cfg_path).await?; + + let nr_identity_public_key: identity::PublicKey = load_public_key( + &cfg.storage_paths.common_paths.keys.public_identity_key_file, + "network requester identity", + )?; + + let nr_encryption_key: encryption::PublicKey = load_public_key( + &cfg.storage_paths + .common_paths + .keys + .public_encryption_key_file, + "network requester encryption", + )?; + + let address = Recipient::new( + nr_identity_public_key, + nr_encryption_key, + gateway_identity_public_key, + ); + + Some(GatewayNetworkRequesterDetails { + enabled: config.network_requester.enabled, + identity_key: nr_identity_public_key.to_base58_string(), + encryption_key: nr_encryption_key.to_base58_string(), + open_proxy: cfg.network_requester.open_proxy, + enabled_statistics: cfg.network_requester.enabled_statistics, + address: address.to_string(), + config_path: display_path(nr_cfg_path), + }) + } else { + None + }; + + let ip_packet_router = if let Some(nr_cfg_path) = &config.storage_paths.ip_packet_router_config + { + let cfg = load_ip_packet_router_config(&config.gateway.id, nr_cfg_path).await?; + + let nr_identity_public_key: identity::PublicKey = load_public_key( + &cfg.storage_paths.common_paths.keys.public_identity_key_file, + "ip packet router identity", + )?; + + let nr_encryption_key: encryption::PublicKey = load_public_key( + &cfg.storage_paths + .common_paths + .keys + .public_encryption_key_file, + "ip packet router encryption", + )?; + + let address = Recipient::new( + nr_identity_public_key, + nr_encryption_key, + gateway_identity_public_key, + ); + + Some(GatewayIpPacketRouterDetails { + enabled: config.ip_packet_router.enabled, + identity_key: nr_identity_public_key.to_base58_string(), + encryption_key: nr_encryption_key.to_base58_string(), + address: address.to_string(), + config_path: display_path(nr_cfg_path), + }) + } else { + None + }; + + Ok(GatewayNodeDetailsResponse { + identity_key: gateway_identity_public_key.to_base58_string(), + sphinx_key: gateway_sphinx_public_key.to_base58_string(), + bind_address: config.gateway.listening_address.to_string(), + mix_port: config.gateway.mix_port, + clients_port: config.gateway.clients_port, + config_path: display_maybe_path(config.save_path.as_ref()), + data_store: display_path(&config.storage_paths.clients_storage), + network_requester, + ip_packet_router, + }) +} diff --git a/gateway/src/http/mod.rs b/gateway/src/http/mod.rs index 9fcaa10209..1d81f4a457 100644 --- a/gateway/src/http/mod.rs +++ b/gateway/src/http/mod.rs @@ -1,22 +1,22 @@ -// Copyright 2023 - Nym Technologies SA +// Copyright 2023-2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only use crate::config::Config; use crate::error::GatewayError; -use crate::node::helpers::load_public_key; +use crate::helpers::load_public_key; use ipnetwork::IpNetwork; -use log::warn; +use log::{debug, warn}; use nym_bin_common::bin_info_owned; use nym_crypto::asymmetric::{encryption, identity}; use nym_network_requester::RequestFilter; -use nym_node::error::NymNodeError; -use nym_node::http::api::api_requests; -use nym_node::http::api::api_requests::v1::network_requester::exit_policy::models::UsedExitPolicy; -use nym_node::http::api::api_requests::SignedHostInformation; -use nym_node::http::router::WireguardAppState; -use nym_node::wireguard::types::GatewayClientRegistry; +use nym_node_http_api::api::api_requests; +use nym_node_http_api::api::api_requests::v1::network_requester::exit_policy::models::UsedExitPolicy; +use nym_node_http_api::api::api_requests::SignedHostInformation; +use nym_node_http_api::router::WireguardAppState; +use nym_node_http_api::NymNodeHttpError; use nym_sphinx::addressing::clients::Recipient; use nym_task::TaskClient; +use nym_wireguard_types::registration::GatewayClientRegistry; use std::net::{IpAddr, Ipv4Addr}; use std::sync::Arc; @@ -52,13 +52,14 @@ fn load_host_details( ip_address: config.host.public_ips.clone(), hostname: config.host.hostname.clone(), keys: api_requests::v1::node::models::HostKeys { - ed25519: identity_keypair.public_key().to_base58_string(), - x25519: sphinx_key.to_base58_string(), + ed25519_identity: identity_keypair.public_key().to_base58_string(), + x25519_sphinx: sphinx_key.to_base58_string(), + x25519_noise: "".to_string(), }, }; let signed_info = SignedHostInformation::new(host_info, identity_keypair.private_key()) - .map_err(NymNodeError::from)?; + .map_err(NymNodeHttpError::from)?; Ok(signed_info) } @@ -242,13 +243,15 @@ impl<'a> HttpApiBuilder<'a> { } pub(crate) fn start(self, task_client: TaskClient) -> Result<(), GatewayError> { + debug!("starting http API"); + // is it suboptimal to load all the keys, etc for the second time after they've already been // retrieved during startup of the rest of the components? // yes, a bit. // but in the grand scheme of things performance penalty is negligible since it's only happening on startup // and makes the code a bit nicer to manage. on top of it, all of it will refactored anyway at some point // (famous last words, eh? - 22.09.23) - let mut config = nym_node::http::Config::new( + let mut config = nym_node_http_api::Config::new( bin_info_owned!(), load_host_details( self.gateway_config, @@ -291,7 +294,7 @@ impl<'a> HttpApiBuilder<'a> { .ok() }); - let router = nym_node::http::NymNodeRouter::new(config, wg_state); + let router = nym_node_http_api::NymNodeRouter::new(config, None, wg_state); let server = router .build_server(&self.gateway_config.http.bind_address)? diff --git a/gateway/src/lib.rs b/gateway/src/lib.rs new file mode 100644 index 0000000000..f6181ac62a --- /dev/null +++ b/gateway/src/lib.rs @@ -0,0 +1,14 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +#![warn(clippy::expect_used)] +#![warn(clippy::unwrap_used)] + +pub mod config; +pub mod error; +pub mod helpers; +pub(crate) mod http; +pub mod node; + +pub use error::GatewayError; +pub use node::{create_gateway, Gateway}; diff --git a/gateway/src/main.rs b/gateway/src/main.rs index 44e33aec73..81c9d329d4 100644 --- a/gateway/src/main.rs +++ b/gateway/src/main.rs @@ -9,16 +9,11 @@ use colored::Colorize; use log::error; use nym_bin_common::bin_info; use nym_bin_common::logging::{maybe_print_banner, setup_logging}; -use nym_bin_common::output_format::OutputFormat; use nym_network_defaults::setup_env; +use std::io::IsTerminal; use std::sync::OnceLock; mod commands; -mod config; -pub(crate) mod error; -mod http; -mod node; -pub(crate) mod support; fn pretty_build_info_static() -> &'static str { static PRETTY_BUILD_INFORMATION: OnceLock = OnceLock::new(); @@ -52,11 +47,12 @@ async fn main() -> anyhow::Result<()> { } commands::execute(args).await.map_err(|err| { - if atty::is(atty::Stream::Stdout) { + if std::io::stdout().is_terminal() { let error_message = format!("{err}").red(); error!("{error_message}"); error!("Exiting..."); } + err }) } diff --git a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs index 24e0393f9d..9ff8882a3a 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs @@ -38,7 +38,7 @@ use tokio::io::{AsyncRead, AsyncWrite}; use tokio_tungstenite::tungstenite::{protocol::Message, Error as WsError}; #[derive(Debug, Error)] -pub(crate) enum RequestHandlingError { +pub enum RequestHandlingError { #[error("Internal gateway storage error")] StorageError(#[from] StorageError), diff --git a/gateway/src/node/client_handling/websocket/connection_handler/mod.rs b/gateway/src/node/client_handling/websocket/connection_handler/mod.rs index 1bc27a6e2f..67ec3e4316 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/mod.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/mod.rs @@ -15,7 +15,7 @@ use zeroize::{Zeroize, ZeroizeOnDrop}; pub(crate) use self::authenticated::AuthenticatedHandler; pub(crate) use self::fresh::FreshHandler; -mod authenticated; +pub(crate) mod authenticated; pub(crate) mod coconut; mod fresh; diff --git a/gateway/src/node/helpers.rs b/gateway/src/node/helpers.rs index 58a11e3f6c..0c920181e0 100644 --- a/gateway/src/node/helpers.rs +++ b/gateway/src/node/helpers.rs @@ -3,123 +3,15 @@ use crate::config::Config; use crate::error::GatewayError; + use crate::node::storage::PersistentStorage; -use nym_crypto::asymmetric::{encryption, identity}; -use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair}; +use nym_crypto::asymmetric::encryption; +use nym_pemstore::traits::PemStorableKeyPair; use nym_pemstore::KeyPairPath; -use nym_sphinx::addressing::clients::Recipient; -use nym_types::gateway::{ - GatewayIpPacketRouterDetails, GatewayNetworkRequesterDetails, GatewayNodeDetailsResponse, -}; + use std::path::Path; -fn display_maybe_path>(path: Option

) -> String { - path.as_ref() - .map(|p| p.as_ref().display().to_string()) - .unwrap_or_default() -} - -fn display_path>(path: P) -> String { - path.as_ref().display().to_string() -} - -pub(crate) async fn node_details( - config: &Config, -) -> Result { - let gateway_identity_public_key: identity::PublicKey = load_public_key( - &config.storage_paths.keys.public_identity_key_file, - "gateway identity", - )?; - - let gateway_sphinx_public_key: encryption::PublicKey = load_public_key( - &config.storage_paths.keys.public_sphinx_key_file, - "gateway sphinx", - )?; - - let network_requester = - if let Some(nr_cfg_path) = &config.storage_paths.network_requester_config { - let cfg = load_network_requester_config(&config.gateway.id, nr_cfg_path).await?; - - let nr_identity_public_key: identity::PublicKey = load_public_key( - &cfg.storage_paths.common_paths.keys.public_identity_key_file, - "network requester identity", - )?; - - let nr_encryption_key: encryption::PublicKey = load_public_key( - &cfg.storage_paths - .common_paths - .keys - .public_encryption_key_file, - "network requester encryption", - )?; - - let address = Recipient::new( - nr_identity_public_key, - nr_encryption_key, - gateway_identity_public_key, - ); - - Some(GatewayNetworkRequesterDetails { - enabled: config.network_requester.enabled, - identity_key: nr_identity_public_key.to_base58_string(), - encryption_key: nr_encryption_key.to_base58_string(), - open_proxy: cfg.network_requester.open_proxy, - enabled_statistics: cfg.network_requester.enabled_statistics, - address: address.to_string(), - config_path: display_path(nr_cfg_path), - }) - } else { - None - }; - - let ip_packet_router = if let Some(nr_cfg_path) = &config.storage_paths.ip_packet_router_config - { - let cfg = load_ip_packet_router_config(&config.gateway.id, nr_cfg_path).await?; - - let nr_identity_public_key: identity::PublicKey = load_public_key( - &cfg.storage_paths.common_paths.keys.public_identity_key_file, - "ip packet router identity", - )?; - - let nr_encryption_key: encryption::PublicKey = load_public_key( - &cfg.storage_paths - .common_paths - .keys - .public_encryption_key_file, - "ip packet router encryption", - )?; - - let address = Recipient::new( - nr_identity_public_key, - nr_encryption_key, - gateway_identity_public_key, - ); - - Some(GatewayIpPacketRouterDetails { - enabled: config.ip_packet_router.enabled, - identity_key: nr_identity_public_key.to_base58_string(), - encryption_key: nr_encryption_key.to_base58_string(), - address: address.to_string(), - config_path: display_path(nr_cfg_path), - }) - } else { - None - }; - - Ok(GatewayNodeDetailsResponse { - identity_key: gateway_identity_public_key.to_base58_string(), - sphinx_key: gateway_sphinx_public_key.to_base58_string(), - bind_address: config.gateway.listening_address.to_string(), - mix_port: config.gateway.mix_port, - clients_port: config.gateway.clients_port, - config_path: display_maybe_path(config.save_path.as_ref()), - data_store: display_path(&config.storage_paths.clients_storage), - network_requester, - ip_packet_router, - }) -} - -pub(crate) async fn load_network_requester_config>( +pub async fn load_network_requester_config>( id: &str, path: P, ) -> Result { @@ -132,7 +24,7 @@ pub(crate) async fn load_network_requester_config>( read_network_requester_config(id, path) } -pub(crate) async fn load_ip_packet_router_config>( +pub async fn load_ip_packet_router_config>( id: &str, path: P, ) -> Result { @@ -145,7 +37,7 @@ pub(crate) async fn load_ip_packet_router_config>( read_ip_packet_router_config(id, path) } -fn read_network_requester_config>( +pub fn read_network_requester_config>( id: &str, path: P, ) -> Result { @@ -159,7 +51,7 @@ fn read_network_requester_config>( }) } -fn read_ip_packet_router_config>( +pub fn read_ip_packet_router_config>( id: &str, path: P, ) -> Result { @@ -182,7 +74,7 @@ pub(crate) async fn initialise_main_storage( Ok(PersistentStorage::init(path, retrieval_limit).await?) } -pub(crate) fn load_keypair( +pub fn load_keypair( paths: KeyPairPath, name: impl Into, ) -> Result { @@ -193,28 +85,6 @@ pub(crate) fn load_keypair( }) } -pub(crate) fn load_public_key(path: P, name: S) -> Result -where - T: PemStorableKey, - P: AsRef, - S: Into, -{ - nym_pemstore::load_key(path.as_ref()).map_err(|err| GatewayError::PublicKeyLoadFailure { - key: name.into(), - path: path.as_ref().to_path_buf(), - err, - }) -} - -/// Loads identity keys stored on disk -pub(crate) fn load_identity_keys(config: &Config) -> Result { - let identity_paths = KeyPairPath::new( - config.storage_paths.keys.private_identity_key(), - config.storage_paths.keys.public_identity_key(), - ); - load_keypair(identity_paths, "gateway identity") -} - /// Loads Sphinx keys stored on disk pub(crate) fn load_sphinx_keys(config: &Config) -> Result { let sphinx_paths = KeyPairPath::new( diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index 26ad47b689..08af357093 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -2,13 +2,12 @@ // SPDX-License-Identifier: GPL-3.0-only use self::helpers::load_ip_packet_router_config; -use self::storage::PersistentStorage; -use crate::commands::helpers::{ - override_ip_packet_router_config, override_network_requester_config, - OverrideIpPacketRouterConfig, OverrideNetworkRequesterConfig, -}; use crate::config::Config; use crate::error::GatewayError; +use crate::helpers::{ + load_identity_keys, override_ip_packet_router_config, override_network_requester_config, + OverrideIpPacketRouterConfig, OverrideNetworkRequesterConfig, +}; use crate::http::HttpApiBuilder; use crate::node::client_handling::active_clients::ActiveClientsStore; use crate::node::client_handling::embedded_clients::{LocalEmbeddedClientHandle, MessageRouter}; @@ -17,8 +16,6 @@ use crate::node::client_handling::websocket::connection_handler::coconut::Coconu use crate::node::helpers::{initialise_main_storage, load_network_requester_config}; use crate::node::mixnet_handling::receiver::connection_handler::ConnectionHandler; use crate::node::statistics::collector::GatewayStatisticsCollector; -use crate::node::storage::Storage; -use anyhow::bail; use dashmap::DashMap; use futures::channel::{mpsc, oneshot}; use log::*; @@ -26,13 +23,14 @@ use nym_crypto::asymmetric::{encryption, identity}; use nym_mixnet_client::forwarder::{MixForwardingSender, PacketForwarder}; use nym_network_defaults::NymNetworkDetails; use nym_network_requester::{LocalGateway, NRServiceProviderBuilder, RequestFilter}; -use nym_node::wireguard::types::GatewayClientRegistry; use nym_statistics_common::collector::StatisticsSender; -use nym_task::{TaskClient, TaskManager}; +use nym_task::{TaskClient, TaskHandle, TaskManager}; +use nym_types::gateway::GatewayNodeDetailsResponse; +use nym_validator_client::nyxd::{Coin, CosmWasmClient}; use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient}; +use nym_wireguard_types::registration::GatewayClientRegistry; use rand::seq::SliceRandom; use rand::thread_rng; -use std::error::Error; use std::net::SocketAddr; use std::path::PathBuf; use std::sync::Arc; @@ -43,6 +41,8 @@ pub(crate) mod mixnet_handling; pub(crate) mod statistics; pub(crate) mod storage; +pub use storage::{InMemStorage, PersistentStorage, Storage}; + // TODO: should this struct live here? struct StartedNetworkRequester { /// Request filter, either an exit policy or the allow list, used by the network requester. @@ -53,7 +53,7 @@ struct StartedNetworkRequester { } /// Wire up and create Gateway instance -pub(crate) async fn create_gateway( +pub async fn create_gateway( config: Config, nr_config_override: Option, ip_config_override: Option, @@ -102,19 +102,19 @@ pub(crate) async fn create_gateway( #[derive(Debug, Clone)] pub struct LocalNetworkRequesterOpts { - config: nym_network_requester::Config, + pub config: nym_network_requester::Config, - custom_mixnet_path: Option, + pub custom_mixnet_path: Option, } #[derive(Debug, Clone)] pub struct LocalIpPacketRouterOpts { - config: nym_ip_packet_router::Config, + pub config: nym_ip_packet_router::Config, - custom_mixnet_path: Option, + pub custom_mixnet_path: Option, } -pub(crate) struct Gateway { +pub struct Gateway { config: Config, network_requester_opts: Option, @@ -129,6 +129,9 @@ pub(crate) struct Gateway { storage: St, client_registry: Arc, + + run_http_server: bool, + task_client: Option, } impl Gateway { @@ -141,35 +144,59 @@ impl Gateway { ) -> Result { Ok(Gateway { storage, - identity_keypair: Arc::new(helpers::load_identity_keys(&config)?), + identity_keypair: Arc::new(load_identity_keys(&config)?), sphinx_keypair: Arc::new(helpers::load_sphinx_keys(&config)?), config, network_requester_opts, ip_packet_router_opts, client_registry: Arc::new(DashMap::new()), + run_http_server: false, + task_client: None, }) } - #[cfg(test)] - pub async fn new_from_keys_and_storage( + pub fn new_loaded( config: Config, network_requester_opts: Option, ip_packet_router_opts: Option, - identity_keypair: identity::KeyPair, - sphinx_keypair: encryption::KeyPair, + identity_keypair: Arc, + sphinx_keypair: Arc, storage: St, ) -> Self { Gateway { config, network_requester_opts, ip_packet_router_opts, - identity_keypair: Arc::new(identity_keypair), - sphinx_keypair: Arc::new(sphinx_keypair), + identity_keypair, + sphinx_keypair, storage, client_registry: Arc::new(DashMap::new()), + run_http_server: false, + task_client: None, } } + pub fn disable_http_server(&mut self) { + self.run_http_server = false + } + + pub fn set_task_client(&mut self, task_client: TaskClient) { + self.task_client = Some(task_client) + } + + pub fn set_wireguard_client_registry(&mut self, client_registry: Arc) { + // sanity check: + if Arc::strong_count(&self.client_registry) != 1 { + panic!("the client registry is already being used elsewhere") + } + self.client_registry = client_registry + } + + pub async fn node_details(&self) -> Result { + // TODO: this is doing redundant key loads, but I guess that's fine for now + crate::helpers::node_details(&self.config).await + } + fn start_mix_socket_listener( &self, ack_sender: MixForwardingSender, @@ -202,7 +229,7 @@ impl Gateway { async fn start_wireguard( &self, shutdown: TaskClient, - ) -> Result> { + ) -> Result> { nym_wireguard::start_wireguard(shutdown, Arc::clone(&self.client_registry)).await } @@ -382,14 +409,6 @@ impl Gateway { Ok(LocalEmbeddedClientHandle::new(address, ipr_mix_sender)) } - async fn wait_for_interrupt( - mut shutdown: TaskManager, - ) -> Result<(), Box> { - let res = shutdown.catch_interrupt().await; - log::info!("Stopping nym gateway"); - res - } - fn random_api_client(&self) -> Result { let endpoints = self.config.get_nym_api_endpoints(); let nym_api = endpoints @@ -433,7 +452,7 @@ impl Gateway { })) } - pub async fn run(self) -> anyhow::Result<()> + pub async fn run(mut self) -> Result<(), GatewayError> where St: Storage + Clone + 'static, { @@ -443,21 +462,43 @@ impl Gateway { warn!("You seem to have bonded your gateway before starting it - that's highly unrecommended as in the future it might result in slashing"); } - let shutdown = TaskManager::new(10); + let shutdown = self + .task_client + .take() + .map(Into::::into) + .unwrap_or_else(|| TaskHandle::Internal(TaskManager::new(10))) + .name_if_unnamed("gateway"); - let coconut_verifier = { - let nyxd_client = self.random_nyxd_client()?; - CoconutVerifier::new(nyxd_client, self.config.gateway.only_coconut_credentials).await - }?; + let nyxd_client = self.random_nyxd_client()?; - let mix_forwarding_channel = - self.start_packet_forwarder(shutdown.subscribe().named("PacketForwarder")); + if self.config.gateway.only_coconut_credentials { + debug!("the gateway is running in coconut-only mode - making sure it has enough tokens for credential redemption"); + let mix_denom_base = nyxd_client.current_chain_details().mix_denom.base.clone(); + + let account = nyxd_client.address(); + let balance = nyxd_client + .get_balance(&account, mix_denom_base.clone()) + .await? + .unwrap_or(Coin::new(0, mix_denom_base)); + + error!("this gateway does not have enough tokens for covering transaction fees for credential redemption"); + + // see if we have at least 1nym (i.e. 1'000'000unym) + if balance.amount < 1_000_000 { + return Err(GatewayError::InsufficientNodeBalance { account, balance }); + } + } + + let coconut_verifier = + CoconutVerifier::new(nyxd_client, self.config.gateway.only_coconut_credentials).await?; + + let mix_forwarding_channel = self.start_packet_forwarder(shutdown.fork("PacketForwarder")); let active_clients_store = ActiveClientsStore::new(); self.start_mix_socket_listener( mix_forwarding_channel.clone(), active_clients_store.clone(), - shutdown.subscribe().named("mixnet_handling::Listener"), + shutdown.fork("mixnet_handling::Listener"), ); if self.config.gateway.enabled_statistics { @@ -476,7 +517,7 @@ impl Gateway { self.start_client_websocket_listener( mix_forwarding_channel.clone(), active_clients_store.clone(), - shutdown.subscribe().named("websocket::Listener"), + shutdown.fork("websocket::Listener"), Arc::new(coconut_verifier), ); @@ -484,7 +525,7 @@ impl Gateway { let embedded_nr = self .start_network_requester( mix_forwarding_channel.clone(), - shutdown.subscribe().named("NetworkRequester"), + shutdown.fork("NetworkRequester"), ) .await?; // insert information about embedded NR to the active clients store @@ -499,40 +540,40 @@ impl Gateway { let embedded_ip_sp = self .start_ip_packet_router( mix_forwarding_channel, - shutdown.subscribe().named("ip_service_provider"), + shutdown.fork("ip_service_provider"), ) .await?; active_clients_store.insert_embedded(embedded_ip_sp); - } + } else { + info!("embedded ip packet router is disabled"); + }; - HttpApiBuilder::new( - &self.config, - self.identity_keypair.as_ref(), - self.sphinx_keypair.clone(), - ) - .with_wireguard_client_registry(self.client_registry.clone()) - .with_maybe_network_requester(self.network_requester_opts.as_ref().map(|o| &o.config)) - .with_maybe_network_request_filter(nr_request_filter) - .with_maybe_ip_packet_router(self.ip_packet_router_opts.as_ref().map(|o| &o.config)) - .start(shutdown.subscribe().named("http-api"))?; + if self.run_http_server { + HttpApiBuilder::new( + &self.config, + self.identity_keypair.as_ref(), + self.sphinx_keypair.clone(), + ) + .with_wireguard_client_registry(self.client_registry.clone()) + .with_maybe_network_requester(self.network_requester_opts.as_ref().map(|o| &o.config)) + .with_maybe_network_request_filter(nr_request_filter) + .with_maybe_ip_packet_router(self.ip_packet_router_opts.as_ref().map(|o| &o.config)) + .start(shutdown.fork("http-api"))?; + } // Once this is a bit more mature, make this a commandline flag instead of a compile time // flag #[cfg(all(feature = "wireguard", target_os = "linux"))] - let wg_api = self - .start_wireguard(shutdown.subscribe().named("wireguard")) - .await - .ok(); + let wg_api = self.start_wireguard(shutdown.fork("wireguard")).await.ok(); #[cfg(all(feature = "wireguard", not(target_os = "linux")))] - self.start_wireguard(shutdown.subscribe().named("wireguard")) - .await; + self.start_wireguard(shutdown.fork("wireguard")).await; info!("Finished nym gateway startup procedure - it should now be able to receive mix and client traffic!"); - if let Err(err) = Self::wait_for_interrupt(shutdown).await { + if let Err(source) = shutdown.wait_for_shutdown().await { // that's a nasty workaround, but anyhow errors are generally nicer, especially on exit - bail!("{err}") + return Err(GatewayError::ShutdownFailure { source }); } #[cfg(all(feature = "wireguard", target_os = "linux"))] if let Some(wg_api) = wg_api { diff --git a/gateway/src/node/storage/error.rs b/gateway/src/node/storage/error.rs index b2feb1becf..f908eac083 100644 --- a/gateway/src/node/storage/error.rs +++ b/gateway/src/node/storage/error.rs @@ -4,7 +4,7 @@ use thiserror::Error; #[derive(Error, Debug)] -pub(crate) enum StorageError { +pub enum StorageError { #[error("Database experienced an internal error: {0}")] InternalDatabaseError(#[from] sqlx::Error), diff --git a/gateway/src/node/storage/mod.rs b/gateway/src/node/storage/mod.rs index 5349686a86..bb7330c2f6 100644 --- a/gateway/src/node/storage/mod.rs +++ b/gateway/src/node/storage/mod.rs @@ -21,7 +21,7 @@ mod models; mod shared_keys; #[async_trait] -pub(crate) trait Storage: Send + Sync { +pub trait Storage: Send + Sync { /// Inserts provided derived shared keys into the database. /// If keys previously existed for the provided client, they are overwritten with the new data. /// @@ -162,7 +162,7 @@ pub(crate) trait Storage: Send + Sync { // note that clone here is fine as upon cloning the same underlying pool will be used #[derive(Clone)] -pub(crate) struct PersistentStorage { +pub struct PersistentStorage { shared_key_manager: SharedKeysManager, inbox_manager: InboxManager, bandwidth_manager: BandwidthManager, @@ -359,9 +359,8 @@ impl Storage for PersistentStorage { } /// In-memory implementation of `Storage`. The intention is primarily in testing environments. -#[cfg(test)] #[derive(Clone)] -pub(crate) struct InMemStorage; +pub struct InMemStorage; //#[cfg(test)] //impl InMemStorage { diff --git a/gateway/src/node/storage/models.rs b/gateway/src/node/storage/models.rs index 2bef3b7265..b3fde7d37a 100644 --- a/gateway/src/node/storage/models.rs +++ b/gateway/src/node/storage/models.rs @@ -3,26 +3,26 @@ use sqlx::FromRow; -pub(crate) struct PersistedSharedKeys { +pub struct PersistedSharedKeys { pub(crate) client_address_bs58: String, pub(crate) derived_aes128_ctr_blake3_hmac_keys_bs58: String, } -pub(crate) struct StoredMessage { +pub struct StoredMessage { pub(crate) id: i64, #[allow(dead_code)] pub(crate) client_address_bs58: String, pub(crate) content: Vec, } -pub(crate) struct PersistedBandwidth { +pub struct PersistedBandwidth { #[allow(dead_code)] pub(crate) client_address_bs58: String, pub(crate) available: i64, } #[derive(Debug, Clone, FromRow)] -pub(crate) struct SpentCredential { +pub struct SpentCredential { #[allow(dead_code)] pub(crate) blinded_serial_number_bs58: String, #[allow(dead_code)] diff --git a/gateway/src/support/config.rs b/gateway/src/support/config.rs deleted file mode 100644 index deae3240de..0000000000 --- a/gateway/src/support/config.rs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use crate::commands::helpers::{try_load_current_config, OverrideConfig}; -use crate::config::Config; -use crate::error::GatewayError; - -pub(crate) fn build_config>( - id: String, - override_args: O, -) -> Result { - let config = try_load_current_config(&id)?; - override_args.into().do_override(config) -} diff --git a/gateway/src/support/mod.rs b/gateway/src/support/mod.rs deleted file mode 100644 index 42dab6772b..0000000000 --- a/gateway/src/support/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -pub(crate) mod config; diff --git a/mixnode/Cargo.toml b/mixnode/Cargo.toml index af2442fd02..647a54c193 100644 --- a/mixnode/Cargo.toml +++ b/mixnode/Cargo.toml @@ -25,13 +25,14 @@ colored = "2.0" cupid = "0.6.1" dirs = "4.0" futures = { workspace = true } -humantime-serde = "1.0" +humantime-serde = { workspace = true } lazy_static = "1.4" log = { workspace = true } rand = "0.7.3" serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } sysinfo = "0.27.7" +time.workspace = true tokio = { workspace = true, features = ["rt-multi-thread", "net", "signal"] } tokio-util = { workspace = true, features = ["codec"] } toml = "0.5.8" @@ -39,15 +40,15 @@ url = { workspace = true, features = ["serde"] } thiserror = { workspace = true } # internal -nym-node = { path = "../nym-node" } - nym-config = { path = "../common/config" } nym-crypto = { path = "../common/crypto" } nym-contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-common" } +nym-http-api-common = { path = "../common/http-api-common" } nym-mixnet-client = { path = "../common/client-libs/mixnet-client" } nym-mixnode-common = { path = "../common/mixnode-common" } nym-metrics = { path = "../common/nym-metrics" } nym-nonexhaustive-delayqueue = { path = "../common/nonexhaustive-delayqueue" } +nym-node-http-api = { path = "../nym-node/nym-node-http-api" } nym-sphinx = { path = "../common/nymsphinx" } nym-sphinx-params = { path = "../common/nymsphinx/params" } nym-pemstore = { path = "../common/pemstore", version = "0.3.0" } diff --git a/mixnode/src/commands/describe.rs b/mixnode/src/commands/describe.rs index 5576296bcd..d5b17682e2 100644 --- a/mixnode/src/commands/describe.rs +++ b/mixnode/src/commands/describe.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::commands::try_load_current_config; -use crate::node::node_description::NodeDescription; use clap::Args; use colored::Colorize; +use nym_mixnode::node::node_description::NodeDescription; use std::io; use std::io::Write; @@ -74,6 +74,6 @@ pub(crate) fn execute(args: Describe) -> anyhow::Result<()> { }; // save the struct - NodeDescription::save_to_file(&node_description, config.storage_paths.node_description)?; + node_description.save_to_file(config.storage_paths.node_description)?; Ok(()) } diff --git a/mixnode/src/commands/init.rs b/mixnode/src/commands/init.rs index cc1f5fe5c8..a60663768a 100644 --- a/mixnode/src/commands/init.rs +++ b/mixnode/src/commands/init.rs @@ -1,127 +1,21 @@ -// Copyright 2020-2023 - Nym Technologies SA +// Copyright 2020-2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use super::OverrideConfig; -use crate::commands::override_config; -use crate::config::{ - default_config_directory, default_config_filepath, default_data_directory, Config, -}; -use crate::node::MixNode; -use clap::Args; -use nym_bin_common::output_format::OutputFormat; -use nym_crypto::asymmetric::{encryption, identity}; -use std::net::IpAddr; -use std::{fs, io}; +use anyhow::bail; +use colored::Colorize; -#[derive(Args, Clone)] +#[allow(dead_code)] +#[derive(clap::Args, Clone)] pub(crate) struct Init { /// Id of the mixnode we want to create config for #[clap(long)] - id: String, - - /// The host on which the mixnode will be running - #[clap(long)] - host: IpAddr, - - /// The port on which the mixnode will be listening for mix packets - #[clap(long)] - mix_port: Option, - - /// The port on which the mixnode will be listening for verloc packets - #[clap(long)] - verloc_port: Option, - - /// The port on which the mixnode will be listening for http requests - #[clap(long)] - http_api_port: Option, - - /// Comma separated list of nym-api endpoints of the validators - // the alias here is included for backwards compatibility (1.1.4 and before) - #[clap(long, alias = "validators", value_delimiter = ',')] - nym_apis: Option>, - - #[clap(short, long, default_value_t = OutputFormat::default())] - output: OutputFormat, - - #[clap(long)] - metrics_key: Option, + id: Option, } -impl From for OverrideConfig { - fn from(init_config: Init) -> Self { - OverrideConfig { - id: init_config.id, - host: Some(init_config.host), - mix_port: init_config.mix_port, - verloc_port: init_config.verloc_port, - http_api_port: init_config.http_api_port, - nym_apis: init_config.nym_apis, - metrics_key: init_config.metrics_key, - } - } -} - -fn init_paths(id: &str) -> io::Result<()> { - fs::create_dir_all(default_data_directory(id))?; - fs::create_dir_all(default_config_directory(id)) -} - -pub(crate) fn execute(args: &Init) -> anyhow::Result<()> { - let override_config_fields = OverrideConfig::from(args.clone()); - let id = override_config_fields.id.clone(); - eprintln!("Initialising mixnode {id}..."); - - let already_init = if default_config_filepath(&id).exists() { - eprintln!("Mixnode \"{id}\" was already initialised before! Config information will be overwritten (but keys will be kept)!"); - true - } else { - init_paths(&id).expect("failed to initialise storage paths"); - false - }; - - let mut config = Config::new(&id); - config = override_config(config, override_config_fields); - - // if node was already initialised, don't generate new keys - if !already_init { - let mut rng = rand::rngs::OsRng; - - let identity_keys = identity::KeyPair::new(&mut rng); - let sphinx_keys = encryption::KeyPair::new(&mut rng); - - nym_pemstore::store_keypair( - &identity_keys, - &nym_pemstore::KeyPairPath::new( - config.storage_paths.private_identity_key(), - config.storage_paths.public_identity_key(), - ), - ) - .expect("Failed to save identity keys"); - - nym_pemstore::store_keypair( - &sphinx_keys, - &nym_pemstore::KeyPairPath::new( - config.storage_paths.private_encryption_key(), - config.storage_paths.public_encryption_key(), - ), - ) - .expect("Failed to save sphinx keys"); - eprintln!("Saved mixnet identity and sphinx keypairs"); - } - - let config_save_location = config.default_location(); - config.save_to_default_location().unwrap_or_else(|_| { - panic!( - "Failed to save the config file to {}", - config_save_location.display() - ) - }); - eprintln!( - "Saved configuration file to {}", - config_save_location.display() - ); - eprintln!("Mixnode configuration completed.\n\n\n"); - - MixNode::new(config)?.print_node_details(args.output); - Ok(()) +pub(crate) fn execute(_args: &Init) -> anyhow::Result<()> { + bail!( + "standalone mixnode initialisation has been removed - please initialise a `nym-node` instead" + .red() + .bold() + ) } diff --git a/mixnode/src/commands/mod.rs b/mixnode/src/commands/mod.rs index 34b75e701a..4891cd2798 100644 --- a/mixnode/src/commands/mod.rs +++ b/mixnode/src/commands/mod.rs @@ -1,20 +1,21 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::config::default_config_filepath; -use crate::error::MixnodeError; -use crate::{config::Config, Cli}; +use crate::Cli; use clap::CommandFactory; use clap::Subcommand; use colored::Colorize; -use log::{error, info, warn}; +use log::{error, warn}; use nym_bin_common::completions::{fig_generate, ArgShell}; -use nym_bin_common::version_checker; use nym_config::defaults::var_names::{BECH32_PREFIX, NYM_API}; use nym_config::OptionalSet; use nym_crypto::bech32_address_validation; +use nym_mixnode::config::{default_config_filepath, Config}; +use nym_mixnode::error::MixnodeError; +use std::io::IsTerminal; use std::net::IpAddr; use std::process; +use std::time::Duration; mod build_info; mod describe; @@ -52,6 +53,7 @@ pub(crate) enum Commands { } // Configuration that can be overridden. +#[allow(dead_code)] struct OverrideConfig { id: String, host: Option, @@ -65,6 +67,13 @@ struct OverrideConfig { pub(crate) async fn execute(args: Cli) -> anyhow::Result<()> { let bin_name = "nym-mixnode"; + warn!("standalone mixnodes have been deprecated - please consider migrating it to a `nym-node` via `nym-node migrate mixnode` command"); + if std::io::stdout().is_terminal() { + // if user is running it in terminal session, + // introduce the delay, so they'd notice the message + tokio::time::sleep(Duration::from_secs(1)).await + } + match args.command { Commands::Describe(m) => describe::execute(m)?, Commands::Init(m) => init::execute(&m)?, @@ -115,25 +124,6 @@ pub(crate) fn validate_bech32_address_or_exit(address: &str) { } } -// this only checks compatibility between config the binary. It does not take into consideration -// network version. It might do so in the future. -pub(crate) fn version_check(cfg: &Config) -> bool { - let binary_version = env!("CARGO_PKG_VERSION"); - let config_version = &cfg.mixnode.version; - if binary_version == config_version { - true - } else { - warn!("The mixnode binary has different version than what is specified in config file! {} and {}", binary_version, config_version); - if version_checker::is_minor_version_compatible(binary_version, config_version) { - info!("but they are still semver compatible. However, consider running the `upgrade` command"); - true - } else { - error!("and they are semver incompatible! - please run the `upgrade` command before attempting `run` again"); - false - } - } -} - fn try_load_current_config(id: &str) -> Result { upgrade_helpers::try_upgrade_config(id)?; diff --git a/mixnode/src/commands/node_details.rs b/mixnode/src/commands/node_details.rs index 24dc71cb2f..cddf41a04f 100644 --- a/mixnode/src/commands/node_details.rs +++ b/mixnode/src/commands/node_details.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::commands::try_load_current_config; -use crate::node::MixNode; use clap::Args; use nym_bin_common::output_format::OutputFormat; +use nym_mixnode::MixNode; #[derive(Args)] pub(crate) struct NodeDetails { diff --git a/mixnode/src/commands/run.rs b/mixnode/src/commands/run.rs index 178ff31cc3..bef1ef3049 100644 --- a/mixnode/src/commands/run.rs +++ b/mixnode/src/commands/run.rs @@ -2,15 +2,14 @@ // SPDX-License-Identifier: GPL-3.0-only use super::OverrideConfig; -use crate::commands::{override_config, try_load_current_config, version_check}; -use crate::node::MixNode; -use anyhow::bail; +use crate::commands::{override_config, try_load_current_config}; use clap::Args; -use log::error; use nym_bin_common::output_format::OutputFormat; use nym_config::helpers::SPECIAL_ADDRESSES; +use nym_mixnode::MixNode; use nym_validator_client::nyxd; use std::net::IpAddr; + #[derive(Args, Clone)] pub(crate) struct Run { /// Id of the nym-mixnode we want to run @@ -79,11 +78,6 @@ pub(crate) async fn execute(args: &Run) -> anyhow::Result<()> { let mut config = try_load_current_config(&args.id)?; config = override_config(config, OverrideConfig::from(args.clone())); - if !version_check(&config) { - error!("failed the local version check"); - bail!("failed the local version check") - } - if SPECIAL_ADDRESSES.contains(&config.mixnode.listening_address) { show_binding_warning(&config.mixnode.listening_address.to_string()); } diff --git a/mixnode/src/commands/sign.rs b/mixnode/src/commands/sign.rs index b178718fc1..405f9845e2 100644 --- a/mixnode/src/commands/sign.rs +++ b/mixnode/src/commands/sign.rs @@ -2,17 +2,15 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::commands::{try_load_current_config, validate_bech32_address_or_exit}; -use crate::node::helpers::load_identity_keys; use anyhow::{bail, Result}; use clap::{ArgGroup, Args}; use log::error; use nym_bin_common::output_format::OutputFormat; use nym_crypto::asymmetric::identity; +use nym_mixnode::node::helpers::load_identity_keys; use nym_types::helpers::ConsoleSigningOutput; use nym_validator_client::nyxd; -use super::version_check; - #[derive(Args, Clone)] #[clap(group(ArgGroup::new("sign").required(true).args(&["wallet_address", "text", "contract_msg"])))] pub(crate) struct Sign { @@ -114,11 +112,6 @@ fn print_signed_contract_msg( pub(crate) fn execute(args: &Sign) -> anyhow::Result<()> { let config = try_load_current_config(&args.id)?; - if !version_check(&config) { - error!("failed the local version check"); - bail!("failed the local version check") - } - let signed_target = match SignedTarget::try_from(args.clone()) { Ok(s) => s, Err(err) => { diff --git a/mixnode/src/commands/upgrade_helpers.rs b/mixnode/src/commands/upgrade_helpers.rs index 66b4a6ef4d..bf5e1a3882 100644 --- a/mixnode/src/commands/upgrade_helpers.rs +++ b/mixnode/src/commands/upgrade_helpers.rs @@ -1,11 +1,11 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::config::old_config_v1_1_21::ConfigV1_1_21; -use crate::config::old_config_v1_1_32::ConfigV1_1_32; -use crate::config::{default_config_filepath, Config}; -use crate::error::MixnodeError; use log::info; +use nym_mixnode::config::old_config_v1_1_21::ConfigV1_1_21; +use nym_mixnode::config::old_config_v1_1_32::ConfigV1_1_32; +use nym_mixnode::config::{default_config_filepath, Config}; +use nym_mixnode::error::MixnodeError; fn try_upgrade_v1_1_21_config(id: &str) -> Result { use nym_config::legacy_helpers::nym_config::MigrationNymConfig; diff --git a/mixnode/src/config/mod.rs b/mixnode/src/config/mod.rs index 03ac439a59..21a2e3949d 100644 --- a/mixnode/src/config/mod.rs +++ b/mixnode/src/config/mod.rs @@ -1,7 +1,6 @@ // Copyright 2020-2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::config::persistence::paths::MixNodePaths; use crate::config::template::CONFIG_TEMPLATE; use log::{debug, warn}; use nym_bin_common::logging::LoggingSettings; @@ -11,10 +10,10 @@ use nym_config::defaults::{ }; use nym_config::helpers::inaddr_any; use nym_config::{ - must_get_home, read_config_from_toml_file, save_formatted_config_to_file, NymConfigTemplate, - DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, NYM_DIR, + must_get_home, read_config_from_toml_file, save_formatted_config_to_file, + serde_helpers::de_maybe_stringified, NymConfigTemplate, DEFAULT_CONFIG_DIR, + DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, NYM_DIR, }; -use nym_node::config; use serde::{Deserialize, Serialize}; use std::io; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; @@ -23,11 +22,13 @@ use std::str::FromStr; use std::time::Duration; use url::Url; -pub(crate) mod old_config_v1_1_21; -pub(crate) mod old_config_v1_1_32; +pub mod old_config_v1_1_21; +pub mod old_config_v1_1_32; pub mod persistence; mod template; +pub use crate::config::persistence::paths::MixNodePaths; + const DEFAULT_MIXNODES_DIR: &str = "mixnodes"; // 'RTT MEASUREMENT' @@ -73,8 +74,8 @@ pub fn default_data_directory>(id: P) -> PathBuf { .join(DEFAULT_DATA_DIR) } -fn default_mixnode_http_config() -> config::Http { - config::Http { +fn default_mixnode_http_config() -> Http { + Http { bind_address: SocketAddr::new( IpAddr::V4(Ipv4Addr::UNSPECIFIED), DEFAULT_HTTP_API_LISTENING_PORT, @@ -91,10 +92,10 @@ pub struct Config { #[serde(skip)] pub(crate) save_path: Option, - pub host: config::Host, + pub host: Host, #[serde(default = "default_mixnode_http_config")] - pub http: config::Http, + pub http: Http, pub mixnode: MixNode, @@ -122,7 +123,7 @@ impl Config { Config { save_path: None, - host: config::Host { + host: Host { // this is a very bad default! public_ips: vec![default_mixnode.listening_address], hostname: None, @@ -136,6 +137,27 @@ impl Config { } } + pub fn externally_loaded( + host: impl Into, + http: impl Into, + mixnode: impl Into, + storage_paths: impl Into, + verloc: impl Into, + logging: impl Into, + debug: impl Into, + ) -> Self { + Config { + save_path: None, + host: host.into(), + http: http.into(), + mixnode: mixnode.into(), + storage_paths: storage_paths.into(), + verloc: verloc.into(), + logging: logging.into(), + debug: debug.into(), + } + } + // simple wrapper that reads config file and assigns path location fn read_from_path>(path: P) -> io::Result { let path = path.as_ref(); @@ -145,9 +167,6 @@ impl Config { Ok(loaded) } - // currently this is dead code, but once we allow loading configs from custom paths - // well, we will have to be using it - #[allow(dead_code)] pub fn read_from_toml_file>(path: P) -> io::Result { Self::read_from_path(path) } @@ -220,6 +239,44 @@ impl Config { } } +// TODO: this is very much a WIP. we need proper ssl certificate support here +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Host { + /// Ip address(es) of this host, such as 1.1.1.1 that external clients will use for connections. + pub public_ips: Vec, + + /// Optional hostname of this node, for example nymtech.net. + // TODO: this is temporary. to be replaced by pulling the data directly from the certs. + #[serde(deserialize_with = "de_maybe_stringified")] + pub hostname: Option, +} + +impl Host { + pub fn validate(&self) -> bool { + if self.public_ips.is_empty() { + return false; + } + + true + } +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Http { + /// Socket address this node will use for binding its http API. + /// default: `0.0.0.0:8000` + pub bind_address: SocketAddr, + + /// Path to assets directory of custom landing page of this node. + #[serde(deserialize_with = "de_maybe_stringified")] + pub landing_page_assets_path: Option, + + #[serde(default)] + pub metrics_key: Option, +} + #[derive(Debug, Deserialize, PartialEq, Serialize)] pub struct MixNode { /// Version of the mixnode for which this configuration was created. diff --git a/mixnode/src/config/old_config_v1_1_32.rs b/mixnode/src/config/old_config_v1_1_32.rs index f72fb49fd2..1c2f2340d5 100644 --- a/mixnode/src/config/old_config_v1_1_32.rs +++ b/mixnode/src/config/old_config_v1_1_32.rs @@ -1,6 +1,7 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use crate::config; use crate::config::persistence::paths::MixNodePaths; use crate::config::{Config, Debug, MixNode, Verloc}; use nym_bin_common::logging::LoggingSettings; @@ -81,7 +82,7 @@ impl From for Config { // /\ ADDED // \/ ADDED - host: nym_node::config::Host { + host: config::Host { // this is a very bad default! public_ips: vec![value.mixnode.listening_address], hostname: None, @@ -89,7 +90,7 @@ impl From for Config { // /\ ADDED // \/ ADDED - http: nym_node::config::Http { + http: config::Http { bind_address: SocketAddr::new( value.mixnode.listening_address, value.mixnode.http_api_port, diff --git a/mixnode/src/config/persistence/paths.rs b/mixnode/src/config/persistence/paths.rs index ca5a571b35..d8b81634c7 100644 --- a/mixnode/src/config/persistence/paths.rs +++ b/mixnode/src/config/persistence/paths.rs @@ -32,6 +32,18 @@ impl MixNodePaths { } } + pub fn new_empty() -> Self { + MixNodePaths { + keys: KeysPaths { + private_identity_key_file: Default::default(), + public_identity_key_file: Default::default(), + private_sphinx_key_file: Default::default(), + public_sphinx_key_file: Default::default(), + }, + node_description: Default::default(), + } + } + pub fn private_identity_key(&self) -> &Path { self.keys.private_identity_key() } diff --git a/mixnode/src/error.rs b/mixnode/src/error.rs index 57c4594747..a12979e86e 100644 --- a/mixnode/src/error.rs +++ b/mixnode/src/error.rs @@ -44,7 +44,6 @@ pub enum MixnodeError { source: io::Error, }, - // TODO: in the future this should work the other way, i.e. NymNode depending on Gateway errors #[error(transparent)] - NymNodeError(#[from] nym_node::error::NymNodeError), + NymNodeHttpError(#[from] nym_node_http_api::NymNodeHttpError), } diff --git a/mixnode/src/lib.rs b/mixnode/src/lib.rs new file mode 100644 index 0000000000..18d0a47309 --- /dev/null +++ b/mixnode/src/lib.rs @@ -0,0 +1,9 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +pub mod config; +pub mod error; +pub mod node; + +pub use error::MixnodeError; +pub use node::MixNode; diff --git a/mixnode/src/main.rs b/mixnode/src/main.rs index 9f9cf24176..0530072993 100644 --- a/mixnode/src/main.rs +++ b/mixnode/src/main.rs @@ -10,9 +10,6 @@ use std::sync::OnceLock; use nym_bin_common::logging::{maybe_print_banner, setup_logging}; mod commands; -mod config; -pub(crate) mod error; -mod node; fn pretty_build_info_static() -> &'static str { static PRETTY_BUILD_INFORMATION: OnceLock = OnceLock::new(); diff --git a/mixnode/src/node/helpers.rs b/mixnode/src/node/helpers.rs index 54515c053b..612e301dc2 100644 --- a/mixnode/src/node/helpers.rs +++ b/mixnode/src/node/helpers.rs @@ -34,7 +34,7 @@ where } /// Loads identity keys stored on disk -pub(crate) fn load_identity_keys(config: &Config) -> Result { +pub fn load_identity_keys(config: &Config) -> Result { let identity_paths = KeyPairPath::new( config.storage_paths.keys.private_identity_key(), config.storage_paths.keys.public_identity_key(), @@ -43,7 +43,7 @@ pub(crate) fn load_identity_keys(config: &Config) -> Result Result { +pub fn load_sphinx_keys(config: &Config) -> Result { let sphinx_paths = KeyPairPath::new( config.storage_paths.keys.private_encryption_key(), config.storage_paths.keys.public_encryption_key(), diff --git a/mixnode/src/node/http/legacy/description.rs b/mixnode/src/node/http/legacy/description.rs index 29a648c0f2..8921551339 100644 --- a/mixnode/src/node/http/legacy/description.rs +++ b/mixnode/src/node/http/legacy/description.rs @@ -3,7 +3,7 @@ use crate::node::node_description::NodeDescription; use axum::extract::Query; -use nym_node::http::api::{FormattedResponse, OutputParams}; +use nym_http_api_common::{FormattedResponse, OutputParams}; /// Returns a description of the node and why someone might want to delegate stake to it. pub(crate) async fn description( diff --git a/mixnode/src/node/http/legacy/hardware.rs b/mixnode/src/node/http/legacy/hardware.rs index 496d034b0b..a64ca3f5b5 100644 --- a/mixnode/src/node/http/legacy/hardware.rs +++ b/mixnode/src/node/http/legacy/hardware.rs @@ -3,7 +3,7 @@ use axum::extract::Query; use cupid::TopologyType; -use nym_node::http::api::{FormattedResponse, OutputParams}; +use nym_http_api_common::{FormattedResponse, OutputParams}; use serde::Serialize; use sysinfo::{System, SystemExt}; diff --git a/mixnode/src/node/http/legacy/state.rs b/mixnode/src/node/http/legacy/state.rs index 914f6a856a..d51d8741b2 100644 --- a/mixnode/src/node/http/legacy/state.rs +++ b/mixnode/src/node/http/legacy/state.rs @@ -1,25 +1,24 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::node::http::legacy::verloc::VerlocState; -use crate::node::node_statistics::SharedNodeStats; use axum::extract::FromRef; +use nym_node_http_api::state::metrics::{SharedMixingStats, SharedVerlocStats}; // this is a temporary thing for the transition period #[derive(Clone, Default)] pub(crate) struct MixnodeAppState { - pub(crate) verloc: VerlocState, - pub(crate) stats: SharedNodeStats, + pub(crate) verloc: SharedVerlocStats, + pub(crate) stats: SharedMixingStats, pub(crate) metrics_key: Option, } -impl FromRef for VerlocState { +impl FromRef for SharedVerlocStats { fn from_ref(app_state: &MixnodeAppState) -> Self { app_state.verloc.clone() } } -impl FromRef for SharedNodeStats { +impl FromRef for SharedMixingStats { fn from_ref(app_state: &MixnodeAppState) -> Self { app_state.stats.clone() } diff --git a/mixnode/src/node/http/legacy/stats.rs b/mixnode/src/node/http/legacy/stats.rs index c986acee2b..8898b8ab74 100644 --- a/mixnode/src/node/http/legacy/stats.rs +++ b/mixnode/src/node/http/legacy/stats.rs @@ -1,14 +1,14 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::node::node_statistics::{NodeStatsSimple, SharedNodeStats}; - use axum::{ extract::{Query, State}, http::HeaderMap, }; +use nym_http_api_common::{FormattedResponse, Output}; use nym_metrics::metrics; -use nym_node::http::api::{FormattedResponse, Output}; +use nym_node_http_api::api::api_requests::v1::metrics::models::MixingStats; +use nym_node_http_api::state::metrics::SharedMixingStats; use serde::{Deserialize, Serialize}; use super::state::MixnodeAppState; @@ -17,7 +17,7 @@ use super::state::MixnodeAppState; #[serde(untagged)] pub enum NodeStatsResponse { Full(String), - Simple(NodeStatsSimple), + Simple(MixingStats), } pub(crate) async fn metrics(State(state): State, headers: HeaderMap) -> String { @@ -38,7 +38,7 @@ pub(crate) async fn metrics(State(state): State, headers: Heade pub(crate) async fn stats( Query(params): Query, - State(stats): State, + State(stats): State, ) -> MixnodeStatsResponse { let output = params.output.unwrap_or_default(); @@ -47,12 +47,11 @@ pub(crate) async fn stats( output.to_response(response) } -async fn generate_stats(full: bool, stats: SharedNodeStats) -> NodeStatsResponse { - let snapshot_data = stats.clone_data().await; +async fn generate_stats(full: bool, stats: SharedMixingStats) -> NodeStatsResponse { if full { NodeStatsResponse::Full(metrics!()) } else { - NodeStatsResponse::Simple(snapshot_data.simplify()) + NodeStatsResponse::Simple(stats.read().await.as_response()) } } diff --git a/mixnode/src/node/http/legacy/verloc.rs b/mixnode/src/node/http/legacy/verloc.rs index eb5800bccc..8376081b4e 100644 --- a/mixnode/src/node/http/legacy/verloc.rs +++ b/mixnode/src/node/http/legacy/verloc.rs @@ -2,30 +2,18 @@ // SPDX-License-Identifier: GPL-3.0-only use axum::extract::{Query, State}; -use nym_mixnode_common::verloc::{AtomicVerlocResult, VerlocResult}; -use nym_node::http::api::{FormattedResponse, OutputParams}; - -#[derive(Clone, Default)] -pub(crate) struct VerlocState { - shared: AtomicVerlocResult, -} - -impl VerlocState { - pub fn new(atomic_verloc_result: AtomicVerlocResult) -> Self { - VerlocState { - shared: atomic_verloc_result, - } - } -} +use nym_http_api_common::{FormattedResponse, OutputParams}; +use nym_node_http_api::api::api_requests::v1::metrics::models::VerlocResultData; +use nym_node_http_api::state::metrics::SharedVerlocStats; /// Provides verifiable location (verloc) measurements for this mixnode - a list of the /// round-trip times, in milliseconds, for all other mixnodes that this node knows about. pub(crate) async fn verloc( - State(verloc): State, + State(verloc): State, Query(output): Query, ) -> MixnodeVerlocResponse { let output = output.output.unwrap_or_default(); - output.to_response(verloc.shared.clone_data().await) + output.to_response(verloc.read().await.current_run_data.clone()) } -pub type MixnodeVerlocResponse = FormattedResponse; +pub type MixnodeVerlocResponse = FormattedResponse; diff --git a/mixnode/src/node/http/mod.rs b/mixnode/src/node/http/mod.rs index 55ca880e63..bb4fc37cc9 100644 --- a/mixnode/src/node/http/mod.rs +++ b/mixnode/src/node/http/mod.rs @@ -1,17 +1,16 @@ -// Copyright 2023 - Nym Technologies SA +// Copyright 2023-2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only use crate::config::Config; use crate::error::MixnodeError; -use crate::node::http::legacy::verloc::VerlocState; use crate::node::node_description::NodeDescription; -use crate::node::node_statistics::SharedNodeStats; use log::info; use nym_bin_common::bin_info_owned; use nym_crypto::asymmetric::{encryption, identity}; -use nym_node::error::NymNodeError; -use nym_node::http::api::api_requests; -use nym_node::http::api::api_requests::SignedHostInformation; +use nym_node_http_api::api::api_requests; +use nym_node_http_api::api::api_requests::SignedHostInformation; +use nym_node_http_api::state::metrics::{SharedMixingStats, SharedVerlocStats}; +use nym_node_http_api::NymNodeHttpError; use nym_task::TaskClient; pub(crate) mod legacy; @@ -25,13 +24,14 @@ fn load_host_details( ip_address: config.host.public_ips.clone(), hostname: config.host.hostname.clone(), keys: api_requests::v1::node::models::HostKeys { - ed25519: identity_keypair.public_key().to_base58_string(), - x25519: sphinx_key.to_base58_string(), + ed25519_identity: identity_keypair.public_key().to_base58_string(), + x25519_sphinx: sphinx_key.to_base58_string(), + x25519_noise: "".to_string(), }, }; let signed_info = SignedHostInformation::new(host_info, identity_keypair.private_key()) - .map_err(NymNodeError::from)?; + .map_err(NymNodeHttpError::from)?; Ok(signed_info) } @@ -71,13 +71,13 @@ impl<'a> HttpApiBuilder<'a> { } #[must_use] - pub(crate) fn with_verloc(mut self, verloc: VerlocState) -> Self { + pub(crate) fn with_verloc(mut self, verloc: SharedVerlocStats) -> Self { self.legacy_mixnode.verloc = verloc; self } #[must_use] - pub(crate) fn with_mixing_stats(mut self, stats: SharedNodeStats) -> Self { + pub(crate) fn with_mixing_stats(mut self, stats: SharedMixingStats) -> Self { self.legacy_mixnode.stats = stats; self } @@ -92,7 +92,7 @@ impl<'a> HttpApiBuilder<'a> { let bind_address = self.mixnode_config.http.bind_address; info!("Starting HTTP API on http://{bind_address}",); - let config = nym_node::http::Config::new( + let config = nym_node_http_api::Config::new( bin_info_owned!(), load_host_details( self.mixnode_config, @@ -103,7 +103,7 @@ impl<'a> HttpApiBuilder<'a> { .with_mixnode(load_mixnode_details(self.mixnode_config)?) .with_landing_page_assets(self.mixnode_config.http.landing_page_assets_path.as_ref()); - let router = nym_node::http::NymNodeRouter::new(config, None); + let router = nym_node_http_api::NymNodeRouter::new(config, None, None); let server = router .with_merged(legacy::routes(self.legacy_mixnode, self.legacy_descriptor)) .build_server(&bind_address)? diff --git a/mixnode/src/node/mod.rs b/mixnode/src/node/mod.rs index 045c83211f..8674d6ffe0 100644 --- a/mixnode/src/node/mod.rs +++ b/mixnode/src/node/mod.rs @@ -4,30 +4,29 @@ use crate::config::Config; use crate::error::MixnodeError; use crate::node::helpers::{load_identity_keys, load_sphinx_keys}; -use crate::node::http::legacy::verloc::VerlocState; use crate::node::http::HttpApiBuilder; use crate::node::listener::connection_handler::packet_processing::PacketProcessor; use crate::node::listener::connection_handler::ConnectionHandler; use crate::node::listener::Listener; use crate::node::node_description::NodeDescription; -use crate::node::node_statistics::SharedNodeStats; use crate::node::packet_delayforwarder::{DelayForwarder, PacketDelayForwardSender}; use log::{error, info, warn}; use nym_bin_common::output_format::OutputFormat; -use nym_bin_common::version_checker::parse_version; use nym_crypto::asymmetric::{encryption, identity}; -use nym_mixnode_common::verloc::{self, AtomicVerlocResult, VerlocMeasurer}; -use nym_task::{TaskClient, TaskManager}; +use nym_mixnode_common::verloc; +use nym_mixnode_common::verloc::VerlocMeasurer; +use nym_node_http_api::state::metrics::{SharedMixingStats, SharedVerlocStats}; +use nym_task::{TaskClient, TaskHandle}; use rand::seq::SliceRandom; use rand::thread_rng; use std::net::SocketAddr; use std::process; use std::sync::Arc; -pub(crate) mod helpers; +pub mod helpers; mod http; mod listener; -pub(crate) mod node_description; +pub mod node_description; mod node_statistics; mod packet_delayforwarder; @@ -37,24 +36,67 @@ pub struct MixNode { descriptor: NodeDescription, identity_keypair: Arc, sphinx_keypair: Arc, + + run_http_server: bool, + task_client: Option, + mixing_stats: Option, + verloc_stats: Option, } impl MixNode { pub fn new(config: Config) -> Result { Ok(MixNode { + run_http_server: false, descriptor: Self::load_node_description(&config), identity_keypair: Arc::new(load_identity_keys(&config)?), sphinx_keypair: Arc::new(load_sphinx_keys(&config)?), config, + task_client: None, + mixing_stats: None, + verloc_stats: None, }) } + pub fn new_loaded( + config: Config, + descriptor: NodeDescription, + identity_keypair: Arc, + sphinx_keypair: Arc, + ) -> Self { + MixNode { + run_http_server: false, + task_client: None, + config, + descriptor, + identity_keypair, + sphinx_keypair, + mixing_stats: None, + verloc_stats: None, + } + } + + pub fn disable_http_server(&mut self) { + self.run_http_server = false + } + + pub fn set_task_client(&mut self, task_client: TaskClient) { + self.task_client = Some(task_client) + } + + pub fn set_mixing_stats(&mut self, mixing_stats: SharedMixingStats) { + self.mixing_stats = Some(mixing_stats); + } + + pub fn set_verloc_stats(&mut self, verloc_stats: SharedVerlocStats) { + self.verloc_stats = Some(verloc_stats) + } + fn load_node_description(config: &Config) -> NodeDescription { NodeDescription::load_from_file(&config.storage_paths.node_description).unwrap_or_default() } /// Prints relevant node details to the console - pub(crate) fn print_node_details(&self, output: OutputFormat) { + pub fn print_node_details(&self, output: OutputFormat) { let node_details = nym_types::mixnode::MixnodeNodeDetailsResponse { identity_key: self.identity_keypair.public_key().to_base58_string(), sphinx_key: self.sphinx_keypair.public_key().to_base58_string(), @@ -70,13 +112,13 @@ impl MixNode { fn start_http_api( &self, - atomic_verloc_result: AtomicVerlocResult, - node_stats_pointer: SharedNodeStats, + atomic_verloc_result: SharedVerlocStats, + node_stats_pointer: SharedMixingStats, metrics_key: Option<&String>, task_client: TaskClient, ) -> Result<(), MixnodeError> { HttpApiBuilder::new(&self.config, &self.identity_keypair, &self.sphinx_keypair) - .with_verloc(VerlocState::new(atomic_verloc_result)) + .with_verloc(atomic_verloc_result) .with_mixing_stats(node_stats_pointer) .with_metrics_key(metrics_key) .with_descriptor(self.descriptor.clone()) @@ -84,19 +126,21 @@ impl MixNode { } fn start_node_stats_controller( - &self, + &mut self, shutdown: TaskClient, - ) -> (SharedNodeStats, node_statistics::UpdateSender) { + ) -> (SharedMixingStats, node_statistics::UpdateSender) { info!("Starting node stats controller..."); + let mixing_stats = self.mixing_stats.take().unwrap_or_default(); + let controller = node_statistics::Controller::new( self.config.debug.node_stats_logging_delay, self.config.debug.node_stats_updating_delay, + mixing_stats.clone(), shutdown, ); - let node_stats_pointer = controller.get_node_stats_data_pointer(); let update_sender = controller.start(); - (node_stats_pointer, update_sender) + (mixing_stats, update_sender) } fn start_socket_listener( @@ -147,22 +191,10 @@ impl MixNode { packet_sender } - fn start_verloc_measurements(&self, shutdown: TaskClient) -> AtomicVerlocResult { + fn start_verloc_measurements(&mut self, shutdown: TaskClient) -> SharedVerlocStats { info!("Starting the round-trip-time measurer..."); - // this is a sanity check to make sure we didn't mess up with the minimum version at some point - // and whether the user has run update if they're using old config - // if this code exists in the node, it MUST BE compatible - let config_version = parse_version(&self.config.mixnode.version) - .expect("malformed version in the config file"); - let minimum_version = parse_version(verloc::MINIMUM_NODE_VERSION).unwrap(); - if config_version < minimum_version { - error!("You seem to have not updated your mixnode configuration file - please run `upgrade` before attempting again"); - process::exit(1) - } - // use the same binding address with the HARDCODED port for time being (I don't like that approach personally) - let listening_address = SocketAddr::new( self.config.mixnode.listening_address, self.config.mixnode.verloc_port, @@ -180,11 +212,13 @@ impl MixNode { .nym_api_urls(self.config.get_nym_api_endpoints()) .build(); + let verloc_state = self.verloc_stats.take().unwrap_or_default(); let mut verloc_measurer = VerlocMeasurer::new(config, Arc::clone(&self.identity_keypair), shutdown); - let atomic_verloc_results = verloc_measurer.get_verloc_results_pointer(); + verloc_measurer.set_shared_state(verloc_state.clone()); + tokio::spawn(async move { verloc_measurer.run().await }); - atomic_verloc_results + verloc_state } fn random_api_client(&self) -> nym_validator_client::NymApiClient { @@ -217,8 +251,8 @@ impl MixNode { }) } - async fn wait_for_interrupt(&self, mut shutdown: TaskManager) { - let _res = shutdown.catch_interrupt().await; + async fn wait_for_interrupt(&self, shutdown: TaskHandle) { + let _res = shutdown.wait_for_shutdown().await; log::info!("Stopping nym mixnode"); } @@ -229,34 +263,42 @@ impl MixNode { warn!("You seem to have bonded your mixnode before starting it - that's highly unrecommended as in the future it might result in slashing"); } - let shutdown = TaskManager::default(); + // Shutdown notifier for signalling tasks to stop + let shutdown = self + .task_client + .take() + .map(Into::::into) + .unwrap_or_default() + .name_if_unnamed("mixnode"); - let (node_stats_pointer, node_stats_update_sender) = self - .start_node_stats_controller(shutdown.subscribe().named("node_statistics::Controller")); + let (node_stats_pointer, node_stats_update_sender) = + self.start_node_stats_controller(shutdown.fork("node_statistics::Controller")); let delay_forwarding_channel = self.start_packet_delay_forwarder( node_stats_update_sender.clone(), - shutdown.subscribe().named("DelayForwarder"), + shutdown.fork("DelayForwarder"), ); self.start_socket_listener( node_stats_update_sender, delay_forwarding_channel, - shutdown.subscribe().named("Listener"), + shutdown.fork("Listener"), ); - let atomic_verloc_results = - self.start_verloc_measurements(shutdown.subscribe().named("VerlocMeasurer")); + let atomic_verloc_results = self.start_verloc_measurements(shutdown.fork("VerlocMeasurer")); // Rocket handles shutdown on it's own, but its shutdown handling should be incorporated // with that of the rest of the tasks. // Currently it's runtime is forcefully terminated once the mixnode exits. - self.start_http_api( - atomic_verloc_results, - node_stats_pointer, - self.config.metrics_key(), - shutdown.subscribe().named("http-api"), - )?; + if self.run_http_server { + self.start_http_api( + atomic_verloc_results, + node_stats_pointer, + self.config.metrics_key(), + shutdown.fork("http-api"), + )?; + } info!("Finished nym mixnode startup procedure - it should now be able to receive mix traffic!"); self.wait_for_interrupt(shutdown).await; + Ok(()) } } diff --git a/mixnode/src/node/node_description.rs b/mixnode/src/node/node_description.rs index 6c41b28f24..c2d6bbacf4 100644 --- a/mixnode/src/node/node_description.rs +++ b/mixnode/src/node/node_description.rs @@ -3,15 +3,23 @@ use serde::Deserialize; use serde::Serialize; +use std::fs::create_dir_all; use std::path::Path; use std::{fs, io}; #[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)] pub struct NodeDescription { - pub(crate) name: String, - pub(crate) description: String, - pub(crate) link: String, - pub(crate) location: String, + #[serde(default)] + pub name: String, + + #[serde(default)] + pub description: String, + + #[serde(default)] + pub link: String, + + #[serde(default)] + pub location: String, } impl Default for NodeDescription { @@ -26,7 +34,7 @@ impl Default for NodeDescription { } impl NodeDescription { - pub(crate) fn load_from_file>(path: P) -> io::Result { + pub fn load_from_file>(path: P) -> io::Result { // let description_file_path: PathBuf = [config_path.to_str().unwrap(), DESCRIPTION_FILE] // .iter() // .collect(); @@ -36,15 +44,14 @@ impl NodeDescription { toml::from_str(&toml).map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err)) } - pub(crate) fn save_to_file>( - description: &NodeDescription, - path: P, - ) -> io::Result<()> { + pub fn save_to_file>(&self, path: P) -> io::Result<()> { // let description_file_path: PathBuf = [config_path.to_str().unwrap(), DESCRIPTION_FILE] // .iter() // .collect(); - let description_toml = - toml::to_string(description).expect("could not encode description to toml"); + let description_toml = toml::to_string(self).expect("could not encode description to toml"); + if let Some(parent) = path.as_ref().parent() { + create_dir_all(parent)?; + } fs::write(path, description_toml)?; Ok(()) } diff --git a/mixnode/src/node/node_statistics.rs b/mixnode/src/node/node_statistics.rs index 9ee8d6b850..7b84f89529 100644 --- a/mixnode/src/node/node_statistics.rs +++ b/mixnode/src/node/node_statistics.rs @@ -8,50 +8,27 @@ use futures::channel::mpsc; use futures::lock::Mutex; use futures::StreamExt; use log::{debug, info, trace}; -use serde::Serialize; +use nym_node_http_api::state::metrics::SharedMixingStats; use std::collections::HashMap; use std::ops::DerefMut; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; -use std::time::{Duration, SystemTime}; -use tokio::sync::{RwLock, RwLockReadGuard}; +use std::time::Duration; +use time::OffsetDateTime; // convenience aliases type PacketsMap = HashMap; type PacketDataReceiver = mpsc::UnboundedReceiver; type PacketDataSender = mpsc::UnboundedSender; -#[derive(Clone, Default)] -pub(crate) struct SharedNodeStats { - inner: Arc>, +trait MixingStatsUpdateExt { + async fn update(&self, new_received: u64, new_sent: PacketsMap, new_dropped: PacketsMap); } -impl SharedNodeStats { - pub(crate) fn new() -> Self { - let now = SystemTime::now(); - - SharedNodeStats { - inner: Arc::new(RwLock::new(NodeStats { - update_time: now, - previous_update_time: now, - packets_received_since_startup: 0, - packets_sent_since_startup_all: 0, - packets_dropped_since_startup_all: 0, - packets_received_since_last_update: 0, - packets_sent_since_last_update: HashMap::new(), - packets_explicitly_dropped_since_last_update: HashMap::new(), - })), - } - } - - pub(crate) async fn update( - &self, - new_received: u64, - new_sent: PacketsMap, - new_dropped: PacketsMap, - ) { - let mut guard = self.inner.write().await; - let snapshot_time = SystemTime::now(); +impl MixingStatsUpdateExt for SharedMixingStats { + async fn update(&self, new_received: u64, new_sent: PacketsMap, new_dropped: PacketsMap) { + let mut guard = self.write().await; + let snapshot_time = OffsetDateTime::now_utc(); guard.previous_update_time = guard.update_time; guard.update_time = snapshot_time; @@ -79,88 +56,6 @@ impl SharedNodeStats { guard.packets_sent_since_last_update = new_sent; guard.packets_explicitly_dropped_since_last_update = new_dropped; } - - pub(crate) async fn clone_data(&self) -> NodeStats { - self.inner.read().await.clone() - } - - async fn read(&self) -> RwLockReadGuard<'_, NodeStats> { - self.inner.read().await - } -} - -#[derive(Clone)] -pub struct NodeStats { - update_time: SystemTime, - - previous_update_time: SystemTime, - - packets_received_since_startup: u64, - packets_sent_since_startup_all: u64, - packets_dropped_since_startup_all: u64, - packets_received_since_last_update: u64, - // note: sent does not imply forwarded. We don't know if it was delivered successfully - packets_sent_since_last_update: PacketsMap, - // we know for sure we dropped packets to those destinations - packets_explicitly_dropped_since_last_update: PacketsMap, -} - -impl Default for NodeStats { - fn default() -> Self { - NodeStats { - update_time: SystemTime::UNIX_EPOCH, - previous_update_time: SystemTime::UNIX_EPOCH, - packets_received_since_startup: 0, - packets_sent_since_startup_all: 0, - packets_dropped_since_startup_all: 0, - packets_received_since_last_update: 0, - packets_sent_since_last_update: Default::default(), - packets_explicitly_dropped_since_last_update: Default::default(), - } - } -} - -impl NodeStats { - pub(crate) fn simplify(&self) -> NodeStatsSimple { - NodeStatsSimple { - update_time: self.update_time, - previous_update_time: self.previous_update_time, - packets_received_since_startup: self.packets_received_since_startup, - packets_sent_since_startup: self.packets_sent_since_startup_all, - packets_explicitly_dropped_since_startup: self.packets_dropped_since_startup_all, - packets_received_since_last_update: self.packets_received_since_last_update, - packets_sent_since_last_update: self.packets_sent_since_last_update.values().sum(), - packets_explicitly_dropped_since_last_update: self - .packets_explicitly_dropped_since_last_update - .values() - .sum(), - } - } -} - -#[derive(Serialize, Clone)] -pub struct NodeStatsSimple { - #[serde(serialize_with = "humantime_serde::serialize")] - update_time: SystemTime, - - #[serde(serialize_with = "humantime_serde::serialize")] - previous_update_time: SystemTime, - - packets_received_since_startup: u64, - - // note: sent does not imply forwarded. We don't know if it was delivered successfully - packets_sent_since_startup: u64, - - // we know for sure we dropped those packets - packets_explicitly_dropped_since_startup: u64, - - packets_received_since_last_update: u64, - - // note: sent does not imply forwarded. We don't know if it was delivered successfully - packets_sent_since_last_update: u64, - - // we know for sure we dropped those packets - packets_explicitly_dropped_since_last_update: u64, } pub(crate) enum PacketEvent { @@ -305,7 +200,7 @@ impl UpdateSender { struct StatsUpdater { updating_delay: Duration, current_packet_data: CurrentPacketData, - current_stats: SharedNodeStats, + current_stats: SharedMixingStats, shutdown: TaskClient, } @@ -313,7 +208,7 @@ impl StatsUpdater { fn new( updating_delay: Duration, current_packet_data: CurrentPacketData, - current_stats: SharedNodeStats, + current_stats: SharedMixingStats, shutdown: TaskClient, ) -> Self { StatsUpdater { @@ -347,12 +242,12 @@ impl StatsUpdater { // since we have the http endpoint now? struct PacketStatsConsoleLogger { logging_delay: Duration, - stats: SharedNodeStats, + stats: SharedMixingStats, shutdown: TaskClient, } impl PacketStatsConsoleLogger { - fn new(logging_delay: Duration, stats: SharedNodeStats, shutdown: TaskClient) -> Self { + fn new(logging_delay: Duration, stats: SharedMixingStats, shutdown: TaskClient) -> Self { PacketStatsConsoleLogger { logging_delay, stats, @@ -365,9 +260,10 @@ impl PacketStatsConsoleLogger { // it's super unlikely this will ever fail, but anything involving time is super weird // so let's just guard against it - if let Ok(time_difference) = stats.update_time.duration_since(stats.previous_update_time) { + let time_difference = stats.update_time - stats.previous_update_time; + if time_difference.is_positive() { // we honestly don't care if it was 30.000828427s or 30.002461449s, 30s is enough - let difference_secs = time_difference.as_secs(); + let difference_secs = time_difference.whole_seconds(); info!( "Since startup mixed {} packets! ({} in last {} seconds)", @@ -449,20 +345,17 @@ pub struct Controller { /// Responsible for updating stats at given interval stats_updater: StatsUpdater, - - /// Pointer to the current node stats - node_stats: SharedNodeStats, } impl Controller { pub(crate) fn new( logging_delay: Duration, stats_updating_delay: Duration, + mixing_stats: SharedMixingStats, shutdown: TaskClient, ) -> Self { let (sender, receiver) = mpsc::unbounded(); let shared_packet_data = CurrentPacketData::new(); - let shared_node_stats = SharedNodeStats::new(); Controller { update_handler: UpdateHandler::new( @@ -473,22 +366,15 @@ impl Controller { update_sender: UpdateSender::new(sender), console_logger: PacketStatsConsoleLogger::new( logging_delay, - shared_node_stats.clone(), + mixing_stats.clone(), shutdown.clone(), ), stats_updater: StatsUpdater::new( stats_updating_delay, shared_packet_data, - shared_node_stats.clone(), + mixing_stats.clone(), shutdown, ), - node_stats: shared_node_stats, - } - } - - pub(crate) fn get_node_stats_data_pointer(&self) -> SharedNodeStats { - SharedNodeStats { - inner: Arc::clone(&self.node_stats.inner), } } @@ -518,10 +404,14 @@ mod tests { let logging_delay = Duration::from_millis(20); let stats_updating_delay = Duration::from_millis(10); let shutdown = TaskManager::default(); - let node_stats_controller = - Controller::new(logging_delay, stats_updating_delay, shutdown.subscribe()); + let stats = SharedMixingStats::new(); + let node_stats_controller = Controller::new( + logging_delay, + stats_updating_delay, + stats.clone(), + shutdown.subscribe(), + ); - let node_stats_pointer = node_stats_controller.get_node_stats_data_pointer(); let update_sender = node_stats_controller.start(); tokio::time::pause(); @@ -534,7 +424,7 @@ mod tests { tokio::task::yield_now().await; // Get output (stats) - let stats = node_stats_pointer.read().await; + let stats = stats.read().await; assert_eq!(&stats.packets_sent_since_startup_all, &2); assert_eq!(&stats.packets_sent_since_last_update.get("foo"), &Some(&2)); assert_eq!(&stats.packets_sent_since_last_update.len(), &1); diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index 68b94fdc0a..83401e0e65 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -25,7 +25,7 @@ console-subscriber = { version = "0.1.1", optional = true } # validator-api need dirs = "4.0" futures = { workspace = true } itertools = "0.12.0" -humantime-serde = "1.0" +humantime-serde = { workspace = true } k256 = { version = "*", features = ["ecdsa-core"] } # needed for the Verifier trait; pull whatever version is used by other dependencies log = { workspace = true } pin-project = "1.0" diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index 5bf10c86c9..be63448ae5 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -301,7 +301,7 @@ checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.28", ] [[package]] @@ -318,7 +318,7 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.28", ] [[package]] @@ -392,6 +392,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + [[package]] name = "base16ct" version = "0.2.0" @@ -433,7 +439,7 @@ checksum = "7e141fb0f8be1c7b45887af94c88b182472b57c96b56773250ae00cd6a14a164" dependencies = [ "bs58 0.5.0", "hmac 0.12.1", - "k256", + "k256 0.13.1", "once_cell", "pbkdf2", "rand_core 0.6.4", @@ -537,6 +543,15 @@ dependencies = [ "generic-array 0.14.7", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array 0.14.7", +] + [[package]] name = "blocking" version = "1.3.1" @@ -558,8 +573,8 @@ version = "0.8.0" source = "git+https://github.com/jstuczyn/bls12_381?branch=feature/gt-serialization-0.8.0#c4543fde7d02efea6ecfcf22e14476ddb516b483" dependencies = [ "digest 0.9.0", - "ff", - "group", + "ff 0.13.0", + "group 0.13.0", "pairing", "rand_core 0.6.4", "subtle 2.4.1", @@ -568,9 +583,9 @@ dependencies = [ [[package]] name = "bnum" -version = "0.8.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab9008b6bb9fc80b5277f2fe481c09e828743d9151203e804583eb4c9e15b31d" +checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653" [[package]] name = "brotli" @@ -854,7 +869,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.28", ] [[package]] @@ -1028,9 +1043,9 @@ source = "git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-valida dependencies = [ "bip32", "cosmos-sdk-proto", - "ecdsa", + "ecdsa 0.16.8", "eyre", - "k256", + "k256 0.13.1", "rand_core 0.6.4", "serde", "serde_json", @@ -1043,32 +1058,31 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.5.3" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9934c79e58d9676edfd592557dee765d2a6ef54c09d5aa2edb06156b00148966" +checksum = "7e272708a9745dad8b591ef8a718571512130f2b39b33e3d7a27c558e3069394" dependencies = [ "digest 0.10.7", - "ecdsa", "ed25519-zebra", - "k256", + "k256 0.11.6", "rand_core 0.6.4", "thiserror", ] [[package]] name = "cosmwasm-derive" -version = "1.5.3" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5e72e330bd3bdab11c52b5ecbdeb6a8697a004c57964caeb5d876f0b088b3c" +checksum = "296db6a3caca5283425ae0cf347f4e46999ba3f6620dbea8939a0e00347831ce" dependencies = [ "syn 1.0.109", ] [[package]] name = "cosmwasm-schema" -version = "1.4.3" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ae2e971fb831d0c4fa3c8c3d2291cdbdd73786a73d65196dbf983d9b2468af" +checksum = "bb6b2fb76758ef59cddc77f2e2ae91c22f77da49037e9f182e9c2833f0e959b1" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -1079,9 +1093,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.4.3" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cadc57fd0825b85bc2f9b972c17da718b9efb4bc17e5935cc2d6036324f853d" +checksum = "2bfa39422f0d9f1c9a6fd3711573258495314dfa3aae738ea825ecd9964bc659" dependencies = [ "proc-macro2", "quote", @@ -1090,11 +1104,11 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.4.3" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e98e19fae6c3f468412f731274b0f9434602722009d6a77432d39c7c4bb09202" +checksum = "1f6dc2ee23313add5ecacc3ccac217b9967ad9d2d11bd56e5da6aa65a9da6138" dependencies = [ - "base64 0.21.4", + "base64 0.13.1", "bnum", "cosmwasm-crypto", "cosmwasm-derive", @@ -1194,6 +1208,18 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crypto-bigint" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" +dependencies = [ + "generic-array 0.14.7", + "rand_core 0.6.4", + "subtle 2.4.1", + "zeroize", +] + [[package]] name = "crypto-bigint" version = "0.5.2" @@ -1261,7 +1287,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.55", + "syn 2.0.28", ] [[package]] @@ -1330,7 +1356,7 @@ checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.28", ] [[package]] @@ -1363,9 +1389,9 @@ dependencies = [ [[package]] name = "cw-storage-plus" -version = "1.2.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5ff29294ee99373e2cd5fd21786a3c0ced99a52fec2ca347d565489c61b723c" +checksum = "3f0e92a069d62067f3472c62e30adedb4cab1754725c0f2a682b3128d2bf3c79" dependencies = [ "cosmwasm-std", "schemars", @@ -1382,31 +1408,30 @@ dependencies = [ "cosmwasm-std", "cw2", "schemars", - "semver 1.0.22", + "semver 1.0.18", "serde", "thiserror", ] [[package]] name = "cw2" -version = "1.1.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6c120b24fbbf5c3bedebb97f2cc85fbfa1c3287e09223428e7e597b5293c1fa" +checksum = "29ac2dc7a55ad64173ca1e0a46697c31b7a5c51342f55a1e84a724da4eb99908" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus", "schemars", - "semver 1.0.22", "serde", "thiserror", ] [[package]] name = "cw20" -version = "1.1.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "526e39bb20534e25a1cd0386727f0038f4da294e5e535729ba3ef54055246abd" +checksum = "011c45920f8200bd5d32d4fe52502506f64f2f75651ab408054d4cfc75ca3a9b" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1417,9 +1442,9 @@ dependencies = [ [[package]] name = "cw3" -version = "1.1.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2967fbd073d4b626dd9e7148e05a84a3bebd9794e71342e12351110ffbb12395" +checksum = "171af3d9127de6805a7dd819fb070c7d2f6c3ea85f4193f42cef259f0a7f33d5" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1432,9 +1457,9 @@ dependencies = [ [[package]] name = "cw4" -version = "1.1.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24754ff6e45f2a1c60adc409d9b2eb87666012c44021329141ffaab3388fccd2" +checksum = "a398696307efadaaa2d0850076f865fa706c959d493cb4203314f72be6b77a64" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1464,7 +1489,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.55", + "syn 2.0.28", ] [[package]] @@ -1475,7 +1500,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 2.0.55", + "syn 2.0.28", ] [[package]] @@ -1507,6 +1532,16 @@ dependencies = [ "uuid 1.4.1", ] +[[package]] +name = "der" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "der" version = "0.7.8" @@ -1682,19 +1717,31 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "304e6508efa593091e97a9abbc10f90aa7ca635b6d2784feff3c89d41dd12272" +[[package]] +name = "ecdsa" +version = "0.14.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" +dependencies = [ + "der 0.6.1", + "elliptic-curve 0.12.3", + "rfc6979 0.3.1", + "signature 1.6.4", +] + [[package]] name = "ecdsa" version = "0.16.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" dependencies = [ - "der", + "der 0.7.8", "digest 0.10.7", - "elliptic-curve", - "rfc6979", + "elliptic-curve 0.13.5", + "rfc6979 0.4.0", "serdect", "signature 2.1.0", - "spki", + "spki 0.7.2", ] [[package]] @@ -1713,7 +1760,7 @@ version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60f6d271ca33075c88028be6f04d502853d63a5ece419d269c15315d4fc1cf1d" dependencies = [ - "pkcs8", + "pkcs8 0.10.2", "signature 2.1.0", ] @@ -1766,21 +1813,41 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +[[package]] +name = "elliptic-curve" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" +dependencies = [ + "base16ct 0.1.1", + "crypto-bigint 0.4.9", + "der 0.6.1", + "digest 0.10.7", + "ff 0.12.1", + "generic-array 0.14.7", + "group 0.12.1", + "pkcs8 0.9.0", + "rand_core 0.6.4", + "sec1 0.3.0", + "subtle 2.4.1", + "zeroize", +] + [[package]] name = "elliptic-curve" version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" dependencies = [ - "base16ct", - "crypto-bigint", + "base16ct 0.2.0", + "crypto-bigint 0.5.2", "digest 0.10.7", - "ff", + "ff 0.13.0", "generic-array 0.14.7", - "group", - "pkcs8", + "group 0.13.0", + "pkcs8 0.10.2", "rand_core 0.6.4", - "sec1", + "sec1 0.7.3", "serdect", "subtle 2.4.1", "zeroize", @@ -1832,7 +1899,7 @@ checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.28", ] [[package]] @@ -1925,6 +1992,16 @@ dependencies = [ "log", ] +[[package]] +name = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ + "rand_core 0.6.4", + "subtle 2.4.1", +] + [[package]] name = "ff" version = "0.13.0" @@ -2151,7 +2228,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.28", ] [[package]] @@ -2508,13 +2585,24 @@ dependencies = [ "system-deps 6.1.1", ] +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff 0.12.1", + "rand_core 0.6.4", + "subtle 2.4.1", +] + [[package]] name = "group" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ - "ff", + "ff 0.13.0", "rand_core 0.6.4", "subtle 2.4.1", ] @@ -2765,6 +2853,20 @@ dependencies = [ "itoa 1.0.9", ] +[[package]] +name = "http-api-client" +version = "0.1.0" +dependencies = [ + "async-trait", + "reqwest", + "serde", + "serde_json", + "thiserror", + "tracing", + "url", + "wasmtimer", +] + [[package]] name = "http-body" version = "0.4.5" @@ -3050,6 +3152,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ + "block-padding", "generic-array 0.14.7", ] @@ -3175,6 +3278,18 @@ dependencies = [ "treediff", ] +[[package]] +name = "k256" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" +dependencies = [ + "cfg-if", + "ecdsa 0.14.8", + "elliptic-curve 0.12.3", + "sha2 0.10.8", +] + [[package]] name = "k256" version = "0.13.1" @@ -3182,11 +3297,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" dependencies = [ "cfg-if", - "ecdsa", - "elliptic-curve", - "once_cell", + "ecdsa 0.16.8", + "elliptic-curve 0.13.5", "sha2 0.10.8", - "signature 2.1.0", ] [[package]] @@ -3662,7 +3775,7 @@ dependencies = [ "bs58 0.5.0", "cosmrs", "cosmwasm-std", - "ecdsa", + "ecdsa 0.16.8", "getset", "nym-credentials-interface", "nym-crypto", @@ -3696,7 +3809,6 @@ dependencies = [ name = "nym-bin-common" version = "0.6.0" dependencies = [ - "atty", "clap", "clap_complete", "clap_complete_fig", @@ -3725,9 +3837,11 @@ dependencies = [ "hyper-util", "log", "nym-bandwidth-controller", + "nym-client-core-config-types", "nym-client-core-gateways-storage", "nym-client-core-surb-storage", "nym-config", + "nym-country-group", "nym-credential-storage", "nym-crypto", "nym-explorer-client", @@ -3762,6 +3876,21 @@ dependencies = [ "zeroize", ] +[[package]] +name = "nym-client-core-config-types" +version = "0.1.0" +dependencies = [ + "humantime-serde", + "nym-config", + "nym-country-group", + "nym-pemstore", + "nym-sphinx-addressing", + "nym-sphinx-params", + "serde", + "thiserror", + "url", +] + [[package]] name = "nym-client-core-gateways-storage" version = "0.1.0" @@ -3803,9 +3932,9 @@ dependencies = [ "bls12_381", "bs58 0.5.0", "digest 0.9.0", - "ff", + "ff 0.13.0", "getrandom 0.2.10", - "group", + "group 0.13.0", "itertools", "nym-dkg", "nym-pemstore", @@ -3914,6 +4043,14 @@ dependencies = [ "thiserror", ] +[[package]] +name = "nym-country-group" +version = "0.1.0" +dependencies = [ + "serde", + "tracing", +] + [[package]] name = "nym-credential-storage" version = "0.1.0" @@ -3986,8 +4123,8 @@ dependencies = [ "bitvec", "bls12_381", "bs58 0.5.0", - "ff", - "group", + "ff 0.13.0", + "group 0.13.0", "lazy_static", "nym-pemstore", "rand 0.8.5", @@ -4108,20 +4245,6 @@ dependencies = [ "serde", ] -[[package]] -name = "nym-http-api-client" -version = "0.1.0" -dependencies = [ - "async-trait", - "reqwest", - "serde", - "serde_json", - "thiserror", - "tracing", - "url", - "wasmtimer", -] - [[package]] name = "nym-id" version = "0.1.0" @@ -4210,6 +4333,8 @@ name = "nym-node-requests" version = "0.1.0" dependencies = [ "base64 0.21.4", + "humantime 2.1.0", + "humantime-serde", "nym-bin-common", "nym-crypto", "nym-exit-policy", @@ -4218,6 +4343,7 @@ dependencies = [ "serde", "serde_json", "thiserror", + "time", ] [[package]] @@ -4547,6 +4673,7 @@ dependencies = [ "eyre", "flate2", "futures", + "http-api-client", "itertools", "log", "nym-api-requests", @@ -4557,7 +4684,6 @@ dependencies = [ "nym-contracts-common", "nym-ephemera-common", "nym-group-contract-common", - "nym-http-api-client", "nym-mixnet-contract-common", "nym-multisig-contract-common", "nym-name-service-common", @@ -4701,7 +4827,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.28", ] [[package]] @@ -4761,7 +4887,7 @@ version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" dependencies = [ - "group", + "group 0.13.0", ] [[package]] @@ -4939,7 +5065,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.28", ] [[package]] @@ -5068,7 +5194,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.28", ] [[package]] @@ -5083,14 +5209,24 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der 0.6.1", + "spki 0.6.0", +] + [[package]] name = "pkcs8" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der", - "spki", + "der 0.7.8", + "spki 0.7.2", ] [[package]] @@ -5229,9 +5365,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] @@ -5271,7 +5407,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.28", ] [[package]] @@ -5321,9 +5457,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.35" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" dependencies = [ "proc-macro2", ] @@ -5572,6 +5708,17 @@ dependencies = [ "winreg 0.50.0", ] +[[package]] +name = "rfc6979" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +dependencies = [ + "crypto-bigint 0.4.9", + "hmac 0.12.1", + "zeroize", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -5650,7 +5797,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.55", + "syn 2.0.28", "walkdir", ] @@ -5677,7 +5824,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.22", + "semver 1.0.18", ] [[package]] @@ -5800,9 +5947,9 @@ dependencies = [ [[package]] name = "schemars" -version = "0.8.16" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" +checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" dependencies = [ "dyn-clone", "indexmap 1.9.3", @@ -5813,9 +5960,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.16" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" +checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c" dependencies = [ "proc-macro2", "quote", @@ -5845,16 +5992,30 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sec1" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +dependencies = [ + "base16ct 0.1.1", + "der 0.6.1", + "generic-array 0.14.7", + "pkcs8 0.9.0", + "subtle 2.4.1", + "zeroize", +] + [[package]] name = "sec1" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ - "base16ct", - "der", + "base16ct 0.2.0", + "der 0.7.8", "generic-array 0.14.7", - "pkcs8", + "pkcs8 0.10.2", "serdect", "subtle 2.4.1", "zeroize", @@ -5914,9 +6075,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.22" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" dependencies = [ "serde", ] @@ -6062,9 +6223,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.197" +version = "1.0.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" dependencies = [ "serde_derive", ] @@ -6089,13 +6250,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.28", ] [[package]] @@ -6128,7 +6289,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.28", ] [[package]] @@ -6178,7 +6339,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.28", ] [[package]] @@ -6187,7 +6348,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" dependencies = [ - "base16ct", + "base16ct 0.2.0", "serde", ] @@ -6297,6 +6458,10 @@ name = "signature" version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] [[package]] name = "signature" @@ -6422,6 +6587,16 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spki" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +dependencies = [ + "base64ct", + "der 0.6.1", +] + [[package]] name = "spki" version = "0.7.2" @@ -6429,7 +6604,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" dependencies = [ "base64ct", - "der", + "der 0.7.8", ] [[package]] @@ -6642,9 +6817,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.55" +version = "2.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" +checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" dependencies = [ "proc-macro2", "quote", @@ -6813,7 +6988,7 @@ dependencies = [ "regex", "reqwest", "rfd", - "semver 1.0.22", + "semver 1.0.18", "serde", "serde_json", "serde_repr", @@ -6846,7 +7021,7 @@ dependencies = [ "cargo_toml", "heck 0.4.1", "json-patch", - "semver 1.0.22", + "semver 1.0.18", "serde", "serde_json", "tauri-utils", @@ -6868,7 +7043,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "semver 1.0.22", + "semver 1.0.18", "serde", "serde_json", "sha2 0.10.8", @@ -6953,7 +7128,7 @@ dependencies = [ "phf 0.10.1", "proc-macro2", "quote", - "semver 1.0.22", + "semver 1.0.18", "serde", "serde_json", "serde_with", @@ -7008,7 +7183,7 @@ dependencies = [ "ed25519-consensus", "flex-error", "futures", - "k256", + "k256 0.13.1", "num-traits", "once_cell", "prost", @@ -7073,7 +7248,7 @@ dependencies = [ "peg", "pin-project", "reqwest", - "semver 1.0.22", + "semver 1.0.18", "serde", "serde_bytes", "serde_json", @@ -7119,22 +7294,22 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" [[package]] name = "thiserror" -version = "1.0.58" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.58" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.28", ] [[package]] @@ -7220,7 +7395,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.28", ] [[package]] @@ -7377,7 +7552,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.28", ] [[package]] @@ -7453,7 +7628,7 @@ dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.28", "termcolor", ] @@ -7751,7 +7926,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.28", "wasm-bindgen-shared", ] @@ -7785,7 +7960,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.28", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -8466,7 +8641,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.28", ] [[package]] diff --git a/nym-connect/desktop/src-tauri/src/error.rs b/nym-connect/desktop/src-tauri/src/error.rs index 5b324a8850..e8fb548942 100644 --- a/nym-connect/desktop/src-tauri/src/error.rs +++ b/nym-connect/desktop/src-tauri/src/error.rs @@ -113,6 +113,9 @@ pub enum BackendError { source: Box, }, + #[error(transparent)] + ConfigUpgradeFailure(#[from] nym_client_core::config::ConfigUpgradeFailure), + #[error("HTTP get request failed: {status_code}")] RequestFail { url: reqwest::Url, diff --git a/nym-node/Cargo.toml b/nym-node/Cargo.toml index 745be5d884..e7e4ae1eda 100644 --- a/nym-node/Cargo.toml +++ b/nym-node/Cargo.toml @@ -14,46 +14,48 @@ license = "GPL-3.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -anyhow = { workspace = true } -bytes = "1.5.0" -colored = "2" -ipnetwork = "0.16" +anyhow.workspace = true +bip39 = { workspace = true, features = ["zeroize"] } +bs58.workspace = true +celes = "2.4.0" # country codes +clap = { workspace = true, features = ["cargo", "env"] } +humantime-serde = { workspace = true } +ipnetwork = "0.16.0" rand = "0.7.3" serde = { workspace = true, features = ["derive"] } -serde_yaml = "0.9.25" -serde_json = { workspace = true } -thiserror = { workspace = true } -tracing = { workspace = true } +serde_json.workspace = true +thiserror.workspace = true +tracing.workspace = true tokio = { workspace = true, features = ["macros", "sync"] } +toml = "0.8.12" +url = { workspace = true, features = ["serde"] } +zeroize = { workspace = true, features = ["zeroize_derive"] } -# Wireguard: -fastrand = "2" +# temporary bonding information v1 (to grab and parse nym-mixnode and nym-gateway package versions) +semver = "1.0.22" -# HTTP API: -axum = { workspace = true } -# useful for `#[axum_macros::debug_handler]` -#axum-macros = "0.3.8" -mime = "0.3.17" -hyper = { workspace = true } -tower = { version = "0.4.13" } -tower-http = { version = "0.4.4", features = ["fs"] } -utoipa = { workspace = true, features = ["axum_extras"] } -utoipa-swagger-ui = { workspace = true, features = ["axum"] } - -# if we ever wanted redoc/rapidoc bridges: -#utoipa-redoc = { version = "0.1.0", features = ["axum"] } -#utoipa-rapidoc = { version = "0.1.0", features = ["axum"] } +# system info: +cupid = "0.6.1" +sysinfo = "0.30.7" +nym-bin-common = { path = "../common/bin-common", features = ["basic_tracing", "output_format"] } +nym-client-core-config-types = { path = "../common/client-core/config-types" } nym-config = { path = "../common/config" } -nym-crypto = { path = "../common/crypto", features = ["asymmetric" ]} -nym-node-requests = { path = "nym-node-requests", default-features = false, features = ["openapi"]} +nym-crypto = { path = "../common/crypto", features = ["asymmetric", "rand"] } +nym-node-http-api = { path = "nym-node-http-api" } +nym-pemstore = { path = "../common/pemstore" } +nym-sphinx-acknowledgements = { path = "../common/nymsphinx/acknowledgements" } +nym-sphinx-addressing = { path = "../common/nymsphinx/addressing" } nym-task = { path = "../common/task" } -nym-wireguard = { path = "../common/wireguard" } -nym-wireguard-types = { path = "../common/wireguard-types", features = ["verify"] } +nym-types = { path = "../common/types" } +nym-wireguard-types = { path = "../common/wireguard-types", default-features = false } -[dev-dependencies] -dashmap = { workspace = true } -rand = "0.7.3" -nym-crypto = { path = "../common/crypto", features = ["rand"]} -hmac = "0.12.1" -x25519-dalek = { version = "2.0.0" } +# nodes: +nym-mixnode = { path = "../mixnode" } +nym-gateway = { path = "../gateway" } +nym-network-requester = { path = "../service-providers/network-requester" } +nym-ip-packet-router = { path = "../service-providers/ip-packet-router" } + +[build-dependencies] +# temporary bonding information v1 (to grab and parse nym-mixnode and nym-gateway package versions) +cargo_metadata = "0.18.1" \ No newline at end of file diff --git a/nym-node/build.rs b/nym-node/build.rs new file mode 100644 index 0000000000..f705ff54e1 --- /dev/null +++ b/nym-node/build.rs @@ -0,0 +1,36 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use cargo_metadata::MetadataCommand; +use std::fs; +use std::path::PathBuf; + +// that's disgusting, but it works, so it's good enough for now ¯\_(ツ)_/¯ +fn main() { + let out_dir: PathBuf = std::env::var("OUT_DIR").unwrap().into(); + + let path: PathBuf = std::env::var("CARGO_MANIFEST_DIR").unwrap().into(); + + let mix_path = path.parent().unwrap().join("mixnode"); + let gateway_path = path.parent().unwrap().join("gateway"); + + let mix_meta = MetadataCommand::new() + .manifest_path("./Cargo.toml") + .current_dir(&mix_path) + .exec() + .unwrap(); + let mix_version = &mix_meta.root_package().unwrap().version; + + let gateway_meta = MetadataCommand::new() + .manifest_path("./Cargo.toml") + .current_dir(&gateway_path) + .exec() + .unwrap(); + + let gateway_version = &gateway_meta.root_package().unwrap().version; + + fs::write(out_dir.join("mixnode_version"), mix_version.to_string()).unwrap(); + fs::write(out_dir.join("gateway_version"), gateway_version.to_string()).unwrap(); + + println!("cargo::rerun-if-changed=build.rs"); +} diff --git a/nym-node/nym-node-http-api/Cargo.toml b/nym-node/nym-node-http-api/Cargo.toml new file mode 100644 index 0000000000..a1150a0a52 --- /dev/null +++ b/nym-node/nym-node-http-api/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "nym-node-http-api" +version = "0.1.0" +edition = "2021" +license.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +axum = { workspace = true, features = ["headers"] } +# \/ will be needed once we update axum to 0.7 +#axum-extra = { version = "0.9.3", features = ["typed-header"] } +#headers = "0.4" + +# useful for `#[axum_macros::debug_handler]` +#axum-macros = "0.3.8" +hyper.workspace = true +thiserror.workspace = true +time = { workspace = true, features = ["serde"] } +tokio = { workspace = true, features = ["macros"] } +tower-http = { version = "0.4.4", features = ["fs"] } +tracing.workspace = true +utoipa = { workspace = true, features = ["axum_extras", "time"] } +utoipa-swagger-ui = { workspace = true, features = ["axum"] } + +colored = "2" +ipnetwork = "0.16" +rand = "0.7.3" + +# Wireguard: +fastrand = "2" + +nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] } +nym-http-api-common = { path = "../../common/http-api-common" } +nym-node-requests = { path = "../nym-node-requests", default-features = false, features = ["openapi"] } +nym-task = { path = "../../common/task" } + +nym-metrics = { path = "../../common/nym-metrics" } +nym-wireguard = { path = "../../common/wireguard" } +nym-wireguard-types = { path = "../../common/wireguard-types", features = ["verify"] } + +[dev-dependencies] +dashmap.workspace = true +serde_json.workspace = true + +hmac = "0.12.1" +tower = { version = "0.4.13" } +x25519-dalek = { version = "2.0.0" } + +nym-crypto = { path = "../../common/crypto", features = ["rand"] } diff --git a/nym-node/nym-node-http-api/src/error.rs b/nym-node/nym-node-http-api/src/error.rs new file mode 100644 index 0000000000..b19b8e429b --- /dev/null +++ b/nym-node/nym-node-http-api/src/error.rs @@ -0,0 +1,26 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use std::net::SocketAddr; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum NymNodeHttpError { + #[error("failed to bind the HTTP API to {bind_address}: {source}")] + HttpBindFailure { + bind_address: SocketAddr, + source: hyper::Error, + }, + + #[error("failed to use nym-node requests: {source}")] + RequestError { + #[from] + source: nym_node_requests::error::Error, + }, + + #[error(transparent)] + KeyRecoveryError { + #[from] + source: nym_crypto::asymmetric::encryption::KeyRecoveryError, + }, +} diff --git a/nym-node/nym-node-http-api/src/lib.rs b/nym-node/nym-node-http-api/src/lib.rs new file mode 100644 index 0000000000..7c690b3ea0 --- /dev/null +++ b/nym-node/nym-node-http-api/src/lib.rs @@ -0,0 +1,66 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use axum::extract::connect_info::IntoMakeServiceWithConnectInfo; +use axum::Router; +use hyper::server::conn::AddrIncoming; +use hyper::Server; +use nym_task::TaskClient; +use std::net::SocketAddr; +use tracing::{debug, error, info}; + +pub mod error; +pub mod middleware; +pub mod router; +pub mod state; + +pub use error::NymNodeHttpError; +pub use router::{api, landing_page, Config, NymNodeRouter}; + +pub struct NymNodeHTTPServer { + task_client: Option, + inner: Server>, +} + +impl NymNodeHTTPServer { + pub(crate) fn new( + inner: Server>, + ) -> Self { + NymNodeHTTPServer { + task_client: None, + inner, + } + } + + #[must_use] + pub fn with_task_client(mut self, task_client: TaskClient) -> Self { + self.task_client = Some(task_client); + self + } + + async fn run_server_forever( + server: Server>, + ) { + if let Err(err) = server.await { + error!("the HTTP server has terminated with the error: {err}"); + } else { + error!("the HTTP server has terminated with producing any errors"); + } + } + + pub async fn run(self) { + info!("Started NymNodeHTTPServer on {}", self.inner.local_addr()); + if let Some(mut task_client) = self.task_client { + tokio::select! { + _ = task_client.recv_with_delay() => { + debug!("NymNodeHTTPServer: Received shutdown"); + } + _ = Self::run_server_forever(self.inner) => { } + } + } else { + Self::run_server_forever(self.inner).await + } + + debug!("NymNodeHTTPServer: Exiting"); + } +} diff --git a/nym-node/src/http/middleware/logging.rs b/nym-node/nym-node-http-api/src/middleware/logging.rs similarity index 100% rename from nym-node/src/http/middleware/logging.rs rename to nym-node/nym-node-http-api/src/middleware/logging.rs diff --git a/nym-node/src/http/middleware/mod.rs b/nym-node/nym-node-http-api/src/middleware/mod.rs similarity index 100% rename from nym-node/src/http/middleware/mod.rs rename to nym-node/nym-node-http-api/src/middleware/mod.rs diff --git a/nym-node/src/http/mod.rs b/nym-node/nym-node-http-api/src/mod.rs similarity index 100% rename from nym-node/src/http/mod.rs rename to nym-node/nym-node-http-api/src/mod.rs diff --git a/nym-node/nym-node-http-api/src/router/api/mod.rs b/nym-node/nym-node-http-api/src/router/api/mod.rs new file mode 100644 index 0000000000..70e1cc433f --- /dev/null +++ b/nym-node/nym-node-http-api/src/router/api/mod.rs @@ -0,0 +1,25 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::api::v1::gateway::client_interfaces::wireguard::WireguardAppState; +use crate::state::AppState; +use axum::Router; +use nym_node_requests::routes; + +pub mod v1; + +pub(crate) use nym_http_api_common::{FormattedResponse, Output, OutputParams}; + +pub use nym_node_requests::api as api_requests; + +#[derive(Debug, Clone)] +pub struct Config { + pub v1_config: v1::Config, +} + +pub(super) fn routes(config: Config, initial_wg_state: WireguardAppState) -> Router { + Router::new().nest( + routes::api::V1, + v1::routes(config.v1_config, initial_wg_state), + ) +} diff --git a/nym-node/src/http/router/api/v1/gateway/client_interfaces/mod.rs b/nym-node/nym-node-http-api/src/router/api/v1/gateway/client_interfaces/mod.rs similarity index 96% rename from nym-node/src/http/router/api/v1/gateway/client_interfaces/mod.rs rename to nym-node/nym-node-http-api/src/router/api/v1/gateway/client_interfaces/mod.rs index 3bd789a248..1de50f5f8c 100644 --- a/nym-node/src/http/router/api/v1/gateway/client_interfaces/mod.rs +++ b/nym-node/nym-node-http-api/src/router/api/v1/gateway/client_interfaces/mod.rs @@ -1,8 +1,8 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::http::api::v1::gateway::client_interfaces::wireguard::WireguardAppState; -use crate::http::api::{FormattedResponse, OutputParams}; +use crate::api::v1::gateway::client_interfaces::wireguard::WireguardAppState; +use crate::api::{FormattedResponse, OutputParams}; use axum::extract::Query; use axum::http::StatusCode; use axum::routing::get; diff --git a/nym-node/src/http/router/api/v1/gateway/client_interfaces/wireguard/client_registry.rs b/nym-node/nym-node-http-api/src/router/api/v1/gateway/client_interfaces/wireguard/client_registry.rs similarity index 97% rename from nym-node/src/http/router/api/v1/gateway/client_interfaces/wireguard/client_registry.rs rename to nym-node/nym-node-http-api/src/router/api/v1/gateway/client_interfaces/wireguard/client_registry.rs index d214d6304f..1b28e29206 100644 --- a/nym-node/src/http/router/api/v1/gateway/client_interfaces/wireguard/client_registry.rs +++ b/nym-node/nym-node-http-api/src/router/api/v1/gateway/client_interfaces/wireguard/client_registry.rs @@ -1,12 +1,12 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::http::api::v1::gateway::client_interfaces::wireguard::{ +use super::error::WireguardError; +use crate::api::v1::gateway::client_interfaces::wireguard::{ WireguardAppState, WireguardAppStateInner, }; -use crate::http::api::{FormattedResponse, OutputParams}; -use crate::http::router::types::RequestError; -use crate::wireguard::error::WireguardError; +use crate::api::{FormattedResponse, OutputParams}; +use crate::router::types::RequestError; use axum::extract::{Path, Query, State}; use axum::http::StatusCode; use axum::Json; diff --git a/nym-node/nym-node-http-api/src/router/api/v1/gateway/client_interfaces/wireguard/error.rs b/nym-node/nym-node-http-api/src/router/api/v1/gateway/client_interfaces/wireguard/error.rs new file mode 100644 index 0000000000..04895b0e4c --- /dev/null +++ b/nym-node/nym-node-http-api/src/router/api/v1/gateway/client_interfaces/wireguard/error.rs @@ -0,0 +1,13 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum WireguardError { + #[error("the client is currently not in the process of being registered")] + RegistrationNotInProgress, + + #[error("the client mac failed to get verified correctly")] + MacVerificationFailure, +} diff --git a/nym-node/src/http/router/api/v1/gateway/client_interfaces/wireguard/mod.rs b/nym-node/nym-node-http-api/src/router/api/v1/gateway/client_interfaces/wireguard/mod.rs similarity index 96% rename from nym-node/src/http/router/api/v1/gateway/client_interfaces/wireguard/mod.rs rename to nym-node/nym-node-http-api/src/router/api/v1/gateway/client_interfaces/wireguard/mod.rs index eec31b244c..775b1a69b5 100644 --- a/nym-node/src/http/router/api/v1/gateway/client_interfaces/wireguard/mod.rs +++ b/nym-node/nym-node-http-api/src/router/api/v1/gateway/client_interfaces/wireguard/mod.rs @@ -1,10 +1,10 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::http::api::v1::gateway::client_interfaces::wireguard::client_registry::{ +use crate::api::v1::gateway::client_interfaces::wireguard::client_registry::{ get_all_clients, get_client, register_client, }; -use crate::wireguard::types::{GatewayClientRegistry, PendingRegistrations}; +use crate::error::NymNodeHttpError; use axum::routing::{get, post}; use axum::Router; use ipnetwork::IpNetwork; @@ -12,9 +12,11 @@ use nym_crypto::asymmetric::encryption::PrivateKey; use nym_node_requests::routes::api::v1::gateway::client_interfaces::wireguard; use nym_wireguard::setup; use nym_wireguard_types::registration::PrivateIPs; +use nym_wireguard_types::registration::{GatewayClientRegistry, PendingRegistrations}; use std::sync::Arc; pub(crate) mod client_registry; +mod error; // I don't see any reason why this state should be accessible to any routes outside /wireguard // if anyone finds compelling reason, it could be moved to the `AppState` struct instead @@ -29,7 +31,7 @@ impl WireguardAppState { registration_in_progress: Arc, binding_port: u16, private_ip_network: IpNetwork, - ) -> Result { + ) -> Result { Ok(WireguardAppState { inner: Some(WireguardAppStateInner { private_key: Arc::new(PrivateKey::from_bytes( @@ -99,7 +101,7 @@ pub(crate) fn routes(initial_state: WireguardAppState) -> Router { #[cfg(test)] mod test { - use crate::http::api::v1::gateway::client_interfaces::wireguard::{ + use crate::api::v1::gateway::client_interfaces::wireguard::{ routes, WireguardAppState, WireguardAppStateInner, }; use axum::body::Body; diff --git a/nym-node/src/http/router/api/v1/gateway/mod.rs b/nym-node/nym-node-http-api/src/router/api/v1/gateway/mod.rs similarity index 91% rename from nym-node/src/http/router/api/v1/gateway/mod.rs rename to nym-node/nym-node-http-api/src/router/api/v1/gateway/mod.rs index 6b9ee71d14..e95f79d643 100644 --- a/nym-node/src/http/router/api/v1/gateway/mod.rs +++ b/nym-node/nym-node-http-api/src/router/api/v1/gateway/mod.rs @@ -1,7 +1,7 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::http::api::v1::gateway::client_interfaces::wireguard::WireguardAppState; +use crate::api::v1::gateway::client_interfaces::wireguard::WireguardAppState; use axum::routing::get; use axum::Router; use nym_node_requests::api::v1::gateway::models; diff --git a/nym-node/src/http/router/api/v1/gateway/root.rs b/nym-node/nym-node-http-api/src/router/api/v1/gateway/root.rs similarity index 93% rename from nym-node/src/http/router/api/v1/gateway/root.rs rename to nym-node/nym-node-http-api/src/router/api/v1/gateway/root.rs index 04edf2681a..cb6fc6cbfd 100644 --- a/nym-node/src/http/router/api/v1/gateway/root.rs +++ b/nym-node/nym-node-http-api/src/router/api/v1/gateway/root.rs @@ -1,7 +1,7 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::http::router::api::{FormattedResponse, OutputParams}; +use crate::router::api::{FormattedResponse, OutputParams}; use axum::extract::Query; use axum::http::StatusCode; use nym_node_requests::api::v1::gateway::models::Gateway; diff --git a/nym-node/src/http/router/api/v1/health.rs b/nym-node/nym-node-http-api/src/router/api/v1/health.rs similarity index 91% rename from nym-node/src/http/router/api/v1/health.rs rename to nym-node/nym-node-http-api/src/router/api/v1/health.rs index 7b26a61c35..4beb5f6af5 100644 --- a/nym-node/src/http/router/api/v1/health.rs +++ b/nym-node/nym-node-http-api/src/router/api/v1/health.rs @@ -1,8 +1,8 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::http::api::{FormattedResponse, OutputParams}; -use crate::http::state::AppState; +use crate::api::{FormattedResponse, OutputParams}; +use crate::state::AppState; use axum::extract::{Query, State}; use nym_node_requests::api::v1::health::models::NodeHealth; diff --git a/nym-node/src/http/router/api/v1/ip_packet_router/mod.rs b/nym-node/nym-node-http-api/src/router/api/v1/ip_packet_router/mod.rs similarity index 100% rename from nym-node/src/http/router/api/v1/ip_packet_router/mod.rs rename to nym-node/nym-node-http-api/src/router/api/v1/ip_packet_router/mod.rs diff --git a/nym-node/src/http/router/api/v1/ip_packet_router/root.rs b/nym-node/nym-node-http-api/src/router/api/v1/ip_packet_router/root.rs similarity index 94% rename from nym-node/src/http/router/api/v1/ip_packet_router/root.rs rename to nym-node/nym-node-http-api/src/router/api/v1/ip_packet_router/root.rs index 498bf7c084..41fc8e536c 100644 --- a/nym-node/src/http/router/api/v1/ip_packet_router/root.rs +++ b/nym-node/nym-node-http-api/src/router/api/v1/ip_packet_router/root.rs @@ -1,7 +1,7 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::http::router::api::{FormattedResponse, OutputParams}; +use crate::router::api::{FormattedResponse, OutputParams}; use axum::extract::Query; use axum::http::StatusCode; use nym_node_requests::api::v1::ip_packet_router::models::IpPacketRouter; diff --git a/nym-node/nym-node-http-api/src/router/api/v1/metrics/mixing.rs b/nym-node/nym-node-http-api/src/router/api/v1/metrics/mixing.rs new file mode 100644 index 0000000000..8ae07c61e6 --- /dev/null +++ b/nym-node/nym-node-http-api/src/router/api/v1/metrics/mixing.rs @@ -0,0 +1,33 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::state::metrics::MetricsAppState; +use axum::extract::{Query, State}; +use nym_http_api_common::{FormattedResponse, OutputParams}; +use nym_node_requests::api::v1::metrics::models::MixingStats; + +/// If applicable, returns mixing statistics information of this node. +/// This information is **PURELY** self-reported and in no way validated. +#[utoipa::path( + get, + path = "/mixing", + context_path = "/api/v1/metrics", + tag = "Metrics", + responses( + (status = 200, content( + ("application/json" = MixingStats), + ("application/yaml" = MixingStats) + )) + ), + params(OutputParams), +)] +pub(crate) async fn mixing_stats( + Query(output): Query, + State(metrics_state): State, +) -> MixingStatsResponse { + let output = output.output.unwrap_or_default(); + let response = metrics_state.mixing_stats.read().await.as_response(); + output.to_response(response) +} + +pub type MixingStatsResponse = FormattedResponse; diff --git a/nym-node/nym-node-http-api/src/router/api/v1/metrics/mod.rs b/nym-node/nym-node-http-api/src/router/api/v1/metrics/mod.rs new file mode 100644 index 0000000000..7f3dc9151e --- /dev/null +++ b/nym-node/nym-node-http-api/src/router/api/v1/metrics/mod.rs @@ -0,0 +1,31 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::api::v1::metrics::mixing::mixing_stats; +use crate::api::v1::metrics::prometheus::prometheus_metrics; +use crate::api::v1::metrics::verloc::verloc_stats; +use crate::state::metrics::MetricsAppState; +use axum::extract::FromRef; +use axum::routing::get; +use axum::Router; +use nym_node_requests::routes::api::v1::metrics; + +pub mod mixing; +pub mod prometheus; +pub mod verloc; + +#[derive(Debug, Clone, Default)] +pub struct Config { + // +} + +pub(super) fn routes(_config: Config) -> Router +where + S: Send + Sync + 'static + Clone, + MetricsAppState: FromRef, +{ + Router::new() + .route(metrics::MIXING, get(mixing_stats)) + .route(metrics::VERLOC, get(verloc_stats)) + .route(metrics::PROMETHEUS, get(prometheus_metrics)) +} diff --git a/nym-node/nym-node-http-api/src/router/api/v1/metrics/prometheus.rs b/nym-node/nym-node-http-api/src/router/api/v1/metrics/prometheus.rs new file mode 100644 index 0000000000..eae64267bd --- /dev/null +++ b/nym-node/nym-node-http-api/src/router/api/v1/metrics/prometheus.rs @@ -0,0 +1,45 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::state::metrics::MetricsAppState; +use axum::extract::State; +use axum::headers::authorization::Bearer; +use axum::headers::Authorization; +use axum::http::StatusCode; +use axum::TypedHeader; +use nym_metrics::metrics; + +/// Returns `prometheus` compatible metrics +#[utoipa::path( + get, + path = "/prometheus", + context_path = "/api/v1/metrics", + tag = "Metrics", + responses( + (status = 200, body = String), + (status = 400, description = "`Authorization` header was missing"), + (status = 401, description = "Access token is missing or invalid"), + (status = 500, description = "No access token has been specified on the node"), + ), + security( + ("prometheus_token" = []) + ) +)] +pub(crate) async fn prometheus_metrics<'a>( + TypedHeader(authorization): TypedHeader>, + State(state): State, +) -> Result { + if authorization.token().is_empty() { + return Err(StatusCode::UNAUTHORIZED); + } + // TODO: is 500 the correct error code here? + let Some(metrics_key) = state.prometheus_access_token else { + return Err(StatusCode::INTERNAL_SERVER_ERROR); + }; + + if metrics_key != authorization.token() { + return Err(StatusCode::UNAUTHORIZED); + } + + Ok(metrics!()) +} diff --git a/nym-node/nym-node-http-api/src/router/api/v1/metrics/verloc.rs b/nym-node/nym-node-http-api/src/router/api/v1/metrics/verloc.rs new file mode 100644 index 0000000000..c622ccf123 --- /dev/null +++ b/nym-node/nym-node-http-api/src/router/api/v1/metrics/verloc.rs @@ -0,0 +1,32 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::state::metrics::MetricsAppState; +use axum::extract::{Query, State}; +use nym_http_api_common::{FormattedResponse, OutputParams}; +use nym_node_requests::api::v1::metrics::models::VerlocStats; + +/// If applicable, returns verloc statistics information of this node. +#[utoipa::path( + get, + path = "/verloc", + context_path = "/api/v1/metrics", + tag = "Metrics", + responses( + (status = 200, content( + ("application/json" = VerlocStats), + ("application/yaml" = VerlocStats) + )) + ), + params(OutputParams), +)] +pub(crate) async fn verloc_stats( + Query(output): Query, + State(metrics_state): State, +) -> VerlocStatsResponse { + let output = output.output.unwrap_or_default(); + let response = metrics_state.verloc.read().await.as_response(); + output.to_response(response) +} + +pub type VerlocStatsResponse = FormattedResponse; diff --git a/nym-node/src/http/router/api/v1/mixnode/mod.rs b/nym-node/nym-node-http-api/src/router/api/v1/mixnode/mod.rs similarity index 100% rename from nym-node/src/http/router/api/v1/mixnode/mod.rs rename to nym-node/nym-node-http-api/src/router/api/v1/mixnode/mod.rs diff --git a/nym-node/src/http/router/api/v1/mixnode/root.rs b/nym-node/nym-node-http-api/src/router/api/v1/mixnode/root.rs similarity index 93% rename from nym-node/src/http/router/api/v1/mixnode/root.rs rename to nym-node/nym-node-http-api/src/router/api/v1/mixnode/root.rs index 1ca216b55d..d7f56ba3e0 100644 --- a/nym-node/src/http/router/api/v1/mixnode/root.rs +++ b/nym-node/nym-node-http-api/src/router/api/v1/mixnode/root.rs @@ -1,7 +1,7 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::http::router::api::{FormattedResponse, OutputParams}; +use crate::router::api::{FormattedResponse, OutputParams}; use axum::extract::Query; use axum::http::StatusCode; use nym_node_requests::api::v1::mixnode::models::Mixnode; diff --git a/nym-node/src/http/router/api/v1/mod.rs b/nym-node/nym-node-http-api/src/router/api/v1/mod.rs similarity index 85% rename from nym-node/src/http/router/api/v1/mod.rs rename to nym-node/nym-node-http-api/src/router/api/v1/mod.rs index 97d7aff172..cae97ac05d 100644 --- a/nym-node/src/http/router/api/v1/mod.rs +++ b/nym-node/nym-node-http-api/src/router/api/v1/mod.rs @@ -1,8 +1,8 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::http::api::v1::gateway::client_interfaces::wireguard::WireguardAppState; -use crate::http::state::AppState; +use crate::api::v1::gateway::client_interfaces::wireguard::WireguardAppState; +use crate::state::AppState; use axum::routing::get; use axum::Router; use nym_node_requests::routes::api::v1; @@ -10,6 +10,7 @@ use nym_node_requests::routes::api::v1; pub mod gateway; pub mod health; pub mod ip_packet_router; +pub mod metrics; pub mod mixnode; pub mod network_requester; pub mod node; @@ -18,6 +19,7 @@ pub mod openapi; #[derive(Debug, Clone)] pub struct Config { pub node: node::Config, + pub metrics: metrics::Config, pub gateway: gateway::Config, pub mixnode: mixnode::Config, pub network_requester: network_requester::Config, @@ -27,6 +29,7 @@ pub struct Config { pub(super) fn routes(config: Config, initial_wg_state: WireguardAppState) -> Router { Router::new() .route(v1::HEALTH, get(health::root_health)) + .nest(v1::METRICS, metrics::routes(config.metrics)) .nest( v1::GATEWAY, gateway::routes(config.gateway, initial_wg_state), diff --git a/nym-node/src/http/router/api/v1/network_requester/exit_policy.rs b/nym-node/nym-node-http-api/src/router/api/v1/network_requester/exit_policy.rs similarity index 93% rename from nym-node/src/http/router/api/v1/network_requester/exit_policy.rs rename to nym-node/nym-node-http-api/src/router/api/v1/network_requester/exit_policy.rs index ab649711f4..70bc69ec62 100644 --- a/nym-node/src/http/router/api/v1/network_requester/exit_policy.rs +++ b/nym-node/nym-node-http-api/src/router/api/v1/network_requester/exit_policy.rs @@ -1,7 +1,7 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::http::api::{FormattedResponse, OutputParams}; +use crate::api::{FormattedResponse, OutputParams}; use axum::extract::Query; use nym_node_requests::api::v1::network_requester::exit_policy::models::UsedExitPolicy; diff --git a/nym-node/src/http/router/api/v1/network_requester/mod.rs b/nym-node/nym-node-http-api/src/router/api/v1/network_requester/mod.rs similarity index 93% rename from nym-node/src/http/router/api/v1/network_requester/mod.rs rename to nym-node/nym-node-http-api/src/router/api/v1/network_requester/mod.rs index 19f08cb368..917b5a668b 100644 --- a/nym-node/src/http/router/api/v1/network_requester/mod.rs +++ b/nym-node/nym-node-http-api/src/router/api/v1/network_requester/mod.rs @@ -1,7 +1,7 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::http::api::v1::network_requester::exit_policy::node_exit_policy; +use crate::api::v1::network_requester::exit_policy::node_exit_policy; use axum::routing::get; use axum::Router; use nym_node_requests::api::v1::network_requester::exit_policy::models::UsedExitPolicy; diff --git a/nym-node/src/http/router/api/v1/network_requester/root.rs b/nym-node/nym-node-http-api/src/router/api/v1/network_requester/root.rs similarity index 94% rename from nym-node/src/http/router/api/v1/network_requester/root.rs rename to nym-node/nym-node-http-api/src/router/api/v1/network_requester/root.rs index 4294a4d348..ac92133e53 100644 --- a/nym-node/src/http/router/api/v1/network_requester/root.rs +++ b/nym-node/nym-node-http-api/src/router/api/v1/network_requester/root.rs @@ -1,7 +1,7 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::http::router::api::{FormattedResponse, OutputParams}; +use crate::router::api::{FormattedResponse, OutputParams}; use axum::extract::Query; use axum::http::StatusCode; use nym_node_requests::api::v1::network_requester::models::NetworkRequester; diff --git a/nym-node/src/http/router/api/v1/node/build_information.rs b/nym-node/nym-node-http-api/src/router/api/v1/node/build_information.rs similarity index 93% rename from nym-node/src/http/router/api/v1/node/build_information.rs rename to nym-node/nym-node-http-api/src/router/api/v1/node/build_information.rs index 98bd47e691..b877f43d17 100644 --- a/nym-node/src/http/router/api/v1/node/build_information.rs +++ b/nym-node/nym-node-http-api/src/router/api/v1/node/build_information.rs @@ -1,7 +1,7 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::http::router::api::{FormattedResponse, OutputParams}; +use crate::router::api::{FormattedResponse, OutputParams}; use axum::extract::Query; use nym_node_requests::api::v1::node::models::BinaryBuildInformationOwned; diff --git a/nym-node/nym-node-http-api/src/router/api/v1/node/description.rs b/nym-node/nym-node-http-api/src/router/api/v1/node/description.rs new file mode 100644 index 0000000000..f49d311505 --- /dev/null +++ b/nym-node/nym-node-http-api/src/router/api/v1/node/description.rs @@ -0,0 +1,31 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::api::{FormattedResponse, OutputParams}; +use crate::router::types::RequestError; +use axum::extract::Query; +use nym_node_requests::api::v1::node::models::NodeDescription; + +/// Returns human-readable description of this node. +#[utoipa::path( + get, + path = "/description", + context_path = "/api/v1", + tag = "Node", + responses( + (status = 200, content( + ("application/json" = NodeDescription), + ("application/yaml" = NodeDescription) + )), + ), + params(OutputParams) +)] +pub(crate) async fn description( + description: NodeDescription, + Query(output): Query, +) -> Result { + let output = output.output.unwrap_or_default(); + Ok(output.to_response(description)) +} + +pub type NodeDescriptionResponse = FormattedResponse; diff --git a/nym-node/nym-node-http-api/src/router/api/v1/node/hardware.rs b/nym-node/nym-node-http-api/src/router/api/v1/node/hardware.rs new file mode 100644 index 0000000000..2d17b9e3a2 --- /dev/null +++ b/nym-node/nym-node-http-api/src/router/api/v1/node/hardware.rs @@ -0,0 +1,41 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::api::{FormattedResponse, OutputParams}; +use crate::router::types::RequestError; +use axum::extract::Query; +use axum::http::StatusCode; +use nym_node_requests::api::v1::node::models::HostSystem; + +/// Returns build system information of the host running the binary. +#[utoipa::path( + get, + path = "/system-info", + context_path = "/api/v1", + tag = "Node", + responses( + (status = 200, content( + ("application/json" = HostSystem), + ("application/yaml" = HostSystem) + )), + (status = 403, body = ErrorResponse, description = "the node does not wish to expose the system information") + ), + params(OutputParams) +)] +pub(crate) async fn host_system( + system_info: Option, + Query(output): Query, +) -> Result { + let output = output.output.unwrap_or_default(); + + let Some(system_info) = system_info else { + return Err(RequestError::new( + "this nym-node does not wish to expose the system information", + StatusCode::FORBIDDEN, + )); + }; + + Ok(output.to_response(system_info)) +} + +pub type HostSystemResponse = FormattedResponse; diff --git a/nym-node/src/http/router/api/v1/node/host_information.rs b/nym-node/nym-node-http-api/src/router/api/v1/node/host_information.rs similarity index 93% rename from nym-node/src/http/router/api/v1/node/host_information.rs rename to nym-node/nym-node-http-api/src/router/api/v1/node/host_information.rs index c52f00a58b..b4d7f6d363 100644 --- a/nym-node/src/http/router/api/v1/node/host_information.rs +++ b/nym-node/nym-node-http-api/src/router/api/v1/node/host_information.rs @@ -1,7 +1,7 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::http::api::{FormattedResponse, OutputParams}; +use crate::api::{FormattedResponse, OutputParams}; use axum::extract::Query; use nym_node_requests::api::v1::node::models::SignedHostInformation; diff --git a/nym-node/src/http/router/api/v1/node/mod.rs b/nym-node/nym-node-http-api/src/router/api/v1/node/mod.rs similarity index 59% rename from nym-node/src/http/router/api/v1/node/mod.rs rename to nym-node/nym-node-http-api/src/router/api/v1/node/mod.rs index 620a446090..c9d228d781 100644 --- a/nym-node/src/http/router/api/v1/node/mod.rs +++ b/nym-node/nym-node-http-api/src/router/api/v1/node/mod.rs @@ -1,15 +1,19 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::http::api::v1::node::build_information::build_information; -use crate::http::api::v1::node::host_information::host_information; -use crate::http::api::v1::node::roles::roles; +use crate::api::v1::node::build_information::build_information; +use crate::api::v1::node::description::description; +use crate::api::v1::node::hardware::host_system; +use crate::api::v1::node::host_information::host_information; +use crate::api::v1::node::roles::roles; use axum::routing::get; use axum::Router; use nym_node_requests::api::v1::node::models; use nym_node_requests::routes::api::v1; pub mod build_information; +pub mod description; +pub mod hardware; pub mod host_information; pub mod roles; @@ -17,7 +21,9 @@ pub mod roles; pub struct Config { pub build_information: models::BinaryBuildInformationOwned, pub host_information: models::SignedHostInformation, + pub system_info: Option, pub roles: models::NodeRoles, + pub description: models::NodeDescription, } pub(super) fn routes(config: Config) -> Router { @@ -43,4 +49,18 @@ pub(super) fn routes(config: Config) -> Router move |query| host_information(host_info, query) }), ) + .route( + v1::SYSTEM_INFO, + get({ + let system_info = config.system_info; + move |query| host_system(system_info, query) + }), + ) + .route( + v1::NODE_DESCRIPTION, + get({ + let node_description = config.description; + move |query| description(node_description, query) + }), + ) } diff --git a/nym-node/src/http/router/api/v1/node/roles.rs b/nym-node/nym-node-http-api/src/router/api/v1/node/roles.rs similarity index 92% rename from nym-node/src/http/router/api/v1/node/roles.rs rename to nym-node/nym-node-http-api/src/router/api/v1/node/roles.rs index 43648e77c0..145b13798d 100644 --- a/nym-node/src/http/router/api/v1/node/roles.rs +++ b/nym-node/nym-node-http-api/src/router/api/v1/node/roles.rs @@ -1,7 +1,7 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::http::router::api::{FormattedResponse, OutputParams}; +use crate::router::api::{FormattedResponse, OutputParams}; use axum::extract::Query; use nym_node_requests::api::v1::node::models::NodeRoles; diff --git a/nym-node/src/http/router/api/v1/openapi.rs b/nym-node/nym-node-http-api/src/router/api/v1/openapi.rs similarity index 69% rename from nym-node/src/http/router/api/v1/openapi.rs rename to nym-node/nym-node-http-api/src/router/api/v1/openapi.rs index e23eca84f3..bd82c3e3a7 100644 --- a/nym-node/src/http/router/api/v1/openapi.rs +++ b/nym-node/nym-node-http-api/src/router/api/v1/openapi.rs @@ -1,12 +1,13 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::http::router::api; -use crate::http::router::types::{ErrorResponse, RequestError}; +use crate::router::api; +use crate::router::types::{ErrorResponse, RequestError}; use axum::Router; use nym_node_requests::api as api_requests; use nym_node_requests::routes::api::v1; -use utoipa::OpenApi; +use utoipa::openapi::security::{Http, HttpAuthScheme}; +use utoipa::{openapi::security::SecurityScheme, Modify, OpenApi}; use utoipa_swagger_ui::SwaggerUi; #[derive(OpenApi)] @@ -16,6 +17,11 @@ use utoipa_swagger_ui::SwaggerUi; api::v1::node::build_information::build_information, api::v1::node::host_information::host_information, api::v1::node::roles::roles, + api::v1::node::hardware::host_system, + api::v1::node::description::description, + api::v1::metrics::mixing::mixing_stats, + api::v1::metrics::verloc::verloc_stats, + api::v1::metrics::prometheus::prometheus_metrics, api::v1::health::root_health, api::v1::gateway::root::root_gateway, api::v1::gateway::client_interfaces::client_interfaces, @@ -41,6 +47,17 @@ use utoipa_swagger_ui::SwaggerUi; api_requests::v1::node::models::HostInformation, api_requests::v1::node::models::HostKeys, api_requests::v1::node::models::NodeRoles, + api_requests::v1::node::models::HostSystem, + api_requests::v1::node::models::Hardware, + api_requests::v1::node::models::Cpu, + api_requests::v1::node::models::CryptoHardware, + api_requests::v1::node::models::NodeDescription, + api_requests::v1::metrics::models::MixingStats, + api_requests::v1::metrics::models::VerlocStats, + api_requests::v1::metrics::models::VerlocResult, + api_requests::v1::metrics::models::VerlocResultData, + api_requests::v1::metrics::models::VerlocNodeResult, + api_requests::v1::metrics::models::VerlocMeasurement, api_requests::v1::gateway::models::Gateway, api_requests::v1::gateway::models::Wireguard, api_requests::v1::gateway::models::ClientInterfaces, @@ -59,11 +76,25 @@ use utoipa_swagger_ui::SwaggerUi; api_requests::v1::network_requester::exit_policy::models::UsedExitPolicy, api_requests::v1::ip_packet_router::models::IpPacketRouter, ), - responses(RequestError) - ) + responses(RequestError), + ), + modifiers(&SecurityAddon), )] pub(crate) struct ApiDoc; +struct SecurityAddon; + +impl Modify for SecurityAddon { + fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) { + if let Some(components) = openapi.components.as_mut() { + components.add_security_scheme( + "prometheus_token", + SecurityScheme::Http(Http::new(HttpAuthScheme::Bearer)), + ) + } + } +} + pub(crate) fn route() -> Router { // provide absolute path to the openapi.json let config = utoipa_swagger_ui::Config::from("/api/v1/api-docs/openapi.json"); diff --git a/nym-node/src/http/router/landing_page.rs b/nym-node/nym-node-http-api/src/router/landing_page.rs similarity index 100% rename from nym-node/src/http/router/landing_page.rs rename to nym-node/nym-node-http-api/src/router/landing_page.rs diff --git a/nym-node/src/http/router/mod.rs b/nym-node/nym-node-http-api/src/router/mod.rs similarity index 62% rename from nym-node/src/http/router/mod.rs rename to nym-node/nym-node-http-api/src/router/mod.rs index 71de3c3c8d..a32f441afa 100644 --- a/nym-node/src/http/router/mod.rs +++ b/nym-node/nym-node-http-api/src/router/mod.rs @@ -1,11 +1,13 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::error::NymNodeError; -pub use crate::http::api::v1::gateway::client_interfaces::wireguard::WireguardAppState; -use crate::http::middleware::logging; -use crate::http::state::AppState; -use crate::http::NymNodeHTTPServer; +pub use crate::api::v1::gateway::client_interfaces::wireguard::WireguardAppState; +use crate::error::NymNodeHttpError; +use crate::middleware::logging; +use crate::state::AppState; +use crate::NymNodeHTTPServer; +use axum::response::Redirect; +use axum::routing::get; use axum::Router; use nym_node_requests::api::v1::gateway::models::{Gateway, Wireguard}; use nym_node_requests::api::v1::ip_packet_router::models::IpPacketRouter; @@ -13,6 +15,7 @@ use nym_node_requests::api::v1::mixnode::models::Mixnode; use nym_node_requests::api::v1::network_requester::exit_policy::models::UsedExitPolicy; use nym_node_requests::api::v1::network_requester::models::NetworkRequester; use nym_node_requests::api::v1::node::models; +use nym_node_requests::api::v1::node::models::{HostSystem, NodeDescription}; use nym_node_requests::api::SignedHostInformation; use nym_node_requests::routes; use std::net::SocketAddr; @@ -41,8 +44,11 @@ impl Config { node: api::v1::node::Config { build_information, host_information, + system_info: None, roles: Default::default(), + description: Default::default(), }, + metrics: Default::default(), gateway: Default::default(), mixnode: Default::default(), network_requester: Default::default(), @@ -70,9 +76,26 @@ impl Config { self } + #[must_use] + pub fn with_system_info(mut self, info: HostSystem) -> Self { + self.api.v1_config.node.system_info = Some(info); + self + } + + #[must_use] + pub fn with_description(mut self, description: NodeDescription) -> Self { + self.api.v1_config.node.description = description; + self + } + #[must_use] pub fn with_gateway(mut self, gateway: Gateway) -> Self { self.api.v1_config.node.roles.gateway_enabled = true; + self.with_gateway_details(gateway) + } + + #[must_use] + pub fn with_gateway_details(mut self, gateway: Gateway) -> Self { self.api.v1_config.gateway.details = Some(gateway); self } @@ -80,6 +103,11 @@ impl Config { #[must_use] pub fn with_mixnode(mut self, mixnode: Mixnode) -> Self { self.api.v1_config.node.roles.mixnode_enabled = true; + self.with_mixnode_details(mixnode) + } + + #[must_use] + pub fn with_mixnode_details(mut self, mixnode: Mixnode) -> Self { self.api.v1_config.mixnode.details = Some(mixnode); self } @@ -87,6 +115,11 @@ impl Config { #[must_use] pub fn with_network_requester(mut self, network_requester: NetworkRequester) -> Self { self.api.v1_config.node.roles.network_requester_enabled = true; + self.with_network_requester_details(network_requester) + } + + #[must_use] + pub fn with_network_requester_details(mut self, network_requester: NetworkRequester) -> Self { self.api.v1_config.network_requester.details = Some(network_requester); self } @@ -100,6 +133,11 @@ impl Config { #[must_use] pub fn with_ip_packet_router(mut self, ip_packet_router: IpPacketRouter) -> Self { self.api.v1_config.node.roles.ip_packet_router_enabled = true; + self.with_ip_packet_router_details(ip_packet_router) + } + + #[must_use] + pub fn with_ip_packet_router_details(mut self, ip_packet_router: IpPacketRouter) -> Self { self.api.v1_config.ip_packet_router.details = Some(ip_packet_router); self } @@ -111,11 +149,42 @@ pub struct NymNodeRouter { impl NymNodeRouter { // TODO: move the wg state to a builder - pub fn new(config: Config, initial_wg_state: Option) -> NymNodeRouter { - let state = AppState::new(); + pub fn new( + config: Config, + app_state: Option, + initial_wg_state: Option, + ) -> NymNodeRouter { + let state = app_state.unwrap_or(AppState::new()); NymNodeRouter { inner: Router::new() + // redirection for old legacy mixnode routes + .route( + "/hardware", + get(|| async { Redirect::permanent(&routes::api::v1::system_info_absolute()) }), + ) + .route( + "/description", + get(|| async { Redirect::permanent(&routes::api::v1::description_absolute()) }), + ) + .route( + "/stats", + get(|| async { + Redirect::permanent(&routes::api::v1::metrics::mixing_absolute()) + }), + ) + .route( + "/verloc", + get(|| async { + Redirect::permanent(&routes::api::v1::metrics::verloc_absolute()) + }), + ) + .route( + "/metrics", + get(|| async { + Redirect::permanent(&routes::api::v1::metrics::prometheus_absolute()) + }), + ) .nest(routes::LANDING_PAGE, landing_page::routes(config.landing)) .nest( routes::API, @@ -142,9 +211,9 @@ impl NymNodeRouter { pub fn build_server( self, bind_address: &SocketAddr, - ) -> Result { + ) -> Result { let axum_server = axum::Server::try_bind(bind_address) - .map_err(|source| NymNodeError::HttpBindFailure { + .map_err(|source| NymNodeHttpError::HttpBindFailure { bind_address: *bind_address, source, })? diff --git a/nym-node/src/http/router/types.rs b/nym-node/nym-node-http-api/src/router/types.rs similarity index 100% rename from nym-node/src/http/router/types.rs rename to nym-node/nym-node-http-api/src/router/types.rs diff --git a/nym-node/nym-node-http-api/src/state/metrics.rs b/nym-node/nym-node-http-api/src/state/metrics.rs new file mode 100644 index 0000000000..d4691727d9 --- /dev/null +++ b/nym-node/nym-node-http-api/src/state/metrics.rs @@ -0,0 +1,148 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::state::AppState; +use axum::extract::FromRef; +use nym_node_requests::api::v1::metrics::models::{ + MixingStats, VerlocResult, VerlocResultData, VerlocStats, +}; +use std::collections::HashMap; +use std::sync::Arc; +use time::OffsetDateTime; +use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; + +pub use nym_node_requests::api::v1::metrics::models::{VerlocMeasurement, VerlocNodeResult}; + +type PacketsMap = HashMap; + +#[derive(Clone, Debug, Default)] +pub struct SharedMixingStats { + inner: Arc>, +} + +impl SharedMixingStats { + pub fn new() -> SharedMixingStats { + let now = OffsetDateTime::now_utc(); + + SharedMixingStats { + inner: Arc::new(RwLock::new(MixingStatsState { + update_time: now, + previous_update_time: now, + ..Default::default() + })), + } + } + + pub async fn read(&self) -> RwLockReadGuard<'_, MixingStatsState> { + self.inner.read().await + } + + pub async fn write(&self) -> RwLockWriteGuard<'_, MixingStatsState> { + self.inner.write().await + } +} + +#[derive(Debug)] +pub struct MixingStatsState { + pub update_time: OffsetDateTime, + + pub previous_update_time: OffsetDateTime, + + pub packets_received_since_startup: u64, + pub packets_sent_since_startup_all: u64, + pub packets_dropped_since_startup_all: u64, + pub packets_received_since_last_update: u64, + + // note: sent does not imply forwarded. We don't know if it was delivered successfully + pub packets_sent_since_last_update: PacketsMap, + + // we know for sure we dropped packets to those destinations + pub packets_explicitly_dropped_since_last_update: PacketsMap, +} + +impl MixingStatsState { + pub fn as_response(&self) -> MixingStats { + MixingStats { + update_time: self.update_time, + previous_update_time: self.previous_update_time, + received_since_startup: self.packets_received_since_startup, + sent_since_startup: self.packets_sent_since_startup_all, + dropped_since_startup: self.packets_dropped_since_startup_all, + received_since_last_update: self.packets_received_since_last_update, + sent_since_last_update: self.packets_sent_since_last_update.values().sum(), + dropped_since_last_update: self + .packets_explicitly_dropped_since_last_update + .values() + .sum(), + } + } +} + +impl Default for MixingStatsState { + fn default() -> Self { + MixingStatsState { + update_time: OffsetDateTime::UNIX_EPOCH, + previous_update_time: OffsetDateTime::UNIX_EPOCH, + packets_received_since_startup: 0, + packets_sent_since_startup_all: 0, + packets_dropped_since_startup_all: 0, + packets_received_since_last_update: 0, + packets_sent_since_last_update: Default::default(), + packets_explicitly_dropped_since_last_update: Default::default(), + } + } +} + +#[derive(Clone, Debug, Default)] +pub struct SharedVerlocStats { + inner: Arc>, +} + +#[derive(Clone, Debug, Default)] +pub struct VerlocStatsState { + pub current_run_data: VerlocResultData, + pub previous_run_data: VerlocResultData, +} + +impl SharedVerlocStats { + pub async fn read(&self) -> RwLockReadGuard<'_, VerlocStatsState> { + self.inner.read().await + } + + pub async fn write(&self) -> RwLockWriteGuard<'_, VerlocStatsState> { + self.inner.write().await + } +} + +impl VerlocStatsState { + pub fn as_response(&self) -> VerlocStats { + let previous = if !self.previous_run_data.run_finished() { + VerlocResult::Unavailable + } else { + VerlocResult::Data(self.previous_run_data.clone()) + }; + + let current = if !self.current_run_data.run_finished() { + VerlocResult::MeasurementInProgress + } else { + VerlocResult::Data(self.current_run_data.clone()) + }; + + VerlocStats { previous, current } + } +} + +#[derive(Debug, Clone, Default)] +pub struct MetricsAppState { + pub(crate) prometheus_access_token: Option, + + pub(crate) mixing_stats: SharedMixingStats, + + pub(crate) verloc: SharedVerlocStats, +} + +impl FromRef for MetricsAppState { + fn from_ref(app_state: &AppState) -> Self { + app_state.metrics.clone() + } +} diff --git a/nym-node/nym-node-http-api/src/state/mod.rs b/nym-node/nym-node-http-api/src/state/mod.rs new file mode 100644 index 0000000000..077ca782b6 --- /dev/null +++ b/nym-node/nym-node-http-api/src/state/mod.rs @@ -0,0 +1,46 @@ +// Copyright 2023-2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::state::metrics::{MetricsAppState, SharedMixingStats, SharedVerlocStats}; +use tokio::time::Instant; + +pub mod metrics; + +#[derive(Debug, Clone)] +pub struct AppState { + pub(crate) startup_time: Instant, + + pub(crate) metrics: MetricsAppState, +} + +impl AppState { + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + AppState { + // is it 100% accurate? + // no. + // does it have to be? + // also no. + startup_time: Instant::now(), + metrics: Default::default(), + } + } + + #[must_use] + pub fn with_mixing_stats(mut self, mixing_stats: SharedMixingStats) -> Self { + self.metrics.mixing_stats = mixing_stats; + self + } + + #[must_use] + pub fn with_verloc_stats(mut self, verloc_stats: SharedVerlocStats) -> Self { + self.metrics.verloc = verloc_stats; + self + } + + #[must_use] + pub fn with_metrics_key(mut self, bearer_token: impl Into>) -> Self { + self.metrics.prometheus_access_token = bearer_token.into(); + self + } +} diff --git a/nym-node/nym-node-requests/Cargo.toml b/nym-node/nym-node-requests/Cargo.toml index 2761a1c9ec..e8fc8c089b 100644 --- a/nym-node/nym-node-requests/Cargo.toml +++ b/nym-node/nym-node-requests/Cargo.toml @@ -12,12 +12,15 @@ license.workspace = true [dependencies] base64 = { workspace = true } +humantime = "2.1.0" +humantime-serde = { workspace = true } schemars = { workspace = true, features = ["preserve_order"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } +time = { workspace = true, features = ["serde", "formatting", "parsing"] } thiserror = { workspace = true } -nym-crypto = { path = "../../common/crypto", features = ["asymmetric"] } +nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "serde"] } nym-exit-policy = { path = "../../common/exit-policy" } nym-wireguard-types = { path = "../../common/wireguard-types", default-features = false } diff --git a/nym-node/nym-node-requests/src/api/mod.rs b/nym-node/nym-node-requests/src/api/mod.rs index 8da95074a9..180685e284 100644 --- a/nym-node/nym-node-requests/src/api/mod.rs +++ b/nym-node/nym-node-requests/src/api/mod.rs @@ -56,7 +56,8 @@ impl SignedData { impl SignedHostInformation { pub fn verify_host_information(&self) -> bool { - let Ok(pub_key) = identity::PublicKey::from_base58_string(&self.keys.ed25519) else { + let Ok(pub_key) = identity::PublicKey::from_base58_string(&self.keys.ed25519_identity) + else { return false; }; diff --git a/nym-node/nym-node-requests/src/api/v1/metrics/mod.rs b/nym-node/nym-node-requests/src/api/v1/metrics/mod.rs new file mode 100644 index 0000000000..5c45219d84 --- /dev/null +++ b/nym-node/nym-node-requests/src/api/v1/metrics/mod.rs @@ -0,0 +1,4 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod models; diff --git a/nym-node/nym-node-requests/src/api/v1/metrics/models.rs b/nym-node/nym-node-requests/src/api/v1/metrics/models.rs new file mode 100644 index 0000000000..3374b1b0c4 --- /dev/null +++ b/nym-node/nym-node-requests/src/api/v1/metrics/models.rs @@ -0,0 +1,279 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_crypto::asymmetric::identity::{self, serde_helpers::bs58_pubkey}; +use serde::{Deserialize, Serialize}; +use std::cmp::Ordering; +use std::fmt; +use std::fmt::{Display, Formatter}; +use std::time::Duration; +use time::OffsetDateTime; + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +pub struct MixingStats { + #[serde(with = "time::serde::rfc3339")] + pub update_time: OffsetDateTime, + + #[serde(with = "time::serde::rfc3339")] + pub previous_update_time: OffsetDateTime, + + pub received_since_startup: u64, + + // note: sent does not imply forwarded. We don't know if it was delivered successfully + pub sent_since_startup: u64, + + // we know for sure we dropped those packets + pub dropped_since_startup: u64, + + pub received_since_last_update: u64, + + // note: sent does not imply forwarded. We don't know if it was delivered successfully + pub sent_since_last_update: u64, + + // we know for sure we dropped those packets + pub dropped_since_last_update: u64, +} + +#[derive(Serialize, Deserialize, Default, Debug, Clone)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +pub struct VerlocStats { + pub previous: VerlocResult, + pub current: VerlocResult, +} + +#[derive(Serialize, Deserialize, Default, Debug, Clone)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +#[serde(rename_all = "camelCase")] +pub enum VerlocResult { + Data(VerlocResultData), + MeasurementInProgress, + #[default] + Unavailable, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +pub struct VerlocResultData { + pub nodes_tested: usize, + + #[serde(with = "time::serde::rfc3339")] + pub run_started: OffsetDateTime, + + #[serde(with = "time::serde::rfc3339::option")] + pub run_finished: Option, + + pub results: Vec, +} + +impl Default for VerlocResultData { + fn default() -> Self { + VerlocResultData { + nodes_tested: 0, + run_started: OffsetDateTime::now_utc(), + run_finished: None, + results: vec![], + } + } +} + +impl VerlocResultData { + pub fn run_finished(&self) -> bool { + self.run_finished.is_some() + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, Copy, Eq, PartialEq)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +pub struct VerlocNodeResult { + #[serde(with = "bs58_pubkey")] + pub node_identity: identity::PublicKey, + + pub latest_measurement: Option, +} + +impl VerlocNodeResult { + pub fn new( + node_identity: identity::PublicKey, + latest_measurement: Option, + ) -> Self { + VerlocNodeResult { + node_identity, + latest_measurement, + } + } +} + +impl PartialOrd for VerlocNodeResult { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for VerlocNodeResult { + fn cmp(&self, other: &Self) -> Ordering { + // if both have measurement, compare measurements + // then if only one have measurement, prefer that one + // completely ignore identity as it makes no sense to order by it + if let Some(self_measurement) = &self.latest_measurement { + if let Some(other_measurement) = &other.latest_measurement { + self_measurement.cmp(other_measurement) + } else { + Ordering::Less + } + } else if other.latest_measurement.is_some() { + Ordering::Greater + } else { + Ordering::Equal + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +pub struct VerlocMeasurement { + /// Minimum RTT duration it took to receive an echo packet. + #[serde(serialize_with = "humantime_serde::serialize")] + pub minimum: Duration, + + /// Average RTT duration it took to receive the echo packets. + #[serde(serialize_with = "humantime_serde::serialize")] + pub mean: Duration, + + /// Maximum RTT duration it took to receive an echo packet. + #[serde(serialize_with = "humantime_serde::serialize")] + pub maximum: Duration, + + /// The standard deviation of the RTT duration it took to receive the echo packets. + #[serde(serialize_with = "humantime_serde::serialize")] + pub standard_deviation: Duration, +} + +impl VerlocMeasurement { + pub fn new(raw_results: &[Duration]) -> Self { + let minimum = raw_results.iter().min().copied().unwrap_or_default(); + let maximum = raw_results.iter().max().copied().unwrap_or_default(); + + let mean = Self::duration_mean(raw_results); + let standard_deviation = Self::duration_standard_deviation(raw_results, mean); + + VerlocMeasurement { + minimum, + mean, + maximum, + standard_deviation, + } + } + + fn duration_mean(data: &[Duration]) -> Duration { + if data.is_empty() { + return Default::default(); + } + + let sum = data.iter().sum::(); + let count = data.len() as u32; + + sum / count + } + + fn duration_standard_deviation(data: &[Duration], mean: Duration) -> Duration { + if data.is_empty() { + return Default::default(); + } + + let variance_micros = data + .iter() + .map(|&value| { + // make sure we don't underflow + let diff = if mean > value { + mean - value + } else { + value - mean + }; + // we don't need nanos precision + let diff_micros = diff.as_micros(); + diff_micros * diff_micros + }) + .sum::() + / data.len() as u128; + + // we shouldn't really overflow as our differences shouldn't be larger than couple seconds at the worst possible case scenario + let std_deviation_micros = (variance_micros as f64).sqrt() as u64; + Duration::from_micros(std_deviation_micros) + } +} + +impl Display for VerlocMeasurement { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "rtt min/avg/max/mdev = {} / {} / {} / {}", + humantime::format_duration(self.minimum), + humantime::format_duration(self.mean), + humantime::format_duration(self.maximum), + humantime::format_duration(self.standard_deviation) + ) + } +} + +impl PartialOrd for VerlocMeasurement { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for VerlocMeasurement { + fn cmp(&self, other: &Self) -> Ordering { + // minimum value is most important, then look at standard deviation, then mean and finally maximum + let min_cmp = self.minimum.cmp(&other.minimum); + if min_cmp != Ordering::Equal { + return min_cmp; + } + let std_dev_cmp = self.standard_deviation.cmp(&other.standard_deviation); + if std_dev_cmp != Ordering::Equal { + return std_dev_cmp; + } + let std_dev_cmp = self.mean.cmp(&other.mean); + if std_dev_cmp != Ordering::Equal { + return std_dev_cmp; + } + self.maximum.cmp(&other.maximum) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn sorting_vec_of_verlocs() { + let some_identity = + identity::PublicKey::from_base58_string("Be9wH7xuXBRJAuV1pC7MALZv6a61RvWQ3SypsNarqTt") + .unwrap(); + let no_measurement = VerlocNodeResult::new(some_identity, None); + let low_min = VerlocNodeResult::new( + some_identity, + Some(VerlocMeasurement { + minimum: Duration::from_millis(42), + mean: Duration::from_millis(43), + maximum: Duration::from_millis(44), + standard_deviation: Duration::from_millis(45), + }), + ); + let higher_min = VerlocNodeResult::new( + some_identity, + Some(VerlocMeasurement { + minimum: Duration::from_millis(420), + mean: Duration::from_millis(430), + maximum: Duration::from_millis(440), + standard_deviation: Duration::from_millis(450), + }), + ); + + let mut vec_verloc = vec![no_measurement, low_min, no_measurement, higher_min]; + vec_verloc.sort(); + + let expected_sorted = vec![low_min, higher_min, no_measurement, no_measurement]; + assert_eq!(expected_sorted, vec_verloc); + } +} diff --git a/nym-node/nym-node-requests/src/api/v1/mod.rs b/nym-node/nym-node-requests/src/api/v1/mod.rs index 33d67d598e..1c6e698940 100644 --- a/nym-node/nym-node-requests/src/api/v1/mod.rs +++ b/nym-node/nym-node-requests/src/api/v1/mod.rs @@ -4,6 +4,7 @@ pub mod gateway; pub mod health; pub mod ip_packet_router; +pub mod metrics; pub mod mixnode; pub mod network_requester; pub mod node; diff --git a/nym-node/nym-node-requests/src/api/v1/node/models.rs b/nym-node/nym-node-requests/src/api/v1/node/models.rs index 3b79601a00..66ca13d99d 100644 --- a/nym-node/nym-node-requests/src/api/v1/node/models.rs +++ b/nym-node/nym-node-requests/src/api/v1/node/models.rs @@ -35,10 +35,99 @@ pub struct HostInformation { #[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema)] #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] pub struct HostKeys { - /// Base58-encoded ed25519 public key of this node. Currently it corresponds to either mixnode's or gateway's identity. - pub ed25519: String, + /// Base58-encoded ed25519 public key of this node. Currently, it corresponds to either mixnode's or gateway's identity. + #[serde(alias = "ed25519")] + pub ed25519_identity: String, /// Base58-encoded x25519 public key of this node used for sphinx/outfox packet creation. - /// Currently it corresponds to either mixnode's or gateway's key. - pub x25519: String, + /// Currently, it corresponds to either mixnode's or gateway's key. + #[serde(alias = "x25519")] + pub x25519_sphinx: String, + + /// Base58-encoded x25519 public key of this node used for the noise protocol. + #[serde(default)] + pub x25519_noise: String, +} + +#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +pub struct HostSystem { + /// Name of the operating system of the host machine. + pub system_name: Option, + + /// Version of the kernel of the host machine, if applicable. + pub kernel_version: Option, + + /// Version of the operating system of the host machine, if applicable. + pub os_version: Option, + + /// The CPU architecture of the host machine (eg. x86, amd64, aarch64, ...). + pub cpu_arch: Option, + + /// Hardware information of the host machine. + pub hardware: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +pub struct Hardware { + /// The information of the host CPU. + pub cpu: Vec, + + /// Total memory, in bytes, available on the host. + pub total_memory: u64, + + /// Detailed information about availability of crypto-specific instructions for future optimisations. + pub crypto: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +pub struct Cpu { + pub name: String, + + /// The CPU frequency in MHz. + pub frequency: u64, + + pub vendor_id: String, + + pub brand: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +pub struct CryptoHardware { + /// Flag to indicate whether the host machine supports AES-NI x86 extension instruction set + pub aesni: bool, + + /// Flag to indicate whether the host machine supports AVX2 x86 extension instruction set + pub avx2: bool, + + /// Number of SMT logical processors available. + pub smt_logical_processor_count: Vec, + + /// Flag to indicate whether the host machine supports OSXSAVE instruction + pub osxsave: bool, + + /// Flag to indicate whether the host machine supports Intel Software Guard Extensions (SGX) set of instruction codes + pub sgx: bool, + + /// Flag to indicate whether the host machine supports XSAVE instruction + pub xsave: bool, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +pub struct NodeDescription { + /// moniker defines a human-readable name for the node. + pub moniker: String, + + /// website defines an optional website link. + pub website: String, + + /// security contact defines an optional email for security contact. + pub security_contact: String, + + /// details define other optional details. + pub details: String, } diff --git a/nym-node/nym-node-requests/src/lib.rs b/nym-node/nym-node-requests/src/lib.rs index 9bb345eca9..d5ae5d5368 100644 --- a/nym-node/nym-node-requests/src/lib.rs +++ b/nym-node/nym-node-requests/src/lib.rs @@ -7,17 +7,21 @@ pub mod api; pub mod error; +macro_rules! absolute_route { + ( $name:ident, $parent:expr, $suffix:expr ) => { + pub fn $name() -> String { + format!("{}{}", $parent, $suffix) + } + }; +} + // still thinking how to nicely organise it pub mod routes { - use crate::absolute_route; - pub const LANDING_PAGE: &str = "/"; pub const ROOT: &str = "/"; pub const API: &str = "/api"; pub mod api { - use super::*; - pub const V1: &str = "/v1"; absolute_route!(v1_absolute, super::API, V1); @@ -28,25 +32,44 @@ pub mod routes { pub const ROLES: &str = "/roles"; pub const BUILD_INFO: &str = "/build-information"; pub const HOST_INFO: &str = "/host-information"; - + pub const SYSTEM_INFO: &str = "/system-info"; + pub const NODE_DESCRIPTION: &str = "/description"; pub const HEALTH: &str = "/health"; + pub const SWAGGER: &str = "/swagger"; + pub const GATEWAY: &str = "/gateway"; pub const MIXNODE: &str = "/mixnode"; + pub const METRICS: &str = "/metrics"; pub const NETWORK_REQUESTER: &str = "/network-requester"; pub const IP_PACKET_ROUTER: &str = "/ip-packet-router"; - pub const SWAGGER: &str = "/swagger"; // define helper functions to get absolute routes absolute_route!(health_absolute, v1_absolute(), HEALTH); absolute_route!(roles_absolute, v1_absolute(), ROLES); absolute_route!(build_info_absolute, v1_absolute(), BUILD_INFO); absolute_route!(host_info_absolute, v1_absolute(), HOST_INFO); + absolute_route!(system_info_absolute, v1_absolute(), SYSTEM_INFO); + absolute_route!(description_absolute, v1_absolute(), NODE_DESCRIPTION); + absolute_route!(gateway_absolute, v1_absolute(), GATEWAY); absolute_route!(mixnode_absolute, v1_absolute(), MIXNODE); + absolute_route!(metrics_absolute, v1_absolute(), METRICS); absolute_route!(network_requester_absolute, v1_absolute(), NETWORK_REQUESTER); absolute_route!(ip_packet_router_absolute, v1_absolute(), IP_PACKET_ROUTER); absolute_route!(swagger_absolute, v1_absolute(), SWAGGER); + pub mod metrics { + use super::*; + + pub const MIXING: &str = "/mixing"; + pub const VERLOC: &str = "/verloc"; + pub const PROMETHEUS: &str = "/prometheus"; + + absolute_route!(mixing_absolute, metrics_absolute(), MIXING); + absolute_route!(verloc_absolute, metrics_absolute(), VERLOC); + absolute_route!(prometheus_absolute, metrics_absolute(), PROMETHEUS); + } + pub mod gateway { use super::*; @@ -106,15 +129,6 @@ pub mod routes { } } -#[macro_export] -macro_rules! absolute_route { - ( $name:ident, $parent:expr, $suffix:expr ) => { - pub fn $name() -> String { - format!("{}{}", $parent, $suffix) - } - }; -} - #[cfg(test)] mod tests { use super::*; diff --git a/nym-node/src/cli/commands/bonding_information.rs b/nym-node/src/cli/commands/bonding_information.rs new file mode 100644 index 0000000000..cefa4da954 --- /dev/null +++ b/nym-node/src/cli/commands/bonding_information.rs @@ -0,0 +1,41 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::cli::helpers::ConfigArgs; +use crate::env::vars::NYMNODE_MODE_ARG; +use crate::node::bonding_information::BondingInformationV1; +use nym_bin_common::output_format::OutputFormat; +use nym_node::config::upgrade_helpers::try_load_current_config; +use nym_node::config::NodeMode; +use nym_node::error::NymNodeError; + +#[derive(Debug, clap::Args)] +pub struct Args { + #[clap(flatten)] + pub(crate) config: ConfigArgs, + + #[clap( + long, + value_enum, + env = NYMNODE_MODE_ARG + )] + pub(crate) mode: Option, + + /// Specify the output format of the bonding information (`text` or `json`) + #[clap( + short, + long, + default_value_t = OutputFormat::default(), + )] + pub(crate) output: OutputFormat, +} + +pub async fn execute(args: Args) -> Result<(), NymNodeError> { + let config = try_load_current_config(args.config.config_path()).await?; + let mut info = BondingInformationV1::try_load(&config)?; + if let Some(mode) = args.mode { + info = info.with_mode(mode) + } + args.output.to_stdout(&info); + Ok(()) +} diff --git a/nym-node/src/cli/commands/build_info.rs b/nym-node/src/cli/commands/build_info.rs new file mode 100644 index 0000000000..e8f28ba3c4 --- /dev/null +++ b/nym-node/src/cli/commands/build_info.rs @@ -0,0 +1,17 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use nym_bin_common::bin_info_owned; +use nym_bin_common::output_format::OutputFormat; +use nym_node::error::NymNodeError; + +#[derive(clap::Args, Debug)] +pub(crate) struct Args { + #[clap(short, long, default_value_t = OutputFormat::default())] + output: OutputFormat, +} + +pub(crate) fn execute(args: Args) -> Result<(), NymNodeError> { + println!("{}", args.output.format(&bin_info_owned!())); + Ok(()) +} diff --git a/nym-node/src/cli/commands/migrate.rs b/nym-node/src/cli/commands/migrate.rs new file mode 100644 index 0000000000..5b34d6e903 --- /dev/null +++ b/nym-node/src/cli/commands/migrate.rs @@ -0,0 +1,631 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::cli::helpers::{ + EntryGatewayArgs, ExitGatewayArgs, HostArgs, HttpArgs, MixnetArgs, MixnodeArgs, WireguardArgs, +}; +use crate::node::description::save_node_description; +use crate::node::helpers::{load_ed25519_identity_public_key, store_x25519_noise_keypair}; +use clap::ValueEnum; +use nym_crypto::asymmetric::x25519; +use nym_gateway::helpers::{load_ip_packet_router_config, load_network_requester_config}; +use nym_gateway::GatewayError; +use nym_mixnode::MixnodeError; +use nym_network_requester::{CustomGatewayDetails, GatewayDetails}; +use nym_node::config; +use nym_node::config::mixnode::DEFAULT_VERLOC_PORT; +use nym_node::config::{default_config_filepath, ConfigBuilder, NodeMode}; +use nym_node::config::{Config, DEFAULT_WIREGUARD_NETWORK_IP}; +use nym_node::error::{EntryGatewayError, ExitGatewayError, NymNodeError}; +use nym_node_http_api::api::api_requests::v1::node::models::NodeDescription; +use rand::rngs::OsRng; +use std::fmt::{Display, Formatter}; +use std::fs::create_dir_all; +use std::net::SocketAddr; +use std::path::{Path, PathBuf}; +use std::{fs, io}; +use tracing::{info, trace, warn}; +use zeroize::Zeroizing; + +#[derive(Debug, Clone, Copy, ValueEnum)] +enum NodeType { + Mixnode, + Gateway, +} + +impl Display for NodeType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + NodeType::Mixnode => write!(f, "mixnode"), + NodeType::Gateway => write!(f, "gateway"), + } + } +} + +#[derive(clap::Args, Debug)] +#[clap(group = clap::ArgGroup::new("old-config").required(true))] +pub(crate) struct Args { + /// Type of node (mixnode or gateway) to migrate into a nym-node. + node_type: NodeType, + + /// Id of the node that's going to get migrated + #[clap(long, group = "old-config")] + id: Option, + + /// Path to a configuration file of the node that's going to get migrated. + #[clap(long, group = "old-config")] + config_file: Option, + + /// Specify whether to preserve id of the imported node. + #[clap(long)] + preserve_id: bool, + + // totally optional arguments for overriding any defaults: + #[clap(flatten)] + host: HostArgs, + + #[clap(flatten)] + http: HttpArgs, + + #[clap(flatten)] + mixnet: MixnetArgs, + + #[clap(flatten)] + wireguard: WireguardArgs, + + #[clap(flatten)] + mixnode: MixnodeArgs, + + #[clap(flatten)] + entry_gateway: EntryGatewayArgs, + + #[clap(flatten)] + exit_gateway: ExitGatewayArgs, +} + +impl Args { + fn take_mnemonic(&mut self) -> Option> { + self.entry_gateway.mnemonic.take().map(Zeroizing::new) + } + + fn config_path(&self) -> PathBuf { + // SAFETY: + // if `config_file` hasn't been specified, `id` MUST be available due to clap's ArgGroup + #[allow(clippy::unwrap_used)] + self.config_file.clone().unwrap_or_else(|| { + let id = self.id.as_ref().unwrap(); + match self.node_type { + NodeType::Mixnode => nym_mixnode::config::default_config_filepath(id), + NodeType::Gateway => nym_gateway::config::default_config_filepath(id), + } + }) + } +} + +fn nym_node_id( + typ: NodeType, + original_id: &str, + preserve_id: bool, +) -> Result { + if preserve_id { + let path = default_config_filepath(original_id); + if path.exists() { + return Err(NymNodeError::MigrationFailure { + node_type: typ.to_string(), + message: format!("nym-node with id '{original_id}' already exists"), + }); + } + } + + let mut candidate = original_id.to_string(); + let mut counter = 0; + loop { + let path = default_config_filepath(&candidate); + if path.exists() { + warn!("nym-node with id '{candidate}' already exists") + } else { + return Ok(candidate); + } + + candidate = format!("{original_id}-{counter}"); + counter += 1; + } +} + +fn copy_old_data, Q: AsRef>( + node_type: NodeType, + from: P, + to: Q, +) -> Result<(), NymNodeError> { + fn copy_inner, Q: AsRef>(from: P, to: Q) -> io::Result<()> { + if let Some(parent) = to.as_ref().parent() { + create_dir_all(parent)?; + } + fs::copy(from, to)?; + Ok(()) + } + + if let Err(err) = copy_inner(from.as_ref(), to.as_ref()) { + return Err(NymNodeError::MigrationFailure { + node_type: node_type.to_string(), + message: format!( + "failed to move '{}' to '{}': {err}", + from.as_ref().display(), + to.as_ref().display() + ), + }); + } + Ok(()) +} + +async fn migrate_mixnode(mut args: Args) -> Result<(), NymNodeError> { + let maybe_custom_mnemonic = args.take_mnemonic(); + let config_file = args.config_path(); + let preserve_id = args.preserve_id; + + info!( + "attempting to migrate mixnode from '{}'", + config_file.display() + ); + let cfg = nym_mixnode::config::Config::read_from_toml_file(&config_file).map_err(|source| { + MixnodeError::ConfigLoadFailure { + id: "???".to_string(), + path: config_file, + source, + } + })?; + + let old_description = if cfg.storage_paths.node_description.exists() { + Some( + nym_mixnode::node::node_description::NodeDescription::load_from_file( + &cfg.storage_paths.node_description, + ) + .map_err(|source| { + nym_node::error::MixnodeError::DescriptionLoadFailure { + path: cfg.storage_paths.node_description, + source, + } + })?, + ) + } else { + None + }; + + let nymnode_id = nym_node_id(NodeType::Mixnode, &cfg.mixnode.id, preserve_id)?; + let nym_node_config_path = default_config_filepath(&nymnode_id); + let data_dir = Config::default_data_directory(&nym_node_config_path)?; + + let ip = cfg.mixnode.listening_address; + + let location = old_description + .as_ref() + .and_then(|d| d.location.parse().ok()); + + // generate nym-node config + let config = ConfigBuilder::new(nymnode_id, nym_node_config_path, data_dir.clone()) + .with_mode(NodeMode::Mixnode) + .with_host(args.host.override_config_section(config::Host { + public_ips: cfg.host.public_ips, + hostname: cfg.host.hostname, + location, + })) + .with_http(args.http.override_config_section(config::Http { + bind_address: cfg.http.bind_address, + landing_page_assets_path: cfg.http.landing_page_assets_path, + access_token: cfg.http.metrics_key, + ..Default::default() + })) + .with_mixnet(args.mixnet.override_config_section(config::Mixnet { + bind_address: SocketAddr::new(ip, cfg.mixnode.mix_port), + nym_api_urls: cfg.mixnode.nym_api_urls, + debug: config::MixnetDebug { + packet_forwarding_initial_backoff: cfg.debug.packet_forwarding_initial_backoff, + packet_forwarding_maximum_backoff: cfg.debug.packet_forwarding_maximum_backoff, + initial_connection_timeout: cfg.debug.initial_connection_timeout, + maximum_connection_buffer_size: cfg.debug.maximum_connection_buffer_size, + }, + ..Default::default() + })) + .with_mixnode(args.mixnode.override_config_section(config::MixnodeConfig { + verloc: config::mixnode::Verloc { + bind_address: SocketAddr::new(ip, cfg.mixnode.verloc_port), + debug: config::mixnode::VerlocDebug { + packets_per_node: cfg.verloc.packets_per_node, + connection_timeout: cfg.verloc.connection_timeout, + packet_timeout: cfg.verloc.packet_timeout, + delay_between_packets: cfg.verloc.delay_between_packets, + tested_nodes_batch_size: cfg.verloc.tested_nodes_batch_size, + testing_interval: cfg.verloc.testing_interval, + retry_timeout: cfg.verloc.retry_timeout, + }, + }, + debug: config::mixnode::Debug { + node_stats_logging_delay: cfg.debug.node_stats_logging_delay, + node_stats_updating_delay: cfg.debug.node_stats_updating_delay, + }, + ..config::MixnodeConfig::new_default() + })) + .with_wireguard(args.wireguard.build_config_section(&data_dir)) + .with_entry_gateway(args.entry_gateway.build_config_section(&data_dir)) + .with_exit_gateway(args.exit_gateway.build_config_section(&data_dir)) + .build(); + + let d_ref = old_description.as_ref(); + + // update description + let node_description = NodeDescription { + moniker: d_ref.map(|d| &d.name).cloned().unwrap_or_default(), + website: d_ref.map(|d| &d.link).cloned().unwrap_or_default(), + security_contact: "".to_string(), + details: d_ref.map(|d| &d.description).cloned().unwrap_or_default(), + }; + save_node_description(&config.storage_paths.description, &node_description)?; + + // create noise keypair + let mut rng = OsRng; + let x25519_noise_keys = x25519::KeyPair::new(&mut rng); + trace!("attempting to store x25519 noise keypair"); + store_x25519_noise_keypair( + &x25519_noise_keys, + config.storage_paths.keys.x25519_noise_storage_paths(), + )?; + + // move existing keys and generate missing data + info!("attempting to copy mixnode keys to their new locations"); + copy_old_data( + NodeType::Mixnode, + cfg.storage_paths.keys.public_identity_key_file, + &config.storage_paths.keys.public_ed25519_identity_key_file, + )?; + copy_old_data( + NodeType::Mixnode, + cfg.storage_paths.keys.private_identity_key_file, + &config.storage_paths.keys.private_ed25519_identity_key_file, + )?; + copy_old_data( + NodeType::Mixnode, + cfg.storage_paths.keys.public_sphinx_key_file, + &config.storage_paths.keys.public_x25519_sphinx_key_file, + )?; + copy_old_data( + NodeType::Mixnode, + cfg.storage_paths.keys.private_sphinx_key_file, + &config.storage_paths.keys.private_x25519_sphinx_key_file, + )?; + + let ed25519_public_key = load_ed25519_identity_public_key( + &config.storage_paths.keys.public_ed25519_identity_key_file, + )?; + + // entry gateway initialisation + crate::node::EntryGatewayData::initialise(&config.entry_gateway, maybe_custom_mnemonic)?; + + // exit gateway initialisation + crate::node::ExitGatewayData::initialise(&config.exit_gateway, ed25519_public_key).await?; + + config.save()?; + + info!( + "mixnode {} has been migrated into a nym-node! all of its data can now be deleted", + cfg.mixnode.id + ); + + Ok(()) +} + +async fn migrate_gateway(mut args: Args) -> Result<(), NymNodeError> { + let config_file = args.config_path(); + let preserve_id = args.preserve_id; + + info!( + "attempting to migrate gateway from '{}'", + config_file.display() + ); + let cfg = nym_gateway::config::Config::read_from_toml_file(&config_file) + .map_err(|source| GatewayError::ConfigLoadFailure { + id: "???".to_string(), + path: config_file, + source, + }) + .map_err(EntryGatewayError::from)?; + + let nr_cfg = match &cfg.storage_paths.network_requester_config { + None => None, + Some(nr_cfg) => Some( + load_network_requester_config("???", nr_cfg) + .await + .map_err(ExitGatewayError::from)?, + ), + }; + + let ipr_cfg = match &cfg.storage_paths.ip_packet_router_config { + None => None, + Some(ipr_cfg) => Some( + load_ip_packet_router_config("???", ipr_cfg) + .await + .map_err(ExitGatewayError::from)?, + ), + }; + + let nymnode_id = nym_node_id(NodeType::Gateway, &cfg.gateway.id, preserve_id)?; + let nym_node_config_path = default_config_filepath(&nymnode_id); + let data_dir = Config::default_data_directory(&nym_node_config_path)?; + + let mode = if cfg.network_requester.enabled && cfg.ip_packet_router.enabled { + NodeMode::ExitGateway + } else { + NodeMode::EntryGateway + }; + + let ip = cfg.gateway.listening_address; + + // prefer new mnemonic explicitly passed with cli; otherwise use the one already present + let mnemonic = args + .take_mnemonic() + .unwrap_or(Zeroizing::new(cfg.gateway.cosmos_mnemonic.clone())); + + let config = ConfigBuilder::new(nymnode_id, nym_node_config_path, data_dir.clone()) + .with_mode(mode) + .with_host(args.host.override_config_section(config::Host { + public_ips: cfg.host.public_ips, + hostname: cfg.host.hostname, + ..Default::default() + })) + .with_http(args.http.override_config_section(config::Http { + bind_address: cfg.http.bind_address, + landing_page_assets_path: cfg.http.landing_page_assets_path, + ..Default::default() + })) + .with_mixnet(args.mixnet.override_config_section(config::Mixnet { + bind_address: SocketAddr::new(ip, cfg.gateway.mix_port), + nym_api_urls: cfg.gateway.nym_api_urls.clone(), + nyxd_urls: cfg.gateway.nyxd_urls.clone(), + debug: config::MixnetDebug { + packet_forwarding_initial_backoff: cfg.debug.packet_forwarding_initial_backoff, + packet_forwarding_maximum_backoff: cfg.debug.packet_forwarding_maximum_backoff, + initial_connection_timeout: cfg.debug.initial_connection_timeout, + maximum_connection_buffer_size: cfg.debug.maximum_connection_buffer_size, + }, + })) + .with_mixnode(args.mixnode.override_config_section(config::MixnodeConfig { + verloc: config::mixnode::Verloc { + bind_address: SocketAddr::new(ip, DEFAULT_VERLOC_PORT), + ..Default::default() + }, + ..config::MixnodeConfig::new_default() + })) + .with_wireguard(args.wireguard.override_config_section(config::Wireguard { + enabled: cfg.wireguard.enabled, + bind_address: cfg.wireguard.bind_address, + private_network_ip: DEFAULT_WIREGUARD_NETWORK_IP, + announced_port: cfg.wireguard.announced_port, + private_network_prefix: cfg.wireguard.private_network_prefix, + // this is fine as currently the paths stored inside gateway itself are empty + storage_paths: config::persistence::WireguardPaths::new(&data_dir), + })) + .with_entry_gateway(args.entry_gateway.override_config_section( + config::EntryGatewayConfig { + storage_paths: config::persistence::EntryGatewayPaths::new(&data_dir), + enforce_zk_nyms: cfg.gateway.only_coconut_credentials, + bind_address: SocketAddr::new(ip, cfg.gateway.clients_port), + announce_ws_port: None, + announce_wss_port: cfg.gateway.clients_wss_port, + debug: config::entry_gateway::Debug { + message_retrieval_limit: cfg.debug.message_retrieval_limit, + }, + }, + )) + .with_exit_gateway( + args.exit_gateway + .override_config_section(config::ExitGatewayConfig { + storage_paths: config::persistence::ExitGatewayPaths::new(&data_dir), + open_proxy: false, + upstream_exit_policy_url: nr_cfg + .as_ref() + .and_then(|c| c.network_requester.upstream_exit_policy_url.clone()) + .unwrap_or( + config::ExitGatewayConfig::new_default(".").upstream_exit_policy_url, + ), + network_requester: config::exit_gateway::NetworkRequester { + debug: config::exit_gateway::NetworkRequesterDebug { + enabled: cfg.network_requester.enabled, + disable_poisson_rate: nr_cfg + .as_ref() + .map(|c| c.network_requester.disable_poisson_rate) + .unwrap_or( + config::exit_gateway::NetworkRequesterDebug::default() + .disable_poisson_rate, + ), + client_debug: nr_cfg.as_ref().map(|c| c.base.debug).unwrap_or_default(), + }, + }, + ip_packet_router: config::exit_gateway::IpPacketRouter { + debug: config::exit_gateway::IpPacketRouterDebug { + enabled: cfg.ip_packet_router.enabled, + disable_poisson_rate: ipr_cfg + .as_ref() + .map(|c| c.ip_packet_router.disable_poisson_rate) + .unwrap_or( + config::exit_gateway::IpPacketRouterDebug::default() + .disable_poisson_rate, + ), + client_debug: ipr_cfg + .as_ref() + .map(|c| c.base.debug) + .unwrap_or_default(), + }, + }, + }), + ) + .build(); + + // create noise keypair + let mut rng = OsRng; + let x25519_noise_keys = x25519::KeyPair::new(&mut rng); + trace!("attempting to store x25519 noise keypair"); + store_x25519_noise_keypair( + &x25519_noise_keys, + config.storage_paths.keys.x25519_noise_storage_paths(), + )?; + + // move existing keys and generate missing data + info!("attempting to copy gateway keys to their new locations"); + + copy_old_data( + NodeType::Gateway, + cfg.storage_paths.keys.public_identity_key_file, + &config.storage_paths.keys.public_ed25519_identity_key_file, + )?; + copy_old_data( + NodeType::Gateway, + cfg.storage_paths.keys.private_identity_key_file, + &config.storage_paths.keys.private_ed25519_identity_key_file, + )?; + copy_old_data( + NodeType::Gateway, + cfg.storage_paths.keys.public_sphinx_key_file, + &config.storage_paths.keys.public_x25519_sphinx_key_file, + )?; + copy_old_data( + NodeType::Gateway, + cfg.storage_paths.keys.private_sphinx_key_file, + &config.storage_paths.keys.private_x25519_sphinx_key_file, + )?; + + let ed25519_public_key = load_ed25519_identity_public_key( + &config.storage_paths.keys.public_ed25519_identity_key_file, + )?; + + // mixnode data initialisation + crate::node::MixnodeData::initialise(&config.mixnode)?; + + // selectively initialise exit gateway + let gateway_details = + GatewayDetails::Custom(CustomGatewayDetails::new(ed25519_public_key)).into(); + let mut rng = OsRng; + + if let Some(nr_cfg) = nr_cfg { + let nr_paths = nr_cfg.storage_paths.common_paths; + let new_nr_paths = &config.exit_gateway.storage_paths.network_requester; + + copy_old_data( + NodeType::Gateway, + nr_paths.keys.public_identity_key_file, + &new_nr_paths.public_ed25519_identity_key_file, + )?; + copy_old_data( + NodeType::Gateway, + nr_paths.keys.private_identity_key_file, + &new_nr_paths.private_ed25519_identity_key_file, + )?; + copy_old_data( + NodeType::Gateway, + nr_paths.keys.public_encryption_key_file, + &new_nr_paths.public_x25519_diffie_hellman_key_file, + )?; + copy_old_data( + NodeType::Gateway, + nr_paths.keys.private_encryption_key_file, + &new_nr_paths.private_x25519_diffie_hellman_key_file, + )?; + copy_old_data( + NodeType::Gateway, + nr_paths.keys.ack_key_file, + &new_nr_paths.ack_key_file, + )?; + copy_old_data( + NodeType::Gateway, + nr_paths.gateway_registrations, + &new_nr_paths.gateway_registrations, + )?; + copy_old_data( + NodeType::Gateway, + nr_paths.reply_surb_database, + &new_nr_paths.reply_surb_database, + )?; + } else { + crate::node::ExitGatewayData::initialise_network_requester( + &mut rng, + &config.exit_gateway, + &gateway_details, + ) + .await?; + } + + if let Some(ipr_cfg) = ipr_cfg { + let ipr_paths = ipr_cfg.storage_paths.common_paths; + let new_ipr_paths = &config.exit_gateway.storage_paths.ip_packet_router; + + copy_old_data( + NodeType::Gateway, + ipr_paths.keys.public_identity_key_file, + &new_ipr_paths.public_ed25519_identity_key_file, + )?; + copy_old_data( + NodeType::Gateway, + ipr_paths.keys.private_identity_key_file, + &new_ipr_paths.private_ed25519_identity_key_file, + )?; + copy_old_data( + NodeType::Gateway, + ipr_paths.keys.public_encryption_key_file, + &new_ipr_paths.public_x25519_diffie_hellman_key_file, + )?; + copy_old_data( + NodeType::Gateway, + ipr_paths.keys.private_encryption_key_file, + &new_ipr_paths.private_x25519_diffie_hellman_key_file, + )?; + copy_old_data( + NodeType::Gateway, + ipr_paths.keys.ack_key_file, + &new_ipr_paths.ack_key_file, + )?; + copy_old_data( + NodeType::Gateway, + ipr_paths.gateway_registrations, + &new_ipr_paths.gateway_registrations, + )?; + copy_old_data( + NodeType::Gateway, + ipr_paths.reply_surb_database, + &new_ipr_paths.reply_surb_database, + )?; + } else { + crate::node::ExitGatewayData::initialise_ip_packet_router_requester( + &mut rng, + &config.exit_gateway, + &gateway_details, + ) + .await?; + } + + save_node_description( + &config.storage_paths.description, + &NodeDescription::default(), + )?; + + // finally move the mnemonic + config + .entry_gateway + .storage_paths + .save_mnemonic_to_file(&mnemonic)?; + + config.save()?; + + info!( + "gateway {} has been migrated into a nym-node! all of its data can now be deleted", + cfg.gateway.id + ); + + Ok(()) +} + +pub(crate) async fn execute(args: Args) -> Result<(), NymNodeError> { + trace!("args: {args:#?}"); + + match args.node_type { + NodeType::Mixnode => migrate_mixnode(args).await, + NodeType::Gateway => migrate_gateway(args).await, + } +} diff --git a/nym-node/src/cli/commands/mod.rs b/nym-node/src/cli/commands/mod.rs new file mode 100644 index 0000000000..16d6e3611b --- /dev/null +++ b/nym-node/src/cli/commands/mod.rs @@ -0,0 +1,9 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +pub(crate) mod bonding_information; +pub(super) mod build_info; +pub(super) mod migrate; +pub(crate) mod node_details; +pub(super) mod run; +pub(super) mod sign; diff --git a/nym-node/src/cli/commands/node_details.rs b/nym-node/src/cli/commands/node_details.rs new file mode 100644 index 0000000000..64e6a4f3ac --- /dev/null +++ b/nym-node/src/cli/commands/node_details.rs @@ -0,0 +1,29 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::cli::helpers::ConfigArgs; +use crate::node::NymNode; +use nym_bin_common::output_format::OutputFormat; +use nym_node::config::upgrade_helpers::try_load_current_config; +use nym_node::error::NymNodeError; + +#[derive(Debug, clap::Args)] +pub(crate) struct Args { + #[clap(flatten)] + pub(crate) config: ConfigArgs, + + /// Specify the output format of the node details (`text` or `json`) + #[clap( + short, + long, + default_value_t = OutputFormat::default(), + )] + pub(crate) output: OutputFormat, +} + +pub async fn execute(args: Args) -> Result<(), NymNodeError> { + let config = try_load_current_config(args.config.config_path()).await?; + let details = NymNode::new(config).await?.display_details(); + args.output.to_stdout(&details); + Ok(()) +} diff --git a/nym-node/src/cli/commands/run/args.rs b/nym-node/src/cli/commands/run/args.rs new file mode 100644 index 0000000000..9746f883b7 --- /dev/null +++ b/nym-node/src/cli/commands/run/args.rs @@ -0,0 +1,148 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::cli::helpers::{ + ConfigArgs, EntryGatewayArgs, ExitGatewayArgs, HostArgs, HttpArgs, MixnetArgs, MixnodeArgs, + WireguardArgs, +}; +use crate::env::vars::*; +use nym_bin_common::output_format::OutputFormat; +use nym_node::config::persistence::NymNodePaths; +use nym_node::config::{Config, ConfigBuilder, NodeMode}; +use nym_node::error::NymNodeError; +use std::path::PathBuf; +use zeroize::Zeroizing; + +#[derive(clap::Args, Debug)] +pub(crate) struct Args { + #[clap(flatten)] + pub(crate) config: ConfigArgs, + + /// Forbid a new node from being initialised if configuration file for the provided specification doesn't already exist + #[clap( + long, + default_value_t = false, + env = NYMNODE_DENY_INIT_ARG, + conflicts_with = "init_only" + )] + pub(crate) deny_init: bool, + + /// If this is a brand new nym-node, specify whether it should only be initialised without actually running the subprocesses. + #[clap( + long, + default_value_t = false, + env = NYMNODE_INIT_ONLY_ARG, + conflicts_with = "deny_init" + )] + pub(crate) init_only: bool, + + /// Specifies the current mode of this nym-node. + #[clap( + long, + value_enum, + env = NYMNODE_MODE_ARG + )] + pub(crate) mode: Option, + + /// If this node has been initialised before, specify whether to write any new changes to the config file. + #[clap( + short, + long, + default_value_t = false, + env = NYMMONDE_WRITE_CONFIG_CHANGES_ARG, + )] + pub(crate) write_changes: bool, + + /// Specify output file for bonding information of this nym-node, i.e. its encoded keys. + /// NOTE: the required bonding information is still a subject to change and this argument should be treated + /// only as a preview of future features. + #[clap( + long, + env = NYMNODE_BONDING_INFORMATION_OUTPUT_ARG + )] + pub(crate) bonding_information_output: Option, + + /// Specify the output format of the bonding information (`text` or `json`) + #[clap( + short, + long, + default_value_t = OutputFormat::default(), + env = NYMNODE_OUTPUT_ARG + )] + pub(crate) output: OutputFormat, + + #[clap(flatten)] + host: HostArgs, + + #[clap(flatten)] + http: HttpArgs, + + #[clap(flatten)] + mixnet: MixnetArgs, + + #[clap(flatten)] + wireguard: WireguardArgs, + + #[clap(flatten)] + mixnode: MixnodeArgs, + + #[clap(flatten)] + entry_gateway: EntryGatewayArgs, + + #[clap(flatten)] + exit_gateway: ExitGatewayArgs, +} + +impl Args { + pub(super) fn take_mnemonic(&mut self) -> Option> { + self.entry_gateway.mnemonic.take().map(Zeroizing::new) + } +} + +impl Args { + pub(crate) fn build_config(self) -> Result { + let config_path = self.config.config_path(); + let data_dir = Config::default_data_directory(&config_path)?; + + let id = self + .config + .id() + .clone() + .ok_or(NymNodeError::MissingInitArg { + section: "global".to_string(), + name: "id".to_string(), + })?; + + let config = ConfigBuilder::new(id, config_path.clone(), data_dir.clone()) + .with_mode(self.mode.unwrap_or_default()) + .with_host(self.host.build_config_section()) + .with_http(self.http.build_config_section()) + .with_mixnet(self.mixnet.build_config_section()) + .with_wireguard(self.wireguard.build_config_section(&data_dir)) + .with_storage_paths(NymNodePaths::new(&data_dir)) + .with_mixnode(self.mixnode.build_config_section()) + .with_entry_gateway(self.entry_gateway.build_config_section(&data_dir)) + .with_exit_gateway(self.exit_gateway.build_config_section(&data_dir)) + .build(); + + Ok(config) + } + + pub(crate) fn override_config(self, mut config: Config) -> Config { + if let Some(mode) = self.mode { + config.mode = mode; + } + config.host = self.host.override_config_section(config.host); + config.http = self.http.override_config_section(config.http); + config.mixnet = self.mixnet.override_config_section(config.mixnet); + config.wireguard = self.wireguard.override_config_section(config.wireguard); + config.mixnode = self.mixnode.override_config_section(config.mixnode); + config.entry_gateway = self + .entry_gateway + .override_config_section(config.entry_gateway); + config.exit_gateway = self + .exit_gateway + .override_config_section(config.exit_gateway); + config + } +} diff --git a/nym-node/src/cli/commands/run/mod.rs b/nym-node/src/cli/commands/run/mod.rs new file mode 100644 index 0000000000..74d4d369a8 --- /dev/null +++ b/nym-node/src/cli/commands/run/mod.rs @@ -0,0 +1,77 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::node::bonding_information::BondingInformationV1; +use crate::node::NymNode; +use nym_node::config::upgrade_helpers::try_load_current_config; +use nym_node::error::NymNodeError; +use std::fs; +use tracing::{debug, info, trace}; + +mod args; + +pub(crate) use args::Args; + +pub(crate) async fn execute(mut args: Args) -> Result<(), NymNodeError> { + trace!("passed arguments: {args:#?}"); + + let config_path = args.config.config_path(); + let output = args.output; + let bonding_info_path = args.bonding_information_output.clone(); + + let config = if !config_path.exists() { + debug!("no configuration file found at '{}'", config_path.display()); + info!("initialising new nym-node"); + if args.deny_init { + return Err(NymNodeError::ForbiddenInitialisation { config_path }); + } + let init_only = args.init_only; + + let maybe_custom_mnemonic = args.take_mnemonic(); + + let config = args.build_config()?; + NymNode::initialise(&config, maybe_custom_mnemonic).await?; + if init_only { + debug!("returning due to the 'init-only' flag"); + return Ok(()); + } + + config + } else { + info!( + "attempting to load nym-node configuration from {}", + config_path.display() + ); + let write_changes = args.write_changes; + let config = args.override_config(try_load_current_config(config_path).await?); + + if write_changes { + config.save()?; + } + config + }; + + let nym_node = NymNode::new(config).await?; + + // if requested, write bonding info + if let Some(bonding_info_path) = bonding_info_path { + info!( + "writing bonding information to '{}'", + bonding_info_path.display() + ); + let info = BondingInformationV1::from_data( + nym_node.mode(), + nym_node.ed25519_identity_key().to_base58_string(), + nym_node.x25519_sphinx_key().to_base58_string(), + ); + let data = output.format(&info); + fs::write(&bonding_info_path, data).map_err(|source| { + NymNodeError::BondingInfoWriteFailure { + path: bonding_info_path, + source, + } + })?; + } + + nym_node.run().await +} diff --git a/nym-node/src/cli/commands/sign.rs b/nym-node/src/cli/commands/sign.rs new file mode 100644 index 0000000000..67c44977cf --- /dev/null +++ b/nym-node/src/cli/commands/sign.rs @@ -0,0 +1,88 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::cli::helpers::ConfigArgs; +use crate::node::helpers::load_ed25519_identity_keypair; +use nym_bin_common::output_format::OutputFormat; +use nym_crypto::asymmetric::identity; +use nym_node::config::upgrade_helpers::try_load_current_config; +use nym_node::error::NymNodeError; +use nym_types::helpers::ConsoleSigningOutput; + +// I don't think it makes sense to expose 'text' and 'contract-msg' as env variables +#[derive(Debug, clap::Args)] +#[clap(group = clap::ArgGroup::new("message").required(true))] +pub struct Args { + #[clap(flatten)] + config: ConfigArgs, + + /// Signs an arbitrary piece of text with your identity key + #[clap(long, group = "message")] + text: Option, + + /// Signs a transaction-specific payload, that is going to be sent to the smart contract, with your identity key + #[clap(long, group = "message")] + contract_msg: Option, + + #[clap(short, long, default_value_t = OutputFormat::default())] + output: OutputFormat, +} + +fn print_signed_text(private_key: &identity::PrivateKey, text: &str, output: OutputFormat) { + eprintln!("Signing the text {text:?} using your node's Ed25519 identity key...",); + + let signature = private_key.sign_text(text); + let sign_output = ConsoleSigningOutput::new(text, signature); + output.to_stdout(&sign_output); +} + +fn print_signed_contract_msg( + private_key: &identity::PrivateKey, + raw_msg: &str, + output: OutputFormat, +) { + let trimmed = raw_msg.trim(); + eprintln!(">>> attempting to sign {trimmed}"); + + let Ok(decoded) = bs58::decode(trimmed).into_vec() else { + println!("it seems you have incorrectly copied the message to sign. Make sure you didn't accidentally skip any characters"); + return; + }; + + eprintln!(">>> decoding the message..."); + + // we don't really care about what particular information is embedded inside of it, + // we just want to know if user correctly copied the string, i.e. whether it's a valid bs58 encoded json + if serde_json::from_slice::(&decoded).is_err() { + println!("it seems you have incorrectly copied the message to sign. Make sure you didn't accidentally skip any characters"); + return; + }; + + // SAFETY: + // if this is a valid json, it MUST be a valid string + #[allow(clippy::unwrap_used)] + let decoded_string = String::from_utf8(decoded.clone()).unwrap(); + let signature = private_key.sign(&decoded).to_base58_string(); + + let sign_output = ConsoleSigningOutput::new(decoded_string, signature); + println!("{}", output.format(&sign_output)); +} + +pub async fn execute(args: Args) -> Result<(), NymNodeError> { + println!("args: {args:?}"); + + let config = try_load_current_config(args.config.config_path()).await?; + let identity_keypair = + load_ed25519_identity_keypair(config.storage_paths.keys.ed25519_identity_storage_paths())?; + + // note: due to clap's ArgGroup, one (and only one) of those branches will be called + if let Some(text) = args.text { + print_signed_text(identity_keypair.private_key(), &text, args.output); + Ok(()) + } else if let Some(contract_msg) = args.contract_msg { + print_signed_contract_msg(identity_keypair.private_key(), &contract_msg, args.output); + Ok(()) + } else { + unreachable!() + } +} diff --git a/nym-node/src/cli/helpers.rs b/nym-node/src/cli/helpers.rs new file mode 100644 index 0000000000..ad4e923269 --- /dev/null +++ b/nym-node/src/cli/helpers.rs @@ -0,0 +1,434 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use super::DEFAULT_NYMNODE_ID; +use crate::env::vars::*; +use celes::Country; +use clap::builder::ArgPredicate; +use clap::Args; +use nym_node::config; +use nym_node::config::default_config_filepath; +use std::net::{IpAddr, SocketAddr}; +use std::path::{Path, PathBuf}; +use url::Url; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +#[derive(Args, Debug)] +pub(crate) struct ConfigArgs { + /// Id of the nym-node to use + #[clap( + long, + default_value = DEFAULT_NYMNODE_ID, + default_value_if("config_file", ArgPredicate::IsPresent, None), + env = NYMNODE_ID_ARG, + group = "config" + )] + id: Option, + + /// Path to a configuration file of this node. + #[clap( + long, + env = NYMNODE_CONFIG_PATH_ARG, + group = "config" + )] + config_file: Option, +} + +impl ConfigArgs { + pub(crate) fn id(&self) -> &Option { + &self.id + } + + pub(crate) fn config_path(&self) -> PathBuf { + // SAFETY: + // if `config_file` hasn't been specified, `id` will default to "DEFAULT_NYMNODE_ID", + // so some value will always be available to use + #[allow(clippy::unwrap_used)] + self.config_file + .clone() + .unwrap_or_else(|| default_config_filepath(self.id.as_ref().unwrap())) + } +} + +#[derive(clap::Args, Debug)] +pub(crate) struct HostArgs { + /// Comma separated list of public ip addresses that will be announced to the nym-api and subsequently to the clients. + /// In nearly all circumstances, it's going to be identical to the address you're going to use for bonding. + #[clap( + long, + value_delimiter = ',', + env = NYMNODE_PUBLIC_IPS_ARG + )] + pub(crate) public_ips: Option>, + + /// Optional hostname associated with this gateway that will be announced to the nym-api and subsequently to the clients + #[clap( + long, + env = NYMNODE_HOSTNAME_ARG + )] + pub(crate) hostname: Option, + + /// Optional **physical** location of this node's server. + /// Either full country name (e.g. 'Poland'), two-letter alpha2 (e.g. 'PL'), + /// three-letter alpha3 (e.g. 'POL') or three-digit numeric-3 (e.g. '616') can be provided. + #[clap( + long, + env = NYMNODE_LOCATION_ARG + )] + pub(crate) location: Option, +} + +impl HostArgs { + // TODO: could we perhaps make a clap error here and call `safe_exit` instead? + pub(crate) fn build_config_section(self) -> config::Host { + self.override_config_section(config::Host::default()) + } + + pub(crate) fn override_config_section(self, mut section: config::Host) -> config::Host { + if let Some(public_ips) = self.public_ips { + section.public_ips = public_ips + } + if let Some(hostname) = self.hostname { + section.hostname = Some(hostname) + } + if let Some(location) = self.location { + section.location = Some(location) + } + section + } +} + +#[derive(clap::Args, Debug)] +pub(crate) struct HttpArgs { + /// Socket address this node will use for binding its http API. + /// default: `0.0.0.0:8080` + #[clap( + long, + env = NYMNODE_HTTP_BIND_ADDRESS_ARG + )] + pub(crate) http_bind_address: Option, + + /// Path to assets directory of custom landing page of this node. + #[clap( + long, + env = NYMNODE_HTTP_LANDING_ASSETS_ARG + )] + pub(crate) landing_page_assets_path: Option, + + /// An optional bearer token for accessing certain http endpoints. + /// Currently only used for prometheus metrics. + #[clap( + long, + env = NYMNODE_HTTP_ACCESS_TOKEN_ARG, + alias = "http-bearer-token" + )] + pub(crate) http_access_token: Option, + + /// Specify whether basic system information should be exposed. + /// default: true + #[clap( + long, + env = NYMNODE_HTTP_EXPOSE_SYSTEM_INFO_ARG, + )] + pub(crate) expose_system_info: Option, + + /// Specify whether basic system hardware information should be exposed. + /// default: true + #[clap( + long, + env = NYMNODE_HTTP_EXPOSE_SYSTEM_HARDWARE_ARG + )] + pub(crate) expose_system_hardware: Option, + + /// Specify whether detailed system crypto hardware information should be exposed. + /// default: true + #[clap( + long, + env = NYMNODE_HTTP_EXPOSE_CRYPTO_HARDWARE_ARG + )] + pub(crate) expose_crypto_hardware: Option, +} + +impl HttpArgs { + // TODO: could we perhaps make a clap error here and call `safe_exit` instead? + pub(crate) fn build_config_section(self) -> config::Http { + self.override_config_section(config::Http::default()) + } + + pub(crate) fn override_config_section(self, mut section: config::Http) -> config::Http { + if let Some(bind_address) = self.http_bind_address { + section.bind_address = bind_address + } + if let Some(landing_page_assets_path) = self.landing_page_assets_path { + section.landing_page_assets_path = Some(landing_page_assets_path) + } + if let Some(access_token) = self.http_access_token { + section.access_token = Some(access_token) + } + if let Some(expose_system_info) = self.expose_system_info { + section.expose_system_info = expose_system_info + } + if let Some(expose_hardware_info) = self.expose_system_hardware { + section.expose_system_hardware = expose_hardware_info + } + if let Some(expose_crypto_hardware) = self.expose_crypto_hardware { + section.expose_crypto_hardware = expose_crypto_hardware + } + section + } +} + +#[derive(clap::Args, Debug)] +pub(crate) struct MixnetArgs { + /// Address this node will bind to for listening for mixnet packets + /// default: `0.0.0.0:1789` + #[clap( + long, + env = NYMNODE_MIXNET_BIND_ADDRESS_ARG + )] + pub(crate) mixnet_bind_address: Option, + + /// Addresses to nym APIs from which the node gets the view of the network. + #[clap( + long, + value_delimiter = ',', + env = NYMNODE_NYM_APIS_ARG + )] + pub(crate) nym_api_urls: Option>, +} + +impl MixnetArgs { + // TODO: could we perhaps make a clap error here and call `safe_exit` instead? + pub(crate) fn build_config_section(self) -> config::Mixnet { + self.override_config_section(config::Mixnet::default()) + } + + pub(crate) fn override_config_section(self, mut section: config::Mixnet) -> config::Mixnet { + if let Some(bind_address) = self.mixnet_bind_address { + section.bind_address = bind_address + } + if let Some(nym_api_urls) = self.nym_api_urls { + section.nym_api_urls = nym_api_urls + } + section + } +} + +#[derive(clap::Args, Debug)] +pub(crate) struct WireguardArgs { + /// Specifies whether the wireguard service is enabled on this node. + #[clap( + long, + env = NYMNODE_WG_ENABLED_ARG + )] + pub(crate) wireguard_enabled: Option, + + /// Socket address this node will use for binding its wireguard interface. + /// default: `0.0.0.0:51822` + #[clap( + long, + env = NYMNODE_WG_BIND_ADDRESS_ARG + )] + pub(crate) wireguard_bind_address: Option, + + /// Ip address of the private wireguard network. + /// default: `10.1.0.0` + #[clap( + long, + env = NYMNODE_WG_IP_NETWORK_ARG, + )] + pub(crate) wireguard_private_network_ip: Option, + + /// Port announced to external clients wishing to connect to the wireguard interface. + /// Useful in the instances where the node is behind a proxy. + #[clap( + long, + env = NYMNODE_WG_ANNOUNCED_PORT_ARG + )] + pub(crate) wireguard_announced_port: Option, + + /// The prefix denoting the maximum number of the clients that can be connected via Wireguard. + /// The maximum value for IPv4 is 32 and for IPv6 is 128 + #[clap( + long, + env = NYMNODE_WG_PRIVATE_NETWORK_PREFIX_ARG + )] + pub(crate) wireguard_private_network_prefix: Option, +} + +impl WireguardArgs { + // TODO: could we perhaps make a clap error here and call `safe_exit` instead? + pub(crate) fn build_config_section>(self, data_dir: P) -> config::Wireguard { + self.override_config_section(config::Wireguard::new_default(data_dir)) + } + + pub(crate) fn override_config_section( + self, + mut section: config::Wireguard, + ) -> config::Wireguard { + if let Some(enabled) = self.wireguard_enabled { + section.enabled = enabled + } + + if let Some(bind_address) = self.wireguard_bind_address { + section.bind_address = bind_address + } + + if let Some(announced_port) = self.wireguard_announced_port { + section.announced_port = announced_port + } + + if let Some(private_network_ip) = self.wireguard_private_network_ip { + section.private_network_ip = private_network_ip + } + + if let Some(private_network_prefix) = self.wireguard_private_network_prefix { + section.private_network_prefix = private_network_prefix + } + + section + } +} + +#[derive(clap::Args, Debug)] +pub(crate) struct MixnodeArgs { + /// Socket address this node will use for binding its verloc API. + /// default: `0.0.0.0:1790` + #[clap( + long, + env = NYMNODE_VERLOC_BIND_ADDRESS_ARG + )] + pub(crate) verloc_bind_address: Option, +} + +impl MixnodeArgs { + // TODO: could we perhaps make a clap error here and call `safe_exit` instead? + pub(crate) fn build_config_section(self) -> config::MixnodeConfig { + self.override_config_section(config::MixnodeConfig::new_default()) + } + + pub(crate) fn override_config_section( + self, + mut section: config::MixnodeConfig, + ) -> config::MixnodeConfig { + if let Some(bind_address) = self.verloc_bind_address { + section.verloc.bind_address = bind_address + } + section + } +} + +#[derive(clap::Args, Debug, Zeroize, ZeroizeOnDrop)] +pub(crate) struct EntryGatewayArgs { + /// Socket address this node will use for binding its client websocket API. + /// default: `0.0.0.0:9000` + #[clap( + long, + env = NYMNODE_ENTRY_BIND_ADDRESS_ARG + )] + #[zeroize(skip)] + pub(crate) entry_bind_address: Option, + + /// Custom announced port for listening for websocket client traffic. + /// If unspecified, the value from the `bind_address` will be used instead + #[clap( + long, + env = NYMNODE_ENTRY_ANNOUNCE_WS_PORT_ARG + )] + pub(crate) announce_ws_port: Option, + + /// If applicable, announced port for listening for secure websocket client traffic. + #[clap( + long, + env = NYMNODE_ENTRY_ANNOUNCE_WSS_PORT_ARG + )] + pub(crate) announce_wss_port: Option, + + /// Indicates whether this gateway is accepting only coconut credentials for accessing the mixnet + /// or if it also accepts non-paying clients + #[clap( + long, + env = NYMNODE_ENFORCE_ZK_NYMS_ARG + )] + pub(crate) enforce_zk_nyms: Option, + + /// Custom cosmos wallet mnemonic used for zk-nym redemption. + /// If no value is provided, a fresh mnemonic is going to be generated. + #[clap( + long, + env = NYMNODE_MNEMONIC_ARG + )] + pub(crate) mnemonic: Option, +} + +impl EntryGatewayArgs { + // TODO: could we perhaps make a clap error here and call `safe_exit` instead? + pub(crate) fn build_config_section>( + self, + data_dir: P, + ) -> config::EntryGatewayConfig { + self.override_config_section(config::EntryGatewayConfig::new_default(data_dir)) + } + + pub(crate) fn override_config_section( + self, + mut section: config::EntryGatewayConfig, + ) -> config::EntryGatewayConfig { + if let Some(bind_address) = self.entry_bind_address { + section.bind_address = bind_address + } + if let Some(ws_port) = self.announce_ws_port { + section.announce_ws_port = Some(ws_port) + } + if let Some(wss_port) = self.announce_wss_port { + section.announce_wss_port = Some(wss_port) + } + if let Some(enforce_zk_nyms) = self.enforce_zk_nyms { + section.enforce_zk_nyms = enforce_zk_nyms + } + + section + } +} + +#[derive(clap::Args, Debug)] +pub(crate) struct ExitGatewayArgs { + /// Specifies the url for an upstream source of the exit policy used by this node. + #[clap( + long, + env = NYMNODE_UPSTREAM_EXIT_POLICY_ARG, + )] + pub(crate) upstream_exit_policy_url: Option, + + /// Specifies whether this exit node should run in 'open-proxy' mode + /// and thus would attempt to resolve **ANY** request it receives. + #[clap( + long, + env = NYMNODE_OPEN_PROXY_ARG, + )] + pub(crate) open_proxy: Option, +} + +impl ExitGatewayArgs { + // TODO: could we perhaps make a clap error here and call `safe_exit` instead? + pub(crate) fn build_config_section>( + self, + data_dir: P, + ) -> config::ExitGatewayConfig { + self.override_config_section(config::ExitGatewayConfig::new_default(data_dir)) + } + + pub(crate) fn override_config_section( + self, + mut section: config::ExitGatewayConfig, + ) -> config::ExitGatewayConfig { + if let Some(upstream_exit_policy) = self.upstream_exit_policy_url { + section.upstream_exit_policy_url = upstream_exit_policy + } + if let Some(open_proxy) = self.open_proxy { + section.open_proxy = open_proxy + } + + section + } +} diff --git a/nym-node/src/cli/mod.rs b/nym-node/src/cli/mod.rs new file mode 100644 index 0000000000..762e2d9bce --- /dev/null +++ b/nym-node/src/cli/mod.rs @@ -0,0 +1,87 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::cli::commands::{bonding_information, build_info, migrate, node_details, run, sign}; +use crate::env::vars::{NYMNODE_CONFIG_ENV_FILE_ARG, NYMNODE_NO_BANNER_ARG}; +use clap::{Parser, Subcommand}; +use nym_bin_common::bin_info; +use nym_node::error::NymNodeError; +use std::sync::OnceLock; + +mod commands; +mod helpers; + +pub const DEFAULT_NYMNODE_ID: &str = "default-nym-node"; + +// Helper for passing LONG_VERSION to clap +fn pretty_build_info_static() -> &'static str { + static PRETTY_BUILD_INFORMATION: OnceLock = OnceLock::new(); + PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print()) +} + +#[derive(Parser, Debug)] +#[clap(author = "Nymtech", version, long_version = pretty_build_info_static(), about)] +pub(crate) struct Cli { + /// Path pointing to an env file that configures the nym-node and overrides any preconfigured values. + #[clap( + short, + long, + env = NYMNODE_CONFIG_ENV_FILE_ARG + )] + pub(crate) config_env_file: Option, + + /// Flag used for disabling the printed banner in tty. + #[clap( + long, + env = NYMNODE_NO_BANNER_ARG + )] + pub(crate) no_banner: bool, + + #[clap(subcommand)] + command: Commands, +} + +impl Cli { + pub(crate) async fn execute(self) -> Result<(), NymNodeError> { + match self.command { + Commands::BuildInfo(args) => build_info::execute(args), + Commands::BondingInformation(args) => bonding_information::execute(args).await, + Commands::NodeDetails(args) => node_details::execute(args).await, + Commands::Run(args) => run::execute(*args).await, + Commands::Migrate(args) => migrate::execute(*args).await, + Commands::Sign(args) => sign::execute(args).await, + } + } +} + +#[derive(Subcommand, Debug)] +pub(crate) enum Commands { + /// Show build information of this binary + BuildInfo(build_info::Args), + + /// Show bonding information of this node depending on its currently selected mode. + BondingInformation(bonding_information::Args), + + /// Show details of this node. + NodeDetails(node_details::Args), + + /// Attempt to migrate an existing mixnode or gateway into a nym-node. + Migrate(Box), + + /// Start this nym-node + Run(Box), + + /// Use identity key of this node to sign provided message. + Sign(sign::Args), +} + +#[cfg(test)] +mod tests { + use super::*; + use clap::CommandFactory; + + #[test] + fn verify_cli() { + Cli::command().debug_assert(); + } +} diff --git a/nym-node/src/config/entry_gateway.rs b/nym-node/src/config/entry_gateway.rs new file mode 100644 index 0000000000..e0db9aca64 --- /dev/null +++ b/nym-node/src/config/entry_gateway.rs @@ -0,0 +1,83 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::config::helpers::ephemeral_gateway_config; +use crate::config::persistence::EntryGatewayPaths; +use crate::config::Config; +use crate::error::EntryGatewayError; +use nym_config::defaults::DEFAULT_CLIENT_LISTENING_PORT; +use nym_config::helpers::inaddr_any; +use nym_config::serde_helpers::de_maybe_port; +use serde::{Deserialize, Serialize}; +use std::net::SocketAddr; +use std::path::Path; + +pub const DEFAULT_WS_PORT: u16 = DEFAULT_CLIENT_LISTENING_PORT; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct EntryGatewayConfig { + pub storage_paths: EntryGatewayPaths, + + /// Indicates whether this gateway is accepting only coconut credentials for accessing the mixnet + /// or if it also accepts non-paying clients + pub enforce_zk_nyms: bool, + + /// Socket address this node will use for binding its client websocket API. + /// default: `0.0.0.0:9000` + pub bind_address: SocketAddr, + + /// Custom announced port for listening for websocket client traffic. + /// If unspecified, the value from the `bind_address` will be used instead + /// default: None + #[serde(deserialize_with = "de_maybe_port")] + pub announce_ws_port: Option, + + /// If applicable, announced port for listening for secure websocket client traffic. + /// (default: None) + #[serde(deserialize_with = "de_maybe_port")] + pub announce_wss_port: Option, + + #[serde(default)] + pub debug: Debug, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Debug { + /// Number of messages from offline client that can be pulled at once (i.e. with a single SQL query) from the storage. + pub message_retrieval_limit: i64, +} + +impl Debug { + const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100; +} + +impl Default for Debug { + fn default() -> Self { + Debug { + message_retrieval_limit: Self::DEFAULT_MESSAGE_RETRIEVAL_LIMIT, + } + } +} + +impl EntryGatewayConfig { + pub fn new_default>(data_dir: P) -> Self { + EntryGatewayConfig { + storage_paths: EntryGatewayPaths::new(data_dir), + enforce_zk_nyms: false, + bind_address: SocketAddr::new(inaddr_any(), DEFAULT_WS_PORT), + announce_ws_port: None, + announce_wss_port: None, + debug: Default::default(), + } + } +} + +// a temporary solution until all nodes are even more tightly integrated +pub fn ephemeral_entry_gateway_config( + config: Config, + mnemonic: &bip39::Mnemonic, +) -> Result { + Ok(ephemeral_gateway_config(config, mnemonic)?) +} diff --git a/nym-node/src/config/exit_gateway.rs b/nym-node/src/config/exit_gateway.rs new file mode 100644 index 0000000000..699264ad63 --- /dev/null +++ b/nym-node/src/config/exit_gateway.rs @@ -0,0 +1,241 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::config::helpers::ephemeral_gateway_config; +use crate::config::persistence::ExitGatewayPaths; +use crate::config::Config; +use crate::error::ExitGatewayError; +use clap::crate_version; +use nym_client_core_config_types::DebugConfig as ClientDebugConfig; +use nym_config::defaults::mainnet; +use nym_gateway::node::{LocalIpPacketRouterOpts, LocalNetworkRequesterOpts}; +use serde::{Deserialize, Serialize}; +use std::path::Path; +use url::Url; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ExitGatewayConfig { + pub storage_paths: ExitGatewayPaths, + + /// specifies whether this exit node should run in 'open-proxy' mode + /// and thus would attempt to resolve **ANY** request it receives. + pub open_proxy: bool, + + /// Specifies the url for an upstream source of the exit policy used by this node. + pub upstream_exit_policy_url: Url, + + pub network_requester: NetworkRequester, + + pub ip_packet_router: IpPacketRouter, +} + +impl ExitGatewayConfig { + pub fn new_default>(data_dir: P) -> Self { + #[allow(clippy::expect_used)] + // SAFETY: + // we expect our default values to be well-formed + ExitGatewayConfig { + storage_paths: ExitGatewayPaths::new(data_dir), + open_proxy: false, + upstream_exit_policy_url: mainnet::EXIT_POLICY_URL + .parse() + .expect("invalid default exit policy URL"), + network_requester: Default::default(), + ip_packet_router: Default::default(), + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +pub struct NetworkRequester { + #[serde(default)] + pub debug: NetworkRequesterDebug, +} + +#[allow(clippy::derivable_impls)] +impl Default for NetworkRequester { + fn default() -> Self { + NetworkRequester { + debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +pub struct NetworkRequesterDebug { + /// Specifies whether network requester service is enabled in this process. + /// This is only here for debugging purposes as exit gateway should always run **both** + /// network requester and an ip packet router. + pub enabled: bool, + + /// Disable Poisson sending rate. + /// This is equivalent to setting client_debug.traffic.disable_main_poisson_packet_distribution = true + /// (or is it (?)) + pub disable_poisson_rate: bool, + + /// Shared detailed client configuration options + #[serde(flatten)] + pub client_debug: ClientDebugConfig, +} + +impl Default for NetworkRequesterDebug { + fn default() -> Self { + NetworkRequesterDebug { + enabled: true, + disable_poisson_rate: true, + client_debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +pub struct IpPacketRouter { + #[serde(default)] + pub debug: IpPacketRouterDebug, +} + +#[allow(clippy::derivable_impls)] +impl Default for IpPacketRouter { + fn default() -> Self { + IpPacketRouter { + debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default)] +pub struct IpPacketRouterDebug { + /// Specifies whether ip packet routing service is enabled in this process. + /// This is only here for debugging purposes as exit gateway should always run **both** + /// network requester and an ip packet router. + pub enabled: bool, + + /// Disable Poisson sending rate. + /// This is equivalent to setting client_debug.traffic.disable_main_poisson_packet_distribution = true + /// (or is it (?)) + pub disable_poisson_rate: bool, + + /// Shared detailed client configuration options + #[serde(flatten)] + pub client_debug: ClientDebugConfig, +} + +impl Default for IpPacketRouterDebug { + fn default() -> Self { + IpPacketRouterDebug { + enabled: true, + disable_poisson_rate: true, + client_debug: Default::default(), + } + } +} + +pub struct EphemeralConfig { + pub gateway: nym_gateway::config::Config, + pub nr_opts: LocalNetworkRequesterOpts, + pub ipr_opts: LocalIpPacketRouterOpts, +} + +fn base_client_config(config: &Config) -> nym_client_core_config_types::Client { + nym_client_core_config_types::Client { + version: format!("{}-nym-node", crate_version!()), + id: config.id.clone(), + // irrelevant field - no need for credentials in embedded mode + disabled_credentials_mode: true, + nyxd_urls: config.mixnet.nyxd_urls.clone(), + nym_api_urls: config.mixnet.nym_api_urls.clone(), + } +} + +// that function is rather disgusting, but I hope it's not going to live for too long +pub fn ephemeral_exit_gateway_config( + config: Config, + mnemonic: &bip39::Mnemonic, +) -> Result { + let nr_opts = LocalNetworkRequesterOpts { + config: nym_network_requester::Config { + base: nym_client_core_config_types::Config { + client: base_client_config(&config), + debug: config.exit_gateway.network_requester.debug.client_debug, + }, + network_requester: nym_network_requester::config::NetworkRequester { + open_proxy: config.exit_gateway.open_proxy, + enabled_statistics: false, + statistics_recipient: None, + disable_poisson_rate: config + .exit_gateway + .network_requester + .debug + .disable_poisson_rate, + upstream_exit_policy_url: Some( + config.exit_gateway.upstream_exit_policy_url.clone(), + ), + }, + storage_paths: nym_network_requester::config::NetworkRequesterPaths { + common_paths: config + .exit_gateway + .storage_paths + .network_requester + .to_common_client_paths(), + }, + network_requester_debug: Default::default(), + logging: config.logging, + }, + custom_mixnet_path: None, + }; + + let ipr_opts = LocalIpPacketRouterOpts { + config: nym_ip_packet_router::Config { + base: nym_client_core_config_types::Config { + client: base_client_config(&config), + debug: config.exit_gateway.ip_packet_router.debug.client_debug, + }, + ip_packet_router: nym_ip_packet_router::config::IpPacketRouter { + disable_poisson_rate: config + .exit_gateway + .ip_packet_router + .debug + .disable_poisson_rate, + upstream_exit_policy_url: Some( + config.exit_gateway.upstream_exit_policy_url.clone(), + ), + }, + storage_paths: nym_ip_packet_router::config::IpPacketRouterPaths { + common_paths: config + .exit_gateway + .storage_paths + .ip_packet_router + .to_common_client_paths(), + ip_packet_router_description: Default::default(), + }, + + logging: config.logging, + }, + custom_mixnet_path: None, + }; + + let pub_id_path = config + .storage_paths + .keys + .public_ed25519_identity_key_file + .clone(); + let ipr_enabled = config.exit_gateway.ip_packet_router.debug.enabled; + let nr_enabled = config.exit_gateway.network_requester.debug.enabled; + + let mut gateway = ephemeral_gateway_config(config, mnemonic)?; + gateway.ip_packet_router.enabled = ipr_enabled; + gateway.network_requester.enabled = nr_enabled; + + // this is temporary until http api is fully managed by nymnode itself + // (because currently gateway is loading its public key for the second time when starting the API to determine addresses of its clients. + // Obviously this doesn't work properly without the valid paths) + gateway.storage_paths.keys.public_identity_key_file = pub_id_path; + + Ok(EphemeralConfig { + nr_opts, + ipr_opts, + gateway, + }) +} diff --git a/nym-node/src/config/helpers.rs b/nym-node/src/config/helpers.rs new file mode 100644 index 0000000000..7ca4dcac97 --- /dev/null +++ b/nym-node/src/config/helpers.rs @@ -0,0 +1,93 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::config::Config; +use clap::crate_version; +use std::net::IpAddr; +use thiserror::Error; + +#[derive(Debug, Error)] +#[error("currently it's not supported to have different ip addresses for clients and mixnet ({clients_bind_ip} and {mix_bind_ip} were used)")] +pub struct UnsupportedGatewayAddresses { + clients_bind_ip: IpAddr, + mix_bind_ip: IpAddr, +} + +// a temporary solution until all nodes are even more tightly integrated +pub fn ephemeral_gateway_config( + config: Config, + mnemonic: &bip39::Mnemonic, +) -> Result { + let host = nym_gateway::config::Host { + public_ips: config.host.public_ips, + hostname: config.host.hostname, + }; + + let http = nym_gateway::config::Http { + bind_address: config.http.bind_address, + landing_page_assets_path: config.http.landing_page_assets_path, + }; + + let clients_bind_ip = config.entry_gateway.bind_address.ip(); + let mix_bind_ip = config.mixnet.bind_address.ip(); + if clients_bind_ip != mix_bind_ip { + return Err(UnsupportedGatewayAddresses { + clients_bind_ip, + mix_bind_ip, + }); + } + + // SAFETY: we're using hardcoded valid url here (that won't be used anyway) + #[allow(clippy::unwrap_used)] + let gateway = nym_gateway::config::Gateway { + // that field is very much irrelevant, but I guess let's keep them for now + version: format!("{}-nym-node", crate_version!()), + id: config.id, + only_coconut_credentials: config.entry_gateway.enforce_zk_nyms, + listening_address: clients_bind_ip, + mix_port: config.mixnet.bind_address.port(), + clients_port: config.entry_gateway.bind_address.port(), + clients_wss_port: config.entry_gateway.announce_wss_port, + enabled_statistics: false, + statistics_service_url: "https://nymtech.net/foobar".parse().unwrap(), + nym_api_urls: config.mixnet.nym_api_urls, + nyxd_urls: config.mixnet.nyxd_urls, + + // that's nasty but can't do anything about it for this temporary solution : ( + cosmos_mnemonic: mnemonic.clone(), + }; + + let wireguard = nym_gateway::config::Wireguard { + enabled: config.wireguard.enabled, + bind_address: config.wireguard.bind_address, + announced_port: config.wireguard.announced_port, + private_network_prefix: config.wireguard.private_network_prefix, + storage_paths: nym_gateway::config::WireguardPaths::new_empty(), + }; + + Ok(nym_gateway::config::Config::externally_loaded( + host, + http, + gateway, + wireguard, + nym_gateway::config::GatewayPaths::new_empty(), + nym_gateway::config::NetworkRequester { enabled: false }, + nym_gateway::config::IpPacketRouter { enabled: false }, + config.logging, + nym_gateway::config::Debug { + packet_forwarding_initial_backoff: config + .mixnet + .debug + .packet_forwarding_initial_backoff, + packet_forwarding_maximum_backoff: config + .mixnet + .debug + .packet_forwarding_maximum_backoff, + initial_connection_timeout: config.mixnet.debug.initial_connection_timeout, + maximum_connection_buffer_size: config.mixnet.debug.maximum_connection_buffer_size, + message_retrieval_limit: config.entry_gateway.debug.message_retrieval_limit, + use_legacy_framed_packet_version: false, + ..Default::default() + }, + )) +} diff --git a/nym-node/src/config/mixnode.rs b/nym-node/src/config/mixnode.rs new file mode 100644 index 0000000000..684ea7129b --- /dev/null +++ b/nym-node/src/config/mixnode.rs @@ -0,0 +1,214 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::config::persistence::MixnodePaths; +use crate::config::Config; +use crate::error::MixnodeError; +use clap::crate_version; +use nym_config::defaults::DEFAULT_VERLOC_LISTENING_PORT; +use nym_config::helpers::inaddr_any; +use serde::{Deserialize, Serialize}; +use std::net::SocketAddr; +use std::time::Duration; + +pub const DEFAULT_VERLOC_PORT: u16 = DEFAULT_VERLOC_LISTENING_PORT; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct MixnodeConfig { + pub storage_paths: MixnodePaths, + + pub verloc: Verloc, + + #[serde(default)] + pub debug: Debug, +} + +impl MixnodeConfig { + pub fn new_default() -> Self { + MixnodeConfig { + storage_paths: MixnodePaths {}, + verloc: Default::default(), + debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Verloc { + /// Socket address this node will use for binding its verloc API. + /// default: `0.0.0.0:1790` + pub bind_address: SocketAddr, + + #[serde(default)] + pub debug: VerlocDebug, +} + +impl Default for Verloc { + fn default() -> Self { + Verloc { + bind_address: SocketAddr::new(inaddr_any(), DEFAULT_VERLOC_PORT), + debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct VerlocDebug { + /// Specifies number of echo packets sent to each node during a measurement run. + pub packets_per_node: usize, + + /// Specifies maximum amount of time to wait for the connection to get established. + #[serde(with = "humantime_serde")] + pub connection_timeout: Duration, + + /// Specifies maximum amount of time to wait for the reply packet to arrive before abandoning the test. + #[serde(with = "humantime_serde")] + pub packet_timeout: Duration, + + /// Specifies delay between subsequent test packets being sent (after receiving a reply). + #[serde(with = "humantime_serde")] + pub delay_between_packets: Duration, + + /// Specifies number of nodes being tested at once. + pub tested_nodes_batch_size: usize, + + /// Specifies delay between subsequent test runs. + #[serde(with = "humantime_serde")] + pub testing_interval: Duration, + + /// Specifies delay between attempting to run the measurement again if the previous run failed + /// due to being unable to get the list of nodes. + #[serde(with = "humantime_serde")] + pub retry_timeout: Duration, +} + +impl VerlocDebug { + const DEFAULT_PACKETS_PER_NODE: usize = 100; + const DEFAULT_CONNECTION_TIMEOUT: Duration = Duration::from_millis(5000); + const DEFAULT_PACKET_TIMEOUT: Duration = Duration::from_millis(1500); + const DEFAULT_DELAY_BETWEEN_PACKETS: Duration = Duration::from_millis(50); + const DEFAULT_BATCH_SIZE: usize = 50; + const DEFAULT_TESTING_INTERVAL: Duration = Duration::from_secs(60 * 60 * 12); + const DEFAULT_RETRY_TIMEOUT: Duration = Duration::from_secs(60 * 30); +} + +impl Default for VerlocDebug { + fn default() -> Self { + VerlocDebug { + packets_per_node: VerlocDebug::DEFAULT_PACKETS_PER_NODE, + connection_timeout: VerlocDebug::DEFAULT_CONNECTION_TIMEOUT, + packet_timeout: VerlocDebug::DEFAULT_PACKET_TIMEOUT, + delay_between_packets: VerlocDebug::DEFAULT_DELAY_BETWEEN_PACKETS, + tested_nodes_batch_size: VerlocDebug::DEFAULT_BATCH_SIZE, + testing_interval: VerlocDebug::DEFAULT_TESTING_INTERVAL, + retry_timeout: VerlocDebug::DEFAULT_RETRY_TIMEOUT, + } + } +} + +impl From for nym_mixnode::config::Verloc { + fn from(value: VerlocDebug) -> Self { + nym_mixnode::config::Verloc { + packets_per_node: value.packets_per_node, + connection_timeout: value.connection_timeout, + packet_timeout: value.packet_timeout, + delay_between_packets: value.delay_between_packets, + tested_nodes_batch_size: value.tested_nodes_batch_size, + testing_interval: value.testing_interval, + retry_timeout: value.retry_timeout, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Debug { + /// Delay between each subsequent node statistics being logged to the console + #[serde(with = "humantime_serde")] + pub node_stats_logging_delay: Duration, + + /// Delay between each subsequent node statistics being updated + #[serde(with = "humantime_serde")] + pub node_stats_updating_delay: Duration, +} + +impl Debug { + const DEFAULT_NODE_STATS_LOGGING_DELAY: Duration = Duration::from_millis(60_000); + const DEFAULT_NODE_STATS_UPDATING_DELAY: Duration = Duration::from_millis(30_000); +} + +impl Default for Debug { + fn default() -> Self { + Debug { + node_stats_logging_delay: Debug::DEFAULT_NODE_STATS_LOGGING_DELAY, + node_stats_updating_delay: Debug::DEFAULT_NODE_STATS_UPDATING_DELAY, + } + } +} + +// a temporary solution until all nodes are even more tightly integrated +pub fn ephemeral_mixnode_config( + config: Config, +) -> Result { + let host = nym_mixnode::config::Host { + public_ips: config.host.public_ips, + hostname: config.host.hostname, + }; + + let http = nym_mixnode::config::Http { + bind_address: config.http.bind_address, + landing_page_assets_path: config.http.landing_page_assets_path, + metrics_key: config.http.access_token, + }; + + let verloc_bind_ip = config.mixnode.verloc.bind_address.ip(); + let mix_bind_ip = config.mixnet.bind_address.ip(); + if verloc_bind_ip != mix_bind_ip { + return Err(MixnodeError::UnsupportedAddresses { + verloc_bind_ip, + mix_bind_ip, + }); + } + + let listening_address = mix_bind_ip; + let mix_port = config.mixnet.bind_address.port(); + let verloc_port = config.mixnode.verloc.bind_address.port(); + let nym_api_urls = config.mixnet.nym_api_urls; + + let mixnode = nym_mixnode::config::MixNode { + // that field is very much irrelevant, but I guess let's keep them for now + version: format!("{}-nym-node", crate_version!()), + id: config.id, + listening_address, + mix_port, + verloc_port, + nym_api_urls, + }; + + Ok(nym_mixnode::config::Config::externally_loaded( + host, + http, + mixnode, + nym_mixnode::config::MixNodePaths::new_empty(), + config.mixnode.verloc.debug, + config.logging, + nym_mixnode::config::Debug { + node_stats_logging_delay: config.mixnode.debug.node_stats_logging_delay, + node_stats_updating_delay: config.mixnode.debug.node_stats_updating_delay, + packet_forwarding_initial_backoff: config + .mixnet + .debug + .packet_forwarding_initial_backoff, + packet_forwarding_maximum_backoff: config + .mixnet + .debug + .packet_forwarding_maximum_backoff, + initial_connection_timeout: config.mixnet.debug.initial_connection_timeout, + maximum_connection_buffer_size: config.mixnet.debug.maximum_connection_buffer_size, + use_legacy_framed_packet_version: false, + }, + )) +} diff --git a/nym-node/src/config/mod.rs b/nym-node/src/config/mod.rs index 51bfe10c67..4ff8519993 100644 --- a/nym-node/src/config/mod.rs +++ b/nym-node/src/config/mod.rs @@ -1,72 +1,492 @@ -// Copyright 2023 - Nym Technologies SA +// Copyright 2023-2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use nym_config::defaults::{DEFAULT_NYM_NODE_HTTP_PORT, WG_PORT}; +use crate::config::persistence::NymNodePaths; +use crate::config::template::CONFIG_TEMPLATE; +use crate::error::NymNodeError; +use celes::Country; +use clap::ValueEnum; +use nym_bin_common::logging::LoggingSettings; +use nym_config::defaults::{ + mainnet, var_names, DEFAULT_MIX_LISTENING_PORT, DEFAULT_NYM_NODE_HTTP_PORT, WG_PORT, +}; +use nym_config::helpers::inaddr_any; +use nym_config::serde_helpers::de_maybe_stringified; +use nym_config::{ + must_get_home, parse_urls, read_config_from_toml_file, save_formatted_config_to_file, + NymConfigTemplate, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, NYM_DIR, +}; use serde::{Deserialize, Serialize}; -use serde_helpers::*; +use std::env; +use std::fmt::{Display, Formatter}; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; +use std::time::Duration; +use tracing::{debug, error}; +use url::Url; +pub mod entry_gateway; +pub mod exit_gateway; +pub mod helpers; +pub mod mixnode; pub mod persistence; -mod serde_helpers; +mod template; +pub mod upgrade_helpers; + +pub use crate::config::entry_gateway::EntryGatewayConfig; +pub use crate::config::exit_gateway::ExitGatewayConfig; +pub use crate::config::mixnode::MixnodeConfig; + +const DEFAULT_NYMNODES_DIR: &str = "nym-nodes"; pub const DEFAULT_WIREGUARD_PORT: u16 = WG_PORT; +pub const DEFAULT_WIREGUARD_NETWORK_IP: IpAddr = IpAddr::V4(Ipv4Addr::new(10, 1, 0, 0)); pub const DEFAULT_WIREGUARD_PREFIX: u8 = 16; pub const DEFAULT_HTTP_PORT: u16 = DEFAULT_NYM_NODE_HTTP_PORT; +pub const DEFAULT_MIXNET_PORT: u16 = DEFAULT_MIX_LISTENING_PORT; + +/// Derive default path to nym-node's config directory. +/// It should get resolved to `$HOME/.nym/nym-nodes//config` +pub fn default_config_directory>(id: P) -> PathBuf { + must_get_home() + .join(NYM_DIR) + .join(DEFAULT_NYMNODES_DIR) + .join(id) + .join(DEFAULT_CONFIG_DIR) +} + +/// Derive default path to nym-node's config file. +/// It should get resolved to `$HOME/.nym/nym-nodes//config/config.toml` +pub fn default_config_filepath>(id: P) -> PathBuf { + default_config_directory(id).join(DEFAULT_CONFIG_FILENAME) +} + +// a temporary solution until all "types" are run at the same time +#[derive(Debug, Default, Serialize, Deserialize, ValueEnum, Clone, Copy)] +#[serde(rename_all = "snake_case")] +pub enum NodeMode { + #[default] + #[clap(alias = "mix")] + Mixnode, + + #[clap(alias = "entry", alias = "gateway")] + EntryGateway, + + #[clap(alias = "exit")] + ExitGateway, +} + +impl Display for NodeMode { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + NodeMode::Mixnode => "mixnode".fmt(f), + NodeMode::EntryGateway => "entry-gateway".fmt(f), + NodeMode::ExitGateway => "exit-gateway".fmt(f), + } + } +} + +pub struct ConfigBuilder { + pub id: String, + + pub config_path: PathBuf, + + pub data_dir: PathBuf, + + pub mode: NodeMode, + + pub mixnet: Option, + + pub host: Option, + + pub http: Option, + + pub wireguard: Option, + + pub storage_paths: Option, + + pub mixnode: Option, + + pub entry_gateway: Option, + + pub exit_gateway: Option, + + pub logging: Option, +} + +impl ConfigBuilder { + pub fn new(id: String, config_path: PathBuf, data_dir: PathBuf) -> Self { + ConfigBuilder { + id, + config_path, + data_dir, + host: None, + http: None, + mixnet: None, + wireguard: None, + mode: NodeMode::default(), + storage_paths: None, + mixnode: None, + entry_gateway: None, + exit_gateway: None, + logging: None, + } + } + + pub fn with_mode(mut self, mode: impl Into) -> Self { + self.mode = mode.into(); + self + } + + pub fn with_host(mut self, section: impl Into>) -> Self { + self.host = section.into(); + self + } + + pub fn with_http(mut self, section: impl Into>) -> Self { + self.http = section.into(); + self + } + + pub fn with_mixnet(mut self, section: impl Into>) -> Self { + self.mixnet = section.into(); + self + } + + pub fn with_wireguard(mut self, section: impl Into>) -> Self { + self.wireguard = section.into(); + self + } + + pub fn with_storage_paths(mut self, section: impl Into>) -> Self { + self.storage_paths = section.into(); + self + } + + pub fn with_mixnode(mut self, section: impl Into>) -> Self { + self.mixnode = section.into(); + self + } + + pub fn with_entry_gateway(mut self, section: impl Into>) -> Self { + self.entry_gateway = section.into(); + self + } + + pub fn with_exit_gateway(mut self, section: impl Into>) -> Self { + self.exit_gateway = section.into(); + self + } + + pub fn with_logging(mut self, section: impl Into>) -> Self { + self.logging = section.into(); + self + } + + pub fn build(self) -> Config { + Config { + id: self.id, + mode: self.mode, + host: self.host.unwrap_or_default(), + http: self.http.unwrap_or_default(), + mixnet: self.mixnet.unwrap_or_default(), + wireguard: self + .wireguard + .unwrap_or_else(|| Wireguard::new_default(&self.data_dir)), + storage_paths: self + .storage_paths + .unwrap_or_else(|| NymNodePaths::new(&self.data_dir)), + mixnode: self.mixnode.unwrap_or_else(MixnodeConfig::new_default), + entry_gateway: self + .entry_gateway + .unwrap_or_else(|| EntryGatewayConfig::new_default(&self.data_dir)), + exit_gateway: self + .exit_gateway + .unwrap_or_else(|| ExitGatewayConfig::new_default(&self.data_dir)), + logging: self.logging.unwrap_or_default(), + save_path: Some(self.config_path), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Config { + // additional metadata holding on-disk location of this config file + #[serde(skip)] + pub(crate) save_path: Option, + + /// Human-readable ID of this particular node. + pub id: String, + + /// Current mode of this nym-node. + /// Expect this field to be changed in the future to allow running the node in multiple modes (i.e. mixnode + gateway) + pub mode: NodeMode, + + pub host: Host, + + pub mixnet: Mixnet, + + /// Storage paths to persistent nym-node data, such as its long term keys. + pub storage_paths: NymNodePaths, + + #[serde(default)] + pub http: Http, + + pub wireguard: Wireguard, + + pub mixnode: MixnodeConfig, + + pub entry_gateway: EntryGatewayConfig, + + pub exit_gateway: ExitGatewayConfig, + + #[serde(default)] + pub logging: LoggingSettings, +} + +impl NymConfigTemplate for Config { + fn template(&self) -> &'static str { + CONFIG_TEMPLATE + } +} + +impl Config { + pub fn save(&self) -> Result<(), NymNodeError> { + let save_location = self.save_location(); + debug!( + "attempting to save config file to '{}'", + save_location.display() + ); + save_formatted_config_to_file(self, &save_location).map_err(|source| { + NymNodeError::ConfigSaveFailure { + id: self.id.clone(), + path: save_location, + source, + } + }) + } + + pub fn save_location(&self) -> PathBuf { + self.save_path + .clone() + .unwrap_or(self.default_save_location()) + } + + pub fn default_save_location(&self) -> PathBuf { + default_config_filepath(&self.id) + } + + pub fn default_data_directory>(config_path: P) -> Result { + let config_path = config_path.as_ref(); + + // we got a proper path to the .toml file + let Some(config_dir) = config_path.parent() else { + error!( + "'{}' does not have a parent directory. Have you pointed to the fs root?", + config_path.display() + ); + return Err(NymNodeError::DataDirDerivationFailure); + }; + + let Some(config_dir_name) = config_dir.file_name() else { + error!( + "could not obtain parent directory name of '{}'. Have you used relative paths?", + config_path.display() + ); + return Err(NymNodeError::DataDirDerivationFailure); + }; + + if config_dir_name != DEFAULT_CONFIG_DIR { + error!( + "the parent directory of '{}' ({}) is not {DEFAULT_CONFIG_DIR}. currently this is not supported", + config_path.display(), config_dir_name.to_str().unwrap_or("UNKNOWN") + ); + return Err(NymNodeError::DataDirDerivationFailure); + } + + let Some(node_dir) = config_dir.parent() else { + error!( + "'{}' does not have a parent directory. Have you pointed to the fs root?", + config_dir.display() + ); + return Err(NymNodeError::DataDirDerivationFailure); + }; + + Ok(node_dir.join(DEFAULT_DATA_DIR)) + } + + // simple wrapper that reads config file and assigns path location + fn read_from_path>(path: P) -> Result { + let path = path.as_ref(); + let mut loaded: Config = + read_config_from_toml_file(path).map_err(|source| NymNodeError::ConfigLoadFailure { + path: path.to_path_buf(), + source, + })?; + loaded.save_path = Some(path.to_path_buf()); + debug!("loaded config file from {}", path.display()); + Ok(loaded) + } + + pub fn read_from_toml_file>(path: P) -> Result { + Self::read_from_path(path) + } +} // TODO: this is very much a WIP. we need proper ssl certificate support here -#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[derive(Debug, Clone, Default, Deserialize, PartialEq, Serialize)] +#[serde(default)] #[serde(deny_unknown_fields)] pub struct Host { /// Ip address(es) of this host, such as 1.1.1.1 that external clients will use for connections. + /// If no values are provided, when this node gets included in the network, + /// its ip addresses will be populated by whatever value is resolved by associated nym-api. pub public_ips: Vec, /// Optional hostname of this node, for example nymtech.net. // TODO: this is temporary. to be replaced by pulling the data directly from the certs. - #[serde(deserialize_with = "de_maybe_string")] + #[serde(deserialize_with = "de_maybe_stringified")] pub hostname: Option, + + /// Optional ISO 3166 alpha-2 two-letter country code of the node's **physical** location + #[serde(deserialize_with = "de_maybe_stringified")] + pub location: Option, } -impl Host { - pub fn validate(&self) -> bool { - if self.public_ips.is_empty() { - return false; - } - - true - } -} - -#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] #[serde(default)] #[serde(deny_unknown_fields)] pub struct Http { /// Socket address this node will use for binding its http API. /// default: `0.0.0.0:8080` - /// note: for legacy reasons, it defaults to port `8000` for mixnodes. pub bind_address: SocketAddr, /// Path to assets directory of custom landing page of this node. - #[serde(deserialize_with = "de_maybe_path")] + #[serde(deserialize_with = "de_maybe_stringified")] pub landing_page_assets_path: Option, + /// An optional bearer token for accessing certain http endpoints. + /// Currently only used for obtaining mixnode's stats. #[serde(default)] - pub metrics_key: Option, + pub access_token: Option, + + /// Specify whether basic system information should be exposed. + /// default: true + pub expose_system_info: bool, + + /// Specify whether basic system hardware information should be exposed. + /// This option is superseded by `expose_system_info` + /// default: true + pub expose_system_hardware: bool, + + /// Specify whether detailed system crypto hardware information should be exposed. + /// This option is superseded by `expose_system_hardware` + /// default: true + pub expose_crypto_hardware: bool, } impl Default for Http { fn default() -> Self { Http { - bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), DEFAULT_HTTP_PORT), + bind_address: SocketAddr::new(inaddr_any(), DEFAULT_HTTP_PORT), landing_page_assets_path: None, - metrics_key: None, + access_token: None, + expose_system_info: true, + expose_system_hardware: true, + expose_crypto_hardware: true, } } } -#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] #[serde(default)] #[serde(deny_unknown_fields)] +pub struct Mixnet { + /// Address this node will bind to for listening for mixnet packets + /// default: `0.0.0.0:1789` + pub bind_address: SocketAddr, + + /// Addresses to nym APIs from which the node gets the view of the network. + pub nym_api_urls: Vec, + + /// Addresses to nyxd which the node uses to interact with the nyx chain. + pub nyxd_urls: Vec, + + #[serde(default)] + pub debug: MixnetDebug, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(default)] +#[serde(deny_unknown_fields)] +pub struct MixnetDebug { + /// Initial value of an exponential backoff to reconnect to dropped TCP connection when + /// forwarding sphinx packets. + #[serde(with = "humantime_serde")] + pub packet_forwarding_initial_backoff: Duration, + + /// Maximum value of an exponential backoff to reconnect to dropped TCP connection when + /// forwarding sphinx packets. + #[serde(with = "humantime_serde")] + pub packet_forwarding_maximum_backoff: Duration, + + /// Timeout for establishing initial connection when trying to forward a sphinx packet. + #[serde(with = "humantime_serde")] + pub initial_connection_timeout: Duration, + + /// Maximum number of packets that can be stored waiting to get sent to a particular connection. + pub maximum_connection_buffer_size: usize, +} + +impl MixnetDebug { + const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000); + const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000); + const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500); + const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000; +} + +impl Default for MixnetDebug { + fn default() -> Self { + MixnetDebug { + packet_forwarding_initial_backoff: Self::DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF, + packet_forwarding_maximum_backoff: Self::DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF, + initial_connection_timeout: Self::DEFAULT_INITIAL_CONNECTION_TIMEOUT, + maximum_connection_buffer_size: Self::DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE, + } + } +} + +impl Default for Mixnet { + fn default() -> Self { + // SAFETY: + // our hardcoded values should always be valid + #[allow(clippy::expect_used)] + // is if there's anything set in the environment, otherwise fallback to mainnet + let nym_api_urls = if let Ok(env_value) = env::var(var_names::NYM_API) { + parse_urls(&env_value) + } else { + vec![mainnet::NYM_API.parse().expect("Invalid default API URL")] + }; + + #[allow(clippy::expect_used)] + let nyxd_urls = if let Ok(env_value) = env::var(var_names::NYXD) { + parse_urls(&env_value) + } else { + vec![mainnet::NYXD_URL.parse().expect("Invalid default nyxd URL")] + }; + + Mixnet { + bind_address: SocketAddr::new(inaddr_any(), DEFAULT_MIXNET_PORT), + nym_api_urls, + nyxd_urls, + debug: Default::default(), + } + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] pub struct Wireguard { /// Specifies whether the wireguard service is enabled on this node. pub enabled: bool, @@ -75,6 +495,10 @@ pub struct Wireguard { /// default: `0.0.0.0:51822` pub bind_address: SocketAddr, + /// Ip address of the private wireguard network. + /// default: `10.1.0.0` + pub private_network_ip: IpAddr, + /// Port announced to external clients wishing to connect to the wireguard interface. /// Useful in the instances where the node is behind a proxy. pub announced_port: u16, @@ -87,17 +511,18 @@ pub struct Wireguard { pub storage_paths: persistence::WireguardPaths, } -impl Default for Wireguard { - fn default() -> Self { +impl Wireguard { + pub fn new_default>(data_dir: P) -> Self { Wireguard { enabled: false, bind_address: SocketAddr::new( IpAddr::V4(Ipv4Addr::UNSPECIFIED), DEFAULT_WIREGUARD_PORT, ), + private_network_ip: DEFAULT_WIREGUARD_NETWORK_IP, announced_port: DEFAULT_WIREGUARD_PORT, private_network_prefix: DEFAULT_WIREGUARD_PREFIX, - storage_paths: persistence::WireguardPaths {}, + storage_paths: persistence::WireguardPaths::new(data_dir), } } } diff --git a/nym-node/src/config/persistence.rs b/nym-node/src/config/persistence.rs index c1cf1824ab..530f815aef 100644 --- a/nym-node/src/config/persistence.rs +++ b/nym-node/src/config/persistence.rs @@ -1,10 +1,376 @@ -// Copyright 2023 - Nym Technologies SA +// Copyright 2023-2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use crate::error::EntryGatewayError; +use nym_client_core_config_types::disk_persistence::{ClientKeysPaths, CommonClientPaths}; +use nym_mixnode::config::persistence::paths::DEFAULT_DESCRIPTION_FILENAME; use serde::{Deserialize, Serialize}; +use std::fs::create_dir_all; +use std::path::{Path, PathBuf}; +use std::{fs, io}; +use tracing::info; +use zeroize::Zeroizing; + +// Global: +pub const DEFAULT_ED25519_PRIVATE_IDENTITY_KEY_FILENAME: &str = "ed25519_identity"; +pub const DEFAULT_ED25519_PUBLIC_IDENTITY_KEY_FILENAME: &str = "ed25519_identity.pub"; +pub const DEFAULT_X25519_PRIVATE_SPHINX_KEY_FILENAME: &str = "x25519_sphinx"; +pub const DEFAULT_X25519_PUBLIC_SPHINX_KEY_FILENAME: &str = "x25519_sphinx.pub"; +pub const DEFAULT_X25519_PRIVATE_NOISE_KEY_FILENAME: &str = "x25519_noise"; +pub const DEFAULT_X25519_PUBLIC_NOISE_KEY_FILENAME: &str = "x25519_noise.pub"; +pub const DEFAULT_NYMNODE_DESCRIPTION_FILENAME: &str = "description.toml"; + +// Mixnode: + +// Entry Gateway: +pub const DEFAULT_CLIENTS_STORAGE_FILENAME: &str = "clients.sqlite"; +pub const DEFAULT_MNEMONIC_FILENAME: &str = "cosmos_mnemonic"; + +// Exit Gateway: +pub const DEFAULT_ED25519_NR_PRIVATE_IDENTITY_KEY_FILENAME: &str = "ed25519_nr_identity"; +pub const DEFAULT_ED25519_NR_PUBLIC_IDENTITY_KEY_FILENAME: &str = "ed25519_nr_identity.pub"; +pub const DEFAULT_X25519_NR_PRIVATE_DH_KEY_FILENAME: &str = "x25519_nr_dh"; +pub const DEFAULT_X25519_NR_PUBLIC_DH_KEY_FILENAME: &str = "x25519_nr_dh.pub"; +pub const DEFAULT_NR_ACK_KEY_FILENAME: &str = "aes128ctr_nr_ack"; +pub const DEFAULT_NR_REPLY_SURB_DB_FILENAME: &str = "nr_persistent_reply_store.sqlite"; +pub const DEFAULT_NR_GATEWAYS_DB_FILENAME: &str = "nr_gateways_info_store.sqlite"; + +pub const DEFAULT_ED25519_IPR_PRIVATE_IDENTITY_KEY_FILENAME: &str = "ed25519_ipr_identity"; +pub const DEFAULT_ED25519_IPR_PUBLIC_IDENTITY_KEY_FILENAME: &str = "ed25519_ipr_identity.pub"; +pub const DEFAULT_X25519_IPR_PRIVATE_DH_KEY_FILENAME: &str = "x25519_ipr_dh"; +pub const DEFAULT_X25519_IPR_PUBLIC_DH_KEY_FILENAME: &str = "x25519_ipr_dh.pub"; +pub const DEFAULT_IPR_ACK_KEY_FILENAME: &str = "aes128ctr_ipr_ack"; +pub const DEFAULT_IPR_REPLY_SURB_DB_FILENAME: &str = "ipr_persistent_reply_store.sqlite"; +pub const DEFAULT_IPR_GATEWAYS_DB_FILENAME: &str = "ipr_gateways_info_store.sqlite"; + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct NymNodePaths { + pub keys: KeysPaths, + + /// Path to a file containing basic node description: human-readable name, website, details, etc. + pub description: PathBuf, +} + +impl NymNodePaths { + pub fn new>(data_dir: P) -> Self { + let data_dir = data_dir.as_ref(); + + NymNodePaths { + keys: KeysPaths::new(data_dir), + description: data_dir.join(DEFAULT_DESCRIPTION_FILENAME), + } + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct KeysPaths { + /// Path to file containing ed25519 identity private key. + pub private_ed25519_identity_key_file: PathBuf, + + /// Path to file containing ed25519 identity public key. + pub public_ed25519_identity_key_file: PathBuf, + + /// Path to file containing x25519 sphinx private key. + pub private_x25519_sphinx_key_file: PathBuf, + + /// Path to file containing x25519 sphinx public key. + pub public_x25519_sphinx_key_file: PathBuf, + + /// Path to file containing x25519 noise private key. + pub private_x25519_noise_key_file: PathBuf, + + /// Path to file containing x25519 noise public key. + pub public_x25519_noise_key_file: PathBuf, +} + +impl KeysPaths { + pub fn new>(data_dir: P) -> Self { + let data_dir = data_dir.as_ref(); + + KeysPaths { + private_ed25519_identity_key_file: data_dir + .join(DEFAULT_ED25519_PRIVATE_IDENTITY_KEY_FILENAME), + public_ed25519_identity_key_file: data_dir + .join(DEFAULT_ED25519_PUBLIC_IDENTITY_KEY_FILENAME), + private_x25519_sphinx_key_file: data_dir + .join(DEFAULT_X25519_PRIVATE_SPHINX_KEY_FILENAME), + public_x25519_sphinx_key_file: data_dir.join(DEFAULT_X25519_PUBLIC_SPHINX_KEY_FILENAME), + private_x25519_noise_key_file: data_dir.join(DEFAULT_X25519_PRIVATE_NOISE_KEY_FILENAME), + public_x25519_noise_key_file: data_dir.join(DEFAULT_X25519_PUBLIC_NOISE_KEY_FILENAME), + } + } + + pub fn ed25519_identity_storage_paths(&self) -> nym_pemstore::KeyPairPath { + nym_pemstore::KeyPairPath::new( + &self.private_ed25519_identity_key_file, + &self.public_ed25519_identity_key_file, + ) + } + + pub fn x25519_sphinx_storage_paths(&self) -> nym_pemstore::KeyPairPath { + nym_pemstore::KeyPairPath::new( + &self.private_x25519_sphinx_key_file, + &self.public_x25519_sphinx_key_file, + ) + } + + pub fn x25519_noise_storage_paths(&self) -> nym_pemstore::KeyPairPath { + nym_pemstore::KeyPairPath::new( + &self.private_x25519_noise_key_file, + &self.public_x25519_noise_key_file, + ) + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct MixnodePaths {} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct EntryGatewayPaths { + /// Path to sqlite database containing all persistent data: messages for offline clients, + /// derived shared keys and available client bandwidths. + pub clients_storage: PathBuf, + + /// Path to file containing cosmos account mnemonic used for zk-nym redemption. + pub cosmos_mnemonic: PathBuf, +} + +impl EntryGatewayPaths { + pub fn new>(data_dir: P) -> Self { + EntryGatewayPaths { + clients_storage: data_dir.as_ref().join(DEFAULT_CLIENTS_STORAGE_FILENAME), + cosmos_mnemonic: data_dir.as_ref().join(DEFAULT_MNEMONIC_FILENAME), + } + } + + pub fn load_mnemonic_from_file(&self) -> Result, EntryGatewayError> { + let stringified = + Zeroizing::new(fs::read_to_string(&self.cosmos_mnemonic).map_err(|source| { + EntryGatewayError::MnemonicLoadFailure { + path: self.cosmos_mnemonic.clone(), + source, + } + })?); + + Ok(Zeroizing::new(bip39::Mnemonic::parse::<&str>( + stringified.as_ref(), + )?)) + } + + pub fn save_mnemonic_to_file( + &self, + mnemonic: &bip39::Mnemonic, + ) -> Result<(), EntryGatewayError> { + // wrapper for io errors + fn _save_to_file(path: &Path, mnemonic: &bip39::Mnemonic) -> io::Result<()> { + if let Some(parent) = path.parent() { + create_dir_all(parent)?; + } + info!("saving entry gateway mnemonic to '{}'", path.display()); + + let stringified = Zeroizing::new(mnemonic.to_string()); + fs::write(path, &stringified) + } + + _save_to_file(&self.cosmos_mnemonic, mnemonic).map_err(|source| { + EntryGatewayError::MnemonicSaveFailure { + path: self.cosmos_mnemonic.clone(), + source, + } + }) + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ExitGatewayPaths { + pub network_requester: NetworkRequesterPaths, + + pub ip_packet_router: IpPacketRouterPaths, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct NetworkRequesterPaths { + /// Path to file containing network requester ed25519 identity private key. + pub private_ed25519_identity_key_file: PathBuf, + + /// Path to file containing network requester ed25519 identity public key. + pub public_ed25519_identity_key_file: PathBuf, + + /// Path to file containing network requester x25519 diffie hellman private key. + pub private_x25519_diffie_hellman_key_file: PathBuf, + + /// Path to file containing network requester x25519 diffie hellman public key. + pub public_x25519_diffie_hellman_key_file: PathBuf, + + /// Path to file containing key used for encrypting and decrypting the content of an + /// acknowledgement so that nobody besides the client knows which packet it refers to. + pub ack_key_file: PathBuf, + + /// Path to the persistent store for received reply surbs, unused encryption keys and used sender tags. + pub reply_surb_database: PathBuf, + + /// Normally this is a path to the file containing information about gateways used by this client, + /// i.e. details such as their public keys, owner addresses or the network information. + /// but in this case it just has the basic information of "we're using custom gateway". + /// Due to how clients are started up, this file has to exist. + pub gateway_registrations: PathBuf, + // it's possible we might have to add credential storage here for return tickets +} + +impl NetworkRequesterPaths { + pub fn new>(data_dir: P) -> Self { + let data_dir = data_dir.as_ref(); + NetworkRequesterPaths { + private_ed25519_identity_key_file: data_dir + .join(DEFAULT_ED25519_NR_PRIVATE_IDENTITY_KEY_FILENAME), + public_ed25519_identity_key_file: data_dir + .join(DEFAULT_ED25519_NR_PUBLIC_IDENTITY_KEY_FILENAME), + private_x25519_diffie_hellman_key_file: data_dir + .join(DEFAULT_X25519_NR_PRIVATE_DH_KEY_FILENAME), + public_x25519_diffie_hellman_key_file: data_dir + .join(DEFAULT_X25519_NR_PUBLIC_DH_KEY_FILENAME), + ack_key_file: data_dir.join(DEFAULT_NR_ACK_KEY_FILENAME), + reply_surb_database: data_dir.join(DEFAULT_NR_REPLY_SURB_DB_FILENAME), + gateway_registrations: data_dir.join(DEFAULT_NR_GATEWAYS_DB_FILENAME), + } + } + + pub fn to_common_client_paths(&self) -> CommonClientPaths { + CommonClientPaths { + keys: ClientKeysPaths { + private_identity_key_file: self.private_ed25519_identity_key_file.clone(), + public_identity_key_file: self.public_ed25519_identity_key_file.clone(), + private_encryption_key_file: self.private_x25519_diffie_hellman_key_file.clone(), + public_encryption_key_file: self.public_x25519_diffie_hellman_key_file.clone(), + ack_key_file: self.ack_key_file.clone(), + }, + gateway_registrations: self.gateway_registrations.clone(), + + // not needed for embedded providers + credentials_database: Default::default(), + reply_surb_database: self.reply_surb_database.clone(), + } + } + + pub fn ed25519_identity_storage_paths(&self) -> nym_pemstore::KeyPairPath { + nym_pemstore::KeyPairPath::new( + &self.private_ed25519_identity_key_file, + &self.public_ed25519_identity_key_file, + ) + } + + pub fn x25519_diffie_hellman_storage_paths(&self) -> nym_pemstore::KeyPairPath { + nym_pemstore::KeyPairPath::new( + &self.private_x25519_diffie_hellman_key_file, + &self.public_x25519_diffie_hellman_key_file, + ) + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct IpPacketRouterPaths { + /// Path to file containing ip packet router ed25519 identity private key. + pub private_ed25519_identity_key_file: PathBuf, + + /// Path to file containing ip packet router ed25519 identity public key. + pub public_ed25519_identity_key_file: PathBuf, + + /// Path to file containing ip packet router x25519 diffie hellman private key. + pub private_x25519_diffie_hellman_key_file: PathBuf, + + /// Path to file containing ip packet router x25519 diffie hellman public key. + pub public_x25519_diffie_hellman_key_file: PathBuf, + + /// Path to file containing key used for encrypting and decrypting the content of an + /// acknowledgement so that nobody besides the client knows which packet it refers to. + pub ack_key_file: PathBuf, + + /// Path to the persistent store for received reply surbs, unused encryption keys and used sender tags. + pub reply_surb_database: PathBuf, + + /// Normally this is a path to the file containing information about gateways used by this client, + /// i.e. details such as their public keys, owner addresses or the network information. + /// but in this case it just has the basic information of "we're using custom gateway". + /// Due to how clients are started up, this file has to exist. + pub gateway_registrations: PathBuf, + // it's possible we might have to add credential storage here for return tickets +} + +impl IpPacketRouterPaths { + pub fn new>(data_dir: P) -> Self { + let data_dir = data_dir.as_ref(); + IpPacketRouterPaths { + private_ed25519_identity_key_file: data_dir + .join(DEFAULT_ED25519_IPR_PRIVATE_IDENTITY_KEY_FILENAME), + public_ed25519_identity_key_file: data_dir + .join(DEFAULT_ED25519_IPR_PUBLIC_IDENTITY_KEY_FILENAME), + private_x25519_diffie_hellman_key_file: data_dir + .join(DEFAULT_X25519_IPR_PRIVATE_DH_KEY_FILENAME), + public_x25519_diffie_hellman_key_file: data_dir + .join(DEFAULT_X25519_IPR_PUBLIC_DH_KEY_FILENAME), + ack_key_file: data_dir.join(DEFAULT_IPR_ACK_KEY_FILENAME), + reply_surb_database: data_dir.join(DEFAULT_IPR_REPLY_SURB_DB_FILENAME), + gateway_registrations: data_dir.join(DEFAULT_IPR_GATEWAYS_DB_FILENAME), + } + } + + pub fn to_common_client_paths(&self) -> CommonClientPaths { + CommonClientPaths { + keys: ClientKeysPaths { + private_identity_key_file: self.private_ed25519_identity_key_file.clone(), + public_identity_key_file: self.public_ed25519_identity_key_file.clone(), + private_encryption_key_file: self.private_x25519_diffie_hellman_key_file.clone(), + public_encryption_key_file: self.public_x25519_diffie_hellman_key_file.clone(), + ack_key_file: self.ack_key_file.clone(), + }, + gateway_registrations: self.gateway_registrations.clone(), + + // not needed for embedded providers + credentials_database: Default::default(), + reply_surb_database: self.reply_surb_database.clone(), + } + } + + pub fn ed25519_identity_storage_paths(&self) -> nym_pemstore::KeyPairPath { + nym_pemstore::KeyPairPath::new( + &self.private_ed25519_identity_key_file, + &self.public_ed25519_identity_key_file, + ) + } + + pub fn x25519_diffie_hellman_storage_paths(&self) -> nym_pemstore::KeyPairPath { + nym_pemstore::KeyPairPath::new( + &self.private_x25519_diffie_hellman_key_file, + &self.public_x25519_diffie_hellman_key_file, + ) + } +} + +impl ExitGatewayPaths { + pub fn new>(data_dir: P) -> Self { + let data_dir = data_dir.as_ref(); + ExitGatewayPaths { + network_requester: NetworkRequesterPaths::new(data_dir), + ip_packet_router: IpPacketRouterPaths::new(data_dir), + } + } +} #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] #[serde(deny_unknown_fields)] pub struct WireguardPaths { // pub keys: } + +impl WireguardPaths { + pub fn new>(_data_dir: P) -> Self { + WireguardPaths {} + } +} diff --git a/nym-node/src/config/serde_helpers.rs b/nym-node/src/config/serde_helpers.rs deleted file mode 100644 index 75bfef8bb9..0000000000 --- a/nym-node/src/config/serde_helpers.rs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use serde::{Deserialize, Deserializer}; -use std::path::PathBuf; - -pub(super) fn de_maybe_path<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let path = PathBuf::deserialize(deserializer)?; - if path.as_os_str().is_empty() { - Ok(None) - } else { - Ok(Some(path)) - } -} - -pub(super) fn de_maybe_string<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let path = String::deserialize(deserializer)?; - if path.is_empty() { - Ok(None) - } else { - Ok(Some(path)) - } -} diff --git a/nym-node/src/config/template.rs b/nym-node/src/config/template.rs new file mode 100644 index 0000000000..2b053b759c --- /dev/null +++ b/nym-node/src/config/template.rs @@ -0,0 +1,252 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +// While using normal toml marshalling would have been way simpler with less overhead, +// I think it's useful to have comments attached to the saved config file to explain behaviour of +// particular fields. +// Note: any changes to the template must be reflected in the appropriate structs. +pub(crate) const CONFIG_TEMPLATE: &str = r#" +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +# do note that some of the configuration options are not explicitly exposed in this template, +# but can still be manually adjusted. in particular delays, rates, etc. +# look at nym-node/src/config/mod.rs file for more details. + +##### main base nym-node config options ##### + +# Human-readable ID of this particular node. +id = '{{ id }}' + +# Current mode of this nym-node. +# Expect this field to be changed in the future to allow running the node in multiple modes (i.e. mixnode + gateway) +mode = '{{ mode }}' + +[host] +# Ip address(es) of this host, such as 1.1.1.1 that external clients will use for connections. +# If no values are provided, when this node gets included in the network, +# its ip addresses will be populated by whatever value is resolved by associated nym-api. +public_ips = [ +{{#each host.public_ips }}'{{this}}',{{/each}} +] + +# (temporary) Optional hostname of this node, for example nymtech.net. +hostname = '{{ host.hostname }}' + +# Optional ISO 3166 alpha-2 two-letter country code of the node's **physical** location +location = '{{ host.location }}' + +[mixnet] +# Address this node will bind to for listening for mixnet packets +# default: `0.0.0.0:1789` +bind_address = '{{ mixnet.bind_address }}' + +# Addresses to nym APIs from which the node gets the view of the network. +nym_api_urls = [ +{{#each mixnet.nym_api_urls }}'{{this}}',{{/each}} +] + +# Addresses to nyxd which the node uses to interact with the nyx chain. +nyxd_urls = [ + {{#each mixnet.nyxd_urls }}'{{this}}',{{/each}} +] + +# Storage paths to persistent nym-node data, such as its long term keys. +[storage_paths] + +# Path to a file containing basic node description: human-readable name, website, details, etc. +description = '{{ storage_paths.description }}' + +[storage_paths.keys] +# Path to file containing ed25519 identity private key. +private_ed25519_identity_key_file = '{{ storage_paths.keys.private_ed25519_identity_key_file }}' + +# Path to file containing ed25519 identity public key. +public_ed25519_identity_key_file = '{{ storage_paths.keys.public_ed25519_identity_key_file }}' + +# Path to file containing x25519 sphinx private key. +private_x25519_sphinx_key_file = '{{ storage_paths.keys.private_x25519_sphinx_key_file }}' + +# Path to file containing x25519 sphinx public key. +public_x25519_sphinx_key_file = '{{ storage_paths.keys.public_x25519_sphinx_key_file }}' + +# Path to file containing x25519 noise private key. +private_x25519_noise_key_file = '{{ storage_paths.keys.private_x25519_noise_key_file }}' + +# Path to file containing x25519 noise public key. +public_x25519_noise_key_file = '{{ storage_paths.keys.public_x25519_noise_key_file }}' + + +##### http-API nym-node config options ##### + +[http] +# Socket address this node will use for binding its http API. +# default: `0.0.0.0:8080` +bind_address = '{{ http.bind_address }}' + +# Path to assets directory of custom landing page of this node +landing_page_assets_path = '{{ http.landing_page_assets_path }}' + +# An optional bearer token for accessing certain http endpoints. +# Currently only used for obtaining mixnode's stats. +access_token = '{{ http.access_token }}' + +# Specify whether basic system information should be exposed. +# default: true +expose_system_info = {{ http.expose_system_info }} + +# Specify whether basic system hardware information should be exposed. +# This option is superseded by `expose_system_info` +# default: true +expose_system_hardware = {{ http.expose_system_hardware }} + +# Specify whether detailed system crypto hardware information should be exposed. +# This option is superseded by `expose_system_hardware` +# default: true +expose_crypto_hardware = {{ http.expose_crypto_hardware }} + +##### wireguard-API nym-node config options ##### + +[wireguard] +# Specifies whether the wireguard service is enabled on this node. +enabled = {{ wireguard.enabled }} + +# Socket address this node will use for binding its wireguard interface. +# default: `0.0.0.0:51822` +bind_address = '{{ wireguard.bind_address }}' + +# Ip address of the private wireguard network. +# default: `10.1.0.0` +private_network_ip = '{{ wireguard.private_network_ip }}' + +# Port announced to external clients wishing to connect to the wireguard interface. +# Useful in the instances where the node is behind a proxy. +announced_port = {{ wireguard.announced_port }} + +# The prefix denoting the maximum number of the clients that can be connected via Wireguard. +# The maximum value for IPv4 is 32 and for IPv6 is 128 +private_network_prefix = {{ wireguard.private_network_prefix }} + +# Paths for wireguard keys, client registries, etc. +[wireguard.storage_paths] +# currently empty + + +##### mixnode mode nym-node config options ##### + +[mixnode] + +[mixnode.verloc] +# Socket address this node will use for binding its verloc API. +# default: `0.0.0.0:1790` +bind_address = '{{ mixnode.verloc.bind_address }}' + +[mixnode.storage_paths] +# currently empty + +##### entry-gateway mode nym-node config options ##### + +[entry_gateway] +# Indicates whether this gateway is accepting only coconut credentials for accessing the mixnet +# or if it also accepts non-paying clients +enforce_zk_nyms = {{ entry_gateway.enforce_zk_nyms }} + +# Socket address this node will use for binding its client websocket API. +# default: `0.0.0.0:9000` +bind_address = '{{ entry_gateway.bind_address }}' + +# Custom announced port for listening for websocket client traffic. +# If unspecified, the value from the `bind_address` will be used instead +# (default: 0 - unspecified) +announce_ws_port = {{#if entry_gateway.announce_ws_port }} {{ entry_gateway.announce_ws_port }} {{else}} 0 {{/if}} + +# If applicable, announced port for listening for secure websocket client traffic. +# (default: 0 - disabled) +announce_wss_port = {{#if entry_gateway.announce_wss_port }} {{ entry_gateway.announce_wss_port }} {{else}} 0 {{/if}} + +[entry_gateway.storage_paths] +# Path to sqlite database containing all persistent data: messages for offline clients, +# derived shared keys and available client bandwidths. +clients_storage = '{{ entry_gateway.storage_paths.clients_storage }}' + +# Path to file containing cosmos account mnemonic used for zk-nym redemption. +cosmos_mnemonic = '{{ entry_gateway.storage_paths.cosmos_mnemonic }}' + +##### exit-gateway mode nym-node config options ##### + +[exit_gateway] + +# specifies whether this exit node should run in 'open-proxy' mode +# and thus would attempt to resolve **ANY** request it receives. +open_proxy = {{ exit_gateway.open_proxy }} + +# Specifies the custom url for an upstream source of the exit policy used by this node. +upstream_exit_policy_url = '{{ exit_gateway.upstream_exit_policy_url }}' + +[exit_gateway.network_requester] +# currently empty (there are some debug options one might want to configure) + +[exit_gateway.ip_packet_router] +# currently empty (there are some debug options one might want to configure) + +[exit_gateway.storage_paths] + +[exit_gateway.storage_paths.network_requester] +# Path to file containing network requester ed25519 identity private key. +private_ed25519_identity_key_file = '{{ exit_gateway.storage_paths.network_requester.private_ed25519_identity_key_file }}' + +# Path to file containing network requester ed25519 identity public key. +public_ed25519_identity_key_file = '{{ exit_gateway.storage_paths.network_requester.public_ed25519_identity_key_file }}' + +# Path to file containing network requester x25519 diffie hellman private key. +private_x25519_diffie_hellman_key_file = '{{ exit_gateway.storage_paths.network_requester.private_x25519_diffie_hellman_key_file }}' + +# Path to file containing network requester x25519 diffie hellman public key. +public_x25519_diffie_hellman_key_file = '{{ exit_gateway.storage_paths.network_requester.public_x25519_diffie_hellman_key_file }}' + +# Path to file containing key used for encrypting and decrypting the content of an +# acknowledgement so that nobody besides the client knows which packet it refers to. +ack_key_file = '{{ exit_gateway.storage_paths.network_requester.ack_key_file }}' + +# Path to the persistent store for received reply surbs, unused encryption keys and used sender tags. +reply_surb_database = '{{ exit_gateway.storage_paths.network_requester.reply_surb_database }}' + +# Normally this is a path to the file containing information about gateways used by this client, +# i.e. details such as their public keys, owner addresses or the network information. +# but in this case it just has the basic information of "we're using custom gateway". +# Due to how clients are started up, this file has to exist. +gateway_registrations = '{{ exit_gateway.storage_paths.network_requester.gateway_registrations }}' + +[exit_gateway.storage_paths.ip_packet_router] +# Path to file containing ip packet router ed25519 identity private key. +private_ed25519_identity_key_file = '{{ exit_gateway.storage_paths.ip_packet_router.private_ed25519_identity_key_file }}' + +# Path to file containing ip packet router ed25519 identity public key. +public_ed25519_identity_key_file = '{{ exit_gateway.storage_paths.ip_packet_router.public_ed25519_identity_key_file }}' + +# Path to file containing ip packet router x25519 diffie hellman private key. +private_x25519_diffie_hellman_key_file = '{{ exit_gateway.storage_paths.ip_packet_router.private_x25519_diffie_hellman_key_file }}' + +# Path to file containing ip packet router x25519 diffie hellman public key. +public_x25519_diffie_hellman_key_file = '{{ exit_gateway.storage_paths.ip_packet_router.public_x25519_diffie_hellman_key_file }}' + +# Path to file containing key used for encrypting and decrypting the content of an +# acknowledgement so that nobody besides the client knows which packet it refers to. +ack_key_file = '{{ exit_gateway.storage_paths.ip_packet_router.ack_key_file }}' + +# Path to the persistent store for received reply surbs, unused encryption keys and used sender tags. +reply_surb_database = '{{ exit_gateway.storage_paths.ip_packet_router.reply_surb_database }}' + +# Normally this is a path to the file containing information about gateways used by this client, +# i.e. details such as their public keys, owner addresses or the network information. +# but in this case it just has the basic information of "we're using custom gateway". +# Due to how clients are started up, this file has to exist. +gateway_registrations = '{{ exit_gateway.storage_paths.ip_packet_router.gateway_registrations }}' + +##### logging configuration options ##### + +[logging] + +# TODO + +"#; diff --git a/nym-node/src/config/upgrade_helpers.rs b/nym-node/src/config/upgrade_helpers.rs new file mode 100644 index 0000000000..ea92ad2414 --- /dev/null +++ b/nym-node/src/config/upgrade_helpers.rs @@ -0,0 +1,22 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::config::Config; +use crate::error::NymNodeError; +use std::path::Path; + +// currently there are no upgrades +async fn try_upgrade_config>(_path: P) -> Result<(), NymNodeError> { + Ok(()) +} + +pub async fn try_load_current_config>( + config_path: P, +) -> Result { + if let Ok(cfg) = Config::read_from_toml_file(config_path.as_ref()) { + return Ok(cfg); + } + + try_upgrade_config(config_path.as_ref()).await?; + Config::read_from_toml_file(config_path) +} diff --git a/nym-node/src/env.rs b/nym-node/src/env.rs new file mode 100644 index 0000000000..a8e785499e --- /dev/null +++ b/nym-node/src/env.rs @@ -0,0 +1,58 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +pub mod vars { + pub const NYMNODE_NO_BANNER_ARG: &str = "NYMNODE_NO_BANNER"; + pub const NYMNODE_CONFIG_ENV_FILE_ARG: &str = "NYMNODE_CONFIG_ENV_FILE_ARG"; + pub const NYMNODE_ID_ARG: &str = "NYMNODE_ID"; + pub const NYMNODE_OUTPUT_ARG: &str = "NYMNODE_OUTPUT"; + pub const NYMNODE_CONFIG_PATH_ARG: &str = "NYMNODE_CONFIG"; + + pub const NYMNODE_DENY_INIT_ARG: &str = "NYMNODE_DENY_INIT"; + + pub const NYMNODE_INIT_ONLY_ARG: &str = "NYMNODE_INIT_ONLY"; + + pub const NYMMONDE_WRITE_CONFIG_CHANGES_ARG: &str = "NYMNODE_WRITE_CONFIG_CHANGES"; + + pub const NYMNODE_BONDING_INFORMATION_OUTPUT_ARG: &str = "NYMNODE_BONDING_INFORMATION_OUTPUT"; + + pub const NYMNODE_MODE_ARG: &str = "NYMNODE_MODE"; + + // host: + pub const NYMNODE_PUBLIC_IPS_ARG: &str = "NYMNODE_PUBLIC_IPS"; + pub const NYMNODE_HOSTNAME_ARG: &str = "NYMNODE_HOSTNAME"; + pub const NYMNODE_LOCATION_ARG: &str = "NYMNODE_LOCATION"; + + // http: + pub const NYMNODE_HTTP_BIND_ADDRESS_ARG: &str = "NYMNODE_HTTP_BIND_ADDRESS"; + pub const NYMNODE_HTTP_LANDING_ASSETS_ARG: &str = "NYMNODE_HTTP_LANDING_ASSETS"; + pub const NYMNODE_HTTP_ACCESS_TOKEN_ARG: &str = "NYMNODE_HTTP_ACCESS_TOKEN"; + pub const NYMNODE_HTTP_EXPOSE_SYSTEM_INFO_ARG: &str = "NYMNODE_HTTP_EXPOSE_SYSTEM_INFO"; + pub const NYMNODE_HTTP_EXPOSE_SYSTEM_HARDWARE_ARG: &str = "NYMNODE_HTTP_EXPOSE_SYSTEM_HARDWARE"; + pub const NYMNODE_HTTP_EXPOSE_CRYPTO_HARDWARE_ARG: &str = "NYMNODE_HTTP_EXPOSE_CRYPTO_HARDWARE"; + + // mixnet: + pub const NYMNODE_MIXNET_BIND_ADDRESS_ARG: &str = "NYMNODE_MIXNET_BIND_ADDRESS"; + pub const NYMNODE_NYM_APIS_ARG: &str = "NYMNODE_NYM_APIS"; + + // wireguard: + pub const NYMNODE_WG_ENABLED_ARG: &str = "NYMNODE_WG_ENABLED"; + pub const NYMNODE_WG_BIND_ADDRESS_ARG: &str = "NYMNODE_WG_BIND_ADDRESS"; + pub const NYMNODE_WG_IP_NETWORK_ARG: &str = "NYMNODE_WG_IP_NETWORK"; + pub const NYMNODE_WG_ANNOUNCED_PORT_ARG: &str = "NYMNODE_WG_ANNOUNCED_PORT"; + pub const NYMNODE_WG_PRIVATE_NETWORK_PREFIX_ARG: &str = "NYMNODE_WG_PRIVATE_NETWORK_PREFIX"; + + // mixnode: + pub const NYMNODE_VERLOC_BIND_ADDRESS_ARG: &str = "NYMNODE_VERLOC_BIND_ADDRESS"; + + // entry gateway: + pub const NYMNODE_ENTRY_BIND_ADDRESS_ARG: &str = "NYMNODE_ENTRY_BIND_ADDRESS"; + pub const NYMNODE_ENTRY_ANNOUNCE_WS_PORT_ARG: &str = "NYMNODE_ENTRY_ANNOUNCE_WS_PORT"; + pub const NYMNODE_ENTRY_ANNOUNCE_WSS_PORT_ARG: &str = "NYMNODE_ENTRY_ANNOUNCE_WSS_PORT"; + pub const NYMNODE_ENFORCE_ZK_NYMS_ARG: &str = "NYMNODE_ENFORCE_ZK_NYMS"; + pub const NYMNODE_MNEMONIC_ARG: &str = "NYMNODE_MNEMONIC"; + + // exit gateway: + pub const NYMNODE_UPSTREAM_EXIT_POLICY_ARG: &str = "NYMNODE_UPSTREAM_EXIT_POLICY"; + pub const NYMNODE_OPEN_PROXY_ARG: &str = "NYMNODE_OPEN_PROXY"; +} diff --git a/nym-node/src/error.rs b/nym-node/src/error.rs index e5a14cf018..c7b42294c1 100644 --- a/nym-node/src/error.rs +++ b/nym-node/src/error.rs @@ -1,16 +1,104 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use crate::config::helpers::UnsupportedGatewayAddresses; use crate::wireguard::error::WireguardError; -use std::net::{IpAddr, SocketAddr}; +use nym_ip_packet_router::error::ClientCoreError; +use nym_node_http_api::NymNodeHttpError; +use std::io; +use std::net::IpAddr; +use std::path::PathBuf; use thiserror::Error; +#[derive(Debug, Error)] +pub enum KeyIOFailure { + #[error("failed to load {keys} keys from {:?} (private key) and {:?} (public key): {err}", .paths.private_key_path, .paths.public_key_path)] + KeyPairLoadFailure { + keys: String, + paths: nym_pemstore::KeyPairPath, + #[source] + err: io::Error, + }, + + #[error("failed to load {key} key from '{}': {err}", path.display())] + KeyLoadFailure { + key: String, + path: PathBuf, + #[source] + err: io::Error, + }, + + #[error("failed to store {keys} keys to {:?} (private key) and {:?} (public key): {err}", .paths.private_key_path, .paths.public_key_path)] + KeyPairStoreFailure { + keys: String, + paths: nym_pemstore::KeyPairPath, + #[source] + err: io::Error, + }, + + #[error("failed to store {key} key to '{}': {err}", path.display())] + KeyStoreFailure { + key: String, + path: PathBuf, + #[source] + err: io::Error, + }, +} + #[derive(Debug, Error)] pub enum NymNodeError { - #[error("failed to bind the HTTP API to {bind_address}: {source}")] - HttpBindFailure { - bind_address: SocketAddr, - source: hyper::Error, + #[error("could not find an existing config file at '{}' and fresh node initialisation has been disabled", config_path.display())] + ForbiddenInitialisation { config_path: PathBuf }, + + #[error("could not derive path to data directory of this nym node")] + DataDirDerivationFailure, + + #[error("could not derive path to config directory of this nym node")] + ConfigDirDerivationFailure, + + #[error(transparent)] + HttpFailure(#[from] NymNodeHttpError), + + #[error("failed to load config file using path '{}'. detailed message: {source}", path.display())] + ConfigLoadFailure { + path: PathBuf, + #[source] + source: io::Error, + }, + + #[error("failed to save config file for id {id} using path '{}'. detailed message: {source}", path.display())] + ConfigSaveFailure { + id: String, + path: PathBuf, + #[source] + source: io::Error, + }, + + #[error("the node description file is malformed: {source}")] + MalformedDescriptionFile { + #[source] + source: toml::de::Error, + }, + + #[error("failed to load description file using path '{}'. detailed message: {source}", path.display())] + DescriptionLoadFailure { + path: PathBuf, + #[source] + source: io::Error, + }, + + #[error("failed to save description file using path '{}'. detailed message: {source}", path.display())] + DescriptionSaveFailure { + path: PathBuf, + #[source] + source: io::Error, + }, + + #[error("failed to write bonding information to '{}': {source}", path.display())] + BondingInfoWriteFailure { + path: PathBuf, + #[source] + source: io::Error, }, #[error("this node hasn't set any valid public addresses to announce. Please modify [host.public_ips] section of your config")] @@ -19,24 +107,110 @@ pub enum NymNodeError { #[error("this node attempted to announce an invalid public address: {address}. Please modify [host.public_ips] section of your config. Alternatively, if you wanted to use it in the local setting, run the node with the '--local' flag.")] InvalidPublicIp { address: IpAddr }, - #[error("failed to use nym-node requests: {source}")] - RequestError { - #[from] - source: nym_node_requests::error::Error, - }, - #[error(transparent)] WireguardError { #[from] source: WireguardError, }, + #[deprecated] #[error(transparent)] KeyRecoveryError { #[from] source: nym_crypto::asymmetric::encryption::KeyRecoveryError, }, - #[error("unimplemented")] - Unimplemented, + #[error(transparent)] + KeyFailure(#[from] KeyIOFailure), + + #[error("could not initialise nym-node as '--{name}' has not been specified which is required for a first time setup. (config section: {section})")] + MissingInitArg { section: String, name: String }, + + #[error("failed to migrate {node_type}: {message}")] + MigrationFailure { node_type: String, message: String }, + + #[error("there was an issue with wireguard IP network: {source}")] + IpNetworkError { + #[from] + source: ipnetwork::IpNetworkError, + }, + + #[error(transparent)] + MixnodeFailure(#[from] MixnodeError), + + #[error(transparent)] + EntryGatewayFailure(#[from] EntryGatewayError), + + #[error(transparent)] + ExitGatewayFailure(#[from] ExitGatewayError), +} + +impl From for NymNodeError { + fn from(value: nym_mixnode::error::MixnodeError) -> Self { + MixnodeError::from(value).into() + } +} + +#[derive(Debug, Error)] +pub enum MixnodeError { + #[error("failed to load mixnode description from {}: {source}", path.display())] + DescriptionLoadFailure { + path: PathBuf, + #[source] + source: io::Error, + }, + + #[error("currently it's not supported to have different ip addresses for verloc and mixnet ({verloc_bind_ip} and {mix_bind_ip} were used)")] + UnsupportedAddresses { + verloc_bind_ip: IpAddr, + mix_bind_ip: IpAddr, + }, + + #[error("mixnode failure: {0}")] + External(#[from] nym_mixnode::error::MixnodeError), +} + +#[derive(Debug, Error)] +pub enum EntryGatewayError { + #[error("failed to load entry gateway account mnemonic from {}: {source}", path.display())] + MnemonicLoadFailure { + path: PathBuf, + #[source] + source: io::Error, + }, + + #[error("failed to save entry gateway account mnemonic from {}: {source}", path.display())] + MnemonicSaveFailure { + path: PathBuf, + #[source] + source: io::Error, + }, + + #[error("the stored mnemonic is malformed: {source}")] + MalformedBip39Mnemonic { + #[from] + source: bip39::Error, + }, + + #[error(transparent)] + UnsupportedAddresses(#[from] UnsupportedGatewayAddresses), + + #[error("entry gateway failure: {0}")] + External(#[from] nym_gateway::GatewayError), +} + +#[derive(Debug, Error)] +pub enum ExitGatewayError { + #[error(transparent)] + KeyFailure(#[from] KeyIOFailure), + + #[error(transparent)] + UnsupportedAddresses(#[from] UnsupportedGatewayAddresses), + + // TODO: more granular errors + #[error(transparent)] + ExternalClientCore(#[from] ClientCoreError), + + #[error("exit gateway failure: {0}")] + External(#[from] nym_gateway::GatewayError), } diff --git a/nym-node/src/http/state.rs b/nym-node/src/http/state.rs deleted file mode 100644 index e6141fd238..0000000000 --- a/nym-node/src/http/state.rs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use tokio::time::Instant; - -#[derive(Debug, Clone)] -pub(crate) struct AppState { - pub(crate) startup_time: Instant, - // wireguard: WireguardAppState, -} - -// #[derive(Debug, Clone)] -// pub struct WireguardAppState { -// // inner: Option, -// } -// -// #[derive(Debug)] -// pub(crate) struct WireguardAppStateInner { -// // -// } -// -// impl FromRef for WireguardAppState { -// fn from_ref(app_state: &AppState) -> Self { -// app_state.wireguard.clone() -// } -// } - -impl AppState { - pub fn new() -> Self { - AppState { - // is it 100% accurate? - // no. - // does it have to be? - // also no. - startup_time: Instant::now(), - } - } -} diff --git a/nym-node/src/lib.rs b/nym-node/src/lib.rs index 70e48aeec1..320d22559d 100644 --- a/nym-node/src/lib.rs +++ b/nym-node/src/lib.rs @@ -1,12 +1,11 @@ -// Copyright 2023 - Nym Technologies SA +// Copyright 2023-2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only #![warn(clippy::expect_used)] #![warn(clippy::unwrap_used)] -// this crate will eventually get converted into proper binary - pub mod config; pub mod error; -pub mod http; pub mod wireguard; + +pub use nym_node_http_api as http; diff --git a/nym-node/src/main.rs b/nym-node/src/main.rs new file mode 100644 index 0000000000..adef6eeed0 --- /dev/null +++ b/nym-node/src/main.rs @@ -0,0 +1,34 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +#![warn(clippy::expect_used)] +#![warn(clippy::unwrap_used)] + +use crate::cli::Cli; +use clap::{crate_name, crate_version, Parser}; +use nym_bin_common::logging::{maybe_print_banner, setup_tracing_logger}; +use nym_config::defaults::setup_env; + +mod cli; +mod env; +pub(crate) mod node; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + // std::env::set_var( + // "RUST_LOG", + // "trace,handlebars=warn,tendermint_rpc=warn,h2=warn,hyper=warn,rustls=warn,reqwest=warn,tungstenite=warn,async_tungstenite=warn,tokio_util=warn,tokio_tungstenite=warn,tokio-util=warn", + // ); + + let cli = Cli::parse(); + setup_env(cli.config_env_file.as_ref()); + setup_tracing_logger(); + + if !cli.no_banner { + maybe_print_banner(crate_name!(), crate_version!()); + } + + cli.execute().await?; + + Ok(()) +} diff --git a/nym-node/src/node/bonding_information.rs b/nym-node/src/node/bonding_information.rs new file mode 100644 index 0000000000..1f405b4a06 --- /dev/null +++ b/nym-node/src/node/bonding_information.rs @@ -0,0 +1,197 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::node::helpers::{load_ed25519_identity_public_key, load_x25519_sphinx_public_key}; +use nym_node::config::{Config, NodeMode}; +use nym_node::error::NymNodeError; +use semver::{BuildMetadata, Version}; +use serde::{Deserialize, Serialize}; +use std::fmt::{Display, Formatter}; + +#[derive(Serialize, Deserialize, Debug)] +#[serde(tag = "node_type")] +pub enum BondingInformationV1 { + Mixnode(MixnodeBondingInformation), + Gateway(GatewayBondingInformation), +} + +impl Display for BondingInformationV1 { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + BondingInformationV1::Mixnode(m) => m.fmt(f), + BondingInformationV1::Gateway(g) => g.fmt(f), + } + } +} + +// TODO: work in progress, I'm not 100% sure yet what will be needed +// #[derive(Serialize, Deserialize, Debug)] +// pub struct BondingInformationV2 { +// pub(crate) ed25519_identity_key: ed25519::PublicKey, +// pub(crate) x25519_sphinx_key: x25519::PublicKey, +// } +// +// impl Display for BondingInformationV2 { +// fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { +// writeln!( +// f, +// "ed25519 identity key: {}", +// self.ed25519_identity_key.to_base58_string() +// )?; +// write!( +// f, +// "x25519 sphinx key: {}", +// self.x25519_sphinx_key.to_base58_string() +// ) +// } +// } + +#[derive(Serialize, Deserialize, Debug)] +pub struct MixnodeBondingInformation { + pub(crate) version: String, + pub(crate) host: String, + pub(crate) identity_key: String, + pub(crate) sphinx_key: String, +} + +impl MixnodeBondingInformation { + pub fn from_data( + ed25519_identity_key: String, + x25519_sphinx_key: String, + ) -> MixnodeBondingInformation { + MixnodeBondingInformation { + version: Self::get_version(), + host: "YOU NEED TO FILL THIS FIELD MANUALLY".to_string(), + identity_key: ed25519_identity_key, + sphinx_key: x25519_sphinx_key, + } + } + + #[allow(clippy::unwrap_used)] + fn get_version() -> String { + // SAFETY: + // 1. the value has been put into the environment during build.rs, so it must exist, + // 2. and the obtained version has already been parsed into semver in build.rs, so it must be a valid semver + let raw = include_str!(concat!(env!("OUT_DIR"), "/mixnode_version")); + let mut semver: Version = raw.parse().unwrap(); + + // if it's not empty, then we messed up our own versioning + assert!(semver.build.is_empty()); + semver.build = BuildMetadata::new("nymnode").unwrap(); + semver.to_string() + } +} + +impl Display for MixnodeBondingInformation { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + writeln!(f, "Node type: Mixnode")?; + writeln!(f, "Identity Key: {}", self.identity_key)?; + writeln!(f, "Sphinx Key: {}", self.sphinx_key)?; + writeln!(f, "Host: {}", self.host)?; + writeln!(f, "Version: {}", self.version)?; + Ok(()) + } +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct GatewayBondingInformation { + pub(crate) version: String, + pub(crate) host: String, + pub(crate) location: String, + pub(crate) identity_key: String, + pub(crate) sphinx_key: String, +} + +impl GatewayBondingInformation { + pub fn from_data( + ed25519_identity_key: String, + x25519_sphinx_key: String, + ) -> GatewayBondingInformation { + GatewayBondingInformation { + version: Self::get_version(), + host: "YOU NEED TO FILL THIS FIELD MANUALLY".to_string(), + location: "YOU NEED TO FILL THIS FIELD MANUALLY".to_string(), + identity_key: ed25519_identity_key, + sphinx_key: x25519_sphinx_key, + } + } + + #[allow(clippy::unwrap_used)] + fn get_version() -> String { + // SAFETY: + // 1. the value has been put into the file during build.rs, so it must exist, + // 2. and the obtained version has already been parsed into semver in build.rs, so it must be a valid semver + let raw = include_str!(concat!(env!("OUT_DIR"), "/gateway_version")); + let mut semver: Version = raw.parse().unwrap(); + + // if it's not empty, then we messed up our own versioning + assert!(semver.build.is_empty()); + semver.build = BuildMetadata::new("nymnode").unwrap(); + semver.to_string() + } +} + +impl Display for GatewayBondingInformation { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + writeln!(f, "Node type: Gateway")?; + writeln!(f, "Identity Key: {}", self.identity_key)?; + writeln!(f, "Sphinx Key: {}", self.sphinx_key)?; + writeln!(f, "Location: {}", self.location)?; + writeln!(f, "Host: {}", self.host)?; + writeln!(f, "Version: {}", self.version)?; + Ok(()) + } +} + +impl BondingInformationV1 { + pub fn from_data( + mode: NodeMode, + ed25519_identity_key: String, + x25519_sphinx_key: String, + ) -> BondingInformationV1 { + match mode { + NodeMode::Mixnode => BondingInformationV1::Mixnode( + MixnodeBondingInformation::from_data(ed25519_identity_key, x25519_sphinx_key), + ), + NodeMode::EntryGateway | NodeMode::ExitGateway => BondingInformationV1::Gateway( + GatewayBondingInformation::from_data(ed25519_identity_key, x25519_sphinx_key), + ), + } + } + + fn ed25519_identity_key(&self) -> String { + match self { + BondingInformationV1::Mixnode(m) => m.identity_key.clone(), + BondingInformationV1::Gateway(g) => g.identity_key.clone(), + } + } + + fn x25519_sphinx_key(&self) -> String { + match self { + BondingInformationV1::Mixnode(m) => m.sphinx_key.clone(), + BondingInformationV1::Gateway(g) => g.sphinx_key.clone(), + } + } + + pub fn try_load(config: &Config) -> Result { + let ed25519_identity_key = load_ed25519_identity_public_key( + &config.storage_paths.keys.public_ed25519_identity_key_file, + )?; + let x25519_sphinx_key = load_x25519_sphinx_public_key( + &config.storage_paths.keys.public_x25519_sphinx_key_file, + )?; + let mode = config.mode; + + Ok(Self::from_data( + mode, + ed25519_identity_key.to_base58_string(), + x25519_sphinx_key.to_base58_string(), + )) + } + + pub fn with_mode(self, mode: NodeMode) -> Self { + let identity_key = self.ed25519_identity_key(); + let sphinx_key = self.x25519_sphinx_key(); + Self::from_data(mode, identity_key, sphinx_key) + } +} diff --git a/nym-node/src/node/description.rs b/nym-node/src/node/description.rs new file mode 100644 index 0000000000..0f5b713727 --- /dev/null +++ b/nym-node/src/node/description.rs @@ -0,0 +1,40 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use nym_node::error::NymNodeError; +use nym_node_http_api::api::api_requests::v1::node::models::NodeDescription; +use std::fs; +use std::fs::create_dir_all; +use std::path::Path; + +pub fn load_node_description>(path: P) -> Result { + let raw = fs::read_to_string(path.as_ref()).map_err(|source| { + NymNodeError::DescriptionLoadFailure { + path: path.as_ref().to_path_buf(), + source, + } + })?; + + toml::from_str(&raw).map_err(|source| NymNodeError::MalformedDescriptionFile { source }) +} + +pub fn save_node_description>( + path: P, + description: &NodeDescription, +) -> Result<(), NymNodeError> { + // SAFETY: + // the unwrap is fine as our description format can be serialised as toml + #[allow(clippy::unwrap_used)] + let serialised = toml::to_string_pretty(description).unwrap(); + if let Some(parent) = path.as_ref().parent() { + create_dir_all(parent).map_err(|source| NymNodeError::DescriptionSaveFailure { + path: path.as_ref().to_path_buf(), + source, + })? + } + + fs::write(path.as_ref(), serialised).map_err(|source| NymNodeError::DescriptionSaveFailure { + path: path.as_ref().to_path_buf(), + source, + }) +} diff --git a/nym-node/src/node/helpers.rs b/nym-node/src/node/helpers.rs new file mode 100644 index 0000000000..a5c23cfb17 --- /dev/null +++ b/nym-node/src/node/helpers.rs @@ -0,0 +1,153 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use nym_crypto::asymmetric::{ed25519, x25519}; +use nym_node::config::NodeMode; +use nym_node::error::{KeyIOFailure, NymNodeError}; +use nym_node_http_api::api::api_requests::v1::node::models::NodeDescription; +use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair}; +use nym_pemstore::KeyPairPath; +use serde::Serialize; +use std::fmt::{Display, Formatter}; +use std::path::Path; + +#[derive(Debug, Serialize)] +pub(crate) struct DisplayDetails { + pub(crate) current_mode: NodeMode, + + pub(crate) description: NodeDescription, + + pub(crate) ed25519_identity_key: String, + pub(crate) x25519_sphinx_key: String, + pub(crate) x25519_noise_key: String, + + pub(crate) exit_network_requester_address: String, + pub(crate) exit_ip_packet_router_address: String, +} + +impl Display for DisplayDetails { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + writeln!(f, "current mode: {}", self.current_mode)?; + writeln!(f, "moniker: '{}'", self.description.moniker)?; + writeln!(f, "website: '{}'", self.description.website)?; + writeln!( + f, + "security contact: '{}'", + self.description.security_contact + )?; + writeln!(f, "details: '{}'", self.description.details)?; + writeln!(f, "ed25519 identity: {}", self.ed25519_identity_key)?; + writeln!(f, "x25519 sphinx: {}", self.x25519_sphinx_key)?; + writeln!(f, "x25519 noise: {}", self.x25519_noise_key)?; + writeln!( + f, + "exit network requester address: {}", + self.exit_network_requester_address + )?; + writeln!( + f, + "exit ip packet router address: {}", + self.exit_ip_packet_router_address + )?; + Ok(()) + } +} + +pub(crate) fn load_keypair( + paths: KeyPairPath, + name: impl Into, +) -> Result { + nym_pemstore::load_keypair(&paths).map_err(|err| KeyIOFailure::KeyPairLoadFailure { + keys: name.into(), + paths, + err, + }) +} + +pub(crate) fn store_keypair( + keys: &T, + paths: KeyPairPath, + name: impl Into, +) -> Result<(), KeyIOFailure> { + nym_pemstore::store_keypair(keys, &paths).map_err(|err| KeyIOFailure::KeyPairStoreFailure { + keys: name.into(), + paths, + err, + }) +} + +pub(crate) fn load_key(path: P, name: impl Into) -> Result +where + T: PemStorableKey, + P: AsRef, +{ + nym_pemstore::load_key(path.as_ref()).map_err(|err| KeyIOFailure::KeyLoadFailure { + key: name.into(), + path: path.as_ref().to_path_buf(), + err, + }) +} + +pub(crate) fn store_key(key: &T, path: P, name: impl Into) -> Result<(), KeyIOFailure> +where + T: PemStorableKey, + P: AsRef, +{ + nym_pemstore::store_key(key, path.as_ref()).map_err(|err| KeyIOFailure::KeyStoreFailure { + key: name.into(), + path: path.as_ref().to_path_buf(), + err, + }) +} + +pub(crate) fn load_ed25519_identity_keypair( + paths: KeyPairPath, +) -> Result { + Ok(load_keypair(paths, "ed25519-identity")?) +} + +#[allow(dead_code)] +pub(crate) fn load_ed25519_identity_public_key>( + path: P, +) -> Result { + Ok(load_key(path, "ed25519-identity-public-key")?) +} + +pub(crate) fn load_x25519_sphinx_keypair( + paths: KeyPairPath, +) -> Result { + Ok(load_keypair(paths, "x25519-sphinx")?) +} + +pub(crate) fn load_x25519_noise_keypair( + paths: KeyPairPath, +) -> Result { + Ok(load_keypair(paths, "x25519-noise")?) +} + +pub(crate) fn load_x25519_sphinx_public_key>( + path: P, +) -> Result { + Ok(load_key(path, "x25519-sphinx-public-key")?) +} + +pub(crate) fn store_ed25519_identity_keypair( + keys: &ed25519::KeyPair, + paths: KeyPairPath, +) -> Result<(), NymNodeError> { + Ok(store_keypair(keys, paths, "ed25519-identity")?) +} + +pub(crate) fn store_x25519_sphinx_keypair( + keys: &x25519::KeyPair, + paths: KeyPairPath, +) -> Result<(), NymNodeError> { + Ok(store_keypair(keys, paths, "x25519-sphinx")?) +} + +pub(crate) fn store_x25519_noise_keypair( + keys: &x25519::KeyPair, + paths: KeyPairPath, +) -> Result<(), NymNodeError> { + Ok(store_keypair(keys, paths, "x25519-noise")?) +} diff --git a/nym-node/src/node/http/mod.rs b/nym-node/src/node/http/mod.rs new file mode 100644 index 0000000000..ca26a8b292 --- /dev/null +++ b/nym-node/src/node/http/mod.rs @@ -0,0 +1,34 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use nym_crypto::asymmetric::{ed25519, x25519}; +use nym_node::config::Config; +use nym_node::error::NymNodeError; +use nym_node_http_api::api::api_requests; +use nym_node_http_api::api::api_requests::SignedHostInformation; +use nym_node_http_api::NymNodeHttpError; + +pub(crate) mod system_info; + +pub(crate) fn sign_host_details( + config: &Config, + x22519_sphinx: &x25519::PublicKey, + x25519_noise: &x25519::PublicKey, + ed22519_identity: &ed25519::KeyPair, +) -> Result { + let host_info = api_requests::v1::node::models::HostInformation { + ip_address: config.host.public_ips.clone(), + hostname: config.host.hostname.clone(), + keys: api_requests::v1::node::models::HostKeys { + ed25519_identity: ed22519_identity.public_key().to_base58_string(), + x25519_sphinx: x22519_sphinx.to_base58_string(), + x25519_noise: x25519_noise.to_base58_string(), + }, + }; + + let signed_info = SignedHostInformation::new(host_info, ed22519_identity.private_key()) + .map_err(NymNodeHttpError::from)?; + Ok(signed_info) +} + +// pub(crate) fn run_http_api(config: &Config, task_client: TaskClient) diff --git a/nym-node/src/node/http/system_info.rs b/nym-node/src/node/http/system_info.rs new file mode 100644 index 0000000000..128ce37229 --- /dev/null +++ b/nym-node/src/node/http/system_info.rs @@ -0,0 +1,80 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use cupid::TopologyType; +use nym_node_http_api::api::api_requests::v1::node::models::{ + Cpu, CryptoHardware, Hardware, HostSystem, +}; +use sysinfo::System; + +fn crypto_hardware() -> Option { + let info = cupid::master()?; + let extended_topology = info.extended_topology_enumeration()?; + + let smt_logical_processor_count = extended_topology + .clone() + .filter_map(|leaf| { + if leaf.level_type() == TopologyType::SMT { + Some(leaf.logical_processor_count()) + } else { + None + } + }) + .collect(); + + Some(CryptoHardware { + aesni: info.aesni(), + avx2: info.avx2(), + smt_logical_processor_count, + osxsave: info.osxsave(), + sgx: info.sgx(), + xsave: info.xsave(), + }) +} + +pub(crate) fn get_system_info( + allow_hardware_info: bool, + allow_crypto_hardware_info: bool, +) -> HostSystem { + if !sysinfo::IS_SUPPORTED_SYSTEM { + return Default::default(); + } + + let mut system = sysinfo::System::new_all(); + // TODO: do we actually need to refresh all here? + system.refresh_all(); + + let crypto_hardware = if allow_crypto_hardware_info && allow_hardware_info { + crypto_hardware() + } else { + None + }; + + let hardware = if allow_hardware_info { + let cpu = system + .cpus() + .iter() + .map(|cpu| Cpu { + name: cpu.name().to_string(), + frequency: cpu.frequency(), + vendor_id: cpu.vendor_id().to_string(), + brand: cpu.brand().to_string(), + }) + .collect(); + Some(Hardware { + cpu, + total_memory: system.total_memory(), + crypto: crypto_hardware, + }) + } else { + None + }; + + HostSystem { + system_name: System::name(), + kernel_version: System::kernel_version(), + os_version: System::os_version(), + cpu_arch: System::cpu_arch(), + hardware, + } +} diff --git a/nym-node/src/node/mod.rs b/nym-node/src/node/mod.rs new file mode 100644 index 0000000000..7127a03a15 --- /dev/null +++ b/nym-node/src/node/mod.rs @@ -0,0 +1,589 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::node::description::{load_node_description, save_node_description}; +use crate::node::helpers::{ + load_ed25519_identity_keypair, load_key, load_x25519_noise_keypair, load_x25519_sphinx_keypair, + store_ed25519_identity_keypair, store_key, store_keypair, store_x25519_noise_keypair, + store_x25519_sphinx_keypair, DisplayDetails, +}; +use crate::node::http::{sign_host_details, system_info::get_system_info}; +use ipnetwork::IpNetwork; +use nym_bin_common::bin_info_owned; +use nym_crypto::asymmetric::{ed25519, x25519}; +use nym_gateway::Gateway; +use nym_mixnode::MixNode; +use nym_network_requester::{ + set_active_gateway, setup_fs_gateways_storage, store_gateway_details, CustomGatewayDetails, + GatewayDetails, GatewayRegistration, +}; +use nym_node::config::entry_gateway::ephemeral_entry_gateway_config; +use nym_node::config::exit_gateway::ephemeral_exit_gateway_config; +use nym_node::config::mixnode::ephemeral_mixnode_config; +use nym_node::config::{Config, EntryGatewayConfig, ExitGatewayConfig, MixnodeConfig, NodeMode}; +use nym_node::error::{EntryGatewayError, ExitGatewayError, MixnodeError, NymNodeError}; +use nym_node_http_api::api::api_requests; +use nym_node_http_api::api::api_requests::v1::node::models::NodeDescription; +use nym_node_http_api::router::WireguardAppState; +use nym_node_http_api::state::metrics::{SharedMixingStats, SharedVerlocStats}; +use nym_node_http_api::state::AppState; +use nym_node_http_api::{NymNodeHTTPServer, NymNodeRouter}; +use nym_sphinx_acknowledgements::AckKey; +use nym_sphinx_addressing::Recipient; +use nym_task::{TaskClient, TaskManager}; +use nym_wireguard_types::registration::GatewayClientRegistry; +use rand::rngs::OsRng; +use rand::{CryptoRng, RngCore}; +use std::path::Path; +use std::sync::Arc; +use tracing::{debug, error, info, trace}; +use zeroize::Zeroizing; + +pub mod bonding_information; +pub mod description; +pub mod helpers; +pub(crate) mod http; + +pub struct MixnodeData { + mixing_stats: SharedMixingStats, +} + +impl MixnodeData { + pub fn initialise(_config: &MixnodeConfig) -> Result<(), MixnodeError> { + Ok(()) + } + + fn new(_config: &MixnodeConfig) -> Result { + Ok(MixnodeData { + mixing_stats: SharedMixingStats::new(), + }) + } +} + +pub struct EntryGatewayData { + mnemonic: Zeroizing, + client_storage: nym_gateway::node::PersistentStorage, + client_registry: Arc, +} + +impl EntryGatewayData { + pub fn initialise( + config: &EntryGatewayConfig, + custom_mnemonic: Option>, + ) -> Result<(), EntryGatewayError> { + // SAFETY: + // this unwrap is fine as 24 word count is a valid argument for generating entropy for a new bip39 mnemonic + #[allow(clippy::unwrap_used)] + let mnemonic = custom_mnemonic + .unwrap_or_else(|| Zeroizing::new(bip39::Mnemonic::generate(24).unwrap())); + config.storage_paths.save_mnemonic_to_file(&mnemonic)?; + + Ok(()) + } + + async fn new(config: &EntryGatewayConfig) -> Result { + Ok(EntryGatewayData { + mnemonic: config.storage_paths.load_mnemonic_from_file()?, + client_storage: nym_gateway::node::PersistentStorage::init( + &config.storage_paths.clients_storage, + config.debug.message_retrieval_limit, + ) + .await + .map_err(nym_gateway::GatewayError::from)?, + client_registry: Arc::new(Default::default()), + }) + } +} + +pub struct ExitGatewayData { + // ideally we'd be storing all the keys here, but unfortunately due to how the service providers + // are currently implemented, they will be loading the data themselves from the provided paths + + // those public keys are just convenience wrappers for http builder and details displayer + nr_ed25519: ed25519::PublicKey, + nr_x25519: x25519::PublicKey, + + ipr_ed25519: ed25519::PublicKey, + ipr_x25519: x25519::PublicKey, +} + +impl ExitGatewayData { + fn initialise_client_keys( + rng: &mut R, + typ: &str, + ed25519_paths: nym_pemstore::KeyPairPath, + x25519_paths: nym_pemstore::KeyPairPath, + ack_key_path: &Path, + ) -> Result<(), ExitGatewayError> { + let ed25519_keys = ed25519::KeyPair::new(rng); + let x25519_keys = x25519::KeyPair::new(rng); + let aes128ctr_key = AckKey::new(rng); + + store_keypair( + &ed25519_keys, + ed25519_paths, + format!("{typ}-ed25519-identity"), + )?; + store_keypair(&x25519_keys, x25519_paths, format!("{typ}-x25519-dh"))?; + store_key(&aes128ctr_key, ack_key_path, format!("{typ}-ack-key"))?; + + Ok(()) + } + + async fn initialise_client_gateway_storage( + storage_path: &Path, + registration: &GatewayRegistration, + ) -> Result<(), ExitGatewayError> { + // insert all required information into the gateways store + // (I hate that we have to do it, but that's currently the simplest thing to do) + let storage = setup_fs_gateways_storage(storage_path).await?; + store_gateway_details(&storage, registration).await?; + set_active_gateway(&storage, ®istration.gateway_id().to_base58_string()).await?; + Ok(()) + } + + pub async fn initialise_network_requester( + rng: &mut R, + config: &ExitGatewayConfig, + registration: &GatewayRegistration, + ) -> Result<(), ExitGatewayError> { + trace!("initialising network requester keys"); + Self::initialise_client_keys( + rng, + "network-requester", + config + .storage_paths + .network_requester + .ed25519_identity_storage_paths(), + config + .storage_paths + .network_requester + .x25519_diffie_hellman_storage_paths(), + &config.storage_paths.network_requester.ack_key_file, + )?; + Self::initialise_client_gateway_storage( + &config.storage_paths.network_requester.gateway_registrations, + registration, + ) + .await + } + + pub async fn initialise_ip_packet_router_requester( + rng: &mut R, + config: &ExitGatewayConfig, + registration: &GatewayRegistration, + ) -> Result<(), ExitGatewayError> { + trace!("initialising ip packet router keys"); + Self::initialise_client_keys( + rng, + "ip-packet-router", + config + .storage_paths + .ip_packet_router + .ed25519_identity_storage_paths(), + config + .storage_paths + .ip_packet_router + .x25519_diffie_hellman_storage_paths(), + &config.storage_paths.ip_packet_router.ack_key_file, + )?; + Self::initialise_client_gateway_storage( + &config.storage_paths.ip_packet_router.gateway_registrations, + registration, + ) + .await + } + + pub async fn initialise( + config: &ExitGatewayConfig, + public_key: ed25519::PublicKey, + ) -> Result<(), ExitGatewayError> { + // generate all the keys for NR and IPR + let mut rng = OsRng; + + let gateway_details = GatewayDetails::Custom(CustomGatewayDetails::new(public_key)).into(); + + // NR: + Self::initialise_network_requester(&mut rng, config, &gateway_details).await?; + + // IPR: + Self::initialise_ip_packet_router_requester(&mut rng, config, &gateway_details).await?; + + Ok(()) + } + + fn new(config: &ExitGatewayConfig) -> Result { + let nr_paths = &config.storage_paths.network_requester; + let nr_ed25519 = load_key( + &nr_paths.public_ed25519_identity_key_file, + "network requester ed25519", + )?; + + let nr_x25519 = load_key( + &nr_paths.public_x25519_diffie_hellman_key_file, + "network requester x25519", + )?; + + let ipr_paths = &config.storage_paths.ip_packet_router; + let ipr_ed25519 = load_key( + &ipr_paths.public_ed25519_identity_key_file, + "ip packet router ed25519", + )?; + + let ipr_x25519 = load_key( + &ipr_paths.public_x25519_diffie_hellman_key_file, + "ip packet router x25519", + )?; + + Ok(ExitGatewayData { + nr_ed25519, + nr_x25519, + ipr_ed25519, + ipr_x25519, + }) + } +} + +pub(crate) struct NymNode { + config: Config, + description: NodeDescription, + + // TODO: currently we're only making measurements in 'mixnode' mode; this should be changed + verloc_stats: SharedVerlocStats, + + #[allow(dead_code)] + mixnode: MixnodeData, + + entry_gateway: EntryGatewayData, + + #[allow(dead_code)] + exit_gateway: ExitGatewayData, + + ed25519_identity_keys: Arc, + x25519_sphinx_keys: Arc, + + // to be used when noise is integrated + #[allow(dead_code)] + x25519_noise_keys: Arc, +} + +impl NymNode { + pub(crate) async fn initialise( + config: &Config, + custom_mnemonic: Option>, + ) -> Result<(), NymNodeError> { + debug!("initialising nym-node with id: {}", config.id); + let mut rng = OsRng; + + // global initialisation + let ed25519_identity_keys = ed25519::KeyPair::new(&mut rng); + let x25519_sphinx_keys = x25519::KeyPair::new(&mut rng); + let x25519_noise_keys = x25519::KeyPair::new(&mut rng); + + trace!("attempting to store ed25519 identity keypair"); + store_ed25519_identity_keypair( + &ed25519_identity_keys, + config.storage_paths.keys.ed25519_identity_storage_paths(), + )?; + + trace!("attempting to store x25519 sphinx keypair"); + store_x25519_sphinx_keypair( + &x25519_sphinx_keys, + config.storage_paths.keys.x25519_sphinx_storage_paths(), + )?; + + trace!("attempting to store x25519 noise keypair"); + store_x25519_noise_keypair( + &x25519_noise_keys, + config.storage_paths.keys.x25519_noise_storage_paths(), + )?; + + trace!("creating description file"); + save_node_description( + &config.storage_paths.description, + &NodeDescription::default(), + )?; + + // mixnode initialisation + MixnodeData::initialise(&config.mixnode)?; + + // entry gateway initialisation + EntryGatewayData::initialise(&config.entry_gateway, custom_mnemonic)?; + + // exit gateway initialisation + ExitGatewayData::initialise(&config.exit_gateway, *ed25519_identity_keys.public_key()) + .await?; + + config.save() + } + + pub(crate) async fn new(config: Config) -> Result { + Ok(NymNode { + ed25519_identity_keys: Arc::new(load_ed25519_identity_keypair( + config.storage_paths.keys.ed25519_identity_storage_paths(), + )?), + x25519_sphinx_keys: Arc::new(load_x25519_sphinx_keypair( + config.storage_paths.keys.x25519_sphinx_storage_paths(), + )?), + x25519_noise_keys: Arc::new(load_x25519_noise_keypair( + config.storage_paths.keys.x25519_noise_storage_paths(), + )?), + description: load_node_description(&config.storage_paths.description)?, + verloc_stats: Default::default(), + mixnode: MixnodeData::new(&config.mixnode)?, + entry_gateway: EntryGatewayData::new(&config.entry_gateway).await?, + exit_gateway: ExitGatewayData::new(&config.exit_gateway)?, + config, + }) + } + + fn exit_network_requester_address(&self) -> Recipient { + Recipient::new( + self.exit_gateway.nr_ed25519, + self.exit_gateway.nr_x25519, + *self.ed25519_identity_keys.public_key(), + ) + } + + fn exit_ip_packet_router_address(&self) -> Recipient { + Recipient::new( + self.exit_gateway.ipr_ed25519, + self.exit_gateway.ipr_x25519, + *self.ed25519_identity_keys.public_key(), + ) + } + + pub(crate) fn display_details(&self) -> DisplayDetails { + DisplayDetails { + current_mode: self.config.mode, + description: self.description.clone(), + ed25519_identity_key: self.ed25519_identity_key().to_base58_string(), + x25519_sphinx_key: self.x25519_sphinx_key().to_base58_string(), + x25519_noise_key: self.x25519_noise_key().to_base58_string(), + exit_network_requester_address: self.exit_network_requester_address().to_string(), + exit_ip_packet_router_address: self.exit_ip_packet_router_address().to_string(), + } + } + + pub(crate) fn mode(&self) -> NodeMode { + self.config.mode + } + + pub(crate) fn ed25519_identity_key(&self) -> &ed25519::PublicKey { + self.ed25519_identity_keys.public_key() + } + + pub(crate) fn x25519_sphinx_key(&self) -> &x25519::PublicKey { + self.x25519_sphinx_keys.public_key() + } + + pub(crate) fn x25519_noise_key(&self) -> &x25519::PublicKey { + self.x25519_noise_keys.public_key() + } + + fn start_mixnode(&self, task_client: TaskClient) -> Result<(), NymNodeError> { + info!("going to start the nym-node in MIXNODE mode"); + + let config = ephemeral_mixnode_config(self.config.clone())?; + let mut mixnode = MixNode::new_loaded( + config, + Default::default(), + self.ed25519_identity_keys.clone(), + self.x25519_sphinx_keys.clone(), + ); + mixnode.disable_http_server(); + mixnode.set_task_client(task_client); + mixnode.set_mixing_stats(self.mixnode.mixing_stats.clone()); + mixnode.set_verloc_stats(self.verloc_stats.clone()); + + tokio::spawn(async move { + if let Err(err) = mixnode.run().await { + error!("the mixnode subtask has failed with the following message: {err}") + } + }); + Ok(()) + } + + fn start_entry_gateway(&self, task_client: TaskClient) -> Result<(), NymNodeError> { + info!("going to start the nym-node in ENTRY GATEWAY mode"); + + let config = + ephemeral_entry_gateway_config(self.config.clone(), &self.entry_gateway.mnemonic)?; + let mut entry_gateway = Gateway::new_loaded( + config, + None, + None, + self.ed25519_identity_keys.clone(), + self.x25519_sphinx_keys.clone(), + self.entry_gateway.client_storage.clone(), + ); + entry_gateway.disable_http_server(); + entry_gateway.set_task_client(task_client); + entry_gateway.set_wireguard_client_registry(self.entry_gateway.client_registry.clone()); + + tokio::spawn(async move { + if let Err(err) = entry_gateway.run().await { + error!("the entry gateway subtask has failed with the following message: {err}") + } + }); + Ok(()) + } + + fn start_exit_gateway(&self, task_client: TaskClient) -> Result<(), NymNodeError> { + info!("going to start the nym-node in EXIT GATEWAY mode"); + + let config = + ephemeral_exit_gateway_config(self.config.clone(), &self.entry_gateway.mnemonic)?; + + let mut exit_gateway = Gateway::new_loaded( + config.gateway, + Some(config.nr_opts), + Some(config.ipr_opts), + self.ed25519_identity_keys.clone(), + self.x25519_sphinx_keys.clone(), + self.entry_gateway.client_storage.clone(), + ); + exit_gateway.disable_http_server(); + exit_gateway.set_task_client(task_client); + exit_gateway.set_wireguard_client_registry(self.entry_gateway.client_registry.clone()); + + tokio::spawn(async move { + if let Err(err) = exit_gateway.run().await { + error!("the exit gateway subtask has failed with the following message: {err}") + } + }); + Ok(()) + } + + pub(crate) fn build_http_server(&self) -> Result { + let host_details = sign_host_details( + &self.config, + self.x25519_sphinx_keys.public_key(), + self.x25519_noise_keys.public_key(), + &self.ed25519_identity_keys, + )?; + + // mixnode info + let mixnode_details = api_requests::v1::mixnode::models::Mixnode {}; + + // entry gateway info + let wireguard = if self.config.wireguard.enabled { + Some(api_requests::v1::gateway::models::Wireguard { + port: self.config.wireguard.announced_port, + public_key: "placeholder key value".to_string(), + }) + } else { + None + }; + let mixnet_websockets = Some(api_requests::v1::gateway::models::WebSockets { + ws_port: self + .config + .entry_gateway + .announce_ws_port + .unwrap_or(self.config.entry_gateway.bind_address.port()), + wss_port: self.config.entry_gateway.announce_wss_port, + }); + let gateway_details = api_requests::v1::gateway::models::Gateway { + client_interfaces: api_requests::v1::gateway::models::ClientInterfaces { + wireguard, + mixnet_websockets, + }, + }; + + // exit gateway info + let nr_details = api_requests::v1::network_requester::models::NetworkRequester { + encoded_identity_key: self.exit_gateway.nr_ed25519.to_base58_string(), + encoded_x25519_key: self.exit_gateway.nr_x25519.to_base58_string(), + address: self.exit_network_requester_address().to_string(), + }; + + let ipr_details = api_requests::v1::ip_packet_router::models::IpPacketRouter { + encoded_identity_key: self.exit_gateway.ipr_ed25519.to_base58_string(), + encoded_x25519_key: self.exit_gateway.ipr_x25519.to_base58_string(), + address: self.exit_ip_packet_router_address().to_string(), + }; + let exit_policy_details = + api_requests::v1::network_requester::exit_policy::models::UsedExitPolicy { + enabled: true, + upstream_source: self + .config + .exit_gateway + .upstream_exit_policy_url + .to_string(), + last_updated: 0, + // TODO: this will require some refactoring to actually retrieve the data from the embedded providers + policy: None, + }; + + let wireguard_private_network = IpNetwork::new( + self.config.wireguard.private_network_ip, + self.config.wireguard.private_network_prefix, + )?; + + let wg_state = WireguardAppState::new( + self.entry_gateway.client_registry.clone(), + Default::default(), + self.config.wireguard.bind_address.port(), + wireguard_private_network, + )?; + + let mut config = nym_node_http_api::Config::new(bin_info_owned!(), host_details) + .with_landing_page_assets(self.config.http.landing_page_assets_path.as_ref()) + .with_mixnode_details(mixnode_details) + .with_gateway_details(gateway_details) + .with_network_requester_details(nr_details) + .with_ip_packet_router_details(ipr_details) + .with_used_exit_policy(exit_policy_details) + .with_description(self.description.clone()); + + if self.config.http.expose_system_info { + config = config.with_system_info(get_system_info( + self.config.http.expose_system_hardware, + self.config.http.expose_crypto_hardware, + )) + } + match self.config.mode { + NodeMode::Mixnode => config.api.v1_config.node.roles.mixnode_enabled = true, + NodeMode::EntryGateway => config.api.v1_config.node.roles.gateway_enabled = true, + NodeMode::ExitGateway => { + config.api.v1_config.node.roles.gateway_enabled = true; + config.api.v1_config.node.roles.network_requester_enabled = true; + config.api.v1_config.node.roles.ip_packet_router_enabled = true; + } + } + + let app_state = AppState::new() + .with_mixing_stats(self.mixnode.mixing_stats.clone()) + .with_verloc_stats(self.verloc_stats.clone()) + .with_metrics_key(self.config.http.access_token.clone()); + + Ok(NymNodeRouter::new(config, Some(app_state), Some(wg_state)) + .build_server(&self.config.http.bind_address)?) + } + + pub(crate) async fn run(self) -> Result<(), NymNodeError> { + let mut task_manager = TaskManager::default().named("NymNode"); + let http_server = self + .build_http_server()? + .with_task_client(task_manager.subscribe_named("http-server")); + tokio::spawn(async move { http_server.run().await }); + + match self.config.mode { + NodeMode::Mixnode => { + self.start_mixnode(task_manager.subscribe_named("mixnode"))?; + let _ = task_manager.catch_interrupt().await; + Ok(()) + } + NodeMode::EntryGateway => { + self.start_entry_gateway(task_manager.subscribe_named("entry-gateway"))?; + let _ = task_manager.catch_interrupt().await; + Ok(()) + } + NodeMode::ExitGateway => { + self.start_exit_gateway(task_manager.subscribe_named("exit-gateway"))?; + let _ = task_manager.catch_interrupt().await; + Ok(()) + } + } + } +} diff --git a/nym-node/src/wireguard/types.rs b/nym-node/src/wireguard/types.rs index e5eb607115..00ddb7a663 100644 --- a/nym-node/src/wireguard/types.rs +++ b/nym-node/src/wireguard/types.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -pub use nym_node_requests::api::v1::gateway::client_interfaces::wireguard::models::*; -pub use nym_wireguard_types::registration::{GatewayClientRegistry, PendingRegistrations}; +// pub use nym_node_requests::api::v1::gateway::client_interfaces::wireguard::models::*; +// pub use nym_wireguard_types::registration::{GatewayClientRegistry, PendingRegistrations}; diff --git a/nym-outfox/Cargo.toml b/nym-outfox/Cargo.toml index bfcd49c233..aad048d9b8 100644 --- a/nym-outfox/Cargo.toml +++ b/nym-outfox/Cargo.toml @@ -13,7 +13,7 @@ repository = { workspace = true } rayon = "1.5.1" blake3 = "1.3" zeroize = "1.5" -chacha20 = "0.9.0" +chacha20 = { version = "0.9.0", features = ["std"] } curve25519-dalek = "3.2" chacha20poly1305 = "0.10.1" getrandom = { workspace = true, features = ["js"] } diff --git a/nym-validator-rewarder/Cargo.toml b/nym-validator-rewarder/Cargo.toml index 0e7d2f6309..7100f87fb8 100644 --- a/nym-validator-rewarder/Cargo.toml +++ b/nym-validator-rewarder/Cargo.toml @@ -28,7 +28,7 @@ zeroize.workspace = true serde_with = "3.4.0" sha2 = "0.10.8" humantime = "2.1.0" -humantime-serde = "1.0" +humantime-serde.workspace = true # internal nym-bin-common = { path = "../common/bin-common", features = ["output_format"] } diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index c93b176e63..9573b2af63 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -1276,6 +1276,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" dependencies = [ "powerfmt", + "serde", ] [[package]] @@ -3010,7 +3011,6 @@ dependencies = [ name = "nym-bin-common" version = "0.6.0" dependencies = [ - "atty", "clap", "clap_complete", "clap_complete_fig", @@ -3249,6 +3249,8 @@ name = "nym-node-requests" version = "0.1.0" dependencies = [ "base64 0.21.4", + "humantime 2.1.0", + "humantime-serde", "nym-bin-common", "nym-crypto", "nym-exit-policy", @@ -3257,6 +3259,7 @@ dependencies = [ "serde", "serde_json", "thiserror", + "time", ] [[package]] @@ -4879,9 +4882,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" diff --git a/sdk/rust/nym-sdk/src/mixnet/config.rs b/sdk/rust/nym-sdk/src/mixnet/config.rs index 264f857413..159348791a 100644 --- a/sdk/rust/nym-sdk/src/mixnet/config.rs +++ b/sdk/rust/nym-sdk/src/mixnet/config.rs @@ -50,6 +50,7 @@ impl Config { BaseClientConfig::from_client_config( ClientConfig::new( DEFAULT_SDK_CLIENT_ID, + env!("CARGO_PKG_VERSION"), !self.enabled_credentials_mode, nyxd_endpoints, nym_api_endpoints, diff --git a/sdk/rust/nym-sdk/src/mixnet/paths.rs b/sdk/rust/nym-sdk/src/mixnet/paths.rs index 8051b49350..510482678c 100644 --- a/sdk/rust/nym-sdk/src/mixnet/paths.rs +++ b/sdk/rust/nym-sdk/src/mixnet/paths.rs @@ -7,7 +7,7 @@ use nym_client_core::client::base_client::{non_wasm_helpers, storage}; use nym_client_core::client::key_manager::persistence::OnDiskKeys; use nym_client_core::client::replies::reply_storage::fs_backend; use nym_client_core::config; -use nym_client_core::config::disk_persistence::keys_paths::ClientKeysPaths; +use nym_client_core::config::disk_persistence::ClientKeysPaths; use nym_client_core::config::disk_persistence::CommonClientPaths; use nym_credential_storage::persistent_storage::PersistentStorage as PersistentCredentialStorage; use std::path::{Path, PathBuf}; diff --git a/service-providers/ip-packet-router/src/config/mod.rs b/service-providers/ip-packet-router/src/config/mod.rs index 048fcd67f8..1c1e155959 100644 --- a/service-providers/ip-packet-router/src/config/mod.rs +++ b/service-providers/ip-packet-router/src/config/mod.rs @@ -16,7 +16,7 @@ use std::{ }; use url::Url; -use crate::config::persistence::IpPacketRouterPaths; +pub use crate::config::persistence::IpPacketRouterPaths; use self::template::CONFIG_TEMPLATE; diff --git a/service-providers/ip-packet-router/src/error.rs b/service-providers/ip-packet-router/src/error.rs index 2cff116db4..aa859963b2 100644 --- a/service-providers/ip-packet-router/src/error.rs +++ b/service-providers/ip-packet-router/src/error.rs @@ -83,6 +83,9 @@ pub enum IpPacketRouterError { #[error("failed to update client activity")] FailedToUpdateClientActivity, + #[error(transparent)] + ConfigUpgradeFailure(#[from] nym_client_core::config::ConfigUpgradeFailure), + #[error(transparent)] NymIdError(#[from] NymIdError), } diff --git a/service-providers/ip-packet-router/src/ip_packet_router.rs b/service-providers/ip-packet-router/src/ip_packet_router.rs index 85f2ce2f3f..8321cddd35 100644 --- a/service-providers/ip-packet-router/src/ip_packet_router.rs +++ b/service-providers/ip-packet-router/src/ip_packet_router.rs @@ -107,7 +107,13 @@ impl IpPacketRouter { #[cfg(not(target_os = "linux"))] pub async fn run_service_provider(self) -> Result<(), IpPacketRouterError> { - todo!("service provider is not yet supported on this platform") + // for debugging purposes, don't crash in debug builds on non-linux platforms + if cfg!(debug_assertions) { + log::error!("ip packet router service provider is not yet supported on this platform"); + Ok(()) + } else { + todo!("service provider is not yet supported on this platform") + } } #[cfg(target_os = "linux")] diff --git a/service-providers/network-requester/Cargo.toml b/service-providers/network-requester/Cargo.toml index 98643296b3..98b756c803 100644 --- a/service-providers/network-requester/Cargo.toml +++ b/service-providers/network-requester/Cargo.toml @@ -23,7 +23,7 @@ bs58 = { workspace = true } clap = { workspace = true, features = ["cargo", "derive"]} dirs = "4.0" futures = { workspace = true } -humantime-serde = "1.1.1" +humantime-serde = { workspace = true } ipnetwork = "0.20.0" log = { workspace = true } pretty_env_logger = "0.4.0" diff --git a/service-providers/network-requester/src/cli/mod.rs b/service-providers/network-requester/src/cli/mod.rs index 5cfa1f046a..5184c5820c 100644 --- a/service-providers/network-requester/src/cli/mod.rs +++ b/service-providers/network-requester/src/cli/mod.rs @@ -110,7 +110,7 @@ pub(crate) struct OverrideConfig { statistics_recipient: Option, } -// NOTE: make sure this is in sync with `gateway/src/commands/helpers.rs::override_network_requester_config` +// NOTE: make sure this is in sync with `gateway/src/helpers.rs::override_network_requester_config` pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Config { // as of 12.09.23 the below is true (not sure how this comment will rot in the future) // medium_toggle: diff --git a/service-providers/network-requester/src/config/mod.rs b/service-providers/network-requester/src/config/mod.rs index 27b0d70739..fdb2d72944 100644 --- a/service-providers/network-requester/src/config/mod.rs +++ b/service-providers/network-requester/src/config/mod.rs @@ -1,7 +1,6 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::config::persistence::NetworkRequesterPaths; use crate::config::template::CONFIG_TEMPLATE; use nym_bin_common::logging::LoggingSettings; use nym_client_core::cli_helpers::CliClientConfig; @@ -21,6 +20,7 @@ use std::str::FromStr; use std::time::Duration; use url::Url; +pub use crate::config::persistence::NetworkRequesterPaths; pub use nym_client_core::config::Config as BaseClientConfig; pub mod helpers; diff --git a/service-providers/network-requester/src/error.rs b/service-providers/network-requester/src/error.rs index 048ca2a5d2..52a3cd8108 100644 --- a/service-providers/network-requester/src/error.rs +++ b/service-providers/network-requester/src/error.rs @@ -70,6 +70,9 @@ pub enum NetworkRequesterError { #[error("can't setup an exit policy without any upstream urls")] NoUpstreamExitPolicy, + #[error(transparent)] + ConfigUpgradeFailure(#[from] nym_client_core::config::ConfigUpgradeFailure), + #[error(transparent)] NymIdError(#[from] NymIdError), } diff --git a/service-providers/network-requester/src/lib.rs b/service-providers/network-requester/src/lib.rs index 457afc99cd..df44f9ffd5 100644 --- a/service-providers/network-requester/src/lib.rs +++ b/service-providers/network-requester/src/lib.rs @@ -16,8 +16,11 @@ pub use nym_client_core::{ base_client::{ non_wasm_helpers::{setup_fs_gateways_storage, setup_fs_reply_surb_backend}, storage::{ - helpers::set_active_gateway, GatewaysDetailsStore, OnDiskGatewaysDetails, - OnDiskPersistent, + gateways_storage::{ + CustomGatewayDetails, GatewayDetails, GatewayRegistration, RemoteGatewayDetails, + }, + helpers::{set_active_gateway, store_gateway_details}, + GatewaysDetailsStore, OnDiskGatewaysDetails, OnDiskPersistent, }, }, key_manager::persistence::OnDiskKeys, diff --git a/tools/nymvisor/Cargo.toml b/tools/nymvisor/Cargo.toml index 7dd7fef743..e7187a5e47 100644 --- a/tools/nymvisor/Cargo.toml +++ b/tools/nymvisor/Cargo.toml @@ -19,7 +19,7 @@ flate2 = "1.0.28" futures = { workspace = true } hex = "0.4.3" humantime = "2.1.0" -humantime-serde = "1.1.1" +humantime-serde = { workspace = true } nix = { version = "0.27.1", features = ["signal", "fs"] } reqwest = { workspace = true, features = ["json", "stream"] } serde = { workspace = true, features = ["derive"] }