Compare commits
269 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3eb862790c | |||
| 7bce29e9ac | |||
| 629d124838 | |||
| fe2d602cd8 | |||
| 8b2f80b03c | |||
| 336cd30dd8 | |||
| a8dc703399 | |||
| b9195ae40d | |||
| 825a138cf3 | |||
| ecf85049c6 | |||
| 0890407886 | |||
| c8562ecac1 | |||
| dd0067f542 | |||
| a18dab55a6 | |||
| 4c8ae077a2 | |||
| 79a7860185 | |||
| bb71da55e8 | |||
| ca18fb9f33 | |||
| ceec8217e0 | |||
| a52e81b66e | |||
| a44339433e | |||
| c9290cbcc0 | |||
| ce3e674528 | |||
| c9f5594ca5 | |||
| a7feeaa660 | |||
| e7bc50fc4a | |||
| e926a1e2c0 | |||
| bd9a628a98 | |||
| 19a9d5413d | |||
| 016ab58648 | |||
| 8ec7534b57 | |||
| 3c66ab9adc | |||
| a66f63e34d | |||
| c9814a1c6e | |||
| 59d31cfa2b | |||
| 6d9bc302ff | |||
| 75cc310fc8 | |||
| bd7eebf463 | |||
| c31561d46d | |||
| faffdf9b2f | |||
| 7081076842 | |||
| b0174dcd0b | |||
| 90de0a30a8 | |||
| 546e7c794f | |||
| a1f68170c9 | |||
| ae29e86db0 | |||
| 359f038dff | |||
| 0aa8084625 | |||
| 9b7815d45b | |||
| ad5a167fe5 | |||
| 16f7ac9998 | |||
| 0235932dda | |||
| 96fd084582 | |||
| 7344248f3b | |||
| 824dfa3d6d | |||
| 2548c8d42d | |||
| f4facc08ea | |||
| f20f96831a | |||
| a94196eb82 | |||
| 02884d183d | |||
| 75b02c739d | |||
| 3b39ec4b28 | |||
| 8a6b6ead95 | |||
| 6b6bbe535f | |||
| 85d9d65da3 | |||
| 9f580d7bc2 | |||
| 4dee8858da | |||
| 49797d46bb | |||
| 4060489bd1 | |||
| 205e44a857 | |||
| 6bf9dca722 | |||
| 48be25f9c7 | |||
| 58080ec681 | |||
| 45e8d3d78e | |||
| c7b8622cf4 | |||
| fbd58122f4 | |||
| 13f8449dc8 | |||
| db36f72200 | |||
| e09986e505 | |||
| bb3c015633 | |||
| b21346064e | |||
| fa81b96951 | |||
| 8cccc9ab24 | |||
| b567ac22d3 | |||
| b43a1b8c94 | |||
| d7da6ed1ab | |||
| 4d62dc9c74 | |||
| 2d39f3c722 | |||
| 3d122f45b4 | |||
| cb375f15c2 | |||
| 7406fdd677 | |||
| d7d4c9f09a | |||
| 94d83648c2 | |||
| aa51af7023 | |||
| f45ed78806 | |||
| c0337ec1d4 | |||
| 6fe049d1a2 | |||
| 63ed99d4d6 | |||
| 2061629d1d | |||
| 9b99a19ba0 | |||
| 6a3afb50b8 | |||
| bea64b926f | |||
| 3b83c30558 | |||
| ceeccbba07 | |||
| be55bb61cb | |||
| f2af35fc2e | |||
| b874fc9314 | |||
| 914c586e68 | |||
| 10ba3c2ab9 | |||
| 7e32787ab2 | |||
| 7062f69e45 | |||
| 5e98c14a98 | |||
| f04d1fea56 | |||
| 836e237116 | |||
| 0f9bd648a1 | |||
| 0c2c0bdc54 | |||
| 991cc3fa01 | |||
| 3510ee8df6 | |||
| 6774158e7a | |||
| f98698a121 | |||
| 8e99c17f49 | |||
| ab4cc9b282 | |||
| dbe6a5de7d | |||
| 1948fd8e67 | |||
| c8f38ae785 | |||
| f32ea17de5 | |||
| 4ac25aef4d | |||
| 3ad6a31e1f | |||
| 6cacc53e5a | |||
| 387933a975 | |||
| f6c24412c0 | |||
| 5c753c0794 | |||
| 67132161f4 | |||
| 643f54024b | |||
| 16aaf7b5df | |||
| 17c6b79735 | |||
| 8bd758ad0e | |||
| a51fc0cb9e | |||
| ae602ae771 | |||
| d6d36364b0 | |||
| accb42cad9 | |||
| dd43c5d2d2 | |||
| e42d46100a | |||
| ed8b1841dc | |||
| dd15a9454a | |||
| f4e42d74c4 | |||
| 9de1e6e844 | |||
| 713df39106 | |||
| 5ec4674f9b | |||
| 58c5092e80 | |||
| 677ad54a7f | |||
| 5e17c3199f | |||
| 3c3a34ec0f | |||
| e9b442e634 | |||
| ca02e2bce1 | |||
| 985ab43fe9 | |||
| f4dad37b14 | |||
| 08a57fa8df | |||
| 98e88e2f11 | |||
| 2a589b049c | |||
| 9bcd56e254 | |||
| 025ba2ec5f | |||
| 1a1d11c447 | |||
| 958bc2ae9a | |||
| d3d5cc3424 | |||
| a834bb17f8 | |||
| cee6d8c308 | |||
| 142eaf533b | |||
| 42365769f8 | |||
| 9fc822298f | |||
| cfb9f3d356 | |||
| ea386b6145 | |||
| 75221cfd3e | |||
| 50f71a21e0 | |||
| cfa9ecfcc4 | |||
| 549b33cd91 | |||
| be46da9906 | |||
| 66d123312f | |||
| a29f3db5fb | |||
| d0fa1792e2 | |||
| f452d97979 | |||
| 9b2d224e54 | |||
| 3f504d7500 | |||
| 67701290d3 | |||
| 22541f5a79 | |||
| bd8f666405 | |||
| a3c1541660 | |||
| 6c1d14a4bc | |||
| defd148d73 | |||
| 162ff71814 | |||
| 5c864cb055 | |||
| cfc13671a4 | |||
| 668a255e0d | |||
| 31d8352621 | |||
| 4f6fe88b4c | |||
| 397ef8723d | |||
| 2c2223947c | |||
| e1c0638f1e | |||
| 13fa2119fc | |||
| 8f24e8f208 | |||
| 37dd20ded1 | |||
| b4b32bb907 | |||
| c1718154cb | |||
| 3eb7710a12 | |||
| d1c9251904 | |||
| 62894e2b40 | |||
| bd7fd1a61c | |||
| 28f118c73b | |||
| 65699736ee | |||
| caba594c95 | |||
| 746d52d017 | |||
| e323c05b33 | |||
| abdf071448 | |||
| a6f2b0e8c8 | |||
| f78b4a1742 | |||
| d4fde7b788 | |||
| d5a2952ef9 | |||
| 206b6ba742 | |||
| 67449b1c19 | |||
| 8d6e5d4fff | |||
| f448355b35 | |||
| cf78af6b98 | |||
| d041cfe5c5 | |||
| 85b0b6d73d | |||
| 685019884f | |||
| cd9d4eebd3 | |||
| 78610c7e28 | |||
| 496870b5f6 | |||
| 7eac5e3529 | |||
| 4ad4072709 | |||
| bd3711892a | |||
| b5926def85 | |||
| 50cc8bd0bf | |||
| fb1b58b5fb | |||
| c3a9ceae52 | |||
| c92a7e3e35 | |||
| e152c9a99e | |||
| 78cce00adf | |||
| 55e00a9a38 | |||
| 6d1b26daeb | |||
| 2d9e34cc81 | |||
| 7232fd83d1 | |||
| 3816142479 | |||
| 112ecc2e4c | |||
| 2f0fbd5ebd | |||
| f45a803139 | |||
| 7e16932c4a | |||
| a0a44509af | |||
| 852ed78e5d | |||
| 0d52800569 | |||
| a1dd9e656d | |||
| 1ea78e8e97 | |||
| 46d68e5448 | |||
| 2c820ca0ea | |||
| 2c731cf048 | |||
| 9a1f28bd43 | |||
| b9493004aa | |||
| b27f806c40 | |||
| 08aaa8813e | |||
| 10ff165c18 | |||
| eec3cc4c47 | |||
| 3126053cbe | |||
| 54266fd5df | |||
| 7407872b71 | |||
| 8d9387d3ac | |||
| e8bc2c7a01 | |||
| 0486cd2e63 | |||
| 604098844a | |||
| 65e35bd2b0 |
@@ -4,18 +4,18 @@ on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'clients/**'
|
||||
- 'common/**'
|
||||
- 'explorer-api/**'
|
||||
- 'gateway/**'
|
||||
- 'integrations/**'
|
||||
- 'mixnode/**'
|
||||
- 'sdk/rust/nym-sdk/**'
|
||||
- 'service-providers/**'
|
||||
- 'nym-api/**'
|
||||
- 'nym-outfox/**'
|
||||
- 'tools/nym-cli/**'
|
||||
- 'tools/ts-rs-cli/**'
|
||||
- "clients/**"
|
||||
- "common/**"
|
||||
- "explorer-api/**"
|
||||
- "gateway/**"
|
||||
- "integrations/**"
|
||||
- "mixnode/**"
|
||||
- "sdk/rust/nym-sdk/**"
|
||||
- "service-providers/**"
|
||||
- "nym-api/**"
|
||||
- "nym-outfox/**"
|
||||
- "tools/nym-cli/**"
|
||||
- "tools/ts-rs-cli/**"
|
||||
|
||||
jobs:
|
||||
publish-nym:
|
||||
@@ -53,6 +53,16 @@ jobs:
|
||||
command: build
|
||||
args: --workspace --release
|
||||
|
||||
- name: Install cargo-deb
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: install
|
||||
args: cargo-deb
|
||||
|
||||
- name: Build deb packages
|
||||
shell: bash
|
||||
run: make deb
|
||||
|
||||
- name: Prepare build output
|
||||
shell: bash
|
||||
env:
|
||||
@@ -67,6 +77,7 @@ jobs:
|
||||
cp target/release/nym-network-statistics $OUTPUT_DIR
|
||||
cp target/release/nym-cli $OUTPUT_DIR
|
||||
cp target/release/explorer-api $OUTPUT_DIR
|
||||
cp target/debian/*.deb $OUTPUT_DIR
|
||||
|
||||
- name: Deploy branch to CI www
|
||||
continue-on-error: true
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
name: ci-cargo-deny
|
||||
on: [workflow_dispatch]
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
cargo-deny:
|
||||
runs-on: ubuntu-22.04
|
||||
@@ -7,10 +10,7 @@ jobs:
|
||||
matrix:
|
||||
checks:
|
||||
# - advisories
|
||||
- licenses
|
||||
- bans sources
|
||||
|
||||
continue-on-error: ${{ matrix.checks == 'licenses' }}
|
||||
- licenses bans sources
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
@@ -17,6 +17,9 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: install yarn in root
|
||||
run: cd ../.. && yarn install
|
||||
|
||||
- name: Install npm
|
||||
run: npm install
|
||||
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
name: ci-nym-vpn-ui-js
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'nym-vpn/ui/src/**'
|
||||
- 'nym-vpn/ui/package.json'
|
||||
- 'nym-vpn/ui/index.html'
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: custom-linux
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- name: Install Yarn
|
||||
run: npm install -g yarn
|
||||
- name: Install dependencies
|
||||
working-directory: nym-vpn/ui
|
||||
run: yarn
|
||||
- name: Type-check
|
||||
working-directory: nym-vpn/ui
|
||||
run: yarn typecheck
|
||||
- name: Check lint
|
||||
working-directory: nym-vpn/ui
|
||||
run: yarn lint
|
||||
- name: Check formatting
|
||||
working-directory: nym-vpn/ui
|
||||
run: yarn fmt:check
|
||||
# - name: Run tests
|
||||
# working-directory: nym-vpn/ui
|
||||
# run: yarn test
|
||||
- name: Check build
|
||||
working-directory: nym-vpn/ui
|
||||
run: yarn build
|
||||
@@ -1,63 +0,0 @@
|
||||
name: ci-nym-vpn-ui-rust
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'nym-vpn/ui/src-tauri/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: custom-linux
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGOTOML_PATH: ./nym-vpn/ui/src-tauri/Cargo.toml
|
||||
steps:
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools libayatana-appindicator3-dev
|
||||
continue-on-error: true
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Prepare build
|
||||
run: mkdir nym-vpn/ui/dist
|
||||
|
||||
- name: Build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --manifest-path ${{ env.CARGOTOML_PATH }} --features custom-protocol
|
||||
|
||||
# - name: Run all tests
|
||||
# uses: actions-rs/cargo@v1
|
||||
# with:
|
||||
# command: test
|
||||
# args: --manifest-path ${{ env.CARGOTOML_PATH }}
|
||||
|
||||
- name: Check formatting
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --manifest-path ${{ env.CARGOTOML_PATH }} --all -- --check
|
||||
|
||||
- name: Annotate with clippy checks
|
||||
uses: actions-rs/clippy-check@v1
|
||||
continue-on-error: true
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --manifest-path ${{ env.CARGOTOML_PATH }} --all-features
|
||||
|
||||
- name: Clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --manifest-path ${{ env.CARGOTOML_PATH }} --all-features --all-targets -- -D warnings
|
||||
Generated
+338
-251
File diff suppressed because it is too large
Load Diff
+10
-3
@@ -67,6 +67,7 @@ members = [
|
||||
"common/nymsphinx/params",
|
||||
"common/nymsphinx/routing",
|
||||
"common/nymsphinx/types",
|
||||
"common/nyxd-scraper",
|
||||
"common/pemstore",
|
||||
"common/socks5-client-core",
|
||||
"common/socks5/proxy-helpers",
|
||||
@@ -101,6 +102,7 @@ members = [
|
||||
"nym-node",
|
||||
"nym-node/nym-node-requests",
|
||||
"nym-outfox",
|
||||
"nym-validator-rewarder",
|
||||
"tools/internal/ssl-inject",
|
||||
"tools/internal/sdk-version-bump",
|
||||
"tools/nym-cli",
|
||||
@@ -123,9 +125,10 @@ default-members = [
|
||||
"nym-api",
|
||||
"tools/nymvisor",
|
||||
"explorer-api",
|
||||
"nym-validator-rewarder",
|
||||
]
|
||||
|
||||
exclude = ["explorer", "contracts", "nym-wallet", "nym-connect/mobile/src-tauri", "nym-connect/desktop", "nym-vpn/ui/src-tauri", "cpu-cycles"]
|
||||
exclude = ["explorer", "contracts", "nym-wallet", "nym-connect/mobile/src-tauri", "nym-connect/desktop", "nym-vpn/ui/src-tauri", "cpu-cycles", "sdk/ffi/cpp"]
|
||||
|
||||
[workspace.package]
|
||||
authors = ["Nym Technologies SA"]
|
||||
@@ -159,6 +162,7 @@ reqwest = "0.11.22"
|
||||
schemars = "0.8.1"
|
||||
serde = "1.0.152"
|
||||
serde_json = "1.0.91"
|
||||
sqlx = "0.6.3"
|
||||
tap = "1.0.1"
|
||||
time = "0.3.30"
|
||||
thiserror = "1.0.48"
|
||||
@@ -199,9 +203,12 @@ cw-controllers = { version = "=1.1.0" }
|
||||
|
||||
# cosmrs-related
|
||||
bip32 = "0.5.1"
|
||||
cosmrs = "=0.15.0"
|
||||
tendermint-rpc = "0.34" # same version as used by cosmrs
|
||||
|
||||
# temporarily using a fork again (yay.) because we need staking and slashing support
|
||||
cosmrs = { git = "https://github.com/jstuczyn/cosmos-rust", branch ="nym-temp/all-validator-features" }
|
||||
#cosmrs = { git = "https://github.com/jstuczyn/cosmos-rust", branch = "nym-temp/all-validator-features" } # unfortuntely we need a fork by yours truly to get the staking support
|
||||
tendermint = "0.34" # same version as used by cosmrs
|
||||
tendermint-rpc = "0.34" # same version as used by cosmrs
|
||||
prost = "0.12"
|
||||
|
||||
# wasm-related dependencies
|
||||
|
||||
@@ -12,6 +12,7 @@ help:
|
||||
@echo " clippy: run clippy for all workspaces"
|
||||
@echo " test: run clippy, unit tests, and formatting."
|
||||
@echo " test-all: like test, but also includes the expensive tests"
|
||||
@echo " deb: build debian packages
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Meta targets
|
||||
@@ -157,6 +158,12 @@ build-explorer-api:
|
||||
build-nym-cli:
|
||||
cargo build -p nym-cli --release
|
||||
|
||||
build-nym-gateway:
|
||||
cargo build -p nym-gateway --release
|
||||
|
||||
build-nym-mixnode:
|
||||
cargo build -p nym-mixnode --release
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Misc
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -169,6 +176,13 @@ run-api-tests:
|
||||
cd nym-api/tests/functional_test && yarn test:qa
|
||||
|
||||
# Build debian package, and update PPA
|
||||
# Requires base64 encode GPG key to be set up in environment PPA_SIGNING_KEY
|
||||
deb:
|
||||
scripts/ppa.sh
|
||||
deb-mixnode: build-nym-mixnode
|
||||
cargo deb -p nym-mixnode
|
||||
|
||||
deb-gateway: build-nym-gateway
|
||||
cargo deb -p nym-gateway
|
||||
|
||||
deb-cli: build-nym-cli
|
||||
cargo deb -p nym-cli
|
||||
|
||||
deb: deb-mixnode deb-gateway deb-cli
|
||||
@@ -0,0 +1,70 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<style>
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background: #333;
|
||||
color: white;
|
||||
}
|
||||
a {
|
||||
color: skyblue;
|
||||
}
|
||||
}
|
||||
.container {
|
||||
font-family: sans-serif;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.intro {
|
||||
text-align: center;
|
||||
}
|
||||
.licenses-list {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.license-used-by {
|
||||
margin-top: -10px;
|
||||
}
|
||||
.license-text {
|
||||
max-height: 200px;
|
||||
overflow-y: scroll;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main class="container">
|
||||
<div class="intro">
|
||||
<h1>Third Party Licenses</h1>
|
||||
<p>This page lists the licenses of the projects used in cargo-about.</p>
|
||||
</div>
|
||||
|
||||
<h2>Overview of licenses:</h2>
|
||||
<ul class="licenses-overview">
|
||||
{{#each overview}}
|
||||
<li><a href="#{{id}}">{{name}}</a> ({{count}})</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
|
||||
<h2>All license text:</h2>
|
||||
<ul class="licenses-list">
|
||||
{{#each licenses}}
|
||||
<li class="license">
|
||||
<h3 id="{{id}}">{{name}}</h3>
|
||||
<h4>Used by:</h4>
|
||||
<ul class="license-used-by">
|
||||
{{#each used_by}}
|
||||
<li><a href="{{#if crate.repository}} {{crate.repository}} {{else}} https://crates.io/crates/{{crate.name}} {{/if}}">{{crate.name}} {{crate.version}}</a></li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
<pre class="license-text">{{text}}</pre>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
private = { ignore = true }
|
||||
|
||||
accepted = [
|
||||
"0BSD",
|
||||
"Apache-2.0",
|
||||
"BSD-2-Clause",
|
||||
"BSD-3-Clause",
|
||||
"CC0-1.0",
|
||||
"ISC",
|
||||
"MIT",
|
||||
"MPL-2.0",
|
||||
"Unicode-DFS-2016",
|
||||
"OpenSSL",
|
||||
]
|
||||
|
||||
workarounds = [
|
||||
"ring",
|
||||
"rustls",
|
||||
]
|
||||
@@ -1667,9 +1667,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.14.9",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz",
|
||||
"integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==",
|
||||
"version": "1.15.4",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
|
||||
"integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -5800,9 +5800,9 @@
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.14.9",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz",
|
||||
"integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==",
|
||||
"version": "1.15.4",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
|
||||
"integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
|
||||
"dev": true
|
||||
},
|
||||
"forwarded": {
|
||||
|
||||
@@ -51,7 +51,7 @@ impl InitialisableClient for NativeClientInit {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
#[derive(Args, Clone, Debug)]
|
||||
pub(crate) struct Init {
|
||||
#[command(flatten)]
|
||||
common_args: CommonClientInitArgs,
|
||||
|
||||
@@ -51,7 +51,7 @@ impl InitialisableClient for Socks5ClientInit {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
#[derive(Args, Clone, Debug)]
|
||||
pub(crate) struct Init {
|
||||
#[command(flatten)]
|
||||
common_args: CommonClientInitArgs,
|
||||
|
||||
@@ -15,4 +15,4 @@ prod:
|
||||
mixnode_identity: 3pMCJswCyA19MGYWGDWT5fBk2M8ybSZGXttyAoNY5gBB
|
||||
gateway_identity: 2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh
|
||||
log_level: error
|
||||
time_zone: utc
|
||||
time_zone: utc
|
||||
+19
-4
@@ -1,4 +1,6 @@
|
||||
import { dir } from "console";
|
||||
import { readFileSync } from "fs";
|
||||
import { dirname } from "path";
|
||||
import { TLogLevelName } from "tslog";
|
||||
|
||||
import YAML from "yaml";
|
||||
@@ -10,9 +12,11 @@ class ConfigHandler {
|
||||
|
||||
public commonConfig: { request_headers: object };
|
||||
|
||||
private currentEnvironment: string;
|
||||
|
||||
public environment: string;
|
||||
|
||||
public environmnetConfig: {
|
||||
public environmentConfig: {
|
||||
log_level: TLogLevelName;
|
||||
time_zone: string;
|
||||
api_base_url: string;
|
||||
@@ -35,8 +39,9 @@ class ConfigHandler {
|
||||
|
||||
private setCommonConfig(): void {
|
||||
try {
|
||||
const baseWorkingDirectory = __dirname;
|
||||
this.commonConfig = YAML.parse(
|
||||
readFileSync("src/config/config.yaml", "utf8")
|
||||
readFileSync(baseWorkingDirectory + "/config.yaml", "utf8"),
|
||||
).common;
|
||||
} catch (error) {
|
||||
throw Error(`Error reading common config: (${error})`);
|
||||
@@ -46,14 +51,24 @@ class ConfigHandler {
|
||||
private setEnvironmentConfig(environment: string): void {
|
||||
this.ensureEnvironmentIsValid(environment);
|
||||
try {
|
||||
this.environmnetConfig = YAML.parse(
|
||||
readFileSync("src/config/config.yaml", "utf8")
|
||||
const baseWorkingDirectory = __dirname;
|
||||
this.environmentConfig = YAML.parse(
|
||||
readFileSync(baseWorkingDirectory + "/config.yaml", "utf8"),
|
||||
)[environment];
|
||||
} catch (error) {
|
||||
console.log("fadsfasdfasdfsdfsa")
|
||||
throw Error(`Error reading environment config: (${error})`);
|
||||
}
|
||||
}
|
||||
|
||||
public getEnvironmentConfig(environment: string): any {
|
||||
const baseWorkingDirectory = __dirname;
|
||||
return (
|
||||
this.environmentConfig ||
|
||||
YAML.parse(readFileSync(baseWorkingDirectory + "/config.yaml", "utf8"))[environment]
|
||||
);
|
||||
}
|
||||
|
||||
private ensureEnvironmentIsValid(environment: string): void {
|
||||
if (this.validEnvironments.indexOf(environment) === -1) {
|
||||
throw Error(`Config environment is not valid: "${environment}"`);
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
ConfigHandler: require('./config/configHandler.ts'),
|
||||
RestClient: require('./restClient/RestClient.ts')
|
||||
};
|
||||
+5
-5
@@ -13,9 +13,9 @@ import ConfigHandler from "../config/configHandler";
|
||||
|
||||
const config = ConfigHandler.getInstance();
|
||||
const log = new Logger({
|
||||
minLevel: config.environmnetConfig.log_level,
|
||||
minLevel: config.environmentConfig.log_level,
|
||||
dateTimeTimezone:
|
||||
config.environmnetConfig.time_zone ||
|
||||
config.environmentConfig.time_zone ||
|
||||
Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
});
|
||||
|
||||
@@ -24,7 +24,7 @@ function isSet(property): boolean {
|
||||
}
|
||||
|
||||
export class RestClient {
|
||||
private static authToken: string;
|
||||
public static authToken: string;
|
||||
|
||||
private axiosInstance: AxiosInstance;
|
||||
|
||||
@@ -83,7 +83,7 @@ export class RestClient {
|
||||
data,
|
||||
additionalConfigs,
|
||||
params,
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
await this.axiosInstance
|
||||
@@ -214,7 +214,7 @@ export class RestClient {
|
||||
|
||||
if (isSet(additionalConfigs)) {
|
||||
logRecord = `${logRecord}\nAdditional Configuration: ${stringify(
|
||||
additionalConfigs
|
||||
additionalConfigs,
|
||||
)}`;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ repository = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
atty = "0.2"
|
||||
const-str = "0.5.6"
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
clap_complete = "4.0"
|
||||
clap_complete_fig = "4.0"
|
||||
@@ -35,9 +36,10 @@ opentelemetry = { version = "0.19.0", optional = true, features = ["rt-tokio"] }
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
vergen = { version = "=7.4.3", default-features = false, features = [
|
||||
vergen = { version = "=8.2.6", default-features = false, features = [
|
||||
"build",
|
||||
"git",
|
||||
"gitcl",
|
||||
"rustc",
|
||||
"cargo",
|
||||
] }
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use vergen::{vergen, Config};
|
||||
use vergen::EmitBuilder;
|
||||
|
||||
fn main() {
|
||||
let mut config = Config::default();
|
||||
if std::env::var("DOCS_RS").is_ok() {
|
||||
// If we don't have access to git information, such as in a docs.rs build, don't error
|
||||
*config.git_mut().skip_if_error_mut() = true;
|
||||
}
|
||||
vergen(config).expect("failed to extract build metadata");
|
||||
EmitBuilder::builder()
|
||||
.all_build()
|
||||
.all_git()
|
||||
.all_rustc()
|
||||
.all_cargo()
|
||||
.emit()
|
||||
.expect("failed to extract build metadata");
|
||||
}
|
||||
|
||||
@@ -40,14 +40,22 @@ pub struct BinaryBuildInformation {
|
||||
/// Provides the rustc channel that was used for the build, for example `nightly`.
|
||||
pub rustc_channel: &'static str,
|
||||
|
||||
// VERGEN_CARGO_PROFILE
|
||||
/// Provides the cargo profile that was used for the build, for example `debug`.
|
||||
// VERGEN_CARGO_DEBUG
|
||||
/// Provides the cargo debug mode that was used for the build.
|
||||
// NOTE: keep the old name cargo_profile instead of cargo_debug for backwards compatibility
|
||||
pub cargo_profile: &'static str,
|
||||
}
|
||||
|
||||
impl BinaryBuildInformation {
|
||||
// explicitly require the build_version to be passed as it's binary specific
|
||||
pub const fn new(binary_name: &'static str, build_version: &'static str) -> Self {
|
||||
let cargo_debug = env!("VERGEN_CARGO_DEBUG");
|
||||
let cargo_profile = if const_str::equal!(cargo_debug, "true") {
|
||||
"debug"
|
||||
} else {
|
||||
"release"
|
||||
};
|
||||
|
||||
BinaryBuildInformation {
|
||||
binary_name,
|
||||
build_timestamp: env!("VERGEN_BUILD_TIMESTAMP"),
|
||||
@@ -57,7 +65,7 @@ impl BinaryBuildInformation {
|
||||
commit_branch: env!("VERGEN_GIT_BRANCH"),
|
||||
rustc_version: env!("VERGEN_RUSTC_SEMVER"),
|
||||
rustc_channel: env!("VERGEN_RUSTC_CHANNEL"),
|
||||
cargo_profile: env!("VERGEN_CARGO_PROFILE"),
|
||||
cargo_profile,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,8 +123,9 @@ pub struct BinaryBuildInformationOwned {
|
||||
/// Provides the rustc channel that was used for the build, for example `nightly`.
|
||||
pub rustc_channel: String,
|
||||
|
||||
// VERGEN_CARGO_PROFILE
|
||||
/// Provides the cargo profile that was used for the build, for example `debug`.
|
||||
// VERGEN_CARGO_DEBUG
|
||||
/// Provides the cargo debug mode that was used for the build.
|
||||
// NOTE: keep the old name cargo_profile instead of cargo_debug for backwards compatibility
|
||||
pub cargo_profile: String,
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ nym-validator-client = { path = "../client-libs/validator-client", default-featu
|
||||
nym-task = { path = "../task" }
|
||||
nym-credential-storage = { path = "../credential-storage" }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
si-scale = "0.2.2"
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-stream]
|
||||
version = "0.1.11"
|
||||
@@ -59,7 +60,7 @@ features = ["time"]
|
||||
version = "0.20.1"
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
|
||||
version = "0.6.2"
|
||||
workspace = true
|
||||
features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"]
|
||||
optional = true
|
||||
|
||||
@@ -90,7 +91,7 @@ tempfile = "3.1.0"
|
||||
|
||||
[build-dependencies]
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
sqlx = { version = "0.6.2", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
|
||||
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -109,6 +109,8 @@ pub async fn initialise_client<C>(
|
||||
) -> Result<InitResultsWithConfig<C::Config>, C::Error>
|
||||
where
|
||||
C: InitialisableClient,
|
||||
<C as InitialisableClient>::Config: std::fmt::Debug,
|
||||
<C as InitialisableClient>::InitArgs: std::fmt::Debug,
|
||||
{
|
||||
info!("initialising {} client", C::NAME);
|
||||
|
||||
@@ -140,17 +142,32 @@ where
|
||||
|
||||
// Attempt to use a user-provided gateway, if possible
|
||||
let user_chosen_gateway_id = common_args.gateway;
|
||||
log::debug!("User chosen gateway id: {user_chosen_gateway_id:?}");
|
||||
|
||||
let selection_spec = GatewaySelectionSpecification::new(
|
||||
user_chosen_gateway_id.map(|id| id.to_base58_string()),
|
||||
Some(common_args.latency_based_selection),
|
||||
false,
|
||||
);
|
||||
log::debug!("Gateway selection specification: {selection_spec:?}");
|
||||
|
||||
// Load and potentially override config
|
||||
log::debug!("Init arguments: {init_args:#?}");
|
||||
let config = C::construct_config(&init_args);
|
||||
log::debug!("Constructed config: {config:#?}");
|
||||
let paths = config.common_paths();
|
||||
let core = config.core_config();
|
||||
|
||||
log::info!(
|
||||
"Using nym-api: {}",
|
||||
core.client
|
||||
.nym_api_urls
|
||||
.iter()
|
||||
.map(|url| url.as_str())
|
||||
.collect::<Vec<&str>>()
|
||||
.join(",")
|
||||
);
|
||||
|
||||
// Setup gateway by either registering a new one, or creating a new config from the selected
|
||||
// one but with keys kept, or reusing the gateway configuration.
|
||||
let key_store = OnDiskKeys::new(paths.keys.clone());
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::packet_statistics_control::PacketStatisticsReporter;
|
||||
use super::received_buffer::ReceivedBufferMessage;
|
||||
use super::topology_control::geo_aware_provider::GeoAwareTopologyProvider;
|
||||
use crate::client::base_client::storage::gateway_details::GatewayDetailsStore;
|
||||
@@ -10,6 +11,7 @@ use crate::client::inbound_messages::{InputMessage, InputMessageReceiver, InputM
|
||||
use crate::client::key_manager::persistence::KeyStore;
|
||||
use crate::client::mix_traffic::transceiver::{GatewayReceiver, GatewayTransceiver, RemoteGateway};
|
||||
use crate::client::mix_traffic::{BatchMixMessageSender, MixTrafficController};
|
||||
use crate::client::packet_statistics_control::PacketStatisticsControl;
|
||||
use crate::client::real_messages_control;
|
||||
use crate::client::real_messages_control::RealMessagesController;
|
||||
use crate::client::received_buffer::{
|
||||
@@ -254,6 +256,7 @@ where
|
||||
self_address: Recipient,
|
||||
topology_accessor: TopologyAccessor,
|
||||
mix_tx: BatchMixMessageSender,
|
||||
stats_tx: PacketStatisticsReporter,
|
||||
shutdown: TaskClient,
|
||||
) {
|
||||
info!("Starting loop cover traffic stream...");
|
||||
@@ -266,6 +269,7 @@ where
|
||||
topology_accessor,
|
||||
debug_config.traffic,
|
||||
debug_config.cover_traffic,
|
||||
stats_tx,
|
||||
);
|
||||
|
||||
stream.start_with_shutdown(shutdown);
|
||||
@@ -285,6 +289,7 @@ where
|
||||
client_connection_rx: ConnectionCommandReceiver,
|
||||
shutdown: TaskClient,
|
||||
packet_type: PacketType,
|
||||
stats_tx: PacketStatisticsReporter,
|
||||
) {
|
||||
info!("Starting real traffic stream...");
|
||||
|
||||
@@ -299,6 +304,7 @@ where
|
||||
reply_controller_receiver,
|
||||
lane_queue_lengths,
|
||||
client_connection_rx,
|
||||
stats_tx,
|
||||
)
|
||||
.start_with_shutdown(shutdown, packet_type);
|
||||
}
|
||||
@@ -312,6 +318,7 @@ where
|
||||
reply_key_storage: SentReplyKeys,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
shutdown: TaskClient,
|
||||
packet_statistics_control: PacketStatisticsReporter,
|
||||
) {
|
||||
info!("Starting received messages buffer controller...");
|
||||
let controller: ReceivedMessagesBufferController<SphinxMessageReceiver> =
|
||||
@@ -321,6 +328,7 @@ where
|
||||
mixnet_receiver,
|
||||
reply_key_storage,
|
||||
reply_controller_sender,
|
||||
packet_statistics_control,
|
||||
);
|
||||
controller.start_with_shutdown(shutdown)
|
||||
}
|
||||
@@ -506,6 +514,13 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start_packet_statistics_control(shutdown: TaskClient) -> PacketStatisticsReporter {
|
||||
info!("Starting packet statistics control...");
|
||||
let (packet_statistics_control, packet_stats_reporter) = PacketStatisticsControl::new();
|
||||
packet_statistics_control.start_with_shutdown(shutdown);
|
||||
packet_stats_reporter
|
||||
}
|
||||
|
||||
fn start_mix_traffic_controller(
|
||||
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
|
||||
shutdown: TaskClient,
|
||||
@@ -633,6 +648,9 @@ where
|
||||
)
|
||||
.await?;
|
||||
|
||||
let packet_stats_reporter =
|
||||
Self::start_packet_statistics_control(shutdown.fork("packet_statistics_control"));
|
||||
|
||||
let gateway_packet_router = PacketRouter::new(
|
||||
ack_sender,
|
||||
mixnet_messages_sender,
|
||||
@@ -662,6 +680,7 @@ where
|
||||
reply_storage.key_storage(),
|
||||
reply_controller_sender.clone(),
|
||||
shutdown.fork("received_messages_buffer"),
|
||||
packet_stats_reporter.clone(),
|
||||
);
|
||||
|
||||
// The message_sender is the transmitter for any component generating sphinx packets
|
||||
@@ -700,6 +719,7 @@ where
|
||||
client_connection_rx,
|
||||
shutdown.fork("real_traffic_controller"),
|
||||
self.config.debug.traffic.packet_type,
|
||||
packet_stats_reporter.clone(),
|
||||
);
|
||||
|
||||
if !self
|
||||
@@ -714,6 +734,7 @@ where
|
||||
self_address,
|
||||
shared_topology_accessor.clone(),
|
||||
message_sender,
|
||||
packet_stats_reporter,
|
||||
shutdown.fork("cover_traffic_stream"),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::mix_traffic::BatchMixMessageSender;
|
||||
use crate::client::packet_statistics_control::{PacketStatisticsEvent, PacketStatisticsReporter};
|
||||
use crate::client::topology_control::TopologyAccessor;
|
||||
use crate::{config, spawn_future};
|
||||
use futures::task::{Context, Poll};
|
||||
@@ -61,6 +62,8 @@ where
|
||||
secondary_packet_size: Option<PacketSize>,
|
||||
|
||||
packet_type: PacketType,
|
||||
|
||||
stats_tx: PacketStatisticsReporter,
|
||||
}
|
||||
|
||||
impl<R> Stream for LoopCoverTrafficStream<R>
|
||||
@@ -97,7 +100,8 @@ where
|
||||
// obviously when we finally make shared rng that is on 'higher' level, this should become
|
||||
// generic `R`
|
||||
impl LoopCoverTrafficStream<OsRng> {
|
||||
pub fn new(
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn new(
|
||||
ack_key: Arc<AckKey>,
|
||||
average_ack_delay: Duration,
|
||||
mix_tx: BatchMixMessageSender,
|
||||
@@ -105,6 +109,7 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
topology_access: TopologyAccessor,
|
||||
traffic_config: config::Traffic,
|
||||
cover_config: config::CoverTraffic,
|
||||
stats_tx: PacketStatisticsReporter,
|
||||
) -> Self {
|
||||
let rng = OsRng;
|
||||
|
||||
@@ -122,6 +127,7 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
primary_packet_size: traffic_config.primary_packet_size,
|
||||
secondary_packet_size: traffic_config.secondary_packet_size,
|
||||
packet_type: traffic_config.packet_type,
|
||||
stats_tx,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,6 +197,10 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
log::warn!("Failed to send cover message - channel closed");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.stats_tx.report(PacketStatisticsEvent::CoverPacketSent(
|
||||
cover_traffic_packet_size.size(),
|
||||
));
|
||||
}
|
||||
|
||||
// TODO: I'm not entirely sure whether this is really required, because I'm not 100%
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
pub use wasmtimer::{std::Instant, tokio::*};
|
||||
|
||||
@@ -7,6 +7,7 @@ pub(crate) mod helpers;
|
||||
pub mod inbound_messages;
|
||||
pub mod key_manager;
|
||||
pub mod mix_traffic;
|
||||
pub(crate) mod packet_statistics_control;
|
||||
pub mod real_messages_control;
|
||||
pub mod received_buffer;
|
||||
pub mod replies;
|
||||
|
||||
@@ -0,0 +1,498 @@
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use si_scale::helpers::bibytes2;
|
||||
|
||||
use crate::spawn_future;
|
||||
|
||||
// Time interval between reporting packet statistics
|
||||
const PACKET_REPORT_INTERVAL_SECS: u64 = 2;
|
||||
// Interval for taking snapshots of the packet statistics
|
||||
const SNAPSHOT_INTERVAL_MS: u64 = 500;
|
||||
// When computing rates, we include snapshots that are up to this old. We set it to some odd number
|
||||
// a tad larger than an integer number of snapshot intervals, so that we don't have to worry about
|
||||
// threshold effects.
|
||||
// Also, set it larger than the packet report interval so that we don't miss notable singular events
|
||||
const RECORDING_WINDOW_MS: u64 = 2300;
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
struct PacketStatistics {
|
||||
// Sent
|
||||
real_packets_sent: u64,
|
||||
real_packets_sent_size: usize,
|
||||
cover_packets_sent: u64,
|
||||
cover_packets_sent_size: usize,
|
||||
|
||||
// Received
|
||||
real_packets_received: u64,
|
||||
real_packets_received_size: usize,
|
||||
cover_packets_received: u64,
|
||||
cover_packets_received_size: usize,
|
||||
|
||||
// Acks
|
||||
total_acks_received: u64,
|
||||
total_acks_received_size: usize,
|
||||
real_acks_received: u64,
|
||||
real_acks_received_size: usize,
|
||||
cover_acks_received: u64,
|
||||
cover_acks_received_size: usize,
|
||||
|
||||
// Types of packets queued
|
||||
// TODO: track the type sent instead
|
||||
real_packets_queued: u64,
|
||||
retransmissions_queued: u64,
|
||||
reply_surbs_queued: u64,
|
||||
additional_reply_surbs_queued: u64,
|
||||
}
|
||||
|
||||
impl PacketStatistics {
|
||||
fn handle_event(&mut self, event: PacketStatisticsEvent) {
|
||||
match event {
|
||||
PacketStatisticsEvent::RealPacketSent(packet_size) => {
|
||||
self.real_packets_sent += 1;
|
||||
self.real_packets_sent_size += packet_size;
|
||||
}
|
||||
PacketStatisticsEvent::CoverPacketSent(packet_size) => {
|
||||
self.cover_packets_sent += 1;
|
||||
self.cover_packets_sent_size += packet_size;
|
||||
}
|
||||
PacketStatisticsEvent::RealPacketReceived(packet_size) => {
|
||||
self.real_packets_received += 1;
|
||||
self.real_packets_received_size += packet_size;
|
||||
}
|
||||
PacketStatisticsEvent::CoverPacketReceived(packet_size) => {
|
||||
self.cover_packets_received += 1;
|
||||
self.cover_packets_received_size += packet_size;
|
||||
}
|
||||
PacketStatisticsEvent::AckReceived(packet_size) => {
|
||||
self.total_acks_received += 1;
|
||||
self.total_acks_received_size += packet_size;
|
||||
}
|
||||
PacketStatisticsEvent::RealAckReceived(packet_size) => {
|
||||
self.real_acks_received += 1;
|
||||
self.real_acks_received_size += packet_size;
|
||||
}
|
||||
PacketStatisticsEvent::CoverAckReceived(packet_size) => {
|
||||
self.cover_acks_received += 1;
|
||||
self.cover_acks_received_size += packet_size;
|
||||
}
|
||||
PacketStatisticsEvent::RealPacketQueued => {
|
||||
self.real_packets_queued += 1;
|
||||
}
|
||||
PacketStatisticsEvent::RetransmissionQueued => {
|
||||
self.retransmissions_queued += 1;
|
||||
}
|
||||
PacketStatisticsEvent::ReplySurbRequestQueued => {
|
||||
self.reply_surbs_queued += 1;
|
||||
}
|
||||
PacketStatisticsEvent::AdditionalReplySurbRequestQueued => {
|
||||
self.additional_reply_surbs_queued += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn summary(&self) -> (String, String) {
|
||||
(
|
||||
format!(
|
||||
"packets sent: {} (real: {}, cover: {}, retransmissions: {})",
|
||||
self.real_packets_sent + self.cover_packets_sent,
|
||||
self.real_packets_sent,
|
||||
self.cover_packets_sent,
|
||||
self.retransmissions_queued,
|
||||
),
|
||||
format!(
|
||||
"packets received: {}, (real: {}, cover: {}, acks: {}, acks for cover: {})",
|
||||
self.real_packets_received + self.cover_packets_received,
|
||||
self.real_packets_received,
|
||||
self.cover_packets_received,
|
||||
self.real_acks_received,
|
||||
self.cover_acks_received,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub for PacketStatistics {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
Self {
|
||||
real_packets_sent: self.real_packets_sent - rhs.real_packets_sent,
|
||||
real_packets_sent_size: self.real_packets_sent_size - rhs.real_packets_sent_size,
|
||||
cover_packets_sent: self.cover_packets_sent - rhs.cover_packets_sent,
|
||||
cover_packets_sent_size: self.cover_packets_sent_size - rhs.cover_packets_sent_size,
|
||||
|
||||
real_packets_received: self.real_packets_received - rhs.real_packets_received,
|
||||
real_packets_received_size: self.real_packets_received_size
|
||||
- rhs.real_packets_received_size,
|
||||
cover_packets_received: self.cover_packets_received - rhs.cover_packets_received,
|
||||
cover_packets_received_size: self.cover_packets_received_size
|
||||
- rhs.cover_packets_received_size,
|
||||
|
||||
total_acks_received: self.total_acks_received - rhs.total_acks_received,
|
||||
total_acks_received_size: self.total_acks_received_size - rhs.total_acks_received_size,
|
||||
real_acks_received: self.real_acks_received - rhs.real_acks_received,
|
||||
real_acks_received_size: self.real_acks_received_size - rhs.real_acks_received_size,
|
||||
cover_acks_received: self.cover_acks_received - rhs.cover_acks_received,
|
||||
cover_acks_received_size: self.cover_acks_received_size - rhs.cover_acks_received_size,
|
||||
|
||||
real_packets_queued: self.real_packets_queued - rhs.real_packets_queued,
|
||||
retransmissions_queued: self.retransmissions_queued - rhs.retransmissions_queued,
|
||||
reply_surbs_queued: self.reply_surbs_queued - rhs.reply_surbs_queued,
|
||||
additional_reply_surbs_queued: self.additional_reply_surbs_queued
|
||||
- rhs.additional_reply_surbs_queued,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct PacketRates {
|
||||
real_packets_sent: f64,
|
||||
real_packets_sent_size: f64,
|
||||
cover_packets_sent: f64,
|
||||
cover_packets_sent_size: f64,
|
||||
|
||||
real_packets_received: f64,
|
||||
real_packets_received_size: f64,
|
||||
cover_packets_received: f64,
|
||||
cover_packets_received_size: f64,
|
||||
|
||||
total_acks_received: f64,
|
||||
total_acks_received_size: f64,
|
||||
real_acks_received: f64,
|
||||
real_acks_received_size: f64,
|
||||
cover_acks_received: f64,
|
||||
cover_acks_received_size: f64,
|
||||
|
||||
real_packets_queued: f64,
|
||||
retransmissions_queued: f64,
|
||||
reply_surbs_queued: f64,
|
||||
additional_reply_surbs_queued: f64,
|
||||
}
|
||||
|
||||
impl From<PacketStatistics> for PacketRates {
|
||||
fn from(stats: PacketStatistics) -> Self {
|
||||
Self {
|
||||
real_packets_sent: stats.real_packets_sent as f64,
|
||||
real_packets_sent_size: stats.real_packets_sent_size as f64,
|
||||
cover_packets_sent: stats.cover_packets_sent as f64,
|
||||
cover_packets_sent_size: stats.cover_packets_sent_size as f64,
|
||||
|
||||
real_packets_received: stats.real_packets_received as f64,
|
||||
real_packets_received_size: stats.real_packets_received_size as f64,
|
||||
cover_packets_received: stats.cover_packets_received as f64,
|
||||
cover_packets_received_size: stats.cover_packets_received_size as f64,
|
||||
|
||||
total_acks_received: stats.total_acks_received as f64,
|
||||
total_acks_received_size: stats.total_acks_received_size as f64,
|
||||
real_acks_received: stats.real_acks_received as f64,
|
||||
real_acks_received_size: stats.real_acks_received_size as f64,
|
||||
cover_acks_received: stats.cover_acks_received as f64,
|
||||
cover_acks_received_size: stats.cover_acks_received_size as f64,
|
||||
|
||||
real_packets_queued: stats.real_packets_queued as f64,
|
||||
retransmissions_queued: stats.retransmissions_queued as f64,
|
||||
reply_surbs_queued: stats.reply_surbs_queued as f64,
|
||||
additional_reply_surbs_queued: stats.additional_reply_surbs_queued as f64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub for PacketRates {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
Self {
|
||||
real_packets_sent: self.real_packets_sent - rhs.real_packets_sent,
|
||||
real_packets_sent_size: self.real_packets_sent_size - rhs.real_packets_sent_size,
|
||||
cover_packets_sent: self.cover_packets_sent - rhs.cover_packets_sent,
|
||||
cover_packets_sent_size: self.cover_packets_sent_size - rhs.cover_packets_sent_size,
|
||||
|
||||
real_packets_received: self.real_packets_received - rhs.real_packets_received,
|
||||
real_packets_received_size: self.real_packets_received_size
|
||||
- rhs.real_packets_received_size,
|
||||
cover_packets_received: self.cover_packets_received - rhs.cover_packets_received,
|
||||
cover_packets_received_size: self.cover_packets_received_size
|
||||
- rhs.cover_packets_received_size,
|
||||
|
||||
total_acks_received: self.total_acks_received - rhs.total_acks_received,
|
||||
total_acks_received_size: self.total_acks_received_size - rhs.total_acks_received_size,
|
||||
real_acks_received: self.real_acks_received - rhs.real_acks_received,
|
||||
real_acks_received_size: self.real_acks_received_size - rhs.real_acks_received_size,
|
||||
cover_acks_received: self.cover_acks_received - rhs.cover_acks_received,
|
||||
cover_acks_received_size: self.cover_acks_received_size - rhs.cover_acks_received_size,
|
||||
|
||||
real_packets_queued: self.real_packets_queued - rhs.real_packets_queued,
|
||||
retransmissions_queued: self.retransmissions_queued - rhs.retransmissions_queued,
|
||||
reply_surbs_queued: self.reply_surbs_queued - rhs.reply_surbs_queued,
|
||||
additional_reply_surbs_queued: self.additional_reply_surbs_queued
|
||||
- rhs.additional_reply_surbs_queued,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Div<f64> for PacketRates {
|
||||
type Output = Self;
|
||||
|
||||
fn div(self, rhs: f64) -> Self::Output {
|
||||
Self {
|
||||
real_packets_sent: self.real_packets_sent / rhs,
|
||||
real_packets_sent_size: self.real_packets_sent_size / rhs,
|
||||
cover_packets_sent: self.cover_packets_sent / rhs,
|
||||
cover_packets_sent_size: self.cover_packets_sent_size / rhs,
|
||||
|
||||
real_packets_received: self.real_packets_received / rhs,
|
||||
real_packets_received_size: self.real_packets_received_size / rhs,
|
||||
cover_packets_received: self.cover_packets_received / rhs,
|
||||
cover_packets_received_size: self.cover_packets_received_size / rhs,
|
||||
|
||||
total_acks_received: self.total_acks_received / rhs,
|
||||
total_acks_received_size: self.total_acks_received_size / rhs,
|
||||
real_acks_received: self.real_acks_received / rhs,
|
||||
real_acks_received_size: self.real_acks_received_size / rhs,
|
||||
cover_acks_received: self.cover_acks_received / rhs,
|
||||
cover_acks_received_size: self.cover_acks_received_size / rhs,
|
||||
|
||||
real_packets_queued: self.real_packets_queued / rhs,
|
||||
retransmissions_queued: self.retransmissions_queued / rhs,
|
||||
reply_surbs_queued: self.reply_surbs_queued / rhs,
|
||||
additional_reply_surbs_queued: self.additional_reply_surbs_queued / rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PacketRates {
|
||||
fn summary(&self) -> String {
|
||||
format!(
|
||||
"rx: {}/s (real: {}/s), tx: {}/s (real: {}/s)",
|
||||
bibytes2(self.real_packets_received_size + self.cover_packets_received_size),
|
||||
bibytes2(self.real_packets_received_size),
|
||||
bibytes2(self.real_packets_sent_size + self.cover_packets_sent_size),
|
||||
bibytes2(self.real_packets_sent_size),
|
||||
)
|
||||
}
|
||||
|
||||
fn detailed_summary(&self) -> String {
|
||||
format!(
|
||||
"RX: {:.1} mixpkt/s, {}/s (real: {}/s, acks: {}/s), TX: {:.1} mixpkt/s, {}/s (real: {}/s)",
|
||||
self.real_packets_received + self.cover_packets_received,
|
||||
bibytes2(self.real_packets_received_size + self.cover_packets_received_size),
|
||||
bibytes2(self.real_packets_received_size),
|
||||
bibytes2(self.total_acks_received_size),
|
||||
self.real_packets_sent + self.cover_packets_sent,
|
||||
bibytes2(self.real_packets_sent_size + self.cover_packets_sent_size),
|
||||
bibytes2(self.real_packets_sent_size),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum PacketStatisticsEvent {
|
||||
// The real packets sent. Recall that acks are sent by the gateway, so it's not included here.
|
||||
RealPacketSent(usize),
|
||||
// The cover packets sent
|
||||
CoverPacketSent(usize),
|
||||
|
||||
// Real packets received
|
||||
RealPacketReceived(usize),
|
||||
// Cover packets received
|
||||
CoverPacketReceived(usize),
|
||||
|
||||
// Ack of any type received. This is mostly used as a consistency check, and should be the sum
|
||||
// of real and cover acks received.
|
||||
AckReceived(usize),
|
||||
// Out of the total acks received, this is the subset of those that were real
|
||||
RealAckReceived(usize),
|
||||
// Out of the total acks received, this is the subset of those that were for cover traffic
|
||||
CoverAckReceived(usize),
|
||||
|
||||
// Types of packets queued
|
||||
RealPacketQueued,
|
||||
RetransmissionQueued,
|
||||
ReplySurbRequestQueued,
|
||||
AdditionalReplySurbRequestQueued,
|
||||
}
|
||||
|
||||
type PacketStatisticsReceiver = tokio::sync::mpsc::UnboundedReceiver<PacketStatisticsEvent>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct PacketStatisticsReporter {
|
||||
stats_tx: tokio::sync::mpsc::UnboundedSender<PacketStatisticsEvent>,
|
||||
}
|
||||
|
||||
impl PacketStatisticsReporter {
|
||||
pub(crate) fn new(stats_tx: tokio::sync::mpsc::UnboundedSender<PacketStatisticsEvent>) -> Self {
|
||||
Self { stats_tx }
|
||||
}
|
||||
|
||||
pub(crate) fn report(&self, event: PacketStatisticsEvent) {
|
||||
self.stats_tx.send(event).unwrap_or_else(|err| {
|
||||
log::error!("Failed to report packet stat: {:?}", err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct PacketStatisticsControl {
|
||||
// Incoming packet stats events from other tasks
|
||||
stats_rx: PacketStatisticsReceiver,
|
||||
|
||||
// Keep track of packet statistics over time
|
||||
stats: PacketStatistics,
|
||||
|
||||
// We keep snapshots of the statistics over time so we can compute rates, and also keeping the
|
||||
// full history allows for some more fancy averaging if we want to do that.
|
||||
history: VecDeque<(Instant, PacketStatistics)>,
|
||||
|
||||
// Keep previous rates so that we can detect notable events
|
||||
rates: VecDeque<(Instant, PacketRates)>,
|
||||
}
|
||||
|
||||
impl PacketStatisticsControl {
|
||||
pub(crate) fn new() -> (Self, PacketStatisticsReporter) {
|
||||
let (stats_tx, stats_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
|
||||
(
|
||||
Self {
|
||||
stats_rx,
|
||||
stats: PacketStatistics::default(),
|
||||
history: VecDeque::new(),
|
||||
rates: VecDeque::new(),
|
||||
},
|
||||
PacketStatisticsReporter::new(stats_tx),
|
||||
)
|
||||
}
|
||||
|
||||
// Add the current stats to the history, and remove old ones.
|
||||
fn update_history(&mut self) {
|
||||
// Update latest
|
||||
self.history.push_back((Instant::now(), self.stats.clone()));
|
||||
|
||||
// Filter out old ones
|
||||
let recording_window = Instant::now() - Duration::from_millis(RECORDING_WINDOW_MS);
|
||||
while self
|
||||
.history
|
||||
.front()
|
||||
.map_or(false, |&(t, _)| t < recording_window)
|
||||
{
|
||||
self.history.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_rates(&self) -> Option<PacketRates> {
|
||||
// NOTE: consider changing this to compute rates over the history instead of using current
|
||||
// stats. Currently it should not make much of a difference since we call this just after
|
||||
// updating the history, but it seems like it could be more internally consistent to do it
|
||||
// that way.
|
||||
|
||||
// Do basic averaging over the entire history, which just uses the first and last
|
||||
if let Some((start, start_stats)) = self.history.front() {
|
||||
let duration_secs = Instant::now().duration_since(*start).as_secs_f64();
|
||||
let delta = self.stats.clone() - start_stats.clone();
|
||||
let rates = PacketRates::from(delta) / duration_secs;
|
||||
Some(rates)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn update_rates(&mut self) {
|
||||
// Update latest
|
||||
if let Some(rates) = self.compute_rates() {
|
||||
self.rates.push_back((Instant::now(), rates));
|
||||
}
|
||||
|
||||
// Filter out old ones
|
||||
let recording_window = Instant::now() - Duration::from_millis(RECORDING_WINDOW_MS);
|
||||
while self
|
||||
.rates
|
||||
.front()
|
||||
.map_or(false, |&(t, _)| t < recording_window)
|
||||
{
|
||||
self.rates.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
fn report_rates(&self) {
|
||||
if let Some((_, rates)) = self.rates.back() {
|
||||
log::info!("{}", rates.summary());
|
||||
log::debug!("{}", rates.detailed_summary());
|
||||
}
|
||||
}
|
||||
|
||||
fn report_counters(&self) {
|
||||
log::trace!("packet statistics: {:?}", &self.stats);
|
||||
let (summary_sent, summary_recv) = self.stats.summary();
|
||||
log::debug!("{}", summary_sent);
|
||||
log::debug!("{}", summary_recv);
|
||||
}
|
||||
|
||||
fn check_for_notable_events(&self) {
|
||||
let Some((_, latest_rates)) = self.rates.back() else {
|
||||
return;
|
||||
};
|
||||
|
||||
// If we get a burst of retransmissions
|
||||
// TODO: consider making this the number of retransmissions since last report instead.
|
||||
if latest_rates.retransmissions_queued > 0.0 {
|
||||
log::debug!(
|
||||
"retransmissions: {:.2} pkt/s",
|
||||
latest_rates.retransmissions_queued
|
||||
);
|
||||
|
||||
// Check what the number of retransmissions was during the recording window
|
||||
if let Some((_, start_stats)) = self.history.front() {
|
||||
let delta = self.stats.clone() - start_stats.clone();
|
||||
log::info!("retransmissions: {}", delta.retransmissions_queued,);
|
||||
} else {
|
||||
log::warn!("Unable to check retransmissions during recording window");
|
||||
}
|
||||
}
|
||||
|
||||
// IDEA: if there is a burst of acks, that could indicate tokio task starvation.
|
||||
}
|
||||
|
||||
pub(crate) async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) {
|
||||
log::debug!("Started PacketStatisticsControl with graceful shutdown support");
|
||||
|
||||
let report_interval = Duration::from_secs(PACKET_REPORT_INTERVAL_SECS);
|
||||
let mut report_interval = tokio::time::interval(report_interval);
|
||||
let snapshot_interval = Duration::from_millis(SNAPSHOT_INTERVAL_MS);
|
||||
let mut snapshot_interval = tokio::time::interval(snapshot_interval);
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
stats_event = self.stats_rx.recv() => match stats_event {
|
||||
Some(stats_event) => {
|
||||
log::trace!("PacketStatisticsControl: Received stats event");
|
||||
self.stats.handle_event(stats_event);
|
||||
},
|
||||
None => {
|
||||
log::trace!("PacketStatisticsControl: stopping since stats channel was closed");
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = snapshot_interval.tick() => {
|
||||
self.update_history();
|
||||
self.update_rates();
|
||||
}
|
||||
_ = report_interval.tick() => {
|
||||
self.report_rates();
|
||||
self.check_for_notable_events();
|
||||
self.report_counters();
|
||||
}
|
||||
_ = shutdown.recv_with_delay() => {
|
||||
log::trace!("PacketStatisticsControl: Received shutdown");
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
log::debug!("PacketStatisticsControl: Exiting");
|
||||
}
|
||||
|
||||
pub(crate) fn start_with_shutdown(mut self, task_client: nym_task::TaskClient) {
|
||||
spawn_future(async move {
|
||||
self.run_with_shutdown(task_client).await;
|
||||
})
|
||||
}
|
||||
}
|
||||
+12
-1
@@ -1,6 +1,8 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::packet_statistics_control::{PacketStatisticsEvent, PacketStatisticsReporter};
|
||||
|
||||
use super::action_controller::{AckActionSender, Action};
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
@@ -17,6 +19,7 @@ pub(super) struct AcknowledgementListener {
|
||||
ack_key: Arc<AckKey>,
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
action_sender: AckActionSender,
|
||||
stats_tx: PacketStatisticsReporter,
|
||||
}
|
||||
|
||||
impl AcknowledgementListener {
|
||||
@@ -24,16 +27,21 @@ impl AcknowledgementListener {
|
||||
ack_key: Arc<AckKey>,
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
action_sender: AckActionSender,
|
||||
stats_tx: PacketStatisticsReporter,
|
||||
) -> Self {
|
||||
AcknowledgementListener {
|
||||
ack_key,
|
||||
ack_receiver,
|
||||
action_sender,
|
||||
stats_tx,
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_ack(&mut self, ack_content: Vec<u8>) {
|
||||
trace!("Received an ack");
|
||||
self.stats_tx
|
||||
.report(PacketStatisticsEvent::AckReceived(ack_content.len()));
|
||||
|
||||
let frag_id = match recover_identifier(&self.ack_key, &ack_content)
|
||||
.map(FragmentIdentifier::try_from_bytes)
|
||||
{
|
||||
@@ -48,11 +56,14 @@ impl AcknowledgementListener {
|
||||
// because nothing was inserted in the first place
|
||||
if frag_id == COVER_FRAG_ID {
|
||||
trace!("Received an ack for a cover message - no need to do anything");
|
||||
self.stats_tx
|
||||
.report(PacketStatisticsEvent::CoverAckReceived(ack_content.len()));
|
||||
return;
|
||||
}
|
||||
|
||||
trace!("Received {} from the mix network", frag_id);
|
||||
|
||||
self.stats_tx
|
||||
.report(PacketStatisticsEvent::RealAckReceived(ack_content.len()));
|
||||
self.action_sender
|
||||
.unbounded_send(Action::new_remove(frag_id))
|
||||
.unwrap();
|
||||
|
||||
@@ -8,6 +8,7 @@ use self::{
|
||||
sent_notification_listener::SentNotificationListener,
|
||||
};
|
||||
use crate::client::inbound_messages::InputMessageReceiver;
|
||||
use crate::client::packet_statistics_control::PacketStatisticsReporter;
|
||||
use crate::client::real_messages_control::message_handler::MessageHandler;
|
||||
use crate::client::replies::reply_controller::ReplyControllerSender;
|
||||
use crate::spawn_future;
|
||||
@@ -208,6 +209,7 @@ where
|
||||
connectors: AcknowledgementControllerConnectors,
|
||||
message_handler: MessageHandler<R>,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
stats_tx: PacketStatisticsReporter,
|
||||
) -> Self {
|
||||
let (retransmission_tx, retransmission_rx) = mpsc::unbounded();
|
||||
|
||||
@@ -224,6 +226,7 @@ where
|
||||
Arc::clone(&ack_key),
|
||||
connectors.ack_receiver,
|
||||
connectors.ack_action_sender.clone(),
|
||||
stats_tx,
|
||||
);
|
||||
|
||||
// will listen for any new messages from the client
|
||||
|
||||
@@ -35,6 +35,8 @@ use crate::client::replies::reply_controller;
|
||||
use crate::config;
|
||||
pub(crate) use acknowledgement_control::{AckActionSender, Action};
|
||||
|
||||
use super::packet_statistics_control::PacketStatisticsReporter;
|
||||
|
||||
pub(crate) mod acknowledgement_control;
|
||||
pub(crate) mod message_handler;
|
||||
pub(crate) mod real_traffic_stream;
|
||||
@@ -143,6 +145,7 @@ impl RealMessagesController<OsRng> {
|
||||
reply_controller_receiver: ReplyControllerReceiver,
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
client_connection_rx: ConnectionCommandReceiver,
|
||||
stats_tx: PacketStatisticsReporter,
|
||||
) -> Self {
|
||||
let rng = OsRng;
|
||||
|
||||
@@ -181,6 +184,7 @@ impl RealMessagesController<OsRng> {
|
||||
ack_controller_connectors,
|
||||
message_handler.clone(),
|
||||
reply_controller_sender,
|
||||
stats_tx.clone(),
|
||||
);
|
||||
|
||||
let reply_control = ReplyController::new(
|
||||
@@ -199,6 +203,7 @@ impl RealMessagesController<OsRng> {
|
||||
topology_access,
|
||||
lane_queue_lengths,
|
||||
client_connection_rx,
|
||||
stats_tx,
|
||||
);
|
||||
|
||||
RealMessagesController {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use self::sending_delay_controller::SendingDelayController;
|
||||
use crate::client::mix_traffic::BatchMixMessageSender;
|
||||
use crate::client::packet_statistics_control::{PacketStatisticsEvent, PacketStatisticsReporter};
|
||||
use crate::client::real_messages_control::acknowledgement_control::SentPacketNotificationSender;
|
||||
use crate::client::topology_control::TopologyAccessor;
|
||||
use crate::client::transmission_buffer::TransmissionBuffer;
|
||||
@@ -113,6 +114,9 @@ where
|
||||
|
||||
/// Report queue lengths so that upstream can backoff sending data, and keep connections open.
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
|
||||
/// Channel used for sending statistics events to `PacketStatisticsControl`.
|
||||
stats_tx: PacketStatisticsReporter,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -171,6 +175,7 @@ where
|
||||
topology_access: TopologyAccessor,
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
client_connection_rx: ConnectionCommandReceiver,
|
||||
stats_tx: PacketStatisticsReporter,
|
||||
) -> Self {
|
||||
OutQueueControl {
|
||||
config,
|
||||
@@ -184,6 +189,7 @@ where
|
||||
transmission_buffer: TransmissionBuffer::new(),
|
||||
client_connection_rx,
|
||||
lane_queue_lengths,
|
||||
stats_tx,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,7 +220,7 @@ where
|
||||
async fn on_message(&mut self, next_message: StreamMessage) {
|
||||
trace!("created new message");
|
||||
|
||||
let (next_message, fragment_id) = match next_message {
|
||||
let (next_message, fragment_id, packet_size) = match next_message {
|
||||
StreamMessage::Cover => {
|
||||
let cover_traffic_packet_size = self.loop_cover_message_size();
|
||||
trace!("the next loop cover message will be put in a {cover_traffic_packet_size} packet");
|
||||
@@ -250,15 +256,28 @@ where
|
||||
"Somehow failed to generate a loop cover message with a valid topology",
|
||||
),
|
||||
None,
|
||||
cover_traffic_packet_size.size(),
|
||||
)
|
||||
}
|
||||
StreamMessage::Real(real_message) => {
|
||||
(real_message.mix_packet, real_message.fragment_id)
|
||||
let packet_size = real_message.packet_size();
|
||||
(
|
||||
real_message.mix_packet,
|
||||
real_message.fragment_id,
|
||||
packet_size,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = self.mix_tx.send(vec![next_message]).await {
|
||||
log::error!("Failed to send: {err}");
|
||||
} else {
|
||||
let event = if fragment_id.is_some() {
|
||||
PacketStatisticsEvent::RealPacketSent(packet_size)
|
||||
} else {
|
||||
PacketStatisticsEvent::CoverPacketSent(packet_size)
|
||||
};
|
||||
self.stats_tx.report(event);
|
||||
}
|
||||
|
||||
// notify ack controller about sending our message only after we actually managed to push it
|
||||
@@ -340,6 +359,28 @@ where
|
||||
let lane_length = self.transmission_buffer.lane_length(&lane);
|
||||
self.lane_queue_lengths.set(&lane, lane_length);
|
||||
|
||||
// This is the last step in the pipeline where we know the type of the message, so
|
||||
// lets count the number of retransmissions and reply surb messages sent here.
|
||||
let stat_event = match lane {
|
||||
TransmissionLane::General => None,
|
||||
TransmissionLane::ConnectionId(_) => None,
|
||||
TransmissionLane::ReplySurbRequest => {
|
||||
Some(PacketStatisticsEvent::ReplySurbRequestQueued)
|
||||
}
|
||||
TransmissionLane::AdditionalReplySurbs => {
|
||||
Some(PacketStatisticsEvent::AdditionalReplySurbRequestQueued)
|
||||
}
|
||||
TransmissionLane::Retransmission => Some(PacketStatisticsEvent::RetransmissionQueued),
|
||||
};
|
||||
if let Some(stat_event) = stat_event {
|
||||
self.stats_tx.report(stat_event);
|
||||
}
|
||||
// To avoid comparing apples to oranges when presenting the fraction of packets that are
|
||||
// retransmissions, we also need to keep track to the total number of real messages queued,
|
||||
// even though we also track the actual number of messages sent later in the pipeline.
|
||||
self.stats_tx
|
||||
.report(PacketStatisticsEvent::RealPacketQueued);
|
||||
|
||||
Some(real_next)
|
||||
}
|
||||
|
||||
@@ -433,6 +474,13 @@ where
|
||||
Poll::Ready(Some((real_messages, conn_id))) => {
|
||||
log::trace!("handling real_messages: size: {}", real_messages.len());
|
||||
|
||||
// This is the last step in the pipeline where we know the type of the message, so
|
||||
// lets count the number of retransmissions here.
|
||||
if conn_id == TransmissionLane::Retransmission {
|
||||
self.stats_tx
|
||||
.report(PacketStatisticsEvent::RetransmissionQueued);
|
||||
}
|
||||
|
||||
// First store what we got for the given connection id
|
||||
self.transmission_buffer.store(&conn_id, real_messages);
|
||||
let real_next = self.pop_next_message().expect("we just added one");
|
||||
@@ -471,10 +519,10 @@ where
|
||||
let mult = self.sending_delay_controller.current_multiplier();
|
||||
let delay = self.current_average_message_sending_delay().as_millis();
|
||||
let status_str = if self.config.traffic.disable_main_poisson_packet_distribution {
|
||||
format!("Status: {lanes} lanes, backlog: {backlog:.2} kiB ({packets}), no delay")
|
||||
format!("Packet backlog: {backlog:.2} kiB ({packets}), {lanes} lanes, no delay")
|
||||
} else {
|
||||
format!(
|
||||
"Status: {lanes} lanes, backlog: {backlog:.2} kiB ({packets}), avg delay: {delay}ms ({mult})"
|
||||
"Packet backlog: {backlog:.2} kiB ({packets}), {lanes} lanes, avg delay: {delay}ms ({mult})"
|
||||
)
|
||||
};
|
||||
if packets > 1000 {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::replies::reply_controller::ReplyControllerSender;
|
||||
use crate::client::replies::reply_storage::SentReplyKeys;
|
||||
use crate::client::{
|
||||
packet_statistics_control::{PacketStatisticsEvent, PacketStatisticsReporter},
|
||||
replies::{reply_controller::ReplyControllerSender, reply_storage::SentReplyKeys},
|
||||
};
|
||||
use crate::spawn_future;
|
||||
use futures::channel::mpsc;
|
||||
use futures::lock::Mutex;
|
||||
@@ -43,15 +45,33 @@ struct ReceivedMessagesBufferInner<R: MessageReceiver> {
|
||||
// but perhaps it should be changed to include timestamps of when the message was reconstructed
|
||||
// and every now and then remove ids older than X
|
||||
recently_reconstructed: HashSet<i32>,
|
||||
|
||||
stats_tx: PacketStatisticsReporter,
|
||||
}
|
||||
|
||||
impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
|
||||
fn recover_from_fragment(&mut self, fragment_data: &[u8]) -> Option<NymMessage> {
|
||||
fn recover_from_fragment(
|
||||
&mut self,
|
||||
fragment_data: &[u8],
|
||||
fragment_data_size: usize,
|
||||
) -> Option<NymMessage> {
|
||||
if nym_sphinx::cover::is_cover(fragment_data) {
|
||||
trace!("The message was a loop cover message! Skipping it");
|
||||
// NOTE: it's important to note that there is quite a bit of difference in size of
|
||||
// received and sent packets due to the sphinx layers being removed by the exit gateway
|
||||
// before it reaches the mixnet client.
|
||||
self.stats_tx
|
||||
.report(PacketStatisticsEvent::CoverPacketReceived(
|
||||
fragment_data_size,
|
||||
));
|
||||
return None;
|
||||
}
|
||||
|
||||
self.stats_tx
|
||||
.report(PacketStatisticsEvent::RealPacketReceived(
|
||||
fragment_data_size,
|
||||
));
|
||||
|
||||
let fragment = match self.message_receiver.recover_fragment(fragment_data) {
|
||||
Err(err) => {
|
||||
warn!("failed to recover fragment from raw data: {err}. The whole underlying message might be corrupted and unrecoverable!");
|
||||
@@ -103,15 +123,17 @@ impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
|
||||
reply_ciphertext: &mut [u8],
|
||||
reply_key: SurbEncryptionKey,
|
||||
) -> Result<Option<NymMessage>, MessageRecoveryError> {
|
||||
let reply_ciphertext_size = reply_ciphertext.len();
|
||||
// note: this performs decryption IN PLACE without extra allocation
|
||||
self.message_receiver
|
||||
.recover_plaintext_from_reply(reply_ciphertext, reply_key)?;
|
||||
let fragment_data = reply_ciphertext;
|
||||
|
||||
Ok(self.recover_from_fragment(fragment_data))
|
||||
Ok(self.recover_from_fragment(fragment_data, reply_ciphertext_size))
|
||||
}
|
||||
|
||||
fn process_received_regular_packet(&mut self, mut raw_fragment: Vec<u8>) -> Option<NymMessage> {
|
||||
let raw_fragment_size = raw_fragment.len();
|
||||
let fragment_data = match self.message_receiver.recover_plaintext_from_regular_packet(
|
||||
self.local_encryption_keypair.private_key(),
|
||||
&mut raw_fragment,
|
||||
@@ -123,7 +145,7 @@ impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
|
||||
Ok(frag_data) => frag_data,
|
||||
};
|
||||
|
||||
self.recover_from_fragment(fragment_data)
|
||||
self.recover_from_fragment(fragment_data, raw_fragment_size)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,6 +163,7 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
local_encryption_keypair: Arc<encryption::KeyPair>,
|
||||
reply_key_storage: SentReplyKeys,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
stats_tx: PacketStatisticsReporter,
|
||||
) -> Self {
|
||||
ReceivedMessagesBuffer {
|
||||
inner: Arc::new(Mutex::new(ReceivedMessagesBufferInner {
|
||||
@@ -149,6 +172,7 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
message_receiver: R::new(),
|
||||
message_sender: None,
|
||||
recently_reconstructed: HashSet::new(),
|
||||
stats_tx,
|
||||
})),
|
||||
reply_key_storage,
|
||||
reply_controller_sender,
|
||||
@@ -353,7 +377,7 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
};
|
||||
|
||||
if let Some(completed) = completed_message {
|
||||
info!("received {completed}");
|
||||
debug!("received {completed}");
|
||||
completed_messages.push(completed)
|
||||
}
|
||||
}
|
||||
@@ -480,11 +504,13 @@ impl<R: MessageReceiver + Clone + Send + 'static> ReceivedMessagesBufferControll
|
||||
mixnet_packet_receiver: MixnetMessageReceiver,
|
||||
reply_key_storage: SentReplyKeys,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
packet_statistics_reporter: PacketStatisticsReporter,
|
||||
) -> Self {
|
||||
let received_buffer = ReceivedMessagesBuffer::new(
|
||||
local_encryption_keypair,
|
||||
reply_key_storage,
|
||||
reply_controller_sender,
|
||||
packet_statistics_reporter,
|
||||
);
|
||||
|
||||
ReceivedMessagesBufferController {
|
||||
|
||||
@@ -65,7 +65,7 @@ pub async fn current_gateways<R: Rng>(
|
||||
.ok_or(ClientCoreError::ListOfNymApisIsEmpty)?;
|
||||
let client = nym_validator_client::client::NymApiClient::new(nym_api.clone());
|
||||
|
||||
log::trace!("Fetching list of gateways from: {nym_api}");
|
||||
log::debug!("Fetching list of gateways from: {nym_api}");
|
||||
|
||||
let gateways = client.get_cached_described_gateways().await?;
|
||||
log::debug!("Found {} gateways", gateways.len());
|
||||
|
||||
@@ -178,7 +178,7 @@ impl<T> From<PersistedGatewayDetails<T>> for GatewayDetails<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum GatewaySelectionSpecification<T = EmptyCustomDetails> {
|
||||
/// Uniformly choose a random remote gateway.
|
||||
UniformRemote { must_use_tls: bool },
|
||||
|
||||
@@ -42,6 +42,14 @@ pub struct Config {
|
||||
nyxd_config: nyxd::Config,
|
||||
}
|
||||
|
||||
impl TryFrom<NymNetworkDetails> for Config {
|
||||
type Error = ValidatorClientError;
|
||||
|
||||
fn try_from(value: NymNetworkDetails) -> Result<Self, Self::Error> {
|
||||
Config::try_from_nym_network_details(&value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn try_from_nym_network_details(
|
||||
details: &NymNetworkDetails,
|
||||
|
||||
@@ -5,21 +5,24 @@ use crate::nym_api::error::NymAPIError;
|
||||
use crate::nym_api::routes::{CORE_STATUS_COUNT, SINCE_ARG};
|
||||
use async_trait::async_trait;
|
||||
use http_api_client::{ApiClient, NO_PARAMS};
|
||||
use nym_api_requests::coconut::models::{
|
||||
EpochCredentialsResponse, IssuedCredentialResponse, IssuedCredentialsResponse,
|
||||
pub use nym_api_requests::{
|
||||
coconut::{
|
||||
models::{
|
||||
EpochCredentialsResponse, IssuedCredential, IssuedCredentialBody,
|
||||
IssuedCredentialResponse, IssuedCredentialsResponse,
|
||||
},
|
||||
BlindSignRequestBody, BlindedSignatureResponse, CredentialsRequestBody,
|
||||
VerifyCredentialBody, VerifyCredentialResponse,
|
||||
},
|
||||
models::{
|
||||
ComputeRewardEstParam, DescribedGateway, GatewayBondAnnotated, GatewayCoreStatusResponse,
|
||||
GatewayStatusReportResponse, GatewayUptimeHistoryResponse, InclusionProbabilityResponse,
|
||||
MixNodeBondAnnotated, MixnodeCoreStatusResponse, MixnodeStatusReportResponse,
|
||||
MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse,
|
||||
StakeSaturationResponse, UptimeResponse,
|
||||
},
|
||||
};
|
||||
use nym_api_requests::coconut::{
|
||||
BlindSignRequestBody, BlindedSignatureResponse, CredentialsRequestBody, VerifyCredentialBody,
|
||||
VerifyCredentialResponse,
|
||||
};
|
||||
use nym_api_requests::models::{
|
||||
ComputeRewardEstParam, DescribedGateway, GatewayBondAnnotated, GatewayCoreStatusResponse,
|
||||
GatewayStatusReportResponse, GatewayUptimeHistoryResponse, InclusionProbabilityResponse,
|
||||
MixNodeBondAnnotated, MixnodeCoreStatusResponse, MixnodeStatusReportResponse,
|
||||
MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse,
|
||||
StakeSaturationResponse, UptimeResponse,
|
||||
};
|
||||
use nym_coconut_dkg_common::types::EpochId;
|
||||
pub use nym_coconut_dkg_common::types::EpochId;
|
||||
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
|
||||
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId};
|
||||
use nym_name_service_common::response::NamesListResponse;
|
||||
|
||||
@@ -8,6 +8,8 @@ use cosmwasm_std::{Fraction, Uint128};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use std::ops::Div;
|
||||
use std::str::FromStr;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, Default, Debug, PartialEq, Eq)]
|
||||
pub struct MismatchedDenoms;
|
||||
@@ -126,6 +128,37 @@ impl From<CosmWasmCoin> for Coin {
|
||||
}
|
||||
}
|
||||
|
||||
// unfortunately cosmwasm didn't re-export this correct so we just redefine its
|
||||
#[derive(Error, Debug, PartialEq, Eq)]
|
||||
pub enum CoinFromStrError {
|
||||
#[error("Missing denominator")]
|
||||
MissingDenom,
|
||||
#[error("Missing amount or non-digit characters in amount")]
|
||||
MissingAmount,
|
||||
#[error("Invalid amount: {0}")]
|
||||
InvalidAmount(#[from] std::num::ParseIntError),
|
||||
}
|
||||
|
||||
impl FromStr for Coin {
|
||||
type Err = CoinFromStrError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let pos = s
|
||||
.find(|c: char| !c.is_ascii_digit())
|
||||
.ok_or(CoinFromStrError::MissingDenom)?;
|
||||
let (amount, denom) = s.split_at(pos);
|
||||
|
||||
if amount.is_empty() {
|
||||
return Err(CoinFromStrError::MissingAmount);
|
||||
}
|
||||
|
||||
Ok(Coin {
|
||||
amount: amount.parse::<u128>()?,
|
||||
denom: denom.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CoinConverter {
|
||||
type Target;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::collect_paged;
|
||||
@@ -7,14 +7,22 @@ use crate::nyxd::error::NyxdError;
|
||||
use crate::nyxd::CosmWasmClient;
|
||||
use async_trait::async_trait;
|
||||
use cosmrs::AccountId;
|
||||
use nym_coconut_dkg_common::dealer::{
|
||||
ContractDealing, DealerDetailsResponse, PagedDealerResponse, PagedDealingsResponse,
|
||||
};
|
||||
use nym_coconut_dkg_common::msg::QueryMsg as DkgQueryMsg;
|
||||
use nym_coconut_dkg_common::types::{DealerDetails, Epoch, EpochId, InitialReplacementData};
|
||||
use nym_coconut_dkg_common::verification_key::{ContractVKShare, PagedVKSharesResponse};
|
||||
use nym_coconut_dkg_common::types::ChunkIndex;
|
||||
use serde::Deserialize;
|
||||
|
||||
pub use nym_coconut_dkg_common::{
|
||||
dealer::{DealerDetailsResponse, PagedDealerResponse},
|
||||
dealing::{
|
||||
DealerDealingsStatusResponse, DealingChunkResponse, DealingChunkStatusResponse,
|
||||
DealingMetadataResponse, DealingStatusResponse,
|
||||
},
|
||||
msg::QueryMsg as DkgQueryMsg,
|
||||
types::{
|
||||
DealerDetails, DealingIndex, Epoch, EpochId, EpochState, InitialReplacementData, State,
|
||||
},
|
||||
verification_key::{ContractVKShare, PagedVKSharesResponse, VkShareResponse},
|
||||
};
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait DkgQueryClient {
|
||||
@@ -22,10 +30,16 @@ pub trait DkgQueryClient {
|
||||
where
|
||||
for<'a> T: Deserialize<'a>;
|
||||
|
||||
async fn get_state(&self) -> Result<State, NyxdError> {
|
||||
let request = DkgQueryMsg::GetState {};
|
||||
self.query_dkg_contract(request).await
|
||||
}
|
||||
|
||||
async fn get_current_epoch(&self) -> Result<Epoch, NyxdError> {
|
||||
let request = DkgQueryMsg::GetCurrentEpochState {};
|
||||
self.query_dkg_contract(request).await
|
||||
}
|
||||
|
||||
async fn get_current_epoch_threshold(&self) -> Result<Option<u64>, NyxdError> {
|
||||
let request = DkgQueryMsg::GetCurrentEpochThreshold {};
|
||||
self.query_dkg_contract(request).await
|
||||
@@ -64,17 +78,86 @@ pub trait DkgQueryClient {
|
||||
self.query_dkg_contract(request).await
|
||||
}
|
||||
|
||||
async fn get_dealings_paged(
|
||||
async fn get_dealings_metadata(
|
||||
&self,
|
||||
idx: u64,
|
||||
start_after: Option<String>,
|
||||
limit: Option<u32>,
|
||||
) -> Result<PagedDealingsResponse, NyxdError> {
|
||||
let request = DkgQueryMsg::GetDealing {
|
||||
idx,
|
||||
limit,
|
||||
start_after,
|
||||
epoch_id: EpochId,
|
||||
dealer: String,
|
||||
dealing_index: DealingIndex,
|
||||
) -> Result<DealingMetadataResponse, NyxdError> {
|
||||
let request = DkgQueryMsg::GetDealingsMetadata {
|
||||
epoch_id,
|
||||
dealer,
|
||||
dealing_index,
|
||||
};
|
||||
|
||||
self.query_dkg_contract(request).await
|
||||
}
|
||||
|
||||
async fn get_dealer_dealings_status(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
dealer: String,
|
||||
) -> Result<DealerDealingsStatusResponse, NyxdError> {
|
||||
let request = DkgQueryMsg::GetDealerDealingsStatus { epoch_id, dealer };
|
||||
|
||||
self.query_dkg_contract(request).await
|
||||
}
|
||||
|
||||
async fn get_dealing_status(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
dealer: String,
|
||||
dealing_index: DealingIndex,
|
||||
) -> Result<DealingStatusResponse, NyxdError> {
|
||||
let request = DkgQueryMsg::GetDealingStatus {
|
||||
epoch_id,
|
||||
dealer,
|
||||
dealing_index,
|
||||
};
|
||||
|
||||
self.query_dkg_contract(request).await
|
||||
}
|
||||
|
||||
async fn get_dealing_chunk_status(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
dealer: String,
|
||||
dealing_index: DealingIndex,
|
||||
chunk_index: ChunkIndex,
|
||||
) -> Result<DealingChunkStatusResponse, NyxdError> {
|
||||
let request = DkgQueryMsg::GetDealingChunkStatus {
|
||||
epoch_id,
|
||||
dealer,
|
||||
dealing_index,
|
||||
chunk_index,
|
||||
};
|
||||
|
||||
self.query_dkg_contract(request).await
|
||||
}
|
||||
|
||||
async fn get_dealing_chunk(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
dealer: String,
|
||||
dealing_index: DealingIndex,
|
||||
chunk_index: ChunkIndex,
|
||||
) -> Result<DealingChunkResponse, NyxdError> {
|
||||
let request = DkgQueryMsg::GetDealingChunk {
|
||||
epoch_id,
|
||||
dealer,
|
||||
dealing_index,
|
||||
chunk_index,
|
||||
};
|
||||
|
||||
self.query_dkg_contract(request).await
|
||||
}
|
||||
|
||||
async fn get_vk_share(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
owner: String,
|
||||
) -> Result<VkShareResponse, NyxdError> {
|
||||
let request = DkgQueryMsg::GetVerificationKey { epoch_id, owner };
|
||||
self.query_dkg_contract(request).await
|
||||
}
|
||||
|
||||
@@ -91,6 +174,11 @@ pub trait DkgQueryClient {
|
||||
};
|
||||
self.query_dkg_contract(request).await
|
||||
}
|
||||
|
||||
async fn get_contract_cw2_version(&self) -> Result<cw2::ContractVersion, NyxdError> {
|
||||
self.query_dkg_contract(DkgQueryMsg::GetCW2ContractVersion {})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
// extension trait to the query client to deal with the paged queries
|
||||
@@ -106,10 +194,6 @@ pub trait PagedDkgQueryClient: DkgQueryClient {
|
||||
collect_paged!(self, get_past_dealers_paged, dealers)
|
||||
}
|
||||
|
||||
async fn get_all_epoch_dealings(&self, idx: u64) -> Result<Vec<ContractDealing>, NyxdError> {
|
||||
collect_paged!(self, get_dealings_paged, dealings, idx)
|
||||
}
|
||||
|
||||
async fn get_all_verification_key_shares(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
@@ -143,6 +227,7 @@ where
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::nyxd::contract_traits::tests::IgnoreValue;
|
||||
use nym_coconut_dkg_common::msg::QueryMsg;
|
||||
|
||||
// it's enough that this compiles and clippy is happy about it
|
||||
#[allow(dead_code)]
|
||||
@@ -151,6 +236,7 @@ mod tests {
|
||||
msg: DkgQueryMsg,
|
||||
) {
|
||||
match msg {
|
||||
DkgQueryMsg::GetState {} => client.get_state().ignore(),
|
||||
DkgQueryMsg::GetCurrentEpochState {} => client.get_current_epoch().ignore(),
|
||||
DkgQueryMsg::GetCurrentEpochThreshold {} => {
|
||||
client.get_current_epoch_threshold().ignore()
|
||||
@@ -165,11 +251,42 @@ mod tests {
|
||||
DkgQueryMsg::GetPastDealers { limit, start_after } => {
|
||||
client.get_past_dealers_paged(start_after, limit).ignore()
|
||||
}
|
||||
DkgQueryMsg::GetDealing {
|
||||
idx,
|
||||
limit,
|
||||
start_after,
|
||||
} => client.get_dealings_paged(idx, start_after, limit).ignore(),
|
||||
DkgQueryMsg::GetDealingStatus {
|
||||
epoch_id,
|
||||
dealer,
|
||||
dealing_index,
|
||||
} => client
|
||||
.get_dealing_status(epoch_id, dealer, dealing_index)
|
||||
.ignore(),
|
||||
DkgQueryMsg::GetDealingsMetadata {
|
||||
epoch_id,
|
||||
dealer,
|
||||
dealing_index,
|
||||
} => client
|
||||
.get_dealings_metadata(epoch_id, dealer, dealing_index)
|
||||
.ignore(),
|
||||
QueryMsg::GetDealerDealingsStatus { epoch_id, dealer } => {
|
||||
client.get_dealer_dealings_status(epoch_id, dealer).ignore()
|
||||
}
|
||||
DkgQueryMsg::GetDealingChunkStatus {
|
||||
epoch_id,
|
||||
dealer,
|
||||
dealing_index,
|
||||
chunk_index,
|
||||
} => client
|
||||
.get_dealing_chunk_status(epoch_id, dealer, dealing_index, chunk_index)
|
||||
.ignore(),
|
||||
DkgQueryMsg::GetDealingChunk {
|
||||
epoch_id,
|
||||
dealer,
|
||||
dealing_index,
|
||||
chunk_index,
|
||||
} => client
|
||||
.get_dealing_chunk(epoch_id, dealer, dealing_index, chunk_index)
|
||||
.ignore(),
|
||||
DkgQueryMsg::GetVerificationKey { epoch_id, owner } => {
|
||||
client.get_vk_share(epoch_id, owner).ignore()
|
||||
}
|
||||
DkgQueryMsg::GetVerificationKeys {
|
||||
epoch_id,
|
||||
limit,
|
||||
@@ -177,6 +294,7 @@ mod tests {
|
||||
} => client
|
||||
.get_vk_shares_paged(epoch_id, start_after, limit)
|
||||
.ignore(),
|
||||
DkgQueryMsg::GetCW2ContractVersion {} => client.get_contract_cw2_version().ignore(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+52
-20
@@ -8,11 +8,11 @@ use crate::nyxd::{Coin, Fee, SigningCosmWasmClient};
|
||||
use crate::signing::signer::OfflineSigner;
|
||||
use async_trait::async_trait;
|
||||
use cosmrs::AccountId;
|
||||
use cosmwasm_std::Addr;
|
||||
use nym_coconut_dkg_common::dealing::{DealingChunkInfo, PartialContractDealing};
|
||||
use nym_coconut_dkg_common::msg::ExecuteMsg as DkgExecuteMsg;
|
||||
use nym_coconut_dkg_common::types::EncodedBTEPublicKeyWithProof;
|
||||
use nym_coconut_dkg_common::types::{DealingIndex, EncodedBTEPublicKeyWithProof};
|
||||
use nym_coconut_dkg_common::verification_key::VerificationKeyShare;
|
||||
use nym_contracts_common::dealings::ContractSafeBytes;
|
||||
use nym_contracts_common::IdentityKey;
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
@@ -25,6 +25,13 @@ pub trait DkgSigningClient {
|
||||
funds: Vec<Coin>,
|
||||
) -> Result<ExecuteResult, NyxdError>;
|
||||
|
||||
async fn initiate_dkg(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> {
|
||||
let req = DkgExecuteMsg::InitiateDkg {};
|
||||
|
||||
self.execute_dkg_contract(fee, req, "initiating the DKG".to_string(), vec![])
|
||||
.await
|
||||
}
|
||||
|
||||
async fn advance_dkg_epoch_state(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> {
|
||||
let req = DkgExecuteMsg::AdvanceEpochState {};
|
||||
|
||||
@@ -42,12 +49,14 @@ pub trait DkgSigningClient {
|
||||
async fn register_dealer(
|
||||
&self,
|
||||
bte_key: EncodedBTEPublicKeyWithProof,
|
||||
identity_key: IdentityKey,
|
||||
announce_address: String,
|
||||
resharing: bool,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
let req = DkgExecuteMsg::RegisterDealer {
|
||||
bte_key_with_proof: bte_key,
|
||||
identity_key,
|
||||
announce_address,
|
||||
resharing,
|
||||
};
|
||||
@@ -56,18 +65,32 @@ pub trait DkgSigningClient {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn submit_dealing_bytes(
|
||||
async fn submit_dealing_metadata(
|
||||
&self,
|
||||
dealing_bytes: ContractSafeBytes,
|
||||
dealing_index: DealingIndex,
|
||||
chunks: Vec<DealingChunkInfo>,
|
||||
resharing: bool,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
let req = DkgExecuteMsg::CommitDealing {
|
||||
dealing_bytes,
|
||||
let req = DkgExecuteMsg::CommitDealingsMetadata {
|
||||
dealing_index,
|
||||
chunks,
|
||||
resharing,
|
||||
};
|
||||
|
||||
self.execute_dkg_contract(fee, req, "dealing commitment".to_string(), vec![])
|
||||
self.execute_dkg_contract(fee, req, "dealing metadata commitment".to_string(), vec![])
|
||||
.await
|
||||
}
|
||||
|
||||
async fn submit_dealing_chunk(
|
||||
&self,
|
||||
chunk: PartialContractDealing,
|
||||
resharing: bool,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
let req = DkgExecuteMsg::CommitDealingsChunk { chunk, resharing };
|
||||
|
||||
self.execute_dkg_contract(fee, req, "dealing chunk commitment".to_string(), vec![])
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -94,9 +117,10 @@ pub trait DkgSigningClient {
|
||||
resharing: bool,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
// the call to unchecked is fine as we're converting from pre-validated `AccountId`
|
||||
let owner = Addr::unchecked(owner.to_string());
|
||||
let req = DkgExecuteMsg::VerifyVerificationKeyShare { owner, resharing };
|
||||
let req = DkgExecuteMsg::VerifyVerificationKeyShare {
|
||||
owner: owner.to_string(),
|
||||
resharing,
|
||||
};
|
||||
|
||||
self.execute_dkg_contract(
|
||||
fee,
|
||||
@@ -146,28 +170,36 @@ mod tests {
|
||||
msg: DkgExecuteMsg,
|
||||
) {
|
||||
match msg {
|
||||
DkgExecuteMsg::InitiateDkg {} => client.initiate_dkg(None).ignore(),
|
||||
DkgExecuteMsg::RegisterDealer {
|
||||
bte_key_with_proof,
|
||||
identity_key,
|
||||
announce_address,
|
||||
resharing,
|
||||
} => client
|
||||
.register_dealer(bte_key_with_proof, announce_address, resharing, None)
|
||||
.register_dealer(
|
||||
bte_key_with_proof,
|
||||
identity_key,
|
||||
announce_address,
|
||||
resharing,
|
||||
None,
|
||||
)
|
||||
.ignore(),
|
||||
DkgExecuteMsg::CommitDealing {
|
||||
dealing_bytes,
|
||||
DkgExecuteMsg::CommitDealingsMetadata {
|
||||
dealing_index,
|
||||
chunks,
|
||||
resharing,
|
||||
} => client
|
||||
.submit_dealing_bytes(dealing_bytes, resharing, None)
|
||||
.submit_dealing_metadata(dealing_index, chunks, resharing, None)
|
||||
.ignore(),
|
||||
DkgExecuteMsg::CommitDealingsChunk { chunk, resharing } => {
|
||||
client.submit_dealing_chunk(chunk, resharing, None).ignore()
|
||||
}
|
||||
DkgExecuteMsg::CommitVerificationKeyShare { share, resharing } => client
|
||||
.submit_verification_key_share(share, resharing, None)
|
||||
.ignore(),
|
||||
DkgExecuteMsg::VerifyVerificationKeyShare { owner, resharing } => client
|
||||
.verify_verification_key_share(
|
||||
&owner.into_string().parse().unwrap(),
|
||||
resharing,
|
||||
None,
|
||||
)
|
||||
.verify_verification_key_share(&owner.parse().unwrap(), resharing, None)
|
||||
.ignore(),
|
||||
DkgExecuteMsg::SurpassedThreshold {} => client.surpass_threshold(None).ignore(),
|
||||
DkgExecuteMsg::AdvanceEpochState {} => client.advance_dkg_epoch_state(None).ignore(),
|
||||
|
||||
@@ -8,26 +8,26 @@ use std::str::FromStr;
|
||||
// TODO: all of those could/should be derived via a macro
|
||||
|
||||
// query clients
|
||||
mod coconut_bandwidth_query_client;
|
||||
mod dkg_query_client;
|
||||
mod ephemera_query_client;
|
||||
mod group_query_client;
|
||||
mod mixnet_query_client;
|
||||
mod multisig_query_client;
|
||||
mod name_service_query_client;
|
||||
mod sp_directory_query_client;
|
||||
mod vesting_query_client;
|
||||
pub mod coconut_bandwidth_query_client;
|
||||
pub mod dkg_query_client;
|
||||
pub mod ephemera_query_client;
|
||||
pub mod group_query_client;
|
||||
pub mod mixnet_query_client;
|
||||
pub mod multisig_query_client;
|
||||
pub mod name_service_query_client;
|
||||
pub mod sp_directory_query_client;
|
||||
pub mod vesting_query_client;
|
||||
|
||||
// signing clients
|
||||
mod coconut_bandwidth_signing_client;
|
||||
mod dkg_signing_client;
|
||||
mod ephemera_signing_client;
|
||||
mod group_signing_client;
|
||||
mod mixnet_signing_client;
|
||||
mod multisig_signing_client;
|
||||
mod name_service_signing_client;
|
||||
mod sp_directory_signing_client;
|
||||
mod vesting_signing_client;
|
||||
pub mod coconut_bandwidth_signing_client;
|
||||
pub mod dkg_signing_client;
|
||||
pub mod ephemera_signing_client;
|
||||
pub mod group_signing_client;
|
||||
pub mod mixnet_signing_client;
|
||||
pub mod multisig_signing_client;
|
||||
pub mod name_service_signing_client;
|
||||
pub mod sp_directory_signing_client;
|
||||
pub mod vesting_signing_client;
|
||||
|
||||
// re-export query traits
|
||||
pub use coconut_bandwidth_query_client::{
|
||||
|
||||
+4
-4
@@ -52,10 +52,6 @@ use wasmtimer::tokio::sleep;
|
||||
pub const DEFAULT_BROADCAST_POLLING_RATE: Duration = Duration::from_secs(4);
|
||||
pub const DEFAULT_BROADCAST_TIMEOUT: Duration = Duration::from_secs(60);
|
||||
|
||||
#[cfg(feature = "http-client")]
|
||||
#[async_trait]
|
||||
impl CosmWasmClient for cosmrs::rpc::HttpClient {}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait CosmWasmClient: TendermintRpcClient {
|
||||
@@ -522,3 +518,7 @@ pub trait CosmWasmClient: TendermintRpcClient {
|
||||
res.try_into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl<T> CosmWasmClient for T where T: TendermintRpcClient {}
|
||||
|
||||
+1
-1
@@ -425,7 +425,7 @@ where
|
||||
amount: amount.into_iter().map(Into::into).collect(),
|
||||
}
|
||||
.to_any()
|
||||
.map_err(|_| NyxdError::SerializationError("MsgExecuteContract".to_owned()))
|
||||
.map_err(|_| NyxdError::SerializationError("MsgSend".to_owned()))
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::nyxd::cosmwasm_client::client_traits::{CosmWasmClient, SigningCosmWasmClient};
|
||||
use crate::nyxd::cosmwasm_client::client_traits::SigningCosmWasmClient;
|
||||
use crate::nyxd::error::NyxdError;
|
||||
use crate::nyxd::{Config, GasPrice, Hash, Height};
|
||||
use crate::rpc::TendermintRpcClient;
|
||||
@@ -26,6 +26,7 @@ use cosmrs::rpc::{HttpClient, HttpClientUrl};
|
||||
pub mod client_traits;
|
||||
mod helpers;
|
||||
pub mod logs;
|
||||
pub mod module_traits;
|
||||
pub mod types;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -329,14 +330,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C, S> CosmWasmClient for MaybeSigningClient<C, S>
|
||||
where
|
||||
C: TendermintRpcClient + Send + Sync,
|
||||
S: Send + Sync,
|
||||
{
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C, S> SigningCosmWasmClient for MaybeSigningClient<C, S>
|
||||
where
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod slashing;
|
||||
pub mod staking;
|
||||
|
||||
pub use staking::query::StakingQueryClient;
|
||||
// pub use slashing::query
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod query;
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod query;
|
||||
|
||||
pub use cosmrs::staking::{
|
||||
QueryHistoricalInfoResponse, QueryValidatorResponse, QueryValidatorsResponse, Validator,
|
||||
};
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::{QueryHistoricalInfoResponse, QueryValidatorResponse, QueryValidatorsResponse};
|
||||
use crate::nyxd::error::NyxdError;
|
||||
use crate::nyxd::{CosmWasmClient, PageRequest};
|
||||
use async_trait::async_trait;
|
||||
use cosmrs::proto::cosmos::staking::v1beta1::{
|
||||
QueryHistoricalInfoRequest as ProtoQueryHistoricalInfoRequest,
|
||||
QueryHistoricalInfoResponse as ProtoQueryHistoricalInfoResponse,
|
||||
QueryValidatorRequest as ProtoQueryValidatorRequest,
|
||||
QueryValidatorResponse as ProtoQueryValidatorResponse,
|
||||
QueryValidatorsRequest as ProtoQueryValidatorsRequest,
|
||||
QueryValidatorsResponse as ProtoQueryValidatorsResponse,
|
||||
};
|
||||
use cosmrs::staking::{QueryHistoricalInfoRequest, QueryValidatorRequest, QueryValidatorsRequest};
|
||||
use cosmrs::AccountId;
|
||||
|
||||
// TODO: change trait restriction from `CosmWasmClient` to `TendermintRpcClient`
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait StakingQueryClient: CosmWasmClient {
|
||||
async fn historical_info(&self, height: i64) -> Result<QueryHistoricalInfoResponse, NyxdError> {
|
||||
let path = Some("/cosmos.staking.v1beta1.Query/HistoricalInfo".to_owned());
|
||||
|
||||
let req = QueryHistoricalInfoRequest { height };
|
||||
|
||||
let res = self
|
||||
.make_abci_query::<ProtoQueryHistoricalInfoRequest, ProtoQueryHistoricalInfoResponse>(
|
||||
path,
|
||||
req.into(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(res.try_into()?)
|
||||
}
|
||||
|
||||
async fn validator(
|
||||
&self,
|
||||
validator_addr: AccountId,
|
||||
) -> Result<QueryValidatorResponse, NyxdError> {
|
||||
let path = Some("/cosmos.staking.v1beta1.Query/Validator".to_owned());
|
||||
|
||||
let req = QueryValidatorRequest { validator_addr };
|
||||
|
||||
let res = self
|
||||
.make_abci_query::<ProtoQueryValidatorRequest, ProtoQueryValidatorResponse>(
|
||||
path,
|
||||
req.into(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(res.try_into()?)
|
||||
}
|
||||
|
||||
async fn validators(
|
||||
&self,
|
||||
status: String,
|
||||
pagination: Option<PageRequest>,
|
||||
) -> Result<QueryValidatorsResponse, NyxdError> {
|
||||
let path = Some("/cosmos.staking.v1beta1.Query/Validators".to_owned());
|
||||
|
||||
let req = QueryValidatorsRequest { status, pagination };
|
||||
|
||||
let res = self
|
||||
.make_abci_query::<ProtoQueryValidatorsRequest, ProtoQueryValidatorsResponse>(
|
||||
path,
|
||||
req.into(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(res.try_into()?)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl<T> StakingQueryClient for T where T: CosmWasmClient {}
|
||||
@@ -29,23 +29,30 @@ use tendermint_rpc::endpoint::*;
|
||||
use tendermint_rpc::{Error as TendermintRpcError, Order};
|
||||
use url::Url;
|
||||
|
||||
pub use crate::nyxd::cosmwasm_client::client_traits::{CosmWasmClient, SigningCosmWasmClient};
|
||||
pub use crate::nyxd::fee::Fee;
|
||||
pub use crate::nyxd::{
|
||||
cosmwasm_client::{
|
||||
client_traits::{CosmWasmClient, SigningCosmWasmClient},
|
||||
module_traits::{self, StakingQueryClient},
|
||||
},
|
||||
fee::Fee,
|
||||
};
|
||||
pub use crate::rpc::TendermintRpcClient;
|
||||
pub use coin::Coin;
|
||||
pub use cosmrs::bank::MsgSend;
|
||||
pub use cosmrs::tendermint::abci::{
|
||||
response::DeliverTx, types::ExecTxResult, Event, EventAttribute,
|
||||
pub use cosmrs::{
|
||||
bank::MsgSend,
|
||||
bip32,
|
||||
crypto::PublicKey,
|
||||
query::{PageRequest, PageResponse},
|
||||
tendermint::{
|
||||
abci::{response::DeliverTx, types::ExecTxResult, Event, EventAttribute},
|
||||
block::Height,
|
||||
hash::{self, Algorithm, Hash},
|
||||
validator::Info as TendermintValidatorInfo,
|
||||
Time as TendermintTime,
|
||||
},
|
||||
tx::{self, Msg},
|
||||
AccountId, Any, Coin as CosmosCoin, Denom, Gas,
|
||||
};
|
||||
pub use cosmrs::tendermint::block::Height;
|
||||
pub use cosmrs::tendermint::hash::{self, Algorithm, Hash};
|
||||
pub use cosmrs::tendermint::validator::Info as TendermintValidatorInfo;
|
||||
pub use cosmrs::tendermint::Time as TendermintTime;
|
||||
pub use cosmrs::tx::Msg;
|
||||
pub use cosmrs::tx::{self};
|
||||
pub use cosmrs::Coin as CosmosCoin;
|
||||
pub use cosmrs::Gas;
|
||||
pub use cosmrs::{bip32, AccountId, Denom};
|
||||
pub use cosmwasm_std::Coin as CosmWasmCoin;
|
||||
pub use cw2;
|
||||
pub use cw3;
|
||||
@@ -55,9 +62,8 @@ pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment};
|
||||
pub use tendermint_rpc::{
|
||||
endpoint::{tx::Response as TxResponse, validators::Response as ValidatorResponse},
|
||||
query::Query,
|
||||
Paging,
|
||||
Paging, Request, Response, SimpleRequest,
|
||||
};
|
||||
pub use tendermint_rpc::{Request, Response, SimpleRequest};
|
||||
|
||||
#[cfg(feature = "http-client")]
|
||||
use crate::http_client;
|
||||
@@ -97,6 +103,14 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<NymNetworkDetails> for Config {
|
||||
type Error = NyxdError;
|
||||
|
||||
fn try_from(value: NymNetworkDetails) -> Result<Self, Self::Error> {
|
||||
Config::try_from_nym_network_details(&value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NyxdClient<C, S = NoSigner> {
|
||||
client: MaybeSigningClient<C, S>,
|
||||
@@ -728,7 +742,7 @@ where
|
||||
where
|
||||
H: Into<Height> + Send,
|
||||
{
|
||||
self.client.validators(height, paging).await
|
||||
TendermintRpcClient::validators(&self.client, height, paging).await
|
||||
}
|
||||
|
||||
async fn latest_consensus_params(
|
||||
@@ -803,14 +817,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C, S> CosmWasmClient for NyxdClient<C, S>
|
||||
where
|
||||
C: TendermintRpcClient + Send + Sync,
|
||||
S: Send + Sync,
|
||||
{
|
||||
}
|
||||
|
||||
impl<C, S> OfflineSigner for NyxdClient<C, S>
|
||||
where
|
||||
S: OfflineSigner,
|
||||
|
||||
@@ -13,8 +13,9 @@ use error::CoconutInterfaceError;
|
||||
pub use nym_coconut::{
|
||||
aggregate_signature_shares, aggregate_verification_keys, blind_sign, hash_to_scalar,
|
||||
prepare_blind_sign, prove_bandwidth_credential, Attribute, Base58, BlindSignRequest,
|
||||
BlindedSignature, Bytable, CoconutError, KeyPair, Parameters, PrivateAttribute,
|
||||
PublicAttribute, Signature, SignatureShare, Theta, VerificationKey,
|
||||
BlindedSerialNumber, BlindedSignature, Bytable, CoconutError, KeyPair, Parameters,
|
||||
PrivateAttribute, PublicAttribute, SecretKey, Signature, SignatureShare, Theta,
|
||||
VerificationKey,
|
||||
};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Getters, CopyGetters, Clone, PartialEq, Eq)]
|
||||
@@ -49,7 +50,11 @@ impl Credential {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn blinded_serial_number(&self) -> String {
|
||||
pub fn blinded_serial_number(&self) -> &BlindedSerialNumber {
|
||||
&self.theta.blinded_serial_number
|
||||
}
|
||||
|
||||
pub fn blinded_serial_number_bs58(&self) -> String {
|
||||
self.theta.blinded_serial_number_bs58()
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use clap::Parser;
|
||||
use log::{debug, info};
|
||||
use nym_coconut_dkg_common::dealing::DEFAULT_DEALINGS;
|
||||
use std::str::FromStr;
|
||||
|
||||
use nym_coconut_dkg_common::msg::InstantiateMsg;
|
||||
@@ -93,6 +94,7 @@ pub async fn generate(args: Args) {
|
||||
multisig_addr: multisig_addr.to_string(),
|
||||
time_configuration: Some(time_configuration),
|
||||
mix_denom,
|
||||
key_size: DEFAULT_DEALINGS as u32,
|
||||
};
|
||||
|
||||
debug!("instantiate_msg: {:?}", instantiate_msg);
|
||||
|
||||
@@ -73,7 +73,7 @@ where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let path = path.as_ref();
|
||||
log::debug!("trying to save config file to {}", path.display());
|
||||
log::info!("saving config file to {}", path.display());
|
||||
|
||||
if let Some(parent) = path.parent() {
|
||||
create_dir_all(parent)?;
|
||||
|
||||
@@ -10,6 +10,8 @@ license.workspace = true
|
||||
cosmwasm-schema = { workspace = true }
|
||||
cosmwasm-std = { workspace = true }
|
||||
cw-utils = { workspace = true }
|
||||
cw2 = { workspace = true }
|
||||
cw4 = { workspace = true }
|
||||
|
||||
contracts-common = { path = "../contracts-common", package = "nym-contracts-common" }
|
||||
nym-multisig-contract-common = { path = "../multisig-contract" }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::types::{ContractSafeBytes, EncodedBTEPublicKeyWithProof, NodeIndex};
|
||||
use crate::types::{EncodedBTEPublicKeyWithProof, NodeIndex};
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::Addr;
|
||||
|
||||
@@ -9,6 +9,7 @@ use cosmwasm_std::Addr;
|
||||
pub struct DealerDetails {
|
||||
pub address: Addr,
|
||||
pub bte_public_key_with_proof: EncodedBTEPublicKeyWithProof,
|
||||
pub ed25519_identity: String,
|
||||
pub announce_address: String,
|
||||
pub assigned_index: NodeIndex,
|
||||
}
|
||||
@@ -64,38 +65,3 @@ impl PagedDealerResponse {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct ContractDealing {
|
||||
pub dealing: ContractSafeBytes,
|
||||
pub dealer: Addr,
|
||||
}
|
||||
|
||||
impl ContractDealing {
|
||||
pub fn new(dealing: ContractSafeBytes, dealer: Addr) -> Self {
|
||||
ContractDealing { dealing, dealer }
|
||||
}
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct PagedDealingsResponse {
|
||||
pub dealings: Vec<ContractDealing>,
|
||||
pub per_page: usize,
|
||||
|
||||
/// Field indicating paging information for the following queries if the caller wishes to get further entries.
|
||||
pub start_next_after: Option<Addr>,
|
||||
}
|
||||
|
||||
impl PagedDealingsResponse {
|
||||
pub fn new(
|
||||
dealings: Vec<ContractDealing>,
|
||||
per_page: usize,
|
||||
start_next_after: Option<Addr>,
|
||||
) -> Self {
|
||||
PagedDealingsResponse {
|
||||
dealings,
|
||||
per_page,
|
||||
start_next_after,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::types::{ChunkIndex, DealingIndex, EpochId, PartialContractDealingData};
|
||||
use contracts_common::dealings::ContractSafeBytes;
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::Addr;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
|
||||
/// Defines the maximum size of a dealing chunk. Currently set to 2kB
|
||||
pub const MAX_DEALING_CHUNK_SIZE: usize = 2048;
|
||||
|
||||
/// Defines the maximum size of a full dealing.
|
||||
/// Currently set to 100kB (which is enough for a dealing created for 100 parties)
|
||||
pub const MAX_DEALING_SIZE: usize = 102400;
|
||||
|
||||
pub const MAX_DEALING_CHUNKS: usize = MAX_DEALING_SIZE / MAX_DEALING_CHUNK_SIZE;
|
||||
|
||||
// 2 public attributes, 2 private attributes, 1 fixed for coconut credential
|
||||
pub const DEFAULT_DEALINGS: usize = 2 + 2 + 1;
|
||||
|
||||
pub fn chunk_dealing(
|
||||
dealing_index: DealingIndex,
|
||||
dealing_bytes: Vec<u8>,
|
||||
chunk_size: usize,
|
||||
) -> HashMap<ChunkIndex, PartialContractDealing> {
|
||||
let mut chunks = HashMap::new();
|
||||
for (chunk_index, chunk) in dealing_bytes.chunks(chunk_size).enumerate() {
|
||||
let chunk = PartialContractDealing {
|
||||
dealing_index,
|
||||
chunk_index: chunk_index as ChunkIndex,
|
||||
data: ContractSafeBytes(chunk.to_vec()),
|
||||
};
|
||||
chunks.insert(chunk_index as ChunkIndex, chunk);
|
||||
}
|
||||
|
||||
chunks
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
#[derive(Copy)]
|
||||
pub struct DealingChunkInfo {
|
||||
pub size: u64,
|
||||
}
|
||||
|
||||
impl DealingChunkInfo {
|
||||
pub fn new(size: usize) -> Self {
|
||||
DealingChunkInfo { size: size as u64 }
|
||||
}
|
||||
|
||||
pub fn construct(dealing_len: usize, chunk_size: usize) -> Vec<Self> {
|
||||
let (full_chunks, overflow) = (dealing_len / chunk_size, dealing_len % chunk_size);
|
||||
|
||||
let mut chunks = Vec::new();
|
||||
for _ in 0..full_chunks {
|
||||
chunks.push(DealingChunkInfo::new(chunk_size));
|
||||
}
|
||||
|
||||
if overflow != 0 {
|
||||
chunks.push(DealingChunkInfo::new(overflow));
|
||||
}
|
||||
|
||||
chunks
|
||||
}
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
#[derive(Copy)]
|
||||
pub struct SubmittedChunk {
|
||||
pub info: DealingChunkInfo,
|
||||
|
||||
pub status: ChunkSubmissionStatus,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
#[derive(Default, Copy)]
|
||||
pub struct ChunkSubmissionStatus {
|
||||
// this field is updated by the contract itself to indicate when this particular chunk has been received
|
||||
pub submission_height: Option<u64>,
|
||||
}
|
||||
|
||||
impl ChunkSubmissionStatus {
|
||||
pub fn submitted(&self) -> bool {
|
||||
self.submission_height.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DealingChunkInfo> for SubmittedChunk {
|
||||
fn from(value: DealingChunkInfo) -> Self {
|
||||
SubmittedChunk::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl SubmittedChunk {
|
||||
pub fn new(info: DealingChunkInfo) -> Self {
|
||||
SubmittedChunk {
|
||||
info,
|
||||
status: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn submitted(&self) -> bool {
|
||||
self.status.submitted()
|
||||
}
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct DealingMetadata {
|
||||
pub dealing_index: DealingIndex,
|
||||
|
||||
pub submitted_chunks: BTreeMap<ChunkIndex, SubmittedChunk>,
|
||||
}
|
||||
|
||||
impl DealingMetadata {
|
||||
pub fn new(dealing_index: DealingIndex, chunks: Vec<DealingChunkInfo>) -> Self {
|
||||
DealingMetadata {
|
||||
dealing_index,
|
||||
submitted_chunks: chunks
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(id, chunk)| (id as ChunkIndex, chunk.into()))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_complete(&self) -> bool {
|
||||
self.submitted_chunks.values().all(|c| c.submitted())
|
||||
}
|
||||
|
||||
pub fn total_size(&self) -> usize {
|
||||
self.submitted_chunks
|
||||
.values()
|
||||
.map(|c| c.info.size as usize)
|
||||
.sum()
|
||||
}
|
||||
|
||||
pub fn submission_statuses(&self) -> BTreeMap<ChunkIndex, ChunkSubmissionStatus> {
|
||||
self.submitted_chunks
|
||||
.iter()
|
||||
.map(|(id, c)| (*id, c.status))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct PartialContractDealing {
|
||||
pub dealing_index: DealingIndex,
|
||||
pub chunk_index: ChunkIndex,
|
||||
pub data: PartialContractDealingData,
|
||||
}
|
||||
|
||||
impl PartialContractDealing {
|
||||
pub fn new(
|
||||
dealing_index: DealingIndex,
|
||||
chunk_index: ChunkIndex,
|
||||
data: PartialContractDealingData,
|
||||
) -> Self {
|
||||
PartialContractDealing {
|
||||
dealing_index,
|
||||
chunk_index,
|
||||
data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct DealingMetadataResponse {
|
||||
pub epoch_id: EpochId,
|
||||
|
||||
pub dealer: Addr,
|
||||
|
||||
pub dealing_index: DealingIndex,
|
||||
|
||||
pub metadata: Option<DealingMetadata>,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct DealingChunkResponse {
|
||||
pub epoch_id: EpochId,
|
||||
|
||||
pub dealer: Addr,
|
||||
|
||||
pub dealing_index: DealingIndex,
|
||||
|
||||
pub chunk_index: ChunkIndex,
|
||||
|
||||
pub chunk: Option<PartialContractDealingData>,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct DealingChunkStatusResponse {
|
||||
pub epoch_id: EpochId,
|
||||
|
||||
pub dealer: Addr,
|
||||
|
||||
pub dealing_index: DealingIndex,
|
||||
|
||||
pub chunk_index: ChunkIndex,
|
||||
|
||||
pub status: ChunkSubmissionStatus,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct DealingStatusResponse {
|
||||
pub epoch_id: EpochId,
|
||||
|
||||
pub dealer: Addr,
|
||||
|
||||
pub dealing_index: DealingIndex,
|
||||
|
||||
pub status: DealingStatus,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct DealingStatus {
|
||||
pub has_metadata: bool,
|
||||
|
||||
pub fully_submitted: bool,
|
||||
|
||||
pub chunk_submission_status: BTreeMap<ChunkIndex, ChunkSubmissionStatus>,
|
||||
}
|
||||
|
||||
impl From<Option<DealingMetadata>> for DealingStatus {
|
||||
fn from(metadata: Option<DealingMetadata>) -> Self {
|
||||
DealingStatus {
|
||||
has_metadata: metadata.is_some(),
|
||||
fully_submitted: metadata
|
||||
.as_ref()
|
||||
.map(|m| m.is_complete())
|
||||
.unwrap_or_default(),
|
||||
chunk_submission_status: metadata
|
||||
.map(|m| m.submission_statuses())
|
||||
.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct DealerDealingsStatusResponse {
|
||||
pub epoch_id: EpochId,
|
||||
|
||||
pub dealer: Addr,
|
||||
|
||||
pub all_dealings_fully_submitted: bool,
|
||||
|
||||
pub dealing_submission_status: BTreeMap<DealingIndex, DealingStatus>,
|
||||
}
|
||||
|
||||
impl DealerDealingsStatusResponse {
|
||||
pub fn full_dealings(&self) -> usize {
|
||||
self.dealing_submission_status
|
||||
.values()
|
||||
.filter(|s| s.fully_submitted)
|
||||
.count()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn chunking_dealings() {
|
||||
const CHUNK_SIZE: usize = 512;
|
||||
|
||||
let test_cases = [
|
||||
(CHUNK_SIZE - 10, CHUNK_SIZE, 1),
|
||||
(CHUNK_SIZE, CHUNK_SIZE, 1),
|
||||
(CHUNK_SIZE + 10, CHUNK_SIZE, 2),
|
||||
(CHUNK_SIZE * 2, CHUNK_SIZE, 2),
|
||||
(CHUNK_SIZE * 2 + 1, CHUNK_SIZE, 3),
|
||||
(CHUNK_SIZE * 10 + 42, CHUNK_SIZE, 11),
|
||||
];
|
||||
|
||||
for (dealing_len, chunk_size, expected_chunks) in test_cases {
|
||||
let chunks = DealingChunkInfo::construct(dealing_len, chunk_size);
|
||||
assert_eq!(expected_chunks, chunks.len());
|
||||
assert_eq!(
|
||||
dealing_len as u64,
|
||||
chunks.iter().map(|c| c.size).sum::<u64>()
|
||||
);
|
||||
|
||||
let mut expected_last = dealing_len % chunk_size;
|
||||
if expected_last == 0 {
|
||||
expected_last = chunk_size;
|
||||
}
|
||||
assert_eq!(chunks.last().unwrap().size, expected_last as u64);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod dealer;
|
||||
pub mod dealing;
|
||||
pub mod event_attributes;
|
||||
pub mod msg;
|
||||
pub mod types;
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::types::{ContractSafeBytes, EncodedBTEPublicKeyWithProof, EpochId, TimeConfiguration};
|
||||
use crate::dealing::{DealingChunkInfo, PartialContractDealing};
|
||||
use crate::types::{
|
||||
ChunkIndex, DealingIndex, EncodedBTEPublicKeyWithProof, EpochId, TimeConfiguration,
|
||||
};
|
||||
use crate::verification_key::VerificationKeyShare;
|
||||
use contracts_common::IdentityKey;
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::Addr;
|
||||
|
||||
#[cfg(feature = "schema")]
|
||||
use crate::{
|
||||
dealer::{DealerDetailsResponse, PagedDealerResponse, PagedDealingsResponse},
|
||||
types::{Epoch, InitialReplacementData},
|
||||
verification_key::PagedVKSharesResponse,
|
||||
dealer::{DealerDetailsResponse, PagedDealerResponse},
|
||||
dealing::{
|
||||
DealerDealingsStatusResponse, DealingChunkResponse, DealingChunkStatusResponse,
|
||||
DealingMetadataResponse, DealingStatusResponse,
|
||||
},
|
||||
types::{Epoch, InitialReplacementData, State},
|
||||
verification_key::{PagedVKSharesResponse, VkShareResponse},
|
||||
};
|
||||
#[cfg(feature = "schema")]
|
||||
use cosmwasm_schema::QueryResponses;
|
||||
@@ -21,18 +28,31 @@ pub struct InstantiateMsg {
|
||||
pub multisig_addr: String,
|
||||
pub time_configuration: Option<TimeConfiguration>,
|
||||
pub mix_denom: String,
|
||||
|
||||
/// Specifies the number of elements in the derived keys
|
||||
pub key_size: u32,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub enum ExecuteMsg {
|
||||
// we could have just re-used AdvanceEpochState, but imo an explicit message is better
|
||||
InitiateDkg {},
|
||||
|
||||
RegisterDealer {
|
||||
bte_key_with_proof: EncodedBTEPublicKeyWithProof,
|
||||
identity_key: IdentityKey,
|
||||
announce_address: String,
|
||||
resharing: bool,
|
||||
},
|
||||
|
||||
CommitDealing {
|
||||
dealing_bytes: ContractSafeBytes,
|
||||
CommitDealingsMetadata {
|
||||
dealing_index: DealingIndex,
|
||||
chunks: Vec<DealingChunkInfo>,
|
||||
resharing: bool,
|
||||
},
|
||||
|
||||
CommitDealingsChunk {
|
||||
chunk: PartialContractDealing,
|
||||
resharing: bool,
|
||||
},
|
||||
|
||||
@@ -42,8 +62,7 @@ pub enum ExecuteMsg {
|
||||
},
|
||||
|
||||
VerifyVerificationKeyShare {
|
||||
// TODO: this should be using a String...
|
||||
owner: Addr,
|
||||
owner: String,
|
||||
resharing: bool,
|
||||
},
|
||||
|
||||
@@ -55,6 +74,9 @@ pub enum ExecuteMsg {
|
||||
#[cw_serde]
|
||||
#[cfg_attr(feature = "schema", derive(QueryResponses))]
|
||||
pub enum QueryMsg {
|
||||
#[cfg_attr(feature = "schema", returns(State))]
|
||||
GetState {},
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(Epoch))]
|
||||
GetCurrentEpochState {},
|
||||
|
||||
@@ -79,19 +101,53 @@ pub enum QueryMsg {
|
||||
start_after: Option<String>,
|
||||
},
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(PagedDealingsResponse))]
|
||||
GetDealing {
|
||||
idx: u64,
|
||||
limit: Option<u32>,
|
||||
start_after: Option<String>,
|
||||
#[cfg_attr(feature = "schema", returns(DealingMetadataResponse))]
|
||||
GetDealingsMetadata {
|
||||
epoch_id: EpochId,
|
||||
dealer: String,
|
||||
dealing_index: DealingIndex,
|
||||
},
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(DealerDealingsStatusResponse))]
|
||||
GetDealerDealingsStatus { epoch_id: EpochId, dealer: String },
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(DealingStatusResponse))]
|
||||
GetDealingStatus {
|
||||
epoch_id: EpochId,
|
||||
dealer: String,
|
||||
dealing_index: DealingIndex,
|
||||
},
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(DealingChunkStatusResponse))]
|
||||
GetDealingChunkStatus {
|
||||
epoch_id: EpochId,
|
||||
dealer: String,
|
||||
dealing_index: DealingIndex,
|
||||
chunk_index: ChunkIndex,
|
||||
},
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(DealingChunkResponse))]
|
||||
GetDealingChunk {
|
||||
epoch_id: EpochId,
|
||||
dealer: String,
|
||||
dealing_index: DealingIndex,
|
||||
chunk_index: ChunkIndex,
|
||||
},
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(VkShareResponse))]
|
||||
GetVerificationKey { epoch_id: EpochId, owner: String },
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(PagedVKSharesResponse))]
|
||||
GetVerificationKeys {
|
||||
epoch_id: EpochId,
|
||||
limit: Option<u32>,
|
||||
start_after: Option<String>,
|
||||
},
|
||||
|
||||
/// Gets the stored contract version information that's required by the CW2 spec interface for migrations.
|
||||
#[serde(rename = "get_cw2_contract_version")]
|
||||
#[cfg_attr(feature = "schema", returns(cw2::ContractVersion))]
|
||||
GetCW2ContractVersion {},
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
|
||||
@@ -8,14 +8,18 @@ use std::str::FromStr;
|
||||
pub use crate::dealer::{DealerDetails, PagedDealerResponse};
|
||||
pub use contracts_common::dealings::ContractSafeBytes;
|
||||
pub use cosmwasm_std::{Addr, Coin, Timestamp};
|
||||
pub use cw4::Cw4Contract;
|
||||
|
||||
pub type EncodedBTEPublicKeyWithProof = String;
|
||||
pub type EncodedBTEPublicKeyWithProofRef<'a> = &'a str;
|
||||
pub type NodeIndex = u64;
|
||||
pub type EpochId = u64;
|
||||
|
||||
// 2 public attributes, 2 private attributes, 1 fixed for coconut credential
|
||||
pub const TOTAL_DEALINGS: usize = 2 + 2 + 1;
|
||||
pub type DealingIndex = u32;
|
||||
// we really don't need to hold more data than that (even u8 would have been enough),
|
||||
// but explicitly make it different type than `DealingIndex` so type system would detect any
|
||||
// accidental misuses
|
||||
pub type ChunkIndex = u16;
|
||||
pub type PartialContractDealingData = ContractSafeBytes;
|
||||
|
||||
#[cw_serde]
|
||||
pub struct InitialReplacementData {
|
||||
@@ -73,13 +77,23 @@ impl Default for TimeConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct State {
|
||||
pub mix_denom: String,
|
||||
pub multisig_addr: Addr,
|
||||
pub group_addr: Cw4Contract,
|
||||
|
||||
/// Specifies the number of elements in the derived keys
|
||||
pub key_size: u32,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
#[derive(Copy, Default)]
|
||||
pub struct Epoch {
|
||||
pub state: EpochState,
|
||||
pub epoch_id: EpochId,
|
||||
pub time_configuration: TimeConfiguration,
|
||||
pub finish_timestamp: Timestamp,
|
||||
pub finish_timestamp: Option<Timestamp>,
|
||||
}
|
||||
|
||||
impl Epoch {
|
||||
@@ -90,36 +104,40 @@ impl Epoch {
|
||||
current_timestamp: Timestamp,
|
||||
) -> Self {
|
||||
let duration = match state {
|
||||
EpochState::WaitingInitialisation => None,
|
||||
EpochState::PublicKeySubmission { .. } => {
|
||||
time_configuration.public_key_submission_time_secs
|
||||
Some(time_configuration.public_key_submission_time_secs)
|
||||
}
|
||||
EpochState::DealingExchange { .. } => {
|
||||
Some(time_configuration.dealing_exchange_time_secs)
|
||||
}
|
||||
EpochState::DealingExchange { .. } => time_configuration.dealing_exchange_time_secs,
|
||||
EpochState::VerificationKeySubmission { .. } => {
|
||||
time_configuration.verification_key_submission_time_secs
|
||||
Some(time_configuration.verification_key_submission_time_secs)
|
||||
}
|
||||
EpochState::VerificationKeyValidation { .. } => {
|
||||
time_configuration.verification_key_validation_time_secs
|
||||
Some(time_configuration.verification_key_validation_time_secs)
|
||||
}
|
||||
EpochState::VerificationKeyFinalization { .. } => {
|
||||
time_configuration.verification_key_finalization_time_secs
|
||||
Some(time_configuration.verification_key_finalization_time_secs)
|
||||
}
|
||||
EpochState::InProgress => time_configuration.in_progress_time_secs,
|
||||
EpochState::InProgress => Some(time_configuration.in_progress_time_secs),
|
||||
};
|
||||
Epoch {
|
||||
state,
|
||||
epoch_id,
|
||||
time_configuration,
|
||||
finish_timestamp: current_timestamp.plus_seconds(duration),
|
||||
finish_timestamp: duration.map(|d| current_timestamp.plus_seconds(d)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn final_timestamp_secs(&self) -> u64 {
|
||||
let mut finish = self.finish_timestamp.seconds();
|
||||
pub fn final_timestamp_secs(&self) -> Option<u64> {
|
||||
let mut finish = self.finish_timestamp?.seconds();
|
||||
let time_configuration = self.time_configuration;
|
||||
let mut curr_epoch_state = self.state;
|
||||
while let Some(state) = curr_epoch_state.next() {
|
||||
curr_epoch_state = state;
|
||||
let adding = match curr_epoch_state {
|
||||
EpochState::WaitingInitialisation => return None,
|
||||
EpochState::PublicKeySubmission { .. } => {
|
||||
time_configuration.public_key_submission_time_secs
|
||||
}
|
||||
@@ -137,12 +155,13 @@ impl Epoch {
|
||||
};
|
||||
finish += adding;
|
||||
}
|
||||
finish
|
||||
Some(finish)
|
||||
}
|
||||
}
|
||||
|
||||
// currently (it is still extremely likely to change, we might be able to get rid of verification key-related complaints),
|
||||
// the epoch can be in the following states (in order):
|
||||
// 0. WaitingInitialisation -> the contract has been instantiated, but awaits for the admin to kick off the process (group members might still be getting added)
|
||||
// 1. PublicKeySubmission -> potential dealers are submitting their BTE and ed25519 public keys to participate in dealing exchange
|
||||
// 2. DealingExchange -> the actual (off-chain) dealing exchange is happening
|
||||
// 3. ComplaintSubmission -> receivers submitting evidence of other dealers sending malformed data
|
||||
@@ -156,6 +175,7 @@ impl Epoch {
|
||||
#[cw_serde]
|
||||
#[derive(Copy)]
|
||||
pub enum EpochState {
|
||||
WaitingInitialisation,
|
||||
PublicKeySubmission { resharing: bool },
|
||||
DealingExchange { resharing: bool },
|
||||
VerificationKeySubmission { resharing: bool },
|
||||
@@ -166,13 +186,14 @@ pub enum EpochState {
|
||||
|
||||
impl Default for EpochState {
|
||||
fn default() -> Self {
|
||||
Self::PublicKeySubmission { resharing: false }
|
||||
Self::WaitingInitialisation
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for EpochState {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
EpochState::WaitingInitialisation => write!(f, "Waiting for initialisation"),
|
||||
EpochState::PublicKeySubmission { resharing } => {
|
||||
write!(f, "PublicKeySubmission (resharing: {resharing})")
|
||||
}
|
||||
@@ -194,8 +215,13 @@ impl Display for EpochState {
|
||||
}
|
||||
|
||||
impl EpochState {
|
||||
pub fn first() -> Self {
|
||||
EpochState::PublicKeySubmission { resharing: false }
|
||||
}
|
||||
|
||||
pub fn next(self) -> Option<Self> {
|
||||
match self {
|
||||
EpochState::WaitingInitialisation => None,
|
||||
EpochState::PublicKeySubmission { resharing } => {
|
||||
Some(EpochState::DealingExchange { resharing })
|
||||
}
|
||||
@@ -226,4 +252,8 @@ impl EpochState {
|
||||
pub fn is_final(&self) -> bool {
|
||||
*self == EpochState::InProgress
|
||||
}
|
||||
|
||||
pub fn is_in_progress(&self) -> bool {
|
||||
matches!(self, EpochState::InProgress)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,13 @@ pub struct ContractVKShare {
|
||||
pub verified: bool,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct VkShareResponse {
|
||||
pub owner: Addr,
|
||||
pub epoch_id: EpochId,
|
||||
pub share: Option<ContractVKShare>,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct PagedVKSharesResponse {
|
||||
pub shares: Vec<ContractVKShare>,
|
||||
@@ -36,7 +43,10 @@ pub fn to_cosmos_msg(
|
||||
multisig_addr: String,
|
||||
expiration_time: Timestamp,
|
||||
) -> StdResult<CosmosMsg> {
|
||||
let verify_vk_share_req = ExecuteMsg::VerifyVerificationKeyShare { owner, resharing };
|
||||
let verify_vk_share_req = ExecuteMsg::VerifyVerificationKeyShare {
|
||||
owner: owner.to_string(),
|
||||
resharing,
|
||||
};
|
||||
let verify_vk_share_msg = CosmosMsg::Wasm(WasmMsg::Execute {
|
||||
contract_addr: coconut_dkg_addr,
|
||||
msg: to_binary(&verify_vk_share_req)?,
|
||||
@@ -57,7 +67,14 @@ pub fn to_cosmos_msg(
|
||||
Ok(msg)
|
||||
}
|
||||
|
||||
pub fn owner_from_cosmos_msgs(msgs: &[CosmosMsg]) -> Option<Addr> {
|
||||
// DKG SAFETY:
|
||||
// each legit verification proposal will only contain a single execute msg,
|
||||
// if they have more than one, we can safely ignore it
|
||||
pub fn owner_from_cosmos_msgs(msgs: &[CosmosMsg]) -> Option<String> {
|
||||
if msgs.len() != 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(CosmosMsg::Wasm(WasmMsg::Execute {
|
||||
contract_addr: _,
|
||||
msg,
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::ops::Deref;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
// some sane upper-bound size on byte sizes
|
||||
// currently set to 128 bytes
|
||||
pub const MAX_DISPLAY_SIZE: usize = 128;
|
||||
|
||||
// TODO: if we are to use this for different types, it might make sense to introduce something like
|
||||
// CommitmentTypeId field on the below for distinguishing different ones. it would somehow become part of the trait
|
||||
// helps to transfer bytes between contract boundary to decrease amount of data sent accross
|
||||
// after it's put to `Binary`
|
||||
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, JsonSchema)]
|
||||
pub struct ContractSafeBytes(pub Vec<u8>);
|
||||
|
||||
@@ -23,6 +23,24 @@ impl Deref for ContractSafeBytes {
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for ContractSafeBytes {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for ContractSafeBytes {
|
||||
fn from(value: Vec<u8>) -> Self {
|
||||
ContractSafeBytes(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a [u8]> for ContractSafeBytes {
|
||||
fn from(value: &'a [u8]) -> Self {
|
||||
value.to_vec().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ContractSafeBytes {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
if !self.0.is_empty() {
|
||||
|
||||
@@ -14,14 +14,14 @@ thiserror = { workspace = true }
|
||||
tokio = { version = "1.24.1", features = ["sync"]}
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
|
||||
version = "0.5"
|
||||
workspace = true
|
||||
features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"]
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio]
|
||||
version = "1.24.1"
|
||||
workspace = true
|
||||
features = [ "rt-multi-thread", "net", "signal", "fs" ]
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
|
||||
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
|
||||
tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] }
|
||||
|
||||
@@ -5,7 +5,9 @@ use nym_bandwidth_controller::acquire::state::State;
|
||||
use nym_client_core::config::disk_persistence::CommonClientPaths;
|
||||
use nym_config::DEFAULT_DATA_DIR;
|
||||
use nym_credential_storage::persistent_storage::PersistentStorage;
|
||||
use nym_validator_client::nyxd::contract_traits::{CoconutBandwidthSigningClient, DkgQueryClient};
|
||||
use nym_validator_client::nyxd::contract_traits::{
|
||||
dkg_query_client::EpochState, CoconutBandwidthSigningClient, DkgQueryClient,
|
||||
};
|
||||
use nym_validator_client::nyxd::Coin;
|
||||
use std::path::PathBuf;
|
||||
use std::process::exit;
|
||||
@@ -87,21 +89,29 @@ where
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.expect("the system clock is set to 01/01/1970 (or earlier)")
|
||||
.as_secs();
|
||||
|
||||
if epoch.state.is_final() {
|
||||
if current_timestamp_secs + SAFETY_BUFFER_SECS >= epoch.finish_timestamp.seconds() {
|
||||
info!("In the next {} minute(s), a transition will take place in the coconut system. Deposits should be halted in this time for safety reasons.", SAFETY_BUFFER_SECS / 60);
|
||||
exit(0);
|
||||
if let Some(finish_timestamp) = epoch.finish_timestamp {
|
||||
if current_timestamp_secs + SAFETY_BUFFER_SECS >= finish_timestamp.seconds() {
|
||||
info!("In the next {} minute(s), a transition will take place in the coconut system. Deposits should be halted in this time for safety reasons.", SAFETY_BUFFER_SECS / 60);
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
} else {
|
||||
} else if let Some(final_timestamp) = epoch.final_timestamp_secs() {
|
||||
// Use 1 additional second to not start the next iteration immediately and spam get_current_epoch queries
|
||||
let secs_until_final = epoch
|
||||
.final_timestamp_secs()
|
||||
.saturating_sub(current_timestamp_secs)
|
||||
+ 1;
|
||||
let secs_until_final = final_timestamp.saturating_sub(current_timestamp_secs) + 1;
|
||||
info!("Approximately {} seconds until coconut is available. Sleeping until then. You can safely kill the process at any moment.", secs_until_final);
|
||||
tokio::time::sleep(Duration::from_secs(secs_until_final)).await;
|
||||
} else if matches!(epoch.state, EpochState::WaitingInitialisation) {
|
||||
info!("dkg hasn't been initialised yet and it is not known when it will be. Going to check again later");
|
||||
tokio::time::sleep(Duration::from_secs(60 * 5)).await;
|
||||
} else {
|
||||
// this should never be the case since the only case where final timestamp is unknown is when it's waiting for initialisation,
|
||||
// but let's guard ourselves against future changes
|
||||
info!("it is unknown when coconut will be come available. Going to check again later");
|
||||
tokio::time::sleep(Duration::from_secs(60 * 5)).await;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,8 +14,7 @@ use std::collections::HashMap;
|
||||
use std::ops::Neg;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(Clone, PartialEq, Eq))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Ciphertexts {
|
||||
pub rr: [G1Projective; NUM_CHUNKS],
|
||||
pub ss: [G1Projective; NUM_CHUNKS],
|
||||
|
||||
@@ -53,8 +53,7 @@ impl PublicKey {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct PublicKeyWithProof {
|
||||
pub(crate) key: PublicKey,
|
||||
pub(crate) proof: ProofOfDiscreteLog,
|
||||
|
||||
@@ -67,8 +67,7 @@ impl<'a> Instance<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(Clone, PartialEq, Eq))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ProofOfChunking {
|
||||
y0: G1Projective,
|
||||
bb: Vec<G1Projective>,
|
||||
|
||||
@@ -13,8 +13,7 @@ use zeroize::Zeroize;
|
||||
const DISCRETE_LOG_DOMAIN: &[u8] =
|
||||
b"NYM_COCONUT_NIDKG_V01_CS01_WITH_BLS12381_XMD:SHA-256_SSWU_RO_PROOF_DISCRETE_LOG";
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ProofOfDiscreteLog {
|
||||
pub(crate) rand_commitment: G1Projective,
|
||||
pub(crate) response: Scalar,
|
||||
|
||||
@@ -76,8 +76,7 @@ impl<'a> Instance<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(Clone, PartialEq, Eq))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ProofOfSecretSharing {
|
||||
ff: G1Projective,
|
||||
aa: G2Projective,
|
||||
|
||||
+100
-20
@@ -82,8 +82,7 @@ impl RecoveredVerificationKeys {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Dealing {
|
||||
pub public_coefficients: PublicCoefficients,
|
||||
pub ciphertexts: Ciphertexts,
|
||||
@@ -321,9 +320,17 @@ impl<'a> TryFrom<&'a nym_contracts_common::dealings::ContractSafeBytes> for Deal
|
||||
}
|
||||
}
|
||||
|
||||
// this assumes all dealings have been verified
|
||||
/// Attempt to run the `VkCombine` algorithm to obtain the public master verification key, `VK`
|
||||
/// alongside shares of the verification key, `shvk_{1}`, `shvk_{2}`, ... `svhk_{n}`, where n is the number of receivers.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `dealings`: map of dealer indices to dealings they generated
|
||||
/// * `threshold`: explicit threshold value of the associated dealings
|
||||
/// * `receivers`:map of receiver indices to their public keys
|
||||
// note: this function assumes all dealings have already been verified
|
||||
pub fn try_recover_verification_keys(
|
||||
dealings: &[Dealing],
|
||||
dealings: &BTreeMap<NodeIndex, Dealing>,
|
||||
threshold: Threshold,
|
||||
receivers: &BTreeMap<NodeIndex, PublicKey>,
|
||||
) -> Result<RecoveredVerificationKeys, DkgError> {
|
||||
@@ -331,24 +338,31 @@ pub fn try_recover_verification_keys(
|
||||
return Err(DkgError::NoDealingsAvailable);
|
||||
}
|
||||
|
||||
let threshold_usize = threshold as usize;
|
||||
let threshold = threshold as usize;
|
||||
|
||||
if dealings.len() < threshold {
|
||||
return Err(DkgError::NotEnoughDealingsAvailable {
|
||||
available: dealings.len(),
|
||||
required: threshold,
|
||||
});
|
||||
}
|
||||
|
||||
if !dealings
|
||||
.iter()
|
||||
.all(|dealing| dealing.public_coefficients.size() == threshold_usize)
|
||||
.values()
|
||||
.all(|dealing| dealing.public_coefficients.size() == threshold)
|
||||
{
|
||||
return Err(DkgError::MismatchedDealings);
|
||||
}
|
||||
|
||||
let indices = receivers.keys().collect::<Vec<_>>();
|
||||
let dealer_indices = dealings.keys().collect::<Vec<_>>();
|
||||
|
||||
// Compute A0, ..., A_{t-1}
|
||||
let mut interpolated_coefficients = Vec::with_capacity(threshold_usize);
|
||||
for k in 0..threshold_usize {
|
||||
let mut samples = Vec::with_capacity(indices.len());
|
||||
for (j, dealing) in dealings.iter().enumerate() {
|
||||
let mut interpolated_coefficients = Vec::with_capacity(threshold);
|
||||
for k in 0..threshold {
|
||||
let mut samples = Vec::with_capacity(dealer_indices.len());
|
||||
for (dealer_index, dealing) in dealings.iter() {
|
||||
samples.push((
|
||||
Scalar::from(*indices[j]),
|
||||
Scalar::from(*dealer_index),
|
||||
*dealing.public_coefficients.nth(k),
|
||||
))
|
||||
}
|
||||
@@ -365,7 +379,7 @@ pub fn try_recover_verification_keys(
|
||||
// shvk_j = A0^{j^0} * A1^{j^1} * ... * A_{t-1}^{j^{t-1}}
|
||||
let verification_key_shares = receivers
|
||||
.keys()
|
||||
.map(|index| interpolated_coefficients.evaluate_at(&Scalar::from(*index)))
|
||||
.map(|receiver_index| interpolated_coefficients.evaluate_at(&Scalar::from(*receiver_index)))
|
||||
.collect();
|
||||
|
||||
Ok(RecoveredVerificationKeys {
|
||||
@@ -457,14 +471,17 @@ mod tests {
|
||||
let dealings = node_indices
|
||||
.iter()
|
||||
.map(|&dealer_index| {
|
||||
Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0
|
||||
(
|
||||
dealer_index,
|
||||
Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
|
||||
let mut derived_secrets = Vec::new();
|
||||
for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() {
|
||||
for (i, (ref dk, _)) in full_keys.iter().enumerate() {
|
||||
let shares = dealings
|
||||
.iter()
|
||||
.values()
|
||||
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap())
|
||||
.collect();
|
||||
derived_secrets.push(
|
||||
@@ -513,9 +530,12 @@ mod tests {
|
||||
let dealings = node_indices
|
||||
.iter()
|
||||
.map(|&dealer_index| {
|
||||
Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0
|
||||
(
|
||||
dealer_index,
|
||||
Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
|
||||
let RecoveredVerificationKeys {
|
||||
recovered_master,
|
||||
@@ -531,6 +551,66 @@ mod tests {
|
||||
.is_ok())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore] // expensive test
|
||||
fn verifying_partial_verification_keys_with_different_dealers_and_receivers() {
|
||||
let dummy_seed = [42u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
let params = setup();
|
||||
|
||||
let dealer_indices = [1, 2, 3, 8];
|
||||
let receiver_indices = [3, 4, 5, 6, 7];
|
||||
let threshold = 3;
|
||||
|
||||
let mut receivers = BTreeMap::new();
|
||||
let mut full_keys = Vec::new();
|
||||
for index in &receiver_indices {
|
||||
let (dk, pk) = keygen(¶ms, &mut rng);
|
||||
receivers.insert(*index, *pk.public_key());
|
||||
full_keys.push((dk, pk))
|
||||
}
|
||||
|
||||
let dealings = dealer_indices
|
||||
.iter()
|
||||
.map(|&dealer_index| {
|
||||
(
|
||||
dealer_index,
|
||||
Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0,
|
||||
)
|
||||
})
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
|
||||
let RecoveredVerificationKeys {
|
||||
recovered_master,
|
||||
recovered_partials,
|
||||
} = try_recover_verification_keys(&dealings, threshold, &receivers).unwrap();
|
||||
|
||||
let g2 = G2Projective::generator();
|
||||
|
||||
let mut derived_secrets = Vec::new();
|
||||
for (i, (dk, _)) in full_keys.iter().enumerate() {
|
||||
let shares = dealings
|
||||
.values()
|
||||
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap())
|
||||
.collect();
|
||||
|
||||
let recovered_secret = combine_shares(shares, &dealer_indices).unwrap();
|
||||
|
||||
// make sure it matches the associated vk
|
||||
assert_eq!(recovered_partials[i], g2 * recovered_secret);
|
||||
|
||||
derived_secrets.push(recovered_secret)
|
||||
}
|
||||
|
||||
assert!(verify_verification_keys(
|
||||
&recovered_master,
|
||||
&recovered_partials,
|
||||
&receivers,
|
||||
threshold
|
||||
)
|
||||
.is_ok())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore] // expensive test
|
||||
fn dealing_roundtrip() {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug, Error, Clone)]
|
||||
pub enum DkgError {
|
||||
#[error("Provided set of values contained duplicate coordinate")]
|
||||
DuplicateCoordinate,
|
||||
|
||||
@@ -13,6 +13,7 @@ pub mod dealing;
|
||||
pub(crate) mod share;
|
||||
pub(crate) mod utils;
|
||||
|
||||
pub use bls12_381::{G2Projective, Scalar};
|
||||
pub use dealing::*;
|
||||
pub use share::*;
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ fn single_sender() {
|
||||
.unwrap();
|
||||
|
||||
// make sure each share is actually decryptable (even though proofs say they must be, perform this sanity check)
|
||||
for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() {
|
||||
for (i, (ref dk, _)) in full_keys.iter().enumerate() {
|
||||
let _recovered = decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap();
|
||||
}
|
||||
|
||||
@@ -91,10 +91,13 @@ fn full_threshold_secret_sharing() {
|
||||
let dealings = node_indices
|
||||
.iter()
|
||||
.map(|&dealer_index| {
|
||||
Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0
|
||||
(
|
||||
dealer_index,
|
||||
Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
for dealing in dealings.iter() {
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
for dealing in dealings.values() {
|
||||
dealing
|
||||
.verify(¶ms, threshold, &receivers, None)
|
||||
.unwrap();
|
||||
@@ -109,9 +112,9 @@ fn full_threshold_secret_sharing() {
|
||||
let g2 = G2Projective::generator();
|
||||
|
||||
let mut derived_secrets = Vec::new();
|
||||
for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() {
|
||||
for (i, (ref dk, _)) in full_keys.iter().enumerate() {
|
||||
let shares = dealings
|
||||
.iter()
|
||||
.values()
|
||||
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap())
|
||||
.collect();
|
||||
|
||||
@@ -169,9 +172,12 @@ fn full_threshold_secret_resharing() {
|
||||
let first_dealings = node_indices
|
||||
.iter()
|
||||
.map(|&dealer_index| {
|
||||
Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0
|
||||
(
|
||||
dealer_index,
|
||||
Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
|
||||
// recover verification keys
|
||||
let RecoveredVerificationKeys {
|
||||
@@ -180,9 +186,9 @@ fn full_threshold_secret_resharing() {
|
||||
} = try_recover_verification_keys(&first_dealings, threshold, &receivers).unwrap();
|
||||
|
||||
let mut derived_secrets = Vec::new();
|
||||
for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() {
|
||||
for (i, (ref dk, _)) in full_keys.iter().enumerate() {
|
||||
let shares = first_dealings
|
||||
.iter()
|
||||
.values()
|
||||
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap())
|
||||
.collect();
|
||||
|
||||
@@ -203,19 +209,22 @@ fn full_threshold_secret_resharing() {
|
||||
.iter()
|
||||
.zip(derived_secrets.iter())
|
||||
.map(|(&dealer_index, prior_secret)| {
|
||||
Dealing::create(
|
||||
&mut rng,
|
||||
¶ms,
|
||||
(
|
||||
dealer_index,
|
||||
threshold,
|
||||
&receivers,
|
||||
Some(*prior_secret),
|
||||
Dealing::create(
|
||||
&mut rng,
|
||||
¶ms,
|
||||
dealer_index,
|
||||
threshold,
|
||||
&receivers,
|
||||
Some(*prior_secret),
|
||||
)
|
||||
.0,
|
||||
)
|
||||
.0
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
|
||||
for (reshared_dealing, prior_vk) in resharing_dealings.iter().zip(recovered_partials.iter()) {
|
||||
for (reshared_dealing, prior_vk) in resharing_dealings.values().zip(recovered_partials.iter()) {
|
||||
reshared_dealing
|
||||
.verify(¶ms, threshold, &receivers, Some(*prior_vk))
|
||||
.unwrap();
|
||||
@@ -228,9 +237,9 @@ fn full_threshold_secret_resharing() {
|
||||
} = try_recover_verification_keys(&resharing_dealings, threshold, &receivers).unwrap();
|
||||
|
||||
let mut reshared_secrets = Vec::new();
|
||||
for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() {
|
||||
for (i, (ref dk, _)) in full_keys.iter().enumerate() {
|
||||
let shares = resharing_dealings
|
||||
.iter()
|
||||
.values()
|
||||
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap())
|
||||
.collect();
|
||||
|
||||
@@ -279,9 +288,12 @@ fn full_threshold_secret_resharing_left_party() {
|
||||
let first_dealings = node_indices
|
||||
.iter()
|
||||
.map(|&dealer_index| {
|
||||
Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0
|
||||
(
|
||||
dealer_index,
|
||||
Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
|
||||
// recover verification keys
|
||||
let RecoveredVerificationKeys {
|
||||
@@ -290,9 +302,9 @@ fn full_threshold_secret_resharing_left_party() {
|
||||
} = try_recover_verification_keys(&first_dealings, threshold, &receivers).unwrap();
|
||||
|
||||
let mut derived_secrets = Vec::new();
|
||||
for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() {
|
||||
for (i, (ref dk, _)) in full_keys.iter().enumerate() {
|
||||
let shares = first_dealings
|
||||
.iter()
|
||||
.values()
|
||||
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap())
|
||||
.collect();
|
||||
|
||||
@@ -323,20 +335,23 @@ fn full_threshold_secret_resharing_left_party() {
|
||||
.iter()
|
||||
.zip(derived_secrets.iter().take(2))
|
||||
.map(|(&dealer_index, prior_secret)| {
|
||||
Dealing::create(
|
||||
&mut rng,
|
||||
¶ms,
|
||||
(
|
||||
dealer_index,
|
||||
threshold,
|
||||
&receivers,
|
||||
Some(*prior_secret),
|
||||
Dealing::create(
|
||||
&mut rng,
|
||||
¶ms,
|
||||
dealer_index,
|
||||
threshold,
|
||||
&receivers,
|
||||
Some(*prior_secret),
|
||||
)
|
||||
.0,
|
||||
)
|
||||
.0
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
|
||||
for (reshared_dealing, prior_vk) in resharing_dealings
|
||||
.iter()
|
||||
.values()
|
||||
.zip(recovered_partials.iter().take(2))
|
||||
{
|
||||
reshared_dealing
|
||||
@@ -351,9 +366,9 @@ fn full_threshold_secret_resharing_left_party() {
|
||||
} = try_recover_verification_keys(&resharing_dealings, threshold, &receivers).unwrap();
|
||||
|
||||
let mut reshared_secrets = Vec::new();
|
||||
for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() {
|
||||
for (i, (ref dk, _)) in full_keys.iter().enumerate() {
|
||||
let shares = resharing_dealings
|
||||
.iter()
|
||||
.values()
|
||||
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap())
|
||||
.collect();
|
||||
|
||||
|
||||
@@ -81,6 +81,15 @@ ExitPolicy accept6 *6:119
|
||||
ExitPolicy accept *4:120
|
||||
ExitPolicy reject6 [FC00::]/7:*
|
||||
|
||||
# Portless
|
||||
ExitPolicy accept *:0
|
||||
ExitPolicy accept *4:0
|
||||
ExitPolicy accept *6:0
|
||||
|
||||
ExitPolicy reject *:0
|
||||
ExitPolicy reject *4:0
|
||||
ExitPolicy reject *6:0
|
||||
|
||||
#ExitPolicy accept *:8080 #and another comment here
|
||||
|
||||
ExitPolicy reject FE80:0000:0000:0000:0202:B3FF:FE1E:8329:*
|
||||
@@ -184,6 +193,60 @@ ExitPolicy reject *:*
|
||||
},
|
||||
);
|
||||
|
||||
// ExitPolicy accept *:0
|
||||
expected.push(
|
||||
Accept,
|
||||
AddressPortPattern {
|
||||
ip_pattern: IpPattern::Star,
|
||||
ports: PortRange::new_zero(),
|
||||
},
|
||||
);
|
||||
|
||||
// ExitPolicy accept *4:0
|
||||
expected.push(
|
||||
Accept,
|
||||
AddressPortPattern {
|
||||
ip_pattern: IpPattern::V4Star,
|
||||
ports: PortRange::new_zero(),
|
||||
},
|
||||
);
|
||||
|
||||
// ExitPolicy accept *6:0
|
||||
expected.push(
|
||||
Accept,
|
||||
AddressPortPattern {
|
||||
ip_pattern: IpPattern::V6Star,
|
||||
ports: PortRange::new_zero(),
|
||||
},
|
||||
);
|
||||
|
||||
// ExitPolicy reject *:0
|
||||
expected.push(
|
||||
Reject,
|
||||
AddressPortPattern {
|
||||
ip_pattern: IpPattern::Star,
|
||||
ports: PortRange::new_zero(),
|
||||
},
|
||||
);
|
||||
|
||||
// ExitPolicy reject *4:0
|
||||
expected.push(
|
||||
Reject,
|
||||
AddressPortPattern {
|
||||
ip_pattern: IpPattern::V4Star,
|
||||
ports: PortRange::new_zero(),
|
||||
},
|
||||
);
|
||||
|
||||
// ExitPolicy reject *6:0
|
||||
expected.push(
|
||||
Reject,
|
||||
AddressPortPattern {
|
||||
ip_pattern: IpPattern::V6Star,
|
||||
ports: PortRange::new_zero(),
|
||||
},
|
||||
);
|
||||
|
||||
// ExitPolicy FE80:0000:0000:0000:0202:B3FF:FE1E:8329:*
|
||||
expected.push(
|
||||
Reject,
|
||||
|
||||
@@ -264,7 +264,13 @@ mod stringified_ip_pattern {
|
||||
impl AddressPortPattern {
|
||||
/// Return true iff this pattern matches a given address and port.
|
||||
pub fn matches(&self, addr: &IpAddr, port: u16) -> bool {
|
||||
self.ip_pattern.matches(addr) && self.ports.contains(port)
|
||||
// For backward compatibility, we treat port 0 as a wildcard until all gateways have
|
||||
// upgraded, at which point we can add *:0 to the policy list.
|
||||
if port == 0 {
|
||||
self.ip_pattern.matches(addr)
|
||||
} else {
|
||||
self.ip_pattern.matches(addr) && self.ports.contains(port)
|
||||
}
|
||||
}
|
||||
|
||||
/// As matches, but accept a SocketAddr.
|
||||
@@ -395,19 +401,9 @@ fn parse_addr(s: &str) -> Result<IpAddr, PolicyError> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Helper: try to parse a port making sure it's non-zero
|
||||
fn parse_port(s: &str) -> Result<u16, PolicyError> {
|
||||
let port = s
|
||||
.parse::<u16>()
|
||||
.map_err(|_| PolicyError::InvalidPort { raw: s.to_string() })?;
|
||||
|
||||
if port == 0 {
|
||||
Err(PolicyError::InvalidPort {
|
||||
raw: port.to_string(),
|
||||
})
|
||||
} else {
|
||||
Ok(port)
|
||||
}
|
||||
s.parse::<u16>()
|
||||
.map_err(|_| PolicyError::InvalidPort { raw: s.to_string() })
|
||||
}
|
||||
|
||||
impl FromStr for IpPattern {
|
||||
@@ -494,6 +490,10 @@ impl PortRange {
|
||||
PortRange::new_unchecked(1, 65535)
|
||||
}
|
||||
|
||||
pub fn new_zero() -> Self {
|
||||
PortRange { start: 0, end: 0 }
|
||||
}
|
||||
|
||||
/// Create a new PortRange.
|
||||
///
|
||||
/// The Portrange contains all ports between `start` and `end` inclusive.
|
||||
@@ -574,6 +574,7 @@ mod test {
|
||||
|
||||
check("marzipan:80");
|
||||
check("1.2.3.4:90-80");
|
||||
check("1.2.3.4:0-80");
|
||||
check("1.2.3.4/100:8888");
|
||||
check("[1.2.3.4]/16:80");
|
||||
check("[::1]/130:8888");
|
||||
@@ -612,6 +613,22 @@ mod test {
|
||||
|
||||
check("0.0.0.0/0:*", &["127.0.0.1:80"], &["[f00b::]:80"]);
|
||||
check("[::]/0:*", &["[f00b::]:80"], &["127.0.0.1:80"]);
|
||||
|
||||
check(
|
||||
"*:0",
|
||||
&["1.2.3.4:0", "[::1]:0", "9.0.0.0:0"],
|
||||
&["1.2.3.4:443", "[::1]:500", "9.0.0.0:80", "[::1]:80"],
|
||||
);
|
||||
check(
|
||||
"*4:0",
|
||||
&["1.2.3.4:0", "9.0.0.0:0"],
|
||||
&["1.2.3.4:443", "9.0.0.0:80", "[::1]:0", "[::1]:80"],
|
||||
);
|
||||
check(
|
||||
"*6:0",
|
||||
&["[::1]:0"],
|
||||
&["[::1]:80", "1.2.3.4:0", "1.2.3.4:443"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -620,6 +637,7 @@ mod test {
|
||||
policy.push(AddressPolicyAction::Accept, "*:443".parse()?);
|
||||
policy.push(AddressPolicyAction::Accept, "[::1]:80".parse()?);
|
||||
policy.push(AddressPolicyAction::Reject, "*:80".parse()?);
|
||||
policy.push(AddressPolicyAction::Accept, "*:0".parse()?);
|
||||
|
||||
let policy = policy; // drop mut
|
||||
assert!(policy
|
||||
@@ -640,6 +658,9 @@ mod test {
|
||||
assert!(policy
|
||||
.allows_sockaddr(&"127.0.0.1:66".parse().unwrap())
|
||||
.is_none());
|
||||
assert!(policy
|
||||
.allows_sockaddr(&"127.0.0.1:0".parse().unwrap())
|
||||
.unwrap());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -672,7 +693,6 @@ mod test {
|
||||
assert_eq!("*".parse::<PortRange>().unwrap(), PortRange::new_all());
|
||||
|
||||
assert!("hello".parse::<PortRange>().is_err());
|
||||
assert!("0".parse::<PortRange>().is_err());
|
||||
assert!("65536".parse::<PortRange>().is_err());
|
||||
assert!("65537".parse::<PortRange>().is_err());
|
||||
assert!("1-2-3".parse::<PortRange>().is_err());
|
||||
@@ -680,6 +700,9 @@ mod test {
|
||||
assert!("1-".parse::<PortRange>().is_err());
|
||||
assert!("-2".parse::<PortRange>().is_err());
|
||||
assert!("-".parse::<PortRange>().is_err());
|
||||
|
||||
assert_eq!("0".parse::<PortRange>().unwrap(), PortRange::new_zero(),);
|
||||
assert!("0-1".parse::<PortRange>().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -8,8 +8,6 @@ documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.3.3"
|
||||
bytes = "1.5.0"
|
||||
@@ -17,3 +15,5 @@ nym-sphinx = { path = "../nymsphinx" }
|
||||
rand = "0.8.5"
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["time"] }
|
||||
tokio-util = { workspace = true, features = ["codec"] }
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use bytes::{Buf, Bytes, BytesMut};
|
||||
use tokio_util::codec::{Decoder, Encoder};
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("{0}")]
|
||||
IO(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
pub const BUFFER_TIMEOUT: Duration = Duration::from_millis(20);
|
||||
|
||||
// TODO: increase this to make max out effective sphinx payload size. Sphinx packets also carry the
|
||||
// MixAck so that's why we can't just use 2kb.
|
||||
pub const MAX_PACKET_SIZE: usize = 1500;
|
||||
|
||||
// Each IP packet is prefixed by a 2 byte length prefix
|
||||
const LENGTH_PREFIX_SIZE: usize = 2;
|
||||
|
||||
// Tokio codec for bundling multiple IP packets into one buffer that is at most 1500 bytes long.
|
||||
// These packets are separated by a 2 byte length prefix. We need a timer so that we don't wait too
|
||||
// long for the buffer to fill up, since this kills latency.
|
||||
pub struct MultiIpPacketCodec {
|
||||
buffer: BytesMut,
|
||||
buffer_timeout: tokio::time::Interval,
|
||||
}
|
||||
|
||||
impl MultiIpPacketCodec {
|
||||
pub fn new(buffer_timeout: Duration) -> Self {
|
||||
MultiIpPacketCodec {
|
||||
buffer: BytesMut::new(),
|
||||
buffer_timeout: tokio::time::interval(buffer_timeout),
|
||||
}
|
||||
}
|
||||
|
||||
// Append a packet to the buffer and return the buffer if it's full
|
||||
pub fn append_packet(&mut self, packet: Bytes) -> Option<Bytes> {
|
||||
let mut bundled_packets = BytesMut::new();
|
||||
self.encode(packet, &mut bundled_packets).unwrap();
|
||||
if bundled_packets.is_empty() {
|
||||
None
|
||||
} else {
|
||||
// log::info!("Sphinx packet utilization: {:.2}", self.buffer.len() as f64 / MAX_PACKET_SIZE as f64);
|
||||
Some(bundled_packets.freeze())
|
||||
}
|
||||
}
|
||||
|
||||
// Flush the current buffer and return it.
|
||||
fn flush_current_buffer(&mut self) -> Bytes {
|
||||
let mut output_buffer = BytesMut::new();
|
||||
std::mem::swap(&mut output_buffer, &mut self.buffer);
|
||||
output_buffer.freeze()
|
||||
}
|
||||
|
||||
// Wait for the buffer_timeout to tick and then flush the buffer.
|
||||
// This is useful when we want to send the buffer even if it's not full.
|
||||
pub async fn buffer_timeout(&mut self) -> Option<Bytes> {
|
||||
// Wait for buffer_timeout to tick
|
||||
let _ = self.buffer_timeout.tick().await;
|
||||
|
||||
// Flush the buffer and return it
|
||||
let packets = self.flush_current_buffer();
|
||||
if packets.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(packets)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Encoder<Bytes> for MultiIpPacketCodec {
|
||||
type Error = Error;
|
||||
|
||||
fn encode(&mut self, packet: Bytes, dst: &mut BytesMut) -> Result<(), Self::Error> {
|
||||
if self.buffer.is_empty() {
|
||||
self.buffer_timeout.reset();
|
||||
}
|
||||
let packet_size = packet.len();
|
||||
|
||||
if self.buffer.len() + packet_size + LENGTH_PREFIX_SIZE > MAX_PACKET_SIZE {
|
||||
// If the packet doesn't fit in the buffer, send the buffer and then add it to the buffer
|
||||
dst.extend_from_slice(&self.buffer);
|
||||
self.buffer = BytesMut::new();
|
||||
self.buffer_timeout.reset();
|
||||
}
|
||||
|
||||
// Add the packet size
|
||||
self.buffer
|
||||
.extend_from_slice(&(packet_size as u16).to_be_bytes());
|
||||
// Add the packet to the buffer
|
||||
self.buffer.extend_from_slice(&packet);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Decoder for MultiIpPacketCodec {
|
||||
type Item = Bytes;
|
||||
type Error = Error;
|
||||
|
||||
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
||||
if src.len() < LENGTH_PREFIX_SIZE {
|
||||
// Not enough bytes to read the length prefix
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let packet_size = u16::from_be_bytes([src[0], src[1]]) as usize;
|
||||
|
||||
if src.len() < packet_size + LENGTH_PREFIX_SIZE {
|
||||
// Not enough bytes to read the packet
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// Remove the length prefix
|
||||
src.advance(LENGTH_PREFIX_SIZE);
|
||||
|
||||
// Read the packet
|
||||
let packet = src.split_to(packet_size);
|
||||
|
||||
Ok(Some(packet.freeze()))
|
||||
}
|
||||
}
|
||||
@@ -1,319 +1,8 @@
|
||||
use std::net::IpAddr;
|
||||
pub mod codec;
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const CURRENT_VERSION: u8 = 1;
|
||||
|
||||
fn generate_random() -> u64 {
|
||||
use rand::RngCore;
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
rng.next_u64()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct IpPacketRequest {
|
||||
pub version: u8,
|
||||
pub data: IpPacketRequestData,
|
||||
}
|
||||
|
||||
impl IpPacketRequest {
|
||||
pub fn new_static_connect_request(
|
||||
ip: IpAddr,
|
||||
reply_to: Recipient,
|
||||
reply_to_hops: Option<u8>,
|
||||
reply_to_avg_mix_delays: Option<f64>,
|
||||
) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketRequestData::StaticConnect(StaticConnectRequest {
|
||||
request_id,
|
||||
ip,
|
||||
reply_to,
|
||||
reply_to_hops,
|
||||
reply_to_avg_mix_delays,
|
||||
}),
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_dynamic_connect_request(
|
||||
reply_to: Recipient,
|
||||
reply_to_hops: Option<u8>,
|
||||
reply_to_avg_mix_delays: Option<f64>,
|
||||
) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketRequestData::DynamicConnect(DynamicConnectRequest {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply_to_hops,
|
||||
reply_to_avg_mix_delays,
|
||||
}),
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_ip_packet(ip_packet: bytes::Bytes) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketRequestData::Data(DataRequest { ip_packet }),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> Option<u64> {
|
||||
match &self.data {
|
||||
IpPacketRequestData::StaticConnect(request) => Some(request.request_id),
|
||||
IpPacketRequestData::DynamicConnect(request) => Some(request.request_id),
|
||||
IpPacketRequestData::Data(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recipient(&self) -> Option<&Recipient> {
|
||||
match &self.data {
|
||||
IpPacketRequestData::StaticConnect(request) => Some(&request.reply_to),
|
||||
IpPacketRequestData::DynamicConnect(request) => Some(&request.reply_to),
|
||||
IpPacketRequestData::Data(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum IpPacketRequestData {
|
||||
StaticConnect(StaticConnectRequest),
|
||||
DynamicConnect(DynamicConnectRequest),
|
||||
Data(DataRequest),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct StaticConnectRequest {
|
||||
pub request_id: u64,
|
||||
pub ip: IpAddr,
|
||||
// The nym-address the response should be sent back to
|
||||
pub reply_to: Recipient,
|
||||
// The number of mix node hops that responses should take, in addition to the entry and exit
|
||||
// node. Zero means only client -> entry -> exit -> client.
|
||||
pub reply_to_hops: Option<u8>,
|
||||
// The average delay at each mix node, in milliseconds. Currently this is not supported by the
|
||||
// ip packet router.
|
||||
pub reply_to_avg_mix_delays: Option<f64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct DynamicConnectRequest {
|
||||
pub request_id: u64,
|
||||
// The nym-address the response should be sent back to
|
||||
pub reply_to: Recipient,
|
||||
// The number of mix node hops that responses should take, in addition to the entry and exit
|
||||
// node. Zero means only client -> entry -> exit -> client.
|
||||
pub reply_to_hops: Option<u8>,
|
||||
// The average delay at each mix node, in milliseconds. Currently this is not supported by the
|
||||
// ip packet router.
|
||||
pub reply_to_avg_mix_delays: Option<f64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct DataRequest {
|
||||
pub ip_packet: bytes::Bytes,
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct IpPacketResponse {
|
||||
pub version: u8,
|
||||
pub data: IpPacketResponseData,
|
||||
}
|
||||
|
||||
impl IpPacketResponse {
|
||||
pub fn new_static_connect_success(request_id: u64, reply_to: Recipient) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketResponseData::StaticConnect(StaticConnectResponse {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: StaticConnectResponseReply::Success,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_static_connect_failure(
|
||||
request_id: u64,
|
||||
reply_to: Recipient,
|
||||
reason: StaticConnectFailureReason,
|
||||
) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketResponseData::StaticConnect(StaticConnectResponse {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: StaticConnectResponseReply::Failure(reason),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_dynamic_connect_success(request_id: u64, reply_to: Recipient, ip: IpAddr) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketResponseData::DynamicConnect(DynamicConnectResponse {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: DynamicConnectResponseReply::Success(DynamicConnectSuccess { ip }),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_dynamic_connect_failure(
|
||||
request_id: u64,
|
||||
reply_to: Recipient,
|
||||
reason: DynamicConnectFailureReason,
|
||||
) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketResponseData::DynamicConnect(DynamicConnectResponse {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: DynamicConnectResponseReply::Failure(reason),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_ip_packet(ip_packet: bytes::Bytes) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketResponseData::Data(DataResponse { ip_packet }),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> Option<u64> {
|
||||
match &self.data {
|
||||
IpPacketResponseData::StaticConnect(response) => Some(response.request_id),
|
||||
IpPacketResponseData::DynamicConnect(response) => Some(response.request_id),
|
||||
IpPacketResponseData::Data(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recipient(&self) -> Option<&Recipient> {
|
||||
match &self.data {
|
||||
IpPacketResponseData::StaticConnect(response) => Some(&response.reply_to),
|
||||
IpPacketResponseData::DynamicConnect(response) => Some(&response.reply_to),
|
||||
IpPacketResponseData::Data(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum IpPacketResponseData {
|
||||
StaticConnect(StaticConnectResponse),
|
||||
DynamicConnect(DynamicConnectResponse),
|
||||
Data(DataResponse),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct StaticConnectResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: StaticConnectResponseReply,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum StaticConnectResponseReply {
|
||||
Success,
|
||||
Failure(StaticConnectFailureReason),
|
||||
}
|
||||
|
||||
impl StaticConnectResponseReply {
|
||||
pub fn is_success(&self) -> bool {
|
||||
match self {
|
||||
StaticConnectResponseReply::Success => true,
|
||||
StaticConnectResponseReply::Failure(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
|
||||
pub enum StaticConnectFailureReason {
|
||||
#[error("requested ip address is already in use")]
|
||||
RequestedIpAlreadyInUse,
|
||||
#[error("requested nym-address is already in use")]
|
||||
RequestedNymAddressAlreadyInUse,
|
||||
#[error("{0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DynamicConnectResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: DynamicConnectResponseReply,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum DynamicConnectResponseReply {
|
||||
Success(DynamicConnectSuccess),
|
||||
Failure(DynamicConnectFailureReason),
|
||||
}
|
||||
|
||||
impl DynamicConnectResponseReply {
|
||||
pub fn is_success(&self) -> bool {
|
||||
match self {
|
||||
DynamicConnectResponseReply::Success(_) => true,
|
||||
DynamicConnectResponseReply::Failure(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DynamicConnectSuccess {
|
||||
pub ip: IpAddr,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
|
||||
pub enum DynamicConnectFailureReason {
|
||||
#[error("requested nym-address is already in use")]
|
||||
RequestedNymAddressAlreadyInUse,
|
||||
#[error("no available ip address")]
|
||||
NoAvailableIp,
|
||||
#[error("{0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DataResponse {
|
||||
pub ip_packet: bytes::Bytes,
|
||||
}
|
||||
pub const CURRENT_VERSION: u8 = 2;
|
||||
|
||||
fn make_bincode_serializer() -> impl bincode::Options {
|
||||
use bincode::Options;
|
||||
@@ -321,63 +10,3 @@ fn make_bincode_serializer() -> impl bincode::Options {
|
||||
.with_big_endian()
|
||||
.with_varint_encoding()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn check_size_of_request() {
|
||||
let connect = IpPacketRequest {
|
||||
version: 4,
|
||||
data: IpPacketRequestData::StaticConnect(
|
||||
StaticConnectRequest {
|
||||
request_id: 123,
|
||||
ip: IpAddr::from([10, 0, 0, 1]),
|
||||
reply_to: Recipient::try_from_base58_string("D1rrpsysCGCYXy9saP8y3kmNpGtJZUXN9SvFoUcqAsM9.9Ssso1ea5NfkbMASdiseDSjTN1fSWda5SgEVjdSN4CvV@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN").unwrap(),
|
||||
reply_to_hops: None,
|
||||
reply_to_avg_mix_delays: None,
|
||||
},
|
||||
)
|
||||
};
|
||||
assert_eq!(connect.to_bytes().unwrap().len(), 107);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_size_of_data() {
|
||||
let data = IpPacketRequest {
|
||||
version: 4,
|
||||
data: IpPacketRequestData::Data(DataRequest {
|
||||
ip_packet: bytes::Bytes::from(vec![1u8; 32]),
|
||||
}),
|
||||
};
|
||||
assert_eq!(data.to_bytes().unwrap().len(), 35);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_and_deserialize_data_request() {
|
||||
let data = IpPacketRequest {
|
||||
version: 4,
|
||||
data: IpPacketRequestData::Data(DataRequest {
|
||||
ip_packet: bytes::Bytes::from(vec![1, 2, 4, 2, 5]),
|
||||
}),
|
||||
};
|
||||
|
||||
let serialized = data.to_bytes().unwrap();
|
||||
let deserialized = IpPacketRequest::from_reconstructed_message(
|
||||
&nym_sphinx::receiver::ReconstructedMessage {
|
||||
message: serialized,
|
||||
sender_tag: None,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(deserialized.version, 4);
|
||||
assert_eq!(
|
||||
deserialized.data,
|
||||
IpPacketRequestData::Data(DataRequest {
|
||||
ip_packet: bytes::Bytes::from(vec![1, 2, 4, 2, 5]),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
use std::net::IpAddr;
|
||||
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{make_bincode_serializer, CURRENT_VERSION};
|
||||
|
||||
fn generate_random() -> u64 {
|
||||
use rand::RngCore;
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
rng.next_u64()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct IpPacketRequest {
|
||||
pub version: u8,
|
||||
pub data: IpPacketRequestData,
|
||||
}
|
||||
|
||||
impl IpPacketRequest {
|
||||
pub fn new_static_connect_request(
|
||||
ip: IpAddr,
|
||||
reply_to: Recipient,
|
||||
reply_to_hops: Option<u8>,
|
||||
reply_to_avg_mix_delays: Option<f64>,
|
||||
) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketRequestData::StaticConnect(StaticConnectRequest {
|
||||
request_id,
|
||||
ip,
|
||||
reply_to,
|
||||
reply_to_hops,
|
||||
reply_to_avg_mix_delays,
|
||||
}),
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_dynamic_connect_request(
|
||||
reply_to: Recipient,
|
||||
reply_to_hops: Option<u8>,
|
||||
reply_to_avg_mix_delays: Option<f64>,
|
||||
) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketRequestData::DynamicConnect(DynamicConnectRequest {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply_to_hops,
|
||||
reply_to_avg_mix_delays,
|
||||
}),
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_disconnect_request(reply_to: Recipient) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketRequestData::Disconnect(DisconnectRequest {
|
||||
request_id,
|
||||
reply_to,
|
||||
}),
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_ip_packet(ip_packet: bytes::Bytes) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketRequestData::Data(DataRequest { ip_packet }),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> Option<u64> {
|
||||
match &self.data {
|
||||
IpPacketRequestData::StaticConnect(request) => Some(request.request_id),
|
||||
IpPacketRequestData::DynamicConnect(request) => Some(request.request_id),
|
||||
IpPacketRequestData::Disconnect(request) => Some(request.request_id),
|
||||
IpPacketRequestData::Data(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recipient(&self) -> Option<&Recipient> {
|
||||
match &self.data {
|
||||
IpPacketRequestData::StaticConnect(request) => Some(&request.reply_to),
|
||||
IpPacketRequestData::DynamicConnect(request) => Some(&request.reply_to),
|
||||
IpPacketRequestData::Disconnect(request) => Some(&request.reply_to),
|
||||
IpPacketRequestData::Data(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum IpPacketRequestData {
|
||||
StaticConnect(StaticConnectRequest),
|
||||
DynamicConnect(DynamicConnectRequest),
|
||||
Disconnect(DisconnectRequest),
|
||||
Data(DataRequest),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct StaticConnectRequest {
|
||||
pub request_id: u64,
|
||||
pub ip: IpAddr,
|
||||
// The nym-address the response should be sent back to
|
||||
pub reply_to: Recipient,
|
||||
// The number of mix node hops that responses should take, in addition to the entry and exit
|
||||
// node. Zero means only client -> entry -> exit -> client.
|
||||
pub reply_to_hops: Option<u8>,
|
||||
// The average delay at each mix node, in milliseconds. Currently this is not supported by the
|
||||
// ip packet router.
|
||||
pub reply_to_avg_mix_delays: Option<f64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct DynamicConnectRequest {
|
||||
pub request_id: u64,
|
||||
// The nym-address the response should be sent back to
|
||||
pub reply_to: Recipient,
|
||||
// The number of mix node hops that responses should take, in addition to the entry and exit
|
||||
// node. Zero means only client -> entry -> exit -> client.
|
||||
pub reply_to_hops: Option<u8>,
|
||||
// The average delay at each mix node, in milliseconds. Currently this is not supported by the
|
||||
// ip packet router.
|
||||
pub reply_to_avg_mix_delays: Option<f64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct DisconnectRequest {
|
||||
pub request_id: u64,
|
||||
// The nym-address the response should be sent back to
|
||||
pub reply_to: Recipient,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct DataRequest {
|
||||
pub ip_packet: bytes::Bytes,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn check_size_of_request() {
|
||||
let connect = IpPacketRequest {
|
||||
version: 4,
|
||||
data: IpPacketRequestData::StaticConnect(
|
||||
StaticConnectRequest {
|
||||
request_id: 123,
|
||||
ip: IpAddr::from([10, 0, 0, 1]),
|
||||
reply_to: Recipient::try_from_base58_string("D1rrpsysCGCYXy9saP8y3kmNpGtJZUXN9SvFoUcqAsM9.9Ssso1ea5NfkbMASdiseDSjTN1fSWda5SgEVjdSN4CvV@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN").unwrap(),
|
||||
reply_to_hops: None,
|
||||
reply_to_avg_mix_delays: None,
|
||||
},
|
||||
)
|
||||
};
|
||||
assert_eq!(connect.to_bytes().unwrap().len(), 107);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_size_of_data() {
|
||||
let data = IpPacketRequest {
|
||||
version: 4,
|
||||
data: IpPacketRequestData::Data(DataRequest {
|
||||
ip_packet: bytes::Bytes::from(vec![1u8; 32]),
|
||||
}),
|
||||
};
|
||||
assert_eq!(data.to_bytes().unwrap().len(), 35);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_and_deserialize_data_request() {
|
||||
let data = IpPacketRequest {
|
||||
version: 4,
|
||||
data: IpPacketRequestData::Data(DataRequest {
|
||||
ip_packet: bytes::Bytes::from(vec![1, 2, 4, 2, 5]),
|
||||
}),
|
||||
};
|
||||
|
||||
let serialized = data.to_bytes().unwrap();
|
||||
let deserialized = IpPacketRequest::from_reconstructed_message(
|
||||
&nym_sphinx::receiver::ReconstructedMessage {
|
||||
message: serialized,
|
||||
sender_tag: None,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(deserialized.version, 4);
|
||||
assert_eq!(
|
||||
deserialized.data,
|
||||
IpPacketRequestData::Data(DataRequest {
|
||||
ip_packet: bytes::Bytes::from(vec![1, 2, 4, 2, 5]),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
use std::net::IpAddr;
|
||||
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{make_bincode_serializer, CURRENT_VERSION};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct IpPacketResponse {
|
||||
pub version: u8,
|
||||
pub data: IpPacketResponseData,
|
||||
}
|
||||
|
||||
impl IpPacketResponse {
|
||||
pub fn new_static_connect_success(request_id: u64, reply_to: Recipient) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketResponseData::StaticConnect(StaticConnectResponse {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: StaticConnectResponseReply::Success,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_static_connect_failure(
|
||||
request_id: u64,
|
||||
reply_to: Recipient,
|
||||
reason: StaticConnectFailureReason,
|
||||
) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketResponseData::StaticConnect(StaticConnectResponse {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: StaticConnectResponseReply::Failure(reason),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_dynamic_connect_success(request_id: u64, reply_to: Recipient, ip: IpAddr) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketResponseData::DynamicConnect(DynamicConnectResponse {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: DynamicConnectResponseReply::Success(DynamicConnectSuccess { ip }),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_dynamic_connect_failure(
|
||||
request_id: u64,
|
||||
reply_to: Recipient,
|
||||
reason: DynamicConnectFailureReason,
|
||||
) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketResponseData::DynamicConnect(DynamicConnectResponse {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: DynamicConnectResponseReply::Failure(reason),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_ip_packet(ip_packet: bytes::Bytes) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketResponseData::Data(DataResponse { ip_packet }),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_version_mismatch(
|
||||
request_id: u64,
|
||||
reply_to: Recipient,
|
||||
request_version: u8,
|
||||
our_version: u8,
|
||||
) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketResponseData::Error(ErrorResponse {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: ErrorResponseReply::VersionMismatch {
|
||||
request_version,
|
||||
response_version: our_version,
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_data_error_response(reply_to: Recipient, reply: ErrorResponseReply) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketResponseData::Error(ErrorResponse {
|
||||
request_id: 0,
|
||||
reply_to,
|
||||
reply,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> Option<u64> {
|
||||
match &self.data {
|
||||
IpPacketResponseData::StaticConnect(response) => Some(response.request_id),
|
||||
IpPacketResponseData::DynamicConnect(response) => Some(response.request_id),
|
||||
IpPacketResponseData::Disconnect(response) => Some(response.request_id),
|
||||
IpPacketResponseData::Data(_) => None,
|
||||
IpPacketResponseData::Error(response) => Some(response.request_id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recipient(&self) -> Option<&Recipient> {
|
||||
match &self.data {
|
||||
IpPacketResponseData::StaticConnect(response) => Some(&response.reply_to),
|
||||
IpPacketResponseData::DynamicConnect(response) => Some(&response.reply_to),
|
||||
IpPacketResponseData::Disconnect(response) => Some(&response.reply_to),
|
||||
IpPacketResponseData::Data(_) => None,
|
||||
IpPacketResponseData::Error(response) => Some(&response.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)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum IpPacketResponseData {
|
||||
StaticConnect(StaticConnectResponse),
|
||||
DynamicConnect(DynamicConnectResponse),
|
||||
Disconnect(DisconnectResponse),
|
||||
Data(DataResponse),
|
||||
Error(ErrorResponse),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct StaticConnectResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: StaticConnectResponseReply,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum StaticConnectResponseReply {
|
||||
Success,
|
||||
Failure(StaticConnectFailureReason),
|
||||
}
|
||||
|
||||
impl StaticConnectResponseReply {
|
||||
pub fn is_success(&self) -> bool {
|
||||
match self {
|
||||
StaticConnectResponseReply::Success => true,
|
||||
StaticConnectResponseReply::Failure(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
|
||||
pub enum StaticConnectFailureReason {
|
||||
#[error("requested ip address is already in use")]
|
||||
RequestedIpAlreadyInUse,
|
||||
#[error("requested nym-address is already in use")]
|
||||
RequestedNymAddressAlreadyInUse,
|
||||
#[error("{0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DynamicConnectResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: DynamicConnectResponseReply,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum DynamicConnectResponseReply {
|
||||
Success(DynamicConnectSuccess),
|
||||
Failure(DynamicConnectFailureReason),
|
||||
}
|
||||
|
||||
impl DynamicConnectResponseReply {
|
||||
pub fn is_success(&self) -> bool {
|
||||
match self {
|
||||
DynamicConnectResponseReply::Success(_) => true,
|
||||
DynamicConnectResponseReply::Failure(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DynamicConnectSuccess {
|
||||
pub ip: IpAddr,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
|
||||
pub enum DynamicConnectFailureReason {
|
||||
#[error("requested nym-address is already in use")]
|
||||
RequestedNymAddressAlreadyInUse,
|
||||
#[error("no available ip address")]
|
||||
NoAvailableIp,
|
||||
#[error("{0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DisconnectResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: DisconnectResponseReply,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum DisconnectResponseReply {
|
||||
Success,
|
||||
Failure(DisconnectFailureReason),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
|
||||
pub enum DisconnectFailureReason {
|
||||
#[error("requested nym-address is not currently connected")]
|
||||
RequestedNymAddressNotConnected,
|
||||
#[error("{0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DataResponse {
|
||||
pub ip_packet: bytes::Bytes,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct ErrorResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: ErrorResponseReply,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
|
||||
pub enum ErrorResponseReply {
|
||||
#[error("{msg}")]
|
||||
Generic { msg: String },
|
||||
#[error(
|
||||
"version mismatch: response is v{request_version} and response is v{response_version}"
|
||||
)]
|
||||
VersionMismatch {
|
||||
request_version: u8,
|
||||
response_version: u8,
|
||||
},
|
||||
#[error("destination failed exit policy filter check: {dst}")]
|
||||
ExitPolicyFilterCheckFailed { dst: String },
|
||||
}
|
||||
@@ -11,6 +11,7 @@ repository.workspace = true
|
||||
cfg-if = { workspace = true }
|
||||
dotenvy = { workspace = true }
|
||||
hex-literal = "0.3.3"
|
||||
log = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
schemars = { workspace = true, features = ["preserve_order"] }
|
||||
serde = { workspace = true, features = ["derive"]}
|
||||
|
||||
@@ -111,6 +111,7 @@ impl NymNetworkDetails {
|
||||
.with_additional_validator_endpoint(ValidatorDetails::new(
|
||||
var(var_names::NYXD).expect("nyxd validator not set"),
|
||||
Some(var(var_names::NYM_API).expect("nym api not set")),
|
||||
get_optional_env(var_names::NYXD_WEBSOCKET),
|
||||
))
|
||||
.with_mixnet_contract(Some(
|
||||
var(var_names::MIXNET_CONTRACT_ADDRESS).expect("mixnet contract not set"),
|
||||
@@ -340,6 +341,9 @@ impl DenomDetailsOwned {
|
||||
pub struct ValidatorDetails {
|
||||
// it is assumed those values are always valid since they're being provided in our defaults file
|
||||
pub nyxd_url: String,
|
||||
//
|
||||
pub websocket_url: Option<String>,
|
||||
|
||||
// Right now api_url is optional as we are not running the api reliably on all validators
|
||||
// however, later on it should be a mandatory field
|
||||
pub api_url: Option<String>,
|
||||
@@ -347,9 +351,10 @@ pub struct ValidatorDetails {
|
||||
}
|
||||
|
||||
impl ValidatorDetails {
|
||||
pub fn new<S: Into<String>>(nyxd_url: S, api_url: Option<S>) -> Self {
|
||||
pub fn new<S: Into<String>>(nyxd_url: S, api_url: Option<S>, websocket_url: Option<S>) -> Self {
|
||||
ValidatorDetails {
|
||||
nyxd_url: nyxd_url.into(),
|
||||
websocket_url: websocket_url.map(Into::into),
|
||||
api_url: api_url.map(Into::into),
|
||||
}
|
||||
}
|
||||
@@ -357,6 +362,7 @@ impl ValidatorDetails {
|
||||
pub fn new_nyxd_only<S: Into<String>>(nyxd_url: S) -> Self {
|
||||
ValidatorDetails {
|
||||
nyxd_url: nyxd_url.into(),
|
||||
websocket_url: None,
|
||||
api_url: None,
|
||||
}
|
||||
}
|
||||
@@ -372,6 +378,12 @@ impl ValidatorDetails {
|
||||
.as_ref()
|
||||
.map(|url| url.parse().expect("the provided api url is invalid!"))
|
||||
}
|
||||
|
||||
pub fn websocket_url(&self) -> Option<Url> {
|
||||
self.websocket_url
|
||||
.as_ref()
|
||||
.map(|url| url.parse().expect("the provided websocket url is invalid!"))
|
||||
}
|
||||
}
|
||||
|
||||
fn fix_deprecated_environmental_variables() {
|
||||
@@ -388,11 +400,25 @@ fn fix_deprecated_environmental_variables() {
|
||||
}
|
||||
}
|
||||
|
||||
// Read the variables from the file and log what the corresponding values in the environment are.
|
||||
fn print_env_vars_with_keys_in_file<P: AsRef<Path> + Copy>(config_env_file: P) {
|
||||
let items = dotenvy::from_path_iter(config_env_file)
|
||||
.expect("Invalid path to environment configuration file");
|
||||
for item in items {
|
||||
let (key, val) = item.expect("Invalid item in environment configuration file");
|
||||
log::debug!("{}: {}", key, val);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_env<P: AsRef<Path>>(config_env_file: Option<P>) {
|
||||
match std::env::var(var_names::CONFIGURED) {
|
||||
// if the configuration is not already set in the env vars
|
||||
Err(std::env::VarError::NotPresent) => {
|
||||
if let Some(config_env_file) = config_env_file {
|
||||
if let Some(config_env_file) = &config_env_file {
|
||||
log::debug!(
|
||||
"Loading environment variables from {:?}",
|
||||
config_env_file.as_ref()
|
||||
);
|
||||
dotenvy::from_path(config_env_file)
|
||||
.expect("Invalid path to environment configuration file");
|
||||
fix_deprecated_environmental_variables();
|
||||
@@ -400,17 +426,25 @@ pub fn setup_env<P: AsRef<Path>>(config_env_file: Option<P>) {
|
||||
// if nothing is set, the use mainnet defaults
|
||||
// if the user has not set `CONFIGURED`, then even if they set any of the env variables,
|
||||
// overwrite them
|
||||
log::debug!("Loading mainnet defaults");
|
||||
crate::mainnet::export_to_env();
|
||||
}
|
||||
}
|
||||
Err(_) => crate::mainnet::export_to_env(),
|
||||
Err(_) => {
|
||||
log::debug!("Environment variables already set. Using them");
|
||||
crate::mainnet::export_to_env()
|
||||
}
|
||||
_ => {
|
||||
fix_deprecated_environmental_variables();
|
||||
}
|
||||
}
|
||||
|
||||
// if we haven't explicitly defined any of the constants, fallback to defaults
|
||||
crate::mainnet::export_to_env_if_not_set()
|
||||
crate::mainnet::export_to_env_if_not_set();
|
||||
|
||||
if let Some(config_env_file) = &config_env_file {
|
||||
print_env_vars_with_keys_in_file(config_env_file);
|
||||
}
|
||||
}
|
||||
|
||||
// Name of the event triggered by the eth contract. If the event name is changed,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use crate::var_names;
|
||||
use crate::{DenomDetails, ValidatorDetails};
|
||||
use std::str::FromStr;
|
||||
|
||||
pub const NETWORK_NAME: &str = "mainnet";
|
||||
|
||||
@@ -25,6 +26,7 @@ pub const REWARDING_VALIDATOR_ADDRESS: &str = "n10yyd98e2tuwu0f7ypz9dy3hhjw7v772
|
||||
pub const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "https://mainnet-stats.nymte.ch:8090/";
|
||||
pub const NYXD_URL: &str = "https://rpc.nymtech.net";
|
||||
pub const NYM_API: &str = "https://validator.nymtech.net/api/";
|
||||
pub const NYXD_WS: &str = "wss://rpc.nymtech.net/websocket";
|
||||
pub const EXPLORER_API: &str = "https://explorer.nymtech.net/api/";
|
||||
|
||||
// I'm making clippy mad on purpose, because that url HAS TO be updated and deployed before merging
|
||||
@@ -32,7 +34,11 @@ pub const EXIT_POLICY_URL: &str =
|
||||
"https://nymtech.net/.wellknown/network-requester/exit-policy.txt";
|
||||
|
||||
pub(crate) fn validators() -> Vec<ValidatorDetails> {
|
||||
vec![ValidatorDetails::new(NYXD_URL, Some(NYM_API))]
|
||||
vec![ValidatorDetails::new(
|
||||
NYXD_URL,
|
||||
Some(NYM_API),
|
||||
Some(NYXD_WS),
|
||||
)]
|
||||
}
|
||||
|
||||
const DEFAULT_SUFFIX: &str = "_MAINNET_DEFAULT";
|
||||
@@ -60,6 +66,12 @@ pub fn read_var_if_not_default(var: &str) -> Option<String> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_parsed_var_if_not_default<T: FromStr>(var: &str) -> Option<Result<T, T::Err>> {
|
||||
read_var_if_not_default(var)
|
||||
.as_deref()
|
||||
.map(FromStr::from_str)
|
||||
}
|
||||
|
||||
pub fn export_to_env() {
|
||||
set_var_to_default(var_names::CONFIGURED, "true");
|
||||
set_var_to_default(var_names::NETWORK_NAME, NETWORK_NAME);
|
||||
@@ -104,6 +116,7 @@ pub fn export_to_env() {
|
||||
);
|
||||
set_var_to_default(var_names::NYXD, NYXD_URL);
|
||||
set_var_to_default(var_names::NYM_API, NYM_API);
|
||||
set_var_to_default(var_names::NYXD_WEBSOCKET, NYXD_WS);
|
||||
set_var_to_default(var_names::EXPLORER_API, EXPLORER_API);
|
||||
set_var_to_default(var_names::EXIT_POLICY_URL, EXIT_POLICY_URL);
|
||||
}
|
||||
@@ -152,6 +165,7 @@ pub fn export_to_env_if_not_set() {
|
||||
);
|
||||
set_var_conditionally_to_default(var_names::NYXD, NYXD_URL);
|
||||
set_var_conditionally_to_default(var_names::NYM_API, NYM_API);
|
||||
set_var_conditionally_to_default(var_names::NYXD_WEBSOCKET, NYXD_WS);
|
||||
set_var_conditionally_to_default(var_names::EXPLORER_API, EXPLORER_API);
|
||||
set_var_conditionally_to_default(var_names::EXIT_POLICY_URL, EXIT_POLICY_URL);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ pub const SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS: &str =
|
||||
pub const NAME_SERVICE_CONTRACT_ADDRESS: &str = "NAME_SERVICE_CONTRACT_ADDRESS";
|
||||
pub const NYXD: &str = "NYXD";
|
||||
pub const NYM_API: &str = "NYM_API";
|
||||
pub const NYXD_WEBSOCKET: &str = "NYXD_WS";
|
||||
pub const EXPLORER_API: &str = "EXPLORER_API";
|
||||
pub const EXIT_POLICY_URL: &str = "EXIT_POLICY";
|
||||
|
||||
|
||||
@@ -255,7 +255,7 @@ fn bench_coconut(c: &mut Criterion) {
|
||||
b.iter(|| {
|
||||
verify_partial_blind_signature(
|
||||
¶ms,
|
||||
&blind_sign_request,
|
||||
blind_sign_request.get_private_attributes_pedersen_commitments(),
|
||||
&public_attributes,
|
||||
random_blind_signature,
|
||||
partial_verification_key,
|
||||
|
||||
@@ -23,6 +23,7 @@ pub use scheme::setup::Parameters;
|
||||
pub use scheme::verification::check_vk_pairing;
|
||||
pub use scheme::verification::prove_bandwidth_credential;
|
||||
pub use scheme::verification::verify_credential;
|
||||
pub use scheme::verification::BlindedSerialNumber;
|
||||
pub use scheme::verification::Theta;
|
||||
pub use scheme::BlindedSignature;
|
||||
pub use scheme::Signature;
|
||||
@@ -43,3 +44,5 @@ mod utils;
|
||||
pub type Attribute = bls12_381::Scalar;
|
||||
pub type PrivateAttribute = Attribute;
|
||||
pub type PublicAttribute = Attribute;
|
||||
|
||||
pub use bls12_381::G1Projective;
|
||||
|
||||
@@ -1,17 +1,46 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use bls12_381::G2Projective;
|
||||
use group::Curve;
|
||||
use std::convert::TryFrom;
|
||||
use std::convert::TryInto;
|
||||
|
||||
use crate::error::{CoconutError, Result};
|
||||
use crate::traits::{Base58, Bytable};
|
||||
use crate::utils::try_deserialize_g2_projective;
|
||||
use bls12_381::{G2Affine, G2Projective};
|
||||
use group::Curve;
|
||||
use std::convert::TryFrom;
|
||||
use std::convert::TryInto;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::ops::Deref;
|
||||
|
||||
pub struct BlindedSerialNumber {
|
||||
pub(crate) inner: G2Projective,
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
pub struct BlindedSerialNumber(G2Projective);
|
||||
|
||||
// use custom Debug implementation to show base58 encoding (rather than raw curve elements)
|
||||
impl Debug for BlindedSerialNumber {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("BlindedSerialNumber")
|
||||
.field(&self.to_bs58())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<G2Projective> for BlindedSerialNumber {
|
||||
fn from(value: G2Projective) -> Self {
|
||||
BlindedSerialNumber(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<G2Affine> for BlindedSerialNumber {
|
||||
fn from(value: G2Affine) -> Self {
|
||||
BlindedSerialNumber(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for BlindedSerialNumber {
|
||||
type Target = G2Projective;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for BlindedSerialNumber {
|
||||
@@ -34,13 +63,13 @@ impl TryFrom<&[u8]> for BlindedSerialNumber {
|
||||
),
|
||||
)?;
|
||||
|
||||
Ok(BlindedSerialNumber { inner })
|
||||
Ok(BlindedSerialNumber(inner))
|
||||
}
|
||||
}
|
||||
|
||||
impl Bytable for BlindedSerialNumber {
|
||||
fn to_byte_vec(&self) -> Vec<u8> {
|
||||
self.inner.to_affine().to_compressed().to_vec()
|
||||
self.0.to_affine().to_compressed().to_vec()
|
||||
}
|
||||
|
||||
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
|
||||
|
||||
@@ -362,12 +362,12 @@ pub fn blind_sign(
|
||||
/// The function returns `true` if the partial blind signature is valid, and `false` otherwise.
|
||||
pub fn verify_partial_blind_signature(
|
||||
params: &Parameters,
|
||||
blind_sign_request: &BlindSignRequest,
|
||||
private_attribute_commitments: &[G1Projective],
|
||||
public_attributes: &[&Attribute],
|
||||
blind_sig: &BlindedSignature,
|
||||
partial_verification_key: &VerificationKey,
|
||||
) -> bool {
|
||||
let num_private_attributes = blind_sign_request.private_attributes_commitments.len();
|
||||
let num_private_attributes = private_attribute_commitments.len();
|
||||
if num_private_attributes + public_attributes.len() > partial_verification_key.beta_g2.len() {
|
||||
return false;
|
||||
}
|
||||
@@ -388,8 +388,7 @@ pub fn verify_partial_blind_signature(
|
||||
];
|
||||
|
||||
// for each private attribute, add (cm_i, beta_i) to the miller terms
|
||||
for (private_attr_commit, beta_g2) in blind_sign_request
|
||||
.private_attributes_commitments
|
||||
for (private_attr_commit, beta_g2) in private_attribute_commitments
|
||||
.iter()
|
||||
.zip(&partial_verification_key.beta_g2)
|
||||
{
|
||||
@@ -422,7 +421,7 @@ pub fn verify_partial_blind_signature(
|
||||
// is equivalent to checking e(a, b) • e(c, d)^{-1} == id
|
||||
// and thus to e(a, b) • e(c^{-1}, d) == id
|
||||
//
|
||||
// compute e(c^1, g2) • e(s, alpha) • e(cm_0, beta_0) • e(cm_i, beta_i) • (s^pub_0, beta_{i+1}) (s^pub_j, beta_{i + j})
|
||||
// compute e(c^{-1}, g2) • e(s, alpha) • e(cm_0, beta_0) • e(cm_i, beta_i) • (s^pub_0, beta_{i+1}) (s^pub_j, beta_{i + j})
|
||||
multi_miller_loop(&terms_refs)
|
||||
.final_exponentiation()
|
||||
.is_identity()
|
||||
@@ -519,7 +518,7 @@ mod tests {
|
||||
|
||||
assert!(verify_partial_blind_signature(
|
||||
¶ms,
|
||||
&request,
|
||||
&request.private_attributes_commitments,
|
||||
&public_attributes,
|
||||
&blind_sig,
|
||||
validator_keypair.verification_key()
|
||||
@@ -539,7 +538,7 @@ mod tests {
|
||||
|
||||
assert!(verify_partial_blind_signature(
|
||||
¶ms,
|
||||
&request,
|
||||
&request.private_attributes_commitments,
|
||||
&[],
|
||||
&blind_sig,
|
||||
validator_keypair.verification_key()
|
||||
@@ -568,7 +567,7 @@ mod tests {
|
||||
// this assertion should fail, as we try to verify with a wrong validator key
|
||||
assert!(!verify_partial_blind_signature(
|
||||
¶ms,
|
||||
&request,
|
||||
&request.private_attributes_commitments,
|
||||
&public_attributes,
|
||||
&blind_sig,
|
||||
validator2_keypair.verification_key()
|
||||
|
||||
@@ -99,7 +99,9 @@ impl SecretKey {
|
||||
Self { x, ys }
|
||||
}
|
||||
|
||||
pub fn into_raw(&self) -> (Scalar, Vec<Scalar>) {
|
||||
/// Extract the Scalar copy of the underlying secrets.
|
||||
/// The caller of this function must exercise extreme care to not misuse the data and ensuring it gets zeroized
|
||||
pub fn hazmat_to_raw(&self) -> (Scalar, Vec<Scalar>) {
|
||||
(self.x, self.ys.clone())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use core::ops::Neg;
|
||||
use std::convert::TryFrom;
|
||||
use std::convert::TryInto;
|
||||
|
||||
use bls12_381::{multi_miller_loop, G1Affine, G2Prepared, G2Projective, Scalar};
|
||||
use group::{Curve, Group};
|
||||
|
||||
use crate::error::{CoconutError, Result};
|
||||
use crate::proofs::ProofKappaZeta;
|
||||
use crate::scheme::double_use::BlindedSerialNumber;
|
||||
use crate::scheme::setup::Parameters;
|
||||
use crate::scheme::Signature;
|
||||
use crate::scheme::VerificationKey;
|
||||
use crate::traits::{Base58, Bytable};
|
||||
use crate::utils::try_deserialize_g2_projective;
|
||||
use crate::Attribute;
|
||||
use bls12_381::{multi_miller_loop, G1Affine, G2Prepared, G2Projective, Scalar};
|
||||
use core::ops::Neg;
|
||||
use group::{Curve, Group};
|
||||
use std::convert::TryFrom;
|
||||
use std::convert::TryInto;
|
||||
|
||||
pub use crate::scheme::double_use::BlindedSerialNumber;
|
||||
|
||||
// TODO NAMING: this whole thing
|
||||
// Theta
|
||||
@@ -25,7 +24,7 @@ pub struct Theta {
|
||||
// blinded_message (kappa)
|
||||
pub blinded_message: G2Projective,
|
||||
// blinded serial number (zeta)
|
||||
pub blinded_serial_number: G2Projective,
|
||||
pub blinded_serial_number: BlindedSerialNumber,
|
||||
// sigma
|
||||
pub credential: Signature,
|
||||
// pi_v
|
||||
@@ -53,15 +52,10 @@ impl TryFrom<&[u8]> for Theta {
|
||||
),
|
||||
)?;
|
||||
|
||||
// safety: we just checked for the length so the unwraps are fine
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let blinded_serial_number_bytes = bytes[96..192].try_into().unwrap();
|
||||
let blinded_serial_number = try_deserialize_g2_projective(
|
||||
&blinded_serial_number_bytes,
|
||||
CoconutError::Deserialization(
|
||||
"failed to deserialize the blinded serial number (zeta)".to_string(),
|
||||
),
|
||||
)?;
|
||||
let blinded_serial_number_bytes = &bytes[96..192];
|
||||
let blinded_serial_number =
|
||||
BlindedSerialNumber::try_from_byte_slice(blinded_serial_number_bytes)?;
|
||||
|
||||
let credential = Signature::try_from(&bytes[192..288])?;
|
||||
|
||||
let pi_v = ProofKappaZeta::from_bytes(&bytes[288..])?;
|
||||
@@ -87,7 +81,7 @@ impl Theta {
|
||||
|
||||
pub fn has_blinded_serial_number(&self, blinded_serial_number_bs58: &str) -> Result<bool> {
|
||||
let blinded_serial_number = BlindedSerialNumber::try_from_bs58(blinded_serial_number_bs58)?;
|
||||
let ret = self.blinded_serial_number.eq(&blinded_serial_number.inner);
|
||||
let ret = self.blinded_serial_number.eq(&blinded_serial_number);
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
@@ -112,10 +106,7 @@ impl Theta {
|
||||
}
|
||||
|
||||
pub fn blinded_serial_number_bs58(&self) -> String {
|
||||
let blinded_serial_nuumber = BlindedSerialNumber {
|
||||
inner: self.blinded_serial_number,
|
||||
};
|
||||
blinded_serial_nuumber.to_bs58()
|
||||
self.blinded_serial_number.to_bs58()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,7 +189,7 @@ pub fn prove_bandwidth_credential(
|
||||
|
||||
Ok(Theta {
|
||||
blinded_message,
|
||||
blinded_serial_number,
|
||||
blinded_serial_number: blinded_serial_number.into(),
|
||||
credential: signature_prime,
|
||||
pi_v,
|
||||
})
|
||||
@@ -228,10 +219,11 @@ pub fn check_vk_pairing(
|
||||
|
||||
// safety: we made an explicit check for if the length of the slice is 0, thus unwrap here is fine
|
||||
#[allow(clippy::unwrap_used)]
|
||||
if &vk.alpha != *dkg_values.last().as_ref().unwrap() {
|
||||
if &vk.alpha != *dkg_values.first().as_ref().unwrap() {
|
||||
return false;
|
||||
}
|
||||
if dkg_values
|
||||
let dkg_betas = &dkg_values[1..];
|
||||
if dkg_betas
|
||||
.iter()
|
||||
.zip(vk.beta_g2.iter())
|
||||
.any(|(dkg_beta, vk_beta)| dkg_beta != vk_beta)
|
||||
@@ -329,8 +321,9 @@ mod tests {
|
||||
let params = setup(2).unwrap();
|
||||
let keypair = keygen(¶ms);
|
||||
let vk = keypair.verification_key();
|
||||
let mut dkg_values = vk.beta_g2.clone();
|
||||
dkg_values.push(vk.alpha);
|
||||
|
||||
let mut dkg_values = vec![vk.alpha];
|
||||
dkg_values.append(&mut vk.beta_g2.clone());
|
||||
assert!(check_vk_pairing(¶ms, &dkg_values, vk));
|
||||
}
|
||||
|
||||
|
||||
@@ -108,6 +108,17 @@ pub fn transpose_matrix<T: Debug>(matrix: Vec<Vec<T>>) -> Vec<Vec<T>> {
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! random_scalars_refs {
|
||||
( $x: ident, $params: expr, $n: expr ) => {
|
||||
let _vec = $params.n_random_scalars($n);
|
||||
#[allow(clippy::map_identity)]
|
||||
let $x = _vec.iter().collect::<Vec<_>>();
|
||||
};
|
||||
}
|
||||
|
||||
pub use random_scalars_refs;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
@@ -170,14 +181,3 @@ pub mod tests {
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! random_scalars_refs {
|
||||
( $x: ident, $params: expr, $n: expr ) => {
|
||||
let _vec = $params.n_random_scalars($n);
|
||||
#[allow(clippy::map_identity)]
|
||||
let $x = _vec.iter().collect::<Vec<_>>();
|
||||
};
|
||||
}
|
||||
|
||||
pub use random_scalars_refs;
|
||||
|
||||
@@ -239,8 +239,15 @@ impl NymMessage {
|
||||
let (packets_used, space_left) =
|
||||
chunking::number_of_required_fragments(total_required_bytes, plaintext_per_packet);
|
||||
|
||||
let wasted_space = space_left as f32 / (bytes.len() + 1 + space_left) as f32;
|
||||
log::trace!("Padding {self_display}: {} of raw plaintext bytes are required. They're going to be put into {packets_used} sphinx packets with {space_left} bytes of leftover space. {wasted_space}% of packet capacity is going to be wasted.", bytes.len() + 1);
|
||||
let wasted_space_percentage =
|
||||
(space_left as f32 / (bytes.len() + 1 + space_left) as f32) * 100.0;
|
||||
log::trace!(
|
||||
"Padding {self_display}: {} of raw plaintext bytes are required. \
|
||||
They're going to be put into {packets_used} sphinx packets with {space_left} bytes \
|
||||
of leftover space. {wasted_space_percentage:.1}% of packet capacity is going to \
|
||||
be wasted.",
|
||||
bytes.len() + 1
|
||||
);
|
||||
|
||||
bytes
|
||||
.into_iter()
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
[package]
|
||||
name = "nyxd-scraper"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
async-trait.workspace = true
|
||||
const_format = "0.2.32"
|
||||
cosmrs.workspace = true
|
||||
eyre = "0.6.9"
|
||||
futures.workspace = true
|
||||
sha2 = "0.10.8"
|
||||
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate", "time"] }
|
||||
tendermint.workspace = true
|
||||
tendermint-rpc = { workspace = true, features = ["websocket-client", "http-client"] }
|
||||
thiserror.workspace = true
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
tokio-stream = "0.1.14"
|
||||
tokio-util = { version = "0.7.10", features = ["rt"]}
|
||||
tracing.workspace = true
|
||||
url.workspace = true
|
||||
|
||||
|
||||
# TEMP
|
||||
nym-bin-common = { path = "../bin-common", features = ["basic_tracing"]}
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
use sqlx::{Connection, SqliteConnection};
|
||||
use std::env;
|
||||
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let database_path = format!("{out_dir}/scraper-example.sqlite");
|
||||
|
||||
let mut conn = SqliteConnection::connect(&format!("sqlite://{database_path}?mode=rwc"))
|
||||
.await
|
||||
.expect("Failed to create SQLx database connection");
|
||||
|
||||
sqlx::migrate!("./sql_migrations")
|
||||
.run(&mut conn)
|
||||
.await
|
||||
.expect("Failed to perform SQLx migrations");
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
println!("cargo:rustc-env=DATABASE_URL=sqlite://{}", &database_path);
|
||||
|
||||
#[cfg(target_family = "windows")]
|
||||
// for some strange reason we need to add a leading `/` to the windows path even though it's
|
||||
// not a valid windows path... but hey, it works...
|
||||
println!("cargo:rustc-env=DATABASE_URL=sqlite:///{}", &database_path);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
CREATE TABLE METADATA
|
||||
(
|
||||
id INTEGER PRIMARY KEY CHECK (id = 0),
|
||||
last_processed_height INTEGER NOT NULL
|
||||
);
|
||||
@@ -0,0 +1,76 @@
|
||||
CREATE TABLE validator
|
||||
(
|
||||
consensus_address TEXT NOT NULL PRIMARY KEY, /* Validator consensus address */
|
||||
consensus_pubkey TEXT NOT NULL UNIQUE /* Validator consensus public key */
|
||||
);
|
||||
|
||||
CREATE TABLE pre_commit
|
||||
(
|
||||
validator_address TEXT NOT NULL REFERENCES validator (consensus_address),
|
||||
height BIGINT NOT NULL,
|
||||
timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL,
|
||||
voting_power BIGINT NOT NULL,
|
||||
proposer_priority BIGINT NOT NULL,
|
||||
UNIQUE (validator_address, timestamp)
|
||||
);
|
||||
CREATE INDEX pre_commit_validator_address_index ON pre_commit (validator_address);
|
||||
CREATE INDEX pre_commit_height_index ON pre_commit (height);
|
||||
|
||||
CREATE TABLE block
|
||||
(
|
||||
height BIGINT UNIQUE PRIMARY KEY,
|
||||
hash TEXT NOT NULL UNIQUE,
|
||||
num_txs INTEGER DEFAULT 0,
|
||||
total_gas BIGINT DEFAULT 0,
|
||||
proposer_address TEXT REFERENCES validator (consensus_address),
|
||||
timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL
|
||||
);
|
||||
CREATE INDEX block_height_index ON block (height);
|
||||
CREATE INDEX block_hash_index ON block (hash);
|
||||
CREATE INDEX block_proposer_address_index ON block (proposer_address);
|
||||
|
||||
-- no JSONB in sqlite (yet) : )
|
||||
CREATE TABLE "transaction"
|
||||
(
|
||||
hash TEXT UNIQUE NOT NULL,
|
||||
height BIGINT NOT NULL REFERENCES block (height),
|
||||
"index" INTEGER NOT NULL,
|
||||
success BOOLEAN NOT NULL,
|
||||
|
||||
/* Body */
|
||||
num_messages INTEGER NOT NULL,
|
||||
-- messages JSONB NOT NULL,-- DEFAULT '[]'::JSONB,
|
||||
memo TEXT,
|
||||
-- signatures TEXT[] NOT NULL,
|
||||
|
||||
/* AuthInfo */
|
||||
-- signer_infos JSONB NOT NULL,-- DEFAULT '[]'::JSONB,
|
||||
-- fee JSONB NOT NULL,-- DEFAULT '{}'::JSONB,
|
||||
|
||||
/* Tx response */
|
||||
gas_wanted BIGINT DEFAULT 0,
|
||||
gas_used BIGINT DEFAULT 0,
|
||||
raw_log TEXT
|
||||
-- logs JSONB
|
||||
);
|
||||
CREATE INDEX transaction_hash_index ON "transaction" (hash);
|
||||
CREATE INDEX transaction_height_index ON "transaction" (height);
|
||||
|
||||
CREATE TABLE message
|
||||
(
|
||||
transaction_hash TEXT NOT NULL REFERENCES "transaction" (hash),
|
||||
"index" BIGINT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
-- value JSONB NOT NULL,
|
||||
-- involved_accounts_addresses TEXT[] NOT NULL,
|
||||
|
||||
height BIGINT NOT NULL,
|
||||
CONSTRAINT unique_message_per_tx UNIQUE (transaction_hash, "index")
|
||||
);
|
||||
CREATE INDEX message_transaction_hash_index ON message (transaction_hash);
|
||||
CREATE INDEX message_type_index ON message (type);
|
||||
|
||||
CREATE TABLE pruning
|
||||
(
|
||||
last_pruned_height BIGINT NOT NULL
|
||||
);
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::block_processor::MAX_RANGE_SIZE;
|
||||
use std::cmp::min;
|
||||
use std::collections::VecDeque;
|
||||
use std::ops::Range;
|
||||
|
||||
pub(crate) fn split_request_range(request_range: Range<u32>) -> VecDeque<Range<u32>> {
|
||||
let mut requests = VecDeque::new();
|
||||
|
||||
let mut start = request_range.start;
|
||||
let mut end = min(request_range.end, start + MAX_RANGE_SIZE as u32);
|
||||
|
||||
loop {
|
||||
requests.push_back(start..end);
|
||||
start = min(start + MAX_RANGE_SIZE as u32, request_range.end);
|
||||
end = min(end + MAX_RANGE_SIZE as u32, request_range.end);
|
||||
|
||||
if start == end {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
requests
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn splitting_request_range() {
|
||||
let range = 0..100;
|
||||
let mut expected = VecDeque::new();
|
||||
expected.push_back(0..30);
|
||||
expected.push_back(30..60);
|
||||
expected.push_back(60..90);
|
||||
expected.push_back(90..100);
|
||||
assert_eq!(expected, split_request_range(range));
|
||||
|
||||
let range = 0..30;
|
||||
let mut expected = VecDeque::new();
|
||||
expected.push_back(0..30);
|
||||
assert_eq!(expected, split_request_range(range));
|
||||
|
||||
let range = 0..60;
|
||||
let mut expected = VecDeque::new();
|
||||
expected.push_back(0..30);
|
||||
expected.push_back(30..60);
|
||||
assert_eq!(expected, split_request_range(range));
|
||||
|
||||
let range = 0..5;
|
||||
let mut expected = VecDeque::new();
|
||||
expected.push_back(0..5);
|
||||
assert_eq!(expected, split_request_range(range));
|
||||
|
||||
let range = 123..200;
|
||||
let mut expected = VecDeque::new();
|
||||
expected.push_back(123..153);
|
||||
expected.push_back(153..183);
|
||||
expected.push_back(183..200);
|
||||
assert_eq!(expected, split_request_range(range));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::block_processor::helpers::split_request_range;
|
||||
use crate::block_processor::types::BlockToProcess;
|
||||
use crate::block_requester::BlockRequest;
|
||||
use crate::error::ScraperError;
|
||||
use crate::modules::{BlockModule, MsgModule, TxModule};
|
||||
use crate::rpc_client::RpcClient;
|
||||
use crate::storage::{persist_block, ScraperStorage};
|
||||
use futures::StreamExt;
|
||||
use std::collections::{BTreeMap, HashSet, VecDeque};
|
||||
use std::ops::{Add, Range};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::mpsc::{Sender, UnboundedReceiver};
|
||||
use tokio::sync::Notify;
|
||||
use tokio::time::{interval_at, Instant};
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
mod helpers;
|
||||
pub(crate) mod types;
|
||||
|
||||
const MISSING_BLOCKS_CHECK_INTERVAL: Duration = Duration::from_secs(30);
|
||||
const MAX_MISSING_BLOCKS_DELAY: Duration = Duration::from_secs(15);
|
||||
const MAX_RANGE_SIZE: usize = 30;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct PendingSync {
|
||||
request_in_flight: HashSet<u32>,
|
||||
queued_requests: VecDeque<Range<u32>>,
|
||||
}
|
||||
|
||||
impl PendingSync {
|
||||
fn is_empty(&self) -> bool {
|
||||
self.request_in_flight.is_empty() && self.queued_requests.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BlockProcessor {
|
||||
cancel: CancellationToken,
|
||||
synced: Arc<Notify>,
|
||||
last_processed_height: u32,
|
||||
last_processed_at: Instant,
|
||||
pending_sync: PendingSync,
|
||||
queued_blocks: BTreeMap<u32, BlockToProcess>,
|
||||
|
||||
rpc_client: RpcClient,
|
||||
incoming: UnboundedReceiverStream<BlockToProcess>,
|
||||
block_requester: Sender<BlockRequest>,
|
||||
storage: ScraperStorage,
|
||||
|
||||
// future work: rather than sending each msg to every msg module,
|
||||
// let them subscribe based on `type_url` inside the message itself
|
||||
// (like "/cosmwasm.wasm.v1.MsgExecuteContract")
|
||||
block_modules: Vec<Box<dyn BlockModule + Send>>,
|
||||
tx_modules: Vec<Box<dyn TxModule + Send>>,
|
||||
msg_modules: Vec<Box<dyn MsgModule + Send>>,
|
||||
}
|
||||
|
||||
impl BlockProcessor {
|
||||
pub async fn new(
|
||||
cancel: CancellationToken,
|
||||
synced: Arc<Notify>,
|
||||
incoming: UnboundedReceiver<BlockToProcess>,
|
||||
block_requester: Sender<BlockRequest>,
|
||||
storage: ScraperStorage,
|
||||
rpc_client: RpcClient,
|
||||
) -> Result<Self, ScraperError> {
|
||||
let last_processed = storage.get_last_processed_height().await?;
|
||||
|
||||
Ok(BlockProcessor {
|
||||
cancel,
|
||||
synced,
|
||||
last_processed_height: last_processed.try_into().unwrap_or_default(),
|
||||
last_processed_at: Instant::now(),
|
||||
pending_sync: Default::default(),
|
||||
queued_blocks: Default::default(),
|
||||
rpc_client,
|
||||
incoming: incoming.into(),
|
||||
block_requester,
|
||||
storage,
|
||||
block_modules: vec![],
|
||||
tx_modules: vec![],
|
||||
msg_modules: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
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?;
|
||||
|
||||
debug!(
|
||||
"this block has {} transaction(s)",
|
||||
full_info.transactions.len()
|
||||
);
|
||||
for tx in &full_info.transactions {
|
||||
debug!("{} has {} message(s)", tx.hash, tx.tx.body.messages.len());
|
||||
for (index, msg) in tx.tx.body.messages.iter().enumerate() {
|
||||
debug!("{index}: {:?}", msg.type_url)
|
||||
}
|
||||
}
|
||||
|
||||
// process the entire block as a transaction so that if anything fails,
|
||||
// we won't end up with a corrupted storage.
|
||||
let mut tx = self.storage.begin_processing_tx().await?;
|
||||
|
||||
persist_block(&full_info, &mut tx).await?;
|
||||
|
||||
// let the modules do whatever they want
|
||||
// the ones wanting the full block:
|
||||
for block_module in &mut self.block_modules {
|
||||
block_module.handle_block(&full_info, &mut tx).await?;
|
||||
}
|
||||
|
||||
// the ones wanting transactions:
|
||||
for block_tx in full_info.transactions {
|
||||
for tx_module in &mut self.tx_modules {
|
||||
tx_module.handle_tx(&block_tx, &mut tx).await?;
|
||||
}
|
||||
// the ones concerned with individual messages
|
||||
for (index, msg) in block_tx.tx.body.messages.iter().enumerate() {
|
||||
for msg_module in &mut self.msg_modules {
|
||||
msg_module
|
||||
.handle_msg(index, msg, &block_tx, &mut tx)
|
||||
.await?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tx.commit()
|
||||
.await
|
||||
.map_err(|source| ScraperError::StorageTxCommitFailure { source })?;
|
||||
|
||||
self.last_processed_height = full_info.block.header.height.value() as u32;
|
||||
self.last_processed_at = Instant::now();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_block_modules(&mut self, modules: Vec<Box<dyn BlockModule + Send>>) {
|
||||
self.block_modules = modules;
|
||||
}
|
||||
|
||||
pub fn set_tx_modules(&mut self, modules: Vec<Box<dyn TxModule + Send>>) {
|
||||
self.tx_modules = modules;
|
||||
}
|
||||
|
||||
pub fn set_msg_modules(&mut self, modules: Vec<Box<dyn MsgModule + Send>>) {
|
||||
self.msg_modules = modules;
|
||||
}
|
||||
|
||||
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 {
|
||||
debug!("no need to request missing blocks");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if self.try_request_pending().await {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// TODO: properly fill in the gaps later with BlockRequest::Specific,
|
||||
let request_range = if let Some((next_available, _)) = self.queued_blocks.first_key_value()
|
||||
{
|
||||
self.last_processed_height + 1..*next_available
|
||||
} else {
|
||||
let current_height = self.rpc_client.current_block_height().await? as u32;
|
||||
self.last_processed_height + 1..current_height + 1
|
||||
};
|
||||
|
||||
self.request_missing_blocks(request_range).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn request_missing_blocks(
|
||||
&mut self,
|
||||
request_range: Range<u32>,
|
||||
) -> Result<(), ScraperError> {
|
||||
let request_range = if request_range.len() > MAX_RANGE_SIZE {
|
||||
let mut split = split_request_range(request_range);
|
||||
|
||||
// SAFETY: we know that after the split of a non-empty range we have AT LEAST one value
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let first = split.pop_front().unwrap();
|
||||
self.pending_sync.queued_requests = split;
|
||||
self.pending_sync.request_in_flight = first.clone().collect();
|
||||
|
||||
first
|
||||
} else {
|
||||
request_range
|
||||
};
|
||||
|
||||
self.send_blocks_request(request_range).await
|
||||
}
|
||||
|
||||
// technically we're not mutating self here,
|
||||
// but we need it to help the compiler figure out the future is `Send`
|
||||
async fn send_blocks_request(&mut self, request_range: Range<u32>) -> Result<(), ScraperError> {
|
||||
debug!("requesting missing blocks: {request_range:?}");
|
||||
|
||||
self.block_requester
|
||||
.send(BlockRequest::Range(request_range))
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn next_incoming(&mut self, block: BlockToProcess) {
|
||||
let height = block.height;
|
||||
|
||||
self.pending_sync.request_in_flight.remove(&height);
|
||||
|
||||
if self.last_processed_height == 0 {
|
||||
// this is the first time we've started up the process
|
||||
debug!("setting up initial processing height");
|
||||
self.last_processed_height = height - 1
|
||||
}
|
||||
|
||||
if height <= self.last_processed_height {
|
||||
warn!("we have already processed block for height {height}");
|
||||
return;
|
||||
}
|
||||
|
||||
if self.last_processed_height + 1 != height {
|
||||
if self.queued_blocks.insert(height, block).is_some() {
|
||||
warn!("we have already queued up block for height {height}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if let Err(err) = self.process_block(block).await {
|
||||
error!("failed to process block at height {height}: {err}");
|
||||
return;
|
||||
}
|
||||
|
||||
// process as much as we can from the queue
|
||||
let mut next = height + 1;
|
||||
while let Some(next_block) = self.queued_blocks.remove(&next) {
|
||||
if let Err(err) = self.process_block(next_block).await {
|
||||
error!("failed to process queued-up block at height {next}: {err}")
|
||||
}
|
||||
next += 1;
|
||||
}
|
||||
|
||||
self.try_request_pending().await;
|
||||
|
||||
if self.pending_sync.is_empty() {
|
||||
self.synced.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
async fn try_request_pending(&mut self) -> bool {
|
||||
if self.pending_sync.request_in_flight.is_empty() {
|
||||
if let Some(next_sync) = self.pending_sync.queued_requests.pop_front() {
|
||||
debug!(
|
||||
"current request range has been resolved. requesting another bunch of blocks"
|
||||
);
|
||||
if let Err(err) = self.send_blocks_request(next_sync.clone()).await {
|
||||
error!("failed to request resync blocks: {err}");
|
||||
self.pending_sync.queued_requests.push_front(next_sync);
|
||||
} else {
|
||||
self.pending_sync.request_in_flight = next_sync.collect()
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
// technically we're not mutating self here,
|
||||
// but we need it to help the compiler figure out the future is `Send`
|
||||
async fn startup_resync(&mut self) -> Result<(), ScraperError> {
|
||||
assert!(self.pending_sync.is_empty());
|
||||
|
||||
let latest_block = self.rpc_client.current_block_height().await? as u32;
|
||||
if latest_block > self.last_processed_height && self.last_processed_height != 0 {
|
||||
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?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn run(&mut self) {
|
||||
info!("starting processing loop");
|
||||
|
||||
// sure, we could be more efficient and reset it on every processed block,
|
||||
// but the overhead is so minimal that it doesn't matter
|
||||
let mut missing_check_interval = interval_at(
|
||||
Instant::now().add(MISSING_BLOCKS_CHECK_INTERVAL),
|
||||
MISSING_BLOCKS_CHECK_INTERVAL,
|
||||
);
|
||||
|
||||
if let Err(err) = self.startup_resync().await {
|
||||
error!("failed to perform startup sync: {err}");
|
||||
self.cancel.cancel();
|
||||
return;
|
||||
};
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = self.cancel.cancelled() => {
|
||||
info!("received cancellation token");
|
||||
break
|
||||
}
|
||||
_ = missing_check_interval.tick() => {
|
||||
if let Err(err) = self.maybe_request_missing_blocks().await {
|
||||
error!("failed to request missing blocks: {err}")
|
||||
}
|
||||
}
|
||||
block = self.incoming.next() => {
|
||||
match block {
|
||||
Some(block) => self.next_incoming(block).await,
|
||||
None => {
|
||||
warn!("stopped receiving new blocks");
|
||||
self.cancel.cancel();
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::ScraperError;
|
||||
use crate::helpers;
|
||||
use tendermint::{abci, block, tx, Block, Hash};
|
||||
use tendermint_rpc::endpoint::{block as block_endpoint, block_results, validators};
|
||||
use tendermint_rpc::event::{Event, EventData};
|
||||
|
||||
// just get all everything out of tx::Response, but parse raw `tx` bytes
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ParsedTransactionResponse {
|
||||
/// The hash of the transaction.
|
||||
///
|
||||
/// Deserialized from a hex-encoded string (there is a discrepancy between
|
||||
/// the format used for the request and the format used for the response in
|
||||
/// the Tendermint RPC).
|
||||
pub hash: Hash,
|
||||
|
||||
pub height: block::Height,
|
||||
|
||||
pub index: u32,
|
||||
|
||||
pub tx_result: abci::types::ExecTxResult,
|
||||
|
||||
pub tx: cosmrs::tx::Tx,
|
||||
|
||||
pub proof: Option<tx::Proof>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FullBlockInformation {
|
||||
/// Basic block information, including its signers.
|
||||
pub block: Block,
|
||||
|
||||
/// All of the emitted events alongside any tx results.
|
||||
pub results: block_results::Response,
|
||||
|
||||
/// Validator set for this particular block
|
||||
pub validators: validators::Response,
|
||||
|
||||
/// Transaction results from this particular block
|
||||
pub transactions: Vec<ParsedTransactionResponse>,
|
||||
}
|
||||
|
||||
impl FullBlockInformation {
|
||||
pub fn ensure_proposer(&self) -> Result<(), ScraperError> {
|
||||
let block_proposer = self.block.header.proposer_address;
|
||||
if !self
|
||||
.validators
|
||||
.validators
|
||||
.iter()
|
||||
.any(|v| v.address == block_proposer)
|
||||
{
|
||||
let proposer = helpers::validator_consensus_address(block_proposer)?;
|
||||
return Err(ScraperError::BlockProposerNotInValidatorSet {
|
||||
height: self.block.header.height.value() as u32,
|
||||
proposer: proposer.to_string(),
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct BlockToProcess {
|
||||
pub(crate) height: u32,
|
||||
pub(crate) block: Block,
|
||||
}
|
||||
|
||||
impl From<Block> for BlockToProcess {
|
||||
fn from(block: Block) -> Self {
|
||||
BlockToProcess {
|
||||
height: block.header.height.value() as u32,
|
||||
block,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Event> for BlockToProcess {
|
||||
type Error = ScraperError;
|
||||
|
||||
fn try_from(event: Event) -> Result<Self, Self::Error> {
|
||||
let query = event.query.clone();
|
||||
|
||||
// TODO: we're losing `result_begin_block` and `result_end_block` here but maybe that's fine?
|
||||
let maybe_block = match event.data {
|
||||
// we don't care about `NewBlock` until CometBFT 0.38, i.e. until we upgrade to wasmd 0.50
|
||||
EventData::NewBlock { .. } => {
|
||||
return Err(ScraperError::InvalidSubscriptionEvent {
|
||||
query,
|
||||
kind: "NewBlock".to_string(),
|
||||
})
|
||||
}
|
||||
EventData::LegacyNewBlock { block, .. } => block,
|
||||
EventData::Tx { .. } => {
|
||||
return Err(ScraperError::InvalidSubscriptionEvent {
|
||||
query,
|
||||
kind: "Tx".to_string(),
|
||||
})
|
||||
}
|
||||
EventData::GenericJsonEvent(_) => {
|
||||
return Err(ScraperError::InvalidSubscriptionEvent {
|
||||
query,
|
||||
kind: "GenericJsonEvent".to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
let Some(block) = maybe_block else {
|
||||
return Err(ScraperError::EmptyBlockData { query });
|
||||
};
|
||||
|
||||
Ok((*block).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<block_endpoint::Response> for BlockToProcess {
|
||||
fn from(value: block_endpoint::Response) -> Self {
|
||||
value.block.into()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::block_processor::types::BlockToProcess;
|
||||
use crate::error::ScraperError;
|
||||
use crate::rpc_client::RpcClient;
|
||||
use futures::StreamExt;
|
||||
use std::ops::Range;
|
||||
use tokio::sync::mpsc::{Receiver, UnboundedSender};
|
||||
use tokio_stream::wrappers::ReceiverStream;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::{error, info, instrument, warn};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum BlockRequest {
|
||||
Range(Range<u32>),
|
||||
|
||||
// UNIMPLEMENTED:
|
||||
#[allow(dead_code)]
|
||||
Specific(Vec<u32>),
|
||||
}
|
||||
|
||||
pub(crate) struct BlockRequester {
|
||||
cancel: CancellationToken,
|
||||
rpc_client: RpcClient,
|
||||
requests: ReceiverStream<BlockRequest>,
|
||||
blocks: UnboundedSender<BlockToProcess>,
|
||||
}
|
||||
|
||||
impl BlockRequester {
|
||||
pub(crate) fn new(
|
||||
cancel: CancellationToken,
|
||||
rpc_client: RpcClient,
|
||||
requests: Receiver<BlockRequest>,
|
||||
blocks: UnboundedSender<BlockToProcess>,
|
||||
) -> Self {
|
||||
BlockRequester {
|
||||
cancel,
|
||||
rpc_client,
|
||||
requests: requests.into(),
|
||||
blocks,
|
||||
}
|
||||
}
|
||||
|
||||
async fn request_and_send(&self, height: u32) -> Result<(), ScraperError> {
|
||||
let block = self.rpc_client.get_basic_block_details(height).await?;
|
||||
self.blocks.send(block.into())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn request_blocks<I: IntoIterator<Item = u32>>(&self, heights: I) {
|
||||
futures::stream::iter(heights)
|
||||
.for_each_concurrent(4, |height| async move {
|
||||
if let Err(err) = self.request_and_send(height).await {
|
||||
error!("failed to request block data: {err}")
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(skip(self))]
|
||||
async fn handle_blocks_request(&self, request: BlockRequest) {
|
||||
info!("received request for missed blocks");
|
||||
|
||||
match request {
|
||||
BlockRequest::Range(range) => self.request_blocks(range).await,
|
||||
BlockRequest::Specific(heights) => self.request_blocks(heights).await,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn run(&mut self) {
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = self.cancel.cancelled() => {
|
||||
info!("received cancellation token");
|
||||
break
|
||||
}
|
||||
maybe_request = self.requests.next() => {
|
||||
match maybe_request {
|
||||
Some(request) => self.handle_blocks_request(request).await,
|
||||
None => {
|
||||
warn!("stopped receiving new requests");
|
||||
self.cancel.cancel();
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use const_format::concatcp;
|
||||
|
||||
// TODO: make those configurable via 'NymNetworkDetails'
|
||||
|
||||
// BECH32_PREFIX defines the main SDK Bech32 prefix of an account's address
|
||||
pub const BECH32_PREFIX: &str = "n";
|
||||
|
||||
// ACCOUNT_PREFIX is the prefix for account keys
|
||||
pub const ACCOUNT_PREFIX: &str = "acc";
|
||||
// VALIDATOR_PREFIX is the prefix for validator keys
|
||||
pub const VALIDATOR_PREFIX: &str = "val";
|
||||
// CONSENSUS_PREFIX is the prefix for consensus keys
|
||||
pub const CONSENSUS_PREFIX: &str = "cons";
|
||||
// PUBKEY_PREFIX is the prefix for public keys
|
||||
pub const PUBKEY_PREFIX: &str = "pub";
|
||||
// OPERATOR_PREFIX is the prefix for operator keys
|
||||
pub const OPERATOR_PREFIX: &str = "oper";
|
||||
// ADDRESS_PREFIX is the prefix for addresses
|
||||
pub const ADDRESS_PREFIX: &str = "addr";
|
||||
|
||||
// BECH32_ACCOUNT_ADDRESS_PREFIX defines the Bech32 prefix of an account's address
|
||||
pub const BECH32_ACCOUNT_ADDRESS_PREFIX: &str = BECH32_PREFIX;
|
||||
// BECH32_ACCOUNT_PUBKEY_PREFIX defines the Bech32 prefix of an account's public key
|
||||
pub const BECH32_ACCOUNT_PUBKEY_PREFIX: &str = concatcp!(BECH32_PREFIX, PUBKEY_PREFIX);
|
||||
// BECH32_VALIDATOR_ADDRESS_PREFIX defines the Bech32 prefix of a validator's operator address
|
||||
pub const BECH32_VALIDATOR_ADDRESS_PREFIX: &str =
|
||||
concatcp!(BECH32_PREFIX, VALIDATOR_PREFIX, OPERATOR_PREFIX);
|
||||
// BECH32_VALIDATOR_PUBKEY_PREFIX defines the Bech32 prefix of a validator's operator public key
|
||||
pub const BECH32_VALIDATOR_PUBKEY_PREFIX: &str = concatcp!(
|
||||
BECH32_PREFIX,
|
||||
VALIDATOR_PREFIX,
|
||||
OPERATOR_PREFIX,
|
||||
PUBKEY_PREFIX
|
||||
);
|
||||
// BECH32_CONSENSUS_ADDRESS_PREFIX defines the Bech32 prefix of a consensus node address
|
||||
pub const BECH32_CONSENSUS_ADDRESS_PREFIX: &str =
|
||||
concatcp!(BECH32_PREFIX, VALIDATOR_PREFIX, CONSENSUS_PREFIX);
|
||||
// BECH32_CONESNSUS_PUBKEY_PREFIX defines the Bech32 prefix of a consensus node public key
|
||||
pub const BECH32_CONESNSUS_PUBKEY_PREFIX: &str = concatcp!(
|
||||
BECH32_PREFIX,
|
||||
VALIDATOR_PREFIX,
|
||||
CONSENSUS_PREFIX,
|
||||
PUBKEY_PREFIX
|
||||
);
|
||||
@@ -0,0 +1,131 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use tendermint::Hash;
|
||||
use thiserror::Error;
|
||||
use tokio::sync::mpsc::error::SendError;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ScraperError {
|
||||
#[error("experienced internal database error: {0}")]
|
||||
InternalDatabaseError(#[from] sqlx::Error),
|
||||
|
||||
#[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")]
|
||||
ScraperAlreadyRunning,
|
||||
|
||||
#[error("failed to establish websocket connection to {url}: {source}")]
|
||||
WebSocketConnectionFailure {
|
||||
url: String,
|
||||
#[source]
|
||||
source: tendermint_rpc::Error,
|
||||
},
|
||||
|
||||
#[error("failed to establish rpc connection to {url}: {source}")]
|
||||
HttpConnectionFailure {
|
||||
url: String,
|
||||
#[source]
|
||||
source: tendermint_rpc::Error,
|
||||
},
|
||||
|
||||
#[error("failed to create chain subscription: {source}")]
|
||||
ChainSubscriptionFailure {
|
||||
#[source]
|
||||
source: tendermint_rpc::Error,
|
||||
},
|
||||
|
||||
#[error("could not obtain basic block information at height: {height}: {source}")]
|
||||
BlockQueryFailure {
|
||||
height: u32,
|
||||
#[source]
|
||||
source: tendermint_rpc::Error,
|
||||
},
|
||||
|
||||
#[error("could not obtain block results information at height: {height}: {source}")]
|
||||
BlockResultsQueryFailure {
|
||||
height: u32,
|
||||
#[source]
|
||||
source: tendermint_rpc::Error,
|
||||
},
|
||||
|
||||
#[error("could not obtain validators information at height: {height}: {source}")]
|
||||
ValidatorsQueryFailure {
|
||||
height: u32,
|
||||
#[source]
|
||||
source: tendermint_rpc::Error,
|
||||
},
|
||||
|
||||
#[error("could not obtain tx results for tx: {hash}: {source}")]
|
||||
TxResultsQueryFailure {
|
||||
hash: Hash,
|
||||
#[source]
|
||||
source: tendermint_rpc::Error,
|
||||
},
|
||||
|
||||
#[error("could not obtain current abci info: {source}")]
|
||||
AbciInfoQueryFailure {
|
||||
#[source]
|
||||
source: tendermint_rpc::Error,
|
||||
},
|
||||
|
||||
#[error("could not parse tx {hash}: {source}")]
|
||||
TxParseFailure {
|
||||
hash: Hash,
|
||||
#[source]
|
||||
source: cosmrs::ErrorReport,
|
||||
},
|
||||
|
||||
#[error("received an invalid chain subscription event of kind {kind} while we were waiting for new block data (query: '{query}')")]
|
||||
InvalidSubscriptionEvent { query: String, kind: String },
|
||||
|
||||
#[error("received block data was empty (query: '{query}')")]
|
||||
EmptyBlockData { query: String },
|
||||
|
||||
#[error("reached maximum number of allowed errors for subscription events")]
|
||||
MaximumSubscriptionFailures,
|
||||
|
||||
#[error("failed to begin storage tx: {source}")]
|
||||
StorageTxBeginFailure {
|
||||
#[source]
|
||||
source: sqlx::Error,
|
||||
},
|
||||
|
||||
#[error("failed to commit storage tx: {source}")]
|
||||
StorageTxCommitFailure {
|
||||
#[source]
|
||||
source: sqlx::Error,
|
||||
},
|
||||
|
||||
#[error("failed to send on a closed channel")]
|
||||
ClosedChannelError,
|
||||
|
||||
#[error("failed to parse validator's address: {source}")]
|
||||
MalformedValidatorAddress {
|
||||
#[source]
|
||||
source: eyre::Report,
|
||||
},
|
||||
|
||||
#[error("failed to parse validator's address: {source}")]
|
||||
MalformedValidatorPubkey {
|
||||
#[source]
|
||||
source: eyre::Report,
|
||||
},
|
||||
|
||||
#[error(
|
||||
"could not find the block proposer ('{proposer}') for height {height} in the validator set"
|
||||
)]
|
||||
BlockProposerNotInValidatorSet { height: u32, proposer: String },
|
||||
|
||||
#[error(
|
||||
"could not find validator information for {address}; the validator has signed a commit"
|
||||
)]
|
||||
MissingValidatorInfoCommitted { address: String },
|
||||
}
|
||||
|
||||
impl<T> From<SendError<T>> for ScraperError {
|
||||
fn from(_: SendError<T>) -> Self {
|
||||
ScraperError::ClosedChannelError
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user