Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8d305c29eb | |||
| e057f672fa | |||
| edc2a367c0 | |||
| e78040f0ec | |||
| 5e0d1bb14e | |||
| c16746a47b | |||
| a21052b72e | |||
| 92e9da7be5 | |||
| 143b336978 | |||
| d4293c9bae | |||
| e2d1806e49 | |||
| 469f85fc49 | |||
| 1202a2f5f4 | |||
| 6030bf6c95 | |||
| 09a771f58f | |||
| 676a909aee | |||
| e37145422c | |||
| 4ad52accc0 | |||
| 784fae2204 | |||
| 8aa5711bee | |||
| 07022314fc | |||
| 76c3081470 | |||
| d399161d31 | |||
| 27fb4ae0cc | |||
| 74392a2886 | |||
| 457c478a03 | |||
| 5e95992427 | |||
| d7eecd481c | |||
| e08fc4894b | |||
| fabd48b7ea | |||
| 894e0bd1bf | |||
| f86e088663 |
@@ -5,7 +5,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-20.04-16-core
|
||||
runs-on: arc-ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Dependencies (Linux)
|
||||
@@ -99,24 +99,3 @@ jobs:
|
||||
run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}
|
||||
working-directory: dist/docs
|
||||
continue-on-error: false
|
||||
|
||||
- name: Matrix - Node Install
|
||||
run: npm install
|
||||
working-directory: .github/workflows/support-files
|
||||
- name: Matrix - Send Notification
|
||||
env:
|
||||
NYM_NOTIFICATION_KIND: cd-docs
|
||||
NYM_PROJECT_NAME: "Docs CD"
|
||||
NYM_CI_WWW_BASE: "${{ secrets.NYM_CD_WWW_BASE }}"
|
||||
NYM_CI_WWW_LOCATION: "${{ env.GITHUB_REF_SLUG }}"
|
||||
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
|
||||
GIT_BRANCH: "${GITHUB_REF##*/}"
|
||||
MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}"
|
||||
MATRIX_ROOM: "${{ secrets.MATRIX_ROOM_DOCS }}"
|
||||
MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}"
|
||||
MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}"
|
||||
MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}"
|
||||
IS_SUCCESS: "${{ job.status == 'success' }}"
|
||||
uses: docker://keybaseio/client:stable-node
|
||||
with:
|
||||
args: .github/workflows/support-files/notifications/entry_point.sh
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
name: ci-build-ts
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths:
|
||||
- "ts-packages/**"
|
||||
@@ -9,7 +10,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-20.04-16-core
|
||||
runs-on: arc-ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install rsync
|
||||
@@ -45,23 +46,3 @@ jobs:
|
||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/ts-${{ env.GITHUB_REF_SLUG }}-example
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
- name: Matrix - Node Install
|
||||
run: npm install
|
||||
working-directory: .github/workflows/support-files
|
||||
- name: Matrix - Send Notification
|
||||
env:
|
||||
NYM_NOTIFICATION_KIND: ts-packages
|
||||
NYM_PROJECT_NAME: "ts-packages"
|
||||
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
|
||||
NYM_CI_WWW_LOCATION: "ts-${{ env.GITHUB_REF_SLUG }}"
|
||||
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
|
||||
GIT_BRANCH: "${GITHUB_REF##*/}"
|
||||
IS_SUCCESS: "${{ job.status == 'success' }}"
|
||||
MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}"
|
||||
MATRIX_ROOM: "${{ secrets.MATRIX_ROOM }}"
|
||||
MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}"
|
||||
MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}"
|
||||
MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}"
|
||||
uses: docker://keybaseio/client:stable-node
|
||||
with:
|
||||
args: .github/workflows/support-files/notifications/entry_point.sh
|
||||
|
||||
@@ -10,7 +10,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-20.04-16-core
|
||||
runs-on: arc-ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Dependencies (Linux)
|
||||
@@ -70,24 +70,3 @@ jobs:
|
||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/docs-${{ env.GITHUB_REF_SLUG }}
|
||||
EXCLUDE: "/node_modules/"
|
||||
|
||||
- name: Matrix - Node Install
|
||||
run: npm install
|
||||
working-directory: .github/workflows/support-files
|
||||
- name: Matrix - Send Notification
|
||||
env:
|
||||
NYM_NOTIFICATION_KIND: ci-docs
|
||||
NYM_PROJECT_NAME: "Docs CI"
|
||||
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
|
||||
NYM_CI_WWW_LOCATION: "docs-${{ env.GITHUB_REF_SLUG }}"
|
||||
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
|
||||
GIT_BRANCH: "${GITHUB_REF##*/}"
|
||||
MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}"
|
||||
MATRIX_ROOM: "${{ secrets.MATRIX_ROOM_DOCS }}"
|
||||
MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}"
|
||||
MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}"
|
||||
MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}"
|
||||
IS_SUCCESS: "${{ job.status == 'success' }}"
|
||||
uses: docker://keybaseio/client:stable-node
|
||||
with:
|
||||
args: .github/workflows/support-files/notifications/entry_point.sh
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
name: ci-lint-typescript
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths:
|
||||
- "ts-packages/**"
|
||||
@@ -14,7 +15,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-20.04-16-core
|
||||
runs-on: arc-ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: rlespinasse/github-slug-action@v3.x
|
||||
@@ -53,24 +54,3 @@ jobs:
|
||||
run: yarn lint
|
||||
- name: Typecheck with tsc
|
||||
run: yarn tsc
|
||||
|
||||
- name: Matrix - Node Install
|
||||
run: npm install
|
||||
working-directory: .github/workflows/support-files
|
||||
- name: Matrix - Send Notification
|
||||
env:
|
||||
NYM_NOTIFICATION_KIND: ts-packages
|
||||
NYM_PROJECT_NAME: "ts-packages"
|
||||
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
|
||||
NYM_CI_WWW_LOCATION: "ts-${{ env.GITHUB_REF_SLUG }}"
|
||||
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
|
||||
GIT_BRANCH: "${GITHUB_REF##*/}"
|
||||
IS_SUCCESS: "${{ job.status == 'success' }}"
|
||||
MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}"
|
||||
MATRIX_ROOM: "${{ secrets.MATRIX_ROOM }}"
|
||||
MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}"
|
||||
MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}"
|
||||
MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}"
|
||||
uses: docker://keybaseio/client:stable-node
|
||||
with:
|
||||
args: .github/workflows/support-files/notifications/entry_point.sh
|
||||
|
||||
@@ -4,7 +4,7 @@ on:
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-20.04-16-core
|
||||
runs-on: arc-ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
||||
Generated
+133
-75
@@ -341,9 +341,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.82"
|
||||
version = "0.1.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1"
|
||||
checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -431,19 +431,19 @@ dependencies = [
|
||||
"rustversion",
|
||||
"serde",
|
||||
"sync_wrapper 0.1.2",
|
||||
"tower",
|
||||
"tower 0.4.13",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.7.5"
|
||||
version = "0.7.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf"
|
||||
checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core 0.4.3",
|
||||
"axum-core 0.4.5",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http 1.1.0",
|
||||
@@ -464,7 +464,7 @@ dependencies = [
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper 1.0.1",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower 0.5.1",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -489,9 +489,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.4.3"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3"
|
||||
checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
@@ -502,7 +502,7 @@ dependencies = [
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"sync_wrapper 0.1.2",
|
||||
"sync_wrapper 1.0.1",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -510,12 +510,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "axum-extra"
|
||||
version = "0.9.3"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0be6ea09c9b96cb5076af0de2e383bd2bc0c18f827cf1967bdd353e0b910d733"
|
||||
checksum = "73c3220b188aea709cf1b6c5f9b01c3bd936bb08bd2b5184a12b35ac8131b1f9"
|
||||
dependencies = [
|
||||
"axum 0.7.5",
|
||||
"axum-core 0.4.3",
|
||||
"axum 0.7.7",
|
||||
"axum-core 0.4.5",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"headers",
|
||||
@@ -525,7 +525,7 @@ dependencies = [
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
"serde",
|
||||
"tower",
|
||||
"tower 0.5.1",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -576,6 +576,12 @@ version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||
|
||||
[[package]]
|
||||
name = "base85rs"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87678d33a2af71f019ed11f52db246ca6c5557edee2cccbe689676d1ad9c6b5a"
|
||||
|
||||
[[package]]
|
||||
name = "basic-toml"
|
||||
version = "0.1.9"
|
||||
@@ -807,9 +813,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.7.1"
|
||||
version = "1.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
|
||||
checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -1009,9 +1015,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.17"
|
||||
version = "4.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac"
|
||||
checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -1019,9 +1025,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.17"
|
||||
version = "4.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73"
|
||||
checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -1031,11 +1037,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_complete"
|
||||
version = "4.5.28"
|
||||
version = "4.5.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b378c786d3bde9442d2c6dd7e6080b2a818db2b96e30d6e7f1b6d224eb617d3"
|
||||
checksum = "8937760c3f4c60871870b8c3ee5f9b30771f792a7045c48bcbba999d7d6b3b8e"
|
||||
dependencies = [
|
||||
"clap 4.5.17",
|
||||
"clap 4.5.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1044,15 +1050,15 @@ version = "4.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d494102c8ff3951810c72baf96910b980fb065ca5d3101243e6a8dc19747c86b"
|
||||
dependencies = [
|
||||
"clap 4.5.17",
|
||||
"clap 4.5.18",
|
||||
"clap_complete",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.13"
|
||||
version = "4.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0"
|
||||
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
@@ -1452,7 +1458,7 @@ dependencies = [
|
||||
"anes",
|
||||
"cast",
|
||||
"ciborium",
|
||||
"clap 4.5.17",
|
||||
"clap 4.5.18",
|
||||
"criterion-plot",
|
||||
"is-terminal",
|
||||
"itertools 0.10.5",
|
||||
@@ -2320,7 +2326,7 @@ name = "explorer-api"
|
||||
version = "1.1.40"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap 4.5.17",
|
||||
"clap 4.5.18",
|
||||
"dotenvy",
|
||||
"humantime-serde",
|
||||
"isocountry",
|
||||
@@ -2488,9 +2494,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.33"
|
||||
version = "1.0.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253"
|
||||
checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide 0.8.0",
|
||||
@@ -3233,9 +3239,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.5"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56"
|
||||
checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
@@ -3246,7 +3252,6 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
@@ -3300,6 +3305,34 @@ dependencies = [
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "importer-cli"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bip39",
|
||||
"clap 4.5.18",
|
||||
"dirs 5.0.1",
|
||||
"importer-contract",
|
||||
"nym-bin-common",
|
||||
"nym-network-defaults",
|
||||
"nym-validator-client",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "importer-contract"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base85rs",
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cosmwasm-storage",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indenter"
|
||||
version = "0.3.3"
|
||||
@@ -4227,14 +4260,14 @@ version = "1.1.44"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"axum 0.7.5",
|
||||
"axum 0.7.7",
|
||||
"axum-extra",
|
||||
"bincode",
|
||||
"bip39",
|
||||
"bloomfilter",
|
||||
"bs58",
|
||||
"cfg-if",
|
||||
"clap 4.5.17",
|
||||
"clap 4.5.18",
|
||||
"console-subscriber",
|
||||
"cosmwasm-std",
|
||||
"cw-utils",
|
||||
@@ -4355,7 +4388,7 @@ dependencies = [
|
||||
"bincode",
|
||||
"bs58",
|
||||
"bytes",
|
||||
"clap 4.5.17",
|
||||
"clap 4.5.18",
|
||||
"defguard_wireguard_rs",
|
||||
"fastrand 2.1.1",
|
||||
"futures",
|
||||
@@ -4433,7 +4466,7 @@ dependencies = [
|
||||
name = "nym-bin-common"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"clap 4.5.17",
|
||||
"clap 4.5.18",
|
||||
"clap_complete",
|
||||
"clap_complete_fig",
|
||||
"const-str",
|
||||
@@ -4475,7 +4508,7 @@ dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bip39",
|
||||
"bs58",
|
||||
"clap 4.5.17",
|
||||
"clap 4.5.18",
|
||||
"clap_complete",
|
||||
"clap_complete_fig",
|
||||
"dotenvy",
|
||||
@@ -4501,7 +4534,7 @@ dependencies = [
|
||||
"bip39",
|
||||
"bs58",
|
||||
"cfg-if",
|
||||
"clap 4.5.17",
|
||||
"clap 4.5.18",
|
||||
"colored",
|
||||
"comfy-table",
|
||||
"cosmrs 0.17.0-pre",
|
||||
@@ -4553,7 +4586,7 @@ name = "nym-client"
|
||||
version = "1.1.41"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"clap 4.5.17",
|
||||
"clap 4.5.18",
|
||||
"dirs 5.0.1",
|
||||
"futures",
|
||||
"log",
|
||||
@@ -4593,7 +4626,7 @@ dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bs58",
|
||||
"cfg-if",
|
||||
"clap 4.5.17",
|
||||
"clap 4.5.18",
|
||||
"comfy-table",
|
||||
"futures",
|
||||
"gloo-timers",
|
||||
@@ -4978,7 +5011,7 @@ name = "nym-data-observatory"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.5",
|
||||
"axum 0.7.7",
|
||||
"chrono",
|
||||
"nym-bin-common",
|
||||
"nym-network-defaults",
|
||||
@@ -5119,7 +5152,7 @@ dependencies = [
|
||||
"async-trait",
|
||||
"bip39",
|
||||
"bs58",
|
||||
"clap 4.5.17",
|
||||
"clap 4.5.18",
|
||||
"colored",
|
||||
"dashmap",
|
||||
"defguard_wireguard_rs",
|
||||
@@ -5178,6 +5211,7 @@ dependencies = [
|
||||
"nym-bandwidth-controller",
|
||||
"nym-credential-storage",
|
||||
"nym-credentials",
|
||||
"nym-credentials-interface",
|
||||
"nym-crypto",
|
||||
"nym-gateway-requests",
|
||||
"nym-network-defaults",
|
||||
@@ -5294,7 +5328,7 @@ dependencies = [
|
||||
name = "nym-http-api-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"axum 0.7.5",
|
||||
"axum 0.7.7",
|
||||
"bytes",
|
||||
"colored",
|
||||
"mime",
|
||||
@@ -5323,7 +5357,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bs58",
|
||||
"clap 4.5.17",
|
||||
"clap 4.5.18",
|
||||
"nym-bin-common",
|
||||
"nym-credential-storage",
|
||||
"nym-id",
|
||||
@@ -5365,7 +5399,7 @@ dependencies = [
|
||||
"bincode",
|
||||
"bs58",
|
||||
"bytes",
|
||||
"clap 4.5.17",
|
||||
"clap 4.5.18",
|
||||
"etherparse",
|
||||
"futures",
|
||||
"log",
|
||||
@@ -5460,9 +5494,9 @@ name = "nym-mixnode"
|
||||
version = "1.1.37"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.5",
|
||||
"axum 0.7.7",
|
||||
"bs58",
|
||||
"clap 4.5.17",
|
||||
"clap 4.5.18",
|
||||
"colored",
|
||||
"cupid",
|
||||
"dirs 5.0.1",
|
||||
@@ -5562,8 +5596,8 @@ name = "nym-network-monitor"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.5",
|
||||
"clap 4.5.17",
|
||||
"axum 0.7.7",
|
||||
"clap 4.5.18",
|
||||
"dashmap",
|
||||
"futures",
|
||||
"log",
|
||||
@@ -5595,7 +5629,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"bs58",
|
||||
"clap 4.5.17",
|
||||
"clap 4.5.18",
|
||||
"dirs 5.0.1",
|
||||
"futures",
|
||||
"humantime-serde",
|
||||
@@ -5647,7 +5681,7 @@ dependencies = [
|
||||
"bs58",
|
||||
"cargo_metadata 0.18.1",
|
||||
"celes",
|
||||
"clap 4.5.17",
|
||||
"clap 4.5.18",
|
||||
"colored",
|
||||
"cupid",
|
||||
"humantime-serde",
|
||||
@@ -5686,7 +5720,7 @@ dependencies = [
|
||||
name = "nym-node-http-api"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"axum 0.7.5",
|
||||
"axum 0.7.7",
|
||||
"axum-extra",
|
||||
"base64 0.22.1",
|
||||
"colored",
|
||||
@@ -5707,7 +5741,7 @@ dependencies = [
|
||||
"thiserror",
|
||||
"time",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower 0.4.13",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
"utoipa",
|
||||
@@ -5794,7 +5828,7 @@ name = "nym-nr-query"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.5.17",
|
||||
"clap 4.5.18",
|
||||
"log",
|
||||
"nym-bin-common",
|
||||
"nym-network-defaults",
|
||||
@@ -5899,6 +5933,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bs58",
|
||||
"hex",
|
||||
"serde",
|
||||
"time",
|
||||
]
|
||||
@@ -5932,7 +5967,7 @@ name = "nym-socks5-client"
|
||||
version = "1.1.41"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"clap 4.5.17",
|
||||
"clap 4.5.18",
|
||||
"log",
|
||||
"nym-bin-common",
|
||||
"nym-client-core",
|
||||
@@ -6167,9 +6202,15 @@ name = "nym-sphinx-framing"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"log",
|
||||
"nym-metrics",
|
||||
"nym-sphinx-acknowledgements",
|
||||
"nym-sphinx-addressing",
|
||||
"nym-sphinx-forwarding",
|
||||
"nym-sphinx-params",
|
||||
"nym-sphinx-types",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
]
|
||||
|
||||
@@ -6329,6 +6370,7 @@ dependencies = [
|
||||
"nym-mixnet-contract-common",
|
||||
"nym-multisig-contract-common",
|
||||
"nym-network-defaults",
|
||||
"nym-serde-helpers",
|
||||
"nym-vesting-contract-common",
|
||||
"prost 0.12.6",
|
||||
"reqwest 0.12.4",
|
||||
@@ -6351,7 +6393,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bip39",
|
||||
"clap 4.5.17",
|
||||
"clap 4.5.18",
|
||||
"cosmwasm-std",
|
||||
"futures",
|
||||
"humantime 2.1.0",
|
||||
@@ -6461,7 +6503,7 @@ version = "0.1.7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
"clap 4.5.17",
|
||||
"clap 4.5.18",
|
||||
"dotenvy",
|
||||
"flate2",
|
||||
"futures",
|
||||
@@ -8626,7 +8668,7 @@ name = "ssl-inject"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.5.17",
|
||||
"clap 4.5.18",
|
||||
"hex",
|
||||
"tokio",
|
||||
]
|
||||
@@ -8861,9 +8903,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||
|
||||
[[package]]
|
||||
name = "tar"
|
||||
version = "0.4.41"
|
||||
version = "0.4.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb797dad5fb5b76fcf519e702f4a589483b5ef06567f160c392832c1f5e44909"
|
||||
checksum = "4ff6c40d3aedb5e06b57c6f669ad17ab063dd1e63d977c6a88e7f4dfa4f04020"
|
||||
dependencies = [
|
||||
"filetime",
|
||||
"libc",
|
||||
@@ -9042,7 +9084,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"bip39",
|
||||
"bs58",
|
||||
"clap 4.5.17",
|
||||
"clap 4.5.18",
|
||||
"console",
|
||||
"cw-utils",
|
||||
"dkg-bypass-contract",
|
||||
@@ -9082,18 +9124,18 @@ checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.63"
|
||||
version = "1.0.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
|
||||
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.63"
|
||||
version = "1.0.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
|
||||
checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -9419,7 +9461,7 @@ dependencies = [
|
||||
"prost 0.11.9",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tower",
|
||||
"tower 0.4.13",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -9445,6 +9487,22 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"pin-project-lite",
|
||||
"sync_wrapper 0.1.2",
|
||||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.5.2"
|
||||
@@ -9472,15 +9530,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tower-layer"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
|
||||
checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
|
||||
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
@@ -9841,7 +9899,7 @@ checksum = "21345172d31092fd48c47fd56c53d4ae9e41c4b1f559fb8c38c1ab1685fd919f"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"camino",
|
||||
"clap 4.5.17",
|
||||
"clap 4.5.18",
|
||||
"uniffi_bindgen",
|
||||
"uniffi_build",
|
||||
"uniffi_core",
|
||||
@@ -9858,7 +9916,7 @@ dependencies = [
|
||||
"askama",
|
||||
"camino",
|
||||
"cargo_metadata 0.15.4",
|
||||
"clap 4.5.17",
|
||||
"clap 4.5.18",
|
||||
"fs-err",
|
||||
"glob",
|
||||
"goblin",
|
||||
@@ -10069,7 +10127,7 @@ version = "7.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "943e0ff606c6d57d410fd5663a4d7c074ab2c5f14ab903b9514565e59fa1189e"
|
||||
dependencies = [
|
||||
"axum 0.7.5",
|
||||
"axum 0.7.7",
|
||||
"mime_guess",
|
||||
"regex",
|
||||
"reqwest 0.12.4",
|
||||
|
||||
+9
-7
@@ -138,6 +138,8 @@ members = [
|
||||
"tools/internal/testnet-manager",
|
||||
"tools/internal/testnet-manager/dkg-bypass-contract",
|
||||
"tools/echo-server",
|
||||
"tools/internal/contract-state-importer/importer-cli",
|
||||
"tools/internal/contract-state-importer/importer-contract",
|
||||
]
|
||||
|
||||
default-members = [
|
||||
@@ -182,9 +184,9 @@ aes-gcm-siv = "0.11.1"
|
||||
aead = "0.5.2"
|
||||
anyhow = "1.0.89"
|
||||
argon2 = "0.5.0"
|
||||
async-trait = "0.1.82"
|
||||
async-trait = "0.1.83"
|
||||
axum = "0.7.5"
|
||||
axum-extra = "0.9.3"
|
||||
axum-extra = "0.9.4"
|
||||
base64 = "0.22.1"
|
||||
bincode = "1.3.3"
|
||||
bip39 = { version = "2.0.0", features = ["zeroize"] }
|
||||
@@ -197,7 +199,7 @@ blake3 = "1.5.4"
|
||||
bloomfilter = "1.0.14"
|
||||
bs58 = "0.5.1"
|
||||
bytecodec = "0.4.15"
|
||||
bytes = "1.7.1"
|
||||
bytes = "1.7.2"
|
||||
cargo_metadata = "0.18.1"
|
||||
celes = "2.4.0"
|
||||
cfg-if = "1.0.0"
|
||||
@@ -205,7 +207,7 @@ chacha20 = "0.9.0"
|
||||
chacha20poly1305 = "0.10.1"
|
||||
chrono = "0.4.31"
|
||||
cipher = "0.4.3"
|
||||
clap = "4.5.17"
|
||||
clap = "4.5.18"
|
||||
clap_complete = "4.5"
|
||||
clap_complete_fig = "4.5"
|
||||
colored = "2.0"
|
||||
@@ -232,7 +234,7 @@ ed25519-dalek = "2.1"
|
||||
etherparse = "0.13.0"
|
||||
eyre = "0.6.9"
|
||||
fastrand = "2.1.1"
|
||||
flate2 = "1.0.33"
|
||||
flate2 = "1.0.34"
|
||||
futures = "0.3.28"
|
||||
generic-array = "0.14.7"
|
||||
getrandom = "0.2.10"
|
||||
@@ -307,9 +309,9 @@ subtle-encoding = "0.5"
|
||||
syn = "1"
|
||||
sysinfo = "0.30.13"
|
||||
tap = "1.0.1"
|
||||
tar = "0.4.41"
|
||||
tar = "0.4.42"
|
||||
tempfile = "3.5.0"
|
||||
thiserror = "1.0.63"
|
||||
thiserror = "1.0.64"
|
||||
time = "0.3.30"
|
||||
tokio = "1.39"
|
||||
tokio-stream = "0.1.16"
|
||||
|
||||
@@ -19,4 +19,7 @@ pub enum Error {
|
||||
#[source]
|
||||
source: hmac::digest::MacError,
|
||||
},
|
||||
|
||||
#[error("conversion: {0}")]
|
||||
Conversion(String),
|
||||
}
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
|
||||
pub mod v1;
|
||||
pub mod v2;
|
||||
pub mod v3;
|
||||
|
||||
mod error;
|
||||
|
||||
pub use error::Error;
|
||||
pub use v2 as latest;
|
||||
pub use v3 as latest;
|
||||
|
||||
pub const CURRENT_VERSION: u8 = 2;
|
||||
pub const CURRENT_VERSION: u8 = 3;
|
||||
|
||||
fn make_bincode_serializer() -> impl bincode::Options {
|
||||
use bincode::Options;
|
||||
|
||||
@@ -10,4 +10,4 @@ pub use registration::{ClientMac, GatewayClient, InitMessage, Nonce};
|
||||
#[cfg(feature = "verify")]
|
||||
pub use registration::HmacSha256;
|
||||
|
||||
const VERSION: u8 = 1;
|
||||
pub const VERSION: u8 = 1;
|
||||
|
||||
@@ -62,8 +62,113 @@ impl From<v1::registration::GatewayClient> for v2::registration::GatewayClient {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v2::registration::GatewayClient> for v1::registration::GatewayClient {
|
||||
fn from(gw_client: v2::registration::GatewayClient) -> Self {
|
||||
Self {
|
||||
pub_key: gw_client.pub_key,
|
||||
private_ip: gw_client.private_ip,
|
||||
mac: gw_client.mac.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v1::registration::ClientMac> for v2::registration::ClientMac {
|
||||
fn from(mac: v1::registration::ClientMac) -> Self {
|
||||
Self::new(mac.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v2::registration::ClientMac> for v1::registration::ClientMac {
|
||||
fn from(mac: v2::registration::ClientMac) -> Self {
|
||||
Self::new(mac.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v2::response::AuthenticatorResponse> for v1::response::AuthenticatorResponse {
|
||||
fn from(authenticator_response: v2::response::AuthenticatorResponse) -> Self {
|
||||
Self {
|
||||
version: authenticator_response.protocol.version,
|
||||
data: authenticator_response.data.into(),
|
||||
reply_to: authenticator_response.reply_to,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v2::response::AuthenticatorResponseData> for v1::response::AuthenticatorResponseData {
|
||||
fn from(authenticator_response_data: v2::response::AuthenticatorResponseData) -> Self {
|
||||
match authenticator_response_data {
|
||||
v2::response::AuthenticatorResponseData::PendingRegistration(
|
||||
pending_registration_response,
|
||||
) => v1::response::AuthenticatorResponseData::PendingRegistration(
|
||||
pending_registration_response.into(),
|
||||
),
|
||||
v2::response::AuthenticatorResponseData::Registered(registered_response) => {
|
||||
v1::response::AuthenticatorResponseData::Registered(registered_response.into())
|
||||
}
|
||||
v2::response::AuthenticatorResponseData::RemainingBandwidth(
|
||||
remaining_bandwidth_response,
|
||||
) => v1::response::AuthenticatorResponseData::RemainingBandwidth(
|
||||
remaining_bandwidth_response.into(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v2::response::PendingRegistrationResponse> for v1::response::PendingRegistrationResponse {
|
||||
fn from(value: v2::response::PendingRegistrationResponse) -> Self {
|
||||
Self {
|
||||
request_id: value.request_id,
|
||||
reply_to: value.reply_to,
|
||||
reply: value.reply.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v2::response::RegisteredResponse> for v1::response::RegisteredResponse {
|
||||
fn from(value: v2::response::RegisteredResponse) -> Self {
|
||||
Self {
|
||||
request_id: value.request_id,
|
||||
reply_to: value.reply_to,
|
||||
reply: value.reply.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v2::response::RemainingBandwidthResponse> for v1::response::RemainingBandwidthResponse {
|
||||
fn from(value: v2::response::RemainingBandwidthResponse) -> Self {
|
||||
Self {
|
||||
request_id: value.request_id,
|
||||
reply_to: value.reply_to,
|
||||
reply: value.reply.map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v2::registration::RegistrationData> for v1::registration::RegistrationData {
|
||||
fn from(value: v2::registration::RegistrationData) -> Self {
|
||||
Self {
|
||||
nonce: value.nonce,
|
||||
gateway_data: value.gateway_data.into(),
|
||||
wg_port: value.wg_port,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v2::registration::RegistredData> for v1::registration::RegistredData {
|
||||
fn from(value: v2::registration::RegistredData) -> Self {
|
||||
Self {
|
||||
pub_key: value.pub_key,
|
||||
private_ip: value.private_ip,
|
||||
wg_port: value.wg_port,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v2::registration::RemainingBandwidthData> for v1::registration::RemainingBandwidthData {
|
||||
fn from(value: v2::registration::RemainingBandwidthData) -> Self {
|
||||
Self {
|
||||
available_bandwidth: value.available_bandwidth as u64,
|
||||
suspended: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,4 +6,4 @@ pub mod registration;
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
|
||||
const VERSION: u8 = 2;
|
||||
pub const VERSION: u8 = 2;
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||
|
||||
use crate::{v2, v3};
|
||||
|
||||
impl From<v2::request::AuthenticatorRequest> for v3::request::AuthenticatorRequest {
|
||||
fn from(authenticator_request: v2::request::AuthenticatorRequest) -> Self {
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
version: 2,
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
},
|
||||
data: authenticator_request.data.into(),
|
||||
reply_to: authenticator_request.reply_to,
|
||||
request_id: authenticator_request.request_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v2::request::AuthenticatorRequestData> for v3::request::AuthenticatorRequestData {
|
||||
fn from(authenticator_request_data: v2::request::AuthenticatorRequestData) -> Self {
|
||||
match authenticator_request_data {
|
||||
v2::request::AuthenticatorRequestData::Initial(init_msg) => {
|
||||
v3::request::AuthenticatorRequestData::Initial(init_msg.into())
|
||||
}
|
||||
v2::request::AuthenticatorRequestData::Final(gw_client) => {
|
||||
v3::request::AuthenticatorRequestData::Final(gw_client.into())
|
||||
}
|
||||
v2::request::AuthenticatorRequestData::QueryBandwidth(pub_key) => {
|
||||
v3::request::AuthenticatorRequestData::QueryBandwidth(pub_key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v2::registration::InitMessage> for v3::registration::InitMessage {
|
||||
fn from(init_msg: v2::registration::InitMessage) -> Self {
|
||||
Self {
|
||||
pub_key: init_msg.pub_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<v2::registration::FinalMessage>> for Box<v3::registration::FinalMessage> {
|
||||
fn from(gw_client: Box<v2::registration::FinalMessage>) -> Self {
|
||||
Box::new(v3::registration::FinalMessage {
|
||||
gateway_client: gw_client.gateway_client.into(),
|
||||
credential: gw_client.credential,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v2::registration::GatewayClient> for v3::registration::GatewayClient {
|
||||
fn from(gw_client: v2::registration::GatewayClient) -> Self {
|
||||
Self {
|
||||
pub_key: gw_client.pub_key,
|
||||
private_ip: gw_client.private_ip,
|
||||
mac: gw_client.mac.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v3::registration::GatewayClient> for v2::registration::GatewayClient {
|
||||
fn from(gw_client: v3::registration::GatewayClient) -> Self {
|
||||
Self {
|
||||
pub_key: gw_client.pub_key,
|
||||
private_ip: gw_client.private_ip,
|
||||
mac: gw_client.mac.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v2::registration::ClientMac> for v3::registration::ClientMac {
|
||||
fn from(mac: v2::registration::ClientMac) -> Self {
|
||||
Self::new(mac.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v3::registration::ClientMac> for v2::registration::ClientMac {
|
||||
fn from(mac: v3::registration::ClientMac) -> Self {
|
||||
Self::new(mac.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<v3::response::AuthenticatorResponse> for v2::response::AuthenticatorResponse {
|
||||
type Error = crate::Error;
|
||||
|
||||
fn try_from(
|
||||
authenticator_response: v3::response::AuthenticatorResponse,
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
data: authenticator_response.data.try_into()?,
|
||||
reply_to: authenticator_response.reply_to,
|
||||
protocol: authenticator_response.protocol,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<v3::response::AuthenticatorResponseData> for v2::response::AuthenticatorResponseData {
|
||||
type Error = crate::Error;
|
||||
|
||||
fn try_from(
|
||||
authenticator_response_data: v3::response::AuthenticatorResponseData,
|
||||
) -> Result<Self, Self::Error> {
|
||||
match authenticator_response_data {
|
||||
v3::response::AuthenticatorResponseData::PendingRegistration(
|
||||
pending_registration_response,
|
||||
) => Ok(
|
||||
v2::response::AuthenticatorResponseData::PendingRegistration(
|
||||
pending_registration_response.into(),
|
||||
),
|
||||
),
|
||||
v3::response::AuthenticatorResponseData::Registered(registered_response) => Ok(
|
||||
v2::response::AuthenticatorResponseData::Registered(registered_response.into()),
|
||||
),
|
||||
v3::response::AuthenticatorResponseData::RemainingBandwidth(
|
||||
remaining_bandwidth_response,
|
||||
) => Ok(v2::response::AuthenticatorResponseData::RemainingBandwidth(
|
||||
remaining_bandwidth_response.into(),
|
||||
)),
|
||||
v3::response::AuthenticatorResponseData::TopUpBandwidth(_) => {
|
||||
Err(Self::Error::Conversion(
|
||||
"a v2 request couldn't produce a v3 only type of response".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v3::response::PendingRegistrationResponse> for v2::response::PendingRegistrationResponse {
|
||||
fn from(value: v3::response::PendingRegistrationResponse) -> Self {
|
||||
Self {
|
||||
request_id: value.request_id,
|
||||
reply_to: value.reply_to,
|
||||
reply: value.reply.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v3::response::RegisteredResponse> for v2::response::RegisteredResponse {
|
||||
fn from(value: v3::response::RegisteredResponse) -> Self {
|
||||
Self {
|
||||
request_id: value.request_id,
|
||||
reply_to: value.reply_to,
|
||||
reply: value.reply.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v3::response::RemainingBandwidthResponse> for v2::response::RemainingBandwidthResponse {
|
||||
fn from(value: v3::response::RemainingBandwidthResponse) -> Self {
|
||||
Self {
|
||||
request_id: value.request_id,
|
||||
reply_to: value.reply_to,
|
||||
reply: value.reply.map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v3::registration::RegistrationData> for v2::registration::RegistrationData {
|
||||
fn from(value: v3::registration::RegistrationData) -> Self {
|
||||
Self {
|
||||
nonce: value.nonce,
|
||||
gateway_data: value.gateway_data.into(),
|
||||
wg_port: value.wg_port,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v3::registration::RegistredData> for v2::registration::RegistredData {
|
||||
fn from(value: v3::registration::RegistredData) -> Self {
|
||||
Self {
|
||||
pub_key: value.pub_key,
|
||||
private_ip: value.private_ip,
|
||||
wg_port: value.wg_port,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v3::registration::RemainingBandwidthData> for v2::registration::RemainingBandwidthData {
|
||||
fn from(value: v3::registration::RemainingBandwidthData) -> Self {
|
||||
Self {
|
||||
available_bandwidth: value.available_bandwidth,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod conversion;
|
||||
pub mod registration;
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
pub mod topup;
|
||||
|
||||
pub const VERSION: u8 = 3;
|
||||
@@ -0,0 +1,227 @@
|
||||
// -2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::Error;
|
||||
use base64::{engine::general_purpose, Engine};
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::net::IpAddr;
|
||||
use std::time::SystemTime;
|
||||
use std::{fmt, ops::Deref, str::FromStr};
|
||||
|
||||
#[cfg(feature = "verify")]
|
||||
use hmac::{Hmac, Mac};
|
||||
#[cfg(feature = "verify")]
|
||||
use nym_crypto::asymmetric::encryption::PrivateKey;
|
||||
#[cfg(feature = "verify")]
|
||||
use sha2::Sha256;
|
||||
|
||||
pub type PendingRegistrations = HashMap<PeerPublicKey, RegistrationData>;
|
||||
pub type PrivateIPs = HashMap<IpAddr, Taken>;
|
||||
|
||||
#[cfg(feature = "verify")]
|
||||
pub type HmacSha256 = Hmac<Sha256>;
|
||||
|
||||
pub type Nonce = u64;
|
||||
pub type Taken = Option<SystemTime>;
|
||||
|
||||
pub const BANDWIDTH_CAP_PER_DAY: u64 = 1024 * 1024 * 1024; // 1 GB
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct InitMessage {
|
||||
/// Base64 encoded x25519 public key
|
||||
pub pub_key: PeerPublicKey,
|
||||
}
|
||||
|
||||
impl InitMessage {
|
||||
pub fn new(pub_key: PeerPublicKey) -> Self {
|
||||
InitMessage { pub_key }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct FinalMessage {
|
||||
/// Gateway client data
|
||||
pub gateway_client: GatewayClient,
|
||||
|
||||
/// Ecash credential
|
||||
pub credential: Option<CredentialSpendingData>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct RegistrationData {
|
||||
pub nonce: u64,
|
||||
pub gateway_data: GatewayClient,
|
||||
pub wg_port: u16,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct RegistredData {
|
||||
pub pub_key: PeerPublicKey,
|
||||
pub private_ip: IpAddr,
|
||||
pub wg_port: u16,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct RemainingBandwidthData {
|
||||
pub available_bandwidth: i64,
|
||||
}
|
||||
|
||||
/// Client that wants to register sends its PublicKey bytes mac digest encrypted with a DH shared secret.
|
||||
/// Gateway/Nym node can then verify pub_key payload using the same process
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct GatewayClient {
|
||||
/// Base64 encoded x25519 public key
|
||||
pub pub_key: PeerPublicKey,
|
||||
|
||||
/// Assigned private IP
|
||||
pub private_ip: IpAddr,
|
||||
|
||||
/// Sha256 hmac on the data (alongside the prior nonce)
|
||||
pub mac: ClientMac,
|
||||
}
|
||||
|
||||
impl GatewayClient {
|
||||
#[cfg(feature = "verify")]
|
||||
pub fn new(
|
||||
local_secret: &PrivateKey,
|
||||
remote_public: x25519_dalek::PublicKey,
|
||||
private_ip: IpAddr,
|
||||
nonce: u64,
|
||||
) -> Self {
|
||||
// convert from 1.0 x25519-dalek private key into 2.0 x25519-dalek
|
||||
#[allow(clippy::expect_used)]
|
||||
let static_secret = x25519_dalek::StaticSecret::from(local_secret.to_bytes());
|
||||
let local_public: x25519_dalek::PublicKey = (&static_secret).into();
|
||||
|
||||
let dh = static_secret.diffie_hellman(&remote_public);
|
||||
|
||||
// TODO: change that to use our nym_crypto::hmac module instead
|
||||
#[allow(clippy::expect_used)]
|
||||
let mut mac = HmacSha256::new_from_slice(dh.as_bytes())
|
||||
.expect("x25519 shared secret is always 32 bytes long");
|
||||
|
||||
mac.update(local_public.as_bytes());
|
||||
mac.update(private_ip.to_string().as_bytes());
|
||||
mac.update(&nonce.to_le_bytes());
|
||||
|
||||
GatewayClient {
|
||||
pub_key: PeerPublicKey::new(local_public),
|
||||
private_ip,
|
||||
mac: ClientMac(mac.finalize().into_bytes().to_vec()),
|
||||
}
|
||||
}
|
||||
|
||||
// Reusable secret should be gateways Wireguard PK
|
||||
// Client should perform this step when generating its payload, using its own WG PK
|
||||
#[cfg(feature = "verify")]
|
||||
pub fn verify(&self, gateway_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
// convert from 1.0 x25519-dalek private key into 2.0 x25519-dalek
|
||||
#[allow(clippy::expect_used)]
|
||||
let static_secret = x25519_dalek::StaticSecret::from(gateway_key.to_bytes());
|
||||
|
||||
let dh = static_secret.diffie_hellman(&self.pub_key);
|
||||
|
||||
// TODO: change that to use our nym_crypto::hmac module instead
|
||||
#[allow(clippy::expect_used)]
|
||||
let mut mac = HmacSha256::new_from_slice(dh.as_bytes())
|
||||
.expect("x25519 shared secret is always 32 bytes long");
|
||||
|
||||
mac.update(self.pub_key.as_bytes());
|
||||
mac.update(self.private_ip.to_string().as_bytes());
|
||||
mac.update(&nonce.to_le_bytes());
|
||||
|
||||
mac.verify_slice(&self.mac)
|
||||
.map_err(|source| Error::FailedClientMacVerification {
|
||||
client: self.pub_key.to_string(),
|
||||
source,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn pub_key(&self) -> PeerPublicKey {
|
||||
self.pub_key
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: change the inner type into generic array of size HmacSha256::OutputSize
|
||||
// TODO2: rely on our internal crypto/hmac
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ClientMac(Vec<u8>);
|
||||
|
||||
impl fmt::Display for ClientMac {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", general_purpose::STANDARD.encode(&self.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientMac {
|
||||
#[allow(dead_code)]
|
||||
pub fn new(mac: Vec<u8>) -> Self {
|
||||
ClientMac(mac)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for ClientMac {
|
||||
type Target = Vec<u8>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ClientMac {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mac_bytes: Vec<u8> =
|
||||
general_purpose::STANDARD
|
||||
.decode(s)
|
||||
.map_err(|source| Error::MalformedClientMac {
|
||||
mac: s.to_string(),
|
||||
source,
|
||||
})?;
|
||||
|
||||
Ok(ClientMac(mac_bytes))
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for ClientMac {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let encoded_key = general_purpose::STANDARD.encode(self.0.clone());
|
||||
serializer.serialize_str(&encoded_key)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ClientMac {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let encoded_key = String::deserialize(deserializer)?;
|
||||
ClientMac::from_str(&encoded_key).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use nym_crypto::asymmetric::encryption;
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "verify")]
|
||||
fn client_request_roundtrip() {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let gateway_key_pair = encryption::KeyPair::new(&mut rng);
|
||||
let client_key_pair = encryption::KeyPair::new(&mut rng);
|
||||
|
||||
let nonce = 1234567890;
|
||||
|
||||
let client = GatewayClient::new(
|
||||
client_key_pair.private_key(),
|
||||
x25519_dalek::PublicKey::from(gateway_key_pair.public_key().to_bytes()),
|
||||
"10.0.0.42".parse().unwrap(),
|
||||
nonce,
|
||||
);
|
||||
assert!(client.verify(gateway_key_pair.private_key(), nonce).is_ok())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::{
|
||||
registration::{FinalMessage, InitMessage},
|
||||
topup::TopUpMessage,
|
||||
};
|
||||
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::make_bincode_serializer;
|
||||
|
||||
use super::VERSION;
|
||||
|
||||
fn generate_random() -> u64 {
|
||||
use rand::RngCore;
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
rng.next_u64()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct AuthenticatorRequest {
|
||||
pub protocol: Protocol,
|
||||
pub data: AuthenticatorRequestData,
|
||||
pub reply_to: Recipient,
|
||||
pub request_id: u64,
|
||||
}
|
||||
|
||||
impl AuthenticatorRequest {
|
||||
pub fn from_reconstructed_message(
|
||||
message: &nym_sphinx::receiver::ReconstructedMessage,
|
||||
) -> Result<Self, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().deserialize(&message.message)
|
||||
}
|
||||
|
||||
pub fn new_initial_request(init_message: InitMessage, reply_to: Recipient) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorRequestData::Initial(init_message),
|
||||
reply_to,
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_final_request(final_message: FinalMessage, reply_to: Recipient) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorRequestData::Final(Box::new(final_message)),
|
||||
reply_to,
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_query_request(peer_public_key: PeerPublicKey, reply_to: Recipient) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorRequestData::QueryBandwidth(peer_public_key),
|
||||
reply_to,
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_topup_request(top_up_message: TopUpMessage, reply_to: Recipient) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorRequestData::TopUpBandwidth(Box::new(top_up_message)),
|
||||
reply_to,
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().serialize(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum AuthenticatorRequestData {
|
||||
Initial(InitMessage),
|
||||
Final(Box<FinalMessage>),
|
||||
QueryBandwidth(PeerPublicKey),
|
||||
TopUpBandwidth(Box<TopUpMessage>),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn check_first_bytes_protocol() {
|
||||
let version = 2;
|
||||
let data = AuthenticatorRequest {
|
||||
protocol: Protocol { version, service_provider_type: ServiceProviderType::Authenticator },
|
||||
data: AuthenticatorRequestData::Initial(InitMessage::new(
|
||||
PeerPublicKey::from_str("yvNUDpT5l7W/xDhiu6HkqTHDQwbs/B3J5UrLmORl1EQ=").unwrap(),
|
||||
)),
|
||||
reply_to: Recipient::try_from_base58_string("D1rrpsysCGCYXy9saP8y3kmNpGtJZUXN9SvFoUcqAsM9.9Ssso1ea5NfkbMASdiseDSjTN1fSWda5SgEVjdSN4CvV@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN").unwrap(),
|
||||
request_id: 1,
|
||||
};
|
||||
let bytes = *data.to_bytes().unwrap().first_chunk::<2>().unwrap();
|
||||
assert_eq!(bytes, [version, ServiceProviderType::Authenticator as u8]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::registration::{RegistrationData, RegistredData, RemainingBandwidthData};
|
||||
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::make_bincode_serializer;
|
||||
|
||||
use super::VERSION;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct AuthenticatorResponse {
|
||||
pub protocol: Protocol,
|
||||
pub data: AuthenticatorResponseData,
|
||||
pub reply_to: Recipient,
|
||||
}
|
||||
|
||||
impl AuthenticatorResponse {
|
||||
pub fn new_pending_registration_success(
|
||||
registration_data: RegistrationData,
|
||||
request_id: u64,
|
||||
reply_to: Recipient,
|
||||
) -> Self {
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorResponseData::PendingRegistration(PendingRegistrationResponse {
|
||||
reply: registration_data,
|
||||
reply_to,
|
||||
request_id,
|
||||
}),
|
||||
reply_to,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_registered(
|
||||
registred_data: RegistredData,
|
||||
reply_to: Recipient,
|
||||
request_id: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorResponseData::Registered(RegisteredResponse {
|
||||
reply: registred_data,
|
||||
reply_to,
|
||||
request_id,
|
||||
}),
|
||||
reply_to,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_remaining_bandwidth(
|
||||
remaining_bandwidth_data: Option<RemainingBandwidthData>,
|
||||
reply_to: Recipient,
|
||||
request_id: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorResponseData::RemainingBandwidth(RemainingBandwidthResponse {
|
||||
reply: remaining_bandwidth_data,
|
||||
reply_to,
|
||||
request_id,
|
||||
}),
|
||||
reply_to,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_topup_bandwidth(
|
||||
remaining_bandwidth_data: RemainingBandwidthData,
|
||||
reply_to: Recipient,
|
||||
request_id: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorResponseData::TopUpBandwidth(TopUpBandwidthResponse {
|
||||
reply: remaining_bandwidth_data,
|
||||
reply_to,
|
||||
request_id,
|
||||
}),
|
||||
reply_to,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recipient(&self) -> Recipient {
|
||||
self.reply_to
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().serialize(self)
|
||||
}
|
||||
|
||||
pub fn from_reconstructed_message(
|
||||
message: &nym_sphinx::receiver::ReconstructedMessage,
|
||||
) -> Result<Self, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().deserialize(&message.message)
|
||||
}
|
||||
|
||||
pub fn id(&self) -> Option<u64> {
|
||||
match &self.data {
|
||||
AuthenticatorResponseData::PendingRegistration(response) => Some(response.request_id),
|
||||
AuthenticatorResponseData::Registered(response) => Some(response.request_id),
|
||||
AuthenticatorResponseData::RemainingBandwidth(response) => Some(response.request_id),
|
||||
AuthenticatorResponseData::TopUpBandwidth(response) => Some(response.request_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum AuthenticatorResponseData {
|
||||
PendingRegistration(PendingRegistrationResponse),
|
||||
Registered(RegisteredResponse),
|
||||
RemainingBandwidth(RemainingBandwidthResponse),
|
||||
TopUpBandwidth(TopUpBandwidthResponse),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct PendingRegistrationResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: RegistrationData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct RegisteredResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: RegistredData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct RemainingBandwidthResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: Option<RemainingBandwidthData>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct TopUpBandwidthResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: RemainingBandwidthData,
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct TopUpMessage {
|
||||
/// Base64 encoded x25519 public key
|
||||
pub pub_key: PeerPublicKey,
|
||||
|
||||
/// Ecash credential
|
||||
pub credential: CredentialSpendingData,
|
||||
}
|
||||
@@ -16,7 +16,7 @@ use nym_credential_storage::models::RetrievedTicketbook;
|
||||
use nym_credential_storage::storage::Storage;
|
||||
use nym_credentials::ecash::bandwidth::CredentialSpendingData;
|
||||
use nym_credentials_interface::{
|
||||
AnnotatedCoinIndexSignature, AnnotatedExpirationDateSignature, VerificationKeyAuth,
|
||||
AnnotatedCoinIndexSignature, AnnotatedExpirationDateSignature, TicketType, VerificationKeyAuth,
|
||||
};
|
||||
use nym_ecash_time::Date;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
@@ -64,9 +64,10 @@ impl<C, St: Storage> BandwidthController<C, St> {
|
||||
BandwidthController { storage, client }
|
||||
}
|
||||
|
||||
/// Tries to retrieve one of the stored, unused credentials that hasn't yet expired.
|
||||
/// Tries to retrieve one of the stored, unused credentials for the given type that hasn't yet expired.
|
||||
pub async fn get_next_usable_ticketbook(
|
||||
&self,
|
||||
ticketbook_type: TicketType,
|
||||
tickets: u32,
|
||||
) -> Result<RetrievedTicketbook, BandwidthControllerError>
|
||||
where
|
||||
@@ -74,7 +75,7 @@ impl<C, St: Storage> BandwidthController<C, St> {
|
||||
{
|
||||
let Some(ticketbook) = self
|
||||
.storage
|
||||
.get_next_unspent_usable_ticketbook(tickets)
|
||||
.get_next_unspent_usable_ticketbook(ticketbook_type.to_string(), tickets)
|
||||
.await
|
||||
.map_err(BandwidthControllerError::credential_storage_error)?
|
||||
else {
|
||||
@@ -181,6 +182,7 @@ impl<C, St: Storage> BandwidthController<C, St> {
|
||||
|
||||
pub async fn prepare_ecash_ticket(
|
||||
&self,
|
||||
ticketbook_type: TicketType,
|
||||
provider_pk: [u8; 32],
|
||||
tickets_to_spend: u32,
|
||||
) -> Result<PreparedCredential, BandwidthControllerError>
|
||||
@@ -188,7 +190,9 @@ impl<C, St: Storage> BandwidthController<C, St> {
|
||||
C: DkgQueryClient + Sync + Send,
|
||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
let retrieved_ticketbook = self.get_next_usable_ticketbook(tickets_to_spend).await?;
|
||||
let retrieved_ticketbook = self
|
||||
.get_next_usable_ticketbook(ticketbook_type, tickets_to_spend)
|
||||
.await?;
|
||||
|
||||
let ticketbook_id = retrieved_ticketbook.ticketbook_id;
|
||||
let epoch_id = retrieved_ticketbook.ticketbook.epoch_id();
|
||||
|
||||
@@ -24,6 +24,7 @@ zeroize.workspace = true
|
||||
nym-bandwidth-controller = { path = "../../bandwidth-controller" }
|
||||
nym-credentials = { path = "../../credentials" }
|
||||
nym-credential-storage = { path = "../../credential-storage" }
|
||||
nym-credentials-interface = { path = "../../credentials-interface" }
|
||||
nym-crypto = { path = "../../crypto" }
|
||||
nym-gateway-requests = { path = "../../gateway-requests" }
|
||||
nym-network-defaults = { path = "../../network-defaults" }
|
||||
|
||||
@@ -16,6 +16,7 @@ use nym_bandwidth_controller::{BandwidthController, BandwidthStatusMessage};
|
||||
use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
|
||||
use nym_credential_storage::storage::Storage as CredentialStorage;
|
||||
use nym_credentials::CredentialSpendingData;
|
||||
use nym_credentials_interface::TicketType;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_gateway_requests::registration::handshake::client_handshake;
|
||||
use nym_gateway_requests::{
|
||||
@@ -748,7 +749,11 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
let prepared_credential = self
|
||||
.unchecked_bandwidth_controller()
|
||||
.prepare_ecash_ticket(self.gateway_identity.to_bytes(), TICKETS_TO_SPEND)
|
||||
.prepare_ecash_ticket(
|
||||
TicketType::V1MixnetEntry,
|
||||
self.gateway_identity.to_bytes(),
|
||||
TICKETS_TO_SPEND,
|
||||
)
|
||||
.await?;
|
||||
|
||||
match self.claim_ecash_bandwidth(prepared_credential.data).await {
|
||||
|
||||
@@ -20,6 +20,7 @@ nym-coconut-bandwidth-contract-common = { path = "../../cosmwasm-smart-contracts
|
||||
nym-ecash-contract-common = { path = "../../cosmwasm-smart-contracts/ecash-contract" }
|
||||
nym-multisig-contract-common = { path = "../../cosmwasm-smart-contracts/multisig-contract" }
|
||||
nym-group-contract-common = { path = "../../cosmwasm-smart-contracts/group-contract" }
|
||||
nym-serde-helpers = { path = "../../serde-helpers", features = ["hex", "base64"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
nym-http-api-client = { path = "../../../common/http-api-client" }
|
||||
|
||||
+44
-10
@@ -5,7 +5,7 @@ use crate::nyxd;
|
||||
use crate::nyxd::coin::Coin;
|
||||
use crate::nyxd::cosmwasm_client::helpers::{create_pagination, next_page_key};
|
||||
use crate::nyxd::cosmwasm_client::types::{
|
||||
Account, CodeDetails, Contract, ContractCodeId, SequenceResponse, SimulateResponse,
|
||||
Account, CodeDetails, Contract, ContractCodeId, Model, SequenceResponse, SimulateResponse,
|
||||
};
|
||||
use crate::nyxd::error::NyxdError;
|
||||
use crate::nyxd::Query;
|
||||
@@ -21,11 +21,11 @@ use cosmrs::proto::cosmos::tx::v1beta1::{
|
||||
SimulateRequest, SimulateResponse as ProtoSimulateResponse,
|
||||
};
|
||||
use cosmrs::proto::cosmwasm::wasm::v1::{
|
||||
QueryCodeRequest, QueryCodeResponse, QueryCodesRequest, QueryCodesResponse,
|
||||
QueryContractHistoryRequest, QueryContractHistoryResponse, QueryContractInfoRequest,
|
||||
QueryContractInfoResponse, QueryContractsByCodeRequest, QueryContractsByCodeResponse,
|
||||
QueryRawContractStateRequest, QueryRawContractStateResponse, QuerySmartContractStateRequest,
|
||||
QuerySmartContractStateResponse,
|
||||
QueryAllContractStateRequest, QueryAllContractStateResponse, QueryCodeRequest,
|
||||
QueryCodeResponse, QueryCodesRequest, QueryCodesResponse, QueryContractHistoryRequest,
|
||||
QueryContractHistoryResponse, QueryContractInfoRequest, QueryContractInfoResponse,
|
||||
QueryContractsByCodeRequest, QueryContractsByCodeResponse, QueryRawContractStateRequest,
|
||||
QueryRawContractStateResponse, QuerySmartContractStateRequest, QuerySmartContractStateResponse,
|
||||
};
|
||||
use cosmrs::tendermint::{block, chain, Hash};
|
||||
use cosmrs::{AccountId, Coin as CosmosCoin, Tx};
|
||||
@@ -218,17 +218,19 @@ pub trait CosmWasmClient: TendermintRpcClient {
|
||||
|
||||
loop {
|
||||
let mut res = self
|
||||
.tx_search(query.clone(), false, page, 100, Order::Ascending)
|
||||
.tx_search(query.clone(), false, page, per_page, Order::Ascending)
|
||||
.await?;
|
||||
|
||||
results.append(&mut res.txs);
|
||||
// sanity check for if tendermint's maximum per_page was modified -
|
||||
// we don't want to accidentally be stuck in an infinite loop
|
||||
if res.total_count == 0 || res.txs.is_empty() {
|
||||
let early_break = res.total_count == 0 || res.txs.is_empty();
|
||||
results.append(&mut res.txs);
|
||||
|
||||
if early_break {
|
||||
break;
|
||||
}
|
||||
|
||||
if res.total_count >= per_page {
|
||||
if res.total_count > results.len() as u32 {
|
||||
page += 1
|
||||
} else {
|
||||
break;
|
||||
@@ -442,6 +444,38 @@ pub trait CosmWasmClient: TendermintRpcClient {
|
||||
.collect::<Result<_, _>>()?)
|
||||
}
|
||||
|
||||
async fn query_all_contract_state(&self, address: &AccountId) -> Result<Vec<Model>, NyxdError> {
|
||||
let path = Some("/cosmwasm.wasm.v1.Query/AllContractState".to_owned());
|
||||
|
||||
let mut models = Vec::new();
|
||||
let mut pagination = None;
|
||||
|
||||
loop {
|
||||
let req = QueryAllContractStateRequest {
|
||||
address: address.to_string(),
|
||||
pagination,
|
||||
};
|
||||
|
||||
let mut res = self
|
||||
.make_abci_query::<_, QueryAllContractStateResponse>(path.clone(), req)
|
||||
.await?;
|
||||
|
||||
let empty_response = res.models.is_empty();
|
||||
models.append(&mut res.models);
|
||||
|
||||
if empty_response {
|
||||
break;
|
||||
}
|
||||
if let Some(next_key) = next_page_key(res.pagination) {
|
||||
pagination = Some(create_pagination(next_key))
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(models.into_iter().map(Into::into).collect())
|
||||
}
|
||||
|
||||
async fn query_contract_raw(
|
||||
&self,
|
||||
address: &AccountId,
|
||||
|
||||
@@ -27,13 +27,34 @@ use cosmrs::vesting::{
|
||||
};
|
||||
use cosmrs::{AccountId, Any, Coin as CosmosCoin};
|
||||
use prost::Message;
|
||||
use serde::Serialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub use cosmrs::abci::GasInfo;
|
||||
pub use cosmrs::abci::MsgResponse;
|
||||
|
||||
pub type ContractCodeId = u64;
|
||||
|
||||
// yet another thing to put in cosmrs
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Model {
|
||||
#[serde(with = "nym_serde_helpers::hex")]
|
||||
pub key: Vec<u8>,
|
||||
|
||||
#[serde(with = "nym_serde_helpers::base64")]
|
||||
pub value: Vec<u8>,
|
||||
}
|
||||
|
||||
// follow the cosmwasm serialisation format, i.e. hex for key and base64 for value
|
||||
|
||||
impl From<cosmrs::proto::cosmwasm::wasm::v1::Model> for Model {
|
||||
fn from(model: cosmrs::proto::cosmwasm::wasm::v1::Model) -> Self {
|
||||
Model {
|
||||
key: model.key,
|
||||
value: model.value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct EmptyMsg {}
|
||||
|
||||
|
||||
@@ -9,10 +9,15 @@ use comfy_table::Table;
|
||||
use nym_credential_storage::initialise_persistent_storage;
|
||||
use nym_credential_storage::storage::Storage;
|
||||
use nym_credentials::ecash::bandwidth::serialiser::VersionedSerialise;
|
||||
use nym_credentials_interface::TicketType;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
/// Specify which type of ticketbook
|
||||
#[clap(long, default_value_t = TicketType::V1MixnetEntry)]
|
||||
pub(crate) ticketbook_type: TicketType,
|
||||
|
||||
/// Specify the index of the ticket to retrieve from the ticketbook.
|
||||
/// By default, the current unspent value is used.
|
||||
#[clap(long, group = "output")]
|
||||
@@ -62,7 +67,7 @@ pub async fn execute(args: Args) -> anyhow::Result<()> {
|
||||
|
||||
let persistent_storage = initialise_persistent_storage(&credentials_store).await;
|
||||
let Some(mut next_ticketbook) = persistent_storage
|
||||
.get_next_unspent_usable_ticketbook(0)
|
||||
.get_next_unspent_usable_ticketbook(args.ticketbook_type.to_string(), 0)
|
||||
.await?
|
||||
else {
|
||||
bail!(
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::utils::CommonConfigsWrapper;
|
||||
use anyhow::bail;
|
||||
use clap::ArgGroup;
|
||||
use clap::Parser;
|
||||
use nym_credential_storage::initialise_persistent_storage;
|
||||
@@ -31,7 +29,7 @@ impl FromStr for CredentialDataWrapper {
|
||||
pub struct Args {
|
||||
/// Config file of the client that is supposed to use the credential.
|
||||
#[clap(long)]
|
||||
pub(crate) client_config: PathBuf,
|
||||
pub(crate) credentials_store: PathBuf,
|
||||
|
||||
/// Explicitly provide the encoded credential data (as base58)
|
||||
#[clap(long, group = "cred_data")]
|
||||
@@ -70,21 +68,7 @@ impl Args {
|
||||
}
|
||||
|
||||
pub async fn execute(args: Args) -> anyhow::Result<()> {
|
||||
let loaded = CommonConfigsWrapper::try_load(&args.client_config)?;
|
||||
|
||||
if let Ok(id) = loaded.try_get_id() {
|
||||
println!("loaded config file for client '{id}'");
|
||||
}
|
||||
|
||||
let Ok(credentials_store) = loaded.try_get_credentials_store() else {
|
||||
bail!("the loaded config does not have a credentials store information")
|
||||
};
|
||||
|
||||
println!(
|
||||
"using credentials store at '{}'",
|
||||
credentials_store.display()
|
||||
);
|
||||
let credentials_store = initialise_persistent_storage(credentials_store).await;
|
||||
let credentials_store = initialise_persistent_storage(args.credentials_store.clone()).await;
|
||||
|
||||
let version = args.version;
|
||||
let standalone = args.standalone;
|
||||
|
||||
@@ -107,7 +107,7 @@ async fn issue_to_file(args: Args, client: SigningClient) -> anyhow::Result<()>
|
||||
utils::issue_credential(&client, &credentials_store, &secret, args.ticketbook_type).await?;
|
||||
|
||||
let ticketbook = credentials_store
|
||||
.get_next_unspent_usable_ticketbook(0)
|
||||
.get_next_unspent_usable_ticketbook(args.ticketbook_type.to_string(), 0)
|
||||
.await?
|
||||
.ok_or(anyhow!("we just issued a ticketbook, it must be present!"))?
|
||||
.ticketbook;
|
||||
|
||||
@@ -7,13 +7,14 @@ pub mod execute_contract;
|
||||
pub mod generators;
|
||||
pub mod init_contract;
|
||||
pub mod migrate_contract;
|
||||
pub mod raw_contract_state;
|
||||
pub mod upload_contract;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||
pub struct Cosmwasm {
|
||||
#[clap(subcommand)]
|
||||
pub command: Option<CosmwasmCommands>,
|
||||
pub command: CosmwasmCommands,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
@@ -28,4 +29,6 @@ pub enum CosmwasmCommands {
|
||||
Migrate(crate::validator::cosmwasm::migrate_contract::Args),
|
||||
/// Execute a WASM smart contract method
|
||||
Execute(crate::validator::cosmwasm::execute_contract::Args),
|
||||
/// Obtain raw contract state of a cosmwasm smart contract
|
||||
RawContractState(crate::validator::cosmwasm::raw_contract_state::Args),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::QueryClient;
|
||||
use clap::Parser;
|
||||
use cosmrs::AccountId;
|
||||
use log::trace;
|
||||
use nym_validator_client::nyxd::CosmWasmClient;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long, value_parser)]
|
||||
#[clap(help = "The address of contract to get the state of")]
|
||||
pub contract: AccountId,
|
||||
|
||||
#[clap(short, long)]
|
||||
#[clap(help = "Output file for the retrieved contract state")]
|
||||
pub output: PathBuf,
|
||||
}
|
||||
|
||||
pub async fn execute(args: Args, client: QueryClient) -> anyhow::Result<()> {
|
||||
trace!("args: {args:?}");
|
||||
|
||||
let output = File::create(&args.output)?;
|
||||
let raw = client.query_all_contract_state(&args.contract).await?;
|
||||
|
||||
serde_json::to_writer(output, &raw)?;
|
||||
println!(
|
||||
"wrote {} key-value from {} pairs into '{}'",
|
||||
raw.len(),
|
||||
args.contract,
|
||||
fs::canonicalize(args.output)?.display()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -73,6 +73,7 @@ impl MemoryEcachTicketbookManager {
|
||||
|
||||
pub async fn get_next_unspent_ticketbook_and_update(
|
||||
&self,
|
||||
ticketbook_type: String,
|
||||
tickets: u32,
|
||||
) -> Option<RetrievedTicketbook> {
|
||||
let mut guard = self.inner.write().await;
|
||||
@@ -81,6 +82,7 @@ impl MemoryEcachTicketbookManager {
|
||||
if !t.ticketbook.expired()
|
||||
&& t.ticketbook.spent_tickets() + tickets as u64
|
||||
<= t.ticketbook.params_total_tickets()
|
||||
&& t.ticketbook.ticketbook_type().to_string() == ticketbook_type
|
||||
{
|
||||
t.ticketbook
|
||||
.update_spent_tickets(t.ticketbook.spent_tickets() + tickets as u64);
|
||||
|
||||
@@ -284,6 +284,7 @@ impl SqliteEcashTicketbookManager {
|
||||
|
||||
pub(crate) async fn get_next_unspent_ticketbook<'a, E>(
|
||||
executor: E,
|
||||
ticketbook_type: String,
|
||||
deadline: Date,
|
||||
tickets: u32,
|
||||
) -> Result<Option<StoredIssuedTicketbook>, sqlx::Error>
|
||||
@@ -296,12 +297,14 @@ where
|
||||
FROM ecash_ticketbook
|
||||
WHERE used_tickets + ? <= total_tickets
|
||||
AND expiration_date >= ?
|
||||
AND ticketbook_type = ?
|
||||
ORDER BY expiration_date ASC
|
||||
LIMIT 1
|
||||
"#,
|
||||
)
|
||||
.bind(tickets)
|
||||
.bind(deadline)
|
||||
.bind(ticketbook_type)
|
||||
.fetch_optional(executor)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -85,17 +85,18 @@ impl Storage for EphemeralStorage {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tries to retrieve one of the stored ticketbook,
|
||||
/// Tries to retrieve one of the stored ticketbook for the specified type,
|
||||
/// that has not yet expired and has required number of unspent tickets.
|
||||
/// it immediately updated the on-disk number of used tickets so that another task
|
||||
/// could obtain their own tickets at the same time
|
||||
async fn get_next_unspent_usable_ticketbook(
|
||||
&self,
|
||||
ticketbook_type: String,
|
||||
tickets: u32,
|
||||
) -> Result<Option<RetrievedTicketbook>, Self::StorageError> {
|
||||
Ok(self
|
||||
.storage_manager
|
||||
.get_next_unspent_ticketbook_and_update(tickets)
|
||||
.get_next_unspent_ticketbook_and_update(ticketbook_type, tickets)
|
||||
.await)
|
||||
}
|
||||
|
||||
|
||||
@@ -171,13 +171,16 @@ impl Storage for PersistentStorage {
|
||||
/// could obtain their own tickets at the same time
|
||||
async fn get_next_unspent_usable_ticketbook(
|
||||
&self,
|
||||
ticketbook_type: String,
|
||||
tickets: u32,
|
||||
) -> Result<Option<RetrievedTicketbook>, Self::StorageError> {
|
||||
let deadline = ecash_today().ecash_date();
|
||||
let mut tx = self.storage_manager.begin_storage_tx().await?;
|
||||
|
||||
// we don't want ticketbooks with expiration in the past
|
||||
let Some(raw) = get_next_unspent_ticketbook(&mut tx, deadline, tickets).await? else {
|
||||
let Some(raw) =
|
||||
get_next_unspent_ticketbook(&mut tx, ticketbook_type, deadline, tickets).await?
|
||||
else {
|
||||
// make sure to finish our tx
|
||||
tx.commit().await?;
|
||||
return Ok(None);
|
||||
|
||||
@@ -45,12 +45,13 @@ pub trait Storage: Send + Sync {
|
||||
|
||||
async fn remove_pending_ticketbook(&self, pending_id: i64) -> Result<(), Self::StorageError>;
|
||||
|
||||
/// Tries to retrieve one of the stored ticketbook,
|
||||
/// Tries to retrieve one of the stored ticketbook for the specified type,
|
||||
/// that has not yet expired and has required number of unspent tickets.
|
||||
/// it immediately updated the on-disk number of used tickets so that another task
|
||||
/// could obtain their own tickets at the same time
|
||||
async fn get_next_unspent_usable_ticketbook(
|
||||
&self,
|
||||
ticketbook_type: String,
|
||||
tickets: u32,
|
||||
) -> Result<Option<RetrievedTicketbook>, Self::StorageError>;
|
||||
|
||||
|
||||
@@ -2,30 +2,17 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_sphinx_acknowledgements::surb_ack::SurbAckRecoveryError;
|
||||
use nym_sphinx_addressing::nodes::NymNodeRoutingAddressError;
|
||||
use nym_sphinx_types::{NymPacketError, OutfoxError, SphinxError};
|
||||
use nym_sphinx_framing::processing::PacketProcessingError;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum MixProcessingError {
|
||||
#[error("failed to process received packet: {0}")]
|
||||
NymPacketProcessingError(#[from] NymPacketError),
|
||||
|
||||
#[error("failed to process received sphinx packet: {0}")]
|
||||
SphinxProcessingError(#[from] SphinxError),
|
||||
|
||||
#[error("the forward hop address was malformed: {0}")]
|
||||
InvalidForwardHopAddress(#[from] NymNodeRoutingAddressError),
|
||||
|
||||
#[error("the final hop did not contain a SURB-Ack")]
|
||||
NoSurbAckInFinalHop,
|
||||
|
||||
#[error("failed to recover the expected SURB-Ack packet: {0}")]
|
||||
MalformedSurbAck(#[from] SurbAckRecoveryError),
|
||||
|
||||
#[error("the received packet was set to use the very old and very much deprecated 'VPN' mode")]
|
||||
ReceivedOldTypeVpnPacket,
|
||||
|
||||
#[error("failed to process received outfox packet: {0}")]
|
||||
OutfoxProcessingError(#[from] OutfoxError),
|
||||
#[error("failed to process received Nym packet: {0}")]
|
||||
NymPacketProcessingError(#[from] PacketProcessingError),
|
||||
}
|
||||
|
||||
@@ -1,38 +1,9 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::packet_processor::error::MixProcessingError;
|
||||
use log::*;
|
||||
use nym_metrics::nanos;
|
||||
use nym_sphinx_acknowledgements::surb_ack::SurbAck;
|
||||
use nym_sphinx_addressing::nodes::NymNodeRoutingAddress;
|
||||
use nym_sphinx_forwarding::packet::MixPacket;
|
||||
use nym_sphinx_framing::packet::FramedNymPacket;
|
||||
use nym_sphinx_params::{PacketSize, PacketType};
|
||||
use nym_sphinx_types::{
|
||||
Delay as SphinxDelay, DestinationAddressBytes, NodeAddressBytes, NymPacket, NymProcessedPacket,
|
||||
PrivateKey, ProcessedPacket,
|
||||
};
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
type ForwardAck = MixPacket;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ProcessedFinalHop {
|
||||
pub destination: DestinationAddressBytes,
|
||||
pub forward_ack: Option<ForwardAck>,
|
||||
pub message: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MixProcessingResult {
|
||||
/// Contains unwrapped data that should first get delayed before being sent to next hop.
|
||||
ForwardHop(MixPacket, Option<SphinxDelay>),
|
||||
|
||||
/// Contains all data extracted out of the final hop packet that could be forwarded to the destination.
|
||||
FinalHop(ProcessedFinalHop),
|
||||
}
|
||||
use nym_sphinx_types::PrivateKey;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SphinxPacketProcessor {
|
||||
@@ -48,280 +19,7 @@ impl SphinxPacketProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs a fresh sphinx unwrapping using no cache.
|
||||
fn perform_initial_packet_processing(
|
||||
&self,
|
||||
packet: NymPacket,
|
||||
) -> Result<NymProcessedPacket, MixProcessingError> {
|
||||
nanos!("perform_initial_packet_processing", {
|
||||
packet.process(&self.sphinx_key).map_err(|err| {
|
||||
debug!("Failed to unwrap NymPacket packet: {err}");
|
||||
MixProcessingError::NymPacketProcessingError(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Takes the received framed packet and tries to unwrap it from the sphinx encryption.
|
||||
fn perform_initial_unwrapping(
|
||||
&self,
|
||||
received: FramedNymPacket,
|
||||
) -> Result<NymProcessedPacket, MixProcessingError> {
|
||||
nanos!("perform_initial_unwrapping", {
|
||||
let packet = received.into_inner();
|
||||
self.perform_initial_packet_processing(packet)
|
||||
})
|
||||
}
|
||||
|
||||
/// Processed received forward hop packet - tries to extract next hop address, sets delay
|
||||
/// and packs all the data in a way that can be easily sent to the next hop.
|
||||
fn process_forward_hop(
|
||||
&self,
|
||||
packet: NymPacket,
|
||||
forward_address: NodeAddressBytes,
|
||||
delay: SphinxDelay,
|
||||
packet_type: PacketType,
|
||||
) -> Result<MixProcessingResult, MixProcessingError> {
|
||||
let next_hop_address = NymNodeRoutingAddress::try_from(forward_address)?;
|
||||
|
||||
let mix_packet = MixPacket::new(next_hop_address, packet, packet_type);
|
||||
Ok(MixProcessingResult::ForwardHop(mix_packet, Some(delay)))
|
||||
}
|
||||
|
||||
/// Split data extracted from the final hop sphinx packet into a SURBAck and message
|
||||
/// that should get delivered to a client.
|
||||
fn split_hop_data_into_ack_and_message(
|
||||
&self,
|
||||
mut extracted_data: Vec<u8>,
|
||||
packet_type: PacketType,
|
||||
) -> Result<(Vec<u8>, Vec<u8>), MixProcessingError> {
|
||||
let ack_len = SurbAck::len(Some(packet_type));
|
||||
|
||||
// in theory it's impossible for this to fail since it managed to go into correct `match`
|
||||
// branch at the caller
|
||||
if extracted_data.len() < ack_len {
|
||||
return Err(MixProcessingError::NoSurbAckInFinalHop);
|
||||
}
|
||||
|
||||
let message = extracted_data.split_off(ack_len);
|
||||
let ack_data = extracted_data;
|
||||
Ok((ack_data, message))
|
||||
}
|
||||
|
||||
/// Tries to extract a SURBAck that could be sent back into the mix network and message
|
||||
/// that should get delivered to a client from received Sphinx packet.
|
||||
fn split_into_ack_and_message(
|
||||
&self,
|
||||
data: Vec<u8>,
|
||||
packet_size: PacketSize,
|
||||
packet_type: PacketType,
|
||||
) -> Result<(Option<MixPacket>, Vec<u8>), MixProcessingError> {
|
||||
match packet_size {
|
||||
PacketSize::AckPacket | PacketSize::OutfoxAckPacket => {
|
||||
trace!("received an ack packet!");
|
||||
Ok((None, data))
|
||||
}
|
||||
PacketSize::RegularPacket
|
||||
| PacketSize::ExtendedPacket8
|
||||
| PacketSize::ExtendedPacket16
|
||||
| PacketSize::ExtendedPacket32
|
||||
| PacketSize::OutfoxRegularPacket => {
|
||||
trace!("received a normal packet!");
|
||||
let (ack_data, message) =
|
||||
self.split_hop_data_into_ack_and_message(data, packet_type)?;
|
||||
let (ack_first_hop, ack_packet) =
|
||||
match SurbAck::try_recover_first_hop_packet(&ack_data, packet_type) {
|
||||
Ok((first_hop, packet)) => (first_hop, packet),
|
||||
Err(err) => {
|
||||
info!("Failed to recover first hop from ack data: {err}");
|
||||
return Err(err.into());
|
||||
}
|
||||
};
|
||||
let forward_ack = MixPacket::new(ack_first_hop, ack_packet, packet_type);
|
||||
Ok((Some(forward_ack), message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Processed received final hop packet - tries to extract SURBAck out of it (assuming the
|
||||
/// packet itself is not an ACK) and splits it from the message that should get delivered
|
||||
/// to the destination.
|
||||
fn process_final_hop(
|
||||
&self,
|
||||
destination: DestinationAddressBytes,
|
||||
payload: Vec<u8>,
|
||||
packet_size: PacketSize,
|
||||
packet_type: PacketType,
|
||||
) -> Result<MixProcessingResult, MixProcessingError> {
|
||||
let (forward_ack, message) =
|
||||
self.split_into_ack_and_message(payload, packet_size, packet_type)?;
|
||||
|
||||
Ok(MixProcessingResult::FinalHop(ProcessedFinalHop {
|
||||
destination,
|
||||
forward_ack,
|
||||
message,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Performs final processing for the unwrapped packet based on whether it was a forward hop
|
||||
/// or a final hop.
|
||||
fn perform_final_processing(
|
||||
&self,
|
||||
packet: NymProcessedPacket,
|
||||
packet_size: PacketSize,
|
||||
packet_type: PacketType,
|
||||
) -> Result<MixProcessingResult, MixProcessingError> {
|
||||
match packet {
|
||||
NymProcessedPacket::Sphinx(packet) => {
|
||||
match packet {
|
||||
ProcessedPacket::ForwardHop(packet, address, delay) => self
|
||||
.process_forward_hop(
|
||||
NymPacket::Sphinx(*packet),
|
||||
address,
|
||||
delay,
|
||||
packet_type,
|
||||
),
|
||||
// right now there's no use for the surb_id included in the header - probably it should get removed from the
|
||||
// sphinx all together?
|
||||
ProcessedPacket::FinalHop(destination, _, payload) => self.process_final_hop(
|
||||
destination,
|
||||
payload.recover_plaintext()?,
|
||||
packet_size,
|
||||
packet_type,
|
||||
),
|
||||
}
|
||||
}
|
||||
NymProcessedPacket::Outfox(packet) => {
|
||||
let next_address = *packet.next_address();
|
||||
let packet = packet.into_packet();
|
||||
if packet.is_final_hop() {
|
||||
self.process_final_hop(
|
||||
DestinationAddressBytes::from_bytes(next_address),
|
||||
packet.recover_plaintext()?.to_vec(),
|
||||
packet_size,
|
||||
packet_type,
|
||||
)
|
||||
} else {
|
||||
let mix_packet = MixPacket::new(
|
||||
NymNodeRoutingAddress::try_from_bytes(&next_address)?,
|
||||
NymPacket::Outfox(packet),
|
||||
PacketType::Outfox,
|
||||
);
|
||||
Ok(MixProcessingResult::ForwardHop(mix_packet, None))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_received(
|
||||
&self,
|
||||
received: FramedNymPacket,
|
||||
) -> Result<MixProcessingResult, MixProcessingError> {
|
||||
// explicit packet size will help to correctly parse final hop
|
||||
nanos!("process_received", {
|
||||
let packet_size = received.packet_size();
|
||||
let packet_type = received.packet_type();
|
||||
|
||||
// unwrap the sphinx packet and if possible and appropriate, cache keys
|
||||
let processed_packet = self.perform_initial_unwrapping(received)?;
|
||||
|
||||
// for forward packets, extract next hop and set delay (but do NOT delay here)
|
||||
// for final packets, extract SURBAck
|
||||
let final_processing_result =
|
||||
self.perform_final_processing(processed_packet, packet_size, packet_type);
|
||||
|
||||
if final_processing_result.is_err() {
|
||||
error!("{:?}", final_processing_result)
|
||||
}
|
||||
|
||||
final_processing_result
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: what more could we realistically test here?
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use nym_sphinx_types::crypto::keygen;
|
||||
|
||||
fn fixture() -> SphinxPacketProcessor {
|
||||
let local_keys = keygen();
|
||||
SphinxPacketProcessor::new(local_keys.0)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn splitting_hop_data_works_for_sufficiently_long_payload() {
|
||||
let processor = fixture();
|
||||
|
||||
let short_data = vec![42u8];
|
||||
assert!(processor
|
||||
.split_hop_data_into_ack_and_message(short_data, PacketType::Mix)
|
||||
.is_err());
|
||||
|
||||
let sufficient_data = vec![42u8; SurbAck::len(Some(PacketType::Mix))];
|
||||
let (ack, data) = processor
|
||||
.split_hop_data_into_ack_and_message(sufficient_data.clone(), PacketType::Mix)
|
||||
.unwrap();
|
||||
assert_eq!(sufficient_data, ack);
|
||||
assert!(data.is_empty());
|
||||
|
||||
let long_data = vec![42u8; SurbAck::len(Some(PacketType::Mix)) * 5];
|
||||
let (ack, data) = processor
|
||||
.split_hop_data_into_ack_and_message(long_data, PacketType::Mix)
|
||||
.unwrap();
|
||||
assert_eq!(ack.len(), SurbAck::len(Some(PacketType::Mix)));
|
||||
assert_eq!(data.len(), SurbAck::len(Some(PacketType::Mix)) * 4)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn splitting_hop_data_works_for_sufficiently_long_payload_outfox() {
|
||||
let processor = fixture();
|
||||
|
||||
let short_data = vec![42u8];
|
||||
assert!(processor
|
||||
.split_hop_data_into_ack_and_message(short_data, PacketType::Outfox)
|
||||
.is_err());
|
||||
|
||||
let sufficient_data = vec![42u8; SurbAck::len(Some(PacketType::Outfox))];
|
||||
let (ack, data) = processor
|
||||
.split_hop_data_into_ack_and_message(sufficient_data.clone(), PacketType::Outfox)
|
||||
.unwrap();
|
||||
assert_eq!(sufficient_data, ack);
|
||||
assert!(data.is_empty());
|
||||
|
||||
let long_data = vec![42u8; SurbAck::len(Some(PacketType::Outfox)) * 5];
|
||||
let (ack, data) = processor
|
||||
.split_hop_data_into_ack_and_message(long_data, PacketType::Outfox)
|
||||
.unwrap();
|
||||
assert_eq!(ack.len(), SurbAck::len(Some(PacketType::Outfox)));
|
||||
assert_eq!(data.len(), SurbAck::len(Some(PacketType::Outfox)) * 4)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn splitting_into_ack_and_message_returns_whole_data_for_ack() {
|
||||
let processor = fixture();
|
||||
|
||||
let data = vec![42u8; SurbAck::len(Some(PacketType::Mix)) + 10];
|
||||
let (ack, message) = processor
|
||||
.split_into_ack_and_message(data.clone(), PacketSize::AckPacket, PacketType::Mix)
|
||||
.unwrap();
|
||||
assert!(ack.is_none());
|
||||
assert_eq!(data, message)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn splitting_into_ack_and_message_returns_whole_data_for_ack_outfox() {
|
||||
let processor = fixture();
|
||||
|
||||
let data = vec![42u8; SurbAck::len(Some(PacketType::Outfox)) + 10];
|
||||
let (ack, message) = processor
|
||||
.split_into_ack_and_message(
|
||||
data.clone(),
|
||||
PacketSize::OutfoxAckPacket,
|
||||
PacketType::Outfox,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(ack.is_none());
|
||||
assert_eq!(data, message)
|
||||
pub fn sphinx_key(&self) -> &PrivateKey {
|
||||
&self.sphinx_key
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,14 @@ repository = { workspace = true }
|
||||
bytes = { workspace = true }
|
||||
tokio-util = { workspace = true, features = ["codec"] }
|
||||
thiserror = { workspace = true }
|
||||
log = { workspace = true }
|
||||
|
||||
nym-sphinx-types = { path = "../types", features = ["sphinx", "outfox"] }
|
||||
nym-sphinx-params = { path = "../params", features = ["sphinx", "outfox"] }
|
||||
nym-sphinx-forwarding = { path = "../forwarding" }
|
||||
nym-metrics = { path = "../../nym-metrics" }
|
||||
nym-sphinx-addressing = { path = "../addressing" }
|
||||
nym-sphinx-acknowledgements = { path = "../acknowledgements" }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
|
||||
pub mod codec;
|
||||
pub mod packet;
|
||||
pub mod processing;
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
use log::{debug, error, info, trace};
|
||||
use nym_sphinx_acknowledgements::surb_ack::{SurbAck, SurbAckRecoveryError};
|
||||
use nym_sphinx_addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError};
|
||||
use nym_sphinx_params::{PacketSize, PacketType};
|
||||
use nym_sphinx_types::{
|
||||
Delay as SphinxDelay, DestinationAddressBytes, NodeAddressBytes, NymPacket, NymPacketError,
|
||||
NymProcessedPacket, OutfoxError, PrivateKey, ProcessedPacket, SphinxError,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::packet::FramedNymPacket;
|
||||
use nym_metrics::nanos;
|
||||
use nym_sphinx_forwarding::packet::MixPacket;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MixProcessingResult {
|
||||
/// Contains unwrapped data that should first get delayed before being sent to next hop.
|
||||
ForwardHop(MixPacket, Option<SphinxDelay>),
|
||||
|
||||
/// Contains all data extracted out of the final hop packet that could be forwarded to the destination.
|
||||
FinalHop(ProcessedFinalHop),
|
||||
}
|
||||
|
||||
type ForwardAck = MixPacket;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ProcessedFinalHop {
|
||||
pub destination: DestinationAddressBytes,
|
||||
pub forward_ack: Option<ForwardAck>,
|
||||
pub message: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum PacketProcessingError {
|
||||
#[error("failed to process received packet: {0}")]
|
||||
NymPacketProcessingError(#[from] NymPacketError),
|
||||
|
||||
#[error("failed to process received sphinx packet: {0}")]
|
||||
SphinxProcessingError(#[from] SphinxError),
|
||||
|
||||
#[error("the forward hop address was malformed: {0}")]
|
||||
InvalidForwardHopAddress(#[from] NymNodeRoutingAddressError),
|
||||
|
||||
#[error("the final hop did not contain a SURB-Ack")]
|
||||
NoSurbAckInFinalHop,
|
||||
|
||||
#[error("failed to recover the expected SURB-Ack packet: {0}")]
|
||||
MalformedSurbAck(#[from] SurbAckRecoveryError),
|
||||
|
||||
#[error("the received packet was set to use the very old and very much deprecated 'VPN' mode")]
|
||||
ReceivedOldTypeVpnPacket,
|
||||
|
||||
#[error("failed to process received outfox packet: {0}")]
|
||||
OutfoxProcessingError(#[from] OutfoxError),
|
||||
}
|
||||
|
||||
pub fn process_framed_packet(
|
||||
received: FramedNymPacket,
|
||||
sphinx_key: &PrivateKey,
|
||||
) -> Result<MixProcessingResult, PacketProcessingError> {
|
||||
nanos!("process_received", {
|
||||
let packet_size = received.packet_size();
|
||||
let packet_type = received.packet_type();
|
||||
|
||||
// unwrap the sphinx packet and if possible and appropriate, cache keys
|
||||
let processed_packet = perform_framed_unwrapping(received, sphinx_key)?;
|
||||
|
||||
// for forward packets, extract next hop and set delay (but do NOT delay here)
|
||||
// for final packets, extract SURBAck
|
||||
let final_processing_result =
|
||||
perform_final_processing(processed_packet, packet_size, packet_type);
|
||||
|
||||
if final_processing_result.is_err() {
|
||||
error!("{:?}", final_processing_result)
|
||||
}
|
||||
|
||||
final_processing_result
|
||||
})
|
||||
}
|
||||
|
||||
fn perform_framed_unwrapping(
|
||||
received: FramedNymPacket,
|
||||
sphinx_key: &PrivateKey,
|
||||
) -> Result<NymProcessedPacket, PacketProcessingError> {
|
||||
nanos!("perform_initial_unwrapping", {
|
||||
let packet = received.into_inner();
|
||||
perform_framed_packet_processing(packet, sphinx_key)
|
||||
})
|
||||
}
|
||||
|
||||
fn perform_framed_packet_processing(
|
||||
packet: NymPacket,
|
||||
sphinx_key: &PrivateKey,
|
||||
) -> Result<NymProcessedPacket, PacketProcessingError> {
|
||||
nanos!("perform_initial_packet_processing", {
|
||||
packet.process(sphinx_key).map_err(|err| {
|
||||
debug!("Failed to unwrap NymPacket packet: {err}");
|
||||
PacketProcessingError::NymPacketProcessingError(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn perform_final_processing(
|
||||
packet: NymProcessedPacket,
|
||||
packet_size: PacketSize,
|
||||
packet_type: PacketType,
|
||||
) -> Result<MixProcessingResult, PacketProcessingError> {
|
||||
match packet {
|
||||
NymProcessedPacket::Sphinx(packet) => {
|
||||
match packet {
|
||||
ProcessedPacket::ForwardHop(packet, address, delay) => {
|
||||
process_forward_hop(NymPacket::Sphinx(*packet), address, delay, packet_type)
|
||||
}
|
||||
// right now there's no use for the surb_id included in the header - probably it should get removed from the
|
||||
// sphinx all together?
|
||||
ProcessedPacket::FinalHop(destination, _, payload) => process_final_hop(
|
||||
destination,
|
||||
payload.recover_plaintext()?,
|
||||
packet_size,
|
||||
packet_type,
|
||||
),
|
||||
}
|
||||
}
|
||||
NymProcessedPacket::Outfox(packet) => {
|
||||
let next_address = *packet.next_address();
|
||||
let packet = packet.into_packet();
|
||||
if packet.is_final_hop() {
|
||||
process_final_hop(
|
||||
DestinationAddressBytes::from_bytes(next_address),
|
||||
packet.recover_plaintext()?.to_vec(),
|
||||
packet_size,
|
||||
packet_type,
|
||||
)
|
||||
} else {
|
||||
let mix_packet = MixPacket::new(
|
||||
NymNodeRoutingAddress::try_from_bytes(&next_address)?,
|
||||
NymPacket::Outfox(packet),
|
||||
PacketType::Outfox,
|
||||
);
|
||||
Ok(MixProcessingResult::ForwardHop(mix_packet, None))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_final_hop(
|
||||
destination: DestinationAddressBytes,
|
||||
payload: Vec<u8>,
|
||||
packet_size: PacketSize,
|
||||
packet_type: PacketType,
|
||||
) -> Result<MixProcessingResult, PacketProcessingError> {
|
||||
let (forward_ack, message) = split_into_ack_and_message(payload, packet_size, packet_type)?;
|
||||
|
||||
Ok(MixProcessingResult::FinalHop(ProcessedFinalHop {
|
||||
destination,
|
||||
forward_ack,
|
||||
message,
|
||||
}))
|
||||
}
|
||||
|
||||
fn split_into_ack_and_message(
|
||||
data: Vec<u8>,
|
||||
packet_size: PacketSize,
|
||||
packet_type: PacketType,
|
||||
) -> Result<(Option<MixPacket>, Vec<u8>), PacketProcessingError> {
|
||||
match packet_size {
|
||||
PacketSize::AckPacket | PacketSize::OutfoxAckPacket => {
|
||||
trace!("received an ack packet!");
|
||||
Ok((None, data))
|
||||
}
|
||||
PacketSize::RegularPacket
|
||||
| PacketSize::ExtendedPacket8
|
||||
| PacketSize::ExtendedPacket16
|
||||
| PacketSize::ExtendedPacket32
|
||||
| PacketSize::OutfoxRegularPacket => {
|
||||
trace!("received a normal packet!");
|
||||
let (ack_data, message) = split_hop_data_into_ack_and_message(data, packet_type)?;
|
||||
let (ack_first_hop, ack_packet) =
|
||||
match SurbAck::try_recover_first_hop_packet(&ack_data, packet_type) {
|
||||
Ok((first_hop, packet)) => (first_hop, packet),
|
||||
Err(err) => {
|
||||
info!("Failed to recover first hop from ack data: {err}");
|
||||
return Err(err.into());
|
||||
}
|
||||
};
|
||||
let forward_ack = MixPacket::new(ack_first_hop, ack_packet, packet_type);
|
||||
Ok((Some(forward_ack), message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn split_hop_data_into_ack_and_message(
|
||||
mut extracted_data: Vec<u8>,
|
||||
packet_type: PacketType,
|
||||
) -> Result<(Vec<u8>, Vec<u8>), PacketProcessingError> {
|
||||
let ack_len = SurbAck::len(Some(packet_type));
|
||||
|
||||
// in theory it's impossible for this to fail since it managed to go into correct `match`
|
||||
// branch at the caller
|
||||
if extracted_data.len() < ack_len {
|
||||
return Err(PacketProcessingError::NoSurbAckInFinalHop);
|
||||
}
|
||||
|
||||
let message = extracted_data.split_off(ack_len);
|
||||
let ack_data = extracted_data;
|
||||
Ok((ack_data, message))
|
||||
}
|
||||
|
||||
fn process_forward_hop(
|
||||
packet: NymPacket,
|
||||
forward_address: NodeAddressBytes,
|
||||
delay: SphinxDelay,
|
||||
packet_type: PacketType,
|
||||
) -> Result<MixProcessingResult, PacketProcessingError> {
|
||||
let next_hop_address = NymNodeRoutingAddress::try_from(forward_address)?;
|
||||
|
||||
let mix_packet = MixPacket::new(next_hop_address, packet, packet_type);
|
||||
Ok(MixProcessingResult::ForwardHop(mix_packet, Some(delay)))
|
||||
}
|
||||
|
||||
// TODO: what more could we realistically test here?
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn splitting_hop_data_works_for_sufficiently_long_payload() {
|
||||
let short_data = vec![42u8];
|
||||
assert!(split_hop_data_into_ack_and_message(short_data, PacketType::Mix).is_err());
|
||||
|
||||
let sufficient_data = vec![42u8; SurbAck::len(Some(PacketType::Mix))];
|
||||
let (ack, data) =
|
||||
split_hop_data_into_ack_and_message(sufficient_data.clone(), PacketType::Mix).unwrap();
|
||||
assert_eq!(sufficient_data, ack);
|
||||
assert!(data.is_empty());
|
||||
|
||||
let long_data: Vec<u8> = vec![42u8; SurbAck::len(Some(PacketType::Mix)) * 5];
|
||||
let (ack, data) = split_hop_data_into_ack_and_message(long_data, PacketType::Mix).unwrap();
|
||||
assert_eq!(ack.len(), SurbAck::len(Some(PacketType::Mix)));
|
||||
assert_eq!(data.len(), SurbAck::len(Some(PacketType::Mix)) * 4)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn splitting_hop_data_works_for_sufficiently_long_payload_outfox() {
|
||||
let short_data = vec![42u8];
|
||||
assert!(split_hop_data_into_ack_and_message(short_data, PacketType::Outfox).is_err());
|
||||
|
||||
let sufficient_data = vec![42u8; SurbAck::len(Some(PacketType::Outfox))];
|
||||
let (ack, data) =
|
||||
split_hop_data_into_ack_and_message(sufficient_data.clone(), PacketType::Outfox)
|
||||
.unwrap();
|
||||
assert_eq!(sufficient_data, ack);
|
||||
assert!(data.is_empty());
|
||||
|
||||
let long_data = vec![42u8; SurbAck::len(Some(PacketType::Outfox)) * 5];
|
||||
let (ack, data) =
|
||||
split_hop_data_into_ack_and_message(long_data, PacketType::Outfox).unwrap();
|
||||
assert_eq!(ack.len(), SurbAck::len(Some(PacketType::Outfox)));
|
||||
assert_eq!(data.len(), SurbAck::len(Some(PacketType::Outfox)) * 4)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn splitting_into_ack_and_message_returns_whole_data_for_ack() {
|
||||
let data = vec![42u8; SurbAck::len(Some(PacketType::Mix)) + 10];
|
||||
let (ack, message) =
|
||||
split_into_ack_and_message(data.clone(), PacketSize::AckPacket, PacketType::Mix)
|
||||
.unwrap();
|
||||
assert!(ack.is_none());
|
||||
assert_eq!(data, message)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn splitting_into_ack_and_message_returns_whole_data_for_ack_outfox() {
|
||||
let data = vec![42u8; SurbAck::len(Some(PacketType::Outfox)) + 10];
|
||||
let (ack, message) = split_into_ack_and_message(
|
||||
data.clone(),
|
||||
PacketSize::OutfoxAckPacket,
|
||||
PacketType::Outfox,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(ack.is_none());
|
||||
assert_eq!(data, message)
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ use crate::rpc_client::RpcClient;
|
||||
use crate::storage::{persist_block, ScraperStorage};
|
||||
use crate::PruningOptions;
|
||||
use futures::StreamExt;
|
||||
use std::cmp::max;
|
||||
use std::collections::{BTreeMap, HashSet, VecDeque};
|
||||
use std::ops::{Add, Range};
|
||||
use std::sync::Arc;
|
||||
@@ -99,7 +100,15 @@ impl BlockProcessor {
|
||||
})
|
||||
}
|
||||
|
||||
async fn process_block(&mut self, block: BlockToProcess) -> Result<(), ScraperError> {
|
||||
pub fn with_pruning(mut self, pruning_options: PruningOptions) -> Self {
|
||||
self.pruning_options = pruning_options;
|
||||
self
|
||||
}
|
||||
|
||||
pub(super) async fn process_block(
|
||||
&mut self,
|
||||
block: BlockToProcess,
|
||||
) -> Result<(), ScraperError> {
|
||||
info!("processing block at height {}", block.height);
|
||||
|
||||
let full_info = self.rpc_client.try_get_full_details(block).await?;
|
||||
@@ -169,6 +178,10 @@ impl BlockProcessor {
|
||||
self.msg_modules = modules;
|
||||
}
|
||||
|
||||
pub(super) fn last_process_height(&self) -> u32 {
|
||||
self.last_processed_height
|
||||
}
|
||||
|
||||
async fn maybe_request_missing_blocks(&mut self) -> Result<(), ScraperError> {
|
||||
// we're still processing, so we're good
|
||||
if self.last_processed_at.elapsed() < MAX_MISSING_BLOCKS_DELAY {
|
||||
@@ -254,6 +267,7 @@ impl BlockProcessor {
|
||||
}
|
||||
|
||||
if to_prune == 0 {
|
||||
self.last_pruned_height = self.last_processed_height;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -353,7 +367,14 @@ impl BlockProcessor {
|
||||
self.maybe_prune_storage().await?;
|
||||
|
||||
let latest_block = self.rpc_client.current_block_height().await? as u32;
|
||||
|
||||
if latest_block > self.last_processed_height && self.last_processed_height != 0 {
|
||||
// in case we were offline for a while,
|
||||
// make sure we don't request blocks we'd have to prune anyway
|
||||
let keep_recent = self.pruning_options.strategy_keep_recent();
|
||||
let last_to_keep = latest_block - keep_recent;
|
||||
self.last_processed_height = max(self.last_processed_height, last_to_keep);
|
||||
|
||||
let request_range = self.last_processed_height + 1..latest_block + 1;
|
||||
info!("we need to request {request_range:?} to resync");
|
||||
self.request_missing_blocks(request_range).await?;
|
||||
|
||||
@@ -16,7 +16,7 @@ pub enum ScraperError {
|
||||
#[error("failed to perform startup SQL migration: {0}")]
|
||||
StartupMigrationFailure(#[from] sqlx::migrate::MigrateError),
|
||||
|
||||
#[error("can't add any modules to the scraper as it's already running")]
|
||||
#[error("the block scraper is already running")]
|
||||
ScraperAlreadyRunning,
|
||||
|
||||
#[error("failed to establish websocket connection to {url}: {source}")]
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::block_processor::types::BlockToProcess;
|
||||
use crate::block_processor::BlockProcessor;
|
||||
use crate::block_requester::BlockRequester;
|
||||
use crate::block_requester::{BlockRequest, BlockRequester};
|
||||
use crate::error::ScraperError;
|
||||
use crate::modules::{BlockModule, MsgModule, TxModule};
|
||||
use crate::rpc_client::RpcClient;
|
||||
use crate::scraper::subscriber::ChainSubscriber;
|
||||
use crate::storage::ScraperStorage;
|
||||
use crate::PruningOptions;
|
||||
use futures::future::join_all;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc::{channel, unbounded_channel};
|
||||
use tokio::sync::mpsc::{
|
||||
channel, unbounded_channel, Receiver, Sender, UnboundedReceiver, UnboundedSender,
|
||||
};
|
||||
use tokio::sync::Notify;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tokio_util::task::TaskTracker;
|
||||
use tracing::info;
|
||||
use tracing::{error, info};
|
||||
use url::Url;
|
||||
|
||||
mod subscriber;
|
||||
@@ -115,6 +119,7 @@ pub struct NyxdScraper {
|
||||
cancel_token: CancellationToken,
|
||||
startup_sync: Arc<Notify>,
|
||||
pub storage: ScraperStorage,
|
||||
rpc_client: RpcClient,
|
||||
}
|
||||
|
||||
impl NyxdScraper {
|
||||
@@ -125,6 +130,7 @@ impl NyxdScraper {
|
||||
pub async fn new(config: Config) -> Result<Self, ScraperError> {
|
||||
config.pruning_options.validate()?;
|
||||
let storage = ScraperStorage::init(&config.database_path).await?;
|
||||
let rpc_client = RpcClient::new(&config.rpc_url)?;
|
||||
|
||||
Ok(NyxdScraper {
|
||||
config,
|
||||
@@ -132,6 +138,7 @@ impl NyxdScraper {
|
||||
cancel_token: CancellationToken::new(),
|
||||
startup_sync: Arc::new(Default::default()),
|
||||
storage,
|
||||
rpc_client,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -151,36 +158,156 @@ impl NyxdScraper {
|
||||
self.task_tracker.close();
|
||||
}
|
||||
|
||||
pub async fn start(&self) -> Result<(), ScraperError> {
|
||||
let (processing_tx, processing_rx) = unbounded_channel();
|
||||
let (req_tx, req_rx) = channel(5);
|
||||
pub async fn process_single_block(&self, height: u32) -> Result<(), ScraperError> {
|
||||
info!(height = height, "attempting to process a single block");
|
||||
if !self.task_tracker.is_empty() {
|
||||
return Err(ScraperError::ScraperAlreadyRunning);
|
||||
}
|
||||
|
||||
let rpc_client = RpcClient::new(&self.config.rpc_url)?;
|
||||
let (_, processing_rx) = unbounded_channel();
|
||||
let (req_tx, _) = channel(5);
|
||||
|
||||
// create the tasks
|
||||
let block_requester = BlockRequester::new(
|
||||
let mut block_processor = self
|
||||
.new_block_processor(req_tx.clone(), processing_rx)
|
||||
.await?
|
||||
.with_pruning(PruningOptions::nothing());
|
||||
|
||||
let block = self.rpc_client.get_basic_block_details(height).await?;
|
||||
|
||||
block_processor.process_block(block.into()).await
|
||||
}
|
||||
|
||||
pub async fn process_block_range(
|
||||
&self,
|
||||
starting_height: Option<u32>,
|
||||
end_height: Option<u32>,
|
||||
) -> Result<(), ScraperError> {
|
||||
if !self.task_tracker.is_empty() {
|
||||
return Err(ScraperError::ScraperAlreadyRunning);
|
||||
}
|
||||
|
||||
let (_, processing_rx) = unbounded_channel();
|
||||
let (req_tx, _) = channel(5);
|
||||
|
||||
let mut block_processor = self
|
||||
.new_block_processor(req_tx.clone(), processing_rx)
|
||||
.await?
|
||||
.with_pruning(PruningOptions::nothing());
|
||||
|
||||
let current_height = self.rpc_client.current_block_height().await? as u32;
|
||||
let last_processed = block_processor.last_process_height();
|
||||
|
||||
let starting_height = match starting_height {
|
||||
// always attempt to use whatever the user has provided
|
||||
Some(explicit) => explicit,
|
||||
None => {
|
||||
// otherwise, attempt to resume where we last stopped
|
||||
// and if we haven't processed anything, start from the current height
|
||||
if last_processed != 0 {
|
||||
last_processed
|
||||
} else {
|
||||
current_height
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let end_height = match end_height {
|
||||
// always attempt to use whatever the user has provided
|
||||
Some(explicit) => explicit,
|
||||
None => {
|
||||
// otherwise, attempt to either go from the start height to the height right
|
||||
// before the final processed block held in the storage (in case there are gaps)
|
||||
// or finally, just go to the current block height
|
||||
if last_processed > starting_height {
|
||||
last_processed - 1
|
||||
} else {
|
||||
current_height
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
info!(
|
||||
starting_height = starting_height,
|
||||
end_height = end_height,
|
||||
"attempting to process block range"
|
||||
);
|
||||
|
||||
let range = (starting_height..=end_height).collect::<Vec<_>>();
|
||||
|
||||
// the most likely bottleneck here are going to be the chain queries,
|
||||
// so batch multiple requests
|
||||
for batch in range.chunks(4) {
|
||||
let batch_result = join_all(
|
||||
batch
|
||||
.iter()
|
||||
.map(|height| self.rpc_client.get_basic_block_details(*height)),
|
||||
)
|
||||
.await;
|
||||
for result in batch_result {
|
||||
match result {
|
||||
Ok(block) => block_processor.process_block(block.into()).await?,
|
||||
Err(err) => {
|
||||
error!("failed to retrieve the block: {err}. stopping...");
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn new_block_requester(
|
||||
&self,
|
||||
req_rx: Receiver<BlockRequest>,
|
||||
processing_tx: UnboundedSender<BlockToProcess>,
|
||||
) -> BlockRequester {
|
||||
BlockRequester::new(
|
||||
self.cancel_token.clone(),
|
||||
rpc_client.clone(),
|
||||
self.rpc_client.clone(),
|
||||
req_rx,
|
||||
processing_tx.clone(),
|
||||
);
|
||||
let block_processor = BlockProcessor::new(
|
||||
)
|
||||
}
|
||||
|
||||
async fn new_block_processor(
|
||||
&self,
|
||||
req_tx: Sender<BlockRequest>,
|
||||
processing_rx: UnboundedReceiver<BlockToProcess>,
|
||||
) -> Result<BlockProcessor, ScraperError> {
|
||||
BlockProcessor::new(
|
||||
self.config.pruning_options,
|
||||
self.cancel_token.clone(),
|
||||
self.startup_sync.clone(),
|
||||
processing_rx,
|
||||
req_tx,
|
||||
self.storage.clone(),
|
||||
rpc_client,
|
||||
self.rpc_client.clone(),
|
||||
)
|
||||
.await?;
|
||||
let chain_subscriber = ChainSubscriber::new(
|
||||
.await
|
||||
}
|
||||
|
||||
async fn new_chain_subscriber(
|
||||
&self,
|
||||
processing_tx: UnboundedSender<BlockToProcess>,
|
||||
) -> Result<ChainSubscriber, ScraperError> {
|
||||
ChainSubscriber::new(
|
||||
&self.config.websocket_url,
|
||||
self.cancel_token.clone(),
|
||||
self.task_tracker.clone(),
|
||||
processing_tx,
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn start(&self) -> Result<(), ScraperError> {
|
||||
let (processing_tx, processing_rx) = unbounded_channel();
|
||||
let (req_tx, req_rx) = channel(5);
|
||||
|
||||
// create the tasks
|
||||
let block_requester = self.new_block_requester(req_rx, processing_tx.clone());
|
||||
let block_processor = self.new_block_processor(req_tx, processing_rx).await?;
|
||||
let chain_subscriber = self.new_chain_subscriber(processing_tx).await?;
|
||||
|
||||
// spawn them
|
||||
self.start_tasks(block_requester, block_processor, chain_subscriber);
|
||||
|
||||
@@ -16,7 +16,7 @@ use url::Url;
|
||||
|
||||
const MAX_FAILURES: usize = 10;
|
||||
const MAX_RECONNECTION_ATTEMPTS: usize = 8;
|
||||
const SOCKET_FAILURE_RESET: Duration = Duration::hours(2);
|
||||
const SOCKET_FAILURE_RESET: Duration = Duration::minutes(15);
|
||||
|
||||
pub struct ChainSubscriber {
|
||||
cancel: CancellationToken,
|
||||
|
||||
@@ -435,9 +435,12 @@ where
|
||||
trace!("update_last_processed");
|
||||
let start = Instant::now();
|
||||
|
||||
sqlx::query!("UPDATE metadata SET last_processed_height = ?", height)
|
||||
.execute(executor)
|
||||
.await?;
|
||||
sqlx::query!(
|
||||
"UPDATE metadata SET last_processed_height = MAX(last_processed_height, ?)",
|
||||
height
|
||||
)
|
||||
.execute(executor)
|
||||
.await?;
|
||||
log_db_operation_time("update_last_processed", start);
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -13,11 +13,13 @@ license.workspace = true
|
||||
[dependencies]
|
||||
|
||||
serde = { workspace = true }
|
||||
hex = { workspace = true, optional = true }
|
||||
bs58 = { workspace = true, optional = true }
|
||||
base64 = { workspace = true, optional = true }
|
||||
time = { workspace = true, features = ["formatting", "parsing"], optional = true }
|
||||
|
||||
[features]
|
||||
hex = ["dep:hex"]
|
||||
bs58 = ["dep:bs58"]
|
||||
base64 = ["dep:base64"]
|
||||
date = ["time"]
|
||||
@@ -32,6 +32,20 @@ pub mod bs58 {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "hex")]
|
||||
pub mod hex {
|
||||
use serde::{Deserialize, Deserializer, Serializer};
|
||||
|
||||
pub fn serialize<S: Serializer>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error> {
|
||||
serializer.serialize_str(&::hex::encode(bytes))
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u8>, D::Error> {
|
||||
let s = String::deserialize(deserializer)?;
|
||||
::hex::decode(&s).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "date")]
|
||||
pub mod date {
|
||||
use serde::ser::Error;
|
||||
|
||||
@@ -3,9 +3,6 @@
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("peers in wireguard don't match with in-memory ")]
|
||||
PeerMismatch,
|
||||
|
||||
#[error("traffic byte data needs to be increasing")]
|
||||
InconsistentConsumedBytes,
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ use defguard_wireguard_rs::{
|
||||
};
|
||||
use futures::channel::oneshot;
|
||||
use nym_authenticator_requests::{
|
||||
v1::registration::BANDWIDTH_CAP_PER_DAY, v2::registration::RemainingBandwidthData,
|
||||
latest::registration::RemainingBandwidthData, v1::registration::BANDWIDTH_CAP_PER_DAY,
|
||||
};
|
||||
use nym_credential_verification::{
|
||||
bandwidth_storage_manager::BandwidthStorageManager, BandwidthFlushingBehaviourConfig,
|
||||
@@ -160,13 +160,10 @@ impl<St: Storage + Clone + 'static> PeerController<St> {
|
||||
.ok_or(Error::MissingClientBandwidthEntry)?
|
||||
.client_id
|
||||
{
|
||||
let bandwidth = storage
|
||||
.get_available_bandwidth(client_id)
|
||||
.await?
|
||||
.ok_or(Error::MissingClientBandwidthEntry)?;
|
||||
storage.create_bandwidth_entry(client_id).await?;
|
||||
Ok(Some(BandwidthStorageManager::new(
|
||||
storage,
|
||||
ClientBandwidth::new(bandwidth.into()),
|
||||
ClientBandwidth::new(Default::default()),
|
||||
client_id,
|
||||
BandwidthFlushingBehaviourConfig::default(),
|
||||
true,
|
||||
@@ -228,14 +225,10 @@ impl<St: Storage + Clone + 'static> PeerController<St> {
|
||||
.available_bandwidth()
|
||||
.await
|
||||
} else {
|
||||
let peer = self
|
||||
.host_information
|
||||
.read()
|
||||
.await
|
||||
.peers
|
||||
.get(key)
|
||||
.ok_or(Error::PeerMismatch)?
|
||||
.clone();
|
||||
let Some(peer) = self.host_information.read().await.peers.get(key).cloned() else {
|
||||
// host information not updated yet
|
||||
return Ok(None);
|
||||
};
|
||||
BANDWIDTH_CAP_PER_DAY.saturating_sub((peer.rx_bytes + peer.tx_bytes) as i64)
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::peer_controller::PeerControlRequest;
|
||||
use defguard_wireguard_rs::host::Peer;
|
||||
use defguard_wireguard_rs::{host::Host, key::Key};
|
||||
use futures::channel::oneshot;
|
||||
use nym_authenticator_requests::v2::registration::BANDWIDTH_CAP_PER_DAY;
|
||||
@@ -71,15 +72,11 @@ impl<St: Storage + Clone + 'static> PeerHandle<St> {
|
||||
Ok(success)
|
||||
}
|
||||
|
||||
async fn active_peer(&mut self, storage_peer: WireguardPeer) -> Result<bool, Error> {
|
||||
let kernel_peer = self
|
||||
.host_information
|
||||
.read()
|
||||
.await
|
||||
.peers
|
||||
.get(&self.public_key)
|
||||
.ok_or(Error::PeerMismatch)?
|
||||
.clone();
|
||||
async fn active_peer(
|
||||
&mut self,
|
||||
storage_peer: WireguardPeer,
|
||||
kernel_peer: Peer,
|
||||
) -> Result<bool, Error> {
|
||||
if let Some(bandwidth_manager) = &self.bandwidth_storage_manager {
|
||||
let spent_bandwidth = (kernel_peer.rx_bytes + kernel_peer.tx_bytes)
|
||||
.checked_sub(storage_peer.rx_bytes as u64 + storage_peer.tx_bytes as u64)
|
||||
@@ -111,11 +108,21 @@ impl<St: Storage + Clone + 'static> PeerHandle<St> {
|
||||
while !self.task_client.is_shutdown() {
|
||||
tokio::select! {
|
||||
_ = self.timeout_check_interval.next() => {
|
||||
let Some(peer) = self.storage.get_wireguard_peer(&self.public_key.to_string()).await? else {
|
||||
let Some(kernel_peer) = self
|
||||
.host_information
|
||||
.read()
|
||||
.await
|
||||
.peers
|
||||
.get(&self.public_key)
|
||||
.cloned() else {
|
||||
// the host information hasn't beed updated yet
|
||||
continue;
|
||||
};
|
||||
let Some(storage_peer) = self.storage.get_wireguard_peer(&self.public_key.to_string()).await? else {
|
||||
log::debug!("Peer {:?} not in storage anymore, shutting down handle", self.public_key);
|
||||
return Ok(());
|
||||
};
|
||||
if !self.active_peer(peer).await? {
|
||||
if !self.active_peer(storage_peer, kernel_peer).await? {
|
||||
log::debug!("Peer {:?} doesn't have bandwidth anymore, shutting down handle", self.public_key);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Generated
+4
-4
@@ -1935,18 +1935,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.63"
|
||||
version = "1.0.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
|
||||
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.63"
|
||||
version = "1.0.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
|
||||
checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -2,6 +2,242 @@
|
||||
|
||||
This page displays a full list of all the changes during our release cycle from [`v2024.3-eclipse`](https://github.com/nymtech/nym/blob/nym-binaries-v2024.3-eclipse/CHANGELOG.md) onwards. Operators can find here the newest updates together with links to relevant documentation. The list is sorted so that the newest changes appear first.
|
||||
|
||||
## `v2024.11-wedel`
|
||||
|
||||
- [Release binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2024.11-wedel)
|
||||
- [Release CHANGELOG.md](https://github.com/nymtech/nym/blob/nym-binaries-v2024.11-wedel/CHANGELOG.md)
|
||||
- [`nym-node`](nodes/nym-node.md) version `1.1.8`
|
||||
|
||||
```sh
|
||||
Binary Name: nym-node
|
||||
Build Timestamp: 2024-09-27T11:02:37.073944654Z
|
||||
Build Version: 1.1.8
|
||||
Commit SHA: c3ec970a377adb25d57be5428551fada2ec55128
|
||||
Commit Date: 2024-09-26T08:24:53.000000000+02:00
|
||||
Commit Branch: master
|
||||
rustc Version: 1.80.1
|
||||
rustc Channel: stable
|
||||
cargo Profile: release
|
||||
```
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
- [New Network Monitor](https://github.com/nymtech/nym/pull/4610): Monitors the Nym network by sending itself packages across the mixnet. Network monitor is running two tokio tasks, one manages mixnet clients and another manages monitoring itself. Monitor is designed to be driven externally, via an `HTTP api`. This means that it does not do any monitoring unless driven by something like [`locust`](https://locust.io/). This allows us to tailor the load externally, potentially distributing it across multiple monitors. Includes a dockerised setup for automatically spinning up monitor and driving it with locust.
|
||||
- *Note: NNM is not deployed on mainnet yet!*
|
||||
|
||||
- [Add get_mixnodes_described to validator_client](https://github.com/nymtech/nym/pull/4725)
|
||||
|
||||
- [Remove deprecated mark_as_success and use new disarm](https://github.com/nymtech/nym/pull/4751): Update function name to keep terminology consistent with tokio `CancellationToken DropGuard`.
|
||||
|
||||
- [Update peer refresh value](https://github.com/nymtech/nym/pull/4754): `lso` expose the value by moving it to wireguard types, and separate the refresh time to the database sync time, so that more probable and needed actions happen faster (refresh) and more improbable ones don't overload the system (peer suspended or stale)
|
||||
~~~admonish example collapsible=true title='Testing steps performed'
|
||||
- **Noted** that the constants `DEFAULT_PEER_TIMEOUT` and `DEFAULT_PEER_TIMEOUT_CHECK` have been moved to `common/wireguard-types/src/lib.rs` and are now being used across modules for consistency
|
||||
- **Observed** that the `peer_controller.rs` now separates the in-memory updates from the storage sync operations to reduce system load
|
||||
- **Identified** that in-memory updates of peer bandwidth usage happen every `DEFAULT_PEER_TIMEOUT_CHECK` (every 5 seconds), while storage updates occur every 5 * `DEFAULT_PEER_TIMEOUT_CHECK` (every 25 seconds)
|
||||
|
||||
**Checked System Load and Performance:**
|
||||
|
||||
- **Monitored** system resource usage (CPU, memory, I/O) during the test to assess the impact of the changes
|
||||
- **Confirmed** that the separation of in-memory updates and storage syncs resulted in reduced system load, particularly I/O operations, compared to previous versions where storage updates occurred more frequently
|
||||
- **Ensured** that the system remained responsive and no performance bottlenecks were introduced
|
||||
|
||||
- **Efficiency Improvement:** The separation of in-memory updates and storage syncs effectively reduced unnecessary database writes, improving system efficiency without compromising data accuracy
|
||||
~~~
|
||||
|
||||
- [Remove duplicate stat count for retransmissions](https://github.com/nymtech/nym/pull/4756)
|
||||
|
||||
- [Make gateway latency check generic](https://github.com/nymtech/nym/pull/4759): Replace concrete gateway type with trait in latency check, so we can make use of it in the vpn client.
|
||||
~~~admonish example collapsible=true title='Testing steps performed'
|
||||
- Initialised new `nym-client` with the `--latency-based-selection` flag and ensured it still works as normal.
|
||||
~~~
|
||||
|
||||
- [chore: remove repetitive words](https://github.com/nymtech/nym/pull/4763)
|
||||
|
||||
- [Avoid race on ip and registration structures](https://github.com/nymtech/nym/pull/4766): To avoid a state where the ip is being cleared out before the registration is also cleared out, couple the two structures under the same lock, since they are anyway very inter-dependent.
|
||||
~~~admonish example collapsible=true title='Testing steps performed'
|
||||
1. - **Checked out** the release/2024.10-wedel branch containing the fix for the race condition on IP and registration structures
|
||||
- **Deployed** the on a controlled test environment to prevent interference
|
||||
|
||||
2. **Monitored Logs:**
|
||||
|
||||
- **Enabled** debug logging to capture all events
|
||||
- **Monitored** logs in real-time to observe the handling of concurrent registration requests
|
||||
- **Checked** for any error messages, warnings, or indications of race conditions
|
||||
|
||||
3. **Verified Client Responses:**
|
||||
|
||||
- Ensured that all clients received appropriate responses:
|
||||
- Successful registration with assigned IP and registration data
|
||||
- Appropriate error messages if no IPs were available or if other issues occurred
|
||||
- Confirmed that no clients were left in an inconsistent state (e.g., assigned an IP but not fully registered)
|
||||
|
||||
4. **Validated Normal Operation:**
|
||||
- **Conducted standard registration processes** with individual clients to confirm that regular functionality is unaffected via `nym-vpn-cli`
|
||||
- Ensured that authenticated clients could communicate over the network as expected
|
||||
~~~
|
||||
|
||||
- [Persist used wireguard private IPs](https://github.com/nymtech/nym/pull/4771)
|
||||
|
||||
- [Enable dependabot version upgrades for root rust workspace](https://github.com/nymtech/nym/pull/4778)
|
||||
|
||||
- [Fix clippy for `unwrap_or_default`](https://github.com/nymtech/nym/pull/4783): Fix nightly build for [beta toolchain](https://github.com/nymtech/nym/actions/runs/10552082396/job/29230401668)
|
||||
|
||||
- [Update dependabot](https://github.com/nymtech/nym/pull/4796): Bump max number of dependabot rust PRs to 10. Add readme entry to workspace package.
|
||||
|
||||
- [Run `cargo-autoinherit` for a few new crates](https://github.com/nymtech/nym/pull/4801): Run cargo-autoinherit for a few new crates - Sort crates list.
|
||||
|
||||
- [Add `axum` server to `nym-api`](https://github.com/nymtech/nym/pull/4803): Summary PR to add axum functionality behind a feature flag `axum`, alongside rocket.
|
||||
|
||||
- [Remove unused wireguard flag from SDK](https://github.com/nymtech/nym/pull/4823)
|
||||
|
||||
- [Expose wireguard details on self described endpoint](https://github.com/nymtech/nym/pull/4825)
|
||||
~~~admonish example collapsible=true title='Testing steps performed'
|
||||
Wireguard details are now visible at the nym-node endpoint `/api/v1/gateway/client-interfaces` as well as on the nym-api self-described endpoint `/api/v1/gateways/described`, above the existing data displaying mixnet_websocket information.
|
||||
|
||||
An example of what will be shown is:
|
||||
```json
|
||||
"wireguard": {
|
||||
"port": 51822,
|
||||
"public_key": "<some public key here>"
|
||||
}
|
||||
```
|
||||
~~~
|
||||
|
||||
- [Revamped ticketbook serialisation and exposed additional cli methods](https://github.com/nymtech/nym/pull/4827): `wip` branch that includes changes needed for `vpn-api` alongside additional `ecash utils`
|
||||
~~~admonish example collapsible=true title='Testing steps performed'
|
||||
Checked the following commands:
|
||||
```sh
|
||||
show-ticket-books # which displays the information about all ticketbooks associated to the client
|
||||
import-ticket-book # which imports a normal ticketbook to the client alongside `--full` flag
|
||||
```
|
||||
|
||||
On the cli, the following were added: `import-coin-index-signatures`, `import-expiration-date-signatures` and `import-master-verification-key`.
|
||||
~~~
|
||||
|
||||
- [Run cargo autoinherit following last weeks dependabot updates](https://github.com/nymtech/nym/pull/4831)
|
||||
|
||||
- [Remove serde_crate named import](https://github.com/nymtech/nym/pull/4832)
|
||||
|
||||
- [Create nym-repo-setup debian package and nym-vpn meta package](https://github.com/nymtech/nym/pull/4837): Create nym-repo-setup debian package that sets up the nymtech debian repo on the system it's installed on. It does 2 things:
|
||||
|
||||
1. Copy the keyring to `/usr/share/keyrings/nymtech.gpg`
|
||||
2. Copy the repo spec to `/etc/apt/sources.list.d/nymtech.list`
|
||||
- Also create a meta package `nym-vpn` which only purpose is to depend on the daemon and UI.
|
||||
|
||||
~~~admonish example collapsible=true title='Usage'
|
||||
1. Install with
|
||||
```sh
|
||||
sudo dpkg -i ./nym-repo-setup.deb
|
||||
```
|
||||
2. Once it's installed, it should be possible to install the vpn client with
|
||||
```sh
|
||||
sudo apt install nym-vpnc
|
||||
```
|
||||
3. To reemove the repo, use
|
||||
```sh
|
||||
sudo apt remove nym-repo-setup
|
||||
```
|
||||
|
||||
NOTE: removing the repo will not remove any installed nym-vpn packages
|
||||
~~~
|
||||
|
||||
~~~admonish example collapsible=true title='Testing steps performed'
|
||||
|
||||
1. **Downloaded** the `nym-repo-setup.deb` package to a Debian-based test system
|
||||
|
||||
2. **Installed** the repository setup package using the command:
|
||||
```bash
|
||||
sudo dpkg -i ./nym-repo-setup.deb
|
||||
```
|
||||
|
||||
3. **Verified** that the GPG keyring was copied to `/usr/share/keyrings/nymtech.gpg`:
|
||||
```bash
|
||||
ls -l /usr/share/keyrings/nymtech.gpg
|
||||
```
|
||||
|
||||
4. **Checked** that the repository specification was added to `/etc/apt/sources.list.d/nymtech.list`:
|
||||
```bash
|
||||
cat /etc/apt/sources.list.d/nymtech.list
|
||||
```
|
||||
|
||||
5. **Updated** the package list:
|
||||
```bash
|
||||
sudo apt update
|
||||
```
|
||||
|
||||
6. **Installed** the VPN client meta-package:
|
||||
```bash
|
||||
sudo apt install nym-vpnc
|
||||
```
|
||||
|
||||
7. **Confirmed** that the `nym-vpnc` package and its dependencies (daemon and UI) were installed successfully
|
||||
|
||||
8. **Tested** the VPN client to ensure it operates as expected
|
||||
|
||||
9. **Removed** the repository setup package:
|
||||
```bash
|
||||
sudo apt remove nym-repo-setup
|
||||
```
|
||||
|
||||
10. **Verified** that the repository specification file `/etc/apt/sources.list.d/nymtech.list` was removed
|
||||
|
||||
11. **Ensured** that the installed `nym-vpnc` packages remained installed and functional after removing the repo setup package
|
||||
~~~
|
||||
|
||||
- [Use ecash credential type for bandwidth value](https://github.com/nymtech/nym/pull/4840)
|
||||
|
||||
- [Start switching over jobs to arc-ubuntu-20.04](https://github.com/nymtech/nym/pull/4843)
|
||||
|
||||
~~~admonish example collapsible=true title='`ci-binary-config-checker`'
|
||||
```
|
||||
- ci-build-upload-binaries
|
||||
- ci-build
|
||||
- ci-cargo-deny
|
||||
- ci-contracts-schema
|
||||
- ci-contracts-upload-binaries
|
||||
- ci-contracts
|
||||
- ci-docs
|
||||
- ci-nym-wallet-rust
|
||||
- ci-sdk-wasm
|
||||
```
|
||||
~~~
|
||||
|
||||
- [Move credential verification into common crate](https://github.com/nymtech/nym/pull/4853)
|
||||
|
||||
- [Revert runner for `ci-docs`](https://github.com/nymtech/nym/pull/4855)
|
||||
|
||||
- [Remove `golang` workaround in `ci-sdk-wasm`](https://github.com/nymtech/nym/pull/4858)
|
||||
|
||||
- [Fix linux conditional in `ci-build.yml`](https://github.com/nymtech/nym/pull/4863)
|
||||
|
||||
- [Disable push trigger and add missing paths in `ci-build`](https://github.com/nymtech/nym/pull/4864)
|
||||
|
||||
- [chore: removed completed queued mixnet migration](https://github.com/nymtech/nym/pull/4865)
|
||||
|
||||
- [Bump defguard to github latest version](https://github.com/nymtech/nym/pull/4872)
|
||||
|
||||
- [Backport #4894 to fix ci](https://github.com/nymtech/nym/pull/4899)
|
||||
|
||||
### Bugfix
|
||||
|
||||
- [Fix test failure in ipr request size](https://github.com/nymtech/nym/pull/4844): Nightly build started failing due to a unit test using `now()`, changing the serialized size. Fixed to use a fixed date.
|
||||
|
||||
- [Fix clippy for nym-wallet and latest rustc](https://github.com/nymtech/nym/pull/4845)
|
||||
|
||||
- [Allow updating globally stored signatures](https://github.com/nymtech/nym/pull/4891)
|
||||
|
||||
- [Bugfix/ticketbook false double spending](https://github.com/nymtech/nym/pull/4892)
|
||||
~~~admonish example collapsible=true title='Testing steps performed'
|
||||
Tested running a client in mixnet mode, with a standard ticketbook, as well as a client using an imported ticketbook. The double spending bug is no longer an issue, bandwidth is consumed properly, and upon consumption of one ticket another ticket is properly obtained.
|
||||
~~~
|
||||
|
||||
### Operators Guide, Tooling & Updates
|
||||
|
||||
- [WSS setup guide updates](https://github.com/nymtech/nym/commit/05d6652177fb77324f8c38b3d8a547d07e729fec): Operators setting up WSS and reverse proxy on Gateways have now cleaner and simpler guide to configure their VPS.
|
||||
|
||||
- [Updat hostname instruction for WSS](https://github.com/nymtech/nym/commit/7146c4c012ba7012dc74edc8510bbf377dc32fba): Adding a hostname instruction for clarity
|
||||
|
||||
## `nym-node` patch from `release/2024.10-caramello`
|
||||
|
||||
- [Patch release binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2024.10-caramello-patch)
|
||||
|
||||
@@ -25,7 +25,7 @@ We don't recommend this setup because it's really difficult to get a static IP a
|
||||
|
||||
### What's the Sphinx packet size?
|
||||
|
||||
The sizes are shown in the configs [here](https://github.com/nymtech/nym/blob/1ba6444e722e7757f1175a296bed6e31e25b8db8/common/nymsphinx/params/src/packet_sizes.rs#L12) (default is the one clients use, the others are for research purposes, not to be used in production as this would fragment the anonymity set). More info can be found [here](https://github.com/nymtech/nym/blob/4844ac953a12b29fa27688609ec193f1d560c996/common/nymsphinx/anonymous-replies/src/reply_surb.rs#L80).
|
||||
The sizes are shown in the configs [here](https://github.com/nymtech/nym/blob/develop/common/nymsphinx/params/src/packet_sizes.rs#L32) (default is the one clients use, the others are for research purposes, not to be used in production as this would fragment the anonymity set). More info can be found [here](https://github.com/nymtech/nym/blob/develop/common/nymsphinx/anonymous-replies/src/reply_surb.rs#L80).
|
||||
|
||||
### Why a Mix Node and a Gateway cannot be bonded with the same wallet?
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
### What determines the rewards when running a `nym-node --mode mixnode`?
|
||||
|
||||
> **Visit [nymtech.net/about/token](https://nymtech.net/about/token) to find live information, graphs and dashboards about NYM token.**
|
||||
|
||||
The stake required for a Mix Node to achieve maximum rewards is called Mix Node saturation point. This is calculated from the staking supply (all circulating supply + part of unlocked tokens). The target level of staking is to have 40% of the staking supply locked in Mix Nodes.
|
||||
|
||||
The node stake saturation point, which we denote by Nsat, is given by the stake supply, target level of staking divided between the rewarded nodes.
|
||||
@@ -20,13 +22,3 @@ The rewarded nodes are the nodes which will receive some rewards by the end of t
|
||||
|
||||
|
||||
For more detailed calculation, read our blog post [Nym Token Economics update](https://blog.nymtech.net/nym-token-economics-update-fedff0ed5267). More info on staking can be found [here](https://blog.nymtech.net/staking-in-nym-introducing-mainnet-mixmining-f9bb1cbc7c36). And [here](https://blog.nymtech.net/want-to-stake-in-nym-here-is-how-to-choose-a-mix-node-to-delegate-nym-to-c3b862add165) is more info on how to choose a Mix Node for delegation. And finally an [update](https://blog.nymtech.net/quarterly-token-economic-parameter-update-b2862948710f) on token economics from July 2023.
|
||||
|
||||
<!--
|
||||
<iframe src="https://status.notrustverify.ch/d-solo/CW3L7dVVk/nym-mixnet?orgId=1&from=1703074829887&to=1705666829887&panelId=31" width="850" height="400" frameborder="0"></iframe>
|
||||
-->
|
||||
|
||||
<iframe src="https://dashboard.notrustverify.ch/d-solo/l71MWkX7k/ntv-mixnode?orgId=1&from=1710949572440&to=1713537972440&panelId=18" width="850" height="400" frameborder="0"></iframe>
|
||||
|
||||
*More graphs and stats at [stats.notrustverify.ch](https://status.notrustverify.ch/d/CW3L7dVVk/nym-mixnet?orgId=1&from=1703074861988&to=1705666862004).*
|
||||
|
||||
|
||||
|
||||
@@ -8,10 +8,10 @@ use futures::channel::mpsc::SendError;
|
||||
use futures::StreamExt;
|
||||
use nym_gateway_storage::{error::StorageError, Storage};
|
||||
use nym_mixnet_client::forwarder::MixForwardingSender;
|
||||
use nym_mixnode_common::packet_processor::processor::ProcessedFinalHop;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_sphinx::framing::codec::NymCodec;
|
||||
use nym_sphinx::framing::packet::FramedNymPacket;
|
||||
use nym_sphinx::framing::processing::ProcessedFinalHop;
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use nym_task::TaskClient;
|
||||
use std::collections::HashMap;
|
||||
@@ -21,6 +21,8 @@ use tokio::net::TcpStream;
|
||||
use tokio_util::codec::Framed;
|
||||
use tracing::*;
|
||||
|
||||
use super::packet_processing::process_packet;
|
||||
|
||||
// defines errors that warrant a panic if not thrown in the context of a shutdown
|
||||
#[derive(Debug, Error)]
|
||||
enum CriticalPacketProcessingError {
|
||||
@@ -184,14 +186,14 @@ impl<St: Storage> ConnectionHandler<St> {
|
||||
// question: can it also be per connection vs global?
|
||||
//
|
||||
|
||||
let processed_final_hop = match self.packet_processor.process_received(framed_sphinx_packet)
|
||||
{
|
||||
Err(err) => {
|
||||
debug!("We failed to process received sphinx packet - {err}");
|
||||
return Ok(());
|
||||
}
|
||||
Ok(processed_final_hop) => processed_final_hop,
|
||||
};
|
||||
let processed_final_hop =
|
||||
match process_packet(framed_sphinx_packet, self.packet_processor.sphinx_key()) {
|
||||
Err(err) => {
|
||||
debug!("We failed to process received sphinx packet - {err}");
|
||||
return Ok(());
|
||||
}
|
||||
Ok(processed_final_hop) => processed_final_hop,
|
||||
};
|
||||
|
||||
self.handle_processed_packet(processed_final_hop).await
|
||||
}
|
||||
|
||||
@@ -3,18 +3,24 @@
|
||||
|
||||
use nym_crypto::asymmetric::encryption;
|
||||
use nym_mixnode_common::packet_processor::error::MixProcessingError;
|
||||
pub use nym_mixnode_common::packet_processor::processor::MixProcessingResult;
|
||||
use nym_mixnode_common::packet_processor::processor::{ProcessedFinalHop, SphinxPacketProcessor};
|
||||
use nym_mixnode_common::packet_processor::processor::SphinxPacketProcessor;
|
||||
use nym_sphinx::framing::packet::FramedNymPacket;
|
||||
use nym_sphinx::framing::processing::{
|
||||
process_framed_packet, MixProcessingResult, PacketProcessingError, ProcessedFinalHop,
|
||||
};
|
||||
use nym_sphinx::PrivateKey;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum GatewayProcessingError {
|
||||
#[error("failed to process received mix packet - {0}")]
|
||||
PacketProcessingError(#[from] MixProcessingError),
|
||||
PacketProcessing(#[from] MixProcessingError),
|
||||
|
||||
#[error("received a forward hop mix packet")]
|
||||
ForwardHopReceivedError,
|
||||
ForwardHopReceived,
|
||||
|
||||
#[error("failed to process received sphinx packet: {0}")]
|
||||
NymPacketProcessing(#[from] PacketProcessingError),
|
||||
}
|
||||
|
||||
// PacketProcessor contains all data required to correctly unwrap and store sphinx packets
|
||||
@@ -24,21 +30,23 @@ pub struct PacketProcessor {
|
||||
}
|
||||
|
||||
impl PacketProcessor {
|
||||
pub fn sphinx_key(&self) -> &PrivateKey {
|
||||
self.inner_processor.sphinx_key()
|
||||
}
|
||||
|
||||
pub(crate) fn new(encryption_key: &encryption::PrivateKey) -> Self {
|
||||
PacketProcessor {
|
||||
inner_processor: SphinxPacketProcessor::new(encryption_key.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn process_received(
|
||||
&self,
|
||||
received: FramedNymPacket,
|
||||
) -> Result<ProcessedFinalHop, GatewayProcessingError> {
|
||||
match self.inner_processor.process_received(received)? {
|
||||
MixProcessingResult::ForwardHop(..) => {
|
||||
Err(GatewayProcessingError::ForwardHopReceivedError)
|
||||
}
|
||||
MixProcessingResult::FinalHop(processed_final) => Ok(processed_final),
|
||||
}
|
||||
pub(crate) fn process_packet(
|
||||
received: FramedNymPacket,
|
||||
sphinx_key: &nym_sphinx::PrivateKey,
|
||||
) -> Result<ProcessedFinalHop, GatewayProcessingError> {
|
||||
match process_framed_packet(received, sphinx_key)? {
|
||||
MixProcessingResult::ForwardHop(..) => Err(GatewayProcessingError::ForwardHopReceived),
|
||||
MixProcessingResult::FinalHop(processed_final) => Ok(processed_final),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::node::listener::connection_handler::packet_processing::{
|
||||
MixProcessingResult, PacketProcessor,
|
||||
};
|
||||
use crate::node::listener::connection_handler::packet_processing::PacketProcessor;
|
||||
use crate::node::packet_delayforwarder::PacketDelayForwardSender;
|
||||
use crate::node::TaskClient;
|
||||
use futures::StreamExt;
|
||||
@@ -13,7 +11,9 @@ use nym_metrics::nanos;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_sphinx::framing::codec::NymCodec;
|
||||
use nym_sphinx::framing::packet::FramedNymPacket;
|
||||
use nym_sphinx::framing::processing::MixProcessingResult;
|
||||
use nym_sphinx::Delay as SphinxDelay;
|
||||
use packet_processing::process_received_packet;
|
||||
use std::net::SocketAddr;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::time::Instant;
|
||||
@@ -38,6 +38,10 @@ impl ConnectionHandler {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn packet_processor(&self) -> &PacketProcessor {
|
||||
&self.packet_processor
|
||||
}
|
||||
|
||||
fn delay_and_forward_packet(&self, mix_packet: MixPacket, delay: Option<SphinxDelay>) {
|
||||
// determine instant at which packet should get forwarded. this way we minimise effect of
|
||||
// being stuck in the queue [of the channel] to get inserted into the delay queue
|
||||
@@ -60,7 +64,10 @@ impl ConnectionHandler {
|
||||
// all processing such, key caching, etc. was done.
|
||||
// however, if it was a forward hop, we still need to delay it
|
||||
nanos!("handle_received_packet", {
|
||||
match self.packet_processor.process_received(framed_sphinx_packet) {
|
||||
self.packet_processor
|
||||
.node_stats_update_sender()
|
||||
.report_received();
|
||||
match process_received_packet(framed_sphinx_packet, self.packet_processor().inner()) {
|
||||
Err(err) => debug!("We failed to process received sphinx packet - {err}"),
|
||||
Ok(res) => match res {
|
||||
MixProcessingResult::ForwardHop(forward_packet, delay) => {
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
use crate::node::node_statistics;
|
||||
use nym_crypto::asymmetric::encryption;
|
||||
use nym_mixnode_common::packet_processor::error::MixProcessingError;
|
||||
pub use nym_mixnode_common::packet_processor::processor::MixProcessingResult;
|
||||
use nym_mixnode_common::packet_processor::processor::SphinxPacketProcessor;
|
||||
use nym_sphinx::framing::packet::FramedNymPacket;
|
||||
use nym_sphinx::framing::processing::{process_framed_packet, MixProcessingResult};
|
||||
|
||||
// PacketProcessor contains all data required to correctly unwrap and forward sphinx packets
|
||||
#[derive(Clone)]
|
||||
pub struct PacketProcessor {
|
||||
pub(crate) struct PacketProcessor {
|
||||
/// Responsible for performing unwrapping
|
||||
inner_processor: SphinxPacketProcessor,
|
||||
|
||||
@@ -29,11 +29,18 @@ impl PacketProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn process_received(
|
||||
&self,
|
||||
received: FramedNymPacket,
|
||||
) -> Result<MixProcessingResult, MixProcessingError> {
|
||||
self.node_stats_update_sender.report_received();
|
||||
self.inner_processor.process_received(received)
|
||||
pub fn inner(&self) -> &SphinxPacketProcessor {
|
||||
&self.inner_processor
|
||||
}
|
||||
|
||||
pub fn node_stats_update_sender(&self) -> &node_statistics::UpdateSender {
|
||||
&self.node_stats_update_sender
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_received_packet(
|
||||
packet: FramedNymPacket,
|
||||
inner_processor: &SphinxPacketProcessor,
|
||||
) -> Result<MixProcessingResult, MixProcessingError> {
|
||||
Ok(process_framed_packet(packet, inner_processor.sphinx_key())?)
|
||||
}
|
||||
|
||||
@@ -131,6 +131,7 @@ nym-http-api-common = { path = "../common/http-api-common", features = ["utoipa"
|
||||
|
||||
[features]
|
||||
no-reward = []
|
||||
v2-performance = []
|
||||
generate-ts = ["ts-rs"]
|
||||
axum = ["dep:axum",
|
||||
"axum-extra",
|
||||
|
||||
@@ -395,18 +395,33 @@ impl StorageManager {
|
||||
start: i64,
|
||||
end: i64,
|
||||
) -> Result<Option<f32>, sqlx::Error> {
|
||||
let result = sqlx::query!(
|
||||
r#"
|
||||
SELECT AVG(reliability) as "reliability: f32" FROM mixnode_status
|
||||
WHERE mixnode_details_id= ? AND timestamp >= ? AND timestamp <= ?
|
||||
"#,
|
||||
id,
|
||||
start,
|
||||
end
|
||||
)
|
||||
.fetch_one(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(result.reliability)
|
||||
if cfg!(feature = "v2-performance") {
|
||||
let result = sqlx::query!(
|
||||
r#"
|
||||
SELECT AVG(reliability) as "reliability: f32" FROM mixnode_status_v2
|
||||
WHERE mixnode_details_id= ? AND timestamp >= ? AND timestamp <= ?
|
||||
"#,
|
||||
id,
|
||||
start,
|
||||
end
|
||||
)
|
||||
.fetch_one(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(result.reliability)
|
||||
} else {
|
||||
let result = sqlx::query!(
|
||||
r#"
|
||||
SELECT AVG(reliability) as "reliability: f32" FROM mixnode_status
|
||||
WHERE mixnode_details_id= ? AND timestamp >= ? AND timestamp <= ?
|
||||
"#,
|
||||
id,
|
||||
start,
|
||||
end
|
||||
)
|
||||
.fetch_one(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(result.reliability)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn get_gateway_average_reliability_in_interval(
|
||||
|
||||
@@ -29,7 +29,7 @@ rand = { workspace = true }
|
||||
fastrand = { workspace = true }
|
||||
|
||||
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] }
|
||||
nym-http-api-common = { path = "../../common/http-api-common" }
|
||||
nym-http-api-common = { path = "../../common/http-api-common", features = ["utoipa"] }
|
||||
nym-node-requests = { path = "../nym-node-requests", default-features = false, features = [
|
||||
"openapi",
|
||||
] }
|
||||
|
||||
@@ -38,6 +38,7 @@ pub(crate) struct DisplayDetails {
|
||||
|
||||
pub(crate) exit_network_requester_address: String,
|
||||
pub(crate) exit_ip_packet_router_address: String,
|
||||
pub(crate) exit_authenticator_address: String,
|
||||
}
|
||||
|
||||
impl Display for DisplayDetails {
|
||||
@@ -64,6 +65,11 @@ impl Display for DisplayDetails {
|
||||
"exit ip packet router address: {}",
|
||||
self.exit_ip_packet_router_address
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
"exit authenticator address: {}",
|
||||
self.exit_authenticator_address
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -522,6 +522,7 @@ impl NymNode {
|
||||
x25519_wireguard_key: self.x25519_wireguard_key().to_base58_string(),
|
||||
exit_network_requester_address: self.exit_network_requester_address().to_string(),
|
||||
exit_ip_packet_router_address: self.exit_ip_packet_router_address().to_string(),
|
||||
exit_authenticator_address: self.exit_authenticator_address().to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ use url::Url;
|
||||
|
||||
pub mod build_info;
|
||||
pub mod init;
|
||||
pub mod process_block;
|
||||
pub mod process_until;
|
||||
pub mod run;
|
||||
pub mod upgrade_helpers;
|
||||
|
||||
@@ -42,6 +44,8 @@ impl Cli {
|
||||
match self.command {
|
||||
Commands::Init(args) => init::execute(args),
|
||||
Commands::Run(args) => run::execute(args).await,
|
||||
Commands::ProcessBlock(args) => process_block::execute(args).await,
|
||||
Commands::ProcessUntil(args) => process_until::execute(args).await,
|
||||
Commands::BuildInfo(args) => build_info::execute(args),
|
||||
}
|
||||
}
|
||||
@@ -97,6 +101,12 @@ pub(crate) enum Commands {
|
||||
/// Run the validator rewarder with the preconfigured settings.
|
||||
Run(run::Args),
|
||||
|
||||
/// Attempt to process a single block.
|
||||
ProcessBlock(process_block::Args),
|
||||
|
||||
/// Attempt to process multiple blocks until the provided height.
|
||||
ProcessUntil(process_until::Args),
|
||||
|
||||
/// Show build information of this binary
|
||||
BuildInfo(build_info::Args),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::cli::{try_load_current_config, ConfigOverridableArgs};
|
||||
use crate::error::NymRewarderError;
|
||||
use nyxd_scraper::NyxdScraper;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, clap::Args)]
|
||||
pub struct Args {
|
||||
#[command(flatten)]
|
||||
config_override: ConfigOverridableArgs,
|
||||
|
||||
/// Height of the block we want to process
|
||||
#[clap(long)]
|
||||
height: u32,
|
||||
|
||||
/// Specifies custom location for the configuration file of nym validators rewarder.
|
||||
#[clap(long)]
|
||||
custom_config_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: Args) -> Result<(), NymRewarderError> {
|
||||
let config =
|
||||
try_load_current_config(&args.custom_config_path)?.with_override(args.config_override);
|
||||
|
||||
NyxdScraper::new(config.scraper_config())
|
||||
.await?
|
||||
.process_single_block(args.height)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::cli::{try_load_current_config, ConfigOverridableArgs};
|
||||
use crate::error::NymRewarderError;
|
||||
use nyxd_scraper::NyxdScraper;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, clap::Args)]
|
||||
pub struct Args {
|
||||
#[command(flatten)]
|
||||
config_override: ConfigOverridableArgs,
|
||||
|
||||
/// Optional starting height for processing the blocks.
|
||||
/// If none is provided, the default behaviour will be applied.
|
||||
#[clap(long)]
|
||||
start_height: Option<u32>,
|
||||
|
||||
/// Height of until we want to be processing the blocks.
|
||||
/// If none is provided, the currrent block height will be used
|
||||
#[clap(long)]
|
||||
stop_height: Option<u32>,
|
||||
|
||||
/// Specifies custom location for the configuration file of nym validators rewarder.
|
||||
#[clap(long)]
|
||||
custom_config_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: Args) -> Result<(), NymRewarderError> {
|
||||
if let (Some(start), Some(end)) = (args.start_height, args.stop_height) {
|
||||
if start > end {
|
||||
eprintln!("the start height can't be larger than the stop height!");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
let config =
|
||||
try_load_current_config(&args.custom_config_path)?.with_override(args.config_override);
|
||||
|
||||
NyxdScraper::new(config.scraper_config())
|
||||
.await?
|
||||
.process_block_range(args.start_height, args.stop_height)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::config::RewardingRatios;
|
||||
use crate::rewarder::Epoch;
|
||||
use nym_compact_ecash::error::CompactEcashError;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_validator_client::nym_api::error::NymAPIError;
|
||||
@@ -178,6 +179,9 @@ pub enum NymRewarderError {
|
||||
|
||||
#[error("pruning.keep_recent must not be smaller than {min_to_keep}. got: {keep_recent}")]
|
||||
TooSmallKeepRecent { min_to_keep: u32, keep_recent: u32 },
|
||||
|
||||
#[error("there were no blocks processed within the epoch {epoch}")]
|
||||
NoBlocksProcessedInEpoch { epoch: Epoch },
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -14,7 +14,7 @@ use nym_network_defaults::setup_env;
|
||||
pub mod cli;
|
||||
pub mod config;
|
||||
pub mod error;
|
||||
mod rewarder;
|
||||
pub mod rewarder;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
|
||||
@@ -49,7 +49,12 @@ impl EpochSigning {
|
||||
) -> Result<Vec<staking::Validator>, NymRewarderError> {
|
||||
// first attempt to get it via the historical info.
|
||||
// if that fails, attempt to use current block information to at least get **something**
|
||||
if let Some(validators) = self.nyxd_client.historical_info(height).await?.hist {
|
||||
if let Ok(Some(validators)) = self
|
||||
.nyxd_client
|
||||
.historical_info(height)
|
||||
.await
|
||||
.map(|v| v.hist)
|
||||
{
|
||||
Ok(validators.valset)
|
||||
} else {
|
||||
let mut page_request = None;
|
||||
@@ -63,6 +68,10 @@ impl EpochSigning {
|
||||
break;
|
||||
};
|
||||
|
||||
if pagination.next_key.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
page_request = Some(PageRequest {
|
||||
key: pagination.next_key,
|
||||
offset: 0,
|
||||
@@ -92,18 +101,28 @@ impl EpochSigning {
|
||||
|
||||
let epoch_start = current_epoch.start_time;
|
||||
let epoch_end = current_epoch.end_time;
|
||||
let first_block = self
|
||||
|
||||
let Some(first_block) = self
|
||||
.nyxd_scraper
|
||||
.storage
|
||||
.get_first_block_height_after(epoch_start)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
let last_block = self
|
||||
else {
|
||||
return Err(NymRewarderError::NoBlocksProcessedInEpoch {
|
||||
epoch: current_epoch,
|
||||
});
|
||||
};
|
||||
|
||||
let Some(last_block) = self
|
||||
.nyxd_scraper
|
||||
.storage
|
||||
.get_last_block_height_before(epoch_end)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
else {
|
||||
return Err(NymRewarderError::NoBlocksProcessedInEpoch {
|
||||
epoch: current_epoch,
|
||||
});
|
||||
};
|
||||
|
||||
// each validator MUST be online at some point during the first 20 blocks, otherwise they're not getting anything.
|
||||
let vp_range_end = min(first_block + 20, last_block);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use crate::error::NymRewarderError;
|
||||
use sqlx::FromRow;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::ops::Add;
|
||||
use std::time::Duration;
|
||||
use time::format_description::well_known::Rfc3339;
|
||||
@@ -34,6 +35,11 @@ impl Epoch {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn has_finished(&self) -> bool {
|
||||
let now = OffsetDateTime::now_utc();
|
||||
self.end_time < now
|
||||
}
|
||||
|
||||
pub fn until_end(&self) -> Duration {
|
||||
let now = OffsetDateTime::now_utc();
|
||||
(self.end_time - now).try_into().unwrap_or_default()
|
||||
@@ -60,3 +66,15 @@ impl Epoch {
|
||||
self.end_time.format(&Rfc3339).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Epoch {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}: {} - {}",
|
||||
self.id,
|
||||
self.start_rfc3339(),
|
||||
self.end_rfc3339()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ use crate::rewarder::block_signing::types::EpochSigningResults;
|
||||
use crate::rewarder::block_signing::EpochSigning;
|
||||
use crate::rewarder::credential_issuance::types::CredentialIssuanceResults;
|
||||
use crate::rewarder::credential_issuance::CredentialIssuance;
|
||||
use crate::rewarder::epoch::Epoch;
|
||||
use crate::rewarder::nyxd_client::NyxdClient;
|
||||
use crate::rewarder::storage::RewarderStorage;
|
||||
use futures::future::{FusedFuture, OptionFuture};
|
||||
@@ -28,6 +27,8 @@ mod nyxd_client;
|
||||
mod storage;
|
||||
mod tasks;
|
||||
|
||||
pub(crate) use crate::rewarder::epoch::Epoch;
|
||||
|
||||
pub struct RewardingResult {
|
||||
pub total_spent: Coin,
|
||||
pub rewarding_tx: Hash,
|
||||
@@ -47,22 +48,30 @@ impl EpochRewards {
|
||||
pub fn amounts(&self) -> Result<Vec<(AccountId, Vec<Coin>)>, NymRewarderError> {
|
||||
let mut amounts = Vec::new();
|
||||
|
||||
if let Ok(Some(signing)) = &self.signing {
|
||||
for (account, signing_amount) in signing.rewarding_amounts(&self.signing_budget) {
|
||||
if signing_amount[0].amount != 0 {
|
||||
amounts.push((account, signing_amount))
|
||||
match &self.signing {
|
||||
Ok(Some(signing)) => {
|
||||
for (account, signing_amount) in signing.rewarding_amounts(&self.signing_budget) {
|
||||
if signing_amount[0].amount != 0 {
|
||||
amounts.push((account, signing_amount))
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => error!("failed to determine rewards for block signing: {err}"),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
if let Ok(Some(credentials)) = &self.credentials {
|
||||
for (account, credential_amount) in
|
||||
credentials.rewarding_amounts(&self.credentials_budget)
|
||||
{
|
||||
if credential_amount[0].amount != 0 {
|
||||
amounts.push((account, credential_amount))
|
||||
match &self.credentials {
|
||||
Ok(Some(credentials)) => {
|
||||
for (account, credential_amount) in
|
||||
credentials.rewarding_amounts(&self.credentials_budget)
|
||||
{
|
||||
if credential_amount[0].amount != 0 {
|
||||
amounts.push((account, credential_amount))
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => error!("failed to determine rewards for credential issuance: {err}"),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
Ok(amounts)
|
||||
@@ -279,6 +288,58 @@ impl Rewarder {
|
||||
self.current_epoch = self.current_epoch.next();
|
||||
}
|
||||
|
||||
async fn ensure_has_epoch_blocks(&self) -> Result<(), NymRewarderError> {
|
||||
// make sure we at least have a single block processed within the epoch
|
||||
let epoch_start = self.current_epoch.start_time;
|
||||
let epoch_end = self.current_epoch.end_time;
|
||||
|
||||
if let Some(epoch_signing) = &self.epoch_signing {
|
||||
if epoch_signing
|
||||
.nyxd_scraper
|
||||
.storage
|
||||
.get_first_block_height_after(epoch_start)
|
||||
.await?
|
||||
.is_none()
|
||||
{
|
||||
return Err(NymRewarderError::NoBlocksProcessedInEpoch {
|
||||
epoch: self.current_epoch,
|
||||
});
|
||||
}
|
||||
|
||||
if epoch_signing
|
||||
.nyxd_scraper
|
||||
.storage
|
||||
.get_last_block_height_before(epoch_end)
|
||||
.await?
|
||||
.is_none()
|
||||
{
|
||||
return Err(NymRewarderError::NoBlocksProcessedInEpoch {
|
||||
epoch: self.current_epoch,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn startup_resync(&mut self) -> Result<(), NymRewarderError> {
|
||||
// no sync required
|
||||
if !self.current_epoch.has_finished() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
info!("attempting to distribute missed rewards");
|
||||
while self.current_epoch.has_finished() {
|
||||
info!("processing epoch {}", self.current_epoch);
|
||||
self.ensure_has_epoch_blocks().await?;
|
||||
|
||||
// we need to perform rewarding from the 'current' epoch until the actual current epoch
|
||||
self.handle_epoch_end().await
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn run(mut self) -> Result<(), NymRewarderError> {
|
||||
info!("Starting nym validators rewarder");
|
||||
|
||||
@@ -306,6 +367,20 @@ impl Rewarder {
|
||||
|
||||
let until_end = self.current_epoch.until_end();
|
||||
|
||||
if let Err(err) = self.startup_resync().await {
|
||||
error!("failed to perform startup sync: {err}");
|
||||
error!("if the failure was due to insufficient number of blocks, your course of action is as follows:");
|
||||
error!("(ideally it would have been automatically resolved in this very method, but that'd require some serious refactoring)");
|
||||
error!(
|
||||
"1. determine height of the first block of the epoch (doesn't have to be exact)"
|
||||
);
|
||||
error!("2. run the following subcommand of the rewarder: `nym-validator-rewarder process-until --start-height=$STARTING_BLOCK");
|
||||
error!("3. !!IMPORTANT!! go to config.toml and temporarily disable block pruning, i.e. `pruning.strategy=nothing`");
|
||||
error!("4. restart nym-validator-rewarder as normal until it sends missing rewards");
|
||||
error!("5. re-enable pruning and restart the nym-validator rewarder");
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
info!(
|
||||
"the initial epoch (id: {}) will finish in {} secs",
|
||||
self.current_epoch.id,
|
||||
|
||||
Generated
+10
-8
@@ -173,9 +173,9 @@ checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.82"
|
||||
version = "0.1.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1"
|
||||
checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1637,9 +1637,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.33"
|
||||
version = "1.0.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253"
|
||||
checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide 0.8.0",
|
||||
@@ -3361,6 +3361,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bs58",
|
||||
"hex",
|
||||
"serde",
|
||||
"time",
|
||||
]
|
||||
@@ -3449,6 +3450,7 @@ dependencies = [
|
||||
"nym-mixnet-contract-common",
|
||||
"nym-multisig-contract-common",
|
||||
"nym-network-defaults",
|
||||
"nym-serde-helpers",
|
||||
"nym-vesting-contract-common",
|
||||
"prost",
|
||||
"reqwest 0.12.4",
|
||||
@@ -5760,18 +5762,18 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.63"
|
||||
version = "1.0.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
|
||||
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.63"
|
||||
version = "1.0.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
|
||||
checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -21,7 +21,7 @@ tokio = { version = "1", features = ["full"] }
|
||||
lazy_static = "1.4.0"
|
||||
# error handling
|
||||
anyhow = "1.0.79"
|
||||
thiserror = "1.0.56"
|
||||
thiserror = "1.0.64"
|
||||
|
||||
[build-dependencies]
|
||||
uniffi = { version = "0.25.2", features = ["build"] }
|
||||
|
||||
@@ -79,6 +79,15 @@ pub enum AuthenticatorError {
|
||||
|
||||
#[error("peers can't be interacted with anymore")]
|
||||
PeerInteractionStopped,
|
||||
|
||||
#[error("operation is not supported")]
|
||||
UnsupportedOperation,
|
||||
|
||||
#[error("operation unavailable for older client")]
|
||||
OldClient,
|
||||
|
||||
#[error("storage should have the requested bandwidht entry")]
|
||||
MissingClientBandwidthEntry,
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, AuthenticatorError>;
|
||||
|
||||
@@ -9,25 +9,24 @@ use std::{
|
||||
use crate::{error::AuthenticatorError, peer_manager::PeerManager};
|
||||
use futures::StreamExt;
|
||||
use log::warn;
|
||||
use nym_authenticator_requests::v2::{
|
||||
self,
|
||||
registration::{
|
||||
FinalMessage, GatewayClient, InitMessage, PendingRegistrations, PrivateIPs,
|
||||
RegistrationData, RegistredData,
|
||||
},
|
||||
};
|
||||
use nym_authenticator_requests::{
|
||||
v1,
|
||||
v2::{
|
||||
v1, v2,
|
||||
v3::{
|
||||
self,
|
||||
registration::{
|
||||
FinalMessage, GatewayClient, InitMessage, PendingRegistrations, PrivateIPs,
|
||||
RegistrationData, RegistredData, RemainingBandwidthData,
|
||||
},
|
||||
request::{AuthenticatorRequest, AuthenticatorRequestData},
|
||||
response::AuthenticatorResponse,
|
||||
},
|
||||
CURRENT_VERSION,
|
||||
};
|
||||
use nym_credential_verification::{
|
||||
bandwidth_storage_manager::BandwidthStorageManager, ecash::EcashManager,
|
||||
BandwidthFlushingBehaviourConfig, ClientBandwidth, CredentialVerifier,
|
||||
};
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_credentials_interface::{CredentialSpendingData, TicketType};
|
||||
use nym_crypto::asymmetric::x25519::KeyPair;
|
||||
use nym_gateway_requests::models::CredentialSpendingRequest;
|
||||
use nym_gateway_storage::Storage;
|
||||
@@ -289,10 +288,6 @@ impl<S: Storage + Clone + 'static> MixnetListener<S> {
|
||||
credential: CredentialSpendingData,
|
||||
client_id: i64,
|
||||
) -> Result<i64> {
|
||||
ecash_verifier
|
||||
.storage()
|
||||
.create_bandwidth_entry(client_id)
|
||||
.await?;
|
||||
let bandwidth = ecash_verifier
|
||||
.storage()
|
||||
.get_available_bandwidth(client_id)
|
||||
@@ -329,6 +324,68 @@ impl<S: Storage + Clone + 'static> MixnetListener<S> {
|
||||
))
|
||||
}
|
||||
|
||||
async fn on_topup_bandwidth_request(
|
||||
&mut self,
|
||||
peer_public_key: PeerPublicKey,
|
||||
credential: CredentialSpendingData,
|
||||
request_id: u64,
|
||||
reply_to: Recipient,
|
||||
) -> AuthenticatorHandleResult {
|
||||
let Some(ecash_verifier) = self.ecash_verifier.clone() else {
|
||||
return Err(AuthenticatorError::UnsupportedOperation);
|
||||
};
|
||||
let client_id = ecash_verifier
|
||||
.storage()
|
||||
.get_wireguard_peer(&peer_public_key.to_string())
|
||||
.await?
|
||||
.ok_or(AuthenticatorError::MissingClientBandwidthEntry)?
|
||||
.client_id
|
||||
.ok_or(AuthenticatorError::OldClient)?;
|
||||
let bandwidth = ecash_verifier
|
||||
.storage()
|
||||
.get_available_bandwidth(client_id)
|
||||
.await?
|
||||
.ok_or(AuthenticatorError::InternalError(
|
||||
"bandwidth entry should have just been created".to_string(),
|
||||
))?;
|
||||
|
||||
let t_type = credential.payment.t_type;
|
||||
let client_bandwidth = ClientBandwidth::new(bandwidth.into());
|
||||
let mut verifier = CredentialVerifier::new(
|
||||
CredentialSpendingRequest::new(credential),
|
||||
ecash_verifier.clone(),
|
||||
BandwidthStorageManager::new(
|
||||
ecash_verifier.storage().clone(),
|
||||
client_bandwidth,
|
||||
client_id,
|
||||
BandwidthFlushingBehaviourConfig::default(),
|
||||
true,
|
||||
),
|
||||
);
|
||||
verifier.verify().await?;
|
||||
|
||||
let amount = TicketType::try_from_encoded(t_type)
|
||||
.map_err(|e| {
|
||||
AuthenticatorError::CredentialVerificationError(
|
||||
nym_credential_verification::Error::UnknownTicketType(e),
|
||||
)
|
||||
})?
|
||||
.to_repr()
|
||||
.bandwidth_value() as i64;
|
||||
let available_bandwidth = ecash_verifier
|
||||
.storage()
|
||||
.increase_bandwidth(client_id, amount)
|
||||
.await?;
|
||||
|
||||
Ok(AuthenticatorResponse::new_topup_bandwidth(
|
||||
RemainingBandwidthData {
|
||||
available_bandwidth,
|
||||
},
|
||||
reply_to,
|
||||
request_id,
|
||||
))
|
||||
}
|
||||
|
||||
async fn on_reconstructed_message(
|
||||
&mut self,
|
||||
reconstructed: ReconstructedMessage,
|
||||
@@ -362,6 +419,15 @@ impl<S: Storage + Clone + 'static> MixnetListener<S> {
|
||||
)
|
||||
.await
|
||||
}
|
||||
AuthenticatorRequestData::TopUpBandwidth(topup_message) => {
|
||||
self.on_topup_bandwidth_request(
|
||||
topup_message.pub_key,
|
||||
topup_message.credential,
|
||||
request.request_id,
|
||||
request.reply_to,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -392,6 +458,7 @@ impl<S: Storage + Clone + 'static> MixnetListener<S> {
|
||||
}
|
||||
|
||||
pub(crate) async fn run(mut self) -> Result<()> {
|
||||
log::info!("Using authenticator version {}", CURRENT_VERSION);
|
||||
let mut task_client = self.task_handle.fork("main_loop");
|
||||
|
||||
while !task_client.is_shutdown() {
|
||||
@@ -440,6 +507,7 @@ fn deserialize_request(reconstructed: &ReconstructedMessage) -> Result<Authentic
|
||||
match request_version {
|
||||
[1, _] => v1::request::AuthenticatorRequest::from_reconstructed_message(reconstructed)
|
||||
.map_err(|err| AuthenticatorError::FailedToDeserializeTaggedPacket { source: err })
|
||||
.map(Into::<v2::request::AuthenticatorRequest>::into)
|
||||
.map(Into::into),
|
||||
[2, request_type] => {
|
||||
if request_type == ServiceProviderType::Authenticator as u8 {
|
||||
@@ -452,6 +520,16 @@ fn deserialize_request(reconstructed: &ReconstructedMessage) -> Result<Authentic
|
||||
Err(AuthenticatorError::InvalidPacketType(request_type))
|
||||
}
|
||||
}
|
||||
[3, request_type] => {
|
||||
if request_type == ServiceProviderType::Authenticator as u8 {
|
||||
v3::request::AuthenticatorRequest::from_reconstructed_message(reconstructed)
|
||||
.map_err(|err| AuthenticatorError::FailedToDeserializeTaggedPacket {
|
||||
source: err,
|
||||
})
|
||||
} else {
|
||||
Err(AuthenticatorError::InvalidPacketType(request_type))
|
||||
}
|
||||
}
|
||||
[version, _] => {
|
||||
log::info!("Received packet with invalid version: v{version}");
|
||||
Err(AuthenticatorError::InvalidPacketVersion(version))
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use crate::error::*;
|
||||
use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask};
|
||||
use futures::channel::oneshot;
|
||||
use nym_authenticator_requests::v2::registration::{GatewayClient, RemainingBandwidthData};
|
||||
use nym_authenticator_requests::latest::registration::{GatewayClient, RemainingBandwidthData};
|
||||
use nym_wireguard::{
|
||||
peer_controller::{
|
||||
AddPeerControlResponse, PeerControlRequest, QueryBandwidthControlResponse,
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
build-importer-contract:
|
||||
$(MAKE) -C importer-contract build
|
||||
@@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "importer-cli"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
readme.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
bip39 = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
dirs = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "net", "signal"] }
|
||||
tracing = { workspace = true }
|
||||
|
||||
importer-contract = { path = "../importer-contract" }
|
||||
nym-validator-client = { path = "../../../../common/client-libs/validator-client" }
|
||||
nym-bin-common = { path = "../../../../common/bin-common", features = ["basic_tracing"] }
|
||||
nym-network-defaults = { path = "../../../../common/network-defaults" }
|
||||
@@ -0,0 +1,477 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use anyhow::bail;
|
||||
use clap::ArgGroup;
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
use importer_contract::contract::EmptyMessage;
|
||||
use importer_contract::{base85rs, ExecuteMsg};
|
||||
use nym_bin_common::bin_info;
|
||||
use nym_bin_common::logging::setup_tracing_logger;
|
||||
use nym_network_defaults::{setup_env, NymNetworkDetails};
|
||||
use nym_validator_client::nyxd::cosmwasm_client::types::{InstantiateOptions, Model};
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::env::current_dir;
|
||||
use std::fs;
|
||||
use std::fs::{create_dir_all, File};
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::OnceLock;
|
||||
use tracing::{debug, info};
|
||||
|
||||
fn pretty_build_info_static() -> &'static str {
|
||||
static PRETTY_BUILD_INFORMATION: OnceLock<String> = OnceLock::new();
|
||||
PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print())
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(author = "Nymtech", version, long_version = pretty_build_info_static(), about)]
|
||||
pub struct Cli {
|
||||
/// Path pointing to an env file that configures the CLI.
|
||||
#[clap(short, long)]
|
||||
pub(crate) config_env_file: Option<PathBuf>,
|
||||
|
||||
#[clap(long)]
|
||||
pub(crate) mnemonic: bip39::Mnemonic,
|
||||
|
||||
#[clap(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CachedState {
|
||||
pub importer_address: AccountId,
|
||||
pub state_imported: bool,
|
||||
}
|
||||
|
||||
impl CachedState {
|
||||
pub fn save(&self) -> anyhow::Result<()> {
|
||||
let path = cached_state_file();
|
||||
if let Some(parent) = path.parent() {
|
||||
create_dir_all(parent)?;
|
||||
}
|
||||
let file = File::create(&path)?;
|
||||
serde_json::to_writer_pretty(file, self)?;
|
||||
|
||||
info!("saved cached details to {}", path.display());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load() -> anyhow::Result<Self> {
|
||||
let file = File::open(cached_state_file())?;
|
||||
Ok(serde_json::from_reader(&file)?)
|
||||
}
|
||||
}
|
||||
|
||||
fn cached_state_file() -> PathBuf {
|
||||
dirs::cache_dir()
|
||||
.unwrap()
|
||||
.join("contract-state-importer")
|
||||
.join(".state.json")
|
||||
}
|
||||
|
||||
// this only works if the cli is called from somewhere within the nym directory
|
||||
// (which realistically is going to be the case most of the time)
|
||||
fn importer_contract_path(explicit: Option<PathBuf>) -> anyhow::Result<PathBuf> {
|
||||
if let Some(explicit) = explicit {
|
||||
return Ok(explicit);
|
||||
}
|
||||
|
||||
for ancestor in current_dir()?.ancestors() {
|
||||
debug!("checking {:?}", fs::canonicalize(ancestor));
|
||||
for content in ancestor.read_dir()? {
|
||||
let dir_entry = content?;
|
||||
let Ok(name) = dir_entry.file_name().into_string() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if name == "target" {
|
||||
let maybe_contract_path = dir_entry
|
||||
.path()
|
||||
.join("wasm32-unknown-unknown")
|
||||
.join("release")
|
||||
.join("importer_contract.wasm");
|
||||
|
||||
if maybe_contract_path.exists() {
|
||||
return Ok(maybe_contract_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bail!("could not find importer_contract.wasm")
|
||||
}
|
||||
|
||||
fn importer_contract_address(explicit: Option<AccountId>) -> anyhow::Result<AccountId> {
|
||||
if let Some(explicit) = explicit {
|
||||
return Ok(explicit);
|
||||
}
|
||||
|
||||
let state = CachedState::load()?;
|
||||
Ok(state.importer_address)
|
||||
}
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
pub struct PrepareArgs {
|
||||
/// Path to the .wasm file with the importer contract
|
||||
/// If not provided, the CLI will attempt to traverse the parent directories until it finds
|
||||
/// "target/wasm32-unknown-unknown/release/importer_contract.wasm"
|
||||
#[clap(long)]
|
||||
pub importer_contract_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
pub struct SetStateArgs {
|
||||
/// Explicit address of the initialised importer contract.
|
||||
/// If not set, the value from the cached state will be attempted to be used
|
||||
#[clap(long)]
|
||||
pub importer_contract_address: Option<AccountId>,
|
||||
|
||||
/// Path to the file containing state dump of a cosmwasm contract
|
||||
#[clap(long)]
|
||||
pub raw_state: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
#[clap(group(ArgGroup::new("contract").required(true)))]
|
||||
pub struct SwapContractArgs {
|
||||
/// Explicit address of the initialised importer contract.
|
||||
/// If not set, the value from the cached state will be attempted to be used
|
||||
#[clap(long)]
|
||||
pub importer_contract_address: Option<AccountId>,
|
||||
|
||||
/// Code id of the previously uploaded cosmwasm smart contract that will be applied to the imported state
|
||||
#[clap(long, group = "contract")]
|
||||
pub target_contract_code_id: Option<u64>,
|
||||
|
||||
/// Path to a cosmwasm smart contract that will be uploaded and applied to the imported state
|
||||
#[clap(long, group = "contract")]
|
||||
pub target_contract_path: Option<PathBuf>,
|
||||
|
||||
/// The custom migrate message used for migrating into target contract.
|
||||
/// If none is provided an empty object will be used instead, i.e. '{}'
|
||||
#[clap(long)]
|
||||
pub migrate_msg: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
#[clap(group(ArgGroup::new("contract").required(true)))]
|
||||
pub struct InitialiseWithStateArgs {
|
||||
/// Path to the .wasm file with the importer contract
|
||||
/// If not provided, the CLI will attempt to traverse the parent directories until it finds
|
||||
/// "target/wasm32-unknown-unknown/release/importer_contract.wasm"
|
||||
#[clap(long)]
|
||||
pub importer_contract_path: Option<PathBuf>,
|
||||
|
||||
/// Path to the file containing state dump of a cosmwasm contract
|
||||
#[clap(long)]
|
||||
pub raw_state: PathBuf,
|
||||
|
||||
/// Code id of the previously uploaded cosmwasm smart contract that will be applied to the imported state
|
||||
#[clap(long, group = "contract")]
|
||||
pub target_contract_code_id: Option<u64>,
|
||||
|
||||
/// Path to a cosmwasm smart contract that will be uploaded and applied to the imported state
|
||||
#[clap(long, group = "contract")]
|
||||
pub target_contract_path: Option<PathBuf>,
|
||||
|
||||
#[clap(long)]
|
||||
pub migrate_msg: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub(crate) enum Commands {
|
||||
/// Upload and instantiates the importer contract
|
||||
PrepareContract(PrepareArgs),
|
||||
|
||||
/// Set the state of the previously instantiated importer contract with the provided state dump
|
||||
SetState(SetStateArgs),
|
||||
|
||||
/// Swap the importer contract code with the one corresponding to the previously uploaded state dump
|
||||
SwapContract(SwapContractArgs),
|
||||
|
||||
/// Combines the functionalities of `prepare-contract`, `set-state` and `swap-contract`
|
||||
InitialiseWithState(InitialiseWithStateArgs),
|
||||
}
|
||||
|
||||
async fn create_importer_contract(
|
||||
explicit_contract_path: Option<PathBuf>,
|
||||
client: &DirectSigningHttpRpcNyxdClient,
|
||||
) -> anyhow::Result<AccountId> {
|
||||
info!("attempting to create the importer contract");
|
||||
|
||||
let importer_path = importer_contract_path(explicit_contract_path)?;
|
||||
info!(
|
||||
"going to use the following importer contract: '{}'",
|
||||
fs::canonicalize(&importer_path)?.display()
|
||||
);
|
||||
|
||||
let mut data = Vec::new();
|
||||
File::open(importer_path)?.read_to_end(&mut data)?;
|
||||
|
||||
let res = client.upload(data, "<empty>", None).await?;
|
||||
let importer_code_id = res.code_id;
|
||||
info!(
|
||||
" ✅ uploaded the importer contract in {}",
|
||||
res.transaction_hash
|
||||
);
|
||||
|
||||
let res = client
|
||||
.instantiate(
|
||||
importer_code_id,
|
||||
&EmptyMessage {},
|
||||
"importer-contract".into(),
|
||||
"<empty>",
|
||||
Some(InstantiateOptions::default().with_admin(client.address())),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
let importer_address = res.contract_address;
|
||||
info!(
|
||||
" ✅ instantiated the importer contract in {}",
|
||||
res.transaction_hash
|
||||
);
|
||||
|
||||
info!("IMPORTER CONTRACT ADDRESS: {importer_address}");
|
||||
|
||||
CachedState {
|
||||
importer_address: importer_address.clone(),
|
||||
state_imported: false,
|
||||
}
|
||||
.save()?;
|
||||
|
||||
Ok(importer_address)
|
||||
}
|
||||
|
||||
async fn execute_prepare_contract(
|
||||
args: PrepareArgs,
|
||||
client: DirectSigningHttpRpcNyxdClient,
|
||||
) -> anyhow::Result<()> {
|
||||
create_importer_contract(args.importer_contract_path, &client).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn approximate_size(pair: &Model) -> usize {
|
||||
base85rs::encode(&pair.key).len() + base85rs::encode(&pair.value).len()
|
||||
}
|
||||
|
||||
fn models_to_exec(data: Vec<Model>) -> ExecuteMsg {
|
||||
let pairs = data
|
||||
.into_iter()
|
||||
.map(|kv| (kv.key, kv.value))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
pairs.into()
|
||||
}
|
||||
|
||||
fn split_into_importable_execute_msgs(
|
||||
kv_pairs: Vec<Model>,
|
||||
approximate_max_chunk: usize,
|
||||
) -> Vec<ExecuteMsg> {
|
||||
let mut chunks: Vec<ExecuteMsg> = Vec::new();
|
||||
|
||||
let mut current_wip_chunk = Vec::new();
|
||||
let mut current_chunk_size = 0;
|
||||
for kv in kv_pairs {
|
||||
if current_chunk_size + approximate_size(&kv) > approximate_max_chunk {
|
||||
let taken = std::mem::take(&mut current_wip_chunk);
|
||||
chunks.push(models_to_exec(taken));
|
||||
current_chunk_size = 0;
|
||||
}
|
||||
current_chunk_size += approximate_size(&kv);
|
||||
current_wip_chunk.push(kv);
|
||||
}
|
||||
|
||||
if !current_wip_chunk.is_empty() {
|
||||
chunks.push(models_to_exec(current_wip_chunk))
|
||||
}
|
||||
chunks
|
||||
}
|
||||
|
||||
async fn set_importer_state(
|
||||
state_dump_path: PathBuf,
|
||||
explicit_importer_address: Option<AccountId>,
|
||||
client: &DirectSigningHttpRpcNyxdClient,
|
||||
) -> anyhow::Result<()> {
|
||||
info!("attempting to set the importer contract state");
|
||||
|
||||
// this is the value that we found to be optimal during v1->v2 mixnet migration
|
||||
const MAX_CHUNK_SIZE: usize = 350 * 1000;
|
||||
|
||||
let importer_address = importer_contract_address(explicit_importer_address)?;
|
||||
|
||||
if let Ok(state) = CachedState::load() {
|
||||
if state.state_imported && state.importer_address == importer_address {
|
||||
bail!("the state has already been imported for {importer_address}")
|
||||
}
|
||||
}
|
||||
|
||||
let dump_file = File::open(state_dump_path)?;
|
||||
info!("attempting to decode the state dump. for bigger contracts this might take a while...");
|
||||
let kv_pairs: Vec<Model> = serde_json::from_reader(&dump_file)?;
|
||||
|
||||
info!("there are {} key-value pairs to import", kv_pairs.len());
|
||||
info!("attempting to split them into {MAX_CHUNK_SIZE}B chunks ExecuteMsgs...");
|
||||
|
||||
let chunks = split_into_importable_execute_msgs(kv_pairs, MAX_CHUNK_SIZE);
|
||||
info!("obtained {} execute msgs", chunks.len());
|
||||
|
||||
let total = chunks.len();
|
||||
for (i, msg) in chunks.into_iter().enumerate() {
|
||||
info!("executing message {}/{total}...", i + 1);
|
||||
let res = client
|
||||
.execute(
|
||||
&importer_address,
|
||||
&msg,
|
||||
None,
|
||||
"importing contract state",
|
||||
Vec::new(),
|
||||
)
|
||||
.await?;
|
||||
info!(" ✅ OK: {}", res.transaction_hash);
|
||||
}
|
||||
|
||||
info!("Finished migrating storage to {importer_address}!");
|
||||
|
||||
CachedState {
|
||||
importer_address,
|
||||
state_imported: true,
|
||||
}
|
||||
.save()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn execute_set_state(
|
||||
args: SetStateArgs,
|
||||
client: DirectSigningHttpRpcNyxdClient,
|
||||
) -> anyhow::Result<()> {
|
||||
set_importer_state(args.raw_state, args.importer_contract_address, &client).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn swap_contract(
|
||||
target_code_id: Option<u64>,
|
||||
target_contract_path: Option<PathBuf>,
|
||||
explicit_importer_address: Option<AccountId>,
|
||||
migrate_msg: Option<serde_json::Value>,
|
||||
client: &DirectSigningHttpRpcNyxdClient,
|
||||
) -> anyhow::Result<()> {
|
||||
info!("attempting to swap the contract code");
|
||||
|
||||
let importer_address = importer_contract_address(explicit_importer_address)?;
|
||||
|
||||
if let Ok(state) = CachedState::load() {
|
||||
if !state.state_imported && state.importer_address == importer_address {
|
||||
bail!("the state hasn't been imported for {importer_address}")
|
||||
}
|
||||
}
|
||||
|
||||
// one of those must have been set via clap
|
||||
let code_id = match target_code_id {
|
||||
Some(explicit) => explicit,
|
||||
None => {
|
||||
// upload the contract
|
||||
let mut data = Vec::new();
|
||||
File::open(target_contract_path.unwrap())?.read_to_end(&mut data)?;
|
||||
|
||||
let res = client.upload(data, "<empty>", None).await?;
|
||||
info!(
|
||||
" ✅ uploaded the target contract in {}",
|
||||
res.transaction_hash
|
||||
);
|
||||
res.code_id
|
||||
}
|
||||
};
|
||||
|
||||
let migrate_msg = migrate_msg.unwrap_or(serde_json::Value::Object(Default::default()));
|
||||
let res = client
|
||||
.migrate(
|
||||
&importer_address,
|
||||
code_id,
|
||||
&migrate_msg,
|
||||
"migrating into target contract",
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
info!(
|
||||
" ✅ migrated into the target contract: {}",
|
||||
res.transaction_hash
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn execute_swap_contract(
|
||||
args: SwapContractArgs,
|
||||
client: DirectSigningHttpRpcNyxdClient,
|
||||
) -> anyhow::Result<()> {
|
||||
swap_contract(
|
||||
args.target_contract_code_id,
|
||||
args.target_contract_path,
|
||||
args.importer_contract_address,
|
||||
args.migrate_msg,
|
||||
&client,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn initialise_with_state(
|
||||
args: InitialiseWithStateArgs,
|
||||
client: DirectSigningHttpRpcNyxdClient,
|
||||
) -> anyhow::Result<()> {
|
||||
let importer_address = create_importer_contract(args.importer_contract_path, &client).await?;
|
||||
set_importer_state(args.raw_state, Some(importer_address.clone()), &client).await?;
|
||||
swap_contract(
|
||||
args.target_contract_code_id,
|
||||
args.target_contract_path,
|
||||
Some(importer_address.clone()),
|
||||
args.migrate_msg,
|
||||
&client,
|
||||
)
|
||||
.await?;
|
||||
|
||||
info!("the contract is ready at {importer_address}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
pub async fn execute(self) -> anyhow::Result<()> {
|
||||
let network_details = NymNetworkDetails::new_from_env();
|
||||
let client_config = nyxd::Config::try_from_nym_network_details(&network_details)?;
|
||||
let nyxd_url = network_details
|
||||
.endpoints
|
||||
.first()
|
||||
.expect("network details are not defined")
|
||||
.nyxd_url
|
||||
.as_str();
|
||||
|
||||
let client = DirectSigningHttpRpcNyxdClient::connect_with_mnemonic(
|
||||
client_config,
|
||||
nyxd_url,
|
||||
self.mnemonic,
|
||||
)?;
|
||||
match self.command {
|
||||
Commands::PrepareContract(args) => execute_prepare_contract(args, client).await,
|
||||
Commands::SetState(args) => execute_set_state(args, client).await,
|
||||
Commands::SwapContract(args) => execute_swap_contract(args, client).await,
|
||||
Commands::InitialiseWithState(args) => initialise_with_state(args, client).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let cli = Cli::parse();
|
||||
setup_env(cli.config_env_file.as_ref());
|
||||
|
||||
setup_tracing_logger();
|
||||
cli.execute().await
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
[package]
|
||||
name = "importer-contract"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
readme.workspace = true
|
||||
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
base85rs = "0.1.3"
|
||||
cosmwasm-std = { workspace = true }
|
||||
cosmwasm-storage = { workspace = true }
|
||||
cosmwasm-schema = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["library"]
|
||||
library = []
|
||||
@@ -0,0 +1,5 @@
|
||||
all: build
|
||||
|
||||
build:
|
||||
RUSTFLAGS='-C link-arg=-s' cargo build --release --lib --target wasm32-unknown-unknown --no-default-features
|
||||
wasm-opt --signext-lowering -O ../../../../target/wasm32-unknown-unknown/release/importer_contract.wasm -o ../../../../target/wasm32-unknown-unknown/release/importer_contract.wasm
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::ExecuteMsg;
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::{Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, StdError};
|
||||
|
||||
#[cw_serde]
|
||||
pub struct EmptyMessage {}
|
||||
|
||||
#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)]
|
||||
pub fn instantiate(
|
||||
_: DepsMut<'_>,
|
||||
_: Env,
|
||||
_: MessageInfo,
|
||||
_: EmptyMessage,
|
||||
) -> Result<Response, StdError> {
|
||||
Ok(Response::new())
|
||||
}
|
||||
|
||||
#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)]
|
||||
pub fn execute(
|
||||
deps: DepsMut<'_>,
|
||||
_env: Env,
|
||||
_info: MessageInfo,
|
||||
msg: ExecuteMsg,
|
||||
) -> Result<Response, StdError> {
|
||||
for (key, value) in msg.pairs {
|
||||
let key = base85rs::decode(&key).unwrap();
|
||||
let value = base85rs::decode(&value).unwrap();
|
||||
deps.storage.set(&key, &value);
|
||||
}
|
||||
|
||||
Ok(Default::default())
|
||||
}
|
||||
|
||||
#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)]
|
||||
pub fn query(_: Deps<'_>, _: Env, _: EmptyMessage) -> Result<QueryResponse, StdError> {
|
||||
Ok(Default::default())
|
||||
}
|
||||
|
||||
#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)]
|
||||
pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: EmptyMessage) -> Result<Response, StdError> {
|
||||
Ok(Default::default())
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
pub mod contract;
|
||||
pub mod msg;
|
||||
|
||||
pub use base85rs;
|
||||
pub use msg::ExecuteMsg;
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cosmwasm_schema::cw_serde;
|
||||
|
||||
#[cw_serde]
|
||||
pub struct ExecuteMsg {
|
||||
pub pairs: Vec<(String, String)>,
|
||||
}
|
||||
|
||||
impl From<Vec<(Vec<u8>, Vec<u8>)>> for ExecuteMsg {
|
||||
fn from(raw: Vec<(Vec<u8>, Vec<u8>)>) -> Self {
|
||||
ExecuteMsg {
|
||||
pairs: raw
|
||||
.into_iter()
|
||||
.map(|(k, v)| (base85rs::encode(&k), base85rs::encode(&v)))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use nym_cli_commands::context::{create_signing_client, ClientArgs};
|
||||
use nym_cli_commands::context::{create_query_client, create_signing_client, ClientArgs};
|
||||
use nym_network_defaults::NymNetworkDetails;
|
||||
|
||||
pub(crate) mod generators;
|
||||
@@ -9,14 +9,14 @@ pub(crate) async fn execute(
|
||||
network_details: &NymNetworkDetails,
|
||||
) -> anyhow::Result<()> {
|
||||
match cosmwasm.command {
|
||||
Some(nym_cli_commands::validator::cosmwasm::CosmwasmCommands::Upload(args)) => {
|
||||
nym_cli_commands::validator::cosmwasm::CosmwasmCommands::Upload(args) => {
|
||||
nym_cli_commands::validator::cosmwasm::upload_contract::upload(
|
||||
args,
|
||||
create_signing_client(global_args, network_details)?,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Some(nym_cli_commands::validator::cosmwasm::CosmwasmCommands::Init(args)) => {
|
||||
nym_cli_commands::validator::cosmwasm::CosmwasmCommands::Init(args) => {
|
||||
nym_cli_commands::validator::cosmwasm::init_contract::init(
|
||||
args,
|
||||
create_signing_client(global_args, network_details)?,
|
||||
@@ -25,24 +25,30 @@ pub(crate) async fn execute(
|
||||
.await
|
||||
}
|
||||
|
||||
Some(nym_cli_commands::validator::cosmwasm::CosmwasmCommands::GenerateInitMessage(
|
||||
generator,
|
||||
)) => generators::execute(generator).await?,
|
||||
Some(nym_cli_commands::validator::cosmwasm::CosmwasmCommands::Migrate(args)) => {
|
||||
nym_cli_commands::validator::cosmwasm::CosmwasmCommands::GenerateInitMessage(generator) => {
|
||||
generators::execute(generator).await?
|
||||
}
|
||||
nym_cli_commands::validator::cosmwasm::CosmwasmCommands::Migrate(args) => {
|
||||
nym_cli_commands::validator::cosmwasm::migrate_contract::migrate(
|
||||
args,
|
||||
create_signing_client(global_args, network_details)?,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Some(nym_cli_commands::validator::cosmwasm::CosmwasmCommands::Execute(args)) => {
|
||||
nym_cli_commands::validator::cosmwasm::CosmwasmCommands::Execute(args) => {
|
||||
nym_cli_commands::validator::cosmwasm::execute_contract::execute(
|
||||
args,
|
||||
create_signing_client(global_args, network_details)?,
|
||||
)
|
||||
.await
|
||||
}
|
||||
_ => unreachable!(),
|
||||
nym_cli_commands::validator::cosmwasm::CosmwasmCommands::RawContractState(args) => {
|
||||
nym_cli_commands::validator::cosmwasm::raw_contract_state::execute(
|
||||
args,
|
||||
create_query_client(network_details)?,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user