Compare commits
77 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 039c599c20 | |||
| 2cd38b6937 | |||
| 1672135308 | |||
| c07ef0253d | |||
| cc799b69d3 | |||
| dd4bbc0708 | |||
| 7b77091fb1 | |||
| 6581ebf235 | |||
| 82ace6d27b | |||
| e362207583 | |||
| 68caecff35 | |||
| 2fae4414d2 | |||
| 6eca09b904 | |||
| 7ab821cb11 | |||
| 0343469179 | |||
| 9904f6b17c | |||
| 5e0eeeddd6 | |||
| b6df383584 | |||
| b7d13d6fa6 | |||
| 838dd630ae | |||
| 3f00e2c317 | |||
| 3cdda8fdfd | |||
| 33f47ef36e | |||
| 7f9dba6e99 | |||
| 96e88b6ea9 | |||
| 180802feb8 | |||
| 87882f70cf | |||
| 4077717d3a | |||
| bc3df31518 | |||
| 61d6acace8 | |||
| abb4e3f988 | |||
| c5488337da | |||
| f06eefe184 | |||
| 46a8697a5d | |||
| 0429238b0f | |||
| 8dc3ba4ec3 | |||
| 712e3f5183 | |||
| 5229df47ab | |||
| 32cffed36b | |||
| 49c710e651 | |||
| 0a5227a894 | |||
| b231eb4f04 | |||
| fdd2c8fac2 | |||
| e2dd8ac743 | |||
| 8001fa7f40 | |||
| 80370b98ec | |||
| 3524089ad8 | |||
| ec7ee49282 | |||
| 653d1c2dea | |||
| b579f987b1 | |||
| 59254c92c3 | |||
| 69887921cc | |||
| e075b07632 | |||
| d32b680351 | |||
| fcd59a19be | |||
| 08b20ac2ab | |||
| 4c007669f9 | |||
| e86fa8fc7f | |||
| c3a8fa8d0d | |||
| d8769157fd | |||
| 7cccf3cfff | |||
| 02eec164f8 | |||
| 4f13ab1e0a | |||
| a34c7ef19f | |||
| f00b18298c | |||
| 0426adc94e | |||
| 4b4a2fe387 | |||
| 1ebb7e06c7 | |||
| 1fd17c5cb3 | |||
| ef65cf4c9e | |||
| 48dad0f16b | |||
| 93ac638765 | |||
| c6589ca92c | |||
| 03d5a87826 | |||
| 512cfd1b74 | |||
| ba0625cd97 | |||
| a2c489dc5b |
@@ -7,7 +7,7 @@ jobs:
|
||||
build:
|
||||
runs-on: arc-ubuntu-22.04
|
||||
env:
|
||||
NEXT_PUBLIC_SITE_URL: https://nymtech.net/docs
|
||||
NEXT_PUBLIC_SITE_URL: https://nym.com/docs
|
||||
defaults:
|
||||
run:
|
||||
working-directory: documentation/docs
|
||||
@@ -15,10 +15,8 @@ jobs:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get install -y build-essential curl wget libssl-dev libudev-dev squashfs-tools protobuf-compiler git python3 && sudo apt-get update --fix-missing
|
||||
- name: Install pip3
|
||||
run: sudo apt install -y python3-pip
|
||||
- name: Install Python3 modules
|
||||
run: sudo pip3 install pandas tabulate
|
||||
run: sudo apt install -y python3-pandas python3-tabulate
|
||||
- name: Install rsync
|
||||
run: sudo apt-get install -y rsync
|
||||
- uses: rlespinasse/github-slug-action@v3.x
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [arc-linux-latest]
|
||||
platform: [ubuntu-22.04]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
env:
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Install cosmwasm-check
|
||||
run: cargo install cosmwasm-check
|
||||
run: cargo install cosmwasm-check --locked
|
||||
|
||||
- name: Install wasm-opt
|
||||
uses: ./.github/actions/install-wasm-opt
|
||||
|
||||
@@ -20,10 +20,8 @@ jobs:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get install -y build-essential curl wget libssl-dev libudev-dev squashfs-tools protobuf-compiler git python3 && sudo apt-get update --fix-missing
|
||||
- name: Install pip3
|
||||
run: sudo apt install -y python3-pip
|
||||
- name: Install Python3 modules
|
||||
run: sudo pip3 install pandas tabulate
|
||||
run: sudo apt install -y python3-pandas python3-tabulate
|
||||
- name: Install rsync
|
||||
run: sudo apt-get install -y rsync
|
||||
- uses: rlespinasse/github-slug-action@v3.x
|
||||
@@ -48,6 +46,8 @@ jobs:
|
||||
run: pnpm i
|
||||
- name: Build project
|
||||
run: pnpm run build
|
||||
- name: Generate sitemap
|
||||
run: npx next-sitemap
|
||||
- name: Move files to /dist/
|
||||
run: ../scripts/move-to-dist.sh
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ on:
|
||||
jobs:
|
||||
sonarqube:
|
||||
name: SonarQube
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: arc-linux-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
|
||||
@@ -2,8 +2,6 @@ name: nightly-build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '14 1 * * *'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
@@ -9,7 +9,7 @@ on:
|
||||
|
||||
jobs:
|
||||
integration-tests:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: arc-linux-latest
|
||||
env:
|
||||
API_BASE_URL: http://localhost:8000
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ env:
|
||||
jobs:
|
||||
check-milestone:
|
||||
name: Check Milestone
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: arc-linux-latest
|
||||
steps:
|
||||
- if: github.event.pull_request.milestone == null && contains( env.LABELS, 'no-milestone' ) == false
|
||||
run: exit 1
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: arc-linux-latest
|
||||
- os: ubuntu-22.04
|
||||
target: x86_64-unknown-linux-gnu
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
|
||||
@@ -36,6 +36,9 @@ jobs:
|
||||
with:
|
||||
go-version: "1.24.6"
|
||||
|
||||
- name: Update root CA certificate bundle
|
||||
run: ./wasm/mix-fetch/go-mix-conn/scripts/update-root-certs.sh
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
|
||||
|
||||
@@ -4,6 +4,100 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2026.6-stilton] (2026-03-25)
|
||||
|
||||
- lp fixes ([#6601])
|
||||
- bugfix: allow deserialisation of LP data from either snake_case or lowercase ([#6586])
|
||||
- bugfix: make sure to run cargo install cosmwasm-check with --locked flag during CI ([#6568])
|
||||
- Add LP to NS UI ([#6562])
|
||||
- feat: nyxd watcher ([#6561])
|
||||
- Additional ticket for agent ([#6551])
|
||||
- bugfix: make sure to use old values from metrics debug config during v12 migration (#6546) ([#6547])
|
||||
- typo ([#6543])
|
||||
- rng changes for a Send variant ([#6541])
|
||||
- Add LP fields ([#6535])
|
||||
- enable LP registration in registration client ([#6534])
|
||||
- chore: rename LpMessage to LpFrame ([#6530])
|
||||
- chore: LP improvements ([#6526])
|
||||
- Remove dep leak of strum iterator ([#6522])
|
||||
- chore: update ts-rs dep ([#6517])
|
||||
- addressing LP PR comments ([#6513])
|
||||
- remove redundant LP state machine in favour of in place processing ([#6512])
|
||||
- chore: split up lp listener ([#6507])
|
||||
- feat: enable mutual KKT exchange ([#6505])
|
||||
- feat: introduce /v3/unstable/nym-nodes/semi-skimmed to aggregate LP information ([#6499])
|
||||
- Max/asyncread asyncwrite nym client ([#6318])
|
||||
- feat: localnet v2 ([#6277])
|
||||
|
||||
[#6601]: https://github.com/nymtech/nym/pull/6601
|
||||
[#6586]: https://github.com/nymtech/nym/pull/6586
|
||||
[#6568]: https://github.com/nymtech/nym/pull/6568
|
||||
[#6562]: https://github.com/nymtech/nym/pull/6562
|
||||
[#6561]: https://github.com/nymtech/nym/pull/6561
|
||||
[#6551]: https://github.com/nymtech/nym/pull/6551
|
||||
[#6547]: https://github.com/nymtech/nym/pull/6547
|
||||
[#6543]: https://github.com/nymtech/nym/pull/6543
|
||||
[#6541]: https://github.com/nymtech/nym/pull/6541
|
||||
[#6535]: https://github.com/nymtech/nym/pull/6535
|
||||
[#6534]: https://github.com/nymtech/nym/pull/6534
|
||||
[#6530]: https://github.com/nymtech/nym/pull/6530
|
||||
[#6526]: https://github.com/nymtech/nym/pull/6526
|
||||
[#6522]: https://github.com/nymtech/nym/pull/6522
|
||||
[#6517]: https://github.com/nymtech/nym/pull/6517
|
||||
[#6513]: https://github.com/nymtech/nym/pull/6513
|
||||
[#6512]: https://github.com/nymtech/nym/pull/6512
|
||||
[#6507]: https://github.com/nymtech/nym/pull/6507
|
||||
[#6505]: https://github.com/nymtech/nym/pull/6505
|
||||
[#6499]: https://github.com/nymtech/nym/pull/6499
|
||||
[#6318]: https://github.com/nymtech/nym/pull/6318
|
||||
[#6277]: https://github.com/nymtech/nym/pull/6277
|
||||
|
||||
## [2026.5-raclette] (2026-03-10)
|
||||
|
||||
- bugfix: correctly populate gateway probe LP data ([#6533])
|
||||
- chore: introduce additional prometheus metrics for registration times ([#6532])
|
||||
- bugfix: lp information to have proper snake_case on API endpoints ([#6531])
|
||||
- removed redundant LP states ([#6509])
|
||||
- chore: removed all matrix notifications from github actions ([#6495])
|
||||
- feat: Lewes Protocol with PSQv2 ([#6491])
|
||||
- build(deps): bump minimatch from 3.1.2 to 3.1.4 in /documentation/docs ([#6486])
|
||||
- build(deps): bump bn.js from 4.12.2 to 4.12.3 in /documentation/docs ([#6484])
|
||||
- build(deps): bump bn.js from 4.12.2 to 4.12.3 ([#6483])
|
||||
- build(deps): bump ajv from 8.17.1 to 8.18.0 in /clients/native/examples/js-examples/websocket ([#6478])
|
||||
- build(deps): bump ajv from 6.12.6 to 6.14.0 in /documentation/docs ([#6477])
|
||||
- build(deps): bump minimatch and glob in /documentation/scripts/post-process ([#6476])
|
||||
- build(deps): bump hono from 4.11.9 to 4.12.0 ([#6475])
|
||||
- build(deps): bump keccak from 0.1.5 to 0.1.6 ([#6472])
|
||||
- build(deps-dev): bump qs from 6.14.1 to 6.14.2 in /clients/native/examples/js-examples/websocket ([#6466])
|
||||
- build(deps): bump mikefarah/yq from 4.52.2 to 4.52.4 ([#6465])
|
||||
- Otel minimal v2 ([#6464])
|
||||
- build(deps): bump qs and express in /wasm/client/internal-dev ([#6461])
|
||||
- bugfix: restore 'latest_measurement' field for nym-node /verloc endpoint ([#6452])
|
||||
- build(deps-dev): bump webpack from 5.77.0 to 5.104.1 in /wasm/node-tester/internal-dev ([#6451])
|
||||
- Max/mixfetch concurrent test ([#6417])
|
||||
|
||||
[#6533]: https://github.com/nymtech/nym/pull/6533
|
||||
[#6532]: https://github.com/nymtech/nym/pull/6532
|
||||
[#6531]: https://github.com/nymtech/nym/pull/6531
|
||||
[#6509]: https://github.com/nymtech/nym/pull/6509
|
||||
[#6495]: https://github.com/nymtech/nym/pull/6495
|
||||
[#6491]: https://github.com/nymtech/nym/pull/6491
|
||||
[#6486]: https://github.com/nymtech/nym/pull/6486
|
||||
[#6484]: https://github.com/nymtech/nym/pull/6484
|
||||
[#6483]: https://github.com/nymtech/nym/pull/6483
|
||||
[#6478]: https://github.com/nymtech/nym/pull/6478
|
||||
[#6477]: https://github.com/nymtech/nym/pull/6477
|
||||
[#6476]: https://github.com/nymtech/nym/pull/6476
|
||||
[#6475]: https://github.com/nymtech/nym/pull/6475
|
||||
[#6472]: https://github.com/nymtech/nym/pull/6472
|
||||
[#6466]: https://github.com/nymtech/nym/pull/6466
|
||||
[#6465]: https://github.com/nymtech/nym/pull/6465
|
||||
[#6464]: https://github.com/nymtech/nym/pull/6464
|
||||
[#6461]: https://github.com/nymtech/nym/pull/6461
|
||||
[#6452]: https://github.com/nymtech/nym/pull/6452
|
||||
[#6451]: https://github.com/nymtech/nym/pull/6451
|
||||
[#6417]: https://github.com/nymtech/nym/pull/6417
|
||||
|
||||
## [2026.4-quark] (2026-02-24)
|
||||
|
||||
- Enhance CI workflow with feature inputs ([#6462])
|
||||
|
||||
Generated
+567
-183
File diff suppressed because it is too large
Load Diff
+13
-8
@@ -157,8 +157,8 @@ members = [
|
||||
"tools/internal/mixnet-connectivity-check",
|
||||
# "tools/internal/sdk-version-bump",
|
||||
"tools/internal/ssl-inject",
|
||||
"tools/internal/testnet-manager",
|
||||
"tools/internal/testnet-manager/dkg-bypass-contract",
|
||||
"tools/internal/localnet-orchestrator",
|
||||
"tools/internal/localnet-orchestrator/dkg-bypass-contract",
|
||||
"tools/internal/validator-status-check",
|
||||
"tools/nym-cli",
|
||||
"tools/nym-id-cli",
|
||||
@@ -171,9 +171,10 @@ members = [
|
||||
"wasm/mix-fetch",
|
||||
"wasm/node-tester",
|
||||
"wasm/zknym-lib",
|
||||
# "nym-gateway-probe",
|
||||
"nym-gateway-probe",
|
||||
"integration-tests",
|
||||
"common/nym-kkt-ciphersuite", "common/nym-kkt-context",
|
||||
"common/nym-kkt-ciphersuite",
|
||||
"common/nym-kkt-context",
|
||||
]
|
||||
|
||||
default-members = [
|
||||
@@ -190,7 +191,8 @@ default-members = [
|
||||
"service-providers/ip-packet-router",
|
||||
"service-providers/network-requester",
|
||||
"tools/nymvisor",
|
||||
"nym-registration-client"
|
||||
"nym-registration-client",
|
||||
"tools/internal/localnet-orchestrator"
|
||||
]
|
||||
|
||||
exclude = ["contracts", "nym-wallet", "cpu-cycles"]
|
||||
@@ -232,6 +234,7 @@ bloomfilter = "3.0.1"
|
||||
bs58 = "0.5.1"
|
||||
bytecodec = "0.4.15"
|
||||
bytes = "1.11.1"
|
||||
cargo-edit = "0.13.8"
|
||||
cargo_metadata = "0.19.2"
|
||||
celes = "2.6.0"
|
||||
cfg-if = "1.0.0"
|
||||
@@ -331,6 +334,7 @@ rayon = "1.5.1"
|
||||
regex = "1.10.6"
|
||||
reqwest = { version = "0.13.1", default-features = false }
|
||||
rs_merkle = "1.5.0"
|
||||
rustls = { version = "0.23.37", default-features = false }
|
||||
schemars = "0.8.22"
|
||||
semver = "1.0.26"
|
||||
serde = "1.0.219"
|
||||
@@ -347,8 +351,8 @@ si-scale = "0.2.3"
|
||||
snow = "0.9.6"
|
||||
sphinx-packet = "=0.6.0"
|
||||
sqlx = "0.8.6"
|
||||
strum = "0.27.2"
|
||||
strum_macros = "0.27.2"
|
||||
strum = "0.28.0"
|
||||
strum_macros = "0.28.0"
|
||||
subtle-encoding = "0.5"
|
||||
syn = "2"
|
||||
sysinfo = "0.37.0"
|
||||
@@ -375,7 +379,7 @@ tracing-opentelemetry = "0.32.1"
|
||||
tracing-subscriber = "0.3.20"
|
||||
tracing-indicatif = "0.3.9"
|
||||
tracing-test = "0.2.5"
|
||||
ts-rs = "10.1.0"
|
||||
ts-rs = "12.0.1"
|
||||
tungstenite = { version = "0.20.1", default-features = false }
|
||||
typed-builder = "0.23.0"
|
||||
uniffi = "0.29.2"
|
||||
@@ -438,6 +442,7 @@ nym-ecash-time = { version = "1.20.4", path = "common/ecash-time" }
|
||||
nym-exit-policy = { version = "1.20.4", path = "common/exit-policy" }
|
||||
nym-ffi-shared = { version = "1.20.4", path = "sdk/ffi/shared" }
|
||||
nym-gateway-client = { version = "1.20.4", path = "common/client-libs/gateway-client", default-features = false }
|
||||
nym-gateway-probe = { version = "1.18.0", path = "nym-gateway-probe" }
|
||||
nym-gateway-requests = { version = "1.20.4", path = "common/gateway-requests" }
|
||||
nym-gateway-storage = { version = "1.20.4", path = "common/gateway-storage" }
|
||||
nym-gateway-stats-storage = { version = "1.20.4", path = "common/gateway-stats-storage" }
|
||||
|
||||
@@ -30,8 +30,11 @@ client ───► Gateway ──┘ mix │ mix ┌─►mix ───►
|
||||
|
||||
```
|
||||
|
||||
<!-- This is broken
|
||||
[](https://github.com/nymtech/nym/actions?query=branch%3Adevelop)
|
||||
-->
|
||||
|
||||
> This project integrates with the Midnight Network
|
||||
|
||||
### Building
|
||||
|
||||
|
||||
@@ -1,32 +1,38 @@
|
||||
---
|
||||
ansible_ssh_private_key_file: ~/.ssh/<SSH_KEY>
|
||||
|
||||
# nym_version: "v2025.21-mozzarella"
|
||||
#
|
||||
# NOTE:
|
||||
# if you want to pin Nym to a specific version instead of using the
|
||||
# latest release from GitHub in /tasks/main.yml then
|
||||
# uncomment the line above and set the tag
|
||||
|
||||
cli_url: "https://github.com/nymtech/nym/releases/download/nym-binaries-{{ nym_version }}/nym-cli"
|
||||
tunnel_manager_url: "https://github.com/nymtech/nym/raw/refs/heads/develop/scripts/nym-node-setup/network-tunnel-manager.sh"
|
||||
quic_bridge_deployment_url: "https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/quic_bridge_deployment.sh"
|
||||
|
||||
# NOTE: These values will be used globally unless overwritten per node in inventory/all
|
||||
###############################################################################
|
||||
## GLOBAL VARS
|
||||
## These values will be used globally unless overwritten per node in inventory/all
|
||||
###############################################################################
|
||||
|
||||
ansible_user: root # used for ssh, like `ssh root@nym-exit.ch-1.mynodes.net`
|
||||
email: "<EMAIL>" # used in certbot, description.toml and landing page
|
||||
website: "<WEBSITE>" # it is used in the description.toml
|
||||
description: "<NODE_PUBLIC_DESCRIPTION>" # or define per node in inventory/all
|
||||
# operator_name: "<OPERATOR_NAME>" # used in landing page if provided
|
||||
|
||||
###############################################################################
|
||||
## GLOBAL VARS
|
||||
## These values will be used globally unless overwritten per node in inventory/all
|
||||
## Set these vars only if you want them globally for all nodes
|
||||
## Per node changes in inventory/all will overwrite these global vars
|
||||
###############################################################################
|
||||
|
||||
# NOTE: Set these vars if you want them globally for all nodes
|
||||
# Per node changes in inventory/all will overwrite these global ones:
|
||||
hostname: "" # this is a fallback, keep it and setup hostname per node in inventory/all
|
||||
# moniker: "<MONIKER>" # if not setup here not in inventory/all it get's derived from the hostname
|
||||
# mode: <MODE> # entry-gateway/exit-gateway/mixnode
|
||||
# wireguard_enabled: <WIREGUARD_ENABLED> # true/false
|
||||
hostname: "" # this is a fallback, keep it and setup hostname per node in inventory/all
|
||||
|
||||
###############################################################################
|
||||
## GLOBAL PACKAGES
|
||||
## These will be installed during deployment
|
||||
###############################################################################
|
||||
|
||||
# NOTE: Possible vars to incule on landing page, etc.
|
||||
# operator_name: "<OPERATOR_NAME>"
|
||||
|
||||
packages:
|
||||
- tmux
|
||||
@@ -42,3 +48,73 @@ packages:
|
||||
- jq
|
||||
- wget
|
||||
- ufw
|
||||
|
||||
|
||||
###############################################################################
|
||||
## OPTIONAL OVERRIDES
|
||||
## All values below already have defaults in the playbook/roles
|
||||
## Uncomment only if you want to override them
|
||||
###############################################################################
|
||||
|
||||
###############################################################################
|
||||
## SYSTEM MAINTENANCE PLAYBOOK KNOBS
|
||||
###############################################################################
|
||||
|
||||
# nym_version: "v2025.21-mozzarella"
|
||||
|
||||
## NOTE:
|
||||
## if you want to pin Nym to a specific version instead of using the
|
||||
## latest release from GitHub in /tasks/main.yml then
|
||||
## uncomment the line above and set the tag
|
||||
|
||||
###############################################################################
|
||||
## SYSTEM MAINTENANCE PLAYBOOK KNOBS
|
||||
###############################################################################
|
||||
|
||||
## JOURNALD LIMITS
|
||||
|
||||
# journald_system_max_use: "100M" # max persistent journal size
|
||||
# journald_runtime_max_use: "50M" # max runtime journal size
|
||||
# journald_system_max_file_size: "25M" # max single journal file
|
||||
# journald_runtime_max_file_size: "10M" # max runtime journal file
|
||||
# journald_max_retention_sec: "3day" # retention time
|
||||
|
||||
# journald_rate_limit_interval: "30s" # rate limit window
|
||||
# journald_rate_limit_burst: "1000" # rate limit burst
|
||||
|
||||
|
||||
## NYM-NODE LOG CONTROL
|
||||
|
||||
# nymnode_log_level_max: "warning" # drop INFO logs
|
||||
# nymnode_rate_limit_interval: "30s" # per nym-node rate limit window
|
||||
# nymnode_rate_limit_burst: "200" # per nym-node rate limit burst
|
||||
|
||||
|
||||
## JOURNAL VACUUM TARGETS
|
||||
|
||||
# journal_vacuum_size: "100M"
|
||||
# journal_vacuum_time: "3days"
|
||||
|
||||
|
||||
## RSYSLOG
|
||||
|
||||
# disable_rsyslog: true
|
||||
|
||||
|
||||
## FSTRIM SCHEDULE
|
||||
|
||||
# fstrim_every_calendar: "*:0/15" # Aggressive
|
||||
# fstrim_every_calendar: "hourly" # Less aggressive
|
||||
|
||||
|
||||
## OPTIONAL CLEANUPS
|
||||
|
||||
# enable_apt_cleanup: true
|
||||
# enable_snap_cleanup: true
|
||||
|
||||
|
||||
## WRITEBACK TUNING
|
||||
|
||||
# enable_writeback_tuning: true
|
||||
# writeback_dirty_writeback_centisecs: 1500
|
||||
# writeback_dirty_expire_centisecs: 6000
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
- name: Restrict logging, vacuum journals, and enable periodic trim
|
||||
hosts: all
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
||||
# global knobs - override in inventory/group_vars/host_vars as needed
|
||||
vars:
|
||||
journald_system_max_use: "100M"
|
||||
journald_runtime_max_use: "50M"
|
||||
journald_system_max_file_size: "25M"
|
||||
journald_runtime_max_file_size: "10M"
|
||||
journald_max_retention_sec: "3day"
|
||||
journald_rate_limit_interval: "30s"
|
||||
journald_rate_limit_burst: "1000"
|
||||
|
||||
# per nym-node rate limit + level cap
|
||||
nymnode_log_level_max: "warning"
|
||||
nymnode_rate_limit_interval: "30s"
|
||||
nymnode_rate_limit_burst: "200"
|
||||
|
||||
# journal vacuum targets
|
||||
journal_vacuum_size: "100M"
|
||||
journal_vacuum_time: "3days"
|
||||
|
||||
# fstrim cadence (note: the systemd override uses cron-like calendar)
|
||||
fstrim_every_calendar: "*:0/15"
|
||||
|
||||
roles:
|
||||
- role: journald_limits
|
||||
- role: nymnode_logging
|
||||
- role: rsyslog_disable
|
||||
- role: journal_vacuum
|
||||
- role: classic_log_cleanup
|
||||
- role: apt_cleanup
|
||||
- role: snap_cleanup
|
||||
- role: fstrim_15min
|
||||
- role: report
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
- name: Clean apt cache
|
||||
command: apt-get clean
|
||||
ignore_errors: true
|
||||
|
||||
- name: Autoremove unused packages
|
||||
command: apt-get -y autoremove
|
||||
ignore_errors: true
|
||||
|
||||
- name: Remove apt lists to reclaim space (they will be re-fetched on update)
|
||||
file:
|
||||
path: /var/lib/apt/lists
|
||||
state: absent
|
||||
ignore_errors: true
|
||||
|
||||
- name: Recreate apt lists directory
|
||||
file:
|
||||
path: /var/lib/apt/lists
|
||||
state: directory
|
||||
mode: "0755"
|
||||
ignore_errors: true
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
- name: Remove classic /var/log files if present (optional)
|
||||
file:
|
||||
path: "{{ item }}"
|
||||
state: absent
|
||||
loop:
|
||||
- /var/log/syslog
|
||||
- /var/log/syslog.1
|
||||
- /var/log/kern.log
|
||||
- /var/log/kern.log.1
|
||||
- /var/log/auth.log
|
||||
- /var/log/auth.log.1
|
||||
- /var/log/ufw.log
|
||||
- /var/log/ufw.log.1
|
||||
ignore_errors: true
|
||||
|
||||
# This is best-effort and may still fail if other packages' postrotate scripts assume services exist.
|
||||
- name: Force logrotate (best-effort)
|
||||
command: "logrotate --force /etc/logrotate.conf"
|
||||
ignore_errors: true
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
fstrim_timer_dropin_dir: "/etc/systemd/system/fstrim.timer.d"
|
||||
fstrim_every_calendar: "*:0/15"
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
- name: Ensure systemd drop-in dir for fstrim.timer exists
|
||||
file:
|
||||
path: "{{ fstrim_timer_dropin_dir }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Override fstrim.timer schedule
|
||||
copy:
|
||||
dest: "{{ fstrim_timer_dropin_dir }}/override.conf"
|
||||
mode: "0644"
|
||||
content: |
|
||||
[Timer]
|
||||
OnCalendar=
|
||||
OnCalendar={{ fstrim_every_calendar }}
|
||||
Persistent=true
|
||||
RandomizedDelaySec=0
|
||||
|
||||
- name: Reload systemd after fstrim override
|
||||
systemd:
|
||||
daemon_reload: true
|
||||
|
||||
- name: Enable and start fstrim timer
|
||||
systemd:
|
||||
name: fstrim.timer
|
||||
enabled: true
|
||||
state: started
|
||||
|
||||
- name: Run fstrim now (best-effort)
|
||||
command: fstrim -av
|
||||
ignore_errors: true
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
journal_vacuum_size: "100M"
|
||||
journal_vacuum_time: "3days"
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
- name: Vacuum journal to size cap (hard)
|
||||
command: "journalctl --vacuum-size={{ journal_vacuum_size }}"
|
||||
|
||||
- name: Vacuum journal older than retention window (time)
|
||||
command: "journalctl --vacuum-time={{ journal_vacuum_time }}"
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
journald_system_max_use: "100M"
|
||||
journald_runtime_max_use: "50M"
|
||||
journald_system_max_file_size: "25M"
|
||||
journald_runtime_max_file_size: "10M"
|
||||
journald_max_retention_sec: "3day"
|
||||
journald_rate_limit_interval: "30s"
|
||||
journald_rate_limit_burst: "1000"
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
- name: Restart journald
|
||||
systemd:
|
||||
name: systemd-journald
|
||||
state: restarted
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
- name: Configure journald limits (persistent, capped, rate-limited)
|
||||
copy:
|
||||
dest: /etc/systemd/journald.conf
|
||||
mode: "0644"
|
||||
content: |
|
||||
[Journal]
|
||||
Storage=persistent
|
||||
Compress=yes
|
||||
Seal=yes
|
||||
|
||||
SystemMaxUse={{ journald_system_max_use }}
|
||||
RuntimeMaxUse={{ journald_runtime_max_use }}
|
||||
SystemMaxFileSize={{ journald_system_max_file_size }}
|
||||
RuntimeMaxFileSize={{ journald_runtime_max_file_size }}
|
||||
MaxRetentionSec={{ journald_max_retention_sec }}
|
||||
|
||||
RateLimitIntervalSec={{ journald_rate_limit_interval }}
|
||||
RateLimitBurst={{ journald_rate_limit_burst }}
|
||||
notify: Restart journald
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
nymnode_log_level_max: "warning"
|
||||
nymnode_rate_limit_interval: "30s"
|
||||
nymnode_rate_limit_burst: "200"
|
||||
nymnode_unit_name: "nym-node" # set to "nym-node.service" if your distro expects it
|
||||
nymnode_dropin_dir: "/etc/systemd/system/nym-node.service.d"
|
||||
nymnode_dropin_file: "10-logging.conf"
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
- name: Ensure systemd drop-in dir for nym-node exists
|
||||
file:
|
||||
path: "{{ nymnode_dropin_dir }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Cap nym-node logs + apply per-unit rate limiting
|
||||
copy:
|
||||
dest: "{{ nymnode_dropin_dir }}/{{ nymnode_dropin_file }}"
|
||||
mode: "0644"
|
||||
content: |
|
||||
[Service]
|
||||
LogLevelMax={{ nymnode_log_level_max }}
|
||||
LogRateLimitIntervalSec={{ nymnode_rate_limit_interval }}
|
||||
LogRateLimitBurst={{ nymnode_rate_limit_burst }}
|
||||
|
||||
- name: Reload systemd after nym-node drop-in
|
||||
systemd:
|
||||
daemon_reload: true
|
||||
|
||||
- name: Restart nym-node to apply new logging limits (best-effort)
|
||||
systemd:
|
||||
name: "{{ nymnode_unit_name }}"
|
||||
state: restarted
|
||||
ignore_errors: true
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
- name: Show journal disk usage
|
||||
command: journalctl --disk-usage
|
||||
register: journal_usage
|
||||
changed_when: false
|
||||
|
||||
- debug:
|
||||
var: journal_usage.stdout
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
- name: Stop/disable rsyslog if installed (best-effort)
|
||||
systemd:
|
||||
name: rsyslog
|
||||
state: stopped
|
||||
enabled: false
|
||||
ignore_errors: true
|
||||
|
||||
- name: Remove rsyslog logrotate stanza if present (prevents logrotate failures)
|
||||
file:
|
||||
path: /etc/logrotate.d/rsyslog
|
||||
state: absent
|
||||
ignore_errors: true
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
- name: Remove disabled snap revisions (best-effort)
|
||||
shell: |
|
||||
set -euo pipefail
|
||||
snap list --all | awk '/disabled/{print $1, $3}' | while read -r name rev; do
|
||||
snap remove "$name" --revision="$rev" || true
|
||||
done
|
||||
args:
|
||||
executable: /bin/bash
|
||||
ignore_errors: true
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.72"
|
||||
version = "1.1.74"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.72"
|
||||
version = "1.1.74"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
|
||||
@@ -34,7 +34,7 @@ where
|
||||
let signing_key = ed25519::PrivateKey::new(&mut rng);
|
||||
let expiration = expiration.unwrap_or_else(ecash_default_expiration_date);
|
||||
|
||||
let deposit_amount = client.get_required_deposit_amount().await?;
|
||||
let deposit_amount = client.get_default_deposit_amount().await?;
|
||||
info!("we'll need to deposit {deposit_amount} to obtain the ticketbook");
|
||||
let result = client
|
||||
.make_ticketbook_deposit(
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "nym-client-core"
|
||||
version.workspace = true
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
rust-version = "1.85"
|
||||
license.workspace = true
|
||||
description = "Crate containing core client functionality and configs, used by all other Nym client implentations"
|
||||
|
||||
@@ -32,6 +32,7 @@ const DEFAULT_MIN_MIXNODE_PERFORMANCE: u8 = 50;
|
||||
const DEFAULT_MIN_GATEWAY_PERFORMANCE: u8 = 50;
|
||||
|
||||
const DEFAULT_MAX_STARTUP_GATEWAY_WAITING_PERIOD: Duration = Duration::from_secs(70 * 60); // 70min -> full epoch (1h) + a bit of overhead
|
||||
const DEFAULT_MAX_STARTUP_TOPOLOGY_WAITING_PERIOD: Duration = Duration::from_secs(70 * 60); // 70min -> full epoch (1h) + a bit of overhead
|
||||
|
||||
// Set this to a high value for now, so that we don't risk sporadic timeouts that might cause
|
||||
// bought bandwidth tokens to not have time to be spent; Once we remove the gateway from the
|
||||
@@ -555,6 +556,11 @@ pub struct Topology {
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub max_startup_gateway_waiting_period: Duration,
|
||||
|
||||
/// Defines how long the client is going to wait on startup for minimal topology to become online,
|
||||
/// before abandoning the procedure.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub max_startup_network_waiting_period: Duration,
|
||||
|
||||
/// Specifies a minimum performance of a mixnode that is used on route construction.
|
||||
/// This setting is only applicable when `NymApi` topology is used.
|
||||
pub minimum_mixnode_performance: u8,
|
||||
@@ -583,6 +589,7 @@ impl Default for Topology {
|
||||
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
|
||||
disable_refreshing: false,
|
||||
max_startup_gateway_waiting_period: DEFAULT_MAX_STARTUP_GATEWAY_WAITING_PERIOD,
|
||||
max_startup_network_waiting_period: DEFAULT_MAX_STARTUP_TOPOLOGY_WAITING_PERIOD,
|
||||
minimum_mixnode_performance: DEFAULT_MIN_MIXNODE_PERFORMANCE,
|
||||
minimum_gateway_performance: DEFAULT_MIN_GATEWAY_PERFORMANCE,
|
||||
use_extended_topology: false,
|
||||
|
||||
@@ -159,6 +159,7 @@ impl From<ConfigV6> for Config {
|
||||
use_extended_topology: value.debug.topology.use_extended_topology,
|
||||
ignore_egress_epoch_role: value.debug.topology.ignore_egress_epoch_role,
|
||||
ignore_ingress_epoch_role: value.debug.topology.ignore_ingress_epoch_role,
|
||||
..Default::default()
|
||||
},
|
||||
reply_surbs: ReplySurbs {
|
||||
minimum_reply_surb_storage_threshold: value
|
||||
|
||||
@@ -160,7 +160,10 @@ where
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
info!("registered with new gateway {} (under address {address}), but this will not be our default address", gateway_details.gateway_id);
|
||||
info!(
|
||||
"registered with new gateway {} (under address {address}), but this will not be our default address",
|
||||
gateway_details.gateway_id
|
||||
);
|
||||
}
|
||||
|
||||
Ok(GatewayInfo {
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
use super::mix_traffic::ClientRequestSender;
|
||||
use super::received_buffer::ReceivedBufferMessage;
|
||||
use super::statistics_control::StatisticsControl;
|
||||
use crate::client::base_client::storage::helpers::store_client_keys;
|
||||
use crate::client::base_client::storage::MixnetClientStorage;
|
||||
use crate::client::base_client::storage::helpers::store_client_keys;
|
||||
use crate::client::cover_traffic_stream::LoopCoverTrafficStream;
|
||||
use crate::client::event_control::EventControl;
|
||||
use crate::client::inbound_messages::{InputMessage, InputMessageReceiver, InputMessageSender};
|
||||
use crate::client::key_manager::persistence::KeyStore;
|
||||
use crate::client::key_manager::ClientKeys;
|
||||
use crate::client::key_manager::persistence::KeyStore;
|
||||
use crate::client::mix_traffic::transceiver::{GatewayReceiver, GatewayTransceiver, RemoteGateway};
|
||||
use crate::client::mix_traffic::{BatchMixMessageSender, MixTrafficController, MixTrafficEvent};
|
||||
use crate::client::real_messages_control;
|
||||
@@ -52,12 +52,12 @@ use nym_sphinx::addressing::nodes::NodeIdentity;
|
||||
use nym_sphinx::receiver::{ReconstructedMessage, SphinxMessageReceiver};
|
||||
use nym_statistics_common::clients::ClientStatsSender;
|
||||
use nym_statistics_common::generate_client_stats_id;
|
||||
use nym_task::connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths};
|
||||
use nym_task::ShutdownTracker;
|
||||
use nym_topology::provider_trait::TopologyProvider;
|
||||
use nym_task::connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths};
|
||||
use nym_topology::HardcodedTopologyProvider;
|
||||
use nym_topology::provider_trait::TopologyProvider;
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use nym_validator_client::{nyxd::contract_traits::DkgQueryClient, UserAgent};
|
||||
use nym_validator_client::{UserAgent, nyxd::contract_traits::DkgQueryClient};
|
||||
use rand::prelude::SliceRandom;
|
||||
use rand::rngs::OsRng;
|
||||
use rand::thread_rng;
|
||||
@@ -220,6 +220,7 @@ pub struct BaseClientBuilder<C, S: MixnetClientStorage> {
|
||||
nym_api_urls: Option<Vec<nym_network_defaults::ApiUrl>>,
|
||||
|
||||
wait_for_gateway: bool,
|
||||
wait_for_initial_topology: bool,
|
||||
custom_topology_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
|
||||
custom_gateway_transceiver: Option<Box<dyn GatewayTransceiver + Send>>,
|
||||
shutdown: Option<ShutdownTracker>,
|
||||
@@ -250,6 +251,7 @@ where
|
||||
dkg_query_client,
|
||||
nym_api_urls: None,
|
||||
wait_for_gateway: false,
|
||||
wait_for_initial_topology: false,
|
||||
custom_topology_provider: None,
|
||||
custom_gateway_transceiver: None,
|
||||
shutdown: None,
|
||||
@@ -305,6 +307,12 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_wait_for_initial_topology(mut self, wait_for_initial_topology: bool) -> Self {
|
||||
self.wait_for_initial_topology = wait_for_initial_topology;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_topology_provider(
|
||||
mut self,
|
||||
@@ -674,6 +682,7 @@ where
|
||||
topology_accessor: TopologyAccessor,
|
||||
local_gateway: NodeIdentity,
|
||||
wait_for_gateway: bool,
|
||||
wait_for_initial_topology: bool,
|
||||
shutdown_tracker: &ShutdownTracker,
|
||||
) -> Result<(), ClientCoreError> {
|
||||
let topology_refresher_config =
|
||||
@@ -694,6 +703,46 @@ where
|
||||
tracing::info!("Obtaining initial network topology");
|
||||
topology_refresher.try_refresh().await;
|
||||
|
||||
// 1. wait for the minimum topology (if applicable)
|
||||
if topology_refresher
|
||||
.ensure_topology_is_routable()
|
||||
.await
|
||||
.is_err()
|
||||
&& wait_for_initial_topology
|
||||
{
|
||||
if let Err(err) = topology_refresher
|
||||
.wait_for_initial_network(topology_config.max_startup_network_waiting_period)
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
"the network did not come become online within the specified timeout: {err}"
|
||||
);
|
||||
return Err(err.into());
|
||||
}
|
||||
}
|
||||
|
||||
// 2. wait for our gateway (if applicable)
|
||||
if topology_refresher
|
||||
.ensure_contains_routable_egress(local_gateway)
|
||||
.await
|
||||
.is_err()
|
||||
&& wait_for_gateway
|
||||
{
|
||||
if let Err(err) = topology_refresher
|
||||
.wait_for_gateway(
|
||||
local_gateway,
|
||||
topology_config.max_startup_gateway_waiting_period,
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
"the gateway did not come back online within the specified timeout: {err}"
|
||||
);
|
||||
return Err(err.into());
|
||||
}
|
||||
}
|
||||
|
||||
// 3. check if the topology is routable (in case we were NOT waiting for it)
|
||||
if let Err(err) = topology_refresher.ensure_topology_is_routable().await {
|
||||
tracing::error!(
|
||||
"The current network topology seem to be insufficient to route any packets through \
|
||||
@@ -702,30 +751,15 @@ where
|
||||
return Err(ClientCoreError::InsufficientNetworkTopology(err));
|
||||
}
|
||||
|
||||
let gateway_wait_timeout = if wait_for_gateway {
|
||||
Some(topology_config.max_startup_gateway_waiting_period)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// 4. check if the gateway exists (in case we were NOT waiting for it)
|
||||
if let Err(err) = topology_refresher
|
||||
.ensure_contains_routable_egress(local_gateway)
|
||||
.await
|
||||
{
|
||||
if let Some(waiting_timeout) = gateway_wait_timeout {
|
||||
if let Err(err) = topology_refresher
|
||||
.wait_for_gateway(local_gateway, waiting_timeout)
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
"the gateway did not come back online within the specified timeout: {err}"
|
||||
);
|
||||
return Err(err.into());
|
||||
}
|
||||
} else {
|
||||
tracing::error!("the gateway we're supposedly connected to does not exist. We'll not be able to send any packets to ourselves: {err}");
|
||||
return Err(err.into());
|
||||
}
|
||||
tracing::error!(
|
||||
"the gateway we're supposedly connected to does not exist. We'll not be able to send any packets to ourselves: {err}"
|
||||
);
|
||||
return Err(err.into());
|
||||
}
|
||||
|
||||
if !topology_config.disable_refreshing {
|
||||
@@ -1024,6 +1058,7 @@ where
|
||||
shared_topology_accessor.clone(),
|
||||
self_address.gateway(),
|
||||
self.wait_for_gateway,
|
||||
self.wait_for_initial_topology,
|
||||
&shutdown_tracker.clone(),
|
||||
)
|
||||
.await?;
|
||||
@@ -1195,9 +1230,11 @@ mod tests {
|
||||
]);
|
||||
|
||||
assert_eq!(network_details.nym_api_urls.as_ref().unwrap().len(), 2);
|
||||
assert!(network_details.nym_api_urls.as_ref().unwrap()[1]
|
||||
.front_hosts
|
||||
.is_some());
|
||||
assert!(
|
||||
network_details.nym_api_urls.as_ref().unwrap()[1]
|
||||
.front_hosts
|
||||
.is_some()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1210,11 +1247,13 @@ mod tests {
|
||||
|
||||
assert_eq!(api_url.url, "https://nym-frontdoor.vercel.app/api/");
|
||||
assert_eq!(api_url.front_hosts.as_ref().unwrap().len(), 2);
|
||||
assert!(api_url
|
||||
.front_hosts
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.contains(&"vercel.app".to_string()));
|
||||
assert!(
|
||||
api_url
|
||||
.front_hosts
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.contains(&"vercel.app".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{
|
||||
client::replies::reply_storage::{fs_backend, CombinedReplyStorage, ReplyStorageBackend},
|
||||
client::replies::reply_storage::{CombinedReplyStorage, ReplyStorageBackend, fs_backend},
|
||||
config,
|
||||
config::Config,
|
||||
error::ClientCoreError,
|
||||
@@ -10,7 +10,7 @@ use crate::{
|
||||
use nym_bandwidth_controller::BandwidthController;
|
||||
use nym_client_core_gateways_storage::OnDiskGatewaysDetails;
|
||||
use nym_credential_storage::storage::Storage as CredentialStorage;
|
||||
use nym_validator_client::{nyxd, QueryHttpRpcNyxdClient};
|
||||
use nym_validator_client::{QueryHttpRpcNyxdClient, nyxd};
|
||||
use std::{io, path::Path};
|
||||
use time::OffsetDateTime;
|
||||
use tracing::{error, info, trace};
|
||||
@@ -24,7 +24,9 @@ async fn setup_fresh_backend<P: AsRef<Path>>(
|
||||
let mut storage_backend = match fs_backend::Backend::init(db_path).await {
|
||||
Ok(backend) => backend,
|
||||
Err(err) => {
|
||||
error!("setup_fresh_backend: Failed to setup persistent storage backend for our reply needs: {err}");
|
||||
error!(
|
||||
"setup_fresh_backend: Failed to setup persistent storage backend for our reply needs: {err}"
|
||||
);
|
||||
return Err(ClientCoreError::SurbStorageError {
|
||||
source: Box::new(err),
|
||||
});
|
||||
@@ -93,7 +95,9 @@ pub async fn setup_fs_reply_surb_backend<P: AsRef<Path>>(
|
||||
match fs_backend::Backend::try_load(db_path).await {
|
||||
Ok(backend) => Ok(backend),
|
||||
Err(err) => {
|
||||
error!("setup_fs_reply_surb_backend: Failed to setup persistent storage backend for our reply needs: {err}. We're going to create a fresh database instead. This behaviour might change in the future");
|
||||
error!(
|
||||
"setup_fs_reply_surb_backend: Failed to setup persistent storage backend for our reply needs: {err}. We're going to create a fresh database instead. This behaviour might change in the future"
|
||||
);
|
||||
archive_corrupted_database(db_path).await?;
|
||||
setup_fresh_backend(db_path, surb_config).await
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::key_manager::persistence::KeyStore;
|
||||
use crate::client::key_manager::ClientKeys;
|
||||
use crate::client::key_manager::persistence::KeyStore;
|
||||
use crate::error::ClientCoreError;
|
||||
use nym_client_core_gateways_storage::{
|
||||
ActiveGateway, GatewayPublishedData, GatewayRegistration, GatewaysDetailsStore,
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod v1_1_33 {
|
||||
use crate::config::disk_persistence::old_v1_1_33::CommonClientPathsV1_1_33;
|
||||
use crate::config::disk_persistence::CommonClientPaths;
|
||||
use crate::config::disk_persistence::old_v1_1_33::CommonClientPathsV1_1_33;
|
||||
use crate::config::old_config_v1_1_33::OldGatewayEndpointConfigV1_1_33;
|
||||
use crate::error::ClientCoreError;
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::cover::generate_loop_cover_packet;
|
||||
use nym_sphinx::params::{PacketSize, PacketType};
|
||||
use nym_sphinx::utils::sample_poisson_duration;
|
||||
use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, ClientStatsSender};
|
||||
use rand::{rngs::OsRng, CryptoRng, Rng};
|
||||
use nym_statistics_common::clients::{ClientStatsSender, packet_statistics::PacketStatisticsEvent};
|
||||
use rand::{CryptoRng, Rng, rngs::OsRng};
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
@@ -20,10 +20,10 @@ use tokio::sync::mpsc::error::TrySendError;
|
||||
use tracing::*;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::time::{sleep, Sleep};
|
||||
use tokio::time::{Sleep, sleep};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasmtimer::tokio::{sleep, Sleep};
|
||||
use wasmtimer::tokio::{Sleep, sleep};
|
||||
|
||||
pub struct LoopCoverTrafficStream<R>
|
||||
where
|
||||
@@ -179,7 +179,9 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
) {
|
||||
Ok(topology) => topology,
|
||||
Err(err) => {
|
||||
warn!("We're not going to send any loop cover message this time, as the current topology seem to be invalid - {err}");
|
||||
warn!(
|
||||
"We're not going to send any loop cover message this time, as the current topology seem to be invalid - {err}"
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -13,10 +13,10 @@ use crate::config::disk_persistence::ClientKeysPaths;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use nym_pemstore::KeyPairPath;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
|
||||
// we have to define it as an async trait since wasm storage is async
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
use async_trait::async_trait;
|
||||
use nym_credential_storage::storage::Storage as CredentialStorage;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_gateway_client::error::GatewayClientError;
|
||||
use nym_gateway_client::GatewayClient;
|
||||
use nym_gateway_client::error::GatewayClientError;
|
||||
pub use nym_gateway_client::{GatewayPacketRouter, PacketRouter};
|
||||
use nym_gateway_requests::ClientRequest;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
|
||||
+3
-3
@@ -2,13 +2,13 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::action_controller::{AckActionSender, Action};
|
||||
use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, ClientStatsSender};
|
||||
use nym_statistics_common::clients::{ClientStatsSender, packet_statistics::PacketStatisticsEvent};
|
||||
|
||||
use futures::StreamExt;
|
||||
use nym_gateway_client::AcknowledgementReceiver;
|
||||
use nym_sphinx::{
|
||||
acknowledgements::{identifier::recover_identifier, AckKey},
|
||||
chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID},
|
||||
acknowledgements::{AckKey, identifier::recover_identifier},
|
||||
chunking::fragment::{COVER_FRAG_ID, FragmentIdentifier},
|
||||
};
|
||||
use nym_task::ShutdownToken;
|
||||
use std::sync::Arc;
|
||||
|
||||
+2
-2
@@ -3,11 +3,11 @@
|
||||
|
||||
use super::PendingAcknowledgement;
|
||||
use crate::client::real_messages_control::acknowledgement_control::RetransmissionRequestSender;
|
||||
use futures::channel::mpsc;
|
||||
use futures::StreamExt;
|
||||
use futures::channel::mpsc;
|
||||
use nym_nonexhaustive_delayqueue::{Expired, NonExhaustiveDelayQueue, QueueKey};
|
||||
use nym_sphinx::chunking::fragment::FragmentIdentifier;
|
||||
use nym_sphinx::Delay as SphinxDelay;
|
||||
use nym_sphinx::chunking::fragment::FragmentIdentifier;
|
||||
use nym_task::ShutdownToken;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
+1
-1
@@ -9,8 +9,8 @@ use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_sphinx::params::PacketType;
|
||||
use nym_task::connections::TransmissionLane;
|
||||
use nym_task::ShutdownToken;
|
||||
use nym_task::connections::TransmissionLane;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use tracing::*;
|
||||
|
||||
|
||||
@@ -16,10 +16,10 @@ use nym_gateway_client::AcknowledgementReceiver;
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_sphinx::params::{PacketSize, PacketType};
|
||||
use nym_sphinx::{
|
||||
Delay as SphinxDelay,
|
||||
acknowledgements::AckKey,
|
||||
addressing::clients::Recipient,
|
||||
chunking::fragment::{Fragment, FragmentIdentifier},
|
||||
Delay as SphinxDelay,
|
||||
};
|
||||
use nym_statistics_common::clients::ClientStatsSender;
|
||||
use rand::{CryptoRng, Rng};
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::{
|
||||
action_controller::{AckActionSender, Action},
|
||||
PendingAcknowledgement, RetransmissionRequestReceiver,
|
||||
action_controller::{AckActionSender, Action},
|
||||
};
|
||||
use crate::client::real_messages_control::acknowledgement_control::PacketDestination;
|
||||
use crate::client::real_messages_control::message_handler::{MessageHandler, PreparationError};
|
||||
@@ -13,7 +13,7 @@ use futures::StreamExt;
|
||||
use nym_sphinx::chunking::fragment::Fragment;
|
||||
use nym_sphinx::preparer::PreparedFragment;
|
||||
use nym_sphinx::{addressing::clients::Recipient, params::PacketType};
|
||||
use nym_task::{connections::TransmissionLane, ShutdownToken};
|
||||
use nym_task::{ShutdownToken, connections::TransmissionLane};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::sync::{Arc, Weak};
|
||||
use tracing::*;
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::action_controller::{AckActionSender, Action};
|
||||
use super::SentPacketNotificationReceiver;
|
||||
use super::action_controller::{AckActionSender, Action};
|
||||
use futures::StreamExt;
|
||||
use nym_sphinx::chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID};
|
||||
use nym_sphinx::chunking::fragment::{COVER_FRAG_ID, FragmentIdentifier};
|
||||
use tracing::*;
|
||||
|
||||
/// Module responsible for starting up retransmission timers.
|
||||
|
||||
@@ -10,17 +10,17 @@ use crate::client::replies::reply_controller::MaxRetransmissions;
|
||||
use crate::client::replies::reply_storage::{ReceivedReplySurbsMap, SentReplyKeys, UsedSenderTags};
|
||||
use crate::client::topology_control::{TopologyAccessor, TopologyReadPermit};
|
||||
use nym_client_core_surb_storage::RetrievedReplySurb;
|
||||
use nym_sphinx::Delay;
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::anonymous_replies::requests::{AnonymousSenderTag, RepliableMessage, ReplyMessage};
|
||||
use nym_sphinx::anonymous_replies::ReplySurbWithKeyRotation;
|
||||
use nym_sphinx::anonymous_replies::requests::{AnonymousSenderTag, RepliableMessage, ReplyMessage};
|
||||
use nym_sphinx::chunking::fragment::{Fragment, FragmentIdentifier};
|
||||
use nym_sphinx::message::NymMessage;
|
||||
use nym_sphinx::params::{PacketSize, PacketType};
|
||||
use nym_sphinx::preparer::{MessagePreparer, PreparedFragment};
|
||||
use nym_sphinx::Delay;
|
||||
use nym_task::connections::TransmissionLane;
|
||||
use nym_task::ShutdownToken;
|
||||
use nym_task::connections::TransmissionLane;
|
||||
use nym_topology::{NymRouteProvider, NymTopologyError};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::collections::HashMap;
|
||||
@@ -272,7 +272,9 @@ where
|
||||
let primary_count = msg.required_packets(self.config.primary_packet_size);
|
||||
let secondary_count = msg.required_packets(secondary_packet);
|
||||
|
||||
trace!("This message would require: {primary_count} primary packets or {secondary_count} secondary packets...");
|
||||
trace!(
|
||||
"This message would require: {primary_count} primary packets or {secondary_count} secondary packets..."
|
||||
);
|
||||
// if there would be no benefit in using the secondary packet - use the primary (duh)
|
||||
if primary_count <= secondary_count {
|
||||
trace!("so choosing primary for this message");
|
||||
|
||||
@@ -25,9 +25,9 @@ use nym_gateway_client::AcknowledgementReceiver;
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_statistics_common::clients::ClientStatsSender;
|
||||
use nym_task::connections::{ConnectionCommandReceiver, LaneQueueLengths};
|
||||
use nym_task::ShutdownToken;
|
||||
use rand::{rngs::OsRng, CryptoRng, Rng};
|
||||
use nym_task::connections::{ConnectionCommandReceiver, LaneQueueLengths};
|
||||
use rand::{CryptoRng, Rng, rngs::OsRng};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::client::replies::reply_controller::key_rotation_helpers::KeyRotationConfig;
|
||||
|
||||
@@ -17,11 +17,11 @@ use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_sphinx::params::PacketSize;
|
||||
use nym_sphinx::preparer::PreparedFragment;
|
||||
use nym_sphinx::utils::sample_poisson_duration;
|
||||
use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, ClientStatsSender};
|
||||
use nym_statistics_common::clients::{ClientStatsSender, packet_statistics::PacketStatisticsEvent};
|
||||
use nym_task::ShutdownToken;
|
||||
use nym_task::connections::{
|
||||
ConnectionCommand, ConnectionCommandReceiver, ConnectionId, LaneQueueLengths, TransmissionLane,
|
||||
};
|
||||
use nym_task::ShutdownToken;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
@@ -29,11 +29,11 @@ use std::time::Duration;
|
||||
use tracing::*;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::time::{sleep, Sleep};
|
||||
use tokio::time::{Sleep, sleep};
|
||||
|
||||
// use nym_wasm_utils::console_log;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasmtimer::tokio::{sleep, Sleep};
|
||||
use wasmtimer::tokio::{Sleep, sleep};
|
||||
mod sending_delay_controller;
|
||||
|
||||
/// Configurable parameters of the `OutQueueControl`
|
||||
@@ -230,7 +230,9 @@ where
|
||||
let (next_message, fragment_id, packet_size) = match next_message {
|
||||
StreamMessage::Cover => {
|
||||
let cover_traffic_packet_size = self.loop_cover_message_size();
|
||||
trace!("the next loop cover message will be put in a {cover_traffic_packet_size} packet");
|
||||
trace!(
|
||||
"the next loop cover message will be put in a {cover_traffic_packet_size} packet"
|
||||
);
|
||||
|
||||
// TODO for way down the line: in very rare cases (during topology update) we might have
|
||||
// to wait a really tiny bit before actually obtaining the permit hence messing with our
|
||||
@@ -244,7 +246,9 @@ where
|
||||
) {
|
||||
Ok(topology) => topology,
|
||||
Err(err) => {
|
||||
warn!("We're not going to send any loop cover message this time, as the current topology seem to be invalid - {err}");
|
||||
warn!(
|
||||
"We're not going to send any loop cover message this time, as the current topology seem to be invalid - {err}"
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -436,7 +440,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref mut next_delay) = &mut self.next_delay {
|
||||
if let Some(next_delay) = &mut self.next_delay {
|
||||
// it is not yet time to return a message
|
||||
if next_delay.as_mut().poll(cx).is_pending() {
|
||||
return Poll::Pending;
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::helpers::{get_time_now, Instant};
|
||||
use crate::client::helpers::{Instant, get_time_now};
|
||||
use std::time::Duration;
|
||||
|
||||
// The minimum time between increasing the average delay between packets. If we hit the ceiling in
|
||||
|
||||
@@ -5,20 +5,20 @@ use crate::client::helpers::get_time_now;
|
||||
use crate::client::replies::{
|
||||
reply_controller::ReplyControllerSender, reply_storage::SentReplyKeys,
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use futures::channel::mpsc;
|
||||
use futures::lock::Mutex;
|
||||
use futures::StreamExt;
|
||||
use nym_crypto::asymmetric::x25519;
|
||||
use nym_crypto::Digest;
|
||||
use nym_crypto::asymmetric::x25519;
|
||||
use nym_gateway_client::MixnetMessageReceiver;
|
||||
use nym_sphinx::anonymous_replies::requests::{
|
||||
RepliableMessage, RepliableMessageContent, ReplyMessage, ReplyMessageContent,
|
||||
};
|
||||
use nym_sphinx::anonymous_replies::{encryption_key::EncryptionKeyDigest, SurbEncryptionKey};
|
||||
use nym_sphinx::anonymous_replies::{SurbEncryptionKey, encryption_key::EncryptionKeyDigest};
|
||||
use nym_sphinx::message::{NymMessage, PlainMessage};
|
||||
use nym_sphinx::params::ReplySurbKeyDigestAlgorithm;
|
||||
use nym_sphinx::receiver::{MessageReceiver, MessageRecoveryError, ReconstructedMessage};
|
||||
use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, ClientStatsSender};
|
||||
use nym_statistics_common::clients::{ClientStatsSender, packet_statistics::PacketStatisticsEvent};
|
||||
use nym_task::ShutdownToken;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
@@ -78,14 +78,19 @@ impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
|
||||
|
||||
let fragment = match self.message_receiver.recover_fragment(fragment_data) {
|
||||
Err(err) => {
|
||||
warn!("failed to recover fragment from raw data: {err}. The whole underlying message might be corrupted and unrecoverable!");
|
||||
warn!(
|
||||
"failed to recover fragment from raw data: {err}. The whole underlying message might be corrupted and unrecoverable!"
|
||||
);
|
||||
return None;
|
||||
}
|
||||
Ok(frag) => frag,
|
||||
};
|
||||
|
||||
if self.recently_reconstructed.contains(&fragment.id()) {
|
||||
debug!("Received a chunk of already re-assembled message ({:?})! It probably got here because the ack got lost", fragment.id());
|
||||
debug!(
|
||||
"Received a chunk of already re-assembled message ({:?})! It probably got here because the ack got lost",
|
||||
fragment.id()
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -93,7 +98,9 @@ impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
|
||||
match self.message_receiver.insert_new_fragment(fragment) {
|
||||
Err(err) => match err {
|
||||
MessageRecoveryError::MalformedReconstructedMessage { source, used_sets } => {
|
||||
error!("message reconstruction failed - {source}. Attempting to re-use the message sets...");
|
||||
error!(
|
||||
"message reconstruction failed - {source}. Attempting to re-use the message sets..."
|
||||
);
|
||||
// TODO: should we really insert reconstructed sets? could this be abused for some attack?
|
||||
for set_id in used_sets {
|
||||
if !self.recently_reconstructed.insert(set_id) {
|
||||
@@ -144,7 +151,9 @@ impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
|
||||
&mut raw_fragment,
|
||||
) {
|
||||
Err(err) => {
|
||||
warn!("failed to recover fragment data: {err}. The whole underlying message might be corrupted and unrecoverable!");
|
||||
warn!(
|
||||
"failed to recover fragment data: {err}. The whole underlying message might be corrupted and unrecoverable!"
|
||||
);
|
||||
return None;
|
||||
}
|
||||
Ok(frag_data) => frag_data,
|
||||
@@ -275,7 +284,9 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
}
|
||||
RepliableMessageContent::Heartbeat(content) => {
|
||||
let additional_reply_surbs = content.additional_reply_surbs;
|
||||
error!("received a repliable heartbeat message - we don't know how to handle it yet (and we won't know until future PRs)");
|
||||
error!(
|
||||
"received a repliable heartbeat message - we don't know how to handle it yet (and we won't know until future PRs)"
|
||||
);
|
||||
(additional_reply_surbs, false)
|
||||
}
|
||||
RepliableMessageContent::DataV2(content) => {
|
||||
@@ -304,7 +315,9 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
}
|
||||
RepliableMessageContent::HeartbeatV2(content) => {
|
||||
let additional_reply_surbs = content.additional_reply_surbs;
|
||||
error!("received a repliable heartbeat message - we don't know how to handle it yet (and we won't know until future PRs)");
|
||||
error!(
|
||||
"received a repliable heartbeat message - we don't know how to handle it yet (and we won't know until future PRs)"
|
||||
);
|
||||
(additional_reply_surbs, false)
|
||||
}
|
||||
};
|
||||
@@ -380,7 +393,9 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
if let Some(sender) = &inner_guard.message_sender {
|
||||
trace!("Sending reconstructed messages to announced sender");
|
||||
if let Err(err) = sender.unbounded_send(reconstructed_messages) {
|
||||
warn!("The reconstructed message receiver went offline without explicit notification (relevant error: - {err})");
|
||||
warn!(
|
||||
"The reconstructed message receiver went offline without explicit notification (relevant error: - {err})"
|
||||
);
|
||||
inner_guard.message_sender = None;
|
||||
inner_guard.messages.extend(err.into_inner());
|
||||
}
|
||||
|
||||
@@ -5,15 +5,15 @@ use crate::client::real_messages_control::acknowledgement_control::PendingAcknow
|
||||
use crate::client::real_messages_control::message_handler::{
|
||||
FragmentWithMaxRetransmissions, MessageHandler, PreparationError,
|
||||
};
|
||||
use crate::client::replies::reply_controller::key_rotation_helpers::SurbRefreshState;
|
||||
use crate::client::replies::reply_controller::Config;
|
||||
use crate::client::replies::reply_controller::key_rotation_helpers::SurbRefreshState;
|
||||
use crate::client::topology_control::TopologyAccessor;
|
||||
use crate::client::transmission_buffer::TransmissionBuffer;
|
||||
use futures::channel::oneshot;
|
||||
use nym_client_core_surb_storage::{ReceivedReplySurb, ReceivedReplySurbsMap};
|
||||
use nym_crypto::aes::cipher::crypto_common::rand_core::CryptoRng;
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_sphinx::anonymous_replies::ReplySurbWithKeyRotation;
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_sphinx::chunking::fragment::FragmentIdentifier;
|
||||
use nym_task::connections::{ConnectionId, TransmissionLane};
|
||||
use nym_topology::NymTopologyMetadata;
|
||||
@@ -50,7 +50,9 @@ impl SenderData {
|
||||
let pending_retransmissions = self.pending_retransmissions.len();
|
||||
let total_pending = pending_retransmissions + pending_replies;
|
||||
|
||||
debug!("total queue size: {total_pending} = pending data {pending_replies} + pending retransmission {pending_retransmissions}");
|
||||
debug!(
|
||||
"total queue size: {total_pending} = pending data {pending_replies} + pending retransmission {pending_retransmissions}"
|
||||
);
|
||||
|
||||
total_pending
|
||||
}
|
||||
@@ -200,7 +202,9 @@ where
|
||||
let total_required_surbs = total_queue + target_surbs_after_clearing_queue;
|
||||
let total_available_surbs = pending_surbs + available_surbs;
|
||||
|
||||
debug!("available surbs: {available_surbs} pending surbs: {pending_surbs} threshold range: {min_surbs_threshold}..+{min_surbs_threshold_buffer}..{max_surbs_threshold}");
|
||||
debug!(
|
||||
"available surbs: {available_surbs} pending surbs: {pending_surbs} threshold range: {min_surbs_threshold}..+{min_surbs_threshold_buffer}..{max_surbs_threshold}"
|
||||
);
|
||||
|
||||
// We should request more surbs if:
|
||||
// 1. We haven't hit the maximum surb threshold, and
|
||||
@@ -225,9 +229,13 @@ where
|
||||
.is_none()
|
||||
{
|
||||
// don't report it every single time
|
||||
warn!("received reply request for {recipient_tag} but we don't have any surbs stored for that recipient!");
|
||||
warn!(
|
||||
"received reply request for {recipient_tag} but we don't have any surbs stored for that recipient!"
|
||||
);
|
||||
} else {
|
||||
trace!("received reply request for {recipient_tag} but we don't have any surbs stored for that recipient!");
|
||||
trace!(
|
||||
"received reply request for {recipient_tag} but we don't have any surbs stored for that recipient!"
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -383,7 +391,9 @@ where
|
||||
let (surbs_for_reply, _) = self.surbs_storage.get_reply_surbs(&target, to_take.len());
|
||||
|
||||
let Some(surbs_for_reply) = surbs_for_reply else {
|
||||
error!("somehow different task has stolen our reply surbs! - this should have been impossible");
|
||||
error!(
|
||||
"somehow different task has stolen our reply surbs! - this should have been impossible"
|
||||
);
|
||||
self.re_insert_pending_retransmission(&target, to_take);
|
||||
return;
|
||||
};
|
||||
@@ -459,7 +469,9 @@ where
|
||||
.get_reply_surbs(&target, to_send_clone.len());
|
||||
|
||||
let Some(surbs_for_reply) = surbs_for_reply else {
|
||||
error!("somehow different task has stolen our reply surbs! - this should have been impossible");
|
||||
error!(
|
||||
"somehow different task has stolen our reply surbs! - this should have been impossible"
|
||||
);
|
||||
self.re_insert_pending_replies(&target, to_send);
|
||||
return;
|
||||
};
|
||||
@@ -543,7 +555,9 @@ where
|
||||
let ack_ref = match timed_out_ack.upgrade() {
|
||||
Some(ack) => ack,
|
||||
None => {
|
||||
debug!("we received the ack for one of the reply packets as we were putting it in the retransmission queue");
|
||||
debug!(
|
||||
"we received the ack for one of the reply packets as we were putting it in the retransmission queue"
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -657,9 +671,13 @@ where
|
||||
|
||||
// only log at higher level if it's the first time this error has occurred in a while
|
||||
if now - last_failure > time::Duration::seconds(30) {
|
||||
warn!("failed to request more surbs to clear pending queue of size {total_queue} (attempted to request: {request_size}): {err}")
|
||||
warn!(
|
||||
"failed to request more surbs to clear pending queue of size {total_queue} (attempted to request: {request_size}): {err}"
|
||||
)
|
||||
} else {
|
||||
debug!("failed to request more surbs to clear pending queue of size {total_queue} (attempted to request: {request_size}): {err}")
|
||||
debug!(
|
||||
"failed to request more surbs to clear pending queue of size {total_queue} (attempted to request: {request_size}): {err}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -681,7 +699,10 @@ where
|
||||
.surbs_storage
|
||||
.surbs_last_received_at(pending_reply_target)
|
||||
else {
|
||||
error!("we have {} pending replies for {pending_reply_target}, but we somehow never received any reply surbs from them!", retransmission_buf.total_size());
|
||||
error!(
|
||||
"we have {} pending replies for {pending_reply_target}, but we somehow never received any reply surbs from them!",
|
||||
retransmission_buf.total_size()
|
||||
);
|
||||
to_remove.push(*pending_reply_target);
|
||||
continue;
|
||||
};
|
||||
@@ -702,7 +723,9 @@ where
|
||||
// if client is offline)
|
||||
if vals.current_clear_rerequest_counter > max_rerequests {
|
||||
to_remove.push(*pending_reply_target);
|
||||
debug!("we have reached the maximum threshold of attempting to request surbs from {pending_reply_target}. dropping the sender");
|
||||
debug!(
|
||||
"we have reached the maximum threshold of attempting to request surbs from {pending_reply_target}. dropping the sender"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -710,7 +733,10 @@ where
|
||||
if diff > max_drop_wait {
|
||||
to_remove.push(*pending_reply_target)
|
||||
} else {
|
||||
debug!("We haven't received any surbs in {} from {pending_reply_target}. Going to explicitly ask for more", humantime::format_duration(diff.unsigned_abs()));
|
||||
debug!(
|
||||
"We haven't received any surbs in {} from {pending_reply_target}. Going to explicitly ask for more",
|
||||
humantime::format_duration(diff.unsigned_abs())
|
||||
);
|
||||
vals.increment_current_clear_rerequest_counter();
|
||||
to_request.push(*pending_reply_target);
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
use crate::client::real_messages_control::acknowledgement_control::PendingAcknowledgement;
|
||||
use futures::channel::{mpsc, oneshot};
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_sphinx::anonymous_replies::ReplySurbWithKeyRotation;
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_task::connections::{ConnectionId, TransmissionLane};
|
||||
use std::sync::Weak;
|
||||
|
||||
|
||||
@@ -43,7 +43,9 @@ where
|
||||
// 1. check whether we sent any surbs in the past to this recipient, otherwise
|
||||
// they have no business in asking for more
|
||||
if !self.tags_storage.exists(&recipient) {
|
||||
warn!("{recipient} asked us for reply SURBs even though we never sent them any anonymous messages before!");
|
||||
warn!(
|
||||
"{recipient} asked us for reply SURBs even though we never sent them any anonymous messages before!"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -54,7 +56,12 @@ where
|
||||
.reply_surbs
|
||||
.maximum_allowed_reply_surb_request_size
|
||||
{
|
||||
warn!("The requested reply surb amount is larger than our maximum allowed ({amount} > {}). Lowering it to a more sane value...", self.config.reply_surbs.maximum_allowed_reply_surb_request_size);
|
||||
warn!(
|
||||
"The requested reply surb amount is larger than our maximum allowed ({amount} > {}). Lowering it to a more sane value...",
|
||||
self.config
|
||||
.reply_surbs
|
||||
.maximum_allowed_reply_surb_request_size
|
||||
);
|
||||
amount = self
|
||||
.config
|
||||
.reply_surbs
|
||||
|
||||
@@ -23,7 +23,7 @@ use nym_sphinx::addressing::Recipient;
|
||||
use nym_statistics_common::clients::{
|
||||
ClientStatsController, ClientStatsReceiver, ClientStatsSender,
|
||||
};
|
||||
use nym_task::{connections::TransmissionLane, ShutdownToken, ShutdownTracker};
|
||||
use nym_task::{ShutdownToken, ShutdownTracker, connections::TransmissionLane};
|
||||
use std::time::Duration;
|
||||
|
||||
/// Time interval between reporting statistics locally (logging/shutdown_token)
|
||||
|
||||
@@ -5,8 +5,8 @@ use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_topology::{NymRouteProvider, NymTopology, NymTopologyError, NymTopologyMetadata};
|
||||
use nym_validator_client::models::KeyRotationId;
|
||||
use std::ops::Deref;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use tokio::sync::{Notify, RwLock, RwLockReadGuard};
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -63,7 +63,9 @@ impl TopologyRefresher {
|
||||
trace!("Refreshing the topology");
|
||||
|
||||
if self.topology_accessor.controlled_manually() {
|
||||
info!("topology is being controlled manually - we're going to wait until the control is released...");
|
||||
info!(
|
||||
"topology is being controlled manually - we're going to wait until the control is released..."
|
||||
);
|
||||
self.topology_accessor
|
||||
.wait_for_released_manual_control()
|
||||
.await;
|
||||
@@ -138,6 +140,35 @@ impl TopologyRefresher {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn wait_for_initial_network(
|
||||
&mut self,
|
||||
timeout_duration: Duration,
|
||||
) -> Result<(), NymTopologyError> {
|
||||
info!(
|
||||
"going to wait for at most {timeout_duration:?} for initial network to become online"
|
||||
);
|
||||
|
||||
let deadline = sleep(timeout_duration);
|
||||
tokio::pin!(deadline);
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = &mut deadline => {
|
||||
return Err(NymTopologyError::TimedOutWaitingForTopology)
|
||||
}
|
||||
_ = self.try_refresh() => {
|
||||
if let Err(err) = self.ensure_topology_is_routable().await {
|
||||
info!("network is still not routable...: {err}");
|
||||
} else {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
sleep(self.refresh_rate).await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// it's perfectly fine if task is interrupted mid-refresh
|
||||
// there's no data to persist or send over
|
||||
pub async fn run(&mut self) {
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
use async_trait::async_trait;
|
||||
use nym_mixnet_contract_common::EpochRewardedSet;
|
||||
use nym_topology::provider_trait::{ToTopologyMetadata, TopologyProvider};
|
||||
use nym_topology::NymTopology;
|
||||
use nym_topology::provider_trait::{ToTopologyMetadata, TopologyProvider};
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use rand::prelude::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
@@ -82,7 +82,9 @@ impl NymApiTopologyProvider {
|
||||
|
||||
fn use_next_nym_api(&mut self) {
|
||||
if self.nym_api_urls.len() == 1 {
|
||||
warn!("There's only a single nym API available - it won't be possible to use a different one");
|
||||
warn!(
|
||||
"There's only a single nym API available - it won't be possible to use a different one"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -155,7 +157,10 @@ impl NymApiTopologyProvider {
|
||||
let mixnodes = mixnodes_res.nodes;
|
||||
|
||||
if !gateways_res.metadata.consistency_check(&metadata) {
|
||||
warn!("inconsistent nodes metadata between mixnodes and gateways calls! {metadata:?} and {:?}", gateways_res.metadata);
|
||||
warn!(
|
||||
"inconsistent nodes metadata between mixnodes and gateways calls! {metadata:?} and {:?}",
|
||||
gateways_res.metadata
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::helpers::{get_time_now, Instant};
|
||||
use crate::client::helpers::{Instant, get_time_now};
|
||||
use crate::client::real_messages_control::real_traffic_stream::RealMessage;
|
||||
use nym_sphinx::chunking::fragment::Fragment;
|
||||
use nym_task::connections::TransmissionLane;
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
use rand::{Rng, seq::SliceRandom};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
time::Duration,
|
||||
|
||||
@@ -7,9 +7,9 @@ use nym_gateway_client::error::GatewayClientError;
|
||||
use nym_task::RegistryAccessError;
|
||||
use nym_topology::node::RoutingNodeError;
|
||||
use nym_topology::{NodeId, NymTopologyError};
|
||||
use nym_validator_client::ValidatorClientError;
|
||||
use nym_validator_client::nym_api::error::NymAPIError;
|
||||
use nym_validator_client::nyxd::error::NyxdError;
|
||||
use nym_validator_client::ValidatorClientError;
|
||||
use rand::distributions::WeightedError;
|
||||
use std::error::Error;
|
||||
use std::path::PathBuf;
|
||||
@@ -56,7 +56,9 @@ pub enum ClientCoreError {
|
||||
#[error("no gateways on network")]
|
||||
NoGatewaysOnNetwork,
|
||||
|
||||
#[error("there are no more new gateways on the network - it seems this client has already registered with all nodes it could have")]
|
||||
#[error(
|
||||
"there are no more new gateways on the network - it seems this client has already registered with all nodes it could have"
|
||||
)]
|
||||
NoNewGatewaysAvailable,
|
||||
|
||||
#[error("list of nym apis is empty")]
|
||||
@@ -127,7 +129,9 @@ pub enum ClientCoreError {
|
||||
#[error("unexpected exit")]
|
||||
UnexpectedExit,
|
||||
|
||||
#[error("this operation would have resulted in the gateway {gateway_id:?} key being overwritten without permission")]
|
||||
#[error(
|
||||
"this operation would have resulted in the gateway {gateway_id:?} key being overwritten without permission"
|
||||
)]
|
||||
ForbiddenGatewayKeyOverwrite { gateway_id: String },
|
||||
|
||||
#[error(
|
||||
@@ -151,7 +155,9 @@ pub enum ClientCoreError {
|
||||
#[error("attempted to obtain fresh gateway details whilst already knowing about one")]
|
||||
UnexpectedGatewayDetails,
|
||||
|
||||
#[error("the provided gateway details (for gateway {gateway_id}) do not correspond to the shared keys")]
|
||||
#[error(
|
||||
"the provided gateway details (for gateway {gateway_id}) do not correspond to the shared keys"
|
||||
)]
|
||||
MismatchedGatewayDetails { gateway_id: String },
|
||||
|
||||
#[error("unable to upgrade config file from `{current_version}`")]
|
||||
@@ -227,7 +233,9 @@ pub enum ClientCoreError {
|
||||
source: url::ParseError,
|
||||
},
|
||||
|
||||
#[error("this client (id: '{client_id}') has already been initialised before. If you want to add additional gateway, use `add-gateway` command")]
|
||||
#[error(
|
||||
"this client (id: '{client_id}') has already been initialised before. If you want to add additional gateway, use `add-gateway` command"
|
||||
)]
|
||||
AlreadyInitialised { client_id: String },
|
||||
|
||||
#[error("this client has already registered with gateway {gateway_id}")]
|
||||
|
||||
@@ -5,13 +5,13 @@ use crate::error::ClientCoreError;
|
||||
use crate::init::types::RegistrationResult;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_gateway_client::client::GatewayListeners;
|
||||
use nym_gateway_client::GatewayClient;
|
||||
use nym_gateway_client::client::GatewayListeners;
|
||||
use nym_topology::node::RoutingNode;
|
||||
use nym_validator_client::UserAgent;
|
||||
use nym_validator_client::client::{IdentityKeyRef, NymApiClientExt};
|
||||
use nym_validator_client::nym_nodes::SkimmedNodesWithMetadata;
|
||||
use nym_validator_client::UserAgent;
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
use rand::{Rng, seq::SliceRandom};
|
||||
#[cfg(unix)]
|
||||
use std::os::fd::RawFd;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
@@ -28,10 +28,10 @@ use nym_wasm_utils::websocket::JSWebsocket;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::net::TcpStream;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::time::sleep;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::time::Instant;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::time::sleep;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasmtimer::std::Instant;
|
||||
|
||||
@@ -7,8 +7,8 @@ use crate::client::base_client::storage::helpers::{
|
||||
has_gateway_details, load_active_gateway_details, load_client_keys, load_gateway_details,
|
||||
store_gateway_details, update_stored_published_data_gateway,
|
||||
};
|
||||
use crate::client::key_manager::persistence::KeyStore;
|
||||
use crate::client::key_manager::ClientKeys;
|
||||
use crate::client::key_manager::persistence::KeyStore;
|
||||
use crate::error::ClientCoreError;
|
||||
use crate::init::helpers::{
|
||||
choose_gateway_by_latency, get_specified_gateway, uniformly_random_gateway,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::key_manager::persistence::KeyStore;
|
||||
use crate::client::key_manager::ClientKeys;
|
||||
use crate::client::key_manager::persistence::KeyStore;
|
||||
use crate::config::Config;
|
||||
use crate::error::ClientCoreError;
|
||||
use crate::init::{setup_gateway, use_loaded_gateway_details};
|
||||
@@ -10,8 +10,8 @@ use nym_client_core_gateways_storage::{
|
||||
GatewayRegistration, GatewaysDetailsStore, RemoteGatewayDetails,
|
||||
};
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_gateway_client::client::{GatewayListeners, InitGatewayClient};
|
||||
use nym_gateway_client::SharedSymmetricKey;
|
||||
use nym_gateway_client::client::{GatewayListeners, InitGatewayClient};
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_topology::node::RoutingNode;
|
||||
use nym_validator_client::client::IdentityKey;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#![allow(deprecated)] // silences clippy warning: use of deprecated associated function `nym_crypto::generic_array::GenericArray::<T, N>::clone_from_slice`: please upgrade to generic-array 1.x - TODO
|
||||
use std::future::Future;
|
||||
|
||||
#[cfg(all(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![allow(deprecated)] // silences clippy warning: use of deprecated associated function `nym_crypto::generic_array::GenericArray::<T, N>::from_exact_iter`: please upgrade to generic-array 1.x - TODO
|
||||
pub use backend::*;
|
||||
pub use combined::CombinedReplyStorage;
|
||||
pub use key_storage::SentReplyKeys;
|
||||
|
||||
@@ -342,7 +342,7 @@ impl SendWithoutResponse for Client {
|
||||
sending_res.map_err(|err| {
|
||||
match err {
|
||||
TrySendError::Full(_) => {
|
||||
warn!(
|
||||
trace!(
|
||||
event = "mixclient.try_send",
|
||||
peer = %address,
|
||||
result = "full_dropped",
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::nyxd::CosmWasmClient;
|
||||
use async_trait::async_trait;
|
||||
use cosmwasm_std::Coin;
|
||||
use nym_ecash_contract_common::deposit::LatestDepositResponse;
|
||||
use nym_ecash_contract_common::deposit_statistics::DepositsStatistics;
|
||||
use nym_ecash_contract_common::msg::QueryMsg as EcashQueryMsg;
|
||||
use serde::Deserialize;
|
||||
|
||||
@@ -17,6 +18,9 @@ pub use nym_ecash_contract_common::blacklist::{
|
||||
pub use nym_ecash_contract_common::deposit::{
|
||||
Deposit, DepositData, DepositId, DepositResponse, PagedDepositsResponse,
|
||||
};
|
||||
pub use nym_ecash_contract_common::reduced_deposit::{
|
||||
WhitelistedAccount, WhitelistedAccountsResponse,
|
||||
};
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
@@ -42,8 +46,18 @@ pub trait EcashQueryClient {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_required_deposit_amount(&self) -> Result<Coin, NyxdError> {
|
||||
self.query_ecash_contract(EcashQueryMsg::GetRequiredDepositAmount {})
|
||||
async fn get_default_deposit_amount(&self) -> Result<Coin, NyxdError> {
|
||||
self.query_ecash_contract(EcashQueryMsg::GetDefaultDepositAmount {})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_reduced_deposit_amount(&self, address: String) -> Result<Option<Coin>, NyxdError> {
|
||||
self.query_ecash_contract(EcashQueryMsg::GetReducedDepositAmount { address })
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_all_whitelisted_accounts(&self) -> Result<WhitelistedAccountsResponse, NyxdError> {
|
||||
self.query_ecash_contract(EcashQueryMsg::GetAllWhitelistedAccounts {})
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -65,6 +79,11 @@ pub trait EcashQueryClient {
|
||||
self.query_ecash_contract(EcashQueryMsg::GetDepositsPaged { start_after, limit })
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_deposits_statistics(&self) -> Result<DepositsStatistics, NyxdError> {
|
||||
self.query_ecash_contract(EcashQueryMsg::GetDepositsStatistics {})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
@@ -122,10 +141,17 @@ mod tests {
|
||||
EcashQueryMsg::GetDepositsPaged { limit, start_after } => {
|
||||
client.get_deposits_paged(start_after, limit).ignore()
|
||||
}
|
||||
EcashQueryMsg::GetRequiredDepositAmount {} => {
|
||||
client.get_required_deposit_amount().ignore()
|
||||
EcashQueryMsg::GetDefaultDepositAmount {} => {
|
||||
client.get_default_deposit_amount().ignore()
|
||||
}
|
||||
EcashQueryMsg::GetReducedDepositAmount { address } => {
|
||||
client.get_reduced_deposit_amount(address).ignore()
|
||||
}
|
||||
EcashQueryMsg::GetAllWhitelistedAccounts {} => {
|
||||
client.get_all_whitelisted_accounts().ignore()
|
||||
}
|
||||
EcashQueryMsg::GetLatestDeposit {} => client.get_latest_deposit().ignore(),
|
||||
EcashQueryMsg::GetDepositsStatistics {} => client.get_deposits_statistics().ignore(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+42
-2
@@ -62,13 +62,47 @@ pub trait EcashSigningClient {
|
||||
new_deposit: Coin,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
let req = EcashExecuteMsg::UpdateDepositValue {
|
||||
let req = EcashExecuteMsg::UpdateDefaultDepositValue {
|
||||
new_deposit: new_deposit.into(),
|
||||
};
|
||||
self.execute_ecash_contract(fee, req, "Ecash::UpdateDepositValue".to_string(), vec![])
|
||||
.await
|
||||
}
|
||||
|
||||
async fn set_reduced_deposit_price(
|
||||
&self,
|
||||
address: String,
|
||||
deposit: Coin,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
let req = EcashExecuteMsg::SetReducedDepositPrice {
|
||||
address,
|
||||
deposit: deposit.into(),
|
||||
};
|
||||
self.execute_ecash_contract(
|
||||
fee,
|
||||
req,
|
||||
"Ecash::SetReducedDepositPrice".to_string(),
|
||||
vec![],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn remove_reduced_deposit_price(
|
||||
&self,
|
||||
address: String,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
let req = EcashExecuteMsg::RemoveReducedDepositPrice { address };
|
||||
self.execute_ecash_contract(
|
||||
fee,
|
||||
req,
|
||||
"Ecash::RemoveReducedDepositPrice".to_string(),
|
||||
vec![],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn propose_for_blacklist(
|
||||
&self,
|
||||
public_key: String,
|
||||
@@ -141,9 +175,15 @@ mod tests {
|
||||
.ignore(),
|
||||
ExecuteMsg::RedeemTickets { .. } => unimplemented!(), // no redeem tickets method for the client
|
||||
ExecuteMsg::UpdateAdmin { admin } => client.update_admin(admin, None).ignore(),
|
||||
ExecuteMsg::UpdateDepositValue { new_deposit } => client
|
||||
ExecuteMsg::UpdateDefaultDepositValue { new_deposit } => client
|
||||
.update_deposit_value(new_deposit.into(), None)
|
||||
.ignore(),
|
||||
ExecuteMsg::SetReducedDepositPrice { address, deposit } => client
|
||||
.set_reduced_deposit_price(address, deposit.into(), None)
|
||||
.ignore(),
|
||||
ExecuteMsg::RemoveReducedDepositPrice { address } => {
|
||||
client.remove_reduced_deposit_price(address, None).ignore()
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -130,7 +130,7 @@ pub trait CosmWasmClient: TendermintRpcClient {
|
||||
|
||||
let req = QueryBalanceRequest {
|
||||
address: address.to_string(),
|
||||
denom: search_denom.to_string(),
|
||||
denom: search_denom,
|
||||
};
|
||||
|
||||
let res = self
|
||||
|
||||
@@ -199,6 +199,18 @@ impl NyxdClient<HttpClient, DirectSecp256k1HdWallet> {
|
||||
let wallet = DirectSecp256k1HdWallet::checked_from_mnemonic(prefix, mnemonic)?;
|
||||
Ok(Self::connect_with_signer(config, client, wallet))
|
||||
}
|
||||
|
||||
pub fn connect_with_mnemonic_and_network_details<U>(
|
||||
endpoint: U,
|
||||
network_details: NymNetworkDetails,
|
||||
mnemonic: bip39::Mnemonic,
|
||||
) -> Result<DirectSigningHttpRpcNyxdClient, NyxdError>
|
||||
where
|
||||
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
|
||||
{
|
||||
let config = Config::try_from_nym_network_details(&network_details)?;
|
||||
Self::connect_with_mnemonic(config, endpoint, mnemonic)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![allow(clippy::derivable_impls)]
|
||||
// MAX: surpressing warning for the moment, will be dealt with in a different PR (TODO)
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
|
||||
@@ -6,6 +6,13 @@ use cosmwasm_std::Coin;
|
||||
|
||||
#[cw_serde]
|
||||
pub struct PoolCounters {
|
||||
/// Represents the total amount of funds deposited into the contract.
|
||||
pub total_deposited: Coin,
|
||||
|
||||
/// Represents the total amount of funds redeemed from the contract that got transferred into the holding account.
|
||||
pub total_redeemed: Coin,
|
||||
|
||||
/// Represents the total amount of tickets requested to be redeemed from the contract and get moved into the holding account,
|
||||
/// after that functionality got disabled.
|
||||
pub tickets_requested_and_not_redeemed: u64,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::Coin;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Aggregate statistics about all deposits made through the ecash contract.
|
||||
#[cw_serde]
|
||||
pub struct DepositsStatistics {
|
||||
/// Total number of deposits ever made (at any price tier),
|
||||
/// derived from the deposit id counter.
|
||||
pub total_deposits_made: u32,
|
||||
|
||||
/// Total value of all deposits ever made (at any price tier),
|
||||
/// sourced from `PoolCounters::total_deposited`.
|
||||
pub total_deposited: Coin,
|
||||
|
||||
/// Number of deposits made at the default (non-reduced) price.
|
||||
pub total_deposits_made_with_default_price: u32,
|
||||
|
||||
/// Total value deposited at the default price.
|
||||
pub total_deposited_with_default_price: Coin,
|
||||
|
||||
/// Number of deposits made at any custom (reduced) price, summed across all whitelisted accounts.
|
||||
pub total_deposits_made_with_custom_price: u32,
|
||||
|
||||
/// Total value deposited at custom prices, summed across all whitelisted accounts.
|
||||
pub total_deposited_with_custom_price: Coin,
|
||||
|
||||
/// Per-account breakdown of deposit counts for whitelisted addresses.
|
||||
// note: we use String for addressing due to serialisation incompatibility
|
||||
pub deposits_made_with_custom_price: HashMap<String, u32>,
|
||||
|
||||
/// Per-account breakdown of deposited amounts for whitelisted addresses.
|
||||
// note: we use String for addressing due to serialisation incompatibility
|
||||
pub deposited_with_custom_price: HashMap<String, Coin>,
|
||||
}
|
||||
@@ -65,4 +65,26 @@ pub enum EcashContractError {
|
||||
|
||||
#[error("the account blacklisting hasn't been fully implemented yet")]
|
||||
UnimplementedBlacklisting,
|
||||
|
||||
#[error("reduced deposit must use the same denom as the default deposit (expected '{expected}', got '{got}')")]
|
||||
InvalidReducedDepositDenom { expected: String, got: String },
|
||||
|
||||
#[error(
|
||||
"reduced deposit amount ({reduced}) must be strictly less than the default ({default})"
|
||||
)]
|
||||
ReducedDepositNotReduced {
|
||||
reduced: cosmwasm_std::Uint128,
|
||||
default: cosmwasm_std::Uint128,
|
||||
},
|
||||
|
||||
#[error("address '{address}' does not have a custom reduced deposit price set")]
|
||||
NoReducedDepositPrice { address: String },
|
||||
|
||||
#[error(
|
||||
"deposit amount ({amount}) must be at least the ticket book size ({ticket_book_size})"
|
||||
)]
|
||||
DepositBelowTicketBookSize {
|
||||
amount: cosmwasm_std::Uint128,
|
||||
ticket_book_size: u64,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
pub mod blacklist;
|
||||
pub mod counters;
|
||||
pub mod deposit;
|
||||
pub mod deposit_statistics;
|
||||
pub mod error;
|
||||
pub mod event_attributes;
|
||||
pub mod events;
|
||||
pub mod msg;
|
||||
pub mod redeem_credential;
|
||||
pub mod reduced_deposit;
|
||||
|
||||
pub use error::EcashContractError;
|
||||
|
||||
@@ -9,6 +9,10 @@ use crate::blacklist::{BlacklistedAccountResponse, PagedBlacklistedAccountRespon
|
||||
#[cfg(feature = "schema")]
|
||||
use crate::deposit::{DepositResponse, LatestDepositResponse, PagedDepositsResponse};
|
||||
#[cfg(feature = "schema")]
|
||||
use crate::deposit_statistics::DepositsStatistics;
|
||||
#[cfg(feature = "schema")]
|
||||
use crate::reduced_deposit::WhitelistedAccountsResponse;
|
||||
#[cfg(feature = "schema")]
|
||||
use cosmwasm_schema::QueryResponses;
|
||||
|
||||
#[cw_serde]
|
||||
@@ -42,10 +46,25 @@ pub enum ExecuteMsg {
|
||||
admin: String,
|
||||
},
|
||||
|
||||
UpdateDepositValue {
|
||||
#[serde(alias = "update_deposit_value")]
|
||||
UpdateDefaultDepositValue {
|
||||
new_deposit: Coin,
|
||||
},
|
||||
|
||||
/// Set (or overwrite) a reduced deposit price for a specific address.
|
||||
/// Only callable by the contract admin.
|
||||
SetReducedDepositPrice {
|
||||
address: String,
|
||||
deposit: Coin,
|
||||
},
|
||||
|
||||
/// Remove the reduced deposit price for a specific address, reverting them to
|
||||
/// the default price. Returns an error if the address has no custom price set.
|
||||
/// Only callable by the contract admin.
|
||||
RemoveReducedDepositPrice {
|
||||
address: String,
|
||||
},
|
||||
|
||||
// TODO: properly implement
|
||||
ProposeToBlacklist {
|
||||
public_key: String,
|
||||
@@ -68,7 +87,15 @@ pub enum QueryMsg {
|
||||
},
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(Coin))]
|
||||
GetRequiredDepositAmount {},
|
||||
#[serde(alias = "get_required_deposit_amount")]
|
||||
#[serde(alias = "GetRequiredDepositAmount")]
|
||||
GetDefaultDepositAmount {},
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(Option<Coin>))]
|
||||
GetReducedDepositAmount { address: String },
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(WhitelistedAccountsResponse))]
|
||||
GetAllWhitelistedAccounts {},
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(DepositResponse))]
|
||||
GetDeposit { deposit_id: u32 },
|
||||
@@ -81,7 +108,22 @@ pub enum QueryMsg {
|
||||
limit: Option<u32>,
|
||||
start_after: Option<u32>,
|
||||
},
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(DepositsStatistics))]
|
||||
GetDepositsStatistics {},
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct MigrateMsg {}
|
||||
pub struct MigrateMsg {
|
||||
/// Initial set of whitelisted accounts with their reduced deposit prices.
|
||||
/// Each entry is validated and stored during migration.
|
||||
pub initial_whitelist: Vec<WhitelistedDeposit>,
|
||||
}
|
||||
|
||||
/// An address and its reduced deposit price, used when seeding the whitelist
|
||||
/// via migration.
|
||||
#[cw_serde]
|
||||
pub struct WhitelistedDeposit {
|
||||
pub address: String,
|
||||
pub deposit: Coin,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::{Addr, Coin};
|
||||
|
||||
#[cw_serde]
|
||||
pub struct WhitelistedAccount {
|
||||
pub address: Addr,
|
||||
pub deposit: Coin,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct WhitelistedAccountsResponse {
|
||||
pub whitelisted_accounts: Vec<WhitelistedAccount>,
|
||||
}
|
||||
@@ -6,7 +6,7 @@ use crate::helpers::LockTimer;
|
||||
use nym_ecash_contract_common::msg::ExecuteMsg;
|
||||
use nym_validator_client::nyxd::contract_traits::NymContractsProvider;
|
||||
use nym_validator_client::nyxd::cosmwasm_client::types::ExecuteResult;
|
||||
use nym_validator_client::nyxd::{Coin, Config, CosmWasmClient, NyxdClient};
|
||||
use nym_validator_client::nyxd::{AccountId, Coin, Config, CosmWasmClient, NyxdClient};
|
||||
use nym_validator_client::{DirectSigningHttpRpcNyxdClient, nyxd};
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
@@ -50,6 +50,10 @@ impl ChainClient {
|
||||
Ok(ChainClient(Arc::new(RwLock::new(client))))
|
||||
}
|
||||
|
||||
pub async fn address(&self) -> AccountId {
|
||||
self.0.read().await.address()
|
||||
}
|
||||
|
||||
pub async fn query_chain(&self) -> ChainReadPermit<'_> {
|
||||
let _acquire_timer = LockTimer::new("acquire chain query permit");
|
||||
self.0.read().await
|
||||
|
||||
@@ -8,6 +8,7 @@ use nym_validator_client::nyxd::contract_traits::EcashQueryClient;
|
||||
use std::sync::Arc;
|
||||
use time::OffsetDateTime;
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::{info, warn};
|
||||
|
||||
pub struct CachedDeposit {
|
||||
valid_until: OffsetDateTime,
|
||||
@@ -56,13 +57,29 @@ impl RequiredDepositCache {
|
||||
|
||||
// update cache
|
||||
drop(read_guard);
|
||||
|
||||
let address = chain_client.address().await;
|
||||
info!("checking deposit required by {address}");
|
||||
let mut write_guard = self.inner.write().await;
|
||||
let deposit_amount = chain_client
|
||||
.query_chain()
|
||||
.await
|
||||
.get_required_deposit_amount()
|
||||
|
||||
let read_permit = chain_client.query_chain().await;
|
||||
let reduced = read_permit
|
||||
.get_reduced_deposit_amount(address.to_string())
|
||||
.await?;
|
||||
|
||||
let deposit_amount = match reduced {
|
||||
Some(reduced) => {
|
||||
info!("we're permitted to use reduced price");
|
||||
reduced
|
||||
}
|
||||
None => {
|
||||
warn!(
|
||||
"using default deposit value {address} is not whitelisted for price reduction"
|
||||
);
|
||||
read_permit.get_default_deposit_amount().await?
|
||||
}
|
||||
};
|
||||
|
||||
let nym_coin: Coin = deposit_amount.into();
|
||||
|
||||
write_guard.update(nym_coin.clone());
|
||||
|
||||
@@ -3,25 +3,19 @@
|
||||
|
||||
use crate::Error;
|
||||
use crate::ecash::error::EcashTicketError;
|
||||
use crate::ecash::helpers::for_each_api_concurrent;
|
||||
use crate::ecash::state::SharedState;
|
||||
use cosmwasm_std::Fraction;
|
||||
use cw_utils::ThresholdResponse;
|
||||
use futures::channel::mpsc::UnboundedReceiver;
|
||||
use futures::{Stream, StreamExt};
|
||||
use nym_api_requests::constants::MIN_BATCH_REDEMPTION_DELAY;
|
||||
use nym_api_requests::ecash::models::{BatchRedeemTicketsBody, VerifyEcashTicketBody};
|
||||
use nym_api_requests::ecash::models::VerifyEcashTicketBody;
|
||||
use nym_credentials_interface::Bandwidth;
|
||||
use nym_credentials_interface::{ClientTicket, TicketType};
|
||||
use nym_validator_client::EcashApiClient;
|
||||
use nym_validator_client::coconut::EcashApiError;
|
||||
use nym_validator_client::nym_api::{EpochId, NymApiClientExt};
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use nym_validator_client::nyxd::contract_traits::{
|
||||
EcashSigningClient, MultisigQueryClient, MultisigSigningClient, PagedMultisigQueryClient,
|
||||
};
|
||||
use nym_validator_client::nyxd::cosmwasm_client::ContractResponseData;
|
||||
use nym_validator_client::nyxd::cw3::Status;
|
||||
use nym_validator_client::nyxd::contract_traits::MultisigQueryClient;
|
||||
use si_scale::helpers::bibytes2;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::ops::Deref;
|
||||
@@ -31,22 +25,6 @@ use tokio::sync::{Mutex, RwLockReadGuard};
|
||||
use tokio::time::{Duration, Instant, interval_at};
|
||||
use tracing::{debug, error, info, instrument, trace, warn};
|
||||
|
||||
enum ProposalResult {
|
||||
Executed,
|
||||
Rejected,
|
||||
Pending,
|
||||
}
|
||||
|
||||
impl ProposalResult {
|
||||
fn is_pending(&self) -> bool {
|
||||
matches!(self, ProposalResult::Pending)
|
||||
}
|
||||
|
||||
fn is_rejected(&self) -> bool {
|
||||
matches!(self, ProposalResult::Rejected)
|
||||
}
|
||||
}
|
||||
|
||||
struct PendingVerification {
|
||||
ticket: ClientTicket,
|
||||
|
||||
@@ -68,43 +46,6 @@ impl PendingVerification {
|
||||
}
|
||||
}
|
||||
|
||||
struct PendingRedemptionVote {
|
||||
proposal_id: u64,
|
||||
digest: Vec<u8>,
|
||||
included_serial_numbers: Vec<Vec<u8>>,
|
||||
epoch_id: EpochId,
|
||||
|
||||
// vec of node ids of apis that haven't sent a valid response
|
||||
pending: Vec<u64>,
|
||||
}
|
||||
|
||||
impl PendingRedemptionVote {
|
||||
fn new(
|
||||
proposal_id: u64,
|
||||
digest: Vec<u8>,
|
||||
included_serial_numbers: Vec<Vec<u8>>,
|
||||
epoch_id: EpochId,
|
||||
pending: Vec<u64>,
|
||||
) -> Self {
|
||||
PendingRedemptionVote {
|
||||
proposal_id,
|
||||
digest,
|
||||
included_serial_numbers,
|
||||
epoch_id,
|
||||
pending,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_request_body(&self, gateway_cosmos_addr: AccountId) -> BatchRedeemTicketsBody {
|
||||
BatchRedeemTicketsBody::new(
|
||||
self.digest.clone(),
|
||||
self.proposal_id,
|
||||
self.included_serial_numbers.clone(),
|
||||
gateway_cosmos_addr,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CredentialHandlerConfig {
|
||||
/// Specifies the multiplier for revoking a malformed/double-spent ticket
|
||||
/// (if it has to go all the way to the nym-api for verification)
|
||||
@@ -132,7 +73,6 @@ pub struct CredentialHandler {
|
||||
ticket_receiver: UnboundedReceiver<ClientTicket>,
|
||||
shared_state: SharedState,
|
||||
pending_tickets: Vec<PendingVerification>,
|
||||
pending_redemptions: Vec<PendingRedemptionVote>,
|
||||
}
|
||||
|
||||
impl CredentialHandler {
|
||||
@@ -184,75 +124,6 @@ impl CredentialHandler {
|
||||
Ok(pending)
|
||||
}
|
||||
|
||||
async fn rebuild_pending_votes(
|
||||
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?;
|
||||
let mut pending = Vec::with_capacity(unverified.len());
|
||||
|
||||
let epoch_id = shared_state.current_epoch_id().await?;
|
||||
let apis = shared_state
|
||||
.api_clients(epoch_id)
|
||||
.await?
|
||||
.iter()
|
||||
.map(|s| (s.cosmos_address.to_string(), s.node_id))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for proposal_id in unverified {
|
||||
// get all of the votes
|
||||
let votes = shared_state
|
||||
.start_query()
|
||||
.await
|
||||
.get_all_votes(proposal_id as u64)
|
||||
.await
|
||||
.map_err(EcashTicketError::chain_query_failure)?
|
||||
.into_iter()
|
||||
.map(|v| v.voter)
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let mut missing_votes = Vec::new();
|
||||
|
||||
// see who hasn't voted
|
||||
for (api_address, api_id) in &apis {
|
||||
// for each signer, check if they have actually voted; if not, that's the missing guy
|
||||
if !votes.contains(api_address) {
|
||||
missing_votes.push(*api_id)
|
||||
}
|
||||
}
|
||||
|
||||
// attempt to rebuild SN and digest from the proposal info + storage data
|
||||
let proposal_info = shared_state
|
||||
.start_query()
|
||||
.await
|
||||
.query_proposal(proposal_id as u64)
|
||||
.await
|
||||
.map_err(EcashTicketError::chain_query_failure)?;
|
||||
|
||||
let tickets = shared_state
|
||||
.storage
|
||||
.get_all_proposed_tickets_with_sn(proposal_id as u32)
|
||||
.await?;
|
||||
let digest =
|
||||
BatchRedeemTicketsBody::make_digest(tickets.iter().map(|t| &t.serial_number));
|
||||
let encoded_digest = bs58::encode(&digest).into_string();
|
||||
if encoded_digest != proposal_info.description {
|
||||
error!("the lost proposal {proposal_id} does not have a matching digest!");
|
||||
continue;
|
||||
}
|
||||
|
||||
pending.push(PendingRedemptionVote {
|
||||
proposal_id: proposal_id as u64,
|
||||
digest,
|
||||
included_serial_numbers: tickets.into_iter().map(|t| t.serial_number).collect(),
|
||||
epoch_id,
|
||||
pending: missing_votes,
|
||||
})
|
||||
}
|
||||
|
||||
Ok(pending)
|
||||
}
|
||||
|
||||
pub(crate) async fn new(
|
||||
config: CredentialHandlerConfig,
|
||||
ticket_receiver: UnboundedReceiver<ClientTicket>,
|
||||
@@ -276,51 +147,15 @@ impl CredentialHandler {
|
||||
// on startup read pending credentials and api responses from the storage
|
||||
let pending_tickets = Self::rebuild_pending_tickets(&shared_state).await?;
|
||||
|
||||
// on startup read pending proposals from the storage
|
||||
// then reconstruct the votes by querying the multisig contract for votes on those proposals
|
||||
// digest from the description and count from the message
|
||||
let pending_redemptions = Self::rebuild_pending_votes(&shared_state).await?;
|
||||
|
||||
Ok(CredentialHandler {
|
||||
config,
|
||||
multisig_threshold,
|
||||
ticket_receiver,
|
||||
shared_state,
|
||||
pending_tickets,
|
||||
pending_redemptions,
|
||||
})
|
||||
}
|
||||
|
||||
// the argument is temporary as we'll be reading from the storage
|
||||
async fn create_redemption_proposal(
|
||||
&self,
|
||||
commitment: &[u8],
|
||||
number_of_tickets: u16,
|
||||
) -> Result<u64, EcashTicketError> {
|
||||
let res = self
|
||||
.shared_state
|
||||
.start_tx()
|
||||
.await
|
||||
.request_ticket_redemption(
|
||||
bs58::encode(commitment).into_string(),
|
||||
number_of_tickets,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(|source| EcashTicketError::RedemptionProposalCreationFailure { source })?;
|
||||
|
||||
// that one is quite tricky because proposal exists on chain, but we didn't get the id...
|
||||
// but it should be quite impossible to ever reach this unless we make breaking changes
|
||||
let proposal_id = res
|
||||
.parse_singleton_u64_contract_data()
|
||||
.inspect_err(|err| error!("reached seemingly impossible error! could not recover the redemption proposal id: {err}"))
|
||||
.map_err(|source| EcashTicketError::ProposalIdParsingFailure { source })?;
|
||||
|
||||
info!("created redemption proposal {proposal_id} to redeem {number_of_tickets} tickets");
|
||||
|
||||
Ok(proposal_id)
|
||||
}
|
||||
|
||||
/// Attempt to send ticket verification request to the provided ecash verifier.
|
||||
async fn verify_ticket(
|
||||
&self,
|
||||
@@ -522,42 +357,7 @@ impl CredentialHandler {
|
||||
async fn resolve_pending(&mut self) -> Result<(), EcashTicketError> {
|
||||
let mut still_failing = Vec::new();
|
||||
|
||||
// 1. attempt to resolve all pending proposals
|
||||
while let Some(mut pending) = self.pending_redemptions.pop() {
|
||||
match self.try_resolve_pending_proposal(&mut pending, None).await {
|
||||
Ok(resolution) => {
|
||||
if resolution.is_pending() {
|
||||
warn!(
|
||||
"still failed to reach quorum for proposal {}. apis: {:?} haven't responded. we'll retry later",
|
||||
pending.proposal_id, pending.pending
|
||||
);
|
||||
still_failing.push(pending);
|
||||
} else {
|
||||
self.shared_state
|
||||
.storage
|
||||
.clear_post_proposal_data(
|
||||
pending.proposal_id as u32,
|
||||
OffsetDateTime::now_utc(),
|
||||
resolution.is_rejected(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!(
|
||||
"experienced internal error when attempting to resolve pending proposal: {err}"
|
||||
);
|
||||
// make sure to update internal state to not lose any data
|
||||
self.pending_redemptions.push(pending);
|
||||
self.pending_redemptions.append(&mut still_failing);
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut still_failing = Vec::new();
|
||||
|
||||
// 2. attempt to verify the remaining tickets
|
||||
// 1. attempt to verify the remaining tickets
|
||||
while let Some(mut pending) = self.pending_tickets.pop() {
|
||||
// possible optimisation: if there's a lot of pending tickets, pre-emptively grab locks for api_clients
|
||||
match self
|
||||
@@ -595,362 +395,14 @@ impl CredentialHandler {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Attempt to send batch redemption request to the provided ecash verifier.
|
||||
async fn redeem_tickets(
|
||||
&self,
|
||||
proposal_id: u64,
|
||||
request: &BatchRedeemTicketsBody,
|
||||
client: &EcashApiClient,
|
||||
) -> Result<bool, EcashTicketError> {
|
||||
match client.api_client.batch_redeem_ecash_tickets(request).await {
|
||||
Ok(res) => {
|
||||
let accepted = if res.proposal_accepted {
|
||||
trace!("{client} has accepted proposal {proposal_id}");
|
||||
true
|
||||
} else {
|
||||
warn!("{client} has rejected proposal {proposal_id}");
|
||||
false
|
||||
};
|
||||
|
||||
Ok(accepted)
|
||||
}
|
||||
Err(err) => {
|
||||
error!(
|
||||
"failed to send proposal {proposal_id} for redemption vote to ecash signer '{client}': {err}. if we don't reach quorum, we'll retry later"
|
||||
);
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn try_execute_proposal(&self, proposal_id: u64) -> Result<(), EcashTicketError> {
|
||||
self.shared_state
|
||||
.start_tx()
|
||||
.await
|
||||
.execute_proposal(proposal_id, None)
|
||||
.await
|
||||
.map_err(
|
||||
|source| EcashTicketError::RedemptionProposalExecutionFailure {
|
||||
proposal_id,
|
||||
source,
|
||||
},
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_proposal_status(&self, proposal_id: u64) -> Result<Status, EcashTicketError> {
|
||||
Ok(self
|
||||
.shared_state
|
||||
.start_query()
|
||||
.await
|
||||
.query_proposal(proposal_id)
|
||||
.await
|
||||
.map_err(EcashTicketError::chain_query_failure)?
|
||||
.status)
|
||||
}
|
||||
|
||||
async fn try_finalize_proposal(
|
||||
&self,
|
||||
proposal_id: u64,
|
||||
) -> Result<ProposalResult, EcashTicketError> {
|
||||
match self.get_proposal_status(proposal_id).await? {
|
||||
Status::Pending => {
|
||||
// the voting hasn't even begun!
|
||||
error!("impossible case! the proposal {proposal_id} is still pending");
|
||||
Ok(ProposalResult::Pending)
|
||||
}
|
||||
Status::Open => {
|
||||
debug!("proposal {proposal_id} is still open and needs more votes");
|
||||
Ok(ProposalResult::Pending)
|
||||
}
|
||||
Status::Rejected => {
|
||||
warn!("proposal {proposal_id} has been rejected");
|
||||
Ok(ProposalResult::Rejected)
|
||||
}
|
||||
Status::Passed => {
|
||||
info!(
|
||||
"proposal {proposal_id} has already been passed - we just need to execute it"
|
||||
);
|
||||
self.try_execute_proposal(proposal_id).await?;
|
||||
info!("executed proposal {proposal_id}");
|
||||
Ok(ProposalResult::Executed)
|
||||
}
|
||||
Status::Executed => {
|
||||
info!("proposal {proposal_id} has already been executed - nothing to do!");
|
||||
Ok(ProposalResult::Executed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn try_resolve_pending_proposal(
|
||||
&self,
|
||||
pending: &mut PendingRedemptionVote,
|
||||
api_clients: Option<RwLockReadGuard<'_, Vec<EcashApiClient>>>,
|
||||
) -> Result<ProposalResult, EcashTicketError> {
|
||||
let proposal_id = pending.proposal_id;
|
||||
|
||||
info!(
|
||||
"attempting to resolve pending redemption proposal {proposal_id} to redeem {} tickets",
|
||||
pending.included_serial_numbers.len()
|
||||
);
|
||||
|
||||
// check if the proposal still needs more votes from the apis
|
||||
let result = self.try_finalize_proposal(proposal_id).await?;
|
||||
if !result.is_pending() {
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
let api_clients = match api_clients {
|
||||
Some(clients) => clients,
|
||||
None => self.shared_state.api_clients(pending.epoch_id).await?,
|
||||
};
|
||||
|
||||
let redemption_request = pending.to_request_body(self.shared_state.address.clone());
|
||||
|
||||
// TODO: optimisation: tell other apis they can purge our tickets even if they haven't voted
|
||||
|
||||
let total = api_clients.len();
|
||||
let api_failures = Mutex::new(Vec::new());
|
||||
let rejected = AtomicUsize::new(0);
|
||||
|
||||
for_each_api_concurrent(&api_clients, &pending.pending, |ecash_client| async {
|
||||
// errors are only returned on hard, storage, failures
|
||||
match self
|
||||
.redeem_tickets(pending.proposal_id, &redemption_request, ecash_client)
|
||||
.await
|
||||
{
|
||||
Err(err) => {
|
||||
error!("internal failure. could not proceed with ticket redemption: {err}");
|
||||
api_failures.lock().await.push(ecash_client.node_id);
|
||||
}
|
||||
Ok(false) => {
|
||||
rejected.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
let api_failures = api_failures.into_inner();
|
||||
let num_failures = api_failures.len();
|
||||
pending.pending = api_failures;
|
||||
|
||||
let rejected = rejected.into_inner();
|
||||
let rejected_ratio = rejected as f32 / total as f32;
|
||||
let rejected_perc = rejected_ratio * 100.;
|
||||
if rejected_ratio >= (1. - self.multisig_threshold) {
|
||||
error!(
|
||||
"{rejected_perc:.2}% of signers rejected proposal {proposal_id}. we won't be able to execute it"
|
||||
);
|
||||
// no need to query the chain as with so many rejections it's impossible it has passed.
|
||||
return Ok(ProposalResult::Rejected);
|
||||
}
|
||||
|
||||
let accepted_ratio = (total - rejected - num_failures) as f32 / total as f32;
|
||||
let accepted_perc = accepted_ratio * 100.;
|
||||
match accepted_ratio {
|
||||
n if n < self.multisig_threshold => {
|
||||
error!(
|
||||
"less than 2/3 of signers ({accepted_perc:.2}%) accepted proposal {proposal_id}. we're not yet be able to execute it to get funds out"
|
||||
);
|
||||
return Ok(ProposalResult::Pending);
|
||||
}
|
||||
n if n < self.config.minimum_api_quorum => {
|
||||
warn!(
|
||||
"the system seems to be a bit unstable: less than 80%, but more than 67% of signers ({accepted_perc:.2}%) accepted proposal {proposal_id}"
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
trace!("{accepted_perc:.2}% of signers accepted proposal {proposal_id}");
|
||||
}
|
||||
}
|
||||
|
||||
// attempt to execute the proposal if it reached the required threshold
|
||||
self.try_finalize_proposal(proposal_id).await
|
||||
}
|
||||
|
||||
async fn maybe_redeem_tickets(&mut self) -> Result<(), EcashTicketError> {
|
||||
if !self.pending_tickets.is_empty() {
|
||||
return Err(EcashTicketError::PendingTickets);
|
||||
}
|
||||
|
||||
let latest_stored = self.shared_state.storage.latest_proposal().await?;
|
||||
|
||||
// check if we have already created the proposal but crashed before persisting it in the db
|
||||
//
|
||||
// if we have some persisted proposals in storage, try to see if there's anything more recent on chain
|
||||
// (i.e. the missing proposal)
|
||||
// if not (i.e. this would have been our first) check the latest page of proposals.
|
||||
// while this is not ideal, realistically speaking we probably crashed few minutes ago
|
||||
// and worst case scenario we'll just recreate the proposal instead
|
||||
//
|
||||
// LIMITATION: if MULTIPLE proposals got created in between, well. though luck.
|
||||
let latest_on_chain = if let Some(latest_stored) = &latest_stored {
|
||||
// those are sorted in ASCENDING way
|
||||
self.shared_state
|
||||
.proposals_since(latest_stored.proposal_id as u64)
|
||||
.await?
|
||||
.pop()
|
||||
} else {
|
||||
// but those are DESCENDING
|
||||
self.shared_state
|
||||
.last_proposal_page()
|
||||
.await?
|
||||
.first()
|
||||
.cloned()
|
||||
};
|
||||
|
||||
let now = OffsetDateTime::now_utc();
|
||||
|
||||
let prior_proposal = match (&latest_stored, latest_on_chain) {
|
||||
(None, None) => {
|
||||
// we haven't created any proposals before
|
||||
trace!("this could be our first redemption proposal");
|
||||
None
|
||||
}
|
||||
(Some(stored), None) => {
|
||||
if stored.created_at + MIN_BATCH_REDEMPTION_DELAY > now {
|
||||
trace!("too soon to create new redemption proposal");
|
||||
return Ok(());
|
||||
}
|
||||
None
|
||||
}
|
||||
(_, Some(on_chain)) => {
|
||||
warn!(
|
||||
"we seem to have crashed after creating proposal, but before persisting it onto disk!"
|
||||
);
|
||||
|
||||
Some(on_chain)
|
||||
}
|
||||
};
|
||||
|
||||
// technically we could have been just caching all of those serial numbers as we verify tickets,
|
||||
// but given how infrequently we call this, there's no point in wasting this memory
|
||||
let verified_tickets = self
|
||||
.shared_state
|
||||
.storage
|
||||
.get_all_verified_tickets_with_sn()
|
||||
.await?;
|
||||
|
||||
// TODO: somehow simplify that nasty nested if
|
||||
if verified_tickets.len() < self.config.minimum_redemption_tickets {
|
||||
// bypass the number of tickets check if we're about to lose our rewards due to expiration
|
||||
if let Some(latest_stored) = latest_stored {
|
||||
if latest_stored.created_at + self.config.maximum_time_between_redemption < now {
|
||||
{}
|
||||
} else {
|
||||
debug!(
|
||||
"we only have {} verified tickets. there's no point in creating a redemption request yet. (we need at least {} (configurable))",
|
||||
verified_tickets.len(),
|
||||
self.config.minimum_redemption_tickets
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
} else {
|
||||
// first proposal
|
||||
debug!(
|
||||
"we only have {} verified tickets. there's no point in creating a redemption request yet. (we need at least {} (configurable))",
|
||||
verified_tickets.len(),
|
||||
self.config.minimum_redemption_tickets
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// this should have been ensured when querying
|
||||
assert!(verified_tickets.len() <= u16::MAX as usize);
|
||||
|
||||
let digest =
|
||||
BatchRedeemTicketsBody::make_digest(verified_tickets.iter().map(|t| &t.serial_number));
|
||||
let encoded_digest = bs58::encode(&digest).into_string();
|
||||
|
||||
let prior_proposal_id = if let Some(prior_proposal) = prior_proposal {
|
||||
if prior_proposal.description == encoded_digest {
|
||||
info!("we have already created proposal for those tickets");
|
||||
Some(prior_proposal.id)
|
||||
} else {
|
||||
warn!(
|
||||
"our missed proposal seem to have been for different tickets - abandoning it"
|
||||
);
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// if the proposal has already existed on chain, do use it. otherwise create a new one
|
||||
let proposal_id = if let Some(prior) = prior_proposal_id {
|
||||
prior
|
||||
} else {
|
||||
self.create_redemption_proposal(&digest, verified_tickets.len() as u16)
|
||||
.await?
|
||||
};
|
||||
|
||||
if proposal_id > u32::MAX as u64 {
|
||||
// realistically will we ever reach it? no.
|
||||
panic!(
|
||||
"we have created more than {} proposals. we can't handle that.",
|
||||
u32::MAX
|
||||
)
|
||||
}
|
||||
|
||||
self.shared_state
|
||||
.storage
|
||||
.insert_redemption_proposal(
|
||||
&verified_tickets,
|
||||
proposal_id as u32,
|
||||
OffsetDateTime::now_utc(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let current_epoch = self.shared_state.current_epoch_id().await?;
|
||||
let api_clients = self.shared_state.api_clients(current_epoch).await?;
|
||||
let ids = api_clients.iter().map(|c| c.node_id).collect();
|
||||
let mut pending = PendingRedemptionVote::new(
|
||||
proposal_id,
|
||||
digest,
|
||||
verified_tickets
|
||||
.into_iter()
|
||||
.map(|t| t.serial_number)
|
||||
.collect(),
|
||||
current_epoch,
|
||||
ids,
|
||||
);
|
||||
|
||||
let resolution = self
|
||||
.try_resolve_pending_proposal(&mut pending, Some(api_clients))
|
||||
.await?;
|
||||
if resolution.is_pending() {
|
||||
warn!(
|
||||
"failed to reach quorum for proposal {proposal_id}. apis: {:?} haven't responded. we'll retry later",
|
||||
pending.pending
|
||||
);
|
||||
self.pending_redemptions.push(pending);
|
||||
} else {
|
||||
self.shared_state
|
||||
.storage
|
||||
.clear_post_proposal_data(
|
||||
proposal_id as u32,
|
||||
OffsetDateTime::now_utc(),
|
||||
resolution.is_rejected(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn periodic_operations(&mut self) -> Result<(), EcashTicketError> {
|
||||
trace!(
|
||||
"attempting to resolve all pending operations -> tickets that are waiting for verification and possibly redemption"
|
||||
"attempting to resolve all pending operations -> tickets that are waiting for verification"
|
||||
);
|
||||
|
||||
// 1. retry all operations that have failed in the past: verification requests and pending redemption
|
||||
// retry the pending verification requests that have failed before
|
||||
self.resolve_pending().await?;
|
||||
|
||||
// 2. if applicable, attempt to redeem all newly verified tickets
|
||||
self.maybe_redeem_tickets().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::future::Future;
|
||||
use std::ops::Deref;
|
||||
use tokio::sync::RwLockReadGuard;
|
||||
|
||||
pub(crate) fn apis_stream<'a>(
|
||||
pub fn apis_stream<'a>(
|
||||
// if needed we could make this argument more generic to accept either locks or iterators, etc.
|
||||
all_clients: &'a RwLockReadGuard<'a, Vec<EcashApiClient>>,
|
||||
filter_by_id: &'a [u64],
|
||||
@@ -22,7 +22,7 @@ pub(crate) fn apis_stream<'a>(
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) async fn for_each_api_concurrent<'a, F, Fut>(
|
||||
pub async fn for_each_api_concurrent<'a, F, Fut>(
|
||||
all_clients: &'a RwLockReadGuard<'a, Vec<EcashApiClient>>,
|
||||
filter_by_id: &'a [u64],
|
||||
f: F,
|
||||
|
||||
@@ -20,7 +20,7 @@ use tracing::error;
|
||||
|
||||
pub mod credential_sender;
|
||||
pub mod error;
|
||||
mod helpers;
|
||||
pub mod helpers;
|
||||
mod state;
|
||||
pub mod traits;
|
||||
|
||||
|
||||
@@ -3,17 +3,12 @@
|
||||
|
||||
use crate::Error;
|
||||
use crate::ecash::error::EcashTicketError;
|
||||
use cosmwasm_std::{CosmosMsg, WasmMsg, from_json};
|
||||
use nym_credentials_interface::VerificationKeyAuth;
|
||||
use nym_ecash_contract_common::msg::ExecuteMsg;
|
||||
use nym_gateway_storage::traits::BandwidthGatewayStorage;
|
||||
use nym_validator_client::coconut::all_ecash_api_clients;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use nym_validator_client::nyxd::contract_traits::{
|
||||
DkgQueryClient, MultisigQueryClient, NymContractsProvider,
|
||||
};
|
||||
use nym_validator_client::nyxd::cw3::ProposalResponse;
|
||||
use nym_validator_client::nyxd::contract_traits::{DkgQueryClient, NymContractsProvider};
|
||||
use nym_validator_client::{DirectSigningHttpRpcNyxdClient, EcashApiClient};
|
||||
use std::collections::BTreeMap;
|
||||
use std::ops::Deref;
|
||||
@@ -77,53 +72,6 @@ impl SharedState {
|
||||
Ok(this)
|
||||
}
|
||||
|
||||
fn created_redemption_proposal(&self, proposal: &ProposalResponse) -> bool {
|
||||
let Some(msg) = proposal.msgs.first() else {
|
||||
return false;
|
||||
};
|
||||
let CosmosMsg::Wasm(WasmMsg::Execute { msg, .. }) = msg else {
|
||||
return false;
|
||||
};
|
||||
let Ok(ExecuteMsg::RedeemTickets { gw, .. }) = from_json(msg) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
gw == self.address.as_ref()
|
||||
}
|
||||
|
||||
/// retrieve all redemption proposals made by this gateway since, but excluding, the provided id
|
||||
pub(crate) async fn proposals_since(
|
||||
&self,
|
||||
proposal_id: u64,
|
||||
) -> Result<Vec<ProposalResponse>, EcashTicketError> {
|
||||
Ok(self
|
||||
.start_query()
|
||||
.await
|
||||
.list_proposals(Some(proposal_id), None)
|
||||
.await
|
||||
.map_err(EcashTicketError::chain_query_failure)?
|
||||
.proposals
|
||||
.into_iter()
|
||||
.filter(|p| self.created_redemption_proposal(p))
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// retrieve all redemption proposals made by this gateway that are available on the last page of the query
|
||||
pub(crate) async fn last_proposal_page(
|
||||
&self,
|
||||
) -> Result<Vec<ProposalResponse>, EcashTicketError> {
|
||||
Ok(self
|
||||
.start_query()
|
||||
.await
|
||||
.reverse_proposals(None, None)
|
||||
.await
|
||||
.map_err(EcashTicketError::chain_query_failure)?
|
||||
.proposals
|
||||
.into_iter()
|
||||
.filter(|p| self.created_redemption_proposal(p))
|
||||
.collect())
|
||||
}
|
||||
|
||||
async fn set_epoch_data(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
@@ -240,24 +188,6 @@ impl SharedState {
|
||||
data.get(&epoch_id).map(|d| &d.master_key).unwrap()
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) async fn start_tx(&self) -> RwLockWriteGuard<'_, DirectSigningHttpRpcNyxdClient> {
|
||||
self.nyxd_client.write().await
|
||||
}
|
||||
|
||||
pub(crate) async fn start_query(&self) -> RwLockReadGuard<'_, DirectSigningHttpRpcNyxdClient> {
|
||||
self.nyxd_client.read().await
|
||||
}
|
||||
|
||||
pub(crate) async fn current_epoch_id(&self) -> Result<EpochId, EcashTicketError> {
|
||||
Ok(self
|
||||
.start_query()
|
||||
.await
|
||||
.get_current_epoch()
|
||||
.await
|
||||
.map_err(EcashTicketError::chain_query_failure)?
|
||||
.epoch_id)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct EpochState {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
use rand::Rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Debug;
|
||||
use strum::IntoEnumIterator as _;
|
||||
use thiserror::Error;
|
||||
use time::{Date, OffsetDateTime};
|
||||
|
||||
@@ -315,6 +316,10 @@ impl TicketType {
|
||||
_ => Err(UnknownTicketType),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exposed_iter() -> impl Iterator<Item = TicketType> {
|
||||
TicketType::iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TicketType> for TicketTypeRepr {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![allow(deprecated)] // silences clippy warning: deprecated associated function `generic_array::GenericArray::<T, N>::from_exact_iter`: please upgrade to generic-array 1.x - TODO
|
||||
|
||||
#[cfg(feature = "asymmetric")]
|
||||
pub mod asymmetric;
|
||||
pub mod bech32_address_validation;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright 2020-2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#![allow(deprecated)] // silences clippy warning: deprecated associated function `nym_crypto::generic_array::GenericArray::<T, N>::clone_from_slice`: please upgrade to generic-array 1.x - TODO
|
||||
|
||||
pub use nym_crypto::generic_array;
|
||||
use nym_crypto::OutputSizeUser;
|
||||
|
||||
@@ -34,6 +34,7 @@ tracing = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
inventory = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt", "macros", "time"] }
|
||||
rustls = { workspace=true }
|
||||
# used for decoding text responses (they were already implicitly included)
|
||||
bytes = { workspace = true }
|
||||
encoding_rs = { workspace = true }
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::collections::HashMap;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
|
||||
pub const NYM_API_DOMAIN: &str = "validator.nymtech.net";
|
||||
pub const NYM_API_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(212, 71, 233, 232))];
|
||||
pub const NYM_API_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(92, 39, 63, 14))];
|
||||
|
||||
pub const NYM_VPN_API_DOMAIN: &str = "nymvpn.com";
|
||||
pub const NYM_VPN_API_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(76, 76, 21, 21))];
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![allow(deprecated)]
|
||||
// silences clippy warning: use of deprecated tuple variant `HttpClientError::GenericRequestFailure`: use another more strongly typed variant - this variant is only left for compatibility reasons - TODO
|
||||
|
||||
//! Nym HTTP API Client
|
||||
//!
|
||||
//! Centralizes and implements the core API client functionality. This crate provides custom,
|
||||
@@ -158,6 +161,8 @@ use reqwest::{RequestBuilder, Response};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Display;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use std::io::ErrorKind;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::time::Duration;
|
||||
use thiserror::Error;
|
||||
@@ -1164,14 +1169,10 @@ impl ApiClientCore for Client {
|
||||
match response {
|
||||
Ok(resp) => return Ok(resp),
|
||||
Err(err) => {
|
||||
// only if there was a network issue should we consider updating the host info
|
||||
//
|
||||
// note: for now this includes DNS resolution failure, I am not sure how I would go about
|
||||
// segregating that based on the interface provided by request for errors.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let is_network_err = err.is_timeout();
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let is_network_err = err.is_timeout() || err.is_connect();
|
||||
let is_network_err = might_be_network_interference(&err);
|
||||
|
||||
if is_network_err {
|
||||
// if we have multiple urls, update to the next
|
||||
@@ -1219,6 +1220,68 @@ impl ApiClientCore for Client {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
const MAX_ERR_SOURCE_ITERATIONS: usize = 4;
|
||||
|
||||
/// This functions attempts to check the error returned by reqwest to see if
|
||||
/// rotating host informtion (for clients with mutliple hosts defined) could be
|
||||
/// helpful. This looks for situations where the error could plausibly be caused
|
||||
/// by a network adversary, or where rotating to an equival hostname might help.
|
||||
///
|
||||
/// For example --> NetworkUnreachable will not be helped by rotating domains,
|
||||
/// but ConnectionReset might be caused by a network adversary blocking by SNI
|
||||
/// which could possibly benefit from rotating domains.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) fn might_be_network_interference(err: &reqwest::Error) -> bool {
|
||||
if err.is_timeout() {
|
||||
return true;
|
||||
}
|
||||
|
||||
if !(err.is_connect() || err.is_request()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The io::Error source is several layers deep, for clarity this is done as a loop
|
||||
// * reqwest::Error -> hyper_util::Error
|
||||
// * hyper_util::Error -> hyper_util::ClientError
|
||||
// * hyper_util::ClientError -> io::Error
|
||||
let mut inner = err.source();
|
||||
for _ in 0..MAX_ERR_SOURCE_ITERATIONS {
|
||||
if let Some(e) = inner {
|
||||
if let Some(io_err) = e.downcast_ref::<std::io::Error>() {
|
||||
// try downcast to io::Error from <dyn std::error:Error>
|
||||
match io_err.kind() {
|
||||
// device not connected to the internet
|
||||
ErrorKind::NetworkUnreachable | ErrorKind::NetworkDown => return false,
|
||||
// connection errors can indicate connection interference
|
||||
ErrorKind::ConnectionReset
|
||||
| ErrorKind::HostUnreachable
|
||||
| ErrorKind::ConnectionRefused => return true,
|
||||
// TLS errors get wrapped in custom io::Errors
|
||||
ErrorKind::Other | ErrorKind::InvalidData => {
|
||||
// io::Error get_ref works while source doesn't here -_-
|
||||
// if you don't like it take it up with the rust devs https://users.rust-lang.org/t/question-about-implementation-of-std-source/121117
|
||||
inner = io_err.get_ref().map(|e| e as &dyn std::error::Error);
|
||||
}
|
||||
_ => return false,
|
||||
}
|
||||
} else if let Some(_tls_err) = e.downcast_ref::<rustls::Error>() {
|
||||
// try downcast to TLS error
|
||||
return true;
|
||||
} else if let Some(resolve_err) = e.downcast_ref::<hickory_resolver::ResolveError>() {
|
||||
// try downcast to DNS error
|
||||
return resolve_err.is_nx_domain();
|
||||
} else {
|
||||
inner = e.source();
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Common usage functionality for the http client.
|
||||
///
|
||||
/// These functions allow for cleaner downstream usage free of type parameters and unneeded imports.
|
||||
|
||||
@@ -10,7 +10,11 @@ license.workspace = true
|
||||
description = "Codec, signing functionality, and different version definitions for IP packet request and responses"
|
||||
|
||||
|
||||
[features]
|
||||
test-utils = ["pnet_packet"]
|
||||
|
||||
[dependencies]
|
||||
pnet_packet = { workspace = true, optional = true }
|
||||
bincode = { workspace = true }
|
||||
bytes = { workspace = true }
|
||||
nym-bin-common = { workspace = true }
|
||||
@@ -18,8 +22,10 @@ nym-crypto = { workspace = true }
|
||||
nym-service-provider-requests-common = { workspace = true }
|
||||
nym-sphinx = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
semver = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
thiserror = { workspace = true }
|
||||
time = { workspace = true }
|
||||
tokio = { workspace = true, features = ["time"] }
|
||||
tokio-util = { workspace = true, features = ["codec"] }
|
||||
tracing = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Extracted from sdk/rust/nym-sdk/examples/ipr_tunnel.rs
|
||||
|
||||
//! ICMP/ICMPv6 packet construction and reply detection helpers for testing
|
||||
//! IPR connectivity. Gated behind the `test-utils` feature.
|
||||
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
|
||||
use pnet_packet::Packet;
|
||||
use pnet_packet::icmp::echo_reply::EchoReplyPacket;
|
||||
use pnet_packet::icmp::echo_request::MutableEchoRequestPacket;
|
||||
use pnet_packet::icmp::{IcmpPacket, IcmpTypes};
|
||||
use pnet_packet::icmpv6::Icmpv6Types;
|
||||
use pnet_packet::ipv4::{Ipv4Flags, MutableIpv4Packet};
|
||||
use pnet_packet::ipv6::MutableIpv6Packet;
|
||||
|
||||
/// Build a complete IPv4 ICMP echo request packet.
|
||||
pub fn build_icmp_ping(src: Ipv4Addr, dst: Ipv4Addr, seq: u16) -> Option<Vec<u8>> {
|
||||
let mut echo = MutableEchoRequestPacket::owned(vec![0u8; 64])?;
|
||||
echo.set_icmp_type(IcmpTypes::EchoRequest);
|
||||
echo.set_icmp_code(pnet_packet::icmp::IcmpCode::new(0));
|
||||
echo.set_sequence_number(seq);
|
||||
let cksum = pnet_packet::icmp::checksum(&IcmpPacket::new(echo.packet())?);
|
||||
echo.set_checksum(cksum);
|
||||
|
||||
let total_len = 20 + echo.packet().len();
|
||||
let mut ip = MutableIpv4Packet::owned(vec![0u8; total_len])?;
|
||||
ip.set_version(4);
|
||||
ip.set_header_length(5);
|
||||
ip.set_total_length(total_len as u16);
|
||||
ip.set_ttl(64);
|
||||
ip.set_next_level_protocol(pnet_packet::ip::IpNextHeaderProtocols::Icmp);
|
||||
ip.set_source(src);
|
||||
ip.set_destination(dst);
|
||||
ip.set_flags(Ipv4Flags::DontFragment);
|
||||
ip.set_payload(echo.packet());
|
||||
|
||||
let mut buf = ip.consume_to_immutable().packet().to_vec();
|
||||
let cksum = ipv4_checksum(&buf);
|
||||
buf[10] = (cksum >> 8) as u8;
|
||||
buf[11] = cksum as u8;
|
||||
Some(buf)
|
||||
}
|
||||
|
||||
/// Build a complete IPv6 ICMPv6 echo request packet.
|
||||
pub fn build_icmpv6_ping(src: Ipv6Addr, dst: Ipv6Addr, seq: u16) -> Option<Vec<u8>> {
|
||||
let mut echo =
|
||||
pnet_packet::icmpv6::echo_request::MutableEchoRequestPacket::owned(vec![0u8; 64])?;
|
||||
echo.set_icmpv6_type(Icmpv6Types::EchoRequest);
|
||||
echo.set_icmpv6_code(pnet_packet::icmpv6::Icmpv6Code::new(0));
|
||||
echo.set_sequence_number(seq);
|
||||
let cksum = pnet_packet::icmpv6::checksum(
|
||||
&pnet_packet::icmpv6::Icmpv6Packet::new(echo.packet())?,
|
||||
&src,
|
||||
&dst,
|
||||
);
|
||||
echo.set_checksum(cksum);
|
||||
|
||||
let payload_len = echo.packet().len();
|
||||
let mut ip = MutableIpv6Packet::owned(vec![0u8; 40 + payload_len])?;
|
||||
ip.set_version(6);
|
||||
ip.set_payload_length(payload_len as u16);
|
||||
ip.set_next_header(pnet_packet::ip::IpNextHeaderProtocols::Icmpv6);
|
||||
ip.set_hop_limit(64);
|
||||
ip.set_source(src);
|
||||
ip.set_destination(dst);
|
||||
ip.set_payload(echo.packet());
|
||||
|
||||
Some(ip.consume_to_immutable().packet().to_vec())
|
||||
}
|
||||
|
||||
/// Check if a raw packet is an IPv4 ICMP echo reply destined to `expected_dst`.
|
||||
pub fn is_echo_reply_v4(data: &[u8], expected_dst: Ipv4Addr) -> bool {
|
||||
let Some(ip) = pnet_packet::ipv4::Ipv4Packet::new(data) else {
|
||||
return false;
|
||||
};
|
||||
if ip.get_destination() != expected_dst {
|
||||
return false;
|
||||
}
|
||||
if ip.get_next_level_protocol() != pnet_packet::ip::IpNextHeaderProtocols::Icmp {
|
||||
return false;
|
||||
}
|
||||
let Some(reply) = EchoReplyPacket::new(ip.payload()) else {
|
||||
return false;
|
||||
};
|
||||
reply.get_icmp_type() == IcmpTypes::EchoReply
|
||||
}
|
||||
|
||||
/// Check if a raw packet is an IPv6 ICMPv6 echo reply destined to `expected_dst`.
|
||||
pub fn is_echo_reply_v6(data: &[u8], expected_dst: Ipv6Addr) -> bool {
|
||||
let Some(ip) = pnet_packet::ipv6::Ipv6Packet::new(data) else {
|
||||
return false;
|
||||
};
|
||||
if ip.get_destination() != expected_dst {
|
||||
return false;
|
||||
}
|
||||
if ip.get_next_header() != pnet_packet::ip::IpNextHeaderProtocols::Icmpv6 {
|
||||
return false;
|
||||
}
|
||||
let Some(reply) = pnet_packet::icmpv6::echo_reply::EchoReplyPacket::new(ip.payload()) else {
|
||||
return false;
|
||||
};
|
||||
reply.get_icmpv6_type() == Icmpv6Types::EchoReply
|
||||
}
|
||||
|
||||
fn ipv4_checksum(header: &[u8]) -> u16 {
|
||||
let mut sum = 0u32;
|
||||
for i in (0..20).step_by(2) {
|
||||
sum += ((header[i] as u32) << 8) | header[i + 1] as u32;
|
||||
}
|
||||
while (sum >> 16) > 0 {
|
||||
sum = (sum & 0xFFFF) + (sum >> 16);
|
||||
}
|
||||
!sum as u16
|
||||
}
|
||||
@@ -3,10 +3,14 @@ use std::fmt::{Display, Formatter};
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
|
||||
pub mod codec;
|
||||
#[cfg(feature = "test-utils")]
|
||||
pub mod icmp_utils;
|
||||
pub mod response_helpers;
|
||||
pub mod sign;
|
||||
pub mod v6;
|
||||
pub mod v7;
|
||||
pub mod v8;
|
||||
pub mod v9;
|
||||
|
||||
// version 3: initial version
|
||||
// version 4: IPv6 support
|
||||
@@ -14,6 +18,8 @@ pub mod v8;
|
||||
// version 6: Increase the available IPs
|
||||
// version 7: Add signature support (for the future)
|
||||
// version 8: Anonymous sends
|
||||
// version 9: LP-framed transport (SphinxStream)
|
||||
// response_helpers: shared IPR response parsing (nym-ip-packet-client + nym-sdk)
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct IpPair {
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use tokio_util::codec::Decoder;
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
use crate::{
|
||||
IpPair,
|
||||
codec::MultiIpPacketCodec,
|
||||
v8::response::{
|
||||
ConnectResponseReply, ControlResponse, InfoLevel, IpPacketResponse, IpPacketResponseData,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum IprResponseError {
|
||||
#[error("no version byte in message")]
|
||||
NoVersionByte,
|
||||
|
||||
#[error("version mismatch: received v{received}, expected v{expected}")]
|
||||
VersionMismatch { expected: u8, received: u8 },
|
||||
|
||||
#[error("expected control response, got {0:?}")]
|
||||
UnexpectedResponse(IpPacketResponseData),
|
||||
|
||||
#[error("connect denied: {0:?}")]
|
||||
ConnectDenied(crate::v8::response::ConnectFailureReason),
|
||||
}
|
||||
|
||||
pub enum MixnetMessageOutcome {
|
||||
IpPackets(Vec<Bytes>),
|
||||
Disconnect,
|
||||
}
|
||||
|
||||
// Extracted from:
|
||||
// nym-ip-packet-client/src/helpers.rs — check_ipr_message_version()
|
||||
// sdk/rust/nym-sdk/src/ip_packet_client/listener.rs — check_ipr_message_version()
|
||||
/// Check that the first byte of an IPR message matches the expected protocol version.
|
||||
pub fn check_ipr_message_version(data: &[u8], expected: u8) -> Result<(), IprResponseError> {
|
||||
let version = data.first().ok_or(IprResponseError::NoVersionByte)?;
|
||||
if *version != expected {
|
||||
return Err(IprResponseError::VersionMismatch {
|
||||
expected,
|
||||
received: *version,
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Extracted from:
|
||||
// nym-ip-packet-client/src/connect.rs — handle_connect_response() + handle_ip_packet_router_response()
|
||||
// sdk/rust/nym-sdk/src/ip_packet_client/discovery.rs — parse_connect_response()
|
||||
/// Parse an IPR connect response, returning allocated IPs on success.
|
||||
pub fn parse_connect_response(response: IpPacketResponse) -> Result<IpPair, IprResponseError> {
|
||||
let control_response = match response.data {
|
||||
IpPacketResponseData::Control(c) => c,
|
||||
other => return Err(IprResponseError::UnexpectedResponse(other)),
|
||||
};
|
||||
|
||||
match *control_response {
|
||||
ControlResponse::Connect(connect_resp) => match connect_resp.reply {
|
||||
ConnectResponseReply::Success(success) => Ok(success.ips),
|
||||
ConnectResponseReply::Failure(reason) => Err(IprResponseError::ConnectDenied(reason)),
|
||||
},
|
||||
_ => Err(IprResponseError::UnexpectedResponse(
|
||||
IpPacketResponseData::Control(control_response),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
// Extracted from:
|
||||
// nym-ip-packet-client/src/listener.rs — IprListener::handle_reconstructed_message()
|
||||
// sdk/rust/nym-sdk/src/ip_packet_client/listener.rs — handle_ipr_response()
|
||||
/// Parse raw IPR response bytes into an outcome.
|
||||
///
|
||||
/// Logs non-fatal conditions (unknown control messages, deserialization
|
||||
/// failures) and returns `None` for them.
|
||||
pub fn handle_ipr_response(data: &[u8]) -> Option<MixnetMessageOutcome> {
|
||||
match IpPacketResponse::from_bytes(data) {
|
||||
Ok(response) => match response.data {
|
||||
IpPacketResponseData::Data(data_response) => {
|
||||
let mut codec = MultiIpPacketCodec::new();
|
||||
let mut buf = BytesMut::from(data_response.ip_packet.as_ref());
|
||||
let mut packets = Vec::new();
|
||||
loop {
|
||||
match codec.decode(&mut buf) {
|
||||
Ok(Some(packet)) => packets.push(packet.into_bytes()),
|
||||
Ok(None) => break,
|
||||
Err(e) => {
|
||||
warn!("Failed to decode bundled IP packet: {e}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(MixnetMessageOutcome::IpPackets(packets))
|
||||
}
|
||||
IpPacketResponseData::Control(control_response) => match *control_response {
|
||||
ControlResponse::Connect(_) => {
|
||||
info!("Received connect response when already connected - ignoring");
|
||||
None
|
||||
}
|
||||
ControlResponse::Disconnect(_) | ControlResponse::UnrequestedDisconnect(_) => {
|
||||
info!("Received disconnect from IPR");
|
||||
Some(MixnetMessageOutcome::Disconnect)
|
||||
}
|
||||
ControlResponse::Pong(_) => {
|
||||
info!("Received pong response");
|
||||
None
|
||||
}
|
||||
ControlResponse::Health(_) => {
|
||||
info!("Received health response");
|
||||
None
|
||||
}
|
||||
ControlResponse::Info(info_resp) => {
|
||||
let msg = format!(
|
||||
"Received info response from the mixnet: {}",
|
||||
info_resp.reply
|
||||
);
|
||||
match info_resp.level {
|
||||
InfoLevel::Info => info!("{msg}"),
|
||||
InfoLevel::Warn => warn!("{msg}"),
|
||||
InfoLevel::Error => error!("{msg}"),
|
||||
}
|
||||
None
|
||||
}
|
||||
},
|
||||
},
|
||||
Err(err) => {
|
||||
warn!("Failed to deserialize IPR response: {err}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,11 +179,15 @@ impl IpPacketResponse {
|
||||
make_bincode_serializer().serialize(self)
|
||||
}
|
||||
|
||||
pub fn from_bytes(data: &[u8]) -> Result<Self, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().deserialize(data)
|
||||
}
|
||||
|
||||
pub fn from_reconstructed_message(
|
||||
message: &nym_sphinx::receiver::ReconstructedMessage,
|
||||
) -> Result<Self, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().deserialize(&message.message)
|
||||
Self::from_bytes(&message.message)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
pub const VERSION: u8 = 9;
|
||||
|
||||
/// Minimum nym-node release version that supports v9 (LP Stream framing).
|
||||
/// Nodes running older versions will not understand LP-wrapped packets.
|
||||
pub const MIN_RELEASE_VERSION: semver::Version = semver::Version::new(1, 30, 0);
|
||||
|
||||
// v9 uses the same wire format as v8. The version bump indicates
|
||||
// the message was sent with LP framing (SphinxStream).
|
||||
//
|
||||
// Types are re-exported for deserialization/matching. Use the wrapper
|
||||
// constructors below to create correctly-versioned packets — never
|
||||
// manually set `protocol.version` or `response.version`.
|
||||
pub use super::v8::{request, response};
|
||||
|
||||
/// Create a v9 connect request (version byte set to 9).
|
||||
pub fn new_connect_request(buffer_timeout: Option<u64>) -> (request::IpPacketRequest, u64) {
|
||||
let (mut req, id) = request::IpPacketRequest::new_connect_request(buffer_timeout);
|
||||
req.protocol.version = VERSION;
|
||||
(req, id)
|
||||
}
|
||||
|
||||
/// Create a v9 data request (version byte set to 9).
|
||||
pub fn new_data_request(data: bytes::Bytes) -> request::IpPacketRequest {
|
||||
let mut req = request::IpPacketRequest::new_data_request(data);
|
||||
req.protocol.version = VERSION;
|
||||
req
|
||||
}
|
||||
|
||||
/// Create a v9 IP packet response (version byte set to 9).
|
||||
pub fn new_ip_packet_response(ip_packet: bytes::Bytes) -> response::IpPacketResponse {
|
||||
let mut resp = response::IpPacketResponse::new_ip_packet(ip_packet);
|
||||
resp.version = VERSION;
|
||||
resp
|
||||
}
|
||||
@@ -22,6 +22,16 @@ pub struct ChainDetails {
|
||||
pub stake_denom: DenomDetailsOwned,
|
||||
}
|
||||
|
||||
impl ChainDetails {
|
||||
pub fn mainnet() -> Self {
|
||||
ChainDetails {
|
||||
bech32_account_prefix: mainnet::BECH32_PREFIX.into(),
|
||||
mix_denom: mainnet::MIX_DENOM.into(),
|
||||
stake_denom: mainnet::STAKE_DENOM.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize, JsonSchema)]
|
||||
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
||||
pub struct NymContracts {
|
||||
@@ -181,11 +191,7 @@ impl NymNetworkDetails {
|
||||
// Consider caching this process (lazy static)
|
||||
NymNetworkDetails {
|
||||
network_name: mainnet::NETWORK_NAME.into(),
|
||||
chain_details: ChainDetails {
|
||||
bech32_account_prefix: mainnet::BECH32_PREFIX.into(),
|
||||
mix_denom: mainnet::MIX_DENOM.into(),
|
||||
stake_denom: mainnet::STAKE_DENOM.into(),
|
||||
},
|
||||
chain_details: ChainDetails::mainnet(),
|
||||
endpoints: mainnet::validators(),
|
||||
contracts: NymContracts {
|
||||
mixnet_contract_address: parse_optional_str(mainnet::MIXNET_CONTRACT_ADDRESS),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user