Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| efb2aed9dd | |||
| aaec2c9e82 | |||
| f6a6601495 | |||
| 66fea38d20 | |||
| c29fce0856 | |||
| 33bdf08804 | |||
| 236555e6c1 | |||
| c54760bb0b | |||
| 1b8a929ff5 | |||
| 72a4624ace | |||
| e5e7ddb0b6 | |||
| 5a07b73375 | |||
| d1f702c4aa | |||
| c20c7147f8 | |||
| 06956226ad | |||
| b06091e548 | |||
| f3bf5d080b | |||
| e06d442e95 | |||
| fc79f739d4 | |||
| 841fb81d24 | |||
| b4ca959800 |
@@ -34,6 +34,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
IPINFO_API_TOKEN: ${{ secrets.IPINFO_API_TOKEN }}
|
||||
steps:
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools protobuf-compiler
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.44.5
|
||||
uses: mikefarah/yq@v4.44.6
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-credential-proxy/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.44.5
|
||||
uses: mikefarah/yq@v4.44.6
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.44.5
|
||||
uses: mikefarah/yq@v4.44.6
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-network-monitor/Cargo.toml
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.44.5
|
||||
uses: mikefarah/yq@v4.44.6
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.44.5
|
||||
uses: mikefarah/yq@v4.44.6
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.44.5
|
||||
uses: mikefarah/yq@v4.44.6
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
name: Build and upload Nyx Chain Watcher container to harbor.nymte.ch
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
WORKING_DIRECTORY: "nyx-chain-watcher"
|
||||
CONTAINER_NAME: "nyx-chain-watcher"
|
||||
|
||||
jobs:
|
||||
build-container:
|
||||
runs-on: arc-ubuntu-22.04-dind
|
||||
steps:
|
||||
- name: Login to Harbor
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: harbor.nymte.ch
|
||||
username: ${{ secrets.HARBOR_ROBOT_USERNAME }}
|
||||
password: ${{ secrets.HARBOR_ROBOT_SECRET }}
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Configure git identity
|
||||
run: |
|
||||
git config --global user.email "lawrence@nymtech.net"
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.44.6
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
- name: Check if tag exists
|
||||
run: |
|
||||
if git rev-parse ${{ steps.get_version.outputs.value }} >/dev/null 2>&1; then
|
||||
echo "Tag ${{ steps.get_version.outputs.value }} already exists"
|
||||
fi
|
||||
|
||||
- name: Remove existing tag if exists
|
||||
run: |
|
||||
if git rev-parse ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }} >/dev/null 2>&1; then
|
||||
git push --delete origin ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}
|
||||
git tag -d ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}
|
||||
fi
|
||||
|
||||
- name: Create tag
|
||||
run: |
|
||||
git tag -a ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }} -m "Version ${{ steps.get_version.outputs.result }}"
|
||||
git push origin ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}
|
||||
|
||||
- name: BuildAndPushImageOnHarbor
|
||||
run: |
|
||||
docker build -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:${{ steps.get_version.outputs.result }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:latest
|
||||
docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.44.5
|
||||
uses: mikefarah/yq@v4.44.6
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
Generated
+142
-124
@@ -11,7 +11,7 @@ dependencies = [
|
||||
"macroific",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -283,7 +283,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -352,7 +352,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -363,7 +363,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -573,7 +573,7 @@ checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1140,7 +1140,7 @@ dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1790,7 +1790,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1948,7 +1948,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim 0.11.1",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1970,7 +1970,7 @@ checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178"
|
||||
dependencies = [
|
||||
"darling_core 0.20.9",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2021,7 +2021,7 @@ dependencies = [
|
||||
"macroific",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2064,7 +2064,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2097,7 +2097,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"proc-macro2-diagnostics",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2165,7 +2165,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2531,7 +2531,7 @@ dependencies = [
|
||||
"macroific",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2765,7 +2765,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2860,7 +2860,7 @@ dependencies = [
|
||||
"proc-macro-error2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3248,6 +3248,12 @@ version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "human-repr"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f58b778a5761513caf593693f8951c97a5b610841e754788400f32102eefdff1"
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "1.3.0"
|
||||
@@ -3822,9 +3828,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.162"
|
||||
version = "0.2.167"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
|
||||
checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
@@ -3950,7 +3956,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3961,7 +3967,7 @@ checksum = "13198c120864097a565ccb3ff947672d969932b7975ebd4085732c9f09435e55"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3974,7 +3980,7 @@ dependencies = [
|
||||
"macroific_core",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4128,6 +4134,21 @@ dependencies = [
|
||||
"wasm-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mixnet-connectivity-check"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.5.20",
|
||||
"futures",
|
||||
"nym-bin-common",
|
||||
"nym-crypto",
|
||||
"nym-network-defaults",
|
||||
"nym-sdk",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "moka"
|
||||
version = "0.12.8"
|
||||
@@ -4431,7 +4452,7 @@ dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4851,6 +4872,7 @@ dependencies = [
|
||||
"nym-gateway-requests",
|
||||
"nym-id",
|
||||
"nym-metrics",
|
||||
"nym-mixnet-client",
|
||||
"nym-network-defaults",
|
||||
"nym-nonexhaustive-delayqueue",
|
||||
"nym-pemstore",
|
||||
@@ -5443,7 +5465,7 @@ dependencies = [
|
||||
"nym-mixnode-common",
|
||||
"nym-network-defaults",
|
||||
"nym-network-requester",
|
||||
"nym-node-http-api",
|
||||
"nym-node-metrics",
|
||||
"nym-sdk",
|
||||
"nym-sphinx",
|
||||
"nym-statistics-common",
|
||||
@@ -5535,9 +5557,11 @@ name = "nym-gateway-stats-storage"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"nym-credentials-interface",
|
||||
"nym-node-metrics",
|
||||
"nym-sphinx",
|
||||
"nym-statistics-common",
|
||||
"sqlx",
|
||||
"strum 0.26.3",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tokio",
|
||||
@@ -5548,7 +5572,6 @@ dependencies = [
|
||||
name = "nym-gateway-storage"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bincode",
|
||||
"defguard_wireguard_rs",
|
||||
"log",
|
||||
@@ -5741,11 +5764,11 @@ name = "nym-mixnet-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"log",
|
||||
"nym-sphinx",
|
||||
"nym-task",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5772,35 +5795,6 @@ dependencies = [
|
||||
"utoipa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-mixnode"
|
||||
version = "1.1.37"
|
||||
dependencies = [
|
||||
"colored",
|
||||
"futures",
|
||||
"nym-contracts-common",
|
||||
"nym-crypto",
|
||||
"nym-http-api-common",
|
||||
"nym-metrics",
|
||||
"nym-mixnet-client",
|
||||
"nym-mixnode-common",
|
||||
"nym-node-http-api",
|
||||
"nym-nonexhaustive-delayqueue",
|
||||
"nym-sphinx",
|
||||
"nym-sphinx-params",
|
||||
"nym-sphinx-types",
|
||||
"nym-task",
|
||||
"nym-topology",
|
||||
"nym-types",
|
||||
"nym-validator-client",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-mixnode-common"
|
||||
version = "0.1.0"
|
||||
@@ -5809,11 +5803,9 @@ dependencies = [
|
||||
"futures",
|
||||
"humantime-serde",
|
||||
"log",
|
||||
"nym-bin-common",
|
||||
"nym-crypto",
|
||||
"nym-metrics",
|
||||
"nym-network-defaults",
|
||||
"nym-node-http-api",
|
||||
"nym-sphinx-acknowledgements",
|
||||
"nym-sphinx-addressing",
|
||||
"nym-sphinx-forwarding",
|
||||
@@ -5821,7 +5813,6 @@ dependencies = [
|
||||
"nym-sphinx-params",
|
||||
"nym-sphinx-types",
|
||||
"nym-task",
|
||||
"nym-validator-client",
|
||||
"rand",
|
||||
"serde",
|
||||
"thiserror",
|
||||
@@ -5871,6 +5862,7 @@ dependencies = [
|
||||
"futures",
|
||||
"log",
|
||||
"nym-bin-common",
|
||||
"nym-client-core",
|
||||
"nym-crypto",
|
||||
"nym-network-defaults",
|
||||
"nym-sdk",
|
||||
@@ -5946,6 +5938,9 @@ name = "nym-node"
|
||||
version = "1.1.12"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"axum 0.7.7",
|
||||
"axum-extra",
|
||||
"bip39",
|
||||
"bs58",
|
||||
"cargo_metadata 0.18.1",
|
||||
@@ -5953,6 +5948,10 @@ dependencies = [
|
||||
"clap 4.5.20",
|
||||
"colored",
|
||||
"cupid",
|
||||
"dashmap",
|
||||
"futures",
|
||||
"headers",
|
||||
"human-repr",
|
||||
"humantime-serde",
|
||||
"ipnetwork 0.20.0",
|
||||
"nym-authenticator",
|
||||
@@ -5961,63 +5960,60 @@ dependencies = [
|
||||
"nym-config",
|
||||
"nym-crypto",
|
||||
"nym-gateway",
|
||||
"nym-gateway-stats-storage",
|
||||
"nym-http-api-common",
|
||||
"nym-ip-packet-router",
|
||||
"nym-mixnode",
|
||||
"nym-metrics",
|
||||
"nym-mixnet-client",
|
||||
"nym-network-requester",
|
||||
"nym-node-http-api",
|
||||
"nym-node-metrics",
|
||||
"nym-node-requests",
|
||||
"nym-nonexhaustive-delayqueue",
|
||||
"nym-pemstore",
|
||||
"nym-sphinx-acknowledgements",
|
||||
"nym-sphinx-addressing",
|
||||
"nym-sphinx-forwarding",
|
||||
"nym-sphinx-framing",
|
||||
"nym-sphinx-types",
|
||||
"nym-task",
|
||||
"nym-topology",
|
||||
"nym-types",
|
||||
"nym-validator-client",
|
||||
"nym-verloc",
|
||||
"nym-wireguard",
|
||||
"nym-wireguard-types",
|
||||
"rand",
|
||||
"semver 1.0.23",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"si-scale",
|
||||
"sysinfo",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"toml 0.8.14",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"url",
|
||||
"utoipa",
|
||||
"utoipa-swagger-ui",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-node-http-api"
|
||||
name = "nym-node-metrics"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"axum 0.7.7",
|
||||
"axum-extra",
|
||||
"base64 0.22.1",
|
||||
"colored",
|
||||
"dashmap",
|
||||
"fastrand 2.1.1",
|
||||
"headers",
|
||||
"hmac",
|
||||
"hyper 1.4.1",
|
||||
"ipnetwork 0.20.0",
|
||||
"nym-crypto",
|
||||
"nym-http-api-common",
|
||||
"futures",
|
||||
"nym-metrics",
|
||||
"nym-node-requests",
|
||||
"nym-task",
|
||||
"nym-wireguard",
|
||||
"rand",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"nym-statistics-common",
|
||||
"strum 0.26.3",
|
||||
"time",
|
||||
"tokio",
|
||||
"tower 0.4.13",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
"utoipa",
|
||||
"utoipa-swagger-ui",
|
||||
"x25519-dalek",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6062,7 +6058,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node-status-api"
|
||||
version = "1.0.0-rc.4"
|
||||
version = "1.0.0-rc.7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.7",
|
||||
@@ -6076,8 +6072,10 @@ dependencies = [
|
||||
"nym-crypto",
|
||||
"nym-explorer-client",
|
||||
"nym-network-defaults",
|
||||
"nym-node-metrics",
|
||||
"nym-node-requests",
|
||||
"nym-node-status-client",
|
||||
"nym-serde-helpers",
|
||||
"nym-statistics-common",
|
||||
"nym-task",
|
||||
"nym-validator-client",
|
||||
@@ -6811,6 +6809,25 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-verloc"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures",
|
||||
"humantime 2.1.0",
|
||||
"nym-crypto",
|
||||
"nym-task",
|
||||
"nym-validator-client",
|
||||
"rand",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-vesting-contract-common"
|
||||
version = "0.7.0"
|
||||
@@ -6892,6 +6909,7 @@ dependencies = [
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
"x25519-dalek",
|
||||
]
|
||||
|
||||
@@ -7023,9 +7041,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.104"
|
||||
version = "0.9.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741"
|
||||
checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
@@ -7225,7 +7243,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"proc-macro2-diagnostics",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7312,7 +7330,7 @@ dependencies = [
|
||||
"pest_meta",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7353,7 +7371,7 @@ checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7568,14 +7586,14 @@ dependencies = [
|
||||
"proc-macro-error-attr2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.89"
|
||||
version = "1.0.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
|
||||
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -7588,7 +7606,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
"version_check",
|
||||
"yansi",
|
||||
]
|
||||
@@ -7651,7 +7669,7 @@ dependencies = [
|
||||
"itertools 0.12.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7884,7 +7902,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8111,7 +8129,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rocket_http",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
"unicode-xid",
|
||||
"version_check",
|
||||
]
|
||||
@@ -8237,7 +8255,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rust-embed-utils",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
@@ -8293,9 +8311,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.40"
|
||||
version = "0.38.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0"
|
||||
checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"errno",
|
||||
@@ -8486,7 +8504,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals 0.29.1",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8518,7 +8536,7 @@ checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8650,7 +8668,7 @@ checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8661,7 +8679,7 @@ checksum = "e578a843d40b4189a4d66bba51d7684f57da5bd7c304c64e14bd63efbef49509"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8672,7 +8690,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8734,7 +8752,7 @@ checksum = "aafbefbe175fa9bf03ca83ef89beecff7d2a95aaacd5732325b90ac8c3bd7b90"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8755,7 +8773,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8806,7 +8824,7 @@ dependencies = [
|
||||
"darling 0.20.9",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9405,7 +9423,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9448,9 +9466,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.87"
|
||||
version = "2.0.90"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
|
||||
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -9756,7 +9774,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9887,7 +9905,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10166,7 +10184,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10330,7 +10348,7 @@ checksum = "0ea0b99e8ec44abd6f94a18f28f7934437809dd062820797c52401298116f70e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
@@ -10357,7 +10375,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals 0.28.0",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10560,7 +10578,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55137c122f712d9330fd985d66fa61bdc381752e89c35708c13ce63049a3002c"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10592,7 +10610,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
"toml 0.5.11",
|
||||
"uniffi_build",
|
||||
"uniffi_meta",
|
||||
@@ -10724,7 +10742,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
@@ -10763,7 +10781,7 @@ checksum = "17e82ab96c5a55263b5bed151b8426410d93aa909a453acdbd4b6792b5af7d64"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10774,7 +10792,7 @@ checksum = "86b8338dc3c9526011ffaa2aa6bd60ddfda9d49d2123108690755c6e34844212"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
"utoipauto-core",
|
||||
]
|
||||
|
||||
@@ -10881,7 +10899,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -10915,7 +10933,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@@ -10949,7 +10967,7 @@ checksum = "4b8220be1fa9e4c889b30fd207d4906657e7e90b12e0e6b0c8b8d8709f5de021"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11477,7 +11495,7 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11497,7 +11515,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
+3
-5
@@ -104,7 +104,6 @@ members = [
|
||||
"explorer-api/explorer-client",
|
||||
"gateway",
|
||||
"integrations/bity",
|
||||
"mixnode",
|
||||
"sdk/ffi/cpp",
|
||||
"sdk/ffi/go",
|
||||
"sdk/ffi/shared",
|
||||
@@ -123,8 +122,8 @@ members = [
|
||||
"nym-data-observatory",
|
||||
"nym-network-monitor",
|
||||
"nym-node",
|
||||
"nym-node/nym-node-http-api",
|
||||
"nym-node/nym-node-requests",
|
||||
"nym-node/nym-node-metrics",
|
||||
"nym-node-status-api/nym-node-status-agent",
|
||||
"nym-node-status-api/nym-node-status-api",
|
||||
"nym-node-status-api/nym-node-status-client",
|
||||
@@ -149,15 +148,13 @@ members = [
|
||||
"tools/internal/contract-state-importer/importer-cli",
|
||||
"tools/internal/contract-state-importer/importer-contract",
|
||||
"tools/internal/testnet-manager",
|
||||
"tools/internal/testnet-manager/dkg-bypass-contract",
|
||||
"tools/internal/testnet-manager/dkg-bypass-contract", "common/verloc", "tools/internal/mixnet-connectivity-check",
|
||||
]
|
||||
|
||||
default-members = [
|
||||
"clients/native",
|
||||
"clients/socks5",
|
||||
"explorer-api",
|
||||
"gateway",
|
||||
"mixnode",
|
||||
"nym-api",
|
||||
"nym-credential-proxy/nym-credential-proxy",
|
||||
"nym-data-observatory",
|
||||
@@ -263,6 +260,7 @@ http-body-util = "0.1"
|
||||
httpcodec = "0.2.3"
|
||||
humantime = "2.1.0"
|
||||
humantime-serde = "1.1.1"
|
||||
human-repr = "1.1.0"
|
||||
hyper = "1.4.1"
|
||||
hyper-util = "0.1"
|
||||
indicatif = "0.17.8"
|
||||
|
||||
+68
-56
@@ -3,37 +3,23 @@ Critical bug or security issue 💥
|
||||
If you're here because you're trying to figure out how to notify us of a security issue, send us a PGP encrypted email to:
|
||||
|
||||
```
|
||||
security@nymte.ch
|
||||
security@nym.com
|
||||
```
|
||||
|
||||
Encrypted with our public key which is available below in plain text and also on keyservers:
|
||||
|
||||
```
|
||||
pub rsa4096 2023-10-30 [SC] [expire : 2026-10-29]
|
||||
sec rsa4096/7C3C727F05090550 2023-10-30 [SC] [expire : 2026-10-29]
|
||||
24B2592E801A5AAA8666C8BA7C3C727F05090550
|
||||
uid [ ultime ] Security Nym Technologies <security@nymte.ch>
|
||||
sub rsa4096 2023-10-30 [E] [expire : 2026-10-29]
|
||||
uid [ ultime ] Security Nym Technologies <security@nym.com>
|
||||
ssb rsa4096/ACD0FBD79DC70ACC 2023-10-30 [E] [expire : 2026-10-29]
|
||||
|
||||
```
|
||||
|
||||
The fingerprint of the key is on the second line above.
|
||||
|
||||
If you need to chat __urgently__ to our team for a __critical__ security issue:
|
||||
|
||||
go to Matrix, and alert the core engineers with a private direct message:
|
||||
|
||||
Jedrzej Stuczynski @jstuczyn:nymtech.chat
|
||||
Mark Sinclair @mark:nymtech.chat
|
||||
Raphaël Walther @raphael:nymtech.chat
|
||||
|
||||
Please avoid opening public issues on GitHub that contain information about a potential security vulnerability as this makes it difficult to reduce the impact and harm of valid security issues.
|
||||
|
||||
If you don't know what Matrix is, you can follow this documentation to create an account on this federation of instant messaging servers:
|
||||
|
||||
[Matrix for Instant Messaging](https://matrix.org/docs/chat_basics/matrix-for-im/)
|
||||
|
||||
|
||||
|
||||
```
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
@@ -48,43 +34,69 @@ vMFUIzBMHOPXH16036zGyFMC1esRd2qqil4b9KtLgCOkrD1VgpjcveoA0VyMJCN6
|
||||
LmKTrVjwjjDMxby+d49BolRWGnCofXozXwvNQx+CYv8M2WPErTpyYoofYFtpqr7A
|
||||
fIufc/e0+um3zoGIbHejrhsbuH9Qf+MKsI+Ng93bdDtjeHz6MEgAlsTm0qeizYpj
|
||||
IyKZIObPmfvrAm08hFZ8JnGk+XuooF36XWbJYjCCy0bOyMw1r7ZG99TcSwARAQAB
|
||||
tC1TZWN1cml0eSBOeW0gVGVjaG5vbG9naWVzIDxzZWN1cml0eUBueW10ZS5jaD6J
|
||||
AlQEEwEKAD4WIQQkslkugBpaqoZmyLp8PHJ/BQkFUAUCZT9elwIbAwUJBaOagAUL
|
||||
CQgHAwUVCgkICwUWAgMBAAIeAQIXgAAKCRB8PHJ/BQkFUL7dD/9zO73uI5VR+SWx
|
||||
PFmJW+9QsPiQbVRvGwNZurctmQ2s2Pe0vHRELFeqD5oYvSx2Lequ3Ir+zn/C3kDM
|
||||
kNs40obSL6jCBiLPkxEY0JqzPM9jZr7EjvlibWV3f6DxooRIqEyfN57I3OBGlqZE
|
||||
0Mx7sQuCcgau8C70DF952QhKUwXC2cmpmDKHVEEoio1xGSD4dQhGapCB32RQGtna
|
||||
OGfAO9celNMvSq0Lp+aJxeACmWFY5T4/y79JPcT5vSs/yEIRmaH/fn2piwaFBsIq
|
||||
gHJJMxO3740P1hF8j7KWUoUofuFaEALHBpEpjWTOj8ej1wmFlu+5F+jSVoc781Wb
|
||||
ZZXu04cOBXnGTogzSxMpBe9TtLb28zd6WzFotC25KTI3pngMzXsQGLJLOwvoZKiS
|
||||
LFjPRjg1rwobmB3Q3J2W5GYSveia0CDsZGP+g87GVVf/oD2Djpa68xyVYwIYeA6T
|
||||
3DNdS77qHiRuGiS4kWXyVjDqOICboR4uCvt09zlkBuLDdTWqWYARUvZjtjs4w/Ol
|
||||
rdrBI3A88ti8fRldYaNpu17ME1ilpN44yKoJtqiWc3Tisk8eYLfx6c7FQF3PrRva
|
||||
mr7FZvhFsYML5CeNFHTEzN6Y3jjKN/60DvCfodWnWFK47Txkl8UAXGY2W9B0fWqQ
|
||||
wUVr8uLuMyyMiKbeoufi7rGOj6AMErkCDQRlP16XARAA8FGmD5J3tM1BOM1niJxZ
|
||||
JTdCauzEtxEoBL0RuqGBkR8U29sRM6DwuzjU7PwscFnBaGyU+eU73GwGkH3ozFfF
|
||||
tllYhQrhP/kkN+0rEO5Xi+nR+4JCFRqrf3nJXAAPfiksURMp8er1dUOY2/e1ZSoL
|
||||
tS+nzUivV8CfE+pgj/5YtGwPC+KYHLATkKkMELCrbW4UO06VWOqQsvr6kivXuJQQ
|
||||
LdEAMpBlADmXFG45DmPKQzsBWUgvTwyGy3LX0nys8cgpex9BH8hhr01QmGyP469s
|
||||
N3cNrtFuu8U6RAsiCD/8mlBuD3EQEU5SF0lc7kCICAZk+wElmXnimEi0TOYsbz6k
|
||||
90lteicX70rA9GNeyI76H+VSOYvWpkRwaJAgUdzrAM1o9SHASq+cZ6nD85OZioQk
|
||||
DWM6+Q+sf2oen0qJnnGmUr93kJIC0PIdgrXRrtiNfeRa1Z/H0LmREyyEMoFiVivn
|
||||
z1vVk85Oq6Sf3ltUwvmDzuuJOtsp2Qp6+x6Snn/yKauI4uf4Cf/wKUch4r6Bwgg5
|
||||
Dw49ky7lwlnALio4GIVoGLpLef93wWoDmp4Klyh3ZPf2nB0U91u3bHRUo7m+D7QJ
|
||||
98cyKtqLLzjg7szGf60pIWNWRsadYQT3bSncynqknAjOV3BCvx6/ivsnpj//QjYR
|
||||
HtviUAcQ1DBB6UC6q23FIs0AEQEAAYkCPAQYAQoAJhYhBCSyWS6AGlqqhmbIunw8
|
||||
cn8FCQVQBQJlP16XAhsMBQkFo5qAAAoJEHw8cn8FCQVQzukP/iLxjOxT+UpPR//c
|
||||
prDVSLkP4pF5bmw36U07jvqpS+/KTXsxiiQleffRabOpNLcd+K1ueavyt9nnIwHH
|
||||
tHS9kM9A7DBw3LnpEbXki46QDCCI6niGijlLOEeAWqnocwMNTT05wVVgCtO3DQP2
|
||||
MoSCcqHpXDChvOyr5d5xjYLVJhlctIMSomcVzGryjknPu0Yj/TkC/4c+m86ZWQUD
|
||||
HqMHQIuiEenvb62/F4c5OJIRZPEn70wdddkgJuJU3eHdHrnuhCkjCC93GQGbGj03
|
||||
Zqos6699y6hmPeD3U5IUv8ujwZYVCCuDm8gJfrp3R6WLfeZeK9WmTVBpCzsDg3fV
|
||||
hSwmOk6pp8DAq1/Dev3yRkFggCEyGK6c9b+a0CRBncl8e5Q0QQIzNiS/uExQP3h+
|
||||
ELJs3P0MLP+6FWhNUry09n3lnWkr1hY+v1M0GAxbfdv/tsCN1Pq/VQEz+CTqXqya
|
||||
ftWldOHWw6Hh+gtwxcHjG4MBOrO5oICQ3lh2hGwQ58cDgZYSK/OGgJ9BggFl1CcM
|
||||
0uGC0/TRCI1zt/4y+7efSZQMZkHo7VC/3MFbp2hcNejpW+BxVuwKTunFvWK3TLhq
|
||||
sSlQ5yyhqchooepsFHq9bosKFjLJC01uprBv1rinoNduOy43FbyS7JPRRspANN0R
|
||||
iC2pMbWdE0ZTQaFq6tPIg058pjqi
|
||||
=nqgX
|
||||
tCxTZWN1cml0eSBOeW0gVGVjaG5vbG9naWVzIDxzZWN1cml0eUBueW0uY29tPokC
|
||||
VAQTAQoAPhYhBCSyWS6AGlqqhmbIunw8cn8FCQVQBQJnSd5VAhsDBQkFo5qABQsJ
|
||||
CAcDBRUKCQgLBRYCAwEAAh4BAheAAAoJEHw8cn8FCQVQPPIP/ipGz2zLAjE2dSE3
|
||||
VcqOvras0DfqIL9HDm26Dg6QO2D/4YRntw0RqVyuy+zFnRUm+RZCKLPLUzbQ9Wjb
|
||||
G/Og5ttQVYQMu5eKu7OMvXkrbRo3teZFU+8IL08zIW6pyf9haxO6YMhLRy6cLYwW
|
||||
0EYC6Qzn5gz3kI7VkI8fWfs2Dk4XEV3D+SVtBoF6KRxMXT6HZvpzoMSEJZBoNj8S
|
||||
jw0TF8TFUQf49jUQbIHumukMswolrHi8a5ej8DSfNwSgz+Tt8oh5lu01kyUJiHn7
|
||||
nuHaY4Y9cHUVAOSwq/hovG52+ZE1r3aiswvle/B19o9pKeWWVvacSptGxDQagBtQ
|
||||
igoNLdRvY0XN2TEyX9pOHR0AoVOxtIW11CpkKuDbQG9vPwovqJ2L6+Fh3pzHYzcI
|
||||
2GIShNm/Z2SZBiUqbljJe9H4UAT/aHgMINkEG8qzUKwO42MA5HJT7YbHTR17/QSF
|
||||
Il5dhneRzmSbNcW2rdRwx/BmzrcsFJfqCt4JG/WDF293xSOjhFqQYvU4gCO+OB7o
|
||||
KXjX907XXDjS2KEJ71OGqVfk/P7BqEfQNfrLtb02TyXJAPQXHhybv23c4E7zUs9V
|
||||
lMjNizzxYB96uwJb0LAB2ijzEwoP91uGT2tFjk6F08x2QiArmXUdgrv44b39Stia
|
||||
gJS0GYKqSzyr10xHhUuDA+GKYtcitC1TZWN1cml0eSBOeW0gVGVjaG5vbG9naWVz
|
||||
IDxzZWN1cml0eUBueW10ZS5jaD6JAjYEMAEKACAWIQQkslkugBpaqoZmyLp8PHJ/
|
||||
BQkFUAUCZ0nftQIdIAAKCRB8PHJ/BQkFUFHDEACtyNuUEjKCLAT5mSfow85PjFgo
|
||||
o8kHjQr/IIQ7ZbBOHeJJcrxDuypssiLh5XUjF3x5BiBfZ6vCxSb81RRwsDMp0mA1
|
||||
qzv9G8sgW0HTQUnZ9oH6CYut2NgzAnQpmuacrunm9Zy0FJ3ejbmwUY/NqK6gJkle
|
||||
66duHKhAy7DWjj7amd0C8bPDR+PA44fI3MezDHkQNaauKZTRqd1TqH8Qk5PAl4cB
|
||||
o5gVzeZh/U7/usvtGhazAIUF5BqK6bTmDnYopg+2x8jjwrG4+08GrttZkNjBLXeA
|
||||
Y/2U064yMz12LPv01qqAFdZ+coRy/ps/gOQTz34/VeW0CFy7TMqs4t3vSBWTqU7w
|
||||
hnw/qj6cM33fdxctj6KDgJSCkZdx2fvwXgxiPqUa5+j9FlFBeD5RDAl6g6t8N1/K
|
||||
Xca+zNYuSZgc297q1D+mtSD1C7uJNPxoAl+Bv5KNKpsjfQ+m04++CIFtGyX22aCA
|
||||
h2/tHwQZIXhOiMAKOoupidDVDhgxtCJ3Ps416xL0sTZfsPfg+j1Uv/Em9pzPClEl
|
||||
fX6+1O4DdSyZUQ4VsjMu/H5W/NQdbHgmqFrxQ6WX/0s5GMwO6GMDiPe8sOrwz9wD
|
||||
WYtyjafxXOHEZ1OjYX5gr7bGaG4oKc2btTJN0B3Phg4dStnHCNjEYccxuV3507fj
|
||||
HnNotkpXF2nGLxy+PYkCVAQTAQoAPhYhBCSyWS6AGlqqhmbIunw8cn8FCQVQBQJl
|
||||
P16XAhsDBQkFo5qABQsJCAcDBRUKCQgLBRYCAwEAAh4BAheAAAoJEHw8cn8FCQVQ
|
||||
vt0P/3M7ve4jlVH5JbE8WYlb71Cw+JBtVG8bA1m6ty2ZDazY97S8dEQsV6oPmhi9
|
||||
LHYt6q7civ7Of8LeQMyQ2zjShtIvqMIGIs+TERjQmrM8z2NmvsSO+WJtZXd/oPGi
|
||||
hEioTJ83nsjc4EaWpkTQzHuxC4JyBq7wLvQMX3nZCEpTBcLZyamYModUQSiKjXEZ
|
||||
IPh1CEZqkIHfZFAa2do4Z8A71x6U0y9KrQun5onF4AKZYVjlPj/Lv0k9xPm9Kz/I
|
||||
QhGZof9+famLBoUGwiqAckkzE7fvjQ/WEXyPspZShSh+4VoQAscGkSmNZM6Px6PX
|
||||
CYWW77kX6NJWhzvzVZtlle7Thw4FecZOiDNLEykF71O0tvbzN3pbMWi0LbkpMjem
|
||||
eAzNexAYsks7C+hkqJIsWM9GODWvChuYHdDcnZbkZhK96JrQIOxkY/6DzsZVV/+g
|
||||
PYOOlrrzHJVjAhh4DpPcM11LvuoeJG4aJLiRZfJWMOo4gJuhHi4K+3T3OWQG4sN1
|
||||
NapZgBFS9mO2OzjD86Wt2sEjcDzy2Lx9GV1ho2m7XswTWKWk3jjIqgm2qJZzdOKy
|
||||
Tx5gt/HpzsVAXc+tG9qavsVm+EWxgwvkJ40UdMTM3pjeOMo3/rQO8J+h1adYUrjt
|
||||
PGSXxQBcZjZb0HR9apDBRWvy4u4zLIyIpt6i5+LusY6PoAwSuQINBGU/XpcBEADw
|
||||
UaYPkne0zUE4zWeInFklN0Jq7MS3ESgEvRG6oYGRHxTb2xEzoPC7ONTs/CxwWcFo
|
||||
bJT55TvcbAaQfejMV8W2WViFCuE/+SQ37SsQ7leL6dH7gkIVGqt/eclcAA9+KSxR
|
||||
Eynx6vV1Q5jb97VlKgu1L6fNSK9XwJ8T6mCP/li0bA8L4pgcsBOQqQwQsKttbhQ7
|
||||
TpVY6pCy+vqSK9e4lBAt0QAykGUAOZcUbjkOY8pDOwFZSC9PDIbLctfSfKzxyCl7
|
||||
H0EfyGGvTVCYbI/jr2w3dw2u0W67xTpECyIIP/yaUG4PcRARTlIXSVzuQIgIBmT7
|
||||
ASWZeeKYSLRM5ixvPqT3SW16JxfvSsD0Y17Ijvof5VI5i9amRHBokCBR3OsAzWj1
|
||||
IcBKr5xnqcPzk5mKhCQNYzr5D6x/ah6fSomecaZSv3eQkgLQ8h2CtdGu2I195FrV
|
||||
n8fQuZETLIQygWJWK+fPW9WTzk6rpJ/eW1TC+YPO64k62ynZCnr7HpKef/Ipq4ji
|
||||
5/gJ//ApRyHivoHCCDkPDj2TLuXCWcAuKjgYhWgYukt5/3fBagOangqXKHdk9/ac
|
||||
HRT3W7dsdFSjub4PtAn3xzIq2osvOODuzMZ/rSkhY1ZGxp1hBPdtKdzKeqScCM5X
|
||||
cEK/Hr+K+yemP/9CNhEe2+JQBxDUMEHpQLqrbcUizQARAQABiQI8BBgBCgAmFiEE
|
||||
JLJZLoAaWqqGZsi6fDxyfwUJBVAFAmU/XpcCGwwFCQWjmoAACgkQfDxyfwUJBVDO
|
||||
6Q/+IvGM7FP5Sk9H/9ymsNVIuQ/ikXlubDfpTTuO+qlL78pNezGKJCV599Fps6k0
|
||||
tx34rW55q/K32ecjAce0dL2Qz0DsMHDcuekRteSLjpAMIIjqeIaKOUs4R4Baqehz
|
||||
Aw1NPTnBVWAK07cNA/YyhIJyoelcMKG87Kvl3nGNgtUmGVy0gxKiZxXMavKOSc+7
|
||||
RiP9OQL/hz6bzplZBQMeowdAi6IR6e9vrb8Xhzk4khFk8SfvTB112SAm4lTd4d0e
|
||||
ue6EKSMIL3cZAZsaPTdmqizrr33LqGY94PdTkhS/y6PBlhUIK4ObyAl+undHpYt9
|
||||
5l4r1aZNUGkLOwODd9WFLCY6TqmnwMCrX8N6/fJGQWCAITIYrpz1v5rQJEGdyXx7
|
||||
lDRBAjM2JL+4TFA/eH4Qsmzc/Qws/7oVaE1SvLT2feWdaSvWFj6/UzQYDFt92/+2
|
||||
wI3U+r9VATP4JOperJp+1aV04dbDoeH6C3DFweMbgwE6s7mggJDeWHaEbBDnxwOB
|
||||
lhIr84aAn0GCAWXUJwzS4YLT9NEIjXO3/jL7t59JlAxmQejtUL/cwVunaFw16Olb
|
||||
4HFW7ApO6cW9YrdMuGqxKVDnLKGpyGih6mwUer1uiwoWMskLTW6msG/WuKeg1247
|
||||
LjcVvJLsk9FGykA03RGILakxtZ0TRlNBoWrq08iDTnymOqI=
|
||||
=QPTf
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
```
|
||||
|
||||
@@ -46,6 +46,7 @@ nym-sphinx = { path = "../nymsphinx" }
|
||||
nym-statistics-common = { path = "../statistics" }
|
||||
nym-pemstore = { path = "../pemstore" }
|
||||
nym-topology = { path = "../topology", features = ["serializable"] }
|
||||
nym-mixnet-client = { path = "../client-libs/mixnet-client", default-features = false }
|
||||
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
|
||||
nym-task = { path = "../task" }
|
||||
nym-credentials-interface = { path = "../credentials-interface" }
|
||||
|
||||
@@ -14,7 +14,7 @@ use std::os::raw::c_int as RawFd;
|
||||
use thiserror::Error;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use futures::channel::{mpsc, oneshot};
|
||||
use futures::channel::oneshot;
|
||||
|
||||
// we need to type erase the error type since we can't have dynamic associated types alongside dynamic dispatch
|
||||
#[derive(Debug, Error)]
|
||||
@@ -170,7 +170,7 @@ pub struct LocalGateway {
|
||||
|
||||
// 'sender' part
|
||||
/// Channel responsible for taking mix packets and forwarding them further into the further mixnet layers.
|
||||
packet_forwarder: mpsc::UnboundedSender<MixPacket>,
|
||||
packet_forwarder: nym_mixnet_client::forwarder::MixForwardingSender,
|
||||
|
||||
// 'receiver' part
|
||||
packet_router_tx: Option<oneshot::Sender<PacketRouter>>,
|
||||
@@ -180,7 +180,7 @@ pub struct LocalGateway {
|
||||
impl LocalGateway {
|
||||
pub fn new(
|
||||
local_identity: identity::PublicKey,
|
||||
packet_forwarder: mpsc::UnboundedSender<MixPacket>,
|
||||
packet_forwarder: nym_mixnet_client::forwarder::MixForwardingSender,
|
||||
packet_router_tx: oneshot::Sender<PacketRouter>,
|
||||
) -> Self {
|
||||
LocalGateway {
|
||||
@@ -208,8 +208,7 @@ mod nonwasm_sealed {
|
||||
impl GatewaySender for LocalGateway {
|
||||
async fn send_mix_packet(&mut self, packet: MixPacket) -> Result<(), ErasedGatewayError> {
|
||||
self.packet_forwarder
|
||||
.unbounded_send(packet)
|
||||
.map_err(|err| err.into_send_error())
|
||||
.forward_packet(packet)
|
||||
.map_err(erase_err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,8 @@ pub(crate) fn ws_fd(_conn: &WsConn) -> Option<RawFd> {
|
||||
#[cfg(unix)]
|
||||
match _conn.get_ref() {
|
||||
MaybeTlsStream::Plain(stream) => Some(stream.as_raw_fd()),
|
||||
&_ => None,
|
||||
MaybeTlsStream::Rustls(tls_stream) => Some(tls_stream.as_raw_fd()),
|
||||
_ => None,
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
None
|
||||
|
||||
@@ -9,10 +9,14 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
futures = { workspace = true }
|
||||
log = { workspace = true }
|
||||
tokio = { workspace = true, features = ["time", "net", "rt"] }
|
||||
tokio-util = { workspace = true, features = ["codec"] }
|
||||
tracing = { workspace = true }
|
||||
tokio = { workspace = true, features = ["time"] }
|
||||
tokio-util = { workspace = true, features = ["codec"], optional = true }
|
||||
|
||||
# internal
|
||||
nym-sphinx = { path = "../../nymsphinx" }
|
||||
nym-task = { path = "../../task" }
|
||||
nym-task = { path = "../../task", optional = true }
|
||||
|
||||
[features]
|
||||
default = ["client"]
|
||||
client = ["tokio-util", "nym-task", "tokio/net", "tokio/rt"]
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
use futures::channel::mpsc;
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
use nym_sphinx::addressing::nodes::NymNodeRoutingAddress;
|
||||
use nym_sphinx::framing::codec::NymCodec;
|
||||
use nym_sphinx::framing::packet::FramedNymPacket;
|
||||
@@ -18,13 +17,14 @@ use std::time::Duration;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::time::sleep;
|
||||
use tokio_util::codec::Framed;
|
||||
use tracing::*;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Config {
|
||||
initial_reconnection_backoff: Duration,
|
||||
maximum_reconnection_backoff: Duration,
|
||||
initial_connection_timeout: Duration,
|
||||
maximum_connection_buffer_size: usize,
|
||||
use_legacy_version: bool,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
@@ -33,14 +33,12 @@ impl Config {
|
||||
maximum_reconnection_backoff: Duration,
|
||||
initial_connection_timeout: Duration,
|
||||
maximum_connection_buffer_size: usize,
|
||||
use_legacy_version: bool,
|
||||
) -> Self {
|
||||
Config {
|
||||
initial_reconnection_backoff,
|
||||
maximum_reconnection_backoff,
|
||||
initial_connection_timeout,
|
||||
maximum_connection_buffer_size,
|
||||
use_legacy_version,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,9 +198,8 @@ impl SendWithoutResponse for Client {
|
||||
packet: NymPacket,
|
||||
packet_type: PacketType,
|
||||
) -> io::Result<()> {
|
||||
trace!("Sending packet to {:?}", address);
|
||||
let framed_packet =
|
||||
FramedNymPacket::new(packet, packet_type, self.config.use_legacy_version);
|
||||
trace!("Sending packet to {address:?}");
|
||||
let framed_packet = FramedNymPacket::new(packet, packet_type);
|
||||
|
||||
if let Some(sender) = self.conn_new.get_mut(&address) {
|
||||
if let Err(err) = sender.channel.try_send(framed_packet) {
|
||||
@@ -260,7 +257,6 @@ mod tests {
|
||||
maximum_reconnection_backoff: Duration::from_millis(300_000),
|
||||
initial_connection_timeout: Duration::from_millis(1_500),
|
||||
maximum_connection_buffer_size: 128,
|
||||
use_legacy_version: false,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,77 +1,72 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::{Client, Config, SendWithoutResponse};
|
||||
use futures::channel::mpsc;
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
use futures::channel::mpsc::SendError;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use std::time::Duration;
|
||||
use tokio::time::Instant;
|
||||
|
||||
pub type MixForwardingSender = mpsc::UnboundedSender<MixPacket>;
|
||||
type MixForwardingReceiver = mpsc::UnboundedReceiver<MixPacket>;
|
||||
|
||||
/// A specialisation of client such that it forwards any received packets on the channel into the
|
||||
/// mix network immediately, i.e. will not try to listen for any responses.
|
||||
pub struct PacketForwarder {
|
||||
mixnet_client: Client,
|
||||
packet_receiver: MixForwardingReceiver,
|
||||
shutdown: nym_task::TaskClient,
|
||||
pub fn mix_forwarding_channels() -> (MixForwardingSender, MixForwardingReceiver) {
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
(tx.into(), rx)
|
||||
}
|
||||
|
||||
impl PacketForwarder {
|
||||
pub fn new(
|
||||
initial_reconnection_backoff: Duration,
|
||||
maximum_reconnection_backoff: Duration,
|
||||
initial_connection_timeout: Duration,
|
||||
maximum_connection_buffer_size: usize,
|
||||
use_legacy_version: bool,
|
||||
shutdown: nym_task::TaskClient,
|
||||
) -> (PacketForwarder, MixForwardingSender) {
|
||||
let client_config = Config::new(
|
||||
initial_reconnection_backoff,
|
||||
maximum_reconnection_backoff,
|
||||
initial_connection_timeout,
|
||||
maximum_connection_buffer_size,
|
||||
use_legacy_version,
|
||||
);
|
||||
#[derive(Clone)]
|
||||
pub struct MixForwardingSender(mpsc::UnboundedSender<PacketToForward>);
|
||||
|
||||
let (packet_sender, packet_receiver) = mpsc::unbounded();
|
||||
impl From<mpsc::UnboundedSender<PacketToForward>> for MixForwardingSender {
|
||||
fn from(tx: mpsc::UnboundedSender<PacketToForward>) -> Self {
|
||||
MixForwardingSender(tx)
|
||||
}
|
||||
}
|
||||
|
||||
(
|
||||
PacketForwarder {
|
||||
mixnet_client: Client::new(client_config),
|
||||
packet_receiver,
|
||||
shutdown,
|
||||
},
|
||||
packet_sender,
|
||||
)
|
||||
impl MixForwardingSender {
|
||||
pub fn forward_packet(&self, packet: impl Into<PacketToForward>) -> Result<(), SendError> {
|
||||
self.0
|
||||
.unbounded_send(packet.into())
|
||||
.map_err(|err| err.into_send_error())
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) {
|
||||
while !self.shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = self.shutdown.recv() => {
|
||||
log::trace!("PacketForwarder: Received shutdown");
|
||||
}
|
||||
Some(mix_packet) = self.packet_receiver.next() => {
|
||||
trace!("Going to forward packet to {}", mix_packet.next_hop());
|
||||
#[allow(clippy::len_without_is_empty)]
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
}
|
||||
|
||||
let next_hop = mix_packet.next_hop();
|
||||
let packet_type = mix_packet.packet_type();
|
||||
let packet = mix_packet.into_packet();
|
||||
// we don't care about responses, we just want to fire packets
|
||||
// as quickly as possible
|
||||
pub type MixForwardingReceiver = mpsc::UnboundedReceiver<PacketToForward>;
|
||||
|
||||
if let Err(err) =
|
||||
self.mixnet_client
|
||||
.send_without_response(next_hop, packet, packet_type)
|
||||
{
|
||||
debug!("failed to forward the packet - {err}")
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct PacketToForward {
|
||||
pub packet: MixPacket,
|
||||
pub forward_delay_target: Option<Instant>,
|
||||
}
|
||||
|
||||
impl From<MixPacket> for PacketToForward {
|
||||
fn from(packet: MixPacket) -> Self {
|
||||
PacketToForward::new_no_delay(packet)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(MixPacket, Option<Instant>)> for PacketToForward {
|
||||
fn from((packet, delay_until): (MixPacket, Option<Instant>)) -> Self {
|
||||
PacketToForward::new(packet, delay_until)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(MixPacket, Instant)> for PacketToForward {
|
||||
fn from((packet, delay_until): (MixPacket, Instant)) -> Self {
|
||||
PacketToForward::new(packet, Some(delay_until))
|
||||
}
|
||||
}
|
||||
|
||||
impl PacketToForward {
|
||||
pub fn new(packet: MixPacket, forward_delay_target: Option<Instant>) -> Self {
|
||||
PacketToForward {
|
||||
packet,
|
||||
forward_delay_target,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_no_delay(packet: MixPacket) -> Self {
|
||||
Self::new(packet, None)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
pub mod client;
|
||||
pub mod forwarder;
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
pub use client::{Client, Config, SendWithoutResponse};
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::ClientBandwidth;
|
||||
use nym_credentials::ecash::utils::ecash_today;
|
||||
use nym_credentials_interface::Bandwidth;
|
||||
use nym_gateway_requests::ServerResponse;
|
||||
use nym_gateway_storage::Storage;
|
||||
use nym_gateway_storage::GatewayStorage;
|
||||
use si_scale::helpers::bibytes2;
|
||||
use time::OffsetDateTime;
|
||||
use tracing::*;
|
||||
@@ -15,17 +15,17 @@ use tracing::*;
|
||||
const FREE_TESTNET_BANDWIDTH_VALUE: Bandwidth = Bandwidth::new_unchecked(64 * 1024 * 1024 * 1024); // 64GB
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BandwidthStorageManager<S> {
|
||||
pub(crate) storage: S,
|
||||
pub struct BandwidthStorageManager {
|
||||
pub(crate) storage: GatewayStorage,
|
||||
pub(crate) client_bandwidth: ClientBandwidth,
|
||||
pub(crate) client_id: i64,
|
||||
pub(crate) bandwidth_cfg: BandwidthFlushingBehaviourConfig,
|
||||
pub(crate) only_coconut_credentials: bool,
|
||||
}
|
||||
|
||||
impl<S: Storage + Clone + 'static> BandwidthStorageManager<S> {
|
||||
impl BandwidthStorageManager {
|
||||
pub fn new(
|
||||
storage: S,
|
||||
storage: GatewayStorage,
|
||||
client_bandwidth: ClientBandwidth,
|
||||
client_id: i64,
|
||||
bandwidth_cfg: BandwidthFlushingBehaviourConfig,
|
||||
|
||||
@@ -13,7 +13,6 @@ use nym_api_requests::constants::MIN_BATCH_REDEMPTION_DELAY;
|
||||
use nym_api_requests::ecash::models::{BatchRedeemTicketsBody, VerifyEcashTicketBody};
|
||||
use nym_credentials_interface::Bandwidth;
|
||||
use nym_credentials_interface::{ClientTicket, TicketType};
|
||||
use nym_gateway_storage::Storage;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nyxd::contract_traits::{
|
||||
EcashSigningClient, MultisigQueryClient, MultisigSigningClient, PagedMultisigQueryClient,
|
||||
@@ -126,21 +125,18 @@ pub struct CredentialHandlerConfig {
|
||||
pub maximum_time_between_redemption: Duration,
|
||||
}
|
||||
|
||||
pub(crate) struct CredentialHandler<St: Storage> {
|
||||
pub(crate) struct CredentialHandler {
|
||||
config: CredentialHandlerConfig,
|
||||
multisig_threshold: f32,
|
||||
ticket_receiver: UnboundedReceiver<ClientTicket>,
|
||||
shared_state: SharedState<St>,
|
||||
shared_state: SharedState,
|
||||
pending_tickets: Vec<PendingVerification>,
|
||||
pending_redemptions: Vec<PendingRedemptionVote>,
|
||||
}
|
||||
|
||||
impl<St> CredentialHandler<St>
|
||||
where
|
||||
St: Storage + Clone + 'static,
|
||||
{
|
||||
impl CredentialHandler {
|
||||
async fn rebuild_pending_tickets(
|
||||
shared_state: &SharedState<St>,
|
||||
shared_state: &SharedState,
|
||||
) -> Result<Vec<PendingVerification>, EcashTicketError> {
|
||||
// 1. get all tickets that were not fully verified
|
||||
let unverified = shared_state.storage.get_all_unverified_tickets().await?;
|
||||
@@ -188,7 +184,7 @@ where
|
||||
}
|
||||
|
||||
async fn rebuild_pending_votes(
|
||||
shared_state: &SharedState<St>,
|
||||
shared_state: &SharedState,
|
||||
) -> Result<Vec<PendingRedemptionVote>, EcashTicketError> {
|
||||
// 1. get all tickets that were not fully verified
|
||||
let unverified = shared_state.storage.get_all_unresolved_proposals().await?;
|
||||
@@ -259,7 +255,7 @@ where
|
||||
pub(crate) async fn new(
|
||||
config: CredentialHandlerConfig,
|
||||
ticket_receiver: UnboundedReceiver<ClientTicket>,
|
||||
shared_state: SharedState<St>,
|
||||
shared_state: SharedState,
|
||||
) -> Result<Self, Error> {
|
||||
let multisig_threshold = shared_state
|
||||
.nyxd_client
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_gateway_storage::error::StorageError;
|
||||
use nym_gateway_storage::error::GatewayStorageError;
|
||||
use nym_validator_client::coconut::EcashApiError;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nyxd::error::NyxdError;
|
||||
@@ -37,7 +37,7 @@ pub enum EcashTicketError {
|
||||
#[error("could not handle the ecash ticket due to internal storage failure: {source}")]
|
||||
InternalStorageFailure {
|
||||
#[from]
|
||||
source: StorageError,
|
||||
source: GatewayStorageError,
|
||||
},
|
||||
|
||||
#[error("failed to create ticket redemption proposal: {source}")]
|
||||
|
||||
@@ -8,7 +8,7 @@ use error::EcashTicketError;
|
||||
use futures::channel::mpsc::{self, UnboundedSender};
|
||||
use nym_credentials::CredentialSpendingData;
|
||||
use nym_credentials_interface::{ClientTicket, CompactEcashError, NymPayInfo, VerificationKeyAuth};
|
||||
use nym_gateway_storage::Storage;
|
||||
use nym_gateway_storage::GatewayStorage;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::DirectSigningHttpRpcNyxdClient;
|
||||
use state::SharedState;
|
||||
@@ -23,24 +23,21 @@ mod state;
|
||||
|
||||
pub const TIME_RANGE_SEC: i64 = 30;
|
||||
|
||||
pub struct EcashManager<S> {
|
||||
shared_state: SharedState<S>,
|
||||
pub struct EcashManager {
|
||||
shared_state: SharedState,
|
||||
|
||||
pk_bytes: [u8; 32], // bytes representation of a pub key representing the verifier
|
||||
pay_infos: Mutex<Vec<NymPayInfo>>,
|
||||
cred_sender: UnboundedSender<ClientTicket>,
|
||||
}
|
||||
|
||||
impl<S> EcashManager<S>
|
||||
where
|
||||
S: Storage + Clone + 'static,
|
||||
{
|
||||
impl EcashManager {
|
||||
pub async fn new(
|
||||
credential_handler_cfg: CredentialHandlerConfig,
|
||||
nyxd_client: DirectSigningHttpRpcNyxdClient,
|
||||
pk_bytes: [u8; 32],
|
||||
shutdown: nym_task::TaskClient,
|
||||
storage: S,
|
||||
storage: GatewayStorage,
|
||||
) -> Result<Self, Error> {
|
||||
let shared_state = SharedState::new(nyxd_client, storage).await?;
|
||||
|
||||
@@ -66,7 +63,7 @@ where
|
||||
self.shared_state.verification_key(epoch_id).await
|
||||
}
|
||||
|
||||
pub fn storage(&self) -> &S {
|
||||
pub fn storage(&self) -> &GatewayStorage {
|
||||
&self.shared_state.storage
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::Error;
|
||||
use cosmwasm_std::{from_binary, CosmosMsg, WasmMsg};
|
||||
use nym_credentials_interface::VerificationKeyAuth;
|
||||
use nym_ecash_contract_common::msg::ExecuteMsg;
|
||||
use nym_gateway_storage::Storage;
|
||||
use nym_gateway_storage::GatewayStorage;
|
||||
use nym_validator_client::coconut::all_ecash_api_clients;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nyxd::contract_traits::{
|
||||
@@ -23,20 +23,17 @@ use tracing::{error, trace, warn};
|
||||
|
||||
// state shared by different subtasks dealing with credentials
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct SharedState<S> {
|
||||
pub(crate) struct SharedState {
|
||||
pub(crate) nyxd_client: Arc<RwLock<DirectSigningHttpRpcNyxdClient>>,
|
||||
pub(crate) address: AccountId,
|
||||
pub(crate) epoch_data: Arc<RwLock<BTreeMap<EpochId, EpochState>>>,
|
||||
pub(crate) storage: S,
|
||||
pub(crate) storage: GatewayStorage,
|
||||
}
|
||||
|
||||
impl<S> SharedState<S>
|
||||
where
|
||||
S: Storage + Clone,
|
||||
{
|
||||
impl SharedState {
|
||||
pub(crate) async fn new(
|
||||
nyxd_client: DirectSigningHttpRpcNyxdClient,
|
||||
storage: S,
|
||||
storage: GatewayStorage,
|
||||
) -> Result<Self, Error> {
|
||||
let address = nyxd_client.address();
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ pub enum Error {
|
||||
OutOfBandwidth { required: i64, available: i64 },
|
||||
|
||||
#[error("Internal gateway storage error")]
|
||||
StorageError(#[from] nym_gateway_storage::error::StorageError),
|
||||
StorageError(#[from] nym_gateway_storage::error::GatewayStorageError),
|
||||
|
||||
#[error("{0}")]
|
||||
UnknownTicketType(#[from] nym_credentials_interface::UnknownTicketType),
|
||||
|
||||
@@ -2,17 +2,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use bandwidth_storage_manager::BandwidthStorageManager;
|
||||
use ecash::EcashManager;
|
||||
use nym_credentials::ecash::utils::{cred_exp_date, ecash_today, EcashTime};
|
||||
use nym_credentials_interface::{Bandwidth, ClientTicket, TicketType};
|
||||
use nym_gateway_requests::models::CredentialSpendingRequest;
|
||||
use std::sync::Arc;
|
||||
use time::{Date, OffsetDateTime};
|
||||
use tracing::*;
|
||||
|
||||
use nym_credentials::ecash::utils::{cred_exp_date, ecash_today, EcashTime};
|
||||
use nym_credentials_interface::{Bandwidth, ClientTicket, TicketType};
|
||||
use nym_gateway_requests::models::CredentialSpendingRequest;
|
||||
use nym_gateway_storage::Storage;
|
||||
|
||||
pub use client_bandwidth::*;
|
||||
use ecash::EcashManager;
|
||||
pub use error::*;
|
||||
|
||||
pub mod bandwidth_storage_manager;
|
||||
@@ -20,17 +18,17 @@ mod client_bandwidth;
|
||||
pub mod ecash;
|
||||
pub mod error;
|
||||
|
||||
pub struct CredentialVerifier<S> {
|
||||
pub struct CredentialVerifier {
|
||||
credential: CredentialSpendingRequest,
|
||||
ecash_verifier: Arc<EcashManager<S>>,
|
||||
bandwidth_storage_manager: BandwidthStorageManager<S>,
|
||||
ecash_verifier: Arc<EcashManager>,
|
||||
bandwidth_storage_manager: BandwidthStorageManager,
|
||||
}
|
||||
|
||||
impl<S: Storage + Clone + 'static> CredentialVerifier<S> {
|
||||
impl CredentialVerifier {
|
||||
pub fn new(
|
||||
credential: CredentialSpendingRequest,
|
||||
ecash_verifier: Arc<EcashManager<S>>,
|
||||
bandwidth_storage_manager: BandwidthStorageManager<S>,
|
||||
ecash_verifier: Arc<EcashManager>,
|
||||
bandwidth_storage_manager: BandwidthStorageManager,
|
||||
) -> Self {
|
||||
CredentialVerifier {
|
||||
credential,
|
||||
|
||||
@@ -225,8 +225,10 @@ impl From<PayInfo> for NymPayInfo {
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Hash,
|
||||
strum::Display,
|
||||
strum::EnumString,
|
||||
strum::EnumIter,
|
||||
|
||||
@@ -41,6 +41,6 @@ aead = ["dep:aead", "aead/std", "aes-gcm-siv", "generic-array"]
|
||||
serde = ["dep:serde", "serde_bytes", "ed25519-dalek/serde", "x25519-dalek/serde"]
|
||||
asymmetric = ["x25519-dalek", "ed25519-dalek", "zeroize"]
|
||||
hashing = ["blake3", "digest", "hkdf", "hmac", "generic-array"]
|
||||
stream_cipher = ["aes", "ctr", "cipher", "generic-array"]
|
||||
stream_cipher = ["aes", "ctr", "cipher", "cipher/zeroize", "generic-array"]
|
||||
sphinx = ["nym-sphinx-types/sphinx"]
|
||||
outfox = ["nym-sphinx-types/outfox"]
|
||||
|
||||
@@ -16,12 +16,14 @@ sqlx = { workspace = true, features = [
|
||||
"migrate",
|
||||
"time",
|
||||
] }
|
||||
strum = { workspace = true }
|
||||
time = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
nym-sphinx = { path = "../nymsphinx" }
|
||||
nym-credentials-interface = { path = "../credentials-interface" }
|
||||
nym-node-metrics = { path = "../../nym-node/nym-node-metrics" }
|
||||
nym-statistics-common = { path = "../statistics" }
|
||||
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use error::StatsStorageError;
|
||||
use models::{ActiveSession, FinishedSession, StoredFinishedSession};
|
||||
use models::StoredFinishedSession;
|
||||
use nym_node_metrics::entry::{ActiveSession, FinishedSession, SessionType};
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use nym_statistics_common::gateways::SessionType;
|
||||
use sessions::SessionManager;
|
||||
use sqlx::ConnectOptions;
|
||||
use std::path::Path;
|
||||
@@ -71,8 +71,8 @@ impl PersistentStatsStorage {
|
||||
.session_manager
|
||||
.insert_finished_session(
|
||||
date,
|
||||
session.duration.whole_milliseconds() as i64,
|
||||
session.typ.to_string().into(),
|
||||
session.duration.as_millis() as i64,
|
||||
session.typ.to_string(),
|
||||
)
|
||||
.await?)
|
||||
}
|
||||
@@ -126,7 +126,7 @@ impl PersistentStatsStorage {
|
||||
.insert_active_session(
|
||||
client_address.as_base58_string(),
|
||||
session.start,
|
||||
session.typ.to_string().into(),
|
||||
session.typ.to_string(),
|
||||
)
|
||||
.await?)
|
||||
}
|
||||
@@ -138,10 +138,7 @@ impl PersistentStatsStorage {
|
||||
) -> Result<(), StatsStorageError> {
|
||||
Ok(self
|
||||
.session_manager
|
||||
.update_active_session_type(
|
||||
client_address.as_base58_string(),
|
||||
session_type.to_string().into(),
|
||||
)
|
||||
.update_active_session_type(client_address.as_base58_string(), session_type.to_string())
|
||||
.await?)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_credentials_interface::TicketType;
|
||||
use nym_statistics_common::gateways::SessionType;
|
||||
use nym_node_metrics::entry::{ActiveSession, FinishedSession, SessionType};
|
||||
use sqlx::prelude::FromRow;
|
||||
use time::{Duration, OffsetDateTime};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
pub use nym_credentials_interface::TicketType;
|
||||
|
||||
#[derive(FromRow)]
|
||||
pub struct StoredFinishedSession {
|
||||
@@ -12,18 +13,28 @@ pub struct StoredFinishedSession {
|
||||
typ: String,
|
||||
}
|
||||
|
||||
impl StoredFinishedSession {
|
||||
pub fn serialize(&self) -> (u64, String) {
|
||||
(
|
||||
self.duration_ms as u64, //we are sure that it fits in a u64, see `fn end_at`
|
||||
self.typ.clone(),
|
||||
)
|
||||
impl From<StoredFinishedSession> for FinishedSession {
|
||||
fn from(value: StoredFinishedSession) -> Self {
|
||||
FinishedSession {
|
||||
duration: std::time::Duration::from_millis(value.duration_ms as u64),
|
||||
typ: SessionType::from_string(value.typ),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FinishedSession {
|
||||
pub duration: Duration,
|
||||
pub typ: SessionType,
|
||||
pub trait ToSessionType {
|
||||
fn to_session_type(&self) -> SessionType;
|
||||
}
|
||||
|
||||
impl ToSessionType for TicketType {
|
||||
fn to_session_type(&self) -> SessionType {
|
||||
match self {
|
||||
TicketType::V1MixnetEntry => SessionType::Mixnet,
|
||||
TicketType::V1MixnetExit => SessionType::Mixnet,
|
||||
TicketType::V1WireguardEntry => SessionType::Vpn,
|
||||
TicketType::V1WireguardExit => SessionType::Vpn,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromRow)]
|
||||
@@ -32,38 +43,6 @@ pub(crate) struct StoredActiveSession {
|
||||
typ: String,
|
||||
}
|
||||
|
||||
pub struct ActiveSession {
|
||||
pub start: OffsetDateTime,
|
||||
pub typ: SessionType,
|
||||
}
|
||||
|
||||
impl ActiveSession {
|
||||
pub fn new(start_time: OffsetDateTime) -> Self {
|
||||
ActiveSession {
|
||||
start: start_time,
|
||||
typ: SessionType::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_type(&mut self, ticket_type: TicketType) {
|
||||
self.typ = ticket_type.into();
|
||||
}
|
||||
|
||||
pub fn end_at(self, stop_time: OffsetDateTime) -> Option<FinishedSession> {
|
||||
let session_duration = stop_time - self.start;
|
||||
//ensure duration is positive to fit in a u64
|
||||
//u64::max milliseconds is 500k millenia so no overflow issue
|
||||
if session_duration > Duration::ZERO {
|
||||
Some(FinishedSession {
|
||||
duration: session_duration,
|
||||
typ: self.typ,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StoredActiveSession> for ActiveSession {
|
||||
fn from(value: StoredActiveSession) -> Self {
|
||||
ActiveSession {
|
||||
|
||||
@@ -9,7 +9,6 @@ edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
bincode = { workspace = true }
|
||||
defguard_wireguard_rs = { workspace = true }
|
||||
log = { workspace = true }
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum StorageError {
|
||||
pub enum GatewayStorageError {
|
||||
#[error("Database experienced an internal error: {0}")]
|
||||
InternalDatabaseError(#[from] sqlx::Error),
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bandwidth::BandwidthManager;
|
||||
use clients::{ClientManager, ClientType};
|
||||
use error::StorageError;
|
||||
use inboxes::InboxManager;
|
||||
use models::{
|
||||
Client, PersistedBandwidth, PersistedSharedKeys, RedemptionProposal, StoredMessage,
|
||||
@@ -29,237 +27,11 @@ mod shared_keys;
|
||||
mod tickets;
|
||||
mod wireguard_peers;
|
||||
|
||||
#[async_trait]
|
||||
pub trait Storage: Send + Sync {
|
||||
async fn get_mixnet_client_id(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
) -> Result<i64, StorageError>;
|
||||
|
||||
/// Inserts provided derived shared keys into the database.
|
||||
/// If keys previously existed for the provided client, they are overwritten with the new data.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `client_address`: base58-encoded address of the client
|
||||
/// * `shared_keys`:
|
||||
/// - legacy: shared encryption (AES128CTR) and mac (hmac-blake3) derived shared keys to store.
|
||||
/// - current: shared AES256-GCM-SIV keys
|
||||
async fn insert_shared_keys(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
shared_keys: &SharedGatewayKey,
|
||||
) -> Result<i64, StorageError>;
|
||||
|
||||
/// Tries to retrieve shared keys stored for the particular client.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `client_address`: address of the client
|
||||
async fn get_shared_keys(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
) -> Result<Option<PersistedSharedKeys>, StorageError>;
|
||||
|
||||
/// Removes from the database shared keys derived with the particular client.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `client_address`: address of the client
|
||||
// currently there is no code flow that causes removal (not overwriting)
|
||||
// of the stored keys. However, retain the function for consistency and completion sake
|
||||
#[allow(dead_code)]
|
||||
async fn remove_shared_keys(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
) -> Result<(), StorageError>;
|
||||
|
||||
/// Tries to retrieve a particular client.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `client_id`: id of the client
|
||||
#[allow(dead_code)]
|
||||
async fn get_client(&self, client_id: i64) -> Result<Option<Client>, StorageError>;
|
||||
|
||||
/// Inserts new message to the storage for an offline client for future retrieval.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `client_address`: address of the client
|
||||
/// * `message`: raw message to store.
|
||||
async fn store_message(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
message: Vec<u8>,
|
||||
) -> Result<(), StorageError>;
|
||||
|
||||
/// Retrieves messages stored for the particular client specified by the provided address.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `client_address`: address of the client
|
||||
/// * `start_after`: optional starting id of the messages to grab
|
||||
///
|
||||
/// returns the retrieved messages alongside optional id of the last message retrieved if
|
||||
/// there are more messages to retrieve.
|
||||
async fn retrieve_messages(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
start_after: Option<i64>,
|
||||
) -> Result<(Vec<StoredMessage>, Option<i64>), StorageError>;
|
||||
|
||||
/// Removes messages with the specified ids
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `ids`: ids of the messages to remove
|
||||
async fn remove_messages(&self, ids: Vec<i64>) -> Result<(), StorageError>;
|
||||
|
||||
/// Creates a new bandwidth entry for the particular client.
|
||||
async fn create_bandwidth_entry(&self, client_id: i64) -> Result<(), StorageError>;
|
||||
|
||||
/// Set the freepass expiration date of the particular client to the provided date.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `client_address`: address of the client
|
||||
/// * `expiration`: the expiration date of the associated free pass.
|
||||
async fn set_expiration(
|
||||
&self,
|
||||
client_id: i64,
|
||||
expiration: OffsetDateTime,
|
||||
) -> Result<(), StorageError>;
|
||||
|
||||
/// Reset all the bandwidth
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `client_address`: address of the client
|
||||
async fn reset_bandwidth(&self, client_id: i64) -> Result<(), StorageError>;
|
||||
|
||||
/// Tries to retrieve available bandwidth for the particular client.
|
||||
async fn get_available_bandwidth(
|
||||
&self,
|
||||
client_id: i64,
|
||||
) -> Result<Option<PersistedBandwidth>, StorageError>;
|
||||
|
||||
/// Increases specified client's bandwidth by the provided amount and returns the current value.
|
||||
async fn increase_bandwidth(&self, client_id: i64, amount: i64) -> Result<i64, StorageError>;
|
||||
|
||||
async fn revoke_ticket_bandwidth(
|
||||
&self,
|
||||
ticket_id: i64,
|
||||
amount: i64,
|
||||
) -> Result<(), StorageError>;
|
||||
|
||||
#[allow(dead_code)]
|
||||
/// Decreases specified client's bandwidth by the provided amount and returns the current value.
|
||||
async fn decrease_bandwidth(&self, client_id: i64, amount: i64) -> Result<i64, StorageError>;
|
||||
|
||||
async fn insert_epoch_signers(
|
||||
&self,
|
||||
epoch_id: i64,
|
||||
signer_ids: Vec<i64>,
|
||||
) -> Result<(), StorageError>;
|
||||
|
||||
async fn insert_received_ticket(
|
||||
&self,
|
||||
client_id: i64,
|
||||
received_at: OffsetDateTime,
|
||||
serial_number: Vec<u8>,
|
||||
data: Vec<u8>,
|
||||
) -> Result<i64, StorageError>;
|
||||
|
||||
// note: this only checks very recent tickets that haven't yet been redeemed
|
||||
// (but it's better than nothing)
|
||||
/// Check if the ticket with the provided serial number if already present in the storage.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `serial_number`: the unique serial number embedded in the ticket
|
||||
async fn contains_ticket(&self, serial_number: &[u8]) -> Result<bool, StorageError>;
|
||||
|
||||
async fn insert_ticket_verification(
|
||||
&self,
|
||||
ticket_id: i64,
|
||||
signer_id: i64,
|
||||
verified_at: OffsetDateTime,
|
||||
accepted: bool,
|
||||
) -> Result<(), StorageError>;
|
||||
|
||||
async fn update_rejected_ticket(&self, ticket_id: i64) -> Result<(), StorageError>;
|
||||
|
||||
async fn update_verified_ticket(&self, ticket_id: i64) -> Result<(), StorageError>;
|
||||
|
||||
async fn remove_verified_ticket_binary_data(&self, ticket_id: i64) -> Result<(), StorageError>;
|
||||
|
||||
async fn get_all_verified_tickets_with_sn(&self) -> Result<Vec<VerifiedTicket>, StorageError>;
|
||||
async fn get_all_proposed_tickets_with_sn(
|
||||
&self,
|
||||
proposal_id: u32,
|
||||
) -> Result<Vec<VerifiedTicket>, StorageError>;
|
||||
|
||||
async fn insert_redemption_proposal(
|
||||
&self,
|
||||
tickets: &[VerifiedTicket],
|
||||
proposal_id: u32,
|
||||
created_at: OffsetDateTime,
|
||||
) -> Result<(), StorageError>;
|
||||
|
||||
async fn clear_post_proposal_data(
|
||||
&self,
|
||||
proposal_id: u32,
|
||||
resolved_at: OffsetDateTime,
|
||||
rejected: bool,
|
||||
) -> Result<(), StorageError>;
|
||||
|
||||
async fn latest_proposal(&self) -> Result<Option<RedemptionProposal>, StorageError>;
|
||||
|
||||
async fn get_all_unverified_tickets(&self) -> Result<Vec<ClientTicket>, StorageError>;
|
||||
async fn get_all_unresolved_proposals(&self) -> Result<Vec<i64>, StorageError>;
|
||||
async fn get_votes(&self, ticket_id: i64) -> Result<Vec<i64>, StorageError>;
|
||||
|
||||
async fn get_signers(&self, epoch_id: i64) -> Result<Vec<i64>, StorageError>;
|
||||
|
||||
/// Insert a wireguard peer in the storage.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `peer`: wireguard peer data to be stored
|
||||
/// * `with_client_id`: if the peer should have a corresponding client_id
|
||||
/// (created with entry wireguard ticket) or live without one (or with an
|
||||
/// exiting one), for temporary backwards compatibility.
|
||||
async fn insert_wireguard_peer(
|
||||
&self,
|
||||
peer: &defguard_wireguard_rs::host::Peer,
|
||||
with_client_id: bool,
|
||||
) -> Result<Option<i64>, StorageError>;
|
||||
|
||||
/// Tries to retrieve available bandwidth for the particular peer.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `peer_public_key`: wireguard public key of the peer to be retrieved.
|
||||
async fn get_wireguard_peer(
|
||||
&self,
|
||||
peer_public_key: &str,
|
||||
) -> Result<Option<WireguardPeer>, StorageError>;
|
||||
|
||||
/// Retrieves all wireguard peers.
|
||||
async fn get_all_wireguard_peers(&self) -> Result<Vec<WireguardPeer>, StorageError>;
|
||||
|
||||
/// Remove a wireguard peer from the storage.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `peer_public_key`: wireguard public key of the peer to be removed.
|
||||
async fn remove_wireguard_peer(&self, peer_public_key: &str) -> Result<(), StorageError>;
|
||||
}
|
||||
pub use error::GatewayStorageError;
|
||||
|
||||
// note that clone here is fine as upon cloning the same underlying pool will be used
|
||||
#[derive(Clone)]
|
||||
pub struct PersistentStorage {
|
||||
pub struct GatewayStorage {
|
||||
client_manager: ClientManager,
|
||||
shared_key_manager: SharedKeysManager,
|
||||
inbox_manager: InboxManager,
|
||||
@@ -268,7 +40,7 @@ pub struct PersistentStorage {
|
||||
wireguard_peer_manager: wireguard_peers::WgPeerManager,
|
||||
}
|
||||
|
||||
impl PersistentStorage {
|
||||
impl GatewayStorage {
|
||||
/// Initialises `PersistentStorage` using the provided path.
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -278,7 +50,7 @@ impl PersistentStorage {
|
||||
pub async fn init<P: AsRef<Path> + Send>(
|
||||
database_path: P,
|
||||
message_retrieval_limit: i64,
|
||||
) -> Result<Self, StorageError> {
|
||||
) -> Result<Self, GatewayStorageError> {
|
||||
debug!(
|
||||
"Attempting to connect to database {:?}",
|
||||
database_path.as_ref().as_os_str()
|
||||
@@ -307,7 +79,7 @@ impl PersistentStorage {
|
||||
}
|
||||
|
||||
// the cloning here are cheap as connection pool is stored behind an Arc
|
||||
Ok(PersistentStorage {
|
||||
Ok(GatewayStorage {
|
||||
client_manager: clients::ClientManager::new(connection_pool.clone()),
|
||||
wireguard_peer_manager: wireguard_peers::WgPeerManager::new(connection_pool.clone()),
|
||||
shared_key_manager: SharedKeysManager::new(connection_pool.clone()),
|
||||
@@ -318,23 +90,22 @@ impl PersistentStorage {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Storage for PersistentStorage {
|
||||
async fn get_mixnet_client_id(
|
||||
impl GatewayStorage {
|
||||
pub async fn get_mixnet_client_id(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
) -> Result<i64, StorageError> {
|
||||
) -> Result<i64, GatewayStorageError> {
|
||||
Ok(self
|
||||
.shared_key_manager
|
||||
.client_id(&client_address.as_base58_string())
|
||||
.await?)
|
||||
}
|
||||
|
||||
async fn insert_shared_keys(
|
||||
pub async fn insert_shared_keys(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
shared_keys: &SharedGatewayKey,
|
||||
) -> Result<i64, StorageError> {
|
||||
) -> Result<i64, GatewayStorageError> {
|
||||
let client_address_bs58 = client_address.as_base58_string();
|
||||
let client_id = match self
|
||||
.shared_key_manager
|
||||
@@ -359,10 +130,10 @@ impl Storage for PersistentStorage {
|
||||
Ok(client_id)
|
||||
}
|
||||
|
||||
async fn get_shared_keys(
|
||||
pub async fn get_shared_keys(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
) -> Result<Option<PersistedSharedKeys>, StorageError> {
|
||||
) -> Result<Option<PersistedSharedKeys>, GatewayStorageError> {
|
||||
let keys = self
|
||||
.shared_key_manager
|
||||
.get_shared_keys(&client_address.as_base58_string())
|
||||
@@ -371,37 +142,37 @@ impl Storage for PersistentStorage {
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
async fn remove_shared_keys(
|
||||
pub async fn remove_shared_keys(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
) -> Result<(), StorageError> {
|
||||
) -> Result<(), GatewayStorageError> {
|
||||
self.shared_key_manager
|
||||
.remove_shared_keys(&client_address.as_base58_string())
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_client(&self, client_id: i64) -> Result<Option<Client>, StorageError> {
|
||||
pub async fn get_client(&self, client_id: i64) -> Result<Option<Client>, GatewayStorageError> {
|
||||
let client = self.client_manager.get_client(client_id).await?;
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
async fn store_message(
|
||||
pub async fn store_message(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
message: Vec<u8>,
|
||||
) -> Result<(), StorageError> {
|
||||
) -> Result<(), GatewayStorageError> {
|
||||
self.inbox_manager
|
||||
.insert_message(&client_address.as_base58_string(), message)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn retrieve_messages(
|
||||
pub async fn retrieve_messages(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
start_after: Option<i64>,
|
||||
) -> Result<(Vec<StoredMessage>, Option<i64>), StorageError> {
|
||||
) -> Result<(Vec<StoredMessage>, Option<i64>), GatewayStorageError> {
|
||||
let messages = self
|
||||
.inbox_manager
|
||||
.get_messages(&client_address.as_base58_string(), start_after)
|
||||
@@ -409,87 +180,95 @@ impl Storage for PersistentStorage {
|
||||
Ok(messages)
|
||||
}
|
||||
|
||||
async fn remove_messages(&self, ids: Vec<i64>) -> Result<(), StorageError> {
|
||||
pub async fn remove_messages(&self, ids: Vec<i64>) -> Result<(), GatewayStorageError> {
|
||||
for id in ids {
|
||||
self.inbox_manager.remove_message(id).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn create_bandwidth_entry(&self, client_id: i64) -> Result<(), StorageError> {
|
||||
pub async fn create_bandwidth_entry(&self, client_id: i64) -> Result<(), GatewayStorageError> {
|
||||
self.bandwidth_manager.insert_new_client(client_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn set_expiration(
|
||||
pub async fn set_expiration(
|
||||
&self,
|
||||
client_id: i64,
|
||||
expiration: OffsetDateTime,
|
||||
) -> Result<(), StorageError> {
|
||||
) -> Result<(), GatewayStorageError> {
|
||||
self.bandwidth_manager
|
||||
.set_expiration(client_id, expiration)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn reset_bandwidth(&self, client_id: i64) -> Result<(), StorageError> {
|
||||
pub async fn reset_bandwidth(&self, client_id: i64) -> Result<(), GatewayStorageError> {
|
||||
self.bandwidth_manager.reset_bandwidth(client_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_available_bandwidth(
|
||||
pub async fn get_available_bandwidth(
|
||||
&self,
|
||||
client_id: i64,
|
||||
) -> Result<Option<PersistedBandwidth>, StorageError> {
|
||||
) -> Result<Option<PersistedBandwidth>, GatewayStorageError> {
|
||||
Ok(self
|
||||
.bandwidth_manager
|
||||
.get_available_bandwidth(client_id)
|
||||
.await?)
|
||||
}
|
||||
|
||||
async fn increase_bandwidth(&self, client_id: i64, amount: i64) -> Result<i64, StorageError> {
|
||||
pub async fn increase_bandwidth(
|
||||
&self,
|
||||
client_id: i64,
|
||||
amount: i64,
|
||||
) -> Result<i64, GatewayStorageError> {
|
||||
Ok(self
|
||||
.bandwidth_manager
|
||||
.increase_bandwidth(client_id, amount)
|
||||
.await?)
|
||||
}
|
||||
|
||||
async fn revoke_ticket_bandwidth(
|
||||
pub async fn revoke_ticket_bandwidth(
|
||||
&self,
|
||||
ticket_id: i64,
|
||||
amount: i64,
|
||||
) -> Result<(), StorageError> {
|
||||
) -> Result<(), GatewayStorageError> {
|
||||
Ok(self
|
||||
.bandwidth_manager
|
||||
.revoke_ticket_bandwidth(ticket_id, amount)
|
||||
.await?)
|
||||
}
|
||||
|
||||
async fn decrease_bandwidth(&self, client_id: i64, amount: i64) -> Result<i64, StorageError> {
|
||||
pub async fn decrease_bandwidth(
|
||||
&self,
|
||||
client_id: i64,
|
||||
amount: i64,
|
||||
) -> Result<i64, GatewayStorageError> {
|
||||
Ok(self
|
||||
.bandwidth_manager
|
||||
.decrease_bandwidth(client_id, amount)
|
||||
.await?)
|
||||
}
|
||||
|
||||
async fn insert_epoch_signers(
|
||||
pub async fn insert_epoch_signers(
|
||||
&self,
|
||||
epoch_id: i64,
|
||||
signer_ids: Vec<i64>,
|
||||
) -> Result<(), StorageError> {
|
||||
) -> Result<(), GatewayStorageError> {
|
||||
self.ticket_manager
|
||||
.insert_ecash_signers(epoch_id, signer_ids)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn insert_received_ticket(
|
||||
pub async fn insert_received_ticket(
|
||||
&self,
|
||||
client_id: i64,
|
||||
received_at: OffsetDateTime,
|
||||
serial_number: Vec<u8>,
|
||||
data: Vec<u8>,
|
||||
) -> Result<i64, StorageError> {
|
||||
) -> Result<i64, GatewayStorageError> {
|
||||
// technically if we crash between those 2 calls we'll have a bit of data inconsistency,
|
||||
// but nothing too tragic. we just won't get paid for a single ticket
|
||||
let ticket_id = self
|
||||
@@ -503,24 +282,24 @@ impl Storage for PersistentStorage {
|
||||
Ok(ticket_id)
|
||||
}
|
||||
|
||||
async fn contains_ticket(&self, serial_number: &[u8]) -> Result<bool, StorageError> {
|
||||
pub async fn contains_ticket(&self, serial_number: &[u8]) -> Result<bool, GatewayStorageError> {
|
||||
Ok(self.ticket_manager.has_ticket_data(serial_number).await?)
|
||||
}
|
||||
|
||||
async fn insert_ticket_verification(
|
||||
pub async fn insert_ticket_verification(
|
||||
&self,
|
||||
ticket_id: i64,
|
||||
signer_id: i64,
|
||||
verified_at: OffsetDateTime,
|
||||
accepted: bool,
|
||||
) -> Result<(), StorageError> {
|
||||
) -> Result<(), GatewayStorageError> {
|
||||
self.ticket_manager
|
||||
.insert_ticket_verification(ticket_id, signer_id, verified_at, accepted)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn update_rejected_ticket(&self, ticket_id: i64) -> Result<(), StorageError> {
|
||||
pub async fn update_rejected_ticket(&self, ticket_id: i64) -> Result<(), GatewayStorageError> {
|
||||
// set the ticket as rejected
|
||||
self.ticket_manager.set_rejected_ticket(ticket_id).await?;
|
||||
|
||||
@@ -531,7 +310,7 @@ impl Storage for PersistentStorage {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn update_verified_ticket(&self, ticket_id: i64) -> Result<(), StorageError> {
|
||||
pub async fn update_verified_ticket(&self, ticket_id: i64) -> Result<(), GatewayStorageError> {
|
||||
// 1. insert into verified table
|
||||
self.ticket_manager
|
||||
.insert_verified_ticket(ticket_id)
|
||||
@@ -545,36 +324,41 @@ impl Storage for PersistentStorage {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remove_verified_ticket_binary_data(&self, ticket_id: i64) -> Result<(), StorageError> {
|
||||
pub async fn remove_verified_ticket_binary_data(
|
||||
&self,
|
||||
ticket_id: i64,
|
||||
) -> Result<(), GatewayStorageError> {
|
||||
self.ticket_manager
|
||||
.remove_binary_ticket_data(ticket_id)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_all_verified_tickets_with_sn(&self) -> Result<Vec<VerifiedTicket>, StorageError> {
|
||||
pub async fn get_all_verified_tickets_with_sn(
|
||||
&self,
|
||||
) -> Result<Vec<VerifiedTicket>, GatewayStorageError> {
|
||||
Ok(self
|
||||
.ticket_manager
|
||||
.get_all_verified_tickets_with_sn()
|
||||
.await?)
|
||||
}
|
||||
|
||||
async fn get_all_proposed_tickets_with_sn(
|
||||
pub async fn get_all_proposed_tickets_with_sn(
|
||||
&self,
|
||||
proposal_id: u32,
|
||||
) -> Result<Vec<VerifiedTicket>, StorageError> {
|
||||
) -> Result<Vec<VerifiedTicket>, GatewayStorageError> {
|
||||
Ok(self
|
||||
.ticket_manager
|
||||
.get_all_proposed_tickets_with_sn(proposal_id as i64)
|
||||
.await?)
|
||||
}
|
||||
|
||||
async fn insert_redemption_proposal(
|
||||
pub async fn insert_redemption_proposal(
|
||||
&self,
|
||||
tickets: &[VerifiedTicket],
|
||||
proposal_id: u32,
|
||||
created_at: OffsetDateTime,
|
||||
) -> Result<(), StorageError> {
|
||||
) -> Result<(), GatewayStorageError> {
|
||||
// if we crash between those, there might a bit of an issue. we should revisit it later
|
||||
|
||||
// 1. insert the actual proposal
|
||||
@@ -592,12 +376,12 @@ impl Storage for PersistentStorage {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn clear_post_proposal_data(
|
||||
pub async fn clear_post_proposal_data(
|
||||
&self,
|
||||
proposal_id: u32,
|
||||
resolved_at: OffsetDateTime,
|
||||
rejected: bool,
|
||||
) -> Result<(), StorageError> {
|
||||
) -> Result<(), GatewayStorageError> {
|
||||
// 1. update proposal metadata
|
||||
self.ticket_manager
|
||||
.update_redemption_proposal(proposal_id as i64, resolved_at, rejected)
|
||||
@@ -616,11 +400,13 @@ impl Storage for PersistentStorage {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn latest_proposal(&self) -> Result<Option<RedemptionProposal>, StorageError> {
|
||||
pub async fn latest_proposal(&self) -> Result<Option<RedemptionProposal>, GatewayStorageError> {
|
||||
Ok(self.ticket_manager.get_latest_redemption_proposal().await?)
|
||||
}
|
||||
|
||||
async fn get_all_unverified_tickets(&self) -> Result<Vec<ClientTicket>, StorageError> {
|
||||
pub async fn get_all_unverified_tickets(
|
||||
&self,
|
||||
) -> Result<Vec<ClientTicket>, GatewayStorageError> {
|
||||
self.ticket_manager
|
||||
.get_unverified_tickets()
|
||||
.await?
|
||||
@@ -629,29 +415,37 @@ impl Storage for PersistentStorage {
|
||||
.collect()
|
||||
}
|
||||
|
||||
async fn get_all_unresolved_proposals(&self) -> Result<Vec<i64>, StorageError> {
|
||||
pub async fn get_all_unresolved_proposals(&self) -> Result<Vec<i64>, GatewayStorageError> {
|
||||
Ok(self
|
||||
.ticket_manager
|
||||
.get_all_unresolved_redemption_proposal_ids()
|
||||
.await?)
|
||||
}
|
||||
|
||||
async fn get_votes(&self, ticket_id: i64) -> Result<Vec<i64>, StorageError> {
|
||||
pub async fn get_votes(&self, ticket_id: i64) -> Result<Vec<i64>, GatewayStorageError> {
|
||||
Ok(self
|
||||
.ticket_manager
|
||||
.get_verification_votes(ticket_id)
|
||||
.await?)
|
||||
}
|
||||
|
||||
async fn get_signers(&self, epoch_id: i64) -> Result<Vec<i64>, StorageError> {
|
||||
pub async fn get_signers(&self, epoch_id: i64) -> Result<Vec<i64>, GatewayStorageError> {
|
||||
Ok(self.ticket_manager.get_epoch_signers(epoch_id).await?)
|
||||
}
|
||||
|
||||
async fn insert_wireguard_peer(
|
||||
/// Insert a wireguard peer in the storage.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `peer`: wireguard peer data to be stored
|
||||
/// * `with_client_id`: if the peer should have a corresponding client_id
|
||||
/// (created with entry wireguard ticket) or live without one (or with an
|
||||
/// exiting one), for temporary backwards compatibility.
|
||||
pub async fn insert_wireguard_peer(
|
||||
&self,
|
||||
peer: &defguard_wireguard_rs::host::Peer,
|
||||
with_client_id: bool,
|
||||
) -> Result<Option<i64>, StorageError> {
|
||||
) -> Result<Option<i64>, GatewayStorageError> {
|
||||
let client_id = match self
|
||||
.wireguard_peer_manager
|
||||
.retrieve_peer(&peer.public_key.to_string())
|
||||
@@ -676,10 +470,15 @@ impl Storage for PersistentStorage {
|
||||
Ok(client_id)
|
||||
}
|
||||
|
||||
async fn get_wireguard_peer(
|
||||
/// Tries to retrieve available bandwidth for the particular peer.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `peer_public_key`: wireguard public key of the peer to be retrieved.
|
||||
pub async fn get_wireguard_peer(
|
||||
&self,
|
||||
peer_public_key: &str,
|
||||
) -> Result<Option<WireguardPeer>, StorageError> {
|
||||
) -> Result<Option<WireguardPeer>, GatewayStorageError> {
|
||||
let peer = self
|
||||
.wireguard_peer_manager
|
||||
.retrieve_peer(peer_public_key)
|
||||
@@ -687,12 +486,21 @@ impl Storage for PersistentStorage {
|
||||
Ok(peer)
|
||||
}
|
||||
|
||||
async fn get_all_wireguard_peers(&self) -> Result<Vec<WireguardPeer>, StorageError> {
|
||||
/// Retrieves all wireguard peers.
|
||||
pub async fn get_all_wireguard_peers(&self) -> Result<Vec<WireguardPeer>, GatewayStorageError> {
|
||||
let ret = self.wireguard_peer_manager.retrieve_all_peers().await?;
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
async fn remove_wireguard_peer(&self, peer_public_key: &str) -> Result<(), StorageError> {
|
||||
/// Remove a wireguard peer from the storage.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `peer_public_key`: wireguard public key of the peer to be removed.
|
||||
pub async fn remove_wireguard_peer(
|
||||
&self,
|
||||
peer_public_key: &str,
|
||||
) -> Result<(), GatewayStorageError> {
|
||||
self.wireguard_peer_manager
|
||||
.remove_peer(peer_public_key)
|
||||
.await?;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::error::StorageError;
|
||||
use crate::error::GatewayStorageError;
|
||||
use nym_credentials_interface::{AvailableBandwidth, ClientTicket, CredentialSpendingData};
|
||||
use nym_gateway_requests::shared_key::{LegacySharedKeys, SharedGatewayKey, SharedSymmetricKey};
|
||||
use sqlx::FromRow;
|
||||
@@ -24,24 +24,24 @@ pub struct PersistedSharedKeys {
|
||||
}
|
||||
|
||||
impl TryFrom<PersistedSharedKeys> for SharedGatewayKey {
|
||||
type Error = StorageError;
|
||||
type Error = GatewayStorageError;
|
||||
|
||||
fn try_from(value: PersistedSharedKeys) -> Result<Self, Self::Error> {
|
||||
match (
|
||||
&value.derived_aes256_gcm_siv_key,
|
||||
&value.derived_aes128_ctr_blake3_hmac_keys_bs58,
|
||||
) {
|
||||
(None, None) => Err(StorageError::MissingSharedKey {
|
||||
(None, None) => Err(GatewayStorageError::MissingSharedKey {
|
||||
id: value.client_id,
|
||||
}),
|
||||
(Some(aes256gcm_siv), _) => {
|
||||
let current_key = SharedSymmetricKey::try_from_bytes(aes256gcm_siv)
|
||||
.map_err(|source| StorageError::DataCorruption(source.to_string()))?;
|
||||
.map_err(|source| GatewayStorageError::DataCorruption(source.to_string()))?;
|
||||
Ok(SharedGatewayKey::Current(current_key))
|
||||
}
|
||||
(None, Some(aes128ctr_hmac)) => {
|
||||
let legacy_key = LegacySharedKeys::try_from_base58_string(aes128ctr_hmac)
|
||||
.map_err(|source| StorageError::DataCorruption(source.to_string()))?;
|
||||
.map_err(|source| GatewayStorageError::DataCorruption(source.to_string()))?;
|
||||
Ok(SharedGatewayKey::Legacy(legacy_key))
|
||||
}
|
||||
}
|
||||
@@ -91,12 +91,12 @@ pub struct UnverifiedTicketData {
|
||||
}
|
||||
|
||||
impl TryFrom<UnverifiedTicketData> for ClientTicket {
|
||||
type Error = StorageError;
|
||||
type Error = GatewayStorageError;
|
||||
|
||||
fn try_from(value: UnverifiedTicketData) -> Result<Self, Self::Error> {
|
||||
Ok(ClientTicket {
|
||||
spending_data: CredentialSpendingData::try_from_bytes(&value.data).map_err(|_| {
|
||||
StorageError::MalformedStoredTicketData {
|
||||
GatewayStorageError::MalformedStoredTicketData {
|
||||
ticket_id: value.ticket_id,
|
||||
}
|
||||
})?,
|
||||
@@ -152,7 +152,7 @@ impl From<defguard_wireguard_rs::host::Peer> for WireguardPeer {
|
||||
}
|
||||
|
||||
impl TryFrom<WireguardPeer> for defguard_wireguard_rs::host::Peer {
|
||||
type Error = crate::error::StorageError;
|
||||
type Error = crate::error::GatewayStorageError;
|
||||
|
||||
fn try_from(value: WireguardPeer) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::fmt;
|
||||
use std::{fmt, str::FromStr};
|
||||
|
||||
use http::HeaderValue;
|
||||
use nym_bin_common::build_information::{BinaryBuildInformation, BinaryBuildInformationOwned};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct UserAgent {
|
||||
pub application: String,
|
||||
pub version: String,
|
||||
@@ -14,6 +15,36 @@ pub struct UserAgent {
|
||||
pub git_commit: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, thiserror::Error)]
|
||||
#[error("invalid user agent string: {0}")]
|
||||
pub struct UserAgentError(String);
|
||||
|
||||
impl FromStr for UserAgent {
|
||||
type Err = UserAgentError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let parts: Vec<&str> = s.split('/').collect();
|
||||
if parts.len() != 4 {
|
||||
return Err(UserAgentError(s.to_string()));
|
||||
}
|
||||
|
||||
Ok(UserAgent {
|
||||
application: parts[0].to_string(),
|
||||
version: parts[1].to_string(),
|
||||
platform: parts[2].to_string(),
|
||||
git_commit: parts[3].to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for UserAgent {
|
||||
type Error = UserAgentError;
|
||||
|
||||
fn try_from(s: &str) -> Result<Self, Self::Error> {
|
||||
UserAgent::from_str(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for UserAgent {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let abbreviated_commit = self.git_commit.chars().take(7).collect::<String>();
|
||||
@@ -54,3 +85,85 @@ impl From<BinaryBuildInformationOwned> for UserAgent {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parsing_valid_user_agent() {
|
||||
let user_agent = "nym-mixnode/0.11.0/x86_64-unknown-linux-gnu/abcdefg";
|
||||
let parsed = UserAgent::from_str(user_agent).unwrap();
|
||||
assert_eq!(
|
||||
parsed,
|
||||
UserAgent {
|
||||
application: "nym-mixnode".to_string(),
|
||||
version: "0.11.0".to_string(),
|
||||
platform: "x86_64-unknown-linux-gnu".to_string(),
|
||||
git_commit: "abcdefg".to_string()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_invalid_user_agent() {
|
||||
let user_agent = "nym-mixnode/0.11.0/x86_64-unknown-linux-gnu";
|
||||
assert!(UserAgent::from_str(user_agent).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn converting_user_agent_to_string() {
|
||||
let user_agent = UserAgent {
|
||||
application: "nym-mixnode".to_string(),
|
||||
version: "0.11.0".to_string(),
|
||||
platform: "x86_64-unknown-linux-gnu".to_string(),
|
||||
git_commit: "abcdefg".to_string(),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
user_agent.to_string(),
|
||||
"nym-mixnode/0.11.0/x86_64-unknown-linux-gnu/abcdefg"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn converting_user_agent_to_display() {
|
||||
let user_agent = UserAgent {
|
||||
application: "nym-mixnode".to_string(),
|
||||
version: "0.11.0".to_string(),
|
||||
platform: "x86_64-unknown-linux-gnu".to_string(),
|
||||
git_commit: "abcdefg".to_string(),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
format!("{}", user_agent),
|
||||
"nym-mixnode/0.11.0/x86_64-unknown-linux-gnu/abcdefg"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn converting_user_agent_to_header_value_fails() {
|
||||
let user_agent = UserAgent {
|
||||
application: "nym-mixnode".to_string(),
|
||||
version: "0.11.0".to_string(),
|
||||
platform: "x86_64-unknown-linux-gnu".to_string(),
|
||||
git_commit: "abcdefg".to_string(),
|
||||
};
|
||||
|
||||
let header_value: Result<HeaderValue, _> = user_agent.clone().try_into();
|
||||
assert!(header_value.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn converting_user_agent_to_header_value_has_same_string_representation() {
|
||||
let user_agent = UserAgent {
|
||||
application: "nym-mixnode".to_string(),
|
||||
version: "0.11.0".to_string(),
|
||||
platform: "x86_64-unknown-linux-gnu".to_string(),
|
||||
git_commit: "abcdefg".to_string(),
|
||||
};
|
||||
|
||||
let header_value: HeaderValue = user_agent.clone().try_into().unwrap();
|
||||
assert_eq!(header_value.to_str().unwrap(), user_agent.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ url = { workspace = true }
|
||||
time.workspace = true
|
||||
thiserror = { workspace = true }
|
||||
|
||||
nym-crypto = { path = "../crypto" }
|
||||
nym-crypto = { path = "../crypto", features = ["asymmetric"] }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
nym-sphinx-acknowledgements = { path = "../nymsphinx/acknowledgements" }
|
||||
nym-sphinx-addressing = { path = "../nymsphinx/addressing" }
|
||||
@@ -35,7 +35,5 @@ nym-sphinx-framing = { path = "../nymsphinx/framing" }
|
||||
nym-sphinx-params = { path = "../nymsphinx/params" }
|
||||
nym-sphinx-types = { path = "../nymsphinx/types" }
|
||||
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" }
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod packet_processor;
|
||||
pub mod verloc;
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::io;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RttError {
|
||||
UnexpectedEchoPacketSize,
|
||||
UnexpectedReplyPacketSize,
|
||||
|
||||
MalformedSenderIdentity,
|
||||
|
||||
MalformedEchoSignature,
|
||||
MalformedReplySignature,
|
||||
|
||||
InvalidEchoSignature,
|
||||
InvalidReplySignature,
|
||||
|
||||
UnreachableNode(String, io::Error),
|
||||
UnexpectedConnectionFailureWrite(String, io::Error),
|
||||
UnexpectedConnectionFailureRead(String, io::Error),
|
||||
ConnectionReadTimeout(String),
|
||||
ConnectionWriteTimeout(String),
|
||||
|
||||
UnexpectedReplySequence,
|
||||
|
||||
ShutdownReceived,
|
||||
}
|
||||
|
||||
impl Display for RttError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
RttError::UnexpectedEchoPacketSize => {
|
||||
write!(f, "The received echo packet had unexpected size")
|
||||
}
|
||||
RttError::UnexpectedReplyPacketSize => {
|
||||
write!(f, "The received reply packet had unexpected size")
|
||||
}
|
||||
RttError::MalformedSenderIdentity => {
|
||||
write!(f, "The received echo packet had malformed sender")
|
||||
}
|
||||
RttError::MalformedEchoSignature => {
|
||||
write!(f, "The received echo packet had malformed signature")
|
||||
}
|
||||
RttError::MalformedReplySignature => {
|
||||
write!(f, "The received reply packet had malformed signature")
|
||||
}
|
||||
RttError::InvalidEchoSignature => {
|
||||
write!(f, "The received echo packet had invalid signature")
|
||||
}
|
||||
RttError::InvalidReplySignature => {
|
||||
write!(f, "The received reply packet had invalid signature")
|
||||
}
|
||||
RttError::UnreachableNode(id, err) => {
|
||||
write!(f, "Could not establish connection to {id} - {err}")
|
||||
}
|
||||
RttError::UnexpectedConnectionFailureWrite(id, err) => {
|
||||
write!(f, "Failed to write echo packet to {id} - {err}")
|
||||
}
|
||||
RttError::UnexpectedConnectionFailureRead(id, err) => {
|
||||
write!(f, "Failed to read reply packet from {id} - {err}")
|
||||
}
|
||||
RttError::ConnectionReadTimeout(id) => {
|
||||
write!(f, "Timed out while trying to read reply packet from {id}")
|
||||
}
|
||||
RttError::ConnectionWriteTimeout(id) => {
|
||||
write!(f, "Timed out while trying to write echo packet to {id}")
|
||||
}
|
||||
RttError::UnexpectedReplySequence => write!(
|
||||
f,
|
||||
"The received reply packet had an unexpected sequence number"
|
||||
),
|
||||
RttError::ShutdownReceived => {
|
||||
write!(f, "Shutdown signal received")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for RttError {}
|
||||
@@ -1,35 +0,0 @@
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_node_http_api::state::metrics::{SharedVerlocStats, VerlocNodeResult};
|
||||
use std::mem;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
pub(crate) trait VerlocStatsUpdateExt {
|
||||
async fn start_new_measurements(&self, nodes_to_test: usize);
|
||||
|
||||
async fn append_measurement_results(&self, new_data: Vec<VerlocNodeResult>);
|
||||
|
||||
async fn finish_measurements(&self);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
async fn append_measurement_results(&self, mut new_data: Vec<VerlocNodeResult>) {
|
||||
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.current_run_data.results.sort()
|
||||
}
|
||||
|
||||
async fn finish_measurements(&self) {
|
||||
self.write().await.current_run_data.run_finished = Some(OffsetDateTime::now_utc())
|
||||
}
|
||||
}
|
||||
@@ -1,383 +0,0 @@
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::verloc::listener::PacketListener;
|
||||
use crate::verloc::sender::{PacketSender, TestedNode};
|
||||
use futures::stream::FuturesUnordered;
|
||||
use futures::StreamExt;
|
||||
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;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio::time::sleep;
|
||||
use url::Url;
|
||||
|
||||
use measurement::VerlocStatsUpdateExt;
|
||||
|
||||
// pub use crate::verloc::measurement::{AtomicVerlocResult, Verloc, VerlocResult};
|
||||
|
||||
pub mod error;
|
||||
pub(crate) mod listener;
|
||||
pub(crate) mod measurement;
|
||||
pub(crate) mod packet;
|
||||
pub(crate) mod sender;
|
||||
|
||||
// TODO: MUST BE UPDATED BEFORE ACTUAL RELEASE!!
|
||||
pub const MINIMUM_NODE_VERSION: &str = "0.10.1";
|
||||
|
||||
// by default all of those are overwritten by config data from mixnodes directly
|
||||
const DEFAULT_VERLOC_PORT: u16 = 1790;
|
||||
const DEFAULT_PACKETS_PER_NODE: usize = 100;
|
||||
const DEFAULT_PACKET_TIMEOUT: Duration = Duration::from_millis(1500);
|
||||
const DEFAULT_CONNECTION_TIMEOUT: Duration = Duration::from_millis(5000);
|
||||
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);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Config {
|
||||
/// Minimum semver version of a node (gateway or mixnode) that is capable of replying to echo packets.
|
||||
minimum_compatible_node_version: version_checker::Version,
|
||||
|
||||
/// Socket address of this node on which it will be listening for the measurement packets.
|
||||
listening_address: SocketAddr,
|
||||
|
||||
/// Specifies number of echo packets sent to each node during a measurement run.
|
||||
packets_per_node: usize,
|
||||
|
||||
/// Specifies maximum amount of time to wait for the reply packet to arrive before abandoning the test.
|
||||
packet_timeout: Duration,
|
||||
|
||||
/// Specifies maximum amount of time to wait for the connection to get established.
|
||||
connection_timeout: Duration,
|
||||
|
||||
/// Specifies delay between subsequent test packets being sent (after receiving a reply).
|
||||
delay_between_packets: Duration,
|
||||
|
||||
/// Specifies number of nodes being tested at once.
|
||||
tested_nodes_batch_size: usize,
|
||||
|
||||
/// Specifies delay between subsequent test runs.
|
||||
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.
|
||||
retry_timeout: Duration,
|
||||
|
||||
/// URLs to the nym apis for obtaining network topology.
|
||||
nym_api_urls: Vec<Url>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn build() -> ConfigBuilder {
|
||||
ConfigBuilder::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub struct ConfigBuilder(Config);
|
||||
|
||||
impl ConfigBuilder {
|
||||
pub fn new() -> ConfigBuilder {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn minimum_compatible_node_version(mut self, version: version_checker::Version) -> Self {
|
||||
self.0.minimum_compatible_node_version = version;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn listening_address(mut self, listening_address: SocketAddr) -> Self {
|
||||
self.0.listening_address = listening_address;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn packets_per_node(mut self, packets_per_node: usize) -> Self {
|
||||
self.0.packets_per_node = packets_per_node;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn packet_timeout(mut self, packet_timeout: Duration) -> Self {
|
||||
self.0.packet_timeout = packet_timeout;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn connection_timeout(mut self, connection_timeout: Duration) -> Self {
|
||||
self.0.connection_timeout = connection_timeout;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn delay_between_packets(mut self, delay_between_packets: Duration) -> Self {
|
||||
self.0.delay_between_packets = delay_between_packets;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tested_nodes_batch_size(mut self, tested_nodes_batch_size: usize) -> Self {
|
||||
self.0.tested_nodes_batch_size = tested_nodes_batch_size;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn testing_interval(mut self, testing_interval: Duration) -> Self {
|
||||
self.0.testing_interval = testing_interval;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn retry_timeout(mut self, retry_timeout: Duration) -> Self {
|
||||
self.0.retry_timeout = retry_timeout;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn nym_api_urls(mut self, nym_api_urls: Vec<Url>) -> Self {
|
||||
self.0.nym_api_urls = nym_api_urls;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> Config {
|
||||
// panics here are fine as those are only ever constructed at the initial setup
|
||||
assert!(
|
||||
!self.0.nym_api_urls.is_empty(),
|
||||
"at least one validator endpoint must be provided",
|
||||
);
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ConfigBuilder {
|
||||
fn default() -> Self {
|
||||
ConfigBuilder(Config {
|
||||
minimum_compatible_node_version: parse_version(MINIMUM_NODE_VERSION).unwrap(),
|
||||
listening_address: format!("[::]:{DEFAULT_VERLOC_PORT}").parse().unwrap(),
|
||||
packets_per_node: DEFAULT_PACKETS_PER_NODE,
|
||||
packet_timeout: DEFAULT_PACKET_TIMEOUT,
|
||||
connection_timeout: DEFAULT_CONNECTION_TIMEOUT,
|
||||
delay_between_packets: DEFAULT_DELAY_BETWEEN_PACKETS,
|
||||
tested_nodes_batch_size: DEFAULT_BATCH_SIZE,
|
||||
testing_interval: DEFAULT_TESTING_INTERVAL,
|
||||
retry_timeout: DEFAULT_RETRY_TIMEOUT,
|
||||
nym_api_urls: vec![NYM_API.parse().expect("Invalid default API URL")],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VerlocMeasurer {
|
||||
config: Config,
|
||||
packet_sender: Arc<PacketSender>,
|
||||
packet_listener: Arc<PacketListener>,
|
||||
shutdown_listener: TaskClient,
|
||||
|
||||
currently_used_api: usize,
|
||||
|
||||
// Note: this client is only fine here as it does not maintain constant connection to the validator.
|
||||
// It only does bunch of REST queries. If we update it at some point to a more sophisticated (maybe signing) client,
|
||||
// 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,
|
||||
state: SharedVerlocStats,
|
||||
}
|
||||
|
||||
impl VerlocMeasurer {
|
||||
pub fn new(
|
||||
mut config: Config,
|
||||
identity: Arc<identity::KeyPair>,
|
||||
shutdown_listener: TaskClient,
|
||||
) -> Self {
|
||||
config.nym_api_urls.shuffle(&mut thread_rng());
|
||||
|
||||
VerlocMeasurer {
|
||||
packet_sender: Arc::new(PacketSender::new(
|
||||
Arc::clone(&identity),
|
||||
config.packets_per_node,
|
||||
config.packet_timeout,
|
||||
config.connection_timeout,
|
||||
config.delay_between_packets,
|
||||
shutdown_listener.clone().named("VerlocPacketSender"),
|
||||
)),
|
||||
packet_listener: Arc::new(PacketListener::new(
|
||||
config.listening_address,
|
||||
Arc::clone(&identity),
|
||||
shutdown_listener.clone().named("VerlocPacketListener"),
|
||||
)),
|
||||
shutdown_listener,
|
||||
currently_used_api: 0,
|
||||
validator_client: nym_validator_client::NymApiClient::new(
|
||||
config.nym_api_urls[0].clone(),
|
||||
),
|
||||
config,
|
||||
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");
|
||||
return;
|
||||
}
|
||||
|
||||
self.currently_used_api = (self.currently_used_api + 1) % self.config.nym_api_urls.len();
|
||||
self.validator_client
|
||||
.change_nym_api(self.config.nym_api_urls[self.currently_used_api].clone())
|
||||
}
|
||||
|
||||
fn start_listening(&self) -> JoinHandle<()> {
|
||||
let packet_listener = Arc::clone(&self.packet_listener);
|
||||
tokio::spawn(packet_listener.run())
|
||||
}
|
||||
|
||||
async fn perform_measurement(&self, nodes_to_test: Vec<TestedNode>) -> MeasurementOutcome {
|
||||
log::trace!("Performing measurements");
|
||||
if nodes_to_test.is_empty() {
|
||||
log::debug!("there are no nodes to measure");
|
||||
return MeasurementOutcome::Done;
|
||||
}
|
||||
|
||||
let mut shutdown_listener = self.shutdown_listener.clone().named("VerlocMeasurement");
|
||||
shutdown_listener.disarm();
|
||||
|
||||
for chunk in nodes_to_test.chunks(self.config.tested_nodes_batch_size) {
|
||||
let mut chunk_results = Vec::with_capacity(chunk.len());
|
||||
|
||||
let mut measurement_chunk = chunk
|
||||
.iter()
|
||||
.map(|node| {
|
||||
let node = *node;
|
||||
let packet_sender = Arc::clone(&self.packet_sender);
|
||||
// TODO: there's a potential issue here. if we make the measurement go into separate
|
||||
// task, we risk biasing results with the bunch of context switches overhead
|
||||
// but if we don't do it, it will take ages to complete
|
||||
|
||||
// TODO: check performance difference when it's not spawned as a separate task
|
||||
tokio::spawn(async move {
|
||||
(
|
||||
packet_sender.send_packets_to_node(node).await,
|
||||
node.identity,
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>();
|
||||
|
||||
// exhaust the results
|
||||
while !shutdown_listener.is_shutdown() {
|
||||
tokio::select! {
|
||||
measurement_result = measurement_chunk.next() => {
|
||||
let Some(result) = measurement_result else {
|
||||
// if the stream has finished, it means we got everything we could have gotten
|
||||
break
|
||||
};
|
||||
|
||||
// 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 {identity}: {err}");
|
||||
None
|
||||
}
|
||||
Ok(result) => Some(result),
|
||||
};
|
||||
chunk_results.push(VerlocNodeResult::new(identity, measurement_result));
|
||||
},
|
||||
_ = shutdown_listener.recv() => {
|
||||
trace!("Shutdown received while measuring");
|
||||
return MeasurementOutcome::Shutdown;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update the results vector with chunks as they become available (by default every 50 nodes)
|
||||
self.state.append_measurement_results(chunk_results).await;
|
||||
}
|
||||
|
||||
MeasurementOutcome::Done
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) {
|
||||
self.start_listening();
|
||||
|
||||
while !self.shutdown_listener.is_shutdown() {
|
||||
info!("Starting verloc measurements");
|
||||
// TODO: should we also measure gateways?
|
||||
|
||||
let all_mixes = match self.validator_client.get_all_described_nodes().await {
|
||||
Ok(nodes) => nodes,
|
||||
Err(err) => {
|
||||
error!(
|
||||
"failed to obtain list of mixnodes from the validator - {}. Going to attempt to use another validator API in the next run",
|
||||
err
|
||||
);
|
||||
self.use_next_nym_api();
|
||||
sleep(self.config.retry_timeout).await;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if all_mixes.is_empty() {
|
||||
warn!("There does not seem there are any nodes to measure...")
|
||||
}
|
||||
|
||||
// we only care about address and identity
|
||||
let tested_nodes = all_mixes
|
||||
.into_iter()
|
||||
.filter(|n| n.description.declared_role.mixnode)
|
||||
.filter_map(|node| {
|
||||
// try to parse the identity and host
|
||||
let node_identity = node.ed25519_identity_key();
|
||||
|
||||
let ip = node.description.host_information.ip_address.first()?;
|
||||
let verloc_port = node.description.verloc_port();
|
||||
let verloc_host = SocketAddr::new(*ip, verloc_port);
|
||||
|
||||
// TODO: possible problem in the future, this does name resolution and theoretically
|
||||
// if a lot of nodes maliciously mis-configured themselves, it might take a while to resolve them all
|
||||
// However, maybe it's not a problem as if they are misconfigured, they will eventually be
|
||||
// pushed out of the network and on top of that, verloc is done in separate task that runs
|
||||
// only every few hours.
|
||||
Some(TestedNode::new(verloc_host, node_identity))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// on start of each run remove old results
|
||||
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");
|
||||
break;
|
||||
}
|
||||
|
||||
// write current time to "run finished" field
|
||||
self.state.finish_measurements().await;
|
||||
|
||||
info!(
|
||||
"Finished performing verloc measurements. The next one will happen in {:?}",
|
||||
self.config.testing_interval
|
||||
);
|
||||
|
||||
tokio::select! {
|
||||
_ = sleep(self.config.testing_interval) => {},
|
||||
_ = self.shutdown_listener.recv() => {
|
||||
log::trace!("Shutdown received while sleeping");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log::trace!("Verloc: Exiting");
|
||||
}
|
||||
}
|
||||
|
||||
enum MeasurementOutcome {
|
||||
Done,
|
||||
Shutdown,
|
||||
}
|
||||
@@ -18,19 +18,13 @@ pub struct FramedNymPacket {
|
||||
}
|
||||
|
||||
impl FramedNymPacket {
|
||||
pub fn new(packet: NymPacket, packet_type: PacketType, use_legacy_version: bool) -> Self {
|
||||
pub fn new(packet: NymPacket, packet_type: PacketType) -> Self {
|
||||
// If this fails somebody is using the library in a super incorrect way, because they
|
||||
// already managed to somehow create a sphinx packet
|
||||
let packet_size = PacketSize::get_type(packet.len()).unwrap();
|
||||
|
||||
let use_legacy = if packet_type == PacketType::Outfox {
|
||||
false
|
||||
} else {
|
||||
use_legacy_version
|
||||
};
|
||||
|
||||
let header = Header {
|
||||
packet_version: PacketVersion::new(use_legacy),
|
||||
packet_version: PacketVersion::new(),
|
||||
packet_size,
|
||||
packet_type,
|
||||
};
|
||||
|
||||
@@ -13,12 +13,8 @@ pub enum PacketVersion {
|
||||
}
|
||||
|
||||
impl PacketVersion {
|
||||
pub fn new(use_legacy: bool) -> Self {
|
||||
if use_legacy {
|
||||
Self::new_legacy()
|
||||
} else {
|
||||
Self::new_versioned(CURRENT_PACKET_VERSION_NUMBER)
|
||||
}
|
||||
pub fn new() -> Self {
|
||||
Self::new_versioned(CURRENT_PACKET_VERSION_NUMBER)
|
||||
}
|
||||
|
||||
pub fn new_legacy() -> Self {
|
||||
|
||||
@@ -26,3 +26,4 @@ nym-sphinx = { path = "../nymsphinx" }
|
||||
nym-credentials-interface = { path = "../credentials-interface" }
|
||||
nym-metrics = { path = "../nym-metrics" }
|
||||
nym-task = { path = "../task" }
|
||||
|
||||
|
||||
@@ -31,40 +31,12 @@ impl GatewayStatsReporter {
|
||||
/// Gateway Statistics events
|
||||
pub enum GatewayStatsEvent {
|
||||
/// Events in the lifecycle of an established client tunnel
|
||||
SessionStatsEvent(SessionEvent),
|
||||
}
|
||||
|
||||
impl GatewayStatsEvent {
|
||||
/// A new session between this gateway and the client remote has successfully opened
|
||||
pub fn new_session_start(client: DestinationAddressBytes) -> GatewayStatsEvent {
|
||||
GatewayStatsEvent::SessionStatsEvent(SessionEvent::SessionStart {
|
||||
start_time: OffsetDateTime::now_utc(),
|
||||
client,
|
||||
})
|
||||
}
|
||||
|
||||
/// An existing session with the client remote has ended
|
||||
pub fn new_session_stop(client: DestinationAddressBytes) -> GatewayStatsEvent {
|
||||
GatewayStatsEvent::SessionStatsEvent(SessionEvent::SessionStop {
|
||||
stop_time: OffsetDateTime::now_utc(),
|
||||
client,
|
||||
})
|
||||
}
|
||||
|
||||
/// A new ecash ticket has been added / requested
|
||||
pub fn new_ecash_ticket(
|
||||
client: DestinationAddressBytes,
|
||||
ticket_type: TicketType,
|
||||
) -> GatewayStatsEvent {
|
||||
GatewayStatsEvent::SessionStatsEvent(SessionEvent::EcashTicket {
|
||||
ticket_type,
|
||||
client,
|
||||
})
|
||||
}
|
||||
SessionStatsEvent(GatewaySessionEvent),
|
||||
}
|
||||
|
||||
/// Events in the lifecycle of an established client tunnel
|
||||
pub enum SessionEvent {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum GatewaySessionEvent {
|
||||
/// A new session between this gateway and the client remote has successfully opened
|
||||
SessionStart {
|
||||
/// The timestamp of the session open event
|
||||
@@ -88,38 +60,31 @@ pub enum SessionEvent {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum SessionType {
|
||||
Vpn,
|
||||
Mixnet,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl SessionType {
|
||||
pub fn to_string(&self) -> &str {
|
||||
match self {
|
||||
Self::Vpn => "vpn",
|
||||
Self::Mixnet => "mixnet",
|
||||
Self::Unknown => "unknown",
|
||||
impl GatewaySessionEvent {
|
||||
/// A new session between this gateway and the client remote has successfully opened
|
||||
pub fn new_session_start(client: DestinationAddressBytes) -> GatewaySessionEvent {
|
||||
GatewaySessionEvent::SessionStart {
|
||||
start_time: OffsetDateTime::now_utc(),
|
||||
client,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_string(s: &str) -> Self {
|
||||
match s {
|
||||
"vpn" => Self::Vpn,
|
||||
"mixnet" => Self::Mixnet,
|
||||
_ => Self::Unknown,
|
||||
/// An existing session with the client remote has ended
|
||||
pub fn new_session_stop(client: DestinationAddressBytes) -> GatewaySessionEvent {
|
||||
GatewaySessionEvent::SessionStop {
|
||||
stop_time: OffsetDateTime::now_utc(),
|
||||
client,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TicketType> for SessionType {
|
||||
fn from(value: TicketType) -> Self {
|
||||
match value {
|
||||
TicketType::V1MixnetEntry => Self::Mixnet,
|
||||
TicketType::V1MixnetExit => Self::Mixnet,
|
||||
TicketType::V1WireguardEntry => Self::Vpn,
|
||||
TicketType::V1WireguardExit => Self::Vpn,
|
||||
/// A new ecash ticket has been added / requested
|
||||
pub fn new_ecash_ticket(
|
||||
client: DestinationAddressBytes,
|
||||
ticket_type: TicketType,
|
||||
) -> GatewaySessionEvent {
|
||||
GatewaySessionEvent::EcashTicket {
|
||||
ticket_type,
|
||||
client,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#![warn(clippy::dbg_macro)]
|
||||
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use sha2::Digest;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
/// Client specific statistics interfaces and events.
|
||||
pub mod clients;
|
||||
@@ -36,3 +36,7 @@ fn generate_stats_id<M: AsRef<[u8]>>(prefix: &str, id_seed: M) -> String {
|
||||
let output = hasher.finalize();
|
||||
format!("{:x}", output)
|
||||
}
|
||||
|
||||
pub fn hash_identifier<M: AsRef<[u8]>>(identifier: M) -> String {
|
||||
format!("{:x}", Sha256::digest(identifier))
|
||||
}
|
||||
|
||||
@@ -455,6 +455,10 @@ impl TaskClient {
|
||||
self.mode.set_should_not_signal_on_drop();
|
||||
}
|
||||
|
||||
pub fn rearm(&mut self) {
|
||||
self.mode.set_should_signal_on_drop();
|
||||
}
|
||||
|
||||
pub fn send_we_stopped(&mut self, err: SentError) {
|
||||
if self.mode.is_dummy() {
|
||||
return;
|
||||
@@ -482,7 +486,7 @@ impl Drop for TaskClient {
|
||||
if !self.mode.should_signal_on_drop() {
|
||||
self.log(
|
||||
Level::Trace,
|
||||
"the task client is getting dropped but inststructed to not signal: this is expected during client shutdown",
|
||||
"the task client is getting dropped but instructed to not signal: this is expected during client shutdown",
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
@@ -527,6 +531,14 @@ impl ClientOperatingMode {
|
||||
}
|
||||
}
|
||||
|
||||
fn set_should_signal_on_drop(&mut self) {
|
||||
use ClientOperatingMode::{Dummy, Listening, ListeningButDontReportHalt};
|
||||
*self = match &self {
|
||||
ListeningButDontReportHalt | Listening => Listening,
|
||||
Dummy => Dummy,
|
||||
};
|
||||
}
|
||||
|
||||
fn set_should_not_signal_on_drop(&mut self) {
|
||||
use ClientOperatingMode::{Dummy, Listening, ListeningButDontReportHalt};
|
||||
*self = match &self {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
[package]
|
||||
name = "nym-verloc"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
readme.workspace = true
|
||||
|
||||
[dependencies]
|
||||
bytes = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
humantime = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
time = { workspace = true }
|
||||
tokio = { workspace = true, features = ["sync", "net", "rt-multi-thread", "io-util"] }
|
||||
tokio-util = { workspace = true, features = ["codec"] }
|
||||
thiserror = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
url = { workspace = true }
|
||||
|
||||
nym-crypto = { path = "../crypto", features = ["asymmetric"] }
|
||||
nym-task = { path = "../task" }
|
||||
nym-validator-client = { path = "../client-libs/validator-client" }
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum VerlocError {
|
||||
#[error("the received echo packet had unexpected size")]
|
||||
UnexpectedEchoPacketSize,
|
||||
|
||||
#[error("the received reply packet had unexpected size")]
|
||||
UnexpectedReplyPacketSize,
|
||||
|
||||
#[error("the received echo packet had malformed sender")]
|
||||
MalformedSenderIdentity,
|
||||
|
||||
#[error("the received echo packet had malformed signature")]
|
||||
MalformedEchoSignature,
|
||||
|
||||
#[error("the received reply packet had malformed signature")]
|
||||
MalformedReplySignature,
|
||||
|
||||
#[error("the received echo packet had invalid signature")]
|
||||
InvalidEchoSignature,
|
||||
|
||||
#[error("the received reply packet had invalid signature")]
|
||||
InvalidReplySignature,
|
||||
|
||||
#[error("could not establish connection to {identity} on {address}: {err}")]
|
||||
UnreachableNode {
|
||||
identity: String,
|
||||
address: SocketAddr,
|
||||
#[source]
|
||||
err: io::Error,
|
||||
},
|
||||
|
||||
#[error("failed to write echo packet to {identity} on {address}: {err}")]
|
||||
UnexpectedConnectionFailureWrite {
|
||||
identity: String,
|
||||
address: SocketAddr,
|
||||
#[source]
|
||||
err: io::Error,
|
||||
},
|
||||
|
||||
#[error("failed to read reply packet from {identity} on {address}: {err}")]
|
||||
UnexpectedConnectionFailureRead {
|
||||
identity: String,
|
||||
address: SocketAddr,
|
||||
#[source]
|
||||
err: io::Error,
|
||||
},
|
||||
|
||||
#[error("timed out while trying to read reply packet from {identity} on {address}")]
|
||||
ConnectionReadTimeout {
|
||||
identity: String,
|
||||
address: SocketAddr,
|
||||
},
|
||||
|
||||
#[error("timed out while trying to write echo packet to {identity} on {address}")]
|
||||
ConnectionWriteTimeout {
|
||||
identity: String,
|
||||
address: SocketAddr,
|
||||
},
|
||||
|
||||
#[error("the received reply packet had an unexpected sequence number")]
|
||||
UnexpectedReplySequence,
|
||||
|
||||
#[error("shutdown signal received")]
|
||||
ShutdownReceived,
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![warn(clippy::expect_used)]
|
||||
#![warn(clippy::unwrap_used)]
|
||||
#![warn(clippy::todo)]
|
||||
#![warn(clippy::dbg_macro)]
|
||||
|
||||
pub mod error;
|
||||
pub mod measurements;
|
||||
pub mod models;
|
||||
@@ -0,0 +1,135 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_validator_client::UserAgent;
|
||||
use std::net::{IpAddr, Ipv6Addr, SocketAddr};
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
|
||||
// by default all of those are overwritten by config data from nym-node directly
|
||||
const DEFAULT_VERLOC_PORT: u16 = 1790;
|
||||
const DEFAULT_PACKETS_PER_NODE: usize = 100;
|
||||
const DEFAULT_PACKET_TIMEOUT: Duration = Duration::from_millis(1500);
|
||||
const DEFAULT_CONNECTION_TIMEOUT: Duration = Duration::from_millis(5000);
|
||||
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);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Config {
|
||||
/// Socket address of this node on which it will be listening for the measurement packets.
|
||||
pub listening_address: SocketAddr,
|
||||
|
||||
/// 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 reply packet to arrive before abandoning the test.
|
||||
pub packet_timeout: Duration,
|
||||
|
||||
/// Specifies maximum amount of time to wait for the connection to get established.
|
||||
pub connection_timeout: Duration,
|
||||
|
||||
/// Specifies delay between subsequent test packets being sent (after receiving a reply).
|
||||
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.
|
||||
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.
|
||||
pub retry_timeout: Duration,
|
||||
|
||||
/// URLs to the nym apis for obtaining network topology.
|
||||
pub nym_api_urls: Vec<Url>,
|
||||
|
||||
/// User agent used for querying the nym-api
|
||||
pub user_agent: UserAgent,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn build(nym_api_urls: Vec<Url>, user_agent: impl Into<UserAgent>) -> ConfigBuilder {
|
||||
ConfigBuilder::new(nym_api_urls, user_agent)
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub struct ConfigBuilder(Config);
|
||||
|
||||
impl ConfigBuilder {
|
||||
pub fn new(nym_api_urls: Vec<Url>, user_agent: impl Into<UserAgent>) -> ConfigBuilder {
|
||||
ConfigBuilder(Config {
|
||||
// '[::]:port'
|
||||
listening_address: SocketAddr::new(
|
||||
IpAddr::V6(Ipv6Addr::UNSPECIFIED),
|
||||
DEFAULT_VERLOC_PORT,
|
||||
),
|
||||
packets_per_node: DEFAULT_PACKETS_PER_NODE,
|
||||
packet_timeout: DEFAULT_PACKET_TIMEOUT,
|
||||
connection_timeout: DEFAULT_CONNECTION_TIMEOUT,
|
||||
delay_between_packets: DEFAULT_DELAY_BETWEEN_PACKETS,
|
||||
tested_nodes_batch_size: DEFAULT_BATCH_SIZE,
|
||||
testing_interval: DEFAULT_TESTING_INTERVAL,
|
||||
retry_timeout: DEFAULT_RETRY_TIMEOUT,
|
||||
nym_api_urls,
|
||||
user_agent: user_agent.into(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn listening_address(mut self, listening_address: SocketAddr) -> Self {
|
||||
self.0.listening_address = listening_address;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn packets_per_node(mut self, packets_per_node: usize) -> Self {
|
||||
self.0.packets_per_node = packets_per_node;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn packet_timeout(mut self, packet_timeout: Duration) -> Self {
|
||||
self.0.packet_timeout = packet_timeout;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn connection_timeout(mut self, connection_timeout: Duration) -> Self {
|
||||
self.0.connection_timeout = connection_timeout;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn delay_between_packets(mut self, delay_between_packets: Duration) -> Self {
|
||||
self.0.delay_between_packets = delay_between_packets;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tested_nodes_batch_size(mut self, tested_nodes_batch_size: usize) -> Self {
|
||||
self.0.tested_nodes_batch_size = tested_nodes_batch_size;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn testing_interval(mut self, testing_interval: Duration) -> Self {
|
||||
self.0.testing_interval = testing_interval;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn retry_timeout(mut self, retry_timeout: Duration) -> Self {
|
||||
self.0.retry_timeout = retry_timeout;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn nym_api_urls(mut self, nym_api_urls: Vec<Url>) -> Self {
|
||||
self.0.nym_api_urls = nym_api_urls;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> Config {
|
||||
// panics here are fine as those are only ever constructed at the initial setup
|
||||
assert!(
|
||||
!self.0.nym_api_urls.is_empty(),
|
||||
"at least one validator endpoint must be provided",
|
||||
);
|
||||
self.0
|
||||
}
|
||||
}
|
||||
+21
-34
@@ -1,29 +1,29 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::verloc::error::RttError;
|
||||
use crate::verloc::packet::{EchoPacket, ReplyPacket};
|
||||
use crate::error::VerlocError;
|
||||
use crate::measurements::packet::{EchoPacket, ReplyPacket};
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_task::TaskClient;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, io, process};
|
||||
use std::{io, process};
|
||||
use thiserror::Error;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio_util::codec::{Decoder, Encoder, Framed};
|
||||
use tracing::{debug, error, info, trace, warn};
|
||||
|
||||
pub(crate) struct PacketListener {
|
||||
pub struct PacketListener {
|
||||
address: SocketAddr,
|
||||
connection_handler: Arc<ConnectionHandler>,
|
||||
shutdown: TaskClient,
|
||||
}
|
||||
|
||||
impl PacketListener {
|
||||
pub(crate) fn new(
|
||||
pub fn new(
|
||||
address: SocketAddr,
|
||||
identity: Arc<identity::KeyPair>,
|
||||
shutdown: TaskClient,
|
||||
@@ -37,13 +37,13 @@ impl PacketListener {
|
||||
}
|
||||
|
||||
impl PacketListener {
|
||||
pub(super) async fn run(self: Arc<Self>) {
|
||||
pub async fn run(self: Arc<Self>) {
|
||||
let listener = match TcpListener::bind(self.address).await {
|
||||
Ok(listener) => listener,
|
||||
Err(err) => {
|
||||
error!(
|
||||
"Failed to bind to {} - {}. Are you sure nothing else is running on the specified port and your user has sufficient permission to bind to the requested address?",
|
||||
self.address, err
|
||||
"Failed to bind to {}: {err}. Are you sure nothing else is running on the specified port and your user has sufficient permission to bind to the requested address?",
|
||||
self.address
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
@@ -71,7 +71,7 @@ impl PacketListener {
|
||||
}
|
||||
},
|
||||
_ = shutdown_listener.recv() => {
|
||||
log::trace!("PacketListener: Received shutdown");
|
||||
trace!("PacketListener: Received shutdown");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,28 +137,18 @@ impl ConnectionHandler {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Error)]
|
||||
enum EchoPacketCodecError {
|
||||
IoError(io::Error),
|
||||
PacketRecoveryError(RttError),
|
||||
#[error("encountered io error {0}")]
|
||||
IoError(#[from] io::Error),
|
||||
|
||||
#[error("failed to correctly decode an echo packet: {0}")]
|
||||
PacketRecoveryError(Box<VerlocError>),
|
||||
}
|
||||
|
||||
impl Display for EchoPacketCodecError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
EchoPacketCodecError::IoError(err) => write!(f, "encountered io error - {err}"),
|
||||
EchoPacketCodecError::PacketRecoveryError(err) => {
|
||||
write!(f, "failed to correctly decode an echo packet - {err}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for EchoPacketCodecError {}
|
||||
|
||||
impl From<io::Error> for EchoPacketCodecError {
|
||||
fn from(err: io::Error) -> Self {
|
||||
EchoPacketCodecError::IoError(err)
|
||||
impl From<VerlocError> for EchoPacketCodecError {
|
||||
fn from(value: VerlocError) -> Self {
|
||||
EchoPacketCodecError::PacketRecoveryError(Box::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,10 +178,7 @@ impl Decoder for EchoPacketCodec {
|
||||
|
||||
let packet_bytes = src.split_to(EchoPacket::SIZE);
|
||||
|
||||
let echo_packet = match EchoPacket::try_from_bytes(&packet_bytes) {
|
||||
Ok(packet) => packet,
|
||||
Err(err) => return Err(EchoPacketCodecError::PacketRecoveryError(err)),
|
||||
};
|
||||
let echo_packet = EchoPacket::try_from_bytes(&packet_bytes)?;
|
||||
|
||||
// reserve enough bytes for the next frame
|
||||
src.reserve(EchoPacket::SIZE);
|
||||
@@ -0,0 +1,225 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::measurements::metrics::SharedVerlocStats;
|
||||
use crate::measurements::sender::TestedNode;
|
||||
use crate::measurements::{Config, PacketListener, PacketSender};
|
||||
use crate::models::VerlocNodeResult;
|
||||
use futures::stream::FuturesUnordered;
|
||||
use futures::StreamExt;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_task::TaskClient;
|
||||
use nym_validator_client::models::NymNodeDescription;
|
||||
use nym_validator_client::NymApiClient;
|
||||
use rand::prelude::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio::time::sleep;
|
||||
use tracing::{debug, error, info, trace, warn};
|
||||
|
||||
pub struct VerlocMeasurer {
|
||||
config: Config,
|
||||
packet_sender: Arc<PacketSender>,
|
||||
packet_listener: Arc<PacketListener>,
|
||||
shutdown_listener: TaskClient,
|
||||
state: SharedVerlocStats,
|
||||
}
|
||||
|
||||
impl VerlocMeasurer {
|
||||
pub fn new(
|
||||
config: Config,
|
||||
identity: Arc<identity::KeyPair>,
|
||||
shutdown_listener: TaskClient,
|
||||
) -> Self {
|
||||
VerlocMeasurer {
|
||||
packet_sender: Arc::new(PacketSender::new(
|
||||
Arc::clone(&identity),
|
||||
config.packets_per_node,
|
||||
config.packet_timeout,
|
||||
config.connection_timeout,
|
||||
config.delay_between_packets,
|
||||
shutdown_listener.clone().named("VerlocPacketSender"),
|
||||
)),
|
||||
packet_listener: Arc::new(PacketListener::new(
|
||||
config.listening_address,
|
||||
Arc::clone(&identity),
|
||||
shutdown_listener.clone().named("VerlocPacketListener"),
|
||||
)),
|
||||
shutdown_listener,
|
||||
config,
|
||||
state: SharedVerlocStats::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_shared_state(&mut self, state: SharedVerlocStats) {
|
||||
self.state = state;
|
||||
}
|
||||
|
||||
fn start_listening(&self) -> JoinHandle<()> {
|
||||
let packet_listener = Arc::clone(&self.packet_listener);
|
||||
tokio::spawn(packet_listener.run())
|
||||
}
|
||||
|
||||
async fn perform_measurement(&self, nodes_to_test: Vec<TestedNode>) -> MeasurementOutcome {
|
||||
trace!("Performing measurements");
|
||||
if nodes_to_test.is_empty() {
|
||||
debug!("there are no nodes to measure");
|
||||
return MeasurementOutcome::Done;
|
||||
}
|
||||
|
||||
let mut shutdown_listener = self.shutdown_listener.clone().named("VerlocMeasurement");
|
||||
shutdown_listener.disarm();
|
||||
|
||||
for chunk in nodes_to_test.chunks(self.config.tested_nodes_batch_size) {
|
||||
let mut chunk_results = Vec::with_capacity(chunk.len());
|
||||
|
||||
let mut measurement_chunk = chunk
|
||||
.iter()
|
||||
.map(|node| {
|
||||
let node = *node;
|
||||
let packet_sender = Arc::clone(&self.packet_sender);
|
||||
// TODO: there's a potential issue here. if we make the measurement go into separate
|
||||
// task, we risk biasing results with the bunch of context switches overhead
|
||||
// but if we don't do it, it will take ages to complete
|
||||
|
||||
// TODO: check performance difference when it's not spawned as a separate task
|
||||
tokio::spawn(async move {
|
||||
(
|
||||
packet_sender.send_packets_to_node(node).await,
|
||||
node.identity,
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>();
|
||||
|
||||
// exhaust the results
|
||||
while !shutdown_listener.is_shutdown() {
|
||||
tokio::select! {
|
||||
measurement_result = measurement_chunk.next() => {
|
||||
let Some(result) = measurement_result else {
|
||||
// if the stream has finished, it means we got everything we could have gotten
|
||||
break
|
||||
};
|
||||
|
||||
// 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 Ok(execution_result) = result else {
|
||||
error!("the verloc measurer has panicked!");
|
||||
continue
|
||||
};
|
||||
let identity = execution_result.1;
|
||||
|
||||
let measurement_result = match execution_result.0 {
|
||||
Err(err) => {
|
||||
debug!("Failed to perform measurement for {identity}: {err}");
|
||||
None
|
||||
}
|
||||
Ok(result) => Some(result),
|
||||
};
|
||||
chunk_results.push(VerlocNodeResult::new(identity, measurement_result));
|
||||
},
|
||||
_ = shutdown_listener.recv() => {
|
||||
trace!("Shutdown received while measuring");
|
||||
return MeasurementOutcome::Shutdown;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update the results vector with chunks as they become available (by default every 50 nodes)
|
||||
self.state.append_measurement_results(chunk_results).await;
|
||||
}
|
||||
|
||||
MeasurementOutcome::Done
|
||||
}
|
||||
|
||||
async fn get_list_of_nodes(&self) -> Option<Vec<NymNodeDescription>> {
|
||||
let mut api_endpoints = self.config.nym_api_urls.clone();
|
||||
api_endpoints.shuffle(&mut thread_rng());
|
||||
for api_endpoint in api_endpoints {
|
||||
let client = NymApiClient::new_with_user_agent(
|
||||
api_endpoint.clone(),
|
||||
self.config.user_agent.clone(),
|
||||
);
|
||||
match client.get_all_described_nodes().await {
|
||||
Ok(res) => return Some(res),
|
||||
Err(err) => {
|
||||
warn!("failed to get described nodes from {api_endpoint}: {err}")
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) {
|
||||
self.start_listening();
|
||||
|
||||
while !self.shutdown_listener.is_shutdown() {
|
||||
info!("Starting verloc measurements");
|
||||
// TODO: should we also measure gateways?
|
||||
|
||||
let Some(all_nodes) = self.get_list_of_nodes().await else {
|
||||
error!("failed to obtain list of all nodes from any available api endpoint");
|
||||
sleep(self.config.retry_timeout).await;
|
||||
continue;
|
||||
};
|
||||
|
||||
if all_nodes.is_empty() {
|
||||
warn!("it does not seem there are any nodes to measure...");
|
||||
sleep(self.config.retry_timeout).await;
|
||||
continue;
|
||||
}
|
||||
|
||||
// we only care about address and identity
|
||||
let tested_nodes = all_nodes
|
||||
.into_iter()
|
||||
.filter_map(|node| {
|
||||
// try to parse the identity and host
|
||||
let node_identity = node.ed25519_identity_key();
|
||||
|
||||
let ip = node.description.host_information.ip_address.first()?;
|
||||
let verloc_port = node.description.verloc_port();
|
||||
let verloc_host = SocketAddr::new(*ip, verloc_port);
|
||||
|
||||
// TODO: possible problem in the future, this does name resolution and theoretically
|
||||
// if a lot of nodes maliciously mis-configured themselves, it might take a while to resolve them all
|
||||
// However, maybe it's not a problem as if they are misconfigured, they will eventually be
|
||||
// pushed out of the network and on top of that, verloc is done in separate task that runs
|
||||
// only every few hours.
|
||||
Some(TestedNode::new(verloc_host, node_identity))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// on start of each run remove old results
|
||||
self.state.start_new_measurements(tested_nodes.len()).await;
|
||||
|
||||
if let MeasurementOutcome::Shutdown = self.perform_measurement(tested_nodes).await {
|
||||
trace!("Shutting down after aborting measurements");
|
||||
break;
|
||||
}
|
||||
|
||||
// write current time to "run finished" field
|
||||
self.state.finish_measurements().await;
|
||||
|
||||
info!(
|
||||
"Finished performing verloc measurements. The next one will happen in {:?}",
|
||||
self.config.testing_interval
|
||||
);
|
||||
|
||||
tokio::select! {
|
||||
_ = sleep(self.config.testing_interval) => {},
|
||||
_ = self.shutdown_listener.recv() => {
|
||||
trace!("Shutdown received while sleeping");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trace!("Verloc: Exiting");
|
||||
}
|
||||
}
|
||||
|
||||
enum MeasurementOutcome {
|
||||
Done,
|
||||
Shutdown,
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::models::{VerlocNodeResult, VerlocResultData};
|
||||
use std::mem;
|
||||
use std::sync::Arc;
|
||||
use time::OffsetDateTime;
|
||||
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct SharedVerlocStats {
|
||||
inner: Arc<RwLock<VerlocStatsState>>,
|
||||
}
|
||||
|
||||
impl SharedVerlocStats {
|
||||
pub(crate) 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 append_measurement_results(&self, mut new_data: Vec<VerlocNodeResult>) {
|
||||
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.current_run_data.results.sort()
|
||||
}
|
||||
|
||||
pub(crate) async fn finish_measurements(&self) {
|
||||
self.write().await.current_run_data.run_finished = Some(OffsetDateTime::now_utc())
|
||||
}
|
||||
}
|
||||
|
||||
#[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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod config;
|
||||
pub mod listener;
|
||||
pub mod measurer;
|
||||
pub mod metrics;
|
||||
pub mod packet;
|
||||
pub mod sender;
|
||||
|
||||
pub use config::{Config, ConfigBuilder};
|
||||
pub use listener::PacketListener;
|
||||
pub use measurer::VerlocMeasurer;
|
||||
pub use metrics::{SharedVerlocStats, VerlocStatsState};
|
||||
pub use packet::{EchoPacket, ReplyPacket};
|
||||
pub use sender::PacketSender;
|
||||
+25
-23
@@ -1,20 +1,20 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::verloc::error::RttError;
|
||||
use nym_crypto::asymmetric::identity::{self, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH};
|
||||
use crate::error::VerlocError;
|
||||
use nym_crypto::asymmetric::ed25519::{self, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH};
|
||||
|
||||
pub(crate) struct EchoPacket {
|
||||
pub struct EchoPacket {
|
||||
sequence_number: u64,
|
||||
sender: identity::PublicKey,
|
||||
sender: ed25519::PublicKey,
|
||||
|
||||
signature: identity::Signature,
|
||||
signature: ed25519::Signature,
|
||||
}
|
||||
|
||||
impl EchoPacket {
|
||||
pub(crate) const SIZE: usize = 8 + PUBLIC_KEY_LENGTH + SIGNATURE_LENGTH;
|
||||
|
||||
pub(crate) fn new(sequence_number: u64, keys: &identity::KeyPair) -> Self {
|
||||
pub(crate) fn new(sequence_number: u64, keys: &ed25519::KeyPair) -> Self {
|
||||
let bytes_to_sign = sequence_number
|
||||
.to_be_bytes()
|
||||
.iter()
|
||||
@@ -42,20 +42,22 @@ impl EchoPacket {
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn try_from_bytes(bytes: &[u8]) -> Result<Self, RttError> {
|
||||
pub(crate) fn try_from_bytes(bytes: &[u8]) -> Result<Self, VerlocError> {
|
||||
if bytes.len() != Self::SIZE {
|
||||
return Err(RttError::UnexpectedEchoPacketSize);
|
||||
return Err(VerlocError::UnexpectedEchoPacketSize);
|
||||
}
|
||||
|
||||
// SAFETY: we have ensured our packet has correct size
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let sequence_number = u64::from_be_bytes(bytes[..8].try_into().unwrap());
|
||||
let sender = identity::PublicKey::from_bytes(&bytes[8..8 + PUBLIC_KEY_LENGTH])
|
||||
.map_err(|_| RttError::MalformedSenderIdentity)?;
|
||||
let signature = identity::Signature::from_bytes(&bytes[8 + PUBLIC_KEY_LENGTH..])
|
||||
.map_err(|_| RttError::MalformedEchoSignature)?;
|
||||
let sender = ed25519::PublicKey::from_bytes(&bytes[8..8 + PUBLIC_KEY_LENGTH])
|
||||
.map_err(|_| VerlocError::MalformedSenderIdentity)?;
|
||||
let signature = ed25519::Signature::from_bytes(&bytes[8 + PUBLIC_KEY_LENGTH..])
|
||||
.map_err(|_| VerlocError::MalformedEchoSignature)?;
|
||||
|
||||
sender
|
||||
.verify(&bytes[..Self::SIZE - SIGNATURE_LENGTH], &signature)
|
||||
.map_err(|_| RttError::InvalidEchoSignature)?;
|
||||
.map_err(|_| VerlocError::InvalidEchoSignature)?;
|
||||
|
||||
Ok(EchoPacket {
|
||||
sequence_number,
|
||||
@@ -64,7 +66,7 @@ impl EchoPacket {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn construct_reply(self, private_key: &identity::PrivateKey) -> ReplyPacket {
|
||||
pub(crate) fn construct_reply(self, private_key: &ed25519::PrivateKey) -> ReplyPacket {
|
||||
let bytes = self.to_bytes();
|
||||
let signature = private_key.sign(bytes);
|
||||
ReplyPacket {
|
||||
@@ -74,9 +76,9 @@ impl EchoPacket {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ReplyPacket {
|
||||
pub struct ReplyPacket {
|
||||
base_packet: EchoPacket,
|
||||
signature: identity::Signature,
|
||||
signature: ed25519::Signature,
|
||||
}
|
||||
|
||||
impl ReplyPacket {
|
||||
@@ -96,21 +98,21 @@ impl ReplyPacket {
|
||||
|
||||
pub(crate) fn try_from_bytes(
|
||||
bytes: &[u8],
|
||||
remote_identity: &identity::PublicKey,
|
||||
) -> Result<Self, RttError> {
|
||||
remote_ed25519: &ed25519::PublicKey,
|
||||
) -> Result<Self, VerlocError> {
|
||||
if bytes.len() != Self::SIZE {
|
||||
return Err(RttError::UnexpectedReplyPacketSize);
|
||||
return Err(VerlocError::UnexpectedReplyPacketSize);
|
||||
}
|
||||
|
||||
let base_packet =
|
||||
EchoPacket::try_from_bytes(&bytes[..8 + PUBLIC_KEY_LENGTH + SIGNATURE_LENGTH])?;
|
||||
let signature =
|
||||
identity::Signature::from_bytes(&bytes[8 + PUBLIC_KEY_LENGTH + SIGNATURE_LENGTH..])
|
||||
.map_err(|_| RttError::MalformedReplySignature)?;
|
||||
ed25519::Signature::from_bytes(&bytes[8 + PUBLIC_KEY_LENGTH + SIGNATURE_LENGTH..])
|
||||
.map_err(|_| VerlocError::MalformedReplySignature)?;
|
||||
|
||||
remote_identity
|
||||
remote_ed25519
|
||||
.verify(&bytes[..Self::SIZE - SIGNATURE_LENGTH], &signature)
|
||||
.map_err(|_| RttError::InvalidReplySignature)?;
|
||||
.map_err(|_| VerlocError::InvalidReplySignature)?;
|
||||
|
||||
Ok(ReplyPacket {
|
||||
base_packet,
|
||||
+50
-46
@@ -1,11 +1,10 @@
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::verloc::error::RttError;
|
||||
use crate::verloc::packet::{EchoPacket, ReplyPacket};
|
||||
use log::*;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_node_http_api::state::metrics::VerlocMeasurement;
|
||||
use crate::error::VerlocError;
|
||||
use crate::measurements::packet::{EchoPacket, ReplyPacket};
|
||||
use crate::models::VerlocMeasurement;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_task::TaskClient;
|
||||
use rand::{thread_rng, Rng};
|
||||
use std::net::SocketAddr;
|
||||
@@ -15,15 +14,16 @@ use std::{fmt, io};
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::time::sleep;
|
||||
use tracing::{debug, trace};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) struct TestedNode {
|
||||
pub(crate) address: SocketAddr,
|
||||
pub(crate) identity: identity::PublicKey,
|
||||
pub(crate) identity: ed25519::PublicKey,
|
||||
}
|
||||
|
||||
impl TestedNode {
|
||||
pub(crate) fn new(address: SocketAddr, identity: identity::PublicKey) -> Self {
|
||||
pub(crate) fn new(address: SocketAddr, identity: ed25519::PublicKey) -> Self {
|
||||
TestedNode { address, identity }
|
||||
}
|
||||
}
|
||||
@@ -38,8 +38,8 @@ impl fmt::Display for TestedNode {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct PacketSender {
|
||||
identity: Arc<identity::KeyPair>,
|
||||
pub struct PacketSender {
|
||||
identity: Arc<ed25519::KeyPair>,
|
||||
// timeout for receiving before sending new one
|
||||
packets_per_node: usize,
|
||||
packet_timeout: Duration,
|
||||
@@ -49,8 +49,8 @@ pub(crate) struct PacketSender {
|
||||
}
|
||||
|
||||
impl PacketSender {
|
||||
pub(super) fn new(
|
||||
identity: Arc<identity::KeyPair>,
|
||||
pub fn new(
|
||||
identity: Arc<ed25519::KeyPair>,
|
||||
packets_per_node: usize,
|
||||
packet_timeout: Duration,
|
||||
connection_timeout: Duration,
|
||||
@@ -82,7 +82,7 @@ impl PacketSender {
|
||||
pub(super) async fn send_packets_to_node(
|
||||
self: Arc<Self>,
|
||||
tested_node: TestedNode,
|
||||
) -> Result<VerlocMeasurement, RttError> {
|
||||
) -> Result<VerlocMeasurement, VerlocError> {
|
||||
let mut shutdown_listener = self.shutdown_listener.fork(tested_node.address.to_string());
|
||||
shutdown_listener.disarm();
|
||||
|
||||
@@ -93,16 +93,18 @@ impl PacketSender {
|
||||
.await
|
||||
{
|
||||
Err(_timeout) => {
|
||||
return Err(RttError::UnreachableNode(
|
||||
tested_node.identity.to_base58_string(),
|
||||
io::ErrorKind::TimedOut.into(),
|
||||
))
|
||||
return Err(VerlocError::UnreachableNode {
|
||||
identity: tested_node.identity.to_string(),
|
||||
err: io::ErrorKind::TimedOut.into(),
|
||||
address: tested_node.address,
|
||||
})
|
||||
}
|
||||
Ok(Err(err)) => {
|
||||
return Err(RttError::UnreachableNode(
|
||||
tested_node.identity.to_base58_string(),
|
||||
return Err(VerlocError::UnreachableNode {
|
||||
identity: tested_node.identity.to_string(),
|
||||
err,
|
||||
))
|
||||
address: tested_node.address,
|
||||
})
|
||||
}
|
||||
Ok(Ok(conn)) => conn,
|
||||
};
|
||||
@@ -121,33 +123,34 @@ impl PacketSender {
|
||||
write = tokio::time::timeout(self.packet_timeout, conn.write_all(packet_bytes.as_ref())) => {
|
||||
match write {
|
||||
Err(_timeout) => {
|
||||
let identity_string = tested_node.identity.to_base58_string();
|
||||
let identity = tested_node.identity;
|
||||
debug!(
|
||||
"failed to write echo packet to {} within {:?}. Stopping the test.",
|
||||
identity_string, self.packet_timeout
|
||||
"failed to write echo packet to {identity} within {:?}. Stopping the test.",
|
||||
self.packet_timeout
|
||||
);
|
||||
return Err(RttError::UnexpectedConnectionFailureWrite(
|
||||
identity_string,
|
||||
io::ErrorKind::TimedOut.into(),
|
||||
));
|
||||
return Err(VerlocError::UnexpectedConnectionFailureWrite{
|
||||
identity: identity.to_string(),
|
||||
err:io::ErrorKind::TimedOut.into(),
|
||||
address: tested_node.address
|
||||
});
|
||||
}
|
||||
Ok(Err(err)) => {
|
||||
let identity_string = tested_node.identity.to_base58_string();
|
||||
let identity = tested_node.identity;
|
||||
debug!(
|
||||
"failed to write echo packet to {} - {}. Stopping the test.",
|
||||
identity_string, err
|
||||
"failed to write echo packet to {identity}: {err}. Stopping the test.",
|
||||
);
|
||||
return Err(RttError::UnexpectedConnectionFailureWrite(
|
||||
identity_string,
|
||||
return Err(VerlocError::UnexpectedConnectionFailureWrite{
|
||||
identity: identity.to_string(),
|
||||
err,
|
||||
));
|
||||
address: tested_node.address
|
||||
});
|
||||
}
|
||||
Ok(Ok(_)) => {}
|
||||
}
|
||||
},
|
||||
_ = shutdown_listener.recv() => {
|
||||
log::trace!("PacketSender: Received shutdown while sending");
|
||||
return Err(RttError::ShutdownReceived);
|
||||
trace!("PacketSender: Received shutdown while sending");
|
||||
return Err(VerlocError::ShutdownReceived);
|
||||
},
|
||||
}
|
||||
|
||||
@@ -156,15 +159,15 @@ impl PacketSender {
|
||||
let reply_packet_future = async {
|
||||
let mut buf = [0u8; ReplyPacket::SIZE];
|
||||
if let Err(err) = conn.read_exact(&mut buf).await {
|
||||
let identity = tested_node.identity;
|
||||
debug!(
|
||||
"failed to read reply packet from {} - {}. Stopping the test.",
|
||||
tested_node.identity.to_base58_string(),
|
||||
err
|
||||
"failed to read reply packet from {identity}: {err}. Stopping the test.",
|
||||
);
|
||||
return Err(RttError::UnexpectedConnectionFailureRead(
|
||||
tested_node.identity.to_base58_string(),
|
||||
return Err(VerlocError::UnexpectedConnectionFailureRead {
|
||||
identity: identity.to_string(),
|
||||
err,
|
||||
));
|
||||
address: tested_node.address,
|
||||
});
|
||||
}
|
||||
ReplyPacket::try_from_bytes(&buf, &tested_node.identity)
|
||||
};
|
||||
@@ -180,15 +183,16 @@ impl PacketSender {
|
||||
"failed to receive reply to our echo packet within {:?}. Stopping the test",
|
||||
self.packet_timeout
|
||||
);
|
||||
return Err(RttError::ConnectionReadTimeout(
|
||||
tested_node.identity.to_base58_string(),
|
||||
));
|
||||
return Err(VerlocError::ConnectionReadTimeout{
|
||||
identity: tested_node.identity.to_string(),
|
||||
address: tested_node.address
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
_ = shutdown_listener.recv() => {
|
||||
log::trace!("PacketSender: Received shutdown while waiting for reply");
|
||||
return Err(RttError::ShutdownReceived);
|
||||
trace!("PacketSender: Received shutdown while waiting for reply");
|
||||
return Err(VerlocError::ShutdownReceived);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -198,7 +202,7 @@ impl PacketSender {
|
||||
// we have received the previous one
|
||||
if reply_packet.base_sequence_number() != seq {
|
||||
debug!("Received reply packet with invalid sequence number! Got {} expected {}. Stopping the test", reply_packet.base_sequence_number(), seq);
|
||||
return Err(RttError::UnexpectedReplySequence);
|
||||
return Err(VerlocError::UnexpectedReplySequence);
|
||||
}
|
||||
|
||||
let time_taken = tokio::time::Instant::now().duration_since(start);
|
||||
@@ -0,0 +1,225 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_crypto::asymmetric::ed25519::{self};
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct VerlocResultData {
|
||||
pub nodes_tested: usize,
|
||||
|
||||
pub run_started: OffsetDateTime,
|
||||
|
||||
pub run_finished: Option<OffsetDateTime>,
|
||||
|
||||
pub results: Vec<VerlocNodeResult>,
|
||||
}
|
||||
|
||||
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(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub struct VerlocNodeResult {
|
||||
pub node_identity: ed25519::PublicKey,
|
||||
|
||||
pub latest_measurement: Option<VerlocMeasurement>,
|
||||
}
|
||||
|
||||
impl VerlocNodeResult {
|
||||
pub fn new(
|
||||
node_identity: ed25519::PublicKey,
|
||||
latest_measurement: Option<VerlocMeasurement>,
|
||||
) -> Self {
|
||||
VerlocNodeResult {
|
||||
node_identity,
|
||||
latest_measurement,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for VerlocNodeResult {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
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)]
|
||||
pub struct VerlocMeasurement {
|
||||
/// Minimum RTT duration it took to receive an echo packet.
|
||||
pub minimum: Duration,
|
||||
|
||||
/// Average RTT duration it took to receive the echo packets.
|
||||
pub mean: Duration,
|
||||
|
||||
/// Maximum RTT duration it took to receive an echo packet.
|
||||
pub maximum: Duration,
|
||||
|
||||
/// The standard deviation of the RTT duration it took to receive the echo packets.
|
||||
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::<Duration>();
|
||||
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::<u128>()
|
||||
/ 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<Ordering> {
|
||||
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 =
|
||||
ed25519::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);
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util"] }
|
||||
tokio-stream = { workspace = true }
|
||||
time = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
nym-authenticator-requests = { path = "../authenticator-requests" }
|
||||
nym-credential-verification = { path = "../credential-verification" }
|
||||
|
||||
@@ -16,7 +16,7 @@ pub enum Error {
|
||||
MissingClientBandwidthEntry,
|
||||
|
||||
#[error("{0}")]
|
||||
GatewayStorage(#[from] nym_gateway_storage::error::StorageError),
|
||||
GatewayStorage(#[from] nym_gateway_storage::error::GatewayStorageError),
|
||||
|
||||
#[error("{0}")]
|
||||
SystemTime(#[from] std::time::SystemTimeError),
|
||||
|
||||
@@ -7,16 +7,18 @@
|
||||
// #![warn(clippy::unwrap_used)]
|
||||
|
||||
use defguard_wireguard_rs::WGApi;
|
||||
#[cfg(target_os = "linux")]
|
||||
use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask};
|
||||
use nym_crypto::asymmetric::encryption::KeyPair;
|
||||
#[cfg(target_os = "linux")]
|
||||
use nym_network_defaults::constants::WG_TUN_BASE_NAME;
|
||||
use nym_wireguard_types::Config;
|
||||
use peer_controller::PeerControlRequest;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc::{self, Receiver, Sender};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use nym_network_defaults::constants::WG_TUN_BASE_NAME;
|
||||
|
||||
pub(crate) mod error;
|
||||
pub mod peer_controller;
|
||||
pub mod peer_handle;
|
||||
@@ -81,8 +83,8 @@ pub struct WireguardData {
|
||||
|
||||
/// Start wireguard device
|
||||
#[cfg(target_os = "linux")]
|
||||
pub async fn start_wireguard<St: nym_gateway_storage::Storage + Clone + 'static>(
|
||||
storage: St,
|
||||
pub async fn start_wireguard(
|
||||
storage: nym_gateway_storage::GatewayStorage,
|
||||
all_peers: Vec<nym_gateway_storage::models::WireguardPeer>,
|
||||
task_client: nym_task::TaskClient,
|
||||
wireguard_data: WireguardData,
|
||||
@@ -93,6 +95,7 @@ pub async fn start_wireguard<St: nym_gateway_storage::Storage + Clone + 'static>
|
||||
use peer_controller::PeerController;
|
||||
use std::collections::HashMap;
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::info;
|
||||
|
||||
let ifname = String::from(WG_TUN_BASE_NAME);
|
||||
let wg_api = defguard_wireguard_rs::WGApi::new(ifname.clone(), false)?;
|
||||
@@ -121,6 +124,7 @@ pub async fn start_wireguard<St: nym_gateway_storage::Storage + Clone + 'static>
|
||||
.await?;
|
||||
peer_bandwidth_managers.insert(peer.public_key.clone(), (bandwidth_manager, peer.clone()));
|
||||
}
|
||||
|
||||
wg_api.create_interface()?;
|
||||
let interface_config = InterfaceConfiguration {
|
||||
name: ifname.clone(),
|
||||
@@ -130,6 +134,11 @@ pub async fn start_wireguard<St: nym_gateway_storage::Storage + Clone + 'static>
|
||||
peers,
|
||||
mtu: None,
|
||||
};
|
||||
info!(
|
||||
"attempting to configure wireguard interface '{ifname}': address={}, port={}",
|
||||
interface_config.address, interface_config.port
|
||||
);
|
||||
|
||||
wg_api.configure_interface(&interface_config)?;
|
||||
std::process::Command::new("ip")
|
||||
.args([
|
||||
|
||||
@@ -7,6 +7,7 @@ use defguard_wireguard_rs::{
|
||||
WireguardInterfaceApi,
|
||||
};
|
||||
use futures::channel::oneshot;
|
||||
use log::info;
|
||||
use nym_authenticator_requests::latest::registration::{
|
||||
RemainingBandwidthData, BANDWIDTH_CAP_PER_DAY,
|
||||
};
|
||||
@@ -14,7 +15,7 @@ use nym_credential_verification::{
|
||||
bandwidth_storage_manager::BandwidthStorageManager, BandwidthFlushingBehaviourConfig,
|
||||
ClientBandwidth,
|
||||
};
|
||||
use nym_gateway_storage::Storage;
|
||||
use nym_gateway_storage::GatewayStorage;
|
||||
use nym_wireguard_types::DEFAULT_PEER_TIMEOUT_CHECK;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tokio::sync::{mpsc, RwLock};
|
||||
@@ -62,24 +63,24 @@ pub struct QueryBandwidthControlResponse {
|
||||
pub bandwidth_data: Option<RemainingBandwidthData>,
|
||||
}
|
||||
|
||||
pub struct PeerController<St: Storage + Clone + 'static> {
|
||||
storage: St,
|
||||
pub struct PeerController {
|
||||
storage: GatewayStorage,
|
||||
// used to receive commands from individual handles too
|
||||
request_tx: mpsc::Sender<PeerControlRequest>,
|
||||
request_rx: mpsc::Receiver<PeerControlRequest>,
|
||||
wg_api: Arc<WgApiWrapper>,
|
||||
host_information: Arc<RwLock<Host>>,
|
||||
bw_storage_managers: HashMap<Key, Option<SharedBandwidthStorageManager<St>>>,
|
||||
bw_storage_managers: HashMap<Key, Option<SharedBandwidthStorageManager>>,
|
||||
timeout_check_interval: IntervalStream,
|
||||
task_client: nym_task::TaskClient,
|
||||
}
|
||||
|
||||
impl<St: Storage + Clone + 'static> PeerController<St> {
|
||||
impl PeerController {
|
||||
pub fn new(
|
||||
storage: St,
|
||||
storage: GatewayStorage,
|
||||
wg_api: Arc<WgApiWrapper>,
|
||||
initial_host_information: Host,
|
||||
bw_storage_managers: HashMap<Key, (Option<SharedBandwidthStorageManager<St>>, Peer)>,
|
||||
bw_storage_managers: HashMap<Key, (Option<SharedBandwidthStorageManager>, Peer)>,
|
||||
request_tx: mpsc::Sender<PeerControlRequest>,
|
||||
request_rx: mpsc::Receiver<PeerControlRequest>,
|
||||
task_client: nym_task::TaskClient,
|
||||
@@ -158,9 +159,9 @@ impl<St: Storage + Clone + 'static> PeerController<St> {
|
||||
}
|
||||
|
||||
pub async fn generate_bandwidth_manager(
|
||||
storage: St,
|
||||
storage: GatewayStorage,
|
||||
public_key: &Key,
|
||||
) -> Result<Option<BandwidthStorageManager<St>>, Error> {
|
||||
) -> Result<Option<BandwidthStorageManager>, Error> {
|
||||
if let Some(client_id) = storage
|
||||
.get_wireguard_peer(&public_key.to_string())
|
||||
.await?
|
||||
@@ -257,6 +258,7 @@ impl<St: Storage + Clone + 'static> PeerController<St> {
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) {
|
||||
info!("started wireguard peer controller");
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = self.timeout_check_interval.next() => {
|
||||
|
||||
@@ -10,7 +10,6 @@ use futures::channel::oneshot;
|
||||
use nym_authenticator_requests::latest::registration::BANDWIDTH_CAP_PER_DAY;
|
||||
use nym_credential_verification::bandwidth_storage_manager::BandwidthStorageManager;
|
||||
use nym_gateway_storage::models::WireguardPeer;
|
||||
use nym_gateway_storage::Storage;
|
||||
use nym_task::TaskClient;
|
||||
use nym_wireguard_types::DEFAULT_PEER_TIMEOUT_CHECK;
|
||||
use std::sync::Arc;
|
||||
@@ -18,26 +17,26 @@ use std::time::{Duration, SystemTime};
|
||||
use tokio::sync::{mpsc, RwLock};
|
||||
use tokio_stream::{wrappers::IntervalStream, StreamExt};
|
||||
|
||||
pub(crate) type SharedBandwidthStorageManager<St> = Arc<RwLock<BandwidthStorageManager<St>>>;
|
||||
pub(crate) type SharedBandwidthStorageManager = Arc<RwLock<BandwidthStorageManager>>;
|
||||
const AUTO_REMOVE_AFTER: Duration = Duration::from_secs(60 * 60 * 24 * 30); // 30 days
|
||||
|
||||
pub struct PeerHandle<St> {
|
||||
pub struct PeerHandle {
|
||||
public_key: Key,
|
||||
host_information: Arc<RwLock<Host>>,
|
||||
peer_storage_manager: PeerStorageManager<St>,
|
||||
bandwidth_storage_manager: Option<SharedBandwidthStorageManager<St>>,
|
||||
peer_storage_manager: PeerStorageManager,
|
||||
bandwidth_storage_manager: Option<SharedBandwidthStorageManager>,
|
||||
request_tx: mpsc::Sender<PeerControlRequest>,
|
||||
timeout_check_interval: IntervalStream,
|
||||
task_client: TaskClient,
|
||||
startup_timestamp: SystemTime,
|
||||
}
|
||||
|
||||
impl<St: Storage + Clone + 'static> PeerHandle<St> {
|
||||
impl PeerHandle {
|
||||
pub fn new(
|
||||
public_key: Key,
|
||||
host_information: Arc<RwLock<Host>>,
|
||||
peer_storage_manager: PeerStorageManager<St>,
|
||||
bandwidth_storage_manager: Option<SharedBandwidthStorageManager<St>>,
|
||||
peer_storage_manager: PeerStorageManager,
|
||||
bandwidth_storage_manager: Option<SharedBandwidthStorageManager>,
|
||||
request_tx: mpsc::Sender<PeerControlRequest>,
|
||||
task_client: &TaskClient,
|
||||
) -> Self {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use crate::error::Error;
|
||||
use defguard_wireguard_rs::host::Peer;
|
||||
use nym_gateway_storage::models::WireguardPeer;
|
||||
use nym_gateway_storage::Storage;
|
||||
use nym_gateway_storage::GatewayStorage;
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
@@ -29,15 +29,15 @@ impl Default for PeerFlushingBehaviourConfig {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PeerStorageManager<S> {
|
||||
pub(crate) storage: S,
|
||||
pub struct PeerStorageManager {
|
||||
pub(crate) storage: GatewayStorage,
|
||||
pub(crate) peer_information: Option<PeerInformation>,
|
||||
pub(crate) cfg: PeerFlushingBehaviourConfig,
|
||||
pub(crate) with_client_id: bool,
|
||||
}
|
||||
|
||||
impl<S: Storage + Clone + 'static> PeerStorageManager<S> {
|
||||
pub(crate) fn new(storage: S, peer: Peer, with_client_id: bool) -> Self {
|
||||
impl PeerStorageManager {
|
||||
pub(crate) fn new(storage: GatewayStorage, peer: Peer, with_client_id: bool) -> Self {
|
||||
let peer_information = Some(PeerInformation::new(peer));
|
||||
Self {
|
||||
storage,
|
||||
|
||||
@@ -20,5 +20,6 @@ REWARDING_VALIDATOR_ADDRESS=n1rfvpsynktze6wvn6ldskj8xgwfzzk5v6pnff39
|
||||
|
||||
EXPLORER_API=https://qa-network-explorer.qa.nymte.ch/api/
|
||||
NYXD=https://qa-validator.qa.nymte.ch
|
||||
NYXD_WS=wss://qa-validator.qa.nymte.ch/websocket/
|
||||
NYM_API=https://qa-nym-api.qa.nymte.ch/api/
|
||||
NYM_VPN_API=https://nym-vpn-api-git-deploy-qa-nyx-network-staging.vercel.app/api/
|
||||
|
||||
+1
-2
@@ -58,7 +58,6 @@ 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-sdk = { path = "../sdk/rust/nym-sdk" }
|
||||
nym-sphinx = { path = "../common/nymsphinx" }
|
||||
nym-statistics-common = { path = "../common/statistics" }
|
||||
@@ -67,13 +66,13 @@ nym-topology = { path = "../common/topology" }
|
||||
nym-types = { path = "../common/types" }
|
||||
nym-validator-client = { path = "../common/client-libs/validator-client" }
|
||||
nym-ip-packet-router = { path = "../service-providers/ip-packet-router" }
|
||||
nym-node-metrics = { path = "../nym-node/nym-node-metrics" }
|
||||
|
||||
nym-wireguard = { path = "../common/wireguard" }
|
||||
nym-wireguard-types = { path = "../common/wireguard-types", default-features = false }
|
||||
|
||||
defguard_wireguard_rs = { workspace = true }
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
sqlx = { workspace = true, features = [
|
||||
|
||||
+13
-147
@@ -1,36 +1,19 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_network_defaults::{DEFAULT_NYM_NODE_HTTP_PORT, TICKETBOOK_VALIDITY_DAYS};
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::path::PathBuf;
|
||||
use nym_network_defaults::TICKETBOOK_VALIDITY_DAYS;
|
||||
use std::net::SocketAddr;
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
// 'DEBUG'
|
||||
// where applicable, the below are defined in milliseconds
|
||||
const DEFAULT_PRESENCE_SENDING_DELAY: Duration = Duration::from_millis(10_000);
|
||||
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;
|
||||
|
||||
const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16;
|
||||
const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100;
|
||||
|
||||
const DEFAULT_CLIENT_BANDWIDTH_MAX_FLUSHING_RATE: Duration = Duration::from_millis(5);
|
||||
const DEFAULT_CLIENT_BANDWIDTH_MAX_DELTA_FLUSHING_AMOUNT: i64 = 512 * 1024; // 512kB
|
||||
// TODO: can we move those away?
|
||||
pub const DEFAULT_CLIENT_BANDWIDTH_MAX_FLUSHING_RATE: Duration = Duration::from_millis(5);
|
||||
pub const DEFAULT_CLIENT_BANDWIDTH_MAX_DELTA_FLUSHING_AMOUNT: i64 = 512 * 1024; // 512kB
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Config {
|
||||
pub host: Host,
|
||||
|
||||
pub http: Http,
|
||||
|
||||
pub gateway: Gateway,
|
||||
|
||||
// pub storage_paths: GatewayPaths,
|
||||
pub network_requester: NetworkRequester,
|
||||
|
||||
pub ip_packet_router: IpPacketRouter,
|
||||
@@ -39,18 +22,13 @@ pub struct Config {
|
||||
}
|
||||
|
||||
impl Config {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn externally_loaded(
|
||||
host: impl Into<Host>,
|
||||
http: impl Into<Http>,
|
||||
pub fn new(
|
||||
gateway: impl Into<Gateway>,
|
||||
network_requester: impl Into<NetworkRequester>,
|
||||
ip_packet_router: impl Into<IpPacketRouter>,
|
||||
debug: impl Into<Debug>,
|
||||
) -> Self {
|
||||
Config {
|
||||
host: host.into(),
|
||||
http: http.into(),
|
||||
gateway: gateway.into(),
|
||||
network_requester: network_requester.into(),
|
||||
ip_packet_router: ip_packet_router.into(),
|
||||
@@ -65,96 +43,23 @@ impl Config {
|
||||
pub fn get_nyxd_urls(&self) -> Vec<Url> {
|
||||
self.gateway.nyxd_urls.clone()
|
||||
}
|
||||
|
||||
pub fn get_cosmos_mnemonic(&self) -> bip39::Mnemonic {
|
||||
self.gateway.cosmos_mnemonic.clone()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this is very much a WIP. we need proper ssl certificate support here
|
||||
#[derive(Debug, PartialEq)]
|
||||
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<IpAddr>,
|
||||
|
||||
/// Optional hostname of this node, for example nymtech.net.
|
||||
// TODO: this is temporary. to be replaced by pulling the data directly from the certs.
|
||||
pub hostname: Option<String>,
|
||||
}
|
||||
|
||||
impl Host {
|
||||
pub fn validate(&self) -> bool {
|
||||
if self.public_ips.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
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.
|
||||
pub landing_page_assets_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we only really care about the mnemonic being zeroized
|
||||
#[derive(Debug, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Gateway {
|
||||
/// Version of the gateway for which this configuration was created.
|
||||
pub version: String,
|
||||
/// Indicates whether this gateway is accepting only zk-nym credentials for accessing the mixnet
|
||||
/// or if it also accepts non-paying clients
|
||||
pub enforce_zk_nyms: bool,
|
||||
|
||||
/// ID specifies the human readable ID of this particular gateway.
|
||||
pub id: String,
|
||||
|
||||
/// Indicates whether this gateway is accepting only coconut credentials for accessing the
|
||||
/// the mixnet, or if it also accepts non-paying clients
|
||||
pub only_coconut_credentials: bool,
|
||||
|
||||
/// Address to which this mixnode will bind to and will be listening for packets.
|
||||
#[zeroize(skip)]
|
||||
pub listening_address: IpAddr,
|
||||
|
||||
/// Port used for listening for all mixnet traffic.
|
||||
/// (default: 1789)
|
||||
pub mix_port: u16,
|
||||
|
||||
/// Port used for listening for all client-related traffic.
|
||||
/// (default: 9000)
|
||||
pub clients_port: u16,
|
||||
|
||||
/// If applicable, announced port for listening for secure websocket client traffic.
|
||||
/// (default: None)
|
||||
pub clients_wss_port: Option<u16>,
|
||||
/// Socket address this node will use for binding its client websocket API.
|
||||
/// default: `0.0.0.0:9000`
|
||||
pub websocket_bind_address: SocketAddr,
|
||||
|
||||
/// Addresses to APIs from which the node gets the view of the network.
|
||||
#[zeroize(skip)]
|
||||
pub nym_api_urls: Vec<Url>,
|
||||
|
||||
/// Addresses to validators which the node uses to check for double spending of ERC20 tokens.
|
||||
#[zeroize(skip)]
|
||||
pub nyxd_urls: Vec<Url>,
|
||||
|
||||
/// Mnemonic of a cosmos wallet used in checking for double spending.
|
||||
// #[deprecated(note = "move to storage")]
|
||||
// TODO: I don't think this should be stored directly in the config...
|
||||
pub cosmos_mnemonic: bip39::Mnemonic,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
@@ -185,60 +90,21 @@ impl Default for IpPacketRouter {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Debug {
|
||||
/// Initial value of an exponential backoff to reconnect to dropped TCP connection when
|
||||
/// forwarding sphinx packets.
|
||||
pub packet_forwarding_initial_backoff: Duration,
|
||||
|
||||
/// Maximum value of an exponential backoff to reconnect to dropped TCP connection when
|
||||
/// forwarding sphinx packets.
|
||||
pub packet_forwarding_maximum_backoff: Duration,
|
||||
|
||||
/// Timeout for establishing initial connection when trying to forward a sphinx packet.
|
||||
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,
|
||||
|
||||
/// Delay between each subsequent presence data being sent.
|
||||
// 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.
|
||||
pub message_retrieval_limit: i64,
|
||||
|
||||
/// Defines maximum delay between client bandwidth information being flushed to the persistent storage.
|
||||
pub client_bandwidth_max_flushing_rate: Duration,
|
||||
|
||||
/// Defines a maximum change in client bandwidth before it gets flushed to the persistent storage.
|
||||
pub client_bandwidth_max_delta_flushing_amount: i64,
|
||||
|
||||
/// Specifies whether the mixnode should be using the legacy framing for the sphinx packets.
|
||||
// it's set to true by default. The reason for that decision is to preserve compatibility with the
|
||||
// existing nodes whilst everyone else is upgrading and getting the code for handling the new field.
|
||||
// It shall be disabled in the subsequent releases.
|
||||
pub use_legacy_framed_packet_version: bool,
|
||||
|
||||
pub zk_nym_tickets: ZkNymTicketHandlerDebug,
|
||||
}
|
||||
|
||||
impl Default for Debug {
|
||||
fn default() -> Self {
|
||||
Debug {
|
||||
packet_forwarding_initial_backoff: DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF,
|
||||
packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF,
|
||||
initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT,
|
||||
presence_sending_delay: DEFAULT_PRESENCE_SENDING_DELAY,
|
||||
maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE,
|
||||
stored_messages_filename_length: DEFAULT_STORED_MESSAGE_FILENAME_LENGTH,
|
||||
message_retrieval_limit: DEFAULT_MESSAGE_RETRIEVAL_LIMIT,
|
||||
client_bandwidth_max_flushing_rate: DEFAULT_CLIENT_BANDWIDTH_MAX_FLUSHING_RATE,
|
||||
client_bandwidth_max_delta_flushing_amount:
|
||||
DEFAULT_CLIENT_BANDWIDTH_MAX_DELTA_FLUSHING_AMOUNT,
|
||||
use_legacy_framed_packet_version: false,
|
||||
zk_nym_tickets: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
+4
-10
@@ -3,7 +3,7 @@
|
||||
|
||||
use nym_authenticator::error::AuthenticatorError;
|
||||
use nym_gateway_stats_storage::error::StatsStorageError;
|
||||
use nym_gateway_storage::error::StorageError;
|
||||
use nym_gateway_storage::error::GatewayStorageError;
|
||||
use nym_ip_packet_router::error::IpPacketRouterError;
|
||||
use nym_network_requester::error::{ClientCoreError, NetworkRequesterError};
|
||||
use nym_validator_client::nyxd::error::NyxdError;
|
||||
@@ -38,7 +38,7 @@ pub enum GatewayError {
|
||||
#[error("storage failure: {source}")]
|
||||
StorageError {
|
||||
#[from]
|
||||
source: StorageError,
|
||||
source: GatewayStorageError,
|
||||
},
|
||||
|
||||
#[error("stats storage failure: {source}")]
|
||||
@@ -74,14 +74,8 @@ pub enum GatewayError {
|
||||
source: AuthenticatorError,
|
||||
},
|
||||
|
||||
#[error("failed to startup local network requester")]
|
||||
NetworkRequesterStartupFailure,
|
||||
|
||||
#[error("failed to startup local ip packet router")]
|
||||
IpPacketRouterStartupFailure,
|
||||
|
||||
#[error("failed to startup local authenticator")]
|
||||
AuthenticatorStartupFailure,
|
||||
#[error("failed to startup local {typ}")]
|
||||
ServiceProviderStartupFailure { typ: &'static str },
|
||||
|
||||
#[error("there are no nym API endpoints available")]
|
||||
NoNymApisAvailable,
|
||||
|
||||
+1
-1
@@ -9,4 +9,4 @@ pub mod error;
|
||||
pub mod node;
|
||||
|
||||
pub use error::GatewayError;
|
||||
pub use node::Gateway;
|
||||
pub use node::GatewayTasksBuilder;
|
||||
|
||||
@@ -5,8 +5,6 @@ use super::websocket::message_receiver::{IsActiveRequestSender, MixMessageSender
|
||||
use crate::node::client_handling::embedded_clients::LocalEmbeddedClientHandle;
|
||||
use dashmap::DashMap;
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use nym_statistics_common::gateways;
|
||||
use nym_statistics_common::gateways::GatewayStatsReporter;
|
||||
use std::sync::Arc;
|
||||
use tracing::warn;
|
||||
|
||||
@@ -34,10 +32,9 @@ impl ActiveClient {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ActiveClientsStore {
|
||||
#[derive(Clone, Default)]
|
||||
pub struct ActiveClientsStore {
|
||||
inner: Arc<DashMap<DestinationAddressBytes, ActiveClient>>,
|
||||
stats_event_reporter: GatewayStatsReporter,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -51,11 +48,8 @@ pub(crate) struct ClientIncomingChannels {
|
||||
|
||||
impl ActiveClientsStore {
|
||||
/// Creates new instance of `ActiveClientsStore` to store in-memory handles to all currently connected clients.
|
||||
pub(crate) fn new(stats_event_reporter: GatewayStatsReporter) -> Self {
|
||||
ActiveClientsStore {
|
||||
inner: Arc::new(DashMap::new()),
|
||||
stats_event_reporter,
|
||||
}
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Tries to obtain sending channel to specified client. Note that if stale entry existed, it is
|
||||
@@ -64,7 +58,7 @@ impl ActiveClientsStore {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `client`: address of the client for which to obtain the handle.
|
||||
pub(crate) fn get_sender(&self, client: DestinationAddressBytes) -> Option<MixMessageSender> {
|
||||
pub fn get_sender(&self, client: DestinationAddressBytes) -> Option<MixMessageSender> {
|
||||
let entry = self.inner.get(&client)?;
|
||||
let handle = entry.value().get_sender();
|
||||
|
||||
@@ -130,8 +124,6 @@ impl ActiveClientsStore {
|
||||
/// * `client`: address of the client for which to remove the handle.
|
||||
pub(crate) fn disconnect(&self, client: DestinationAddressBytes) {
|
||||
self.inner.remove(&client);
|
||||
self.stats_event_reporter
|
||||
.report(gateways::GatewayStatsEvent::new_session_stop(client));
|
||||
}
|
||||
|
||||
/// Insert new client handle into the store.
|
||||
@@ -153,12 +145,10 @@ impl ActiveClientsStore {
|
||||
if self.inner.insert(client, entry).is_some() {
|
||||
panic!("inserted a duplicate remote client")
|
||||
}
|
||||
self.stats_event_reporter
|
||||
.report(gateways::GatewayStatsEvent::new_session_start(client));
|
||||
}
|
||||
|
||||
/// Inserts a handle to the embedded client
|
||||
pub(crate) fn insert_embedded(&self, local_client_handle: LocalEmbeddedClientHandle) {
|
||||
pub fn insert_embedded(&self, local_client_handle: LocalEmbeddedClientHandle) {
|
||||
let key = local_client_handle.client_destination();
|
||||
let entry = ActiveClient::Embedded(local_client_handle);
|
||||
if self.inner.insert(key, entry).is_some() {
|
||||
|
||||
@@ -9,10 +9,11 @@ use nym_network_requester::{GatewayPacketRouter, PacketRouter};
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use nym_task::TaskClient;
|
||||
use tokio::task::JoinHandle;
|
||||
use tracing::{debug, error, trace};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct LocalEmbeddedClientHandle {
|
||||
pub struct LocalEmbeddedClientHandle {
|
||||
/// Nym address of the embedded client.
|
||||
pub(crate) address: Recipient,
|
||||
|
||||
@@ -52,8 +53,8 @@ impl MessageRouter {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn start_with_shutdown(self, shutdown: TaskClient) {
|
||||
tokio::spawn(self.run_with_shutdown(shutdown));
|
||||
pub(crate) fn start_with_shutdown(self, shutdown: TaskClient) -> JoinHandle<()> {
|
||||
tokio::spawn(self.run_with_shutdown(shutdown))
|
||||
}
|
||||
|
||||
fn handle_received_messages(&self, messages: Vec<Vec<u8>>) {
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::node::ActiveClientsStore;
|
||||
use nym_credential_verification::{ecash::EcashManager, BandwidthFlushingBehaviourConfig};
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_statistics_common::gateways::GatewayStatsReporter;
|
||||
use nym_gateway_storage::GatewayStorage;
|
||||
use nym_mixnet_client::forwarder::MixForwardingSender;
|
||||
use nym_node_metrics::events::MetricEventsSender;
|
||||
use std::sync::Arc;
|
||||
|
||||
// I can see this being possible expanded with say storage or client store
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct CommonHandlerState<S> {
|
||||
pub(crate) ecash_verifier: Arc<EcashManager<S>>,
|
||||
pub(crate) storage: S,
|
||||
pub(crate) struct CommonHandlerState {
|
||||
pub(crate) ecash_verifier: Arc<EcashManager>,
|
||||
pub(crate) storage: GatewayStorage,
|
||||
pub(crate) local_identity: Arc<identity::KeyPair>,
|
||||
pub(crate) only_coconut_credentials: bool,
|
||||
pub(crate) bandwidth_cfg: BandwidthFlushingBehaviourConfig,
|
||||
pub(crate) stats_event_reporter: GatewayStatsReporter,
|
||||
pub(crate) metrics_sender: MetricEventsSender,
|
||||
pub(crate) outbound_mix_sender: MixForwardingSender,
|
||||
pub(crate) active_clients_store: ActiveClientsStore,
|
||||
}
|
||||
|
||||
@@ -24,9 +24,10 @@ use nym_gateway_requests::{
|
||||
ClientControlRequest, ClientRequest, GatewayRequestsError, SensitiveServerResponse,
|
||||
SimpleGatewayRequestsError,
|
||||
};
|
||||
use nym_gateway_storage::{error::StorageError, Storage};
|
||||
use nym_gateway_storage::error::GatewayStorageError;
|
||||
use nym_node_metrics::events::MetricsEvent;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_statistics_common::gateways;
|
||||
use nym_statistics_common::gateways::GatewaySessionEvent;
|
||||
use nym_task::TaskClient;
|
||||
use nym_validator_client::coconut::EcashApiError;
|
||||
use rand::{random, CryptoRng, Rng};
|
||||
@@ -39,7 +40,7 @@ use tracing::*;
|
||||
#[derive(Debug, Error)]
|
||||
pub enum RequestHandlingError {
|
||||
#[error("Internal gateway storage error")]
|
||||
StorageError(#[from] StorageError),
|
||||
StorageError(#[from] GatewayStorageError),
|
||||
|
||||
#[error(
|
||||
"the database entry for bandwidth of the registered client {client_address} is missing!"
|
||||
@@ -136,9 +137,9 @@ impl IntoWSMessage for ServerResponse {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct AuthenticatedHandler<R, S, St> {
|
||||
inner: FreshHandler<R, S, St>,
|
||||
bandwidth_storage_manager: BandwidthStorageManager<St>,
|
||||
pub(crate) struct AuthenticatedHandler<R, S> {
|
||||
inner: FreshHandler<R, S>,
|
||||
bandwidth_storage_manager: BandwidthStorageManager,
|
||||
client: ClientDetails,
|
||||
mix_receiver: MixMessageReceiver,
|
||||
// Occasionally the handler is requested to ping the connected client for confirm that it's
|
||||
@@ -149,20 +150,13 @@ pub(crate) struct AuthenticatedHandler<R, S, St> {
|
||||
}
|
||||
|
||||
// explicitly remove handle from the global store upon being dropped
|
||||
impl<R, S, St> Drop for AuthenticatedHandler<R, S, St> {
|
||||
impl<R, S> Drop for AuthenticatedHandler<R, S> {
|
||||
fn drop(&mut self) {
|
||||
self.inner
|
||||
.active_clients_store
|
||||
.disconnect(self.client.address)
|
||||
self.disconnect_client()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R, S, St> AuthenticatedHandler<R, S, St>
|
||||
where
|
||||
// TODO: those trait bounds here don't really make sense....
|
||||
R: Rng + CryptoRng,
|
||||
St: Storage + Clone + 'static,
|
||||
{
|
||||
impl<R, S> AuthenticatedHandler<R, S> {
|
||||
/// Upgrades `FreshHandler` into the Authenticated variant implying the client is now authenticated
|
||||
/// and thus allowed to perform more actions with the gateway, such as redeeming bandwidth or
|
||||
/// sending sphinx packets.
|
||||
@@ -173,7 +167,7 @@ where
|
||||
/// * `client`: details (i.e. address and shared keys) of the registered client
|
||||
/// * `mix_receiver`: channel used for receiving messages from the mixnet destined for this client.
|
||||
pub(crate) async fn upgrade(
|
||||
fresh: FreshHandler<R, S, St>,
|
||||
fresh: FreshHandler<R, S>,
|
||||
client: ClientDetails,
|
||||
mix_receiver: MixMessageReceiver,
|
||||
is_active_request_receiver: IsActiveRequestReceiver,
|
||||
@@ -191,7 +185,7 @@ where
|
||||
client_address: client.address.as_base58_string(),
|
||||
})?;
|
||||
|
||||
Ok(AuthenticatedHandler {
|
||||
let handler = AuthenticatedHandler {
|
||||
bandwidth_storage_manager: BandwidthStorageManager::new(
|
||||
fresh.shared_state.storage.clone(),
|
||||
ClientBandwidth::new(bandwidth.into()),
|
||||
@@ -204,14 +198,24 @@ where
|
||||
mix_receiver,
|
||||
is_active_request_receiver,
|
||||
is_active_ping_pending_reply: None,
|
||||
})
|
||||
};
|
||||
handler.send_metrics(GatewaySessionEvent::new_session_start(
|
||||
handler.client.address,
|
||||
));
|
||||
|
||||
Ok(handler)
|
||||
}
|
||||
|
||||
/// Explicitly removes handle from the global store.
|
||||
fn disconnect(self) {
|
||||
fn disconnect_client(&mut self) {
|
||||
self.inner
|
||||
.shared_state
|
||||
.active_clients_store
|
||||
.disconnect(self.client.address)
|
||||
.disconnect(self.client.address);
|
||||
self.send_metrics(GatewaySessionEvent::new_session_stop(self.client.address));
|
||||
}
|
||||
|
||||
fn send_metrics(&self, event: impl Into<MetricsEvent>) {
|
||||
self.inner.send_metrics(event)
|
||||
}
|
||||
|
||||
/// Forwards the received mix packet from the client into the mix network.
|
||||
@@ -220,7 +224,12 @@ where
|
||||
///
|
||||
/// * `mix_packet`: packet received from the client that should get forwarded into the network.
|
||||
fn forward_packet(&self, mix_packet: MixPacket) {
|
||||
if let Err(err) = self.inner.outbound_mix_sender.unbounded_send(mix_packet) {
|
||||
if let Err(err) = self
|
||||
.inner
|
||||
.shared_state
|
||||
.outbound_mix_sender
|
||||
.forward_packet(mix_packet)
|
||||
{
|
||||
error!("We failed to forward requested mix packet - {err}. Presumably our mix forwarder has crashed. We cannot continue.");
|
||||
process::exit(1);
|
||||
}
|
||||
@@ -259,8 +268,8 @@ where
|
||||
trace!("available total bandwidth: {available_total}");
|
||||
|
||||
if let Ok(ticket_type) = maybe_ticket_type {
|
||||
self.inner.shared_state.stats_event_reporter.report(
|
||||
gateways::GatewayStatsEvent::new_ecash_ticket(self.client.address, ticket_type),
|
||||
self.inner.shared_state.metrics_sender.report_unchecked(
|
||||
GatewaySessionEvent::new_ecash_ticket(self.client.address, ticket_type),
|
||||
);
|
||||
} else {
|
||||
error!("Somehow verified a ticket with an unknown ticket type");
|
||||
@@ -372,7 +381,10 @@ where
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `raw_request`: raw message to handle.
|
||||
async fn handle_text(&mut self, raw_request: String) -> Message {
|
||||
async fn handle_text(&mut self, raw_request: String) -> Message
|
||||
where
|
||||
R: Rng + CryptoRng,
|
||||
{
|
||||
trace!("text request");
|
||||
|
||||
let request = match ClientControlRequest::try_from(raw_request) {
|
||||
@@ -470,7 +482,10 @@ where
|
||||
client = %self.client.address.as_base58_string()
|
||||
)
|
||||
)]
|
||||
async fn handle_request(&mut self, raw_request: Message) -> Option<Message> {
|
||||
async fn handle_request(&mut self, raw_request: Message) -> Option<Message>
|
||||
where
|
||||
R: Rng + CryptoRng,
|
||||
{
|
||||
trace!("new request");
|
||||
|
||||
// apparently tungstenite auto-handles ping/pong/close messages so for now let's ignore
|
||||
@@ -542,8 +557,8 @@ where
|
||||
/// and for sphinx packets received from the mix network that should be sent back to the client.
|
||||
pub(crate) async fn listen_for_requests(mut self, mut shutdown: TaskClient)
|
||||
where
|
||||
R: Rng + CryptoRng,
|
||||
S: AsyncRead + AsyncWrite + Unpin,
|
||||
St: Storage,
|
||||
{
|
||||
trace!("Started listening for ALL incoming requests...");
|
||||
|
||||
@@ -612,7 +627,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
self.disconnect();
|
||||
trace!("The stream was closed!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,9 @@
|
||||
|
||||
use crate::node::client_handling::websocket::common_state::CommonHandlerState;
|
||||
use crate::node::client_handling::websocket::connection_handler::INITIAL_MESSAGE_TIMEOUT;
|
||||
use crate::node::client_handling::{
|
||||
active_clients::ActiveClientsStore,
|
||||
websocket::{
|
||||
connection_handler::{
|
||||
AuthenticatedHandler, ClientDetails, InitialAuthResult, SocketStream,
|
||||
},
|
||||
message_receiver::{IsActive, IsActiveRequestSender},
|
||||
},
|
||||
use crate::node::client_handling::websocket::{
|
||||
connection_handler::{AuthenticatedHandler, ClientDetails, InitialAuthResult, SocketStream},
|
||||
message_receiver::{IsActive, IsActiveRequestSender},
|
||||
};
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
@@ -27,11 +22,11 @@ use nym_gateway_requests::{
|
||||
types::{ClientControlRequest, ServerResponse},
|
||||
BinaryResponse, SharedGatewayKey, CURRENT_PROTOCOL_VERSION, INITIAL_PROTOCOL_VERSION,
|
||||
};
|
||||
use nym_gateway_storage::{error::StorageError, Storage};
|
||||
use nym_mixnet_client::forwarder::MixForwardingSender;
|
||||
use nym_gateway_storage::error::GatewayStorageError;
|
||||
use nym_node_metrics::events::MetricsEvent;
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use nym_task::TaskClient;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use rand::CryptoRng;
|
||||
use std::net::SocketAddr;
|
||||
use std::time::Duration;
|
||||
use thiserror::Error;
|
||||
@@ -43,7 +38,7 @@ use tracing::*;
|
||||
#[derive(Debug, Error)]
|
||||
pub(crate) enum InitialAuthenticationError {
|
||||
#[error("Internal gateway storage error")]
|
||||
StorageError(#[from] StorageError),
|
||||
StorageError(#[from] GatewayStorageError),
|
||||
|
||||
#[error(
|
||||
"our datastore is corrupted. the stored key for client {client_id} is malformed: {source}"
|
||||
@@ -51,7 +46,7 @@ pub(crate) enum InitialAuthenticationError {
|
||||
MalformedStoredSharedKey {
|
||||
client_id: String,
|
||||
#[source]
|
||||
source: StorageError,
|
||||
source: GatewayStorageError,
|
||||
},
|
||||
|
||||
#[error("Failed to perform registration handshake: {0}")]
|
||||
@@ -107,11 +102,9 @@ pub(crate) enum InitialAuthenticationError {
|
||||
EmptyClientDetails,
|
||||
}
|
||||
|
||||
pub(crate) struct FreshHandler<R, S, St> {
|
||||
pub(crate) struct FreshHandler<R, S> {
|
||||
rng: R,
|
||||
pub(crate) shared_state: CommonHandlerState<St>,
|
||||
pub(crate) active_clients_store: ActiveClientsStore,
|
||||
pub(crate) outbound_mix_sender: MixForwardingSender,
|
||||
pub(crate) shared_state: CommonHandlerState,
|
||||
pub(crate) socket_connection: SocketStream<S>,
|
||||
pub(crate) peer_address: SocketAddr,
|
||||
pub(crate) shutdown: TaskClient,
|
||||
@@ -120,11 +113,7 @@ pub(crate) struct FreshHandler<R, S, St> {
|
||||
pub(crate) negotiated_protocol: Option<u8>,
|
||||
}
|
||||
|
||||
impl<R, S, St> FreshHandler<R, S, St>
|
||||
where
|
||||
R: Rng + CryptoRng,
|
||||
St: Storage + Clone + 'static,
|
||||
{
|
||||
impl<R, S> FreshHandler<R, S> {
|
||||
// for time being we assume handle is always constructed from raw socket.
|
||||
// if we decide we want to change it, that's not too difficult
|
||||
// also at this point I'm not entirely sure how to deal with this warning without
|
||||
@@ -133,16 +122,12 @@ where
|
||||
pub(crate) fn new(
|
||||
rng: R,
|
||||
conn: S,
|
||||
outbound_mix_sender: MixForwardingSender,
|
||||
active_clients_store: ActiveClientsStore,
|
||||
shared_state: CommonHandlerState<St>,
|
||||
shared_state: CommonHandlerState,
|
||||
peer_address: SocketAddr,
|
||||
shutdown: TaskClient,
|
||||
) -> Self {
|
||||
FreshHandler {
|
||||
rng,
|
||||
active_clients_store,
|
||||
outbound_mix_sender,
|
||||
socket_connection: SocketStream::RawTcp(conn),
|
||||
peer_address,
|
||||
negotiated_protocol: None,
|
||||
@@ -151,6 +136,10 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn send_metrics(&self, event: impl Into<MetricsEvent>) {
|
||||
self.shared_state.metrics_sender.report_unchecked(event)
|
||||
}
|
||||
|
||||
/// Attempts to perform websocket handshake with the remote and upgrades the raw TCP socket
|
||||
/// to the framed WebSocket.
|
||||
pub(crate) async fn perform_websocket_handshake(&mut self) -> Result<(), WsError>
|
||||
@@ -494,7 +483,7 @@ where
|
||||
// The other handler reported that the client is not active, so we can
|
||||
// disconnect the other client and continue with this connection.
|
||||
debug!("Other handler reports it is not active");
|
||||
self.active_clients_store.disconnect(address);
|
||||
self.shared_state.active_clients_store.disconnect(address);
|
||||
}
|
||||
IsActive::Active => {
|
||||
// The other handled reported a positive reply, so we have to assume it's
|
||||
@@ -513,14 +502,14 @@ where
|
||||
Ok(Err(_)) => {
|
||||
// Other channel failed to reply (the channel sender probably dropped)
|
||||
info!("Other connection failed to reply, disconnecting it in favour of this new connection");
|
||||
self.active_clients_store.disconnect(address);
|
||||
self.shared_state.active_clients_store.disconnect(address);
|
||||
}
|
||||
Err(_) => {
|
||||
// Timeout waiting for reply
|
||||
warn!(
|
||||
"Other connection timed out, disconnecting it in favour of this new connection"
|
||||
);
|
||||
self.active_clients_store.disconnect(address);
|
||||
self.shared_state.active_clients_store.disconnect(address);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -562,7 +551,11 @@ where
|
||||
.map_err(InitialAuthenticationError::MalformedIV)?;
|
||||
|
||||
// Check for duplicate clients
|
||||
if let Some(client_tx) = self.active_clients_store.get_remote_client(address) {
|
||||
if let Some(client_tx) = self
|
||||
.shared_state
|
||||
.active_clients_store
|
||||
.get_remote_client(address)
|
||||
{
|
||||
warn!("Detected duplicate connection for client: {address}");
|
||||
self.handle_duplicate_client(address, client_tx.is_active_request_sender)
|
||||
.await?;
|
||||
@@ -678,7 +671,11 @@ where
|
||||
|
||||
debug!(remote_client = %remote_identity);
|
||||
|
||||
if self.active_clients_store.is_active(remote_address) {
|
||||
if self
|
||||
.shared_state
|
||||
.active_clients_store
|
||||
.is_active(remote_address)
|
||||
{
|
||||
return Err(InitialAuthenticationError::DuplicateConnection);
|
||||
}
|
||||
|
||||
@@ -786,7 +783,7 @@ where
|
||||
pub(crate) async fn handle_until_authenticated_or_failure(
|
||||
mut self,
|
||||
shutdown: &mut TaskClient,
|
||||
) -> Option<AuthenticatedHandler<R, S, St>>
|
||||
) -> Option<AuthenticatedHandler<R, S>>
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin + Send,
|
||||
R: CryptoRng + RngCore + Send,
|
||||
@@ -822,7 +819,7 @@ where
|
||||
let (mix_sender, mix_receiver) = mpsc::unbounded();
|
||||
// Channel for handlers to ask other handlers if they are still active.
|
||||
let (is_active_request_sender, is_active_request_receiver) = mpsc::unbounded();
|
||||
self.active_clients_store.insert_remote(
|
||||
self.shared_state.active_clients_store.insert_remote(
|
||||
registration_details.address,
|
||||
mix_sender,
|
||||
is_active_request_sender,
|
||||
|
||||
@@ -5,7 +5,6 @@ use crate::config::Config;
|
||||
use nym_credential_verification::BandwidthFlushingBehaviourConfig;
|
||||
use nym_gateway_requests::shared_key::SharedGatewayKey;
|
||||
use nym_gateway_requests::ServerResponse;
|
||||
use nym_gateway_storage::Storage;
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::time::Duration;
|
||||
@@ -90,11 +89,10 @@ impl InitialAuthResult {
|
||||
|
||||
// imo there's no point in including the peer address in anything higher than debug
|
||||
#[instrument(level = "debug", skip_all, fields(peer = %handle.peer_address))]
|
||||
pub(crate) async fn handle_connection<R, S, St>(mut handle: FreshHandler<R, S, St>)
|
||||
pub(crate) async fn handle_connection<R, S>(mut handle: FreshHandler<R, S>)
|
||||
where
|
||||
R: Rng + CryptoRng + Send,
|
||||
S: AsyncRead + AsyncWrite + Unpin + Send,
|
||||
St: Storage + Clone + 'static,
|
||||
{
|
||||
// don't accept any new requests if we have already received shutdown
|
||||
if handle.shutdown.is_shutdown() {
|
||||
|
||||
@@ -1,41 +1,37 @@
|
||||
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::node::client_handling::active_clients::ActiveClientsStore;
|
||||
use crate::node::client_handling::websocket::common_state::CommonHandlerState;
|
||||
use crate::node::client_handling::websocket::connection_handler::FreshHandler;
|
||||
use nym_gateway_storage::Storage;
|
||||
use nym_mixnet_client::forwarder::MixForwardingSender;
|
||||
use nym_task::TaskClient;
|
||||
use rand::rngs::OsRng;
|
||||
use std::net::SocketAddr;
|
||||
use std::process;
|
||||
use tokio::task::JoinHandle;
|
||||
use tracing::*;
|
||||
|
||||
pub(crate) struct Listener<S> {
|
||||
pub struct Listener {
|
||||
address: SocketAddr,
|
||||
shared_state: CommonHandlerState<S>,
|
||||
shared_state: CommonHandlerState,
|
||||
shutdown: TaskClient,
|
||||
}
|
||||
|
||||
impl<S> Listener<S>
|
||||
where
|
||||
S: Storage + Send + Sync + Clone + 'static,
|
||||
{
|
||||
pub(crate) fn new(address: SocketAddr, shared_state: CommonHandlerState<S>) -> Self {
|
||||
impl Listener {
|
||||
pub(crate) fn new(
|
||||
address: SocketAddr,
|
||||
shared_state: CommonHandlerState,
|
||||
shutdown: TaskClient,
|
||||
) -> Self {
|
||||
Listener {
|
||||
address,
|
||||
shared_state,
|
||||
shutdown,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: change the signature to pub(crate) async fn run(&self, handler: Handler)
|
||||
|
||||
pub(crate) async fn run(
|
||||
&mut self,
|
||||
outbound_mix_sender: MixForwardingSender,
|
||||
active_clients_store: ActiveClientsStore,
|
||||
mut shutdown: nym_task::TaskClient,
|
||||
) {
|
||||
pub(crate) async fn run(&mut self) {
|
||||
info!("Starting websocket listener at {}", self.address);
|
||||
let tcp_listener = match tokio::net::TcpListener::bind(self.address).await {
|
||||
Ok(listener) => listener,
|
||||
@@ -45,24 +41,22 @@ where
|
||||
}
|
||||
};
|
||||
|
||||
while !shutdown.is_shutdown() {
|
||||
while !self.shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown.recv() => {
|
||||
_ = self.shutdown.recv() => {
|
||||
trace!("client_handling::Listener: received shutdown");
|
||||
}
|
||||
connection = tcp_listener.accept() => {
|
||||
match connection {
|
||||
Ok((socket, remote_addr)) => {
|
||||
let shutdown = shutdown.clone().named(format!("ClientConnectionHandler_{remote_addr}"));
|
||||
let shutdown = self.shutdown.fork(format!("websocket-handler-{remote_addr}"));
|
||||
trace!("received a socket connection from {remote_addr}");
|
||||
// TODO: I think we *REALLY* need a mechanism for having a maximum number of connected
|
||||
// clients or spawned tokio tasks -> perhaps a worker system?
|
||||
let handle = FreshHandler::new(
|
||||
OsRng,
|
||||
socket,
|
||||
outbound_mix_sender.clone(),
|
||||
active_clients_store.clone(),
|
||||
self.shared_state.clone(),
|
||||
remote_addr,
|
||||
shutdown,
|
||||
@@ -77,15 +71,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn start(
|
||||
mut self,
|
||||
outbound_mix_sender: MixForwardingSender,
|
||||
active_clients_store: ActiveClientsStore,
|
||||
shutdown: nym_task::TaskClient,
|
||||
) -> JoinHandle<()> {
|
||||
tokio::spawn(async move {
|
||||
self.run(outbound_mix_sender, active_clients_store, shutdown)
|
||||
.await
|
||||
})
|
||||
pub fn start(mut self) -> JoinHandle<()> {
|
||||
tokio::spawn(async move { self.run().await })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::node::client_handling::embedded_clients::{LocalEmbeddedClientHandle, MessageRouter};
|
||||
use crate::node::client_handling::websocket::message_receiver::{
|
||||
MixMessageReceiver, MixMessageSender,
|
||||
};
|
||||
use crate::GatewayError;
|
||||
use async_trait::async_trait;
|
||||
use futures::channel::{mpsc, oneshot};
|
||||
use nym_authenticator::Authenticator;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_ip_packet_router::error::IpPacketRouterError;
|
||||
use nym_ip_packet_router::IpPacketRouter;
|
||||
use nym_mixnet_client::forwarder::MixForwardingSender;
|
||||
use nym_network_requester::error::NetworkRequesterError;
|
||||
use nym_network_requester::NRServiceProviderBuilder;
|
||||
use nym_sdk::mixnet::Recipient;
|
||||
use nym_sdk::{GatewayTransceiver, LocalGateway, PacketRouter};
|
||||
use nym_task::TaskClient;
|
||||
use std::fmt::Display;
|
||||
use tokio::task::JoinHandle;
|
||||
use tracing::error;
|
||||
|
||||
pub trait LocalRecipient {
|
||||
fn address(&self) -> Recipient;
|
||||
}
|
||||
|
||||
impl LocalRecipient for nym_network_requester::core::OnStartData {
|
||||
fn address(&self) -> Recipient {
|
||||
self.address
|
||||
}
|
||||
}
|
||||
|
||||
impl LocalRecipient for nym_ip_packet_router::OnStartData {
|
||||
fn address(&self) -> Recipient {
|
||||
self.address
|
||||
}
|
||||
}
|
||||
|
||||
impl LocalRecipient for nym_authenticator::OnStartData {
|
||||
fn address(&self) -> Recipient {
|
||||
self.address
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait RunnableServiceProvider {
|
||||
const NAME: &'static str;
|
||||
|
||||
type OnStartData: LocalRecipient;
|
||||
type Error;
|
||||
async fn run_service_provider(self) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl RunnableServiceProvider for NRServiceProviderBuilder {
|
||||
const NAME: &'static str = "network requester";
|
||||
type OnStartData = nym_network_requester::core::OnStartData;
|
||||
type Error = NetworkRequesterError;
|
||||
|
||||
async fn run_service_provider(self) -> Result<(), Self::Error> {
|
||||
self.run_service_provider().await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl RunnableServiceProvider for IpPacketRouter {
|
||||
const NAME: &'static str = "ip router";
|
||||
type OnStartData = nym_ip_packet_router::OnStartData;
|
||||
type Error = IpPacketRouterError;
|
||||
|
||||
async fn run_service_provider(self) -> Result<(), Self::Error> {
|
||||
self.run_service_provider().await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl RunnableServiceProvider for Authenticator {
|
||||
const NAME: &'static str = "authenticator";
|
||||
type OnStartData = nym_authenticator::OnStartData;
|
||||
type Error = nym_authenticator::error::AuthenticatorError;
|
||||
|
||||
async fn run_service_provider(self) -> Result<(), Self::Error> {
|
||||
self.run_service_provider().await
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ServiceProviderBeingBuilt<T: RunnableServiceProvider> {
|
||||
on_start_rx: oneshot::Receiver<T::OnStartData>,
|
||||
sp_builder: T,
|
||||
sp_message_router_builder: SpMessageRouterBuilder,
|
||||
}
|
||||
|
||||
pub struct StartedServiceProvider<T: RunnableServiceProvider> {
|
||||
pub sp_join_handle: JoinHandle<()>,
|
||||
pub message_router_join_handle: JoinHandle<()>,
|
||||
pub on_start_data: T::OnStartData,
|
||||
pub handle: LocalEmbeddedClientHandle,
|
||||
}
|
||||
|
||||
impl<T> ServiceProviderBeingBuilt<T>
|
||||
where
|
||||
T: RunnableServiceProvider + Send + Sync + 'static,
|
||||
T::Error: Display + Send + Sync + 'static,
|
||||
{
|
||||
pub(crate) fn new(
|
||||
on_start_rx: oneshot::Receiver<T::OnStartData>,
|
||||
sp_builder: T,
|
||||
sp_message_router_builder: SpMessageRouterBuilder,
|
||||
) -> Self {
|
||||
ServiceProviderBeingBuilt {
|
||||
on_start_rx,
|
||||
sp_builder,
|
||||
sp_message_router_builder,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn start_service_provider(
|
||||
mut self,
|
||||
) -> Result<StartedServiceProvider<T>, GatewayError> {
|
||||
let sp_join_handle = tokio::task::spawn(async move {
|
||||
if let Err(err) = self.sp_builder.run_service_provider().await {
|
||||
error!(
|
||||
"the {} service provider encountered an error: {err}",
|
||||
T::NAME
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
let on_start_data = self
|
||||
.on_start_rx
|
||||
.await
|
||||
.map_err(|_| GatewayError::ServiceProviderStartupFailure { typ: T::NAME })?;
|
||||
|
||||
// this should be instantaneous since the data is sent on this channel before the on start is called;
|
||||
// the failure should be impossible
|
||||
let Ok(Some(packet_router)) = self.sp_message_router_builder.router_receiver.try_recv()
|
||||
else {
|
||||
return Err(GatewayError::ServiceProviderStartupFailure { typ: T::NAME });
|
||||
};
|
||||
|
||||
let mix_sender = self.sp_message_router_builder.mix_sender();
|
||||
let message_router_join_handle = self
|
||||
.sp_message_router_builder
|
||||
.start_message_router(packet_router);
|
||||
|
||||
Ok(StartedServiceProvider {
|
||||
sp_join_handle,
|
||||
message_router_join_handle,
|
||||
handle: LocalEmbeddedClientHandle::new(on_start_data.address(), mix_sender),
|
||||
on_start_data,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExitServiceProviders {
|
||||
pub(crate) network_requester: ServiceProviderBeingBuilt<NRServiceProviderBuilder>,
|
||||
pub(crate) ip_router: ServiceProviderBeingBuilt<IpPacketRouter>,
|
||||
}
|
||||
|
||||
impl ExitServiceProviders {
|
||||
pub async fn start_service_providers(
|
||||
self,
|
||||
) -> Result<
|
||||
(
|
||||
StartedServiceProvider<NRServiceProviderBuilder>,
|
||||
StartedServiceProvider<IpPacketRouter>,
|
||||
),
|
||||
GatewayError,
|
||||
> {
|
||||
let started_nr = self.network_requester.start_service_provider().await?;
|
||||
let started_ipr = self.ip_router.start_service_provider().await?;
|
||||
|
||||
Ok((started_nr, started_ipr))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SpMessageRouterBuilder {
|
||||
mix_sender: Option<MixMessageSender>,
|
||||
mix_receiver: MixMessageReceiver,
|
||||
router_receiver: oneshot::Receiver<PacketRouter>,
|
||||
gateway_transceiver: Option<LocalGateway>,
|
||||
shutdown: TaskClient,
|
||||
}
|
||||
|
||||
impl SpMessageRouterBuilder {
|
||||
pub(crate) fn new(
|
||||
node_identity: ed25519::PublicKey,
|
||||
forwarding_channel: MixForwardingSender,
|
||||
shutdown: TaskClient,
|
||||
) -> Self {
|
||||
let (mix_sender, mix_receiver) = mpsc::unbounded();
|
||||
let (router_tx, router_rx) = oneshot::channel();
|
||||
|
||||
let transceiver = LocalGateway::new(node_identity, forwarding_channel, router_tx);
|
||||
|
||||
SpMessageRouterBuilder {
|
||||
mix_sender: Some(mix_sender),
|
||||
mix_receiver,
|
||||
router_receiver: router_rx,
|
||||
gateway_transceiver: Some(transceiver),
|
||||
shutdown,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
pub(crate) fn gateway_transceiver(&mut self) -> Box<dyn GatewayTransceiver + Send + Sync> {
|
||||
Box::new(
|
||||
self.gateway_transceiver
|
||||
.take()
|
||||
.expect("attempting to use the same gateway transceiver twice"),
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
fn mix_sender(&mut self) -> MixMessageSender {
|
||||
self.mix_sender
|
||||
.take()
|
||||
.expect("attempting to use the same mix sender twice")
|
||||
}
|
||||
|
||||
fn start_message_router(self, packet_router: PacketRouter) -> JoinHandle<()> {
|
||||
MessageRouter::new(self.mix_receiver, packet_router).start_with_shutdown(self.shutdown)
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ use crate::node::client_handling::websocket::message_receiver::MixMessageSender;
|
||||
use crate::node::mixnet_handling::receiver::packet_processing::PacketProcessor;
|
||||
use futures::channel::mpsc::SendError;
|
||||
use futures::StreamExt;
|
||||
use nym_gateway_storage::{error::StorageError, Storage};
|
||||
use nym_gateway_storage::{error::GatewayStorageError, GatewayStorage};
|
||||
use nym_mixnet_client::forwarder::MixForwardingSender;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_sphinx::framing::codec::NymCodec;
|
||||
@@ -30,7 +30,7 @@ enum CriticalPacketProcessingError {
|
||||
AckForwardingFailure { source: SendError },
|
||||
}
|
||||
|
||||
pub(crate) struct ConnectionHandler<St: Storage> {
|
||||
pub(crate) struct ConnectionHandler {
|
||||
packet_processor: PacketProcessor,
|
||||
|
||||
// TODO: investigate performance trade-offs for whether this cache even makes sense
|
||||
@@ -39,11 +39,11 @@ pub(crate) struct ConnectionHandler<St: Storage> {
|
||||
// and each `get` internally copies the channel, however, is it really that expensive?
|
||||
clients_store_cache: HashMap<DestinationAddressBytes, MixMessageSender>,
|
||||
active_clients_store: ActiveClientsStore,
|
||||
storage: St,
|
||||
storage: GatewayStorage,
|
||||
ack_sender: MixForwardingSender,
|
||||
}
|
||||
|
||||
impl<St: Storage + Clone> Clone for ConnectionHandler<St> {
|
||||
impl Clone for ConnectionHandler {
|
||||
fn clone(&self) -> Self {
|
||||
// remove stale entries from the cache while cloning
|
||||
let mut clients_store_cache = HashMap::with_capacity(self.clients_store_cache.capacity());
|
||||
@@ -63,10 +63,10 @@ impl<St: Storage + Clone> Clone for ConnectionHandler<St> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<St: Storage> ConnectionHandler<St> {
|
||||
impl ConnectionHandler {
|
||||
pub(crate) fn new(
|
||||
packet_processor: PacketProcessor,
|
||||
storage: St,
|
||||
storage: GatewayStorage,
|
||||
ack_sender: MixForwardingSender,
|
||||
active_clients_store: ActiveClientsStore,
|
||||
) -> Self {
|
||||
@@ -123,7 +123,7 @@ impl<St: Storage> ConnectionHandler<St> {
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
message: Vec<u8>,
|
||||
) -> Result<(), StorageError> {
|
||||
) -> Result<(), GatewayStorageError> {
|
||||
debug!("Storing received message for {client_address} on the disk...",);
|
||||
|
||||
self.storage.store_message(client_address, message).await
|
||||
@@ -137,14 +137,9 @@ impl<St: Storage> ConnectionHandler<St> {
|
||||
if let Some(forward_ack) = forward_ack {
|
||||
let next_hop = forward_ack.next_hop();
|
||||
trace!("Sending ack from packet for {client_address} to {next_hop}",);
|
||||
|
||||
self.ack_sender
|
||||
.unbounded_send(forward_ack)
|
||||
.map_err(
|
||||
|source| CriticalPacketProcessingError::AckForwardingFailure {
|
||||
source: source.into_send_error(),
|
||||
},
|
||||
)?;
|
||||
.forward_packet(forward_ack)
|
||||
.map_err(|source| CriticalPacketProcessingError::AckForwardingFailure { source })?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::node::mixnet_handling::receiver::connection_handler::ConnectionHandler;
|
||||
use nym_gateway_storage::Storage;
|
||||
use nym_task::TaskClient;
|
||||
use std::net::SocketAddr;
|
||||
use std::process;
|
||||
@@ -20,10 +19,7 @@ impl Listener {
|
||||
Listener { address, shutdown }
|
||||
}
|
||||
|
||||
pub(crate) async fn run<St>(&mut self, connection_handler: ConnectionHandler<St>)
|
||||
where
|
||||
St: Storage + Clone + 'static,
|
||||
{
|
||||
pub(crate) async fn run(&mut self, connection_handler: ConnectionHandler) {
|
||||
info!("Starting mixnet listener at {}", self.address);
|
||||
let tcp_listener = match tokio::net::TcpListener::bind(self.address).await {
|
||||
Ok(listener) => listener,
|
||||
@@ -52,10 +48,7 @@ impl Listener {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn start<St>(mut self, connection_handler: ConnectionHandler<St>) -> JoinHandle<()>
|
||||
where
|
||||
St: Storage + Clone + 'static,
|
||||
{
|
||||
pub(crate) fn start(mut self, connection_handler: ConnectionHandler) -> JoinHandle<()> {
|
||||
info!("Running mix listener on {:?}", self.address.to_string());
|
||||
|
||||
tokio::spawn(async move { self.run(connection_handler).await })
|
||||
|
||||
+349
-585
File diff suppressed because it is too large
Load Diff
@@ -1,86 +0,0 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_gateway_stats_storage::PersistentStatsStorage;
|
||||
use nym_node_http_api::state::metrics::SharedSessionStats;
|
||||
use nym_statistics_common::gateways::{
|
||||
GatewayStatsEvent, GatewayStatsReceiver, GatewayStatsReporter,
|
||||
};
|
||||
use nym_task::TaskClient;
|
||||
use sessions::SessionStatsHandler;
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
use tracing::{error, trace, warn};
|
||||
|
||||
pub mod sessions;
|
||||
|
||||
const STATISTICS_UPDATE_TIMER_INTERVAL: Duration = Duration::from_secs(3600); //update timer, no need to check everytime
|
||||
|
||||
pub(crate) struct GatewayStatisticsCollector {
|
||||
stats_event_rx: GatewayStatsReceiver,
|
||||
session_stats: SessionStatsHandler,
|
||||
//here goes additionnal stats handler
|
||||
}
|
||||
|
||||
impl GatewayStatisticsCollector {
|
||||
pub fn new(
|
||||
shared_session_stats: SharedSessionStats,
|
||||
stats_storage: PersistentStatsStorage,
|
||||
) -> (GatewayStatisticsCollector, GatewayStatsReporter) {
|
||||
let (stats_event_tx, stats_event_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
|
||||
let session_stats = SessionStatsHandler::new(shared_session_stats, stats_storage);
|
||||
let collector = GatewayStatisticsCollector {
|
||||
stats_event_rx,
|
||||
session_stats,
|
||||
};
|
||||
let reporter = GatewayStatsReporter::new(stats_event_tx);
|
||||
(collector, reporter)
|
||||
}
|
||||
|
||||
async fn update_shared_state(&mut self, update_time: OffsetDateTime) {
|
||||
if let Err(e) = self
|
||||
.session_stats
|
||||
.maybe_update_shared_state(update_time)
|
||||
.await
|
||||
{
|
||||
error!("Failed to update session stats - {e}");
|
||||
}
|
||||
//here goes additionnal stats handler update
|
||||
}
|
||||
|
||||
async fn on_start(&mut self) {
|
||||
if let Err(e) = self.session_stats.on_start().await {
|
||||
error!("Failed to cleanup session stats handler - {e}");
|
||||
}
|
||||
//here goes additionnal stats handler start cleanup
|
||||
}
|
||||
|
||||
pub async fn run(&mut self, mut shutdown: TaskClient) {
|
||||
self.on_start().await;
|
||||
let mut update_interval = tokio::time::interval(STATISTICS_UPDATE_TIMER_INTERVAL);
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown.recv() => {
|
||||
trace!("StatisticsCollector: Received shutdown");
|
||||
},
|
||||
_ = update_interval.tick() => {
|
||||
let now = OffsetDateTime::now_utc();
|
||||
self.update_shared_state(now).await;
|
||||
},
|
||||
|
||||
Some(stat_event) = self.stats_event_rx.recv() => {
|
||||
//dispatching event to proper handler
|
||||
match stat_event {
|
||||
GatewayStatsEvent::SessionStatsEvent(event) => {
|
||||
if let Err(e) = self.session_stats.handle_event(event).await{
|
||||
warn!("Session event handling error - {e}");
|
||||
}},
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
# Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
[package]
|
||||
name = "nym-mixnode"
|
||||
license = "GPL-3.0"
|
||||
version = "1.1.37"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
"Drazen Urch <durch@users.noreply.github.com>",
|
||||
]
|
||||
description = "Implementation of a Loopix-based Mixnode"
|
||||
edition = "2021"
|
||||
rust-version = "1.70"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
colored = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
time.workspace = true
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "net", "signal"] }
|
||||
tokio-util = { workspace = true, features = ["codec"] }
|
||||
url = { workspace = true, features = ["serde"] }
|
||||
tracing = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
# internal
|
||||
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-task = { path = "../common/task" }
|
||||
nym-types = { path = "../common/types" }
|
||||
nym-topology = { path = "../common/topology" }
|
||||
nym-validator-client = { path = "../common/client-libs/validator-client" }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true, features = [
|
||||
"rt-multi-thread",
|
||||
"net",
|
||||
"signal",
|
||||
"test-util",
|
||||
] }
|
||||
|
||||
nym-sphinx-types = { path = "../common/nymsphinx/types" }
|
||||
nym-sphinx-params = { path = "../common/nymsphinx/params" }
|
||||
@@ -1,75 +0,0 @@
|
||||
<!--
|
||||
Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
|
||||
SPDX-License-Identifier: GPL-3.0-only
|
||||
-->
|
||||
|
||||
# Nym Mixnode
|
||||
|
||||
A Rust mixnode implementation.
|
||||
|
||||
## License
|
||||
|
||||
Copyright (C) 2020 Nym Technologies SA <contact@nymtech.net>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
## Usage
|
||||
|
||||
* `nym-mixnode` prints a help message showing usage options
|
||||
* `nym-mixnode run --help` prints a help message showing usage options for the run command
|
||||
* `nym-mixnode run --layer 1 --host x.x.x.x` will start the mixnode in layer 1 and bind to the specified host IP address. Coordinate with other people in your network to find out which layer needs coverage.
|
||||
|
||||
By default, the Nym Mixnode will start on port 1789. If desired, you can change the port using the `--port` option.
|
||||
|
||||
## Install debian
|
||||
|
||||
```bash
|
||||
sudo curl -s --compressed "http://apt.nymtech.net.s3-website.eu-central-1.amazonaws.com/nymtech.gpg" | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/nymtech.gpg > /dev/null
|
||||
sudo echo "deb [signed-by=/etc/apt/trusted.gpg.d/nymtech.gpg] http://apt.nymtech.net.s3-website.eu-central-1.amazonaws.com/ squeeze main" > /etc/apt/sources.list.d/nymtech.list
|
||||
|
||||
sudo apt-get update
|
||||
sudo apt-get install nym-mixnode
|
||||
|
||||
# See below for starting and managing the node
|
||||
```
|
||||
|
||||
## Systemd support
|
||||
|
||||
```bash
|
||||
sudo systemctl enable nym-mixnode
|
||||
|
||||
# Run
|
||||
sudo systemctl start nym-mixnode
|
||||
|
||||
# Check status
|
||||
sudo systemctl status nym-mixnode
|
||||
|
||||
# Logs
|
||||
journalctl -f -u nym-mixnode
|
||||
|
||||
```
|
||||
|
||||
## Build debian package
|
||||
|
||||
```bash
|
||||
# cargo install cargo-deb
|
||||
|
||||
# Build package
|
||||
cargo deb -p nym-mixnode
|
||||
|
||||
# Install
|
||||
|
||||
# This will init the mixnode to `/etc/nym` as `nym` user, and create a systemd service
|
||||
sudo dpkg -i target/debian/<PACKAGE>
|
||||
```
|
||||
@@ -1,237 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
|
||||
// 'RTT MEASUREMENT'
|
||||
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);
|
||||
|
||||
// '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);
|
||||
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;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Config {
|
||||
pub host: Host,
|
||||
|
||||
pub http: Http,
|
||||
|
||||
pub mixnode: MixNode,
|
||||
|
||||
pub verloc: Verloc,
|
||||
|
||||
pub debug: Debug,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn externally_loaded(
|
||||
host: impl Into<Host>,
|
||||
http: impl Into<Http>,
|
||||
mixnode: impl Into<MixNode>,
|
||||
verloc: impl Into<Verloc>,
|
||||
debug: impl Into<Debug>,
|
||||
) -> Self {
|
||||
Config {
|
||||
host: host.into(),
|
||||
http: http.into(),
|
||||
mixnode: mixnode.into(),
|
||||
verloc: verloc.into(),
|
||||
debug: debug.into(),
|
||||
}
|
||||
}
|
||||
|
||||
// builder methods
|
||||
pub fn with_custom_nym_apis(mut self, nym_api_urls: Vec<Url>) -> Self {
|
||||
self.mixnode.nym_api_urls = nym_api_urls;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_listening_address(mut self, listening_address: IpAddr) -> Self {
|
||||
self.mixnode.listening_address = listening_address;
|
||||
|
||||
let http_port = self.http.bind_address.port();
|
||||
self.http.bind_address = SocketAddr::new(listening_address, http_port);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_mix_port(mut self, port: u16) -> Self {
|
||||
self.mixnode.mix_port = port;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_verloc_port(mut self, port: u16) -> Self {
|
||||
self.mixnode.verloc_port = port;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_http_api_port(mut self, port: u16) -> Self {
|
||||
let http_ip = self.http.bind_address.ip();
|
||||
self.http.bind_address = SocketAddr::new(http_ip, port);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_nym_api_endpoints(&self) -> Vec<Url> {
|
||||
self.mixnode.nym_api_urls.clone()
|
||||
}
|
||||
|
||||
pub fn with_metrics_key(mut self, metrics_key: String) -> Self {
|
||||
self.http.metrics_key = Some(metrics_key);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn metrics_key(&self) -> Option<&String> {
|
||||
self.http.metrics_key.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this is very much a WIP. we need proper ssl certificate support here
|
||||
#[derive(Debug, PartialEq)]
|
||||
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<IpAddr>,
|
||||
|
||||
/// Optional hostname of this node, for example nymtech.net.
|
||||
// TODO: this is temporary. to be replaced by pulling the data directly from the certs.
|
||||
pub hostname: Option<String>,
|
||||
}
|
||||
|
||||
impl Host {
|
||||
pub fn validate(&self) -> bool {
|
||||
if self.public_ips.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
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.
|
||||
pub landing_page_assets_path: Option<PathBuf>,
|
||||
|
||||
pub metrics_key: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct MixNode {
|
||||
/// Version of the mixnode for which this configuration was created.
|
||||
pub version: String,
|
||||
|
||||
/// ID specifies the human readable ID of this particular mixnode.
|
||||
pub id: String,
|
||||
|
||||
/// Address to which this mixnode will bind to and will be listening for packets.
|
||||
pub listening_address: IpAddr,
|
||||
|
||||
/// Port used for listening for all mixnet traffic.
|
||||
/// (default: 1789)
|
||||
pub mix_port: u16,
|
||||
|
||||
/// Port used for listening for verloc traffic.
|
||||
/// (default: 1790)
|
||||
pub verloc_port: u16,
|
||||
|
||||
/// Addresses to nym APIs from which the node gets the view of the network.
|
||||
pub nym_api_urls: Vec<Url>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Verloc {
|
||||
/// 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.
|
||||
pub connection_timeout: Duration,
|
||||
|
||||
/// Specifies maximum amount of time to wait for the reply packet to arrive before abandoning the test.
|
||||
pub packet_timeout: Duration,
|
||||
|
||||
/// Specifies delay between subsequent test packets being sent (after receiving a reply).
|
||||
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.
|
||||
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.
|
||||
pub retry_timeout: Duration,
|
||||
}
|
||||
|
||||
impl Default for Verloc {
|
||||
fn default() -> Self {
|
||||
Verloc {
|
||||
packets_per_node: DEFAULT_PACKETS_PER_NODE,
|
||||
connection_timeout: DEFAULT_CONNECTION_TIMEOUT,
|
||||
packet_timeout: DEFAULT_PACKET_TIMEOUT,
|
||||
delay_between_packets: DEFAULT_DELAY_BETWEEN_PACKETS,
|
||||
tested_nodes_batch_size: DEFAULT_BATCH_SIZE,
|
||||
testing_interval: DEFAULT_TESTING_INTERVAL,
|
||||
retry_timeout: DEFAULT_RETRY_TIMEOUT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Debug {
|
||||
/// Delay between each subsequent node statistics being logged to the console
|
||||
pub node_stats_logging_delay: Duration,
|
||||
|
||||
/// Delay between each subsequent node statistics being updated
|
||||
pub node_stats_updating_delay: Duration,
|
||||
|
||||
/// Initial value of an exponential backoff to reconnect to dropped TCP connection when
|
||||
/// forwarding sphinx packets.
|
||||
pub packet_forwarding_initial_backoff: Duration,
|
||||
|
||||
/// Maximum value of an exponential backoff to reconnect to dropped TCP connection when
|
||||
/// forwarding sphinx packets.
|
||||
pub packet_forwarding_maximum_backoff: Duration,
|
||||
|
||||
/// Timeout for establishing initial connection when trying to forward a sphinx packet.
|
||||
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,
|
||||
|
||||
/// Specifies whether the mixnode should be using the legacy framing for the sphinx packets.
|
||||
// it's set to true by default. The reason for that decision is to preserve compatibility with the
|
||||
// existing nodes whilst everyone else is upgrading and getting the code for handling the new field.
|
||||
// It shall be disabled in the subsequent releases.
|
||||
pub use_legacy_framed_packet_version: bool,
|
||||
}
|
||||
|
||||
impl Default for Debug {
|
||||
fn default() -> Self {
|
||||
Debug {
|
||||
node_stats_logging_delay: DEFAULT_NODE_STATS_LOGGING_DELAY,
|
||||
node_stats_updating_delay: DEFAULT_NODE_STATS_UPDATING_DELAY,
|
||||
packet_forwarding_initial_backoff: DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF,
|
||||
packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF,
|
||||
initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT,
|
||||
maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE,
|
||||
use_legacy_framed_packet_version: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
pub mod config;
|
||||
pub mod node;
|
||||
|
||||
pub use node::MixNode;
|
||||
@@ -1,129 +0,0 @@
|
||||
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::node::listener::connection_handler::packet_processing::PacketProcessor;
|
||||
use crate::node::packet_delayforwarder::PacketDelayForwardSender;
|
||||
use crate::node::TaskClient;
|
||||
use futures::StreamExt;
|
||||
use nym_metrics::nanos;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_sphinx::framing::codec::NymCodec;
|
||||
use nym_sphinx::framing::packet::FramedNymPacket;
|
||||
use nym_sphinx::framing::processing::MixProcessingResult;
|
||||
use nym_sphinx::Delay as SphinxDelay;
|
||||
use packet_processing::process_received_packet;
|
||||
use std::net::SocketAddr;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::time::Instant;
|
||||
use tokio_util::codec::Framed;
|
||||
use tracing::{debug, error, info, trace, warn};
|
||||
|
||||
pub(crate) mod packet_processing;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ConnectionHandler {
|
||||
packet_processor: PacketProcessor,
|
||||
delay_forwarding_channel: PacketDelayForwardSender,
|
||||
}
|
||||
|
||||
impl ConnectionHandler {
|
||||
pub(crate) fn new(
|
||||
packet_processor: PacketProcessor,
|
||||
delay_forwarding_channel: PacketDelayForwardSender,
|
||||
) -> Self {
|
||||
ConnectionHandler {
|
||||
packet_processor,
|
||||
delay_forwarding_channel,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn packet_processor(&self) -> &PacketProcessor {
|
||||
&self.packet_processor
|
||||
}
|
||||
|
||||
fn delay_and_forward_packet(&self, mix_packet: MixPacket, delay: Option<SphinxDelay>) {
|
||||
// determine instant at which packet should get forwarded. this way we minimise effect of
|
||||
// being stuck in the queue [of the channel] to get inserted into the delay queue
|
||||
let forward_instant = delay.map(|delay| Instant::now() + delay.to_duration());
|
||||
|
||||
// if unbounded_send() failed it means that the receiver channel was disconnected
|
||||
// and hence something weird must have happened without a way of recovering
|
||||
self.delay_forwarding_channel
|
||||
.unbounded_send((mix_packet, forward_instant))
|
||||
.expect("the delay-forwarder has died!");
|
||||
}
|
||||
|
||||
fn handle_received_packet(&self, framed_sphinx_packet: FramedNymPacket) {
|
||||
//
|
||||
// TODO: here be replay attack detection - it will require similar key cache to the one in
|
||||
// packet processor for vpn packets,
|
||||
// question: can it also be per connection vs global?
|
||||
//
|
||||
|
||||
// all processing such, key caching, etc. was done.
|
||||
// however, if it was a forward hop, we still need to delay it
|
||||
nanos!("handle_received_packet", {
|
||||
self.packet_processor
|
||||
.node_stats_update_sender()
|
||||
.report_received();
|
||||
match process_received_packet(framed_sphinx_packet, self.packet_processor().inner()) {
|
||||
Err(err) => debug!("We failed to process received sphinx packet - {err}"),
|
||||
Ok(res) => match res {
|
||||
MixProcessingResult::ForwardHop(forward_packet, delay) => {
|
||||
self.delay_and_forward_packet(forward_packet, delay)
|
||||
}
|
||||
MixProcessingResult::FinalHop(..) => {
|
||||
warn!("Somehow processed a loop cover message that we haven't implemented yet!")
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn handle_connection(
|
||||
self,
|
||||
conn: TcpStream,
|
||||
remote: SocketAddr,
|
||||
mut shutdown: TaskClient,
|
||||
) {
|
||||
debug!("Starting connection handler for {:?}", remote);
|
||||
shutdown.disarm();
|
||||
let mut framed_conn = Framed::new(conn, NymCodec);
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown.recv() => {
|
||||
trace!("ConnectionHandler: received shutdown");
|
||||
}
|
||||
framed_sphinx_packet = framed_conn.next() => {
|
||||
match framed_sphinx_packet {
|
||||
Some(Ok(framed_sphinx_packet)) => {
|
||||
// TODO: benchmark spawning tokio task with full processing vs just processing it
|
||||
// synchronously (without delaying inside of course,
|
||||
// delay is moved to a global DelayQueue)
|
||||
// under higher load in single and multi-threaded situation.
|
||||
|
||||
// in theory we could process multiple sphinx packet from the same connection in parallel,
|
||||
// but we already handle multiple concurrent connections so if anything, making
|
||||
// that change would only slow things down
|
||||
self.handle_received_packet(framed_sphinx_packet);
|
||||
}
|
||||
Some(Err(err)) => {
|
||||
error!(
|
||||
"{remote:?} - The socket connection got corrupted with error: {err}. Closing the socket",
|
||||
);
|
||||
return;
|
||||
}
|
||||
None => break, // stream got closed by remote
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
info!(
|
||||
"Closing connection from {:?}",
|
||||
framed_conn.into_inner().peer_addr()
|
||||
);
|
||||
trace!("ConnectionHandler: Exiting");
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::node::node_statistics;
|
||||
use nym_crypto::asymmetric::encryption;
|
||||
use nym_mixnode_common::packet_processor::error::MixProcessingError;
|
||||
use nym_mixnode_common::packet_processor::processor::SphinxPacketProcessor;
|
||||
use nym_sphinx::framing::packet::FramedNymPacket;
|
||||
use nym_sphinx::framing::processing::{process_framed_packet, MixProcessingResult};
|
||||
|
||||
// PacketProcessor contains all data required to correctly unwrap and forward sphinx packets
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct PacketProcessor {
|
||||
/// Responsible for performing unwrapping
|
||||
inner_processor: SphinxPacketProcessor,
|
||||
|
||||
/// Responsible for updating metrics data
|
||||
node_stats_update_sender: node_statistics::UpdateSender,
|
||||
}
|
||||
|
||||
impl PacketProcessor {
|
||||
pub(crate) fn new(
|
||||
encryption_key: &encryption::PrivateKey,
|
||||
node_stats_update_sender: node_statistics::UpdateSender,
|
||||
) -> Self {
|
||||
PacketProcessor {
|
||||
inner_processor: SphinxPacketProcessor::new(encryption_key.into()),
|
||||
node_stats_update_sender,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inner(&self) -> &SphinxPacketProcessor {
|
||||
&self.inner_processor
|
||||
}
|
||||
|
||||
pub fn node_stats_update_sender(&self) -> &node_statistics::UpdateSender {
|
||||
&self.node_stats_update_sender
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_received_packet(
|
||||
packet: FramedNymPacket,
|
||||
inner_processor: &SphinxPacketProcessor,
|
||||
) -> Result<MixProcessingResult, MixProcessingError> {
|
||||
Ok(process_framed_packet(packet, inner_processor.sphinx_key())?)
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::node::listener::connection_handler::ConnectionHandler;
|
||||
use std::net::SocketAddr;
|
||||
use std::process;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::task::JoinHandle;
|
||||
use tracing::{error, info, trace, warn};
|
||||
|
||||
use super::TaskClient;
|
||||
|
||||
pub(crate) mod connection_handler;
|
||||
|
||||
pub(crate) struct Listener {
|
||||
address: SocketAddr,
|
||||
shutdown: TaskClient,
|
||||
}
|
||||
|
||||
impl Listener {
|
||||
pub(crate) fn new(address: SocketAddr, shutdown: TaskClient) -> Self {
|
||||
Listener { address, shutdown }
|
||||
}
|
||||
|
||||
async fn run(&mut self, connection_handler: ConnectionHandler) {
|
||||
trace!("Starting Listener");
|
||||
let listener = match TcpListener::bind(self.address).await {
|
||||
Ok(listener) => listener,
|
||||
Err(err) => {
|
||||
error!("Failed to bind to {} - {err}. Are you sure nothing else is running on the specified port and your user has sufficient permission to bind to the requested address?", self.address);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
while !self.shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = self.shutdown.recv() => {
|
||||
trace!("Listener: Received shutdown");
|
||||
}
|
||||
connection = listener.accept() => {
|
||||
match connection {
|
||||
Ok((socket, remote_addr)) => {
|
||||
let handler = connection_handler.clone();
|
||||
tokio::spawn(handler.handle_connection(socket, remote_addr, self.shutdown.clone()));
|
||||
}
|
||||
Err(err) => warn!("Failed to accept incoming connection - {err}"),
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
trace!("Listener: Exiting");
|
||||
}
|
||||
|
||||
pub(crate) fn start(mut self, connection_handler: ConnectionHandler) -> JoinHandle<()> {
|
||||
info!("Running mix listener on {:?}", self.address.to_string());
|
||||
|
||||
tokio::spawn(async move { self.run(connection_handler).await })
|
||||
}
|
||||
}
|
||||
@@ -1,217 +0,0 @@
|
||||
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::node::listener::connection_handler::packet_processing::PacketProcessor;
|
||||
use crate::node::listener::connection_handler::ConnectionHandler;
|
||||
use crate::node::listener::Listener;
|
||||
use crate::node::packet_delayforwarder::{DelayForwarder, PacketDelayForwardSender};
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
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 std::net::SocketAddr;
|
||||
use std::process;
|
||||
use std::sync::Arc;
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
mod listener;
|
||||
mod node_statistics;
|
||||
mod packet_delayforwarder;
|
||||
|
||||
// the MixNode will live for whole duration of this program
|
||||
pub struct MixNode {
|
||||
config: Config,
|
||||
identity_keypair: Arc<identity::KeyPair>,
|
||||
sphinx_keypair: Arc<encryption::KeyPair>,
|
||||
|
||||
task_client: Option<TaskClient>,
|
||||
mixing_stats: Option<SharedMixingStats>,
|
||||
verloc_stats: Option<SharedVerlocStats>,
|
||||
}
|
||||
|
||||
impl MixNode {
|
||||
pub fn new_loaded(
|
||||
config: Config,
|
||||
identity_keypair: Arc<identity::KeyPair>,
|
||||
sphinx_keypair: Arc<encryption::KeyPair>,
|
||||
) -> Self {
|
||||
MixNode {
|
||||
task_client: None,
|
||||
config,
|
||||
identity_keypair,
|
||||
sphinx_keypair,
|
||||
mixing_stats: None,
|
||||
verloc_stats: None,
|
||||
}
|
||||
}
|
||||
|
||||
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 start_node_stats_controller(
|
||||
&mut self,
|
||||
shutdown: TaskClient,
|
||||
) -> (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 update_sender = controller.start();
|
||||
|
||||
(mixing_stats, update_sender)
|
||||
}
|
||||
|
||||
fn start_socket_listener(
|
||||
&self,
|
||||
node_stats_update_sender: node_statistics::UpdateSender,
|
||||
delay_forwarding_channel: PacketDelayForwardSender,
|
||||
shutdown: TaskClient,
|
||||
) {
|
||||
info!("Starting socket listener...");
|
||||
|
||||
let packet_processor =
|
||||
PacketProcessor::new(self.sphinx_keypair.private_key(), node_stats_update_sender);
|
||||
|
||||
let connection_handler = ConnectionHandler::new(packet_processor, delay_forwarding_channel);
|
||||
|
||||
let listening_address = SocketAddr::new(
|
||||
self.config.mixnode.listening_address,
|
||||
self.config.mixnode.mix_port,
|
||||
);
|
||||
|
||||
Listener::new(listening_address, shutdown).start(connection_handler);
|
||||
}
|
||||
|
||||
fn start_packet_delay_forwarder(
|
||||
&mut self,
|
||||
node_stats_update_sender: node_statistics::UpdateSender,
|
||||
shutdown: TaskClient,
|
||||
) -> PacketDelayForwardSender {
|
||||
info!("Starting packet delay-forwarder...");
|
||||
|
||||
let client_config = nym_mixnet_client::Config::new(
|
||||
self.config.debug.packet_forwarding_initial_backoff,
|
||||
self.config.debug.packet_forwarding_maximum_backoff,
|
||||
self.config.debug.initial_connection_timeout,
|
||||
self.config.debug.maximum_connection_buffer_size,
|
||||
self.config.debug.use_legacy_framed_packet_version,
|
||||
);
|
||||
|
||||
let mut packet_forwarder = DelayForwarder::new(
|
||||
nym_mixnet_client::Client::new(client_config),
|
||||
node_stats_update_sender,
|
||||
shutdown,
|
||||
);
|
||||
|
||||
let packet_sender = packet_forwarder.sender();
|
||||
|
||||
tokio::spawn(async move { packet_forwarder.run().await });
|
||||
packet_sender
|
||||
}
|
||||
|
||||
fn start_verloc_measurements(&mut self, shutdown: TaskClient) -> SharedVerlocStats {
|
||||
info!("Starting the round-trip-time measurer...");
|
||||
|
||||
// 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,
|
||||
);
|
||||
|
||||
let config = verloc::ConfigBuilder::new()
|
||||
.listening_address(listening_address)
|
||||
.packets_per_node(self.config.verloc.packets_per_node)
|
||||
.connection_timeout(self.config.verloc.connection_timeout)
|
||||
.packet_timeout(self.config.verloc.packet_timeout)
|
||||
.delay_between_packets(self.config.verloc.delay_between_packets)
|
||||
.tested_nodes_batch_size(self.config.verloc.tested_nodes_batch_size)
|
||||
.testing_interval(self.config.verloc.testing_interval)
|
||||
.retry_timeout(self.config.verloc.retry_timeout)
|
||||
.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);
|
||||
verloc_measurer.set_shared_state(verloc_state.clone());
|
||||
|
||||
tokio::spawn(async move { verloc_measurer.run().await });
|
||||
verloc_state
|
||||
}
|
||||
|
||||
async fn check_if_bonded(&self) -> bool {
|
||||
// TODO: if anything, this should be getting data directly from the contract
|
||||
// as opposed to the validator API
|
||||
for api_url in self.config.get_nym_api_endpoints() {
|
||||
let client = nym_validator_client::NymApiClient::new(api_url.clone());
|
||||
match client.get_all_basic_nodes(None).await {
|
||||
Ok(nodes) => {
|
||||
return nodes.iter().any(|node| {
|
||||
&node.ed25519_identity_pubkey == self.identity_keypair.public_key()
|
||||
})
|
||||
}
|
||||
Err(err) => {
|
||||
error!("failed to grab initial network mixnodes from {api_url}: {err}",);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
error!(
|
||||
"failed to grab initial network mixnodes from any of the available apis. Please try to startup again in few minutes",
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
async fn wait_for_interrupt(&self, shutdown: TaskHandle) {
|
||||
let _res = shutdown.wait_for_shutdown().await;
|
||||
info!("Stopping nym mixnode");
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) {
|
||||
info!("Starting nym mixnode");
|
||||
|
||||
if self.check_if_bonded().await {
|
||||
warn!("You seem to have bonded your mixnode before starting it - that's highly unrecommended as in the future it might result in slashing");
|
||||
}
|
||||
|
||||
// Shutdown notifier for signalling tasks to stop
|
||||
let shutdown = self
|
||||
.task_client
|
||||
.take()
|
||||
.map(Into::<TaskHandle>::into)
|
||||
.unwrap_or_default()
|
||||
.name_if_unnamed("mixnode");
|
||||
|
||||
let (_, 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.fork("DelayForwarder"),
|
||||
);
|
||||
self.start_socket_listener(
|
||||
node_stats_update_sender,
|
||||
delay_forwarding_channel,
|
||||
shutdown.fork("Listener"),
|
||||
);
|
||||
self.start_verloc_measurements(shutdown.fork("VerlocMeasurer"));
|
||||
|
||||
info!("Finished nym mixnode startup procedure - it should now be able to receive mix traffic!");
|
||||
self.wait_for_interrupt(shutdown).await;
|
||||
}
|
||||
}
|
||||
@@ -1,435 +0,0 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_metrics::inc_by;
|
||||
|
||||
use super::TaskClient;
|
||||
use futures::channel::mpsc;
|
||||
use futures::lock::Mutex;
|
||||
use futures::StreamExt;
|
||||
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;
|
||||
use time::OffsetDateTime;
|
||||
use tracing::{debug, info, trace};
|
||||
|
||||
// convenience aliases
|
||||
type PacketsMap = HashMap<String, u64>;
|
||||
type PacketDataReceiver = mpsc::UnboundedReceiver<PacketEvent>;
|
||||
type PacketDataSender = mpsc::UnboundedSender<PacketEvent>;
|
||||
|
||||
trait MixingStatsUpdateExt {
|
||||
async fn update(&self, new_received: u64, new_sent: PacketsMap, new_dropped: PacketsMap);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
guard.packets_received_since_startup += new_received;
|
||||
for count in new_sent.values() {
|
||||
guard.packets_sent_since_startup_all += count;
|
||||
}
|
||||
|
||||
for count in new_dropped.values() {
|
||||
guard.packets_dropped_since_startup_all += count;
|
||||
}
|
||||
|
||||
inc_by!("packets_received_since_startup", new_received);
|
||||
inc_by!(
|
||||
"packets_sent_since_startup_all",
|
||||
new_sent.values().sum::<u64>()
|
||||
);
|
||||
inc_by!(
|
||||
"packets_dropped_since_startup_all",
|
||||
new_dropped.values().sum::<u64>()
|
||||
);
|
||||
|
||||
guard.packets_received_since_last_update = new_received;
|
||||
guard.packets_sent_since_last_update = new_sent;
|
||||
guard.packets_explicitly_dropped_since_last_update = new_dropped;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum PacketEvent {
|
||||
Sent(String),
|
||||
Received,
|
||||
Dropped(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct CurrentPacketData {
|
||||
inner: Arc<PacketDataInner>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct PacketDataInner {
|
||||
received: AtomicU64,
|
||||
sent: Mutex<PacketsMap>,
|
||||
dropped: Mutex<PacketsMap>,
|
||||
}
|
||||
|
||||
impl CurrentPacketData {
|
||||
pub(crate) fn new() -> Self {
|
||||
CurrentPacketData {
|
||||
inner: Arc::new(PacketDataInner {
|
||||
received: AtomicU64::new(0),
|
||||
sent: Mutex::new(HashMap::new()),
|
||||
dropped: Mutex::new(HashMap::new()),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn increment_received(&self) {
|
||||
self.inner.received.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
async fn increment_sent(&self, destination: String) {
|
||||
let mut unlocked = self.inner.sent.lock().await;
|
||||
let receiver_count = unlocked.entry(destination).or_insert(0);
|
||||
*receiver_count += 1;
|
||||
}
|
||||
|
||||
async fn increment_dropped(&self, destination: String) {
|
||||
let mut unlocked = self.inner.dropped.lock().await;
|
||||
let dropped_count = unlocked.entry(destination).or_insert(0);
|
||||
*dropped_count += 1;
|
||||
}
|
||||
|
||||
async fn acquire_and_reset(&self) -> (u64, PacketsMap, PacketsMap) {
|
||||
let mut unlocked_sent = self.inner.sent.lock().await;
|
||||
let mut unlocked_dropped = self.inner.dropped.lock().await;
|
||||
let received = self.inner.received.swap(0, Ordering::SeqCst);
|
||||
|
||||
let sent = std::mem::take(unlocked_sent.deref_mut());
|
||||
let dropped = std::mem::take(unlocked_dropped.deref_mut());
|
||||
|
||||
(received, sent, dropped)
|
||||
}
|
||||
}
|
||||
|
||||
// Worker that listens to a channel and updates the shared current packet data
|
||||
struct UpdateHandler {
|
||||
current_data: CurrentPacketData,
|
||||
update_receiver: PacketDataReceiver,
|
||||
shutdown: TaskClient,
|
||||
}
|
||||
|
||||
impl UpdateHandler {
|
||||
fn new(
|
||||
current_data: CurrentPacketData,
|
||||
update_receiver: PacketDataReceiver,
|
||||
shutdown: TaskClient,
|
||||
) -> Self {
|
||||
UpdateHandler {
|
||||
current_data,
|
||||
update_receiver,
|
||||
shutdown,
|
||||
}
|
||||
}
|
||||
|
||||
async fn run(&mut self) {
|
||||
trace!("Starting UpdateHandler");
|
||||
while !self.shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
Some(packet_data) = self.update_receiver.next() => {
|
||||
match packet_data {
|
||||
PacketEvent::Received => self.current_data.increment_received(),
|
||||
PacketEvent::Sent(destination) => {
|
||||
self.current_data.increment_sent(destination).await
|
||||
}
|
||||
PacketEvent::Dropped(destination) => {
|
||||
self.current_data.increment_dropped(destination).await
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = self.shutdown.recv() => {
|
||||
trace!("UpdateHandler: Received shutdown");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trace!("UpdateHandler: Exiting");
|
||||
}
|
||||
}
|
||||
|
||||
// Channel to report statistics
|
||||
#[derive(Clone)]
|
||||
pub struct UpdateSender(PacketDataSender);
|
||||
|
||||
impl UpdateSender {
|
||||
pub(crate) fn new(update_sender: PacketDataSender) -> Self {
|
||||
UpdateSender(update_sender)
|
||||
}
|
||||
|
||||
pub(crate) fn report_sent(&self, destination: String) {
|
||||
// in unbounded_send() failed it means that the receiver channel was disconnected
|
||||
// and hence something weird must have happened without a way of recovering
|
||||
self.0
|
||||
.unbounded_send(PacketEvent::Sent(destination))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
// TODO: in the future this could be slightly optimised to get rid of the channel
|
||||
// in favour of incrementing value directly
|
||||
pub(crate) fn report_received(&self) {
|
||||
// in unbounded_send() failed it means that the receiver channel was disconnected
|
||||
// and hence something weird must have happened without a way of recovering
|
||||
self.0.unbounded_send(PacketEvent::Received).unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn report_dropped(&self, destination: String) {
|
||||
// in unbounded_send() failed it means that the receiver channel was disconnected
|
||||
// and hence something weird must have happened without a way of recovering
|
||||
self.0
|
||||
.unbounded_send(PacketEvent::Dropped(destination))
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
// Worker that periodically updates the shared node stats from the current packet data buffer that
|
||||
// the `UpdateHandler` updates.
|
||||
struct StatsUpdater {
|
||||
updating_delay: Duration,
|
||||
current_packet_data: CurrentPacketData,
|
||||
current_stats: SharedMixingStats,
|
||||
shutdown: TaskClient,
|
||||
}
|
||||
|
||||
impl StatsUpdater {
|
||||
fn new(
|
||||
updating_delay: Duration,
|
||||
current_packet_data: CurrentPacketData,
|
||||
current_stats: SharedMixingStats,
|
||||
shutdown: TaskClient,
|
||||
) -> Self {
|
||||
StatsUpdater {
|
||||
updating_delay,
|
||||
current_packet_data,
|
||||
current_stats,
|
||||
shutdown,
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_stats(&self) {
|
||||
// grab new data since last update
|
||||
let (received, sent, dropped) = self.current_packet_data.acquire_and_reset().await;
|
||||
self.current_stats.update(received, sent, dropped).await;
|
||||
}
|
||||
|
||||
async fn run(&mut self) {
|
||||
while !self.shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
_ = tokio::time::sleep(self.updating_delay) => self.update_stats().await,
|
||||
_ = self.shutdown.recv() => {
|
||||
trace!("StatsUpdater: Received shutdown");
|
||||
}
|
||||
}
|
||||
}
|
||||
trace!("StatsUpdater: Exiting");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: question: should this data still be logged to the console or should we perhaps remove it
|
||||
// since we have the http endpoint now?
|
||||
struct PacketStatsConsoleLogger {
|
||||
logging_delay: Duration,
|
||||
stats: SharedMixingStats,
|
||||
shutdown: TaskClient,
|
||||
}
|
||||
|
||||
impl PacketStatsConsoleLogger {
|
||||
fn new(logging_delay: Duration, stats: SharedMixingStats, shutdown: TaskClient) -> Self {
|
||||
PacketStatsConsoleLogger {
|
||||
logging_delay,
|
||||
stats,
|
||||
shutdown,
|
||||
}
|
||||
}
|
||||
|
||||
async fn log_running_stats(&mut self) {
|
||||
let stats = self.stats.read().await;
|
||||
|
||||
// it's super unlikely this will ever fail, but anything involving time is super weird
|
||||
// so let's just guard against it
|
||||
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.whole_seconds();
|
||||
|
||||
info!(
|
||||
"Since startup mixed {} packets! ({} in last {} seconds)",
|
||||
stats.packets_sent_since_startup_all,
|
||||
stats.packets_sent_since_last_update.values().sum::<u64>(),
|
||||
difference_secs,
|
||||
);
|
||||
if stats.packets_dropped_since_startup_all > 0 {
|
||||
info!(
|
||||
"Since startup dropped {} packets! ({} in last {} seconds)",
|
||||
stats.packets_dropped_since_startup_all,
|
||||
stats
|
||||
.packets_explicitly_dropped_since_last_update
|
||||
.values()
|
||||
.sum::<u64>(),
|
||||
difference_secs,
|
||||
);
|
||||
}
|
||||
|
||||
debug!(
|
||||
"Since startup received {} packets ({} in last {} seconds)",
|
||||
stats.packets_received_since_startup,
|
||||
stats.packets_received_since_last_update,
|
||||
difference_secs,
|
||||
);
|
||||
trace!(
|
||||
"Since startup sent packets to the following: \n{:#?} \n And in last {} seconds: {:#?})",
|
||||
stats.packets_sent_since_startup_all,
|
||||
difference_secs,
|
||||
stats.packets_sent_since_last_update
|
||||
);
|
||||
} else {
|
||||
info!(
|
||||
"Since startup mixed {} packets!",
|
||||
stats.packets_sent_since_startup_all,
|
||||
);
|
||||
if stats.packets_dropped_since_startup_all > 0 {
|
||||
info!(
|
||||
"Since startup dropped {} packets!",
|
||||
stats.packets_dropped_since_startup_all,
|
||||
);
|
||||
}
|
||||
|
||||
debug!(
|
||||
"Since startup received {} packets",
|
||||
stats.packets_received_since_startup
|
||||
);
|
||||
trace!(
|
||||
"Since startup sent packets {}",
|
||||
stats.packets_sent_since_startup_all
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async fn run(&mut self) {
|
||||
trace!("Starting PacketStatsConsoleLogger");
|
||||
while !self.shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
_ = tokio::time::sleep(self.logging_delay) => self.log_running_stats().await,
|
||||
_ = self.shutdown.recv() => {
|
||||
trace!("PacketStatsConsoleLogger: Received shutdown");
|
||||
}
|
||||
};
|
||||
}
|
||||
trace!("PacketStatsConsoleLogger: Exiting");
|
||||
}
|
||||
}
|
||||
|
||||
// basically an easy single entry point to start all of the required tasks
|
||||
pub struct Controller {
|
||||
/// Responsible for handling data coming from UpdateSender
|
||||
update_handler: UpdateHandler,
|
||||
|
||||
/// Wrapper around channel sending information about new packet being received or sent
|
||||
update_sender: UpdateSender,
|
||||
|
||||
/// Responsible for logging stats to the console at given interval
|
||||
console_logger: PacketStatsConsoleLogger,
|
||||
|
||||
/// Responsible for updating stats at given interval
|
||||
stats_updater: StatsUpdater,
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
Controller {
|
||||
update_handler: UpdateHandler::new(
|
||||
shared_packet_data.clone(),
|
||||
receiver,
|
||||
shutdown.clone(),
|
||||
),
|
||||
update_sender: UpdateSender::new(sender),
|
||||
console_logger: PacketStatsConsoleLogger::new(
|
||||
logging_delay,
|
||||
mixing_stats.clone(),
|
||||
shutdown.clone(),
|
||||
),
|
||||
stats_updater: StatsUpdater::new(
|
||||
stats_updating_delay,
|
||||
shared_packet_data,
|
||||
mixing_stats.clone(),
|
||||
shutdown,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// reporter is how node is going to be accessing the metrics data
|
||||
pub(crate) fn start(self) -> UpdateSender {
|
||||
// move out of self
|
||||
let mut update_handler = self.update_handler;
|
||||
let mut stats_updater = self.stats_updater;
|
||||
let mut console_logger = self.console_logger;
|
||||
|
||||
tokio::spawn(async move { update_handler.run().await });
|
||||
tokio::spawn(async move { stats_updater.run().await });
|
||||
tokio::spawn(async move { console_logger.run().await });
|
||||
|
||||
self.update_sender
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use nym_metrics::metrics;
|
||||
use nym_task::TaskManager;
|
||||
|
||||
#[tokio::test]
|
||||
async fn node_stats_reported_are_received() {
|
||||
let logging_delay = Duration::from_millis(20);
|
||||
let stats_updating_delay = Duration::from_millis(10);
|
||||
let shutdown = TaskManager::default();
|
||||
let stats = SharedMixingStats::new();
|
||||
let node_stats_controller = Controller::new(
|
||||
logging_delay,
|
||||
stats_updating_delay,
|
||||
stats.clone(),
|
||||
shutdown.subscribe(),
|
||||
);
|
||||
|
||||
let update_sender = node_stats_controller.start();
|
||||
tokio::time::pause();
|
||||
|
||||
// Pass input
|
||||
update_sender.report_sent("foo".to_string());
|
||||
update_sender.report_sent("foo".to_string());
|
||||
tokio::task::yield_now().await;
|
||||
|
||||
tokio::time::advance(Duration::from_secs(1)).await;
|
||||
tokio::task::yield_now().await;
|
||||
|
||||
// Get output (stats)
|
||||
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);
|
||||
assert_eq!(&stats.packets_received_since_startup, &0);
|
||||
assert_eq!(&stats.packets_dropped_since_startup_all, &0);
|
||||
assert_eq!(metrics!(), "# HELP nym_mixnode_packets_dropped_since_startup_all nym_mixnode_packets_dropped_since_startup_all\n# TYPE nym_mixnode_packets_dropped_since_startup_all counter\nnym_mixnode_packets_dropped_since_startup_all 0\n# HELP nym_mixnode_packets_received_since_startup nym_mixnode_packets_received_since_startup\n# TYPE nym_mixnode_packets_received_since_startup counter\nnym_mixnode_packets_received_since_startup 0\n# HELP nym_mixnode_packets_sent_since_startup_all nym_mixnode_packets_sent_since_startup_all\n# TYPE nym_mixnode_packets_sent_since_startup_all counter\nnym_mixnode_packets_sent_since_startup_all 2\n")
|
||||
}
|
||||
}
|
||||
@@ -1,319 +0,0 @@
|
||||
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use super::TaskClient;
|
||||
use crate::node::node_statistics::UpdateSender;
|
||||
use futures::channel::mpsc;
|
||||
use futures::StreamExt;
|
||||
use nym_nonexhaustive_delayqueue::{Expired, NonExhaustiveDelayQueue};
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use std::io;
|
||||
use tokio::time::Instant;
|
||||
use tracing::trace;
|
||||
|
||||
// Delay + MixPacket vs Instant + MixPacket
|
||||
|
||||
// rather than using Duration directly, we use an Instant, this way we minimise skew due to
|
||||
// time packet spent waiting in the queue to get delayed
|
||||
pub(crate) type PacketDelayForwardSender = mpsc::UnboundedSender<(MixPacket, Option<Instant>)>;
|
||||
type PacketDelayForwardReceiver = mpsc::UnboundedReceiver<(MixPacket, Option<Instant>)>;
|
||||
|
||||
/// Entity responsible for delaying received sphinx packet and forwarding it to next node.
|
||||
pub(crate) struct DelayForwarder<C>
|
||||
where
|
||||
C: nym_mixnet_client::SendWithoutResponse,
|
||||
{
|
||||
delay_queue: NonExhaustiveDelayQueue<MixPacket>,
|
||||
mixnet_client: C,
|
||||
packet_sender: PacketDelayForwardSender,
|
||||
packet_receiver: PacketDelayForwardReceiver,
|
||||
node_stats_update_sender: UpdateSender,
|
||||
shutdown: TaskClient,
|
||||
}
|
||||
|
||||
impl<C> DelayForwarder<C>
|
||||
where
|
||||
C: nym_mixnet_client::SendWithoutResponse,
|
||||
{
|
||||
pub(crate) fn new(
|
||||
client: C,
|
||||
node_stats_update_sender: UpdateSender,
|
||||
shutdown: TaskClient,
|
||||
) -> DelayForwarder<C> {
|
||||
let (packet_sender, packet_receiver) = mpsc::unbounded();
|
||||
|
||||
DelayForwarder::<C> {
|
||||
delay_queue: NonExhaustiveDelayQueue::new(),
|
||||
mixnet_client: client,
|
||||
packet_sender,
|
||||
packet_receiver,
|
||||
node_stats_update_sender,
|
||||
shutdown,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn sender(&self) -> PacketDelayForwardSender {
|
||||
self.packet_sender.clone()
|
||||
}
|
||||
|
||||
fn forward_packet(&mut self, packet: MixPacket) {
|
||||
let next_hop = packet.next_hop();
|
||||
let packet_type = packet.packet_type();
|
||||
let packet = packet.into_packet();
|
||||
|
||||
if let Err(err) = self
|
||||
.mixnet_client
|
||||
.send_without_response(next_hop, packet, packet_type)
|
||||
{
|
||||
if err.kind() == io::ErrorKind::WouldBlock {
|
||||
// we only know for sure if we dropped a packet if our sending queue was full
|
||||
// in any other case the connection might still be re-established (or created for the first time)
|
||||
// and the packet might get sent, but we won't know about it
|
||||
self.node_stats_update_sender
|
||||
.report_dropped(next_hop.to_string())
|
||||
} else if err.kind() == io::ErrorKind::NotConnected {
|
||||
// let's give the benefit of the doubt and assume we manage to establish connection
|
||||
self.node_stats_update_sender
|
||||
.report_sent(next_hop.to_string());
|
||||
}
|
||||
} else {
|
||||
self.node_stats_update_sender
|
||||
.report_sent(next_hop.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
/// Upon packet being finished getting delayed, forward it to the mixnet.
|
||||
fn handle_done_delaying(&mut self, packet: Expired<MixPacket>) {
|
||||
let delayed_packet = packet.into_inner();
|
||||
self.forward_packet(delayed_packet)
|
||||
}
|
||||
|
||||
fn handle_new_packet(&mut self, new_packet: (MixPacket, Option<Instant>)) {
|
||||
// in case of a zero delay packet, don't bother putting it in the delay queue,
|
||||
// just forward it immediately
|
||||
if let Some(instant) = new_packet.1 {
|
||||
// check if the delay has already expired, if so, don't bother putting it through
|
||||
// the delay queue only to retrieve it immediately. Just forward it.
|
||||
if instant.checked_duration_since(Instant::now()).is_none() {
|
||||
self.forward_packet(new_packet.0)
|
||||
} else {
|
||||
self.delay_queue.insert_at(new_packet.0, instant);
|
||||
}
|
||||
} else {
|
||||
self.forward_packet(new_packet.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn run(&mut self) {
|
||||
trace!("Starting DelayForwarder");
|
||||
loop {
|
||||
tokio::select! {
|
||||
delayed = self.delay_queue.next() => {
|
||||
self.handle_done_delaying(delayed.unwrap());
|
||||
}
|
||||
new_packet = self.packet_receiver.next() => {
|
||||
// this one is impossible to ever panic - the object itself contains a sender
|
||||
// and hence it can't happen that ALL senders are dropped
|
||||
self.handle_new_packet(new_packet.unwrap())
|
||||
}
|
||||
_ = self.shutdown.recv() => {
|
||||
trace!("DelayForwarder: Received shutdown");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
trace!("DelayForwarder: Exiting");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
use nym_sphinx::NymPacket;
|
||||
use nym_task::TaskManager;
|
||||
|
||||
use nym_sphinx::addressing::nodes::NymNodeRoutingAddress;
|
||||
use nym_sphinx_params::packet_sizes::PacketSize;
|
||||
use nym_sphinx_params::PacketType;
|
||||
use nym_sphinx_types::{
|
||||
crypto, Delay as SphinxDelay, Destination, DestinationAddressBytes, Node, NodeAddressBytes,
|
||||
DESTINATION_ADDRESS_LENGTH, IDENTIFIER_LENGTH, NODE_ADDRESS_LENGTH,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
struct TestClient {
|
||||
pub packets_sent: Arc<Mutex<Vec<(NymNodeRoutingAddress, NymPacket, PacketType)>>>,
|
||||
}
|
||||
|
||||
impl nym_mixnet_client::SendWithoutResponse for TestClient {
|
||||
fn send_without_response(
|
||||
&mut self,
|
||||
address: NymNodeRoutingAddress,
|
||||
packet: NymPacket,
|
||||
packet_type: PacketType,
|
||||
) -> io::Result<()> {
|
||||
self.packets_sent
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push((address, packet, packet_type));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn make_valid_sphinx_packet(size: PacketSize) -> NymPacket {
|
||||
let (_, node1_pk) = crypto::keygen();
|
||||
let node1 = Node::new(
|
||||
NodeAddressBytes::from_bytes([5u8; NODE_ADDRESS_LENGTH]),
|
||||
node1_pk,
|
||||
);
|
||||
let (_, node2_pk) = crypto::keygen();
|
||||
let node2 = Node::new(
|
||||
NodeAddressBytes::from_bytes([4u8; NODE_ADDRESS_LENGTH]),
|
||||
node2_pk,
|
||||
);
|
||||
let (_, node3_pk) = crypto::keygen();
|
||||
let node3 = Node::new(
|
||||
NodeAddressBytes::from_bytes([2u8; NODE_ADDRESS_LENGTH]),
|
||||
node3_pk,
|
||||
);
|
||||
|
||||
let route = [node1, node2, node3];
|
||||
let destination = Destination::new(
|
||||
DestinationAddressBytes::from_bytes([3u8; DESTINATION_ADDRESS_LENGTH]),
|
||||
[4u8; IDENTIFIER_LENGTH],
|
||||
);
|
||||
let delays = vec![
|
||||
SphinxDelay::new_from_nanos(42),
|
||||
SphinxDelay::new_from_nanos(42),
|
||||
SphinxDelay::new_from_nanos(42),
|
||||
];
|
||||
NymPacket::sphinx_build(size.payload_size(), b"foomp", &route, &destination, &delays)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn make_valid_outfox_packet(size: PacketSize) -> NymPacket {
|
||||
let (_, node1_pk) = crypto::keygen();
|
||||
let node1 = Node::new(
|
||||
NodeAddressBytes::from_bytes([5u8; NODE_ADDRESS_LENGTH]),
|
||||
node1_pk,
|
||||
);
|
||||
let (_, node2_pk) = crypto::keygen();
|
||||
let node2 = Node::new(
|
||||
NodeAddressBytes::from_bytes([4u8; NODE_ADDRESS_LENGTH]),
|
||||
node2_pk,
|
||||
);
|
||||
let (_, node3_pk) = crypto::keygen();
|
||||
let node3 = Node::new(
|
||||
NodeAddressBytes::from_bytes([2u8; NODE_ADDRESS_LENGTH]),
|
||||
node3_pk,
|
||||
);
|
||||
|
||||
let (_, node4_pk) = crypto::keygen();
|
||||
let node4 = Node::new(
|
||||
NodeAddressBytes::from_bytes([2u8; NODE_ADDRESS_LENGTH]),
|
||||
node4_pk,
|
||||
);
|
||||
|
||||
let destination = Destination::new(
|
||||
DestinationAddressBytes::from_bytes([3u8; DESTINATION_ADDRESS_LENGTH]),
|
||||
[4u8; IDENTIFIER_LENGTH],
|
||||
);
|
||||
|
||||
let route = &[node1, node2, node3, node4];
|
||||
|
||||
let payload = vec![1; 48];
|
||||
|
||||
NymPacket::outfox_build(payload, route, &destination, Some(size.plaintext_size())).unwrap()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn packets_received_are_forwarded() {
|
||||
// Wire up the DelayForwarder
|
||||
let (stats_sender, _stats_receiver) = mpsc::unbounded();
|
||||
let node_stats_update_sender = UpdateSender::new(stats_sender);
|
||||
let client = TestClient::default();
|
||||
let client_packets_sent = client.packets_sent.clone();
|
||||
let shutdown = TaskManager::default();
|
||||
let mut delay_forwarder =
|
||||
DelayForwarder::new(client, node_stats_update_sender, shutdown.subscribe());
|
||||
let packet_sender = delay_forwarder.sender();
|
||||
|
||||
// Spawn the worker, listening on packet_sender channel
|
||||
tokio::spawn(async move { delay_forwarder.run().await });
|
||||
|
||||
// Send a `MixPacket` down the channel without any delay attached.
|
||||
let next_hop =
|
||||
NymNodeRoutingAddress::from(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)), 42));
|
||||
let mix_packet = MixPacket::new(
|
||||
next_hop,
|
||||
make_valid_sphinx_packet(PacketSize::default()),
|
||||
PacketType::default(),
|
||||
);
|
||||
let forward_instant = None;
|
||||
packet_sender
|
||||
.unbounded_send((mix_packet, forward_instant))
|
||||
.unwrap();
|
||||
|
||||
// Give the the worker a chance to act
|
||||
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||
|
||||
// The client should have forwarded the packet straight away
|
||||
assert_eq!(
|
||||
client_packets_sent
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|(a, _, _)| *a)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![next_hop]
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn outfox_packets_received_are_forwarded() {
|
||||
// Wire up the DelayForwarder
|
||||
let (stats_sender, _stats_receiver) = mpsc::unbounded();
|
||||
let node_stats_update_sender = UpdateSender::new(stats_sender);
|
||||
let client = TestClient::default();
|
||||
let client_packets_sent = client.packets_sent.clone();
|
||||
let shutdown = TaskManager::default();
|
||||
let mut delay_forwarder =
|
||||
DelayForwarder::new(client, node_stats_update_sender, shutdown.subscribe());
|
||||
let packet_sender = delay_forwarder.sender();
|
||||
|
||||
// Spawn the worker, listening on packet_sender channel
|
||||
tokio::spawn(async move { delay_forwarder.run().await });
|
||||
|
||||
// Send a `MixPacket` down the channel without any delay attached.
|
||||
let next_hop =
|
||||
NymNodeRoutingAddress::from(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)), 42));
|
||||
let mix_packet = MixPacket::new(
|
||||
next_hop,
|
||||
make_valid_outfox_packet(PacketSize::default()),
|
||||
PacketType::default(),
|
||||
);
|
||||
let forward_instant = None;
|
||||
packet_sender
|
||||
.unbounded_send((mix_packet, forward_instant))
|
||||
.unwrap();
|
||||
|
||||
// Give the the worker a chance to act
|
||||
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||
|
||||
// The client should have forwarded the packet straight away
|
||||
assert_eq!(
|
||||
client_packets_sent
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|(a, _, _)| *a)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![next_hop]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -203,7 +203,7 @@ async fn batch_redeem_tickets(
|
||||
|
||||
// 5. check if **every** serial number included in the request has been verified by us
|
||||
// if we have more than requested, tough luck, they're going to lose them
|
||||
let verified = state.get_redeemable_tickets(provider_info).await?;
|
||||
let verified = state.get_redeemable_tickets(&provider_info).await?;
|
||||
let verified_tickets: HashSet<_> = verified.iter().map(|sn| sn.deref()).collect();
|
||||
|
||||
for sn in &received {
|
||||
@@ -215,8 +215,14 @@ async fn batch_redeem_tickets(
|
||||
}
|
||||
}
|
||||
|
||||
// 6. vote on the proposal
|
||||
// TODO: offload it to separate task with work queue and batching (of tx messages) to vote for multiple proposals in the same tx
|
||||
// similarly to what we do inside the credential proxy
|
||||
state.accept_proposal(proposal_id).await?;
|
||||
|
||||
// 7. update the time of the last verification for this provider
|
||||
state.update_last_batch_verification(&provider_info).await?;
|
||||
|
||||
Ok(Json(EcashBatchTicketRedemptionResponse {
|
||||
proposal_accepted: true,
|
||||
}))
|
||||
|
||||
@@ -890,7 +890,7 @@ impl EcashState {
|
||||
|
||||
pub async fn get_redeemable_tickets(
|
||||
&self,
|
||||
provider_info: TicketProvider,
|
||||
provider_info: &TicketProvider,
|
||||
) -> Result<Vec<SerialNumberWrapper>> {
|
||||
let since = provider_info
|
||||
.last_batch_verification
|
||||
@@ -903,6 +903,14 @@ impl EcashState {
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub async fn update_last_batch_verification(&self, provider: &TicketProvider) -> Result<()> {
|
||||
Ok(self
|
||||
.aux
|
||||
.storage
|
||||
.update_last_batch_verification(provider.id, OffsetDateTime::now_utc())
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn get_ticket_data_by_serial_number(
|
||||
&self,
|
||||
serial_number: &[u8],
|
||||
|
||||
@@ -82,6 +82,11 @@ pub trait EcashStorageManagerExt {
|
||||
provider_id: i64,
|
||||
since: OffsetDateTime,
|
||||
) -> Result<Vec<SerialNumberWrapper>, sqlx::Error>;
|
||||
async fn update_last_batch_verification(
|
||||
&self,
|
||||
provider_id: i64,
|
||||
last_batch_verification: OffsetDateTime,
|
||||
) -> Result<(), sqlx::Error>;
|
||||
|
||||
async fn get_spent_tickets_on(
|
||||
&self,
|
||||
@@ -215,15 +220,15 @@ impl EcashStorageManagerExt for StorageManager {
|
||||
"#,
|
||||
expiration_date
|
||||
)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter_map(|r| r.merkle_leaf.try_into().inspect_err(|_| error!("possible database corruption: one of the stored merkle leaves is not a valid 32byte hash")).ok().map(|merkle_leaf| IssuedHash {
|
||||
deposit_id: r.deposit_id,
|
||||
merkle_leaf,
|
||||
merkle_index: r.merkle_index as usize,
|
||||
}))
|
||||
.collect())
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter_map(|r| r.merkle_leaf.try_into().inspect_err(|_| error!("possible database corruption: one of the stored merkle leaves is not a valid 32byte hash")).ok().map(|merkle_leaf| IssuedHash {
|
||||
deposit_id: r.deposit_id,
|
||||
merkle_leaf,
|
||||
merkle_index: r.merkle_index as usize,
|
||||
}))
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Store the provided issued credential information.
|
||||
@@ -344,8 +349,8 @@ impl EcashStorageManagerExt for StorageManager {
|
||||
verified_at,
|
||||
provider_id
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -382,6 +387,25 @@ impl EcashStorageManagerExt for StorageManager {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn update_last_batch_verification(
|
||||
&self,
|
||||
provider_id: i64,
|
||||
last_batch_verification: OffsetDateTime,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
UPDATE ticket_providers
|
||||
SET last_batch_verification = ?
|
||||
WHERE id = ?
|
||||
"#,
|
||||
last_batch_verification,
|
||||
provider_id
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_spent_tickets_on(
|
||||
&self,
|
||||
date: Date,
|
||||
@@ -510,8 +534,8 @@ impl EcashStorageManagerExt for StorageManager {
|
||||
epoch_id,
|
||||
data
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -544,8 +568,8 @@ impl EcashStorageManagerExt for StorageManager {
|
||||
epoch_id,
|
||||
data
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -112,6 +112,11 @@ pub trait EcashStorageExt {
|
||||
provider_id: i64,
|
||||
since: OffsetDateTime,
|
||||
) -> Result<Vec<SerialNumberWrapper>, NymApiStorageError>;
|
||||
async fn update_last_batch_verification(
|
||||
&self,
|
||||
provider_id: i64,
|
||||
last_batch_verification: OffsetDateTime,
|
||||
) -> Result<(), NymApiStorageError>;
|
||||
|
||||
async fn get_all_spent_tickets_on(
|
||||
&self,
|
||||
@@ -395,6 +400,17 @@ impl EcashStorageExt for NymApiStorage {
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn update_last_batch_verification(
|
||||
&self,
|
||||
provider_id: i64,
|
||||
last_batch_verification: OffsetDateTime,
|
||||
) -> Result<(), NymApiStorageError> {
|
||||
Ok(self
|
||||
.manager
|
||||
.update_last_batch_verification(provider_id, last_batch_verification)
|
||||
.await?)
|
||||
}
|
||||
|
||||
async fn get_all_spent_tickets_on(
|
||||
&self,
|
||||
date: Date,
|
||||
|
||||
@@ -68,6 +68,9 @@ pub(crate) struct Args {
|
||||
/// default: `127.0.0.1:8080` in `debug` builds and `0.0.0.0:8080` in `release`
|
||||
#[clap(long)]
|
||||
pub(crate) bind_address: Option<SocketAddr>,
|
||||
|
||||
#[clap(hide = true, long, default_value_t = false)]
|
||||
pub(crate) allow_illegal_ips: bool,
|
||||
// #[clap(short, long, default_value_t = OutputFormat::default())]
|
||||
// output: OutputFormat,
|
||||
}
|
||||
|
||||
@@ -100,6 +100,9 @@ pub(crate) struct Args {
|
||||
/// default: `127.0.0.1:8080` in `debug` builds and `0.0.0.0:8080` in `release`
|
||||
#[clap(long)]
|
||||
pub(crate) bind_address: Option<SocketAddr>,
|
||||
|
||||
#[clap(hide = true, long, default_value_t = false)]
|
||||
pub(crate) allow_illegal_ips: bool,
|
||||
}
|
||||
|
||||
async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result<ShutdownHandles> {
|
||||
|
||||
@@ -175,6 +175,9 @@ impl Config {
|
||||
if let Some(http_bind_address) = args.bind_address {
|
||||
self.base.bind_address = http_bind_address
|
||||
}
|
||||
if args.allow_illegal_ips {
|
||||
self.topology_cacher.debug.node_describe_allow_illegal_ips = true
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ pub(crate) struct OverrideConfig {
|
||||
/// Socket address this api will use for binding its http API.
|
||||
/// default: `127.0.0.1:8080` in `debug` builds and `0.0.0.0:8080` in `release`
|
||||
pub(crate) bind_address: Option<SocketAddr>,
|
||||
|
||||
pub(crate) allow_illegal_ips: bool,
|
||||
}
|
||||
|
||||
impl From<init::Args> for OverrideConfig {
|
||||
@@ -44,6 +46,7 @@ impl From<init::Args> for OverrideConfig {
|
||||
announce_address: args.announce_address,
|
||||
monitor_credentials_mode: Some(args.monitor_credentials_mode),
|
||||
bind_address: args.bind_address,
|
||||
allow_illegal_ips: args.allow_illegal_ips,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,6 +62,7 @@ impl From<run::Args> for OverrideConfig {
|
||||
announce_address: args.announce_address,
|
||||
monitor_credentials_mode: args.monitor_credentials_mode,
|
||||
bind_address: args.bind_address,
|
||||
allow_illegal_ips: args.allow_illegal_ips,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ utoipa-swagger-ui = { workspace = true, features = ["axum"] }
|
||||
|
||||
# internal
|
||||
nym-bin-common = { path = "../common/bin-common" }
|
||||
nym-client-core = { path = "../common/client-core" }
|
||||
nym-crypto = { path = "../common/crypto" }
|
||||
nym-network-defaults = { path = "../common/network-defaults" }
|
||||
nym-sdk = { path = "../sdk/rust/nym-sdk" }
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user