Compare commits
91 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ef33ce797 | |||
| ff91d4619e | |||
| 9d01474277 | |||
| 8d10552d7c | |||
| 04fd197f5a | |||
| 4eadaf8292 | |||
| 32e39ebc6b | |||
| 117eb83a0b | |||
| c964c137f4 | |||
| 35b43d5b20 | |||
| bf88b34898 | |||
| 93140a1aa7 | |||
| f594bfc9ab | |||
| 4327e2945a | |||
| 8670693952 | |||
| 57c38ef222 | |||
| 8e05386a0b | |||
| 13cfa55e6c | |||
| 18e628acde | |||
| e67b2b020a | |||
| 9b627dd70f | |||
| 9a0b769425 | |||
| 8e14f5f884 | |||
| 1b64cb42b0 | |||
| 03c4895f2b | |||
| dcfb092758 | |||
| 9305ad5364 | |||
| ea5aef6c2f | |||
| 61a4433cd9 | |||
| 5c89d36140 | |||
| 5ab164d229 | |||
| 26538c5884 | |||
| adb248dbcc | |||
| fffec65cab | |||
| bb24004d46 | |||
| c487eff7ca | |||
| 5fa21c9aae | |||
| fd18aae0d6 | |||
| c202e2d598 | |||
| 62d23cff9f | |||
| e454d71b78 | |||
| a7874add88 | |||
| 0a47d5dcf8 | |||
| 3d84be22e2 | |||
| 6ccbb30491 | |||
| 91c205f83a | |||
| 4a704e992a | |||
| 6c88c7df42 | |||
| 2a748fc968 | |||
| 25766dc0ec | |||
| 07544d939e | |||
| 102cd1033c | |||
| 676e93a372 | |||
| 5a6770e5e2 | |||
| 529e8d49ee | |||
| 01c7ea72dd | |||
| dfd1df5706 | |||
| 11d6ee2fdb | |||
| d704c428fc | |||
| bca070c1bd | |||
| a94c035c0a | |||
| 24480418f0 | |||
| 226c040a13 | |||
| a46245ffe3 | |||
| 7c1c13e139 | |||
| 836a93cd96 | |||
| 3d2914b3e5 | |||
| 9b02de3e75 | |||
| b47a742dd0 | |||
| 6e14882246 | |||
| f3d8aba82c | |||
| a7466a0e02 | |||
| 78f45012db | |||
| f6a2f62ea9 | |||
| 3efeededc5 | |||
| c482350ec6 | |||
| 72a4a26c40 | |||
| 5d9b5a0d70 | |||
| c070e4bfee | |||
| 4d3d60b78e | |||
| 41fb17a31b | |||
| 7d5e3ef7d3 | |||
| 4f283f565c | |||
| f7a7a8072f | |||
| acd068e5ab | |||
| 8d5a41a790 | |||
| caa17d933c | |||
| 039b05cf7e | |||
| 37b10b59aa | |||
| a9ede22bbd | |||
| b656003306 |
@@ -0,0 +1 @@
|
|||||||
|
nym-validator-rewarder/.sqlx/** diff=nodiff
|
||||||
@@ -31,3 +31,5 @@ updates:
|
|||||||
update-types:
|
update-types:
|
||||||
- "patch"
|
- "patch"
|
||||||
open-pull-requests-limit: 10
|
open-pull-requests-limit: 10
|
||||||
|
assignees:
|
||||||
|
- "octol"
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ jobs:
|
|||||||
target/release/nym-socks5-client
|
target/release/nym-socks5-client
|
||||||
target/release/nym-api
|
target/release/nym-api
|
||||||
target/release/nym-network-requester
|
target/release/nym-network-requester
|
||||||
target/release/nym-data-observatory
|
|
||||||
target/release/nym-cli
|
target/release/nym-cli
|
||||||
target/release/nymvisor
|
target/release/nymvisor
|
||||||
target/release/nym-node
|
target/release/nym-node
|
||||||
@@ -97,7 +96,6 @@ jobs:
|
|||||||
cp target/release/nym-socks5-client $OUTPUT_DIR
|
cp target/release/nym-socks5-client $OUTPUT_DIR
|
||||||
cp target/release/nym-api $OUTPUT_DIR
|
cp target/release/nym-api $OUTPUT_DIR
|
||||||
cp target/release/nym-network-requester $OUTPUT_DIR
|
cp target/release/nym-network-requester $OUTPUT_DIR
|
||||||
cp target/release/nym-data-observatory $OUTPUT_DIR
|
|
||||||
cp target/release/nymvisor $OUTPUT_DIR
|
cp target/release/nymvisor $OUTPUT_DIR
|
||||||
cp target/release/nym-node $OUTPUT_DIR
|
cp target/release/nym-node $OUTPUT_DIR
|
||||||
cp target/release/nym-cli $OUTPUT_DIR
|
cp target/release/nym-cli $OUTPUT_DIR
|
||||||
|
|||||||
@@ -8,16 +8,18 @@ on:
|
|||||||
- 'explorer-api/**'
|
- 'explorer-api/**'
|
||||||
- 'gateway/**'
|
- 'gateway/**'
|
||||||
- 'integrations/**'
|
- 'integrations/**'
|
||||||
- 'mixnode/**'
|
|
||||||
- 'sdk/rust/**'
|
|
||||||
- 'sdk/lib/**'
|
|
||||||
- 'service-providers/**'
|
|
||||||
- 'nym-network-monitor/**'
|
|
||||||
- 'nym-api/**'
|
- 'nym-api/**'
|
||||||
|
- 'nym-credential-proxy/**'
|
||||||
|
- 'nym-network-monitor/**'
|
||||||
- 'nym-node/**'
|
- 'nym-node/**'
|
||||||
|
- 'nym-node-status-api/**'
|
||||||
- 'nym-outfox/**'
|
- 'nym-outfox/**'
|
||||||
- 'nym-data-observatory/**'
|
|
||||||
- 'nym-validator-rewarder/**'
|
- 'nym-validator-rewarder/**'
|
||||||
|
- 'nyx-chain-watcher/**'
|
||||||
|
- 'sdk/ffi/**'
|
||||||
|
- 'sdk/rust/**'
|
||||||
|
- 'service-providers/**'
|
||||||
|
- 'nym-browser-extension/storage/**'
|
||||||
- 'tools/**'
|
- 'tools/**'
|
||||||
- 'wasm/**'
|
- 'wasm/**'
|
||||||
- 'Cargo.toml'
|
- 'Cargo.toml'
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
name: ci-sdk-wasm
|
name: ci-sdk-wasm
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- 'wasm/**'
|
- 'wasm/**'
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
git config --global user.name "Lawrence Stalder"
|
git config --global user.name "Lawrence Stalder"
|
||||||
|
|
||||||
- name: Get version from cargo.toml
|
- name: Get version from cargo.toml
|
||||||
uses: mikefarah/yq@v4.44.6
|
uses: mikefarah/yq@v4.45.1
|
||||||
id: get_version
|
id: get_version
|
||||||
with:
|
with:
|
||||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-credential-proxy/Cargo.toml
|
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-credential-proxy/Cargo.toml
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
git config --global user.name "Lawrence Stalder"
|
git config --global user.name "Lawrence Stalder"
|
||||||
|
|
||||||
- name: Get version from cargo.toml
|
- name: Get version from cargo.toml
|
||||||
uses: mikefarah/yq@v4.44.6
|
uses: mikefarah/yq@v4.45.1
|
||||||
id: get_version
|
id: get_version
|
||||||
with:
|
with:
|
||||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
git config --global user.name "Lawrence Stalder"
|
git config --global user.name "Lawrence Stalder"
|
||||||
|
|
||||||
- name: Get version from cargo.toml
|
- name: Get version from cargo.toml
|
||||||
uses: mikefarah/yq@v4.44.6
|
uses: mikefarah/yq@v4.45.1
|
||||||
id: get_version
|
id: get_version
|
||||||
with:
|
with:
|
||||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-network-monitor/Cargo.toml
|
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-network-monitor/Cargo.toml
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ jobs:
|
|||||||
git config --global user.name "Lawrence Stalder"
|
git config --global user.name "Lawrence Stalder"
|
||||||
|
|
||||||
- name: Get version from cargo.toml
|
- name: Get version from cargo.toml
|
||||||
uses: mikefarah/yq@v4.44.6
|
uses: mikefarah/yq@v4.45.1
|
||||||
id: get_version
|
id: get_version
|
||||||
with:
|
with:
|
||||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
git config --global user.name "Lawrence Stalder"
|
git config --global user.name "Lawrence Stalder"
|
||||||
|
|
||||||
- name: Get version from cargo.toml
|
- name: Get version from cargo.toml
|
||||||
uses: mikefarah/yq@v4.44.6
|
uses: mikefarah/yq@v4.45.1
|
||||||
id: get_version
|
id: get_version
|
||||||
with:
|
with:
|
||||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
git config --global user.name "Lawrence Stalder"
|
git config --global user.name "Lawrence Stalder"
|
||||||
|
|
||||||
- name: Get version from cargo.toml
|
- name: Get version from cargo.toml
|
||||||
uses: mikefarah/yq@v4.44.6
|
uses: mikefarah/yq@v4.45.1
|
||||||
id: get_version
|
id: get_version
|
||||||
with:
|
with:
|
||||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
git config --global user.name "Lawrence Stalder"
|
git config --global user.name "Lawrence Stalder"
|
||||||
|
|
||||||
- name: Get version from cargo.toml
|
- name: Get version from cargo.toml
|
||||||
uses: mikefarah/yq@v4.44.6
|
uses: mikefarah/yq@v4.45.1
|
||||||
id: get_version
|
id: get_version
|
||||||
with:
|
with:
|
||||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
git config --global user.name "Lawrence Stalder"
|
git config --global user.name "Lawrence Stalder"
|
||||||
|
|
||||||
- name: Get version from cargo.toml
|
- name: Get version from cargo.toml
|
||||||
uses: mikefarah/yq@v4.44.6
|
uses: mikefarah/yq@v4.45.1
|
||||||
id: get_version
|
id: get_version
|
||||||
with:
|
with:
|
||||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||||
|
|||||||
+2
-1
@@ -54,7 +54,8 @@ nym-network-monitor/__pycache__
|
|||||||
nym-network-monitor/*.key
|
nym-network-monitor/*.key
|
||||||
nym-network-monitor/.envrc
|
nym-network-monitor/.envrc
|
||||||
nym-network-monitor/.envrc
|
nym-network-monitor/.envrc
|
||||||
|
nym-api/redocly/formatted-openapi.json
|
||||||
|
|
||||||
|
|
||||||
*.sqlite
|
*.sqlite
|
||||||
.build
|
.build
|
||||||
|
|||||||
@@ -4,6 +4,91 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [2025.1-reeses] (2025-01-15)
|
||||||
|
|
||||||
|
- Feture/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])
|
||||||
|
- reduce log severity for number of packets being delayed ([#5321])
|
||||||
|
- feat: warn users if node is run in exit mode only ([#5320])
|
||||||
|
- Bugfix/contract version assignment ([#5318])
|
||||||
|
- fixed client session histogram buckets ([#5316])
|
||||||
|
- amend 250gb limit ([#5313])
|
||||||
|
- feature: expand nym-node prometheus metrics ([#5298])
|
||||||
|
- Cherry picked #5286 ([#5287])
|
||||||
|
- Add close to credential storage ([#5283])
|
||||||
|
- feature: wireguard metrics ([#5278])
|
||||||
|
- Add PATCH support to nym-http-api-client ([#5260])
|
||||||
|
- chore: removed legacy socks5 listener ([#5259])
|
||||||
|
- bugfix: make sure to apply gateway score filtering when choosing initial node ([#5256])
|
||||||
|
- Update TS bindings ([#5255])
|
||||||
|
- Add conversion unit tests for auth msg ([#5251])
|
||||||
|
- Add control messages to GatewayTransciver ([#5247])
|
||||||
|
- Remove unneeded async function annotation ([#5246])
|
||||||
|
- bugfix: make sure to update timestamp of last batch verification to prevent double redemption ([#5239])
|
||||||
|
- Add FromStr impl for UserAgent ([#5236])
|
||||||
|
- Extend swagger docs ([#5235])
|
||||||
|
- TicketType derive Hash and Eq ([#5233])
|
||||||
|
- Add fd callback to client core ([#5230])
|
||||||
|
- Extend raw ws fd for gateway client ([#5218])
|
||||||
|
- Shipping raw metrics to PG ([#5216])
|
||||||
|
- Change sqlite journal mode to WAL ([#5213])
|
||||||
|
- Derive serialize for UserAgent ([#5210])
|
||||||
|
- Restore Location fields ([#5208])
|
||||||
|
- better date serialization ([#5207])
|
||||||
|
- Fix overflow ([#5204])
|
||||||
|
- feature: hopefully final steps of the smoosh™️ ([#5201])
|
||||||
|
- Fix overflow ([#5184])
|
||||||
|
- NS API - Gateway stats scraping ([#5180])
|
||||||
|
- introduced initial internal commands for nym-cli: ecash key and request generation ([#5174])
|
||||||
|
- Move NS client to separate package under NS API ([#5171])
|
||||||
|
- build(deps): bump micromatch from 4.0.4 to 4.0.8 in /testnet-faucet ([#4813])
|
||||||
|
|
||||||
|
[#5346]: https://github.com/nymtech/nym/pull/5346
|
||||||
|
[#5331]: https://github.com/nymtech/nym/pull/5331
|
||||||
|
[#5330]: https://github.com/nymtech/nym/pull/5330
|
||||||
|
[#5329]: https://github.com/nymtech/nym/pull/5329
|
||||||
|
[#5321]: https://github.com/nymtech/nym/pull/5321
|
||||||
|
[#5320]: https://github.com/nymtech/nym/pull/5320
|
||||||
|
[#5318]: https://github.com/nymtech/nym/pull/5318
|
||||||
|
[#5316]: https://github.com/nymtech/nym/pull/5316
|
||||||
|
[#5313]: https://github.com/nymtech/nym/pull/5313
|
||||||
|
[#5298]: https://github.com/nymtech/nym/pull/5298
|
||||||
|
[#5287]: https://github.com/nymtech/nym/pull/5287
|
||||||
|
[#5283]: https://github.com/nymtech/nym/pull/5283
|
||||||
|
[#5278]: https://github.com/nymtech/nym/pull/5278
|
||||||
|
[#5260]: https://github.com/nymtech/nym/pull/5260
|
||||||
|
[#5259]: https://github.com/nymtech/nym/pull/5259
|
||||||
|
[#5256]: https://github.com/nymtech/nym/pull/5256
|
||||||
|
[#5255]: https://github.com/nymtech/nym/pull/5255
|
||||||
|
[#5251]: https://github.com/nymtech/nym/pull/5251
|
||||||
|
[#5247]: https://github.com/nymtech/nym/pull/5247
|
||||||
|
[#5246]: https://github.com/nymtech/nym/pull/5246
|
||||||
|
[#5239]: https://github.com/nymtech/nym/pull/5239
|
||||||
|
[#5236]: https://github.com/nymtech/nym/pull/5236
|
||||||
|
[#5235]: https://github.com/nymtech/nym/pull/5235
|
||||||
|
[#5233]: https://github.com/nymtech/nym/pull/5233
|
||||||
|
[#5230]: https://github.com/nymtech/nym/pull/5230
|
||||||
|
[#5218]: https://github.com/nymtech/nym/pull/5218
|
||||||
|
[#5216]: https://github.com/nymtech/nym/pull/5216
|
||||||
|
[#5213]: https://github.com/nymtech/nym/pull/5213
|
||||||
|
[#5210]: https://github.com/nymtech/nym/pull/5210
|
||||||
|
[#5208]: https://github.com/nymtech/nym/pull/5208
|
||||||
|
[#5207]: https://github.com/nymtech/nym/pull/5207
|
||||||
|
[#5204]: https://github.com/nymtech/nym/pull/5204
|
||||||
|
[#5201]: https://github.com/nymtech/nym/pull/5201
|
||||||
|
[#5184]: https://github.com/nymtech/nym/pull/5184
|
||||||
|
[#5180]: https://github.com/nymtech/nym/pull/5180
|
||||||
|
[#5174]: https://github.com/nymtech/nym/pull/5174
|
||||||
|
[#5171]: https://github.com/nymtech/nym/pull/5171
|
||||||
|
[#4813]: https://github.com/nymtech/nym/pull/4813
|
||||||
|
|
||||||
|
## [2024.14-crunch-patched] (2024-12-17)
|
||||||
|
|
||||||
|
- Fixes an issue to allow previously registred clients to connect to latest nym-nodes
|
||||||
|
- Fixes compatibility issues between nym-nodes and older clients
|
||||||
|
|
||||||
## [2024.14-crunch] (2024-12-11)
|
## [2024.14-crunch] (2024-12-11)
|
||||||
|
|
||||||
- Merge/release/2024.14-crunch ([#5242])
|
- Merge/release/2024.14-crunch ([#5242])
|
||||||
|
|||||||
Generated
+1327
-647
File diff suppressed because it is too large
Load Diff
+69
-73
@@ -53,8 +53,8 @@ members = [
|
|||||||
"common/execute",
|
"common/execute",
|
||||||
"common/exit-policy",
|
"common/exit-policy",
|
||||||
"common/gateway-requests",
|
"common/gateway-requests",
|
||||||
"common/gateway-storage",
|
|
||||||
"common/gateway-stats-storage",
|
"common/gateway-stats-storage",
|
||||||
|
"common/gateway-storage",
|
||||||
"common/http-api-client",
|
"common/http-api-client",
|
||||||
"common/http-api-common",
|
"common/http-api-common",
|
||||||
"common/inclusion-probability",
|
"common/inclusion-probability",
|
||||||
@@ -93,6 +93,7 @@ members = [
|
|||||||
"common/topology",
|
"common/topology",
|
||||||
"common/tun",
|
"common/tun",
|
||||||
"common/types",
|
"common/types",
|
||||||
|
"common/verloc",
|
||||||
"common/wasm/client-core",
|
"common/wasm/client-core",
|
||||||
"common/wasm/storage",
|
"common/wasm/storage",
|
||||||
"common/wasm/utils",
|
"common/wasm/utils",
|
||||||
@@ -104,6 +105,22 @@ members = [
|
|||||||
"explorer-api/explorer-client",
|
"explorer-api/explorer-client",
|
||||||
"gateway",
|
"gateway",
|
||||||
"integrations/bity",
|
"integrations/bity",
|
||||||
|
"nym-api",
|
||||||
|
"nym-api/nym-api-requests",
|
||||||
|
"nym-browser-extension/storage",
|
||||||
|
"nym-credential-proxy/nym-credential-proxy",
|
||||||
|
"nym-credential-proxy/nym-credential-proxy-requests",
|
||||||
|
"nym-credential-proxy/vpn-api-lib-wasm",
|
||||||
|
"nym-network-monitor",
|
||||||
|
"nym-node",
|
||||||
|
"nym-node-status-api/nym-node-status-agent",
|
||||||
|
"nym-node-status-api/nym-node-status-api",
|
||||||
|
"nym-node-status-api/nym-node-status-client",
|
||||||
|
"nym-node/nym-node-metrics",
|
||||||
|
"nym-node/nym-node-requests",
|
||||||
|
"nym-outfox",
|
||||||
|
"nym-validator-rewarder",
|
||||||
|
"nyx-chain-watcher",
|
||||||
"sdk/ffi/cpp",
|
"sdk/ffi/cpp",
|
||||||
"sdk/ffi/go",
|
"sdk/ffi/go",
|
||||||
"sdk/ffi/shared",
|
"sdk/ffi/shared",
|
||||||
@@ -112,26 +129,16 @@ members = [
|
|||||||
"service-providers/common",
|
"service-providers/common",
|
||||||
"service-providers/ip-packet-router",
|
"service-providers/ip-packet-router",
|
||||||
"service-providers/network-requester",
|
"service-providers/network-requester",
|
||||||
"nym-api",
|
|
||||||
"nym-api/nym-api-requests",
|
|
||||||
"nym-browser-extension/storage",
|
|
||||||
"nym-credential-proxy/nym-credential-proxy",
|
|
||||||
"nym-credential-proxy/nym-credential-proxy-requests",
|
|
||||||
"nym-credential-proxy/vpn-api-lib-wasm",
|
|
||||||
"nym-network-monitor",
|
|
||||||
"nyx-chain-watcher",
|
|
||||||
"nym-node",
|
|
||||||
"nym-node/nym-node-requests",
|
|
||||||
"nym-node/nym-node-metrics",
|
|
||||||
"nym-node-status-api/nym-node-status-agent",
|
|
||||||
"nym-node-status-api/nym-node-status-api",
|
|
||||||
"nym-node-status-api/nym-node-status-client",
|
|
||||||
"nym-outfox",
|
|
||||||
"nym-validator-rewarder",
|
|
||||||
"tools/echo-server",
|
"tools/echo-server",
|
||||||
"tools/internal/ssl-inject",
|
"tools/echo-server",
|
||||||
|
"tools/internal/contract-state-importer/importer-cli",
|
||||||
|
"tools/internal/contract-state-importer/importer-contract",
|
||||||
|
"tools/internal/mixnet-connectivity-check",
|
||||||
# "tools/internal/sdk-version-bump",
|
# "tools/internal/sdk-version-bump",
|
||||||
|
"tools/internal/ssl-inject",
|
||||||
"tools/internal/testnet-manager",
|
"tools/internal/testnet-manager",
|
||||||
|
"tools/internal/testnet-manager",
|
||||||
|
"tools/internal/testnet-manager/dkg-bypass-contract",
|
||||||
"tools/internal/testnet-manager/dkg-bypass-contract",
|
"tools/internal/testnet-manager/dkg-bypass-contract",
|
||||||
"tools/nym-cli",
|
"tools/nym-cli",
|
||||||
"tools/nym-id-cli",
|
"tools/nym-id-cli",
|
||||||
@@ -143,13 +150,6 @@ members = [
|
|||||||
"wasm/mix-fetch",
|
"wasm/mix-fetch",
|
||||||
"wasm/node-tester",
|
"wasm/node-tester",
|
||||||
"wasm/zknym-lib",
|
"wasm/zknym-lib",
|
||||||
"tools/echo-server",
|
|
||||||
"tools/internal/contract-state-importer/importer-cli",
|
|
||||||
"tools/internal/contract-state-importer/importer-contract",
|
|
||||||
"tools/internal/testnet-manager",
|
|
||||||
"tools/internal/testnet-manager/dkg-bypass-contract",
|
|
||||||
"common/verloc",
|
|
||||||
"tools/internal/mixnet-connectivity-check",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
default-members = [
|
default-members = [
|
||||||
@@ -173,7 +173,6 @@ exclude = [
|
|||||||
"explorer",
|
"explorer",
|
||||||
"contracts",
|
"contracts",
|
||||||
"nym-wallet",
|
"nym-wallet",
|
||||||
"nym-vpn/ui/src-tauri",
|
|
||||||
"cpu-cycles",
|
"cpu-cycles",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -193,9 +192,10 @@ aes = "0.8.1"
|
|||||||
aes-gcm = "0.10.1"
|
aes-gcm = "0.10.1"
|
||||||
aes-gcm-siv = "0.11.1"
|
aes-gcm-siv = "0.11.1"
|
||||||
aead = "0.5.2"
|
aead = "0.5.2"
|
||||||
anyhow = "1.0.90"
|
anyhow = "1.0.95"
|
||||||
|
arc-swap = "1.7.1"
|
||||||
argon2 = "0.5.0"
|
argon2 = "0.5.0"
|
||||||
async-trait = "0.1.83"
|
async-trait = "0.1.85"
|
||||||
axum-client-ip = "0.6.1"
|
axum-client-ip = "0.6.1"
|
||||||
axum = "0.7.5"
|
axum = "0.7.5"
|
||||||
axum-extra = "0.9.4"
|
axum-extra = "0.9.4"
|
||||||
@@ -204,7 +204,7 @@ bincode = "1.3.3"
|
|||||||
bip39 = { version = "2.0.0", features = ["zeroize"] }
|
bip39 = { version = "2.0.0", features = ["zeroize"] }
|
||||||
bit-vec = "0.7.0" # can we unify those?
|
bit-vec = "0.7.0" # can we unify those?
|
||||||
bitvec = "1.0.0"
|
bitvec = "1.0.0"
|
||||||
blake3 = "1.5.4"
|
blake3 = "1.5.5"
|
||||||
bloomfilter = "1.0.14"
|
bloomfilter = "1.0.14"
|
||||||
bs58 = "0.5.1"
|
bs58 = "0.5.1"
|
||||||
bytecodec = "0.4.15"
|
bytecodec = "0.4.15"
|
||||||
@@ -214,20 +214,20 @@ celes = "2.4.0"
|
|||||||
cfg-if = "1.0.0"
|
cfg-if = "1.0.0"
|
||||||
chacha20 = "0.9.0"
|
chacha20 = "0.9.0"
|
||||||
chacha20poly1305 = "0.10.1"
|
chacha20poly1305 = "0.10.1"
|
||||||
chrono = "0.4.31"
|
chrono = "0.4.39"
|
||||||
cipher = "0.4.3"
|
cipher = "0.4.3"
|
||||||
clap = "4.5.20"
|
clap = "4.5.26"
|
||||||
clap_complete = "4.5"
|
clap_complete = "4.5"
|
||||||
clap_complete_fig = "4.5"
|
clap_complete_fig = "4.5"
|
||||||
colored = "2.0"
|
colored = "2.0"
|
||||||
comfy-table = "7.1.1"
|
comfy-table = "7.1.3"
|
||||||
console = "0.15.8"
|
console = "0.15.10"
|
||||||
console-subscriber = "0.1.1"
|
console-subscriber = "0.1.1"
|
||||||
console_error_panic_hook = "0.1"
|
console_error_panic_hook = "0.1"
|
||||||
const-str = "0.5.6"
|
const-str = "0.5.6"
|
||||||
const_format = "0.2.33"
|
const_format = "0.2.34"
|
||||||
criterion = "0.4"
|
criterion = "0.5"
|
||||||
csv = "1.3.0"
|
csv = "1.3.1"
|
||||||
ctr = "0.9.1"
|
ctr = "0.9.1"
|
||||||
cupid = "0.6.1"
|
cupid = "0.6.1"
|
||||||
curve25519-dalek = "4.1"
|
curve25519-dalek = "4.1"
|
||||||
@@ -244,8 +244,8 @@ etherparse = "0.13.0"
|
|||||||
envy = "0.4"
|
envy = "0.4"
|
||||||
eyre = "0.6.9"
|
eyre = "0.6.9"
|
||||||
fastrand = "2.1.1"
|
fastrand = "2.1.1"
|
||||||
flate2 = "1.0.34"
|
flate2 = "1.0.35"
|
||||||
futures = "0.3.28"
|
futures = "0.3.31"
|
||||||
futures-util = "0.3"
|
futures-util = "0.3"
|
||||||
generic-array = "0.14.7"
|
generic-array = "0.14.7"
|
||||||
getrandom = "0.2.10"
|
getrandom = "0.2.10"
|
||||||
@@ -254,6 +254,7 @@ handlebars = "3.5.5"
|
|||||||
headers = "0.4.0"
|
headers = "0.4.0"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
hex-literal = "0.3.3"
|
hex-literal = "0.3.3"
|
||||||
|
hickory-resolver = "0.24.2"
|
||||||
hkdf = "0.12.3"
|
hkdf = "0.12.3"
|
||||||
hmac = "0.12.1"
|
hmac = "0.12.1"
|
||||||
http = "1"
|
http = "1"
|
||||||
@@ -264,7 +265,7 @@ humantime-serde = "1.1.1"
|
|||||||
human-repr = "1.1.0"
|
human-repr = "1.1.0"
|
||||||
hyper = "1.4.1"
|
hyper = "1.4.1"
|
||||||
hyper-util = "0.1"
|
hyper-util = "0.1"
|
||||||
indicatif = "0.17.8"
|
indicatif = "0.17.9"
|
||||||
inquire = "0.6.2"
|
inquire = "0.6.2"
|
||||||
ip_network = "0.4.1"
|
ip_network = "0.4.1"
|
||||||
ipnetwork = "0.20"
|
ipnetwork = "0.20"
|
||||||
@@ -289,7 +290,7 @@ parking_lot = "0.12.3"
|
|||||||
pem = "0.8"
|
pem = "0.8"
|
||||||
petgraph = "0.6.5"
|
petgraph = "0.6.5"
|
||||||
pin-project = "1.1"
|
pin-project = "1.1"
|
||||||
pin-project-lite = "0.2.14"
|
pin-project-lite = "0.2.16"
|
||||||
pretty_env_logger = "0.4.0"
|
pretty_env_logger = "0.4.0"
|
||||||
publicsuffix = "2.2.3"
|
publicsuffix = "2.2.3"
|
||||||
quote = "1"
|
quote = "1"
|
||||||
@@ -307,11 +308,11 @@ rocket_cors = "0.6.0"
|
|||||||
rocket_okapi = "0.8.0"
|
rocket_okapi = "0.8.0"
|
||||||
safer-ffi = "0.1.13"
|
safer-ffi = "0.1.13"
|
||||||
schemars = "0.8.21"
|
schemars = "0.8.21"
|
||||||
semver = "1.0.23"
|
semver = "1.0.24"
|
||||||
serde = "1.0.211"
|
serde = "1.0.217"
|
||||||
serde_bytes = "0.11.15"
|
serde_bytes = "0.11.15"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_json = "1.0.132"
|
serde_json = "1.0.135"
|
||||||
serde_json_path = "0.7.1"
|
serde_json_path = "0.7.1"
|
||||||
serde_repr = "0.1"
|
serde_repr = "0.1"
|
||||||
serde_with = "3.9.0"
|
serde_with = "3.9.0"
|
||||||
@@ -324,36 +325,36 @@ strum = "0.26"
|
|||||||
strum_macros = "0.26"
|
strum_macros = "0.26"
|
||||||
subtle-encoding = "0.5"
|
subtle-encoding = "0.5"
|
||||||
syn = "1"
|
syn = "1"
|
||||||
sysinfo = "0.30.13"
|
sysinfo = "0.33.0"
|
||||||
tap = "1.0.1"
|
tap = "1.0.1"
|
||||||
tar = "0.4.42"
|
tar = "0.4.43"
|
||||||
tempfile = "3.14"
|
tempfile = "3.15"
|
||||||
thiserror = "1.0.64"
|
thiserror = "1.0.64"
|
||||||
time = "0.3.30"
|
time = "0.3.37"
|
||||||
tokio = "1.39"
|
tokio = "1.39"
|
||||||
tokio-stream = "0.1.16"
|
tokio-stream = "0.1.17"
|
||||||
tokio-test = "0.4.4"
|
tokio-test = "0.4.4"
|
||||||
tokio-tun = "0.11.5"
|
tokio-tun = "0.11.5"
|
||||||
tokio-tungstenite = { version = "0.20.1" }
|
tokio-tungstenite = { version = "0.20.1" }
|
||||||
tokio-util = "0.7.12"
|
tokio-util = "0.7.13"
|
||||||
toml = "0.8.14"
|
toml = "0.8.19"
|
||||||
tower = "0.4.13"
|
tower = "0.4.13"
|
||||||
tower-http = "0.5.2"
|
tower-http = "0.5.2"
|
||||||
tracing = "0.1.37"
|
tracing = "0.1.41"
|
||||||
tracing-opentelemetry = "0.19.0"
|
tracing-opentelemetry = "0.19.0"
|
||||||
tracing-subscriber = "0.3.16"
|
tracing-subscriber = "0.3.19"
|
||||||
tracing-tree = "0.2.2"
|
tracing-tree = "0.2.2"
|
||||||
tracing-log = "0.2"
|
tracing-log = "0.2"
|
||||||
ts-rs = "10.0.0"
|
ts-rs = "10.1.0"
|
||||||
tungstenite = { version = "0.20.1", default-features = false }
|
tungstenite = { version = "0.20.1", default-features = false }
|
||||||
url = "2.5"
|
url = "2.5"
|
||||||
utoipa = "4.2"
|
utoipa = "5.2"
|
||||||
utoipa-swagger-ui = "7.1"
|
utoipa-swagger-ui = "8.0"
|
||||||
utoipauto = "0.1"
|
utoipauto = "0.2"
|
||||||
uuid = "*"
|
uuid = "*"
|
||||||
vergen = { version = "=8.3.1", default-features = false }
|
vergen = { version = "=8.3.1", default-features = false }
|
||||||
walkdir = "2"
|
walkdir = "2"
|
||||||
wasm-bindgen-test = "0.3.43"
|
wasm-bindgen-test = "0.3.49"
|
||||||
x25519-dalek = "2.0.0"
|
x25519-dalek = "2.0.0"
|
||||||
zeroize = "1.6.0"
|
zeroize = "1.6.0"
|
||||||
|
|
||||||
@@ -386,29 +387,24 @@ cw-controllers = { version = "=1.1.0" }
|
|||||||
# cosmrs-related
|
# cosmrs-related
|
||||||
bip32 = { version = "0.5.2", default-features = false }
|
bip32 = { version = "0.5.2", default-features = false }
|
||||||
|
|
||||||
# temporarily using a fork again (yay.) because we need staking and slashing support (which are already on main but not released)
|
|
||||||
# plus response message parsing (which is, as of the time of writing this message, waiting to get merged)
|
cosmrs = { version = "0.21.0" }
|
||||||
#cosmrs = { path = "../cosmos-rust-fork/cosmos-rust/cosmrs" }
|
tendermint = "0.40.0"
|
||||||
cosmrs = { git = "https://github.com/cosmos/cosmos-rust", rev = "4b1332e6d8258ac845cef71589c8d362a669675a" } # unfortuntely we need a fork by yours truly to get the staking support
|
tendermint-rpc = "0.40.0"
|
||||||
tendermint = "0.37.0" # same version as used by cosmrs
|
prost = { version = "0.13", default-features = false }
|
||||||
tendermint-rpc = "0.37.0" # same version as used by cosmrs
|
|
||||||
prost = { version = "0.12", default-features = false }
|
|
||||||
|
|
||||||
# wasm-related dependencies
|
# wasm-related dependencies
|
||||||
gloo-utils = "0.2.0"
|
gloo-utils = "0.2.0"
|
||||||
gloo-net = "0.5.0"
|
gloo-net = "0.6.0"
|
||||||
|
|
||||||
# use a separate branch due to feature unification failures
|
indexed_db_futures = "0.6.0"
|
||||||
# this is blocked until the upstream removes outdates `wasm_bindgen` feature usage
|
js-sys = "0.3.76"
|
||||||
# indexed_db_futures = "0.4.1"
|
|
||||||
indexed_db_futures = { git = "https://github.com/TiemenSch/rust-indexed-db", branch = "update-uuid" }
|
|
||||||
js-sys = "0.3.70"
|
|
||||||
serde-wasm-bindgen = "0.6.5"
|
serde-wasm-bindgen = "0.6.5"
|
||||||
tsify = "0.4.5"
|
tsify = "0.4.5"
|
||||||
wasm-bindgen = "0.2.99"
|
wasm-bindgen = "0.2.99"
|
||||||
wasm-bindgen-futures = "0.4.45"
|
wasm-bindgen-futures = "0.4.49"
|
||||||
wasmtimer = "0.2.0"
|
wasmtimer = "0.4.1"
|
||||||
web-sys = "0.3.72"
|
web-sys = "0.3.76"
|
||||||
|
|
||||||
# Profile settings for individual crates
|
# Profile settings for individual crates
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
Boost Software License - Version 1.0 - August 17th, 2003
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person or organization
|
||||||
|
obtaining a copy of the software and accompanying documentation covered by
|
||||||
|
this license (the "Software") to use, reproduce, display, distribute,
|
||||||
|
execute, and transmit the Software, and to prepare derivative works of the
|
||||||
|
Software, and to permit third-parties to whom the Software is furnished to
|
||||||
|
do so, all subject to the following:
|
||||||
|
|
||||||
|
The copyright notices in the Software and this entire statement, including
|
||||||
|
the above license grant, this restriction and the following disclaimer,
|
||||||
|
must be included in all copies of the Software, in whole or in part, and
|
||||||
|
all derivative works of the Software, unless such copies or derivative
|
||||||
|
works are solely in the form of machine-executable object code generated by
|
||||||
|
a source language processor.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||||
|
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||||
|
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
@@ -13,8 +13,8 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr
|
|||||||
* `nym-client` - an executable which you can build into your own applications. Use it for interacting with Nym nodes.
|
* `nym-client` - an executable which you can build into your own applications. Use it for interacting with Nym nodes.
|
||||||
* `nym-socks5-client` - a Socks5 proxy you can run on your machine and use with existing applications.
|
* `nym-socks5-client` - a Socks5 proxy you can run on your machine and use with existing applications.
|
||||||
* `nym-explorer` - a (projected) block explorer and (existing) mixnet viewer.
|
* `nym-explorer` - a (projected) block explorer and (existing) mixnet viewer.
|
||||||
* `nym-wallet` - a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework.
|
* `nym-wallet` - a desktop wallet implemented using the [Tauri](https://tauri.app)) framework.
|
||||||
* `nym-cli` - a tool for interacting with the network from the CLI.
|
* `nym-cli` - a tool for interacting with the network from the CLI.
|
||||||
<!-- coming soon
|
<!-- coming soon
|
||||||
* `nym-network-monitor` - sends packets through the full system to check that they are working as expected, and stores node uptime histories as the basis of a rewards system ("mixmining" or "proof-of-mixing").
|
* `nym-network-monitor` - sends packets through the full system to check that they are working as expected, and stores node uptime histories as the basis of a rewards system ("mixmining" or "proof-of-mixing").
|
||||||
-->
|
-->
|
||||||
@@ -42,10 +42,10 @@ client ───► Gateway ──┘ mix │ mix ┌─►mix ───►
|
|||||||
|
|
||||||
References for developers:
|
References for developers:
|
||||||
|
|
||||||
* [Dev Docs](https://nymtech.net/docs/developers)
|
* [Dev Docs](https://nym.com/docs/developers)
|
||||||
* [SDKs](https://nymtech.net/docs/developers/rust)
|
* [SDKs](https://nym.com/docs/developers/rust)
|
||||||
* [Network Docs](https://nymtech.net/docs/network)
|
* [Network Docs](https://nym.com/docs/network)
|
||||||
* [Release Cycle - git flow](https://nymtech.net/docs/operators/release-cycle)
|
* [Release Cycle - git flow](https://nym.com/docs/operators/release-cycle)
|
||||||
|
|
||||||
### Developer chat
|
### Developer chat
|
||||||
|
|
||||||
@@ -66,4 +66,4 @@ As a general approach, licensing is as follows this pattern:
|
|||||||
- libraries and components are Apache 2.0 or MIT
|
- libraries and components are Apache 2.0 or MIT
|
||||||
- documentation is Apache 2.0 or CC0-1.0
|
- documentation is Apache 2.0 or CC0-1.0
|
||||||
|
|
||||||
Nym Node Operators and Validators Temrs and Conditions can be found [here](https://nymtech.net/terms-and-conditions/operators/v1.0.0).
|
Nym Node Operators and Validators Terms and Conditions can be found [here](https://nym.com/operators-validators-terms).
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "nym-client"
|
name = "nym-client"
|
||||||
version = "1.1.45"
|
version = "1.1.46"
|
||||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||||
description = "Implementation of the Nym Client"
|
description = "Implementation of the Nym Client"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "nym-socks5-client"
|
name = "nym-socks5-client"
|
||||||
version = "1.1.45"
|
version = "1.1.46"
|
||||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
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"
|
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ pub type HmacSha256 = Hmac<Sha256>;
|
|||||||
pub type Nonce = u64;
|
pub type Nonce = u64;
|
||||||
pub type Taken = Option<SystemTime>;
|
pub type Taken = Option<SystemTime>;
|
||||||
|
|
||||||
pub const BANDWIDTH_CAP_PER_DAY: u64 = 1024 * 1024 * 1024; // 1 GB
|
pub const BANDWIDTH_CAP_PER_DAY: u64 = 250 * 1024 * 1024 * 1024; // 250 GB
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
pub struct IpPair {
|
pub struct IpPair {
|
||||||
|
|||||||
@@ -553,7 +553,6 @@ pub struct Topology {
|
|||||||
|
|
||||||
/// Specifies whether this client should attempt to retrieve all available network nodes
|
/// Specifies whether this client should attempt to retrieve all available network nodes
|
||||||
/// as opposed to just active mixnodes/gateways.
|
/// as opposed to just active mixnodes/gateways.
|
||||||
/// Useless without `ignore_epoch_roles = true`
|
|
||||||
pub use_extended_topology: bool,
|
pub use_extended_topology: bool,
|
||||||
|
|
||||||
/// Specifies whether this client should ignore the current epoch role of the target egress node
|
/// Specifies whether this client should ignore the current epoch role of the target egress node
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ pub mod error;
|
|||||||
mod manager;
|
mod manager;
|
||||||
mod models;
|
mod models;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct OnDiskGatewaysDetails {
|
pub struct OnDiskGatewaysDetails {
|
||||||
manager: StorageManager,
|
manager: StorageManager,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ pub enum InMemStorageError {
|
|||||||
MalformedGateway(#[from] BadGateway),
|
MalformedGateway(#[from] BadGateway),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct InMemGatewaysDetails {
|
pub struct InMemGatewaysDetails {
|
||||||
inner: Arc<RwLock<InMemStorageInner>>,
|
inner: Arc<RwLock<InMemStorageInner>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
struct InMemStorageInner {
|
struct InMemStorageInner {
|
||||||
active_gateway: Option<String>,
|
active_gateway: Option<String>,
|
||||||
gateways: HashMap<String, GatewayRegistration>,
|
gateways: HashMap<String, GatewayRegistration>,
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ where
|
|||||||
hardcoded_topology.entry_capable_nodes().cloned().collect()
|
hardcoded_topology.entry_capable_nodes().cloned().collect()
|
||||||
} else {
|
} else {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
crate::init::helpers::current_gateways(
|
crate::init::helpers::gateways_for_init(
|
||||||
&mut rng,
|
&mut rng,
|
||||||
&core.client.nym_api_urls,
|
&core.client.nym_api_urls,
|
||||||
user_agent,
|
user_agent,
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ where
|
|||||||
hardcoded_topology.entry_capable_nodes().cloned().collect()
|
hardcoded_topology.entry_capable_nodes().cloned().collect()
|
||||||
} else {
|
} else {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
crate::init::helpers::current_gateways(
|
crate::init::helpers::gateways_for_init(
|
||||||
&mut rng,
|
&mut rng,
|
||||||
&core.client.nym_api_urls,
|
&core.client.nym_api_urls,
|
||||||
user_agent,
|
user_agent,
|
||||||
|
|||||||
@@ -472,6 +472,7 @@ where
|
|||||||
.claim_initial_bandwidth()
|
.claim_initial_bandwidth()
|
||||||
.await
|
.await
|
||||||
.map_err(gateway_failure)?;
|
.map_err(gateway_failure)?;
|
||||||
|
|
||||||
gateway_client
|
gateway_client
|
||||||
.start_listening_for_mixnet_messages()
|
.start_listening_for_mixnet_messages()
|
||||||
.map_err(gateway_failure)?;
|
.map_err(gateway_failure)?;
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ pub trait MixnetClientStorage {
|
|||||||
fn gateway_details_store(&self) -> &Self::GatewaysDetailsStore;
|
fn gateway_details_store(&self) -> &Self::GatewaysDetailsStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct Ephemeral {
|
pub struct Ephemeral {
|
||||||
key_store: InMemEphemeralKeys,
|
key_store: InMemEphemeralKeys,
|
||||||
reply_store: reply_storage::Empty,
|
reply_store: reply_storage::Empty,
|
||||||
@@ -114,6 +114,7 @@ impl MixnetClientStorage for Ephemeral {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
not(target_arch = "wasm32"),
|
not(target_arch = "wasm32"),
|
||||||
feature = "fs-surb-storage",
|
feature = "fs-surb-storage",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
use crate::client::key_manager::ClientKeys;
|
use crate::client::key_manager::ClientKeys;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
use std::sync::Arc;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
@@ -64,6 +65,7 @@ pub enum OnDiskKeysError {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub struct OnDiskKeys {
|
pub struct OnDiskKeys {
|
||||||
paths: ClientKeysPaths,
|
paths: ClientKeysPaths,
|
||||||
@@ -193,9 +195,9 @@ impl KeyStore for OnDiskKeys {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct InMemEphemeralKeys {
|
pub struct InMemEphemeralKeys {
|
||||||
keys: Mutex<Option<ClientKeys>>,
|
keys: Arc<Mutex<Option<ClientKeys>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
|||||||
@@ -9,10 +9,12 @@ use self::{
|
|||||||
acknowledgement_control::AcknowledgementController, real_traffic_stream::OutQueueControl,
|
acknowledgement_control::AcknowledgementController, real_traffic_stream::OutQueueControl,
|
||||||
};
|
};
|
||||||
use crate::client::real_messages_control::message_handler::MessageHandler;
|
use crate::client::real_messages_control::message_handler::MessageHandler;
|
||||||
|
use crate::client::replies::reply_controller;
|
||||||
use crate::client::replies::reply_controller::{
|
use crate::client::replies::reply_controller::{
|
||||||
ReplyController, ReplyControllerReceiver, ReplyControllerSender,
|
ReplyController, ReplyControllerReceiver, ReplyControllerSender,
|
||||||
};
|
};
|
||||||
use crate::client::replies::reply_storage::CombinedReplyStorage;
|
use crate::client::replies::reply_storage::CombinedReplyStorage;
|
||||||
|
use crate::config;
|
||||||
use crate::{
|
use crate::{
|
||||||
client::{
|
client::{
|
||||||
inbound_messages::InputMessageReceiver, mix_traffic::BatchMixMessageSender,
|
inbound_messages::InputMessageReceiver, mix_traffic::BatchMixMessageSender,
|
||||||
@@ -27,16 +29,13 @@ use nym_gateway_client::AcknowledgementReceiver;
|
|||||||
use nym_sphinx::acknowledgements::AckKey;
|
use nym_sphinx::acknowledgements::AckKey;
|
||||||
use nym_sphinx::addressing::clients::Recipient;
|
use nym_sphinx::addressing::clients::Recipient;
|
||||||
use nym_sphinx::params::PacketType;
|
use nym_sphinx::params::PacketType;
|
||||||
|
use nym_statistics_common::clients::ClientStatsSender;
|
||||||
use nym_task::connections::{ConnectionCommandReceiver, LaneQueueLengths};
|
use nym_task::connections::{ConnectionCommandReceiver, LaneQueueLengths};
|
||||||
use rand::{rngs::OsRng, CryptoRng, Rng};
|
use rand::{rngs::OsRng, CryptoRng, Rng};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::client::replies::reply_controller;
|
|
||||||
use crate::config;
|
|
||||||
pub(crate) use acknowledgement_control::{AckActionSender, Action};
|
pub(crate) use acknowledgement_control::{AckActionSender, Action};
|
||||||
|
|
||||||
use nym_statistics_common::clients::ClientStatsSender;
|
|
||||||
|
|
||||||
pub(crate) mod acknowledgement_control;
|
pub(crate) mod acknowledgement_control;
|
||||||
pub(crate) mod message_handler;
|
pub(crate) mod message_handler;
|
||||||
pub(crate) mod real_traffic_stream;
|
pub(crate) mod real_traffic_stream;
|
||||||
|
|||||||
+4
-1
@@ -70,7 +70,10 @@ impl SendingDelayController {
|
|||||||
lower_bound,
|
lower_bound,
|
||||||
multiplier_elevated_counter: 0,
|
multiplier_elevated_counter: 0,
|
||||||
time_when_logged_about_elevated_multiplier: now
|
time_when_logged_about_elevated_multiplier: now
|
||||||
- Duration::from_secs(INTERVAL_BETWEEN_WARNING_ABOUT_ELEVATED_MULTIPLIER_SECS),
|
.checked_sub(Duration::from_secs(
|
||||||
|
INTERVAL_BETWEEN_WARNING_ABOUT_ELEVATED_MULTIPLIER_SECS,
|
||||||
|
))
|
||||||
|
.unwrap_or(now),
|
||||||
time_when_changed: now,
|
time_when_changed: now,
|
||||||
time_when_backpressure_detected: now,
|
time_when_backpressure_detected: now,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
#![warn(clippy::todo)]
|
#![warn(clippy::todo)]
|
||||||
#![warn(clippy::dbg_macro)]
|
#![warn(clippy::dbg_macro)]
|
||||||
|
|
||||||
use std::time::Duration;
|
use futures::StreamExt;
|
||||||
|
|
||||||
use nym_client_core_config_types::StatsReporting;
|
use nym_client_core_config_types::StatsReporting;
|
||||||
use nym_sphinx::addressing::Recipient;
|
use nym_sphinx::addressing::Recipient;
|
||||||
use nym_statistics_common::clients::{
|
use nym_statistics_common::clients::{
|
||||||
ClientStatsController, ClientStatsReceiver, ClientStatsSender,
|
ClientStatsController, ClientStatsReceiver, ClientStatsSender,
|
||||||
};
|
};
|
||||||
use nym_task::connections::TransmissionLane;
|
use nym_task::connections::TransmissionLane;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
client::inbound_messages::{InputMessage, InputMessageSender},
|
client::inbound_messages::{InputMessage, InputMessageSender},
|
||||||
@@ -94,10 +94,32 @@ impl StatisticsControl {
|
|||||||
async fn run_with_shutdown(&mut self, mut task_client: nym_task::TaskClient) {
|
async fn run_with_shutdown(&mut self, mut task_client: nym_task::TaskClient) {
|
||||||
log::debug!("Started StatisticsControl with graceful shutdown support");
|
log::debug!("Started StatisticsControl with graceful shutdown support");
|
||||||
|
|
||||||
let mut stats_report_interval =
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
tokio::time::interval(self.reporting_config.reporting_interval);
|
let mut stats_report_interval = tokio_stream::wrappers::IntervalStream::new(
|
||||||
let mut local_report_interval = tokio::time::interval(LOCAL_REPORT_INTERVAL);
|
tokio::time::interval(self.reporting_config.reporting_interval),
|
||||||
let mut snapshot_interval = tokio::time::interval(SNAPSHOT_INTERVAL);
|
);
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
let mut local_report_interval = tokio_stream::wrappers::IntervalStream::new(
|
||||||
|
tokio::time::interval(LOCAL_REPORT_INTERVAL),
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
let mut snapshot_interval =
|
||||||
|
tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(SNAPSHOT_INTERVAL));
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
let mut stats_report_interval = gloo_timers::future::IntervalStream::new(
|
||||||
|
self.reporting_config.reporting_interval.as_millis() as u32,
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
let mut local_report_interval =
|
||||||
|
gloo_timers::future::IntervalStream::new(LOCAL_REPORT_INTERVAL.as_millis() as u32);
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
let mut snapshot_interval =
|
||||||
|
gloo_timers::future::IntervalStream::new(SNAPSHOT_INTERVAL.as_millis() as u32);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
@@ -108,16 +130,20 @@ impl StatisticsControl {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ = snapshot_interval.tick() => {
|
_ = snapshot_interval.next() => {
|
||||||
self.stats.snapshot();
|
self.stats.snapshot();
|
||||||
}
|
}
|
||||||
_ = stats_report_interval.tick(), if self.reporting_config.enabled && self.reporting_config.provider_address.is_some() => {
|
_ = stats_report_interval.next() => {
|
||||||
// SAFTEY : this branch executes only if reporting is not none, so unwrapp is fine
|
let Some(recipient) = self.reporting_config.provider_address else {
|
||||||
#[allow(clippy::unwrap_used)]
|
continue
|
||||||
self.report_stats(self.reporting_config.provider_address.unwrap()).await;
|
};
|
||||||
|
|
||||||
|
if self.reporting_config.enabled {
|
||||||
|
self.report_stats(recipient).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = local_report_interval.tick() => {
|
_ = local_report_interval.next() => {
|
||||||
self.stats.local_report(&mut task_client);
|
self.stats.local_report(&mut task_client);
|
||||||
}
|
}
|
||||||
_ = task_client.recv_with_delay() => {
|
_ = task_client.recv_with_delay() => {
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ impl<'a, G: ConnectableGateway> GatewayWithLatency<'a, G> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn current_gateways<R: Rng>(
|
pub async fn gateways_for_init<R: Rng>(
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
nym_apis: &[Url],
|
nym_apis: &[Url],
|
||||||
user_agent: Option<UserAgent>,
|
user_agent: Option<UserAgent>,
|
||||||
@@ -108,8 +108,11 @@ pub async fn current_gateways<R: Rng>(
|
|||||||
|
|
||||||
log::trace!("Gateways: {:#?}", gateways);
|
log::trace!("Gateways: {:#?}", gateways);
|
||||||
|
|
||||||
|
// filter out gateways below minimum performance and ones that could operate as a mixnode
|
||||||
|
// (we don't want instability)
|
||||||
let valid_gateways = gateways
|
let valid_gateways = gateways
|
||||||
.iter()
|
.iter()
|
||||||
|
.filter(|g| !g.supported_roles.mixnode)
|
||||||
.filter(|g| g.performance.round_to_integer() >= minimum_performance)
|
.filter(|g| g.performance.round_to_integer() >= minimum_performance)
|
||||||
.filter_map(|gateway| gateway.try_into().ok())
|
.filter_map(|gateway| gateway.try_into().ok())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ use nym_topology::node::RoutingNode;
|
|||||||
use nym_validator_client::client::IdentityKey;
|
use nym_validator_client::client::IdentityKey;
|
||||||
use nym_validator_client::nyxd::AccountId;
|
use nym_validator_client::nyxd::AccountId;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::fmt::Display;
|
use std::fmt::{Debug, Display};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
@@ -221,6 +221,34 @@ pub enum GatewaySetup {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Debug for GatewaySetup {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
GatewaySetup::MustLoad { gateway_id } => f
|
||||||
|
.debug_struct("GatewaySetup::MustLoad")
|
||||||
|
.field("gateway_id", gateway_id)
|
||||||
|
.finish(),
|
||||||
|
GatewaySetup::New {
|
||||||
|
specification,
|
||||||
|
available_gateways,
|
||||||
|
} => f
|
||||||
|
.debug_struct("GatewaySetup::New")
|
||||||
|
.field("specification", specification)
|
||||||
|
.field("available_gateways", available_gateways)
|
||||||
|
.field("gateways", specification)
|
||||||
|
.finish(),
|
||||||
|
GatewaySetup::ReuseConnection {
|
||||||
|
gateway_details, ..
|
||||||
|
} => f
|
||||||
|
.debug_struct("GatewaySetup::ReuseConnection")
|
||||||
|
.field("authenticated_ephemeral_client", &"***")
|
||||||
|
.field("gateway_details", gateway_details)
|
||||||
|
.field("client_keys", &"***")
|
||||||
|
.finish(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl GatewaySetup {
|
impl GatewaySetup {
|
||||||
pub fn try_reuse_connection(init_res: InitialisationResult) -> Result<Self, ClientCoreError> {
|
pub fn try_reuse_connection(init_res: InitialisationResult) -> Result<Self, ClientCoreError> {
|
||||||
if let Some(authenticated_ephemeral_client) = init_res.authenticated_ephemeral_client {
|
if let Some(authenticated_ephemeral_client) = init_res.authenticated_ephemeral_client {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ mod error;
|
|||||||
mod manager;
|
mod manager;
|
||||||
mod models;
|
mod models;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Backend {
|
pub struct Backend {
|
||||||
temporary_old_path: Option<PathBuf>,
|
temporary_old_path: Option<PathBuf>,
|
||||||
database_path: PathBuf,
|
database_path: PathBuf,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ pub mod fs_backend;
|
|||||||
#[error("no information provided")]
|
#[error("no information provided")]
|
||||||
pub struct UndefinedError;
|
pub struct UndefinedError;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Empty {
|
pub struct Empty {
|
||||||
// we need to keep 'basic' metadata here to "load" the CombinedReplyStorage
|
// we need to keep 'basic' metadata here to "load" the CombinedReplyStorage
|
||||||
pub min_surb_threshold: usize,
|
pub min_surb_threshold: usize,
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ license.workspace = true
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
dashmap = { workspace = true }
|
||||||
futures = { workspace = true }
|
futures = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
tokio = { workspace = true, features = ["time"] }
|
tokio = { workspace = true, features = ["time", "sync"] }
|
||||||
tokio-util = { workspace = true, features = ["codec"], optional = true }
|
tokio-util = { workspace = true, features = ["codec"], optional = true }
|
||||||
|
tokio-stream = { workspace = true }
|
||||||
|
|
||||||
# internal
|
# internal
|
||||||
nym-sphinx = { path = "../../nymsphinx" }
|
nym-sphinx = { path = "../../nymsphinx" }
|
||||||
|
|||||||
@@ -1,21 +1,24 @@
|
|||||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use futures::channel::mpsc;
|
use dashmap::DashMap;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use nym_sphinx::addressing::nodes::NymNodeRoutingAddress;
|
use nym_sphinx::addressing::nodes::NymNodeRoutingAddress;
|
||||||
use nym_sphinx::framing::codec::NymCodec;
|
use nym_sphinx::framing::codec::NymCodec;
|
||||||
use nym_sphinx::framing::packet::FramedNymPacket;
|
use nym_sphinx::framing::packet::FramedNymPacket;
|
||||||
use nym_sphinx::params::PacketType;
|
use nym_sphinx::params::PacketType;
|
||||||
use nym_sphinx::NymPacket;
|
use nym_sphinx::NymPacket;
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::sync::atomic::{AtomicU32, Ordering};
|
use std::ops::Deref;
|
||||||
|
use std::sync::atomic::{AtomicU32, AtomicUsize, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
use tokio::sync::mpsc::error::TrySendError;
|
||||||
use tokio::time::sleep;
|
use tokio::time::sleep;
|
||||||
|
use tokio_stream::wrappers::ReceiverStream;
|
||||||
use tokio_util::codec::Framed;
|
use tokio_util::codec::Framed;
|
||||||
use tracing::*;
|
use tracing::*;
|
||||||
|
|
||||||
@@ -55,11 +58,37 @@ pub trait SendWithoutResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
conn_new: HashMap<NymNodeRoutingAddress, ConnectionSender>,
|
active_connections: ActiveConnections,
|
||||||
|
connections_count: Arc<AtomicUsize>,
|
||||||
config: Config,
|
config: Config,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ConnectionSender {
|
#[derive(Default, Clone)]
|
||||||
|
pub struct ActiveConnections {
|
||||||
|
inner: Arc<DashMap<NymNodeRoutingAddress, ConnectionSender>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveConnections {
|
||||||
|
pub fn pending_packets(&self) -> usize {
|
||||||
|
self.inner
|
||||||
|
.iter()
|
||||||
|
.map(|sender| {
|
||||||
|
let max_capacity = sender.channel.max_capacity();
|
||||||
|
let capacity = sender.channel.capacity();
|
||||||
|
max_capacity - capacity
|
||||||
|
})
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for ActiveConnections {
|
||||||
|
type Target = DashMap<NymNodeRoutingAddress, ConnectionSender>;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ConnectionSender {
|
||||||
channel: mpsc::Sender<FramedNymPacket>,
|
channel: mpsc::Sender<FramedNymPacket>,
|
||||||
current_reconnection_attempt: Arc<AtomicU32>,
|
current_reconnection_attempt: Arc<AtomicU32>,
|
||||||
}
|
}
|
||||||
@@ -73,46 +102,53 @@ impl ConnectionSender {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
struct ManagedConnection {
|
||||||
pub fn new(config: Config) -> Client {
|
address: SocketAddr,
|
||||||
Client {
|
message_receiver: ReceiverStream<FramedNymPacket>,
|
||||||
conn_new: HashMap::new(),
|
connection_timeout: Duration,
|
||||||
config,
|
current_reconnection: Arc<AtomicU32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ManagedConnection {
|
||||||
|
fn new(
|
||||||
|
address: SocketAddr,
|
||||||
|
message_receiver: mpsc::Receiver<FramedNymPacket>,
|
||||||
|
connection_timeout: Duration,
|
||||||
|
current_reconnection: Arc<AtomicU32>,
|
||||||
|
) -> Self {
|
||||||
|
ManagedConnection {
|
||||||
|
address,
|
||||||
|
message_receiver: ReceiverStream::new(message_receiver),
|
||||||
|
connection_timeout,
|
||||||
|
current_reconnection,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn manage_connection(
|
async fn run(self) {
|
||||||
address: SocketAddr,
|
let address = self.address;
|
||||||
receiver: mpsc::Receiver<FramedNymPacket>,
|
|
||||||
connection_timeout: Duration,
|
|
||||||
current_reconnection: &AtomicU32,
|
|
||||||
) {
|
|
||||||
let connection_fut = TcpStream::connect(address);
|
let connection_fut = TcpStream::connect(address);
|
||||||
|
|
||||||
let conn = match tokio::time::timeout(connection_timeout, connection_fut).await {
|
let conn = match tokio::time::timeout(self.connection_timeout, connection_fut).await {
|
||||||
Ok(stream_res) => match stream_res {
|
Ok(stream_res) => match stream_res {
|
||||||
Ok(stream) => {
|
Ok(stream) => {
|
||||||
debug!("Managed to establish connection to {}", address);
|
debug!("Managed to establish connection to {}", self.address);
|
||||||
// if we managed to connect, reset the reconnection count (whatever it might have been)
|
// if we managed to connect, reset the reconnection count (whatever it might have been)
|
||||||
current_reconnection.store(0, Ordering::Release);
|
self.current_reconnection.store(0, Ordering::Release);
|
||||||
Framed::new(stream, NymCodec)
|
Framed::new(stream, NymCodec)
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
debug!(
|
debug!("failed to establish connection to {address} (err: {err})",);
|
||||||
"failed to establish connection to {} (err: {})",
|
|
||||||
address, err
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
debug!(
|
debug!(
|
||||||
"failed to connect to {} within {:?}",
|
"failed to connect to {address} within {:?}",
|
||||||
address, connection_timeout
|
self.connection_timeout
|
||||||
);
|
);
|
||||||
|
|
||||||
// we failed to connect - increase reconnection attempt
|
// we failed to connect - increase reconnection attempt
|
||||||
current_reconnection.fetch_add(1, Ordering::SeqCst);
|
self.current_reconnection.fetch_add(1, Ordering::SeqCst);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -120,15 +156,28 @@ impl Client {
|
|||||||
// Take whatever the receiver channel produces and put it on the connection.
|
// Take whatever the receiver channel produces and put it on the connection.
|
||||||
// We could have as well used conn.send_all(receiver.map(Ok)), but considering we don't care
|
// We could have as well used conn.send_all(receiver.map(Ok)), but considering we don't care
|
||||||
// about neither receiver nor the connection, it doesn't matter which one gets consumed
|
// about neither receiver nor the connection, it doesn't matter which one gets consumed
|
||||||
if let Err(err) = receiver.map(Ok).forward(conn).await {
|
if let Err(err) = self.message_receiver.map(Ok).forward(conn).await {
|
||||||
warn!("Failed to forward packets to {} - {err}", address);
|
warn!("Failed to forward packets to {address}: {err}");
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
"connection manager to {} is finished. Either the connection failed or mixnet client got dropped",
|
"connection manager to {address} is finished. Either the connection failed or mixnet client got dropped",
|
||||||
address
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Client {
|
||||||
|
pub fn new(config: Config, connections_count: Arc<AtomicUsize>) -> Client {
|
||||||
|
Client {
|
||||||
|
active_connections: Default::default(),
|
||||||
|
connections_count,
|
||||||
|
config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn active_connections(&self) -> ActiveConnections {
|
||||||
|
self.active_connections.clone()
|
||||||
|
}
|
||||||
|
|
||||||
/// If we're trying to reconnect, determine how long we should wait.
|
/// If we're trying to reconnect, determine how long we should wait.
|
||||||
fn determine_backoff(&self, current_attempt: u32) -> Option<Duration> {
|
fn determine_backoff(&self, current_attempt: u32) -> Option<Duration> {
|
||||||
@@ -148,7 +197,7 @@ impl Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn make_connection(&mut self, address: NymNodeRoutingAddress, pending_packet: FramedNymPacket) {
|
fn make_connection(&mut self, address: NymNodeRoutingAddress, pending_packet: FramedNymPacket) {
|
||||||
let (mut sender, receiver) = mpsc::channel(self.config.maximum_connection_buffer_size);
|
let (sender, receiver) = mpsc::channel(self.config.maximum_connection_buffer_size);
|
||||||
|
|
||||||
// this CAN'T fail because we just created the channel which has a non-zero capacity
|
// this CAN'T fail because we just created the channel which has a non-zero capacity
|
||||||
if self.config.maximum_connection_buffer_size > 0 {
|
if self.config.maximum_connection_buffer_size > 0 {
|
||||||
@@ -156,15 +205,16 @@ impl Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if we already tried to connect to `address` before, grab the current attempt count
|
// if we already tried to connect to `address` before, grab the current attempt count
|
||||||
let current_reconnection_attempt = if let Some(existing) = self.conn_new.get_mut(&address) {
|
let current_reconnection_attempt =
|
||||||
existing.channel = sender;
|
if let Some(mut existing) = self.active_connections.get_mut(&address) {
|
||||||
Arc::clone(&existing.current_reconnection_attempt)
|
existing.channel = sender;
|
||||||
} else {
|
Arc::clone(&existing.current_reconnection_attempt)
|
||||||
let new_entry = ConnectionSender::new(sender);
|
} else {
|
||||||
let current_attempt = Arc::clone(&new_entry.current_reconnection_attempt);
|
let new_entry = ConnectionSender::new(sender);
|
||||||
self.conn_new.insert(address, new_entry);
|
let current_attempt = Arc::clone(&new_entry.current_reconnection_attempt);
|
||||||
current_attempt
|
self.active_connections.insert(address, new_entry);
|
||||||
};
|
current_attempt
|
||||||
|
};
|
||||||
|
|
||||||
// load the actual value.
|
// load the actual value.
|
||||||
let reconnection_attempt = current_reconnection_attempt.load(Ordering::Acquire);
|
let reconnection_attempt = current_reconnection_attempt.load(Ordering::Acquire);
|
||||||
@@ -173,6 +223,7 @@ impl Client {
|
|||||||
// copy the value before moving into another task
|
// copy the value before moving into another task
|
||||||
let initial_connection_timeout = self.config.initial_connection_timeout;
|
let initial_connection_timeout = self.config.initial_connection_timeout;
|
||||||
|
|
||||||
|
let connections_count = self.connections_count.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
// before executing the manager, wait for what was specified, if anything
|
// before executing the manager, wait for what was specified, if anything
|
||||||
if let Some(backoff) = backoff {
|
if let Some(backoff) = backoff {
|
||||||
@@ -180,13 +231,16 @@ impl Client {
|
|||||||
sleep(backoff).await;
|
sleep(backoff).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::manage_connection(
|
connections_count.fetch_add(1, Ordering::SeqCst);
|
||||||
|
ManagedConnection::new(
|
||||||
address.into(),
|
address.into(),
|
||||||
receiver,
|
receiver,
|
||||||
initial_connection_timeout,
|
initial_connection_timeout,
|
||||||
¤t_reconnection_attempt,
|
current_reconnection_attempt,
|
||||||
)
|
)
|
||||||
.await
|
.run()
|
||||||
|
.await;
|
||||||
|
connections_count.fetch_sub(1, Ordering::SeqCst);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -201,49 +255,47 @@ impl SendWithoutResponse for Client {
|
|||||||
trace!("Sending packet to {address:?}");
|
trace!("Sending packet to {address:?}");
|
||||||
let framed_packet = FramedNymPacket::new(packet, packet_type);
|
let framed_packet = FramedNymPacket::new(packet, packet_type);
|
||||||
|
|
||||||
if let Some(sender) = self.conn_new.get_mut(&address) {
|
let Some(sender) = self.active_connections.get_mut(&address) else {
|
||||||
if let Err(err) = sender.channel.try_send(framed_packet) {
|
|
||||||
if err.is_full() {
|
|
||||||
debug!("Connection to {} seems to not be able to handle all the traffic - dropping the current packet", address);
|
|
||||||
// it's not a 'big' error, but we did not manage to send the packet
|
|
||||||
// if the queue is full, we can't really do anything but to drop the packet
|
|
||||||
Err(io::Error::new(
|
|
||||||
io::ErrorKind::WouldBlock,
|
|
||||||
"connection queue is full",
|
|
||||||
))
|
|
||||||
} else if err.is_disconnected() {
|
|
||||||
debug!(
|
|
||||||
"Connection to {} seems to be dead. attempting to re-establish it...",
|
|
||||||
address
|
|
||||||
);
|
|
||||||
// it's not a 'big' error, but we did not manage to send the packet, but queue
|
|
||||||
// it up to send it as soon as the connection is re-established
|
|
||||||
self.make_connection(address, err.into_inner());
|
|
||||||
Err(io::Error::new(
|
|
||||||
io::ErrorKind::ConnectionAborted,
|
|
||||||
"reconnection attempt is in progress",
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
// this can't really happen, but let's safe-guard against it in case something changes in futures library
|
|
||||||
Err(io::Error::new(
|
|
||||||
io::ErrorKind::Other,
|
|
||||||
"unknown connection buffer error",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// there was never a connection to begin with
|
// there was never a connection to begin with
|
||||||
debug!("establishing initial connection to {}", address);
|
debug!("establishing initial connection to {}", address);
|
||||||
// it's not a 'big' error, but we did not manage to send the packet, but queue the packet
|
// it's not a 'big' error, but we did not manage to send the packet, but queue the packet
|
||||||
// for sending for as soon as the connection is created
|
// for sending for as soon as the connection is created
|
||||||
self.make_connection(address, framed_packet);
|
self.make_connection(address, framed_packet);
|
||||||
Err(io::Error::new(
|
return Err(io::Error::new(
|
||||||
io::ErrorKind::NotConnected,
|
io::ErrorKind::NotConnected,
|
||||||
"connection is in progress",
|
"connection is in progress",
|
||||||
))
|
));
|
||||||
}
|
};
|
||||||
|
|
||||||
|
let sending_res = sender.channel.try_send(framed_packet);
|
||||||
|
drop(sender);
|
||||||
|
|
||||||
|
sending_res.map_err(|err| {
|
||||||
|
match err {
|
||||||
|
TrySendError::Full(_) => {
|
||||||
|
debug!("Connection to {address} seems to not be able to handle all the traffic - dropping the current packet");
|
||||||
|
// it's not a 'big' error, but we did not manage to send the packet
|
||||||
|
// if the queue is full, we can't really do anything but to drop the packet
|
||||||
|
io::Error::new(
|
||||||
|
io::ErrorKind::WouldBlock,
|
||||||
|
"connection queue is full",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
TrySendError::Closed(dropped) => {
|
||||||
|
debug!(
|
||||||
|
"Connection to {address} seems to be dead. attempting to re-establish it...",
|
||||||
|
);
|
||||||
|
|
||||||
|
// it's not a 'big' error, but we did not manage to send the packet, but queue
|
||||||
|
// it up to send it as soon as the connection is re-established
|
||||||
|
self.make_connection(address, dropped);
|
||||||
|
io::Error::new(
|
||||||
|
io::ErrorKind::ConnectionAborted,
|
||||||
|
"reconnection attempt is in progress",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,12 +304,15 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn dummy_client() -> Client {
|
fn dummy_client() -> Client {
|
||||||
Client::new(Config {
|
Client::new(
|
||||||
initial_reconnection_backoff: Duration::from_millis(10_000),
|
Config {
|
||||||
maximum_reconnection_backoff: Duration::from_millis(300_000),
|
initial_reconnection_backoff: Duration::from_millis(10_000),
|
||||||
initial_connection_timeout: Duration::from_millis(1_500),
|
maximum_reconnection_backoff: Duration::from_millis(300_000),
|
||||||
maximum_connection_buffer_size: 128,
|
initial_connection_timeout: Duration::from_millis(1_500),
|
||||||
})
|
maximum_connection_buffer_size: 128,
|
||||||
|
},
|
||||||
|
Default::default(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -19,8 +19,9 @@ use nym_api_requests::ecash::{
|
|||||||
PartialExpirationDateSignatureResponse, VerificationKeyResponse,
|
PartialExpirationDateSignatureResponse, VerificationKeyResponse,
|
||||||
};
|
};
|
||||||
use nym_api_requests::models::{
|
use nym_api_requests::models::{
|
||||||
ApiHealthResponse, GatewayBondAnnotated, GatewayCoreStatusResponse, MixnodeCoreStatusResponse,
|
ApiHealthResponse, GatewayBondAnnotated, GatewayCoreStatusResponse,
|
||||||
MixnodeStatusResponse, NymNodeDescription, RewardEstimationResponse, StakeSaturationResponse,
|
HistoricalPerformanceResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse,
|
||||||
|
NymNodeDescription, RewardEstimationResponse, StakeSaturationResponse,
|
||||||
};
|
};
|
||||||
use nym_api_requests::models::{LegacyDescribedGateway, MixNodeBondAnnotated};
|
use nym_api_requests::models::{LegacyDescribedGateway, MixNodeBondAnnotated};
|
||||||
use nym_api_requests::nym_nodes::SkimmedNode;
|
use nym_api_requests::nym_nodes::SkimmedNode;
|
||||||
@@ -264,6 +265,31 @@ impl<C, S> Client<C, S> {
|
|||||||
Ok(self.nym_api.get_gateways_detailed_unfiltered().await?)
|
Ok(self.nym_api.get_gateways_detailed_unfiltered().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_full_node_performance_history(
|
||||||
|
&self,
|
||||||
|
node_id: NodeId,
|
||||||
|
) -> Result<Vec<HistoricalPerformanceResponse>, ValidatorClientError> {
|
||||||
|
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
|
||||||
|
let mut page = 0;
|
||||||
|
let mut history = Vec::new();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let mut res = self
|
||||||
|
.nym_api
|
||||||
|
.get_node_performance_history(node_id, Some(page), None)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
history.append(&mut res.history.data);
|
||||||
|
if history.len() < res.history.pagination.total {
|
||||||
|
page += 1
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(history)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: combine with NymApiClient...
|
// TODO: combine with NymApiClient...
|
||||||
pub async fn get_all_cached_described_nodes(
|
pub async fn get_all_cached_described_nodes(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use nym_api_requests::ecash::models::{
|
|||||||
use nym_api_requests::ecash::VerificationKeyResponse;
|
use nym_api_requests::ecash::VerificationKeyResponse;
|
||||||
use nym_api_requests::models::{
|
use nym_api_requests::models::{
|
||||||
AnnotationResponse, ApiHealthResponse, LegacyDescribedMixNode, NodePerformanceResponse,
|
AnnotationResponse, ApiHealthResponse, LegacyDescribedMixNode, NodePerformanceResponse,
|
||||||
NodeRefreshBody, NymNodeDescription, RewardedSetResponse,
|
NodeRefreshBody, NymNodeDescription, PerformanceHistoryResponse, RewardedSetResponse,
|
||||||
};
|
};
|
||||||
use nym_api_requests::nym_nodes::PaginatedCachedNodesResponse;
|
use nym_api_requests::nym_nodes::PaginatedCachedNodesResponse;
|
||||||
use nym_api_requests::pagination::PaginatedResponse;
|
use nym_api_requests::pagination::PaginatedResponse;
|
||||||
@@ -163,6 +163,35 @@ pub trait NymApiClientExt: ApiClient {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "debug", skip_all)]
|
||||||
|
async fn get_node_performance_history(
|
||||||
|
&self,
|
||||||
|
node_id: NodeId,
|
||||||
|
page: Option<u32>,
|
||||||
|
per_page: Option<u32>,
|
||||||
|
) -> Result<PerformanceHistoryResponse, NymAPIError> {
|
||||||
|
let mut params = Vec::new();
|
||||||
|
|
||||||
|
if let Some(page) = page {
|
||||||
|
params.push(("page", page.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(per_page) = per_page {
|
||||||
|
params.push(("per_page", per_page.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
self.get_json(
|
||||||
|
&[
|
||||||
|
routes::API_VERSION,
|
||||||
|
routes::NYM_NODES_ROUTES,
|
||||||
|
routes::NYM_NODES_PERFORMANCE_HISTORY,
|
||||||
|
&*node_id.to_string(),
|
||||||
|
],
|
||||||
|
¶ms,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip_all)]
|
#[tracing::instrument(level = "debug", skip_all)]
|
||||||
async fn get_nodes_described(
|
async fn get_nodes_described(
|
||||||
&self,
|
&self,
|
||||||
@@ -179,8 +208,15 @@ pub trait NymApiClientExt: ApiClient {
|
|||||||
params.push(("per_page", per_page.to_string()))
|
params.push(("per_page", per_page.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
self.get_json(&[routes::API_VERSION, "nym-nodes", "described"], ¶ms)
|
self.get_json(
|
||||||
.await
|
&[
|
||||||
|
routes::API_VERSION,
|
||||||
|
routes::NYM_NODES_ROUTES,
|
||||||
|
routes::NYM_NODES_DESCRIBED,
|
||||||
|
],
|
||||||
|
¶ms,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip_all)]
|
#[tracing::instrument(level = "debug", skip_all)]
|
||||||
@@ -199,8 +235,15 @@ pub trait NymApiClientExt: ApiClient {
|
|||||||
params.push(("per_page", per_page.to_string()))
|
params.push(("per_page", per_page.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
self.get_json(&[routes::API_VERSION, "nym-nodes", "bonded"], ¶ms)
|
self.get_json(
|
||||||
.await
|
&[
|
||||||
|
routes::API_VERSION,
|
||||||
|
routes::NYM_NODES_ROUTES,
|
||||||
|
routes::NYM_NODES_BONDED,
|
||||||
|
],
|
||||||
|
¶ms,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deprecated]
|
#[deprecated]
|
||||||
@@ -210,7 +253,7 @@ pub trait NymApiClientExt: ApiClient {
|
|||||||
&[
|
&[
|
||||||
routes::API_VERSION,
|
routes::API_VERSION,
|
||||||
"unstable",
|
"unstable",
|
||||||
"nym-nodes",
|
routes::NYM_NODES_ROUTES,
|
||||||
"mixnodes",
|
"mixnodes",
|
||||||
"skimmed",
|
"skimmed",
|
||||||
],
|
],
|
||||||
@@ -226,7 +269,7 @@ pub trait NymApiClientExt: ApiClient {
|
|||||||
&[
|
&[
|
||||||
routes::API_VERSION,
|
routes::API_VERSION,
|
||||||
"unstable",
|
"unstable",
|
||||||
"nym-nodes",
|
routes::NYM_NODES_ROUTES,
|
||||||
"gateways",
|
"gateways",
|
||||||
"skimmed",
|
"skimmed",
|
||||||
],
|
],
|
||||||
@@ -238,7 +281,11 @@ pub trait NymApiClientExt: ApiClient {
|
|||||||
#[instrument(level = "debug", skip(self))]
|
#[instrument(level = "debug", skip(self))]
|
||||||
async fn get_rewarded_set(&self) -> Result<RewardedSetResponse, NymAPIError> {
|
async fn get_rewarded_set(&self) -> Result<RewardedSetResponse, NymAPIError> {
|
||||||
self.get_json(
|
self.get_json(
|
||||||
&[routes::API_VERSION, "nym-nodes", "rewarded-set"],
|
&[
|
||||||
|
routes::API_VERSION,
|
||||||
|
routes::NYM_NODES_ROUTES,
|
||||||
|
routes::NYM_NODES_REWARDED_SET,
|
||||||
|
],
|
||||||
NO_PARAMS,
|
NO_PARAMS,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -271,7 +318,7 @@ pub trait NymApiClientExt: ApiClient {
|
|||||||
&[
|
&[
|
||||||
routes::API_VERSION,
|
routes::API_VERSION,
|
||||||
"unstable",
|
"unstable",
|
||||||
"nym-nodes",
|
routes::NYM_NODES_ROUTES,
|
||||||
"skimmed",
|
"skimmed",
|
||||||
"entry-gateways",
|
"entry-gateways",
|
||||||
"all",
|
"all",
|
||||||
@@ -308,7 +355,7 @@ pub trait NymApiClientExt: ApiClient {
|
|||||||
&[
|
&[
|
||||||
routes::API_VERSION,
|
routes::API_VERSION,
|
||||||
"unstable",
|
"unstable",
|
||||||
"nym-nodes",
|
routes::NYM_NODES_ROUTES,
|
||||||
"skimmed",
|
"skimmed",
|
||||||
"mixnodes",
|
"mixnodes",
|
||||||
"active",
|
"active",
|
||||||
@@ -345,7 +392,7 @@ pub trait NymApiClientExt: ApiClient {
|
|||||||
&[
|
&[
|
||||||
routes::API_VERSION,
|
routes::API_VERSION,
|
||||||
"unstable",
|
"unstable",
|
||||||
"nym-nodes",
|
routes::NYM_NODES_ROUTES,
|
||||||
"skimmed",
|
"skimmed",
|
||||||
"mixnodes",
|
"mixnodes",
|
||||||
"all",
|
"all",
|
||||||
@@ -377,7 +424,12 @@ pub trait NymApiClientExt: ApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.get_json(
|
self.get_json(
|
||||||
&[routes::API_VERSION, "unstable", "nym-nodes", "skimmed"],
|
&[
|
||||||
|
routes::API_VERSION,
|
||||||
|
"unstable",
|
||||||
|
routes::NYM_NODES_ROUTES,
|
||||||
|
"skimmed",
|
||||||
|
],
|
||||||
¶ms,
|
¶ms,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -686,8 +738,8 @@ pub trait NymApiClientExt: ApiClient {
|
|||||||
self.get_json(
|
self.get_json(
|
||||||
&[
|
&[
|
||||||
routes::API_VERSION,
|
routes::API_VERSION,
|
||||||
"nym-nodes",
|
routes::NYM_NODES_ROUTES,
|
||||||
"performance",
|
routes::NYM_NODES_PERFORMANCE,
|
||||||
&node_id.to_string(),
|
&node_id.to_string(),
|
||||||
],
|
],
|
||||||
NO_PARAMS,
|
NO_PARAMS,
|
||||||
@@ -702,8 +754,8 @@ pub trait NymApiClientExt: ApiClient {
|
|||||||
self.get_json(
|
self.get_json(
|
||||||
&[
|
&[
|
||||||
routes::API_VERSION,
|
routes::API_VERSION,
|
||||||
"nym-nodes",
|
routes::NYM_NODES_ROUTES,
|
||||||
"annotation",
|
routes::NYM_NODES_ANNOTATION,
|
||||||
&node_id.to_string(),
|
&node_id.to_string(),
|
||||||
],
|
],
|
||||||
NO_PARAMS,
|
NO_PARAMS,
|
||||||
@@ -927,7 +979,11 @@ pub trait NymApiClientExt: ApiClient {
|
|||||||
request: &NodeRefreshBody,
|
request: &NodeRefreshBody,
|
||||||
) -> Result<(), NymAPIError> {
|
) -> Result<(), NymAPIError> {
|
||||||
self.post_json(
|
self.post_json(
|
||||||
&[routes::API_VERSION, "nym-nodes", "refresh-described"],
|
&[
|
||||||
|
routes::API_VERSION,
|
||||||
|
routes::NYM_NODES_ROUTES,
|
||||||
|
routes::NYM_NODES_REFRESH_DESCRIBED,
|
||||||
|
],
|
||||||
NO_PARAMS,
|
NO_PARAMS,
|
||||||
request,
|
request,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -34,6 +34,19 @@ pub mod ecash {
|
|||||||
pub const EPOCH_ID_PARAM: &str = "epoch_id";
|
pub const EPOCH_ID_PARAM: &str = "epoch_id";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const NYM_NODES_ROUTES: &str = "nym-nodes";
|
||||||
|
|
||||||
|
pub use nym_nodes::*;
|
||||||
|
pub mod nym_nodes {
|
||||||
|
pub const NYM_NODES_PERFORMANCE_HISTORY: &str = "performance-history";
|
||||||
|
pub const NYM_NODES_PERFORMANCE: &str = "performance";
|
||||||
|
pub const NYM_NODES_ANNOTATION: &str = "annotation";
|
||||||
|
pub const NYM_NODES_DESCRIBED: &str = "described";
|
||||||
|
pub const NYM_NODES_BONDED: &str = "bonded";
|
||||||
|
pub const NYM_NODES_REWARDED_SET: &str = "rewarded-set";
|
||||||
|
pub const NYM_NODES_REFRESH_DESCRIBED: &str = "refresh-described";
|
||||||
|
}
|
||||||
|
|
||||||
pub const STATUS_ROUTES: &str = "status";
|
pub const STATUS_ROUTES: &str = "status";
|
||||||
pub const API_STATUS_ROUTES: &str = "api-status";
|
pub const API_STATUS_ROUTES: &str = "api-status";
|
||||||
pub const HEALTH: &str = "health";
|
pub const HEALTH: &str = "health";
|
||||||
|
|||||||
+31
@@ -153,13 +153,20 @@ pub trait CosmWasmClient: TendermintRpcClient {
|
|||||||
let req = QueryAllBalancesRequest {
|
let req = QueryAllBalancesRequest {
|
||||||
address: address.to_string(),
|
address: address.to_string(),
|
||||||
pagination,
|
pagination,
|
||||||
|
resolve_denom: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut res = self
|
let mut res = self
|
||||||
.make_abci_query::<_, QueryAllBalancesResponse>(path.clone(), req)
|
.make_abci_query::<_, QueryAllBalancesResponse>(path.clone(), req)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
let early_break = res.balances.is_empty();
|
||||||
raw_balances.append(&mut res.balances);
|
raw_balances.append(&mut res.balances);
|
||||||
|
|
||||||
|
if early_break {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(next_key) = next_page_key(res.pagination) {
|
if let Some(next_key) = next_page_key(res.pagination) {
|
||||||
pagination = Some(create_pagination(next_key))
|
pagination = Some(create_pagination(next_key))
|
||||||
} else {
|
} else {
|
||||||
@@ -187,7 +194,13 @@ pub trait CosmWasmClient: TendermintRpcClient {
|
|||||||
.make_abci_query::<_, QueryTotalSupplyResponse>(path.clone(), req)
|
.make_abci_query::<_, QueryTotalSupplyResponse>(path.clone(), req)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
let early_break = res.supply.is_empty();
|
||||||
supply.append(&mut res.supply);
|
supply.append(&mut res.supply);
|
||||||
|
|
||||||
|
if early_break {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(next_key) = next_page_key(res.pagination) {
|
if let Some(next_key) = next_page_key(res.pagination) {
|
||||||
pagination = Some(create_pagination(next_key))
|
pagination = Some(create_pagination(next_key))
|
||||||
} else {
|
} else {
|
||||||
@@ -328,7 +341,13 @@ pub trait CosmWasmClient: TendermintRpcClient {
|
|||||||
.make_abci_query::<_, QueryCodesResponse>(path.clone(), req)
|
.make_abci_query::<_, QueryCodesResponse>(path.clone(), req)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
let early_break = res.code_infos.is_empty();
|
||||||
raw_codes.append(&mut res.code_infos);
|
raw_codes.append(&mut res.code_infos);
|
||||||
|
|
||||||
|
if early_break {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(next_key) = next_page_key(res.pagination) {
|
if let Some(next_key) = next_page_key(res.pagination) {
|
||||||
pagination = Some(create_pagination(next_key))
|
pagination = Some(create_pagination(next_key))
|
||||||
} else {
|
} else {
|
||||||
@@ -373,7 +392,13 @@ pub trait CosmWasmClient: TendermintRpcClient {
|
|||||||
.make_abci_query::<_, QueryContractsByCodeResponse>(path.clone(), req)
|
.make_abci_query::<_, QueryContractsByCodeResponse>(path.clone(), req)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
let early_break = res.contracts.is_empty();
|
||||||
raw_contracts.append(&mut res.contracts);
|
raw_contracts.append(&mut res.contracts);
|
||||||
|
|
||||||
|
if early_break {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(next_key) = next_page_key(res.pagination) {
|
if let Some(next_key) = next_page_key(res.pagination) {
|
||||||
pagination = Some(create_pagination(next_key))
|
pagination = Some(create_pagination(next_key))
|
||||||
} else {
|
} else {
|
||||||
@@ -429,7 +454,13 @@ pub trait CosmWasmClient: TendermintRpcClient {
|
|||||||
.make_abci_query::<_, QueryContractHistoryResponse>(path.clone(), req)
|
.make_abci_query::<_, QueryContractHistoryResponse>(path.clone(), req)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
let early_break = res.entries.is_empty();
|
||||||
raw_entries.append(&mut res.entries);
|
raw_entries.append(&mut res.entries);
|
||||||
|
|
||||||
|
if early_break {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(next_key) = next_page_key(res.pagination) {
|
if let Some(next_key) = next_page_key(res.pagination) {
|
||||||
pagination = Some(create_pagination(next_key))
|
pagination = Some(create_pagination(next_key))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -4,9 +4,11 @@
|
|||||||
use crate::rpc::TendermintRpcClient;
|
use crate::rpc::TendermintRpcClient;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
|
use cosmrs::tendermint;
|
||||||
use cosmrs::tendermint::{block::Height, evidence::Evidence, Hash};
|
use cosmrs::tendermint::{block::Height, evidence::Evidence, Hash};
|
||||||
use reqwest::header::HeaderMap;
|
use reqwest::header::HeaderMap;
|
||||||
use reqwest::{header, RequestBuilder};
|
use reqwest::{header, RequestBuilder};
|
||||||
|
use tendermint_rpc::dialect::{v0_34, v0_37, v0_38, LatestDialect};
|
||||||
use tendermint_rpc::{
|
use tendermint_rpc::{
|
||||||
client::CompatMode,
|
client::CompatMode,
|
||||||
dialect::{self, Dialect},
|
dialect::{self, Dialect},
|
||||||
@@ -21,8 +23,21 @@ macro_rules! perform_with_compat {
|
|||||||
($self:expr, $request:expr) => {{
|
($self:expr, $request:expr) => {{
|
||||||
let request = $request;
|
let request = $request;
|
||||||
match $self.compat {
|
match $self.compat {
|
||||||
CompatMode::V0_37 => $self.perform_v0_37(request).await,
|
CompatMode::V0_38 => {
|
||||||
CompatMode::V0_34 => $self.perform_v0_34(request).await,
|
$self
|
||||||
|
.perform_request_with_dialect(request, dialect::v0_38::Dialect)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
CompatMode::V0_37 => {
|
||||||
|
$self
|
||||||
|
.perform_request_with_dialect(request, dialect::v0_37::Dialect)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
CompatMode::V0_34 => {
|
||||||
|
$self
|
||||||
|
.perform_request_with_dialect(request, dialect::v0_34::Dialect)
|
||||||
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
@@ -70,7 +85,11 @@ impl ReqwestRpcClient {
|
|||||||
.headers(headers)
|
.headers(headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn perform_request<R, S>(&self, request: R) -> Result<R::Output, Error>
|
async fn perform_request_with_dialect<R, S>(
|
||||||
|
&self,
|
||||||
|
request: R,
|
||||||
|
_dialect: S,
|
||||||
|
) -> Result<R::Output, Error>
|
||||||
where
|
where
|
||||||
R: SimpleRequest<S>,
|
R: SimpleRequest<S>,
|
||||||
S: Dialect,
|
S: Dialect,
|
||||||
@@ -81,26 +100,25 @@ impl ReqwestRpcClient {
|
|||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(TendermintRpcErrorMap::into_rpc_err)?;
|
.map_err(TendermintRpcErrorMap::into_rpc_err)?;
|
||||||
|
let response_status = response.status();
|
||||||
let bytes = response
|
let bytes = response
|
||||||
.bytes()
|
.bytes()
|
||||||
.await
|
.await
|
||||||
.map_err(TendermintRpcErrorMap::into_rpc_err)?;
|
.map_err(TendermintRpcErrorMap::into_rpc_err)?;
|
||||||
|
|
||||||
|
// Successful JSON-RPC requests are expected to return a 200 OK HTTP status.
|
||||||
|
// Otherwise, this means that the HTTP request failed as a whole,
|
||||||
|
// as opposed to the JSON-RPC request returning an error,
|
||||||
|
// and we cannot expect the response body to be a valid JSON-RPC response.
|
||||||
|
if response_status != reqwest::StatusCode::OK {
|
||||||
|
// hehe, that's so nasty but we have to somehow convert between different versions of the same lib
|
||||||
|
return Err(Error::http_request_failed(
|
||||||
|
response_status.as_u16().try_into().unwrap(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
R::Response::from_string(bytes).map(Into::into)
|
R::Response::from_string(bytes).map(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn perform_v0_34<R>(&self, request: R) -> Result<R::Output, Error>
|
|
||||||
where
|
|
||||||
R: SimpleRequest<dialect::v0_34::Dialect>,
|
|
||||||
{
|
|
||||||
self.perform_request(request).await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn perform_v0_37<R>(&self, request: R) -> Result<R::Output, Error>
|
|
||||||
where
|
|
||||||
R: SimpleRequest<dialect::v0_37::Dialect>,
|
|
||||||
{
|
|
||||||
self.perform_request(request).await
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
trait TendermintRpcErrorMap {
|
trait TendermintRpcErrorMap {
|
||||||
@@ -120,18 +138,50 @@ impl TendermintRpcClient for ReqwestRpcClient {
|
|||||||
where
|
where
|
||||||
R: SimpleRequest,
|
R: SimpleRequest,
|
||||||
{
|
{
|
||||||
self.perform_request(request).await
|
self.perform_request_with_dialect(request, LatestDialect)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn block_results<H>(&self, height: H) -> Result<block_results::Response, Error>
|
async fn block<H>(&self, height: H) -> Result<endpoint::block::Response, Error>
|
||||||
where
|
where
|
||||||
H: Into<Height> + Send,
|
H: Into<Height> + Send,
|
||||||
{
|
{
|
||||||
perform_with_compat!(self, block_results::Request::new(height.into()))
|
perform_with_compat!(self, endpoint::block::Request::new(height.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn latest_block_results(&self) -> Result<block_results::Response, Error> {
|
async fn block_by_hash(
|
||||||
perform_with_compat!(self, block_results::Request::default())
|
&self,
|
||||||
|
hash: tendermint::Hash,
|
||||||
|
) -> Result<endpoint::block_by_hash::Response, Error> {
|
||||||
|
perform_with_compat!(self, endpoint::block_by_hash::Request::new(hash))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn latest_block(&self) -> Result<endpoint::block::Response, Error> {
|
||||||
|
perform_with_compat!(self, endpoint::block::Request::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn block_results<H>(&self, height: H) -> Result<endpoint::block_results::Response, Error>
|
||||||
|
where
|
||||||
|
H: Into<Height> + Send,
|
||||||
|
{
|
||||||
|
perform_with_compat!(self, endpoint::block_results::Request::new(height.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn latest_block_results(&self) -> Result<endpoint::block_results::Response, Error> {
|
||||||
|
perform_with_compat!(self, endpoint::block_results::Request::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn block_search(
|
||||||
|
&self,
|
||||||
|
query: Query,
|
||||||
|
page: u32,
|
||||||
|
per_page: u8,
|
||||||
|
order: Order,
|
||||||
|
) -> Result<endpoint::block_search::Response, Error> {
|
||||||
|
perform_with_compat!(
|
||||||
|
self,
|
||||||
|
endpoint::block_search::Request::new(query, page, per_page, order)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn header<H>(&self, height: H) -> Result<endpoint::header::Response, Error>
|
async fn header<H>(&self, height: H) -> Result<endpoint::header::Response, Error>
|
||||||
@@ -140,11 +190,26 @@ impl TendermintRpcClient for ReqwestRpcClient {
|
|||||||
{
|
{
|
||||||
let height = height.into();
|
let height = height.into();
|
||||||
match self.compat {
|
match self.compat {
|
||||||
CompatMode::V0_37 => self.perform(endpoint::header::Request::new(height)).await,
|
CompatMode::V0_38 => {
|
||||||
|
self.perform_request_with_dialect(
|
||||||
|
endpoint::header::Request::new(height),
|
||||||
|
v0_38::Dialect,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
CompatMode::V0_37 => {
|
||||||
|
self.perform_request_with_dialect(
|
||||||
|
endpoint::header::Request::new(height),
|
||||||
|
v0_37::Dialect,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
CompatMode::V0_34 => {
|
CompatMode::V0_34 => {
|
||||||
// Back-fill with a request to /block endpoint and
|
// Back-fill with a request to /block endpoint and
|
||||||
// taking just the header from the response.
|
// taking just the header from the response.
|
||||||
let resp = self.perform_v0_34(block::Request::new(height)).await?;
|
let resp = self
|
||||||
|
.perform_request_with_dialect(block::Request::new(height), v0_34::Dialect)
|
||||||
|
.await?;
|
||||||
Ok(resp.into())
|
Ok(resp.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,12 +217,25 @@ impl TendermintRpcClient for ReqwestRpcClient {
|
|||||||
|
|
||||||
async fn header_by_hash(&self, hash: Hash) -> Result<header_by_hash::Response, Error> {
|
async fn header_by_hash(&self, hash: Hash) -> Result<header_by_hash::Response, Error> {
|
||||||
match self.compat {
|
match self.compat {
|
||||||
CompatMode::V0_37 => self.perform(header_by_hash::Request::new(hash)).await,
|
CompatMode::V0_38 => {
|
||||||
|
self.perform_request_with_dialect(
|
||||||
|
header_by_hash::Request::new(hash),
|
||||||
|
v0_38::Dialect,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
CompatMode::V0_37 => {
|
||||||
|
self.perform_request_with_dialect(
|
||||||
|
header_by_hash::Request::new(hash),
|
||||||
|
v0_37::Dialect,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
CompatMode::V0_34 => {
|
CompatMode::V0_34 => {
|
||||||
// Back-fill with a request to /block_by_hash endpoint and
|
// Back-fill with a request to /block_by_hash endpoint and
|
||||||
// taking just the header from the response.
|
// taking just the header from the response.
|
||||||
let resp = self
|
let resp = self
|
||||||
.perform_v0_34(block_by_hash::Request::new(hash))
|
.perform_request_with_dialect(block_by_hash::Request::new(hash), v0_34::Dialect)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(resp.into())
|
Ok(resp.into())
|
||||||
}
|
}
|
||||||
@@ -167,8 +245,18 @@ impl TendermintRpcClient for ReqwestRpcClient {
|
|||||||
/// `/broadcast_evidence`: broadcast an evidence.
|
/// `/broadcast_evidence`: broadcast an evidence.
|
||||||
async fn broadcast_evidence(&self, e: Evidence) -> Result<evidence::Response, Error> {
|
async fn broadcast_evidence(&self, e: Evidence) -> Result<evidence::Response, Error> {
|
||||||
match self.compat {
|
match self.compat {
|
||||||
CompatMode::V0_37 => self.perform(evidence::Request::new(e)).await,
|
CompatMode::V0_38 => {
|
||||||
CompatMode::V0_34 => self.perform_v0_34(evidence::Request::new(e)).await,
|
self.perform_request_with_dialect(evidence::Request::new(e), v0_38::Dialect)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
CompatMode::V0_37 => {
|
||||||
|
self.perform_request_with_dialect(evidence::Request::new(e), v0_37::Dialect)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
CompatMode::V0_34 => {
|
||||||
|
self.perform_request_with_dialect(evidence::Request::new(e), v0_34::Dialect)
|
||||||
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ cosmwasm-std = { workspace = true }
|
|||||||
cosmwasm-schema = { workspace = true }
|
cosmwasm-schema = { workspace = true }
|
||||||
cw-storage-plus = { workspace = true }
|
cw-storage-plus = { workspace = true }
|
||||||
schemars = { workspace = true }
|
schemars = { workspace = true }
|
||||||
|
utoipa = { workspace = true, optional = true }
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
|
|
||||||
@@ -23,4 +24,5 @@ serde_json = { workspace = true }
|
|||||||
vergen = { workspace = true, features = ["build", "git", "gitcl", "rustc", "cargo"] }
|
vergen = { workspace = true, features = ["build", "git", "gitcl", "rustc", "cargo"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
naive_float = []
|
naive_float = []
|
||||||
|
utoipa = ["dep:utoipa"]
|
||||||
|
|||||||
@@ -221,6 +221,7 @@ fn default_unknown() -> String {
|
|||||||
// TODO: there's no reason this couldn't be used for proper binaries, but in that case
|
// TODO: there's no reason this couldn't be used for proper binaries, but in that case
|
||||||
// perhaps the struct should get renamed and moved to a "more" common crate
|
// perhaps the struct should get renamed and moved to a "more" common crate
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
|
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
||||||
pub struct ContractBuildInformation {
|
pub struct ContractBuildInformation {
|
||||||
/// Provides the name of the binary, i.e. the content of `CARGO_PKG_NAME` environmental variable.
|
/// Provides the name of the binary, i.e. the content of `CARGO_PKG_NAME` environmental variable.
|
||||||
#[serde(default = "default_unknown")]
|
#[serde(default = "default_unknown")]
|
||||||
|
|||||||
@@ -42,9 +42,11 @@ pub struct Gateway {
|
|||||||
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
||||||
pub struct GatewayBond {
|
pub struct GatewayBond {
|
||||||
/// Original amount pledged by the operator of this node.
|
/// Original amount pledged by the operator of this node.
|
||||||
|
#[cfg_attr(feature = "utoipa", schema(value_type = crate::CoinSchema))]
|
||||||
pub pledge_amount: Coin,
|
pub pledge_amount: Coin,
|
||||||
|
|
||||||
/// Address of the owner of this gateway.
|
/// Address of the owner of this gateway.
|
||||||
|
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
|
||||||
pub owner: Addr,
|
pub owner: Addr,
|
||||||
|
|
||||||
/// Block height at which this gateway has been bonded.
|
/// Block height at which this gateway has been bonded.
|
||||||
@@ -55,6 +57,7 @@ pub struct GatewayBond {
|
|||||||
|
|
||||||
/// Entity who bonded this gateway on behalf of the owner.
|
/// Entity who bonded this gateway on behalf of the owner.
|
||||||
/// If exists, it's most likely the address of the vesting contract.
|
/// If exists, it's most likely the address of the vesting contract.
|
||||||
|
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
|
||||||
pub proxy: Option<Addr>,
|
pub proxy: Option<Addr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,20 +82,25 @@ impl MixNodeDetails {
|
|||||||
|
|
||||||
// currently this struct is shared between mixnodes and nymnodes
|
// currently this struct is shared between mixnodes and nymnodes
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
|
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
||||||
pub struct NodeRewarding {
|
pub struct NodeRewarding {
|
||||||
/// Information provided by the operator that influence the cost function.
|
/// Information provided by the operator that influence the cost function.
|
||||||
pub cost_params: NodeCostParams,
|
pub cost_params: NodeCostParams,
|
||||||
|
|
||||||
/// Total pledge and compounded reward earned by the node operator.
|
/// Total pledge and compounded reward earned by the node operator.
|
||||||
|
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
|
||||||
pub operator: Decimal,
|
pub operator: Decimal,
|
||||||
|
|
||||||
/// Total delegation and compounded reward earned by all node delegators.
|
/// Total delegation and compounded reward earned by all node delegators.
|
||||||
|
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
|
||||||
pub delegates: Decimal,
|
pub delegates: Decimal,
|
||||||
|
|
||||||
/// Cumulative reward earned by the "unit delegation" since the block 0.
|
/// Cumulative reward earned by the "unit delegation" since the block 0.
|
||||||
|
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
|
||||||
pub total_unit_reward: Decimal,
|
pub total_unit_reward: Decimal,
|
||||||
|
|
||||||
/// Value of the theoretical "unit delegation" that has delegated to this node at block 0.
|
/// Value of the theoretical "unit delegation" that has delegated to this node at block 0.
|
||||||
|
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
|
||||||
pub unit_delegation: Decimal,
|
pub unit_delegation: Decimal,
|
||||||
|
|
||||||
/// Marks the epoch when this node was last rewarded so that we wouldn't accidentally attempt
|
/// Marks the epoch when this node was last rewarded so that we wouldn't accidentally attempt
|
||||||
@@ -492,14 +497,17 @@ impl NodeRewarding {
|
|||||||
::cosmwasm_schema::schemars::JsonSchema,
|
::cosmwasm_schema::schemars::JsonSchema,
|
||||||
)]
|
)]
|
||||||
#[schemars(crate = "::cosmwasm_schema::schemars")]
|
#[schemars(crate = "::cosmwasm_schema::schemars")]
|
||||||
|
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
||||||
pub struct MixNodeBond {
|
pub struct MixNodeBond {
|
||||||
/// Unique id assigned to the bonded mixnode.
|
/// Unique id assigned to the bonded mixnode.
|
||||||
pub mix_id: NodeId,
|
pub mix_id: NodeId,
|
||||||
|
|
||||||
/// Address of the owner of this mixnode.
|
/// Address of the owner of this mixnode.
|
||||||
|
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
|
||||||
pub owner: Addr,
|
pub owner: Addr,
|
||||||
|
|
||||||
/// Original amount pledged by the operator of this node.
|
/// Original amount pledged by the operator of this node.
|
||||||
|
#[cfg_attr(feature = "utoipa", schema(value_type = crate::CoinSchema))]
|
||||||
pub original_pledge: Coin,
|
pub original_pledge: Coin,
|
||||||
|
|
||||||
// REMOVED (but might be needed due to legacy things, idk yet)
|
// REMOVED (but might be needed due to legacy things, idk yet)
|
||||||
@@ -510,6 +518,7 @@ pub struct MixNodeBond {
|
|||||||
|
|
||||||
/// Entity who bonded this mixnode on behalf of the owner.
|
/// Entity who bonded this mixnode on behalf of the owner.
|
||||||
/// If exists, it's most likely the address of the vesting contract.
|
/// If exists, it's most likely the address of the vesting contract.
|
||||||
|
#[cfg_attr(feature = "utoipa", schema(value_type = Option<String>))]
|
||||||
pub proxy: Option<Addr>,
|
pub proxy: Option<Addr>,
|
||||||
|
|
||||||
/// Block height at which this mixnode has been bonded.
|
/// Block height at which this mixnode has been bonded.
|
||||||
@@ -545,6 +554,7 @@ impl MixNodeBond {
|
|||||||
feature = "generate-ts",
|
feature = "generate-ts",
|
||||||
ts(export, export_to = "ts-packages/types/src/types/rust/Mixnode.ts")
|
ts(export, export_to = "ts-packages/types/src/types/rust/Mixnode.ts")
|
||||||
)]
|
)]
|
||||||
|
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
||||||
pub struct MixNode {
|
pub struct MixNode {
|
||||||
/// Network address of this mixnode, for example 1.1.1.1 or foo.mixnode.com
|
/// Network address of this mixnode, for example 1.1.1.1 or foo.mixnode.com
|
||||||
pub host: String,
|
pub host: String,
|
||||||
@@ -571,11 +581,14 @@ pub struct MixNode {
|
|||||||
/// The cost parameters, or the cost function, defined for the particular mixnode that influences
|
/// The cost parameters, or the cost function, defined for the particular mixnode that influences
|
||||||
/// how the rewards should be split between the node operator and its delegators.
|
/// how the rewards should be split between the node operator and its delegators.
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
|
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
||||||
pub struct NodeCostParams {
|
pub struct NodeCostParams {
|
||||||
/// The profit margin of the associated node, i.e. the desired percent of the reward to be distributed to the operator.
|
/// The profit margin of the associated node, i.e. the desired percent of the reward to be distributed to the operator.
|
||||||
|
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
|
||||||
pub profit_margin_percent: Percent,
|
pub profit_margin_percent: Percent,
|
||||||
|
|
||||||
/// Operating cost of the associated node per the entire interval.
|
/// Operating cost of the associated node per the entire interval.
|
||||||
|
#[cfg_attr(feature = "utoipa", schema(value_type = crate::CoinSchema))]
|
||||||
pub interval_operating_cost: Coin,
|
pub interval_operating_cost: Coin,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -680,7 +693,9 @@ pub struct PendingMixNodeChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Default, Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
|
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
||||||
pub struct LegacyPendingMixNodeChanges {
|
pub struct LegacyPendingMixNodeChanges {
|
||||||
|
#[cfg_attr(feature = "utoipa", schema(value_type = Option<u32>))]
|
||||||
pub pledge_change: Option<EpochEventId>,
|
pub pledge_change: Option<EpochEventId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -863,11 +863,4 @@ pub enum QueryMsg {
|
|||||||
pub struct MigrateMsg {
|
pub struct MigrateMsg {
|
||||||
pub unsafe_skip_state_updates: Option<bool>,
|
pub unsafe_skip_state_updates: Option<bool>,
|
||||||
pub vesting_contract_address: Option<String>,
|
pub vesting_contract_address: Option<String>,
|
||||||
pub current_nym_node_semver: String,
|
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
pub version_score_weights: OutdatedVersionWeights,
|
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
pub version_score_params: VersionScoreFormulaParams,
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,6 +113,10 @@ impl Role {
|
|||||||
pub fn is_standby(&self) -> bool {
|
pub fn is_standby(&self) -> bool {
|
||||||
matches!(self, Role::Standby)
|
matches!(self, Role::Standby)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_mixnode(&self) -> bool {
|
||||||
|
matches!(self, Role::Layer1 | Role::Layer2 | Role::Layer3)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Role {
|
impl Display for Role {
|
||||||
@@ -231,6 +235,7 @@ pub struct RoleMetadata {
|
|||||||
|
|
||||||
/// Full details associated with given node.
|
/// Full details associated with given node.
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
|
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
||||||
pub struct NymNodeDetails {
|
pub struct NymNodeDetails {
|
||||||
/// Basic bond information of this node, such as owner address, original pledge, etc.
|
/// Basic bond information of this node, such as owner address, original pledge, etc.
|
||||||
pub bond_information: NymNodeBond,
|
pub bond_information: NymNodeBond,
|
||||||
@@ -288,14 +293,19 @@ impl NymNodeDetails {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
|
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
||||||
pub struct NymNodeBond {
|
pub struct NymNodeBond {
|
||||||
/// Unique id assigned to the bonded node.
|
/// Unique id assigned to the bonded node.
|
||||||
|
#[cfg_attr(feature = "utoipa", schema(value_type = u32))]
|
||||||
pub node_id: NodeId,
|
pub node_id: NodeId,
|
||||||
|
|
||||||
/// Address of the owner of this nym-node.
|
/// Address of the owner of this nym-node.
|
||||||
|
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
|
||||||
pub owner: Addr,
|
pub owner: Addr,
|
||||||
|
|
||||||
/// Original amount pledged by the operator of this node.
|
/// Original amount pledged by the operator of this node.
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "utoipa", schema(value_type = crate::CoinSchema))]
|
||||||
pub original_pledge: Coin,
|
pub original_pledge: Coin,
|
||||||
|
|
||||||
/// Block height at which this nym-node has been bonded.
|
/// Block height at which this nym-node has been bonded.
|
||||||
@@ -348,6 +358,7 @@ impl NymNodeBond {
|
|||||||
feature = "generate-ts",
|
feature = "generate-ts",
|
||||||
ts(export, export_to = "ts-packages/types/src/types/rust/NymNode.ts")
|
ts(export, export_to = "ts-packages/types/src/types/rust/NymNode.ts")
|
||||||
)]
|
)]
|
||||||
|
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
||||||
pub struct NymNode {
|
pub struct NymNode {
|
||||||
/// Network address of this nym-node, for example 1.1.1.1 or foo.mixnode.com
|
/// Network address of this nym-node, for example 1.1.1.1 or foo.mixnode.com
|
||||||
/// that is used to discover other capabilities of this node.
|
/// that is used to discover other capabilities of this node.
|
||||||
@@ -358,6 +369,7 @@ pub struct NymNode {
|
|||||||
pub custom_http_port: Option<u16>,
|
pub custom_http_port: Option<u16>,
|
||||||
|
|
||||||
/// Base58-encoded ed25519 EdDSA public key.
|
/// Base58-encoded ed25519 EdDSA public key.
|
||||||
|
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
|
||||||
pub identity_key: IdentityKey,
|
pub identity_key: IdentityKey,
|
||||||
// TODO: I don't think we want to include sphinx keys here,
|
// TODO: I don't think we want to include sphinx keys here,
|
||||||
// given we want to rotate them and keeping that in sync with contract will be a PITA
|
// given we want to rotate them and keeping that in sync with contract will be a PITA
|
||||||
@@ -435,8 +447,11 @@ pub struct NodeConfigUpdate {
|
|||||||
export_to = "ts-packages/types/src/types/rust/PendingNodeChanges.ts"
|
export_to = "ts-packages/types/src/types/rust/PendingNodeChanges.ts"
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
|
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
||||||
pub struct PendingNodeChanges {
|
pub struct PendingNodeChanges {
|
||||||
|
#[cfg_attr(feature = "utoipa", schema(value_type = Option<u32>))]
|
||||||
pub pledge_change: Option<EpochEventId>,
|
pub pledge_change: Option<EpochEventId>,
|
||||||
|
#[cfg_attr(feature = "utoipa", schema(value_type = Option<u32>))]
|
||||||
pub cost_params_change: Option<IntervalEventId>,
|
pub cost_params_change: Option<IntervalEventId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,31 +21,37 @@ pub type WorkFactor = Decimal;
|
|||||||
)]
|
)]
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
#[derive(Copy)]
|
#[derive(Copy)]
|
||||||
|
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
||||||
pub struct IntervalRewardParams {
|
pub struct IntervalRewardParams {
|
||||||
/// Current value of the rewarding pool.
|
/// Current value of the rewarding pool.
|
||||||
/// It is expected to be constant throughout the interval.
|
/// It is expected to be constant throughout the interval.
|
||||||
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
||||||
|
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
|
||||||
pub reward_pool: Decimal,
|
pub reward_pool: Decimal,
|
||||||
|
|
||||||
/// Current value of the staking supply.
|
/// Current value of the staking supply.
|
||||||
/// It is expected to be constant throughout the interval.
|
/// It is expected to be constant throughout the interval.
|
||||||
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
||||||
|
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
|
||||||
pub staking_supply: Decimal,
|
pub staking_supply: Decimal,
|
||||||
|
|
||||||
/// Defines the percentage of stake needed to reach saturation for all of the nodes in the rewarded set.
|
/// Defines the percentage of stake needed to reach saturation for all of the nodes in the rewarded set.
|
||||||
/// Also known as `beta`.
|
/// Also known as `beta`.
|
||||||
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
||||||
|
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
|
||||||
pub staking_supply_scale_factor: Percent,
|
pub staking_supply_scale_factor: Percent,
|
||||||
|
|
||||||
// computed values
|
// computed values
|
||||||
/// Current value of the computed reward budget per epoch, per node.
|
/// Current value of the computed reward budget per epoch, per node.
|
||||||
/// It is expected to be constant throughout the interval.
|
/// It is expected to be constant throughout the interval.
|
||||||
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
||||||
|
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
|
||||||
pub epoch_reward_budget: Decimal,
|
pub epoch_reward_budget: Decimal,
|
||||||
|
|
||||||
/// Current value of the stake saturation point.
|
/// Current value of the stake saturation point.
|
||||||
/// It is expected to be constant throughout the interval.
|
/// It is expected to be constant throughout the interval.
|
||||||
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
||||||
|
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
|
||||||
pub stake_saturation_point: Decimal,
|
pub stake_saturation_point: Decimal,
|
||||||
|
|
||||||
// constants(-ish)
|
// constants(-ish)
|
||||||
@@ -54,6 +60,7 @@ pub struct IntervalRewardParams {
|
|||||||
/// It is not really expected to be changing very often.
|
/// It is not really expected to be changing very often.
|
||||||
/// As a matter of fact, unless there's a very specific reason, it should remain constant.
|
/// As a matter of fact, unless there's a very specific reason, it should remain constant.
|
||||||
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
||||||
|
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
|
||||||
pub sybil_resistance: Percent,
|
pub sybil_resistance: Percent,
|
||||||
|
|
||||||
// default: 10
|
// default: 10
|
||||||
@@ -61,6 +68,7 @@ pub struct IntervalRewardParams {
|
|||||||
/// It is not really expected to be changing very often.
|
/// It is not really expected to be changing very often.
|
||||||
/// As a matter of fact, unless there's a very specific reason, it should remain constant.
|
/// As a matter of fact, unless there's a very specific reason, it should remain constant.
|
||||||
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
||||||
|
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
|
||||||
pub active_set_work_factor: Decimal,
|
pub active_set_work_factor: Decimal,
|
||||||
|
|
||||||
// default: 2%
|
// default: 2%
|
||||||
@@ -70,6 +78,7 @@ pub struct IntervalRewardParams {
|
|||||||
/// It is not really expected to be changing very often.
|
/// It is not really expected to be changing very often.
|
||||||
/// As a matter of fact, unless there's a very specific reason, it should remain constant.
|
/// As a matter of fact, unless there's a very specific reason, it should remain constant.
|
||||||
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
||||||
|
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
|
||||||
pub interval_pool_emission: Percent,
|
pub interval_pool_emission: Percent,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,6 +99,7 @@ impl IntervalRewardParams {
|
|||||||
)]
|
)]
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
#[derive(Copy)]
|
#[derive(Copy)]
|
||||||
|
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
||||||
pub struct RewardingParams {
|
pub struct RewardingParams {
|
||||||
/// Parameters that should remain unchanged throughout an interval.
|
/// Parameters that should remain unchanged throughout an interval.
|
||||||
pub interval: IntervalRewardParams,
|
pub interval: IntervalRewardParams,
|
||||||
@@ -254,6 +264,7 @@ impl RewardingParams {
|
|||||||
)]
|
)]
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
#[derive(Copy)]
|
#[derive(Copy)]
|
||||||
|
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
||||||
pub struct RewardedSetParams {
|
pub struct RewardedSetParams {
|
||||||
/// The expected number of nodes assigned entry gateway role (i.e. [`Role::EntryGateway`])
|
/// The expected number of nodes assigned entry gateway role (i.e. [`Role::EntryGateway`])
|
||||||
pub entry_gateways: u32,
|
pub entry_gateways: u32,
|
||||||
|
|||||||
@@ -17,10 +17,12 @@ pub mod simulator;
|
|||||||
)]
|
)]
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
#[derive(Copy, Default)]
|
#[derive(Copy, Default)]
|
||||||
|
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
||||||
pub struct RewardEstimate {
|
pub struct RewardEstimate {
|
||||||
/// The amount of **decimal** coins that are going to get distributed to the node,
|
/// The amount of **decimal** coins that are going to get distributed to the node,
|
||||||
/// i.e. the operator and all its delegators.
|
/// i.e. the operator and all its delegators.
|
||||||
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
||||||
|
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
|
||||||
pub total_node_reward: Decimal,
|
pub total_node_reward: Decimal,
|
||||||
|
|
||||||
// note that operator reward includes the operating_cost,
|
// note that operator reward includes the operating_cost,
|
||||||
@@ -28,14 +30,17 @@ pub struct RewardEstimate {
|
|||||||
// in that case the operator reward would still be `1nym` as opposed to 0
|
// in that case the operator reward would still be `1nym` as opposed to 0
|
||||||
/// The share of the reward that is going to get distributed to the node operator.
|
/// The share of the reward that is going to get distributed to the node operator.
|
||||||
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
||||||
|
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
|
||||||
pub operator: Decimal,
|
pub operator: Decimal,
|
||||||
|
|
||||||
/// The share of the reward that is going to get distributed among the node delegators.
|
/// The share of the reward that is going to get distributed among the node delegators.
|
||||||
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
||||||
|
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
|
||||||
pub delegates: Decimal,
|
pub delegates: Decimal,
|
||||||
|
|
||||||
/// The operating cost of this node. Note: it's already included in the operator reward.
|
/// The operating cost of this node. Note: it's already included in the operator reward.
|
||||||
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
||||||
|
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
|
||||||
pub operating_cost: Decimal,
|
pub operating_cost: Decimal,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -175,6 +175,14 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "utoipa")]
|
||||||
|
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
||||||
|
#[cfg_attr(feature = "utoipa", schema(title = "Coin"))]
|
||||||
|
pub struct CoinSchema {
|
||||||
|
pub denom: String,
|
||||||
|
pub amount: String,
|
||||||
|
}
|
||||||
|
|
||||||
/// The current state of the mixnet contract.
|
/// The current state of the mixnet contract.
|
||||||
#[cw_serde]
|
#[cw_serde]
|
||||||
pub struct ContractState {
|
pub struct ContractState {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ use std::error::Error;
|
|||||||
// `SELECT total_tickets, used_tickets FROM ecash_ticketbook WHERE expiration_date >= ?`, today_date
|
// `SELECT total_tickets, used_tickets FROM ecash_ticketbook WHERE expiration_date >= ?`, today_date
|
||||||
// then for each calculate the diff total_tickets - used_tickets and multiply the result by the size of the ticket
|
// then for each calculate the diff total_tickets - used_tickets and multiply the result by the size of the ticket
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait Storage: Send + Sync {
|
pub trait Storage: Clone + Send + Sync {
|
||||||
type StorageError: Error;
|
type StorageError: Error;
|
||||||
|
|
||||||
async fn close(&self);
|
async fn close(&self);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ serde = { workspace = true, features = ["derive"] }
|
|||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
strum = { workspace = true, features = ["derive"] }
|
strum = { workspace = true, features = ["derive"] }
|
||||||
time = { workspace = true, features = ["serde"] }
|
time = { workspace = true, features = ["serde"] }
|
||||||
|
utoipa = { workspace = true }
|
||||||
rand = { workspace = true }
|
rand = { workspace = true }
|
||||||
|
|
||||||
nym-compact-ecash = { path = "../nym_offline_compact_ecash" }
|
nym-compact-ecash = { path = "../nym_offline_compact_ecash" }
|
||||||
|
|||||||
@@ -86,7 +86,6 @@ impl Display for AddressPolicyAction {
|
|||||||
/// ```
|
/// ```
|
||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||||
#[cfg_attr(feature = "openapi", aliases(ExitPolicy))]
|
|
||||||
pub struct AddressPolicy {
|
pub struct AddressPolicy {
|
||||||
/// A list of rules to apply to find out whether an address is
|
/// A list of rules to apply to find out whether an address is
|
||||||
/// contained by this policy.
|
/// contained by this policy.
|
||||||
@@ -727,10 +726,10 @@ mod test {
|
|||||||
let policy = AddressPolicy::parse_from_torrc(
|
let policy = AddressPolicy::parse_from_torrc(
|
||||||
r#"
|
r#"
|
||||||
ExitPolicy reject 1.2.3.4/32:*
|
ExitPolicy reject 1.2.3.4/32:*
|
||||||
ExitPolicy reject 1.2.3.5:*
|
ExitPolicy reject 1.2.3.5:*
|
||||||
ExitPolicy reject 1.2.3.6/16:*
|
ExitPolicy reject 1.2.3.6/16:*
|
||||||
ExitPolicy reject 1.2.3.6/16:123-456
|
ExitPolicy reject 1.2.3.6/16:123-456
|
||||||
ExitPolicy accept *:53
|
ExitPolicy accept *:53
|
||||||
ExitPolicy accept6 *6:119
|
ExitPolicy accept6 *6:119
|
||||||
ExitPolicy accept *4:120
|
ExitPolicy accept *4:120
|
||||||
ExitPolicy reject6 [FC00::]/7:*
|
ExitPolicy reject6 [FC00::]/7:*
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ license.workspace = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
bincode = { workspace = true }
|
bincode = { workspace = true }
|
||||||
defguard_wireguard_rs = { workspace = true }
|
defguard_wireguard_rs = { workspace = true }
|
||||||
log = { workspace = true }
|
|
||||||
sqlx = { workspace = true, features = [
|
sqlx = { workspace = true, features = [
|
||||||
"runtime-tokio-rustls",
|
"runtime-tokio-rustls",
|
||||||
"sqlite",
|
"sqlite",
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
ALTER TABLE message_store
|
||||||
|
ADD COLUMN timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||||
@@ -2,9 +2,11 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use crate::models::StoredMessage;
|
use crate::models::StoredMessage;
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct InboxManager {
|
pub struct InboxManager {
|
||||||
connection_pool: sqlx::SqlitePool,
|
connection_pool: sqlx::SqlitePool,
|
||||||
/// Maximum number of messages that can be obtained from the database per operation.
|
/// Maximum number of messages that can be obtained from the database per operation.
|
||||||
/// It is used to prevent out of memory errors in the case of client receiving a lot of data while
|
/// It is used to prevent out of memory errors in the case of client receiving a lot of data while
|
||||||
@@ -71,44 +73,22 @@ impl InboxManager {
|
|||||||
// get 1 additional message to check whether there will be more to grab
|
// get 1 additional message to check whether there will be more to grab
|
||||||
// next time
|
// next time
|
||||||
let limit = self.retrieval_limit + 1;
|
let limit = self.retrieval_limit + 1;
|
||||||
let mut res = if let Some(start_after) = start_after {
|
let start_after = start_after.unwrap_or(-1);
|
||||||
sqlx::query_as!(
|
|
||||||
StoredMessage,
|
let mut res: Vec<StoredMessage> = sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
SELECT
|
SELECT id, client_address_bs58, content, timestamp
|
||||||
id as "id!",
|
FROM message_store
|
||||||
client_address_bs58 as "client_address_bs58!",
|
WHERE client_address_bs58 = ? AND id > ?
|
||||||
content as "content!"
|
ORDER BY id ASC
|
||||||
FROM message_store
|
LIMIT ?;
|
||||||
WHERE client_address_bs58 = ? AND id > ?
|
"#,
|
||||||
ORDER BY id ASC
|
)
|
||||||
LIMIT ?;
|
.bind(client_address_bs58)
|
||||||
"#,
|
.bind(start_after)
|
||||||
client_address_bs58,
|
.bind(limit)
|
||||||
start_after,
|
.fetch_all(&self.connection_pool)
|
||||||
limit
|
.await?;
|
||||||
)
|
|
||||||
.fetch_all(&self.connection_pool)
|
|
||||||
.await?
|
|
||||||
} else {
|
|
||||||
sqlx::query_as!(
|
|
||||||
StoredMessage,
|
|
||||||
r#"
|
|
||||||
SELECT
|
|
||||||
id as "id!",
|
|
||||||
client_address_bs58 as "client_address_bs58!",
|
|
||||||
content as "content!"
|
|
||||||
FROM message_store
|
|
||||||
WHERE client_address_bs58 = ?
|
|
||||||
ORDER BY id ASC
|
|
||||||
LIMIT ?;
|
|
||||||
"#,
|
|
||||||
client_address_bs58,
|
|
||||||
limit
|
|
||||||
)
|
|
||||||
.fetch_all(&self.connection_pool)
|
|
||||||
.await?
|
|
||||||
};
|
|
||||||
|
|
||||||
if res.len() > self.retrieval_limit as usize {
|
if res.len() > self.retrieval_limit as usize {
|
||||||
res.truncate(self.retrieval_limit as usize);
|
res.truncate(self.retrieval_limit as usize);
|
||||||
@@ -146,4 +126,13 @@ impl InboxManager {
|
|||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn remove_stale(&self, cutoff: OffsetDateTime) -> Result<(), sqlx::Error> {
|
||||||
|
let affected = sqlx::query!("DELETE FROM message_store WHERE timestamp < ?", cutoff)
|
||||||
|
.execute(&self.connection_pool)
|
||||||
|
.await?
|
||||||
|
.rows_affected();
|
||||||
|
debug!("Removed {affected} stale messages");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
use bandwidth::BandwidthManager;
|
use bandwidth::BandwidthManager;
|
||||||
use clients::{ClientManager, ClientType};
|
use clients::{ClientManager, ClientType};
|
||||||
use inboxes::InboxManager;
|
|
||||||
use models::{
|
use models::{
|
||||||
Client, PersistedBandwidth, PersistedSharedKeys, RedemptionProposal, StoredMessage,
|
Client, PersistedBandwidth, PersistedSharedKeys, RedemptionProposal, StoredMessage,
|
||||||
VerifiedTicket, WireguardPeer,
|
VerifiedTicket, WireguardPeer,
|
||||||
@@ -31,6 +30,7 @@ mod tickets;
|
|||||||
mod wireguard_peers;
|
mod wireguard_peers;
|
||||||
|
|
||||||
pub use error::GatewayStorageError;
|
pub use error::GatewayStorageError;
|
||||||
|
pub use inboxes::InboxManager;
|
||||||
|
|
||||||
// note that clone here is fine as upon cloning the same underlying pool will be used
|
// note that clone here is fine as upon cloning the same underlying pool will be used
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -53,7 +53,7 @@ impl GatewayStorage {
|
|||||||
&self.shared_key_manager
|
&self.shared_key_manager
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn inbox_manager(&self) -> &InboxManager {
|
pub fn inbox_manager(&self) -> &InboxManager {
|
||||||
&self.inbox_manager
|
&self.inbox_manager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,11 +48,13 @@ impl TryFrom<PersistedSharedKeys> for SharedGatewayKey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(FromRow)]
|
||||||
pub struct StoredMessage {
|
pub struct StoredMessage {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub client_address_bs58: String,
|
pub client_address_bs58: String,
|
||||||
pub content: Vec<u8>,
|
pub content: Vec<u8>,
|
||||||
|
pub timestamp: OffsetDateTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, FromRow)]
|
#[derive(Debug, Clone, FromRow)]
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ async-trait = { workspace = true }
|
|||||||
reqwest = { workspace = true, features = ["json"] }
|
reqwest = { workspace = true, features = ["json"] }
|
||||||
http.workspace = true
|
http.workspace = true
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
|
once_cell = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
@@ -22,7 +23,13 @@ tracing = { workspace = true }
|
|||||||
|
|
||||||
nym-bin-common = { path = "../bin-common" }
|
nym-bin-common = { path = "../bin-common" }
|
||||||
|
|
||||||
|
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
|
||||||
|
hickory-resolver = { workspace = true, features = ["dns-over-https-rustls", "webpki-roots"] }
|
||||||
|
|
||||||
# for request timeout until https://github.com/seanmonstar/reqwest/issues/1135 is fixed
|
# for request timeout until https://github.com/seanmonstar/reqwest/issues/1135 is fixed
|
||||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer]
|
[target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer]
|
||||||
workspace = true
|
workspace = true
|
||||||
features = ["tokio"]
|
features = ["tokio"]
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tokio = { workspace = true, features=["rt", "macros"] }
|
||||||
|
|||||||
@@ -0,0 +1,177 @@
|
|||||||
|
//! DNS resolver configuration for internal lookups.
|
||||||
|
//!
|
||||||
|
//! The resolver itself is the set combination of the google, cloudflare, and quad9 endpoints
|
||||||
|
//! supporting DoH and DoT.
|
||||||
|
//!
|
||||||
|
//! This resolver implements a fallback mechanism where, should the DNS-over-TLS resolution fail, a
|
||||||
|
//! followup resolution will be done using the hosts configured default (e.g. `/etc/resolve.conf` on
|
||||||
|
//! linux).
|
||||||
|
//!
|
||||||
|
//! Requires the `dns-over-https-rustls`, `webpki-roots` feature for the
|
||||||
|
//! `hickory-resolver` crate
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
|
use crate::ClientBuilder;
|
||||||
|
|
||||||
|
use std::{net::SocketAddr, sync::Arc};
|
||||||
|
|
||||||
|
use hickory_resolver::lookup_ip::LookupIp;
|
||||||
|
use hickory_resolver::{
|
||||||
|
config::{LookupIpStrategy, NameServerConfigGroup, ResolverConfig, ResolverOpts},
|
||||||
|
error::ResolveError,
|
||||||
|
lookup_ip::LookupIpIntoIter,
|
||||||
|
TokioAsyncResolver,
|
||||||
|
};
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
use reqwest::dns::{Addrs, Name, Resolve, Resolving};
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
|
impl ClientBuilder {
|
||||||
|
/// Override the DNS resolver implementation used by the underlying http client.
|
||||||
|
pub fn dns_resolver<R: Resolve + 'static>(mut self, resolver: Arc<R>) -> Self {
|
||||||
|
self.reqwest_client_builder = self.reqwest_client_builder.dns_resolver(resolver);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SocketAddrs {
|
||||||
|
iter: LookupIpIntoIter,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
#[error("hickory-dns resolver error: {hickory_error}")]
|
||||||
|
pub struct HickoryDnsError {
|
||||||
|
#[from]
|
||||||
|
hickory_error: ResolveError,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrapper around an `AsyncResolver`, which implements the `Resolve` trait.
|
||||||
|
#[derive(Debug, Default, Clone)]
|
||||||
|
pub struct HickoryDnsResolver {
|
||||||
|
/// Since we might not have been called in the context of a
|
||||||
|
/// Tokio Runtime in initialization, so we must delay the actual
|
||||||
|
/// construction of the resolver.
|
||||||
|
state: Arc<OnceCell<TokioAsyncResolver>>,
|
||||||
|
fallback: Arc<OnceCell<TokioAsyncResolver>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Resolve for HickoryDnsResolver {
|
||||||
|
fn resolve(&self, name: Name) -> Resolving {
|
||||||
|
let resolver = self.state.clone();
|
||||||
|
let fallback = self.fallback.clone();
|
||||||
|
Box::pin(async move {
|
||||||
|
let resolver = resolver.get_or_try_init(new_resolver)?;
|
||||||
|
|
||||||
|
// try the primary DNS resolver that we set up (DoH or DoT or whatever)
|
||||||
|
let lookup = match resolver.lookup_ip(name.as_str()).await {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(e) => {
|
||||||
|
// on failure use the fall back system configured DNS resolver
|
||||||
|
warn!("primary DNS failed w/ error {e}: using system fallback");
|
||||||
|
let resolver = fallback.get_or_try_init(new_resolver_system)?;
|
||||||
|
resolver.lookup_ip(name.as_str()).await?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let addrs: Addrs = Box::new(SocketAddrs {
|
||||||
|
iter: lookup.into_iter(),
|
||||||
|
});
|
||||||
|
Ok(addrs)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for SocketAddrs {
|
||||||
|
type Item = SocketAddr;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.iter.next().map(|ip_addr| SocketAddr::new(ip_addr, 0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HickoryDnsResolver {
|
||||||
|
/// Attempt to resolve a domain name to a set of ['IpAddr']s
|
||||||
|
pub async fn resolve_str(&self, name: &str) -> Result<LookupIp, HickoryDnsError> {
|
||||||
|
let resolver = self.state.get_or_try_init(new_resolver)?;
|
||||||
|
|
||||||
|
// try the primary DNS resolver that we set up (DoH or DoT or whatever)
|
||||||
|
let lookup = match resolver.lookup_ip(name).await {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(e) => {
|
||||||
|
// on failure use the fall back system configured DNS resolver
|
||||||
|
warn!("primary DNS failed w/ error {e}: using system fallback");
|
||||||
|
let resolver = self.fallback.get_or_try_init(new_resolver_system)?;
|
||||||
|
resolver.lookup_ip(name).await?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(lookup)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new resolver with a custom DoT based configuration. The options are overridden to look
|
||||||
|
/// up for both IPv4 and IPv6 addresses to work with "happy eyeballs" algorithm.
|
||||||
|
fn new_resolver() -> Result<TokioAsyncResolver, HickoryDnsError> {
|
||||||
|
let mut name_servers = NameServerConfigGroup::google_tls();
|
||||||
|
name_servers.merge(NameServerConfigGroup::google_https());
|
||||||
|
// name_servers.merge(NameServerConfigGroup::google_h3());
|
||||||
|
name_servers.merge(NameServerConfigGroup::quad9_tls());
|
||||||
|
name_servers.merge(NameServerConfigGroup::quad9_https());
|
||||||
|
name_servers.merge(NameServerConfigGroup::cloudflare_tls());
|
||||||
|
name_servers.merge(NameServerConfigGroup::cloudflare_https());
|
||||||
|
|
||||||
|
let config = ResolverConfig::from_parts(None, Vec::new(), name_servers);
|
||||||
|
|
||||||
|
let mut opts = ResolverOpts::default();
|
||||||
|
opts.ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
|
||||||
|
// Would like to enable this when 0.25 stabilizes
|
||||||
|
// opts.server_ordering_strategy = ServerOrderingStrategy::RoundRobin;
|
||||||
|
|
||||||
|
Ok(TokioAsyncResolver::tokio(config, opts))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new resolver with the default configuration, which reads from the system DNS config
|
||||||
|
/// (i.e. `/etc/resolve.conf` in unix). The options are overridden to look up for both IPv4 and IPv6
|
||||||
|
/// addresses to work with "happy eyeballs" algorithm.
|
||||||
|
fn new_resolver_system() -> Result<TokioAsyncResolver, HickoryDnsError> {
|
||||||
|
let (config, mut opts) = hickory_resolver::system_conf::read_system_conf()?;
|
||||||
|
opts.ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
|
||||||
|
Ok(TokioAsyncResolver::tokio(config, opts))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn reqwest_hickory_doh() {
|
||||||
|
let resolver = HickoryDnsResolver::default();
|
||||||
|
let client = reqwest::ClientBuilder::new()
|
||||||
|
.dns_resolver(resolver.into())
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let resp = client
|
||||||
|
.get("http://ifconfig.me:80")
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.bytes()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(!resp.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn dns_lookup() -> Result<(), HickoryDnsError> {
|
||||||
|
let resolver = HickoryDnsResolver::default();
|
||||||
|
|
||||||
|
let domain = "ifconfig.me";
|
||||||
|
let addrs = resolver.resolve_str(domain).await?;
|
||||||
|
|
||||||
|
assert!(addrs.into_iter().next().is_some());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,17 +6,23 @@ use reqwest::header::HeaderValue;
|
|||||||
use reqwest::{RequestBuilder, Response, StatusCode};
|
use reqwest::{RequestBuilder, Response, StatusCode};
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::Display;
|
|
||||||
use std::time::Duration;
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tracing::{instrument, warn};
|
use tracing::{instrument, warn};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::{fmt::Display, time::Duration};
|
||||||
|
|
||||||
pub use reqwest::IntoUrl;
|
pub use reqwest::IntoUrl;
|
||||||
|
|
||||||
|
mod user_agent;
|
||||||
pub use user_agent::UserAgent;
|
pub use user_agent::UserAgent;
|
||||||
|
|
||||||
mod user_agent;
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
mod dns;
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub use dns::HickoryDnsResolver;
|
||||||
|
|
||||||
// The timeout is relatively high as we are often making requests over the mixnet, where latency is
|
// The timeout is relatively high as we are often making requests over the mixnet, where latency is
|
||||||
// high and chatty protocols take a while to complete.
|
// high and chatty protocols take a while to complete.
|
||||||
@@ -86,11 +92,18 @@ impl ClientBuilder {
|
|||||||
// TODO: or should we maybe default to https?
|
// TODO: or should we maybe default to https?
|
||||||
Self::new(alt)
|
Self::new(alt)
|
||||||
} else {
|
} else {
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
let reqwest_client_builder = reqwest::ClientBuilder::new();
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
let reqwest_client_builder =
|
||||||
|
reqwest::ClientBuilder::new().dns_resolver(Arc::new(HickoryDnsResolver::default()));
|
||||||
|
|
||||||
Ok(ClientBuilder {
|
Ok(ClientBuilder {
|
||||||
url: url.into_url()?,
|
url: url.into_url()?,
|
||||||
timeout: None,
|
timeout: None,
|
||||||
custom_user_agent: false,
|
custom_user_agent: false,
|
||||||
reqwest_client_builder: reqwest::ClientBuilder::new(),
|
reqwest_client_builder,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ use serde::{Deserialize, Serialize};
|
|||||||
pub mod middleware;
|
pub mod middleware;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
|
||||||
pub enum FormattedResponse<T> {
|
pub enum FormattedResponse<T> {
|
||||||
Json(Json<T>),
|
Json(Json<T>),
|
||||||
Yaml(Yaml<T>),
|
Yaml(Yaml<T>),
|
||||||
|
|||||||
@@ -65,6 +65,14 @@ impl<T> NonExhaustiveDelayQueue<T> {
|
|||||||
pub fn remove(&mut self, key: &QueueKey) -> Expired<T> {
|
pub fn remove(&mut self, key: &QueueKey) -> Expired<T> {
|
||||||
self.inner.remove(key)
|
self.inner.remove(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.inner.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.inner.is_empty()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Default for NonExhaustiveDelayQueue<T> {
|
impl<T> Default for NonExhaustiveDelayQueue<T> {
|
||||||
|
|||||||
@@ -12,6 +12,6 @@ license.workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
prometheus = { workspace = true }
|
prometheus = { workspace = true }
|
||||||
log = { workspace = true }
|
tracing = { workspace = true }
|
||||||
dashmap = { workspace = true }
|
dashmap = { workspace = true }
|
||||||
lazy_static = { workspace = true }
|
lazy_static = { workspace = true }
|
||||||
|
|||||||
+390
-81
@@ -1,14 +1,18 @@
|
|||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
pub use log::error;
|
|
||||||
use log::{debug, warn};
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
pub use std::time::Instant;
|
use tracing::{debug, error, warn};
|
||||||
|
|
||||||
use prometheus::{core::Collector, Encoder as _, IntCounter, IntGauge, Registry, TextEncoder};
|
use prometheus::{
|
||||||
|
core::Collector, Encoder as _, Gauge, Histogram, HistogramOpts, IntCounter, IntGauge, Registry,
|
||||||
|
TextEncoder,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub use prometheus::HistogramTimer;
|
||||||
|
pub use std::time::Instant;
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! prepend_package_name {
|
macro_rules! prepend_package_name {
|
||||||
($name: literal) => {
|
($name: tt) => {
|
||||||
&format!(
|
&format!(
|
||||||
"{}_{}",
|
"{}_{}",
|
||||||
std::module_path!()
|
std::module_path!()
|
||||||
@@ -23,15 +27,29 @@ macro_rules! prepend_package_name {
|
|||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! inc_by {
|
macro_rules! inc_by {
|
||||||
|
($name:literal, $x:expr, $help: expr) => {
|
||||||
|
$crate::REGISTRY.maybe_register_and_inc_by(
|
||||||
|
$crate::prepend_package_name!($name),
|
||||||
|
$x as i64,
|
||||||
|
$help,
|
||||||
|
);
|
||||||
|
};
|
||||||
($name:literal, $x:expr) => {
|
($name:literal, $x:expr) => {
|
||||||
$crate::REGISTRY.inc_by($crate::prepend_package_name!($name), $x as i64);
|
$crate::REGISTRY.maybe_register_and_inc_by(
|
||||||
|
$crate::prepend_package_name!($name),
|
||||||
|
$x as i64,
|
||||||
|
None,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! inc {
|
macro_rules! inc {
|
||||||
|
($name:literal, $help: expr) => {
|
||||||
|
$crate::REGISTRY.maybe_register_and_inc($crate::prepend_package_name!($name), $help);
|
||||||
|
};
|
||||||
($name:literal) => {
|
($name:literal) => {
|
||||||
$crate::REGISTRY.inc($crate::prepend_package_name!($name));
|
$crate::REGISTRY.maybe_register_and_inc($crate::prepend_package_name!($name), None);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,6 +60,71 @@ macro_rules! metrics {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! set_metric {
|
||||||
|
($name:literal, $x:expr, $help: expr) => {
|
||||||
|
$crate::REGISTRY.maybe_register_and_set(
|
||||||
|
$crate::prepend_package_name!($name),
|
||||||
|
$x as i64,
|
||||||
|
$help,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
($name:literal, $x:expr) => {
|
||||||
|
$crate::REGISTRY.maybe_register_and_set(
|
||||||
|
$crate::prepend_package_name!($name),
|
||||||
|
$x as i64,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! set_metric_float {
|
||||||
|
($name:literal, $x:expr, $help: expr) => {
|
||||||
|
$crate::REGISTRY.maybe_register_and_set_float(
|
||||||
|
$crate::prepend_package_name!($name),
|
||||||
|
$x as f64,
|
||||||
|
$help,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
($name:literal, $x:expr) => {
|
||||||
|
$crate::REGISTRY.maybe_register_and_set_float(
|
||||||
|
$crate::prepend_package_name!($name),
|
||||||
|
$x as f64,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! add_histogram_obs {
|
||||||
|
($name:expr, $x:expr, $b:expr, $help:expr) => {
|
||||||
|
$crate::REGISTRY.maybe_register_and_add_to_histogram(
|
||||||
|
$crate::prepend_package_name!($name),
|
||||||
|
$x as f64,
|
||||||
|
Some($b),
|
||||||
|
$help,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
($name:expr, $x:expr, $b:expr) => {
|
||||||
|
$crate::REGISTRY.maybe_register_and_add_to_histogram(
|
||||||
|
$crate::prepend_package_name!($name),
|
||||||
|
$x as f64,
|
||||||
|
Some($b),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
($name:expr, $x:expr) => {
|
||||||
|
$crate::REGISTRY.maybe_register_and_add_to_histogram(
|
||||||
|
$crate::prepend_package_name!($name),
|
||||||
|
$x as f64,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! nanos {
|
macro_rules! nanos {
|
||||||
( $name:literal, $x:expr ) => {{
|
( $name:literal, $x:expr ) => {{
|
||||||
@@ -50,7 +133,7 @@ macro_rules! nanos {
|
|||||||
let r = $x;
|
let r = $x;
|
||||||
let duration = start.elapsed().as_nanos() as i64;
|
let duration = start.elapsed().as_nanos() as i64;
|
||||||
let name = $crate::prepend_package_name!($name);
|
let name = $crate::prepend_package_name!($name);
|
||||||
$crate::REGISTRY.inc_by(&format!("{}_nanos", $name), duration);
|
$crate::REGISTRY.maybe_register_and_inc_by(&format!("{}_nanos", $name), duration, None);
|
||||||
r
|
r
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
@@ -59,15 +142,100 @@ lazy_static::lazy_static! {
|
|||||||
pub static ref REGISTRY: MetricsController = MetricsController::default();
|
pub static ref REGISTRY: MetricsController = MetricsController::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn metrics_registry() -> &'static MetricsController {
|
||||||
|
®ISTRY
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct MetricsController {
|
pub struct MetricsController {
|
||||||
registry: Registry,
|
registry: Registry,
|
||||||
registry_index: DashMap<String, Metric>,
|
registry_index: DashMap<String, Metric>,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Metric {
|
pub enum Metric {
|
||||||
C(Box<IntCounter>),
|
IntCounter(Box<IntCounter>),
|
||||||
G(Box<IntGauge>),
|
IntGauge(Box<IntGauge>),
|
||||||
|
FloatGauge(Box<Gauge>),
|
||||||
|
Histogram(Box<Histogram>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Metric {
|
||||||
|
pub fn new_int_counter(name: &str, help: &str) -> Option<Self> {
|
||||||
|
match IntCounter::new(sanitize_metric_name(name), help) {
|
||||||
|
Ok(c) => Some(c.into()),
|
||||||
|
Err(err) => {
|
||||||
|
error!("Failed to create counter {name:?}: {err}");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_int_gauge(name: &str, help: &str) -> Option<Self> {
|
||||||
|
match IntGauge::new(sanitize_metric_name(name), help) {
|
||||||
|
Ok(g) => Some(g.into()),
|
||||||
|
Err(err) => {
|
||||||
|
error!("Failed to create gauge {name:?}: {err}");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_float_gauge(name: &str, help: &str) -> Option<Self> {
|
||||||
|
match Gauge::new(sanitize_metric_name(name), help) {
|
||||||
|
Ok(g) => Some(g.into()),
|
||||||
|
Err(err) => {
|
||||||
|
error!("Failed to create gauge {name:?}: {err}");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_histogram(name: &str, help: &str, buckets: Option<&[f64]>) -> Option<Self> {
|
||||||
|
let mut opts = HistogramOpts::new(sanitize_metric_name(name), help);
|
||||||
|
if let Some(buckets) = buckets {
|
||||||
|
opts = opts.buckets(buckets.to_vec())
|
||||||
|
}
|
||||||
|
match Histogram::with_opts(opts) {
|
||||||
|
Ok(h) => Some(Metric::Histogram(Box::new(h))),
|
||||||
|
Err(err) => {
|
||||||
|
error!("failed to create histogram {name:?}: {err}");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_collector(&self) -> Box<dyn Collector> {
|
||||||
|
match self {
|
||||||
|
Metric::IntCounter(c) => c.clone(),
|
||||||
|
Metric::IntGauge(g) => g.clone(),
|
||||||
|
Metric::FloatGauge(g) => g.clone(),
|
||||||
|
Metric::Histogram(h) => h.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<IntCounter> for Metric {
|
||||||
|
fn from(v: IntCounter) -> Self {
|
||||||
|
Metric::IntCounter(Box::new(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<IntGauge> for Metric {
|
||||||
|
fn from(v: IntGauge) -> Self {
|
||||||
|
Metric::IntGauge(Box::new(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Gauge> for Metric {
|
||||||
|
fn from(v: Gauge) -> Self {
|
||||||
|
Metric::FloatGauge(Box::new(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Histogram> for Metric {
|
||||||
|
fn from(v: Histogram) -> Self {
|
||||||
|
Metric::Histogram(Box::new(v))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fq_name(c: &dyn Collector) -> String {
|
fn fq_name(c: &dyn Collector) -> String {
|
||||||
@@ -81,34 +249,92 @@ impl Metric {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn fq_name(&self) -> String {
|
fn fq_name(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Metric::C(c) => fq_name(c.as_ref()),
|
Metric::IntCounter(c) => fq_name(c.as_ref()),
|
||||||
Metric::G(g) => fq_name(g.as_ref()),
|
Metric::IntGauge(g) => fq_name(g.as_ref()),
|
||||||
|
Metric::FloatGauge(g) => fq_name(g.as_ref()),
|
||||||
|
Metric::Histogram(h) => fq_name(h.as_ref()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn inc(&self) {
|
fn inc(&self) {
|
||||||
match self {
|
match self {
|
||||||
Metric::C(c) => c.inc(),
|
Metric::IntCounter(c) => c.inc(),
|
||||||
Metric::G(g) => g.inc(),
|
Metric::IntGauge(g) => g.inc(),
|
||||||
|
Metric::FloatGauge(g) => g.inc(),
|
||||||
|
Metric::Histogram(_) => {
|
||||||
|
warn!("invalid operation: attempted to call increment on a histogram")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn inc_by(&self, value: i64) {
|
fn inc_by(&self, value: i64) {
|
||||||
match self {
|
match self {
|
||||||
Metric::C(c) => c.inc_by(value as u64),
|
Metric::IntCounter(c) => c.inc_by(value as u64),
|
||||||
Metric::G(g) => g.add(value),
|
Metric::IntGauge(g) => g.add(value),
|
||||||
|
Metric::FloatGauge(g) => {
|
||||||
|
warn!("attempted to increment a float gauge ('{}') by an integer - this is most likely a bug", self.fq_name());
|
||||||
|
g.add(value as f64)
|
||||||
|
}
|
||||||
|
Metric::Histogram(_) => {
|
||||||
|
warn!("invalid operation: attempted to call increment on a histogram")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn set(&self, value: i64) {
|
fn set(&self, value: i64) {
|
||||||
match self {
|
match self {
|
||||||
Metric::C(_c) => {
|
Metric::IntCounter(_c) => {
|
||||||
warn!("Cannot set value for counter {:?}", self.fq_name());
|
warn!("Cannot set value for counter {:?}", self.fq_name());
|
||||||
}
|
}
|
||||||
Metric::G(g) => g.set(value),
|
Metric::IntGauge(g) => g.set(value),
|
||||||
|
Metric::FloatGauge(g) => {
|
||||||
|
warn!("attempted to set a float gauge ('{}') to an integer value - this is most likely a bug", self.fq_name());
|
||||||
|
g.set(value as f64)
|
||||||
|
}
|
||||||
|
Metric::Histogram(_) => {
|
||||||
|
warn!("invalid operation: attempted to call set on a histogram")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn set_float(&self, value: f64) {
|
||||||
|
match self {
|
||||||
|
Metric::IntCounter(_c) => {
|
||||||
|
warn!("Cannot set value for counter {:?}", self.fq_name());
|
||||||
|
}
|
||||||
|
Metric::IntGauge(g) => {
|
||||||
|
warn!("attempted to set a integer gauge ('{}') to a float value - this is most likely a bug", self.fq_name());
|
||||||
|
g.set(value as i64)
|
||||||
|
}
|
||||||
|
Metric::FloatGauge(g) => g.set(value),
|
||||||
|
Metric::Histogram(_) => {
|
||||||
|
warn!("invalid operation: attempted to call increment on a histogram")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn add_histogram_observation(&self, value: f64) {
|
||||||
|
match self {
|
||||||
|
Metric::Histogram(h) => {
|
||||||
|
h.observe(value);
|
||||||
|
}
|
||||||
|
_ => warn!("attempted to add histogram observation on a non-histogram metric"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn start_timer(&self) -> Option<HistogramTimer> {
|
||||||
|
match self {
|
||||||
|
Metric::Histogram(h) => Some(h.start_timer()),
|
||||||
|
_ => {
|
||||||
|
warn!("attempted to start histogram observation on a non-histogram metric");
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,93 +371,165 @@ impl MetricsController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set(&self, name: &str, value: i64) {
|
pub fn register_int_gauge<'a>(&self, name: &str, help: impl Into<Option<&'a str>>) {
|
||||||
|
let Some(metric) = Metric::new_int_gauge(name, help.into().unwrap_or(name)) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
self.register_metric(metric);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register_float_gauge<'a>(&self, name: &str, help: impl Into<Option<&'a str>>) {
|
||||||
|
let Some(metric) = Metric::new_float_gauge(name, help.into().unwrap_or(name)) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
self.register_metric(metric);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register_int_counter<'a>(&self, name: &str, help: impl Into<Option<&'a str>>) {
|
||||||
|
let Some(metric) = Metric::new_int_counter(name, help.into().unwrap_or(name)) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
self.register_metric(metric);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register_histogram<'a>(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
help: impl Into<Option<&'a str>>,
|
||||||
|
buckets: Option<&[f64]>,
|
||||||
|
) {
|
||||||
|
let Some(metric) = Metric::new_histogram(name, help.into().unwrap_or(name), buckets) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
self.register_metric(metric);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&self, name: &str, value: i64) -> bool {
|
||||||
if let Some(metric) = self.registry_index.get(name) {
|
if let Some(metric) = self.registry_index.get(name) {
|
||||||
metric.set(value);
|
metric.set(value);
|
||||||
|
true
|
||||||
} else {
|
} else {
|
||||||
let gauge = match IntGauge::new(sanitize_metric_name(name), name) {
|
false
|
||||||
Ok(g) => g,
|
|
||||||
Err(e) => {
|
|
||||||
debug!("Failed to create gauge {:?}:\n{}", name, e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
self.register_gauge(Box::new(gauge));
|
|
||||||
self.set(name, value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inc(&self, name: &str) {
|
pub fn set_float(&self, name: &str, value: f64) -> bool {
|
||||||
|
if let Some(metric) = self.registry_index.get(name) {
|
||||||
|
metric.set_float(value);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_to_histogram(&self, name: &str, value: f64) -> bool {
|
||||||
|
if let Some(metric) = self.registry_index.get(name) {
|
||||||
|
metric.add_histogram_observation(value);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_timer(&self, name: &str) -> Option<HistogramTimer> {
|
||||||
|
self.registry_index
|
||||||
|
.get(name)
|
||||||
|
.and_then(|metric| metric.start_timer())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inc(&self, name: &str) -> bool {
|
||||||
if let Some(metric) = self.registry_index.get(name) {
|
if let Some(metric) = self.registry_index.get(name) {
|
||||||
metric.inc();
|
metric.inc();
|
||||||
|
true
|
||||||
} else {
|
} else {
|
||||||
let counter = match IntCounter::new(sanitize_metric_name(name), name) {
|
false
|
||||||
Ok(c) => c,
|
|
||||||
Err(e) => {
|
|
||||||
debug!("Failed to create counter {:?}:\n{}", name, e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
self.register_counter(Box::new(counter));
|
|
||||||
self.inc(name)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inc_by(&self, name: &str, value: i64) {
|
pub fn inc_by(&self, name: &str, value: i64) -> bool {
|
||||||
if let Some(metric) = self.registry_index.get(name) {
|
if let Some(metric) = self.registry_index.get(name) {
|
||||||
metric.inc_by(value);
|
metric.inc_by(value);
|
||||||
|
true
|
||||||
} else {
|
} else {
|
||||||
let counter = match IntCounter::new(sanitize_metric_name(name), name) {
|
false
|
||||||
Ok(c) => c,
|
|
||||||
Err(e) => {
|
|
||||||
debug!("Failed to create counter {:?}:\n{}", name, e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
self.register_counter(Box::new(counter));
|
|
||||||
self.inc_by(name, value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_gauge(&self, metric: Box<IntGauge>) {
|
pub fn maybe_register_and_set<'a>(
|
||||||
let fq_name = metric
|
&self,
|
||||||
.desc()
|
name: &str,
|
||||||
.first()
|
value: i64,
|
||||||
.map(|d| d.fq_name.clone())
|
help: impl Into<Option<&'a str>>,
|
||||||
.unwrap_or_default();
|
) {
|
||||||
|
if !self.set(name, value) {
|
||||||
|
let help = help.into();
|
||||||
|
self.register_int_gauge(name, help);
|
||||||
|
self.set(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn maybe_register_and_set_float<'a>(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
value: f64,
|
||||||
|
help: impl Into<Option<&'a str>>,
|
||||||
|
) {
|
||||||
|
if !self.set_float(name, value) {
|
||||||
|
let help = help.into();
|
||||||
|
self.register_float_gauge(name, help);
|
||||||
|
self.set_float(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn maybe_register_and_add_to_histogram<'a>(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
value: f64,
|
||||||
|
buckets: Option<&[f64]>,
|
||||||
|
help: impl Into<Option<&'a str>>,
|
||||||
|
) {
|
||||||
|
if !self.add_to_histogram(name, value) {
|
||||||
|
let help = help.into();
|
||||||
|
self.register_histogram(name, help, buckets);
|
||||||
|
self.add_to_histogram(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn maybe_register_and_inc<'a>(&self, name: &str, help: impl Into<Option<&'a str>>) {
|
||||||
|
if !self.inc(name) {
|
||||||
|
let help = help.into();
|
||||||
|
self.register_int_counter(name, help);
|
||||||
|
self.inc(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn maybe_register_and_inc_by<'a>(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
value: i64,
|
||||||
|
help: impl Into<Option<&'a str>>,
|
||||||
|
) {
|
||||||
|
if !self.inc_by(name, value) {
|
||||||
|
let help = help.into();
|
||||||
|
self.register_int_counter(name, help);
|
||||||
|
self.inc_by(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register_metric(&self, metric: impl Into<Metric>) {
|
||||||
|
let m = metric.into();
|
||||||
|
let fq_name = m.fq_name();
|
||||||
|
|
||||||
if self.registry_index.contains_key(&fq_name) {
|
if self.registry_index.contains_key(&fq_name) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.registry.register(metric.clone()) {
|
match self.registry.register(m.as_collector()) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
self.registry_index
|
self.registry_index.insert(fq_name, m);
|
||||||
.insert(fq_name, Metric::G(metric.clone()));
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(err) => {
|
||||||
debug!("Failed to register {:?}:\n{}", fq_name, e)
|
debug!("Failed to register '{fq_name}': {err}")
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn register_counter(&self, metric: Box<IntCounter>) {
|
|
||||||
let fq_name = metric
|
|
||||||
.desc()
|
|
||||||
.first()
|
|
||||||
.map(|d| d.fq_name.clone())
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
if self.registry_index.contains_key(&fq_name) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
match self.registry.register(metric.clone()) {
|
|
||||||
Ok(_) => {
|
|
||||||
self.registry_index
|
|
||||||
.insert(fq_name, Metric::C(metric.clone()));
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
debug!("Failed to register {:?}:\n{}", fq_name, e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -275,4 +573,15 @@ mod tests {
|
|||||||
"packets_sent_34_242_65_133:1789"
|
"packets_sent_34_242_65_133:1789"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn prepend_package_name() {
|
||||||
|
let literal = prepend_package_name!("foo");
|
||||||
|
assert_eq!(literal, "nym_metrics_foo");
|
||||||
|
|
||||||
|
let bar = "bar";
|
||||||
|
let format = format!("foomp_{}", bar);
|
||||||
|
let formatted = prepend_package_name!(format);
|
||||||
|
assert_eq!(formatted, "nym_metrics_foomp_bar");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,4 +63,4 @@ par_signing = ["rayon"]
|
|||||||
# but given it's not done very frequently, it shouldn't be too much of a problem
|
# but given it's not done very frequently, it shouldn't be too much of a problem
|
||||||
# furthermore, we can't and shouldn't dedicate the entire nym-api CPU just for verification,
|
# furthermore, we can't and shouldn't dedicate the entire nym-api CPU just for verification,
|
||||||
# but this feature might potentially be desirable for clients.
|
# but this feature might potentially be desirable for clients.
|
||||||
par_verify = ["rayon"]
|
par_verify = ["rayon"]
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
use crate::ecash_group_parameters;
|
use crate::ecash_group_parameters;
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::helpers::{g1_tuple_to_bytes, recover_g1_tuple};
|
use crate::helpers::{g1_tuple_to_bytes, recover_g1_tuple};
|
||||||
use bls12_381::{G1Projective, Scalar};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use subtle::Choice;
|
use subtle::Choice;
|
||||||
|
|
||||||
|
pub use bls12_381::{G1Projective, G2Projective, Scalar};
|
||||||
pub type SignerIndex = u64;
|
pub type SignerIndex = u64;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ pub mod common_types;
|
|||||||
pub mod constants;
|
pub mod constants;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
mod helpers;
|
mod helpers;
|
||||||
mod proofs;
|
pub mod proofs;
|
||||||
pub mod scheme;
|
pub mod scheme;
|
||||||
pub mod tests;
|
pub mod tests;
|
||||||
mod traits;
|
mod traits;
|
||||||
|
|||||||
@@ -27,3 +27,5 @@ nym-credentials-interface = { path = "../credentials-interface" }
|
|||||||
nym-metrics = { path = "../nym-metrics" }
|
nym-metrics = { path = "../nym-metrics" }
|
||||||
nym-task = { path = "../task" }
|
nym-task = { path = "../task" }
|
||||||
|
|
||||||
|
[target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer]
|
||||||
|
workspace = true
|
||||||
|
|||||||
@@ -3,10 +3,7 @@
|
|||||||
|
|
||||||
use super::ClientStatsEvents;
|
use super::ClientStatsEvents;
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use std::{
|
use std::{collections::VecDeque, time::Duration};
|
||||||
collections::VecDeque,
|
|
||||||
time::{Duration, Instant},
|
|
||||||
};
|
|
||||||
|
|
||||||
use nym_metrics::{inc, inc_by};
|
use nym_metrics::{inc, inc_by};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -18,6 +15,51 @@ use si_scale::helpers::bibytes2;
|
|||||||
// Also, set it larger than the packet report interval so that we don't miss notable singular events
|
// Also, set it larger than the packet report interval so that we don't miss notable singular events
|
||||||
const RECORDING_WINDOW_MS: u64 = 2300;
|
const RECORDING_WINDOW_MS: u64 = 2300;
|
||||||
|
|
||||||
|
#[derive(PartialOrd, PartialEq, Clone, Copy, Debug)]
|
||||||
|
struct Instant {
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
inner: std::time::Instant,
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
inner: wasmtimer::std::Instant,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Instant {
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
fn inner(&self) -> &std::time::Instant {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
fn inner(&self) -> &wasmtimer::std::Instant {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
fn now() -> Self {
|
||||||
|
Instant {
|
||||||
|
inner: std::time::Instant::now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
fn now() -> Self {
|
||||||
|
Instant {
|
||||||
|
inner: wasmtimer::std::Instant::now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn checked_sub(&self, duration: Duration) -> Option<Instant> {
|
||||||
|
self.inner()
|
||||||
|
.checked_sub(duration)
|
||||||
|
.map(|inner| Instant { inner })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn duration_since(&self, earlier: &Instant) -> Duration {
|
||||||
|
self.inner.duration_since(*earlier.inner())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||||
pub(crate) struct PacketStatistics {
|
pub(crate) struct PacketStatistics {
|
||||||
// Sent
|
// Sent
|
||||||
@@ -424,7 +466,11 @@ impl PacketStatisticsControl {
|
|||||||
self.history.push_back((Instant::now(), self.stats.clone()));
|
self.history.push_back((Instant::now(), self.stats.clone()));
|
||||||
|
|
||||||
// Filter out old ones
|
// Filter out old ones
|
||||||
let recording_window = Instant::now() - Duration::from_millis(RECORDING_WINDOW_MS);
|
let now = Instant::now();
|
||||||
|
let recording_window = Instant::now()
|
||||||
|
.checked_sub(Duration::from_millis(RECORDING_WINDOW_MS))
|
||||||
|
.unwrap_or(now);
|
||||||
|
|
||||||
while self
|
while self
|
||||||
.history
|
.history
|
||||||
.front()
|
.front()
|
||||||
@@ -442,7 +488,7 @@ impl PacketStatisticsControl {
|
|||||||
|
|
||||||
// Do basic averaging over the entire history, which just uses the first and last
|
// Do basic averaging over the entire history, which just uses the first and last
|
||||||
if let Some((start, start_stats)) = self.history.front() {
|
if let Some((start, start_stats)) = self.history.front() {
|
||||||
let duration_secs = Instant::now().duration_since(*start).as_secs_f64();
|
let duration_secs = Instant::now().duration_since(start).as_secs_f64();
|
||||||
let delta = self.stats.clone() - start_stats.clone();
|
let delta = self.stats.clone() - start_stats.clone();
|
||||||
let rates = PacketRates::from(delta) / duration_secs;
|
let rates = PacketRates::from(delta) / duration_secs;
|
||||||
Some(rates)
|
Some(rates)
|
||||||
@@ -458,7 +504,10 @@ impl PacketStatisticsControl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Filter out old ones
|
// Filter out old ones
|
||||||
let recording_window = Instant::now() - Duration::from_millis(RECORDING_WINDOW_MS);
|
let now = Instant::now();
|
||||||
|
let recording_window = now
|
||||||
|
.checked_sub(Duration::from_millis(RECORDING_WINDOW_MS))
|
||||||
|
.unwrap_or(now);
|
||||||
while self
|
while self
|
||||||
.rates
|
.rates
|
||||||
.front()
|
.front()
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ impl Default for ClientStatsReport {
|
|||||||
pub struct OsInformation {
|
pub struct OsInformation {
|
||||||
pub(crate) os_type: String,
|
pub(crate) os_type: String,
|
||||||
pub(crate) os_version: Option<String>,
|
pub(crate) os_version: Option<String>,
|
||||||
pub(crate) os_arch: Option<String>,
|
pub(crate) os_arch: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OsInformation {
|
impl OsInformation {
|
||||||
|
|||||||
@@ -8,10 +8,13 @@ license.workspace = true
|
|||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
cfg-if = { workspace = true }
|
||||||
futures = { workspace = true }
|
futures = { workspace = true }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
tokio = { workspace = true, features = ["macros", "sync"] }
|
tokio = { workspace = true, features = ["macros", "sync"] }
|
||||||
|
tokio-util = { workspace = true, features = ["rt"] }
|
||||||
|
tracing = { workspace = true }
|
||||||
|
|
||||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio]
|
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@@ -0,0 +1,366 @@
|
|||||||
|
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use crate::{TaskClient, TaskManager};
|
||||||
|
use futures::stream::FuturesUnordered;
|
||||||
|
use futures::StreamExt;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio::task::JoinSet;
|
||||||
|
use tokio::time::sleep;
|
||||||
|
use tokio_util::sync::{CancellationToken, DropGuard};
|
||||||
|
use tokio_util::task::TaskTracker;
|
||||||
|
use tracing::{debug, info, trace};
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
use tokio::signal::unix::{signal, SignalKind};
|
||||||
|
|
||||||
|
pub const DEFAULT_MAX_SHUTDOWN_DURATION: Duration = Duration::from_secs(5);
|
||||||
|
|
||||||
|
pub fn token_name(name: &Option<String>) -> String {
|
||||||
|
name.clone().unwrap_or_else(|| "unknown".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
// a wrapper around tokio's CancellationToken that adds optional `name` information to more easily
|
||||||
|
// track down sources of shutdown
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct ShutdownToken {
|
||||||
|
name: Option<String>,
|
||||||
|
inner: CancellationToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for ShutdownToken {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
// make sure to not accidentally overflow the stack if we keep cloning the handle
|
||||||
|
let name = if let Some(name) = &self.name {
|
||||||
|
if name != Self::OVERFLOW_NAME && name.len() < Self::MAX_NAME_LENGTH {
|
||||||
|
Some(format!("{name}-child"))
|
||||||
|
} else {
|
||||||
|
Some(Self::OVERFLOW_NAME.to_string())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
ShutdownToken {
|
||||||
|
name,
|
||||||
|
inner: self.inner.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for ShutdownToken {
|
||||||
|
type Target = CancellationToken;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShutdownToken {
|
||||||
|
const MAX_NAME_LENGTH: usize = 128;
|
||||||
|
const OVERFLOW_NAME: &'static str = "reached maximum ShutdownToken children name depth";
|
||||||
|
|
||||||
|
pub fn new(name: impl Into<String>) -> Self {
|
||||||
|
ShutdownToken {
|
||||||
|
name: Some(name.into()),
|
||||||
|
inner: CancellationToken::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a ShutdownToken which will get cancelled whenever the current token gets cancelled.
|
||||||
|
// Unlike a cloned/forked ShutdownToken, cancelling a child token does not cancel the parent token.
|
||||||
|
#[must_use]
|
||||||
|
pub fn child_token<S: Into<String>>(&self, child_suffix: S) -> Self {
|
||||||
|
let suffix = child_suffix.into();
|
||||||
|
let child_name = if let Some(base) = &self.name {
|
||||||
|
format!("{base}-{suffix}")
|
||||||
|
} else {
|
||||||
|
format!("unknown-{suffix}")
|
||||||
|
};
|
||||||
|
|
||||||
|
ShutdownToken {
|
||||||
|
name: Some(child_name),
|
||||||
|
inner: self.inner.child_token(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a clone of the ShutdownToken which will get cancelled whenever the current token gets cancelled, and vice versa.
|
||||||
|
#[must_use]
|
||||||
|
pub fn clone_with_suffix<S: Into<String>>(&self, child_suffix: S) -> Self {
|
||||||
|
let mut child = self.clone();
|
||||||
|
let suffix = child_suffix.into();
|
||||||
|
let child_name = if let Some(base) = &self.name {
|
||||||
|
format!("{base}-{suffix}")
|
||||||
|
} else {
|
||||||
|
format!("unknown-{suffix}")
|
||||||
|
};
|
||||||
|
|
||||||
|
child.name = Some(child_name);
|
||||||
|
child
|
||||||
|
}
|
||||||
|
|
||||||
|
// exposed method with the old name for easier migration
|
||||||
|
// it will eventually be removed so please try to use `.clone_with_suffix` instead
|
||||||
|
#[must_use]
|
||||||
|
pub fn fork<S: Into<String>>(&self, child_suffix: S) -> Self {
|
||||||
|
self.clone_with_suffix(child_suffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// exposed method with the old name for easier migration
|
||||||
|
// it will eventually be removed so please try to use `.clone().named(name)` instead
|
||||||
|
#[must_use]
|
||||||
|
pub fn fork_named<S: Into<String>>(&self, name: S) -> Self {
|
||||||
|
self.clone().named(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn named<S: Into<String>>(mut self, name: S) -> Self {
|
||||||
|
self.name = Some(name.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn add_suffix<S: Into<String>>(self, suffix: S) -> Self {
|
||||||
|
let suffix = suffix.into();
|
||||||
|
let name = if let Some(base) = &self.name {
|
||||||
|
format!("{base}-{suffix}")
|
||||||
|
} else {
|
||||||
|
format!("unknown-{suffix}")
|
||||||
|
};
|
||||||
|
self.named(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returned guard will cancel this token (and all its children) on drop unless disarmed.
|
||||||
|
pub fn drop_guard(self) -> ShutdownDropGuard {
|
||||||
|
ShutdownDropGuard {
|
||||||
|
name: self.name,
|
||||||
|
inner: self.inner.drop_guard(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> String {
|
||||||
|
token_name(&self.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run_until_cancelled<F>(&self, fut: F) -> Option<F::Output>
|
||||||
|
where
|
||||||
|
F: Future,
|
||||||
|
{
|
||||||
|
let res = self.inner.run_until_cancelled(fut).await;
|
||||||
|
trace!("'{}' got cancelled", self.name());
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ShutdownDropGuard {
|
||||||
|
name: Option<String>,
|
||||||
|
inner: DropGuard,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for ShutdownDropGuard {
|
||||||
|
type Target = DropGuard;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShutdownDropGuard {
|
||||||
|
pub fn disarm(self) -> ShutdownToken {
|
||||||
|
ShutdownToken {
|
||||||
|
name: self.name,
|
||||||
|
inner: self.inner.disarm(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> String {
|
||||||
|
token_name(&self.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ShutdownManager {
|
||||||
|
pub root_token: ShutdownToken,
|
||||||
|
|
||||||
|
legacy_task_manager: Option<TaskManager>,
|
||||||
|
|
||||||
|
shutdown_signals: JoinSet<()>,
|
||||||
|
|
||||||
|
// the reason I'm not using a `JoinSet` is because it forces us to use futures with the same `::Output` type
|
||||||
|
tracker: TaskTracker,
|
||||||
|
|
||||||
|
max_shutdown_duration: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for ShutdownManager {
|
||||||
|
type Target = TaskTracker;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.tracker
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShutdownManager {
|
||||||
|
pub fn new(root_token_name: impl Into<String>) -> Self {
|
||||||
|
let manager = ShutdownManager {
|
||||||
|
root_token: ShutdownToken::new(root_token_name),
|
||||||
|
legacy_task_manager: None,
|
||||||
|
shutdown_signals: Default::default(),
|
||||||
|
tracker: Default::default(),
|
||||||
|
max_shutdown_duration: Default::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// we need to add an explicit watcher for the cancellation token being cancelled
|
||||||
|
// so that we could cancel all legacy tasks
|
||||||
|
let cancel_watcher = manager.root_token.clone();
|
||||||
|
manager.with_shutdown(async move { cancel_watcher.cancelled().await })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_legacy_task_manager(mut self) -> Self {
|
||||||
|
let mut legacy_manager =
|
||||||
|
TaskManager::default().named(format!("{}-legacy", self.root_token.name()));
|
||||||
|
let mut legacy_error_rx = legacy_manager.task_return_error_rx();
|
||||||
|
let mut legacy_drop_rx = legacy_manager.task_drop_rx();
|
||||||
|
|
||||||
|
self.legacy_task_manager = Some(legacy_manager);
|
||||||
|
|
||||||
|
// add a task that listens for legacy task clients being dropped to trigger cancellation
|
||||||
|
self.with_shutdown(async move {
|
||||||
|
tokio::select! {
|
||||||
|
_ = legacy_error_rx.recv() => (),
|
||||||
|
_ = legacy_drop_rx.recv() => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("received legacy shutdown signal");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub fn with_default_shutdown_signals(self) -> std::io::Result<Self> {
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(unix)] {
|
||||||
|
self.with_interrupt_signal()
|
||||||
|
.with_terminate_signal()?
|
||||||
|
.with_quit_signal()
|
||||||
|
} else {
|
||||||
|
Ok(self.with_interrupt_signal())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_shutdown<F>(mut self, shutdown: F) -> Self
|
||||||
|
where
|
||||||
|
F: Future<Output = ()>,
|
||||||
|
F: Send + 'static,
|
||||||
|
{
|
||||||
|
let shutdown_token = self.root_token.clone();
|
||||||
|
self.shutdown_signals.spawn(async move {
|
||||||
|
shutdown.await;
|
||||||
|
|
||||||
|
info!("sending cancellation after receiving shutdown signal");
|
||||||
|
shutdown_token.cancel();
|
||||||
|
});
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub fn with_shutdown_signal(self, signal_kind: SignalKind) -> std::io::Result<Self> {
|
||||||
|
let mut sig = signal(signal_kind)?;
|
||||||
|
Ok(self.with_shutdown(async move {
|
||||||
|
sig.recv().await;
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub fn with_interrupt_signal(self) -> Self {
|
||||||
|
self.with_shutdown(async move {
|
||||||
|
let _ = tokio::signal::ctrl_c().await;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub fn with_terminate_signal(self) -> std::io::Result<Self> {
|
||||||
|
self.with_shutdown_signal(SignalKind::terminate())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub fn with_quit_signal(self) -> std::io::Result<Self> {
|
||||||
|
self.with_shutdown_signal(SignalKind::quit())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_shutdown_duration(mut self, duration: Duration) -> Self {
|
||||||
|
self.max_shutdown_duration = duration;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn child_token<S: Into<String>>(&self, child_suffix: S) -> ShutdownToken {
|
||||||
|
self.root_token.child_token(child_suffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clone_token<S: Into<String>>(&self, child_suffix: S) -> ShutdownToken {
|
||||||
|
self.root_token.clone_with_suffix(child_suffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn subscribe_legacy<S: Into<String>>(&self, child_suffix: S) -> TaskClient {
|
||||||
|
// alternatively we could have set self.legacy_task_manager = Some(TaskManager::default());
|
||||||
|
// on demand if it wasn't unavailable, but then we'd have to use mutable reference
|
||||||
|
#[allow(clippy::expect_used)]
|
||||||
|
self.legacy_task_manager
|
||||||
|
.as_ref()
|
||||||
|
.expect("did not enable legacy shutdown support")
|
||||||
|
.subscribe_named(child_suffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn finish_shutdown(mut self) {
|
||||||
|
let mut wait_futures = FuturesUnordered::<Pin<Box<dyn Future<Output = ()>>>>::new();
|
||||||
|
|
||||||
|
// force shutdown via ctrl-c
|
||||||
|
wait_futures.push(Box::pin(async move {
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
let interrupt_future = tokio::signal::ctrl_c();
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
let interrupt_future = futures::future::pending::<()>();
|
||||||
|
|
||||||
|
let _ = interrupt_future.await;
|
||||||
|
info!("received interrupt - forcing shutdown");
|
||||||
|
}));
|
||||||
|
|
||||||
|
// timeout
|
||||||
|
wait_futures.push(Box::pin(async move {
|
||||||
|
sleep(self.max_shutdown_duration).await;
|
||||||
|
info!("timeout reached, forcing shutdown");
|
||||||
|
}));
|
||||||
|
|
||||||
|
// graceful
|
||||||
|
wait_futures.push(Box::pin(async move {
|
||||||
|
self.tracker.wait().await;
|
||||||
|
debug!("migrated tasks successfully shutdown");
|
||||||
|
if let Some(legacy) = self.legacy_task_manager.as_mut() {
|
||||||
|
legacy.wait_for_graceful_shutdown().await;
|
||||||
|
debug!("legacy tasks successfully shutdown");
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("all registered tasks successfully shutdown")
|
||||||
|
}));
|
||||||
|
|
||||||
|
wait_futures.next().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn wait_for_shutdown_signal(mut self) {
|
||||||
|
self.shutdown_signals.join_next().await;
|
||||||
|
|
||||||
|
if let Some(legacy_manager) = self.legacy_task_manager.as_mut() {
|
||||||
|
info!("attempting to shutdown legacy tasks");
|
||||||
|
let _ = legacy_manager.signal_shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("waiting for tasks to finish... (press ctrl-c to force)");
|
||||||
|
self.finish_shutdown().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
pub mod cancellation;
|
||||||
pub mod connections;
|
pub mod connections;
|
||||||
pub mod event;
|
pub mod event;
|
||||||
pub mod manager;
|
pub mod manager;
|
||||||
@@ -8,9 +9,11 @@ pub mod manager;
|
|||||||
pub mod signal;
|
pub mod signal;
|
||||||
pub mod spawn;
|
pub mod spawn;
|
||||||
|
|
||||||
|
pub use cancellation::{ShutdownDropGuard, ShutdownManager, ShutdownToken};
|
||||||
pub use event::{StatusReceiver, StatusSender, TaskStatus, TaskStatusEvent};
|
pub use event::{StatusReceiver, StatusSender, TaskStatus, TaskStatusEvent};
|
||||||
pub use manager::{TaskClient, TaskHandle, TaskManager};
|
pub use manager::{TaskClient, TaskHandle, TaskManager};
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
pub use signal::wait_for_signal_and_error;
|
|
||||||
|
|
||||||
pub use spawn::{spawn, spawn_with_report_error};
|
pub use spawn::{spawn, spawn_with_report_error};
|
||||||
|
pub use tokio_util::task::TaskTracker;
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub use signal::{wait_for_signal, wait_for_signal_and_error};
|
||||||
|
|||||||
@@ -185,6 +185,19 @@ impl TaskManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// used for compatibility with the ShutdownManager
|
||||||
|
pub(crate) fn task_return_error_rx(&mut self) -> ErrorReceiver {
|
||||||
|
self.task_return_error_rx
|
||||||
|
.take()
|
||||||
|
.expect("unable to get error channel: attempt to wait twice?")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn task_drop_rx(&mut self) -> ErrorReceiver {
|
||||||
|
self.task_drop_rx
|
||||||
|
.take()
|
||||||
|
.expect("unable to get task drop channel: attempt to wait twice?")
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn wait_for_error(&mut self) -> Option<SentError> {
|
pub async fn wait_for_error(&mut self) -> Option<SentError> {
|
||||||
let mut error_rx = self
|
let mut error_rx = self
|
||||||
.task_return_error_rx
|
.task_return_error_rx
|
||||||
@@ -208,6 +221,13 @@ impl TaskManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn wait_for_graceful_shutdown(&mut self) {
|
||||||
|
if let Some(notify_rx) = self.notify_rx.take() {
|
||||||
|
drop(notify_rx);
|
||||||
|
}
|
||||||
|
self.notify_tx.closed().await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn wait_for_shutdown(&mut self) {
|
pub async fn wait_for_shutdown(&mut self) {
|
||||||
log::debug!("Waiting for shutdown");
|
log::debug!("Waiting for shutdown");
|
||||||
if let Some(notify_rx) = self.notify_rx.take() {
|
if let Some(notify_rx) = self.notify_rx.take() {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ readme.workspace = true
|
|||||||
sha2 = { workspace = true }
|
sha2 = { workspace = true }
|
||||||
rs_merkle = { workspace = true }
|
rs_merkle = { workspace = true }
|
||||||
schemars = { workspace = true }
|
schemars = { workspace = true }
|
||||||
|
utoipa = { workspace = true }
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
time = { workspace = true }
|
time = { workspace = true }
|
||||||
|
|
||||||
@@ -23,4 +24,4 @@ nym-serde-helpers = { path = "../serde-helpers", features = ["date", "base64", "
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rand_chacha = { workspace = true }
|
rand_chacha = { workspace = true }
|
||||||
rand = { workspace = true }
|
rand = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
|
|||||||
@@ -14,15 +14,18 @@ use serde::{Deserialize, Serialize};
|
|||||||
use sha2::Digest;
|
use sha2::Digest;
|
||||||
use std::fmt::{Debug, Formatter};
|
use std::fmt::{Debug, Formatter};
|
||||||
use time::Date;
|
use time::Date;
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
// no point in importing the entire contract commons just for this one type
|
// no point in importing the entire contract commons just for this one type
|
||||||
pub type DepositId = u32;
|
pub type DepositId = u32;
|
||||||
pub type DKGEpochId = u64;
|
pub type DKGEpochId = u64;
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, JsonSchema)]
|
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, JsonSchema, ToSchema)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct IssuedTicketbook {
|
pub struct IssuedTicketbook {
|
||||||
|
#[schema(value_type = u32)]
|
||||||
pub deposit_id: DepositId,
|
pub deposit_id: DepositId,
|
||||||
|
#[schema(value_type = u32)]
|
||||||
pub epoch_id: DKGEpochId,
|
pub epoch_id: DKGEpochId,
|
||||||
|
|
||||||
// 96 bytes serialised 'BlindedSignature'
|
// 96 bytes serialised 'BlindedSignature'
|
||||||
@@ -37,9 +40,11 @@ pub struct IssuedTicketbook {
|
|||||||
|
|
||||||
#[schemars(with = "String")]
|
#[schemars(with = "String")]
|
||||||
#[serde(with = "nym_serde_helpers::date")]
|
#[serde(with = "nym_serde_helpers::date")]
|
||||||
|
#[schema(value_type = String)]
|
||||||
pub expiration_date: Date,
|
pub expiration_date: Date,
|
||||||
|
|
||||||
#[schemars(with = "String")]
|
#[schemars(with = "String")]
|
||||||
|
#[schema(value_type = String)]
|
||||||
pub ticketbook_type: TicketType,
|
pub ticketbook_type: TicketType,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +85,7 @@ pub struct InsertedMerkleLeaf {
|
|||||||
pub leaf: MerkleLeaf,
|
pub leaf: MerkleLeaf,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialOrd, PartialEq, Eq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialOrd, PartialEq, Eq, ToSchema)]
|
||||||
pub struct MerkleLeaf {
|
pub struct MerkleLeaf {
|
||||||
#[schemars(with = "String")]
|
#[schemars(with = "String")]
|
||||||
#[serde(with = "nym_serde_helpers::hex")]
|
#[serde(with = "nym_serde_helpers::hex")]
|
||||||
@@ -162,16 +167,14 @@ impl IssuedTicketbooksMerkleTree {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
#[derive(Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||||
pub struct IssuedTicketbooksFullMerkleProof {
|
pub struct IssuedTicketbooksFullMerkleProof {
|
||||||
#[schemars(with = "String")]
|
#[schemars(with = "String")]
|
||||||
#[serde(with = "inner_proof_base64_serde")]
|
#[serde(with = "inner_proof_base64_serde")]
|
||||||
|
#[schema(value_type = String)]
|
||||||
inner_proof: MerkleProof<Sha256>,
|
inner_proof: MerkleProof<Sha256>,
|
||||||
|
|
||||||
included_leaves: Vec<MerkleLeaf>,
|
included_leaves: Vec<MerkleLeaf>,
|
||||||
|
|
||||||
total_leaves: usize,
|
total_leaves: usize,
|
||||||
|
|
||||||
#[schemars(with = "String")]
|
#[schemars(with = "String")]
|
||||||
#[serde(with = "nym_serde_helpers::hex")]
|
#[serde(with = "nym_serde_helpers::hex")]
|
||||||
root: Vec<u8>,
|
root: Vec<u8>,
|
||||||
|
|||||||
@@ -401,20 +401,17 @@ impl NymTopology {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// a 'valid' egress is one assigned to either entry role (i.e. entry for another client)
|
// a 'valid' egress is one that is currently **not** acting as a mixnode
|
||||||
// or exit role (as a service provider)
|
|
||||||
if !ignore_epoch_roles {
|
if !ignore_epoch_roles {
|
||||||
let Some(role) = self.rewarded_set.role(node.node_id) else {
|
if let Some(role) = self.rewarded_set.role(node.node_id) {
|
||||||
return Err(NymTopologyError::InvalidEgressRole {
|
if role.is_mixnode() {
|
||||||
node_identity: Box::new(node_identity),
|
return Err(NymTopologyError::InvalidEgressRole {
|
||||||
});
|
node_identity: Box::new(node_identity),
|
||||||
};
|
});
|
||||||
if !matches!(role, Role::EntryGateway | Role::ExitGateway) {
|
}
|
||||||
return Err(NymTopologyError::InvalidEgressRole {
|
|
||||||
node_identity: Box::new(node_identity),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(node)
|
Ok(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ strum = { workspace = true, features = ["derive"] }
|
|||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
ts-rs = { workspace = true }
|
ts-rs = { workspace = true }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
|
utoipa = { workspace = true }
|
||||||
x25519-dalek = { workspace = true, features = ["static_secrets"] }
|
x25519-dalek = { workspace = true, features = ["static_secrets"] }
|
||||||
|
|
||||||
cosmwasm-std = { workspace = true }
|
cosmwasm-std = { workspace = true }
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use nym_mixnet_contract_common::NodeId;
|
|||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{collections::HashSet, sync::LazyLock, time::SystemTime};
|
use std::{collections::HashSet, sync::LazyLock, time::SystemTime};
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
static NETWORK_MONITORS: LazyLock<HashSet<String>> = LazyLock::new(|| {
|
static NETWORK_MONITORS: LazyLock<HashSet<String>> = LazyLock::new(|| {
|
||||||
let mut nm = HashSet::new();
|
let mut nm = HashSet::new();
|
||||||
@@ -10,8 +11,9 @@ static NETWORK_MONITORS: LazyLock<HashSet<String>> = LazyLock::new(|| {
|
|||||||
nm
|
nm
|
||||||
});
|
});
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone)]
|
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, ToSchema)]
|
||||||
pub struct NodeResult {
|
pub struct NodeResult {
|
||||||
|
#[schema(value_type = u32)]
|
||||||
pub node_id: NodeId,
|
pub node_id: NodeId,
|
||||||
pub identity: String,
|
pub identity: String,
|
||||||
pub reliability: u8,
|
pub reliability: u8,
|
||||||
@@ -34,7 +36,7 @@ pub enum MonitorResults {
|
|||||||
Gateway(Vec<NodeResult>),
|
Gateway(Vec<NodeResult>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
#[derive(Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||||
pub struct MonitorMessage {
|
pub struct MonitorMessage {
|
||||||
results: Vec<NodeResult>,
|
results: Vec<NodeResult>,
|
||||||
signature: String,
|
signature: String,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use crate::measurements::packet::{EchoPacket, ReplyPacket};
|
|||||||
use bytes::{BufMut, BytesMut};
|
use bytes::{BufMut, BytesMut};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use nym_crypto::asymmetric::identity;
|
use nym_crypto::asymmetric::identity;
|
||||||
use nym_task::TaskClient;
|
use nym_task::ShutdownToken;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{io, process};
|
use std::{io, process};
|
||||||
@@ -19,19 +19,19 @@ use tracing::{debug, error, info, trace, warn};
|
|||||||
pub struct PacketListener {
|
pub struct PacketListener {
|
||||||
address: SocketAddr,
|
address: SocketAddr,
|
||||||
connection_handler: Arc<ConnectionHandler>,
|
connection_handler: Arc<ConnectionHandler>,
|
||||||
shutdown: TaskClient,
|
shutdown_token: ShutdownToken,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PacketListener {
|
impl PacketListener {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
address: SocketAddr,
|
address: SocketAddr,
|
||||||
identity: Arc<identity::KeyPair>,
|
identity: Arc<identity::KeyPair>,
|
||||||
shutdown: TaskClient,
|
shutdown_token: ShutdownToken,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
PacketListener {
|
PacketListener {
|
||||||
address,
|
address,
|
||||||
connection_handler: Arc::new(ConnectionHandler { identity }),
|
connection_handler: Arc::new(ConnectionHandler { identity }),
|
||||||
shutdown,
|
shutdown_token,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,26 +51,22 @@ impl PacketListener {
|
|||||||
|
|
||||||
info!("Started listening for echo packets on {}", self.address);
|
info!("Started listening for echo packets on {}", self.address);
|
||||||
|
|
||||||
let mut shutdown_listener = self.shutdown.clone();
|
while !self.shutdown_token.is_cancelled() {
|
||||||
|
|
||||||
while !shutdown_listener.is_shutdown() {
|
|
||||||
// cloning the arc as each accepted socket is handled in separate task
|
// cloning the arc as each accepted socket is handled in separate task
|
||||||
let connection_handler = Arc::clone(&self.connection_handler);
|
let connection_handler = Arc::clone(&self.connection_handler);
|
||||||
let mut handler_shutdown_listener = self.shutdown.clone();
|
|
||||||
handler_shutdown_listener.disarm();
|
|
||||||
|
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
socket = listener.accept() => {
|
socket = listener.accept() => {
|
||||||
match socket {
|
match socket {
|
||||||
Ok((socket, remote_addr)) => {
|
Ok((socket, remote_addr)) => {
|
||||||
debug!("New verloc connection from {}", remote_addr);
|
debug!("New verloc connection from {remote_addr}");
|
||||||
|
let cancel = self.shutdown_token.child_token(format!("handler_{remote_addr}"));
|
||||||
tokio::spawn(connection_handler.handle_connection(socket, remote_addr, handler_shutdown_listener));
|
tokio::spawn(async move { cancel.run_until_cancelled(connection_handler.handle_connection(socket, remote_addr)).await });
|
||||||
}
|
}
|
||||||
Err(err) => warn!("Failed to accept incoming connection - {err}"),
|
Err(err) => warn!("Failed to accept incoming connection - {err}"),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ = shutdown_listener.recv() => {
|
_ = self.shutdown_token.cancelled() => {
|
||||||
trace!("PacketListener: Received shutdown");
|
trace!("PacketListener: Received shutdown");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,50 +84,29 @@ impl ConnectionHandler {
|
|||||||
packet.construct_reply(self.identity.private_key())
|
packet.construct_reply(self.identity.private_key())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn handle_connection(
|
pub(crate) async fn handle_connection(self: Arc<Self>, conn: TcpStream, remote: SocketAddr) {
|
||||||
self: Arc<Self>,
|
debug!("Starting connection handler for {remote}");
|
||||||
conn: TcpStream,
|
|
||||||
remote: SocketAddr,
|
|
||||||
mut shutdown_listener: TaskClient,
|
|
||||||
) {
|
|
||||||
debug!("Starting connection handler for {:?}", remote);
|
|
||||||
|
|
||||||
let mut framed_conn = Framed::new(conn, EchoPacketCodec);
|
let mut framed_conn = Framed::new(conn, EchoPacketCodec);
|
||||||
while !shutdown_listener.is_shutdown() {
|
while let Some(echo_packet) = framed_conn.next().await {
|
||||||
tokio::select! {
|
let reply_packet = match echo_packet {
|
||||||
biased;
|
Ok(echo_packet) => self.handle_echo_packet(echo_packet),
|
||||||
_ = shutdown_listener.recv() => {
|
Err(err) => {
|
||||||
trace!("ConnectionHandler: Shutdown received");
|
debug!(
|
||||||
|
"The socket connection got corrupted with error: {err}. Closing the socket"
|
||||||
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
maybe_echo_packet = framed_conn.next() => {
|
};
|
||||||
// handle echo packet
|
|
||||||
let reply_packet = match maybe_echo_packet {
|
|
||||||
Some(Ok(echo_packet)) => self.handle_echo_packet(echo_packet),
|
|
||||||
Some(Err(err)) => {
|
|
||||||
debug!(
|
|
||||||
"The socket connection got corrupted with error: {err}. Closing the socket",
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
debug!("The socket connection got terminated by the remote!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// write back the reply (note the lack of framing)
|
// write back the reply (note the lack of framing)
|
||||||
if let Err(err) = framed_conn
|
if let Err(err) = framed_conn
|
||||||
.get_mut()
|
.get_mut()
|
||||||
.write_all(reply_packet.to_bytes().as_ref())
|
.write_all(reply_packet.to_bytes().as_ref())
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
debug!(
|
debug!("Failed to write reply packet back to the sender: {err}. Closing the socket on our end");
|
||||||
"Failed to write reply packet back to the sender - {}. Closing the socket on our end",
|
return;
|
||||||
err
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use crate::models::VerlocNodeResult;
|
|||||||
use futures::stream::FuturesUnordered;
|
use futures::stream::FuturesUnordered;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use nym_crypto::asymmetric::identity;
|
use nym_crypto::asymmetric::identity;
|
||||||
use nym_task::TaskClient;
|
use nym_task::ShutdownToken;
|
||||||
use nym_validator_client::models::NymNodeDescription;
|
use nym_validator_client::models::NymNodeDescription;
|
||||||
use nym_validator_client::NymApiClient;
|
use nym_validator_client::NymApiClient;
|
||||||
use rand::prelude::SliceRandom;
|
use rand::prelude::SliceRandom;
|
||||||
@@ -23,7 +23,7 @@ pub struct VerlocMeasurer {
|
|||||||
config: Config,
|
config: Config,
|
||||||
packet_sender: Arc<PacketSender>,
|
packet_sender: Arc<PacketSender>,
|
||||||
packet_listener: Arc<PacketListener>,
|
packet_listener: Arc<PacketListener>,
|
||||||
shutdown_listener: TaskClient,
|
shutdown_token: ShutdownToken,
|
||||||
state: SharedVerlocStats,
|
state: SharedVerlocStats,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ impl VerlocMeasurer {
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
config: Config,
|
config: Config,
|
||||||
identity: Arc<identity::KeyPair>,
|
identity: Arc<identity::KeyPair>,
|
||||||
shutdown_listener: TaskClient,
|
shutdown_token: ShutdownToken,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
VerlocMeasurer {
|
VerlocMeasurer {
|
||||||
packet_sender: Arc::new(PacketSender::new(
|
packet_sender: Arc::new(PacketSender::new(
|
||||||
@@ -40,14 +40,14 @@ impl VerlocMeasurer {
|
|||||||
config.packet_timeout,
|
config.packet_timeout,
|
||||||
config.connection_timeout,
|
config.connection_timeout,
|
||||||
config.delay_between_packets,
|
config.delay_between_packets,
|
||||||
shutdown_listener.clone().named("VerlocPacketSender"),
|
shutdown_token.clone_with_suffix("packet_sender"),
|
||||||
)),
|
)),
|
||||||
packet_listener: Arc::new(PacketListener::new(
|
packet_listener: Arc::new(PacketListener::new(
|
||||||
config.listening_address,
|
config.listening_address,
|
||||||
Arc::clone(&identity),
|
Arc::clone(&identity),
|
||||||
shutdown_listener.clone().named("VerlocPacketListener"),
|
shutdown_token.clone_with_suffix("packet_listener"),
|
||||||
)),
|
)),
|
||||||
shutdown_listener,
|
shutdown_token,
|
||||||
config,
|
config,
|
||||||
state: SharedVerlocStats::default(),
|
state: SharedVerlocStats::default(),
|
||||||
}
|
}
|
||||||
@@ -69,9 +69,6 @@ impl VerlocMeasurer {
|
|||||||
return MeasurementOutcome::Done;
|
return MeasurementOutcome::Done;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut shutdown_listener = self.shutdown_listener.clone().named("VerlocMeasurement");
|
|
||||||
shutdown_listener.disarm();
|
|
||||||
|
|
||||||
for chunk in nodes_to_test.chunks(self.config.tested_nodes_batch_size) {
|
for chunk in nodes_to_test.chunks(self.config.tested_nodes_batch_size) {
|
||||||
let mut chunk_results = Vec::with_capacity(chunk.len());
|
let mut chunk_results = Vec::with_capacity(chunk.len());
|
||||||
|
|
||||||
@@ -95,7 +92,7 @@ impl VerlocMeasurer {
|
|||||||
.collect::<FuturesUnordered<_>>();
|
.collect::<FuturesUnordered<_>>();
|
||||||
|
|
||||||
// exhaust the results
|
// exhaust the results
|
||||||
while !shutdown_listener.is_shutdown() {
|
while !self.shutdown_token.is_cancelled() {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
measurement_result = measurement_chunk.next() => {
|
measurement_result = measurement_chunk.next() => {
|
||||||
let Some(result) = measurement_result else {
|
let Some(result) = measurement_result else {
|
||||||
@@ -120,7 +117,7 @@ impl VerlocMeasurer {
|
|||||||
};
|
};
|
||||||
chunk_results.push(VerlocNodeResult::new(identity, measurement_result));
|
chunk_results.push(VerlocNodeResult::new(identity, measurement_result));
|
||||||
},
|
},
|
||||||
_ = shutdown_listener.recv() => {
|
_ = self.shutdown_token.cancelled() => {
|
||||||
trace!("Shutdown received while measuring");
|
trace!("Shutdown received while measuring");
|
||||||
return MeasurementOutcome::Shutdown;
|
return MeasurementOutcome::Shutdown;
|
||||||
}
|
}
|
||||||
@@ -155,7 +152,7 @@ impl VerlocMeasurer {
|
|||||||
pub async fn run(&mut self) {
|
pub async fn run(&mut self) {
|
||||||
self.start_listening();
|
self.start_listening();
|
||||||
|
|
||||||
while !self.shutdown_listener.is_shutdown() {
|
while !self.shutdown_token.is_cancelled() {
|
||||||
info!("Starting verloc measurements");
|
info!("Starting verloc measurements");
|
||||||
// TODO: should we also measure gateways?
|
// TODO: should we also measure gateways?
|
||||||
|
|
||||||
@@ -209,7 +206,7 @@ impl VerlocMeasurer {
|
|||||||
|
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = sleep(self.config.testing_interval) => {},
|
_ = sleep(self.config.testing_interval) => {},
|
||||||
_ = self.shutdown_listener.recv() => {
|
_ = self.shutdown_token.cancelled() => {
|
||||||
trace!("Shutdown received while sleeping");
|
trace!("Shutdown received while sleeping");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use crate::error::VerlocError;
|
|||||||
use crate::measurements::packet::{EchoPacket, ReplyPacket};
|
use crate::measurements::packet::{EchoPacket, ReplyPacket};
|
||||||
use crate::models::VerlocMeasurement;
|
use crate::models::VerlocMeasurement;
|
||||||
use nym_crypto::asymmetric::ed25519;
|
use nym_crypto::asymmetric::ed25519;
|
||||||
use nym_task::TaskClient;
|
use nym_task::ShutdownToken;
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -45,7 +45,7 @@ pub struct PacketSender {
|
|||||||
packet_timeout: Duration,
|
packet_timeout: Duration,
|
||||||
connection_timeout: Duration,
|
connection_timeout: Duration,
|
||||||
delay_between_packets: Duration,
|
delay_between_packets: Duration,
|
||||||
shutdown_listener: TaskClient,
|
shutdown_token: ShutdownToken,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PacketSender {
|
impl PacketSender {
|
||||||
@@ -55,7 +55,7 @@ impl PacketSender {
|
|||||||
packet_timeout: Duration,
|
packet_timeout: Duration,
|
||||||
connection_timeout: Duration,
|
connection_timeout: Duration,
|
||||||
delay_between_packets: Duration,
|
delay_between_packets: Duration,
|
||||||
shutdown_listener: TaskClient,
|
shutdown_token: ShutdownToken,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
PacketSender {
|
PacketSender {
|
||||||
identity,
|
identity,
|
||||||
@@ -63,7 +63,7 @@ impl PacketSender {
|
|||||||
packet_timeout,
|
packet_timeout,
|
||||||
connection_timeout,
|
connection_timeout,
|
||||||
delay_between_packets,
|
delay_between_packets,
|
||||||
shutdown_listener,
|
shutdown_token,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,9 +83,6 @@ impl PacketSender {
|
|||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
tested_node: TestedNode,
|
tested_node: TestedNode,
|
||||||
) -> Result<VerlocMeasurement, VerlocError> {
|
) -> Result<VerlocMeasurement, VerlocError> {
|
||||||
let mut shutdown_listener = self.shutdown_listener.fork(tested_node.address.to_string());
|
|
||||||
shutdown_listener.disarm();
|
|
||||||
|
|
||||||
let mut conn = match tokio::time::timeout(
|
let mut conn = match tokio::time::timeout(
|
||||||
self.connection_timeout,
|
self.connection_timeout,
|
||||||
TcpStream::connect(tested_node.address),
|
TcpStream::connect(tested_node.address),
|
||||||
@@ -148,7 +145,7 @@ impl PacketSender {
|
|||||||
Ok(Ok(_)) => {}
|
Ok(Ok(_)) => {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ = shutdown_listener.recv() => {
|
_ = self.shutdown_token.cancelled() => {
|
||||||
trace!("PacketSender: Received shutdown while sending");
|
trace!("PacketSender: Received shutdown while sending");
|
||||||
return Err(VerlocError::ShutdownReceived);
|
return Err(VerlocError::ShutdownReceived);
|
||||||
},
|
},
|
||||||
@@ -190,7 +187,7 @@ impl PacketSender {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ = shutdown_listener.recv() => {
|
_ = self.shutdown_token.cancelled() => {
|
||||||
trace!("PacketSender: Received shutdown while waiting for reply");
|
trace!("PacketSender: Received shutdown while waiting for reply");
|
||||||
return Err(VerlocError::ShutdownReceived);
|
return Err(VerlocError::ShutdownReceived);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[build]
|
||||||
|
target = "wasm32-unknown-unknown"
|
||||||
|
target_arch = "wasm32"
|
||||||
@@ -390,7 +390,6 @@ pub struct TopologyWasm {
|
|||||||
|
|
||||||
/// Specifies whether this client should attempt to retrieve all available network nodes
|
/// Specifies whether this client should attempt to retrieve all available network nodes
|
||||||
/// as opposed to just active mixnodes/gateways.
|
/// as opposed to just active mixnodes/gateways.
|
||||||
/// Useless without `ignore_epoch_roles = true`
|
|
||||||
pub use_extended_topology: bool,
|
pub use_extended_topology: bool,
|
||||||
|
|
||||||
/// Specifies whether this client should ignore the current epoch role of the target egress node
|
/// Specifies whether this client should ignore the current epoch role of the target egress node
|
||||||
|
|||||||
@@ -274,7 +274,6 @@ pub struct TopologyWasmOverride {
|
|||||||
|
|
||||||
/// Specifies whether this client should attempt to retrieve all available network nodes
|
/// Specifies whether this client should attempt to retrieve all available network nodes
|
||||||
/// as opposed to just active mixnodes/gateways.
|
/// as opposed to just active mixnodes/gateways.
|
||||||
/// Useless without `ignore_epoch_roles = true`
|
|
||||||
#[tsify(optional)]
|
#[tsify(optional)]
|
||||||
pub use_extended_topology: Option<bool>,
|
pub use_extended_topology: Option<bool>,
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,15 @@ use crate::error::WasmCoreError;
|
|||||||
use crate::storage::wasm_client_traits::WasmClientStorage;
|
use crate::storage::wasm_client_traits::WasmClientStorage;
|
||||||
use crate::storage::ClientStorage;
|
use crate::storage::ClientStorage;
|
||||||
use js_sys::Promise;
|
use js_sys::Promise;
|
||||||
|
use nym_client_core::client::base_client::storage::helpers::set_active_gateway;
|
||||||
|
use nym_client_core::client::base_client::storage::GatewaysDetailsStore;
|
||||||
use nym_client_core::client::replies::reply_storage::browser_backend;
|
use nym_client_core::client::replies::reply_storage::browser_backend;
|
||||||
use nym_client_core::config;
|
use nym_client_core::config;
|
||||||
use nym_client_core::init::helpers::current_gateways;
|
use nym_client_core::error::ClientCoreError;
|
||||||
|
use nym_client_core::init::helpers::gateways_for_init;
|
||||||
use nym_client_core::init::types::GatewaySelectionSpecification;
|
use nym_client_core::init::types::GatewaySelectionSpecification;
|
||||||
use nym_client_core::init::{
|
use nym_client_core::init::{
|
||||||
self,
|
self, setup_gateway,
|
||||||
types::{GatewaySetup, InitialisationResult},
|
types::{GatewaySetup, InitialisationResult},
|
||||||
};
|
};
|
||||||
use nym_sphinx::addressing::clients::Recipient;
|
use nym_sphinx::addressing::clients::Recipient;
|
||||||
@@ -18,7 +21,7 @@ use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
|||||||
use nym_topology::wasm_helpers::WasmFriendlyNymTopology;
|
use nym_topology::wasm_helpers::WasmFriendlyNymTopology;
|
||||||
use nym_topology::{NymTopology, RoutingNode};
|
use nym_topology::{NymTopology, RoutingNode};
|
||||||
use nym_validator_client::client::IdentityKey;
|
use nym_validator_client::client::IdentityKey;
|
||||||
use nym_validator_client::NymApiClient;
|
use nym_validator_client::{NymApiClient, UserAgent};
|
||||||
use rand::thread_rng;
|
use rand::thread_rng;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use wasm_bindgen::prelude::wasm_bindgen;
|
use wasm_bindgen::prelude::wasm_bindgen;
|
||||||
@@ -26,6 +29,7 @@ use wasm_bindgen_futures::future_to_promise;
|
|||||||
use wasm_utils::error::PromisableResult;
|
use wasm_utils::error::PromisableResult;
|
||||||
|
|
||||||
pub use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
|
pub use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
|
||||||
|
use wasm_utils::console_log;
|
||||||
|
|
||||||
// don't get too excited about the name, under the hood it's just a big fat placeholder
|
// don't get too excited about the name, under the hood it's just a big fat placeholder
|
||||||
// with no disk_persistence
|
// with no disk_persistence
|
||||||
@@ -130,10 +134,19 @@ pub async fn setup_gateway_from_api(
|
|||||||
minimum_performance: u8,
|
minimum_performance: u8,
|
||||||
) -> Result<InitialisationResult, WasmCoreError> {
|
) -> Result<InitialisationResult, WasmCoreError> {
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
let gateways = current_gateways(&mut rng, nym_apis, None, minimum_performance).await?;
|
let gateways = gateways_for_init(&mut rng, nym_apis, None, minimum_performance).await?;
|
||||||
setup_gateway_wasm(client_store, force_tls, chosen_gateway, gateways).await
|
setup_gateway_wasm(client_store, force_tls, chosen_gateway, gateways).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn current_gateways_wasm(
|
||||||
|
nym_apis: &[Url],
|
||||||
|
user_agent: Option<UserAgent>,
|
||||||
|
minimum_performance: u8,
|
||||||
|
) -> Result<Vec<RoutingNode>, ClientCoreError> {
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
gateways_for_init(&mut rng, nym_apis, user_agent, minimum_performance).await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn setup_from_topology(
|
pub async fn setup_from_topology(
|
||||||
explicit_gateway: Option<IdentityKey>,
|
explicit_gateway: Option<IdentityKey>,
|
||||||
force_tls: bool,
|
force_tls: bool,
|
||||||
@@ -143,3 +156,76 @@ pub async fn setup_from_topology(
|
|||||||
let gateways = topology.entry_capable_nodes().cloned().collect::<Vec<_>>();
|
let gateways = topology.entry_capable_nodes().cloned().collect::<Vec<_>>();
|
||||||
setup_gateway_wasm(client_store, force_tls, explicit_gateway, gateways).await
|
setup_gateway_wasm(client_store, force_tls, explicit_gateway, gateways).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn generate_new_client_keys(store: &ClientStorage) -> Result<(), WasmCoreError> {
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
init::generate_new_client_keys(&mut rng, store).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn add_gateway(
|
||||||
|
preferred_gateway: Option<IdentityKey>,
|
||||||
|
latency_based_selection: Option<bool>,
|
||||||
|
force_tls: bool,
|
||||||
|
nym_apis: &[Url],
|
||||||
|
user_agent: UserAgent,
|
||||||
|
min_performance: u8,
|
||||||
|
storage: &ClientStorage,
|
||||||
|
) -> Result<(), WasmCoreError> {
|
||||||
|
let selection_spec = GatewaySelectionSpecification::new(
|
||||||
|
preferred_gateway.clone(),
|
||||||
|
latency_based_selection,
|
||||||
|
force_tls,
|
||||||
|
);
|
||||||
|
|
||||||
|
let preferred_gateway = preferred_gateway
|
||||||
|
.as_ref()
|
||||||
|
.map(|g| g.parse())
|
||||||
|
.transpose()
|
||||||
|
.map_err(|source| WasmCoreError::InvalidGatewayIdentity { source })?;
|
||||||
|
|
||||||
|
let registered_gateways = storage.all_gateways_identities().await.map_err(|source| {
|
||||||
|
ClientCoreError::GatewaysDetailsStoreError {
|
||||||
|
source: Box::new(source),
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// if user provided gateway id (and we can't overwrite data), make sure we're not trying to register
|
||||||
|
// with a known gateway
|
||||||
|
if let Some(user_chosen) = preferred_gateway {
|
||||||
|
if registered_gateways.contains(&user_chosen) {
|
||||||
|
return Err(ClientCoreError::AlreadyRegistered {
|
||||||
|
gateway_id: user_chosen.to_base58_string(),
|
||||||
|
}
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup gateway by either registering a new one, or creating a new config from the selected
|
||||||
|
// one but with keys kept, or reusing the gateway configuration.
|
||||||
|
let available_gateways =
|
||||||
|
current_gateways_wasm(nym_apis, Some(user_agent), min_performance).await?;
|
||||||
|
|
||||||
|
// since we're registering with a brand new gateway,
|
||||||
|
// make sure the list of available gateways doesn't overlap the list of known gateways
|
||||||
|
let available_gateways = available_gateways
|
||||||
|
.into_iter()
|
||||||
|
.filter(|g| !registered_gateways.contains(&g.identity()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if available_gateways.is_empty() {
|
||||||
|
return Err(ClientCoreError::NoNewGatewaysAvailable.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let gateway_setup = GatewaySetup::New {
|
||||||
|
specification: selection_spec,
|
||||||
|
available_gateways,
|
||||||
|
};
|
||||||
|
|
||||||
|
let init_details = setup_gateway(gateway_setup, storage, storage).await?;
|
||||||
|
let gateway = init_details.gateway_id().to_base58_string();
|
||||||
|
set_active_gateway(storage, &gateway).await?;
|
||||||
|
|
||||||
|
console_log!("finished registration with gateway {gateway}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -128,8 +128,12 @@ impl GatewaysDetailsStore for ClientStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn all_gateways(&self) -> Result<Vec<GatewayRegistration>, Self::StorageError> {
|
async fn all_gateways(&self) -> Result<Vec<GatewayRegistration>, Self::StorageError> {
|
||||||
todo!()
|
let identities = self.registered_gateways().await?;
|
||||||
// let identities = self.all
|
let mut registered = Vec::with_capacity(identities.len());
|
||||||
|
for gateway_id in identities {
|
||||||
|
registered.push(self.load_gateway_details(&gateway_id).await?);
|
||||||
|
}
|
||||||
|
Ok(registered)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn has_gateway_details(&self, gateway_id: &str) -> Result<bool, Self::StorageError> {
|
async fn has_gateway_details(&self, gateway_id: &str) -> Result<bool, Self::StorageError> {
|
||||||
|
|||||||
@@ -4,13 +4,15 @@
|
|||||||
use crate::error::WasmCoreError;
|
use crate::error::WasmCoreError;
|
||||||
use crate::storage::wasm_client_traits::{v1, v2, WasmClientStorage};
|
use crate::storage::wasm_client_traits::{v1, v2, WasmClientStorage};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use js_sys::{Array, Promise};
|
use js_sys::Promise;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
use wasm_bindgen_futures::future_to_promise;
|
use wasm_bindgen_futures::future_to_promise;
|
||||||
use wasm_storage::traits::BaseWasmStorage;
|
use wasm_storage::traits::BaseWasmStorage;
|
||||||
use wasm_storage::{IdbVersionChangeEvent, WasmStorage};
|
use wasm_storage::{
|
||||||
|
Build, Database, RawDbResult, TryFromJs, TryToJs, VersionChangeEvent, WasmStorage,
|
||||||
|
};
|
||||||
use wasm_utils::error::{simple_js_error, PromisableResult};
|
use wasm_utils::error::{simple_js_error, PromisableResult};
|
||||||
use zeroize::Zeroizing;
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
@@ -44,26 +46,29 @@ impl ClientStorage {
|
|||||||
// special care must be taken on JS side to ensure it's correctly used there.
|
// special care must be taken on JS side to ensure it's correctly used there.
|
||||||
let passphrase = Zeroizing::new(passphrase);
|
let passphrase = Zeroizing::new(passphrase);
|
||||||
|
|
||||||
let migrate_fn = Some(|evt: &IdbVersionChangeEvent| -> Result<(), JsValue> {
|
let migrate_fn = Some(|evt: VersionChangeEvent, db: Database| -> RawDbResult<()> {
|
||||||
// Even if the web-sys bindings expose the version as a f64, the IndexedDB API
|
// Even if the web-sys bindings expose the version as a f64, the IndexedDB API
|
||||||
// works with an unsigned integer.
|
// works with an unsigned integer.
|
||||||
// See <https://github.com/rustwasm/wasm-bindgen/issues/1149>
|
// See <https://github.com/rustwasm/wasm-bindgen/issues/1149>
|
||||||
let old_version = evt.old_version() as u32;
|
let old_version = evt.old_version() as u32;
|
||||||
let db = evt.db();
|
|
||||||
|
|
||||||
if old_version < 1 {
|
if old_version < 1 {
|
||||||
// migrating to version 2
|
// migrating to version 2
|
||||||
|
|
||||||
db.create_object_store(v1::KEYS_STORE)?;
|
db.create_object_store(v1::KEYS_STORE).build()?;
|
||||||
db.create_object_store(v1::CORE_STORE)?;
|
db.create_object_store(v1::CORE_STORE).build()?;
|
||||||
|
|
||||||
db.create_object_store(v2::GATEWAY_REGISTRATIONS_ACTIVE_GATEWAY_STORE)?;
|
db.create_object_store(v2::GATEWAY_REGISTRATIONS_ACTIVE_GATEWAY_STORE)
|
||||||
db.create_object_store(v2::GATEWAY_REGISTRATIONS_REGISTERED_GATEWAYS_STORE)?;
|
.build()?;
|
||||||
|
db.create_object_store(v2::GATEWAY_REGISTRATIONS_REGISTERED_GATEWAYS_STORE)
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// version 1 -> unimplemented migration
|
// version 1 -> unimplemented migration
|
||||||
if old_version < 2 {
|
if old_version < 2 {
|
||||||
return Err(simple_js_error("this client is incompatible with existing storage. please initialise it again."));
|
return Err(simple_js_error("this client is incompatible with existing storage. please initialise it again.").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -110,7 +115,7 @@ impl BaseWasmStorage for ClientStorage {
|
|||||||
async fn read_value<T, K>(&self, store: &str, key: K) -> Result<Option<T>, Self::StorageError>
|
async fn read_value<T, K>(&self, store: &str, key: K) -> Result<Option<T>, Self::StorageError>
|
||||||
where
|
where
|
||||||
T: DeserializeOwned,
|
T: DeserializeOwned,
|
||||||
K: JsCast,
|
K: TryToJs,
|
||||||
{
|
{
|
||||||
Ok(self.inner.read_value(store, key).await?)
|
Ok(self.inner.read_value(store, key).await?)
|
||||||
}
|
}
|
||||||
@@ -123,33 +128,33 @@ impl BaseWasmStorage for ClientStorage {
|
|||||||
) -> Result<(), Self::StorageError>
|
) -> Result<(), Self::StorageError>
|
||||||
where
|
where
|
||||||
T: Serialize,
|
T: Serialize,
|
||||||
K: JsCast,
|
K: TryToJs + TryFromJs,
|
||||||
{
|
{
|
||||||
Ok(self.inner.store_value(store, key, value).await?)
|
Ok(self.inner.store_value(store, key, value).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn remove_value<K>(&self, store: &str, key: K) -> Result<(), Self::StorageError>
|
async fn remove_value<K>(&self, store: &str, key: K) -> Result<(), Self::StorageError>
|
||||||
where
|
where
|
||||||
K: JsCast,
|
K: TryToJs,
|
||||||
{
|
{
|
||||||
Ok(self.inner.remove_value(store, key).await?)
|
Ok(self.inner.remove_value(store, key).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn has_value<K>(&self, store: &str, key: K) -> Result<bool, Self::StorageError>
|
async fn has_value<K>(&self, store: &str, key: K) -> Result<bool, Self::StorageError>
|
||||||
where
|
where
|
||||||
K: JsCast,
|
K: TryToJs,
|
||||||
{
|
{
|
||||||
Ok(self.inner.has_value(store, key).await?)
|
Ok(self.inner.has_value(store, key).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn key_count<K>(&self, store: &str, key: K) -> Result<u32, Self::StorageError>
|
async fn key_count<K>(&self, store: &str, key: K) -> Result<u32, Self::StorageError>
|
||||||
where
|
where
|
||||||
K: JsCast,
|
K: TryToJs,
|
||||||
{
|
{
|
||||||
Ok(self.inner.key_count(store, key).await?)
|
Ok(self.inner.key_count(store, key).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_all_keys(&self, store: &str) -> Result<Array, Self::StorageError> {
|
async fn get_all_keys(&self, store: &str) -> Result<Vec<JsValue>, Self::StorageError> {
|
||||||
Ok(self.inner.get_all_keys(store).await?)
|
Ok(self.inner.get_all_keys(store).await?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,6 +119,15 @@ pub trait WasmClientStorage: BaseWasmStorage {
|
|||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn has_identity_key(&self) -> Result<bool, <Self as WasmClientStorage>::StorageError> {
|
||||||
|
self.has_value(
|
||||||
|
v1::KEYS_STORE,
|
||||||
|
JsValue::from_str(v1::ED25519_IDENTITY_KEYPAIR),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
async fn store_identity_keypair(
|
async fn store_identity_keypair(
|
||||||
&self,
|
&self,
|
||||||
keypair: &identity::KeyPair,
|
keypair: &identity::KeyPair,
|
||||||
@@ -277,8 +286,8 @@ pub trait WasmClientStorage: BaseWasmStorage {
|
|||||||
.await
|
.await
|
||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
.map(|arr| {
|
.map(|arr| {
|
||||||
arr.to_vec()
|
arr.iter()
|
||||||
.into_iter()
|
.cloned()
|
||||||
.filter_map(|key| key.as_string())
|
.filter_map(|key| key.as_string())
|
||||||
.collect()
|
.collect()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[build]
|
||||||
|
target = "wasm32-unknown-unknown"
|
||||||
|
target_arch = "wasm32"
|
||||||
@@ -9,7 +9,7 @@ license.workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-trait = { workspace = true }
|
async-trait = { workspace = true }
|
||||||
futures = { workspace = true }
|
getrandom = { workspace = true, features = ["js"] }
|
||||||
js-sys = { workspace = true }
|
js-sys = { workspace = true }
|
||||||
wasm-bindgen = { workspace = true }
|
wasm-bindgen = { workspace = true }
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use indexed_db_futures::web_sys::DomException;
|
|
||||||
use serde_wasm_bindgen::Error;
|
use serde_wasm_bindgen::Error;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use wasm_bindgen::JsValue;
|
use wasm_bindgen::JsValue;
|
||||||
@@ -12,15 +11,11 @@ pub enum StorageError {
|
|||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Json(String),
|
Json(String),
|
||||||
|
|
||||||
#[error("DomException {name} ({code}): {message}")]
|
#[error("storage failure: {message}")]
|
||||||
DomException {
|
InternalStorageFailure { message: String },
|
||||||
/// DomException code
|
|
||||||
code: u16,
|
#[error("failed to open the db file: {message}")]
|
||||||
/// Specific name of the DomException
|
DbOpenFailure { message: String },
|
||||||
name: String,
|
|
||||||
/// Message given to the DomException
|
|
||||||
message: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[error("FATAL ERROR: storage key is somehow present {count} times in the table!")]
|
#[error("FATAL ERROR: storage key is somehow present {count} times in the table!")]
|
||||||
DuplicateKey { count: u32 },
|
DuplicateKey { count: u32 },
|
||||||
@@ -46,12 +41,18 @@ impl From<StorageError> for JsValue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<DomException> for StorageError {
|
impl From<indexed_db_futures::error::Error> for StorageError {
|
||||||
fn from(value: DomException) -> StorageError {
|
fn from(value: indexed_db_futures::error::Error) -> Self {
|
||||||
StorageError::DomException {
|
StorageError::InternalStorageFailure {
|
||||||
name: value.name(),
|
message: value.to_string(),
|
||||||
message: value.message(),
|
}
|
||||||
code: value.code(),
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<indexed_db_futures::error::OpenDbError> for StorageError {
|
||||||
|
fn from(value: indexed_db_futures::error::OpenDbError) -> Self {
|
||||||
|
StorageError::DbOpenFailure {
|
||||||
|
message: value.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2023-2025 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::cipher_export::StoredExportedStoreCipher;
|
use crate::cipher_export::StoredExportedStoreCipher;
|
||||||
use crate::error::StorageError;
|
use crate::error::StorageError;
|
||||||
|
use indexed_db_futures::transaction::TransactionMode;
|
||||||
use nym_store_cipher::{
|
use nym_store_cipher::{
|
||||||
Aes256Gcm, Algorithm, EncryptedData, KdfInfo, KeySizeUser, Params, StoreCipher, Unsigned,
|
Aes256Gcm, Algorithm, EncryptedData, KdfInfo, KeySizeUser, Params, StoreCipher, Unsigned,
|
||||||
Version,
|
Version,
|
||||||
@@ -13,7 +14,10 @@ use std::future::IntoFuture;
|
|||||||
use wasm_bindgen::JsValue;
|
use wasm_bindgen::JsValue;
|
||||||
use wasm_utils::console_log;
|
use wasm_utils::console_log;
|
||||||
|
|
||||||
|
pub use indexed_db_futures::database::{Database, VersionChangeEvent};
|
||||||
pub use indexed_db_futures::prelude::*;
|
pub use indexed_db_futures::prelude::*;
|
||||||
|
pub use indexed_db_futures::primitive::{TryFromJs, TryToJs};
|
||||||
|
pub use indexed_db_futures::Result as RawDbResult;
|
||||||
|
|
||||||
mod cipher_export;
|
mod cipher_export;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
@@ -54,31 +58,29 @@ impl WasmStorage {
|
|||||||
passphrase: Option<&[u8]>,
|
passphrase: Option<&[u8]>,
|
||||||
) -> Result<Self, StorageError>
|
) -> Result<Self, StorageError>
|
||||||
where
|
where
|
||||||
F: Fn(&IdbVersionChangeEvent) -> Result<(), JsValue> + 'static,
|
F: Fn(VersionChangeEvent, Database) -> RawDbResult<()> + 'static,
|
||||||
{
|
{
|
||||||
let mut db_req: OpenDbRequest = IdbDatabase::open_u32(db_name, version)?;
|
|
||||||
|
|
||||||
// we must always ensure the cipher table is present
|
// we must always ensure the cipher table is present
|
||||||
db_req.set_on_upgrade_needed(Some(
|
let db = Database::open(db_name)
|
||||||
move |evt: &IdbVersionChangeEvent| -> Result<(), JsValue> {
|
.with_version(version)
|
||||||
|
.with_on_upgrade_needed(move |event, db| {
|
||||||
// Even if the web-sys bindings expose the version as a f64, the IndexedDB API
|
// Even if the web-sys bindings expose the version as a f64, the IndexedDB API
|
||||||
// works with an unsigned integer.
|
// works with an unsigned integer.
|
||||||
// See <https://github.com/rustwasm/wasm-bindgen/issues/1149>
|
// See <https://github.com/rustwasm/wasm-bindgen/issues/1149>
|
||||||
let old_version = evt.old_version() as u32;
|
let old_version = event.old_version() as u32;
|
||||||
|
|
||||||
if old_version < 1 {
|
if old_version < 1 {
|
||||||
evt.db().create_object_store(CIPHER_INFO_STORE)?;
|
db.create_object_store(CIPHER_INFO_STORE).build()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(migrate) = migrate_fn.as_ref() {
|
if let Some(migrate) = migrate_fn.as_ref() {
|
||||||
migrate(evt)
|
migrate(event, db)
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
));
|
.await?;
|
||||||
|
|
||||||
let db: IdbDatabase = db_req.into_future().await?;
|
|
||||||
let inner = IdbWrapper(db);
|
let inner = IdbWrapper(db);
|
||||||
let store_cipher = inner.setup_store_cipher(passphrase).await?;
|
let store_cipher = inner.setup_store_cipher(passphrase).await?;
|
||||||
|
|
||||||
@@ -94,13 +96,12 @@ impl WasmStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn remove(db_name: &str) -> Result<(), StorageError> {
|
pub async fn remove(db_name: &str) -> Result<(), StorageError> {
|
||||||
IdbDatabase::delete_by_name(db_name)?.into_future().await?;
|
Database::delete_by_name(db_name)?.into_future().await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn exists(db_name: &str) -> Result<bool, StorageError> {
|
pub async fn exists(db_name: &str) -> Result<bool, StorageError> {
|
||||||
let db_req: OpenDbRequest = IdbDatabase::open(db_name)?;
|
let db = Database::open(db_name).await?;
|
||||||
let db: IdbDatabase = db_req.into_future().await?;
|
|
||||||
|
|
||||||
// if the db was already created before, at the very least cipher info store should exist,
|
// if the db was already created before, at the very least cipher info store should exist,
|
||||||
// thus the iterator should return at least one value
|
// thus the iterator should return at least one value
|
||||||
@@ -139,7 +140,7 @@ impl WasmStorage {
|
|||||||
pub async fn read_value<T, K>(&self, store: &str, key: K) -> Result<Option<T>, StorageError>
|
pub async fn read_value<T, K>(&self, store: &str, key: K) -> Result<Option<T>, StorageError>
|
||||||
where
|
where
|
||||||
T: DeserializeOwned,
|
T: DeserializeOwned,
|
||||||
K: wasm_bindgen::JsCast,
|
K: TryToJs,
|
||||||
{
|
{
|
||||||
self.inner
|
self.inner
|
||||||
.read_value_raw(store, key)
|
.read_value_raw(store, key)
|
||||||
@@ -156,7 +157,7 @@ impl WasmStorage {
|
|||||||
) -> Result<(), StorageError>
|
) -> Result<(), StorageError>
|
||||||
where
|
where
|
||||||
T: Serialize,
|
T: Serialize,
|
||||||
K: wasm_bindgen::JsCast,
|
K: TryToJs + TryFromJs,
|
||||||
{
|
{
|
||||||
self.inner
|
self.inner
|
||||||
.store_value_raw(store, key, &self.serialize_value(&value)?)
|
.store_value_raw(store, key, &self.serialize_value(&value)?)
|
||||||
@@ -165,14 +166,14 @@ impl WasmStorage {
|
|||||||
|
|
||||||
pub async fn remove_value<K>(&self, store: &str, key: K) -> Result<(), StorageError>
|
pub async fn remove_value<K>(&self, store: &str, key: K) -> Result<(), StorageError>
|
||||||
where
|
where
|
||||||
K: wasm_bindgen::JsCast,
|
K: TryToJs,
|
||||||
{
|
{
|
||||||
self.inner.remove_value_raw(store, key).await
|
self.inner.remove_value_raw(store, key).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn has_value<K>(&self, store: &str, key: K) -> Result<bool, StorageError>
|
pub async fn has_value<K>(&self, store: &str, key: K) -> Result<bool, StorageError>
|
||||||
where
|
where
|
||||||
K: wasm_bindgen::JsCast,
|
K: TryToJs,
|
||||||
{
|
{
|
||||||
match self.key_count(store, key).await? {
|
match self.key_count(store, key).await? {
|
||||||
0 => Ok(false),
|
0 => Ok(false),
|
||||||
@@ -183,82 +184,98 @@ impl WasmStorage {
|
|||||||
|
|
||||||
pub async fn key_count<K>(&self, store: &str, key: K) -> Result<u32, StorageError>
|
pub async fn key_count<K>(&self, store: &str, key: K) -> Result<u32, StorageError>
|
||||||
where
|
where
|
||||||
K: wasm_bindgen::JsCast,
|
K: TryToJs,
|
||||||
{
|
{
|
||||||
self.inner.get_key_count(store, key).await
|
self.inner.get_key_count(store, key).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_all_keys(&self, store: &str) -> Result<js_sys::Array, StorageError> {
|
pub async fn get_all_keys(&self, store: &str) -> Result<Vec<JsValue>, StorageError> {
|
||||||
self.inner.get_all_keys(store).await
|
self.inner.get_all_keys(store).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct IdbWrapper(IdbDatabase);
|
struct IdbWrapper(Database);
|
||||||
|
|
||||||
impl IdbWrapper {
|
impl IdbWrapper {
|
||||||
async fn read_value_raw<K>(&self, store: &str, key: K) -> Result<Option<JsValue>, StorageError>
|
async fn read_value_raw<K>(&self, store: &str, key: K) -> Result<Option<JsValue>, StorageError>
|
||||||
where
|
where
|
||||||
K: wasm_bindgen::JsCast,
|
K: TryToJs,
|
||||||
{
|
{
|
||||||
self.0
|
self.0
|
||||||
.transaction_on_one_with_mode(store, IdbTransactionMode::Readonly)?
|
.transaction(store)
|
||||||
|
.with_mode(TransactionMode::Readonly)
|
||||||
|
.build()?
|
||||||
.object_store(store)?
|
.object_store(store)?
|
||||||
.get(&key)?
|
.get(&key)
|
||||||
|
.primitive()?
|
||||||
.await
|
.await
|
||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn store_value_raw<K>(
|
async fn store_value_raw<K, T>(
|
||||||
&self,
|
&self,
|
||||||
store: &str,
|
store: &str,
|
||||||
key: K,
|
key: K,
|
||||||
value: &JsValue,
|
value: &T,
|
||||||
) -> Result<(), StorageError>
|
) -> Result<(), StorageError>
|
||||||
where
|
where
|
||||||
K: wasm_bindgen::JsCast,
|
K: TryToJs + TryFromJs,
|
||||||
|
T: TryToJs,
|
||||||
{
|
{
|
||||||
self.0
|
let tx = self
|
||||||
.transaction_on_one_with_mode(store, IdbTransactionMode::Readwrite)?
|
.0
|
||||||
.object_store(store)?
|
.transaction(store)
|
||||||
.put_key_val_owned(key, value)?
|
.with_mode(TransactionMode::Readwrite)
|
||||||
.into_future()
|
.build()?;
|
||||||
.await
|
|
||||||
.map_err(Into::into)
|
let store = tx.object_store(store)?;
|
||||||
|
store.put(value).with_key(key).primitive()?.await?;
|
||||||
|
|
||||||
|
tx.commit().await.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn remove_value_raw<K>(&self, store: &str, key: K) -> Result<(), StorageError>
|
async fn remove_value_raw<K>(&self, store: &str, key: K) -> Result<(), StorageError>
|
||||||
where
|
where
|
||||||
K: wasm_bindgen::JsCast,
|
K: TryToJs,
|
||||||
{
|
{
|
||||||
self.0
|
let tx = self
|
||||||
.transaction_on_one_with_mode(store, IdbTransactionMode::Readwrite)?
|
.0
|
||||||
.object_store(store)?
|
.transaction(store)
|
||||||
.delete_owned(key)?
|
.with_mode(TransactionMode::Readwrite)
|
||||||
.into_future()
|
.build()?;
|
||||||
.await
|
|
||||||
.map_err(Into::into)
|
let store = tx.object_store(store)?;
|
||||||
|
store.delete(key).primitive()?.await?;
|
||||||
|
|
||||||
|
tx.commit().await.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_key_count<K>(&self, store: &str, key: K) -> Result<u32, StorageError>
|
async fn get_key_count<K>(&self, store: &str, key: K) -> Result<u32, StorageError>
|
||||||
where
|
where
|
||||||
K: wasm_bindgen::JsCast,
|
K: TryToJs,
|
||||||
{
|
{
|
||||||
self.0
|
self.0
|
||||||
.transaction_on_one_with_mode(store, IdbTransactionMode::Readwrite)?
|
.transaction(store)
|
||||||
|
.with_mode(TransactionMode::Readonly)
|
||||||
|
.build()?
|
||||||
.object_store(store)?
|
.object_store(store)?
|
||||||
.count_with_key_owned(key)?
|
.count()
|
||||||
.into_future()
|
.with_query(key)
|
||||||
|
.primitive()?
|
||||||
.await
|
.await
|
||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_all_keys(&self, store: &str) -> Result<js_sys::Array, StorageError> {
|
async fn get_all_keys(&self, store: &str) -> Result<Vec<JsValue>, StorageError> {
|
||||||
self.0
|
self.0
|
||||||
.transaction_on_one_with_mode(store, IdbTransactionMode::Readonly)?
|
.transaction(store)
|
||||||
|
.with_mode(TransactionMode::Readonly)
|
||||||
|
.build()?
|
||||||
.object_store(store)?
|
.object_store(store)?
|
||||||
.get_all_keys()?
|
.get_all_keys()
|
||||||
.into_future()
|
.primitive()?
|
||||||
.await
|
.await?
|
||||||
|
.collect::<Result<Vec<_>, _>>()
|
||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,11 @@
|
|||||||
|
|
||||||
use crate::WasmStorage;
|
use crate::WasmStorage;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use js_sys::Array;
|
use indexed_db_futures::primitive::{TryFromJs, TryToJs};
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
use wasm_bindgen::JsValue;
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
pub trait BaseWasmStorage {
|
pub trait BaseWasmStorage {
|
||||||
@@ -17,7 +18,7 @@ pub trait BaseWasmStorage {
|
|||||||
async fn read_value<T, K>(&self, store: &str, key: K) -> Result<Option<T>, Self::StorageError>
|
async fn read_value<T, K>(&self, store: &str, key: K) -> Result<Option<T>, Self::StorageError>
|
||||||
where
|
where
|
||||||
T: DeserializeOwned,
|
T: DeserializeOwned,
|
||||||
K: wasm_bindgen::JsCast;
|
K: TryToJs;
|
||||||
|
|
||||||
async fn store_value<T, K>(
|
async fn store_value<T, K>(
|
||||||
&self,
|
&self,
|
||||||
@@ -27,21 +28,21 @@ pub trait BaseWasmStorage {
|
|||||||
) -> Result<(), Self::StorageError>
|
) -> Result<(), Self::StorageError>
|
||||||
where
|
where
|
||||||
T: Serialize,
|
T: Serialize,
|
||||||
K: wasm_bindgen::JsCast;
|
K: TryToJs + TryFromJs;
|
||||||
|
|
||||||
async fn remove_value<K>(&self, store: &str, key: K) -> Result<(), Self::StorageError>
|
async fn remove_value<K>(&self, store: &str, key: K) -> Result<(), Self::StorageError>
|
||||||
where
|
where
|
||||||
K: wasm_bindgen::JsCast;
|
K: TryToJs;
|
||||||
|
|
||||||
async fn has_value<K>(&self, store: &str, key: K) -> Result<bool, Self::StorageError>
|
async fn has_value<K>(&self, store: &str, key: K) -> Result<bool, Self::StorageError>
|
||||||
where
|
where
|
||||||
K: wasm_bindgen::JsCast;
|
K: TryToJs;
|
||||||
|
|
||||||
async fn key_count<K>(&self, store: &str, key: K) -> Result<u32, Self::StorageError>
|
async fn key_count<K>(&self, store: &str, key: K) -> Result<u32, Self::StorageError>
|
||||||
where
|
where
|
||||||
K: wasm_bindgen::JsCast;
|
K: TryToJs;
|
||||||
|
|
||||||
async fn get_all_keys(&self, store: &str) -> Result<js_sys::Array, Self::StorageError>;
|
async fn get_all_keys(&self, store: &str) -> Result<Vec<JsValue>, Self::StorageError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
@@ -55,7 +56,7 @@ impl BaseWasmStorage for WasmStorage {
|
|||||||
async fn read_value<T, K>(&self, store: &str, key: K) -> Result<Option<T>, Self::StorageError>
|
async fn read_value<T, K>(&self, store: &str, key: K) -> Result<Option<T>, Self::StorageError>
|
||||||
where
|
where
|
||||||
T: DeserializeOwned,
|
T: DeserializeOwned,
|
||||||
K: wasm_bindgen::JsCast,
|
K: TryToJs,
|
||||||
{
|
{
|
||||||
self.read_value(store, key).await
|
self.read_value(store, key).await
|
||||||
}
|
}
|
||||||
@@ -68,33 +69,33 @@ impl BaseWasmStorage for WasmStorage {
|
|||||||
) -> Result<(), Self::StorageError>
|
) -> Result<(), Self::StorageError>
|
||||||
where
|
where
|
||||||
T: Serialize,
|
T: Serialize,
|
||||||
K: wasm_bindgen::JsCast,
|
K: TryToJs + TryFromJs,
|
||||||
{
|
{
|
||||||
self.store_value(store, key, value).await
|
self.store_value(store, key, value).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn remove_value<K>(&self, store: &str, key: K) -> Result<(), Self::StorageError>
|
async fn remove_value<K>(&self, store: &str, key: K) -> Result<(), Self::StorageError>
|
||||||
where
|
where
|
||||||
K: wasm_bindgen::JsCast,
|
K: TryToJs,
|
||||||
{
|
{
|
||||||
self.remove_value(store, key).await
|
self.remove_value(store, key).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn has_value<K>(&self, store: &str, key: K) -> Result<bool, Self::StorageError>
|
async fn has_value<K>(&self, store: &str, key: K) -> Result<bool, Self::StorageError>
|
||||||
where
|
where
|
||||||
K: wasm_bindgen::JsCast,
|
K: TryToJs,
|
||||||
{
|
{
|
||||||
self.has_value(store, key).await
|
self.has_value(store, key).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn key_count<K>(&self, store: &str, key: K) -> Result<u32, Self::StorageError>
|
async fn key_count<K>(&self, store: &str, key: K) -> Result<u32, Self::StorageError>
|
||||||
where
|
where
|
||||||
K: wasm_bindgen::JsCast,
|
K: TryToJs,
|
||||||
{
|
{
|
||||||
self.key_count(store, key).await
|
self.key_count(store, key).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_all_keys(&self, store: &str) -> Result<Array, Self::StorageError> {
|
async fn get_all_keys(&self, store: &str) -> Result<Vec<JsValue>, Self::StorageError> {
|
||||||
self.get_all_keys(store).await
|
self.get_all_keys(store).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[build]
|
||||||
|
target = "wasm32-unknown-unknown"
|
||||||
|
target_arch = "wasm32"
|
||||||
@@ -267,18 +267,27 @@ impl PeerController {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_metrics(&self, new_host: &Host) {
|
async fn update_metrics(&self, new_host: &Host) {
|
||||||
let now = SystemTime::now();
|
let now = SystemTime::now();
|
||||||
const ACTIVITY_THRESHOLD: Duration = Duration::from_secs(60);
|
const ACTIVITY_THRESHOLD: Duration = Duration::from_secs(60);
|
||||||
|
|
||||||
|
let old_host = self.host_information.read().await;
|
||||||
|
|
||||||
let total_peers = new_host.peers.len();
|
let total_peers = new_host.peers.len();
|
||||||
let mut active_peers = 0;
|
let mut active_peers = 0;
|
||||||
let mut total_rx = 0;
|
let mut new_rx = 0;
|
||||||
let mut total_tx = 0;
|
let mut new_tx = 0;
|
||||||
|
|
||||||
for peer in new_host.peers.values() {
|
for (peer_key, peer) in new_host.peers.iter() {
|
||||||
total_rx += peer.rx_bytes;
|
// only consider pre-existing peers,
|
||||||
total_tx += peer.tx_bytes;
|
// so that the value would always be increasing
|
||||||
|
if let Some(prior) = old_host.peers.get(peer_key) {
|
||||||
|
let delta_rx = peer.rx_bytes.saturating_sub(prior.rx_bytes);
|
||||||
|
let delta_tx = prior.tx_bytes.saturating_sub(prior.tx_bytes);
|
||||||
|
|
||||||
|
new_rx += delta_rx;
|
||||||
|
new_tx += delta_tx;
|
||||||
|
}
|
||||||
|
|
||||||
// if a peer hasn't performed a handshake in last minute,
|
// if a peer hasn't performed a handshake in last minute,
|
||||||
// I think it's reasonable to assume it's no longer active
|
// I think it's reasonable to assume it's no longer active
|
||||||
@@ -296,10 +305,10 @@ impl PeerController {
|
|||||||
self.metrics.wireguard.update(
|
self.metrics.wireguard.update(
|
||||||
// if the conversion fails it means we're running not running on a 64bit system
|
// if the conversion fails it means we're running not running on a 64bit system
|
||||||
// and that's a reason enough for this failure.
|
// and that's a reason enough for this failure.
|
||||||
total_rx.try_into().expect(
|
new_rx.try_into().expect(
|
||||||
"failed to convert bytes from u64 to usize - are you running on non 64bit system?",
|
"failed to convert bytes from u64 to usize - are you running on non 64bit system?",
|
||||||
),
|
),
|
||||||
total_tx.try_into().expect(
|
new_tx.try_into().expect(
|
||||||
"failed to convert bytes from u64 to usize - are you running on non 64bit system?",
|
"failed to convert bytes from u64 to usize - are you running on non 64bit system?",
|
||||||
),
|
),
|
||||||
total_peers,
|
total_peers,
|
||||||
@@ -316,7 +325,7 @@ impl PeerController {
|
|||||||
log::error!("Can't read wireguard kernel data");
|
log::error!("Can't read wireguard kernel data");
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
self.update_metrics(&host);
|
self.update_metrics(&host).await;
|
||||||
|
|
||||||
*self.host_information.write().await = host;
|
*self.host_information.write().await = host;
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user