Compare commits

...

30 Commits

Author SHA1 Message Date
Jon Häggblad 22d6eaf9b8 Some minor conversions 2025-03-18 22:04:30 +01:00
Jon Häggblad a6b0b6f0f6 EXISTS return i64 2025-03-18 21:51:33 +01:00
Jon Häggblad a4bad2e697 Upgrade to sqlx 0.8.3 2025-03-18 21:34:51 +01:00
mfahampshire a19ee8f2aa fix accidental localhost link (#5643) 2025-03-18 17:23:22 +01:00
benedetta davico abfc68108a Merge pull request #5497 from helicopter-1/spelling
Corrected typos
2025-03-18 16:53:37 +01:00
dependabot[bot] ed90e358fb build(deps): bump zeroize from 1.6.0 to 1.8.1 (#5630)
Bumps [zeroize](https://github.com/RustCrypto/utils) from 1.6.0 to 1.8.1.
- [Commits](https://github.com/RustCrypto/utils/compare/zeroize-v1.6.0...zeroize-v1.8.1)

---
updated-dependencies:
- dependency-name: zeroize
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-18 15:23:16 +01:00
benedetta davico c7d0e26946 Merge pull request #5640 from nymtech/release/2025.5-chokito
Merge chokito to develop
2025-03-18 14:50:45 +01:00
Jon Häggblad 8d65c25986 Remove explorer-api from the main workspace (#5635) 2025-03-18 14:09:24 +01:00
benedetta davico a143d5f4f6 Merge pull request #5557 from nymtech/feature/exit-policies
Wireguard exit policies (and tests)
2025-03-18 12:29:40 +01:00
dependabot[bot] c041d11673 build(deps): bump zip from 2.2.2 to 2.4.1 (#5639)
Bumps [zip](https://github.com/zip-rs/zip2) from 2.2.2 to 2.4.1.
- [Release notes](https://github.com/zip-rs/zip2/releases)
- [Changelog](https://github.com/zip-rs/zip2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zip-rs/zip2/compare/v2.2.2...v2.4.1)

---
updated-dependencies:
- dependency-name: zip
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-18 10:59:08 +01:00
benedettadavico 82e82943aa update changelog 2025-03-18 10:39:55 +01:00
dependabot[bot] 19ffe217f1 build(deps): bump http from 1.2.0 to 1.3.1 (#5626) 2025-03-17 18:47:40 +01:00
dependabot[bot] 079bfa52e7 build(deps): bump the patch-updates group with 8 updates (#5624)
Bumps the patch-updates group with 8 updates:

| Package | From | To |
| --- | --- | --- |
| [async-trait](https://github.com/dtolnay/async-trait) | `0.1.87` | `0.1.88` |
| [clap](https://github.com/clap-rs/clap) | `4.5.31` | `4.5.32` |
| [env_logger](https://github.com/rust-cli/env_logger) | `0.11.6` | `0.11.7` |
| [http-body-util](https://github.com/hyperium/http-body) | `0.1.2` | `0.1.3` |
| [quote](https://github.com/dtolnay/quote) | `1.0.39` | `1.0.40` |
| [tokio](https://github.com/tokio-rs/tokio) | `1.44.0` | `1.44.1` |
| [tokio-util](https://github.com/tokio-rs/tokio) | `0.7.13` | `0.7.14` |
| [indexed_db_futures](https://github.com/Alorel/rust-indexed-db) | `0.6.0` | `0.6.1` |


Updates `async-trait` from 0.1.87 to 0.1.88
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.87...0.1.88)

Updates `clap` from 4.5.31 to 4.5.32
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.5.31...clap_complete-v4.5.32)

Updates `env_logger` from 0.11.6 to 0.11.7
- [Release notes](https://github.com/rust-cli/env_logger/releases)
- [Changelog](https://github.com/rust-cli/env_logger/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-cli/env_logger/compare/v0.11.6...v0.11.7)

Updates `http-body-util` from 0.1.2 to 0.1.3
- [Release notes](https://github.com/hyperium/http-body/releases)
- [Commits](https://github.com/hyperium/http-body/compare/http-body-util-v0.1.2...http-body-util-v0.1.3)

Updates `quote` from 1.0.39 to 1.0.40
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.39...1.0.40)

Updates `tokio` from 1.44.0 to 1.44.1
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.44.0...tokio-1.44.1)

Updates `tokio-util` from 0.7.13 to 0.7.14
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-util-0.7.13...tokio-util-0.7.14)

Updates `indexed_db_futures` from 0.6.0 to 0.6.1
- [Release notes](https://github.com/Alorel/rust-indexed-db/releases)
- [Commits](https://github.com/Alorel/rust-indexed-db/compare/v0.6.0...v0.6.1)

---
updated-dependencies:
- dependency-name: async-trait
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: env_logger
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: http-body-util
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: quote
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: tokio-util
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: indexed_db_futures
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-17 16:30:29 +01:00
dependabot[bot] be9a2c26e7 build(deps): bump once_cell from 1.20.3 to 1.21.1 (#5629)
Bumps [once_cell](https://github.com/matklad/once_cell) from 1.20.3 to 1.21.1.
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.20.3...v1.21.1)

---
updated-dependencies:
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-17 16:29:02 +01:00
mfahampshire d6f3eb6411 Max/new explorer url (#5522)
* new api link for explorer v2

* remove footer add explorer to navbar

* include image

* @ fix menu icons

* + explorer link in footer

---------

Co-authored-by: RadekSabacky <radek@nymtech.net>
2025-03-17 14:15:10 +00:00
dependabot[bot] 144f3bed9c build(deps): bump celes from 2.5.0 to 2.6.0 (#5627)
Bumps [celes](https://github.com/mikelodder7/celes) from 2.5.0 to 2.6.0.
- [Commits](https://github.com/mikelodder7/celes/commits)

---
updated-dependencies:
- dependency-name: celes
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-17 13:46:33 +01:00
dependabot[bot] c1174e64d4 build(deps): bump humantime from 2.1.0 to 2.2.0 (#5625) 2025-03-17 12:59:56 +01:00
dependabot[bot] 312ecbe4dc build(deps): bump tempfile from 3.18.0 to 3.19.0 (#5631) 2025-03-17 12:53:24 +01:00
dependabot[bot] d2afa587e4 build(deps): bump uuid from 1.15.1 to 1.16.0 (#5628) 2025-03-17 12:52:17 +01:00
Tommy Verrall 224c4c1870 fix tests and ensure everything is working... 2025-03-17 11:07:54 +01:00
dynco-nym 3f8abdb74f Add /v3/nym-nodes (#5569)
* Add /v3/nym-nodes
- returns extended node info from local DB
- endpoint caching
- add bond_info & self_described to DB nym_nodes
- update mixnode & gateway bond status on data refresh
- add `active` column to DB nym_nodes
- use only active & bonded nodes in scraping/testrun tasks

* Improve log

* PR feedback
- remove active field from nym_nodes
- delete obsolete nym_nodes

* node-status-api: cargo sqlx prepare

* Remove guardrails in CI file

* Revert "node-status-api: cargo sqlx prepare"

This reverts commit 1fcd895f0d.

* Try to ignore sqlx files

* cargo sqlx prepare

* Repair harbor tag check

* Try without checkout action

* add awk

* Update log
2025-03-15 00:17:40 +01:00
Jędrzej Stuczyński 0f6ec8610e hotfix: correctly increment ws connection counter (#5620) 2025-03-14 15:47:17 +00:00
dynco-nym 3baac1292d Add workflow to check if tag exists (#5617)
* Add workflow

* Check harbor for tag

* Remove leftover comments

* Try out cargo metadata

* Revert "Try out cargo metadata"

This reverts commit b83fbad1ca.
2025-03-14 16:31:49 +01:00
benedetta davico c3b8c4b2f7 Merge pull request #5616 from nymtech/bd/remove-explorer-api-ci
Remove explorer-api from ci-build-binaries
2025-03-13 13:36:30 +01:00
benedettadavico 271b9e545c remove bump to explorer-api 2025-03-13 13:35:06 +01:00
benedetta davico 9641f01670 remove explorer-api from ci-build-binaries 2025-03-13 13:31:46 +01:00
benedettadavico a7bb3e8d91 bump versions for chokito 2025-03-13 13:19:37 +01:00
Tommy Verrall 681c054890 rename file 2025-03-04 18:08:26 +01:00
Tommy Verrall f623bbd57c wireguard exit policy rules 2025-03-04 18:06:01 +01:00
helicopter-1 d4d576f363 Fix typos in CHANGELOG.md 2025-02-20 21:28:47 +01:00
104 changed files with 2724 additions and 1204 deletions
+1
View File
@@ -1 +1,2 @@
nym-validator-rewarder/.sqlx/** diff=nodiff
nym-node-status-api/nym-node-status-api/.sqlx/** diff=nodiff
@@ -100,7 +100,6 @@ jobs:
cp target/release/nymvisor $OUTPUT_DIR
cp target/release/nym-node $OUTPUT_DIR
cp target/release/nym-cli $OUTPUT_DIR
cp target/release/explorer-api $OUTPUT_DIR
if [ ${{ github.event_name == 'workflow_dispatch' && inputs.enable_deb == true }} = true ]; then
cp target/debian/*.deb $OUTPUT_DIR
fi
@@ -0,0 +1,57 @@
name: ci-check-ns-api-version
on:
pull_request:
paths:
- "nym-node-status-api/**"
env:
WORKING_DIRECTORY: "nym-node-status-api/nym-node-status-api"
jobs:
check-if-tag-exists:
runs-on: arc-ubuntu-22.04-dind
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.1
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
- name: Check if git tag exists
run: |
TAG=${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}
if [[ -z "$TAG" ]]; then
echo "Tag is empty"
exit 1
fi
git ls-remote --tags origin | awk '{print $2}'
if git ls-remote --tags origin | awk '{print $2}' | grep -q "refs/tags/$TAG$" ; then
echo "Tag '$TAG' ALREADY EXISTS on the remote"
exit 1
else
echo "Tag '$TAG' does not exist on the remote"
fi
- name: Check if harbor tag exists
run: |
TAG=${{ steps.get_version.outputs.result }}
registry=https://harbor.nymte.ch
repo_name=nym/node-status-api
if [[ -z $TAG ]]; then
echo "Tag is empty"
exit 1
fi
curl -su ${{ secrets.HARBOR_ROBOT_USERNAME }}:${{ secrets.HARBOR_ROBOT_SECRET }} "$registry/v2/$repo_name/tags/list" | jq
exists=$(curl -su ${{ secrets.HARBOR_ROBOT_USERNAME }}:${{ secrets.HARBOR_ROBOT_SECRET }} "$registry/v2/$repo_name/tags/list" | jq --arg tag $TAG '.tags | contains([$tag])' )
if [[ $exists = "true" ]]; then
echo "Version '$TAG' defined in Cargo.toml ALREADY EXISTS as tag in harbor repo"
exit 1
elif [[ $exists = "false" ]]; then
echo "Version '$TAG' doesn't exist on the remote"
else
echo "Unknown output '$exists'"
exit 1
fi
+108 -10
View File
@@ -4,6 +4,104 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [2025.5-chokito] (2025-03-18)
- build(deps): bump braces from 3.0.2 to 3.0.3 in /sdk/typescript/packages/nodejs-client ([#5611])
- build(deps-dev): bump webpack-dev-middleware from 5.3.3 to 5.3.4 in /wasm/client/internal-dev ([#5610])
- Export lane queue lengths in sdk ([#5609])
- Chore/more payment watcher debug endpoints ([#5608])
- build(deps): bump @babel/helpers from 7.24.4 to 7.26.10 ([#5606])
- Chore/update bls12 381 fork ([#5605])
- chore: change auth v2 timestamp skew and allow values from the future ([#5604])
- Chore/payment watcher debug endpoints ([#5601])
- Allow resetting all SURB sender tags ([#5600])
- introduce internal tool for checking signer status ([#5598])
- build(deps-dev): bump webpack from 5.77.0 to 5.98.0 in /wasm/mix-fetch/internal-dev ([#5597])
- build(deps): bump body-parser and express in /wasm/mix-fetch/internal-dev ([#5596])
- build(deps): bump serve-static and express in /wasm/mix-fetch/internal-dev ([#5594])
- build(deps-dev): bump ws from 8.13.0 to 8.18.1 in /wasm/mix-fetch/internal-dev ([#5593])
- build(deps): bump cookie and express in /wasm/client/internal-dev ([#5592])
- build(deps): bump cookie and express in /wasm/mix-fetch/internal-dev ([#5591])
- build(deps): bump braces from 3.0.2 to 3.0.3 in /wasm/zknym-lib/internal-dev ([#5590])
- build(deps): bump webpack-dev-middleware from 5.3.3 to 5.3.4 in /wasm/zknym-lib/internal-dev ([#5589])
- build(deps): bump tempfile from 3.17.1 to 3.18.0 ([#5588])
- build(deps): bump tokio from 1.43.0 to 1.44.0 ([#5587])
- build(deps): bump the patch-updates group with 8 updates ([#5585])
- build(deps): bump ring from 0.17.9 to 0.17.13 ([#5583])
- delete double memo field in send modal ([#5578])
- Server Side internal DoT/DoH opt out ([#5577])
- Rust SDK SURB example: change hardcoded file to tempdir ([#5576])
- Add /v3/nym-nodes ([#5569])
- chore: start sending v2 sphinx packets ([#5554])
- build(deps): bump the patch-updates group across 1 directory with 14 updates ([#5549])
- build(deps): bump uuid from 1.13.2 to 1.15.1 ([#5542])
- build(deps): bump rs_merkle from 1.4.2 to 1.5.0 ([#5541])
- feature: v2 authentication request ([#5537])
- Set RUSTUP_PERMIT_COPY_RENAME ([#5533])
- feature: disallow routing mix packets to nodes not present in the topology ([#5526])
- Make "Memo" visible per default on send NYM ([#5524])
- feat: make sure any terminated task kills the watcher and write run info to db ([#5517])
- Another total_stake SQL fix ([#5516])
- Fix total_stake on SQL update ([#5514])
- build(deps): bump flate2 from 1.0.35 to 1.1.0 ([#5510])
- build(deps): bump itertools from 0.13.0 to 0.14.0 ([#5509])
- build(deps): bump the patch-updates group with 2 updates ([#5505])
- Treat gateways as Nym Nodes ([#5504])
- Update version in Cargo.toml ([#5503])
- feat: use ct_eq for checking bearer token ([#5501])
- Add extra args for the probe ([#5499])
- Fix stats bug & remove HM caching ([#5495])
- fix: Cargo.lock for contracts ([#5489])
- Display error messages if IPv4 or IPv6 address not found on nymtun0 ([#5465])
[#5611]: https://github.com/nymtech/nym/pull/5611
[#5610]: https://github.com/nymtech/nym/pull/5610
[#5609]: https://github.com/nymtech/nym/pull/5609
[#5608]: https://github.com/nymtech/nym/pull/5608
[#5606]: https://github.com/nymtech/nym/pull/5606
[#5605]: https://github.com/nymtech/nym/pull/5605
[#5604]: https://github.com/nymtech/nym/pull/5604
[#5601]: https://github.com/nymtech/nym/pull/5601
[#5600]: https://github.com/nymtech/nym/pull/5600
[#5598]: https://github.com/nymtech/nym/pull/5598
[#5597]: https://github.com/nymtech/nym/pull/5597
[#5596]: https://github.com/nymtech/nym/pull/5596
[#5594]: https://github.com/nymtech/nym/pull/5594
[#5593]: https://github.com/nymtech/nym/pull/5593
[#5592]: https://github.com/nymtech/nym/pull/5592
[#5591]: https://github.com/nymtech/nym/pull/5591
[#5590]: https://github.com/nymtech/nym/pull/5590
[#5589]: https://github.com/nymtech/nym/pull/5589
[#5588]: https://github.com/nymtech/nym/pull/5588
[#5587]: https://github.com/nymtech/nym/pull/5587
[#5585]: https://github.com/nymtech/nym/pull/5585
[#5583]: https://github.com/nymtech/nym/pull/5583
[#5578]: https://github.com/nymtech/nym/pull/5578
[#5577]: https://github.com/nymtech/nym/pull/5577
[#5576]: https://github.com/nymtech/nym/pull/5576
[#5569]: https://github.com/nymtech/nym/pull/5569
[#5554]: https://github.com/nymtech/nym/pull/5554
[#5549]: https://github.com/nymtech/nym/pull/5549
[#5542]: https://github.com/nymtech/nym/pull/5542
[#5541]: https://github.com/nymtech/nym/pull/5541
[#5537]: https://github.com/nymtech/nym/pull/5537
[#5533]: https://github.com/nymtech/nym/pull/5533
[#5526]: https://github.com/nymtech/nym/pull/5526
[#5524]: https://github.com/nymtech/nym/pull/5524
[#5517]: https://github.com/nymtech/nym/pull/5517
[#5516]: https://github.com/nymtech/nym/pull/5516
[#5514]: https://github.com/nymtech/nym/pull/5514
[#5510]: https://github.com/nymtech/nym/pull/5510
[#5509]: https://github.com/nymtech/nym/pull/5509
[#5505]: https://github.com/nymtech/nym/pull/5505
[#5504]: https://github.com/nymtech/nym/pull/5504
[#5503]: https://github.com/nymtech/nym/pull/5503
[#5501]: https://github.com/nymtech/nym/pull/5501
[#5499]: https://github.com/nymtech/nym/pull/5499
[#5495]: https://github.com/nymtech/nym/pull/5495
[#5489]: https://github.com/nymtech/nym/pull/5489
[#5465]: https://github.com/nymtech/nym/pull/5465
## [2025.4-dorina-patched] (2025-03-06)
- use legacy crypto for constructing SURB headers ([#5579])
@@ -160,7 +258,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- Downgrade harmless log message from info to debug ([#5403])
- Redirect from mixnode page to nodes page ([#5397])
- chore :update version of chain watcher and validator rewarder ([#5394])
- bugfix: correctly handle ingore epoch roles flag ([#5390])
- bugfix: correctly handle ignore epoch roles flag ([#5390])
- bugfix: terminate mixnet socket listener on shutdown ([#5389])
- feat: make client ignore dual mode nodes by default ([#5388])
- Handle ecash network errors differently ([#5378])
@@ -181,7 +279,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- Use expect in geodata test to give error message on failure ([#5314])
- feature: periodically remove stale gateway messages ([#5312])
- build(deps): bump the patch-updates group across 1 directory with 35 updates ([#5310])
- Add dependabot assignes for the root cargo ecosystem ([#5297])
- Add dependabot assigns for the root cargo ecosystem ([#5297])
- Move tun constants to network defaults ([#5286])
- Include IPINFO_API_TOKEN in nightly CI ([#5285])
- Nyx Chain Watcher ([#5274])
@@ -234,7 +332,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [2025.1-reeses] (2025-01-15)
- Feture/legacy alert ([#5346])
- Feature, Future/legacy alert ([#5346])
- chore: readjusted --mode behaviour to fix the regression ([#5331])
- chore: apply 1.84 linter suggestions ([#5330])
- bugfix: make sure refresh data key matches bond info ([#5329])
@@ -314,7 +412,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [2024.14-crunch-patched] (2024-12-17)
- Fixes an issue to allow previously registred clients to connect to latest nym-nodes
- Fixes an issue to allow previously registered clients to connect to latest nym-nodes
- Fixes compatibility issues between nym-nodes and older clients
## [2024.14-crunch] (2024-12-11)
@@ -322,7 +420,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- Merge/release/2024.14-crunch ([#5242])
- bugfix: added explicit openapi servers to account for route prefixes ([#5237])
- Further config score adjustments ([#5225])
- feature: remve any filtering on node semver ([#5224])
- feature: remove any filtering on node semver ([#5224])
- Backport #5218 ([#5220])
- Derive serialize for UserAgent (#5210) ([#5217])
- dont consider legacy nodes for rewarded set selection ([#5215])
@@ -501,7 +599,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- bugfix/feature: added NymApiClient method to get all skimmed nodes ([#5062])
- Merge1/release/2024.13 magura ([#5061])
- added hacky routes to return nymnodes alongside legacy nodes ([#5051])
- bugfix: mark migrated gateways as rewarded in the previous epoch in case theyre in the rewarded set ([#5049])
- bugfix: mark migrated gateways as rewarded in the previous epoch in case they're, their, there in the rewarded set ([#5049])
- bugfix: adjust runtime storage migration ([#5047])
- bugfix: supersede 'cb13be27f8f61d9ae74d924e85d2e6787895eb14' by using… ([#5046])
- bugfix: restore default http port for nym-api ([#5045])
@@ -562,7 +660,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- Fix broken build after merge ([#4937])
- bugfix: correctly paginate through 'search_tx' endpoint ([#4936])
- Add more conversions for responses of authenticator messages ([#4929])
- Directory Sevices v2.1 ([#4903])
- Directory Services, Devices v2.1 ([#4903])
- Migrate Legacy Node (Frontend) ([#4826])
- Fix critical issues SI84 and SI85 from Cure53 ([#4758])
@@ -946,7 +1044,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- Remove stale peers ([#4640])
- Add generic wg private network routing ([#4636])
- Feature/new node endpoints ([#4635])
- standarised ContractBuildInformation and added it to all contracts ([#4631])
- standardised ContractBuildInformation and added it to all contracts ([#4631])
- validate nym-node public ips on startup ([#4630])
- Bump defguard wg ([#4625])
- Fix cargo warnings ([#4624])
@@ -1567,7 +1665,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- clean-up nym-api startup arguments/flags to use clap 3 and its macro-derived arguments ([#2772])
- renamed all references to validator_api to nym_api
- renamed all references to nymd to nyxd ([#2696])
- all-binaries: standarised argument names (note: old names should still be accepted) ([#2762]
- all-binaries: standardised argument names (note: old names should still be accepted) ([#2762]
### Fixed
@@ -2072,7 +2170,7 @@ The release also include some additional work for distributed key generation in
- Explorer UI tests missing data-testid [\#903](https://github.com/nymtech/nym/pull/903) ([tommyv1987](https://github.com/tommyv1987))
- Fix up Nym-Wallet README.md [\#899](https://github.com/nymtech/nym/pull/899) ([tommyv1987](https://github.com/tommyv1987))
- Feature/batch delegator rewarding [\#898](https://github.com/nymtech/nym/pull/898) ([jstuczyn](https://github.com/jstuczyn))
- Bug mapp nodemap [\#897](https://github.com/nymtech/nym/pull/897) ([Aid19801](https://github.com/Aid19801))
- Bug map nodemap [\#897](https://github.com/nymtech/nym/pull/897) ([Aid19801](https://github.com/Aid19801))
- Bug fix/macos keyboard shortcuts [\#896](https://github.com/nymtech/nym/pull/896) ([fmtabbara](https://github.com/fmtabbara))
- Add a Mobile Nav to the Network Explorer [\#895](https://github.com/nymtech/nym/pull/895) ([Aid19801](https://github.com/Aid19801))
- Only use ts-rs in tests [\#894](https://github.com/nymtech/nym/pull/894) ([durch](https://github.com/durch))
Generated
+213 -677
View File
File diff suppressed because it is too large Load Diff
+14 -15
View File
@@ -98,9 +98,9 @@ members = [
"common/wireguard",
"common/wireguard-types",
"documentation/autodoc",
"explorer-api",
"explorer-api/explorer-api-requests",
"explorer-api/explorer-client",
# "explorer-api",
# "explorer-api/explorer-api-requests",
# "explorer-api/explorer-client",
"gateway",
"integrations/bity",
"nym-api",
@@ -153,7 +153,6 @@ members = [
default-members = [
"clients/native",
"clients/socks5",
"explorer-api",
"nym-api",
"nym-credential-proxy/nym-credential-proxy",
"nym-node",
@@ -194,7 +193,7 @@ ammonia = "4"
anyhow = "1.0.97"
arc-swap = "1.7.1"
argon2 = "0.5.0"
async-trait = "0.1.87"
async-trait = "0.1.88"
axum = "0.7.5"
axum-client-ip = "0.6.1"
axum-extra = "0.9.4"
@@ -211,13 +210,13 @@ bs58 = "0.5.1"
bytecodec = "0.4.15"
bytes = "1.10.1"
cargo_metadata = "0.18.1"
celes = "2.5.0"
celes = "2.6.0"
cfg-if = "1.0.0"
chacha20 = "0.9.0"
chacha20poly1305 = "0.10.1"
chrono = "0.4.40"
cipher = "0.4.3"
clap = "4.5.31"
clap = "4.5.32"
clap_complete = "4.5"
clap_complete_fig = "4.5"
colored = "2.2"
@@ -242,7 +241,7 @@ dotenvy = "0.15.6"
ecdsa = "0.16"
ed25519-dalek = "2.1"
encoding_rs = "0.8.35"
env_logger = "0.11.6"
env_logger = "0.11.7"
envy = "0.4"
etherparse = "0.13.0"
eyre = "0.6.9"
@@ -264,7 +263,7 @@ http = "1"
http-body-util = "0.1"
httpcodec = "0.2.3"
human-repr = "1.1.0"
humantime = "2.1.0"
humantime = "2.2.0"
humantime-serde = "1.1.1"
hyper = "1.6.0"
hyper-util = "0.1"
@@ -285,7 +284,7 @@ moka = { version = "0.12", features = ["future"] }
nix = "0.27.1"
notify = "5.1.0"
okapi = "0.7.0"
once_cell = "1.20.3"
once_cell = "1.21.1"
opentelemetry = "0.19.0"
opentelemetry-jaeger = "0.18.0"
parking_lot = "0.12.3"
@@ -323,7 +322,7 @@ serde_yaml = "0.9.25"
sha2 = "0.10.8"
si-scale = "0.2.3"
sphinx-packet = "=0.3.2"
sqlx = "0.7.4"
sqlx = "0.8.3"
strum = "0.26"
strum_macros = "0.26"
subtle-encoding = "0.5"
@@ -331,7 +330,7 @@ syn = "1"
sysinfo = "0.33.0"
tap = "1.0.1"
tar = "0.4.44"
tempfile = "3.18"
tempfile = "3.19"
thiserror = "2.0"
time = "0.3.39"
tokio = "1.44"
@@ -340,7 +339,7 @@ tokio-stream = "0.1.17"
tokio-test = "0.4.4"
tokio-tun = "0.11.5"
tokio-tungstenite = { version = "0.20.1" }
tokio-util = "0.7.13"
tokio-util = "0.7.14"
toml = "0.8.20"
tower = "0.5.2"
tower-http = "0.5.2"
@@ -362,7 +361,7 @@ vergen = { version = "=8.3.1", default-features = false }
walkdir = "2"
wasm-bindgen-test = "0.3.49"
x25519-dalek = "2.0.0"
zeroize = "1.6.0"
zeroize = "1.8.1"
prometheus = { version = "0.13.0" }
@@ -403,7 +402,7 @@ prost = { version = "0.13", default-features = false }
gloo-utils = "0.2.0"
gloo-net = "0.6.0"
indexed_db_futures = "0.6.0"
indexed_db_futures = "0.6.1"
js-sys = "0.3.76"
serde-wasm-bindgen = "0.6.5"
tsify = "0.4.5"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.50"
version = "1.1.51"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.50"
version = "1.1.51"
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"
@@ -87,7 +87,7 @@ impl StorageManager {
sqlx::query!("SELECT EXISTS (SELECT 1 FROM registered_gateway WHERE gateway_id_bs58 = ?) AS 'exists'", gateway_id)
.fetch_one(&self.connection_pool)
.await
.map(|result| result.exists == Some(1))
.map(|result| result.exists == 1)
}
pub(crate) async fn maybe_get_registered_gateway(
+1 -1
View File
@@ -182,7 +182,7 @@ impl PersistentStatsStorage {
pub async fn get_started_sessions_count(
&self,
start_date: Date,
) -> Result<i32, StatsStorageError> {
) -> Result<i64, StatsStorageError> {
Ok(self
.session_manager
.get_started_sessions_count(start_date)
+1 -1
View File
@@ -148,7 +148,7 @@ impl SessionManager {
.await
}
pub(crate) async fn get_started_sessions_count(&self, start_date: Date) -> Result<i32> {
pub(crate) async fn get_started_sessions_count(&self, start_date: Date) -> Result<i64> {
Ok(sqlx::query!(
"SELECT COUNT(*) as count FROM sessions_active WHERE date(start_time) = ?",
start_date
+1 -1
View File
@@ -17,7 +17,7 @@ sqlx = { workspace = true, features = [
"macros",
"migrate",
"time",
"chrono"
# "chrono"
] }
time = { workspace = true }
thiserror = { workspace = true }
+1 -1
View File
@@ -92,7 +92,7 @@ impl TicketStorageManager {
)
.fetch_one(&self.connection_pool)
.await
.map(|result| result.exists == Some(1))
.map(|result| result.exists == 1)
}
pub(crate) async fn remove_binary_ticket_data(
+1 -1
View File
@@ -111,7 +111,7 @@ impl StorageManager {
consensus_address: &str,
start_height: i64,
end_height: i64,
) -> Result<i32, sqlx::Error> {
) -> Result<i64, sqlx::Error> {
trace!("get_signed_between");
let start = Instant::now();
+2 -2
View File
@@ -170,7 +170,7 @@ impl ScraperStorage {
consensus_address: &str,
start_height: i64,
end_height: i64,
) -> Result<i32, ScraperError> {
) -> Result<i64, ScraperError> {
Ok(self
.manager
.get_signed_between(consensus_address, start_height, end_height)
@@ -182,7 +182,7 @@ impl ScraperStorage {
consensus_address: &str,
start_time: OffsetDateTime,
end_time: OffsetDateTime,
) -> Result<i32, ScraperError> {
) -> Result<i64, ScraperError> {
let Some(block_start) = self.get_first_block_height_after(start_time).await? else {
return Ok(0);
};
@@ -0,0 +1,23 @@
import Image from "next/image";
import Link from "next/link";
import explorerLogo from "../public/images/smiley.png";
export const Explorer = () => {
return (
<Link
href={"https://nym.com/explorer"}
target="_blank"
rel="noopener noreferrer"
>
<Image
src={explorerLogo}
style = {{
marginRight: "0.6rem"
}}
alt={"Network Explorer"}
width={24}
height={24}
/>
</Link>
);
};
+1 -1
View File
@@ -5,7 +5,7 @@ const links = [
["Matrix", "https://matrix.to/#/#dev:nymtech.chat"],
["GitHub", "https://nymtech.net/go/github/nym"],
["Nym Wallet", "https://nymtech.net/download/wallet"],
["Nym Explorer", "https://explorer.nymtech.net/"],
["Nym Explorer", "https://nym.com/explorer/"],
["Nym Blog", "https://nymtech.medium.com/"],
["Twitter", "https://nymtech.net/go/x"],
["Telegram", "https://nymtech.net/go/telegram"],
@@ -6,10 +6,6 @@ import { useTheme } from "@mui/material/styles";
import Image from "next/image";
import Link from "next/link";
// import networkDocs from "../public/images/landing/network-docs.png";
// import developerDocs from "../public/images/landing/developer-docs.png";
// import sdkDocs from "../public/images/landing/sdk-docs.png";
// import operatorGuide from "../public/images/landing/operator-guide.png";
import networkDocs from "../public/images/landing/Vector1.png";
import developerDocs from "../public/images/landing/Vector2.png";
import sdkDocs from "../public/images/landing/Vector3.png";
@@ -41,13 +37,6 @@ export const LandingPage = () => {
href: "/developers",
icon: sdkDocs,
},
// {
// text: "SDKs",
// description: "Rust and Typescript SDK docs",
// href: "/developers/rust",
// icon: sdkDocs,
// },
{
text: "APIs",
description: "Interactive API specs for Nym infrastructure",
@@ -1,7 +1,7 @@
import { Box } from "@mui/material";
import Image from "next/image";
import Link from "next/link";
import matrixLogo from "../public/images/matrix-logo.png";
export const Matrix = () => {
return (
<Link
@@ -9,7 +9,14 @@ export const Matrix = () => {
target="_blank"
rel="noopener noreferrer"
>
<Image src={matrixLogo} alt={"Matrix Logo"} width={20} height={24} />
<Image src={matrixLogo}
style = {{
marginRight: "0.6rem"
}}
alt={"Matrix Logo"}
width={20}
height={24}
/>
</Link>
);
};
@@ -1,7 +1,7 @@
# Explorer API
The Explorer API is the backend for the [Mixnet Explorer](https://explorer.nymtech.net/).
The Explorer API is the backend for the [Mixnet Explorer](https://nym.com/explorer).
**This will soon be deprecated in favour of the [Node Status API](ns-api.mdx).**
@@ -30,7 +30,7 @@ The `--id` in the example above is a local identifier so that you can name your
The `--use-reply-surbs` field denotes whether you wish to send [SURBs](../../network/concepts/anonymous-replies) along with your request. It defaults to `false`, we are explicitly setting it as `true`. It defaults to `false` for compatibility with versions of the pre-smoosh `nym-network-requester` binary which will soon be deprecated.
The `--provider` field needs to be filled with the Nym address of an Exit Gateway that can make network requests on your behalf. You can select one from the [mixnet explorer](https://explorer.nymtech.net/network-components/gateways) by copying its `Client ID` and using this as the value of the `--provider` flag. Alternatively, you could use [Harbourmaster](https://harbourmaster.nymtech.net/).
The `--provider` field needs to be filled with the Nym address of an Exit Gateway that can make network requests on your behalf. You can select one from the [mixnet explorer](https://nym.com/explorer) by copying its `Client ID` and using this as the value of the `--provider` flag. Alternatively, you could use [Harbourmaster](https://harbourmaster.nymtech.net/).
## Choosing a Gateway
By default - as in the example above - your client will choose a random gateway to connect to.
@@ -42,6 +42,6 @@ However, there are several options for choosing a gateway, if you do not want on
* send few ping messages to all of them, and measure response times.
* create a weighted distribution to randomly choose one, favouring ones with lower latency.
You can select one from the [mixnet explorer](https://explorer.nymtech.net/network-components/gateways) by copying its `Client ID` and using this as the value of the `--provider` flag. Alternatively, you could use [Harbourmaster](https://harbourmaster.nymtech.net/).
You can select one from the [mixnet explorer](https://nym.com/explorer) by copying its `Client ID` and using this as the value of the `--provider` flag. Alternatively, you could use [Harbourmaster](https://harbourmaster.nymtech.net/).
> Note this doesn't mean that your client will pick the closest gateway to you, but it will be far more likely to connect to gateway with a 20ms ping rather than 200ms
@@ -31,7 +31,7 @@ Initialising a new client instance can be done with the following command:
The `--id` in the example above is a local identifier so that you can name your clients; it is **never** transmitted over the network.
There is an optional `--gateway` flag that you can use if you want to use a specific gateway. The supplied argument is the `Identity Key` of the gateway you wish to use, which can be found on the [mixnet explorer](https://explorer.nymtech.net/network-components/gateways). Alternatively, you could use [Harbourmaster](https://harbourmaster.nymtech.net/)
There is an optional `--gateway` flag that you can use if you want to use a specific gateway. The supplied argument is the `Identity Key` of the gateway you wish to use, which can be found on the [mixnet explorer](https://nym.com/explorer). Alternatively, you could use [Harbourmaster](https://harbourmaster.nymtech.net/)
Not passing this argument will randomly select a gateway for your client.
@@ -19,4 +19,4 @@ To illustrate this: `DpB3cHAchJiNBQi5FrZx2csXb1mrHkpYh9Wzf8Rjsuko.ANNWrvHqMYuert
- `DpB3cHAchJiNBQi5FrZx2csXb1mrHkpYh9Wzf8Rjsuko`: is the client's identity key;
- `ANNWrvHqMYuertHGHUrZdBntQhpzfbWekB39qez9U2Vx`: is the client's Diffie Hellman key;
- `2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh`: is the gateway's identity, which is what you'll need to check the state of the gateway in the [Nym Explorer](https://explorer.nymtech.net/network-components/gateways).
- `2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh`: is the gateway's identity, which is what you'll need to check the state of the gateway in the [Nym Explorer](https://nym.com/explorer).
@@ -10,7 +10,7 @@ Yes, there are..
**Built by Nym**
* [Nym Explorer](https://explorer.nymtech.net/)
* [Nym Explorer](https://nym.com/explorer)
* [Sandbox testnet](https://sandbox-explorer.nymtech.net/)
* [Nym Harbourmaster](https://harbourmaster.nymtech.net)
@@ -259,7 +259,7 @@ That can be done only on an IP+destination port basis, however. Common P2P ports
<p>
You also have the option of blocking this IP address and others on the Nym network if you so desire.
The Nym project provides a <a href="https://explorer.nymtech.net/network-components/gateways">
The Nym project provides a <a href="https://nym.com/explorer">
web service</a> to fetch a list of all IP addresses of Nym Gateway Exit nodes that allow exiting to a
specified IP:port combination. Please be considerate when using these options.</p>
@@ -44,7 +44,7 @@ From `nym-node v1.3.0` operators can choose multiple functionalities for their `
### Mixnet Routing
***Mixnet mode (5-hop) is the full anonymising option of NymVPN. Read more about the Mixnet architecture [here](http://localhost:3000/docs/network/architecture)***
***Mixnet mode (5-hop) is the full anonymising option of NymVPN. Read more about the Mixnet architecture [here](../../../network/architecture)***
Nym Node has three functionalities in the Mixnet: `entry-gateway`, `mixnode` and `exit-gateway`. These are selected with a flag `--mode <MODE>` alongside `nym-node` command `run` .
@@ -3,7 +3,7 @@
Nym Node operators running Gateway functionality are already familiar with the monitoring tool [Harbourmaster.nymtech.net](https://harbourmaster.nymtech.net). Under the hood of Nym Harbourmaster runs iterations of `nym-gateway-probe` doing various checks and displaying the results on the interface. Operators don't have to rely on the probe ran by Nym and wait for the data to refresh. With `nym-gateway-probe` everyone can check any Gateway's networking status from their own computer at any time. In one command the client queries data from:
- [`nym-api`](https://validator.nymtech.net/api/v1/gateways)
- [`explorer-api`](https://explorer.nymtech.net/api/v1/gateways)
- [`explorer-api`](https://mainnet-node-status-api.nymtech.cc/swagger/#/Gateways)
- [`harbour-master`](https://harbourmaster.nymtech.net/)
@@ -88,4 +88,3 @@ For any `nym-node --mode exit-gateway` the aim is to have this outcome:
**If your Gateway is blacklisted, the probe will not work.**
If you don't provide a `--gateway` flag it will pick a random one to test.
@@ -360,7 +360,7 @@ $1 \ NYM = 1 \_ 000 \_ 000 \ uNYM$
The Reward Estimation API endpoint allows Mix Node operators to estimate the rewards they could earn for running a Nym Mix Node with a specific `MIX_ID`.
> The `<MIX_ID>` can be found in the "Mix ID" column of the [Network Explorer](https://explorer.nymtech.net/network-components/mixnodes/active).
> The `<MIX_ID>` can be found in the "Mix ID" column of the [Network Explorer](https://nym.com/explorer).
The endpoint is a particularly common for Mix Node operators as it can provide an estimate of potential earnings based on factors such as the amount of traffic routed through the Mix Node, the quality of the Mix Node's performance, and the overall demand for Mix Nodes in the network. This information can be useful for Mix Node operators in deciding whether or not to run a Mix Node and in optimizing its operations for maximum profitability.
@@ -73,7 +73,7 @@ The directory structure for each node will be roughly as follows:
```
<Callout>
If you `cat` the `public_sphinx.pem` key, the output will be different from the public key you will see on Nym [dashboard](https://explorer.nymtech.net/). The reason for this is that `.pem` files are encoded in **base64**, however on the web they are in **base58**. Don't be confused if your keys look different. They are the same keys, just with different encoding.
If you `cat` the `public_sphinx.pem` key, the output will be different from the public key you will see on Nym [dashboard](https://nym.com/explorer). The reason for this is that `.pem` files are encoded in **base64**, however on the web they are in **base58**. Don't be confused if your keys look different. They are the same keys, just with different encoding.
</Callout>
### Accidentally killing your node process on exiting session
@@ -101,7 +101,7 @@ You don't have to do any additional configuration for your node to implement thi
### How can I tell my node is up and running and mixing traffic?
First of all check the 'Mixnodes' section of either of the Nym Network Explorers:
* [Mainnet](https://explorer.nymtech.net/)
* [Mainnet](https://nym.com/explorer)
* [Sandbox testnet](https://sandbox-explorer.nymtech.net/)
You can also check [Nym Harbourmaster](https://harbourmaster.nymtech.net) which now also includes mixnode mode.
Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

+12 -5
View File
@@ -1,7 +1,8 @@
import React from "react";
import { DocsThemeConfig, useConfig } from "nextra-theme-docs";
import { Footer } from "./components/footer";
// import { Footer } from "./components/footer";
import { Matrix } from "./components/matrix-link";
import { Explorer } from "./components/explorer-link";
import { useRouter } from "next/router";
const config: DocsThemeConfig = {
@@ -66,9 +67,9 @@ const config: DocsThemeConfig = {
},
docsRepositoryBase:
"https://github.com/nymtech/nym/tree/develop/documentation/docs/",
footer: {
text: Footer,
},
// footer: {
// text: Footer,
// },
darkMode: true,
nextThemes: {
defaultTheme: "dark",
@@ -77,8 +78,14 @@ const config: DocsThemeConfig = {
defaultMenuCollapseLevel: 1,
autoCollapse: true,
},
navbar: {
extraContent: <Matrix />,
extraContent: (
<>
<Explorer />
<Matrix />
</>
),
},
toc: {
float: false,
@@ -19,7 +19,6 @@ pub(crate) struct Config {
pub(crate) bandwidth: BandwidthFlushingBehaviourConfig,
}
// I can see this being possible expanded with say storage or client store
#[derive(Clone)]
pub(crate) struct CommonHandlerState {
pub(crate) cfg: Config,
@@ -131,9 +131,6 @@ impl<R, S> FreshHandler<R, S> {
// for time being we assume handle is always constructed from raw socket.
// if we decide we want to change it, that's not too difficult
// also at this point I'm not entirely sure how to deal with this warning without
// some considerable refactoring
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
rng: R,
conn: S,
@@ -6,9 +6,8 @@ use crate::node::client_handling::websocket::connection_handler::FreshHandler;
use nym_task::TaskClient;
use rand::rngs::OsRng;
use std::net::SocketAddr;
use std::process;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::{io, process};
use tokio::net::TcpStream;
use tokio::task::JoinHandle;
use tracing::*;
@@ -34,6 +33,76 @@ impl Listener {
}
}
fn active_connections(&self) -> usize {
self.shared_state
.metrics
.network
.active_ingress_websocket_connections_count()
}
fn prepare_connection_handler(
&self,
socket: TcpStream,
remote_address: SocketAddr,
) -> FreshHandler<OsRng, TcpStream> {
let shutdown = self
.shutdown
.fork(format!("websocket_handler_{remote_address}"));
FreshHandler::new(
OsRng,
socket,
self.shared_state.clone(),
remote_address,
shutdown,
)
}
fn try_handle_accepted_connection(&self, accepted: io::Result<(TcpStream, SocketAddr)>) {
match accepted {
Ok((socket, remote_address)) => {
trace!("received a socket connection from {remote_address}");
let active = self.active_connections();
// 1. check if we're within the connection limit
if active >= self.maximum_open_connections {
warn!(
"connection limit exceeded ({}). can't accept request from {remote_address}",
self.maximum_open_connections
);
return;
}
debug!("there are currently {active} connected clients on the gateway websocket");
// 2. prepare shared data for the new connection handler
let handle = self.prepare_connection_handler(socket, remote_address);
// 3. increment the connection counter.
// make sure to do it before spawning the task,
// as another connection might get accepted before the task is scheduled
// for execution
self.shared_state
.metrics
.network
.new_ingress_websocket_client();
// 4. spawn the task handling the client connection
tokio::spawn(async move {
// TODO: refactor it similarly to the mixnet listener on the nym-node
let metrics_ref = handle.shared_state.metrics.clone();
// 4.1. handle all client requests until connection gets terminated
handle.start_handling().await;
// 4.2. decrement the connection counter
metrics_ref.network.disconnected_ingress_websocket_client();
});
}
Err(err) => warn!("failed to accept client connection: {err}"),
}
}
// TODO: change the signature to pub(crate) async fn run(&self, handler: Handler)
pub(crate) async fn run(&mut self) {
@@ -46,8 +115,6 @@ impl Listener {
}
};
let open_connections = Arc::new(AtomicUsize::new(0));
while !self.shutdown.is_shutdown() {
tokio::select! {
biased;
@@ -55,38 +122,7 @@ impl Listener {
trace!("client_handling::Listener: received shutdown");
}
connection = tcp_listener.accept() => {
match connection {
Ok((socket, remote_addr)) => {
let shutdown = self.shutdown.fork(format!("websocket_handler_{remote_addr}"));
trace!("received a socket connection from {remote_addr}");
if open_connections.fetch_add(1, Ordering::SeqCst) >= self.maximum_open_connections {
warn!("connection limit exceeded ({}). can't accept request from {remote_addr}", self.maximum_open_connections);
continue;
}
// TODO: I think we *REALLY* need a mechanism for having a maximum number of connected
// clients or spawned tokio tasks -> perhaps a worker system?
let handle = FreshHandler::new(
OsRng,
socket,
self.shared_state.clone(),
remote_addr,
shutdown,
);
let open_connections = open_connections.clone();
tokio::spawn(async move {
// TODO: refactor it similarly to the mixnet listener on the nym-node
let metrics_ref = handle.shared_state.metrics.clone();
metrics_ref.network.new_ingress_websocket_client();
open_connections.fetch_add(1, Ordering::SeqCst);
handle.start_handling().await;
metrics_ref.network.disconnected_ingress_websocket_client();
open_connections.fetch_sub(1, Ordering::SeqCst);
});
}
Err(err) => warn!("failed to get client: {err}"),
}
self.try_handle_accepted_connection(connection)
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
[package]
name = "nym-api"
license = "GPL-3.0"
version = "1.1.53"
version = "1.1.54"
authors.workspace = true
edition = "2021"
rust-version.workspace = true
+7 -7
View File
@@ -747,7 +747,7 @@ impl StorageManager {
&self,
db_mixnode_id: i64,
since: i64,
) -> Result<i32, sqlx::Error> {
) -> Result<i64, sqlx::Error> {
let count = sqlx::query!(
r#"
SELECT COUNT(*) as count FROM
@@ -786,7 +786,7 @@ impl StorageManager {
&self,
gateway_id: i64,
since: i64,
) -> Result<i32, sqlx::Error> {
) -> Result<i64, sqlx::Error> {
let count = sqlx::query!(
r#"
SELECT COUNT(*) as count FROM
@@ -824,7 +824,7 @@ impl StorageManager {
)
.fetch_one(&self.connection_pool)
.await
.map(|result| result.exists == Some(1))
.map(|result| result.exists == 1)
}
/// Creates new entry for mixnode historical uptime
@@ -966,7 +966,7 @@ impl StorageManager {
&self,
since: i64,
until: i64,
) -> Result<i32, sqlx::Error> {
) -> Result<i64, sqlx::Error> {
let count = sqlx::query!(
"SELECT COUNT(*) as count FROM monitor_run WHERE timestamp > ? AND timestamp < ?",
since,
@@ -1227,7 +1227,7 @@ impl StorageManager {
.await
}
pub(super) async fn get_mixnode_statuses_count(&self, db_id: i64) -> Result<i32, sqlx::Error> {
pub(super) async fn get_mixnode_statuses_count(&self, db_id: i64) -> Result<i64, sqlx::Error> {
sqlx::query!(
r#"
SELECT COUNT(*) as count
@@ -1279,7 +1279,7 @@ impl StorageManager {
.await
}
pub(super) async fn get_gateway_statuses_count(&self, db_id: i64) -> Result<i32, sqlx::Error> {
pub(super) async fn get_gateway_statuses_count(&self, db_id: i64) -> Result<i64, sqlx::Error> {
sqlx::query!(
r#"
SELECT COUNT(*) as count
@@ -1341,7 +1341,7 @@ pub(crate) mod v3_migration {
sqlx::query!("SELECT EXISTS (SELECT 1 FROM v3_migration_info) AS 'exists'",)
.fetch_one(&self.connection_pool)
.await
.map(|result| result.exists == Some(1))
.map(|result| result.exists == 1)
}
pub(crate) async fn set_v3_migration_completion(&self) -> Result<(), sqlx::Error> {
+2 -2
View File
@@ -679,7 +679,7 @@ impl NymApiStorage {
&self,
mix_id: NodeId,
since: Option<i64>,
) -> Result<i32, NymApiStorageError> {
) -> Result<i64, NymApiStorageError> {
let db_id = self.manager.get_mixnode_database_id(mix_id).await?;
if let Some(node_id) = db_id {
@@ -707,7 +707,7 @@ impl NymApiStorage {
&self,
identity: &str,
since: Option<i64>,
) -> Result<i32, NymApiStorageError> {
) -> Result<i64, NymApiStorageError> {
let node_id = self
.manager
.get_gateway_database_id_by_identity(identity)
@@ -3,7 +3,7 @@
set -eu
export ENVIRONMENT=${ENVIRONMENT:-"mainnet"}
probe_git_ref="nym-vpn-core-v1.3.2"
probe_git_ref="nym-vpn-core-v1.4.0"
crate_root=$(dirname $(realpath "$0"))
monorepo_root=$(realpath "${crate_root}/../..")
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n INSERT INTO mixnode_daily_stats (\n mix_id, date_utc,\n total_stake, packets_received,\n packets_sent, packets_dropped\n ) VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(mix_id, date_utc) DO UPDATE SET\n total_stake = excluded.total_stake,\n packets_received = mixnode_daily_stats.packets_received + excluded.packets_received,\n packets_sent = mixnode_daily_stats.packets_sent + excluded.packets_sent,\n packets_dropped = mixnode_daily_stats.packets_dropped + excluded.packets_dropped\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 6
},
"nullable": []
},
"hash": "01ee4a30bc3104712e5bc371a45d614a89d88adf02358800433e06100c13c548"
}
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
"query": "\n SELECT mix_id as node_id, host, http_api_port \n FROM mixnodes \n WHERE bonded = true\n ",
"query": "\n SELECT mix_id as node_id, host, http_api_port\n FROM mixnodes\n WHERE bonded = true\n ",
"describe": {
"columns": [
{
@@ -28,5 +28,5 @@
false
]
},
"hash": "8c6c1c67df2d06da74825872f448d30505801b56506668499f9da718f1d6b8f8"
"hash": "021c6c65d1ed806d8430bef7883906b42a7e4b280c8efb32db15d7c6a51d7a27"
}
@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "\n INSERT INTO mixnode_description (\n mix_id, moniker, website, security_contact, details, last_updated_utc\n ) VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT (mix_id) DO UPDATE SET\n moniker = excluded.moniker,\n website = excluded.website,\n security_contact = excluded.security_contact,\n details = excluded.details,\n last_updated_utc = excluded.last_updated_utc\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 6
},
"nullable": []
},
"hash": "06065394c157927e4002ddd5c7c1af626ae15728d615f539470cd7c189312385"
}
@@ -0,0 +1,44 @@
{
"db_name": "SQLite",
"query": "\n SELECT\n date_utc as \"date_utc!\",\n SUM(total_stake) as \"total_stake!: i64\",\n SUM(packets_received) as \"total_packets_received!: i64\",\n SUM(packets_sent) as \"total_packets_sent!: i64\",\n SUM(packets_dropped) as \"total_packets_dropped!: i64\"\n FROM (\n SELECT\n date_utc,\n n.total_stake,\n n.packets_received,\n n.packets_sent,\n n.packets_dropped\n FROM nym_node_daily_mixing_stats n\n UNION ALL\n SELECT\n m.date_utc,\n m.total_stake,\n m.packets_received,\n m.packets_sent,\n m.packets_dropped\n FROM mixnode_daily_stats m\n LEFT JOIN nym_node_daily_mixing_stats ON m.mix_id = nym_node_daily_mixing_stats.node_id\n WHERE nym_node_daily_mixing_stats.node_id IS NULL\n )\n GROUP BY date_utc\n ORDER BY date_utc ASC\n ",
"describe": {
"columns": [
{
"name": "date_utc!",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "total_stake!: i64",
"ordinal": 1,
"type_info": "Int"
},
{
"name": "total_packets_received!: i64",
"ordinal": 2,
"type_info": "Int"
},
{
"name": "total_packets_sent!: i64",
"ordinal": 3,
"type_info": "Int"
},
{
"name": "total_packets_dropped!: i64",
"ordinal": 4,
"type_info": "Int"
}
],
"parameters": {
"Right": 0
},
"nullable": [
false,
false,
true,
true,
true
]
},
"hash": "124d45b9604439584650f401607c46bdbd162c7c689f74fe9ddfdfd48f5ddc07"
}
@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "UPDATE mixnodes\n SET bonded = ?, last_updated_utc = ?\n WHERE id = ?;",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "18abc8fde56cf86baed7b4afa38f2c63cdf90f2f3b6d81afb9000bb0968dcaea"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n INSERT INTO mixnode_packet_stats_raw (\n mix_id, timestamp_utc, packets_received, packets_sent, packets_dropped\n ) VALUES (?, ?, ?, ?, ?)\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 5
},
"nullable": []
},
"hash": "21e44766729777756f6eb04bf3b81df3e591008a1e3fd664ed83ca86ac51bd8c"
}
@@ -0,0 +1,26 @@
{
"db_name": "SQLite",
"query": "SELECT\n node_id,\n bond_info as \"bond_info: serde_json::Value\"\n FROM\n nym_nodes\n WHERE\n bond_info IS NOT NULL\n AND\n self_described IS NOT NULL\n ",
"describe": {
"columns": [
{
"name": "node_id",
"ordinal": 0,
"type_info": "Int64"
},
{
"name": "bond_info: serde_json::Value",
"ordinal": 1,
"type_info": "Text"
}
],
"parameters": {
"Right": 0
},
"nullable": [
false,
true
]
},
"hash": "227539374e7473f6f9642289c5b5d1bcd636315ab23537cb5f6d2f82a2bcb7bf"
}
@@ -0,0 +1,20 @@
{
"db_name": "SQLite",
"query": "\n SELECT gateway_identity_key\n FROM gateways\n WHERE bonded = true\n ",
"describe": {
"columns": [
{
"name": "gateway_identity_key",
"ordinal": 0,
"type_info": "Text"
}
],
"parameters": {
"Right": 0
},
"nullable": [
false
]
},
"hash": "25300e435780101fa207c8e26ef2f49ba5db84d63e89440bb494e8327fe73686"
}
@@ -0,0 +1,86 @@
{
"db_name": "SQLite",
"query": "SELECT\n node_id,\n ed25519_identity_pubkey,\n total_stake,\n ip_addresses as \"ip_addresses!: serde_json::Value\",\n mix_port,\n x25519_sphinx_pubkey,\n node_role as \"node_role: serde_json::Value\",\n supported_roles as \"supported_roles: serde_json::Value\",\n entry as \"entry: serde_json::Value\",\n performance,\n self_described as \"self_described: serde_json::Value\",\n bond_info as \"bond_info: serde_json::Value\"\n FROM\n nym_nodes\n WHERE\n self_described IS NOT NULL\n AND\n bond_info IS NOT NULL\n ",
"describe": {
"columns": [
{
"name": "node_id",
"ordinal": 0,
"type_info": "Int64"
},
{
"name": "ed25519_identity_pubkey",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "total_stake",
"ordinal": 2,
"type_info": "Int64"
},
{
"name": "ip_addresses!: serde_json::Value",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "mix_port",
"ordinal": 4,
"type_info": "Int64"
},
{
"name": "x25519_sphinx_pubkey",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "node_role: serde_json::Value",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "supported_roles: serde_json::Value",
"ordinal": 7,
"type_info": "Text"
},
{
"name": "entry: serde_json::Value",
"ordinal": 8,
"type_info": "Text"
},
{
"name": "performance",
"ordinal": 9,
"type_info": "Text"
},
{
"name": "self_described: serde_json::Value",
"ordinal": 10,
"type_info": "Text"
},
{
"name": "bond_info: serde_json::Value",
"ordinal": 11,
"type_info": "Text"
}
],
"parameters": {
"Right": 0
},
"nullable": [
false,
false,
false,
false,
false,
false,
false,
false,
true,
false,
true,
true
]
},
"hash": "283f49a65c7d70bf271702ff6a5c7ad6e68c81932d295ff18ed198c54706a57c"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n INSERT INTO mixnode_description (\n mix_id, moniker, website, security_contact, details, last_updated_utc\n ) VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT (mix_id) DO UPDATE SET\n moniker = excluded.moniker,\n website = excluded.website,\n security_contact = excluded.security_contact,\n details = excluded.details,\n last_updated_utc = excluded.last_updated_utc\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 6
},
"nullable": []
},
"hash": "3825020ab0ecbffe83270c57ec74fb7987a6807d3381a6068c79b127a668b190"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "INSERT INTO mixnodes\n (mix_id, identity_key, bonded, total_stake,\n host, http_api_port, full_details,\n self_described, last_updated_utc, is_dp_delegatee)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(mix_id) DO UPDATE SET\n bonded=excluded.bonded,\n total_stake=excluded.total_stake, host=excluded.host,\n http_api_port=excluded.http_api_port,\n full_details=excluded.full_details,self_described=excluded.self_described,\n last_updated_utc=excluded.last_updated_utc,\n is_dp_delegatee = excluded.is_dp_delegatee;",
"describe": {
"columns": [],
"parameters": {
"Right": 10
},
"nullable": []
},
"hash": "3cd5cb4bfca4243925da4ddbccd811e842090e98982e1032670df77961870b32"
}
@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "UPDATE gateways\n SET bonded = ?, last_updated_utc = ?\n WHERE id = ?;",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "3d3a1fa429e3090741c6b6a8e82e692afc04b51e8782bcbf59f1eb4116112536"
}
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
"query": "SELECT\n id as \"id!\",\n gateway_identity_key as \"gateway_identity_key!\",\n self_described as \"self_described?\",\n explorer_pretty_bond as \"explorer_pretty_bond?\"\n FROM gateways\n WHERE gateway_identity_key = ?\n ORDER BY gateway_identity_key\n LIMIT 1",
"query": "SELECT\n id as \"id!\",\n gateway_identity_key as \"gateway_identity_key!\",\n self_described as \"self_described?\",\n explorer_pretty_bond as \"explorer_pretty_bond?\"\n FROM gateways\n WHERE gateway_identity_key = ?\n AND bonded = true\n ORDER BY gateway_identity_key\n LIMIT 1",
"describe": {
"columns": [
{
@@ -34,5 +34,5 @@
true
]
},
"hash": "3d5fc502f976f5081f01352856b8632c29c81bfafb043bb8744129cf9e0266ad"
"hash": "3e7e987780937873cdb393b157d7708c9f01047b0689eb0d4f7a973b328c609d"
}
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
"query": "SELECT\n gw.gateway_identity_key as \"gateway_identity_key!\",\n gw.bonded as \"bonded: bool\",\n gw.blacklisted as \"blacklisted: bool\",\n gw.performance as \"performance!\",\n gw.self_described as \"self_described?\",\n gw.explorer_pretty_bond as \"explorer_pretty_bond?\",\n gw.last_probe_result as \"last_probe_result?\",\n gw.last_probe_log as \"last_probe_log?\",\n gw.last_testrun_utc as \"last_testrun_utc?\",\n gw.last_updated_utc as \"last_updated_utc!\",\n COALESCE(gd.moniker, \"NA\") as \"moniker!\",\n COALESCE(gd.website, \"NA\") as \"website!\",\n COALESCE(gd.security_contact, \"NA\") as \"security_contact!\",\n COALESCE(gd.details, \"NA\") as \"details!\"\n FROM gateways gw\n LEFT JOIN gateway_description gd\n ON gw.gateway_identity_key = gd.gateway_identity_key\n ORDER BY gw.gateway_identity_key",
"query": "SELECT\n gw.gateway_identity_key as \"gateway_identity_key!\",\n gw.bonded as \"bonded: bool\",\n gw.performance as \"performance!\",\n gw.self_described as \"self_described?\",\n gw.explorer_pretty_bond as \"explorer_pretty_bond?\",\n gw.last_probe_result as \"last_probe_result?\",\n gw.last_probe_log as \"last_probe_log?\",\n gw.last_testrun_utc as \"last_testrun_utc?\",\n gw.last_updated_utc as \"last_updated_utc!\",\n COALESCE(gd.moniker, \"NA\") as \"moniker!\",\n COALESCE(gd.website, \"NA\") as \"website!\",\n COALESCE(gd.security_contact, \"NA\") as \"security_contact!\",\n COALESCE(gd.details, \"NA\") as \"details!\"\n FROM gateways gw\n LEFT JOIN gateway_description gd\n ON gw.gateway_identity_key = gd.gateway_identity_key\n ORDER BY gw.gateway_identity_key",
"describe": {
"columns": [
{
@@ -14,63 +14,58 @@
"type_info": "Int64"
},
{
"name": "blacklisted: bool",
"name": "performance!",
"ordinal": 2,
"type_info": "Int64"
},
{
"name": "performance!",
"ordinal": 3,
"type_info": "Int64"
},
{
"name": "self_described?",
"ordinal": 4,
"ordinal": 3,
"type_info": "Text"
},
{
"name": "explorer_pretty_bond?",
"ordinal": 5,
"ordinal": 4,
"type_info": "Text"
},
{
"name": "last_probe_result?",
"ordinal": 6,
"ordinal": 5,
"type_info": "Text"
},
{
"name": "last_probe_log?",
"ordinal": 7,
"ordinal": 6,
"type_info": "Text"
},
{
"name": "last_testrun_utc?",
"ordinal": 8,
"ordinal": 7,
"type_info": "Int64"
},
{
"name": "last_updated_utc!",
"ordinal": 9,
"ordinal": 8,
"type_info": "Int64"
},
{
"name": "moniker!",
"ordinal": 10,
"ordinal": 9,
"type_info": "Text"
},
{
"name": "website!",
"ordinal": 11,
"ordinal": 10,
"type_info": "Text"
},
{
"name": "security_contact!",
"ordinal": 12,
"ordinal": 11,
"type_info": "Text"
},
{
"name": "details!",
"ordinal": 13,
"ordinal": 12,
"type_info": "Text"
}
],
@@ -82,7 +77,6 @@
false,
false,
false,
false,
true,
true,
true,
@@ -94,5 +88,5 @@
false
]
},
"hash": "71a455c705f9c25d3843ff2fb8629d1320a5eb10797cdb5a435455d22c6aeac1"
"hash": "3eb1d8491bda3c1d6e071b6eb364b9a979f4bdb11ea81b2d0f022555bab51ecb"
}
@@ -0,0 +1,20 @@
{
"db_name": "SQLite",
"query": "\n SELECT\n total_stake\n FROM mixnodes\n WHERE mix_id = ?\n ",
"describe": {
"columns": [
{
"name": "total_stake",
"ordinal": 0,
"type_info": "Int64"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false
]
},
"hash": "3fc2baabf194b147b20be2a49401cc0c100a1d7a7c347393adde2410fa6f4dfe"
}
@@ -1,32 +0,0 @@
{
"db_name": "SQLite",
"query": "SELECT\n id as \"id!\",\n gateway_identity_key as \"identity_key!\",\n bonded as \"bonded: bool\"\n FROM gateways\n WHERE bonded = ?",
"describe": {
"columns": [
{
"name": "id!",
"ordinal": 0,
"type_info": "Int64"
},
{
"name": "identity_key!",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "bonded: bool",
"ordinal": 2,
"type_info": "Int64"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
false,
false
]
},
"hash": "4b61a4bc32333c92a8f5ad4ad0017b40dc01845f554b5479f37855d89b309e6f"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n INSERT INTO nym_node_daily_mixing_stats (\n node_id, date_utc,\n total_stake, packets_received,\n packets_sent, packets_dropped\n ) VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(node_id, date_utc) DO UPDATE SET\n total_stake = excluded.total_stake,\n packets_received = nym_node_daily_mixing_stats.packets_received + excluded.packets_received,\n packets_sent = nym_node_daily_mixing_stats.packets_sent + excluded.packets_sent,\n packets_dropped = nym_node_daily_mixing_stats.packets_dropped + excluded.packets_dropped\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 6
},
"nullable": []
},
"hash": "5912ea335a957d217f5e2b3a63a25b31715c2098310fe7a9db688bc2fd36aad4"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "INSERT INTO nym_nodes\n (node_id, ed25519_identity_pubkey,\n total_stake,\n ip_addresses, mix_port,\n x25519_sphinx_pubkey, node_role,\n supported_roles, entry,\n self_described,\n bond_info,\n performance, last_updated_utc\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(node_id) DO UPDATE SET\n ed25519_identity_pubkey=excluded.ed25519_identity_pubkey,\n ip_addresses=excluded.ip_addresses,\n mix_port=excluded.mix_port,\n x25519_sphinx_pubkey=excluded.x25519_sphinx_pubkey,\n node_role=excluded.node_role,\n supported_roles=excluded.supported_roles,\n entry=excluded.entry,\n self_described=excluded.self_described,\n bond_info=excluded.bond_info,\n performance=excluded.performance,\n last_updated_utc=excluded.last_updated_utc\n ;",
"describe": {
"columns": [],
"parameters": {
"Right": 13
},
"nullable": []
},
"hash": "664e059ac2c58e1115fe214376a6b326b31c93298f20019772cce2e277a194f8"
}
@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "INSERT INTO mixnodes\n (mix_id, identity_key, bonded, total_stake,\n host, http_api_port, blacklisted, full_details,\n self_described, last_updated_utc, is_dp_delegatee)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(mix_id) DO UPDATE SET\n bonded=excluded.bonded,\n total_stake=excluded.total_stake, host=excluded.host,\n http_api_port=excluded.http_api_port,blacklisted=excluded.blacklisted,\n full_details=excluded.full_details,self_described=excluded.self_described,\n last_updated_utc=excluded.last_updated_utc,\n is_dp_delegatee = excluded.is_dp_delegatee;",
"describe": {
"columns": [],
"parameters": {
"Right": 11
},
"nullable": []
},
"hash": "6eb1a682cf13205cf701590021cdf795147ac3724e89df5b2f24f7215d87dce1"
}
@@ -0,0 +1,26 @@
{
"db_name": "SQLite",
"query": "SELECT\n node_id,\n self_described as \"self_described: serde_json::Value\"\n FROM\n nym_nodes\n WHERE\n self_described IS NOT NULL\n ",
"describe": {
"columns": [
{
"name": "node_id",
"ordinal": 0,
"type_info": "Int64"
},
{
"name": "self_described: serde_json::Value",
"ordinal": 1,
"type_info": "Text"
}
],
"parameters": {
"Right": 0
},
"nullable": [
false,
true
]
},
"hash": "7526390c3d17622564067f153867295a279179315650f6fb363d5f3177bf70e3"
}
@@ -1,32 +0,0 @@
{
"db_name": "SQLite",
"query": "\n SELECT packets_received, packets_sent, packets_dropped\n FROM mixnode_packet_stats_raw\n WHERE mix_id = ?\n ORDER BY timestamp_utc DESC\n LIMIT 1 OFFSET 1\n ",
"describe": {
"columns": [
{
"name": "packets_received",
"ordinal": 0,
"type_info": "Int64"
},
{
"name": "packets_sent",
"ordinal": 1,
"type_info": "Int64"
},
{
"name": "packets_dropped",
"ordinal": 2,
"type_info": "Int64"
}
],
"parameters": {
"Right": 1
},
"nullable": [
true,
true,
true
]
},
"hash": "7b3e44e1dea0568d331017216e5375684086bbeb80f3b251adc761bae0dafe92"
}
@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "\n INSERT INTO mixnode_packet_stats_raw (\n mix_id, timestamp_utc, packets_received, packets_sent, packets_dropped\n ) VALUES (?, ?, ?, ?, ?)\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 5
},
"nullable": []
},
"hash": "84e784b7ceb479f37fa312dd9a8bb0b3debc04cd251cdfaa438bce7aca7c5992"
}
@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "UPDATE gateways\n SET blacklisted = true\n WHERE gateway_identity_key = ?;",
"describe": {
"columns": [],
"parameters": {
"Right": 1
},
"nullable": []
},
"hash": "8571faad2f66e08f24acfbfe036d17ca6eb090df7f6d52ef89c5d51564f8b45c"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n INSERT INTO nym_node_descriptions (\n node_id, moniker, website, security_contact, details, last_updated_utc\n ) VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT (node_id) DO UPDATE SET\n moniker = excluded.moniker,\n website = excluded.website,\n security_contact = excluded.security_contact,\n details = excluded.details,\n last_updated_utc = excluded.last_updated_utc\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 6
},
"nullable": []
},
"hash": "9104e50524ad5a103bd199a0531d73b74876e9aecda2117227e2e180258d91a1"
}
@@ -0,0 +1,86 @@
{
"db_name": "SQLite",
"query": "SELECT\n node_id,\n ed25519_identity_pubkey,\n total_stake,\n ip_addresses as \"ip_addresses!: serde_json::Value\",\n mix_port,\n x25519_sphinx_pubkey,\n node_role as \"node_role: serde_json::Value\",\n supported_roles as \"supported_roles: serde_json::Value\",\n entry as \"entry: serde_json::Value\",\n performance,\n self_described as \"self_described: serde_json::Value\",\n bond_info as \"bond_info: serde_json::Value\"\n FROM\n nym_nodes\n ",
"describe": {
"columns": [
{
"name": "node_id",
"ordinal": 0,
"type_info": "Int64"
},
{
"name": "ed25519_identity_pubkey",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "total_stake",
"ordinal": 2,
"type_info": "Int64"
},
{
"name": "ip_addresses!: serde_json::Value",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "mix_port",
"ordinal": 4,
"type_info": "Int64"
},
{
"name": "x25519_sphinx_pubkey",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "node_role: serde_json::Value",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "supported_roles: serde_json::Value",
"ordinal": 7,
"type_info": "Text"
},
{
"name": "entry: serde_json::Value",
"ordinal": 8,
"type_info": "Text"
},
{
"name": "performance",
"ordinal": 9,
"type_info": "Text"
},
{
"name": "self_described: serde_json::Value",
"ordinal": 10,
"type_info": "Text"
},
{
"name": "bond_info: serde_json::Value",
"ordinal": 11,
"type_info": "Text"
}
],
"parameters": {
"Right": 0
},
"nullable": [
false,
false,
false,
false,
false,
false,
false,
false,
true,
false,
true,
true
]
},
"hash": "9334f0c91252fcd7ec72558a271222615bb282e5334665700709ae475a5daea2"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "INSERT INTO gateways\n (gateway_identity_key, bonded,\n self_described, explorer_pretty_bond,\n last_updated_utc, performance)\n VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(gateway_identity_key) DO UPDATE SET\n bonded=excluded.bonded,\n self_described=excluded.self_described,\n explorer_pretty_bond=excluded.explorer_pretty_bond,\n last_updated_utc=excluded.last_updated_utc,\n performance = excluded.performance;",
"describe": {
"columns": [],
"parameters": {
"Right": 6
},
"nullable": []
},
"hash": "9796d354ae075eab4cbd3438839c39da94025494395ec7b093aefef696f2d0c5"
}
@@ -0,0 +1,32 @@
{
"db_name": "SQLite",
"query": "\n SELECT\n COALESCE(packets_received, 0) as packets_received,\n COALESCE(packets_sent, 0) as packets_sent,\n COALESCE(packets_dropped, 0) as packets_dropped\n FROM nym_nodes_packet_stats_raw\n WHERE node_id = ?\n ORDER BY timestamp_utc DESC\n LIMIT 1 OFFSET 1\n ",
"describe": {
"columns": [
{
"name": "packets_received",
"ordinal": 0,
"type_info": "Int64"
},
{
"name": "packets_sent",
"ordinal": 1,
"type_info": "Int64"
},
{
"name": "packets_dropped",
"ordinal": 2,
"type_info": "Int64"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
false,
false
]
},
"hash": "97f4b340bb0f6c9c78d77b24d4f9d3d13ab61cbaac6f8c3cfc06c95e0f13252f"
}
@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "\n INSERT INTO mixnode_daily_stats (\n mix_id, date_utc, total_stake, packets_received, packets_sent, packets_dropped\n ) VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(mix_id, date_utc) DO UPDATE SET\n total_stake = excluded.total_stake,\n packets_received = mixnode_daily_stats.packets_received + excluded.packets_received,\n packets_sent = mixnode_daily_stats.packets_sent + excluded.packets_sent,\n packets_dropped = mixnode_daily_stats.packets_dropped + excluded.packets_dropped\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 6
},
"nullable": []
},
"hash": "a14d5767512bc88ac47b667c47a773b548ccb20bc539839766080d8455439339"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "DELETE FROM nym_nodes\n WHERE node_id = ?",
"describe": {
"columns": [],
"parameters": {
"Right": 1
},
"nullable": []
},
"hash": "b41fc93f5dc7a397e8898776bf1335d9c26fe1447309f46623bcfee4537991b1"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "UPDATE\n mixnodes\n SET\n bonded = false\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 0
},
"nullable": []
},
"hash": "c42917c9542c1d720d92035863064741aefc9f7a7d1630f6b863ebd8174b6684"
}
@@ -1,44 +0,0 @@
{
"db_name": "SQLite",
"query": "\n SELECT\n date_utc as \"date_utc!\",\n packets_received as \"total_packets_received!: i64\",\n packets_sent as \"total_packets_sent!: i64\",\n packets_dropped as \"total_packets_dropped!: i64\",\n total_stake as \"total_stake!: i64\"\n FROM (\n SELECT\n date_utc,\n SUM(packets_received) as packets_received,\n SUM(packets_sent) as packets_sent,\n SUM(packets_dropped) as packets_dropped,\n SUM(total_stake) as total_stake\n FROM mixnode_daily_stats\n GROUP BY date_utc\n ORDER BY date_utc DESC\n LIMIT 30\n )\n GROUP BY date_utc\n ORDER BY date_utc\n ",
"describe": {
"columns": [
{
"name": "date_utc!",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "total_packets_received!: i64",
"ordinal": 1,
"type_info": "Int64"
},
{
"name": "total_packets_sent!: i64",
"ordinal": 2,
"type_info": "Int64"
},
{
"name": "total_packets_dropped!: i64",
"ordinal": 3,
"type_info": "Int64"
},
{
"name": "total_stake!: i64",
"ordinal": 4,
"type_info": "Int64"
}
],
"parameters": {
"Right": 0
},
"nullable": [
false,
true,
true,
true,
false
]
},
"hash": "c5e3cd7284b334df5aa979b1627ea1f6dc2aed00cedde25f2be3567e47064351"
}
@@ -1,32 +0,0 @@
{
"db_name": "SQLite",
"query": "SELECT\n id as \"id!\",\n identity_key as \"identity_key!\",\n bonded as \"bonded: bool\"\n FROM mixnodes\n WHERE bonded = ?",
"describe": {
"columns": [
{
"name": "id!",
"ordinal": 0,
"type_info": "Int64"
},
{
"name": "identity_key!",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "bonded: bool",
"ordinal": 2,
"type_info": "Int64"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
false,
false
]
},
"hash": "c7ba2621becb9ac4b5dee0ce303dadfcf19095935867a51cbd5b8362d1505fcc"
}
@@ -0,0 +1,20 @@
{
"db_name": "SQLite",
"query": "\n SELECT mix_id\n FROM mixnodes\n WHERE bonded = true\n ",
"describe": {
"columns": [
{
"name": "mix_id",
"ordinal": 0,
"type_info": "Int64"
}
],
"parameters": {
"Right": 0
},
"nullable": [
false
]
},
"hash": "c910788edefe64bbb34379702bcbde9ec6159c9fa03b13652e1f620dcd92125e"
}
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
"query": "\n SELECT\n total_stake\n FROM mixnodes\n WHERE mix_id = ?\n ",
"query": "\n SELECT\n total_stake\n FROM nym_nodes\n WHERE node_id = ?\n ",
"describe": {
"columns": [
{
@@ -16,5 +16,5 @@
false
]
},
"hash": "969acbff4b128d3a3dc998bb5db1a4c8b4c106767e0276435dc23fcfd6acfe4f"
"hash": "d2e07d44594ca5b44a6100482ff432c39d761f2a0ac1d6515cf73416f2eb6c61"
}
@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "INSERT INTO gateways\n (gateway_identity_key, bonded, blacklisted,\n self_described, explorer_pretty_bond,\n last_updated_utc, performance)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(gateway_identity_key) DO UPDATE SET\n bonded=excluded.bonded,\n blacklisted=excluded.blacklisted,\n self_described=excluded.self_described,\n explorer_pretty_bond=excluded.explorer_pretty_bond,\n last_updated_utc=excluded.last_updated_utc,\n performance = excluded.performance;",
"describe": {
"columns": [],
"parameters": {
"Right": 7
},
"nullable": []
},
"hash": "d8ea93e781666e6267902170709ee2aa37f6163525bbdce1a4cebef4a285f8d9"
}
@@ -0,0 +1,32 @@
{
"db_name": "SQLite",
"query": "\n SELECT\n COALESCE(packets_received, 0) as packets_received,\n COALESCE(packets_sent, 0) as packets_sent,\n COALESCE(packets_dropped, 0) as packets_dropped\n FROM mixnode_packet_stats_raw\n WHERE mix_id = ?\n ORDER BY timestamp_utc DESC\n LIMIT 1 OFFSET 1\n ",
"describe": {
"columns": [
{
"name": "packets_received",
"ordinal": 0,
"type_info": "Int64"
},
{
"name": "packets_sent",
"ordinal": 1,
"type_info": "Int64"
},
{
"name": "packets_dropped",
"ordinal": 2,
"type_info": "Int64"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
false,
false
]
},
"hash": "f1f47a4490c3e1330885ef3cf3cda054f2cf760520a46a94db22a02a9cb53dba"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n INSERT INTO gateway_description (\n gateway_identity_key,\n moniker,\n website,\n security_contact,\n details,\n last_updated_utc\n ) VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT (gateway_identity_key) DO UPDATE SET\n moniker = excluded.moniker,\n website = excluded.website,\n security_contact = excluded.security_contact,\n details = excluded.details,\n last_updated_utc = excluded.last_updated_utc\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 6
},
"nullable": []
},
"hash": "f2a7e7229b414a6283685558d1d0731d21b1a92eb39481cf342b87c3b5b0f759"
}
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
"query": "SELECT\n mn.mix_id as \"mix_id!\",\n mn.bonded as \"bonded: bool\",\n mn.blacklisted as \"blacklisted: bool\",\n mn.is_dp_delegatee as \"is_dp_delegatee: bool\",\n mn.total_stake as \"total_stake!\",\n mn.full_details as \"full_details!\",\n mn.self_described as \"self_described\",\n mn.last_updated_utc as \"last_updated_utc!\",\n COALESCE(md.moniker, \"NA\") as \"moniker!\",\n COALESCE(md.website, \"NA\") as \"website!\",\n COALESCE(md.security_contact, \"NA\") as \"security_contact!\",\n COALESCE(md.details, \"NA\") as \"details!\"\n FROM mixnodes mn\n LEFT JOIN mixnode_description md ON mn.mix_id = md.mix_id\n ORDER BY mn.mix_id",
"query": "SELECT\n mn.mix_id as \"mix_id!\",\n mn.bonded as \"bonded: bool\",\n mn.is_dp_delegatee as \"is_dp_delegatee: bool\",\n mn.total_stake as \"total_stake!\",\n mn.full_details as \"full_details!\",\n mn.self_described as \"self_described\",\n mn.last_updated_utc as \"last_updated_utc!\",\n COALESCE(md.moniker, \"NA\") as \"moniker!\",\n COALESCE(md.website, \"NA\") as \"website!\",\n COALESCE(md.security_contact, \"NA\") as \"security_contact!\",\n COALESCE(md.details, \"NA\") as \"details!\"\n FROM mixnodes mn\n LEFT JOIN mixnode_description md ON mn.mix_id = md.mix_id\n ORDER BY mn.mix_id",
"describe": {
"columns": [
{
@@ -14,53 +14,48 @@
"type_info": "Int64"
},
{
"name": "blacklisted: bool",
"name": "is_dp_delegatee: bool",
"ordinal": 2,
"type_info": "Int64"
},
{
"name": "is_dp_delegatee: bool",
"name": "total_stake!",
"ordinal": 3,
"type_info": "Int64"
},
{
"name": "total_stake!",
"ordinal": 4,
"type_info": "Int64"
},
{
"name": "full_details!",
"ordinal": 5,
"ordinal": 4,
"type_info": "Text"
},
{
"name": "self_described",
"ordinal": 6,
"ordinal": 5,
"type_info": "Text"
},
{
"name": "last_updated_utc!",
"ordinal": 7,
"ordinal": 6,
"type_info": "Int64"
},
{
"name": "moniker!",
"ordinal": 8,
"ordinal": 7,
"type_info": "Text"
},
{
"name": "website!",
"ordinal": 9,
"ordinal": 8,
"type_info": "Text"
},
{
"name": "security_contact!",
"ordinal": 10,
"ordinal": 9,
"type_info": "Text"
},
{
"name": "details!",
"ordinal": 11,
"ordinal": 10,
"type_info": "Text"
}
],
@@ -72,7 +67,6 @@
false,
false,
false,
false,
true,
true,
false,
@@ -82,5 +76,5 @@
false
]
},
"hash": "f0a4316081d1be9444a87b95d933d31cb4bcc4071d31d8d2f7755e2d2c2e3e35"
"hash": "f75af377da33db1455c6e0f612e0fa9583888f343b8b59faf37fc6799b244379"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "UPDATE\n gateways\n SET\n bonded = false\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 0
},
"nullable": []
},
"hash": "f7e3fa31d68c028bf39cc95389f29f8758ec922dd2e7ea064a1e537e580c9ee5"
}
@@ -0,0 +1,20 @@
{
"db_name": "SQLite",
"query": "SELECT\n node_id as \"node_id!: i64\"\n FROM\n nym_nodes\n ",
"describe": {
"columns": [
{
"name": "node_id!: i64",
"ordinal": 0,
"type_info": "Int64"
}
],
"parameters": {
"Right": 0
},
"nullable": [
true
]
},
"hash": "fb2fcd26974ce4c3f5055fa2ca8c266223814c19ab2a5763a0575274d684d02e"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n INSERT INTO nym_nodes_packet_stats_raw (\n node_id, timestamp_utc, packets_received, packets_sent, packets_dropped\n ) VALUES (?, ?, ?, ?, ?)\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 5
},
"nullable": []
},
"hash": "fcb1698d9e0e3a14524c92e7c99a811588c2bbc50d4975487a0464321a1b18c9"
}
@@ -3,7 +3,7 @@
[package]
name = "nym-node-status-api"
version = "1.0.2"
version = "1.0.3"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
@@ -5,13 +5,8 @@ set -e
user_rust_log_preference=$RUST_LOG
export ENVIRONMENT=${ENVIRONMENT:-"mainnet"}
export NYM_API_CLIENT_TIMEOUT=60
export EXPLORER_CLIENT_TIMEOUT=60
export NODE_STATUS_API_TESTRUN_REFRESH_INTERVAL=120
if [ "$ENVIRONMENT" = "mainnet" ]; then
export NYM_NODE_STATUS_API_HM_URL="https://harbourmaster.nymtech.net"
fi
# public counterpart of the agent's private key.
# For TESTING only. NOT used in any other environment
export NODE_STATUS_API_AGENT_KEY_LIST="H4z8kx5Kkf5JMQHhxaW1MwYndjKCDHC7HsVhHTFfBZ4J"
@@ -0,0 +1,61 @@
ALTER TABLE nym_nodes ADD COLUMN self_described TEXT;
ALTER TABLE nym_nodes ADD COLUMN bond_info TEXT;
-- # Why recreate tables?
-- I need DELETE with CASCADE functionality, but ALTER TABLE doesn't support
-- adding constraints (which CASCADE is). So I recreate tables with proper
-- constraints and fill them with existing data.
-- To avoid invalidating existing FK references, temporarily disable FK enforcement.
PRAGMA foreign_keys=off;
DROP INDEX IF EXISTS idx_nym_nodes_packet_stats_raw_node_id_timestamp_utc;
ALTER TABLE nym_node_descriptions RENAME TO _nym_node_descriptions_old;
ALTER TABLE nym_nodes_packet_stats_raw RENAME TO _nym_nodes_packet_stats_raw_old;
ALTER TABLE nym_node_daily_mixing_stats RENAME TO _nym_node_daily_mixing_stats_old;
CREATE TABLE nym_node_descriptions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
node_id INTEGER UNIQUE NOT NULL,
moniker VARCHAR,
website VARCHAR,
security_contact VARCHAR,
details VARCHAR,
last_updated_utc INTEGER NOT NULL,
FOREIGN KEY (node_id) REFERENCES nym_nodes (node_id) ON DELETE CASCADE
);
CREATE TABLE nym_nodes_packet_stats_raw (
id INTEGER PRIMARY KEY AUTOINCREMENT,
node_id INTEGER NOT NULL,
timestamp_utc INTEGER NOT NULL,
packets_received INTEGER,
packets_sent INTEGER,
packets_dropped INTEGER,
FOREIGN KEY (node_id) REFERENCES nym_nodes (node_id) ON DELETE CASCADE
);
CREATE INDEX idx_nym_nodes_packet_stats_raw_node_id_timestamp_utc ON nym_nodes_packet_stats_raw (node_id, timestamp_utc);
CREATE TABLE nym_node_daily_mixing_stats (
id INTEGER PRIMARY KEY AUTOINCREMENT,
node_id INTEGER NOT NULL,
total_stake BIGINT NOT NULL,
date_utc VARCHAR NOT NULL,
packets_received INTEGER DEFAULT 0,
packets_sent INTEGER DEFAULT 0,
packets_dropped INTEGER DEFAULT 0,
FOREIGN KEY (node_id) REFERENCES nym_nodes (node_id) ON DELETE CASCADE,
UNIQUE (node_id, date_utc) -- This constraint automatically creates an index
);
INSERT INTO nym_node_descriptions SELECT * FROM _nym_node_descriptions_old;
INSERT INTO nym_nodes_packet_stats_raw SELECT * FROM _nym_nodes_packet_stats_raw_old;
INSERT INTO nym_node_daily_mixing_stats SELECT * FROM _nym_node_daily_mixing_stats_old;
DROP TABLE _nym_node_descriptions_old;
DROP TABLE _nym_nodes_packet_stats_raw_old;
DROP TABLE _nym_node_daily_mixing_stats_old;
PRAGMA foreign_keys=on;
@@ -1,5 +1,9 @@
use anyhow::{anyhow, Result};
use sqlx::{migrate::Migrator, sqlite::SqliteConnectOptions, ConnectOptions, SqlitePool};
use sqlx::{
migrate::Migrator,
sqlite::{SqliteAutoVacuum, SqliteConnectOptions, SqliteSynchronous},
ConnectOptions, SqlitePool,
};
use std::str::FromStr;
pub(crate) mod models;
@@ -16,6 +20,10 @@ pub(crate) struct Storage {
impl Storage {
pub async fn init(connection_url: String) -> Result<Self> {
let connect_options = SqliteConnectOptions::from_str(&connection_url)?
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
.synchronous(SqliteSynchronous::Normal)
.auto_vacuum(SqliteAutoVacuum::Incremental)
.foreign_keys(true)
.create_if_missing(true)
.disable_statement_logging();
@@ -2,20 +2,33 @@ use std::str::FromStr;
use crate::{
http::{self, models::SummaryHistory},
utils::NumericalCheckedCast,
utils::{decimal_to_i64, NumericalCheckedCast},
};
use anyhow::Context;
use nym_contracts_common::Percent;
use nym_crypto::asymmetric::{ed25519, x25519};
use nym_network_defaults::DEFAULT_NYM_NODE_HTTP_PORT;
use nym_node_requests::api::v1::node::models::NodeDescription;
use nym_validator_client::nym_api::SkimmedNode;
use nym_validator_client::{
client::NymNodeDetails, models::NymNodeDescription, nym_api::SkimmedNode,
};
use serde::{Deserialize, Serialize};
use sqlx::FromRow;
use strum_macros::{EnumString, FromRepr};
use time::{Date, OffsetDateTime};
use utoipa::ToSchema;
macro_rules! serialize_opt_to_value {
($var:expr) => {{
match $var {
None => Ok(None),
Some(ref value) => serde_json::to_value(value).map(Some).map_err(|err| {
anyhow::anyhow!("Failed to serialize {}: {:?}", stringify!($var), err)
}),
}
}};
}
pub(crate) struct GatewayInsertRecord {
pub(crate) identity_key: String,
pub(crate) bonded: bool,
@@ -360,6 +373,7 @@ impl TryFrom<GatewaySessionsRecord> for http::models::SessionStats {
}
}
#[derive(strum_macros::Display)]
pub(crate) enum ScrapeNodeKind {
LegacyMixnode { mix_id: i64 },
MixingNymNode { node_id: i64 },
@@ -406,11 +420,11 @@ impl ScraperNodeInfo {
}
}
#[allow(dead_code)] // it's not dead code but clippy doesn't detect usage in sqlx macros
#[derive(sqlx::Decode, Debug)]
pub(crate) struct NymNodeDto {
pub node_id: i64,
pub ed25519_identity_pubkey: String,
#[allow(dead_code)] // it's not dead code but clippy doesn't detect usage in sqlx macros
pub total_stake: i64,
pub ip_addresses: serde_json::Value,
pub mix_port: i64,
@@ -419,8 +433,11 @@ pub(crate) struct NymNodeDto {
pub supported_roles: serde_json::Value,
pub entry: Option<serde_json::Value>,
pub performance: String,
pub self_described: Option<serde_json::Value>,
pub bond_info: Option<serde_json::Value>,
}
#[allow(dead_code)] // it's not dead code but clippy doesn't detect usage in sqlx macros
#[derive(Debug)]
pub(crate) struct NymNodeInsertRecord {
pub node_id: i64,
@@ -433,13 +450,27 @@ pub(crate) struct NymNodeInsertRecord {
pub supported_roles: serde_json::Value,
pub performance: String,
pub entry: Option<serde_json::Value>,
pub self_described: Option<serde_json::Value>,
pub bond_info: Option<serde_json::Value>,
pub last_updated_utc: String,
}
impl NymNodeInsertRecord {
pub fn new(skimmed_node: SkimmedNode, total_stake: i64) -> anyhow::Result<Self> {
pub fn new(
skimmed_node: SkimmedNode,
bond_info: Option<&NymNodeDetails>,
self_described: Option<&NymNodeDescription>,
) -> anyhow::Result<Self> {
let now = OffsetDateTime::now_utc().to_string();
// if bond info is missing, set stake to 0
let total_stake = bond_info
.map(|info| decimal_to_i64(info.total_stake()))
.unwrap_or(0);
let entry = serialize_opt_to_value!(skimmed_node.entry)?;
let bond_info = serialize_opt_to_value!(bond_info)?;
let self_described = serialize_opt_to_value!(self_described)?;
let record = Self {
node_id: skimmed_node.node_id.into(),
ed25519_identity_pubkey: skimmed_node.ed25519_identity_pubkey.to_base58_string(),
@@ -450,10 +481,9 @@ impl NymNodeInsertRecord {
node_role: serde_json::to_value(&skimmed_node.role)?,
supported_roles: serde_json::to_value(skimmed_node.supported_roles)?,
performance: skimmed_node.performance.value().to_string(),
entry: match skimmed_node.entry {
Some(entry) => Some(serde_json::to_value(entry)?),
None => None,
},
entry,
self_described,
bond_info,
last_updated_utc: now,
};
@@ -30,11 +30,22 @@ pub(crate) async fn select_gateway_identity(
Ok(record.gateway_identity_key)
}
pub(crate) async fn insert_gateways(
pub(crate) async fn update_bonded_gateways(
pool: &DbPool,
gateways: Vec<GatewayInsertRecord>,
) -> anyhow::Result<()> {
let mut db = pool.acquire().await?;
let mut tx = pool.begin().await?;
sqlx::query!(
r#"UPDATE
gateways
SET
bonded = false
"#,
)
.execute(&mut *tx)
.await?;
for record in gateways {
sqlx::query!(
"INSERT INTO gateways
@@ -55,10 +66,12 @@ pub(crate) async fn insert_gateways(
record.last_updated_utc,
record.performance
)
.execute(&mut *db)
.execute(&mut *tx)
.await?;
}
tx.commit().await?;
Ok(())
}
@@ -101,7 +114,7 @@ pub(crate) async fn get_all_gateways(pool: &DbPool) -> anyhow::Result<Vec<Gatewa
Ok(items)
}
pub(crate) async fn get_all_gateway_id_keys(pool: &DbPool) -> anyhow::Result<HashSet<String>> {
pub(crate) async fn get_bonded_gateway_id_keys(pool: &DbPool) -> anyhow::Result<HashSet<String>> {
let mut conn = pool.acquire().await?;
let items = sqlx::query!(
r#"
@@ -11,12 +11,24 @@ use crate::{
http::models::{DailyStats, Mixnode},
};
pub(crate) async fn insert_mixnodes(
pub(crate) async fn update_mixnodes(
pool: &DbPool,
mixnodes: Vec<MixnodeRecord>,
) -> anyhow::Result<()> {
let mut conn = pool.acquire().await?;
let mut tx = pool.begin().await?;
// mark all as unbonded
sqlx::query!(
r#"UPDATE
mixnodes
SET
bonded = false
"#,
)
.execute(&mut *tx)
.await?;
// existing nodes get updated on insert
for record in mixnodes.iter() {
// https://www.sqlite.org/lang_upsert.html
sqlx::query!(
@@ -43,10 +55,12 @@ pub(crate) async fn insert_mixnodes(
record.last_updated_utc,
record.is_dp_delegatee
)
.execute(&mut *conn)
.execute(&mut *tx)
.await?;
}
tx.commit().await?;
Ok(())
}
@@ -126,7 +140,7 @@ pub(crate) async fn get_daily_stats(pool: &DbPool) -> anyhow::Result<Vec<DailySt
Ok(items)
}
pub(crate) async fn get_all_mix_ids(pool: &DbPool) -> anyhow::Result<HashSet<i64>> {
pub(crate) async fn get_bonded_mix_ids(pool: &DbPool) -> anyhow::Result<HashSet<i64>> {
let mut conn = pool.acquire().await?;
let items = sqlx::query!(
r#"
@@ -9,12 +9,15 @@ mod summary;
pub(crate) mod testruns;
pub(crate) use gateways::{
get_all_gateway_id_keys, get_all_gateways, insert_gateways, select_gateway_identity,
get_all_gateways, get_bonded_gateway_id_keys, select_gateway_identity, update_bonded_gateways,
};
pub(crate) use gateways_stats::{delete_old_records, get_sessions_stats, insert_session_records};
pub(crate) use misc::insert_summaries;
pub(crate) use mixnodes::{get_all_mix_ids, get_all_mixnodes, get_daily_stats, insert_mixnodes};
pub(crate) use nym_nodes::{get_nym_nodes, insert_nym_nodes};
pub(crate) use mixnodes::{get_all_mixnodes, get_bonded_mix_ids, get_daily_stats, update_mixnodes};
pub(crate) use nym_nodes::{
get_active_nym_nodes, get_all_nym_nodes, get_described_node_bond_info, get_node_descriptions,
update_nym_nodes,
};
pub(crate) use packet_stats::{
get_raw_node_stats, insert_daily_node_stats, insert_node_packet_stats,
};
@@ -1,22 +1,21 @@
use std::collections::HashMap;
use anyhow::Context;
use futures_util::TryStreamExt;
use nym_validator_client::{client::NymNodeDetails, nym_api::SkimmedNode};
use nym_validator_client::{
client::{NodeId, NymNodeDetails},
models::NymNodeDescription,
};
use std::collections::{HashMap, HashSet};
use tracing::instrument;
use crate::{
db::{
models::{NymNodeDto, NymNodeInsertRecord},
DbPool,
},
utils::decimal_to_i64,
use crate::db::{
models::{NymNodeDto, NymNodeInsertRecord},
DbPool,
};
pub(crate) async fn get_nym_nodes(pool: &DbPool) -> anyhow::Result<Vec<SkimmedNode>> {
pub(crate) async fn get_all_nym_nodes(pool: &DbPool) -> anyhow::Result<Vec<NymNodeDto>> {
let mut conn = pool.acquire().await?;
let items = sqlx::query_as!(
sqlx::query_as!(
NymNodeDto,
r#"SELECT
node_id,
@@ -28,44 +27,77 @@ pub(crate) async fn get_nym_nodes(pool: &DbPool) -> anyhow::Result<Vec<SkimmedNo
node_role as "node_role: serde_json::Value",
supported_roles as "supported_roles: serde_json::Value",
entry as "entry: serde_json::Value",
performance
performance,
self_described as "self_described: serde_json::Value",
bond_info as "bond_info: serde_json::Value"
FROM
nym_nodes
"#,
)
.fetch(&mut *conn)
.try_collect::<Vec<NymNodeDto>>()
.await?;
let mut skimmed_nodes = Vec::new();
for item in items {
let node_id = item.node_id;
match SkimmedNode::try_from(item) {
Ok(node) => skimmed_nodes.push(node),
Err(e) => {
tracing::warn!("Failed to decode node_id={}: {}", node_id, e);
}
}
}
Ok(skimmed_nodes)
.await
.map_err(From::from)
}
#[instrument(level = "debug", skip_all)]
pub(crate) async fn insert_nym_nodes(
pool: &DbPool,
nym_nodes: Vec<SkimmedNode>,
bonded_node_info: &HashMap<u32, NymNodeDetails>,
) -> anyhow::Result<()> {
/// if a node doesn't expose its self-described endpoint, it can't route traffic
/// - https://nym.com/docs/operators/nodes/nym-node/bonding
///
/// same if it's not bonded in the mixnet smart contract
/// - https://nym.com/docs/operators/tokenomics/mixnet-rewards#rewarded-set-selection
pub(crate) async fn get_active_nym_nodes(pool: &DbPool) -> anyhow::Result<Vec<NymNodeDto>> {
let mut conn = pool.acquire().await?;
for nym_node in nym_nodes.into_iter() {
let total_stake = bonded_node_info
.get(&nym_node.node_id)
.map(|details| decimal_to_i64(details.total_stake()))
.unwrap_or(0);
sqlx::query_as!(
NymNodeDto,
r#"SELECT
node_id,
ed25519_identity_pubkey,
total_stake,
ip_addresses as "ip_addresses!: serde_json::Value",
mix_port,
x25519_sphinx_pubkey,
node_role as "node_role: serde_json::Value",
supported_roles as "supported_roles: serde_json::Value",
entry as "entry: serde_json::Value",
performance,
self_described as "self_described: serde_json::Value",
bond_info as "bond_info: serde_json::Value"
FROM
nym_nodes
WHERE
self_described IS NOT NULL
AND
bond_info IS NOT NULL
"#,
)
.fetch(&mut *conn)
.try_collect::<Vec<NymNodeDto>>()
.await
.map_err(From::from)
}
let record = NymNodeInsertRecord::new(nym_node, total_stake)?;
#[instrument(level = "debug", skip_all, fields(node_records=node_records.len()))]
pub(crate) async fn update_nym_nodes(
pool: &DbPool,
node_records: Vec<NymNodeInsertRecord>,
) -> anyhow::Result<usize> {
let mut tx = pool.begin().await?;
let mut nodes_to_delete = sqlx::query!(
r#"SELECT
node_id as "node_id!: i64"
FROM
nym_nodes
"#,
)
.fetch_all(&mut *tx)
.await
.map(|records| records.into_iter().map(|record| record.node_id as NodeId))?
.collect::<HashSet<NodeId>>();
let inserted = node_records.len();
for record in node_records {
// https://www.sqlite.org/lang_upsert.html
sqlx::query!(
"INSERT INTO nym_nodes
@@ -74,9 +106,11 @@ pub(crate) async fn insert_nym_nodes(
ip_addresses, mix_port,
x25519_sphinx_pubkey, node_role,
supported_roles, entry,
self_described,
bond_info,
performance, last_updated_utc
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(node_id) DO UPDATE SET
ed25519_identity_pubkey=excluded.ed25519_identity_pubkey,
ip_addresses=excluded.ip_addresses,
@@ -85,6 +119,8 @@ pub(crate) async fn insert_nym_nodes(
node_role=excluded.node_role,
supported_roles=excluded.supported_roles,
entry=excluded.entry,
self_described=excluded.self_described,
bond_info=excluded.bond_info,
performance=excluded.performance,
last_updated_utc=excluded.last_updated_utc
;",
@@ -97,13 +133,104 @@ pub(crate) async fn insert_nym_nodes(
record.node_role,
record.supported_roles,
record.entry,
record.self_described,
record.bond_info,
record.performance,
record.last_updated_utc,
)
.execute(&mut *conn)
.execute(&mut *tx)
.await
.with_context(|| format!("node_id={}", record.node_id))?;
.with_context(|| format!("Failed to INSERT node_id={}", record.node_id))?;
// if node was updated, remove it from the list
nodes_to_delete.remove(&(record.node_id as NodeId));
}
Ok(())
if !nodes_to_delete.is_empty() {
tracing::debug!("DELETING {} obsolete nodes", nodes_to_delete.len());
}
// clean up leftover nodes, which weren't inserted/updated
for node_id in nodes_to_delete {
sqlx::query!(
"DELETE FROM nym_nodes
WHERE node_id = ?",
node_id,
)
.execute(&mut *tx)
.await
.map_err(|e| anyhow::anyhow!("Failed to DELETE node_id={}: {}", node_id, e))?;
}
tx.commit().await?;
Ok(inserted)
}
pub(crate) async fn get_described_node_bond_info(
pool: &DbPool,
) -> anyhow::Result<HashMap<NodeId, NymNodeDetails>> {
let mut conn = pool.acquire().await?;
sqlx::query!(
r#"SELECT
node_id,
bond_info as "bond_info: serde_json::Value"
FROM
nym_nodes
WHERE
bond_info IS NOT NULL
AND
self_described IS NOT NULL
"#,
)
.fetch_all(&mut *conn)
.await
.map(|records| {
records
.into_iter()
.filter_map(|record| {
record
.bond_info
// only return details for nodes which have details stored
.and_then(|bond_info| serde_json::from_value::<NymNodeDetails>(bond_info).ok())
.map(|res| (record.node_id as NodeId, res))
})
.collect::<HashMap<_, _>>()
})
.map_err(From::from)
}
pub(crate) async fn get_node_descriptions(
pool: &DbPool,
) -> anyhow::Result<HashMap<NodeId, NymNodeDescription>> {
let mut conn = pool.acquire().await?;
sqlx::query!(
r#"SELECT
node_id,
self_described as "self_described: serde_json::Value"
FROM
nym_nodes
WHERE
self_described IS NOT NULL
"#,
)
.fetch_all(&mut *conn)
.await
.map(|records| {
records
.into_iter()
.filter_map(|record| {
record
.self_described
// only return details for nodes which have details stored
.and_then(|description| {
serde_json::from_value::<NymNodeDescription>(description).ok()
})
.map(|res| (record.node_id as NodeId, res))
})
.collect::<HashMap<_, _>>()
})
.map_err(From::from)
}
@@ -7,45 +7,56 @@ use crate::{
};
use anyhow::Result;
use chrono::Utc;
use nym_validator_client::nym_api::SkimmedNode;
pub(crate) async fn get_nodes_for_scraping(pool: &DbPool) -> Result<Vec<ScraperNodeInfo>> {
let mut nodes_to_scrape = Vec::new();
let mixnode_ids = queries::get_all_mix_ids(pool).await?;
let gateway_keys = queries::get_all_gateway_id_keys(pool).await?;
let mixnode_ids = queries::get_bonded_mix_ids(pool).await?;
let gateway_keys = queries::get_bonded_gateway_id_keys(pool).await?;
let mut entry_exit_nodes = 0;
queries::get_nym_nodes(pool)
.await?
.into_iter()
.for_each(|node| {
// due to polyfilling, Nym nodes table might contain legacy mixnodes
// as well. Mark them as such here.
let node_kind = if mixnode_ids.contains(&node.node_id.into()) {
ScrapeNodeKind::LegacyMixnode {
mix_id: node.node_id.into(),
let skimmed_nodes = queries::get_active_nym_nodes(pool).await.map(|nodes_dto| {
nodes_dto.into_iter().filter_map(|node| {
let node_id = node.node_id;
match SkimmedNode::try_from(node) {
Ok(node) => Some(node),
Err(e) => {
tracing::error!("Failed to decode node_id={}: {}", node_id, e);
None
}
} else if gateway_keys.contains(&node.ed25519_identity_pubkey.to_base58_string()) {
entry_exit_nodes += 1;
ScrapeNodeKind::EntryExitNymNode {
node_id: node.node_id.into(),
identity_key: node.ed25519_identity_pubkey.to_base58_string(),
}
} else {
ScrapeNodeKind::MixingNymNode {
node_id: node.node_id.into(),
}
};
nodes_to_scrape.push(ScraperNodeInfo {
node_kind,
hosts: node
.ip_addresses
.into_iter()
.map(|ip| ip.to_string())
.collect::<Vec<_>>(),
http_api_port: node.mix_port.into(),
})
});
}
})
})?;
skimmed_nodes.for_each(|node| {
// TODO: relies on polyfilling: Nym nodes table might contain legacy mixnodes
// as well. Categorize them here.
let node_kind = if mixnode_ids.contains(&node.node_id.into()) {
ScrapeNodeKind::LegacyMixnode {
mix_id: node.node_id.into(),
}
} else if gateway_keys.contains(&node.ed25519_identity_pubkey.to_base58_string()) {
entry_exit_nodes += 1;
ScrapeNodeKind::EntryExitNymNode {
node_id: node.node_id.into(),
identity_key: node.ed25519_identity_pubkey.to_base58_string(),
}
} else {
ScrapeNodeKind::MixingNymNode {
node_id: node.node_id.into(),
}
};
nodes_to_scrape.push(ScraperNodeInfo {
node_kind,
hosts: node
.ip_addresses
.into_iter()
.map(|ip| ip.to_string())
.collect::<Vec<_>>(),
http_api_port: node.mix_port.into(),
})
});
tracing::debug!("Fetched {} 🌟 total nym nodes", nodes_to_scrape.len());
tracing::debug!("Fetched {} 🚪 entry/exit nodes", entry_exit_nodes);
@@ -10,6 +10,7 @@ use crate::http::{server::HttpServer, state::AppState};
pub(crate) mod gateways;
pub(crate) mod metrics;
pub(crate) mod mixnodes;
pub(crate) mod nym_nodes;
pub(crate) mod services;
pub(crate) mod summary;
pub(crate) mod testruns;
@@ -38,6 +39,7 @@ impl RouterBuilder {
.nest("/summary", summary::routes())
.nest("/metrics", metrics::routes()),
)
.nest("/v3", Router::new().nest("/nym-nodes", nym_nodes::routes()))
.nest(
"/internal",
Router::new().nest("/testruns", testruns::routes()),
@@ -0,0 +1,42 @@
use axum::{
extract::{Query, State},
Json, Router,
};
use tracing::instrument;
use crate::http::{
error::{HttpError, HttpResult},
models::ExtendedNymNode,
state::AppState,
PagedResult, Pagination,
};
pub(crate) fn routes() -> Router<AppState> {
Router::new().route("/", axum::routing::get(nym_nodes))
}
#[utoipa::path(
tag = "Nym Nodes",
get,
params(
Pagination
),
path = "/v3/nym-nodes",
responses(
(status = 200, body = PagedResult<ExtendedNymNode>)
)
)]
#[instrument(level = tracing::Level::DEBUG, skip_all, fields(page=pagination.page, size=pagination.size))]
async fn nym_nodes(
Query(pagination): Query<Pagination>,
State(state): State<AppState>,
) -> HttpResult<Json<PagedResult<ExtendedNymNode>>> {
let db = state.db_pool();
let nodes = state.cache().get_nym_nodes_list(db).await.map_err(|e| {
tracing::error!("{e}");
HttpError::internal()
})?;
Ok(Json(PagedResult::paginate(pagination, nodes)))
}
@@ -1,4 +1,6 @@
use cosmwasm_std::Decimal;
use nym_node_requests::api::v1::node::models::NodeDescription;
use nym_validator_client::client::NodeId;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
@@ -45,6 +47,23 @@ pub struct Mixnode {
pub last_updated_utc: String,
}
#[derive(Clone, Debug, utoipa::ToSchema, Deserialize, Serialize)]
pub(crate) struct ExtendedNymNode {
pub(crate) node_id: NodeId,
pub(crate) identity_key: String,
pub(crate) uptime: f64,
#[schema(value_type = String)]
pub(crate) total_stake: Decimal,
pub(crate) original_pledge: u128,
pub(crate) bonding_address: String,
pub(crate) bonded: bool,
pub(crate) node_type: String,
pub(crate) ip_address: String,
pub(crate) accepted_tnc: bool,
pub(crate) description: serde_json::Value,
pub(crate) rewarding_details: serde_json::Value,
}
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
pub struct DailyStats {
pub date_utc: String,
@@ -1,12 +1,16 @@
use std::{sync::Arc, time::Duration};
use std::{collections::HashMap, sync::Arc, time::Duration};
use cosmwasm_std::Decimal;
use moka::{future::Cache, Entry};
use nym_contracts_common::NaiveFloat;
use nym_crypto::asymmetric::ed25519::PublicKey;
use nym_validator_client::{models::DescribedNodeType, nym_api::SkimmedNode};
use tokio::sync::RwLock;
use tracing::instrument;
use crate::{
db::DbPool,
http::models::{DailyStats, Gateway, Mixnode, SummaryHistory},
db::{queries, DbPool},
http::models::{DailyStats, ExtendedNymNode, Gateway, Mixnode, SummaryHistory},
};
use super::models::SessionStats;
@@ -53,6 +57,7 @@ impl AppState {
static GATEWAYS_LIST_KEY: &str = "gateways";
static MIXNODES_LIST_KEY: &str = "mixnodes";
static NYM_NODES_LIST_KEY: &str = "nym_nodes";
static MIXSTATS_LIST_KEY: &str = "mixstats";
static SUMMARY_HISTORY_LIST_KEY: &str = "summary-history";
static SESSION_STATS_LIST_KEY: &str = "session-stats";
@@ -63,6 +68,7 @@ const MIXNODE_STATS_HISTORY_DAYS: usize = 30;
pub(crate) struct HttpCache {
gateways: Cache<String, Arc<RwLock<Vec<Gateway>>>>,
mixnodes: Cache<String, Arc<RwLock<Vec<Mixnode>>>>,
nym_nodes: Cache<String, Arc<RwLock<Vec<ExtendedNymNode>>>>,
mixstats: Cache<String, Arc<RwLock<Vec<DailyStats>>>>,
history: Cache<String, Arc<RwLock<Vec<SummaryHistory>>>>,
session_stats: Cache<String, Arc<RwLock<Vec<SessionStats>>>>,
@@ -79,6 +85,10 @@ impl HttpCache {
.max_capacity(2)
.time_to_live(Duration::from_secs(ttl_seconds))
.build(),
nym_nodes: Cache::builder()
.max_capacity(2)
.time_to_live(Duration::from_secs(ttl_seconds))
.build(),
mixstats: Cache::builder()
.max_capacity(2)
.time_to_live(Duration::from_secs(ttl_seconds))
@@ -181,6 +191,47 @@ impl HttpCache {
}
}
pub async fn upsert_nym_node_list(
&self,
nym_node_list: Vec<ExtendedNymNode>,
) -> Entry<String, Arc<RwLock<Vec<ExtendedNymNode>>>> {
self.nym_nodes
.entry_by_ref(NYM_NODES_LIST_KEY)
.and_upsert_with(|maybe_entry| async {
if let Some(entry) = maybe_entry {
let v = entry.into_value();
let mut guard = v.write().await;
*guard = nym_node_list;
v.clone()
} else {
Arc::new(RwLock::new(nym_node_list))
}
})
.await
}
pub async fn get_nym_nodes_list(&self, db: &DbPool) -> anyhow::Result<Vec<ExtendedNymNode>> {
match self.nym_nodes.get(NYM_NODES_LIST_KEY).await {
Some(guard) => {
tracing::trace!("Fetching from cache...");
let read_lock = guard.read().await;
Ok(read_lock.clone())
}
None => {
tracing::trace!("No nym nodes in cache, refreshing cache from DB...");
let nym_nodes = aggregate_node_info_from_db(db).await.inspect(|nym_nodes| {
if nym_nodes.is_empty() {
tracing::warn!("Database contains 0 nym nodes");
}
})?;
self.upsert_nym_node_list(nym_nodes.clone()).await;
Ok(nym_nodes)
}
}
}
pub async fn upsert_mixnode_stats(
&self,
mixnode_stats: Vec<DailyStats>,
@@ -293,3 +344,81 @@ impl HttpCache {
.await
}
}
#[instrument(level = "info", skip_all)]
async fn aggregate_node_info_from_db(pool: &DbPool) -> anyhow::Result<Vec<ExtendedNymNode>> {
let node_bond_info = queries::get_described_node_bond_info(pool).await?;
tracing::debug!("Described nodes with bond info: {}", node_bond_info.len());
let skimmed_nodes = queries::get_all_nym_nodes(pool).await.map(|records| {
records
.into_iter()
.filter_map(|dto| SkimmedNode::try_from(dto).ok())
.map(|skimmed_node| (skimmed_node.node_id, skimmed_node))
.collect::<HashMap<_, _>>()
})?;
tracing::debug!("Skimmed nodes: {}", skimmed_nodes.len());
let described_nodes = queries::get_node_descriptions(pool).await?;
tracing::debug!("Described nodes: {}", described_nodes.len());
let mut parsed_nym_nodes = Vec::new();
for (node_id, described_node) in described_nodes {
let bond_details = node_bond_info.get(&node_id);
let bonded = bond_details.is_some();
let total_stake = bond_details
.map(|details| details.total_stake())
.unwrap_or(Decimal::zero());
let identity_key = described_node.ed25519_identity_key().to_string();
let original_pledge = bond_details
.map(|details| details.original_pledge().amount.u128())
.unwrap_or(0u128);
let rewarding_details = &node_bond_info
.get(&node_id)
.map(|details| details.rewarding_details.clone());
let uptime = skimmed_nodes
.get(&node_id)
.map(|node| node.performance.naive_to_f64())
.unwrap_or(0.0);
let node_type = match described_node.contract_node_type {
DescribedNodeType::NymNode => "nym_node".to_string(),
DescribedNodeType::LegacyMixnode => "legacy_mixnode".to_string(),
DescribedNodeType::LegacyGateway => "legacy_gateway".to_string(),
};
let ip_address = described_node
.description
.host_information
.ip_address
.first()
.map(ToString::to_string)
.unwrap_or_default();
let accepted_tnc = described_node
.description
.auxiliary_details
.accepted_operator_terms_and_conditions;
let description = described_node.description;
let bonding_address = bond_details
.map(|details| details.bond_information.owner.to_string())
.unwrap_or_default();
parsed_nym_nodes.push(ExtendedNymNode {
node_id,
identity_key,
total_stake,
uptime,
ip_address,
original_pledge,
bonding_address,
bonded,
node_type,
accepted_tnc,
description: serde_json::to_value(description).unwrap_or_default(),
rewarding_details: serde_json::to_value(rewarding_details).unwrap_or_default(),
});
}
Ok(parsed_nym_nodes)
}
@@ -134,8 +134,9 @@ impl Scraper {
node.node_id()
),
Err(e) => debug!(
"📝 ❌ Description task #{} for node {} failed: {}",
"📝 ❌ Description task #{} for {} {} failed: {}",
task_id,
node.node_kind,
node.node_id(),
e
),
@@ -178,8 +179,9 @@ impl Scraper {
node.node_id()
),
Err(e) => debug!(
"📊 ❌ Packet stats task #{} for node {} failed: {}",
"📊 ❌ Packet stats task #{} for {} {} failed: {}",
task_id,
node.node_kind,
node.node_id(),
e
),
@@ -1,9 +1,10 @@
#![allow(deprecated)]
use crate::db::models::{
gateway, mixnode, GatewayInsertRecord, MixnodeRecord, NetworkSummary, ASSIGNED_ENTRY_COUNT,
ASSIGNED_EXIT_COUNT, ASSIGNED_MIXING_COUNT, GATEWAYS_BONDED_COUNT, GATEWAYS_HISTORICAL_COUNT,
MIXNODES_HISTORICAL_COUNT, MIXNODES_LEGACY_COUNT, NYMNODES_DESCRIBED_COUNT, NYMNODE_COUNT,
gateway, mixnode, GatewayInsertRecord, MixnodeRecord, NetworkSummary, NymNodeInsertRecord,
ASSIGNED_ENTRY_COUNT, ASSIGNED_EXIT_COUNT, ASSIGNED_MIXING_COUNT, GATEWAYS_BONDED_COUNT,
GATEWAYS_HISTORICAL_COUNT, MIXNODES_HISTORICAL_COUNT, MIXNODES_LEGACY_COUNT,
NYMNODES_DESCRIBED_COUNT, NYMNODE_COUNT,
};
use crate::db::{queries, DbPool};
use crate::monitor::geodata::{Location, NodeGeoData};
@@ -11,7 +12,7 @@ use crate::utils::{decimal_to_i64, LogError, NumericalCheckedCast};
use anyhow::anyhow;
use moka::future::Cache;
use nym_network_defaults::NymNetworkDetails;
use nym_validator_client::client::{NodeId, NymApiClientExt};
use nym_validator_client::client::{NodeId, NymApiClientExt, NymNodeDetails};
use nym_validator_client::models::{
LegacyDescribedMixNode, MixNodeBondAnnotated, NymNodeDescription,
};
@@ -107,19 +108,24 @@ impl Monitor {
let described_nodes = api_client
.get_all_described_nodes()
.await
.log_error("get_all_described_nodes")?;
.log_error("get_all_described_nodes")?
.into_iter()
.map(|elem| (elem.node_id, elem))
.collect::<HashMap<_, _>>();
tracing::info!("🟣 described nodes: {}", described_nodes.len());
let gateways = described_nodes
.iter()
.filter(|node| {
node.description.declared_role.entry
|| node.description.declared_role.exit_ipr
|| node.description.declared_role.exit_nr
.filter_map(|(_, node)| {
if node.description.declared_role.entry || node.description.declared_role.exit_ipr {
Some(node)
} else {
None
}
})
.collect::<Vec<_>>();
let bonded_node_info = api_client
let bonded_nym_nodes = api_client
.get_all_bonded_nym_nodes()
.await?
.into_iter()
@@ -127,22 +133,27 @@ impl Monitor {
// for faster reads
.collect::<HashMap<_, _>>();
tracing::info!("🟣 bonded_nodes: {}", bonded_node_info.len());
tracing::info!("🟣 bonded_nodes: {}", bonded_nym_nodes.len());
// returns only bonded nodes
let nym_nodes = api_client
.get_all_basic_nodes()
.await
.log_error("get_all_basic_nodes")?;
queries::insert_nym_nodes(&self.db_pool, nym_nodes.clone(), &bonded_node_info)
tracing::info!("🟣 get_all_basic_nodes: {}", nym_nodes.len());
let nym_node_records =
self.prepare_nym_node_data(nym_nodes.clone(), &bonded_nym_nodes, &described_nodes);
queries::update_nym_nodes(&self.db_pool, nym_node_records)
.await
.map(|_| {
tracing::debug!("{} nym nodes written to DB!", nym_nodes.len());
.map(|inserted| {
tracing::debug!("{} nym nodes written to DB!", inserted);
})?;
let mut gateway_geodata = Vec::new();
for gateway in gateways.iter() {
if let Some(node_details) = bonded_node_info.get(&gateway.node_id) {
if let Some(node_details) = bonded_nym_nodes.get(&gateway.node_id) {
let bond_info = &node_details.bond_information;
let gw_geodata = NodeGeoData {
identity_key: bond_info.node.identity_key.to_owned(),
@@ -170,6 +181,7 @@ impl Monitor {
.map(|elem| elem.identity_key().to_owned())
.collect::<HashSet<_>>();
// TODO this assumes polyfilling of legacy mixnodes on Nym API
let mixnodes_legacy = nym_nodes
.iter()
.filter(|node| {
@@ -208,11 +220,12 @@ impl Monitor {
let assigned_mixing_count = mixing_assigned_nodes.len();
let count_legacy_mixnodes = mixnodes_legacy.len();
let gateway_records = self.prepare_gateway_data(&gateways, gateway_geodata, &nym_nodes)?;
let gateway_records =
self.prepare_gateway_data(&gateways, gateway_geodata, &nym_nodes, &bonded_nym_nodes)?;
let pool = self.db_pool.clone();
let gateways_count = gateway_records.len();
queries::insert_gateways(&pool, gateway_records)
queries::update_bonded_gateways(&pool, gateway_records)
.await
.map(|_| {
tracing::debug!("{} gateway records written to DB!", gateways_count);
@@ -224,7 +237,7 @@ impl Monitor {
delegation_program_members,
)?;
let mixnodes_count = mixnode_records.len();
queries::insert_mixnodes(&pool, mixnode_records)
queries::update_mixnodes(&pool, mixnode_records)
.await
.map(|_| {
tracing::debug!("{} mixnode info written to DB!", mixnodes_count);
@@ -317,17 +330,45 @@ impl Monitor {
}
}
fn prepare_nym_node_data(
&self,
skimmed_nodes: Vec<SkimmedNode>,
bonded_node_info: &HashMap<NodeId, NymNodeDetails>,
described_nodes: &HashMap<NodeId, NymNodeDescription>,
) -> Vec<NymNodeInsertRecord> {
skimmed_nodes
.into_iter()
.filter_map(|skimmed_node| {
let node_id = skimmed_node.node_id;
let bond_info = bonded_node_info.get(&skimmed_node.node_id);
let self_described = described_nodes.get(&skimmed_node.node_id);
match NymNodeInsertRecord::new(skimmed_node, bond_info, self_described) {
Ok(record) => Some(record),
Err(err) => {
tracing::error!(
"Failed to create insert record for node {}: {}",
node_id,
err
);
None
}
}
})
.collect::<Vec<_>>()
}
fn prepare_gateway_data(
&self,
described_gateways: &[&NymNodeDescription],
gateway_geodata: Vec<NodeGeoData>,
skimmed_gateways: &[SkimmedNode],
bonded_nodes: &HashMap<NodeId, NymNodeDetails>,
) -> anyhow::Result<Vec<GatewayInsertRecord>> {
let mut gateway_records = Vec::new();
for gateway in described_gateways {
let identity_key = gateway.ed25519_identity_key().to_base58_string();
let bonded = true;
let bonded = bonded_nodes.contains_key(&gateway.node_id);
let last_updated_utc = chrono::offset::Utc::now().timestamp();
let self_described = serde_json::to_string(&gateway.description)?;
@@ -373,6 +414,7 @@ impl Monitor {
for mixnode in mixnodes {
let mix_id = mixnode.mix_id();
let identity_key = mixnode.identity_key();
// only bonded nodes are given to this function
let bonded = true;
let total_stake = decimal_to_i64(mixnode.mixnode_details.total_stake());
let node_info = mixnode.mix_node();
@@ -24,6 +24,7 @@ pub(crate) async fn try_queue_testrun(
explorer_pretty_bond as "explorer_pretty_bond?"
FROM gateways
WHERE gateway_identity_key = ?
AND bonded = true
ORDER BY gateway_identity_key
LIMIT 1"#,
identity_key,
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-node"
version = "1.6.2"
version = "1.7.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
+1 -1
View File
@@ -45,7 +45,7 @@ impl NetworkStats {
pub fn active_ingress_websocket_connections_count(&self) -> usize {
self.active_ingress_websocket_connections
.load(Ordering::Relaxed)
.load(Ordering::SeqCst)
}
pub fn active_egress_mixnet_connections_counter(&self) -> Arc<AtomicUsize> {
@@ -0,0 +1,239 @@
#!/bin/bash
# Nym Exit Policy Verification Unit Tests
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[0;33m'
NC='\033[0m'
NYM_CHAIN="NYM-EXIT"
WG_INTERFACE="nymwg"
check_port_range_rules() {
local port_range="$1"
local protocol="${2:-tcp}"
local chain="${3:-$NYM_CHAIN}"
# Extract start and end ports
local start_port=$(echo "$port_range" | cut -d'-' -f1)
local end_port=$(echo "$port_range" | cut -d'-' -f2)
if iptables -t filter -C "$chain" -p "$protocol" --dport "$start_port:$end_port" -j ACCEPT 2>/dev/null; then
echo -e "${GREEN}✓ Rule exists: $chain $protocol port range $start_port:$end_port${NC}"
return 0
else
echo -e "${RED}✗ Rule missing: $chain $protocol port range $start_port:$end_port${NC}"
echo -e "${YELLOW}Dumping all rules in $chain:${NC}"
iptables -L "$chain" -n | grep "$protocol"
return 1
fi
}
# Test port range rules
test_port_range_rules() {
echo -e "${YELLOW}Testing Port Range Rules...${NC}"
# Select the essential port ranges for testing
local port_ranges=(
"20-21:tcp:FTP"
"80-81:tcp:HTTP"
"2082-2083:tcp:CPanel"
"5222-5223:tcp:XMPP"
"27000-27050:tcp:Steam (sampling)"
"989-990:tcp:FTP over TLS"
"5000-5005:tcp:RTP/VoIP"
"8087-8088:tcp:Simplify Media"
"8232-8233:tcp:Zcash"
"8332-8333:tcp:Bitcoin"
)
local total_failures=0
for range in "${port_ranges[@]}"; do
IFS=':' read -r port_range protocol service <<< "$range"
# Extract start and end ports
local start_port=$(echo "$port_range" | cut -d'-' -f1)
local end_port=$(echo "$port_range" | cut -d'-' -f2)
echo -e "${YELLOW}Testing $service $protocol port range $port_range${NC}"
if iptables -t filter -C "$NYM_CHAIN" -p "$protocol" --dport "$start_port:$end_port" -j ACCEPT 2>/dev/null; then
echo -e "${GREEN}✓ Rule exists: $NYM_CHAIN $protocol port range $start_port:$end_port${NC}"
else
echo -e "${RED}✗ Rule missing: $NYM_CHAIN $protocol port range $start_port:$end_port${NC}"
((total_failures++))
echo -e "${YELLOW}Existing rules for protocol $protocol:${NC}"
iptables -L "$NYM_CHAIN" -n | grep "$protocol"
fi
done
if [ $total_failures -eq 0 ]; then
return 0
else
return 1
fi
}
test_critical_services() {
echo -e "${YELLOW}Testing Critical Service Rules...${NC}"
local tcp_services=(
22 # SSH
53 # DNS
443 # HTTPS
853 # DNS over TLS
1194 # OpenVPN
)
local udp_services=(
53 # DNS
123 # NTP
1194 # OpenVPN
)
local failures=0
# Test TCP services
for port in "${tcp_services[@]}"; do
local rule_found=false
# First check for exact match
if iptables -t filter -C "$NYM_CHAIN" -p tcp --dport "$port" -j ACCEPT 2>/dev/null; then
echo -e "${GREEN}✓ Rule exists: NYM-EXIT tcp port $port${NC}"
rule_found=true
else
# If not found as exact port, search for it in port ranges
# This checks if the port is covered by any range rule
if iptables-save | grep -E "^-A $NYM_CHAIN.*tcp.*dpts:" | grep -qP "dpts:(\d+:)?$port(:|\d+)" || \
iptables-save | grep -E "^-A $NYM_CHAIN.*tcp.*dpts:" | grep -qP "dpts:$port:"; then
echo -e "${GREEN}✓ Rule exists: NYM-EXIT tcp port $port (covered by a range rule)${NC}"
rule_found=true
else
echo -e "${RED}✗ Rule missing: NYM-EXIT tcp port $port${NC}"
((failures++))
fi
fi
done
# Test UDP services - similar approach
for port in "${udp_services[@]}"; do
local rule_found=false
if iptables -t filter -C "$NYM_CHAIN" -p udp --dport "$port" -j ACCEPT 2>/dev/null; then
echo -e "${GREEN}✓ Rule exists: NYM-EXIT udp port $port${NC}"
rule_found=true
else
# If not found as exact port, search for it in port ranges
if iptables-save | grep -E "^-A $NYM_CHAIN.*udp.*dpts:" | grep -qP "dpts:(\d+:)?$port(:|\d+)" || \
iptables-save | grep -E "^-A $NYM_CHAIN.*udp.*dpts:" | grep -qP "dpts:$port:"; then
echo -e "${GREEN}✓ Rule exists: NYM-EXIT udp port $port (covered by a range rule)${NC}"
rule_found=true
else
echo -e "${RED}✗ Rule missing: NYM-EXIT udp port $port${NC}"
((failures++))
fi
fi
done
echo -e "${YELLOW}Relevant existing rules for HTTP (port 80):${NC}"
iptables-save | grep -E "$NYM_CHAIN.*tcp" | grep -E "(dpt|dpts):.*80"
return $failures
}
# Verify default reject rule exists
test_default_reject_rule() {
echo -e "${YELLOW}This test takes some time, do not quit the process${NC}"
echo
echo -e "${YELLOW}Testing Default Reject Rule...${NC}"
# Try different patterns to detect the reject rule
if iptables -L "$NYM_CHAIN" | grep -q "REJECT.*all.*anywhere.*anywhere.*reject-with"; then
echo -e "${GREEN}✓ Default REJECT rule exists${NC}"
return 0
elif iptables -L "$NYM_CHAIN" | grep -q "REJECT.*all -- .*everywhere.*everywhere"; then
echo -e "${GREEN}✓ Default REJECT rule exists${NC}"
return 0
elif iptables -L "$NYM_CHAIN" | grep -q "REJECT.*all.*0.0.0.0/0.*0.0.0.0/0"; then
echo -e "${GREEN}✓ Default REJECT rule exists${NC}"
return 0
elif iptables -n -L "$NYM_CHAIN" | grep -qE "REJECT.*all.*0.0.0.0/0.*0.0.0.0/0"; then
echo -e "${GREEN}✓ Default REJECT rule exists${NC}"
return 0
elif iptables -L "$NYM_CHAIN" | tail -1 | grep -q "REJECT"; then
echo -e "${GREEN}✓ Default REJECT rule exists at the end of chain${NC}"
return 0
else
echo -e "${RED}✗ Default REJECT rule missing${NC}"
# Display the last 3 rules in the chain for debugging
echo -e "${YELLOW}Last 3 rules in the chain:${NC}"
iptables -L "$NYM_CHAIN" | tail -3
return 1
fi
}
run_all_tests() {
local total_failures=0
local total_tests=0
local skip_default_reject=false
# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--skip-default-reject)
skip_default_reject=true
shift
;;
*)
echo -e "${RED}Unknown argument: $1${NC}"
exit 1
;;
esac
done
local test_functions=(
"test_port_range_rules"
"test_critical_services"
)
if [ "$skip_default_reject" = false ]; then
test_functions+=("test_default_reject_rule")
fi
echo -e "${YELLOW}Running Nym Exit Policy Verification Tests...${NC}"
for test_func in "${test_functions[@]}"; do
((total_tests++))
$test_func
if [ $? -ne 0 ]; then
((total_failures++))
echo -e "${RED}Test $test_func FAILED${NC}"
else
echo -e "${GREEN}Test $test_func PASSED${NC}"
fi
done
echo -e "\n${YELLOW}Test Summary:${NC}"
echo -e "Total Tests: $total_tests"
echo -e "Failures: $total_failures"
if [ $total_failures -eq 0 ]; then
echo -e "${GREEN}All Tests Passed Successfully!${NC}"
exit 0
else
echo -e "${RED}Some Tests Failed. Please review the iptables configuration.${NC}"
exit 1
fi
}
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}This script must be run as root${NC}"
exit 1
fi
# Run the tests
run_all_tests "$@"
@@ -0,0 +1,40 @@
#!/bin/bash
validate_exit_policy() {
echo "=== Nym Exit Policy Blocking Validation ==="
# Check iptables rules
echo "Checking iptables NYM-EXIT chain:"
sudo iptables -L NYM-EXIT -v -n
# Test IP ranges and individual IPs
test_ips=(
"5.188.10.0/24" # Blocked network range
"31.132.36.50" # Specific blocked IP
"37.9.42.100" # Another blocked IP
)
for target in "${test_ips[@]}"; do
echo -e "\n\e[33mTesting blocking for $target\e[0m"
# Multiple connection test methods
methods=(
"ping -c 4 -W 2"
"curl -m 5 http://$target"
"nc -z -w 5 $target 80"
"telnet $target 80"
)
for method in "${methods[@]}"; do
echo -n "Testing with $method: "
if sudo timeout 5 $method >/dev/null 2>&1; then
echo -e "\e[31mFAILED: Connection succeeded (Blocking ineffective)\e[0m"
else
echo -e "\e[32mBLOCKED\e[0m"
fi
done
done
}
# Run the test
validate_exit_policy

Some files were not shown because too many files have changed in this diff Show More