Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a880b59f03 | |||
| 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 | |||
| b2266d04ef | |||
| 911b365609 | |||
| e9acc014ed | |||
| 0f66e5a154 | |||
| f8337d9b38 | |||
| 4fb252c44b | |||
| 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
|
||||
|
||||
@@ -30,11 +30,13 @@ jobs:
|
||||
release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }}
|
||||
client_hash: ${{ steps.binary-hashes.outputs.client_hash }}
|
||||
nymvisor_hash: ${{ steps.binary-hashes.outputs.nymvisor_hash }}
|
||||
nymnode_hash: ${{ steps.binary-hashes.outputs.nymnode_hash }}
|
||||
socks5_hash: ${{ steps.binary-hashes.outputs.socks5_hash }}
|
||||
netreq_hash: ${{ steps.binary-hashes.outputs.netreq_hash }}
|
||||
cli_hash: ${{ steps.binary-hashes.outputs.cli_hash }}
|
||||
client_version: ${{ steps.binary-versions.outputs.client_version }}
|
||||
nymvisor_version: ${{ steps.binary-versions.outputs.nymvisor_version }}
|
||||
nymnode_version: ${{ steps.binary-versions.outputs.nymnode_version }}
|
||||
socks5_version: ${{ steps.binary-versions.outputs.socks5_version }}
|
||||
netreq_version: ${{ steps.binary-versions.outputs.netreq_version }}
|
||||
cli_version: ${{ steps.binary-versions.outputs.cli_version }}
|
||||
@@ -54,7 +56,7 @@ jobs:
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.88.0
|
||||
toolchain: 1.86.0
|
||||
override: true
|
||||
|
||||
- name: Build all binaries
|
||||
@@ -74,6 +76,7 @@ jobs:
|
||||
target/release/nym-network-requester
|
||||
target/release/nym-cli
|
||||
target/release/nymvisor
|
||||
target/release/nym-node
|
||||
retention-days: 30
|
||||
|
||||
- id: create-release
|
||||
@@ -88,6 +91,7 @@ jobs:
|
||||
target/release/nym-network-requester
|
||||
target/release/nym-cli
|
||||
target/release/nymvisor
|
||||
target/release/nym-node
|
||||
|
||||
push-release-data-client:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,48 +4,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2025.16-halloumi] (2025-09-16)
|
||||
|
||||
- Backport metadata endpoint ([#6010])
|
||||
- bugfix: make sure tables are removed in correct order to not trigger FK constraint issue ([#5987])
|
||||
- chore: move authenticator into gateway crate ([#5982])
|
||||
- Fix the ns api ci workflow ([#5981])
|
||||
- Remove freshness check on testrun submit ([#5977])
|
||||
- Update sysinfo to the latest ([#5976])
|
||||
- bugfix: manually calculate per node work on rewarded set changes ([#5972])
|
||||
- fixing the ci for ns agent ([#5965])
|
||||
- Feature/testing utils ([#5963])
|
||||
- bugfix: fix ci-build for linux (and use updated runner) ([#5958])
|
||||
- chore: updated refs to cheddar rev of nym repo ([#5955])
|
||||
- http api client adjustment ([#5953])
|
||||
- chore: fix rust 1.89 clippy issues ([#5944])
|
||||
- Wireguard metadata client library ([#5943])
|
||||
- chore: remove unused import ([#5942])
|
||||
- feat: introduce additional checks when attempting to send to bounded channels ([#5941])
|
||||
- Move credential verifier in peer controller ([#5938])
|
||||
- change PK/FK on expiration date signatures tables ([#5934])
|
||||
- Wireguard private metadata ([#5915])
|
||||
|
||||
[#6010]: https://github.com/nymtech/nym/pull/6010
|
||||
[#5987]: https://github.com/nymtech/nym/pull/5987
|
||||
[#5982]: https://github.com/nymtech/nym/pull/5982
|
||||
[#5981]: https://github.com/nymtech/nym/pull/5981
|
||||
[#5977]: https://github.com/nymtech/nym/pull/5977
|
||||
[#5976]: https://github.com/nymtech/nym/pull/5976
|
||||
[#5972]: https://github.com/nymtech/nym/pull/5972
|
||||
[#5965]: https://github.com/nymtech/nym/pull/5965
|
||||
[#5963]: https://github.com/nymtech/nym/pull/5963
|
||||
[#5958]: https://github.com/nymtech/nym/pull/5958
|
||||
[#5955]: https://github.com/nymtech/nym/pull/5955
|
||||
[#5953]: https://github.com/nymtech/nym/pull/5953
|
||||
[#5944]: https://github.com/nymtech/nym/pull/5944
|
||||
[#5943]: https://github.com/nymtech/nym/pull/5943
|
||||
[#5942]: https://github.com/nymtech/nym/pull/5942
|
||||
[#5941]: https://github.com/nymtech/nym/pull/5941
|
||||
[#5938]: https://github.com/nymtech/nym/pull/5938
|
||||
[#5934]: https://github.com/nymtech/nym/pull/5934
|
||||
[#5915]: https://github.com/nymtech/nym/pull/5915
|
||||
|
||||
## [2025.15-gruyere] (2025-08-20)
|
||||
|
||||
- Migrate strum to 0.27.2 ([#5960])
|
||||
|
||||
Generated
+235
-77
@@ -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"
|
||||
@@ -1345,7 +1373,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
@@ -2370,7 +2391,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2424,6 +2445,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 +2519,8 @@ dependencies = [
|
||||
"ff",
|
||||
"generic-array 0.14.7",
|
||||
"group",
|
||||
"hkdf",
|
||||
"pem-rfc7468",
|
||||
"pkcs8",
|
||||
"rand_core 0.6.4",
|
||||
"sec1",
|
||||
@@ -3349,6 +3382,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 +3549,7 @@ dependencies = [
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"socket2 0.5.10",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -3582,7 +3639,7 @@ dependencies = [
|
||||
"libc",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"socket2 0.5.10",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -3827,26 +3884,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 +3991,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",
|
||||
@@ -3986,7 +4030,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4088,6 +4132,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 +4589,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,15 +4887,9 @@ 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.65"
|
||||
version = "1.1.64"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -5016,7 +5080,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-cli"
|
||||
version = "1.1.62"
|
||||
version = "1.1.61"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.22.1",
|
||||
@@ -5098,7 +5162,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-client"
|
||||
version = "1.1.62"
|
||||
version = "1.1.61"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"clap",
|
||||
@@ -5211,6 +5275,7 @@ dependencies = [
|
||||
name = "nym-client-core-gateways-storage"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"cosmrs",
|
||||
"nym-crypto",
|
||||
@@ -5229,6 +5294,7 @@ dependencies = [
|
||||
name = "nym-client-core-surb-storage"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"dashmap",
|
||||
"nym-crypto",
|
||||
@@ -5371,7 +5437,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-credential-proxy"
|
||||
version = "0.1.7"
|
||||
version = "0.1.8"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
@@ -5389,6 +5455,7 @@ dependencies = [
|
||||
"nym-credentials-interface",
|
||||
"nym-crypto",
|
||||
"nym-ecash-contract-common",
|
||||
"nym-ecash-signer-check",
|
||||
"nym-http-api-common",
|
||||
"nym-network-defaults",
|
||||
"nym-validator-client",
|
||||
@@ -5439,6 +5506,7 @@ dependencies = [
|
||||
name = "nym-credential-storage"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"bincode",
|
||||
"log",
|
||||
@@ -5553,6 +5621,7 @@ dependencies = [
|
||||
"generic-array 0.14.7",
|
||||
"hkdf",
|
||||
"hmac",
|
||||
"jwt-simple",
|
||||
"nym-pemstore",
|
||||
"nym-sphinx-types",
|
||||
"rand 0.8.5",
|
||||
@@ -5641,14 +5710,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"
|
||||
@@ -5808,6 +5869,7 @@ dependencies = [
|
||||
name = "nym-gateway-stats-storage"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"nym-node-metrics",
|
||||
"nym-sphinx",
|
||||
"nym-statistics-common",
|
||||
@@ -5823,6 +5885,7 @@ dependencies = [
|
||||
name = "nym-gateway-storage"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"bincode",
|
||||
"defguard_wireguard_rs",
|
||||
@@ -6160,7 +6223,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-requester"
|
||||
version = "1.1.63"
|
||||
version = "1.1.62"
|
||||
dependencies = [
|
||||
"addr",
|
||||
"anyhow",
|
||||
@@ -6210,7 +6273,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node"
|
||||
version = "1.18.0"
|
||||
version = "1.17.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
@@ -6235,7 +6298,7 @@ dependencies = [
|
||||
"hkdf",
|
||||
"human-repr",
|
||||
"humantime-serde",
|
||||
"indicatif 0.17.11",
|
||||
"indicatif",
|
||||
"ipnetwork",
|
||||
"lioness",
|
||||
"nym-bin-common",
|
||||
@@ -6351,7 +6414,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node-status-api"
|
||||
version = "3.3.0"
|
||||
version = "3.3.1"
|
||||
dependencies = [
|
||||
"ammonia",
|
||||
"anyhow",
|
||||
@@ -6673,9 +6736,29 @@ 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.62"
|
||||
version = "1.1.61"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"clap",
|
||||
@@ -6816,10 +6899,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",
|
||||
]
|
||||
|
||||
@@ -7109,6 +7194,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"
|
||||
@@ -7390,7 +7491,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nymvisor"
|
||||
version = "0.1.27"
|
||||
version = "0.1.26"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -7453,6 +7554,7 @@ dependencies = [
|
||||
name = "nyxd-scraper"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"const_format",
|
||||
"cosmrs",
|
||||
@@ -7676,6 +7778,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"
|
||||
@@ -8244,7 +8358,7 @@ dependencies = [
|
||||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls 0.23.29",
|
||||
"socket2",
|
||||
"socket2 0.5.10",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -8281,9 +8395,9 @@ dependencies = [
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2",
|
||||
"socket2 0.5.10",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8656,6 +8770,7 @@ dependencies = [
|
||||
"pkcs1",
|
||||
"pkcs8",
|
||||
"rand_core 0.6.4",
|
||||
"sha2 0.10.9",
|
||||
"signature",
|
||||
"spki",
|
||||
"subtle 2.6.1",
|
||||
@@ -8767,7 +8882,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.15",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9489,9 +9604,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"
|
||||
@@ -9567,6 +9682,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"
|
||||
@@ -9935,6 +10060,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"
|
||||
@@ -10051,7 +10189,7 @@ dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"once_cell",
|
||||
"rustix 1.0.8",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10175,11 +10313,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",
|
||||
@@ -10359,9 +10497,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",
|
||||
@@ -10372,10 +10510,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]]
|
||||
@@ -10409,7 +10547,7 @@ dependencies = [
|
||||
"postgres-protocol",
|
||||
"postgres-types",
|
||||
"rand 0.9.2",
|
||||
"socket2",
|
||||
"socket2 0.5.10",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"whoami",
|
||||
@@ -10585,7 +10723,7 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"pin-project",
|
||||
"prost",
|
||||
"socket2",
|
||||
"socket2 0.5.10",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tower 0.4.13",
|
||||
@@ -10736,7 +10874,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",
|
||||
@@ -11432,6 +11570,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"
|
||||
@@ -11732,7 +11879,7 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11825,6 +11972,17 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.4"
|
||||
|
||||
+8
-8
@@ -53,7 +53,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 +95,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",
|
||||
@@ -123,7 +122,7 @@ 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",
|
||||
"nyx-chain-watcher",
|
||||
@@ -182,7 +181,7 @@ homepage = "https://nymtech.net"
|
||||
documentation = "https://nymtech.net"
|
||||
edition = "2021"
|
||||
license = "Apache-2.0"
|
||||
rust-version = "1.80"
|
||||
rust-version = "1.81"
|
||||
readme = "README.md"
|
||||
|
||||
[workspace.dependencies]
|
||||
@@ -223,7 +222,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 +269,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 +326,14 @@ 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"
|
||||
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"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.62"
|
||||
version = "1.1.61"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.62"
|
||||
version = "1.1.61"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
|
||||
@@ -3,6 +3,7 @@ name = "nym-client-core-gateways-storage"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -26,6 +27,7 @@ features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate", "time"]
|
||||
optional = true
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
sqlx = { workspace = true, features = [
|
||||
"runtime-tokio-rustls",
|
||||
|
||||
@@ -2,23 +2,30 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
#[cfg(feature = "fs-gateways-storage")]
|
||||
{
|
||||
use anyhow::Context;
|
||||
use sqlx::{Connection, SqliteConnection};
|
||||
use std::env;
|
||||
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let out_dir = env::var("OUT_DIR")?;
|
||||
let database_path = format!("{out_dir}/gateways-storage-example.sqlite");
|
||||
|
||||
// remove the db file if it already existed from previous build
|
||||
// in case it was from a different branch
|
||||
if std::fs::exists(&database_path)? {
|
||||
std::fs::remove_file(&database_path)?;
|
||||
}
|
||||
|
||||
let mut conn = SqliteConnection::connect(&format!("sqlite://{database_path}?mode=rwc"))
|
||||
.await
|
||||
.expect("Failed to create SQLx database connection");
|
||||
.context("Failed to create SQLx database connection")?;
|
||||
|
||||
sqlx::migrate!("./fs_gateways_migrations")
|
||||
.run(&mut conn)
|
||||
.await
|
||||
.expect("Failed to perform SQLx migrations");
|
||||
.context("Failed to perform SQLx migrations")?;
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
println!("cargo:rustc-env=DATABASE_URL=sqlite://{}", &database_path);
|
||||
@@ -28,4 +35,6 @@ async fn main() {
|
||||
// not a valid windows path... but hey, it works...
|
||||
println!("cargo:rustc-env=DATABASE_URL=sqlite:///{}", &database_path);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -30,6 +30,7 @@ optional = true
|
||||
path = "../../../sqlx-pool-guard"
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
sqlx = { workspace = true, features = [
|
||||
"runtime-tokio-rustls",
|
||||
|
||||
@@ -2,23 +2,24 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
#[cfg(feature = "fs-surb-storage")]
|
||||
{
|
||||
use anyhow::Context;
|
||||
use sqlx::{Connection, SqliteConnection};
|
||||
use std::env;
|
||||
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let out_dir = env::var("OUT_DIR")?;
|
||||
let database_path = format!("{out_dir}/fs-surbs-example.sqlite");
|
||||
|
||||
let mut conn = SqliteConnection::connect(&format!("sqlite://{database_path}?mode=rwc"))
|
||||
.await
|
||||
.expect("Failed to create SQLx database connection");
|
||||
.context("Failed to create SQLx database connection")?;
|
||||
|
||||
sqlx::migrate!("./fs_surbs_migrations")
|
||||
.run(&mut conn)
|
||||
.await
|
||||
.expect("Failed to perform SQLx migrations");
|
||||
.context("Failed to perform SQLx migrations")?;
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
println!("cargo:rustc-env=DATABASE_URL=sqlite://{}", &database_path);
|
||||
@@ -28,4 +29,6 @@ async fn main() {
|
||||
// not a valid windows path... but hey, it works...
|
||||
println!("cargo:rustc-env=DATABASE_URL=sqlite:///{}", &database_path);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+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(
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
use clap::{Args, Subcommand};
|
||||
|
||||
pub mod ecash;
|
||||
pub mod nyx;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||
@@ -16,4 +17,6 @@ pub struct Internal {
|
||||
pub enum InternalCommands {
|
||||
/// Ecash related internal commands
|
||||
Ecash(ecash::InternalEcash),
|
||||
|
||||
Nyx(nyx::InternalNyx),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::SigningClient;
|
||||
use anyhow::bail;
|
||||
use clap::Parser;
|
||||
use nym_mixnet_contract_common::nym_node::Role;
|
||||
use nym_mixnet_contract_common::reward_params::NodeRewardingParameters;
|
||||
use nym_mixnet_contract_common::{
|
||||
EpochRewardedSet, EpochState, NodeId, RewardingParams, RoleAssignment,
|
||||
};
|
||||
use nym_validator_client::nyxd::contract_traits::mixnet_query_client::MixnetQueryClientExt;
|
||||
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, MixnetSigningClient};
|
||||
use rand::prelude::*;
|
||||
use rand::thread_rng;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {}
|
||||
|
||||
fn choose_new_nodes(
|
||||
params: &RewardingParams,
|
||||
rewarded_set: &EpochRewardedSet,
|
||||
role: Role,
|
||||
) -> Vec<NodeId> {
|
||||
let mut rng = thread_rng();
|
||||
|
||||
match role {
|
||||
Role::EntryGateway => rewarded_set
|
||||
.assignment
|
||||
.entry_gateways
|
||||
.choose_multiple(&mut rng, params.rewarded_set.entry_gateways as usize)
|
||||
.copied()
|
||||
.collect(),
|
||||
Role::Layer1 => rewarded_set
|
||||
.assignment
|
||||
.layer1
|
||||
.choose_multiple(&mut rng, params.rewarded_set.mixnodes as usize / 3)
|
||||
.copied()
|
||||
.collect(),
|
||||
Role::Layer2 => rewarded_set
|
||||
.assignment
|
||||
.layer2
|
||||
.choose_multiple(&mut rng, params.rewarded_set.mixnodes as usize / 3)
|
||||
.copied()
|
||||
.collect(),
|
||||
Role::Layer3 => rewarded_set
|
||||
.assignment
|
||||
.layer3
|
||||
.choose_multiple(&mut rng, params.rewarded_set.mixnodes as usize / 3)
|
||||
.copied()
|
||||
.collect(),
|
||||
Role::ExitGateway => rewarded_set
|
||||
.assignment
|
||||
.exit_gateways
|
||||
.choose_multiple(&mut rng, params.rewarded_set.exit_gateways as usize)
|
||||
.copied()
|
||||
.collect(),
|
||||
Role::Standby => rewarded_set
|
||||
.assignment
|
||||
.standby
|
||||
.choose_multiple(&mut rng, params.rewarded_set.standby as usize)
|
||||
.copied()
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn force_advance_epoch(_: Args, client: SigningClient) -> anyhow::Result<()> {
|
||||
let current_epoch = client.get_current_interval_details().await?;
|
||||
let epoch_status = client.get_current_epoch_status().await?;
|
||||
if epoch_status.being_advanced_by.as_str() != client.address().to_string() {
|
||||
bail!(
|
||||
"this client is not authorised to perform any epoch operations. we need {}",
|
||||
client.address()
|
||||
)
|
||||
}
|
||||
|
||||
let rewarding_params = client.get_rewarding_parameters().await?;
|
||||
let current_rewarded_set = client.get_rewarded_set().await?;
|
||||
|
||||
if !current_epoch.is_current_epoch_over {
|
||||
println!("the current epoch is not over yet - there's nothing to do")
|
||||
}
|
||||
|
||||
// is this most efficient? no. but it's simple
|
||||
loop {
|
||||
let epoch_status = client.get_current_epoch_status().await?;
|
||||
|
||||
match epoch_status.state {
|
||||
EpochState::InProgress => break,
|
||||
EpochState::Rewarding { final_node_id, .. } => {
|
||||
println!("rewarding {final_node_id} with big fat 0...");
|
||||
client
|
||||
.reward_node(
|
||||
final_node_id,
|
||||
NodeRewardingParameters::new(Default::default(), Default::default()),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
EpochState::ReconcilingEvents => {
|
||||
println!("trying to reconcile events...");
|
||||
client.reconcile_epoch_events(None, None).await?;
|
||||
}
|
||||
EpochState::RoleAssignment { next } => {
|
||||
let nodes = choose_new_nodes(&rewarding_params, ¤t_rewarded_set, next);
|
||||
println!("assigning {nodes:?} as {next}");
|
||||
|
||||
client
|
||||
.assign_roles(RoleAssignment { role: next, nodes }, None)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{Args, Subcommand};
|
||||
|
||||
pub mod force_advance_epoch;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||
pub struct InternalNyx {
|
||||
#[clap(subcommand)]
|
||||
pub command: InternalNyxCommands,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum InternalNyxCommands {
|
||||
/// Attempt to force advance the current epoch
|
||||
ForceAdvanceEpoch(force_advance_epoch::Args),
|
||||
}
|
||||
@@ -3,6 +3,7 @@ name = "nym-credential-storage"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -33,6 +34,7 @@ features = ["rt-multi-thread", "net", "signal", "fs"]
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
sqlx = { workspace = true, features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
|
||||
@@ -3,22 +3,29 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
use anyhow::Context;
|
||||
use sqlx::{Connection, SqliteConnection};
|
||||
use std::env;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let out_dir = env::var("OUT_DIR")?;
|
||||
let database_path = format!("{out_dir}/coconut-credential-example.sqlite");
|
||||
|
||||
// remove the db file if it already existed from previous build
|
||||
// in case it was from a different branch
|
||||
if std::fs::exists(&database_path)? {
|
||||
std::fs::remove_file(&database_path)?;
|
||||
}
|
||||
|
||||
let mut conn = SqliteConnection::connect(&format!("sqlite://{database_path}?mode=rwc"))
|
||||
.await
|
||||
.expect("Failed to create SQLx database connection");
|
||||
.context("Failed to create SQLx database connection")?;
|
||||
|
||||
sqlx::migrate!("./migrations")
|
||||
.run(&mut conn)
|
||||
.await
|
||||
.expect("Failed to perform SQLx migrations");
|
||||
.context("Failed to perform SQLx migrations")?;
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
println!("cargo:rustc-env=DATABASE_URL=sqlite://{}", &database_path);
|
||||
@@ -27,4 +34,6 @@ async fn main() {
|
||||
// for some strange reason we need to add a leading `/` to the windows path even though it's
|
||||
// not a valid windows path... but hey, it works...
|
||||
println!("cargo:rustc-env=DATABASE_URL=sqlite:///{}", &database_path);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ impl IssuanceTicketBook {
|
||||
signing_request.withdrawal_request.clone(),
|
||||
self.deposit_id,
|
||||
request_signature,
|
||||
signing_request.ecash_pub_key.clone(),
|
||||
signing_request.ecash_pub_key,
|
||||
signing_request.expiration_date,
|
||||
signing_request.ticketbook_type,
|
||||
)
|
||||
|
||||
@@ -18,6 +18,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 +40,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -15,6 +15,9 @@ use nym_validator_client::ecash::models::EcashSignerStatusResponse;
|
||||
use nym_validator_client::models::{
|
||||
ChainBlocksStatusResponse, ChainStatusResponse, SignerInformationResponse,
|
||||
};
|
||||
use nym_validator_client::nyxd::contract_traits::dkg_query_client::{
|
||||
ContractVKShare, DealerDetails, Epoch,
|
||||
};
|
||||
|
||||
mod client_check;
|
||||
pub mod error;
|
||||
@@ -48,7 +51,22 @@ pub async fn check_signers(
|
||||
check_signers_with_client(&client).await
|
||||
}
|
||||
|
||||
pub struct DkgDetails {
|
||||
pub dkg_epoch: Epoch,
|
||||
pub threshold: Option<u64>,
|
||||
pub network_dealers: Vec<DealerDetails>,
|
||||
pub submitted_shared: HashMap<u64, ContractVKShare>,
|
||||
}
|
||||
|
||||
pub async fn check_signers_with_client<C>(client: &C) -> Result<SignersTestResult, SignerCheckError>
|
||||
where
|
||||
C: DkgQueryClient + Sync,
|
||||
{
|
||||
let dkg_details = dkg_details_with_client(client).await?;
|
||||
check_known_dealers(dkg_details).await
|
||||
}
|
||||
|
||||
pub async fn dkg_details_with_client<C>(client: &C) -> Result<DkgDetails, SignerCheckError>
|
||||
where
|
||||
C: DkgQueryClient + Sync,
|
||||
{
|
||||
@@ -79,16 +97,31 @@ where
|
||||
.map(|share| (share.node_index, share))
|
||||
.collect();
|
||||
|
||||
Ok(DkgDetails {
|
||||
dkg_epoch,
|
||||
threshold,
|
||||
network_dealers: dealers,
|
||||
submitted_shared: shares,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn check_known_dealers(
|
||||
dkg_details: DkgDetails,
|
||||
) -> Result<SignersTestResult, SignerCheckError> {
|
||||
// 6. for each dealer attempt to perform the checks
|
||||
let results = dealers
|
||||
let results = dkg_details
|
||||
.network_dealers
|
||||
.into_iter()
|
||||
.map(|d| {
|
||||
let share = shares.get(&d.assigned_index);
|
||||
check_client(d, dkg_epoch.epoch_id, share)
|
||||
let share = dkg_details.submitted_shared.get(&d.assigned_index);
|
||||
check_client(d, dkg_details.dkg_epoch.epoch_id, share)
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>()
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
|
||||
Ok(SignersTestResult { threshold, results })
|
||||
Ok(SignersTestResult {
|
||||
threshold: dkg_details.threshold,
|
||||
results,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -89,7 +89,7 @@ mod tests {
|
||||
.unwrap();
|
||||
let blind_sig = issue(
|
||||
keypair.secret_key(),
|
||||
sig_req.ecash_pub_key.clone(),
|
||||
sig_req.ecash_pub_key,
|
||||
&sig_req.withdrawal_request,
|
||||
expiration_date.ecash_unix_timestamp(),
|
||||
issuance.ticketbook_type().encode(),
|
||||
|
||||
@@ -7,6 +7,7 @@ homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
sqlx = { workspace = true, features = [
|
||||
@@ -27,6 +28,7 @@ nym-statistics-common = { path = "../statistics" }
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
sqlx = { workspace = true, features = [
|
||||
"runtime-tokio-rustls",
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use anyhow::Context;
|
||||
use sqlx::{Connection, SqliteConnection};
|
||||
use std::env;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let out_dir = env::var("OUT_DIR")?;
|
||||
let database_path = format!("{out_dir}/gateway-stats-example.sqlite");
|
||||
|
||||
// remove the db file if it already existed from previous build
|
||||
// in case it was from a different branch
|
||||
if std::fs::exists(&database_path)? {
|
||||
std::fs::remove_file(&database_path)?;
|
||||
}
|
||||
|
||||
let mut conn = SqliteConnection::connect(&format!("sqlite://{database_path}?mode=rwc"))
|
||||
.await
|
||||
.expect("Failed to create SQLx database connection");
|
||||
.context("Failed to create SQLx database connection")?;
|
||||
|
||||
sqlx::migrate!("./migrations")
|
||||
.run(&mut conn)
|
||||
.await
|
||||
.expect("Failed to perform SQLx migrations");
|
||||
.context("Failed to perform SQLx migrations")?;
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
println!("cargo:rustc-env=DATABASE_URL=sqlite://{}", &database_path);
|
||||
@@ -25,4 +32,6 @@ async fn main() {
|
||||
// for some strange reason we need to add a leading `/` to the windows path even though it's
|
||||
// not a valid windows path... but hey, it works...
|
||||
println!("cargo:rustc-env=DATABASE_URL=sqlite:///{}", &database_path);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
@@ -31,6 +32,7 @@ nym-gateway-requests = { path = "../gateway-requests" }
|
||||
nym-sphinx = { path = "../nymsphinx" }
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
sqlx = { workspace = true, features = [
|
||||
"runtime-tokio-rustls",
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use anyhow::Context;
|
||||
use sqlx::{Connection, SqliteConnection};
|
||||
use std::env;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let out_dir = env::var("OUT_DIR")?;
|
||||
let database_path = format!("{out_dir}/gateway-example.sqlite");
|
||||
|
||||
// remove the db file if it already existed from previous build
|
||||
// in case it was from a different branch
|
||||
if std::fs::exists(&database_path)? {
|
||||
std::fs::remove_file(&database_path)?;
|
||||
}
|
||||
|
||||
let mut conn = SqliteConnection::connect(&format!("sqlite://{database_path}?mode=rwc"))
|
||||
.await
|
||||
.expect("Failed to create SQLx database connection");
|
||||
.context("Failed to create SQLx database connection")?;
|
||||
|
||||
sqlx::migrate!("./migrations")
|
||||
.run(&mut conn)
|
||||
.await
|
||||
.expect("Failed to perform SQLx migrations");
|
||||
.context("Failed to perform SQLx migrations")?;
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
println!("cargo:rustc-env=DATABASE_URL=sqlite://{}", &database_path);
|
||||
@@ -25,4 +32,6 @@ async fn main() {
|
||||
// for some strange reason we need to add a leading `/` to the windows path even though it's
|
||||
// not a valid windows path... but hey, it works...
|
||||
println!("cargo:rustc-env=DATABASE_URL=sqlite:///{}", &database_path);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -151,6 +151,7 @@ use reqwest::{RequestBuilder, Response};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Display;
|
||||
use std::net::IpAddr;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::time::Duration;
|
||||
use thiserror::Error;
|
||||
@@ -158,6 +159,7 @@ use tracing::{debug, instrument, warn};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use std::net::SocketAddr;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[cfg(feature = "tunneling")]
|
||||
@@ -412,11 +414,14 @@ impl ClientBuilder {
|
||||
//
|
||||
// I am going to leave these here anyways so that removing a decompression method
|
||||
// from the features list will throw an error if it is not also removed here.
|
||||
warn!("FORCING IPv4 CONNECTIONS");
|
||||
|
||||
reqwest::ClientBuilder::new()
|
||||
.gzip(true)
|
||||
.deflate(true)
|
||||
.brotli(true)
|
||||
.zstd(true)
|
||||
.local_address(IpAddr::from_str("0.0.0.0:0").unwrap())
|
||||
};
|
||||
|
||||
ClientBuilder {
|
||||
|
||||
@@ -319,9 +319,9 @@ mod tests {
|
||||
let sk = grp.random_scalar();
|
||||
let sk_user = SecretKeyUser { sk };
|
||||
let pk_user = sk_user.public_key();
|
||||
public_keys.push(pk_user.clone());
|
||||
public_keys.push(pk_user);
|
||||
}
|
||||
public_keys.push(user_keypair.public_key().clone());
|
||||
public_keys.push(user_keypair.public_key());
|
||||
|
||||
let (req, req_info) =
|
||||
withdrawal_request(user_keypair.secret_key(), expiration_date, t_type).unwrap();
|
||||
@@ -462,9 +462,9 @@ mod tests {
|
||||
let sk = grp.random_scalar();
|
||||
let sk_user = SecretKeyUser { sk };
|
||||
let pk_user = sk_user.public_key();
|
||||
public_keys.push(pk_user.clone());
|
||||
public_keys.push(pk_user);
|
||||
}
|
||||
public_keys.push(user_keypair.public_key().clone());
|
||||
public_keys.push(user_keypair.public_key());
|
||||
|
||||
let (req, req_info) =
|
||||
withdrawal_request(user_keypair.secret_key(), expiration_date, t_type).unwrap();
|
||||
|
||||
@@ -401,7 +401,7 @@ impl Bytable for SecretKeyUser {
|
||||
|
||||
impl Base58 for SecretKeyUser {}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy, Serialize, Deserialize)]
|
||||
pub struct PublicKeyUser {
|
||||
pub(crate) pk: G1Projective,
|
||||
}
|
||||
@@ -554,7 +554,7 @@ impl KeyPairUser {
|
||||
}
|
||||
|
||||
pub fn public_key(&self) -> PublicKeyUser {
|
||||
self.public_key.clone()
|
||||
self.public_key
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -7,6 +7,7 @@ homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -36,5 +37,6 @@ url.workspace = true
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
|
||||
@@ -1,22 +1,30 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
use sqlx::{Connection, SqliteConnection};
|
||||
use std::env;
|
||||
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let out_dir = env::var("OUT_DIR")?;
|
||||
let database_path = format!("{out_dir}/scraper-example.sqlite");
|
||||
|
||||
// remove the db file if it already existed from previous build
|
||||
// in case it was from a different branch
|
||||
if std::fs::exists(&database_path)? {
|
||||
std::fs::remove_file(&database_path)?;
|
||||
}
|
||||
|
||||
let mut conn = SqliteConnection::connect(&format!("sqlite://{database_path}?mode=rwc"))
|
||||
.await
|
||||
.expect("Failed to create SQLx database connection");
|
||||
.context("Failed to create SQLx database connection")?;
|
||||
|
||||
sqlx::migrate!("./sql_migrations")
|
||||
.run(&mut conn)
|
||||
.await
|
||||
.expect("Failed to perform SQLx migrations");
|
||||
.context("Failed to perform SQLx migrations")?;
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
println!("cargo:rustc-env=DATABASE_URL=sqlite://{}", &database_path);
|
||||
@@ -25,4 +33,6 @@ async fn main() {
|
||||
// for some strange reason we need to add a leading `/` to the windows path even though it's
|
||||
// not a valid windows path... but hey, it works...
|
||||
println!("cargo:rustc-env=DATABASE_URL=sqlite:///{}", &database_path);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
@@ -130,12 +130,7 @@ impl VerlocMeasurement {
|
||||
let variance_micros = data
|
||||
.iter()
|
||||
.map(|&value| {
|
||||
// make sure we don't underflow
|
||||
let diff = if mean > value {
|
||||
mean - value
|
||||
} else {
|
||||
value - mean
|
||||
};
|
||||
let diff = mean.abs_diff(value);
|
||||
// we don't need nanos precision
|
||||
let diff_micros = diff.as_micros();
|
||||
diff_micros * diff_micros
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
[package]
|
||||
name = "nym-api"
|
||||
license = "GPL-3.0"
|
||||
version = "1.1.65"
|
||||
version = "1.1.64"
|
||||
authors.workspace = true
|
||||
edition = "2021"
|
||||
rust-version.workspace = true
|
||||
|
||||
+15
-10
@@ -1,16 +1,20 @@
|
||||
use anyhow::Context;
|
||||
use sqlx::{Connection, FromRow, SqliteConnection};
|
||||
use std::env;
|
||||
|
||||
const SQLITE_DB_FILENAME: &str = "nym-api-example.sqlite";
|
||||
|
||||
// it's fine if compilation fails
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::expect_used)]
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let out_dir = env::var("OUT_DIR")?;
|
||||
let database_path = format!("{out_dir}/{SQLITE_DB_FILENAME}");
|
||||
|
||||
// remove the db file if it already existed from previous build
|
||||
// in case it was from a different branch
|
||||
if std::fs::exists(&database_path)? {
|
||||
std::fs::remove_file(&database_path)?;
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
write_db_path_to_file(&out_dir, SQLITE_DB_FILENAME)
|
||||
.await
|
||||
@@ -18,12 +22,12 @@ async fn main() {
|
||||
|
||||
let mut conn = SqliteConnection::connect(&format!("sqlite://{database_path}?mode=rwc"))
|
||||
.await
|
||||
.expect("Failed to create SQLx database connection");
|
||||
.context("Failed to create SQLx database connection")?;
|
||||
|
||||
sqlx::migrate!("./migrations")
|
||||
.run(&mut conn)
|
||||
.await
|
||||
.expect("Failed to perform SQLx migrations");
|
||||
.context("Failed to perform SQLx migrations")?;
|
||||
|
||||
#[derive(FromRow)]
|
||||
struct Exists {
|
||||
@@ -33,8 +37,7 @@ async fn main() {
|
||||
// check if it was already run
|
||||
let res: Exists = sqlx::query_as("SELECT EXISTS (SELECT 1 FROM v3_migration_info) AS 'exists'")
|
||||
.fetch_one(&mut conn)
|
||||
.await
|
||||
.unwrap();
|
||||
.await?;
|
||||
|
||||
let already_run = res.exists;
|
||||
|
||||
@@ -58,7 +61,7 @@ async fn main() {
|
||||
)
|
||||
.execute(&mut conn)
|
||||
.await
|
||||
.expect("failed to update post v3 migration tables");
|
||||
.context("failed to update post v3 migration tables")?;
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
@@ -68,6 +71,8 @@ async fn main() {
|
||||
// for some strange reason we need to add a leading `/` to the windows path even though it's
|
||||
// not a valid windows path... but hey, it works...
|
||||
println!("cargo:rustc-env=DATABASE_URL=sqlite:///{}", &database_path);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// use `./enter_db.sh` to inspect DB
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -50,7 +50,7 @@ impl CredentialRequest for BlindSignRequestBody {
|
||||
}
|
||||
|
||||
fn ecash_pubkey(&self) -> PublicKeyUser {
|
||||
self.ecash_pubkey.clone()
|
||||
self.ecash_pubkey
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ pub(crate) fn blind_sign<C: CredentialRequest>(
|
||||
) -> Result<BlindedSignature, EcashError> {
|
||||
Ok(nym_compact_ecash::scheme::withdrawal::issue(
|
||||
signing_key,
|
||||
request.ecash_pubkey().clone(),
|
||||
request.ecash_pubkey(),
|
||||
request.withdrawal_request(),
|
||||
request.expiration_date_timestamp(),
|
||||
request.ticketbook_type(),
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
[package]
|
||||
name = "nym-credential-proxy"
|
||||
version = "0.2.0"
|
||||
version = "0.1.8"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -50,14 +51,19 @@ nym-validator-client = { path = "../../common/client-libs/validator-client" }
|
||||
nym-network-defaults = { path = "../../common/network-defaults" }
|
||||
|
||||
nym-credential-proxy-requests = { path = "../nym-credential-proxy-requests", features = ["openapi"] }
|
||||
nym-ecash-signer-check = { path = "../../common/ecash-signer-check" }
|
||||
|
||||
[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"] }
|
||||
|
||||
[features]
|
||||
default = ["cors"]
|
||||
cors = ["tower-http"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1,22 +1,31 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
use sqlx::{Connection, SqliteConnection};
|
||||
use std::env;
|
||||
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let out_dir = env::var("OUT_DIR")?;
|
||||
let database_path = format!("{out_dir}/nym-credential-proxy-example.sqlite");
|
||||
|
||||
// remove the db file if it already existed from previous build
|
||||
// in case it was from a different branch
|
||||
if std::fs::exists(&database_path)? {
|
||||
std::fs::remove_file(&database_path)?;
|
||||
}
|
||||
|
||||
let mut conn = SqliteConnection::connect(&format!("sqlite://{database_path}?mode=rwc"))
|
||||
.await
|
||||
.expect("Failed to create SQLx database connection");
|
||||
.context("Failed to create SQLx database connection")?;
|
||||
|
||||
sqlx::migrate!("./migrations")
|
||||
.run(&mut conn)
|
||||
.await
|
||||
.expect("Failed to perform SQLx migrations");
|
||||
.context("Failed to perform SQLx migrations")?;
|
||||
|
||||
println!("cargo:rustc-env=DATABASE_URL=sqlite://{}", &database_path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
CREATE TABLE ecash_deposit
|
||||
(
|
||||
-- id assigned [by the contract] to the deposit
|
||||
deposit_id INTEGER PRIMARY KEY NOT NULL,
|
||||
|
||||
-- associated tx hash
|
||||
deposit_tx_hash TEXT NOT NULL,
|
||||
|
||||
-- indication of when the deposit request has been created
|
||||
-- (so that based on block timestamp we could potentially determine latency)
|
||||
requested_on TIMESTAMP WITHOUT TIME ZONE NOT NULL,
|
||||
|
||||
-- the amount put in the deposit (informative, as we expect this to change in the future)
|
||||
deposit_amount TEXT NOT NULL,
|
||||
|
||||
-- the private key generated for the purposes of the deposit (the public component has been put in the transaction)
|
||||
ed25519_deposit_private_key BLOB NOT NULL
|
||||
);
|
||||
|
||||
|
||||
INSERT INTO ecash_deposit(deposit_id, deposit_tx_hash, requested_on, deposit_amount, ed25519_deposit_private_key)
|
||||
SELECT deposit_id, deposit_tx_hash, requested_on, deposit_amount, ed25519_deposit_private_key
|
||||
FROM ticketbook_deposit;
|
||||
|
||||
|
||||
CREATE TABLE ecash_deposit_usage
|
||||
(
|
||||
deposit_id INTEGER PRIMARY KEY REFERENCES ecash_deposit (deposit_id),
|
||||
ticketbooks_requested_on TIMESTAMP WITHOUT TIME ZONE NOT NULL,
|
||||
client_pubkey BLOB NOT NULL,
|
||||
request_uuid TEXT UNIQUE NOT NULL,
|
||||
|
||||
-- this has to be improved later on to resume issuance or something, but for now it's fine
|
||||
ticketbook_request_error TEXT
|
||||
);
|
||||
|
||||
INSERT INTO ecash_deposit_usage(deposit_id, ticketbooks_requested_on, client_pubkey, request_uuid)
|
||||
SELECT deposit_id, 0, client_pubkey, request_uuid
|
||||
FROM ticketbook_deposit;
|
||||
|
||||
|
||||
CREATE TABLE partial_blinded_wallet_new
|
||||
(
|
||||
corresponding_deposit INTEGER NOT NULL REFERENCES ecash_deposit_usage (deposit_id),
|
||||
epoch_id INTEGER NOT NULL,
|
||||
expiration_date DATE NOT NULL,
|
||||
node_id INTEGER NOT NULL,
|
||||
created TIMESTAMP WITHOUT TIME ZONE NOT NULL,
|
||||
blinded_signature BLOB NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE partial_blinded_wallet_failure_new
|
||||
(
|
||||
corresponding_deposit INTEGER NOT NULL REFERENCES ecash_deposit_usage (deposit_id),
|
||||
epoch_id INTEGER NOT NULL,
|
||||
expiration_date DATE NOT NULL,
|
||||
node_id INTEGER NOT NULL,
|
||||
created TIMESTAMP WITHOUT TIME ZONE NOT NULL,
|
||||
failure_message TEXT NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO partial_blinded_wallet_new
|
||||
SELECT *
|
||||
FROM partial_blinded_wallet;
|
||||
INSERT INTO partial_blinded_wallet_failure_new
|
||||
SELECT *
|
||||
FROM partial_blinded_wallet_failure;
|
||||
|
||||
DROP TABLE partial_blinded_wallet;
|
||||
DROP TABLE partial_blinded_wallet_failure;
|
||||
DROP TABLE ticketbook_deposit;
|
||||
|
||||
ALTER TABLE partial_blinded_wallet_new
|
||||
RENAME TO partial_blinded_wallet;
|
||||
ALTER TABLE partial_blinded_wallet_failure_new
|
||||
RENAME TO partial_blinded_wallet_failure;
|
||||
@@ -10,6 +10,7 @@ use std::fs::create_dir_all;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::OnceLock;
|
||||
use std::time::Duration;
|
||||
use tracing::info;
|
||||
|
||||
fn pretty_build_info_static() -> &'static str {
|
||||
@@ -64,6 +65,23 @@ pub struct Cli {
|
||||
)]
|
||||
pub(crate) max_concurrent_deposits: usize,
|
||||
|
||||
/// Specify the size of the deposits buffer the credential proxy should have available at any time
|
||||
/// (default: 256)
|
||||
#[clap(
|
||||
long,
|
||||
env = "NYM_CREDENTIAL_PROXY_DEPOSITS_BUFFER",
|
||||
default_value_t = 256
|
||||
)]
|
||||
pub(crate) deposits_buffer_size: usize,
|
||||
|
||||
#[clap(
|
||||
long,
|
||||
env = "NYM_CREDENTIAL_PROXY_QUORUM_CHECK_INTERVAL",
|
||||
default_value = "5m",
|
||||
value_parser = humantime::parse_duration
|
||||
)]
|
||||
pub(crate) quorum_check_interval: Duration,
|
||||
|
||||
#[clap(long, env = "NYM_CREDENTIAL_PROXY_PERSISTENT_STORAGE_STORAGE")]
|
||||
pub(crate) persistent_storage_path: Option<PathBuf>,
|
||||
}
|
||||
@@ -90,10 +108,7 @@ impl Cli {
|
||||
create_dir_all(parent).unwrap();
|
||||
}
|
||||
|
||||
info!(
|
||||
"setting the storage path path to {}",
|
||||
default_path.display()
|
||||
);
|
||||
info!("setting the storage path to {}", default_path.display());
|
||||
|
||||
default_path
|
||||
})
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// Copyright 2024 Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::deposit_maker::{DepositRequest, DepositResponse};
|
||||
use crate::error::VpnApiError;
|
||||
use crate::error::CredentialProxyError;
|
||||
use crate::http::state::ApiState;
|
||||
use crate::storage::models::BlindedShares;
|
||||
use futures::{stream, StreamExt};
|
||||
@@ -11,52 +10,20 @@ use nym_credential_proxy_requests::api::v1::ticketbook::models::{
|
||||
TicketbookWalletSharesResponse, WalletShare, WebhookTicketbookWalletShares,
|
||||
WebhookTicketbookWalletSharesRequest,
|
||||
};
|
||||
use nym_credentials::IssuanceTicketBook;
|
||||
use nym_credentials_interface::Base58;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_validator_client::ecash::BlindSignRequestBody;
|
||||
use nym_validator_client::nyxd::Coin;
|
||||
use rand::rngs::OsRng;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
use tokio::sync::{oneshot, Mutex};
|
||||
use tokio::time::{timeout, Instant};
|
||||
use tracing::{debug, error, info, instrument, warn};
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::time::timeout;
|
||||
use tracing::{debug, error, info, instrument};
|
||||
use uuid::Uuid;
|
||||
|
||||
// use the same type alias as our contract without importing the whole thing just for this single line
|
||||
pub type NodeId = u64;
|
||||
|
||||
#[instrument(skip(state), ret, err(Display))]
|
||||
async fn make_deposit(
|
||||
state: &ApiState,
|
||||
pub_key: ed25519::PublicKey,
|
||||
deposit_amount: &Coin,
|
||||
) -> Result<DepositResponse, VpnApiError> {
|
||||
let start = Instant::now();
|
||||
let (on_done_tx, on_done_rx) = oneshot::channel();
|
||||
let request = DepositRequest::new(pub_key, deposit_amount, on_done_tx);
|
||||
state.request_deposit(request).await;
|
||||
|
||||
let time_taken = start.elapsed();
|
||||
let formatted = humantime::format_duration(time_taken);
|
||||
|
||||
let Ok(deposit_response) = on_done_rx.await else {
|
||||
error!("failed to receive deposit response: the corresponding sender channel got dropped by the DepositMaker!");
|
||||
return Err(VpnApiError::DepositFailure);
|
||||
};
|
||||
|
||||
if time_taken > Duration::from_secs(20) {
|
||||
warn!("attempting to resolve deposit request took {formatted}. perhaps the buffer is too small or the process/chain is overloaded?")
|
||||
} else {
|
||||
debug!("attempting to resolve deposit request took {formatted}")
|
||||
}
|
||||
|
||||
deposit_response.ok_or(VpnApiError::DepositFailure)
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
skip(state, request_data, request, requested_on),
|
||||
fields(
|
||||
@@ -69,13 +36,13 @@ pub(crate) async fn try_obtain_wallet_shares(
|
||||
request: Uuid,
|
||||
requested_on: OffsetDateTime,
|
||||
request_data: TicketbookRequest,
|
||||
) -> Result<Vec<WalletShare>, VpnApiError> {
|
||||
let mut rng = OsRng;
|
||||
|
||||
let ed25519_keypair = ed25519::KeyPair::new(&mut rng);
|
||||
) -> Result<Vec<WalletShare>, CredentialProxyError> {
|
||||
// don't proceed if we don't have quorum available as the request will definitely fail
|
||||
if !state.quorum_available() {
|
||||
return Err(CredentialProxyError::UnavailableSigningQuorum);
|
||||
}
|
||||
|
||||
let epoch = state.current_epoch_id().await?;
|
||||
let deposit_amount = state.deposit_amount().await?;
|
||||
let threshold = state.ecash_threshold(epoch).await?;
|
||||
let expiration_date = request_data.expiration_date;
|
||||
|
||||
@@ -87,30 +54,11 @@ pub(crate) async fn try_obtain_wallet_shares(
|
||||
.await?;
|
||||
let ecash_api_clients = state.ecash_clients(epoch).await?.clone();
|
||||
|
||||
let DepositResponse {
|
||||
deposit_id,
|
||||
tx_hash,
|
||||
} = make_deposit(state, *ed25519_keypair.public_key(), &deposit_amount).await?;
|
||||
|
||||
info!(deposit_id = %deposit_id, "deposit finished");
|
||||
|
||||
// store the deposit information so if we fail, we could perhaps still reuse it for another issuance
|
||||
state
|
||||
.storage()
|
||||
.insert_deposit_data(
|
||||
deposit_id,
|
||||
tx_hash,
|
||||
requested_on,
|
||||
request,
|
||||
deposit_amount,
|
||||
&request_data.ecash_pubkey,
|
||||
&ed25519_keypair,
|
||||
)
|
||||
let deposit_data = state
|
||||
.get_deposit(request, requested_on, request_data.ecash_pubkey)
|
||||
.await?;
|
||||
|
||||
let plaintext =
|
||||
IssuanceTicketBook::request_plaintext(&request_data.withdrawal_request, deposit_id);
|
||||
let signature = ed25519_keypair.private_key().sign(plaintext);
|
||||
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(),
|
||||
@@ -135,7 +83,7 @@ pub(crate) async fn try_obtain_wallet_shares(
|
||||
client.api_client.blind_sign(&credential_request),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| VpnApiError::EcashApiRequestTimeout {
|
||||
.map_err(|_| CredentialProxyError::EcashApiRequestTimeout {
|
||||
client_repr: client.to_string(),
|
||||
})
|
||||
.and_then(|res| res.map_err(Into::into));
|
||||
@@ -176,10 +124,14 @@ pub(crate) async fn try_obtain_wallet_shares(
|
||||
let shares = wallet_shares.len();
|
||||
|
||||
if shares < threshold as usize {
|
||||
return Err(VpnApiError::InsufficientNumberOfCredentials {
|
||||
let err = CredentialProxyError::InsufficientNumberOfCredentials {
|
||||
available: shares,
|
||||
threshold,
|
||||
});
|
||||
};
|
||||
state
|
||||
.insert_deposit_usage_error(deposit_id, err.to_string())
|
||||
.await;
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
Ok(wallet_shares
|
||||
@@ -199,12 +151,14 @@ async fn try_obtain_wallet_shares_async(
|
||||
request_data: TicketbookRequest,
|
||||
device_id: &str,
|
||||
credential_id: &str,
|
||||
) -> Result<Vec<WalletShare>, VpnApiError> {
|
||||
) -> Result<Vec<WalletShare>, CredentialProxyError> {
|
||||
let shares = match try_obtain_wallet_shares(state, request, requested_on, request_data).await {
|
||||
Ok(shares) => shares,
|
||||
Err(err) => {
|
||||
let obtained = match err {
|
||||
VpnApiError::InsufficientNumberOfCredentials { available, .. } => available,
|
||||
CredentialProxyError::InsufficientNumberOfCredentials { available, .. } => {
|
||||
available
|
||||
}
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
@@ -235,7 +189,7 @@ async fn try_obtain_blinded_ticketbook_async_inner(
|
||||
request_data: TicketbookAsyncRequest,
|
||||
params: TicketbookObtainQueryParams,
|
||||
pending: &BlindedShares,
|
||||
) -> Result<(), VpnApiError> {
|
||||
) -> Result<(), CredentialProxyError> {
|
||||
let epoch_id = state.current_epoch_id().await?;
|
||||
|
||||
let device_id = &request_data.device_id;
|
||||
@@ -318,7 +272,7 @@ async fn try_trigger_webhook_request_for_error(
|
||||
request_data: TicketbookAsyncRequest,
|
||||
pending: &BlindedShares,
|
||||
error_message: String,
|
||||
) -> Result<(), VpnApiError> {
|
||||
) -> Result<(), CredentialProxyError> {
|
||||
let device_id = &request_data.device_id;
|
||||
let credential_id = &request_data.credential_id;
|
||||
let secret = request_data.secret.clone();
|
||||
|
||||
@@ -1,205 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::error::VpnApiError;
|
||||
use crate::http::state::ChainClient;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_ecash_contract_common::deposit::DepositId;
|
||||
use nym_validator_client::nyxd::cosmwasm_client::ContractResponseData;
|
||||
use nym_validator_client::nyxd::{Coin, Hash};
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct DepositResponse {
|
||||
pub tx_hash: Hash,
|
||||
pub deposit_id: DepositId,
|
||||
}
|
||||
|
||||
pub(crate) struct DepositRequest {
|
||||
pubkey: ed25519::PublicKey,
|
||||
deposit_amount: Coin,
|
||||
on_done: oneshot::Sender<Option<DepositResponse>>,
|
||||
}
|
||||
|
||||
impl DepositRequest {
|
||||
pub(crate) fn new(
|
||||
pubkey: ed25519::PublicKey,
|
||||
deposit_amount: &Coin,
|
||||
on_done: oneshot::Sender<Option<DepositResponse>>,
|
||||
) -> Self {
|
||||
DepositRequest {
|
||||
pubkey,
|
||||
deposit_amount: deposit_amount.clone(),
|
||||
on_done,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type DepositRequestReceiver = mpsc::Receiver<DepositRequest>;
|
||||
|
||||
pub(crate) fn new_control_channels(
|
||||
max_concurrent_deposits: usize,
|
||||
) -> (DepositRequestSender, DepositRequestReceiver) {
|
||||
let (tx, rx) = mpsc::channel(max_concurrent_deposits);
|
||||
(tx.into(), rx)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DepositRequestSender(mpsc::Sender<DepositRequest>);
|
||||
|
||||
impl From<mpsc::Sender<DepositRequest>> for DepositRequestSender {
|
||||
fn from(inner: mpsc::Sender<DepositRequest>) -> Self {
|
||||
DepositRequestSender(inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl DepositRequestSender {
|
||||
pub(crate) async fn request_deposit(&self, request: DepositRequest) {
|
||||
if self.0.send(request).await.is_err() {
|
||||
error!("failed to request deposit: the DepositMaker must have died!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct DepositMaker {
|
||||
client: ChainClient,
|
||||
max_concurrent_deposits: usize,
|
||||
deposit_request_sender: DepositRequestSender,
|
||||
deposit_request_receiver: DepositRequestReceiver,
|
||||
short_sha: &'static str,
|
||||
cancellation_token: CancellationToken,
|
||||
}
|
||||
|
||||
impl DepositMaker {
|
||||
pub(crate) fn new(
|
||||
short_sha: &'static str,
|
||||
client: ChainClient,
|
||||
max_concurrent_deposits: usize,
|
||||
cancellation_token: CancellationToken,
|
||||
) -> Self {
|
||||
let (deposit_request_sender, deposit_request_receiver) =
|
||||
new_control_channels(max_concurrent_deposits);
|
||||
|
||||
DepositMaker {
|
||||
client,
|
||||
max_concurrent_deposits,
|
||||
deposit_request_sender,
|
||||
deposit_request_receiver,
|
||||
short_sha,
|
||||
cancellation_token,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn deposit_request_sender(&self) -> DepositRequestSender {
|
||||
self.deposit_request_sender.clone()
|
||||
}
|
||||
|
||||
pub(crate) async fn process_deposit_requests(
|
||||
&mut self,
|
||||
requests: Vec<DepositRequest>,
|
||||
) -> Result<(), VpnApiError> {
|
||||
let chain_write_permit = self.client.start_chain_tx().await;
|
||||
|
||||
info!("starting deposits");
|
||||
let mut contents = Vec::new();
|
||||
let mut replies = Vec::new();
|
||||
for request in requests {
|
||||
// check if the channel is still open in case the receiver client has cancelled the request
|
||||
if request.on_done.is_closed() {
|
||||
warn!(
|
||||
"the request for deposit from {} got cancelled",
|
||||
request.pubkey
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
contents.push((request.pubkey.to_base58_string(), request.deposit_amount));
|
||||
replies.push(request.on_done);
|
||||
}
|
||||
|
||||
let deposits_res = chain_write_permit
|
||||
.make_deposits(self.short_sha, contents)
|
||||
.await;
|
||||
let execute_res = match deposits_res {
|
||||
Ok(res) => res,
|
||||
Err(err) => {
|
||||
// we have to let requesters know the deposit(s) failed
|
||||
for reply in replies {
|
||||
if reply.send(None).is_err() {
|
||||
warn!("one of the deposit requesters has been terminated")
|
||||
}
|
||||
}
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
|
||||
let tx_hash = execute_res.transaction_hash;
|
||||
info!("{} deposits made in transaction: {tx_hash}", replies.len());
|
||||
|
||||
let contract_data = match execute_res.to_contract_data() {
|
||||
Ok(contract_data) => contract_data,
|
||||
Err(err) => {
|
||||
// that one is tricky. deposits technically got made, but we somehow failed to parse response,
|
||||
// in this case terminate the proxy with 0 exit code so it wouldn't get automatically restarted
|
||||
// because it requires some serious MANUAL intervention
|
||||
error!("CRITICAL FAILURE: failed to parse out deposit information from the contract transaction. either the chain got upgraded and the schema changed or the ecash contract got changed! terminating the process. it has to be inspected manually. error was: {err}");
|
||||
self.cancellation_token.cancel();
|
||||
return Err(VpnApiError::DepositFailure);
|
||||
}
|
||||
};
|
||||
|
||||
if contract_data.len() != replies.len() {
|
||||
// another critical failure, that one should be quite impossible and thus has to be manually inspected
|
||||
error!("CRITICAL FAILURE: failed to parse out all deposit information from the contract transaction. got {} responses while we sent {} deposits! either the chain got upgraded and the schema changed or the ecash contract got changed! terminating the process. it has to be inspected manually", contract_data.len(), replies.len());
|
||||
self.cancellation_token.cancel();
|
||||
return Err(VpnApiError::DepositFailure);
|
||||
}
|
||||
|
||||
for (reply_channel, response) in replies.into_iter().zip(contract_data) {
|
||||
let response_index = response.message_index;
|
||||
let deposit_id = match response.parse_singleton_u32_contract_data() {
|
||||
Ok(deposit_id) => deposit_id,
|
||||
Err(err) => {
|
||||
// another impossibility
|
||||
error!("CRITICAL FAILURE: failed to parse out deposit id out of the response at index {response_index}: {err}. either the chain got upgraded and the schema changed or the ecash contract got changed! terminating the process. it has to be inspected manually");
|
||||
self.cancellation_token.cancel();
|
||||
return Err(VpnApiError::DepositFailure);
|
||||
}
|
||||
};
|
||||
|
||||
if reply_channel
|
||||
.send(Some(DepositResponse {
|
||||
deposit_id,
|
||||
tx_hash,
|
||||
}))
|
||||
.is_err()
|
||||
{
|
||||
warn!("one of the deposit requesters has been terminated. deposit {deposit_id} will remain unclaimed!");
|
||||
// this shouldn't happen as the requester task shouldn't be killed, but it's not a critical failure
|
||||
// we just lost some tokens, but it's not an undefined on-chain behaviour
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn run_forever(mut self) {
|
||||
info!("starting the deposit maker task");
|
||||
loop {
|
||||
let mut receive_buffer = Vec::with_capacity(self.max_concurrent_deposits);
|
||||
tokio::select! {
|
||||
_ = self.cancellation_token.cancelled() => {
|
||||
break
|
||||
}
|
||||
received = self.deposit_request_receiver.recv_many(&mut receive_buffer, self.max_concurrent_deposits) => {
|
||||
debug!("received {received} deposit requests");
|
||||
if let Err(err) = self.process_deposit_requests(receive_buffer).await {
|
||||
error!("failed to process received deposit requests: {err}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::error::CredentialProxyError;
|
||||
use crate::storage::models::StorableEcashDeposit;
|
||||
use nym_compact_ecash::WithdrawalRequest;
|
||||
use nym_credentials::IssuanceTicketBook;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_validator_client::nyxd::{Coin, Hash};
|
||||
use time::OffsetDateTime;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
pub(crate) struct BufferedDeposit {
|
||||
pub(crate) deposit_id: u32,
|
||||
|
||||
// note: this type implements `ZeroizeOnDrop`
|
||||
pub(crate) ed25519_private_key: ed25519::PrivateKey,
|
||||
}
|
||||
|
||||
impl TryFrom<StorableEcashDeposit> for BufferedDeposit {
|
||||
type Error = CredentialProxyError;
|
||||
|
||||
fn try_from(deposit: StorableEcashDeposit) -> Result<Self, Self::Error> {
|
||||
let ed25519_private_key = ed25519::PrivateKey::from_bytes(
|
||||
deposit.ed25519_deposit_private_key.as_ref(),
|
||||
)
|
||||
.map_err(|err| CredentialProxyError::DatabaseInconsistency {
|
||||
reason: format!("one of the stored deposit ed25519 private keys is malformed: {err}"),
|
||||
})?;
|
||||
|
||||
Ok(BufferedDeposit {
|
||||
deposit_id: deposit.deposit_id,
|
||||
ed25519_private_key,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl BufferedDeposit {
|
||||
pub(crate) fn new(deposit_id: u32, ed25519_private_key: ed25519::PrivateKey) -> Self {
|
||||
BufferedDeposit {
|
||||
deposit_id,
|
||||
ed25519_private_key,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn sign_ticketbook_plaintext(
|
||||
&self,
|
||||
withdrawal_request: &WithdrawalRequest,
|
||||
) -> ed25519::Signature {
|
||||
let plaintext = IssuanceTicketBook::request_plaintext(withdrawal_request, self.deposit_id);
|
||||
self.ed25519_private_key.sign(plaintext)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct PerformedDeposits {
|
||||
pub(crate) 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,
|
||||
}
|
||||
|
||||
impl PerformedDeposits {
|
||||
pub(crate) fn to_storable(&self) -> Vec<StorableEcashDeposit> {
|
||||
self.deposits_data
|
||||
.iter()
|
||||
.map(|d| StorableEcashDeposit {
|
||||
deposit_id: d.deposit_id,
|
||||
deposit_tx_hash: self.tx_hash.to_string(),
|
||||
requested_on: self.requested_on,
|
||||
deposit_amount: self.deposit_amount.to_string(),
|
||||
ed25519_deposit_private_key: Zeroizing::new(d.ed25519_private_key.to_bytes()),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn request_sizes(total: usize, max_request_size: usize) -> impl Iterator<Item = usize> {
|
||||
(0..total)
|
||||
.step_by(max_request_size)
|
||||
.map(move |start| std::cmp::min(max_request_size, total - start))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn request_sizes_test() {
|
||||
assert_eq!(
|
||||
request_sizes(100, 32).collect::<Vec<_>>(),
|
||||
vec![32, 32, 32, 4]
|
||||
);
|
||||
|
||||
assert_eq!(request_sizes(10, 32).collect::<Vec<_>>(), vec![10]);
|
||||
assert_eq!(request_sizes(32, 32).collect::<Vec<_>>(), vec![32]);
|
||||
assert_eq!(request_sizes(33, 32).collect::<Vec<_>>(), vec![32, 1]);
|
||||
assert_eq!(request_sizes(1, 32).collect::<Vec<_>>(), vec![1]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
// 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::refill_task::RefillTask;
|
||||
use crate::error::CredentialProxyError;
|
||||
use crate::http::state::required_deposit_cache::RequiredDepositCache;
|
||||
use crate::http::state::ChainClient;
|
||||
use crate::storage::CredentialProxyStorage;
|
||||
use nym_compact_ecash::PublicKeyUser;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_ecash_contract_common::deposit::DepositId;
|
||||
use nym_validator_client::nyxd::cosmwasm_client::ContractResponseData;
|
||||
use nym_validator_client::nyxd::Coin;
|
||||
use rand::rngs::OsRng;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
use tokio::sync::Mutex as AsyncMutex;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::{debug, error, info, instrument, warn};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub(crate) mod helpers;
|
||||
mod refill_task;
|
||||
|
||||
// TODO: I guess make it configurable
|
||||
const DEPOSITS_THRESHOLD_P: f32 = 0.1;
|
||||
|
||||
struct DepositsBufferInner {
|
||||
client: ChainClient,
|
||||
|
||||
required_deposit_cache: RequiredDepositCache,
|
||||
|
||||
storage: CredentialProxyStorage,
|
||||
target_amount: usize,
|
||||
max_concurrent_deposits: usize,
|
||||
unused_deposits: AsyncMutex<Vec<BufferedDeposit>>,
|
||||
|
||||
deposits_refill_task: RefillTask,
|
||||
short_sha: &'static str,
|
||||
cancellation_token: CancellationToken,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct DepositsBuffer {
|
||||
inner: Arc<DepositsBufferInner>,
|
||||
}
|
||||
|
||||
impl DepositsBuffer {
|
||||
pub(crate) async fn new(
|
||||
storage: CredentialProxyStorage,
|
||||
client: ChainClient,
|
||||
required_deposit_cache: RequiredDepositCache,
|
||||
short_sha: &'static str,
|
||||
target_amount: usize,
|
||||
max_concurrent_deposits: usize,
|
||||
cancellation_token: CancellationToken,
|
||||
) -> Result<Self, CredentialProxyError> {
|
||||
let unused_deposits = storage.load_unused_deposits().await?;
|
||||
info!("managed to load {} deposits", unused_deposits.len());
|
||||
|
||||
Ok(DepositsBuffer {
|
||||
inner: Arc::new(DepositsBufferInner {
|
||||
client,
|
||||
required_deposit_cache,
|
||||
storage,
|
||||
target_amount,
|
||||
max_concurrent_deposits,
|
||||
unused_deposits: AsyncMutex::new(unused_deposits),
|
||||
deposits_refill_task: RefillTask::default(),
|
||||
short_sha,
|
||||
cancellation_token,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
async fn deposit_amount(&self) -> Result<Coin, CredentialProxyError> {
|
||||
self.inner
|
||||
.required_deposit_cache
|
||||
.get_or_update(&self.inner.client)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(skip(self), err(Display))]
|
||||
async fn make_deposits_request(
|
||||
&self,
|
||||
amount: usize,
|
||||
) -> Result<PerformedDeposits, CredentialProxyError> {
|
||||
let requested_on = OffsetDateTime::now_utc();
|
||||
let chain_write_permit = self.inner.client.start_chain_tx().await;
|
||||
let mut rng = OsRng;
|
||||
|
||||
let deposit_amount = self.deposit_amount().await?;
|
||||
let keys = (0..amount)
|
||||
.map(|_| ed25519::PrivateKey::new(&mut rng))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
info!("starting {amount} deposits");
|
||||
let mut contents = Vec::new();
|
||||
for key in &keys {
|
||||
let public_key: ed25519::PublicKey = key.into();
|
||||
contents.push((public_key.to_base58_string(), deposit_amount.clone()));
|
||||
}
|
||||
|
||||
let execute_res = chain_write_permit
|
||||
.make_deposits(self.inner.short_sha, contents)
|
||||
.await?;
|
||||
|
||||
let tx_hash = execute_res.transaction_hash;
|
||||
info!("{amount} deposits made in transaction: {tx_hash}");
|
||||
|
||||
let contract_data = match execute_res.to_contract_data() {
|
||||
Ok(contract_data) => contract_data,
|
||||
Err(err) => {
|
||||
// that one is tricky. deposits technically got made, but we somehow failed to parse response,
|
||||
// in this case terminate the proxy with 0 exit code so it wouldn't get automatically restarted
|
||||
// because it requires some serious MANUAL intervention
|
||||
error!("CRITICAL FAILURE: failed to parse out deposit information from the contract transaction. either the chain got upgraded and the schema changed or the ecash contract got changed! terminating the process. it has to be inspected manually. error was: {err}");
|
||||
self.inner.cancellation_token.cancel();
|
||||
return Err(CredentialProxyError::DepositFailure);
|
||||
}
|
||||
};
|
||||
|
||||
if contract_data.len() != amount {
|
||||
// another critical failure, that one should be quite impossible and thus has to be manually inspected
|
||||
error!("CRITICAL FAILURE: failed to parse out all deposit information from the contract transaction. got {} responses while we sent {amount} deposits! either the chain got upgraded and the schema changed or the ecash contract got changed! terminating the process. it has to be inspected manually", contract_data.len());
|
||||
self.inner.cancellation_token.cancel();
|
||||
return Err(CredentialProxyError::DepositFailure);
|
||||
}
|
||||
|
||||
let mut deposits_data = Vec::new();
|
||||
for (key, response) in keys.into_iter().zip(contract_data) {
|
||||
let response_index = response.message_index;
|
||||
let deposit_id = match response.parse_singleton_u32_contract_data() {
|
||||
Ok(deposit_id) => deposit_id,
|
||||
Err(err) => {
|
||||
// another impossibility
|
||||
error!("CRITICAL FAILURE: failed to parse out deposit id out of the response at index {response_index}: {err}. either the chain got upgraded and the schema changed or the ecash contract got changed! terminating the process. it has to be inspected manually");
|
||||
self.inner.cancellation_token.cancel();
|
||||
return Err(CredentialProxyError::DepositFailure);
|
||||
}
|
||||
};
|
||||
|
||||
deposits_data.push(BufferedDeposit::new(deposit_id, key));
|
||||
}
|
||||
|
||||
Ok(PerformedDeposits {
|
||||
deposits_data,
|
||||
tx_hash,
|
||||
requested_on,
|
||||
deposit_amount,
|
||||
})
|
||||
}
|
||||
|
||||
async fn insert_new_deposits(
|
||||
&self,
|
||||
mut deposits: PerformedDeposits,
|
||||
) -> Result<(), CredentialProxyError> {
|
||||
// 1. insert into the db
|
||||
self.inner.storage.insert_new_deposits(&deposits).await?;
|
||||
|
||||
// 2. update the buffer
|
||||
self.inner
|
||||
.unused_deposits
|
||||
.lock()
|
||||
.await
|
||||
.append(&mut deposits.deposits_data);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Start refilling our deposit buffer.
|
||||
/// It chunks the amount required based on the configured maximum request size
|
||||
/// and updates global state after each successful transaction.
|
||||
async fn refill_deposits(&self) -> Result<(), CredentialProxyError> {
|
||||
let available = self.inner.unused_deposits.lock().await.len();
|
||||
|
||||
let target = self.deposits_upper_threshold();
|
||||
let to_request = target - available;
|
||||
|
||||
for request_chunk in request_sizes(to_request, self.inner.max_concurrent_deposits) {
|
||||
// note: we check for cancellation between individual requests
|
||||
// as opposed to wrapping that in tokio::select! so that we would never abandon chain operations
|
||||
// as we wouldn't want to lose funds
|
||||
if self.inner.cancellation_token.is_cancelled() {
|
||||
info!("received cancellation during deposits refilling");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// make sure to insert deposits into db/vec as we get them so on initial run,
|
||||
// we'd start trickling down data as soon as possible
|
||||
let deposits = self.make_deposits_request(request_chunk).await?;
|
||||
self.insert_new_deposits(deposits).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// if we're here, we know we're below the threshold
|
||||
fn maybe_refill_deposits(&self) {
|
||||
if let Some(mut guard) = self.inner.deposits_refill_task.try_get_new_task_guard() {
|
||||
let this = self.clone();
|
||||
*guard = Some(tokio::spawn(async move { this.refill_deposits().await }));
|
||||
}
|
||||
}
|
||||
|
||||
fn deposits_lower_threshold(&self) -> usize {
|
||||
self.inner.target_amount - (self.inner.target_amount as f32 * DEPOSITS_THRESHOLD_P) as usize
|
||||
}
|
||||
|
||||
fn deposits_upper_threshold(&self) -> usize {
|
||||
self.inner.target_amount + (self.inner.target_amount as f32 * DEPOSITS_THRESHOLD_P) as usize
|
||||
}
|
||||
|
||||
async fn mark_deposit_as_used(
|
||||
&self,
|
||||
deposit_id: DepositId,
|
||||
requested_on: OffsetDateTime,
|
||||
client_pubkey: PublicKeyUser,
|
||||
request_uuid: Uuid,
|
||||
) -> Result<(), CredentialProxyError> {
|
||||
self.inner
|
||||
.storage
|
||||
.insert_deposit_usage(deposit_id, requested_on, client_pubkey, request_uuid)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn wait_for_deposit(
|
||||
&self,
|
||||
request_uuid: Uuid,
|
||||
requested_on: OffsetDateTime,
|
||||
client_pubkey: PublicKeyUser,
|
||||
) -> Result<BufferedDeposit, CredentialProxyError> {
|
||||
loop {
|
||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||
if let Some(buffered_deposit) = self.inner.unused_deposits.lock().await.pop() {
|
||||
// if the db call fails, we technically don't lose the deposit (we'll 'recover' it on restart)
|
||||
self.mark_deposit_as_used(
|
||||
buffered_deposit.deposit_id,
|
||||
requested_on,
|
||||
client_pubkey,
|
||||
request_uuid,
|
||||
)
|
||||
.await?;
|
||||
return Ok(buffered_deposit);
|
||||
} else {
|
||||
// make sure there's always a task working in the background in case deposits get used up too quickly
|
||||
self.maybe_refill_deposits()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn get_valid_deposit(
|
||||
&self,
|
||||
request_uuid: Uuid,
|
||||
requested_on: OffsetDateTime,
|
||||
client_pubkey: PublicKeyUser,
|
||||
) -> Result<BufferedDeposit, CredentialProxyError> {
|
||||
let mut deposits_guard = self.inner.unused_deposits.lock().await;
|
||||
let deposits_available = deposits_guard.len();
|
||||
|
||||
debug!("we have {deposits_available} unused deposits available");
|
||||
|
||||
let maybe_deposit = deposits_guard.pop();
|
||||
drop(deposits_guard);
|
||||
|
||||
if deposits_available < self.deposits_lower_threshold() {
|
||||
// if we're below threshold, start refill task
|
||||
self.maybe_refill_deposits()
|
||||
}
|
||||
|
||||
match maybe_deposit {
|
||||
None => {
|
||||
warn!("we currently don't have any usable deposits! are we using them up faster than we request them?");
|
||||
|
||||
// we have to wait until refill task has completed (either initiated by this or another fn call)
|
||||
self.wait_for_deposit(request_uuid, requested_on, client_pubkey)
|
||||
.await
|
||||
}
|
||||
Some(buffered_deposit) => {
|
||||
self.mark_deposit_as_used(
|
||||
buffered_deposit.deposit_id,
|
||||
requested_on,
|
||||
client_pubkey,
|
||||
request_uuid,
|
||||
)
|
||||
.await?;
|
||||
Ok(buffered_deposit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) 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() {
|
||||
info!("the deposit refill task is currently in progress - waiting for the current transaction to finish before concluding shutdown");
|
||||
let _ = task_handle.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DepositsBufferInner {
|
||||
//
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::error::CredentialProxyError;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Mutex as StdMutex, MutexGuard};
|
||||
use tokio::task::JoinHandle;
|
||||
use tracing::{debug, error};
|
||||
|
||||
pub(super) type RefillTaskResult = Result<(), CredentialProxyError>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(super) struct RefillTask {
|
||||
// note that we can only have a single transaction in progress (or it'd mess up with our sequence numbers)
|
||||
// if we find that we're using up deposits more quickly than we're refilling them,
|
||||
// we'll have to increase the number of deposits per transaction
|
||||
join_handle: StdMutex<Option<JoinHandle<RefillTaskResult>>>,
|
||||
|
||||
in_progress: AtomicBool,
|
||||
}
|
||||
|
||||
impl RefillTask {
|
||||
/// Attempt to set the `in_progress` value to `true` if it's not already `true`.
|
||||
/// Returns boolean indicating whether it was successful
|
||||
fn try_set_in_progress(&self) -> bool {
|
||||
self.in_progress
|
||||
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
pub(super) fn try_get_new_task_guard(
|
||||
&self,
|
||||
) -> Option<MutexGuard<'_, Option<JoinHandle<RefillTaskResult>>>> {
|
||||
// sanity check for concurrent request
|
||||
if !self.try_set_in_progress() {
|
||||
debug!("another task has already started deposit refill request");
|
||||
return None;
|
||||
}
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
let guard = self.join_handle.lock().expect("mutex got poisoned");
|
||||
|
||||
if let Some(existing_handle) = guard.as_ref() {
|
||||
if !existing_handle.is_finished() {
|
||||
error!("CRITICAL BUG: there was already a deposit refill task spawned that hasn't yet finished")
|
||||
}
|
||||
}
|
||||
|
||||
Some(guard)
|
||||
}
|
||||
|
||||
pub(super) fn take_task_join_handle(&self) -> Option<JoinHandle<RefillTaskResult>> {
|
||||
#[allow(clippy::expect_used)]
|
||||
self.join_handle.lock().expect("mutex got poisoned").take()
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2024 Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_ecash_signer_check::SignerCheckError;
|
||||
use nym_validator_client::coconut::EcashApiError;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nyxd::error::NyxdError;
|
||||
@@ -10,7 +11,7 @@ use thiserror::Error;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum VpnApiError {
|
||||
pub enum CredentialProxyError {
|
||||
#[error("encountered an internal io error: {source}")]
|
||||
IoError {
|
||||
#[from]
|
||||
@@ -118,11 +119,20 @@ pub enum VpnApiError {
|
||||
|
||||
#[error("failed to create deposit")]
|
||||
DepositFailure,
|
||||
|
||||
#[error("can't obtain sufficient number of credential shares due to unavailable quorum")]
|
||||
UnavailableSigningQuorum,
|
||||
|
||||
#[error("failed to perform quorum check: {source}")]
|
||||
QuorumCheckFailure {
|
||||
#[from]
|
||||
source: SignerCheckError,
|
||||
},
|
||||
}
|
||||
|
||||
impl VpnApiError {
|
||||
pub fn database_inconsistency<S: Into<String>>(reason: S) -> VpnApiError {
|
||||
VpnApiError::DatabaseInconsistency {
|
||||
impl CredentialProxyError {
|
||||
pub fn database_inconsistency<S: Into<String>>(reason: S) -> CredentialProxyError {
|
||||
CredentialProxyError::DatabaseInconsistency {
|
||||
reason: reason.into(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,15 +6,17 @@ use time::OffsetDateTime;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
use crate::deposits_buffer::DepositsBuffer;
|
||||
use crate::http::state::required_deposit_cache::RequiredDepositCache;
|
||||
use crate::quorum_checker::QuorumStateChecker;
|
||||
use crate::{
|
||||
cli::Cli,
|
||||
deposit_maker::DepositMaker,
|
||||
error::VpnApiError,
|
||||
error::CredentialProxyError,
|
||||
http::{
|
||||
state::{ApiState, ChainClient},
|
||||
HttpServer,
|
||||
},
|
||||
storage::VpnApiStorage,
|
||||
storage::CredentialProxyStorage,
|
||||
tasks::StoragePruner,
|
||||
};
|
||||
|
||||
@@ -77,6 +79,7 @@ pub async fn wait_for_signal() {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::panic)]
|
||||
fn build_sha_short() -> &'static str {
|
||||
let bin_info = bin_info!();
|
||||
if bin_info.commit_sha.len() < 7 {
|
||||
@@ -91,30 +94,46 @@ fn build_sha_short() -> &'static str {
|
||||
&bin_info.commit_sha[..7]
|
||||
}
|
||||
|
||||
pub(crate) async fn run_api(cli: Cli) -> Result<(), VpnApiError> {
|
||||
pub(crate) async fn run_api(cli: Cli) -> Result<(), CredentialProxyError> {
|
||||
// create the tasks
|
||||
let bind_address = cli.bind_address();
|
||||
|
||||
let storage = VpnApiStorage::init(cli.persistent_storage_path()).await?;
|
||||
let storage = CredentialProxyStorage::init(cli.persistent_storage_path()).await?;
|
||||
let mnemonic = cli.mnemonic;
|
||||
let auth_token = cli.http_auth_token;
|
||||
let webhook_cfg = cli.webhook;
|
||||
let chain_client = ChainClient::new(mnemonic)?;
|
||||
let cancellation_token = CancellationToken::new();
|
||||
|
||||
let deposit_maker = DepositMaker::new(
|
||||
build_sha_short(),
|
||||
let required_deposit_cache = RequiredDepositCache::default();
|
||||
|
||||
let quorum_state_checker = QuorumStateChecker::new(
|
||||
chain_client.clone(),
|
||||
cli.quorum_check_interval,
|
||||
cancellation_token.clone(),
|
||||
)
|
||||
.await?;
|
||||
let quorum_state = quorum_state_checker.quorum_state_ref();
|
||||
|
||||
let deposits_buffer = DepositsBuffer::new(
|
||||
storage.clone(),
|
||||
chain_client.clone(),
|
||||
required_deposit_cache.clone(),
|
||||
build_sha_short(),
|
||||
cli.deposits_buffer_size,
|
||||
cli.max_concurrent_deposits,
|
||||
cancellation_token.clone(),
|
||||
);
|
||||
)
|
||||
.await?;
|
||||
|
||||
let deposit_request_sender = deposit_maker.deposit_request_sender();
|
||||
// let deposit_request_sender = deposit_maker.deposit_request_sender();
|
||||
let api_state = ApiState::new(
|
||||
storage.clone(),
|
||||
quorum_state,
|
||||
webhook_cfg,
|
||||
chain_client,
|
||||
deposit_request_sender,
|
||||
deposits_buffer,
|
||||
required_deposit_cache,
|
||||
cancellation_token.clone(),
|
||||
)
|
||||
.await?;
|
||||
@@ -129,7 +148,7 @@ pub(crate) async fn run_api(cli: Cli) -> Result<(), VpnApiError> {
|
||||
// spawn all the tasks
|
||||
api_state.try_spawn(http_server.run_forever());
|
||||
api_state.try_spawn(storage_pruner.run_forever());
|
||||
api_state.try_spawn(deposit_maker.run_forever());
|
||||
api_state.try_spawn(quorum_state_checker.run_forever());
|
||||
|
||||
// wait for cancel signal (SIGINT, SIGTERM or SIGQUIT)
|
||||
wait_for_signal().await;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2024 Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::error::VpnApiError;
|
||||
use crate::error::CredentialProxyError;
|
||||
use crate::http::types::RequestError;
|
||||
use axum::http::StatusCode;
|
||||
use rand::rngs::OsRng;
|
||||
@@ -16,7 +16,7 @@ pub fn random_uuid() -> Uuid {
|
||||
Uuid::from_bytes(bytes)
|
||||
}
|
||||
|
||||
pub fn db_failure<T>(err: VpnApiError, uuid: Uuid) -> Result<T, RequestError> {
|
||||
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}"),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2024 Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::error::VpnApiError;
|
||||
use crate::error::CredentialProxyError;
|
||||
use crate::http::router::build_router;
|
||||
use crate::http::state::ApiState;
|
||||
use axum::Router;
|
||||
@@ -34,13 +34,13 @@ impl HttpServer {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_forever(self) -> Result<(), VpnApiError> {
|
||||
pub async fn run_forever(self) -> Result<(), CredentialProxyError> {
|
||||
let address = self.bind_address;
|
||||
info!("starting the http server on http://{address}");
|
||||
|
||||
let listener = tokio::net::TcpListener::bind(address)
|
||||
.await
|
||||
.map_err(|source| VpnApiError::SocketBindFailure { address, source })?;
|
||||
.map_err(|source| CredentialProxyError::SocketBindFailure { address, source })?;
|
||||
|
||||
let cancellation = self.cancellation;
|
||||
|
||||
@@ -51,6 +51,6 @@ impl HttpServer {
|
||||
)
|
||||
.with_graceful_shutdown(async move { cancellation.cancelled().await })
|
||||
.await
|
||||
.map_err(|source| VpnApiError::HttpServerFailure { source })
|
||||
.map_err(|source| CredentialProxyError::HttpServerFailure { source })
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
// Copyright 2024 Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::error::VpnApiError;
|
||||
use crate::error::CredentialProxyError;
|
||||
use crate::http::helpers::{db_failure, random_uuid};
|
||||
use crate::http::router::api::v1::ticketbook::FormattedTicketbookWalletSharesResponse;
|
||||
use crate::http::state::ApiState;
|
||||
@@ -35,7 +35,7 @@ async fn shares_to_response(
|
||||
let threshold = state.response_ecash_threshold(uuid, epoch_id).await?;
|
||||
if shares.len() < threshold as usize {
|
||||
return Err(RequestError::new_server_error(
|
||||
VpnApiError::InsufficientNumberOfCredentials {
|
||||
CredentialProxyError::InsufficientNumberOfCredentials {
|
||||
available: shares.len(),
|
||||
threshold,
|
||||
},
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
// Copyright 2024 Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::deposit_maker::{DepositRequest, DepositRequestSender};
|
||||
use crate::error::VpnApiError;
|
||||
use crate::deposits_buffer::helpers::BufferedDeposit;
|
||||
use crate::deposits_buffer::DepositsBuffer;
|
||||
use crate::error::CredentialProxyError;
|
||||
use crate::helpers::LockTimer;
|
||||
use crate::http::state::required_deposit_cache::RequiredDepositCache;
|
||||
use crate::http::types::RequestError;
|
||||
use crate::nym_api_helpers::{
|
||||
ensure_sane_expiration_date, query_all_threshold_apis, CachedEpoch, CachedImmutableEpochItem,
|
||||
CachedImmutableItems,
|
||||
};
|
||||
use crate::storage::VpnApiStorage;
|
||||
use crate::quorum_checker::QuorumState;
|
||||
use crate::storage::CredentialProxyStorage;
|
||||
use crate::webhook::ZkNymWebHookConfig;
|
||||
use axum::http::StatusCode;
|
||||
use bip39::Mnemonic;
|
||||
@@ -19,7 +22,7 @@ use nym_compact_ecash::scheme::coin_indices_signatures::{
|
||||
use nym_compact_ecash::scheme::expiration_date_signatures::{
|
||||
aggregate_annotated_expiration_signatures, ExpirationDateSignatureShare,
|
||||
};
|
||||
use nym_compact_ecash::Base58;
|
||||
use nym_compact_ecash::{Base58, PublicKeyUser};
|
||||
use nym_credential_proxy_requests::api::v1::ticketbook::models::{
|
||||
AggregatedCoinIndicesSignaturesResponse, AggregatedExpirationDateSignaturesResponse,
|
||||
MasterVerificationKeyResponse,
|
||||
@@ -29,12 +32,13 @@ use nym_credentials::{
|
||||
AggregatedCoinIndicesSignatures, AggregatedExpirationDateSignatures, EpochVerificationKey,
|
||||
};
|
||||
use nym_credentials_interface::VerificationKeyAuth;
|
||||
use nym_ecash_contract_common::deposit::DepositId;
|
||||
use nym_ecash_contract_common::msg::ExecuteMsg;
|
||||
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, EcashQueryClient, NymContractsProvider, PagedDkgQueryClient,
|
||||
DkgQueryClient, NymContractsProvider, PagedDkgQueryClient,
|
||||
};
|
||||
use nym_validator_client::nyxd::cosmwasm_client::types::ExecuteResult;
|
||||
use nym_validator_client::nyxd::{Coin, CosmWasmClient, NyxdClient};
|
||||
@@ -49,33 +53,46 @@ use tokio::task::JoinHandle;
|
||||
use tokio::time::Instant;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tokio_util::task::TaskTracker;
|
||||
use tracing::{debug, info, warn};
|
||||
use tracing::{debug, error, info, instrument, warn};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub(crate) mod required_deposit_cache;
|
||||
|
||||
// currently we need to hold our keypair so that we could request a freepass credential
|
||||
#[derive(Clone)]
|
||||
pub struct ApiState {
|
||||
inner: Arc<ApiStateInner>,
|
||||
inner: Arc<CredentialProxyStateInner>,
|
||||
}
|
||||
|
||||
// a lot of functionalities, mostly to do with caching and storage is just copy-pasted from nym-api,
|
||||
// since we have to do more or less the same work
|
||||
impl ApiState {
|
||||
pub async fn new(
|
||||
storage: VpnApiStorage,
|
||||
pub(crate) async fn new(
|
||||
storage: CredentialProxyStorage,
|
||||
quorum_state: QuorumState,
|
||||
zk_nym_web_hook_config: ZkNymWebHookConfig,
|
||||
client: ChainClient,
|
||||
deposit_requester: DepositRequestSender,
|
||||
deposits_buffer: DepositsBuffer,
|
||||
required_deposit_cache: RequiredDepositCache,
|
||||
cancellation_token: CancellationToken,
|
||||
) -> Result<Self, VpnApiError> {
|
||||
) -> Result<Self, CredentialProxyError> {
|
||||
let state = ApiState {
|
||||
inner: Arc::new(ApiStateInner {
|
||||
inner: Arc::new(CredentialProxyStateInner {
|
||||
storage,
|
||||
client,
|
||||
ecash_state: EcashState::default(),
|
||||
ecash_state: 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(),
|
||||
},
|
||||
zk_nym_web_hook_config,
|
||||
task_tracker: TaskTracker::new(),
|
||||
deposit_requester,
|
||||
deposits_buffer,
|
||||
cancellation_token,
|
||||
}),
|
||||
};
|
||||
@@ -88,7 +105,7 @@ impl ApiState {
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
async fn build_initial_cache(&self) -> Result<(), VpnApiError> {
|
||||
async fn build_initial_cache(&self) -> Result<(), CredentialProxyError> {
|
||||
let today = ecash_today().date();
|
||||
|
||||
let epoch_id = self.current_epoch_id().await?;
|
||||
@@ -123,6 +140,7 @@ impl ApiState {
|
||||
|
||||
pub(crate) async fn cancel_and_wait(&self) {
|
||||
self.inner.cancellation_token.cancel();
|
||||
self.inner.deposits_buffer.wait_for_shutdown().await;
|
||||
self.inner.task_tracker.wait().await
|
||||
}
|
||||
|
||||
@@ -130,7 +148,11 @@ impl ApiState {
|
||||
&self.inner.zk_nym_web_hook_config
|
||||
}
|
||||
|
||||
async fn ensure_credentials_issuable(&self) -> Result<(), VpnApiError> {
|
||||
pub(crate) fn quorum_available(&self) -> bool {
|
||||
self.inner.ecash_state.quorum_state.available()
|
||||
}
|
||||
|
||||
async fn ensure_credentials_issuable(&self) -> Result<(), CredentialProxyError> {
|
||||
let epoch = self.current_epoch().await?;
|
||||
|
||||
if epoch.state.is_final() {
|
||||
@@ -141,41 +163,29 @@ impl ApiState {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let finish_dt = OffsetDateTime::from_unix_timestamp(final_timestamp as i64).unwrap();
|
||||
|
||||
Err(VpnApiError::CredentialsNotYetIssuable {
|
||||
Err(CredentialProxyError::CredentialsNotYetIssuable {
|
||||
availability: finish_dt,
|
||||
})
|
||||
} else if epoch.state.is_waiting_initialisation() {
|
||||
Err(VpnApiError::UninitialisedDkg)
|
||||
Err(CredentialProxyError::UninitialisedDkg)
|
||||
} else {
|
||||
Err(VpnApiError::UnknownEcashFailure)
|
||||
Err(CredentialProxyError::UnknownEcashFailure)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn storage(&self) -> &VpnApiStorage {
|
||||
pub(crate) fn storage(&self) -> &CredentialProxyStorage {
|
||||
&self.inner.storage
|
||||
}
|
||||
|
||||
pub async fn deposit_amount(&self) -> Result<Coin, VpnApiError> {
|
||||
let read_guard = self.inner.ecash_state.required_deposit_cache.read().await;
|
||||
if read_guard.is_valid() {
|
||||
return Ok(read_guard.required_amount.clone());
|
||||
}
|
||||
|
||||
// update cache
|
||||
drop(read_guard);
|
||||
let mut write_guard = self.inner.ecash_state.required_deposit_cache.write().await;
|
||||
let deposit_amount = self
|
||||
.query_chain()
|
||||
pub async fn deposit_amount(&self) -> Result<Coin, CredentialProxyError> {
|
||||
self.inner
|
||||
.ecash_state
|
||||
.required_deposit_cache
|
||||
.get_or_update(&self.inner.client)
|
||||
.await
|
||||
.get_required_deposit_amount()
|
||||
.await?;
|
||||
|
||||
write_guard.update(deposit_amount.clone().into());
|
||||
|
||||
Ok(deposit_amount.into())
|
||||
}
|
||||
|
||||
async fn current_epoch(&self) -> Result<Epoch, VpnApiError> {
|
||||
async fn current_epoch(&self) -> Result<Epoch, CredentialProxyError> {
|
||||
let read_guard = self.inner.ecash_state.cached_epoch.read().await;
|
||||
if read_guard.is_valid() {
|
||||
return Ok(read_guard.current_epoch);
|
||||
@@ -190,7 +200,7 @@ impl ApiState {
|
||||
Ok(epoch)
|
||||
}
|
||||
|
||||
pub async fn current_epoch_id(&self) -> Result<EpochId, VpnApiError> {
|
||||
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);
|
||||
@@ -209,16 +219,38 @@ impl ApiState {
|
||||
self.inner.client.query_chain().await
|
||||
}
|
||||
|
||||
pub(crate) async fn request_deposit(&self, request: DepositRequest) {
|
||||
pub(crate) async fn get_deposit(
|
||||
&self,
|
||||
request_uuid: Uuid,
|
||||
requested_on: OffsetDateTime,
|
||||
client_pubkey: PublicKeyUser,
|
||||
) -> Result<BufferedDeposit, CredentialProxyError> {
|
||||
let start = Instant::now();
|
||||
self.inner.deposit_requester.request_deposit(request).await;
|
||||
let deposit = self
|
||||
.inner
|
||||
.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 push new deposit request onto the queue took {formatted}. perhaps the buffer is too small or the process/chain is overloaded?")
|
||||
warn!("attempting to get buffered deposit took {formatted}. perhaps the buffer is too small or the process/chain is overloaded?")
|
||||
} else {
|
||||
debug!("attempting to push new deposit request onto the queue took {formatted}")
|
||||
debug!("attempting to get buffered deposit took {formatted}")
|
||||
};
|
||||
|
||||
deposit
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_deposit_usage_error(&self, deposit_id: DepositId, error: String) {
|
||||
if let Err(err) = self
|
||||
.inner
|
||||
.storage
|
||||
.insert_deposit_usage_error(deposit_id, error)
|
||||
.await
|
||||
{
|
||||
error!("failed to insert information about deposit (id: {deposit_id}) usage failure: {err}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,7 +267,7 @@ impl ApiState {
|
||||
Option<AggregatedExpirationDateSignaturesResponse>,
|
||||
Option<AggregatedCoinIndicesSignaturesResponse>,
|
||||
),
|
||||
VpnApiError,
|
||||
CredentialProxyError,
|
||||
> {
|
||||
let master_verification_key = if include_master_verification_key {
|
||||
debug!("including master verification key in the response");
|
||||
@@ -338,7 +370,7 @@ impl ApiState {
|
||||
pub(crate) async fn ecash_clients(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<RwLockReadGuard<'_, Vec<EcashApiClient>>, VpnApiError> {
|
||||
) -> Result<RwLockReadGuard<'_, Vec<EcashApiClient>>, CredentialProxyError> {
|
||||
self.inner
|
||||
.ecash_state
|
||||
.epoch_clients
|
||||
@@ -355,7 +387,10 @@ impl ApiState {
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn ecash_threshold(&self, epoch_id: EpochId) -> Result<u64, VpnApiError> {
|
||||
pub(crate) async fn ecash_threshold(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<u64, CredentialProxyError> {
|
||||
self.inner
|
||||
.ecash_state
|
||||
.threshold_values
|
||||
@@ -368,7 +403,7 @@ impl ApiState {
|
||||
{
|
||||
Ok(threshold)
|
||||
} else {
|
||||
Err(VpnApiError::UnavailableThreshold { epoch_id })
|
||||
Err(CredentialProxyError::UnavailableThreshold { epoch_id })
|
||||
}
|
||||
})
|
||||
.await
|
||||
@@ -388,7 +423,7 @@ impl ApiState {
|
||||
pub(crate) async fn master_verification_key(
|
||||
&self,
|
||||
epoch_id: Option<EpochId>,
|
||||
) -> Result<RwLockReadGuard<'_, VerificationKeyAuth>, VpnApiError> {
|
||||
) -> Result<RwLockReadGuard<'_, VerificationKeyAuth>, CredentialProxyError> {
|
||||
let epoch_id = match epoch_id {
|
||||
Some(id) => id,
|
||||
None => self.current_epoch_id().await?,
|
||||
@@ -415,7 +450,7 @@ impl ApiState {
|
||||
let threshold = self.ecash_threshold(epoch_id).await?;
|
||||
|
||||
if all_apis.len() < threshold as usize {
|
||||
return Err(VpnApiError::InsufficientNumberOfSigners {
|
||||
return Err(CredentialProxyError::InsufficientNumberOfSigners {
|
||||
threshold,
|
||||
available: all_apis.len(),
|
||||
});
|
||||
@@ -442,7 +477,7 @@ impl ApiState {
|
||||
pub(crate) async fn master_coin_index_signatures(
|
||||
&self,
|
||||
epoch_id: Option<EpochId>,
|
||||
) -> Result<RwLockReadGuard<'_, AggregatedCoinIndicesSignatures>, VpnApiError> {
|
||||
) -> Result<RwLockReadGuard<'_, AggregatedCoinIndicesSignatures>, CredentialProxyError> {
|
||||
let epoch_id = match epoch_id {
|
||||
Some(id) => id,
|
||||
None => self.current_epoch_id().await?,
|
||||
@@ -519,7 +554,7 @@ impl ApiState {
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
expiration_date: Date,
|
||||
) -> Result<RwLockReadGuard<'_, AggregatedExpirationDateSignatures>, VpnApiError> {
|
||||
) -> Result<RwLockReadGuard<'_, AggregatedExpirationDateSignatures>, CredentialProxyError> {
|
||||
self.inner
|
||||
.ecash_state
|
||||
.expiration_date_signatures
|
||||
@@ -598,25 +633,25 @@ impl ApiState {
|
||||
pub struct ChainClient(Arc<RwLock<DirectSigningHttpRpcNyxdClient>>);
|
||||
|
||||
impl ChainClient {
|
||||
pub fn new(mnemonic: Mnemonic) -> Result<Self, VpnApiError> {
|
||||
pub fn new(mnemonic: 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(|| VpnApiError::NoNyxEndpointsAvailable)?
|
||||
.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(VpnApiError::UnavailableEcashContract);
|
||||
return Err(CredentialProxyError::UnavailableEcashContract);
|
||||
}
|
||||
|
||||
if client.dkg_contract_address().is_none() {
|
||||
return Err(VpnApiError::UnavailableDKGContract);
|
||||
return Err(CredentialProxyError::UnavailableDKGContract);
|
||||
}
|
||||
|
||||
Ok(ChainClient(Arc::new(RwLock::new(client))))
|
||||
@@ -637,14 +672,12 @@ impl ChainClient {
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
struct ApiStateInner {
|
||||
storage: VpnApiStorage,
|
||||
struct CredentialProxyStateInner {
|
||||
storage: CredentialProxyStorage,
|
||||
|
||||
client: ChainClient,
|
||||
|
||||
deposit_requester: DepositRequestSender,
|
||||
deposits_buffer: DepositsBuffer,
|
||||
|
||||
zk_nym_web_hook_config: ZkNymWebHookConfig,
|
||||
|
||||
@@ -655,39 +688,10 @@ struct ApiStateInner {
|
||||
cancellation_token: CancellationToken,
|
||||
}
|
||||
|
||||
pub(crate) struct CachedDeposit {
|
||||
valid_until: OffsetDateTime,
|
||||
required_amount: Coin,
|
||||
}
|
||||
|
||||
impl CachedDeposit {
|
||||
const MAX_VALIDITY: time::Duration = time::Duration::MINUTE;
|
||||
|
||||
fn is_valid(&self) -> bool {
|
||||
self.valid_until > OffsetDateTime::now_utc()
|
||||
}
|
||||
|
||||
fn update(&mut self, required_amount: Coin) {
|
||||
self.valid_until = OffsetDateTime::now_utc() + Self::MAX_VALIDITY;
|
||||
self.required_amount = required_amount;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CachedDeposit {
|
||||
fn default() -> Self {
|
||||
CachedDeposit {
|
||||
valid_until: OffsetDateTime::UNIX_EPOCH,
|
||||
required_amount: Coin {
|
||||
amount: u128::MAX,
|
||||
denom: "unym".to_string(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct EcashState {
|
||||
pub(crate) required_deposit_cache: RwLock<CachedDeposit>,
|
||||
pub(crate) required_deposit_cache: RequiredDepositCache,
|
||||
|
||||
pub(crate) quorum_state: QuorumState,
|
||||
|
||||
pub(crate) cached_epoch: RwLock<CachedEpoch>,
|
||||
|
||||
@@ -714,11 +718,12 @@ pub(crate) struct ChainWritePermit<'a> {
|
||||
}
|
||||
|
||||
impl ChainWritePermit<'_> {
|
||||
#[instrument(skip(self, short_sha, info), err(Display))]
|
||||
pub(crate) async fn make_deposits(
|
||||
self,
|
||||
short_sha: &'static str,
|
||||
info: Vec<(String, Coin)>,
|
||||
) -> Result<ExecuteResult, VpnApiError> {
|
||||
) -> Result<ExecuteResult, CredentialProxyError> {
|
||||
let address = self.inner.address();
|
||||
let starting_sequence = self.inner.get_sequence(&address).await?.sequence;
|
||||
|
||||
@@ -727,7 +732,7 @@ impl ChainWritePermit<'_> {
|
||||
let ecash_contract = self
|
||||
.inner
|
||||
.ecash_contract_address()
|
||||
.ok_or(VpnApiError::UnavailableEcashContract)?;
|
||||
.ok_or(CredentialProxyError::UnavailableEcashContract)?;
|
||||
let deposit_messages = info
|
||||
.into_iter()
|
||||
.map(|(identity_key, amount)| {
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::error::CredentialProxyError;
|
||||
use crate::http::state::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 {
|
||||
valid_until: OffsetDateTime,
|
||||
required_amount: Coin,
|
||||
}
|
||||
|
||||
impl CachedDeposit {
|
||||
const MAX_VALIDITY: time::Duration = time::Duration::MINUTE;
|
||||
|
||||
fn is_valid(&self) -> bool {
|
||||
self.valid_until > OffsetDateTime::now_utc()
|
||||
}
|
||||
|
||||
fn update(&mut self, required_amount: Coin) {
|
||||
self.valid_until = OffsetDateTime::now_utc() + Self::MAX_VALIDITY;
|
||||
self.required_amount = required_amount;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CachedDeposit {
|
||||
fn default() -> Self {
|
||||
CachedDeposit {
|
||||
valid_until: OffsetDateTime::UNIX_EPOCH,
|
||||
required_amount: Coin {
|
||||
amount: u128::MAX,
|
||||
denom: "unym".to_string(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub(crate) struct RequiredDepositCache {
|
||||
inner: Arc<RwLock<CachedDeposit>>,
|
||||
}
|
||||
|
||||
impl RequiredDepositCache {
|
||||
pub(crate) async fn get_or_update(
|
||||
&self,
|
||||
chain_client: &ChainClient,
|
||||
) -> Result<Coin, CredentialProxyError> {
|
||||
let read_guard = self.inner.read().await;
|
||||
if read_guard.is_valid() {
|
||||
return Ok(read_guard.required_amount.clone());
|
||||
}
|
||||
|
||||
// update cache
|
||||
drop(read_guard);
|
||||
let mut write_guard = self.inner.write().await;
|
||||
let deposit_amount = chain_client
|
||||
.query_chain()
|
||||
.await
|
||||
.get_required_deposit_amount()
|
||||
.await?;
|
||||
|
||||
let nym_coin: Coin = deposit_amount.into();
|
||||
|
||||
write_guard.update(nym_coin.clone());
|
||||
Ok(nym_coin)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2024 Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::error::VpnApiError;
|
||||
use crate::error::CredentialProxyError;
|
||||
use axum::http::StatusCode;
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use axum::Json;
|
||||
@@ -35,7 +35,7 @@ impl RequestError {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_server_error(err: VpnApiError, uuid: Uuid) -> Self {
|
||||
pub fn new_server_error(err: CredentialProxyError, uuid: Uuid) -> Self {
|
||||
RequestError::new_with_uuid(err.to_string(), uuid, StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
// Copyright 2024 Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#![warn(clippy::expect_used)]
|
||||
#![warn(clippy::unwrap_used)]
|
||||
#![warn(clippy::todo)]
|
||||
#![warn(clippy::dbg_macro)]
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(unix)] {
|
||||
use crate::cli::Cli;
|
||||
@@ -18,7 +13,6 @@ cfg_if::cfg_if! {
|
||||
pub mod cli;
|
||||
pub mod config;
|
||||
pub mod credentials;
|
||||
mod deposit_maker;
|
||||
pub mod error;
|
||||
pub mod helpers;
|
||||
pub mod http;
|
||||
@@ -26,6 +20,8 @@ cfg_if::cfg_if! {
|
||||
pub mod storage;
|
||||
pub mod tasks;
|
||||
mod webhook;
|
||||
mod deposits_buffer;
|
||||
mod quorum_checker;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,5 +51,6 @@ async fn main() -> anyhow::Result<()> {
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
eprintln!("This tool is only supported on Unix systems");
|
||||
#[allow(clippy::exit)]
|
||||
std::process::exit(1)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// TODO: this was just copied from nym-api;
|
||||
// it should have been therefore extracted to a common crate instead and imported as dependency
|
||||
|
||||
use crate::error::VpnApiError;
|
||||
use crate::error::CredentialProxyError;
|
||||
use futures::{stream, StreamExt};
|
||||
use nym_credentials::ecash::utils::{cred_exp_date, ecash_today, EcashTime};
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
@@ -129,16 +129,18 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_sane_expiration_date(expiration_date: Date) -> Result<(), VpnApiError> {
|
||||
pub(crate) fn ensure_sane_expiration_date(
|
||||
expiration_date: Date,
|
||||
) -> Result<(), CredentialProxyError> {
|
||||
let today = ecash_today();
|
||||
|
||||
if expiration_date < today.date() {
|
||||
// what's the point of signatures with expiration in the past?
|
||||
return Err(VpnApiError::ExpirationDateTooEarly);
|
||||
return Err(CredentialProxyError::ExpirationDateTooEarly);
|
||||
}
|
||||
|
||||
if expiration_date > cred_exp_date().ecash_date() {
|
||||
return Err(VpnApiError::ExpirationDateTooLate);
|
||||
return Err(CredentialProxyError::ExpirationDateTooLate);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -148,10 +150,10 @@ pub(crate) async fn query_all_threshold_apis<F, T, U>(
|
||||
all_apis: Vec<EcashApiClient>,
|
||||
threshold: u64,
|
||||
f: F,
|
||||
) -> Result<Vec<T>, VpnApiError>
|
||||
) -> Result<Vec<T>, CredentialProxyError>
|
||||
where
|
||||
F: Fn(EcashApiClient) -> U,
|
||||
U: Future<Output = Result<T, VpnApiError>>,
|
||||
U: Future<Output = Result<T, CredentialProxyError>>,
|
||||
{
|
||||
let shares = Mutex::new(Vec::with_capacity(all_apis.len()));
|
||||
|
||||
@@ -172,7 +174,7 @@ where
|
||||
let shares = shares.into_inner();
|
||||
|
||||
if shares.len() < threshold as usize {
|
||||
return Err(VpnApiError::InsufficientNumberOfSigners {
|
||||
return Err(CredentialProxyError::InsufficientNumberOfSigners {
|
||||
threshold,
|
||||
available: shares.len(),
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user