Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 55a0f80d73 | |||
| d3cdaf373b | |||
| 7c5f10a219 | |||
| f90fc4f2f0 | |||
| e95aca715c | |||
| 8e7d1d510d | |||
| 4062734a31 | |||
| ccd8ff26a3 | |||
| 43d043a9cd | |||
| 3d6cf730c2 | |||
| c0f8d98b63 | |||
| 91995da4f1 | |||
| 01fa1df66c | |||
| baddaaac22 | |||
| 2c4b5f168b | |||
| a557ac22c7 | |||
| 55ef89178b | |||
| d97be2d8ef | |||
| efd61eb47c | |||
| 4a01973b31 | |||
| 9ad9c3b8e7 | |||
| 6706500132 | |||
| 33fe059c28 | |||
| d6ed2b770b | |||
| 7c18a3dced | |||
| 09475ab4e0 | |||
| b7606cd2ef | |||
| 006a57312d | |||
| 9b5aded8a5 | |||
| f4a69636fe | |||
| 0463d88646 | |||
| 534bf5d824 | |||
| 34684b14db | |||
| 66d0296f47 | |||
| 03bbbf44e9 |
@@ -16,7 +16,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.45.4
|
||||
uses: mikefarah/yq@v4.47.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.45.4
|
||||
uses: mikefarah/yq@v4.47.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -6,7 +6,7 @@ jobs:
|
||||
greeting:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/first-interaction@v1
|
||||
- uses: actions/first-interaction@v3
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-message: 'Thank you for raising this issue'
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v4
|
||||
- name: Download report from previous job
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: report
|
||||
path: .github/workflows/support-files/notifications
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Java
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: "17"
|
||||
@@ -91,7 +91,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Download binary artifact
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: nyms5-apk-arch64
|
||||
path: apk
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.45.4
|
||||
uses: mikefarah/yq@v4.47.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-credential-proxy/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.45.4
|
||||
uses: mikefarah/yq@v4.47.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.45.4
|
||||
uses: mikefarah/yq@v4.47.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-network-monitor/Cargo.toml
|
||||
|
||||
@@ -34,18 +34,22 @@ jobs:
|
||||
- name: Get version from cargo.toml
|
||||
id: get_version
|
||||
run: |
|
||||
yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
VERSION=$(yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml)
|
||||
echo "result=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Set GIT_TAG variable
|
||||
run: echo "GIT_TAG=${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set RELEASE_TAG variable
|
||||
- name: Initialise RELEASE_TAG
|
||||
run: echo "RELEASE_TAG=" >> $GITHUB_ENV
|
||||
|
||||
- name: Set RELEASE_TAG for release
|
||||
if: github.event.inputs.release_image == 'true'
|
||||
run: echo "RELEASE_TAG=golden-" >> $GITHUB_ENV
|
||||
|
||||
|
||||
- name: Set IMAGE_NAME_AND_TAGS variable
|
||||
run: echo "IMAGE_NAME_AND_TAGS=${{ env.CONTAINER_NAME }}:${{ env.RELEASE_TAG }}${{ steps.get_version.outputs.result }}" >> $GITHUB_ENV
|
||||
|
||||
|
||||
- name: New env vars
|
||||
run: echo "RELEASE_TAG='$RELEASE_TAG' GIT_TAG='$GIT_TAG' IMAGE_NAME_AND_TAGS='$IMAGE_NAME_AND_TAGS'"
|
||||
|
||||
@@ -65,6 +69,6 @@ jobs:
|
||||
|
||||
- name: BuildAndPushImageOnHarbor
|
||||
run: |
|
||||
docker build -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.IMAGE_NAME_AND_TAGS }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:latest
|
||||
docker build -f ${{ env.WORKING_DIRECTORY }}/Dockerfile-pg . -t harbor.nymte.ch/nym/${{ env.IMAGE_NAME_AND_TAGS }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:latest
|
||||
docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.45.4
|
||||
uses: mikefarah/yq@v4.47.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-api/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.45.4
|
||||
uses: mikefarah/yq@v4.47.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.45.4
|
||||
uses: mikefarah/yq@v4.47.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.45.4
|
||||
uses: mikefarah/yq@v4.47.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.45.4
|
||||
uses: mikefarah/yq@v4.47.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
# Security and sensitive files
|
||||
.env*
|
||||
*.key
|
||||
*.pem
|
||||
*.p12
|
||||
*.pfx
|
||||
secrets/
|
||||
private/
|
||||
config/secrets/
|
||||
|
||||
# Development files
|
||||
node_modules/
|
||||
.npm/
|
||||
.npmrc
|
||||
.nvmrc
|
||||
*.log
|
||||
*.tmp
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Build artifacts
|
||||
dist/
|
||||
build/
|
||||
target/
|
||||
*.tgz
|
||||
*.tar.gz
|
||||
|
||||
# IDE files
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Test files
|
||||
test/
|
||||
tests/
|
||||
__tests__/
|
||||
*.test.js
|
||||
*.test.ts
|
||||
*.spec.js
|
||||
*.spec.ts
|
||||
|
||||
# Documentation
|
||||
docs/
|
||||
*.md
|
||||
!README.md
|
||||
|
||||
# CI/CD files
|
||||
.github/
|
||||
.gitlab-ci.yml
|
||||
.travis.yml
|
||||
.circleci/
|
||||
azure-pipelines.yml
|
||||
|
||||
# Scripts
|
||||
scripts/
|
||||
!scripts/security-check.sh
|
||||
@@ -0,0 +1,21 @@
|
||||
audit-level=moderate
|
||||
fund=false
|
||||
update-notifier=false
|
||||
ignore-scripts=false
|
||||
strict-ssl=true
|
||||
|
||||
registry=https://registry.npmjs.org/
|
||||
audit=true
|
||||
package-lock=true
|
||||
package-lock-only=false
|
||||
save-exact=false
|
||||
|
||||
# use npm ci for production builds (faster and more secure)
|
||||
# this will be enforced in CI/CD scripts
|
||||
|
||||
# prevent installation of optional dependencies that might contain vulnerabilities
|
||||
optional=false
|
||||
audit=true
|
||||
update-notifier=false
|
||||
|
||||
save-exact=false
|
||||
Generated
+332
-70
@@ -847,6 +847,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "binstring"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0669d5a35b64fdb5ab7fb19cae13148b6b5cbdf4b8247faf54ece47f699c8cef"
|
||||
|
||||
[[package]]
|
||||
name = "bip32"
|
||||
version = "0.5.3"
|
||||
@@ -942,6 +948,17 @@ dependencies = [
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blake2b_simd"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06e903a20b159e944f91ec8499fe1e55651480c541ea0a584f5d967c49ad9d99"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"arrayvec",
|
||||
"constant_time_eq",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blake3"
|
||||
version = "1.8.2"
|
||||
@@ -1332,6 +1349,17 @@ version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
|
||||
|
||||
[[package]]
|
||||
name = "coarsetime"
|
||||
version = "0.1.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91849686042de1b41cd81490edc83afbcb0abe5a9b6f2c4114f23ce8cca1bcf4"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasix",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.4"
|
||||
@@ -1368,19 +1396,6 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "console"
|
||||
version = "0.15.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
|
||||
dependencies = [
|
||||
"encode_unicode",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"unicode-width 0.2.1",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "console"
|
||||
version = "0.16.0"
|
||||
@@ -1868,6 +1883,12 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ct-codecs"
|
||||
version = "1.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b10589d1a5e400d61f9f38f12f884cfd080ff345de8f17efda36fe0e4a02aa8"
|
||||
|
||||
[[package]]
|
||||
name = "ctr"
|
||||
version = "0.9.2"
|
||||
@@ -1898,7 +1919,7 @@ dependencies = [
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"schannel",
|
||||
"socket2",
|
||||
"socket2 0.5.10",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -2331,12 +2352,6 @@ version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
|
||||
[[package]]
|
||||
name = "dotenv"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
|
||||
|
||||
[[package]]
|
||||
name = "dotenvy"
|
||||
version = "0.15.7"
|
||||
@@ -2370,7 +2385,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2424,6 +2439,16 @@ dependencies = [
|
||||
"signature",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ed25519-compact"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9b3460f44bea8cd47f45a0c70892f1eff856d97cd55358b2f73f663789f6190"
|
||||
dependencies = [
|
||||
"ct-codecs",
|
||||
"getrandom 0.2.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ed25519-consensus"
|
||||
version = "2.1.0"
|
||||
@@ -2488,6 +2513,8 @@ dependencies = [
|
||||
"ff",
|
||||
"generic-array 0.14.7",
|
||||
"group",
|
||||
"hkdf",
|
||||
"pem-rfc7468",
|
||||
"pkcs8",
|
||||
"rand_core 0.6.4",
|
||||
"sec1",
|
||||
@@ -3349,6 +3376,30 @@ dependencies = [
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac-sha1-compact"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18492c9f6f9a560e0d346369b665ad2bdbc89fa9bceca75796584e79042694c3"
|
||||
|
||||
[[package]]
|
||||
name = "hmac-sha256"
|
||||
version = "1.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad6880c8d4a9ebf39c6e8b77007ce223f646a4d21ce29d99f70cb16420545425"
|
||||
dependencies = [
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac-sha512"
|
||||
version = "1.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e89e8d20b3799fa526152a5301a771eaaad80857f83e01b23216ceaafb2d9280"
|
||||
dependencies = [
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.11"
|
||||
@@ -3492,7 +3543,7 @@ dependencies = [
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"socket2 0.5.10",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -3582,7 +3633,7 @@ dependencies = [
|
||||
"libc",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"socket2 0.5.10",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -3827,26 +3878,13 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indicatif"
|
||||
version = "0.17.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235"
|
||||
dependencies = [
|
||||
"console 0.15.11",
|
||||
"number_prefix",
|
||||
"portable-atomic",
|
||||
"unicode-width 0.2.1",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indicatif"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd"
|
||||
dependencies = [
|
||||
"console 0.16.0",
|
||||
"console",
|
||||
"portable-atomic",
|
||||
"unicode-width 0.2.1",
|
||||
"unit-prefix",
|
||||
@@ -3947,7 +3985,7 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f"
|
||||
dependencies = [
|
||||
"socket2",
|
||||
"socket2 0.5.10",
|
||||
"widestring",
|
||||
"windows-sys 0.48.0",
|
||||
"winreg",
|
||||
@@ -4088,6 +4126,32 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jwt-simple"
|
||||
version = "0.12.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "731011e9647a71ff4f8474176ff6ce6e0d2de87a0173f15613af3a84c3e3401a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"binstring",
|
||||
"blake2b_simd",
|
||||
"coarsetime",
|
||||
"ct-codecs",
|
||||
"ed25519-compact",
|
||||
"hmac-sha1-compact",
|
||||
"hmac-sha256",
|
||||
"hmac-sha512",
|
||||
"k256",
|
||||
"p256",
|
||||
"p384",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"superboring",
|
||||
"thiserror 2.0.12",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "k256"
|
||||
version = "0.13.4"
|
||||
@@ -4519,9 +4583,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mock_instant"
|
||||
version = "0.5.3"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e1d4c44418358edcac6e1d9ce59cea7fb38052429c7704033f1196f0c179e6a"
|
||||
checksum = "dce6dd36094cac388f119d2e9dc82dc730ef91c32a6222170d630e5414b956e6"
|
||||
|
||||
[[package]]
|
||||
name = "moka"
|
||||
@@ -4817,12 +4881,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "number_prefix"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
||||
|
||||
[[package]]
|
||||
name = "nym-api"
|
||||
version = "1.1.64"
|
||||
@@ -4843,7 +4901,7 @@ dependencies = [
|
||||
"cw3",
|
||||
"cw4",
|
||||
"dashmap",
|
||||
"dotenv",
|
||||
"dotenvy",
|
||||
"futures",
|
||||
"humantime-serde",
|
||||
"moka",
|
||||
@@ -4888,6 +4946,7 @@ dependencies = [
|
||||
"sqlx",
|
||||
"tempfile",
|
||||
"tendermint",
|
||||
"test-with",
|
||||
"thiserror 2.0.12",
|
||||
"time",
|
||||
"tokio",
|
||||
@@ -4951,6 +5010,25 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-authenticator-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"futures",
|
||||
"nym-authenticator-requests",
|
||||
"nym-credentials-interface",
|
||||
"nym-crypto",
|
||||
"nym-sdk",
|
||||
"nym-service-provider-requests-common",
|
||||
"nym-wireguard-types",
|
||||
"semver 1.0.26",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-authenticator-requests"
|
||||
version = "0.1.0"
|
||||
@@ -5386,6 +5464,7 @@ dependencies = [
|
||||
"nym-bin-common",
|
||||
"nym-compact-ecash",
|
||||
"nym-config",
|
||||
"nym-credential-proxy-lib",
|
||||
"nym-credential-proxy-requests",
|
||||
"nym-credentials",
|
||||
"nym-credentials-interface",
|
||||
@@ -5416,6 +5495,43 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-credential-proxy-lib"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
"bip39",
|
||||
"bs58",
|
||||
"futures",
|
||||
"humantime",
|
||||
"nym-compact-ecash",
|
||||
"nym-credential-proxy-requests",
|
||||
"nym-credentials",
|
||||
"nym-credentials-interface",
|
||||
"nym-crypto",
|
||||
"nym-ecash-contract-common",
|
||||
"nym-ecash-signer-check",
|
||||
"nym-network-defaults",
|
||||
"nym-validator-client",
|
||||
"rand 0.8.5",
|
||||
"reqwest 0.12.22",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"tempfile",
|
||||
"thiserror 2.0.12",
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-credential-proxy-requests"
|
||||
version = "0.1.0"
|
||||
@@ -5548,6 +5664,7 @@ dependencies = [
|
||||
"aead",
|
||||
"aes",
|
||||
"aes-gcm-siv",
|
||||
"base64 0.22.1",
|
||||
"blake3",
|
||||
"bs58",
|
||||
"cipher",
|
||||
@@ -5557,6 +5674,7 @@ dependencies = [
|
||||
"generic-array 0.14.7",
|
||||
"hkdf",
|
||||
"hmac",
|
||||
"jwt-simple",
|
||||
"nym-pemstore",
|
||||
"nym-sphinx-types",
|
||||
"rand 0.8.5",
|
||||
@@ -5645,14 +5763,6 @@ dependencies = [
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-execute"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-exit-policy"
|
||||
version = "0.1.0"
|
||||
@@ -5953,6 +6063,20 @@ dependencies = [
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-ip-packet-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures",
|
||||
"nym-ip-packet-requests",
|
||||
"nym-sdk",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-ip-packet-requests"
|
||||
version = "0.1.0"
|
||||
@@ -6241,7 +6365,7 @@ dependencies = [
|
||||
"hkdf",
|
||||
"human-repr",
|
||||
"humantime-serde",
|
||||
"indicatif 0.17.11",
|
||||
"indicatif",
|
||||
"ipnetwork",
|
||||
"lioness",
|
||||
"nym-bin-common",
|
||||
@@ -6357,7 +6481,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node-status-api"
|
||||
version = "3.3.0"
|
||||
version = "3.3.2"
|
||||
dependencies = [
|
||||
"ammonia",
|
||||
"anyhow",
|
||||
@@ -6679,6 +6803,26 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-signers-monitor"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"humantime",
|
||||
"itertools 0.14.0",
|
||||
"nym-bin-common",
|
||||
"nym-ecash-signer-check",
|
||||
"nym-network-defaults",
|
||||
"nym-task",
|
||||
"nym-validator-client",
|
||||
"time",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"url",
|
||||
"zulip-client",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.61"
|
||||
@@ -6822,10 +6966,12 @@ dependencies = [
|
||||
name = "nym-sphinx-addressing"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"nym-crypto",
|
||||
"nym-sphinx-types",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
@@ -7115,6 +7261,22 @@ dependencies = [
|
||||
"x25519-dalek",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-upgrade-mode-check"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"jwt-simple",
|
||||
"nym-crypto",
|
||||
"nym-http-api-client",
|
||||
"reqwest 0.12.22",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.12",
|
||||
"time",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-validator-client"
|
||||
version = "0.1.0"
|
||||
@@ -7285,6 +7447,26 @@ dependencies = [
|
||||
"ts-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-wg-gateway-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"nym-authenticator-client",
|
||||
"nym-authenticator-requests",
|
||||
"nym-bandwidth-controller",
|
||||
"nym-credentials-interface",
|
||||
"nym-crypto",
|
||||
"nym-node-requests",
|
||||
"nym-pemstore",
|
||||
"nym-sdk",
|
||||
"nym-statistics-common",
|
||||
"nym-validator-client",
|
||||
"rand 0.8.5",
|
||||
"thiserror 2.0.12",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-wireguard"
|
||||
version = "0.1.0"
|
||||
@@ -7683,6 +7865,18 @@ dependencies = [
|
||||
"sha2 0.10.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "p384"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6"
|
||||
dependencies = [
|
||||
"ecdsa",
|
||||
"elliptic-curve",
|
||||
"primeorder",
|
||||
"sha2 0.10.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pairing"
|
||||
version = "0.23.0"
|
||||
@@ -8122,6 +8316,28 @@ dependencies = [
|
||||
"elliptic-curve",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr2"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
@@ -8251,7 +8467,7 @@ dependencies = [
|
||||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls 0.23.29",
|
||||
"socket2",
|
||||
"socket2 0.5.10",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -8288,7 +8504,7 @@ dependencies = [
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2",
|
||||
"socket2 0.5.10",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
@@ -8663,6 +8879,7 @@ dependencies = [
|
||||
"pkcs1",
|
||||
"pkcs8",
|
||||
"rand_core 0.6.4",
|
||||
"sha2 0.10.9",
|
||||
"signature",
|
||||
"spki",
|
||||
"subtle 2.6.1",
|
||||
@@ -9496,9 +9713,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.10"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d"
|
||||
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
|
||||
|
||||
[[package]]
|
||||
name = "sluice"
|
||||
@@ -9574,6 +9791,16 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sphinx-packet"
|
||||
version = "0.6.0"
|
||||
@@ -9942,6 +10169,19 @@ version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142"
|
||||
|
||||
[[package]]
|
||||
name = "superboring"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "515cce34a781d7250b8a65706e0f2a5b99236ea605cb235d4baed6685820478f"
|
||||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
"hmac-sha256",
|
||||
"hmac-sha512",
|
||||
"rand 0.8.5",
|
||||
"rsa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
@@ -10174,6 +10414,19 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "test-with"
|
||||
version = "0.15.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0f370b9efbfbbc5f057cbce9888373eaeb146a3095bb8cc869b199c94d15559"
|
||||
dependencies = [
|
||||
"proc-macro-error2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "testnet-manager"
|
||||
version = "0.1.0"
|
||||
@@ -10182,11 +10435,11 @@ dependencies = [
|
||||
"bip39",
|
||||
"bs58",
|
||||
"clap",
|
||||
"console 0.15.11",
|
||||
"console",
|
||||
"cw-utils",
|
||||
"dkg-bypass-contract",
|
||||
"humantime",
|
||||
"indicatif 0.17.11",
|
||||
"indicatif",
|
||||
"nym-bin-common",
|
||||
"nym-coconut-dkg-common",
|
||||
"nym-compact-ecash",
|
||||
@@ -10366,9 +10619,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.46.1"
|
||||
version = "1.47.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17"
|
||||
checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
@@ -10379,10 +10632,10 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"slab",
|
||||
"socket2",
|
||||
"socket2 0.6.0",
|
||||
"tokio-macros",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10416,7 +10669,7 @@ dependencies = [
|
||||
"postgres-protocol",
|
||||
"postgres-types",
|
||||
"rand 0.9.2",
|
||||
"socket2",
|
||||
"socket2 0.5.10",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"whoami",
|
||||
@@ -10592,7 +10845,7 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"pin-project",
|
||||
"prost",
|
||||
"socket2",
|
||||
"socket2 0.5.10",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tower 0.4.13",
|
||||
@@ -10743,7 +10996,7 @@ version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c714cc8fc46db04fcfddbd274c6ef59bebb1b435155984e7c6e89c3ce66f200"
|
||||
dependencies = [
|
||||
"indicatif 0.18.0",
|
||||
"indicatif",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-subscriber",
|
||||
@@ -11439,6 +11692,15 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
|
||||
|
||||
[[package]]
|
||||
name = "wasix"
|
||||
version = "0.12.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1fbb4ef9bbca0c1170e0b00dd28abc9e3b68669821600cad1caaed606583c6d"
|
||||
dependencies = [
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.100"
|
||||
|
||||
+12
-7
@@ -43,6 +43,7 @@ members = [
|
||||
"common/cosmwasm-smart-contracts/nym-performance-contract",
|
||||
"common/cosmwasm-smart-contracts/nym-pool-contract",
|
||||
"common/cosmwasm-smart-contracts/vesting-contract",
|
||||
"common/credential-proxy",
|
||||
"common/credential-storage",
|
||||
"common/credential-utils",
|
||||
"common/credential-verification",
|
||||
@@ -53,7 +54,6 @@ members = [
|
||||
"common/ecash-signer-check",
|
||||
"common/ecash-signer-check-types",
|
||||
"common/ecash-time",
|
||||
"common/execute",
|
||||
"common/exit-policy",
|
||||
"common/gateway-requests",
|
||||
"common/gateway-stats-storage",
|
||||
@@ -96,7 +96,7 @@ members = [
|
||||
"common/ticketbooks-merkle",
|
||||
"common/topology",
|
||||
"common/tun",
|
||||
"common/types",
|
||||
"common/types", "common/upgrade-mode-check",
|
||||
"common/verloc",
|
||||
"common/wasm/client-core",
|
||||
"common/wasm/storage",
|
||||
@@ -112,10 +112,12 @@ members = [
|
||||
"gateway",
|
||||
"nym-api",
|
||||
"nym-api/nym-api-requests",
|
||||
"nym-authenticator-client",
|
||||
"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-ip-packet-client",
|
||||
"nym-network-monitor",
|
||||
"nym-node",
|
||||
"nym-node-status-api/nym-node-status-agent",
|
||||
@@ -123,9 +125,10 @@ members = [
|
||||
"nym-node-status-api/nym-node-status-client",
|
||||
"nym-node/nym-node-metrics",
|
||||
"nym-node/nym-node-requests",
|
||||
"nym-outfox",
|
||||
"nym-outfox", "nym-signers-monitor",
|
||||
"nym-statistics-api",
|
||||
"nym-validator-rewarder",
|
||||
"nym-wg-gateway-client",
|
||||
"nyx-chain-watcher",
|
||||
"sdk/ffi/cpp",
|
||||
"sdk/ffi/go",
|
||||
@@ -223,7 +226,7 @@ clap_complete = "4.5"
|
||||
clap_complete_fig = "4.5"
|
||||
colored = "2.2"
|
||||
comfy-table = "7.1.4"
|
||||
console = "0.15.11"
|
||||
console = "0.16.0"
|
||||
console-subscriber = "0.4.1"
|
||||
console_error_panic_hook = "0.1"
|
||||
const-str = "0.5.6"
|
||||
@@ -270,11 +273,12 @@ humantime = "2.2.0"
|
||||
humantime-serde = "1.1.1"
|
||||
hyper = "1.6.0"
|
||||
hyper-util = "0.1"
|
||||
indicatif = "0.17.11"
|
||||
indicatif = "0.18.0"
|
||||
inquire = "0.6.2"
|
||||
ip_network = "0.4.1"
|
||||
ipnetwork = "0.20"
|
||||
itertools = "0.14.0"
|
||||
jwt-simple = { version = "0.12.12", default-features = false, features = ["pure-rust"] }
|
||||
k256 = "0.13"
|
||||
lazy_static = "1.5.0"
|
||||
ledger-transport = "0.10.0"
|
||||
@@ -326,14 +330,15 @@ sqlx = "0.8.6"
|
||||
strum = "0.27.2"
|
||||
strum_macros = "0.27.2"
|
||||
subtle-encoding = "0.5"
|
||||
syn = "1"
|
||||
syn = "2"
|
||||
sysinfo = "0.37.0"
|
||||
tap = "1.0.1"
|
||||
tar = "0.4.44"
|
||||
test-with = { version = "0.15.4", default-features = false }
|
||||
tempfile = "3.20"
|
||||
thiserror = "2.0"
|
||||
time = "0.3.41"
|
||||
tokio = "1.45"
|
||||
tokio = "1.47"
|
||||
tokio-postgres = "0.7"
|
||||
tokio-stream = "0.1.17"
|
||||
tokio-test = "0.4.4"
|
||||
|
||||
@@ -175,6 +175,7 @@ impl MixTrafficController {
|
||||
},
|
||||
None => {
|
||||
tracing::trace!("MixTrafficController, client request channel closed");
|
||||
break
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::helpers::get_time_now;
|
||||
use crate::client::replies::{
|
||||
reply_controller::ReplyControllerSender, reply_storage::SentReplyKeys,
|
||||
};
|
||||
@@ -22,7 +23,7 @@ use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, C
|
||||
use nym_task::TaskClient;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::time::Duration;
|
||||
use tracing::*;
|
||||
|
||||
// The interval at which we check for stale buffers
|
||||
@@ -54,7 +55,7 @@ struct ReceivedMessagesBufferInner<R: MessageReceiver> {
|
||||
stats_tx: ClientStatsSender,
|
||||
|
||||
// Periodically check for stale buffers to clean up
|
||||
last_stale_check: Instant,
|
||||
last_stale_check: crate::client::helpers::Instant,
|
||||
}
|
||||
|
||||
impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
|
||||
@@ -154,7 +155,7 @@ impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
|
||||
}
|
||||
|
||||
fn cleanup_stale_buffers(&mut self) {
|
||||
let now = Instant::now();
|
||||
let now = get_time_now();
|
||||
if now - self.last_stale_check > STALE_BUFFER_CHECK_INTERVAL {
|
||||
self.last_stale_check = now;
|
||||
self.message_receiver
|
||||
@@ -190,7 +191,7 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
message_sender: None,
|
||||
recently_reconstructed: HashSet::new(),
|
||||
stats_tx,
|
||||
last_stale_check: Instant::now(),
|
||||
last_stale_check: get_time_now(),
|
||||
})),
|
||||
reply_key_storage,
|
||||
reply_controller_sender,
|
||||
|
||||
+3
-14
@@ -28,7 +28,7 @@ use cosmrs::proto::cosmwasm::wasm::v1::{
|
||||
QueryRawContractStateResponse, QuerySmartContractStateRequest, QuerySmartContractStateResponse,
|
||||
};
|
||||
use cosmrs::tendermint::{block, chain, Hash};
|
||||
use cosmrs::{AccountId, Coin as CosmosCoin, Tx};
|
||||
use cosmrs::{AccountId, Coin as CosmosCoin};
|
||||
use prost::Message;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -556,23 +556,12 @@ pub trait CosmWasmClient: TendermintRpcClient {
|
||||
Ok(serde_json::from_slice(&res.data)?)
|
||||
}
|
||||
|
||||
// deprecation warning is due to the fact the protobuf files built were based on cosmos-sdk 0.44,
|
||||
// where they prefer using tx_bytes directly. However, in 0.42, which we are using at the time
|
||||
// of writing this, the option does not work
|
||||
// TODO: we should really stop using the `tx` argument here and use `tx_bytes` exlusively,
|
||||
// however, at the time of writing this update, while our QA and mainnet networks do support it,
|
||||
// sandbox is still running old version of wasmd that lacks support for `tx_bytes`
|
||||
#[allow(deprecated)]
|
||||
async fn query_simulate(
|
||||
&self,
|
||||
tx: Option<Tx>,
|
||||
tx_bytes: Vec<u8>,
|
||||
) -> Result<SimulateResponse, NyxdError> {
|
||||
async fn query_simulate(&self, tx_bytes: Vec<u8>) -> Result<SimulateResponse, NyxdError> {
|
||||
let path = Some("/cosmos.tx.v1beta1.Service/Simulate".to_owned());
|
||||
|
||||
let req = SimulateRequest {
|
||||
tx: tx.map(Into::into),
|
||||
tx_bytes,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let res = self
|
||||
|
||||
+7
-10
@@ -81,17 +81,14 @@ where
|
||||
auth_info: single_unspecified_signer_auth(public_key, sequence_response.sequence),
|
||||
signatures: vec![Vec::new()],
|
||||
};
|
||||
self.query_simulate(Some(partial_tx), Vec::new()).await
|
||||
|
||||
// for completion sake, once we're able to transition into using `tx_bytes`,
|
||||
// we might want to use something like this instead:
|
||||
// let tx_raw: tx::Raw = cosmrs::proto::cosmos::tx::v1beta1::TxRaw {
|
||||
// body_bytes: partial_tx.body.into_bytes().unwrap(),
|
||||
// auth_info_bytes: partial_tx.auth_info.into_bytes().unwrap(),
|
||||
// signatures: partial_tx.signatures,
|
||||
// }
|
||||
// .into();
|
||||
// self.query_simulate(None, tx_raw.to_bytes().unwrap()).await
|
||||
let tx_raw: tx::Raw = cosmrs::proto::cosmos::tx::v1beta1::TxRaw {
|
||||
body_bytes: partial_tx.body.into_bytes()?,
|
||||
auth_info_bytes: partial_tx.auth_info.into_bytes()?,
|
||||
signatures: partial_tx.signatures,
|
||||
}
|
||||
.into();
|
||||
self.query_simulate(tx_raw.to_bytes()?).await
|
||||
}
|
||||
|
||||
async fn upload(
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
[package]
|
||||
name = "nym-credential-proxy-lib"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
readme.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
axum = { workspace = true }
|
||||
bip39 = { workspace = true, features = ["zeroize"] }
|
||||
bs58 = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
humantime = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
reqwest = { workspace = true, features = ["rustls-tls"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
strum = { workspace = true, features = ["derive"] }
|
||||
strum_macros = { workspace = true }
|
||||
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate", "time"] }
|
||||
time = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["sync"] }
|
||||
tokio-util = { workspace = true, features = ["rt"] }
|
||||
tracing = { workspace = true }
|
||||
uuid = { workspace = true, features = ["serde"] }
|
||||
url = { workspace = true }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
nym-credentials = { path = "../credentials" }
|
||||
nym-crypto = { path = "../crypto", features = ["asymmetric", "rand", "serde"] }
|
||||
nym-credentials-interface = { path = "../credentials-interface" }
|
||||
nym-credential-proxy-requests = { path = "../../nym-credential-proxy/nym-credential-proxy-requests" }
|
||||
nym-ecash-signer-check = { path = "../ecash-signer-check" }
|
||||
nym-ecash-contract-common = { path = "../cosmwasm-smart-contracts/ecash-contract" }
|
||||
nym-compact-ecash = { path = "../nym_offline_compact_ecash" }
|
||||
nym-validator-client = { path = "../client-libs/validator-client" }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = { workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
+10
-10
@@ -10,11 +10,11 @@ use nym_validator_client::nyxd::{Coin, Hash};
|
||||
use time::OffsetDateTime;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
pub(crate) struct BufferedDeposit {
|
||||
pub(crate) deposit_id: u32,
|
||||
pub struct BufferedDeposit {
|
||||
pub deposit_id: u32,
|
||||
|
||||
// note: this type implements `ZeroizeOnDrop`
|
||||
pub(crate) ed25519_private_key: ed25519::PrivateKey,
|
||||
pub ed25519_private_key: ed25519::PrivateKey,
|
||||
}
|
||||
|
||||
impl TryFrom<StorableEcashDeposit> for BufferedDeposit {
|
||||
@@ -36,14 +36,14 @@ impl TryFrom<StorableEcashDeposit> for BufferedDeposit {
|
||||
}
|
||||
|
||||
impl BufferedDeposit {
|
||||
pub(crate) fn new(deposit_id: u32, ed25519_private_key: ed25519::PrivateKey) -> Self {
|
||||
pub fn new(deposit_id: u32, ed25519_private_key: ed25519::PrivateKey) -> Self {
|
||||
BufferedDeposit {
|
||||
deposit_id,
|
||||
ed25519_private_key,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn sign_ticketbook_plaintext(
|
||||
pub fn sign_ticketbook_plaintext(
|
||||
&self,
|
||||
withdrawal_request: &WithdrawalRequest,
|
||||
) -> ed25519::Signature {
|
||||
@@ -52,13 +52,13 @@ impl BufferedDeposit {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct PerformedDeposits {
|
||||
pub(crate) deposits_data: Vec<BufferedDeposit>,
|
||||
pub struct PerformedDeposits {
|
||||
pub deposits_data: Vec<BufferedDeposit>,
|
||||
|
||||
// shared by all performed deposits as they were included in the same tx
|
||||
pub(crate) tx_hash: Hash,
|
||||
pub(crate) requested_on: OffsetDateTime,
|
||||
pub(crate) deposit_amount: Coin,
|
||||
pub tx_hash: Hash,
|
||||
pub requested_on: OffsetDateTime,
|
||||
pub deposit_amount: Coin,
|
||||
}
|
||||
|
||||
impl PerformedDeposits {
|
||||
+9
-7
@@ -1,11 +1,11 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::deposits_buffer::helpers::{request_sizes, BufferedDeposit, PerformedDeposits};
|
||||
use crate::deposits_buffer::helpers::request_sizes;
|
||||
use crate::deposits_buffer::refill_task::RefillTask;
|
||||
use crate::error::CredentialProxyError;
|
||||
use crate::http::state::required_deposit_cache::RequiredDepositCache;
|
||||
use crate::http::state::ChainClient;
|
||||
use crate::shared_state::nyxd_client::ChainClient;
|
||||
use crate::shared_state::required_deposit_cache::RequiredDepositCache;
|
||||
use crate::storage::CredentialProxyStorage;
|
||||
use nym_compact_ecash::PublicKeyUser;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
@@ -21,6 +21,8 @@ use tokio_util::sync::CancellationToken;
|
||||
use tracing::{debug, error, info, instrument, warn};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub use helpers::{BufferedDeposit, PerformedDeposits};
|
||||
|
||||
pub(crate) mod helpers;
|
||||
mod refill_task;
|
||||
|
||||
@@ -43,12 +45,12 @@ struct DepositsBufferInner {
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct DepositsBuffer {
|
||||
pub struct DepositsBuffer {
|
||||
inner: Arc<DepositsBufferInner>,
|
||||
}
|
||||
|
||||
impl DepositsBuffer {
|
||||
pub(crate) async fn new(
|
||||
pub async fn new(
|
||||
storage: CredentialProxyStorage,
|
||||
client: ChainClient,
|
||||
required_deposit_cache: RequiredDepositCache,
|
||||
@@ -250,7 +252,7 @@ impl DepositsBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn get_valid_deposit(
|
||||
pub async fn get_valid_deposit(
|
||||
&self,
|
||||
request_uuid: Uuid,
|
||||
requested_on: OffsetDateTime,
|
||||
@@ -290,7 +292,7 @@ impl DepositsBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn wait_for_shutdown(&self) {
|
||||
pub async fn wait_for_shutdown(&self) {
|
||||
let task_handle = self.inner.deposits_refill_task.take_task_join_handle();
|
||||
if let Some(task_handle) = task_handle {
|
||||
if !task_handle.is_finished() {
|
||||
+25
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2025 Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_ecash_signer_check::SignerCheckError;
|
||||
@@ -128,6 +128,30 @@ pub enum CredentialProxyError {
|
||||
#[from]
|
||||
source: SignerCheckError,
|
||||
},
|
||||
|
||||
#[error(
|
||||
"this operation couldn't be completed as the program is in the process of shutting down"
|
||||
)]
|
||||
ShutdownInProgress,
|
||||
|
||||
#[error("failed to obtain wallet shares with id {id}: {message}")]
|
||||
ShareByIdLoadError { message: String, id: i64 },
|
||||
|
||||
#[error("failed to obtain wallet shares with device_id {device_id} and credential_id: {credential_id}: {message}")]
|
||||
ShareByDeviceLoadError {
|
||||
message: String,
|
||||
device_id: String,
|
||||
credential_id: String,
|
||||
},
|
||||
|
||||
#[error("could not find shares with id {id}")]
|
||||
SharesByIdNotFound { id: i64 },
|
||||
|
||||
#[error("could not find shares with device_id {device_id} and credential_id: {credential_id}")]
|
||||
SharesByDeviceNotFound {
|
||||
device_id: String,
|
||||
credential_id: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl CredentialProxyError {
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use rand::rngs::OsRng;
|
||||
use rand::RngCore;
|
||||
use time::OffsetDateTime;
|
||||
use tracing::{debug, info, warn};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub fn random_uuid() -> Uuid {
|
||||
let mut bytes = [0u8; 16];
|
||||
let mut rng = OsRng;
|
||||
rng.fill_bytes(&mut bytes);
|
||||
Uuid::from_bytes(bytes)
|
||||
}
|
||||
|
||||
pub struct LockTimer {
|
||||
created: OffsetDateTime,
|
||||
message: String,
|
||||
}
|
||||
|
||||
impl LockTimer {
|
||||
pub fn new<S: Into<String>>(message: S) -> Self {
|
||||
LockTimer {
|
||||
message: message.into(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for LockTimer {
|
||||
fn drop(&mut self) {
|
||||
let time_taken = OffsetDateTime::now_utc() - self.created;
|
||||
let time_taken_formatted = humantime::format_duration(time_taken.unsigned_abs());
|
||||
if time_taken > time::Duration::SECOND * 10 {
|
||||
warn!(time_taken = %time_taken_formatted, "{}", self.message)
|
||||
} else if time_taken > time::Duration::SECOND * 5 {
|
||||
info!(time_taken = %time_taken_formatted, "{}", self.message)
|
||||
} else {
|
||||
debug!(time_taken = %time_taken_formatted, "{}", self.message)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LockTimer {
|
||||
fn default() -> Self {
|
||||
LockTimer {
|
||||
created: OffsetDateTime::now_utc(),
|
||||
message: "released the lock".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// #[allow(clippy::panic)]
|
||||
// fn build_sha_short() -> &'static str {
|
||||
// let bin_info = bin_info!();
|
||||
// if bin_info.commit_sha.len() < 7 {
|
||||
// panic!("unavailable build commit sha")
|
||||
// }
|
||||
//
|
||||
// if bin_info.commit_sha == "VERGEN_IDEMPOTENT_OUTPUT" {
|
||||
// error!("the binary hasn't been built correctly. it doesn't have a commit sha information");
|
||||
// return "unknown";
|
||||
// }
|
||||
//
|
||||
// &bin_info.commit_sha[..7]
|
||||
// }
|
||||
+16
-2
@@ -1,11 +1,12 @@
|
||||
// Copyright 2024 Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::CredentialProxyError;
|
||||
use axum::http::StatusCode;
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use axum::Json;
|
||||
use nym_credential_proxy_requests::api::v1::ErrorResponse;
|
||||
use tracing::warn;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -35,6 +36,10 @@ impl RequestError {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_plain_error(err: CredentialProxyError) -> Self {
|
||||
Self::from_err(err, StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
|
||||
pub fn new_server_error(err: CredentialProxyError, uuid: Uuid) -> Self {
|
||||
RequestError::new_with_uuid(err.to_string(), uuid, StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
@@ -59,3 +64,12 @@ impl IntoResponse for RequestError {
|
||||
(self.status, Json(self.inner)).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn db_failure<T>(err: CredentialProxyError, uuid: Uuid) -> Result<T, RequestError> {
|
||||
warn!("db failure: {err}");
|
||||
Err(RequestError::new_with_uuid(
|
||||
format!("oh no, something went wrong {err}"),
|
||||
uuid,
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
))
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2025 Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
pub mod deposits_buffer;
|
||||
pub mod error;
|
||||
pub mod helpers;
|
||||
pub mod http_helpers;
|
||||
pub mod nym_api_helpers;
|
||||
pub mod quorum_checker;
|
||||
pub mod shared_state;
|
||||
pub mod storage;
|
||||
pub mod ticketbook_manager;
|
||||
pub mod webhook;
|
||||
+9
-15
@@ -19,9 +19,9 @@ use time::{Date, OffsetDateTime};
|
||||
use tokio::sync::{Mutex, RwLock, RwLockReadGuard};
|
||||
use tracing::warn;
|
||||
|
||||
pub(crate) struct CachedEpoch {
|
||||
pub struct CachedEpoch {
|
||||
valid_until: OffsetDateTime,
|
||||
pub(crate) current_epoch: Epoch,
|
||||
pub current_epoch: Epoch,
|
||||
}
|
||||
|
||||
impl Default for CachedEpoch {
|
||||
@@ -34,11 +34,11 @@ impl Default for CachedEpoch {
|
||||
}
|
||||
|
||||
impl CachedEpoch {
|
||||
pub(crate) fn is_valid(&self) -> bool {
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.valid_until > OffsetDateTime::now_utc()
|
||||
}
|
||||
|
||||
pub(crate) fn update(&mut self, epoch: Epoch) {
|
||||
pub fn update(&mut self, epoch: Epoch) {
|
||||
let now = OffsetDateTime::now_utc();
|
||||
|
||||
let validity_duration = if let Some(epoch_finish) = epoch.deadline {
|
||||
@@ -58,13 +58,13 @@ impl CachedEpoch {
|
||||
}
|
||||
|
||||
// a map of items that never change for given key
|
||||
pub(crate) struct CachedImmutableItems<K, V> {
|
||||
pub struct CachedImmutableItems<K, V> {
|
||||
// I wonder if there's a more efficient structure with OnceLock or OnceCell or something
|
||||
inner: RwLock<HashMap<K, V>>,
|
||||
}
|
||||
|
||||
// an item that stays constant throughout given epoch
|
||||
pub(crate) type CachedImmutableEpochItem<T> = CachedImmutableItems<EpochId, T>;
|
||||
pub type CachedImmutableEpochItem<T> = CachedImmutableItems<EpochId, T>;
|
||||
|
||||
impl<K, V> Default for CachedImmutableItems<K, V> {
|
||||
fn default() -> Self {
|
||||
@@ -86,11 +86,7 @@ impl<K, V> CachedImmutableItems<K, V>
|
||||
where
|
||||
K: Eq + Hash,
|
||||
{
|
||||
pub(crate) async fn get_or_init<F, U, E>(
|
||||
&self,
|
||||
key: K,
|
||||
f: F,
|
||||
) -> Result<RwLockReadGuard<'_, V>, E>
|
||||
pub async fn get_or_init<F, U, E>(&self, key: K, f: F) -> Result<RwLockReadGuard<'_, V>, E>
|
||||
where
|
||||
F: FnOnce() -> U,
|
||||
U: Future<Output = Result<V, E>>,
|
||||
@@ -129,9 +125,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_sane_expiration_date(
|
||||
expiration_date: Date,
|
||||
) -> Result<(), CredentialProxyError> {
|
||||
pub fn ensure_sane_expiration_date(expiration_date: Date) -> Result<(), CredentialProxyError> {
|
||||
let today = ecash_today();
|
||||
|
||||
if expiration_date < today.date() {
|
||||
@@ -146,7 +140,7 @@ pub(crate) fn ensure_sane_expiration_date(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn query_all_threshold_apis<F, T, U>(
|
||||
pub async fn query_all_threshold_apis<F, T, U>(
|
||||
all_apis: Vec<EcashApiClient>,
|
||||
threshold: u64,
|
||||
f: F,
|
||||
+4
-4
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::error::CredentialProxyError;
|
||||
use crate::http::state::ChainClient;
|
||||
use crate::shared_state::nyxd_client::ChainClient;
|
||||
use nym_ecash_signer_check::{check_known_dealers, dkg_details_with_client};
|
||||
use std::ops::Deref;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
@@ -12,17 +12,17 @@ use tokio_util::sync::CancellationToken;
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct QuorumState {
|
||||
pub struct QuorumState {
|
||||
available: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl QuorumState {
|
||||
pub(crate) fn available(&self) -> bool {
|
||||
pub fn available(&self) -> bool {
|
||||
self.available.load(Ordering::Acquire)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct QuorumStateChecker {
|
||||
pub struct QuorumStateChecker {
|
||||
client: ChainClient,
|
||||
cancellation_token: CancellationToken,
|
||||
check_interval: Duration,
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright 2025 Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::nym_api_helpers::{CachedEpoch, CachedImmutableEpochItem, CachedImmutableItems};
|
||||
use crate::quorum_checker::QuorumState;
|
||||
use crate::shared_state::required_deposit_cache::RequiredDepositCache;
|
||||
use nym_compact_ecash::VerificationKeyAuth;
|
||||
use nym_credentials::{AggregatedCoinIndicesSignatures, AggregatedExpirationDateSignatures};
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::EcashApiClient;
|
||||
use time::Date;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
pub struct EcashState {
|
||||
pub required_deposit_cache: RequiredDepositCache,
|
||||
|
||||
pub quorum_state: QuorumState,
|
||||
|
||||
pub cached_epoch: RwLock<CachedEpoch>,
|
||||
|
||||
pub master_verification_key: CachedImmutableEpochItem<VerificationKeyAuth>,
|
||||
|
||||
pub threshold_values: CachedImmutableEpochItem<u64>,
|
||||
|
||||
pub epoch_clients: CachedImmutableEpochItem<Vec<EcashApiClient>>,
|
||||
|
||||
pub coin_index_signatures: CachedImmutableEpochItem<AggregatedCoinIndicesSignatures>,
|
||||
|
||||
pub expiration_date_signatures:
|
||||
CachedImmutableItems<(EpochId, Date), AggregatedExpirationDateSignatures>,
|
||||
}
|
||||
|
||||
impl EcashState {
|
||||
pub fn new(
|
||||
required_deposit_cache: RequiredDepositCache,
|
||||
quorum_state: QuorumState,
|
||||
) -> EcashState {
|
||||
EcashState {
|
||||
required_deposit_cache,
|
||||
quorum_state,
|
||||
cached_epoch: Default::default(),
|
||||
master_verification_key: Default::default(),
|
||||
threshold_values: Default::default(),
|
||||
epoch_clients: Default::default(),
|
||||
coin_index_signatures: Default::default(),
|
||||
expiration_date_signatures: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,495 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::deposits_buffer::{BufferedDeposit, DepositsBuffer};
|
||||
use crate::error::CredentialProxyError;
|
||||
use crate::nym_api_helpers::{ensure_sane_expiration_date, query_all_threshold_apis};
|
||||
use crate::shared_state::ecash_state::EcashState;
|
||||
use crate::shared_state::nyxd_client::ChainClient;
|
||||
use crate::storage::CredentialProxyStorage;
|
||||
use nym_compact_ecash::scheme::coin_indices_signatures::{
|
||||
aggregate_annotated_indices_signatures, CoinIndexSignatureShare,
|
||||
};
|
||||
use nym_compact_ecash::scheme::expiration_date_signatures::{
|
||||
aggregate_annotated_expiration_signatures, ExpirationDateSignatureShare,
|
||||
};
|
||||
use nym_compact_ecash::{Base58, PublicKeyUser, VerificationKeyAuth};
|
||||
use nym_credential_proxy_requests::api::v1::ticketbook::models::{
|
||||
AggregatedCoinIndicesSignaturesResponse, AggregatedExpirationDateSignaturesResponse,
|
||||
GlobalDataParams, MasterVerificationKeyResponse,
|
||||
};
|
||||
use nym_credentials::ecash::utils::EcashTime;
|
||||
use nym_credentials::{
|
||||
AggregatedCoinIndicesSignatures, AggregatedExpirationDateSignatures, EpochVerificationKey,
|
||||
};
|
||||
use nym_ecash_contract_common::deposit::DepositId;
|
||||
use nym_validator_client::coconut::EcashApiError;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nyxd::contract_traits::dkg_query_client::Epoch;
|
||||
use nym_validator_client::nyxd::contract_traits::{DkgQueryClient, PagedDkgQueryClient};
|
||||
use nym_validator_client::nyxd::Coin;
|
||||
use nym_validator_client::{DirectSigningHttpRpcNyxdClient, EcashApiClient};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use time::{Date, OffsetDateTime};
|
||||
use tokio::sync::RwLockReadGuard;
|
||||
use tokio::time::Instant;
|
||||
use tracing::{debug, error, info, warn};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub mod ecash_state;
|
||||
pub mod nyxd_client;
|
||||
pub mod required_deposit_cache;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CredentialProxyState {
|
||||
inner: Arc<CredentialProxyStateInner>,
|
||||
}
|
||||
|
||||
impl CredentialProxyState {
|
||||
pub fn new(
|
||||
storage: CredentialProxyStorage,
|
||||
client: ChainClient,
|
||||
deposits_buffer: DepositsBuffer,
|
||||
ecash_state: EcashState,
|
||||
) -> Self {
|
||||
CredentialProxyState {
|
||||
inner: Arc::new(CredentialProxyStateInner {
|
||||
storage,
|
||||
client,
|
||||
deposits_buffer,
|
||||
ecash_state,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn storage(&self) -> &CredentialProxyStorage {
|
||||
&self.inner.storage
|
||||
}
|
||||
|
||||
pub async fn deposit_amount(&self) -> Result<Coin, CredentialProxyError> {
|
||||
self.ecash_state()
|
||||
.required_deposit_cache
|
||||
.get_or_update(self.client())
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn client(&self) -> &ChainClient {
|
||||
&self.inner.client
|
||||
}
|
||||
|
||||
pub fn deposits_buffer(&self) -> &DepositsBuffer {
|
||||
&self.inner.deposits_buffer
|
||||
}
|
||||
|
||||
pub fn ecash_state(&self) -> &EcashState {
|
||||
&self.inner.ecash_state
|
||||
}
|
||||
|
||||
pub(crate) async fn query_chain(&self) -> RwLockReadGuard<'_, DirectSigningHttpRpcNyxdClient> {
|
||||
self.inner.client.query_chain().await
|
||||
}
|
||||
|
||||
pub async fn ensure_credentials_issuable(&self) -> Result<(), CredentialProxyError> {
|
||||
let epoch = self.current_epoch().await?;
|
||||
|
||||
if epoch.state.is_final() {
|
||||
Ok(())
|
||||
} else if let Some(final_timestamp) = epoch.final_timestamp_secs() {
|
||||
// SAFETY: the timestamp values in our DKG contract should be valid timestamps,
|
||||
// otherwise it means the chain is seriously misbehaving
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let finish_dt = OffsetDateTime::from_unix_timestamp(final_timestamp as i64).unwrap();
|
||||
|
||||
Err(CredentialProxyError::CredentialsNotYetIssuable {
|
||||
availability: finish_dt,
|
||||
})
|
||||
} else if epoch.state.is_waiting_initialisation() {
|
||||
Err(CredentialProxyError::UninitialisedDkg)
|
||||
} else {
|
||||
Err(CredentialProxyError::UnknownEcashFailure)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_deposit(
|
||||
&self,
|
||||
request_uuid: Uuid,
|
||||
requested_on: OffsetDateTime,
|
||||
client_pubkey: PublicKeyUser,
|
||||
) -> Result<BufferedDeposit, CredentialProxyError> {
|
||||
let start = Instant::now();
|
||||
let deposit = self
|
||||
.deposits_buffer()
|
||||
.get_valid_deposit(request_uuid, requested_on, client_pubkey)
|
||||
.await;
|
||||
|
||||
let time_taken = start.elapsed();
|
||||
let formatted = humantime::format_duration(time_taken);
|
||||
if time_taken > Duration::from_secs(10) {
|
||||
warn!("attempting to get buffered deposit took {formatted}. perhaps the buffer is too small or the process/chain is overloaded?")
|
||||
} else {
|
||||
debug!("attempting to get buffered deposit took {formatted}")
|
||||
};
|
||||
|
||||
deposit
|
||||
}
|
||||
|
||||
pub async fn insert_deposit_usage_error(&self, deposit_id: DepositId, error: String) {
|
||||
if let Err(err) = self
|
||||
.storage()
|
||||
.insert_deposit_usage_error(deposit_id, error)
|
||||
.await
|
||||
{
|
||||
error!("failed to insert information about deposit (id: {deposit_id}) usage failure: {err}")
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn current_epoch_id(&self) -> Result<EpochId, CredentialProxyError> {
|
||||
let read_guard = self.inner.ecash_state.cached_epoch.read().await;
|
||||
if read_guard.is_valid() {
|
||||
return Ok(read_guard.current_epoch.epoch_id);
|
||||
}
|
||||
|
||||
// update cache
|
||||
drop(read_guard);
|
||||
let mut write_guard = self.inner.ecash_state.cached_epoch.write().await;
|
||||
let epoch = self.query_chain().await.get_current_epoch().await?;
|
||||
|
||||
write_guard.update(epoch);
|
||||
Ok(epoch.epoch_id)
|
||||
}
|
||||
|
||||
pub async fn current_epoch(&self) -> Result<Epoch, CredentialProxyError> {
|
||||
let read_guard = self.ecash_state().cached_epoch.read().await;
|
||||
if read_guard.is_valid() {
|
||||
return Ok(read_guard.current_epoch);
|
||||
}
|
||||
|
||||
// update cache
|
||||
drop(read_guard);
|
||||
let mut write_guard = self.ecash_state().cached_epoch.write().await;
|
||||
let epoch = self.query_chain().await.get_current_epoch().await?;
|
||||
|
||||
write_guard.update(epoch);
|
||||
Ok(epoch)
|
||||
}
|
||||
|
||||
pub async fn global_data(
|
||||
&self,
|
||||
global_data: GlobalDataParams,
|
||||
epoch_id: EpochId,
|
||||
expiration_date: Date,
|
||||
) -> Result<
|
||||
(
|
||||
Option<MasterVerificationKeyResponse>,
|
||||
Option<AggregatedExpirationDateSignaturesResponse>,
|
||||
Option<AggregatedCoinIndicesSignaturesResponse>,
|
||||
),
|
||||
CredentialProxyError,
|
||||
> {
|
||||
let master_verification_key = if global_data.include_master_verification_key {
|
||||
debug!("including master verification key in the response");
|
||||
Some(
|
||||
self.master_verification_key(Some(epoch_id))
|
||||
.await
|
||||
.map(|key| MasterVerificationKeyResponse {
|
||||
epoch_id,
|
||||
bs58_encoded_key: key.to_bs58(),
|
||||
})
|
||||
.inspect_err(|err| warn!("request failure: {err}"))?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let aggregated_expiration_date_signatures =
|
||||
if global_data.include_expiration_date_signatures {
|
||||
debug!("including expiration date signatures in the response");
|
||||
Some(
|
||||
self.master_expiration_date_signatures(epoch_id, expiration_date)
|
||||
.await
|
||||
.map(|signatures| AggregatedExpirationDateSignaturesResponse {
|
||||
signatures: signatures.clone(),
|
||||
})
|
||||
.inspect_err(|err| warn!("request failure: {err}"))?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let aggregated_coin_index_signatures = if global_data.include_coin_index_signatures {
|
||||
debug!("including coin index signatures in the response");
|
||||
Some(
|
||||
self.master_coin_index_signatures(Some(epoch_id))
|
||||
.await
|
||||
.map(|signatures| AggregatedCoinIndicesSignaturesResponse {
|
||||
signatures: signatures.clone(),
|
||||
})
|
||||
.inspect_err(|err| warn!("request failure: {err}"))?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok((
|
||||
master_verification_key,
|
||||
aggregated_expiration_date_signatures,
|
||||
aggregated_coin_index_signatures,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn master_verification_key(
|
||||
&self,
|
||||
epoch_id: Option<EpochId>,
|
||||
) -> Result<RwLockReadGuard<'_, VerificationKeyAuth>, CredentialProxyError> {
|
||||
let epoch_id = match epoch_id {
|
||||
Some(id) => id,
|
||||
None => self.current_epoch_id().await?,
|
||||
};
|
||||
|
||||
self.inner
|
||||
.ecash_state
|
||||
.master_verification_key
|
||||
.get_or_init(epoch_id, || async {
|
||||
// 1. check the storage
|
||||
if let Some(stored) = self
|
||||
.inner
|
||||
.storage
|
||||
.get_master_verification_key(epoch_id)
|
||||
.await?
|
||||
{
|
||||
return Ok(stored.key);
|
||||
}
|
||||
|
||||
info!("attempting to establish master verification key for epoch {epoch_id}...");
|
||||
|
||||
// 2. perform actual aggregation
|
||||
let all_apis = self.ecash_clients(epoch_id).await?;
|
||||
let threshold = self.ecash_threshold(epoch_id).await?;
|
||||
|
||||
if all_apis.len() < threshold as usize {
|
||||
return Err(CredentialProxyError::InsufficientNumberOfSigners {
|
||||
threshold,
|
||||
available: all_apis.len(),
|
||||
});
|
||||
}
|
||||
|
||||
let master_key = nym_credentials::aggregate_verification_keys(&all_apis)?;
|
||||
|
||||
let epoch = EpochVerificationKey {
|
||||
epoch_id,
|
||||
key: master_key,
|
||||
};
|
||||
|
||||
// 3. save the key in the storage for when we reboot
|
||||
self.inner
|
||||
.storage
|
||||
.insert_master_verification_key(&epoch)
|
||||
.await?;
|
||||
|
||||
Ok(epoch.key)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn master_coin_index_signatures(
|
||||
&self,
|
||||
epoch_id: Option<EpochId>,
|
||||
) -> Result<RwLockReadGuard<'_, AggregatedCoinIndicesSignatures>, CredentialProxyError> {
|
||||
let epoch_id = match epoch_id {
|
||||
Some(id) => id,
|
||||
None => self.current_epoch_id().await?,
|
||||
};
|
||||
|
||||
self.inner
|
||||
.ecash_state
|
||||
.coin_index_signatures
|
||||
.get_or_init(epoch_id, || async {
|
||||
// 1. check the storage
|
||||
if let Some(master_sigs) = self
|
||||
.inner
|
||||
.storage
|
||||
.get_master_coin_index_signatures(epoch_id)
|
||||
.await?
|
||||
{
|
||||
return Ok(master_sigs);
|
||||
}
|
||||
|
||||
info!(
|
||||
"attempting to establish master coin index signatures for epoch {epoch_id}..."
|
||||
);
|
||||
|
||||
// 2. go around APIs and attempt to aggregate the data
|
||||
let master_vk = self.master_verification_key(Some(epoch_id)).await?;
|
||||
let all_apis = self.ecash_clients(epoch_id).await?;
|
||||
let threshold = self.ecash_threshold(epoch_id).await?;
|
||||
|
||||
let get_partial_signatures = |api: EcashApiClient| async {
|
||||
// move the api into the closure
|
||||
let api = api;
|
||||
let node_index = api.node_id;
|
||||
let partial_vk = api.verification_key;
|
||||
|
||||
let partial = api
|
||||
.api_client
|
||||
.partial_coin_indices_signatures(Some(epoch_id))
|
||||
.await?
|
||||
.signatures;
|
||||
Ok(CoinIndexSignatureShare {
|
||||
index: node_index,
|
||||
key: partial_vk,
|
||||
signatures: partial,
|
||||
})
|
||||
};
|
||||
|
||||
let shares =
|
||||
query_all_threshold_apis(all_apis.clone(), threshold, get_partial_signatures)
|
||||
.await?;
|
||||
|
||||
let aggregated = aggregate_annotated_indices_signatures(
|
||||
nym_credentials_interface::ecash_parameters(),
|
||||
&master_vk,
|
||||
&shares,
|
||||
)?;
|
||||
|
||||
let sigs = AggregatedCoinIndicesSignatures {
|
||||
epoch_id,
|
||||
signatures: aggregated,
|
||||
};
|
||||
|
||||
// 3. save the signatures in the storage for when we reboot
|
||||
self.inner
|
||||
.storage
|
||||
.insert_master_coin_index_signatures(&sigs)
|
||||
.await?;
|
||||
|
||||
Ok(sigs)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn master_expiration_date_signatures(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
expiration_date: Date,
|
||||
) -> Result<RwLockReadGuard<'_, AggregatedExpirationDateSignatures>, CredentialProxyError> {
|
||||
self.inner.ecash_state
|
||||
.expiration_date_signatures
|
||||
.get_or_init((epoch_id, expiration_date), || async {
|
||||
// 1. sanity check to see if the expiration_date is not nonsense
|
||||
ensure_sane_expiration_date(expiration_date)?;
|
||||
|
||||
// 2. check the storage
|
||||
if let Some(master_sigs) = self
|
||||
.storage()
|
||||
.get_master_expiration_date_signatures(expiration_date, epoch_id)
|
||||
.await?
|
||||
{
|
||||
return Ok(master_sigs);
|
||||
}
|
||||
|
||||
|
||||
info!(
|
||||
"attempting to establish master expiration date signatures for {expiration_date} and epoch {epoch_id}..."
|
||||
);
|
||||
|
||||
// 3. go around APIs and attempt to aggregate the data
|
||||
let epoch_id = self.current_epoch_id().await?;
|
||||
let master_vk = self.master_verification_key(Some(epoch_id)).await?;
|
||||
let all_apis = self.ecash_clients(epoch_id).await?;
|
||||
let threshold = self.ecash_threshold(epoch_id).await?;
|
||||
|
||||
let get_partial_signatures = |api: EcashApiClient| async {
|
||||
// move the api into the closure
|
||||
let api = api;
|
||||
let node_index = api.node_id;
|
||||
let partial_vk = api.verification_key;
|
||||
|
||||
let partial = api
|
||||
.api_client
|
||||
.partial_expiration_date_signatures(Some(expiration_date), Some(epoch_id))
|
||||
.await?
|
||||
.signatures;
|
||||
Ok(ExpirationDateSignatureShare {
|
||||
index: node_index,
|
||||
key: partial_vk,
|
||||
signatures: partial,
|
||||
})
|
||||
};
|
||||
|
||||
let shares =
|
||||
query_all_threshold_apis(all_apis.clone(), threshold, get_partial_signatures)
|
||||
.await?;
|
||||
|
||||
let aggregated = aggregate_annotated_expiration_signatures(
|
||||
&master_vk,
|
||||
expiration_date.ecash_unix_timestamp(),
|
||||
&shares,
|
||||
)?;
|
||||
|
||||
let sigs = AggregatedExpirationDateSignatures {
|
||||
epoch_id,
|
||||
expiration_date,
|
||||
signatures: aggregated,
|
||||
};
|
||||
|
||||
// 4. save the signatures in the storage for when we reboot
|
||||
self.inner.storage
|
||||
.insert_master_expiration_date_signatures(&sigs)
|
||||
.await?;
|
||||
|
||||
Ok(sigs)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn ecash_clients(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<RwLockReadGuard<'_, Vec<EcashApiClient>>, CredentialProxyError> {
|
||||
self.inner
|
||||
.ecash_state
|
||||
.epoch_clients
|
||||
.get_or_init(epoch_id, || async {
|
||||
Ok(self
|
||||
.query_chain()
|
||||
.await
|
||||
.get_all_verification_key_shares(epoch_id)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(TryInto::try_into)
|
||||
.collect::<anyhow::Result<Vec<_>, EcashApiError>>()?)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn ecash_threshold(&self, epoch_id: EpochId) -> Result<u64, CredentialProxyError> {
|
||||
self.inner
|
||||
.ecash_state
|
||||
.threshold_values
|
||||
.get_or_init(epoch_id, || async {
|
||||
if let Some(threshold) = self
|
||||
.query_chain()
|
||||
.await
|
||||
.get_epoch_threshold(epoch_id)
|
||||
.await?
|
||||
{
|
||||
Ok(threshold)
|
||||
} else {
|
||||
Err(CredentialProxyError::UnavailableThreshold { epoch_id })
|
||||
}
|
||||
})
|
||||
.await
|
||||
.map(|t| *t)
|
||||
}
|
||||
}
|
||||
|
||||
struct CredentialProxyStateInner {
|
||||
storage: CredentialProxyStorage,
|
||||
|
||||
client: ChainClient,
|
||||
|
||||
deposits_buffer: DepositsBuffer,
|
||||
|
||||
ecash_state: EcashState,
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::CredentialProxyError;
|
||||
use crate::helpers::LockTimer;
|
||||
use nym_ecash_contract_common::msg::ExecuteMsg;
|
||||
use nym_validator_client::nyxd::contract_traits::NymContractsProvider;
|
||||
use nym_validator_client::nyxd::cosmwasm_client::types::ExecuteResult;
|
||||
use nym_validator_client::nyxd::{Coin, CosmWasmClient, NyxdClient};
|
||||
use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient};
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||
use tracing::{instrument, warn};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ChainClient(Arc<RwLock<DirectSigningHttpRpcNyxdClient>>);
|
||||
|
||||
impl ChainClient {
|
||||
pub fn new(mnemonic: bip39::Mnemonic) -> Result<Self, CredentialProxyError> {
|
||||
let network_details = nym_network_defaults::NymNetworkDetails::new_from_env();
|
||||
let client_config = nyxd::Config::try_from_nym_network_details(&network_details)?;
|
||||
|
||||
let nyxd_url = network_details
|
||||
.endpoints
|
||||
.first()
|
||||
.ok_or_else(|| CredentialProxyError::NoNyxEndpointsAvailable)?
|
||||
.nyxd_url
|
||||
.as_str();
|
||||
|
||||
let client = NyxdClient::connect_with_mnemonic(client_config, nyxd_url, mnemonic)?;
|
||||
|
||||
if client.ecash_contract_address().is_none() {
|
||||
return Err(CredentialProxyError::UnavailableEcashContract);
|
||||
}
|
||||
|
||||
if client.dkg_contract_address().is_none() {
|
||||
return Err(CredentialProxyError::UnavailableDKGContract);
|
||||
}
|
||||
|
||||
Ok(ChainClient(Arc::new(RwLock::new(client))))
|
||||
}
|
||||
|
||||
pub async fn query_chain(&self) -> ChainReadPermit<'_> {
|
||||
let _acquire_timer = LockTimer::new("acquire chain query permit");
|
||||
self.0.read().await
|
||||
}
|
||||
|
||||
pub async fn start_chain_tx(&self) -> ChainWritePermit<'_> {
|
||||
let _acquire_timer = LockTimer::new("acquire exclusive chain write permit");
|
||||
|
||||
ChainWritePermit {
|
||||
lock_timer: LockTimer::new("exclusive chain access permit"),
|
||||
inner: self.0.write().await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type ChainReadPermit<'a> = RwLockReadGuard<'a, DirectSigningHttpRpcNyxdClient>;
|
||||
|
||||
// explicitly wrap the WriteGuard for extra information regarding time taken
|
||||
pub struct ChainWritePermit<'a> {
|
||||
// it's not really dead, we only care about it being dropped
|
||||
#[allow(dead_code)]
|
||||
lock_timer: LockTimer,
|
||||
inner: RwLockWriteGuard<'a, DirectSigningHttpRpcNyxdClient>,
|
||||
}
|
||||
|
||||
impl ChainWritePermit<'_> {
|
||||
#[instrument(skip(self, short_sha, info), err(Display))]
|
||||
pub async fn make_deposits(
|
||||
self,
|
||||
short_sha: &'static str,
|
||||
info: Vec<(String, Coin)>,
|
||||
) -> Result<ExecuteResult, CredentialProxyError> {
|
||||
let address = self.inner.address();
|
||||
let starting_sequence = self.inner.get_sequence(&address).await?.sequence;
|
||||
|
||||
let deposits = info.len();
|
||||
|
||||
let ecash_contract = self
|
||||
.inner
|
||||
.ecash_contract_address()
|
||||
.ok_or(CredentialProxyError::UnavailableEcashContract)?;
|
||||
let deposit_messages = info
|
||||
.into_iter()
|
||||
.map(|(identity_key, amount)| {
|
||||
(
|
||||
ExecuteMsg::DepositTicketBookFunds { identity_key },
|
||||
vec![amount],
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let res = self
|
||||
.inner
|
||||
.execute_multiple(
|
||||
ecash_contract,
|
||||
deposit_messages,
|
||||
None,
|
||||
format!("cp-{short_sha}: performing {deposits} deposits"),
|
||||
)
|
||||
.await?;
|
||||
|
||||
loop {
|
||||
let updated_sequence = self.inner.get_sequence(&address).await?.sequence;
|
||||
|
||||
if updated_sequence > starting_sequence {
|
||||
break;
|
||||
}
|
||||
warn!("wrong sequence number... waiting before releasing chain lock");
|
||||
tokio::time::sleep(Duration::from_millis(50)).await;
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for ChainWritePermit<'_> {
|
||||
type Target = DirectSigningHttpRpcNyxdClient;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.inner.deref()
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -2,14 +2,14 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::error::CredentialProxyError;
|
||||
use crate::http::state::ChainClient;
|
||||
use crate::shared_state::nyxd_client::ChainClient;
|
||||
use nym_validator_client::nyxd::contract_traits::EcashQueryClient;
|
||||
use nym_validator_client::nyxd::Coin;
|
||||
use std::sync::Arc;
|
||||
use time::OffsetDateTime;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
pub(crate) struct CachedDeposit {
|
||||
pub struct CachedDeposit {
|
||||
valid_until: OffsetDateTime,
|
||||
required_amount: Coin,
|
||||
}
|
||||
@@ -40,12 +40,12 @@ impl Default for CachedDeposit {
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub(crate) struct RequiredDepositCache {
|
||||
pub struct RequiredDepositCache {
|
||||
inner: Arc<RwLock<CachedDeposit>>,
|
||||
}
|
||||
|
||||
impl RequiredDepositCache {
|
||||
pub(crate) async fn get_or_update(
|
||||
pub async fn get_or_update(
|
||||
&self,
|
||||
chain_client: &ChainClient,
|
||||
) -> Result<Coin, CredentialProxyError> {
|
||||
+27
-26
@@ -1,7 +1,6 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::credentials::ticketbook::NodeId;
|
||||
use crate::deposits_buffer::helpers::{BufferedDeposit, PerformedDeposits};
|
||||
use crate::error::CredentialProxyError;
|
||||
use crate::storage::manager::SqliteStorageManager;
|
||||
@@ -26,6 +25,10 @@ use uuid::Uuid;
|
||||
|
||||
mod manager;
|
||||
pub mod models;
|
||||
pub(crate) mod pruner;
|
||||
|
||||
// TODO: proper import
|
||||
type NodeId = u64;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CredentialProxyStorage {
|
||||
@@ -74,7 +77,7 @@ impl CredentialProxyStorage {
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) async fn load_blinded_shares_status_by_shares_id(
|
||||
pub async fn load_blinded_shares_status_by_shares_id(
|
||||
&self,
|
||||
id: i64,
|
||||
) -> Result<Option<BlindedShares>, CredentialProxyError> {
|
||||
@@ -84,7 +87,7 @@ impl CredentialProxyStorage {
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub(crate) async fn load_wallet_shares_by_shares_id(
|
||||
pub async fn load_wallet_shares_by_shares_id(
|
||||
&self,
|
||||
id: i64,
|
||||
) -> Result<Vec<MinimalWalletShare>, CredentialProxyError> {
|
||||
@@ -94,7 +97,7 @@ impl CredentialProxyStorage {
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub(crate) async fn load_shares_error_by_shares_id(
|
||||
pub async fn load_shares_error_by_shares_id(
|
||||
&self,
|
||||
id: i64,
|
||||
) -> Result<Option<String>, CredentialProxyError> {
|
||||
@@ -105,7 +108,7 @@ impl CredentialProxyStorage {
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) async fn load_blinded_shares_status_by_device_and_credential_id(
|
||||
pub async fn load_blinded_shares_status_by_device_and_credential_id(
|
||||
&self,
|
||||
device_id: &str,
|
||||
credential_id: &str,
|
||||
@@ -116,7 +119,7 @@ impl CredentialProxyStorage {
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub(crate) async fn load_wallet_shares_by_device_and_credential_id(
|
||||
pub async fn load_wallet_shares_by_device_and_credential_id(
|
||||
&self,
|
||||
device_id: &str,
|
||||
credential_id: &str,
|
||||
@@ -127,7 +130,7 @@ impl CredentialProxyStorage {
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub(crate) async fn load_shares_error_by_device_and_credential_id(
|
||||
pub async fn load_shares_error_by_device_and_credential_id(
|
||||
&self,
|
||||
device_id: &str,
|
||||
credential_id: &str,
|
||||
@@ -138,7 +141,7 @@ impl CredentialProxyStorage {
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_new_pending_async_shares_request(
|
||||
pub async fn insert_new_pending_async_shares_request(
|
||||
&self,
|
||||
request: Uuid,
|
||||
device_id: &str,
|
||||
@@ -150,7 +153,7 @@ impl CredentialProxyStorage {
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub(crate) async fn update_pending_async_blinded_shares_issued(
|
||||
pub async fn update_pending_async_blinded_shares_issued(
|
||||
&self,
|
||||
available_shares: usize,
|
||||
device_id: &str,
|
||||
@@ -166,7 +169,7 @@ impl CredentialProxyStorage {
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub(crate) async fn update_pending_async_blinded_shares_error(
|
||||
pub async fn update_pending_async_blinded_shares_error(
|
||||
&self,
|
||||
available_shares: usize,
|
||||
device_id: &str,
|
||||
@@ -184,7 +187,7 @@ impl CredentialProxyStorage {
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub(crate) async fn prune_old_blinded_shares(&self) -> Result<(), CredentialProxyError> {
|
||||
pub async fn prune_old_blinded_shares(&self) -> Result<(), CredentialProxyError> {
|
||||
let max_age = OffsetDateTime::now_utc() - time::Duration::days(31);
|
||||
|
||||
self.storage_manager
|
||||
@@ -199,7 +202,7 @@ impl CredentialProxyStorage {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_new_deposits(
|
||||
pub async fn insert_new_deposits(
|
||||
&self,
|
||||
deposits: &PerformedDeposits,
|
||||
) -> Result<(), CredentialProxyError> {
|
||||
@@ -211,9 +214,7 @@ impl CredentialProxyStorage {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn load_unused_deposits(
|
||||
&self,
|
||||
) -> Result<Vec<BufferedDeposit>, CredentialProxyError> {
|
||||
pub async fn load_unused_deposits(&self) -> Result<Vec<BufferedDeposit>, CredentialProxyError> {
|
||||
self.storage_manager
|
||||
.load_unused_deposits()
|
||||
.await?
|
||||
@@ -222,7 +223,7 @@ impl CredentialProxyStorage {
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_deposit_usage(
|
||||
pub async fn insert_deposit_usage(
|
||||
&self,
|
||||
deposit_id: DepositId,
|
||||
requested_on: OffsetDateTime,
|
||||
@@ -240,7 +241,7 @@ impl CredentialProxyStorage {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_deposit_usage_error(
|
||||
pub async fn insert_deposit_usage_error(
|
||||
&self,
|
||||
deposit_id: DepositId,
|
||||
error: String,
|
||||
@@ -251,7 +252,7 @@ impl CredentialProxyStorage {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_partial_wallet_share(
|
||||
pub async fn insert_partial_wallet_share(
|
||||
&self,
|
||||
deposit_id: DepositId,
|
||||
epoch_id: EpochId,
|
||||
@@ -291,7 +292,7 @@ impl CredentialProxyStorage {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn get_master_verification_key(
|
||||
pub async fn get_master_verification_key(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<Option<EpochVerificationKey>, CredentialProxyError> {
|
||||
@@ -309,7 +310,7 @@ impl CredentialProxyStorage {
|
||||
Ok(Some(deserialised))
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_master_verification_key(
|
||||
pub async fn insert_master_verification_key(
|
||||
&self,
|
||||
key: &EpochVerificationKey,
|
||||
) -> Result<(), CredentialProxyError> {
|
||||
@@ -320,7 +321,7 @@ impl CredentialProxyStorage {
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_master_coin_index_signatures(
|
||||
pub async fn get_master_coin_index_signatures(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<Option<AggregatedCoinIndicesSignatures>, CredentialProxyError> {
|
||||
@@ -340,7 +341,7 @@ impl CredentialProxyStorage {
|
||||
Ok(Some(deserialised))
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_master_coin_index_signatures(
|
||||
pub async fn insert_master_coin_index_signatures(
|
||||
&self,
|
||||
signatures: &AggregatedCoinIndicesSignatures,
|
||||
) -> Result<(), CredentialProxyError> {
|
||||
@@ -355,7 +356,7 @@ impl CredentialProxyStorage {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn get_master_expiration_date_signatures(
|
||||
pub async fn get_master_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: EpochId,
|
||||
@@ -376,7 +377,7 @@ impl CredentialProxyStorage {
|
||||
Ok(Some(deserialised))
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_master_expiration_date_signatures(
|
||||
pub async fn insert_master_expiration_date_signatures(
|
||||
&self,
|
||||
signatures: &AggregatedExpirationDateSignatures,
|
||||
) -> Result<(), CredentialProxyError> {
|
||||
@@ -398,7 +399,7 @@ impl CredentialProxyStorage {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::http::helpers;
|
||||
use crate::helpers::random_uuid;
|
||||
use crate::storage::models::BlindedSharesStatus;
|
||||
use nym_compact_ecash::scheme::keygen::KeyPairUser;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
@@ -480,7 +481,7 @@ mod tests {
|
||||
async fn test_add() -> anyhow::Result<()> {
|
||||
let storage = get_storage().await?;
|
||||
|
||||
let dummy_uuid = helpers::random_uuid();
|
||||
let dummy_uuid = random_uuid();
|
||||
println!("🚀 insert_pending_blinded_share...");
|
||||
|
||||
storage.insert_dummy_used_deposit(dummy_uuid).await?;
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
// Copyright 2024 Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::storage::CredentialProxyStorage;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
@@ -0,0 +1,163 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::deposits_buffer::DepositsBuffer;
|
||||
use crate::error::CredentialProxyError;
|
||||
use crate::quorum_checker::QuorumStateChecker;
|
||||
use crate::shared_state::ecash_state::EcashState;
|
||||
use crate::shared_state::nyxd_client::ChainClient;
|
||||
use crate::shared_state::required_deposit_cache::RequiredDepositCache;
|
||||
use crate::shared_state::CredentialProxyState;
|
||||
use crate::storage::pruner::StoragePruner;
|
||||
use crate::storage::CredentialProxyStorage;
|
||||
use crate::webhook::ZkNymWebhook;
|
||||
use nym_credentials::ecash::utils::ecash_today;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use std::future::Future;
|
||||
use std::time::Duration;
|
||||
use time::Date;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tokio_util::task::TaskTracker;
|
||||
|
||||
mod shares_handlers;
|
||||
pub mod ticketbook_handlers;
|
||||
pub mod wallet_shares;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct ShutdownTracker {
|
||||
pub shutdown_token: CancellationToken,
|
||||
pub tracker: TaskTracker,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TicketbookManager {
|
||||
pub(crate) state: CredentialProxyState,
|
||||
pub(crate) webhook: ZkNymWebhook,
|
||||
pub(crate) shutdown_tracker: ShutdownTracker,
|
||||
}
|
||||
|
||||
impl TicketbookManager {
|
||||
pub async fn new(
|
||||
build_sha: &'static str,
|
||||
quorum_check_interval: Duration,
|
||||
deposits_buffer_size: usize,
|
||||
max_concurrent_deposits: usize,
|
||||
storage: CredentialProxyStorage,
|
||||
mnemonic: bip39::Mnemonic,
|
||||
webhook: ZkNymWebhook,
|
||||
) -> Result<Self, CredentialProxyError> {
|
||||
let chain_client = ChainClient::new(mnemonic)?;
|
||||
let shutdown_tracker = ShutdownTracker::default();
|
||||
|
||||
let quorum_state_checker = QuorumStateChecker::new(
|
||||
chain_client.clone(),
|
||||
quorum_check_interval,
|
||||
shutdown_tracker.shutdown_token.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let required_deposit_cache = RequiredDepositCache::default();
|
||||
|
||||
let deposits_buffer = DepositsBuffer::new(
|
||||
storage.clone(),
|
||||
chain_client.clone(),
|
||||
required_deposit_cache.clone(),
|
||||
build_sha,
|
||||
deposits_buffer_size,
|
||||
max_concurrent_deposits,
|
||||
shutdown_tracker.shutdown_token.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let storage_pruner =
|
||||
StoragePruner::new(shutdown_tracker.shutdown_token.clone(), storage.clone());
|
||||
|
||||
let this = TicketbookManager {
|
||||
state: CredentialProxyState::new(
|
||||
storage.clone(),
|
||||
chain_client,
|
||||
deposits_buffer,
|
||||
EcashState::new(
|
||||
required_deposit_cache,
|
||||
quorum_state_checker.quorum_state_ref(),
|
||||
),
|
||||
),
|
||||
webhook,
|
||||
shutdown_tracker,
|
||||
};
|
||||
|
||||
// since this is startup,
|
||||
// might as well do all the needed network queries to establish needed global signatures
|
||||
// if we don't already have them
|
||||
this.build_initial_cache().await?;
|
||||
|
||||
// spawn the background tasks
|
||||
this.try_spawn_in_background(quorum_state_checker.run_forever());
|
||||
this.try_spawn_in_background(storage_pruner.run_forever());
|
||||
|
||||
Ok(this)
|
||||
}
|
||||
|
||||
async fn build_initial_cache(&self) -> Result<(), CredentialProxyError> {
|
||||
let today = ecash_today().date();
|
||||
|
||||
let epoch_id = self.state.current_epoch_id().await?;
|
||||
let _ = self.state.deposit_amount().await?;
|
||||
let _ = self.state.master_verification_key(Some(epoch_id)).await?;
|
||||
let _ = self.state.ecash_threshold(epoch_id).await?;
|
||||
let _ = self.state.ecash_clients(epoch_id).await?;
|
||||
let _ = self
|
||||
.state
|
||||
.master_coin_index_signatures(Some(epoch_id))
|
||||
.await?;
|
||||
let _ = self
|
||||
.state
|
||||
.master_expiration_date_signatures(epoch_id, today)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn cancel_and_wait(&self) {
|
||||
self.shutdown_tracker.shutdown_token.cancel();
|
||||
self.state.deposits_buffer().wait_for_shutdown().await;
|
||||
self.shutdown_tracker.tracker.wait().await
|
||||
}
|
||||
|
||||
pub fn shutdown_token(&self) -> CancellationToken {
|
||||
self.shutdown_tracker.shutdown_token.clone()
|
||||
}
|
||||
|
||||
/// Ensure the required global data for the specified epoch and expiration date exists in our cache (and storage)
|
||||
async fn ensure_global_data_cached(
|
||||
&self,
|
||||
epoch: EpochId,
|
||||
expiration_date: Date,
|
||||
) -> Result<(), CredentialProxyError> {
|
||||
let _ = self.state.master_verification_key(Some(epoch)).await?;
|
||||
let _ = self.state.master_coin_index_signatures(Some(epoch)).await?;
|
||||
let _ = self
|
||||
.state
|
||||
.master_expiration_date_signatures(epoch, expiration_date)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn try_spawn_in_background<F>(&self, task: F) -> Option<JoinHandle<F::Output>>
|
||||
where
|
||||
F: Future + Send + 'static,
|
||||
F::Output: Send + 'static,
|
||||
{
|
||||
// don't spawn new task if we've received cancellation token
|
||||
if self.shutdown_tracker.shutdown_token.is_cancelled() {
|
||||
None
|
||||
} else {
|
||||
self.shutdown_tracker.tracker.reopen();
|
||||
// TODO: later use a task queue since most requests will be blocked waiting on chain permit anyway
|
||||
let join_handle = self.shutdown_tracker.tracker.spawn(task);
|
||||
self.shutdown_tracker.tracker.close();
|
||||
Some(join_handle)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::CredentialProxyError;
|
||||
use crate::storage::models::MinimalWalletShare;
|
||||
use crate::ticketbook_manager::TicketbookManager;
|
||||
use nym_credential_proxy_requests::api::v1::ticketbook::models::{
|
||||
GlobalDataParams, TicketbookWalletSharesResponse,
|
||||
};
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use tracing::{debug, span, Instrument, Level};
|
||||
use uuid::Uuid;
|
||||
|
||||
impl TicketbookManager {
|
||||
async fn shares_to_response(
|
||||
&self,
|
||||
shares: Vec<MinimalWalletShare>,
|
||||
params: GlobalDataParams,
|
||||
) -> Result<TicketbookWalletSharesResponse, CredentialProxyError> {
|
||||
// in all calls we ensured the shares are non-empty
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let first = shares.first().unwrap();
|
||||
let expiration_date = first.expiration_date;
|
||||
let epoch_id = first.epoch_id as EpochId;
|
||||
|
||||
let threshold = self.state.ecash_threshold(epoch_id).await?;
|
||||
if shares.len() < threshold as usize {
|
||||
return Err(CredentialProxyError::InsufficientNumberOfCredentials {
|
||||
available: shares.len(),
|
||||
threshold,
|
||||
});
|
||||
}
|
||||
|
||||
// grab any requested additional data
|
||||
let (
|
||||
master_verification_key,
|
||||
aggregated_expiration_date_signatures,
|
||||
aggregated_coin_index_signatures,
|
||||
) = self
|
||||
.state
|
||||
.global_data(params, epoch_id, expiration_date)
|
||||
.await?;
|
||||
|
||||
// finally produce a response
|
||||
Ok(TicketbookWalletSharesResponse {
|
||||
epoch_id,
|
||||
shares: shares.into_iter().map(Into::into).collect(),
|
||||
master_verification_key,
|
||||
aggregated_coin_index_signatures,
|
||||
aggregated_expiration_date_signatures,
|
||||
})
|
||||
}
|
||||
|
||||
/// Query by id for blinded shares of a bandwidth voucher
|
||||
pub async fn query_for_shares_by_id(
|
||||
&self,
|
||||
uuid: Uuid,
|
||||
params: GlobalDataParams,
|
||||
share_id: i64,
|
||||
) -> Result<TicketbookWalletSharesResponse, CredentialProxyError> {
|
||||
let span = span!(Level::INFO, "query shares by id", uuid = %uuid, share_id = %share_id);
|
||||
async move {
|
||||
debug!("");
|
||||
|
||||
// TODO: edge case: this will **NOT** work if shares got created in epoch X,
|
||||
// but this query happened in epoch X+1
|
||||
let shares = self
|
||||
.state
|
||||
.storage()
|
||||
.load_wallet_shares_by_shares_id(share_id)
|
||||
.await?;
|
||||
if shares.is_empty() {
|
||||
debug!("shares not found");
|
||||
|
||||
// check for explicit error
|
||||
if let Some(error_message) = self
|
||||
.state
|
||||
.storage()
|
||||
.load_shares_error_by_shares_id(share_id)
|
||||
.await?
|
||||
{
|
||||
return Err(CredentialProxyError::ShareByIdLoadError {
|
||||
message: error_message,
|
||||
id: share_id,
|
||||
});
|
||||
}
|
||||
|
||||
return Err(CredentialProxyError::SharesByIdNotFound { id: share_id });
|
||||
}
|
||||
|
||||
self.shares_to_response(shares, params).await
|
||||
}
|
||||
.instrument(span)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Query by id for blinded wallet shares of a ticketbook
|
||||
pub async fn query_for_shares_by_device_id_and_credential_id(
|
||||
&self,
|
||||
uuid: Uuid,
|
||||
params: GlobalDataParams,
|
||||
device_id: String,
|
||||
credential_id: String,
|
||||
) -> Result<TicketbookWalletSharesResponse, CredentialProxyError> {
|
||||
let span = span!(Level::INFO, "query shares by device and credential ids", uuid = %uuid, device_id = %device_id, credential_id = %credential_id);
|
||||
async move {
|
||||
debug!("");
|
||||
|
||||
// TODO: edge case: this will **NOT** work if shares got created in epoch X,
|
||||
// but this query happened in epoch X+1
|
||||
let shares = self
|
||||
.state
|
||||
.storage()
|
||||
.load_wallet_shares_by_device_and_credential_id(&device_id, &credential_id)
|
||||
.await?;
|
||||
|
||||
if shares.is_empty() {
|
||||
debug!("shares not found");
|
||||
|
||||
// check for explicit error
|
||||
if let Some(error_message) = self
|
||||
.state
|
||||
.storage()
|
||||
.load_shares_error_by_device_and_credential_id(&device_id, &credential_id)
|
||||
.await?
|
||||
{
|
||||
return Err(CredentialProxyError::ShareByDeviceLoadError {
|
||||
message: error_message,
|
||||
device_id,
|
||||
credential_id,
|
||||
});
|
||||
}
|
||||
|
||||
return Err(CredentialProxyError::SharesByDeviceNotFound {
|
||||
device_id,
|
||||
credential_id,
|
||||
});
|
||||
}
|
||||
|
||||
self.shares_to_response(shares, params).await
|
||||
}
|
||||
.instrument(span)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::CredentialProxyError;
|
||||
use crate::nym_api_helpers::ensure_sane_expiration_date;
|
||||
use crate::ticketbook_manager::TicketbookManager;
|
||||
use nym_compact_ecash::Base58;
|
||||
use nym_credential_proxy_requests::api::v1::ticketbook::models::{
|
||||
CurrentEpochResponse, DepositResponse, GlobalDataParams, MasterVerificationKeyResponse,
|
||||
PartialVerificationKey, PartialVerificationKeysResponse, TicketbookAsyncRequest,
|
||||
TicketbookObtainParams, TicketbookRequest, TicketbookWalletSharesAsyncResponse,
|
||||
TicketbookWalletSharesResponse,
|
||||
};
|
||||
use time::OffsetDateTime;
|
||||
use tracing::{error, info, span, warn, Instrument, Level};
|
||||
use uuid::Uuid;
|
||||
|
||||
impl TicketbookManager {
|
||||
pub async fn obtain_ticketbook_shares(
|
||||
&self,
|
||||
uuid: Uuid,
|
||||
request: TicketbookRequest,
|
||||
params: GlobalDataParams,
|
||||
) -> Result<TicketbookWalletSharesResponse, CredentialProxyError> {
|
||||
let requested_on = OffsetDateTime::now_utc();
|
||||
let span = span!(Level::INFO, "obtain ticketboook", uuid = %uuid);
|
||||
|
||||
async move {
|
||||
info!("");
|
||||
|
||||
self.state.ensure_credentials_issuable().await?;
|
||||
let epoch_id = self.state.current_epoch_id().await?;
|
||||
ensure_sane_expiration_date(request.expiration_date)?;
|
||||
|
||||
// if additional data was requested, grab them first in case there are any cache/network issues
|
||||
let (
|
||||
master_verification_key,
|
||||
aggregated_expiration_date_signatures,
|
||||
aggregated_coin_index_signatures,
|
||||
) = self
|
||||
.state
|
||||
.global_data(params, epoch_id, request.expiration_date)
|
||||
.await?;
|
||||
|
||||
let shares = self
|
||||
.try_obtain_wallet_shares(uuid, requested_on, request)
|
||||
.await
|
||||
.inspect_err(|err| warn!("shares request failure: {err}"))?;
|
||||
|
||||
info!("request was successful!");
|
||||
Ok(TicketbookWalletSharesResponse {
|
||||
epoch_id,
|
||||
shares,
|
||||
master_verification_key,
|
||||
aggregated_coin_index_signatures,
|
||||
aggregated_expiration_date_signatures,
|
||||
})
|
||||
}
|
||||
.instrument(span)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn obtain_ticketbook_shares_async(
|
||||
&self,
|
||||
uuid: Uuid,
|
||||
request: TicketbookAsyncRequest,
|
||||
params: TicketbookObtainParams,
|
||||
) -> Result<TicketbookWalletSharesAsyncResponse, CredentialProxyError> {
|
||||
let requested_on = OffsetDateTime::now_utc();
|
||||
let span = span!(Level::INFO, "[async] obtain ticketboook", uuid = %uuid);
|
||||
async move {
|
||||
info!("");
|
||||
|
||||
// 1. perform basic validation
|
||||
self.state.ensure_credentials_issuable().await?;
|
||||
|
||||
ensure_sane_expiration_date(request.inner.expiration_date)?;
|
||||
|
||||
// 2. store the request to retrieve the id
|
||||
let pending = self
|
||||
.state
|
||||
.storage()
|
||||
.insert_new_pending_async_shares_request(
|
||||
uuid,
|
||||
&request.device_id,
|
||||
&request.credential_id,
|
||||
)
|
||||
.await
|
||||
.inspect_err(|err| error!("failed to insert new pending async shares: {err}"))?;
|
||||
|
||||
let id = pending.id;
|
||||
|
||||
// 3. try to spawn a new task attempting to resolve the request
|
||||
let this = self.clone();
|
||||
if self
|
||||
.try_spawn_in_background(async move {
|
||||
this.try_obtain_blinded_ticketbook_async(
|
||||
uuid,
|
||||
requested_on,
|
||||
request,
|
||||
params,
|
||||
pending,
|
||||
)
|
||||
.await
|
||||
})
|
||||
.is_none()
|
||||
{
|
||||
warn!("could not start async ticketbook issuance due to shutdown in progress");
|
||||
return Err(CredentialProxyError::ShutdownInProgress);
|
||||
}
|
||||
|
||||
// 4. in the meantime, return the id to the user
|
||||
Ok(TicketbookWalletSharesAsyncResponse { id, uuid })
|
||||
}
|
||||
.instrument(span)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn current_deposit(&self) -> Result<DepositResponse, CredentialProxyError> {
|
||||
let current_deposit = self.state.deposit_amount().await?;
|
||||
Ok(DepositResponse {
|
||||
current_deposit_amount: current_deposit.amount,
|
||||
current_deposit_denom: current_deposit.denom,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn partial_verification_keys(
|
||||
&self,
|
||||
) -> Result<PartialVerificationKeysResponse, CredentialProxyError> {
|
||||
self.state.ensure_credentials_issuable().await?;
|
||||
|
||||
let epoch_id = self.state.current_epoch_id().await?;
|
||||
let signers = self.state.ecash_clients(epoch_id).await?;
|
||||
Ok(PartialVerificationKeysResponse {
|
||||
epoch_id,
|
||||
keys: signers
|
||||
.iter()
|
||||
.map(|signer| PartialVerificationKey {
|
||||
node_index: signer.node_id,
|
||||
bs58_encoded_key: signer.verification_key.to_bs58(),
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn master_verification_key(
|
||||
&self,
|
||||
) -> Result<MasterVerificationKeyResponse, CredentialProxyError> {
|
||||
self.state.ensure_credentials_issuable().await?;
|
||||
|
||||
let epoch_id = self.state.current_epoch_id().await?;
|
||||
let key = self.state.master_verification_key(Some(epoch_id)).await?;
|
||||
Ok(MasterVerificationKeyResponse {
|
||||
epoch_id,
|
||||
bs58_encoded_key: key.to_bs58(),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn current_epoch(&self) -> Result<CurrentEpochResponse, CredentialProxyError> {
|
||||
self.state.ensure_credentials_issuable().await?;
|
||||
let epoch_id = self.state.current_epoch_id().await?;
|
||||
Ok(CurrentEpochResponse { epoch_id })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,343 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::CredentialProxyError;
|
||||
use crate::storage::models::BlindedShares;
|
||||
use crate::ticketbook_manager::TicketbookManager;
|
||||
use futures::{stream, StreamExt};
|
||||
use nym_compact_ecash::Base58;
|
||||
use nym_credential_proxy_requests::api::v1::ticketbook::models::{
|
||||
TicketbookAsyncRequest, TicketbookObtainParams, TicketbookRequest,
|
||||
TicketbookWalletSharesResponse, WalletShare, WebhookTicketbookWalletShares,
|
||||
WebhookTicketbookWalletSharesRequest,
|
||||
};
|
||||
use nym_validator_client::ecash::BlindSignRequestBody;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::time::timeout;
|
||||
use tracing::{debug, error, info, instrument};
|
||||
use uuid::Uuid;
|
||||
|
||||
impl TicketbookManager {
|
||||
#[instrument(
|
||||
skip(self, request_data, request, requested_on),
|
||||
fields(
|
||||
expiration_date = %request_data.expiration_date,
|
||||
ticketbook_type = %request_data.ticketbook_type
|
||||
)
|
||||
)]
|
||||
pub async fn try_obtain_wallet_shares(
|
||||
&self,
|
||||
request: Uuid,
|
||||
requested_on: OffsetDateTime,
|
||||
request_data: TicketbookRequest,
|
||||
) -> Result<Vec<WalletShare>, CredentialProxyError> {
|
||||
// don't proceed if we don't have quorum available as the request will definitely fail
|
||||
if !self.state.ecash_state().quorum_state.available() {
|
||||
return Err(CredentialProxyError::UnavailableSigningQuorum);
|
||||
}
|
||||
|
||||
let epoch = self.state.current_epoch_id().await?;
|
||||
let threshold = self.state.ecash_threshold(epoch).await?;
|
||||
let expiration_date = request_data.expiration_date;
|
||||
|
||||
// before we commit to making the deposit, ensure we have required signatures cached and stored
|
||||
self.ensure_global_data_cached(epoch, expiration_date)
|
||||
.await?;
|
||||
let ecash_api_clients = self.state.ecash_clients(epoch).await?.clone();
|
||||
|
||||
let deposit_data = self
|
||||
.state
|
||||
.get_deposit(request, requested_on, request_data.ecash_pubkey)
|
||||
.await?;
|
||||
let deposit_id = deposit_data.deposit_id;
|
||||
let signature = deposit_data.sign_ticketbook_plaintext(&request_data.withdrawal_request);
|
||||
|
||||
let credential_request = BlindSignRequestBody::new(
|
||||
request_data.withdrawal_request.into(),
|
||||
deposit_id,
|
||||
signature,
|
||||
request_data.ecash_pubkey,
|
||||
request_data.expiration_date,
|
||||
request_data.ticketbook_type,
|
||||
);
|
||||
|
||||
let wallet_shares = Arc::new(Mutex::new(HashMap::new()));
|
||||
|
||||
info!("attempting to contract all nym-apis for the partial wallets...");
|
||||
stream::iter(ecash_api_clients)
|
||||
.for_each_concurrent(None, |client| async {
|
||||
// move the client into the block
|
||||
let client = client;
|
||||
|
||||
debug!("contacting {client} for blinded partial wallet");
|
||||
let res = timeout(
|
||||
Duration::from_secs(5),
|
||||
client.api_client.blind_sign(&credential_request),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| CredentialProxyError::EcashApiRequestTimeout {
|
||||
client_repr: client.to_string(),
|
||||
})
|
||||
.and_then(|res| res.map_err(Into::into));
|
||||
|
||||
// 1. try to store it
|
||||
if let Err(err) = self
|
||||
.state
|
||||
.storage()
|
||||
.insert_partial_wallet_share(
|
||||
deposit_id,
|
||||
epoch,
|
||||
expiration_date,
|
||||
client.node_id,
|
||||
&res,
|
||||
)
|
||||
.await
|
||||
{
|
||||
error!("failed to persist issued partial share: {err}")
|
||||
}
|
||||
|
||||
// 2. add it to the map
|
||||
match res {
|
||||
Ok(share) => {
|
||||
wallet_shares
|
||||
.lock()
|
||||
.await
|
||||
.insert(client.node_id, share.blinded_signature);
|
||||
}
|
||||
Err(err) => {
|
||||
error!("failed to obtain partial blinded wallet share from {client}: {err}")
|
||||
}
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
// SAFETY: the futures have completed, so we MUST have the only arc reference
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let wallet_shares = Arc::into_inner(wallet_shares).unwrap().into_inner();
|
||||
let shares = wallet_shares.len();
|
||||
|
||||
if shares < threshold as usize {
|
||||
let err = CredentialProxyError::InsufficientNumberOfCredentials {
|
||||
available: shares,
|
||||
threshold,
|
||||
};
|
||||
self.state
|
||||
.insert_deposit_usage_error(deposit_id, err.to_string())
|
||||
.await;
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
Ok(wallet_shares
|
||||
.into_iter()
|
||||
.map(|(node_index, share)| WalletShare {
|
||||
node_index,
|
||||
bs58_encoded_share: share.to_bs58(),
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub async fn try_obtain_wallet_shares_async(
|
||||
&self,
|
||||
request: Uuid,
|
||||
requested_on: OffsetDateTime,
|
||||
request_data: TicketbookRequest,
|
||||
device_id: &str,
|
||||
credential_id: &str,
|
||||
) -> Result<Vec<WalletShare>, CredentialProxyError> {
|
||||
let shares = match self
|
||||
.try_obtain_wallet_shares(request, requested_on, request_data)
|
||||
.await
|
||||
{
|
||||
Ok(shares) => shares,
|
||||
Err(err) => {
|
||||
let obtained = match err {
|
||||
CredentialProxyError::InsufficientNumberOfCredentials { available, .. } => {
|
||||
available
|
||||
}
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
// currently there's no retry mechanisms, but, who knows, that might change
|
||||
if let Err(err) = self
|
||||
.state
|
||||
.storage()
|
||||
.update_pending_async_blinded_shares_error(
|
||||
obtained,
|
||||
device_id,
|
||||
credential_id,
|
||||
&err.to_string(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
error!("failed to update database with the error information: {err}")
|
||||
}
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
|
||||
Ok(shares)
|
||||
}
|
||||
|
||||
async fn try_obtain_blinded_ticketbook_async_inner(
|
||||
&self,
|
||||
request: Uuid,
|
||||
requested_on: OffsetDateTime,
|
||||
request_data: TicketbookAsyncRequest,
|
||||
params: TicketbookObtainParams,
|
||||
pending: &BlindedShares,
|
||||
) -> Result<(), CredentialProxyError> {
|
||||
let epoch_id = self.state.current_epoch_id().await?;
|
||||
|
||||
let device_id = &request_data.device_id;
|
||||
let credential_id = &request_data.credential_id;
|
||||
let secret = request_data.secret.clone();
|
||||
|
||||
// 1. try to obtain global data
|
||||
let (
|
||||
master_verification_key,
|
||||
aggregated_expiration_date_signatures,
|
||||
aggregated_coin_index_signatures,
|
||||
) = self
|
||||
.state
|
||||
.global_data(params.global, epoch_id, request_data.inner.expiration_date)
|
||||
.await?;
|
||||
|
||||
// 2. try to obtain shares (failures are written to the DB)
|
||||
let shares = self
|
||||
.try_obtain_wallet_shares_async(
|
||||
request,
|
||||
requested_on,
|
||||
request_data.inner,
|
||||
device_id,
|
||||
credential_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 3. update the storage, if possible
|
||||
// (as long as we can trigger webhook, we should still be good)
|
||||
if let Err(err) = self
|
||||
.state
|
||||
.storage()
|
||||
.update_pending_async_blinded_shares_issued(shares.len(), device_id, credential_id)
|
||||
.await
|
||||
{
|
||||
error!(uuid = %request, "failed to update db with issued information: {err}")
|
||||
}
|
||||
|
||||
// 4. build the webhook request body
|
||||
let data = Some(TicketbookWalletSharesResponse {
|
||||
epoch_id,
|
||||
shares,
|
||||
master_verification_key,
|
||||
aggregated_coin_index_signatures,
|
||||
aggregated_expiration_date_signatures,
|
||||
});
|
||||
|
||||
let ticketbook_wallet_shares = WebhookTicketbookWalletShares {
|
||||
id: pending.id,
|
||||
status: pending.status.to_string(),
|
||||
device_id: device_id.clone(),
|
||||
credential_id: credential_id.clone(),
|
||||
data,
|
||||
error_message: None,
|
||||
created: pending.created,
|
||||
updated: pending.updated,
|
||||
};
|
||||
|
||||
let webhook_request = WebhookTicketbookWalletSharesRequest {
|
||||
ticketbook_wallet_shares,
|
||||
secret,
|
||||
};
|
||||
|
||||
// 5. call the webhook
|
||||
self.webhook.try_trigger(request, &webhook_request).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn try_trigger_webhook_request_for_error(
|
||||
&self,
|
||||
request: Uuid,
|
||||
request_data: TicketbookAsyncRequest,
|
||||
pending: &BlindedShares,
|
||||
error_message: String,
|
||||
) -> Result<(), CredentialProxyError> {
|
||||
let device_id = &request_data.device_id;
|
||||
let credential_id = &request_data.credential_id;
|
||||
let secret = request_data.secret.clone();
|
||||
|
||||
let ticketbook_wallet_shares = WebhookTicketbookWalletShares {
|
||||
id: pending.id,
|
||||
status: "error".to_string(),
|
||||
device_id: device_id.clone(),
|
||||
credential_id: credential_id.clone(),
|
||||
data: None,
|
||||
error_message: Some(error_message),
|
||||
created: pending.created,
|
||||
updated: pending.updated,
|
||||
};
|
||||
|
||||
let webhook_request = WebhookTicketbookWalletSharesRequest {
|
||||
ticketbook_wallet_shares,
|
||||
secret,
|
||||
};
|
||||
|
||||
self.webhook.try_trigger(request, &webhook_request).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
skip_all,
|
||||
fields(
|
||||
credential_id = %request_data.credential_id,
|
||||
device_id = %request_data.device_id)
|
||||
)
|
||||
]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) async fn try_obtain_blinded_ticketbook_async(
|
||||
&self,
|
||||
request: Uuid,
|
||||
requested_on: OffsetDateTime,
|
||||
request_data: TicketbookAsyncRequest,
|
||||
params: TicketbookObtainParams,
|
||||
pending: BlindedShares,
|
||||
) {
|
||||
let skip_webhook = params.skip_webhook;
|
||||
if let Err(err) = self
|
||||
.try_obtain_blinded_ticketbook_async_inner(
|
||||
request,
|
||||
requested_on,
|
||||
request_data.clone(),
|
||||
params,
|
||||
&pending,
|
||||
)
|
||||
.await
|
||||
{
|
||||
if skip_webhook {
|
||||
info!(uuid = %request,"the webhook is not going to be called for this request");
|
||||
return;
|
||||
}
|
||||
|
||||
// post to the webhook to notify of errors on this side
|
||||
if let Err(webhook_err) = self
|
||||
.try_trigger_webhook_request_for_error(
|
||||
request,
|
||||
request_data,
|
||||
&pending,
|
||||
format!("Failed to get ticketbook: {err}"),
|
||||
)
|
||||
.await
|
||||
{
|
||||
error!(uuid = %request, "failed to make webhook request to report error: {webhook_err}")
|
||||
}
|
||||
error!(uuid = %request, "failed to resolve the blinded ticketbook issuance: {err}")
|
||||
} else {
|
||||
info!(uuid = %request, "managed to resolve the blinded ticketbook issuance")
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-31
@@ -1,57 +1,34 @@
|
||||
// Copyright 2024 Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::CredentialProxyError;
|
||||
use clap::Args;
|
||||
use reqwest::header::AUTHORIZATION;
|
||||
use serde::Serialize;
|
||||
use tracing::{debug, error, instrument, span, Instrument, Level};
|
||||
use url::Url;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
pub struct ZkNymWebHookConfig {
|
||||
#[clap(long, env = "WEBHOOK_ZK_NYMS_URL")]
|
||||
pub webhook_url: Url,
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ZkNymWebhook {
|
||||
pub webhook_client_url: Url,
|
||||
|
||||
#[clap(long, env = "WEBHOOK_ZK_NYMS_CLIENT_ID")]
|
||||
pub webhook_client_id: String,
|
||||
|
||||
#[clap(long, env = "WEBHOOK_ZK_NYMS_CLIENT_SECRET")]
|
||||
pub webhook_client_secret: String,
|
||||
}
|
||||
|
||||
impl ZkNymWebHookConfig {
|
||||
pub fn ensure_valid_client_url(&self) -> Result<(), CredentialProxyError> {
|
||||
self.client_url()
|
||||
.map_err(|_| CredentialProxyError::InvalidWebhookUrl)
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
fn client_url(&self) -> Result<Url, url::ParseError> {
|
||||
self.webhook_url.join(&self.webhook_client_id)
|
||||
}
|
||||
|
||||
fn unchecked_client_url(&self) -> Url {
|
||||
// we ensured we have valid url on startup
|
||||
#[allow(clippy::unwrap_used)]
|
||||
self.client_url().unwrap()
|
||||
}
|
||||
|
||||
impl ZkNymWebhook {
|
||||
fn bearer_token(&self) -> String {
|
||||
format!("Bearer {}", self.webhook_client_secret)
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn try_trigger<T: Serialize + ?Sized>(&self, original_uuid: Uuid, payload: &T) {
|
||||
let url = self.unchecked_client_url();
|
||||
let url = self.webhook_client_url.clone();
|
||||
let span = span!(Level::DEBUG, "webhook", uuid = %original_uuid, url = %url);
|
||||
|
||||
async move {
|
||||
debug!("🕸️ about to trigger the webhook");
|
||||
|
||||
match reqwest::Client::new()
|
||||
.post(url.clone())
|
||||
.post(url)
|
||||
.header(AUTHORIZATION, self.bearer_token())
|
||||
.json(payload)
|
||||
.send()
|
||||
@@ -11,6 +11,7 @@ repository = { workspace = true }
|
||||
aes-gcm-siv = { workspace = true, optional = true }
|
||||
aes = { workspace = true, optional = true }
|
||||
aead = { workspace = true, optional = true }
|
||||
base64.workspace = true
|
||||
bs58 = { workspace = true }
|
||||
blake3 = { workspace = true, features = ["traits-preview"], optional = true }
|
||||
ctr = { workspace = true, optional = true }
|
||||
@@ -18,6 +19,7 @@ digest = { workspace = true, optional = true }
|
||||
generic-array = { workspace = true, optional = true }
|
||||
hkdf = { workspace = true, optional = true }
|
||||
hmac = { workspace = true, optional = true }
|
||||
jwt-simple = { workspace = true, optional = true }
|
||||
cipher = { workspace = true, optional = true }
|
||||
x25519-dalek = { workspace = true, features = ["static_secrets"], optional = true }
|
||||
ed25519-dalek = { workspace = true, features = ["rand_core"], optional = true }
|
||||
@@ -39,6 +41,7 @@ rand_chacha = { workspace = true }
|
||||
[features]
|
||||
default = []
|
||||
aead = ["dep:aead", "aead/std", "aes-gcm-siv", "generic-array"]
|
||||
naive_jwt = ["asymmetric", "jwt-simple"]
|
||||
serde = ["dep:serde", "serde_bytes", "ed25519-dalek/serde", "x25519-dalek/serde"]
|
||||
asymmetric = ["x25519-dalek", "ed25519-dalek", "zeroize"]
|
||||
hashing = ["blake3", "digest", "hkdf", "hmac", "generic-array", "sha2"]
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub use ed25519_dalek::SignatureError;
|
||||
use ed25519_dalek::{SecretKey, Signer, SigningKey};
|
||||
pub use ed25519_dalek::{Verifier, PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, SIGNATURE_LENGTH};
|
||||
|
||||
use ed25519_dalek::Signer;
|
||||
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
|
||||
use std::fmt::{self, Debug, Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
@@ -13,6 +14,9 @@ use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
#[cfg(feature = "serde")]
|
||||
pub mod serde_helpers;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
pub use serde_helpers::*;
|
||||
|
||||
#[cfg(feature = "sphinx")]
|
||||
use nym_sphinx_types::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH};
|
||||
|
||||
@@ -81,8 +85,8 @@ impl KeyPair {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_secret(secret: SecretKey, index: u32) -> Self {
|
||||
let ed25519_signing_key = SigningKey::from(secret);
|
||||
pub fn from_secret(secret: ed25519_dalek::SecretKey, index: u32) -> Self {
|
||||
let ed25519_signing_key = ed25519_dalek::SigningKey::from(secret);
|
||||
|
||||
KeyPair {
|
||||
private_key: PrivateKey(ed25519_signing_key.to_bytes()),
|
||||
@@ -276,7 +280,7 @@ impl Display for PrivateKey {
|
||||
|
||||
impl<'a> From<&'a PrivateKey> for PublicKey {
|
||||
fn from(pk: &'a PrivateKey) -> Self {
|
||||
PublicKey(SigningKey::from_bytes(&pk.0).verifying_key())
|
||||
PublicKey(ed25519_dalek::SigningKey::from_bytes(&pk.0).verifying_key())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,7 +324,7 @@ impl PrivateKey {
|
||||
}
|
||||
|
||||
pub fn sign<M: AsRef<[u8]>>(&self, message: M) -> Signature {
|
||||
let signing_key: SigningKey = self.0.into();
|
||||
let signing_key: ed25519_dalek::SigningKey = self.0.into();
|
||||
let sig = signing_key.sign(message.as_ref());
|
||||
Signature(sig)
|
||||
}
|
||||
@@ -425,9 +429,57 @@ impl<'d> Deserialize<'d> for Signature {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "naive_jwt")]
|
||||
impl PublicKey {
|
||||
pub fn to_jwt_compatible_key(&self) -> jwt_simple::algorithms::Ed25519PublicKey {
|
||||
(*self).into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "naive_jwt")]
|
||||
impl From<PublicKey> for jwt_simple::algorithms::Ed25519PublicKey {
|
||||
fn from(value: PublicKey) -> Self {
|
||||
// SAFETY: we have a valid ed25519 pubkey, we're just changing to a different library wrapper
|
||||
#[allow(clippy::unwrap_used)]
|
||||
jwt_simple::algorithms::Ed25519PublicKey::from_bytes(&value.to_bytes()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "naive_jwt")]
|
||||
impl PrivateKey {
|
||||
pub fn to_jwt_compatible_keys(&self) -> jwt_simple::algorithms::Ed25519KeyPair {
|
||||
let pub_key = self.public_key();
|
||||
let mut bytes = zeroize::Zeroizing::new([0u8; 64]);
|
||||
|
||||
bytes[..SECRET_KEY_LENGTH]
|
||||
.copy_from_slice(zeroize::Zeroizing::new(self.to_bytes()).as_ref());
|
||||
bytes[SECRET_KEY_LENGTH..].copy_from_slice(&pub_key.to_bytes());
|
||||
|
||||
// SAFETY: we have a valid ed25519 keys, we're just changing to a different library wrapper
|
||||
#[allow(clippy::unwrap_used)]
|
||||
jwt_simple::algorithms::Ed25519KeyPair::from_bytes(bytes.as_ref()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "naive_jwt")]
|
||||
impl KeyPair {
|
||||
pub fn to_jwt_compatible_keys(&self) -> jwt_simple::algorithms::Ed25519KeyPair {
|
||||
let mut bytes = zeroize::Zeroizing::new([0u8; 64]);
|
||||
|
||||
bytes[..SECRET_KEY_LENGTH]
|
||||
.copy_from_slice(zeroize::Zeroizing::new(self.private_key.to_bytes()).as_ref());
|
||||
bytes[SECRET_KEY_LENGTH..].copy_from_slice(&self.public_key.to_bytes());
|
||||
|
||||
// SAFETY: we have a valid ed25519 keys, we're just changing to a different library wrapper
|
||||
#[allow(clippy::unwrap_used)]
|
||||
jwt_simple::algorithms::Ed25519KeyPair::from_bytes(bytes.as_ref()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rand::thread_rng;
|
||||
|
||||
fn assert_zeroize_on_drop<T: ZeroizeOnDrop>() {}
|
||||
|
||||
@@ -438,4 +490,29 @@ mod tests {
|
||||
assert_zeroize::<PrivateKey>();
|
||||
assert_zeroize_on_drop::<PrivateKey>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(feature = "naive_jwt", feature = "rand"))]
|
||||
fn check_jwt_key_compat_conversion() {
|
||||
let mut rng = thread_rng();
|
||||
let keys = KeyPair::new(&mut rng);
|
||||
let jwt_keys = keys.to_jwt_compatible_keys();
|
||||
|
||||
// internally they're represented by hidden `Edwards25519KeyPair` (plus key_id)
|
||||
// which has way nicer API for assertions
|
||||
let jwt_keys_inner =
|
||||
jwt_simple::algorithms::Edwards25519KeyPair::from_bytes(&jwt_keys.to_bytes()).unwrap();
|
||||
|
||||
let compact_ed25519 = jwt_keys_inner.as_ref();
|
||||
assert!(compact_ed25519
|
||||
.sk
|
||||
.validate_public_key(&compact_ed25519.pk)
|
||||
.is_ok());
|
||||
|
||||
let dummy_message = "hello world";
|
||||
let sig1 = keys.private_key.sign(dummy_message).to_bytes();
|
||||
let sig2 = compact_ed25519.sk.sign(dummy_message, None).to_vec();
|
||||
|
||||
assert_eq!(sig1.to_vec(), sig2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use base64::Engine;
|
||||
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
|
||||
use std::fmt::{self, Debug, Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
@@ -158,6 +159,15 @@ impl PublicKey {
|
||||
.map_err(|source| KeyRecoveryError::MalformedPublicKeyString { source })?;
|
||||
Self::from_bytes(&bytes)
|
||||
}
|
||||
|
||||
pub fn from_base64(s: &str) -> Option<Self> {
|
||||
let bytes = base64::engine::general_purpose::STANDARD.decode(s).ok()?;
|
||||
Self::from_bytes(&bytes).ok()
|
||||
}
|
||||
|
||||
pub fn to_base64(&self) -> String {
|
||||
base64::engine::general_purpose::STANDARD.encode(self.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PublicKey {
|
||||
|
||||
@@ -157,6 +157,14 @@ impl<LS, TS, LC, TC> SignerResult<LS, TS, LC, TC> {
|
||||
pub fn malformed_details(&self) -> bool {
|
||||
self.information.parse().is_err()
|
||||
}
|
||||
|
||||
pub fn try_get_test_result(&self) -> Option<&SignerTestResult<LS, TS, LC, TC>> {
|
||||
if let SignerStatus::Tested { result } = &self.status {
|
||||
Some(result)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<LS, TS, LC, TC> SignerResult<LS, TS, LC, TC>
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
[package]
|
||||
name = "nym-execute"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = { workspace = true, features = ["full"] }
|
||||
quote = { workspace = true }
|
||||
@@ -1,110 +0,0 @@
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
parse_macro_input, Block, ExprMethodCall, FnArg, Ident, ItemFn, LitStr, ReturnType, Token,
|
||||
VisPublic, Visibility,
|
||||
};
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn execute(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let f = parse_macro_input!(item as ItemFn);
|
||||
let target = parse_macro_input!(attr as LitStr).value();
|
||||
|
||||
let cl = if target == "mixnet" {
|
||||
quote! {self.mixnet_contract_address()}
|
||||
} else if target == "vesting" {
|
||||
quote! {self.vesting_contract_address()}
|
||||
} else {
|
||||
panic!("Only `mixnet` and `vesting` targets are supported!")
|
||||
};
|
||||
let cl = proc_macro::TokenStream::from(cl);
|
||||
let cl = parse_macro_input!(cl as ExprMethodCall);
|
||||
|
||||
let orig_f = f.clone();
|
||||
let mut execute_f = f.clone();
|
||||
let mut simulate_f = f.clone();
|
||||
let name = f.sig.ident;
|
||||
let name_str = name.to_string();
|
||||
let call_args = f.sig.inputs.into_iter().filter_map(|arg| match arg {
|
||||
FnArg::Receiver(_) => None,
|
||||
FnArg::Typed(arg) => Some(arg.pat),
|
||||
});
|
||||
let execute_args = call_args.clone();
|
||||
let simulate_args = call_args;
|
||||
|
||||
execute_f.sig.asyncness = Some(Token));
|
||||
simulate_f.sig.asyncness = Some(Token));
|
||||
|
||||
execute_f.vis = Visibility::Public(VisPublic {
|
||||
pub_token: Token),
|
||||
});
|
||||
simulate_f.vis = Visibility::Public(VisPublic {
|
||||
pub_token: Token),
|
||||
});
|
||||
|
||||
execute_f.sig.ident = Ident::new(
|
||||
&format!("execute{}", execute_f.sig.ident),
|
||||
execute_f.sig.ident.span(),
|
||||
);
|
||||
|
||||
simulate_f.sig.ident = Ident::new(
|
||||
&format!("simulate{}", simulate_f.sig.ident),
|
||||
simulate_f.sig.ident.span(),
|
||||
);
|
||||
|
||||
let execute_output = quote! {
|
||||
-> Result<ExecuteResult, NyxdError>
|
||||
};
|
||||
let o_ts = proc_macro::TokenStream::from(execute_output);
|
||||
execute_f.sig.output = parse_macro_input!(o_ts as ReturnType);
|
||||
|
||||
let simulate_output = quote! {
|
||||
-> Result<SimulateResponse, NyxdError>
|
||||
};
|
||||
let o_ts = proc_macro::TokenStream::from(simulate_output);
|
||||
simulate_f.sig.output = parse_macro_input!(o_ts as ReturnType);
|
||||
|
||||
let simulate_block = quote! {
|
||||
{
|
||||
let (msg, _fee) = self.#name(#(#simulate_args),*);
|
||||
let msg = self.wrap_contract_execute_message(
|
||||
#cl,
|
||||
&msg,
|
||||
vec![],
|
||||
)?;
|
||||
|
||||
self.simulate(vec![msg]).await
|
||||
}
|
||||
};
|
||||
|
||||
let ts = proc_macro::TokenStream::from(simulate_block);
|
||||
simulate_f.block = Box::new(parse_macro_input!(ts as Block));
|
||||
|
||||
let execute_block = quote! {
|
||||
{
|
||||
let (req, fee) = self.#name(#(#execute_args),*);
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
|
||||
self.client
|
||||
.execute(
|
||||
self.address(),
|
||||
#cl,
|
||||
&req,
|
||||
fee,
|
||||
#name_str,
|
||||
vec![],
|
||||
)
|
||||
.await
|
||||
}
|
||||
};
|
||||
|
||||
let ts = proc_macro::TokenStream::from(execute_block);
|
||||
execute_f.block = Box::new(parse_macro_input!(ts as Block));
|
||||
|
||||
let out = quote! {
|
||||
#orig_f
|
||||
#execute_f
|
||||
#simulate_f
|
||||
};
|
||||
|
||||
out.into()
|
||||
}
|
||||
@@ -16,3 +16,6 @@ thiserror = { workspace = true }
|
||||
[dev-dependencies]
|
||||
rand = { workspace = true }
|
||||
nym-crypto = { path = "../../crypto", features = ["rand"] }
|
||||
bincode = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
@@ -7,7 +7,7 @@
|
||||
use crate::nodes::{NodeIdentity, NODE_IDENTITY_SIZE};
|
||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
use nym_sphinx_types::Destination;
|
||||
use serde::de::{Error as SerdeError, Unexpected, Visitor};
|
||||
use serde::de::{Error as SerdeError, SeqAccess, Unexpected, Visitor};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::fmt::{self, Formatter};
|
||||
use std::str::FromStr;
|
||||
@@ -64,7 +64,7 @@ impl<'de> Deserialize<'de> for Recipient {
|
||||
{
|
||||
struct RecipientVisitor;
|
||||
|
||||
impl Visitor<'_> for RecipientVisitor {
|
||||
impl<'de> Visitor<'de> for RecipientVisitor {
|
||||
type Value = Recipient;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
|
||||
@@ -90,6 +90,42 @@ impl<'de> Deserialize<'de> for Recipient {
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: SeqAccess<'de>,
|
||||
{
|
||||
// if we know the size hint, check if it matches expectation,
|
||||
// otherwise return an error
|
||||
if let Some(size_hint) = seq.size_hint() {
|
||||
if size_hint != Recipient::LEN {
|
||||
return Err(SerdeError::invalid_length(size_hint, &self));
|
||||
}
|
||||
}
|
||||
|
||||
let mut recipient_bytes = [0u8; Recipient::LEN];
|
||||
|
||||
// clippy's suggestion is completely wrong and it iterates wrong sequence
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
for i in 0..Recipient::LEN {
|
||||
let Some(elem) = seq.next_element::<u8>()? else {
|
||||
return Err(SerdeError::invalid_length(i + 1, &self));
|
||||
};
|
||||
recipient_bytes[i] = elem;
|
||||
}
|
||||
|
||||
// make sure there are no trailing bytes
|
||||
if seq.next_element::<u8>()?.is_some() {
|
||||
return Err(SerdeError::invalid_length(Recipient::LEN + 1, &self));
|
||||
}
|
||||
|
||||
Recipient::try_from_bytes(recipient_bytes).map_err(|_| {
|
||||
SerdeError::invalid_value(
|
||||
Unexpected::Other("At least one of the curve points was malformed"),
|
||||
&self,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_bytes(RecipientVisitor)
|
||||
@@ -245,6 +281,18 @@ impl FromStr for Recipient {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn mock_recipient() -> Recipient {
|
||||
Recipient::try_from_bytes([
|
||||
67, 5, 132, 146, 3, 236, 116, 89, 254, 57, 131, 159, 69, 181, 55, 208, 12, 108, 136,
|
||||
83, 58, 76, 171, 195, 31, 98, 92, 64, 68, 53, 156, 184, 100, 189, 73, 3, 238, 103, 156,
|
||||
108, 124, 199, 42, 79, 172, 98, 81, 177, 182, 100, 167, 164, 74, 183, 199, 213, 162,
|
||||
173, 102, 112, 30, 159, 148, 66, 44, 75, 230, 182, 138, 114, 170, 163, 209, 82, 204,
|
||||
100, 118, 91, 57, 150, 212, 147, 151, 135, 148, 16, 213, 223, 182, 164, 242, 37, 40,
|
||||
73, 137, 228,
|
||||
])
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_conversion_works() {
|
||||
let mut rng = rand::thread_rng();
|
||||
@@ -308,4 +356,40 @@ mod tests {
|
||||
recovered_recipient.gateway.to_bytes()
|
||||
);
|
||||
}
|
||||
|
||||
// calls `visit_bytes`
|
||||
#[test]
|
||||
fn bincode_serialisation_works() {
|
||||
let recipient = mock_recipient();
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
|
||||
struct MyStruct {
|
||||
recipient: Recipient,
|
||||
}
|
||||
let a = MyStruct { recipient };
|
||||
let s = bincode::serialize(&a).unwrap();
|
||||
|
||||
let b = bincode::deserialize(&s).unwrap();
|
||||
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
|
||||
// calls `visit_seq`
|
||||
#[test]
|
||||
fn json_serialisation_works() {
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
let recipient = mock_recipient();
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
|
||||
struct MyStruct {
|
||||
recipient: Recipient,
|
||||
}
|
||||
let a = MyStruct { recipient };
|
||||
let s = serde_json::to_string(&a).unwrap();
|
||||
|
||||
let b = serde_json::from_str(&s).unwrap();
|
||||
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,6 +180,7 @@ impl NymPacket {
|
||||
}
|
||||
|
||||
#[cfg(feature = "sphinx")]
|
||||
#[allow(unreachable_patterns)]
|
||||
pub fn sphinx_packet_ref(&self) -> Option<&SphinxPacket> {
|
||||
match self {
|
||||
NymPacket::Sphinx(packet) => Some(packet),
|
||||
@@ -188,6 +189,7 @@ impl NymPacket {
|
||||
}
|
||||
|
||||
#[cfg(feature = "sphinx")]
|
||||
#[allow(unreachable_patterns)]
|
||||
pub fn to_sphinx_packet(self) -> Option<SphinxPacket> {
|
||||
match self {
|
||||
NymPacket::Sphinx(packet) => Some(packet),
|
||||
|
||||
@@ -110,6 +110,7 @@ impl ShutdownToken {
|
||||
// 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]
|
||||
#[deprecated(note = "use .clone_with_suffix instead")]
|
||||
pub fn fork<S: Into<String>>(&self, child_suffix: S) -> Self {
|
||||
self.clone_with_suffix(child_suffix)
|
||||
}
|
||||
@@ -117,6 +118,7 @@ impl ShutdownToken {
|
||||
// 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]
|
||||
#[deprecated(note = "use .clone().named(name) instead")]
|
||||
pub fn fork_named<S: Into<String>>(&self, name: S) -> Self {
|
||||
self.clone().named(name)
|
||||
}
|
||||
@@ -232,6 +234,16 @@ impl ShutdownManager {
|
||||
manager.with_shutdown(async move { cancel_watcher.cancelled().await })
|
||||
}
|
||||
|
||||
pub fn empty_mock() -> Self {
|
||||
ShutdownManager {
|
||||
root_token: ShutdownToken::ephemeral(),
|
||||
legacy_task_manager: None,
|
||||
shutdown_signals: Default::default(),
|
||||
tracker: Default::default(),
|
||||
max_shutdown_duration: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_legacy_task_manager(mut self) -> Self {
|
||||
let mut legacy_manager =
|
||||
TaskManager::default().named(format!("{}-legacy", self.root_token.name()));
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "nym-upgrade-mode-check"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
readme.workspace = true
|
||||
|
||||
[dependencies]
|
||||
jwt-simple = { workspace = true }
|
||||
reqwest = { workspace = true, features = ["rustls-tls"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
time = { workspace = true, features = ["serde"] }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
nym-http-api-client = { path = "../http-api-client", default-features = false }
|
||||
nym-crypto = { path = "../crypto", features = ["asymmetric", "serde", "naive_jwt"] }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
time = { workspace = true, features = ["macros"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,123 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::UpgradeModeCheckError;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_http_api_client::generate_user_agent;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
|
||||
pub struct UpgradeModeAttestation {
|
||||
#[serde(flatten)]
|
||||
pub content: UpgradeModeAttestationContent,
|
||||
|
||||
#[serde(with = "ed25519::bs58_ed25519_signature")]
|
||||
pub signature: ed25519::Signature,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
|
||||
#[serde(tag = "type")]
|
||||
#[serde(rename = "upgrade_mode")]
|
||||
pub struct UpgradeModeAttestationContent {
|
||||
#[serde(with = "time::serde::timestamp")]
|
||||
pub starting_time: OffsetDateTime,
|
||||
|
||||
#[serde(with = "ed25519::bs58_ed25519_pubkey")]
|
||||
pub attester_public_key: ed25519::PublicKey,
|
||||
}
|
||||
|
||||
impl UpgradeModeAttestation {
|
||||
pub fn verify(&self) -> bool {
|
||||
self.content
|
||||
.attester_public_key
|
||||
.verify(self.content.as_json(), &self.signature)
|
||||
.is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeAttestationContent {
|
||||
pub fn as_json(&self) -> String {
|
||||
// SAFETY: Serialize impl is valid and we have no non-string map keys
|
||||
#[allow(clippy::unwrap_used)]
|
||||
serde_json::to_string(&self).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_new_attestation(key: &ed25519::PrivateKey) -> UpgradeModeAttestation {
|
||||
generate_new_attestation_with_starting_time(key, OffsetDateTime::now_utc())
|
||||
}
|
||||
|
||||
pub fn generate_new_attestation_with_starting_time(
|
||||
key: &ed25519::PrivateKey,
|
||||
starting_time: OffsetDateTime,
|
||||
) -> UpgradeModeAttestation {
|
||||
let content = UpgradeModeAttestationContent {
|
||||
starting_time,
|
||||
attester_public_key: key.into(),
|
||||
};
|
||||
UpgradeModeAttestation {
|
||||
signature: key.sign(content.as_json()),
|
||||
content,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn attempt_retrieve(
|
||||
url: &str,
|
||||
) -> Result<Option<UpgradeModeAttestation>, UpgradeModeCheckError> {
|
||||
let retrieval_failure = |source| UpgradeModeCheckError::AttestationRetrievalFailure {
|
||||
url: url.to_string(),
|
||||
source,
|
||||
};
|
||||
|
||||
let attestation = reqwest::ClientBuilder::new()
|
||||
.user_agent(generate_user_agent!())
|
||||
.timeout(Duration::from_secs(5))
|
||||
.build()
|
||||
.map_err(retrieval_failure)?
|
||||
.get(url)
|
||||
.send()
|
||||
.await
|
||||
.map_err(retrieval_failure)?
|
||||
.json::<Option<UpgradeModeAttestation>>()
|
||||
.await
|
||||
.map_err(retrieval_failure)?;
|
||||
|
||||
Ok(attestation)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn upgrade_mode_attestation_serde_json() -> anyhow::Result<()> {
|
||||
// unix timestamp: 1629720000
|
||||
let starting_time = time::macros::datetime!(2021-08-23 12:00 UTC);
|
||||
|
||||
let key = ed25519::PrivateKey::from_bytes(&[
|
||||
108, 49, 193, 21, 126, 161, 249, 85, 242, 207, 74, 195, 238, 6, 64, 149, 201, 140, 248,
|
||||
163, 122, 170, 79, 198, 87, 85, 36, 29, 243, 92, 64, 161,
|
||||
])?;
|
||||
|
||||
let attestation = generate_new_attestation_with_starting_time(&key, starting_time);
|
||||
|
||||
let attestation_json = serde_json::to_string(&attestation)?;
|
||||
let attestation_content_json = attestation.content.as_json();
|
||||
|
||||
let expected_attestation = r#"{"type":"upgrade_mode","starting_time":1629720000,"attester_public_key":"3pkFcBXCEmbmXBT2G8CkFMuKisJcH54mbBGvncHaDibt","signature":"5rWUr2ypaDTtrMKegMP3tQkkZGFAuhNTnEVCVe5Azv6QqvLzoGdQiMkFmeyhDd1XSfoXpL9fFM58rsdA1kf4GYMM"}"#;
|
||||
let expected_content = r#"{"type":"upgrade_mode","starting_time":1629720000,"attester_public_key":"3pkFcBXCEmbmXBT2G8CkFMuKisJcH54mbBGvncHaDibt"}"#;
|
||||
|
||||
assert_eq!(attestation_content_json, expected_content);
|
||||
assert_eq!(attestation_json, expected_attestation);
|
||||
|
||||
let recovered_attestation = serde_json::from_str(&attestation_json)?;
|
||||
assert_eq!(attestation, recovered_attestation);
|
||||
|
||||
let recovered_content = serde_json::from_str(&attestation_content_json)?;
|
||||
assert_eq!(attestation.content, recovered_content);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_crypto::asymmetric::ed25519::Ed25519RecoveryError;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum UpgradeModeCheckError {
|
||||
#[error("failed to decode jwt metadata")]
|
||||
TokenMetadataDecodeFailure { source: jwt_simple::Error },
|
||||
|
||||
#[error("the jwt metadata didn't contain explicit public key")]
|
||||
MissingTokenPublicKey,
|
||||
|
||||
#[error("the attached public key was not valid ed25519 public key")]
|
||||
MalformedEd25519PublicKey { source: Ed25519RecoveryError },
|
||||
|
||||
#[error("failed to verify the jwt: {source}")]
|
||||
JwtVerificationFailure { source: jwt_simple::Error },
|
||||
|
||||
#[error("failed to retrieve attestation from {url}:{source}")]
|
||||
AttestationRetrievalFailure { url: String, source: reqwest::Error },
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{UpgradeModeAttestation, UpgradeModeCheckError};
|
||||
use jwt_simple::claims::Claims;
|
||||
use jwt_simple::common::{KeyMetadata, VerificationOptions};
|
||||
use jwt_simple::prelude::{EdDSAKeyPairLike, EdDSAPublicKeyLike};
|
||||
use jwt_simple::token::Token;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use std::collections::HashSet;
|
||||
use std::time::Duration;
|
||||
|
||||
// for now use static issuer such as "nym-credential-proxy"
|
||||
pub fn generate_jwt_for_upgrade_mode_attestation(
|
||||
attestation: UpgradeModeAttestation,
|
||||
validity: Duration,
|
||||
keys: &ed25519::KeyPair,
|
||||
issuer: Option<&'static str>,
|
||||
) -> String {
|
||||
let claim = Claims::with_custom_claims(attestation, validity.into());
|
||||
let mut claim = if let Some(issuer) = issuer {
|
||||
claim.with_issuer(issuer)
|
||||
} else {
|
||||
claim
|
||||
};
|
||||
claim.create_nonce();
|
||||
|
||||
let md = KeyMetadata::default().with_public_key(keys.public_key().to_base58_string());
|
||||
|
||||
let mut jwt_keys = keys.to_jwt_compatible_keys();
|
||||
// SAFETY: trait impl for EdDSA is infallible
|
||||
#[allow(clippy::unwrap_used)]
|
||||
jwt_keys.attach_metadata(md).unwrap();
|
||||
|
||||
// SAFETY: our construction of the jwt is valid
|
||||
#[allow(clippy::unwrap_used)]
|
||||
jwt_keys.sign(claim).unwrap()
|
||||
}
|
||||
|
||||
pub fn validate_upgrade_mode_jwt(
|
||||
token: &str,
|
||||
expected_issuer: Option<&'static str>,
|
||||
) -> Result<UpgradeModeAttestation, UpgradeModeCheckError> {
|
||||
// for now, we completely ignore the validity of the pubkey (I know, I know).
|
||||
// that will be changed later on
|
||||
// so as a bypass we have to extract the claimed issuer from the jwt to verify against it
|
||||
let metadata = Token::decode_metadata(token)
|
||||
.map_err(|source| UpgradeModeCheckError::TokenMetadataDecodeFailure { source })?;
|
||||
|
||||
let pub_key = metadata
|
||||
.public_key()
|
||||
.ok_or(UpgradeModeCheckError::MissingTokenPublicKey)?;
|
||||
|
||||
let ed25519_pub_key = ed25519::PublicKey::from_base58_string(pub_key)
|
||||
.map_err(|source| UpgradeModeCheckError::MalformedEd25519PublicKey { source })?;
|
||||
|
||||
let mut opts = VerificationOptions::default();
|
||||
if let Some(issuer) = expected_issuer {
|
||||
opts.allowed_issuers = Some(HashSet::from_iter(vec![issuer.to_string()]));
|
||||
}
|
||||
|
||||
let attestation = ed25519_pub_key
|
||||
.to_jwt_compatible_key()
|
||||
.verify_token::<UpgradeModeAttestation>(token, Some(opts))
|
||||
.map_err(|source| UpgradeModeCheckError::JwtVerificationFailure { source })?
|
||||
.custom;
|
||||
|
||||
Ok(attestation)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::generate_new_attestation;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
|
||||
#[test]
|
||||
fn generate_and_validate_jwt() {
|
||||
let attestation_key = ed25519::PrivateKey::from_bytes(&[
|
||||
108, 49, 193, 21, 126, 161, 249, 85, 242, 207, 74, 195, 238, 6, 64, 149, 201, 140, 248,
|
||||
163, 122, 170, 79, 198, 87, 85, 36, 29, 243, 92, 64, 161,
|
||||
])
|
||||
.unwrap();
|
||||
let jwt_key = ed25519::PrivateKey::from_bytes(&[
|
||||
152, 17, 144, 255, 213, 219, 246, 208, 109, 33, 100, 73, 1, 141, 32, 63, 141, 89, 167,
|
||||
2, 52, 215, 241, 219, 200, 18, 159, 241, 76, 111, 42, 32,
|
||||
])
|
||||
.unwrap();
|
||||
let keys = ed25519::KeyPair::from(jwt_key);
|
||||
|
||||
let attestation = generate_new_attestation(&attestation_key);
|
||||
let jwt_issuer = generate_jwt_for_upgrade_mode_attestation(
|
||||
attestation,
|
||||
Duration::from_secs(60 * 60),
|
||||
&keys,
|
||||
Some("nym-credential-proxy"),
|
||||
);
|
||||
// we expect 'nym-credential-proxy' issuer
|
||||
assert!(validate_upgrade_mode_jwt(&jwt_issuer, Some("nym-credential-proxy")).is_ok());
|
||||
|
||||
// we don't care about issuer
|
||||
assert!(validate_upgrade_mode_jwt(&jwt_issuer, None).is_ok());
|
||||
|
||||
// we expect another-issuer
|
||||
assert!(validate_upgrade_mode_jwt(&jwt_issuer, Some("another-issuer")).is_err());
|
||||
|
||||
let jwt_no_issuer = generate_jwt_for_upgrade_mode_attestation(
|
||||
attestation,
|
||||
Duration::from_secs(60 * 60),
|
||||
&keys,
|
||||
None,
|
||||
);
|
||||
// we expect 'nym-credential-proxy' issuer
|
||||
assert!(validate_upgrade_mode_jwt(&jwt_no_issuer, Some("nym-credential-proxy")).is_err());
|
||||
|
||||
// we don't care about issuer
|
||||
assert!(validate_upgrade_mode_jwt(&jwt_no_issuer, None).is_ok());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub(crate) mod attestation;
|
||||
pub(crate) mod error;
|
||||
pub(crate) mod jwt;
|
||||
|
||||
pub use attestation::{
|
||||
attempt_retrieve, generate_new_attestation, generate_new_attestation_with_starting_time,
|
||||
UpgradeModeAttestation,
|
||||
};
|
||||
pub use error::UpgradeModeCheckError;
|
||||
pub use jwt::{generate_jwt_for_upgrade_mode_attestation, validate_upgrade_mode_jwt};
|
||||
@@ -25,7 +25,7 @@
|
||||
//! ```
|
||||
|
||||
use crate::error::ZulipClientError;
|
||||
use crate::message::{SendMessageResponse, SendableMessage};
|
||||
use crate::message::{DirectMessage, SendMessageResponse, SendableMessage, StreamMessage};
|
||||
use nym_bin_common::bin_info;
|
||||
use nym_http_api_client::UserAgent;
|
||||
use reqwest::{header, Method, RequestBuilder};
|
||||
@@ -92,6 +92,20 @@ impl Client {
|
||||
.map_err(|source| ZulipClientError::RequestDecodeFailure { source })
|
||||
}
|
||||
|
||||
pub async fn send_direct_message(
|
||||
&self,
|
||||
msg: impl Into<DirectMessage>,
|
||||
) -> Result<SendMessageResponse, ZulipClientError> {
|
||||
self.send_message(msg.into()).await
|
||||
}
|
||||
|
||||
pub async fn send_channel_message(
|
||||
&self,
|
||||
msg: impl Into<StreamMessage>,
|
||||
) -> Result<SendMessageResponse, ZulipClientError> {
|
||||
self.send_message(msg.into()).await
|
||||
}
|
||||
|
||||
fn build_request(&self, method: Method, endpoint: &'static str) -> RequestBuilder {
|
||||
let url = format!("{}{endpoint}", self.server_url);
|
||||
trace!("posting to {url}");
|
||||
|
||||
@@ -22,7 +22,7 @@ pub enum SendMessageResponse {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[serde(tag = "type")]
|
||||
pub enum SendableMessageContent {
|
||||
@@ -40,7 +40,7 @@ pub enum SendableMessageContent {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct SendableMessage {
|
||||
#[serde(flatten)]
|
||||
@@ -117,17 +117,17 @@ impl StreamMessage {
|
||||
pub fn new(
|
||||
to: impl Into<ToChannel>,
|
||||
content: impl Into<String>,
|
||||
topic: Option<String>,
|
||||
topic: impl IntoMaybeTopic,
|
||||
) -> Self {
|
||||
StreamMessage {
|
||||
to: to.into().to_string(),
|
||||
topic,
|
||||
topic: topic.into_maybe_topic(),
|
||||
content: content.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn no_topic(to: impl Into<ToChannel>, content: impl Into<String>) -> Self {
|
||||
Self::new(to, content, None)
|
||||
Self::new(to, content, None::<String>)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
@@ -194,22 +194,74 @@ impl From<StreamMessage> for SendableMessageContent {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> From<(T, S, Option<S>)> for StreamMessage
|
||||
impl<T, S, U> From<(T, S, U)> for StreamMessage
|
||||
where
|
||||
T: Into<ToChannel>,
|
||||
S: Into<String>,
|
||||
U: IntoMaybeTopic,
|
||||
{
|
||||
fn from((to, content, topic): (T, S, Option<S>)) -> Self {
|
||||
StreamMessage::new(to, content, topic.map(Into::into))
|
||||
fn from((to, content, topic): (T, S, U)) -> Self {
|
||||
StreamMessage::new(to, content, topic)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> From<(T, S, Option<S>)> for SendableMessage
|
||||
impl<T, S> From<(T, S)> for StreamMessage
|
||||
where
|
||||
T: Into<ToChannel>,
|
||||
S: Into<String>,
|
||||
{
|
||||
fn from(inner: (T, S, Option<S>)) -> Self {
|
||||
fn from((to, content): (T, S)) -> Self {
|
||||
StreamMessage::no_topic(to, content)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, U> From<(T, S, U)> for SendableMessage
|
||||
where
|
||||
T: Into<ToChannel>,
|
||||
S: Into<String>,
|
||||
U: IntoMaybeTopic,
|
||||
{
|
||||
fn from(inner: (T, S, U)) -> Self {
|
||||
StreamMessage::from(inner).into()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoMaybeTopic {
|
||||
fn into_maybe_topic(self) -> Option<String>;
|
||||
}
|
||||
|
||||
impl<S> IntoMaybeTopic for &Option<S>
|
||||
where
|
||||
S: Into<String> + Clone,
|
||||
{
|
||||
fn into_maybe_topic(self) -> Option<String> {
|
||||
self.clone().map(|s| s.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> IntoMaybeTopic for Option<S>
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
fn into_maybe_topic(self) -> Option<String> {
|
||||
self.map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoMaybeTopic for String {
|
||||
fn into_maybe_topic(self) -> Option<String> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoMaybeTopic for &String {
|
||||
fn into_maybe_topic(self) -> Option<String> {
|
||||
Some(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoMaybeTopic for &str {
|
||||
fn into_maybe_topic(self) -> Option<String> {
|
||||
Some(self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+1
-1
@@ -744,7 +744,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -119,7 +119,6 @@ exceptions = [
|
||||
#{ allow = ["Zlib"], crate = "adler32" },
|
||||
{ allow = ["GPL-3.0"], crate = "nym-api" },
|
||||
{ allow = ["GPL-3.0"], crate = "nym-gateway" },
|
||||
{ allow = ["GPL-3.0"], crate = "nym-mixnode" },
|
||||
{ allow = ["GPL-3.0"], crate = "nym-network-requester" },
|
||||
{ allow = ["GPL-3.0"], crate = "nym-node" },
|
||||
{ allow = ["GPL-3.0"], crate = "nym-validator-rewarder" },
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
},
|
||||
"mixmining_reserve": {
|
||||
"denom": "unym",
|
||||
"amount": "184339094131786"
|
||||
"amount": "182883243257647"
|
||||
},
|
||||
"vesting_tokens": {
|
||||
"denom": "unym",
|
||||
@@ -13,6 +13,6 @@
|
||||
},
|
||||
"circulating_supply": {
|
||||
"denom": "unym",
|
||||
"amount": "815660905868214"
|
||||
"amount": "817116756742353"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
815_660_905
|
||||
817_116_756
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
5_120
|
||||
5_080
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
0.74%
|
||||
0.77%
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
38.479
|
||||
37.305
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1_040_817
|
||||
250_000
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
30.63%
|
||||
7.34%
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
249_796_152
|
||||
60_000_000
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
249_796_152
|
||||
60_000_000
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
| **Item** | **Description** | **Amount in NYM** |
|
||||
|:-------------------|:------------------------------------------------------|--------------------:|
|
||||
| Total Supply | Maximum amount of NYM token in existence | 1_000_000_000 |
|
||||
| Mixmining Reserve | Tokens releasing for operators rewards | 184_339_094 |
|
||||
| Mixmining Reserve | Tokens releasing for operators rewards | 182_883_243 |
|
||||
| Vesting Tokens | Tokens locked outside of cicrulation for future claim | 0 |
|
||||
| Circulating Supply | Amount of unlocked tokens | 815_660_905 |
|
||||
| Stake Saturation | Optimal size of node self-bond + delegation | 1_040_817 |
|
||||
| Circulating Supply | Amount of unlocked tokens | 817_116_756 |
|
||||
| Stake Saturation | Optimal size of node self-bond + delegation | 250_000 |
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
{
|
||||
"interval": {
|
||||
"reward_pool": "184339094131786.145886263466367605",
|
||||
"staking_supply": "249796152422140.492822331813424919",
|
||||
"staking_supply_scale_factor": "0.30625",
|
||||
"epoch_reward_budget": "5120530392.54961516350731851",
|
||||
"stake_saturation_point": "1040817301758.918720093049222603",
|
||||
"reward_pool": "182883243257647.891553460395608456",
|
||||
"staking_supply": "60000000000000",
|
||||
"staking_supply_scale_factor": "0.07342892",
|
||||
"epoch_reward_budget": "5080090090.490219209818344322",
|
||||
"stake_saturation_point": "250000000000",
|
||||
"sybil_resistance": "0.3",
|
||||
"active_set_work_factor": "10",
|
||||
"interval_pool_emission": "0.02"
|
||||
},
|
||||
"rewarded_set": {
|
||||
"entry_gateways": 50,
|
||||
"exit_gateways": 70,
|
||||
"mixnodes": 120,
|
||||
"entry_gateways": 80,
|
||||
"exit_gateways": 100,
|
||||
"mixnodes": 60,
|
||||
"standby": 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
Friday, August 22nd 2025, 10:15:08 UTC
|
||||
Thursday, September 4th 2025, 09:12:25 UTC
|
||||
|
||||
@@ -58,8 +58,8 @@ Options:
|
||||
Specifies whether the wireguard service is enabled on this node [env: NYMNODE_WG_ENABLED=] [possible values: true, false]
|
||||
--wireguard-bind-address <WIREGUARD_BIND_ADDRESS>
|
||||
Socket address this node will use for binding its wireguard interface. default: `[::]:51822` [env: NYMNODE_WG_BIND_ADDRESS=]
|
||||
--wireguard-tunnel-announced-port <WIREGUARD_TUNNEL_ANNOUNCED_PORT>
|
||||
Tunnel port announced to external clients wishing to connect to the wireguard interface. Useful in the instances where the node is behind a proxy [env: NYMNODE_WG_ANNOUNCED_PORT=]
|
||||
--wireguard-announced-port <WIREGUARD_ANNOUNCED_PORT>
|
||||
Port announced to external clients wishing to connect to the wireguard interface. Useful in the instances where the node is behind a proxy [env: NYMNODE_WG_ANNOUNCED_PORT=]
|
||||
--wireguard-private-network-prefix <WIREGUARD_PRIVATE_NETWORK_PREFIX>
|
||||
The prefix denoting the maximum number of the clients that can be connected via Wireguard. The maximum value for IPv4 is 32 and for IPv6 is 128 [env: NYMNODE_WG_PRIVATE_NETWORK_PREFIX=]
|
||||
--verloc-bind-address <VERLOC_BIND_ADDRESS>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
| [AmeriNoc](https://www.amerinoc.com) | USA | Yes | nan | nan | 07/2025 |
|
||||
| [BitLaunch](https://bitlaunch.io) | Canada, USA, UK | No | Yes | Expensive. Digial Ocean through BitLanch has IPv6 | 05/2024 |
|
||||
| [Cherry Servers](https://www.cherryservers.com) | Lithuania, Netherlands, USA, Singapore | No | Yes | Issued IP doesn’t match the location offered by the provider. | 05/2024 |
|
||||
| [Colocall](https://www.colocall.net/) | Ukraine | Yes | nan | 07/2025 | nan |
|
||||
| [Colocall](https://www.colocall.net/) | Ukraine | Yes | nan | nan | 07/2025 |
|
||||
| [DataPacket](https://www.datapacket.com/pricing) | NL, GR, SK, BE, RO, HU, DK, IE, DE, UA, PT, GB, ES, FR, IT, NO, CZ, BG, SE, AT, PL, HR, CH, USA, CO, AR, PE, MX, CL, TR, ZA, NG, IL, HK, AU, SG, JP | Yes | nan | nan | 07/2025 |
|
||||
| [Dataclub](https://www.dataclub.eu/) | Latvia, Sweden, Netherlands | Yes | nan | nan | 07/2027 |
|
||||
| [Flokinet](https://flokinet.is) | Netherlands, Iceland, Romania,France | Yes, needs a ticket and custom setup | yes, including XMR | Very slow customer support | 05/2024 |
|
||||
@@ -13,6 +13,7 @@
|
||||
| [HostSailor](https://hostsailor.com) | USA | Yes, based on ticket | Yes | The IPv6 setup needs custom research and is not documented | 05/2024 |
|
||||
| [Hostiko](https://hostiko.com.ua) | Ukraine, Germany | Yes, on by default | Yes | Ukrainian provider. They allow Exit nodes on Germany boxes but limit the bandwidth, you also have to restrict certain ports like 25 and 587. Make sure you open a ticket. | 07/2024 |
|
||||
| [Hostinger](https://hostinger.com) | France, Lithuania, India, USA, Brazil | Yes, out of the box | Yes | Not fast enough, Crypto payments must be done per each server monthly or annually. | 07/2025 |
|
||||
| [Hostraha](https://hostraha.com) | Kenya and other African countries | No, but advertised otherwise | Yes, USDT TRC20 | Don't recommend. Unresponsive technical and billing support, never provided IPv6 even though advertised and paid for. When VPS cancelled, company still tried to bill the credit card on file multiple times. | 08/2025 |
|
||||
| [Hostroyale](https://hostroyale.com/hosting/dedicated-server/) | Various countries with different pricing | nan | Yes | nan | 07/2025 |
|
||||
| [Hostslick](https://hostslick.com) | Netherlands, Germany | Yes, on by default | Yes | Good amount of bandwidth for the price. Make sure you open the ticket if you want to run Exit node | 07/2024 |
|
||||
| [Incognet](https://incognet.io) | Netherlands and USA | Yes, on by default | Yes | They allow Tor exit nodes but you must adhere to their rules https://incognet.io/tor-exits | 07/2024 |
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
[Zenlayer](https://www.zenlayer.com/bare-metal/), [advertised over 50 locations](50+ https://www.zenlayer.com/global-network),,,,07/2025
|
||||
[PrivateLayer](https://privatelayer.com),Swiss,Yes,Yes,Slow customer response,07/2025
|
||||
[AmeriNoc](https://www.amerinoc.com),USA,Yes,,,07/2025
|
||||
[Colocall](https://www.colocall.net/),Ukraine,Yes,,07/2025,
|
||||
[Colocall](https://www.colocall.net/),Ukraine,Yes,,,07/2025
|
||||
[Incognet](https://incognet.io/kansas-city-dedicated-servers),"USA, Netherlands",Yes,,,07/2025
|
||||
[FranTech](https://my.frantech.ca),USA,Yes,,,07/2025
|
||||
[Psychz](https://www.psychz.net),"US, UK, Brazil, Japan, Russia, South Africa and many more",Yes,,,07/2025
|
||||
@@ -41,4 +41,4 @@
|
||||
[Dataclub](https://www.dataclub.eu/),"Latvia, Sweden, Netherlands",Yes,,,07/2027
|
||||
[Privex](https://www.privex.io/tor-exit-policy/),"USA, Germany, Sweden",Yes,Yes,,07/2025
|
||||
[Svea](https://svea.net/vps),Sweden,Yes,,,07/2025
|
||||
[Hostraha](https://hostraha.com),"Kenya and other African countries", "No, but advertised otherwise", "Yes, USDT TRC20", "Don't recommend. Unresponsive technical and billing support, never provided IPv6 even though advertised and paid for. When VPS cancelled, company still tried to bill the credit card on file multiple times.", 08/2025
|
||||
[Hostraha](https://hostraha.com),Kenya and other African countries,"No, but advertised otherwise","Yes, USDT TRC20","Don't recommend. Unresponsive technical and billing support, never provided IPv6 even though advertised and paid for. When VPS cancelled, company still tried to bill the credit card on file multiple times.",08/2025
|
||||
|
||||
|
+1
-1
@@ -86,5 +86,5 @@ defguard_wireguard_rs = { workspace = true }
|
||||
[dev-dependencies]
|
||||
nym-gateway-storage = { path = "../common/gateway-storage", features = ["mock"] }
|
||||
nym-wireguard = { path = "../common/wireguard", features = ["mock"] }
|
||||
mock_instant = "0.5.3"
|
||||
mock_instant = "0.6.0"
|
||||
time = { workspace = true }
|
||||
|
||||
+2
-1
@@ -126,7 +126,8 @@ cw3 = { workspace = true }
|
||||
cw-utils = { workspace = true }
|
||||
rand_chacha = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
dotenv = "0.15"
|
||||
dotenvy = { workspace = true }
|
||||
test-with = { workspace = true, default-features = false }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -8,7 +8,9 @@ use nym_crypto::asymmetric::x25519::serde_helpers::bs58_x25519_pubkey;
|
||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
use nym_mixnet_contract_common::reward_params::Performance;
|
||||
use nym_mixnet_contract_common::NodeId;
|
||||
use nym_network_defaults::{DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT};
|
||||
use nym_network_defaults::{
|
||||
DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT, WG_METADATA_PORT, WG_TUNNEL_PORT,
|
||||
};
|
||||
use nym_node_requests::api::v1::authenticator::models::Authenticator;
|
||||
use nym_node_requests::api::v1::gateway::models::Wireguard;
|
||||
use nym_node_requests::api::v1::ip_packet_router::models::IpPacketRouter;
|
||||
@@ -313,11 +315,20 @@ impl From<Authenticator> for AuthenticatorDetails {
|
||||
pub struct WireguardDetails {
|
||||
// NOTE: the port field is deprecated in favour of tunnel_port
|
||||
pub port: u16,
|
||||
#[serde(default = "default_tunnel_port")]
|
||||
pub tunnel_port: u16,
|
||||
#[serde(default = "default_metadata_port")]
|
||||
pub metadata_port: u16,
|
||||
pub public_key: String,
|
||||
}
|
||||
|
||||
fn default_tunnel_port() -> u16 {
|
||||
WG_TUNNEL_PORT
|
||||
}
|
||||
fn default_metadata_port() -> u16 {
|
||||
WG_METADATA_PORT
|
||||
}
|
||||
|
||||
// works for current simple case.
|
||||
impl From<Wireguard> for WireguardDetails {
|
||||
fn from(value: Wireguard) -> Self {
|
||||
|
||||
@@ -31,6 +31,7 @@ pub use schema_helpers::*;
|
||||
|
||||
pub use nym_mixnet_contract_common::{EpochId, KeyRotationId, KeyRotationState};
|
||||
pub use nym_node_requests::api::v1::node::models::BinaryBuildInformationOwned;
|
||||
pub use nym_noise_keys::VersionedNoiseKey;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
pub struct RequestError {
|
||||
|
||||
@@ -11,7 +11,7 @@ use anyhow::{bail, Result};
|
||||
use nym_coconut_dkg_common::types::{Epoch, EpochId, EpochState};
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_dkg::bte::keys::KeyPair as DkgKeyPair;
|
||||
use nym_task::{TaskClient, TaskManager};
|
||||
use nym_task::{ShutdownManager, ShutdownToken};
|
||||
use rand::rngs::OsRng;
|
||||
use rand::{CryptoRng, Rng, RngCore};
|
||||
use std::path::PathBuf;
|
||||
@@ -273,7 +273,7 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
tick_duration < min
|
||||
}
|
||||
|
||||
pub(crate) async fn run(mut self, mut shutdown: TaskClient) {
|
||||
pub(crate) async fn run(mut self, shutdown: ShutdownToken) {
|
||||
let mut interval = interval(self.polling_rate);
|
||||
interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
|
||||
|
||||
@@ -282,7 +282,7 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
let mut last_polled = OffsetDateTime::now_utc();
|
||||
let mut last_tick_duration = Default::default();
|
||||
|
||||
while !shutdown.is_shutdown() {
|
||||
while !shutdown.is_cancelled() {
|
||||
tokio::select! {
|
||||
_ = interval.tick() => {
|
||||
let now = OffsetDateTime::now_utc();
|
||||
@@ -300,7 +300,7 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
error!("failed to update the DKG state: {err}")
|
||||
}
|
||||
}
|
||||
_ = shutdown.recv() => {
|
||||
_ = shutdown.cancelled() => {
|
||||
trace!("DkgController: Received shutdown");
|
||||
}
|
||||
}
|
||||
@@ -314,12 +314,12 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
dkg_bte_keypair: DkgKeyPair,
|
||||
identity_key: ed25519::PublicKey,
|
||||
rng: R,
|
||||
shutdown: &TaskManager,
|
||||
shutdown_manager: &ShutdownManager,
|
||||
) -> Result<()>
|
||||
where
|
||||
R: Sync + Send + 'static,
|
||||
{
|
||||
let shutdown_listener = shutdown.subscribe();
|
||||
let shutdown_listener = shutdown_manager.clone_token("DKG controller");
|
||||
let dkg_controller = DkgController::new(
|
||||
config,
|
||||
nyxd_client,
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::node_status_api::models::NymApiStorageError;
|
||||
use crate::support::config::Config;
|
||||
use crate::support::storage::NymApiStorage;
|
||||
use nym_ecash_time::ecash_today_date;
|
||||
use nym_task::TaskClient;
|
||||
use nym_task::ShutdownToken;
|
||||
use std::time::Duration;
|
||||
use time::Date;
|
||||
use tokio::task::JoinHandle;
|
||||
@@ -20,11 +20,15 @@ pub struct EcashBackgroundStateCleaner {
|
||||
verified_tickets_retention_period_days: u32,
|
||||
|
||||
storage: NymApiStorage,
|
||||
task_client: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
}
|
||||
|
||||
impl EcashBackgroundStateCleaner {
|
||||
pub fn new(global_config: &Config, storage: NymApiStorage, task_client: TaskClient) -> Self {
|
||||
pub fn new(
|
||||
global_config: &Config,
|
||||
storage: NymApiStorage,
|
||||
shutdown_token: ShutdownToken,
|
||||
) -> Self {
|
||||
EcashBackgroundStateCleaner {
|
||||
run_interval: global_config.ecash_signer.debug.stale_data_cleaner_interval,
|
||||
issued_ticketbooks_retention_period_days: global_config
|
||||
@@ -36,7 +40,7 @@ impl EcashBackgroundStateCleaner {
|
||||
.debug
|
||||
.verified_tickets_retention_period_days,
|
||||
storage,
|
||||
task_client,
|
||||
shutdown_token,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +72,7 @@ impl EcashBackgroundStateCleaner {
|
||||
let mut ticker = tokio::time::interval(self.run_interval);
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = self.task_client.recv() => {
|
||||
_ = self.shutdown_token.cancelled() => {
|
||||
trace!("EcashBackgroundStateCleaner: Received shutdown");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ use nym_ecash_contract_common::deposit::{Deposit, DepositId};
|
||||
use nym_ecash_contract_common::msg::ExecuteMsg;
|
||||
use nym_ecash_contract_common::redeem_credential::BATCH_REDEMPTION_PROPOSAL_TITLE;
|
||||
use nym_ecash_time::{ecash_default_expiration_date, ecash_today_date};
|
||||
use nym_task::TaskClient;
|
||||
use nym_task::ShutdownManager;
|
||||
use nym_ticketbooks_merkle::{IssuedTicketbook, IssuedTicketbooksFullMerkleProof, MerkleLeaf};
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use nym_validator_client::EcashApiClient;
|
||||
@@ -126,7 +126,7 @@ impl EcashState {
|
||||
key_pair: KeyPair,
|
||||
comm_channel: D,
|
||||
storage: NymApiStorage,
|
||||
task_client: TaskClient,
|
||||
shutdown_manager: &ShutdownManager,
|
||||
) -> Self
|
||||
where
|
||||
C: LocalClient + Send + Sync + 'static,
|
||||
@@ -135,7 +135,11 @@ impl EcashState {
|
||||
Self {
|
||||
config: EcashStateConfig::new(global_config),
|
||||
background_cleaner_state: BackgroundCleanerState::WaitingStartup(
|
||||
EcashBackgroundStateCleaner::new(global_config, storage.clone(), task_client),
|
||||
EcashBackgroundStateCleaner::new(
|
||||
global_config,
|
||||
storage.clone(),
|
||||
shutdown_manager.clone_token("ecash-state-data-cleaner"),
|
||||
),
|
||||
),
|
||||
global: GlobalEcachState::new(contract_address),
|
||||
local: LocalEcashState::new(
|
||||
|
||||
@@ -59,7 +59,7 @@ use nym_crypto::asymmetric::ed25519;
|
||||
use nym_dkg::{NodeIndex, Threshold};
|
||||
use nym_ecash_contract_common::blacklist::{BlacklistedAccountResponse, Blacklisting};
|
||||
use nym_ecash_contract_common::deposit::{Deposit, DepositId, DepositResponse};
|
||||
use nym_task::TaskClient;
|
||||
use nym_task::ShutdownManager;
|
||||
use nym_validator_client::nym_api::routes::{
|
||||
ECASH_BLIND_SIGN, ECASH_ISSUED_TICKETBOOKS_CHALLENGE_COMMITMENT, ECASH_ISSUED_TICKETBOOKS_FOR,
|
||||
ECASH_ROUTES, V1_API_VERSION,
|
||||
@@ -1348,7 +1348,7 @@ impl TestFixture {
|
||||
staged_key_pair,
|
||||
comm_channel,
|
||||
storage.clone(),
|
||||
TaskClient::dummy(),
|
||||
&ShutdownManager::empty_mock(),
|
||||
);
|
||||
|
||||
// ideally this would have been generic, but that's way too much work
|
||||
@@ -1489,7 +1489,6 @@ mod credential_tests {
|
||||
use super::*;
|
||||
use crate::ecash::storage::EcashStorageExt;
|
||||
use axum::http::StatusCode;
|
||||
use nym_task::TaskClient;
|
||||
use nym_ticketbooks_merkle::MerkleLeaf;
|
||||
|
||||
#[tokio::test]
|
||||
@@ -1580,7 +1579,7 @@ mod credential_tests {
|
||||
staged_key_pair,
|
||||
comm_channel,
|
||||
storage.clone(),
|
||||
TaskClient::dummy(),
|
||||
&ShutdownManager::empty_mock(),
|
||||
);
|
||||
|
||||
let deposit_id = 42;
|
||||
|
||||
@@ -16,12 +16,13 @@ use crate::mixnet_contract_cache::cache::MixnetContractCache;
|
||||
use crate::node_describe_cache::cache::DescribedNodes;
|
||||
use crate::node_status_api::{NodeStatusCache, ONE_DAY};
|
||||
use crate::support::caching::cache::SharedCache;
|
||||
use crate::support::caching::refresher::RefreshRequester;
|
||||
use crate::support::nyxd::Client;
|
||||
use crate::support::storage::NymApiStorage;
|
||||
use error::RewardingError;
|
||||
pub(crate) use helpers::RewardedNodeWithParams;
|
||||
use nym_mixnet_contract_common::{CurrentIntervalResponse, Interval};
|
||||
use nym_task::{TaskClient, TaskManager};
|
||||
use nym_task::{ShutdownManager, ShutdownToken};
|
||||
use std::time::Duration;
|
||||
use tokio::time::sleep;
|
||||
use tracing::{error, info, trace, warn};
|
||||
@@ -37,7 +38,8 @@ mod transition_beginning;
|
||||
// this is struct responsible for advancing an epoch
|
||||
pub struct EpochAdvancer {
|
||||
nyxd_client: Client,
|
||||
nym_contract_cache: MixnetContractCache,
|
||||
mixnet_contract_cache: MixnetContractCache,
|
||||
mixnet_contract_cache_refresh_requester: RefreshRequester,
|
||||
described_cache: SharedCache<DescribedNodes>,
|
||||
status_cache: NodeStatusCache,
|
||||
storage: NymApiStorage,
|
||||
@@ -52,14 +54,16 @@ impl EpochAdvancer {
|
||||
|
||||
pub(crate) fn new(
|
||||
nyxd_client: Client,
|
||||
nym_contract_cache: MixnetContractCache,
|
||||
mixnet_contract_cache: MixnetContractCache,
|
||||
mixnet_contract_cache_refresh_requester: RefreshRequester,
|
||||
status_cache: NodeStatusCache,
|
||||
described_cache: SharedCache<DescribedNodes>,
|
||||
storage: NymApiStorage,
|
||||
) -> Self {
|
||||
EpochAdvancer {
|
||||
nyxd_client,
|
||||
nym_contract_cache,
|
||||
mixnet_contract_cache,
|
||||
mixnet_contract_cache_refresh_requester,
|
||||
described_cache,
|
||||
status_cache,
|
||||
storage,
|
||||
@@ -119,7 +123,7 @@ impl EpochAdvancer {
|
||||
|
||||
let epoch_end = interval.current_epoch_end();
|
||||
|
||||
let nym_nodes = self.nym_contract_cache.nym_nodes().await;
|
||||
let nym_nodes = self.mixnet_contract_cache.nym_nodes().await;
|
||||
|
||||
if nym_nodes.is_empty() {
|
||||
// that's a bit weird, but ok
|
||||
@@ -161,10 +165,15 @@ impl EpochAdvancer {
|
||||
let cutoff = (epoch_end - 2 * ONE_DAY).unix_timestamp();
|
||||
self.storage.purge_old_statuses(cutoff).await?;
|
||||
|
||||
// after all epoch progression has finished - force refresh the mixnet contract cache,
|
||||
// so we'd know about new rewarded set (that's easier than manually overwriting the data)
|
||||
self.mixnet_contract_cache_refresh_requester
|
||||
.request_cache_refresh();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn wait_until_epoch_end(&mut self, shutdown: &mut TaskClient) -> Option<Interval> {
|
||||
async fn wait_until_epoch_end(&mut self, shutdown_token: &ShutdownToken) -> Option<Interval> {
|
||||
const POLL_INTERVAL: Duration = Duration::from_secs(120);
|
||||
|
||||
loop {
|
||||
@@ -175,7 +184,7 @@ impl EpochAdvancer {
|
||||
_ = sleep(POLL_INTERVAL) => {
|
||||
continue
|
||||
},
|
||||
_ = shutdown.recv() => {
|
||||
_ = shutdown_token.cancelled() => {
|
||||
trace!("wait_until_epoch_end: Received shutdown");
|
||||
break None
|
||||
}
|
||||
@@ -203,7 +212,7 @@ impl EpochAdvancer {
|
||||
_ = sleep(wait_time) => {
|
||||
|
||||
},
|
||||
_ = shutdown.recv() => {
|
||||
_ = shutdown_token.cancelled() => {
|
||||
trace!("wait_until_epoch_end: Received shutdown");
|
||||
break None
|
||||
}
|
||||
@@ -212,17 +221,20 @@ impl EpochAdvancer {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn run(&mut self, mut shutdown: TaskClient) -> Result<(), RewardingError> {
|
||||
pub(crate) async fn run(
|
||||
&mut self,
|
||||
shutdown_token: ShutdownToken,
|
||||
) -> Result<(), RewardingError> {
|
||||
info!("waiting for initial contract cache values before we can start rewarding");
|
||||
self.nym_contract_cache
|
||||
self.mixnet_contract_cache
|
||||
.naive_wait_for_initial_values()
|
||||
.await;
|
||||
|
||||
info!("waiting for initial self-described cache values before we can start rewarding");
|
||||
self.described_cache.naive_wait_for_initial_values().await;
|
||||
|
||||
while !shutdown.is_shutdown() {
|
||||
let interval_details = match self.wait_until_epoch_end(&mut shutdown).await {
|
||||
while !shutdown_token.is_cancelled() {
|
||||
let interval_details = match self.wait_until_epoch_end(&shutdown_token).await {
|
||||
// received a shutdown
|
||||
None => return Ok(()),
|
||||
Some(interval) => interval,
|
||||
@@ -240,20 +252,22 @@ impl EpochAdvancer {
|
||||
pub(crate) fn start(
|
||||
nyxd_client: Client,
|
||||
nym_contract_cache: &MixnetContractCache,
|
||||
mixnet_contract_cache_refresh_requester: RefreshRequester,
|
||||
status_cache: &NodeStatusCache,
|
||||
described_cache: SharedCache<DescribedNodes>,
|
||||
storage: &NymApiStorage,
|
||||
shutdown: &TaskManager,
|
||||
shutdown_manager: &ShutdownManager,
|
||||
) {
|
||||
let mut rewarded_set_updater = EpochAdvancer::new(
|
||||
let mut epoch_advancer = EpochAdvancer::new(
|
||||
nyxd_client,
|
||||
nym_contract_cache.to_owned(),
|
||||
mixnet_contract_cache_refresh_requester,
|
||||
status_cache.to_owned(),
|
||||
described_cache,
|
||||
storage.to_owned(),
|
||||
);
|
||||
let shutdown_listener = shutdown.subscribe();
|
||||
tokio::spawn(async move { rewarded_set_updater.run(shutdown_listener).await });
|
||||
let shutdown_listener = shutdown_manager.clone_token("epoch-advancer");
|
||||
tokio::spawn(async move { epoch_advancer.run(shutdown_listener).await });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ impl EpochAdvancer {
|
||||
Ok(rewarded_set) => rewarded_set,
|
||||
Err(err) => {
|
||||
warn!("failed to obtain the current rewarded set: {err}. falling back to the cached version");
|
||||
self.nym_contract_cache
|
||||
self.mixnet_contract_cache
|
||||
.rewarded_set_owned()
|
||||
.await
|
||||
.unwrap()
|
||||
@@ -94,7 +94,7 @@ impl EpochAdvancer {
|
||||
|
||||
// SAFETY: `EpochAdvancer` is not started until cache is properly initialised
|
||||
let reward_params = self
|
||||
.nym_contract_cache
|
||||
.mixnet_contract_cache
|
||||
.interval_reward_params()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use crate::mixnet_contract_cache::cache::MixnetContractCache;
|
||||
use crate::support::caching::refresher::{CacheUpdateWatcher, RefreshRequester};
|
||||
use nym_mixnet_contract_common::{Interval, KeyRotationState};
|
||||
use nym_task::TaskClient;
|
||||
use nym_task::ShutdownToken;
|
||||
use time::OffsetDateTime;
|
||||
use tracing::{debug, error, info, trace};
|
||||
|
||||
@@ -110,14 +110,14 @@ impl KeyRotationController {
|
||||
}
|
||||
}
|
||||
|
||||
async fn run(&mut self, mut task_client: TaskClient) {
|
||||
async fn run(&mut self, shutdown_token: ShutdownToken) {
|
||||
self.contract_cache.naive_wait_for_initial_values().await;
|
||||
self.handle_contract_cache_update().await;
|
||||
|
||||
while !task_client.is_shutdown() {
|
||||
while !shutdown_token.is_cancelled() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = task_client.recv() => {
|
||||
_ = shutdown_token.cancelled() => {
|
||||
trace!("KeyRotationController: Received shutdown");
|
||||
}
|
||||
_ = self.contract_cache_watcher.changed() => {
|
||||
@@ -129,8 +129,8 @@ impl KeyRotationController {
|
||||
trace!("KeyRotationController: exiting")
|
||||
}
|
||||
|
||||
pub(crate) fn start(mut self, task_client: TaskClient) {
|
||||
tokio::spawn(async move { self.run(task_client).await });
|
||||
pub(crate) fn start(mut self, shutdown_token: ShutdownToken) {
|
||||
tokio::spawn(async move { self.run(shutdown_token).await });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use nym_sphinx::params::PacketType;
|
||||
use nym_sphinx::receiver::MessageReceiver;
|
||||
use nym_task::TaskManager;
|
||||
use nym_task::ShutdownManager;
|
||||
use std::sync::Arc;
|
||||
use tracing::info;
|
||||
|
||||
@@ -167,12 +167,12 @@ impl<R: MessageReceiver + Send + Sync + 'static> NetworkMonitorRunnables<R> {
|
||||
// TODO: note, that is not exactly doing what we want, because when
|
||||
// `ReceivedProcessor` is constructed, it already spawns a future
|
||||
// this needs to be refactored!
|
||||
pub(crate) fn spawn_tasks(self, shutdown: &TaskManager) {
|
||||
pub(crate) fn spawn_tasks(self, shutdown: &ShutdownManager) {
|
||||
let mut packet_receiver = self.packet_receiver;
|
||||
let mut monitor = self.monitor;
|
||||
let shutdown_listener = shutdown.subscribe();
|
||||
let shutdown_listener = shutdown.clone_token("NM-packet-receiver");
|
||||
tokio::spawn(async move { packet_receiver.run(shutdown_listener).await });
|
||||
let shutdown_listener = shutdown.subscribe();
|
||||
let shutdown_listener = shutdown.clone_token("NM-main");
|
||||
tokio::spawn(async move { monitor.run(shutdown_listener).await });
|
||||
}
|
||||
}
|
||||
@@ -241,7 +241,7 @@ pub(crate) async fn start<R: MessageReceiver + Send + Sync + 'static>(
|
||||
node_status_cache: NodeStatusCache,
|
||||
storage: &NymApiStorage,
|
||||
nyxd_client: nyxd::Client,
|
||||
shutdown: &TaskManager,
|
||||
shutdown: &ShutdownManager,
|
||||
) {
|
||||
let monitor_builder = setup(
|
||||
config,
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::support::config;
|
||||
use nym_mixnet_contract_common::NodeId;
|
||||
use nym_sphinx::params::PacketType;
|
||||
use nym_sphinx::receiver::MessageReceiver;
|
||||
use nym_task::TaskClient;
|
||||
use nym_task::ShutdownToken;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use tokio::time::{sleep, Duration, Instant};
|
||||
use tracing::{debug, error, info, trace};
|
||||
@@ -325,7 +325,7 @@ impl<R: MessageReceiver + Send + Sync> Monitor<R> {
|
||||
self.test_nonce += 1;
|
||||
}
|
||||
|
||||
pub(crate) async fn run(&mut self, mut shutdown: TaskClient) {
|
||||
pub(crate) async fn run(&mut self, shutdown_token: ShutdownToken) {
|
||||
self.received_processor.start_receiving();
|
||||
|
||||
// wait for validator cache to be ready
|
||||
@@ -334,18 +334,18 @@ impl<R: MessageReceiver + Send + Sync> Monitor<R> {
|
||||
.await;
|
||||
|
||||
let mut run_interval = tokio::time::interval(self.run_interval);
|
||||
while !shutdown.is_shutdown() {
|
||||
while !shutdown_token.is_cancelled() {
|
||||
tokio::select! {
|
||||
_ = run_interval.tick() => {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown.recv() => {
|
||||
_ = shutdown_token.cancelled() => {
|
||||
trace!("UpdateHandler: Received shutdown");
|
||||
}
|
||||
_ = self.test_run() => (),
|
||||
}
|
||||
}
|
||||
_ = shutdown.recv() => {
|
||||
_ = shutdown_token.cancelled() => {
|
||||
trace!("UpdateHandler: Received shutdown");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use futures::channel::mpsc;
|
||||
use futures::StreamExt;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_gateway_client::{AcknowledgementReceiver, MixnetMessageReceiver};
|
||||
use nym_task::TaskClient;
|
||||
use nym_task::ShutdownToken;
|
||||
use tracing::{error, trace};
|
||||
|
||||
pub(crate) type GatewayClientUpdateSender = mpsc::UnboundedSender<GatewayClientUpdate>;
|
||||
@@ -57,11 +57,11 @@ impl PacketReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn run(&mut self, mut shutdown: TaskClient) {
|
||||
while !shutdown.is_shutdown() {
|
||||
pub(crate) async fn run(&mut self, shutdown_token: ShutdownToken) {
|
||||
while !shutdown_token.is_cancelled() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown.recv() => {
|
||||
_ = shutdown_token.cancelled() => {
|
||||
trace!("UpdateHandler: Received shutdown");
|
||||
}
|
||||
// unwrap here is fine as it can only return a `None` if the PacketSender has died
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user