Compare commits
197 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 099de36c60 | |||
| 9e5890a0d7 | |||
| 3bda5f59a3 | |||
| 154dfa089b | |||
| bd0cbbc18a | |||
| ed0e7a7a25 | |||
| 5b35cfcfb2 | |||
| d3ba008b88 | |||
| a04a782dbf | |||
| f5d9fda0b1 | |||
| aebd386382 | |||
| 9a6f96b5e0 | |||
| 5a3ff0f9f7 | |||
| 160db34651 | |||
| ae20d2afb8 | |||
| 41b7a2a20d | |||
| 208ec4574b | |||
| 2bff66e2c7 | |||
| 1aad5fc1bf | |||
| cb3e73fbd7 | |||
| eabb36b975 | |||
| 2eed8e3f6c | |||
| bfac3e0b89 | |||
| 90680ceb16 | |||
| f9c5684d6c | |||
| ffb053fe4a | |||
| e83be64a52 | |||
| 32c897f789 | |||
| 9ff37d2f9f | |||
| a6ebfb521d | |||
| ac23ef924a | |||
| 5a770614dd | |||
| 8f8cd79a65 | |||
| d8f73ef97a | |||
| c7fb89bd5e | |||
| 3c2d47ad18 | |||
| 6f13720530 | |||
| 0efd7a2318 | |||
| 2ca2b9e032 | |||
| d92a8ea028 | |||
| 7483d10701 | |||
| ca75c06f4c | |||
| 73632a0ae7 | |||
| 3d3dd80247 | |||
| 1d481db179 | |||
| cae97663c1 | |||
| 795329b874 | |||
| 87ea3fcfc4 | |||
| 289343d1c8 | |||
| f96f74f2f1 | |||
| 3ec2ea904f | |||
| 04373589b1 | |||
| 1a8814ccdc | |||
| d62a41b9c1 | |||
| d3e30e98f9 | |||
| 88a49dfc7e | |||
| 66a54aeab3 | |||
| d6afa74284 | |||
| 49e2be5b04 | |||
| 1cfddb942b | |||
| 49c43617c9 | |||
| ff01fc79e3 | |||
| 5cf53b7002 | |||
| 387d07fb93 | |||
| dcd6dcc6e3 | |||
| e7d0c1812a | |||
| 7bbac26676 | |||
| 688ac2efb5 | |||
| f348e6972a | |||
| dd97eb13a8 | |||
| 92d9cb7dab | |||
| 5a4dfafe9f | |||
| fa93c4598f | |||
| edbcade5f5 | |||
| 3f0194a9aa | |||
| c2517ac63b | |||
| 3fa74c90ff | |||
| 96f3192694 | |||
| f61b898c4f | |||
| c9ff550311 | |||
| 740cc72ec8 | |||
| 6e7bac1e7e | |||
| 691884e20a | |||
| 400d71bf07 | |||
| ffe55ba072 | |||
| 00f1ce98ba | |||
| b02bbdef19 | |||
| 78e1d84905 | |||
| 2638952f5a | |||
| 9a3bd7a2a9 | |||
| ad9aee0ec0 | |||
| f687ebb0f5 | |||
| ddf2770c8e | |||
| 16c942d72e | |||
| 0ee727bac1 | |||
| 675cf3d7da | |||
| 9a0cbf5072 | |||
| 6f3dd9f778 | |||
| 7a7fbce8ea | |||
| 36242fa257 | |||
| b764fcc756 | |||
| ac676760d4 | |||
| 20819331f3 | |||
| 6b6980c523 | |||
| 8b0953624f | |||
| 24a260fbc9 | |||
| 510ad11c98 | |||
| 627334cfe2 | |||
| d4c98e3ff5 | |||
| 9821dd994b | |||
| a977310225 | |||
| 8e16678f74 | |||
| 52c46f371e | |||
| 3010d5192f | |||
| 721ad9d8bb | |||
| 85803ec11c | |||
| 83da1f228b | |||
| c663ba08f2 | |||
| 92bf31d9f4 | |||
| 646f522142 | |||
| be3dd2c250 | |||
| db826c4fb4 | |||
| b960dc8aaf | |||
| da70ae70a5 | |||
| 914b8a6dc2 | |||
| ad2552ec78 | |||
| 45686f7ca6 | |||
| 27554f52e3 | |||
| 29edc8799a | |||
| 46875cdf2f | |||
| 629081b5ec | |||
| d2c77d7f64 | |||
| eab7eb03c7 | |||
| ecc47cd418 | |||
| 71c975d20c | |||
| f0705cd1f9 | |||
| b6d5f780d2 | |||
| 0b46e5b753 | |||
| 2c65460164 | |||
| e86419540c | |||
| 3771cb9188 | |||
| e8f6d6e55d | |||
| 536b892c91 | |||
| a40cd73dec | |||
| d7255374de | |||
| 0b6cb236d8 | |||
| f0361a200b | |||
| f1c5e8bdc0 | |||
| b03d737393 | |||
| 3088b69711 | |||
| 412b7b9898 | |||
| 30754a7a4a | |||
| e99b04f1c6 | |||
| 279fea9a0b | |||
| c2aba223b8 | |||
| 501f314266 | |||
| 3ecd2af216 | |||
| 9b44674f43 | |||
| 588839740f | |||
| 4353bab636 | |||
| 05957c366f | |||
| 60e14f866e | |||
| cec05a99f4 | |||
| d487f4d98c | |||
| b9e9809938 | |||
| 9b50188d7d | |||
| 0e3dbece8b | |||
| 052f7649a8 | |||
| 3fde9e648f | |||
| 0b37b9fb1c | |||
| e273bfc25e | |||
| d2ef94f1bd | |||
| 92ab794294 | |||
| 3f0210d56a | |||
| 9b53473bee | |||
| 5fdae14cb9 | |||
| ccb4d7fd5e | |||
| a8e520d13b | |||
| 148db2f350 | |||
| 2f4fad3ce3 | |||
| cc604c5f18 | |||
| d0aece501f | |||
| 22b5670396 | |||
| 4ebbf175fc | |||
| 79e9399dfe | |||
| 8450df28df | |||
| 0b23d1624f | |||
| 2026ffd61f | |||
| 48e5aecda1 | |||
| d8e484b77e | |||
| d4ca2a7220 | |||
| 2f0074821c | |||
| d5e332ad39 | |||
| 14bf5645b1 | |||
| a11582749c | |||
| 339c6c6d24 | |||
| bd6ba89e96 |
@@ -9,10 +9,17 @@ on:
|
||||
default: false
|
||||
type: boolean
|
||||
enable_wireguard:
|
||||
description: 'Add --features wireguard'
|
||||
description: "Add --features wireguard"
|
||||
required: true
|
||||
default: false
|
||||
type: boolean
|
||||
enable_deb:
|
||||
description: "True to enable cargo-deb installation and .deb package building"
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
schedule:
|
||||
- cron: "14 0 * * *"
|
||||
pull_request:
|
||||
paths:
|
||||
- "clients/**"
|
||||
@@ -28,6 +35,7 @@ on:
|
||||
- "sdk/rust/nym-sdk/**"
|
||||
- "service-providers/**"
|
||||
- "tools/**"
|
||||
- "nymvisor/**"
|
||||
|
||||
jobs:
|
||||
publish-nym:
|
||||
@@ -62,7 +70,9 @@ jobs:
|
||||
- name: Set CARGO_FEATURES
|
||||
run: |
|
||||
echo 'CARGO_FEATURES=--features wireguard' >> $GITHUB_ENV
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.enable_wireguard == true
|
||||
if: >
|
||||
github.event_name == 'schedule' ||
|
||||
(github.event_name == 'workflow_dispatch' && inputs.enable_wireguard == true)
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
@@ -80,12 +90,12 @@ jobs:
|
||||
with:
|
||||
command: install
|
||||
args: cargo-deb
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.enable_deb == true
|
||||
|
||||
- name: Build deb packages
|
||||
shell: bash
|
||||
run: make deb
|
||||
|
||||
# If this was a manual workflow_dispatch, publish binaries.
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.enable_deb == true
|
||||
|
||||
- name: Upload Artifact
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
@@ -101,12 +111,13 @@ jobs:
|
||||
target/release/nym-network-requester
|
||||
target/release/nym-network-statistics
|
||||
target/release/nym-cli
|
||||
target/release/nymvisor
|
||||
retention-days: 30
|
||||
|
||||
# If this was a pull_request, upload to build server
|
||||
# If this was a pull_request or nightly, upload to build server
|
||||
|
||||
- name: Prepare build output
|
||||
if: github.event_name == 'pull_request'
|
||||
# if: github.event_name == 'schedule' || github.event_name == 'pull_request'
|
||||
shell: bash
|
||||
env:
|
||||
OUTPUT_DIR: ci-builds/${{ github.ref_name }}
|
||||
@@ -118,12 +129,14 @@ jobs:
|
||||
cp target/release/nym-api $OUTPUT_DIR
|
||||
cp target/release/nym-network-requester $OUTPUT_DIR
|
||||
cp target/release/nym-network-statistics $OUTPUT_DIR
|
||||
cp target/release/nymvisor $OUTPUT_DIR
|
||||
cp target/release/nym-cli $OUTPUT_DIR
|
||||
cp target/release/explorer-api $OUTPUT_DIR
|
||||
cp target/debian/*.deb $OUTPUT_DIR
|
||||
if [ ${{ github.event_name == 'workflow_dispatch' && inputs.enable_deb == true }} = true ]; then
|
||||
cp target/debian/*.deb $OUTPUT_DIR
|
||||
fi
|
||||
|
||||
- name: Deploy branch to CI www
|
||||
if: github.event_name == 'pull_request'
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Publish Nym binaries
|
||||
name: publish-nym-binaries
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -29,6 +29,7 @@ jobs:
|
||||
client_hash: ${{ steps.binary-hashes.outputs.client_hash }}
|
||||
mixnode_hash: ${{ steps.binary-hashes.outputs.mixnode_hash }}
|
||||
gateway_hash: ${{ steps.binary-hashes.outputs.gateway_hash }}
|
||||
nymvisor_hash: ${{ steps.binary-hashes.outputs.nymvisor_hash }}
|
||||
socks5_hash: ${{ steps.binary-hashes.outputs.socks5_hash }}
|
||||
netreq_hash: ${{ steps.binary-hashes.outputs.netreq_hash }}
|
||||
cli_hash: ${{ steps.binary-hashes.outputs.cli_hash }}
|
||||
@@ -36,6 +37,7 @@ jobs:
|
||||
client_version: ${{ steps.binary-versions.outputs.client_version }}
|
||||
mixnode_version: ${{ steps.binary-versions.outputs.mixnode_version }}
|
||||
gateway_version: ${{ steps.binary-versions.outputs.gateway_version }}
|
||||
nymvisor_version: ${{ steps.binary-versions.outputs.nymvisor_version }}
|
||||
socks5_version: ${{ steps.binary-versions.outputs.socks5_version }}
|
||||
netreq_version: ${{ steps.binary-versions.outputs.netreq_version }}
|
||||
cli_version: ${{ steps.binary-versions.outputs.cli_version }}
|
||||
@@ -78,6 +80,7 @@ jobs:
|
||||
target/release/nym-network-requester
|
||||
target/release/nym-network-statistics
|
||||
target/release/nym-cli
|
||||
target/release/nymvisor
|
||||
retention-days: 30
|
||||
|
||||
- id: create-release
|
||||
@@ -95,6 +98,7 @@ jobs:
|
||||
target/release/nym-network-requester
|
||||
target/release/nym-network-statistics
|
||||
target/release/nym-cli
|
||||
target/release/nymvisor
|
||||
|
||||
push-release-data-client:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Publish Nym Connect - desktop (MacOS)
|
||||
name: publish-nym-connect-macos
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Publish Nym Connect - desktop (Ubuntu)
|
||||
name: publish-nym-connect-ubuntu
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Publish Nym Connect - desktop (Windows 10)
|
||||
name: publish-nym-connect-win10
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Build release of Nym smart contracts
|
||||
name: publish-nym-contracts
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Publish Nym Wallet (MacOS)
|
||||
name: publish-nym-wallet-macos
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Publish Nym Wallet (Ubuntu)
|
||||
name: publish-nym-wallet-ubuntu
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Publish Nym Wallet (Windows 10)
|
||||
name: publish-nym-wallet-win10
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Publish Typescript SDK
|
||||
name: publish-sdk-npm
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Releases - calculate file hashes
|
||||
name: release-calculate-hash
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
@@ -4,6 +4,23 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2024.1-marabou] (2024-02-15)
|
||||
|
||||
**New Features:**
|
||||
- Introduced nymvisor support for nym-api, gateway, and mixnode binaries ([#4158])
|
||||
- Revamped nym-api execution with the addition of init and run commands ([#4225])
|
||||
|
||||
**Enhancements:**
|
||||
- Implemented internal improvements for gateways to optimize internal packet routing
|
||||
- Improved routing score calculation
|
||||
|
||||
**Bug Fixes:**
|
||||
- Resolved various bugs to enhance overall stability
|
||||
|
||||
[#4158]: https://github.com/nymtech/nym/pull/4158
|
||||
[#4225]: https://github.com/nymtech/nym/pull/4225
|
||||
|
||||
|
||||
## [2023.5-rolo] (2023-11-28)
|
||||
|
||||
- Gateway won't open websocket listener until embedded Network Requester becomes available ([#4166])
|
||||
|
||||
Generated
+804
-2183
File diff suppressed because it is too large
Load Diff
+8
-5
@@ -32,7 +32,7 @@ members = [
|
||||
"common/cosmwasm-smart-contracts/coconut-bandwidth-contract",
|
||||
"common/cosmwasm-smart-contracts/coconut-dkg",
|
||||
"common/cosmwasm-smart-contracts/contracts-common",
|
||||
"common/cosmwasm-smart-contracts/ephemera",
|
||||
# "common/cosmwasm-smart-contracts/ephemera",
|
||||
"common/cosmwasm-smart-contracts/group-contract",
|
||||
"common/cosmwasm-smart-contracts/mixnet-contract",
|
||||
"common/cosmwasm-smart-contracts/multisig-contract",
|
||||
@@ -56,6 +56,7 @@ members = [
|
||||
"common/node-tester-utils",
|
||||
"common/nonexhaustive-delayqueue",
|
||||
"common/nymcoconut",
|
||||
"common/nym-id",
|
||||
"common/nymsphinx",
|
||||
"common/nymsphinx/acknowledgements",
|
||||
"common/nymsphinx/addressing",
|
||||
@@ -104,8 +105,9 @@ members = [
|
||||
"nym-outfox",
|
||||
"nym-validator-rewarder",
|
||||
"tools/internal/ssl-inject",
|
||||
"tools/internal/sdk-version-bump",
|
||||
# "tools/internal/sdk-version-bump",
|
||||
"tools/nym-cli",
|
||||
"tools/nym-id-cli",
|
||||
"tools/nym-nr-query",
|
||||
"tools/nymvisor",
|
||||
"tools/ts-rs-cli",
|
||||
@@ -143,6 +145,7 @@ anyhow = "1.0.71"
|
||||
async-trait = "0.1.68"
|
||||
axum = "0.6.20"
|
||||
base64 = "0.21.4"
|
||||
bs58 = "0.5.0"
|
||||
bip39 = { version = "2.0.0", features = ["zeroize"] }
|
||||
clap = "4.4.7"
|
||||
cfg-if = "1.0.0"
|
||||
@@ -158,7 +161,7 @@ log = "0.4"
|
||||
once_cell = "1.7.2"
|
||||
parking_lot = "0.12.1"
|
||||
rand = "0.8.5"
|
||||
reqwest = "0.11.22"
|
||||
reqwest = { version = "0.11.22", default_features = false, features = ["rustls-tls"] }
|
||||
schemars = "0.8.1"
|
||||
serde = "1.0.152"
|
||||
serde_json = "1.0.91"
|
||||
@@ -168,9 +171,9 @@ time = "0.3.30"
|
||||
thiserror = "1.0.48"
|
||||
tokio = "1.33.0"
|
||||
tokio-util = "0.7.10"
|
||||
tokio-tungstenite = "0.20.1"
|
||||
tokio-tungstenite = { version = "0.20.1", features = ["rustls"] }
|
||||
tracing = "0.1.37"
|
||||
tungstenite = { version = "0.20.1", default-features = false }
|
||||
tungstenite = { version = "0.20.1", default-features = false, features = ["rustls"] }
|
||||
ts-rs = "7.0.0"
|
||||
utoipa = "3.5.0"
|
||||
utoipa-swagger-ui = "3.1.5"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.32"
|
||||
version = "1.1.33"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
@@ -21,18 +21,19 @@ futures = { workspace = true } # bunch of futures stuff, however, now that I thi
|
||||
# and the single instance of abortable we have should really be refactored anyway
|
||||
url = { workspace = true }
|
||||
|
||||
bs58 = { workspace = true }
|
||||
clap = { workspace = true, features = ["cargo", "derive"] }
|
||||
dirs = "4.0"
|
||||
lazy_static = "1.4.0"
|
||||
log = { workspace = true } # self explanatory
|
||||
pretty_env_logger = "0.4" # for formatting log messages
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] } # rng-related traits + some rng implementation to use
|
||||
serde = { workspace = true, features = ["derive"] } # for config serialization/deserialization
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tap = "1.0.1"
|
||||
time = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "net", "signal"] } # async runtime
|
||||
tokio-tungstenite = { workspace = true }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
## internal
|
||||
nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
|
||||
@@ -50,5 +51,6 @@ nym-task = { path = "../../common/task" }
|
||||
nym-topology = { path = "../../common/topology" }
|
||||
nym-validator-client = { path = "../../common/client-libs/validator-client", features = ["http-client"] }
|
||||
nym-client-websocket-requests = { path = "websocket-requests" }
|
||||
nym-id = { path = "../../common/nym-id" }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -2160,9 +2160,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ip": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
|
||||
"integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=",
|
||||
"version": "1.1.9",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz",
|
||||
"integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ipaddr.js": {
|
||||
@@ -6157,9 +6157,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"ip": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
|
||||
"integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=",
|
||||
"version": "1.1.9",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz",
|
||||
"integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==",
|
||||
"dev": true
|
||||
},
|
||||
"ipaddr.js": {
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::commands::try_load_current_config;
|
||||
use crate::error::ClientError;
|
||||
use clap::ArgGroup;
|
||||
|
||||
use nym_id::import_credential;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result<Vec<u8>> {
|
||||
bs58::decode(raw).into_vec()
|
||||
}
|
||||
|
||||
#[derive(clap::Args)]
|
||||
#[clap(group(ArgGroup::new("cred_data").required(true)))]
|
||||
pub(crate) struct Args {
|
||||
/// Id of client that is going to import the credential
|
||||
#[clap(long)]
|
||||
pub id: String,
|
||||
|
||||
/// Explicitly provide the encoded credential data (as base58)
|
||||
#[clap(long, group = "cred_data", value_parser = parse_encoded_credential_data)]
|
||||
pub(crate) credential_data: Option<Vec<u8>>,
|
||||
|
||||
/// Specifies the path to file containing binary credential data
|
||||
#[clap(long, group = "cred_data")]
|
||||
pub(crate) credential_path: Option<PathBuf>,
|
||||
|
||||
// currently hidden as there exists only a single serialization standard
|
||||
#[clap(long, hide = true)]
|
||||
pub(crate) version: Option<u8>,
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: Args) -> Result<(), ClientError> {
|
||||
let config = try_load_current_config(&args.id)?;
|
||||
|
||||
let credentials_store = nym_credential_storage::initialise_persistent_storage(
|
||||
&config.storage_paths.common_paths.credentials_database,
|
||||
)
|
||||
.await;
|
||||
|
||||
let raw_credential = match args.credential_data {
|
||||
Some(data) => data,
|
||||
None => {
|
||||
// SAFETY: one of those arguments must have been set
|
||||
fs::read(args.credential_path.unwrap())?
|
||||
}
|
||||
};
|
||||
|
||||
import_credential(credentials_store, raw_credential, args.version).await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -8,7 +8,6 @@ use crate::client::config::{BaseClientConfig, Config};
|
||||
use crate::error::ClientError;
|
||||
use clap::CommandFactory;
|
||||
use clap::{Parser, Subcommand};
|
||||
use lazy_static::lazy_static;
|
||||
use log::{error, info};
|
||||
use nym_bin_common::bin_info;
|
||||
use nym_bin_common::completions::{fig_generate, ArgShell};
|
||||
@@ -21,18 +20,16 @@ use nym_client_core::error::ClientCoreError;
|
||||
use nym_config::OptionalSet;
|
||||
use std::error::Error;
|
||||
use std::net::IpAddr;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
pub(crate) mod build_info;
|
||||
pub(crate) mod import_credential;
|
||||
pub(crate) mod init;
|
||||
pub(crate) mod run;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref PRETTY_BUILD_INFORMATION: String = bin_info!().pretty_print();
|
||||
}
|
||||
|
||||
// Helper for passing LONG_VERSION to clap
|
||||
fn pretty_build_info_static() -> &'static str {
|
||||
&PRETTY_BUILD_INFORMATION
|
||||
static PRETTY_BUILD_INFORMATION: OnceLock<String> = OnceLock::new();
|
||||
PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print())
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
@@ -58,6 +55,9 @@ pub(crate) enum Commands {
|
||||
/// Run the Nym client with provided configuration client optionally overriding set parameters
|
||||
Run(run::Run),
|
||||
|
||||
/// Import a pre-generated credential
|
||||
ImportCredential(import_credential::Args),
|
||||
|
||||
/// Show build information of this binary
|
||||
BuildInfo(build_info::BuildInfo),
|
||||
|
||||
@@ -86,6 +86,7 @@ pub(crate) async fn execute(args: Cli) -> Result<(), Box<dyn Error + Send + Sync
|
||||
match args.command {
|
||||
Commands::Init(m) => init::execute(m).await?,
|
||||
Commands::Run(m) => run::execute(m).await?,
|
||||
Commands::ImportCredential(m) => import_credential::execute(m).await?,
|
||||
Commands::BuildInfo(m) => build_info::execute(m),
|
||||
Commands::Completions(s) => s.generate(&mut Cli::command(), bin_name),
|
||||
Commands::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name),
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use nym_client_core::error::ClientCoreError;
|
||||
|
||||
use nym_id::NymIdError;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum ClientError {
|
||||
#[error("I/O error: {0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
|
||||
#[error("client-core error: {0}")]
|
||||
#[error(transparent)]
|
||||
ClientCoreError(#[from] ClientCoreError),
|
||||
|
||||
#[error("Failed to load config for: {0}")]
|
||||
@@ -20,4 +22,7 @@ pub enum ClientError {
|
||||
|
||||
#[error("Attempted to start the client in invalid socket mode")]
|
||||
InvalidSocketMode,
|
||||
|
||||
#[error(transparent)]
|
||||
NymIdError(#[from] NymIdError),
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.32"
|
||||
version = "1.1.33"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
@@ -8,17 +8,18 @@ rust-version = "1.56"
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
bs58 = { workspace = true }
|
||||
clap = { workspace = true, features = ["cargo", "derive"] }
|
||||
lazy_static = "1.4.0"
|
||||
log = { workspace = true }
|
||||
pretty_env_logger = "0.4"
|
||||
serde = { workspace = true, features = ["derive"] } # for config serialization/deserialization
|
||||
serde_json = { workspace = true }
|
||||
tap = "1.0.1"
|
||||
thiserror = { workspace = true }
|
||||
tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal"] }
|
||||
rand = "0.7.3"
|
||||
time = { workspace = true }
|
||||
url = { workspace = true }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
# internal
|
||||
nym-bin-common = { path = "../../common/bin-common", features = ["output_format"] }
|
||||
@@ -34,6 +35,7 @@ nym-ordered-buffer = { path = "../../common/socks5/ordered-buffer" }
|
||||
nym-pemstore = { path = "../../common/pemstore" }
|
||||
nym-topology = { path = "../../common/topology" }
|
||||
nym-socks5-client-core = { path = "../../common/socks5-client-core" }
|
||||
nym-id = { path = "../../common/nym-id" }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::commands::try_load_current_config;
|
||||
use crate::error::Socks5ClientError;
|
||||
use clap::ArgGroup;
|
||||
|
||||
use nym_id::import_credential;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result<Vec<u8>> {
|
||||
bs58::decode(raw).into_vec()
|
||||
}
|
||||
|
||||
#[derive(clap::Args)]
|
||||
#[clap(group(ArgGroup::new("cred_data").required(true)))]
|
||||
pub(crate) struct Args {
|
||||
/// Id of client that is going to import the credential
|
||||
#[clap(long)]
|
||||
pub id: String,
|
||||
|
||||
/// Explicitly provide the encoded credential data (as base58)
|
||||
#[clap(long, group = "cred_data", value_parser = parse_encoded_credential_data)]
|
||||
pub(crate) credential_data: Option<Vec<u8>>,
|
||||
|
||||
/// Specifies the path to file containing binary credential data
|
||||
#[clap(long, group = "cred_data")]
|
||||
pub(crate) credential_path: Option<PathBuf>,
|
||||
|
||||
// currently hidden as there exists only a single serialization standard
|
||||
#[clap(long, hide = true)]
|
||||
pub(crate) version: Option<u8>,
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: Args) -> Result<(), Socks5ClientError> {
|
||||
let config = try_load_current_config(&args.id)?;
|
||||
|
||||
let credentials_store = nym_credential_storage::initialise_persistent_storage(
|
||||
&config.storage_paths.common_paths.credentials_database,
|
||||
)
|
||||
.await;
|
||||
|
||||
let raw_credential = match args.credential_data {
|
||||
Some(data) => data,
|
||||
None => {
|
||||
// SAFETY: one of those arguments must have been set
|
||||
fs::read(args.credential_path.unwrap())?
|
||||
}
|
||||
};
|
||||
|
||||
import_credential(credentials_store, raw_credential, args.version).await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -9,7 +9,6 @@ use crate::config::{BaseClientConfig, Config, SocksClientPaths};
|
||||
use crate::error::Socks5ClientError;
|
||||
use clap::CommandFactory;
|
||||
use clap::{Parser, Subcommand};
|
||||
use lazy_static::lazy_static;
|
||||
use log::{error, info};
|
||||
use nym_bin_common::bin_info;
|
||||
use nym_bin_common::completions::{fig_generate, ArgShell};
|
||||
@@ -24,18 +23,16 @@ use nym_config::OptionalSet;
|
||||
use nym_sphinx::params::{PacketSize, PacketType};
|
||||
use std::error::Error;
|
||||
use std::net::IpAddr;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
pub(crate) mod build_info;
|
||||
mod import_credential;
|
||||
pub mod init;
|
||||
pub(crate) mod run;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref PRETTY_BUILD_INFORMATION: String = bin_info!().pretty_print();
|
||||
}
|
||||
|
||||
// Helper for passing LONG_VERSION to clap
|
||||
fn pretty_build_info_static() -> &'static str {
|
||||
&PRETTY_BUILD_INFORMATION
|
||||
static PRETTY_BUILD_INFORMATION: OnceLock<String> = OnceLock::new();
|
||||
PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print())
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
@@ -61,6 +58,9 @@ pub(crate) enum Commands {
|
||||
/// Run the Nym client with provided configuration client optionally overriding set parameters
|
||||
Run(run::Run),
|
||||
|
||||
/// Import a pre-generated credential
|
||||
ImportCredential(import_credential::Args),
|
||||
|
||||
/// Show build information of this binary
|
||||
BuildInfo(build_info::BuildInfo),
|
||||
|
||||
@@ -92,6 +92,7 @@ pub(crate) async fn execute(args: Cli) -> Result<(), Box<dyn Error + Send + Sync
|
||||
match args.command {
|
||||
Commands::Init(m) => init::execute(m).await?,
|
||||
Commands::Run(m) => run::execute(m).await?,
|
||||
Commands::ImportCredential(m) => import_credential::execute(m).await?,
|
||||
Commands::BuildInfo(m) => build_info::execute(m),
|
||||
Commands::Completions(s) => s.generate(&mut Cli::command(), bin_name),
|
||||
Commands::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name),
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use nym_client_core::error::ClientCoreError;
|
||||
|
||||
use nym_id::NymIdError;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Socks5ClientError {
|
||||
#[error("I/O error: {0}")]
|
||||
@@ -18,6 +20,9 @@ pub enum Socks5ClientError {
|
||||
#[error("Fail to bind address")]
|
||||
FailToBindAddress,
|
||||
|
||||
#[error("client-core error: {0}")]
|
||||
#[error(transparent)]
|
||||
ClientCoreError(#[from] ClientCoreError),
|
||||
|
||||
#[error(transparent)]
|
||||
NymIdError(#[from] NymIdError),
|
||||
}
|
||||
|
||||
@@ -21,6 +21,9 @@ pub enum BandwidthControllerError {
|
||||
#[error("There was a credential storage error - {0}")]
|
||||
CredentialStorageError(Box<dyn std::error::Error + Send + Sync>),
|
||||
|
||||
#[error("the credential storage does not contain any usable credentials")]
|
||||
NoCredentialsAvailable,
|
||||
|
||||
// this should really be fully incorporated into the above, but messing with coconut is the last thing I want to do now
|
||||
#[error(transparent)]
|
||||
StorageError(#[from] StorageError),
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
|
||||
use crate::error::BandwidthControllerError;
|
||||
use crate::utils::stored_credential_to_issued_bandwidth;
|
||||
use log::{error, warn};
|
||||
use log::{debug, error, warn};
|
||||
use nym_credential_storage::storage::Storage;
|
||||
use nym_credentials::coconut::bandwidth::issued::BandwidthCredentialIssuedDataVariant;
|
||||
use nym_credentials::coconut::bandwidth::CredentialSpendingData;
|
||||
use nym_credentials::coconut::utils::obtain_aggregate_verification_key;
|
||||
use nym_credentials::IssuedBandwidthCredential;
|
||||
use nym_credentials_interface::VerificationKey;
|
||||
use nym_validator_client::coconut::all_coconut_api_clients;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
@@ -33,11 +35,67 @@ pub struct PreparedCredential {
|
||||
pub credential_id: i64,
|
||||
}
|
||||
|
||||
pub struct RetrievedCredential {
|
||||
pub credential: IssuedBandwidthCredential,
|
||||
pub credential_id: i64,
|
||||
}
|
||||
|
||||
impl<C, St: Storage> BandwidthController<C, St> {
|
||||
pub fn new(storage: St, client: C) -> Self {
|
||||
BandwidthController { storage, client }
|
||||
}
|
||||
|
||||
/// Tries to retrieve one of the stored, unused credentials that hasn't yet expired.
|
||||
/// It marks any retrieved intermediate credentials as expired.
|
||||
pub async fn get_next_usable_credential(
|
||||
&self,
|
||||
) -> Result<RetrievedCredential, BandwidthControllerError>
|
||||
where
|
||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
loop {
|
||||
let Some(maybe_next) = self
|
||||
.storage
|
||||
.get_next_unspent_credential()
|
||||
.await
|
||||
.map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))?
|
||||
else {
|
||||
return Err(BandwidthControllerError::NoCredentialsAvailable);
|
||||
};
|
||||
let id = maybe_next.id;
|
||||
|
||||
// try to deserialize it
|
||||
let valid_credential = match stored_credential_to_issued_bandwidth(maybe_next) {
|
||||
// check if it has already expired
|
||||
Ok(credential) => match credential.variant_data() {
|
||||
BandwidthCredentialIssuedDataVariant::Voucher(_) => {
|
||||
debug!("credential {id} is a bandwidth voucher");
|
||||
credential
|
||||
}
|
||||
BandwidthCredentialIssuedDataVariant::FreePass(freepass_info) => {
|
||||
debug!("credential {id} is a free pass");
|
||||
if freepass_info.expired() {
|
||||
warn!("the free pass (id: {id}) has already expired! The expiration was set to {}", freepass_info.expiry_date());
|
||||
self.storage.mark_expired(id).await.map_err(|err| {
|
||||
BandwidthControllerError::CredentialStorageError(Box::new(err))
|
||||
})?;
|
||||
continue;
|
||||
}
|
||||
credential
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
error!("failed to deserialize credential with id {id}: {err}. it may need to be manually removed from the storage");
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
return Ok(RetrievedCredential {
|
||||
credential: valid_credential,
|
||||
credential_id: id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn storage(&self) -> &St {
|
||||
&self.storage
|
||||
}
|
||||
@@ -61,29 +119,16 @@ impl<C, St: Storage> BandwidthController<C, St> {
|
||||
C: DkgQueryClient + Sync + Send,
|
||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
let retrieved_credential = self
|
||||
.storage
|
||||
.get_next_unspent_credential()
|
||||
.await
|
||||
.map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))?;
|
||||
let retrieved_credential = self.get_next_usable_credential().await?;
|
||||
|
||||
let epoch_id = retrieved_credential.epoch_id as EpochId;
|
||||
let credential_id = retrieved_credential.id;
|
||||
let epoch_id = retrieved_credential.credential.epoch_id();
|
||||
let credential_id = retrieved_credential.credential_id;
|
||||
|
||||
let issued_bandwidth = stored_credential_to_issued_bandwidth(retrieved_credential)?;
|
||||
let verification_key = self.get_aggregate_verification_key(epoch_id).await?;
|
||||
|
||||
let verification_key = match self.get_aggregate_verification_key(epoch_id).await {
|
||||
Ok(key) => key,
|
||||
Err(err) => {
|
||||
warn!("failed to obtain master verification key: {err}. Putting the credential back into the database");
|
||||
|
||||
// TODO: ERROR RECOVERY:
|
||||
error!("unimplemented: putting the credential back into the database");
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
|
||||
let spend_request = issued_bandwidth.prepare_for_spending(&verification_key)?;
|
||||
let spend_request = retrieved_credential
|
||||
.credential
|
||||
.prepare_for_spending(&verification_key)?;
|
||||
|
||||
Ok(PreparedCredential {
|
||||
data: spend_request,
|
||||
|
||||
@@ -69,6 +69,35 @@ impl BinaryBuildInformation {
|
||||
}
|
||||
}
|
||||
|
||||
// Varient where we want to use the metadata generated by vergen in the consuming crate.
|
||||
pub const fn new_with_local_vergen(
|
||||
binary_name: &'static str,
|
||||
build_timestamp: &'static str,
|
||||
build_version: &'static str,
|
||||
commit_sha: &'static str,
|
||||
commit_timestamp: &'static str,
|
||||
commit_branch: &'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,
|
||||
build_version,
|
||||
commit_sha,
|
||||
commit_timestamp,
|
||||
commit_branch,
|
||||
rustc_version: env!("VERGEN_RUSTC_SEMVER"),
|
||||
rustc_channel: env!("VERGEN_RUSTC_CHANNEL"),
|
||||
cargo_profile,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_owned(&self) -> BinaryBuildInformationOwned {
|
||||
BinaryBuildInformationOwned {
|
||||
binary_name: self.binary_name.to_owned(),
|
||||
@@ -187,3 +216,33 @@ macro_rules! bin_info_owned {
|
||||
.to_owned()
|
||||
};
|
||||
}
|
||||
|
||||
// variant that picks up the vergen build information generated by the build.rs in the consumer
|
||||
// crate.
|
||||
#[macro_export]
|
||||
macro_rules! bin_info_local_vergen {
|
||||
() => {
|
||||
$crate::build_information::BinaryBuildInformation::new_with_local_vergen(
|
||||
env!("CARGO_PKG_NAME"),
|
||||
env!("VERGEN_BUILD_TIMESTAMP"),
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
env!("VERGEN_GIT_SHA"),
|
||||
env!("VERGEN_GIT_COMMIT_TIMESTAMP"),
|
||||
env!("VERGEN_GIT_BRANCH"),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! bin_info_local_vergen_owned {
|
||||
() => {
|
||||
$crate::build_information::BinaryBuildInformation::new_with_local_vergen(
|
||||
env!("CARGO_PKG_NAME"),
|
||||
env!("VERGEN_BUILD_TIMESTAMP"),
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
env!("VERGEN_GIT_SHA"),
|
||||
env!("VERGEN_GIT_COMMIT_TIMESTAMP"),
|
||||
env!("VERGEN_GIT_BRANCH"),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ use nym_topology::provider_trait::TopologyProvider;
|
||||
use nym_topology::HardcodedTopologyProvider;
|
||||
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
|
||||
use std::fmt::Debug;
|
||||
use std::os::raw::c_int as RawFd;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use url::Url;
|
||||
@@ -103,6 +104,12 @@ pub struct ClientState {
|
||||
pub shared_lane_queue_lengths: LaneQueueLengths,
|
||||
pub reply_controller_sender: ReplyControllerSender,
|
||||
pub topology_accessor: TopologyAccessor,
|
||||
pub gateway_connection: GatewayConnection,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct GatewayConnection {
|
||||
pub gateway_ws_fd: Option<RawFd>,
|
||||
}
|
||||
|
||||
pub enum ClientInputStatus {
|
||||
@@ -666,6 +673,7 @@ where
|
||||
shutdown.fork("gateway_transceiver"),
|
||||
)
|
||||
.await?;
|
||||
let gateway_ws_fd = gateway_transceiver.ws_fd();
|
||||
|
||||
let reply_storage = Self::setup_persistent_reply_storage(
|
||||
reply_storage_backend,
|
||||
@@ -759,6 +767,7 @@ where
|
||||
shared_lane_queue_lengths,
|
||||
reply_controller_sender,
|
||||
topology_accessor: shared_topology_accessor,
|
||||
gateway_connection: GatewayConnection { gateway_ws_fd },
|
||||
},
|
||||
task_handle: shutdown,
|
||||
})
|
||||
|
||||
@@ -8,6 +8,7 @@ use nym_gateway_client::GatewayClient;
|
||||
pub use nym_gateway_client::{GatewayPacketRouter, PacketRouter};
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use std::fmt::Debug;
|
||||
use std::os::raw::c_int as RawFd;
|
||||
use thiserror::Error;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -25,6 +26,7 @@ fn erase_err<E: std::error::Error + Send + Sync + 'static>(err: E) -> ErasedGate
|
||||
/// This combines combines the functionalities of being able to send and receive mix packets.
|
||||
pub trait GatewayTransceiver: GatewaySender + GatewayReceiver {
|
||||
fn gateway_identity(&self) -> identity::PublicKey;
|
||||
fn ws_fd(&self) -> Option<RawFd>;
|
||||
}
|
||||
|
||||
/// This trait defines the functionality of sending `MixPacket` into the mixnet,
|
||||
@@ -66,6 +68,9 @@ impl<G: GatewayTransceiver + ?Sized + Send> GatewayTransceiver for Box<G> {
|
||||
fn gateway_identity(&self) -> identity::PublicKey {
|
||||
(**self).gateway_identity()
|
||||
}
|
||||
fn ws_fd(&self) -> Option<RawFd> {
|
||||
(**self).ws_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
@@ -112,6 +117,9 @@ where
|
||||
fn gateway_identity(&self) -> identity::PublicKey {
|
||||
self.gateway_client.gateway_identity()
|
||||
}
|
||||
fn ws_fd(&self) -> Option<RawFd> {
|
||||
self.gateway_client.ws_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
@@ -187,6 +195,9 @@ mod nonwasm_sealed {
|
||||
fn gateway_identity(&self) -> identity::PublicKey {
|
||||
self.local_identity
|
||||
}
|
||||
fn ws_fd(&self) -> Option<RawFd> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -259,4 +270,7 @@ impl GatewayTransceiver for MockGateway {
|
||||
fn gateway_identity(&self) -> identity::PublicKey {
|
||||
self.dummy_identity
|
||||
}
|
||||
fn ws_fd(&self) -> Option<RawFd> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,11 +266,11 @@ impl std::ops::Div<f64> for PacketRates {
|
||||
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),
|
||||
"down: {}/s, up: {}/s (cover down: {}/s, cover up: {}/s)",
|
||||
bibytes2(self.real_packets_received_size),
|
||||
bibytes2(self.real_packets_sent_size + self.cover_packets_sent_size),
|
||||
bibytes2(self.real_packets_sent_size),
|
||||
bibytes2(self.cover_packets_received_size),
|
||||
bibytes2(self.cover_packets_sent_size),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -288,6 +288,7 @@ impl PacketRates {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum PacketStatisticsEvent {
|
||||
// The real packets sent. Recall that acks are sent by the gateway, so it's not included here.
|
||||
RealPacketSent(usize),
|
||||
@@ -443,7 +444,11 @@ impl PacketStatisticsControl {
|
||||
// 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,);
|
||||
log::info!(
|
||||
"mix packet retransmissions/real mix packets: {}/{}",
|
||||
delta.retransmissions_queued,
|
||||
delta.real_packets_queued,
|
||||
);
|
||||
} else {
|
||||
log::warn!("Unable to check retransmissions during recording window");
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ features = ["net", "sync", "time"]
|
||||
workspace = true
|
||||
# the choice of this particular tls feature was arbitrary;
|
||||
# if you reckon a different one would be more appropriate, feel free to change it
|
||||
features = ["native-tls"]
|
||||
# features = ["native-tls"]
|
||||
|
||||
# wasm-only dependencies
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen]
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::packet_router::PacketRouter;
|
||||
pub use crate::packet_router::{
|
||||
AcknowledgementReceiver, AcknowledgementSender, MixnetMessageReceiver, MixnetMessageSender,
|
||||
};
|
||||
use crate::socket_state::{PartiallyDelegated, SocketState};
|
||||
use crate::socket_state::{ws_fd, PartiallyDelegated, SocketState};
|
||||
use crate::traits::GatewayPacketRouter;
|
||||
use crate::{cleanup_socket_message, try_decrypt_binary_message};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
@@ -20,7 +20,8 @@ use nym_gateway_requests::authentication::encrypted_address::EncryptedAddressByt
|
||||
use nym_gateway_requests::iv::IV;
|
||||
use nym_gateway_requests::registration::handshake::{client_handshake, SharedKeys};
|
||||
use nym_gateway_requests::{
|
||||
BinaryRequest, ClientControlRequest, ServerResponse, CURRENT_PROTOCOL_VERSION,
|
||||
BinaryRequest, ClientControlRequest, ServerResponse, CREDENTIAL_UPDATE_V1_PROTOCOL_VERSION,
|
||||
CURRENT_PROTOCOL_VERSION,
|
||||
};
|
||||
use nym_network_defaults::{REMAINING_BANDWIDTH_THRESHOLD, TOKENS_TO_BURN};
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
@@ -32,11 +33,15 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tungstenite::protocol::Message;
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::fd::RawFd;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::time::sleep;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio_tungstenite::connect_async;
|
||||
|
||||
#[cfg(not(unix))]
|
||||
use std::os::raw::c_int as RawFd;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_utils::websocket::JSWebsocket;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
@@ -152,6 +157,14 @@ impl<C, St> GatewayClient<C, St> {
|
||||
self.gateway_identity
|
||||
}
|
||||
|
||||
pub fn ws_fd(&self) -> Option<RawFd> {
|
||||
match &self.connection {
|
||||
SocketState::Available(conn) => ws_fd(conn.as_ref()),
|
||||
SocketState::PartiallyDelegated(conn) => conn.ws_fd(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remaining_bandwidth(&self) -> i64 {
|
||||
self.bandwidth_remaining
|
||||
}
|
||||
@@ -382,6 +395,8 @@ impl<C, St> GatewayClient<C, St> {
|
||||
&self,
|
||||
gateway_protocol: Option<u8>,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
debug!("gateway protocol: {gateway_protocol:?}, ours: {CURRENT_PROTOCOL_VERSION}");
|
||||
|
||||
// right now there are no failure cases here, but this might change in the future
|
||||
match gateway_protocol {
|
||||
None => {
|
||||
@@ -491,6 +506,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
self.check_gateway_protocol(protocol_version)?;
|
||||
self.authenticated = status;
|
||||
self.bandwidth_remaining = bandwidth_remaining;
|
||||
self.negotiated_protocol = protocol_version;
|
||||
Ok(())
|
||||
}
|
||||
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
|
||||
@@ -577,6 +593,18 @@ impl<C, St> GatewayClient<C, St> {
|
||||
return self.try_claim_testnet_bandwidth().await;
|
||||
}
|
||||
|
||||
let Some(gateway_protocol) = self.negotiated_protocol else {
|
||||
return Err(GatewayClientError::OutdatedGatewayCredentialVersion {
|
||||
negotiated_protocol: None,
|
||||
});
|
||||
};
|
||||
|
||||
if gateway_protocol < CREDENTIAL_UPDATE_V1_PROTOCOL_VERSION {
|
||||
return Err(GatewayClientError::OutdatedGatewayCredentialVersion {
|
||||
negotiated_protocol: Some(gateway_protocol),
|
||||
});
|
||||
}
|
||||
|
||||
let prepared_credential = self
|
||||
.bandwidth_controller
|
||||
.as_ref()
|
||||
|
||||
@@ -47,6 +47,9 @@ pub enum GatewayClientError {
|
||||
#[error("Credential could not be serialized")]
|
||||
SerializeCredential,
|
||||
|
||||
#[error("can not spend bandwidth credential with the gateway as it's using outdated protocol (version: {negotiated_protocol:?})")]
|
||||
OutdatedGatewayCredentialVersion { negotiated_protocol: Option<u8> },
|
||||
|
||||
#[error("Client is not authenticated")]
|
||||
NotAuthenticated,
|
||||
|
||||
|
||||
@@ -11,9 +11,12 @@ use futures::{SinkExt, StreamExt};
|
||||
use log::*;
|
||||
use nym_gateway_requests::registration::handshake::SharedKeys;
|
||||
use nym_task::TaskClient;
|
||||
use std::os::raw::c_int as RawFd;
|
||||
use std::sync::Arc;
|
||||
use tungstenite::Message;
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::fd::AsRawFd;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::net::TcpStream;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -37,9 +40,22 @@ type WsConn = JSWebsocket;
|
||||
|
||||
type SplitStreamReceiver = oneshot::Receiver<Result<SplitStream<WsConn>, GatewayClientError>>;
|
||||
|
||||
pub(crate) fn ws_fd(_conn: &WsConn) -> Option<RawFd> {
|
||||
#[cfg(unix)]
|
||||
match _conn.get_ref() {
|
||||
MaybeTlsStream::Plain(stream) => Some(stream.as_raw_fd()),
|
||||
&_ => unreachable!(
|
||||
"If tls features are enabled, the inner stream needs to be unpacked into raw fd"
|
||||
),
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) struct PartiallyDelegated {
|
||||
sink_half: SplitSink<WsConn, Message>,
|
||||
delegated_stream: (SplitStreamReceiver, oneshot::Sender<()>),
|
||||
ws_fd: Option<RawFd>,
|
||||
}
|
||||
|
||||
impl PartiallyDelegated {
|
||||
@@ -92,6 +108,8 @@ impl PartiallyDelegated {
|
||||
let (notify_sender, notify_receiver) = oneshot::channel();
|
||||
let (stream_sender, stream_receiver) = oneshot::channel();
|
||||
|
||||
let ws_fd = ws_fd(&conn);
|
||||
|
||||
let (sink, mut stream) = conn.split();
|
||||
|
||||
let mixnet_receiver_future = async move {
|
||||
@@ -141,11 +159,16 @@ impl PartiallyDelegated {
|
||||
tokio::spawn(mixnet_receiver_future);
|
||||
|
||||
PartiallyDelegated {
|
||||
ws_fd,
|
||||
sink_half: sink,
|
||||
delegated_stream: (stream_receiver, notify_sender),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn ws_fd(&self) -> Option<RawFd> {
|
||||
self.ws_fd
|
||||
}
|
||||
|
||||
// if we want to send a message and don't care about response, we can don't need to reunite the split,
|
||||
// the sink itself is enough
|
||||
pub(crate) async fn send_without_response(
|
||||
|
||||
@@ -31,7 +31,6 @@ log = { workspace = true }
|
||||
url = { workspace = true, features = ["serde"] }
|
||||
tokio = { workspace = true, features = ["sync", "time"] }
|
||||
futures = { workspace = true }
|
||||
openssl = { version = "^0.10.55", features = ["vendored"], optional = true }
|
||||
|
||||
nym-coconut = { path = "../../nymcoconut" }
|
||||
nym-network-defaults = { path = "../../network-defaults" }
|
||||
@@ -90,7 +89,7 @@ required-features = ["http-client"]
|
||||
|
||||
[features]
|
||||
default = ["http-client"]
|
||||
http-client = ["cosmrs/rpc", "openssl"]
|
||||
http-client = ["cosmrs/rpc"]
|
||||
generate-ts = []
|
||||
contract-testing = ["nym-mixnet-contract-common/contract-testing"]
|
||||
|
||||
|
||||
@@ -397,7 +397,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
routes::API_VERSION,
|
||||
routes::COCONUT_ROUTES,
|
||||
routes::BANDWIDTH,
|
||||
routes::COCONUT_FREE_PASS_NONCE,
|
||||
routes::COCONUT_FREE_PASS,
|
||||
],
|
||||
NO_PARAMS,
|
||||
request,
|
||||
|
||||
@@ -7,19 +7,20 @@ use crate::nyxd::error::NyxdError;
|
||||
use crate::nyxd::CosmWasmClient;
|
||||
use async_trait::async_trait;
|
||||
use cosmrs::AccountId;
|
||||
use nym_coconut_dkg_common::types::ChunkIndex;
|
||||
use cosmwasm_std::Addr;
|
||||
use log::trace;
|
||||
use nym_coconut_dkg_common::types::{ChunkIndex, NodeIndex, StateAdvanceResponse};
|
||||
use serde::Deserialize;
|
||||
|
||||
use nym_coconut_dkg_common::dealer::RegisteredDealerDetails;
|
||||
pub use nym_coconut_dkg_common::{
|
||||
dealer::{DealerDetailsResponse, PagedDealerResponse},
|
||||
dealer::{DealerDetailsResponse, PagedDealerIndexResponse, PagedDealerResponse},
|
||||
dealing::{
|
||||
DealerDealingsStatusResponse, DealingChunkResponse, DealingChunkStatusResponse,
|
||||
DealingMetadataResponse, DealingStatusResponse,
|
||||
},
|
||||
msg::QueryMsg as DkgQueryMsg,
|
||||
types::{
|
||||
DealerDetails, DealingIndex, Epoch, EpochId, EpochState, InitialReplacementData, State,
|
||||
},
|
||||
types::{DealerDetails, DealingIndex, Epoch, EpochId, EpochState, State},
|
||||
verification_key::{ContractVKShare, PagedVKSharesResponse, VkShareResponse},
|
||||
};
|
||||
|
||||
@@ -40,13 +41,25 @@ pub trait DkgQueryClient {
|
||||
self.query_dkg_contract(request).await
|
||||
}
|
||||
|
||||
async fn can_advance_state(&self) -> Result<StateAdvanceResponse, NyxdError> {
|
||||
let request = DkgQueryMsg::CanAdvanceState {};
|
||||
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
|
||||
}
|
||||
|
||||
async fn get_initial_dealers(&self) -> Result<Option<InitialReplacementData>, NyxdError> {
|
||||
let request = DkgQueryMsg::GetInitialDealers {};
|
||||
async fn get_registered_dealer_details(
|
||||
&self,
|
||||
address: &AccountId,
|
||||
epoch_id: Option<EpochId>,
|
||||
) -> Result<RegisteredDealerDetails, NyxdError> {
|
||||
let request = DkgQueryMsg::GetRegisteredDealer {
|
||||
dealer_address: address.to_string(),
|
||||
epoch_id,
|
||||
};
|
||||
self.query_dkg_contract(request).await
|
||||
}
|
||||
|
||||
@@ -69,12 +82,12 @@ pub trait DkgQueryClient {
|
||||
self.query_dkg_contract(request).await
|
||||
}
|
||||
|
||||
async fn get_past_dealers_paged(
|
||||
async fn get_dealer_indices_paged(
|
||||
&self,
|
||||
start_after: Option<String>,
|
||||
limit: Option<u32>,
|
||||
) -> Result<PagedDealerResponse, NyxdError> {
|
||||
let request = DkgQueryMsg::GetPastDealers { start_after, limit };
|
||||
) -> Result<PagedDealerIndexResponse, NyxdError> {
|
||||
let request = DkgQueryMsg::GetDealerIndices { start_after, limit };
|
||||
self.query_dkg_contract(request).await
|
||||
}
|
||||
|
||||
@@ -190,8 +203,8 @@ pub trait PagedDkgQueryClient: DkgQueryClient {
|
||||
collect_paged!(self, get_current_dealers_paged, dealers)
|
||||
}
|
||||
|
||||
async fn get_all_past_dealers(&self) -> Result<Vec<DealerDetails>, NyxdError> {
|
||||
collect_paged!(self, get_past_dealers_paged, dealers)
|
||||
async fn get_all_dealer_indices(&self) -> Result<Vec<(Addr, NodeIndex)>, NyxdError> {
|
||||
collect_paged!(self, get_dealer_indices_paged, indices)
|
||||
}
|
||||
|
||||
async fn get_all_verification_key_shares(
|
||||
@@ -218,6 +231,7 @@ where
|
||||
let dkg_contract_address = &self
|
||||
.dkg_contract_address()
|
||||
.ok_or_else(|| NyxdError::unavailable_contract_address("dkg contract"))?;
|
||||
trace!("using the following dkg contract: {dkg_contract_address}");
|
||||
self.query_contract_smart(dkg_contract_address, &query)
|
||||
.await
|
||||
}
|
||||
@@ -238,18 +252,24 @@ mod tests {
|
||||
match msg {
|
||||
DkgQueryMsg::GetState {} => client.get_state().ignore(),
|
||||
DkgQueryMsg::GetCurrentEpochState {} => client.get_current_epoch().ignore(),
|
||||
DkgQueryMsg::CanAdvanceState {} => client.can_advance_state().ignore(),
|
||||
DkgQueryMsg::GetCurrentEpochThreshold {} => {
|
||||
client.get_current_epoch_threshold().ignore()
|
||||
}
|
||||
DkgQueryMsg::GetInitialDealers {} => client.get_initial_dealers().ignore(),
|
||||
DkgQueryMsg::GetRegisteredDealer {
|
||||
dealer_address,
|
||||
epoch_id,
|
||||
} => client
|
||||
.get_registered_dealer_details(&dealer_address.parse().unwrap(), epoch_id)
|
||||
.ignore(),
|
||||
DkgQueryMsg::GetDealerDetails { dealer_address } => client
|
||||
.get_dealer_details(&dealer_address.parse().unwrap())
|
||||
.ignore(),
|
||||
DkgQueryMsg::GetCurrentDealers { limit, start_after } => client
|
||||
.get_current_dealers_paged(start_after, limit)
|
||||
.ignore(),
|
||||
DkgQueryMsg::GetPastDealers { limit, start_after } => {
|
||||
client.get_past_dealers_paged(start_after, limit).ignore()
|
||||
DkgQueryMsg::GetDealerIndices { limit, start_after } => {
|
||||
client.get_dealer_indices_paged(start_after, limit).ignore()
|
||||
}
|
||||
DkgQueryMsg::GetDealingStatus {
|
||||
epoch_id,
|
||||
|
||||
+19
-12
@@ -39,13 +39,6 @@ pub trait DkgSigningClient {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn surpass_threshold(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> {
|
||||
let req = DkgExecuteMsg::SurpassedThreshold {};
|
||||
|
||||
self.execute_dkg_contract(fee, req, "surpass DKG threshold".to_string(), vec![])
|
||||
.await
|
||||
}
|
||||
|
||||
async fn register_dealer(
|
||||
&self,
|
||||
bte_key: EncodedBTEPublicKeyWithProof,
|
||||
@@ -85,10 +78,9 @@ pub trait DkgSigningClient {
|
||||
async fn submit_dealing_chunk(
|
||||
&self,
|
||||
chunk: PartialContractDealing,
|
||||
resharing: bool,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
let req = DkgExecuteMsg::CommitDealingsChunk { chunk, resharing };
|
||||
let req = DkgExecuteMsg::CommitDealingsChunk { chunk };
|
||||
|
||||
self.execute_dkg_contract(fee, req, "dealing chunk commitment".to_string(), vec![])
|
||||
.await
|
||||
@@ -130,6 +122,20 @@ pub trait DkgSigningClient {
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn trigger_dkg_reset(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> {
|
||||
let req = DkgExecuteMsg::TriggerReset {};
|
||||
|
||||
self.execute_dkg_contract(fee, req, "trigger DKG reset".to_string(), vec![])
|
||||
.await
|
||||
}
|
||||
|
||||
async fn trigger_dkg_resharing(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> {
|
||||
let req = DkgExecuteMsg::TriggerResharing {};
|
||||
|
||||
self.execute_dkg_contract(fee, req, "trigger DKG resharing".to_string(), vec![])
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
@@ -192,8 +198,8 @@ mod tests {
|
||||
} => client
|
||||
.submit_dealing_metadata(dealing_index, chunks, resharing, None)
|
||||
.ignore(),
|
||||
DkgExecuteMsg::CommitDealingsChunk { chunk, resharing } => {
|
||||
client.submit_dealing_chunk(chunk, resharing, None).ignore()
|
||||
DkgExecuteMsg::CommitDealingsChunk { chunk } => {
|
||||
client.submit_dealing_chunk(chunk, None).ignore()
|
||||
}
|
||||
DkgExecuteMsg::CommitVerificationKeyShare { share, resharing } => client
|
||||
.submit_verification_key_share(share, resharing, None)
|
||||
@@ -201,8 +207,9 @@ mod tests {
|
||||
DkgExecuteMsg::VerifyVerificationKeyShare { owner, resharing } => client
|
||||
.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(),
|
||||
DkgExecuteMsg::TriggerReset {} => client.trigger_dkg_reset(None).ignore(),
|
||||
DkgExecuteMsg::TriggerResharing {} => client.trigger_dkg_resharing(None).ignore(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ use crate::signing::tx_signer::TxSigner;
|
||||
use crate::signing::AccountData;
|
||||
use crate::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRpcNyxdClient, ReqwestRpcClient};
|
||||
use async_trait::async_trait;
|
||||
use cosmrs::cosmwasm;
|
||||
use cosmrs::tendermint::{abci, evidence::Evidence, Genesis};
|
||||
use cosmrs::tx::{Raw, SignDoc};
|
||||
use cosmwasm_std::Addr;
|
||||
@@ -40,7 +39,7 @@ pub use crate::rpc::TendermintRpcClient;
|
||||
pub use coin::Coin;
|
||||
pub use cosmrs::{
|
||||
bank::MsgSend,
|
||||
bip32,
|
||||
bip32, cosmwasm,
|
||||
crypto::PublicKey,
|
||||
query::{PageRequest, PageResponse},
|
||||
tendermint::{
|
||||
|
||||
@@ -9,7 +9,7 @@ license.workspace = true
|
||||
anyhow = { workspace = true }
|
||||
base64 = "0.13.0"
|
||||
bip39 = { workspace = true }
|
||||
bs58 = "0.4"
|
||||
bs58 = { workspace = true }
|
||||
comfy-table = "6.0.0"
|
||||
cfg-if = "1.0.0"
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
@@ -55,6 +55,7 @@ nym-credentials = { path = "../../common/credentials" }
|
||||
nym-credentials-interface = { path = "../../common/credentials-interface" }
|
||||
nym-credential-storage = { path = "../../common/credential-storage" }
|
||||
nym-credential-utils = { path = "../../common/credential-utils" }
|
||||
nym-id = { path = "../nym-id" }
|
||||
|
||||
nym-pemstore = { path = "../../common/pemstore", version = "0.3.0" }
|
||||
nym-types = { path = "../../common/types" }
|
||||
|
||||
@@ -35,10 +35,12 @@ fn parse_rfc3339_expiration_date(raw: &str) -> Result<OffsetDateTime, time::erro
|
||||
#[clap(group(ArgGroup::new("expiration").required(true)))]
|
||||
pub struct Args {
|
||||
/// Specifies the expiration date of the free pass(es)
|
||||
/// Requires
|
||||
/// Can't be set to more than a week into the future.
|
||||
#[clap(long, group = "expiration", value_parser = parse_rfc3339_expiration_date)]
|
||||
pub(crate) expiration_date: Option<OffsetDateTime>,
|
||||
|
||||
/// The expiration of the free pass(es) expresses as unix timestamp.
|
||||
/// Can't be set to more than a week into the future.
|
||||
#[clap(long, group = "expiration")]
|
||||
pub(crate) expiration_timestamp: Option<i64>,
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::utils::CommonConfigsWrapper;
|
||||
use anyhow::bail;
|
||||
use clap::ArgGroup;
|
||||
use clap::Parser;
|
||||
use nym_credential_storage::initialise_persistent_storage;
|
||||
use nym_id::import_credential;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result<Vec<u8>> {
|
||||
bs58::decode(raw).into_vec()
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[clap(group(ArgGroup::new("cred_data").required(true)))]
|
||||
pub struct Args {
|
||||
/// Config file of the client that is supposed to use the credential.
|
||||
#[clap(long)]
|
||||
pub(crate) client_config: PathBuf,
|
||||
|
||||
/// Explicitly provide the encoded credential data (as base58)
|
||||
#[clap(long, group = "cred_data", value_parser = parse_encoded_credential_data)]
|
||||
pub(crate) credential_data: Option<Vec<u8>>,
|
||||
|
||||
/// Specifies the path to file containing binary credential data
|
||||
#[clap(long, group = "cred_data")]
|
||||
pub(crate) credential_path: Option<PathBuf>,
|
||||
|
||||
// currently hidden as there exists only a single serialization standard
|
||||
#[clap(long, hide = true)]
|
||||
pub(crate) version: Option<u8>,
|
||||
}
|
||||
|
||||
pub async fn execute(args: Args) -> anyhow::Result<()> {
|
||||
let loaded = CommonConfigsWrapper::try_load(args.client_config)?;
|
||||
|
||||
if let Ok(id) = loaded.try_get_id() {
|
||||
println!("loaded config file for client '{id}'");
|
||||
}
|
||||
|
||||
let Ok(credentials_store) = loaded.try_get_credentials_store() else {
|
||||
bail!("the loaded config does not have a credentials store information")
|
||||
};
|
||||
|
||||
println!(
|
||||
"using credentials store at '{}'",
|
||||
credentials_store.display()
|
||||
);
|
||||
let credentials_store = initialise_persistent_storage(credentials_store).await;
|
||||
|
||||
let raw_credential = match args.credential_data {
|
||||
Some(data) => data,
|
||||
None => {
|
||||
// SAFETY: one of those arguments must have been set
|
||||
fs::read(args.credential_path.unwrap())?
|
||||
}
|
||||
};
|
||||
|
||||
import_credential(credentials_store, raw_credential, args.version).await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
use clap::{Args, Subcommand};
|
||||
|
||||
pub mod generate_freepass;
|
||||
pub mod import_credential;
|
||||
pub mod issue_credentials;
|
||||
pub mod recover_credentials;
|
||||
|
||||
@@ -19,4 +20,5 @@ pub enum CoconutCommands {
|
||||
GenerateFreepass(generate_freepass::Args),
|
||||
IssueCredentials(issue_credentials::Args),
|
||||
RecoverCredentials(recover_credentials::Args),
|
||||
ImportCredential(import_credential::Args),
|
||||
}
|
||||
|
||||
@@ -14,20 +14,32 @@ pub struct DealerDetails {
|
||||
pub assigned_index: NodeIndex,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct DealerRegistrationDetails {
|
||||
pub bte_public_key_with_proof: EncodedBTEPublicKeyWithProof,
|
||||
pub ed25519_identity: String,
|
||||
pub announce_address: String,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
#[derive(Copy)]
|
||||
pub enum DealerType {
|
||||
Current,
|
||||
Past,
|
||||
Current { assigned_index: NodeIndex },
|
||||
Past { assigned_index: NodeIndex },
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl DealerType {
|
||||
pub fn is_current(&self) -> bool {
|
||||
matches!(&self, DealerType::Current)
|
||||
matches!(&self, DealerType::Current { .. })
|
||||
}
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct RegisteredDealerDetails {
|
||||
pub details: Option<DealerRegistrationDetails>,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct DealerDetailsResponse {
|
||||
pub details: Option<DealerDetails>,
|
||||
@@ -65,3 +77,20 @@ impl PagedDealerResponse {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct PagedDealerIndexResponse {
|
||||
pub indices: Vec<(Addr, NodeIndex)>,
|
||||
|
||||
/// Field indicating paging information for the following queries if the caller wishes to get further entries.
|
||||
pub start_next_after: Option<Addr>,
|
||||
}
|
||||
|
||||
impl PagedDealerIndexResponse {
|
||||
pub fn new(indices: Vec<(Addr, NodeIndex)>, start_next_after: Option<Addr>) -> Self {
|
||||
PagedDealerIndexResponse {
|
||||
indices,
|
||||
start_next_after,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,15 @@ use cosmwasm_schema::cw_serde;
|
||||
|
||||
#[cfg(feature = "schema")]
|
||||
use crate::{
|
||||
dealer::{DealerDetailsResponse, PagedDealerResponse},
|
||||
dealer::{
|
||||
DealerDetailsResponse, PagedDealerIndexResponse, PagedDealerResponse,
|
||||
RegisteredDealerDetails,
|
||||
},
|
||||
dealing::{
|
||||
DealerDealingsStatusResponse, DealingChunkResponse, DealingChunkStatusResponse,
|
||||
DealingMetadataResponse, DealingStatusResponse,
|
||||
},
|
||||
types::{Epoch, InitialReplacementData, State},
|
||||
types::{Epoch, State, StateAdvanceResponse},
|
||||
verification_key::{PagedVKSharesResponse, VkShareResponse},
|
||||
};
|
||||
#[cfg(feature = "schema")]
|
||||
@@ -53,7 +56,6 @@ pub enum ExecuteMsg {
|
||||
|
||||
CommitDealingsChunk {
|
||||
chunk: PartialContractDealing,
|
||||
resharing: bool,
|
||||
},
|
||||
|
||||
CommitVerificationKeyShare {
|
||||
@@ -66,9 +68,11 @@ pub enum ExecuteMsg {
|
||||
resharing: bool,
|
||||
},
|
||||
|
||||
SurpassedThreshold {},
|
||||
|
||||
AdvanceEpochState {},
|
||||
|
||||
TriggerReset {},
|
||||
|
||||
TriggerResharing {},
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
@@ -83,8 +87,14 @@ pub enum QueryMsg {
|
||||
#[cfg_attr(feature = "schema", returns(u64))]
|
||||
GetCurrentEpochThreshold {},
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(Option<InitialReplacementData>))]
|
||||
GetInitialDealers {},
|
||||
#[cfg_attr(feature = "schema", returns(StateAdvanceResponse))]
|
||||
CanAdvanceState {},
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(RegisteredDealerDetails))]
|
||||
GetRegisteredDealer {
|
||||
dealer_address: String,
|
||||
epoch_id: Option<EpochId>,
|
||||
},
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(DealerDetailsResponse))]
|
||||
GetDealerDetails { dealer_address: String },
|
||||
@@ -95,8 +105,8 @@ pub enum QueryMsg {
|
||||
start_after: Option<String>,
|
||||
},
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(PagedDealerResponse))]
|
||||
GetPastDealers {
|
||||
#[cfg_attr(feature = "schema", returns(PagedDealerIndexResponse))]
|
||||
GetDealerIndices {
|
||||
limit: Option<u32>,
|
||||
start_after: Option<String>,
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@ use cosmwasm_schema::cw_serde;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
|
||||
pub use crate::dealer::{DealerDetails, PagedDealerResponse};
|
||||
pub use crate::dealer::{DealerDetails, DealerRegistrationDetails, PagedDealerResponse};
|
||||
pub use contracts_common::dealings::ContractSafeBytes;
|
||||
pub use cosmwasm_std::{Addr, Coin, Timestamp};
|
||||
pub use cw4::Cw4Contract;
|
||||
@@ -22,9 +22,19 @@ pub type ChunkIndex = u16;
|
||||
pub type PartialContractDealingData = ContractSafeBytes;
|
||||
|
||||
#[cw_serde]
|
||||
pub struct InitialReplacementData {
|
||||
pub initial_dealers: Vec<Addr>,
|
||||
pub initial_height: u64,
|
||||
#[derive(Copy, Default)]
|
||||
pub struct StateAdvanceResponse {
|
||||
pub current_state: EpochState,
|
||||
pub progress: StateProgress,
|
||||
pub deadline: Option<Timestamp>,
|
||||
pub reached_deadline: bool,
|
||||
pub is_complete: bool,
|
||||
}
|
||||
|
||||
impl StateAdvanceResponse {
|
||||
pub fn can_advance(&self) -> bool {
|
||||
self.reached_deadline || self.is_complete
|
||||
}
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
@@ -40,6 +50,26 @@ pub struct TimeConfiguration {
|
||||
pub in_progress_time_secs: u64,
|
||||
}
|
||||
|
||||
impl TimeConfiguration {
|
||||
pub fn state_duration(&self, state: EpochState) -> Option<u64> {
|
||||
match state {
|
||||
EpochState::WaitingInitialisation => None,
|
||||
EpochState::PublicKeySubmission { .. } => Some(self.public_key_submission_time_secs),
|
||||
EpochState::DealingExchange { .. } => Some(self.dealing_exchange_time_secs),
|
||||
EpochState::VerificationKeySubmission { .. } => {
|
||||
Some(self.verification_key_submission_time_secs)
|
||||
}
|
||||
EpochState::VerificationKeyValidation { .. } => {
|
||||
Some(self.verification_key_validation_time_secs)
|
||||
}
|
||||
EpochState::VerificationKeyFinalization { .. } => {
|
||||
Some(self.verification_key_finalization_time_secs)
|
||||
}
|
||||
EpochState::InProgress => Some(self.in_progress_time_secs),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for TimeConfiguration {
|
||||
type Err = String;
|
||||
|
||||
@@ -87,13 +117,41 @@ pub struct State {
|
||||
pub key_size: u32,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
#[derive(Copy, Default)]
|
||||
pub struct StateProgress {
|
||||
/// Counts the number of dealers that have registered in this epoch.
|
||||
// ideally we want to have here all group members
|
||||
pub registered_dealers: u32,
|
||||
|
||||
/// Counts the number of resharing dealers that have registered in this epoch.
|
||||
/// This field is only populated during a resharing exchange.
|
||||
/// It is always <= registered_dealers.
|
||||
pub registered_resharing_dealers: u32,
|
||||
|
||||
/// Counts the number of fully received dealings (i.e. full chunks) from all the allowed dealers.
|
||||
// we expect registered_dealers * state.key_size number of dealings here (each dealer has to submit key_size number of dealings)
|
||||
pub submitted_dealings: u32,
|
||||
|
||||
/// Counts the number of submitted verification key shared from the dealers.
|
||||
// we expect registered_dealers number of keys here
|
||||
pub submitted_key_shares: u32,
|
||||
|
||||
/// Counts the number of verified key shares.
|
||||
// we expect submitted_key_shares number of verified keys here
|
||||
pub verified_keys: u32,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
#[derive(Copy, Default)]
|
||||
pub struct Epoch {
|
||||
pub state: EpochState,
|
||||
pub epoch_id: EpochId,
|
||||
pub state_progress: StateProgress,
|
||||
pub time_configuration: TimeConfiguration,
|
||||
pub finish_timestamp: Option<Timestamp>,
|
||||
|
||||
#[serde(alias = "finish_timestamp")]
|
||||
pub deadline: Option<Timestamp>,
|
||||
}
|
||||
|
||||
impl Epoch {
|
||||
@@ -103,35 +161,45 @@ impl Epoch {
|
||||
time_configuration: TimeConfiguration,
|
||||
current_timestamp: Timestamp,
|
||||
) -> Self {
|
||||
let duration = match state {
|
||||
EpochState::WaitingInitialisation => None,
|
||||
EpochState::PublicKeySubmission { .. } => {
|
||||
Some(time_configuration.public_key_submission_time_secs)
|
||||
}
|
||||
EpochState::DealingExchange { .. } => {
|
||||
Some(time_configuration.dealing_exchange_time_secs)
|
||||
}
|
||||
EpochState::VerificationKeySubmission { .. } => {
|
||||
Some(time_configuration.verification_key_submission_time_secs)
|
||||
}
|
||||
EpochState::VerificationKeyValidation { .. } => {
|
||||
Some(time_configuration.verification_key_validation_time_secs)
|
||||
}
|
||||
EpochState::VerificationKeyFinalization { .. } => {
|
||||
Some(time_configuration.verification_key_finalization_time_secs)
|
||||
}
|
||||
EpochState::InProgress => Some(time_configuration.in_progress_time_secs),
|
||||
};
|
||||
let duration = time_configuration.state_duration(state);
|
||||
|
||||
Epoch {
|
||||
state,
|
||||
epoch_id,
|
||||
state_progress: Default::default(),
|
||||
time_configuration,
|
||||
finish_timestamp: duration.map(|d| current_timestamp.plus_seconds(d)),
|
||||
deadline: duration.map(|d| current_timestamp.plus_seconds(d)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(mut self, next_state: EpochState, current_timestamp: Timestamp) -> Self {
|
||||
self.state = next_state;
|
||||
let duration = self.time_configuration.state_duration(next_state);
|
||||
self.deadline = duration.map(|d| current_timestamp.plus_seconds(d));
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn next_reset(self, current_timestamp: Timestamp) -> Self {
|
||||
Epoch::new(
|
||||
EpochState::PublicKeySubmission { resharing: false },
|
||||
self.epoch_id + 1,
|
||||
self.time_configuration,
|
||||
current_timestamp,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn next_resharing(self, current_timestamp: Timestamp) -> Self {
|
||||
Epoch::new(
|
||||
EpochState::PublicKeySubmission { resharing: true },
|
||||
self.epoch_id + 1,
|
||||
self.time_configuration,
|
||||
current_timestamp,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn final_timestamp_secs(&self) -> Option<u64> {
|
||||
let mut finish = self.finish_timestamp?.seconds();
|
||||
let mut finish = self.deadline?.seconds();
|
||||
let time_configuration = self.time_configuration;
|
||||
let mut curr_epoch_state = self.state;
|
||||
while let Some(state) = curr_epoch_state.next() {
|
||||
@@ -256,4 +324,8 @@ impl EpochState {
|
||||
pub fn is_in_progress(&self) -> bool {
|
||||
matches!(self, EpochState::InProgress)
|
||||
}
|
||||
|
||||
pub fn is_dealing_exchange(&self) -> bool {
|
||||
matches!(self, EpochState::DealingExchange { .. })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ license = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
bs58 = "0.4.0"
|
||||
bs58 = { workspace = true }
|
||||
cosmwasm-std = { workspace = true }
|
||||
cosmwasm-schema = { workspace = true }
|
||||
schemars = "0.8"
|
||||
|
||||
@@ -13,5 +13,6 @@ CREATE TABLE coconut_credentials
|
||||
credential_type TEXT NOT NULL,
|
||||
credential_data BLOB NOT NULL,
|
||||
epoch_id INTEGER NOT NULL,
|
||||
consumed BOOLEAN NOT NULL
|
||||
consumed BOOLEAN NOT NULL,
|
||||
expired BOOLEAN NOT NULL
|
||||
);
|
||||
@@ -48,13 +48,18 @@ impl CoconutCredentialManager {
|
||||
credential_type,
|
||||
epoch_id,
|
||||
consumed: false,
|
||||
expired: false,
|
||||
})
|
||||
}
|
||||
|
||||
/// Tries to retrieve one of the stored, unused credentials.
|
||||
pub async fn get_next_unspent_credential(&self) -> Option<StoredIssuedCredential> {
|
||||
let creds = self.inner.read().await;
|
||||
creds.data.iter().find(|c| !c.consumed).cloned()
|
||||
creds
|
||||
.data
|
||||
.iter()
|
||||
.find(|c| !c.consumed && !c.expired)
|
||||
.cloned()
|
||||
}
|
||||
|
||||
/// Consumes in the database the specified credential.
|
||||
@@ -68,4 +73,16 @@ impl CoconutCredentialManager {
|
||||
cred.consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Marks the specified credential as expired
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `id`: Id of the credential to mark as expired.
|
||||
pub async fn mark_expired(&self, id: i64) {
|
||||
let mut creds = self.inner.write().await;
|
||||
if let Some(cred) = creds.data.get_mut(id as usize) {
|
||||
cred.expired = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@ impl CoconutCredentialManager {
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO coconut_credentials(serialization_revision, credential_type, credential_data, epoch_id, consumed)
|
||||
VALUES (?, ?, ?, ?, false)
|
||||
INSERT INTO coconut_credentials(serialization_revision, credential_type, credential_data, epoch_id, consumed, expired)
|
||||
VALUES (?, ?, ?, ?, false, false)
|
||||
"#,
|
||||
serialization_revision, credential_type, credential_data, epoch_id
|
||||
).execute(&self.connection_pool).await?;
|
||||
@@ -38,9 +38,11 @@ impl CoconutCredentialManager {
|
||||
pub async fn get_next_unspent_credential(
|
||||
&self,
|
||||
) -> Result<Option<StoredIssuedCredential>, sqlx::Error> {
|
||||
sqlx::query_as("SELECT * FROM coconut_credentials WHERE NOT consumed LIMIT 1")
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await
|
||||
sqlx::query_as(
|
||||
"SELECT * FROM coconut_credentials WHERE NOT consumed AND NOT expired LIMIT 1",
|
||||
)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Consumes in the database the specified credential.
|
||||
@@ -57,4 +59,19 @@ impl CoconutCredentialManager {
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Marks the specified credential as expired
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `id`: Id of the credential to mark as expired.
|
||||
pub async fn mark_expired(&self, id: i64) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
"UPDATE coconut_credentials SET expired = TRUE WHERE id = ?",
|
||||
id
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,14 +44,11 @@ impl Storage for EphemeralStorage {
|
||||
|
||||
async fn get_next_unspent_credential(
|
||||
&self,
|
||||
) -> Result<StoredIssuedCredential, Self::StorageError> {
|
||||
let credential = self
|
||||
) -> Result<Option<StoredIssuedCredential>, Self::StorageError> {
|
||||
Ok(self
|
||||
.coconut_credential_manager
|
||||
.get_next_unspent_credential()
|
||||
.await
|
||||
.ok_or(StorageError::NoCredential)?;
|
||||
|
||||
Ok(credential)
|
||||
.await)
|
||||
}
|
||||
|
||||
async fn consume_coconut_credential(&self, id: i64) -> Result<(), StorageError> {
|
||||
@@ -61,4 +58,10 @@ impl Storage for EphemeralStorage {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn mark_expired(&self, id: i64) -> Result<(), Self::StorageError> {
|
||||
self.coconut_credential_manager.mark_expired(id).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ pub struct StoredIssuedCredential {
|
||||
|
||||
pub epoch_id: u32,
|
||||
pub consumed: bool,
|
||||
pub expired: bool,
|
||||
}
|
||||
|
||||
pub struct StorableIssuedCredential<'a> {
|
||||
|
||||
@@ -76,14 +76,11 @@ impl Storage for PersistentStorage {
|
||||
|
||||
async fn get_next_unspent_credential(
|
||||
&self,
|
||||
) -> Result<StoredIssuedCredential, Self::StorageError> {
|
||||
let credential = self
|
||||
) -> Result<Option<StoredIssuedCredential>, Self::StorageError> {
|
||||
Ok(self
|
||||
.coconut_credential_manager
|
||||
.get_next_unspent_credential()
|
||||
.await?
|
||||
.ok_or(StorageError::NoCredential)?;
|
||||
|
||||
Ok(credential)
|
||||
.await?)
|
||||
}
|
||||
|
||||
async fn consume_coconut_credential(&self, id: i64) -> Result<(), StorageError> {
|
||||
@@ -93,4 +90,10 @@ impl Storage for PersistentStorage {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn mark_expired(&self, id: i64) -> Result<(), Self::StorageError> {
|
||||
self.coconut_credential_manager.mark_expired(id).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,10 +14,11 @@ pub trait Storage: Send + Sync {
|
||||
bandwidth_credential: StorableIssuedCredential<'a>,
|
||||
) -> Result<(), Self::StorageError>;
|
||||
|
||||
/// Tries to retrieve one of the stored, unused credentials.
|
||||
/// Tries to retrieve one of the stored, unused credentials,
|
||||
/// that is also not marked as expired
|
||||
async fn get_next_unspent_credential(
|
||||
&self,
|
||||
) -> Result<StoredIssuedCredential, Self::StorageError>;
|
||||
) -> Result<Option<StoredIssuedCredential>, Self::StorageError>;
|
||||
|
||||
/// Marks as consumed in the database the specified credential.
|
||||
///
|
||||
@@ -25,4 +26,11 @@ pub trait Storage: Send + Sync {
|
||||
///
|
||||
/// * `id`: Id of the credential to be consumed.
|
||||
async fn consume_coconut_credential(&self, id: i64) -> Result<(), Self::StorageError>;
|
||||
|
||||
/// Marks the specified credential as expired
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `id`: Id of the credential to mark as expired.
|
||||
async fn mark_expired(&self, id: i64) -> Result<(), Self::StorageError>;
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ impl RecoveryStorage {
|
||||
|
||||
pub fn voucher_filename(voucher: &IssuanceBandwidthCredential) -> String {
|
||||
let prefix = voucher.typ().to_string();
|
||||
let suffix = voucher.blinded_g1_serial_number_bs58();
|
||||
let suffix = voucher.blinded_serial_number_bs58();
|
||||
format!("{prefix}-{suffix}.{DUMPED_VOUCHER_EXTENSION}")
|
||||
}
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ where
|
||||
.as_secs();
|
||||
|
||||
if epoch.state.is_final() {
|
||||
if let Some(finish_timestamp) = epoch.finish_timestamp {
|
||||
if let Some(finish_timestamp) = epoch.deadline {
|
||||
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);
|
||||
|
||||
@@ -10,9 +10,9 @@ use thiserror::Error;
|
||||
pub use nym_coconut::{
|
||||
aggregate_signature_shares, aggregate_verification_keys, blind_sign, hash_to_scalar, keygen,
|
||||
prepare_blind_sign, prove_bandwidth_credential, verify_credential, Attribute, Base58,
|
||||
BlindSignRequest, BlindedSignature, Bytable, CoconutError, KeyPair, Parameters,
|
||||
PrivateAttribute, PublicAttribute, SecretKey, Signature, SignatureShare, VerificationKey,
|
||||
VerifyCredentialRequest,
|
||||
BlindSignRequest, BlindedSerialNumber, BlindedSignature, Bytable, CoconutError, KeyPair,
|
||||
Parameters, PrivateAttribute, PublicAttribute, SecretKey, Signature, SignatureShare,
|
||||
VerificationKey, VerifyCredentialRequest,
|
||||
};
|
||||
|
||||
pub const VOUCHER_INFO_TYPE: &str = "BandwidthVoucher";
|
||||
@@ -129,4 +129,8 @@ impl CredentialSpendingData {
|
||||
// the first attribute is variant specific bandwidth encoding, the second one should be the type
|
||||
self.public_attributes_plain.first()
|
||||
}
|
||||
|
||||
pub fn blinded_serial_number(&self) -> BlindedSerialNumber {
|
||||
self.verify_credential_request.blinded_serial_number()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
pub const MAX_FREE_PASS_VALIDITY: Duration = Duration::WEEK; // 1 week
|
||||
|
||||
#[derive(Zeroize, ZeroizeOnDrop, Serialize, Deserialize)]
|
||||
#[derive(Debug, Zeroize, ZeroizeOnDrop, Serialize, Deserialize)]
|
||||
pub struct FreePassIssuedData {
|
||||
/// the plain validity value of this credential expressed as unix timestamp
|
||||
#[zeroize(skip)]
|
||||
@@ -30,6 +30,14 @@ impl<'a> From<&'a FreePassIssuanceData> for FreePassIssuedData {
|
||||
}
|
||||
|
||||
impl FreePassIssuedData {
|
||||
pub fn expired(&self) -> bool {
|
||||
self.expiry_date <= OffsetDateTime::now_utc()
|
||||
}
|
||||
|
||||
pub fn expiry_date(&self) -> OffsetDateTime {
|
||||
self.expiry_date
|
||||
}
|
||||
|
||||
pub fn expiry_date_plain(&self) -> String {
|
||||
self.expiry_date.unix_timestamp().to_string()
|
||||
}
|
||||
@@ -85,7 +93,7 @@ impl FreePassIssuanceData {
|
||||
pub async fn obtain_free_pass_nonce(
|
||||
&self,
|
||||
client: &nym_validator_client::client::NymApiClient,
|
||||
) -> Result<u32, Error> {
|
||||
) -> Result<[u8; 16], Error> {
|
||||
let server_response = client.free_pass_nonce().await?;
|
||||
Ok(server_response.current_nonce)
|
||||
}
|
||||
@@ -94,12 +102,11 @@ impl FreePassIssuanceData {
|
||||
&self,
|
||||
signing_request: &CredentialSigningData,
|
||||
account_data: &AccountData,
|
||||
issuer_nonce: u32,
|
||||
issuer_nonce: [u8; 16],
|
||||
) -> Result<FreePassRequest, Error> {
|
||||
let plaintext = issuer_nonce.to_be_bytes();
|
||||
let nonce_signature = account_data
|
||||
.private_key()
|
||||
.sign(&plaintext)
|
||||
.sign(&issuer_nonce)
|
||||
.map_err(|_| Error::Secp256k1SignFailure)?;
|
||||
|
||||
Ok(FreePassRequest {
|
||||
|
||||
@@ -9,10 +9,10 @@ use crate::coconut::bandwidth::{
|
||||
};
|
||||
use crate::coconut::utils::scalar_serde_helper;
|
||||
use crate::error::Error;
|
||||
use bls12_381::G1Projective;
|
||||
use nym_credentials_interface::{
|
||||
aggregate_signature_shares, hash_to_scalar, prepare_blind_sign, Attribute, BlindedSignature,
|
||||
Parameters, PrivateAttribute, PublicAttribute, Signature, SignatureShare, VerificationKey,
|
||||
aggregate_signature_shares, hash_to_scalar, prepare_blind_sign, Attribute, BlindedSerialNumber,
|
||||
BlindedSignature, Parameters, PrivateAttribute, PublicAttribute, Signature, SignatureShare,
|
||||
VerificationKey,
|
||||
};
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
@@ -140,15 +140,14 @@ impl IssuanceBandwidthCredential {
|
||||
Self::new(FreePassIssuanceData::new(expiry_date))
|
||||
}
|
||||
|
||||
pub fn blind_serial_number_in_g1subgroup(&self) -> G1Projective {
|
||||
bandwidth_credential_params().gen1() * self.serial_number
|
||||
pub fn blind_serial_number(&self) -> BlindedSerialNumber {
|
||||
(bandwidth_credential_params().gen2() * self.serial_number).into()
|
||||
}
|
||||
|
||||
// NOT TO BE CONFUSED WITH BLINDED SERIAL NUMBER IN CREDENTIAL ITSELF
|
||||
pub fn blinded_g1_serial_number_bs58(&self) -> String {
|
||||
pub fn blinded_serial_number_bs58(&self) -> String {
|
||||
use nym_credentials_interface::Base58;
|
||||
|
||||
self.blind_serial_number_in_g1subgroup().to_bs58()
|
||||
self.blind_serial_number().to_bs58()
|
||||
}
|
||||
|
||||
pub fn typ(&self) -> CredentialType {
|
||||
@@ -316,9 +315,12 @@ impl IssuanceBandwidthCredential {
|
||||
}
|
||||
|
||||
// TODO: is that actually needed?
|
||||
// idea: make it consistent with the issued credential and its vX serde
|
||||
pub fn try_from_recovered_bytes(bytes: &[u8]) -> Result<Self, Error> {
|
||||
use bincode::Options;
|
||||
Ok(make_recovery_bincode_serializer().deserialize(bytes)?)
|
||||
make_recovery_bincode_serializer()
|
||||
.deserialize(bytes)
|
||||
.map_err(|source| Error::RecoveryCredentialDeserializationFailure { source })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,3 +330,18 @@ fn make_recovery_bincode_serializer() -> impl bincode::Options {
|
||||
.with_big_endian()
|
||||
.with_varint_encoding()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn assert_zeroize_on_drop<T: ZeroizeOnDrop>() {}
|
||||
|
||||
fn assert_zeroize<T: Zeroize>() {}
|
||||
|
||||
#[test]
|
||||
fn credential_is_zeroized() {
|
||||
assert_zeroize::<IssuanceBandwidthCredential>();
|
||||
assert_zeroize_on_drop::<IssuanceBandwidthCredential>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
pub const CURRENT_SERIALIZATION_REVISION: u8 = 1;
|
||||
|
||||
#[derive(Zeroize, Serialize, Deserialize)]
|
||||
#[derive(Debug, Zeroize, Serialize, Deserialize)]
|
||||
pub enum BandwidthCredentialIssuedDataVariant {
|
||||
Voucher(BandwidthVoucherIssuedData),
|
||||
FreePass(FreePassIssuedData),
|
||||
@@ -116,6 +116,23 @@ impl IssuedBandwidthCredential {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_unpack(bytes: &[u8], revision: impl Into<Option<u8>>) -> Result<Self, Error> {
|
||||
let revision = revision.into().unwrap_or(CURRENT_SERIALIZATION_REVISION);
|
||||
|
||||
match revision {
|
||||
1 => Self::unpack_v1(bytes),
|
||||
_ => Err(Error::UnknownSerializationRevision { revision }),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn epoch_id(&self) -> EpochId {
|
||||
self.epoch_id
|
||||
}
|
||||
|
||||
pub fn variant_data(&self) -> &BandwidthCredentialIssuedDataVariant {
|
||||
&self.variant_data
|
||||
}
|
||||
|
||||
pub fn current_serialization_revision(&self) -> u8 {
|
||||
CURRENT_SERIALIZATION_REVISION
|
||||
}
|
||||
@@ -130,7 +147,12 @@ impl IssuedBandwidthCredential {
|
||||
/// Unpack (deserialize) the credential data from the given bytes using v1 serializer.
|
||||
pub fn unpack_v1(bytes: &[u8]) -> Result<Self, Error> {
|
||||
use bincode::Options;
|
||||
Ok(make_storable_bincode_serializer().deserialize(bytes)?)
|
||||
make_storable_bincode_serializer()
|
||||
.deserialize(bytes)
|
||||
.map_err(|source| Error::SerializationFailure {
|
||||
source,
|
||||
revision: 1,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn randomise_signature(&mut self) {
|
||||
@@ -183,3 +205,18 @@ fn make_storable_bincode_serializer() -> impl bincode::Options {
|
||||
.with_big_endian()
|
||||
.with_varint_encoding()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn assert_zeroize_on_drop<T: ZeroizeOnDrop>() {}
|
||||
|
||||
fn assert_zeroize<T: Zeroize>() {}
|
||||
|
||||
#[test]
|
||||
fn credential_is_zeroized() {
|
||||
assert_zeroize::<IssuedBandwidthCredential>();
|
||||
assert_zeroize_on_drop::<IssuedBandwidthCredential>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ use nym_validator_client::nyxd::{Coin, Hash};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
#[derive(Zeroize, ZeroizeOnDrop, Serialize, Deserialize)]
|
||||
#[derive(Debug, Zeroize, ZeroizeOnDrop, Serialize, Deserialize)]
|
||||
pub struct BandwidthVoucherIssuedData {
|
||||
/// the plain value (e.g., bandwidth) encoded in this voucher
|
||||
// note: for legacy reasons we're only using the value of the coin and ignoring the denom
|
||||
@@ -30,6 +30,10 @@ impl<'a> From<&'a BandwidthVoucherIssuanceData> for BandwidthVoucherIssuedData {
|
||||
}
|
||||
|
||||
impl BandwidthVoucherIssuedData {
|
||||
pub fn value(&self) -> &Coin {
|
||||
&self.value
|
||||
}
|
||||
|
||||
pub fn value_plain(&self) -> String {
|
||||
self.value.amount.to_string()
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use nym_credentials_interface::CoconutError;
|
||||
use nym_crypto::asymmetric::encryption::KeyRecoveryError;
|
||||
use nym_validator_client::ValidatorClientError;
|
||||
|
||||
use crate::coconut::bandwidth::issued::CURRENT_SERIALIZATION_REVISION;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
@@ -12,8 +13,18 @@ pub enum Error {
|
||||
#[error("IO error")]
|
||||
IOError(#[from] std::io::Error),
|
||||
|
||||
#[error("failed to (de)serialize credential structure: {0}")]
|
||||
SerializationFailure(#[from] bincode::Error),
|
||||
#[error("failed to deserialize a recovery credential: {source}")]
|
||||
RecoveryCredentialDeserializationFailure { source: bincode::Error },
|
||||
|
||||
#[error("failed to (de)serialize provided credential using revision {revision}: {source}")]
|
||||
SerializationFailure {
|
||||
#[source]
|
||||
source: bincode::Error,
|
||||
revision: u8,
|
||||
},
|
||||
|
||||
#[error("unknown credential serializatio revision {revision}. the current (and max supported) version is {CURRENT_SERIALIZATION_REVISION}")]
|
||||
UnknownSerializationRevision { revision: u8 },
|
||||
|
||||
#[error("The detailed description is yet to be determined")]
|
||||
BandwidthCredentialError,
|
||||
|
||||
@@ -9,3 +9,4 @@ pub use coconut::bandwidth::{
|
||||
IssuedBandwidthCredential,
|
||||
};
|
||||
pub use coconut::utils::{obtain_aggregate_signature, obtain_aggregate_verification_key};
|
||||
pub use error::Error;
|
||||
|
||||
@@ -9,7 +9,7 @@ repository = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
aes = { version = "0.8.1", optional = true }
|
||||
bs58 = "0.4.0"
|
||||
bs58 = { workspace = true }
|
||||
blake3 = { version = "1.3.1", features = ["traits-preview"], optional = true }
|
||||
ctr = { version = "0.9.1", optional = true }
|
||||
digest = { version = "0.10.3", optional = true }
|
||||
|
||||
@@ -14,7 +14,7 @@ bitvec = "1.0.0"
|
||||
# as we need to be able to serialize Gt so that we could create the lookup table for baby-step-giant-step algorithm
|
||||
bls12_381 = { workspace = true, default-features = false, features = ["alloc", "pairings", "experimental", "zeroize"] }
|
||||
nym-contracts-common = { path = "../cosmwasm-smart-contracts/contracts-common", optional = true }
|
||||
bs58 = "0.4"
|
||||
bs58 = { workspace = true }
|
||||
|
||||
|
||||
lazy_static = "1.4.0"
|
||||
|
||||
@@ -11,6 +11,7 @@ license.workspace = true
|
||||
[dependencies]
|
||||
bincode = "1.3.3"
|
||||
bytes = "1.5.0"
|
||||
nym-bin-common = { path = "../bin-common" }
|
||||
nym-sphinx = { path = "../nymsphinx" }
|
||||
rand = "0.8.5"
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
||||
@@ -1,8 +1,32 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
|
||||
pub mod codec;
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
|
||||
pub const CURRENT_VERSION: u8 = 2;
|
||||
// version 3: initial version
|
||||
// version 4: IPv6 support
|
||||
pub const CURRENT_VERSION: u8 = 4;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct IpPair {
|
||||
pub ipv4: Ipv4Addr,
|
||||
pub ipv6: Ipv6Addr,
|
||||
}
|
||||
|
||||
impl IpPair {
|
||||
pub fn new(ipv4: Ipv4Addr, ipv6: Ipv6Addr) -> Self {
|
||||
IpPair { ipv4, ipv6 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for IpPair {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "IPv4: {}, IPV6: {}", self.ipv4, self.ipv6)
|
||||
}
|
||||
}
|
||||
|
||||
fn make_bincode_serializer() -> impl bincode::Options {
|
||||
use bincode::Options;
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
use std::net::IpAddr;
|
||||
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{make_bincode_serializer, CURRENT_VERSION};
|
||||
use crate::{make_bincode_serializer, IpPair, CURRENT_VERSION};
|
||||
|
||||
fn generate_random() -> u64 {
|
||||
use rand::RngCore;
|
||||
@@ -19,10 +17,11 @@ pub struct IpPacketRequest {
|
||||
|
||||
impl IpPacketRequest {
|
||||
pub fn new_static_connect_request(
|
||||
ip: IpAddr,
|
||||
ips: IpPair,
|
||||
reply_to: Recipient,
|
||||
reply_to_hops: Option<u8>,
|
||||
reply_to_avg_mix_delays: Option<f64>,
|
||||
buffer_timeout: Option<u64>,
|
||||
) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
@@ -30,10 +29,11 @@ impl IpPacketRequest {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketRequestData::StaticConnect(StaticConnectRequest {
|
||||
request_id,
|
||||
ip,
|
||||
ips,
|
||||
reply_to,
|
||||
reply_to_hops,
|
||||
reply_to_avg_mix_delays,
|
||||
buffer_timeout,
|
||||
}),
|
||||
},
|
||||
request_id,
|
||||
@@ -44,6 +44,7 @@ impl IpPacketRequest {
|
||||
reply_to: Recipient,
|
||||
reply_to_hops: Option<u8>,
|
||||
reply_to_avg_mix_delays: Option<f64>,
|
||||
buffer_timeout: Option<u64>,
|
||||
) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
@@ -54,6 +55,7 @@ impl IpPacketRequest {
|
||||
reply_to,
|
||||
reply_to_hops,
|
||||
reply_to_avg_mix_delays,
|
||||
buffer_timeout,
|
||||
}),
|
||||
},
|
||||
request_id,
|
||||
@@ -74,10 +76,10 @@ impl IpPacketRequest {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_ip_packet(ip_packet: bytes::Bytes) -> Self {
|
||||
pub fn new_data_request(ip_packets: bytes::Bytes) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketRequestData::Data(DataRequest { ip_packet }),
|
||||
data: IpPacketRequestData::Data(DataRequest { ip_packets }),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +89,8 @@ impl IpPacketRequest {
|
||||
IpPacketRequestData::DynamicConnect(request) => Some(request.request_id),
|
||||
IpPacketRequestData::Disconnect(request) => Some(request.request_id),
|
||||
IpPacketRequestData::Data(_) => None,
|
||||
IpPacketRequestData::Ping(request) => Some(request.request_id),
|
||||
IpPacketRequestData::Health(request) => Some(request.request_id),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +100,8 @@ impl IpPacketRequest {
|
||||
IpPacketRequestData::DynamicConnect(request) => Some(&request.reply_to),
|
||||
IpPacketRequestData::Disconnect(request) => Some(&request.reply_to),
|
||||
IpPacketRequestData::Data(_) => None,
|
||||
IpPacketRequestData::Ping(request) => Some(&request.reply_to),
|
||||
IpPacketRequestData::Health(request) => Some(&request.reply_to),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,35 +125,58 @@ pub enum IpPacketRequestData {
|
||||
DynamicConnect(DynamicConnectRequest),
|
||||
Disconnect(DisconnectRequest),
|
||||
Data(DataRequest),
|
||||
Ping(PingRequest),
|
||||
Health(HealthRequest),
|
||||
}
|
||||
|
||||
// A static connect request is when the client provides the internal IP address it will use on the
|
||||
// ip packet router.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct StaticConnectRequest {
|
||||
pub request_id: u64,
|
||||
pub ip: IpAddr,
|
||||
|
||||
pub ips: IpPair,
|
||||
|
||||
// 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>,
|
||||
|
||||
// The maximum time in milliseconds the IPR should wait when filling up a mix packet
|
||||
// with ip packets.
|
||||
pub buffer_timeout: Option<u64>,
|
||||
}
|
||||
|
||||
// A dynamic connect request is when the client does not provide the internal IP address it will use
|
||||
// on the ip packet router, and instead requests one to be assigned to it.
|
||||
#[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>,
|
||||
|
||||
// The maximum time in milliseconds the IPR should wait when filling up a mix packet
|
||||
// with ip packets.
|
||||
pub buffer_timeout: Option<u64>,
|
||||
}
|
||||
|
||||
// A disconnect request is when the client wants to disconnect from the ip packet router and free
|
||||
// up the allocated IP address.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct DisconnectRequest {
|
||||
pub request_id: u64,
|
||||
@@ -155,14 +184,32 @@ pub struct DisconnectRequest {
|
||||
pub reply_to: Recipient,
|
||||
}
|
||||
|
||||
// A data request is when the client wants to send an IP packet to a destination.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct DataRequest {
|
||||
pub ip_packet: bytes::Bytes,
|
||||
pub ip_packets: bytes::Bytes,
|
||||
}
|
||||
|
||||
// A ping request is when the client wants to check if the ip packet router is still alive.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct PingRequest {
|
||||
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 HealthRequest {
|
||||
pub request_id: u64,
|
||||
// The nym-address the response should be sent back to
|
||||
pub reply_to: Recipient,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn check_size_of_request() {
|
||||
@@ -171,14 +218,15 @@ mod tests {
|
||||
data: IpPacketRequestData::StaticConnect(
|
||||
StaticConnectRequest {
|
||||
request_id: 123,
|
||||
ip: IpAddr::from([10, 0, 0, 1]),
|
||||
ips: IpPair::new(Ipv4Addr::from_str("10.0.0.1").unwrap(), Ipv6Addr::from_str("2001:db8:a160::1").unwrap()),
|
||||
reply_to: Recipient::try_from_base58_string("D1rrpsysCGCYXy9saP8y3kmNpGtJZUXN9SvFoUcqAsM9.9Ssso1ea5NfkbMASdiseDSjTN1fSWda5SgEVjdSN4CvV@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN").unwrap(),
|
||||
reply_to_hops: None,
|
||||
reply_to_avg_mix_delays: None,
|
||||
buffer_timeout: None,
|
||||
},
|
||||
)
|
||||
),
|
||||
};
|
||||
assert_eq!(connect.to_bytes().unwrap().len(), 107);
|
||||
assert_eq!(connect.to_bytes().unwrap().len(), 123);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -186,7 +234,7 @@ mod tests {
|
||||
let data = IpPacketRequest {
|
||||
version: 4,
|
||||
data: IpPacketRequestData::Data(DataRequest {
|
||||
ip_packet: bytes::Bytes::from(vec![1u8; 32]),
|
||||
ip_packets: bytes::Bytes::from(vec![1u8; 32]),
|
||||
}),
|
||||
};
|
||||
assert_eq!(data.to_bytes().unwrap().len(), 35);
|
||||
@@ -197,7 +245,7 @@ mod tests {
|
||||
let data = IpPacketRequest {
|
||||
version: 4,
|
||||
data: IpPacketRequestData::Data(DataRequest {
|
||||
ip_packet: bytes::Bytes::from(vec![1, 2, 4, 2, 5]),
|
||||
ip_packets: bytes::Bytes::from(vec![1, 2, 4, 2, 5]),
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -214,7 +262,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
deserialized.data,
|
||||
IpPacketRequestData::Data(DataRequest {
|
||||
ip_packet: bytes::Bytes::from(vec![1, 2, 4, 2, 5]),
|
||||
ip_packets: bytes::Bytes::from(vec![1, 2, 4, 2, 5]),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
use std::net::IpAddr;
|
||||
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{make_bincode_serializer, CURRENT_VERSION};
|
||||
use crate::{make_bincode_serializer, IpPair, CURRENT_VERSION};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct IpPacketResponse {
|
||||
@@ -38,13 +36,13 @@ impl IpPacketResponse {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_dynamic_connect_success(request_id: u64, reply_to: Recipient, ip: IpAddr) -> Self {
|
||||
pub fn new_dynamic_connect_success(request_id: u64, reply_to: Recipient, ips: IpPair) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketResponseData::DynamicConnect(DynamicConnectResponse {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: DynamicConnectResponseReply::Success(DynamicConnectSuccess { ip }),
|
||||
reply: DynamicConnectResponseReply::Success(DynamicConnectSuccess { ips }),
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -64,6 +62,45 @@ impl IpPacketResponse {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_disconnect_success(request_id: u64, reply_to: Recipient) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketResponseData::Disconnect(DisconnectResponse {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: DisconnectResponseReply::Success,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_disconnect_failure(
|
||||
request_id: u64,
|
||||
reply_to: Recipient,
|
||||
reason: DisconnectFailureReason,
|
||||
) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketResponseData::Disconnect(DisconnectResponse {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: DisconnectResponseReply::Failure(reason),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_unrequested_disconnect(
|
||||
reply_to: Recipient,
|
||||
reason: UnrequestedDisconnectReason,
|
||||
) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
data: IpPacketResponseData::UnrequestedDisconnect(UnrequestedDisconnect {
|
||||
reply_to,
|
||||
reason,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_ip_packet(ip_packet: bytes::Bytes) -> Self {
|
||||
Self {
|
||||
version: CURRENT_VERSION,
|
||||
@@ -106,7 +143,10 @@ impl IpPacketResponse {
|
||||
IpPacketResponseData::StaticConnect(response) => Some(response.request_id),
|
||||
IpPacketResponseData::DynamicConnect(response) => Some(response.request_id),
|
||||
IpPacketResponseData::Disconnect(response) => Some(response.request_id),
|
||||
IpPacketResponseData::UnrequestedDisconnect(_) => None,
|
||||
IpPacketResponseData::Data(_) => None,
|
||||
IpPacketResponseData::Pong(response) => Some(response.request_id),
|
||||
IpPacketResponseData::Health(response) => Some(response.request_id),
|
||||
IpPacketResponseData::Error(response) => Some(response.request_id),
|
||||
}
|
||||
}
|
||||
@@ -116,7 +156,10 @@ impl IpPacketResponse {
|
||||
IpPacketResponseData::StaticConnect(response) => Some(&response.reply_to),
|
||||
IpPacketResponseData::DynamicConnect(response) => Some(&response.reply_to),
|
||||
IpPacketResponseData::Disconnect(response) => Some(&response.reply_to),
|
||||
IpPacketResponseData::UnrequestedDisconnect(response) => Some(&response.reply_to),
|
||||
IpPacketResponseData::Data(_) => None,
|
||||
IpPacketResponseData::Pong(response) => Some(&response.reply_to),
|
||||
IpPacketResponseData::Health(response) => Some(&response.reply_to),
|
||||
IpPacketResponseData::Error(response) => Some(&response.reply_to),
|
||||
}
|
||||
}
|
||||
@@ -137,10 +180,28 @@ impl IpPacketResponse {
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum IpPacketResponseData {
|
||||
// Response for a static connect request
|
||||
StaticConnect(StaticConnectResponse),
|
||||
|
||||
// Response for a dynamic connect request
|
||||
DynamicConnect(DynamicConnectResponse),
|
||||
|
||||
// Response for a disconnect initiqated by the client
|
||||
Disconnect(DisconnectResponse),
|
||||
|
||||
// Message from the server that the client got disconnected without the client initiating it
|
||||
UnrequestedDisconnect(UnrequestedDisconnect),
|
||||
|
||||
// Response to a data request
|
||||
Data(DataResponse),
|
||||
|
||||
// Response to ping request
|
||||
Pong(PongResponse),
|
||||
|
||||
// Response for a health request
|
||||
Health(HealthResponse),
|
||||
|
||||
// Error response
|
||||
Error(ErrorResponse),
|
||||
}
|
||||
|
||||
@@ -200,7 +261,7 @@ impl DynamicConnectResponseReply {
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DynamicConnectSuccess {
|
||||
pub ip: IpAddr,
|
||||
pub ips: IpPair,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
|
||||
@@ -234,11 +295,48 @@ pub enum DisconnectFailureReason {
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct UnrequestedDisconnect {
|
||||
pub reply_to: Recipient,
|
||||
pub reason: UnrequestedDisconnectReason,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
|
||||
pub enum UnrequestedDisconnectReason {
|
||||
#[error("client mixnet traffic timeout")]
|
||||
ClientMixnetTrafficTimeout,
|
||||
#[error("client tun traffic timeout")]
|
||||
ClientTunTrafficTimeout,
|
||||
#[error("{0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DataResponse {
|
||||
pub ip_packet: bytes::Bytes,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct PongResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct HealthResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: HealthResponseReply,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct HealthResponseReply {
|
||||
// Return the binary build information of the IPR
|
||||
pub build_info: nym_bin_common::build_information::BinaryBuildInformationOwned,
|
||||
// Return if the IPR has performed a successful routing test.
|
||||
pub routable: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct ErrorResponse {
|
||||
pub request_id: u64,
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "nym-id"
|
||||
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]
|
||||
thiserror.workspace = true
|
||||
time.workspace = true
|
||||
tracing.workspace = true
|
||||
zeroize.workspace = true
|
||||
|
||||
nym-credential-storage = { path = "../credential-storage" }
|
||||
nym-credentials = { path = "../credentials" }
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::error::Error;
|
||||
use thiserror::Error;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum NymIdError {
|
||||
#[error("failed to deserialize provided credential: {source}")]
|
||||
CredentialDeserializationFailure { source: nym_credentials::Error },
|
||||
|
||||
#[error("attempted to import an expired credential (it expired on {expiration})")]
|
||||
ExpiredCredentialImport { expiration: OffsetDateTime },
|
||||
|
||||
#[error("failed to store credential in the provided store: {source}")]
|
||||
StorageError {
|
||||
source: Box<dyn Error + Send + Sync>,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::NymIdError;
|
||||
use nym_credential_storage::models::StorableIssuedCredential;
|
||||
use nym_credential_storage::storage::Storage;
|
||||
use nym_credentials::coconut::bandwidth::issued::BandwidthCredentialIssuedDataVariant;
|
||||
use nym_credentials::IssuedBandwidthCredential;
|
||||
use tracing::{debug, warn};
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
pub async fn import_credential<S>(
|
||||
credentials_store: S,
|
||||
raw_credential: Vec<u8>,
|
||||
credential_version: impl Into<Option<u8>>,
|
||||
) -> Result<(), NymIdError>
|
||||
where
|
||||
S: Storage,
|
||||
<S as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
let raw_credential = Zeroizing::new(raw_credential);
|
||||
|
||||
// note: the type itself implements ZeroizeOnDrop
|
||||
let credential = IssuedBandwidthCredential::try_unpack(&raw_credential, credential_version)
|
||||
.map_err(|source| NymIdError::CredentialDeserializationFailure { source })?;
|
||||
|
||||
debug!(
|
||||
"attempting to import credential of type {}",
|
||||
credential.typ()
|
||||
);
|
||||
|
||||
match credential.variant_data() {
|
||||
BandwidthCredentialIssuedDataVariant::Voucher(voucher_info) => {
|
||||
debug!("with value of {}", voucher_info.value())
|
||||
}
|
||||
BandwidthCredentialIssuedDataVariant::FreePass(freepass_info) => {
|
||||
debug!("with expiry at {}", freepass_info.expiry_date());
|
||||
if freepass_info.expired() {
|
||||
warn!("the free pass has already expired!");
|
||||
|
||||
// technically we can import it, but the gateway will just reject it so what's the point
|
||||
return Err(NymIdError::ExpiredCredentialImport {
|
||||
expiration: freepass_info.expiry_date(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY:
|
||||
// for the epoch to run over u32::MAX, we'd have to advance it for few centuries every block...
|
||||
// the alternative is a very particularly malformed serialized data, but at that point blowing up is the right call
|
||||
// because we can't rely on it anyway
|
||||
#[allow(clippy::expect_used)]
|
||||
let storable = StorableIssuedCredential {
|
||||
serialization_revision: credential.current_serialization_revision(),
|
||||
credential_data: &raw_credential,
|
||||
credential_type: credential.typ().to_string(),
|
||||
epoch_id: credential
|
||||
.epoch_id()
|
||||
.try_into()
|
||||
.expect("our epoch is has run over u32::MAX!"),
|
||||
};
|
||||
|
||||
credentials_store
|
||||
.insert_issued_credential(storable)
|
||||
.await
|
||||
.map_err(|source| NymIdError::StorageError {
|
||||
source: Box::new(source),
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![warn(clippy::expect_used)]
|
||||
#![warn(clippy::unwrap_used)]
|
||||
|
||||
pub mod error;
|
||||
pub mod import_credential;
|
||||
|
||||
pub use error::NymIdError;
|
||||
pub use import_credential::import_credential;
|
||||
@@ -15,7 +15,7 @@ rand = "0.8"
|
||||
thiserror = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_derive = "1.0"
|
||||
bs58 = "0.4.0"
|
||||
bs58 = { workspace = true }
|
||||
sha2 = "0.9"
|
||||
zeroize = { workspace = true, optional = true }
|
||||
|
||||
|
||||
@@ -24,6 +24,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::VerifyCredentialRequest;
|
||||
pub use scheme::BlindedSignature;
|
||||
pub use scheme::Signature;
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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 VerifyCredentialRequest {
|
||||
// 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 VerifyCredentialRequest {
|
||||
),
|
||||
)?;
|
||||
|
||||
// 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 VerifyCredentialRequest {
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -111,11 +105,12 @@ impl VerifyCredentialRequest {
|
||||
VerifyCredentialRequest::try_from(bytes)
|
||||
}
|
||||
|
||||
pub fn blinded_serial_number(&self) -> BlindedSerialNumber {
|
||||
self.blinded_serial_number
|
||||
}
|
||||
|
||||
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 +193,7 @@ pub fn prove_bandwidth_credential(
|
||||
|
||||
Ok(VerifyCredentialRequest {
|
||||
blinded_message,
|
||||
blinded_serial_number,
|
||||
blinded_serial_number: blinded_serial_number.into(),
|
||||
credential: signature_prime,
|
||||
pi_v,
|
||||
})
|
||||
|
||||
@@ -9,7 +9,7 @@ repository = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
bs58 = "0.4"
|
||||
bs58 = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros
|
||||
tendermint.workspace = true
|
||||
tendermint-rpc = { workspace = true, features = ["websocket-client", "http-client"] }
|
||||
thiserror.workspace = true
|
||||
time = { workspace = true }
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
tokio-stream = "0.1.14"
|
||||
tokio-util = { version = "0.7.10", features = ["rt"]}
|
||||
|
||||
@@ -84,7 +84,7 @@ pub enum ScraperError {
|
||||
EmptyBlockData { query: String },
|
||||
|
||||
#[error("reached maximum number of allowed errors for subscription events")]
|
||||
MaximumSubscriptionFailures,
|
||||
MaximumWebSocketFailures,
|
||||
|
||||
#[error("failed to begin storage tx: {source}")]
|
||||
StorageTxBeginFailure {
|
||||
|
||||
@@ -6,11 +6,10 @@ use crate::block_requester::BlockRequester;
|
||||
use crate::error::ScraperError;
|
||||
use crate::modules::{BlockModule, MsgModule, TxModule};
|
||||
use crate::rpc_client::RpcClient;
|
||||
use crate::scraper::subscriber::{run_websocket_driver, ChainSubscriber};
|
||||
use crate::scraper::subscriber::ChainSubscriber;
|
||||
use crate::storage::ScraperStorage;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use tendermint_rpc::WebSocketClientDriver;
|
||||
use tokio::sync::mpsc::{channel, unbounded_channel};
|
||||
use tokio::sync::Notify;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
@@ -67,20 +66,15 @@ impl NyxdScraperBuilder {
|
||||
block_processor.set_tx_modules(self.tx_modules);
|
||||
block_processor.set_msg_modules(self.msg_modules);
|
||||
|
||||
let mut chain_subscriber = ChainSubscriber::new(
|
||||
let chain_subscriber = ChainSubscriber::new(
|
||||
&scraper.config.websocket_url,
|
||||
scraper.cancel_token.clone(),
|
||||
scraper.task_tracker.clone(),
|
||||
processing_tx,
|
||||
)
|
||||
.await?;
|
||||
let ws_driver = chain_subscriber.ws_driver();
|
||||
|
||||
scraper.start_tasks(
|
||||
block_requester,
|
||||
block_processor,
|
||||
chain_subscriber,
|
||||
ws_driver,
|
||||
);
|
||||
scraper.start_tasks(block_requester, block_processor, chain_subscriber);
|
||||
|
||||
Ok(scraper)
|
||||
}
|
||||
@@ -141,7 +135,6 @@ impl NyxdScraper {
|
||||
mut block_requester: BlockRequester,
|
||||
mut block_processor: BlockProcessor,
|
||||
mut chain_subscriber: ChainSubscriber,
|
||||
ws_driver: WebSocketClientDriver,
|
||||
) {
|
||||
self.task_tracker
|
||||
.spawn(async move { block_requester.run().await });
|
||||
@@ -149,8 +142,7 @@ impl NyxdScraper {
|
||||
.spawn(async move { block_processor.run().await });
|
||||
self.task_tracker
|
||||
.spawn(async move { chain_subscriber.run().await });
|
||||
self.task_tracker
|
||||
.spawn(run_websocket_driver(ws_driver, self.cancel_token.clone()));
|
||||
|
||||
self.task_tracker.close();
|
||||
}
|
||||
|
||||
@@ -176,21 +168,16 @@ impl NyxdScraper {
|
||||
rpc_client,
|
||||
)
|
||||
.await?;
|
||||
let mut chain_subscriber = ChainSubscriber::new(
|
||||
let chain_subscriber = ChainSubscriber::new(
|
||||
&self.config.websocket_url,
|
||||
self.cancel_token.clone(),
|
||||
self.task_tracker.clone(),
|
||||
processing_tx,
|
||||
)
|
||||
.await?;
|
||||
let ws_driver = chain_subscriber.ws_driver();
|
||||
|
||||
// spawn them
|
||||
self.start_tasks(
|
||||
block_requester,
|
||||
block_processor,
|
||||
chain_subscriber,
|
||||
ws_driver,
|
||||
);
|
||||
self.start_tasks(block_requester, block_processor, chain_subscriber);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -201,10 +188,18 @@ impl NyxdScraper {
|
||||
}
|
||||
|
||||
pub async fn stop(self) {
|
||||
info!("stopping the chain scrapper");
|
||||
info!("stopping the chain scraper");
|
||||
assert!(self.task_tracker.is_closed());
|
||||
|
||||
self.cancel_token.cancel();
|
||||
self.task_tracker.wait().await
|
||||
}
|
||||
|
||||
pub fn cancel_token(&self) -> CancellationToken {
|
||||
self.cancel_token.clone()
|
||||
}
|
||||
|
||||
pub fn is_cancelled(&self) -> bool {
|
||||
self.cancel_token.is_cancelled()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,18 +6,25 @@ use crate::error::ScraperError;
|
||||
use tendermint_rpc::event::Event;
|
||||
use tendermint_rpc::query::EventType;
|
||||
use tendermint_rpc::{SubscriptionClient, WebSocketClient, WebSocketClientDriver};
|
||||
use time::{Duration, OffsetDateTime};
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
use tokio_stream::StreamExt;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tokio_util::task::TaskTracker;
|
||||
use tracing::{error, info, warn};
|
||||
use url::Url;
|
||||
|
||||
const MAX_FAILURES: usize = 10;
|
||||
const MAX_RECONNECTION_ATTEMPTS: usize = 8;
|
||||
const SOCKET_FAILURE_RESET: Duration = Duration::hours(2);
|
||||
|
||||
pub struct ChainSubscriber {
|
||||
cancel: CancellationToken,
|
||||
task_tracker: TaskTracker,
|
||||
|
||||
block_sender: UnboundedSender<BlockToProcess>,
|
||||
|
||||
websocket_endpoint: Url,
|
||||
websocket_client: WebSocketClient,
|
||||
websocket_driver: Option<WebSocketClientDriver>,
|
||||
}
|
||||
@@ -26,6 +33,7 @@ impl ChainSubscriber {
|
||||
pub async fn new(
|
||||
websocket_endpoint: &Url,
|
||||
cancel: CancellationToken,
|
||||
task_tracker: TaskTracker,
|
||||
block_sender: UnboundedSender<BlockToProcess>,
|
||||
) -> Result<Self, ScraperError> {
|
||||
// sure, we could have just used websocket client entirely, but let's keep the logic for
|
||||
@@ -39,7 +47,9 @@ impl ChainSubscriber {
|
||||
|
||||
Ok(ChainSubscriber {
|
||||
cancel,
|
||||
task_tracker,
|
||||
block_sender,
|
||||
websocket_endpoint: websocket_endpoint.clone(),
|
||||
websocket_client: client,
|
||||
websocket_driver: Some(driver),
|
||||
})
|
||||
@@ -53,8 +63,48 @@ impl ChainSubscriber {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn run(&mut self) -> Result<(), ScraperError> {
|
||||
let _drop_guard = self.cancel.clone().drop_guard();
|
||||
async fn remake_connection(&mut self) -> Result<(), ScraperError> {
|
||||
info!(
|
||||
"attempting to reestablish connection to {}",
|
||||
self.websocket_endpoint
|
||||
);
|
||||
|
||||
let (client, driver) = WebSocketClient::new(self.websocket_endpoint.as_str())
|
||||
.await
|
||||
.map_err(|source| ScraperError::WebSocketConnectionFailure {
|
||||
url: self.websocket_endpoint.to_string(),
|
||||
source,
|
||||
})?;
|
||||
self.websocket_client = client;
|
||||
self.websocket_driver = Some(driver);
|
||||
|
||||
info!(
|
||||
"managed to reestablish the websocket connection to {}",
|
||||
self.websocket_endpoint
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns whether the method exited due to the cancellation
|
||||
async fn run_chain_subscription(&mut self) -> Result<bool, ScraperError> {
|
||||
let Some(ws_driver) = self.websocket_driver.take() else {
|
||||
error!("the websocket driver hasn't been created - we probably failed to establish the connection");
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
let driver_cancel = CancellationToken::new();
|
||||
let _driver_guard = driver_cancel.clone().drop_guard();
|
||||
|
||||
// spawn the websocket driver task
|
||||
let driver_handle = {
|
||||
self.task_tracker.reopen();
|
||||
let handle = self
|
||||
.task_tracker
|
||||
.spawn(run_websocket_driver(ws_driver, driver_cancel));
|
||||
self.task_tracker.close();
|
||||
handle
|
||||
};
|
||||
tokio::pin!(driver_handle);
|
||||
|
||||
info!("creating chain subscription");
|
||||
let mut subs = self
|
||||
@@ -70,12 +120,17 @@ impl ChainSubscriber {
|
||||
tokio::select! {
|
||||
_ = self.cancel.cancelled() => {
|
||||
info!("received cancellation token");
|
||||
break
|
||||
// note: `_driver_guard` will get dropped here thus causing cancellation of the driver task
|
||||
return Ok(true)
|
||||
}
|
||||
_ = &mut driver_handle => {
|
||||
error!("our websocket driver has finished execution");
|
||||
return Ok(self.cancel.is_cancelled())
|
||||
}
|
||||
maybe_event = subs.next() => {
|
||||
let Some(maybe_event) = maybe_event else {
|
||||
warn!("stopped receiving new events");
|
||||
break;
|
||||
return Ok(false)
|
||||
};
|
||||
match maybe_event {
|
||||
Ok(event) => {
|
||||
@@ -92,38 +147,95 @@ impl ChainSubscriber {
|
||||
}
|
||||
}
|
||||
if failures >= MAX_FAILURES {
|
||||
// note: the drop_guard will get dropped and thus cause a shutdown
|
||||
return Err(ScraperError::MaximumSubscriptionFailures);
|
||||
return Ok(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn ws_driver(&mut self) -> WebSocketClientDriver {
|
||||
#[allow(clippy::expect_used)]
|
||||
self.websocket_driver
|
||||
.take()
|
||||
.expect("websocket driver has already been started!")
|
||||
async fn websocket_backoff(&mut self, failure_count: usize) -> bool {
|
||||
const MINIMUM_WAIT_MS: u64 = 10_000;
|
||||
const INCREMENTAL_WAIT_MS: u64 = 30_000;
|
||||
|
||||
let backoff_duration_ms = MINIMUM_WAIT_MS + INCREMENTAL_WAIT_MS * failure_count as u64;
|
||||
info!("going to wait {backoff_duration_ms} ms before re-attempting the reconnection");
|
||||
|
||||
tokio::select! {
|
||||
_ = self.cancel.cancelled() => {
|
||||
info!("received cancellation token");
|
||||
true
|
||||
}
|
||||
_ = tokio::time::sleep(std::time::Duration::from_millis(backoff_duration_ms)) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn run(&mut self) -> Result<(), ScraperError> {
|
||||
let _drop_guard = self.cancel.clone().drop_guard();
|
||||
let mut socket_failures = 0;
|
||||
let mut last_failure = OffsetDateTime::now_utc();
|
||||
|
||||
loop {
|
||||
if self.cancel.is_cancelled() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match self.run_chain_subscription().await {
|
||||
Ok(cancelled) => {
|
||||
if cancelled {
|
||||
// we're in the middle of a shutdown
|
||||
return Ok(());
|
||||
}
|
||||
socket_failures += 1;
|
||||
}
|
||||
Err(err) => {
|
||||
error!("failed to create chain subscription: {err}");
|
||||
socket_failures += 1;
|
||||
}
|
||||
}
|
||||
|
||||
warn!("current socket failure count: {socket_failures}. the last failure was at {last_failure}");
|
||||
|
||||
let now = OffsetDateTime::now_utc();
|
||||
|
||||
// if it's been a while since the last failure, reset the count
|
||||
if now - last_failure > SOCKET_FAILURE_RESET {
|
||||
warn!("resetting the failure count to 1");
|
||||
socket_failures = 1;
|
||||
}
|
||||
last_failure = now;
|
||||
|
||||
if socket_failures >= MAX_RECONNECTION_ATTEMPTS {
|
||||
error!("reached the maximum allowed failure count");
|
||||
return Err(ScraperError::MaximumWebSocketFailures);
|
||||
}
|
||||
|
||||
// BACKOFF
|
||||
let cancelled = self.websocket_backoff(socket_failures).await;
|
||||
if cancelled {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Err(err) = self.remake_connection().await {
|
||||
error!("failed to re-establish the websocket connection: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_websocket_driver(driver: WebSocketClientDriver, cancel: CancellationToken) {
|
||||
pub async fn run_websocket_driver(driver: WebSocketClientDriver, driver_cancel: CancellationToken) {
|
||||
info!("starting websocket driver");
|
||||
tokio::select! {
|
||||
_ = cancel.cancelled() => {
|
||||
_ = driver_cancel.cancelled() => {
|
||||
info!("received cancellation token")
|
||||
}
|
||||
res = driver.run() => {
|
||||
match res {
|
||||
Ok(_) => info!("our websocket driver has finished execution"),
|
||||
Err(err) => {
|
||||
// TODO: in the future just attempt to reconnect
|
||||
error!("our websocket driver has errored out: {err}")
|
||||
error!("our websocket driver has errored out: {err}");
|
||||
}
|
||||
}
|
||||
cancel.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ documentation = { workspace = true }
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bs58 = "0.4"
|
||||
bs58 = { workspace = true }
|
||||
log = { workspace = true }
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::net::Ipv6Addr;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::{IpAddr, Ipv4Addr},
|
||||
@@ -19,6 +20,12 @@ const TUN_WRITE_TIMEOUT_MS: u64 = 1000;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum TunDeviceError {
|
||||
#[error("{0}")]
|
||||
IO(#[from] std::io::Error),
|
||||
|
||||
#[error("{0}")]
|
||||
TokioTun(#[from] tokio_tun::Error),
|
||||
|
||||
#[error("timeout writing to tun device, dropping packet")]
|
||||
TunWriteTimeout,
|
||||
|
||||
@@ -44,14 +51,18 @@ pub enum TunDeviceError {
|
||||
FailedToLockPeer,
|
||||
}
|
||||
|
||||
fn setup_tokio_tun_device(name: &str, address: Ipv4Addr, netmask: Ipv4Addr) -> tokio_tun::Tun {
|
||||
fn setup_tokio_tun_device(
|
||||
name: &str,
|
||||
address: Ipv4Addr,
|
||||
netmask: Ipv4Addr,
|
||||
) -> Result<tokio_tun::Tun, TunDeviceError> {
|
||||
log::info!("Creating TUN device with: address={address}, netmask={netmask}");
|
||||
// Read MTU size from env variable NYM_MTU_SIZE, else default to 1420.
|
||||
let mtu = std::env::var("NYM_MTU_SIZE")
|
||||
.map(|mtu| mtu.parse().expect("NYM_MTU_SIZE must be a valid integer"))
|
||||
.unwrap_or(1420);
|
||||
log::info!("Using MTU size: {mtu}");
|
||||
tokio_tun::Tun::builder()
|
||||
Ok(tokio_tun::Tun::builder()
|
||||
.name(name)
|
||||
.tap(false)
|
||||
.packet_info(false)
|
||||
@@ -59,8 +70,7 @@ fn setup_tokio_tun_device(name: &str, address: Ipv4Addr, netmask: Ipv4Addr) -> t
|
||||
.up()
|
||||
.address(address)
|
||||
.netmask(netmask)
|
||||
.try_build()
|
||||
.expect("Failed to setup tun device, do you have permission?")
|
||||
.try_build()?)
|
||||
}
|
||||
|
||||
pub struct TunDevice {
|
||||
@@ -103,16 +113,18 @@ pub struct NatInner {
|
||||
|
||||
pub struct TunDeviceConfig {
|
||||
pub base_name: String,
|
||||
pub ip: Ipv4Addr,
|
||||
pub netmask: Ipv4Addr,
|
||||
pub ipv4: Ipv4Addr,
|
||||
pub netmaskv4: Ipv4Addr,
|
||||
pub ipv6: Ipv6Addr,
|
||||
pub netmaskv6: String,
|
||||
}
|
||||
|
||||
impl TunDevice {
|
||||
pub fn new(
|
||||
routing_mode: RoutingMode,
|
||||
config: TunDeviceConfig,
|
||||
) -> (Self, TunTaskTx, TunTaskResponseRx) {
|
||||
let tun = Self::new_device_only(config);
|
||||
) -> Result<(Self, TunTaskTx, TunTaskResponseRx), TunDeviceError> {
|
||||
let tun = Self::new_device_only(config)?;
|
||||
|
||||
// Channels to communicate with the other tasks
|
||||
let (tun_task_tx, tun_task_rx) = tun_task_channel();
|
||||
@@ -125,20 +137,32 @@ impl TunDevice {
|
||||
routing_mode,
|
||||
};
|
||||
|
||||
(tun_device, tun_task_tx, tun_task_response_rx)
|
||||
Ok((tun_device, tun_task_tx, tun_task_response_rx))
|
||||
}
|
||||
|
||||
pub fn new_device_only(config: TunDeviceConfig) -> tokio_tun::Tun {
|
||||
pub fn new_device_only(config: TunDeviceConfig) -> Result<tokio_tun::Tun, TunDeviceError> {
|
||||
let TunDeviceConfig {
|
||||
base_name,
|
||||
ip,
|
||||
netmask,
|
||||
ipv4,
|
||||
netmaskv4,
|
||||
ipv6,
|
||||
netmaskv6,
|
||||
} = config;
|
||||
let name = format!("{base_name}%d");
|
||||
|
||||
let tun = setup_tokio_tun_device(&name, ip, netmask);
|
||||
let tun = setup_tokio_tun_device(&name, ipv4, netmaskv4)?;
|
||||
log::info!("Created TUN device: {}", tun.name());
|
||||
tun
|
||||
std::process::Command::new("ip")
|
||||
.args([
|
||||
"-6",
|
||||
"addr",
|
||||
"add",
|
||||
&format!("{}/{}", ipv6, netmaskv6),
|
||||
"dev",
|
||||
&tun.name(),
|
||||
])
|
||||
.output()?;
|
||||
Ok(tun)
|
||||
}
|
||||
|
||||
// Send outbound packets out on the wild internet
|
||||
|
||||
Generated
+34
-23
@@ -116,6 +116,15 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3"
|
||||
|
||||
[[package]]
|
||||
name = "bs58"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.12.1"
|
||||
@@ -178,6 +187,7 @@ dependencies = [
|
||||
name = "coconut-test"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bs58 0.4.0",
|
||||
"cosmwasm-std",
|
||||
"cosmwasm-storage",
|
||||
"cw-controllers",
|
||||
@@ -194,8 +204,10 @@ dependencies = [
|
||||
"nym-coconut-dkg-common",
|
||||
"nym-group-contract-common",
|
||||
"nym-multisig-contract-common",
|
||||
"rand_chacha 0.2.2",
|
||||
"schemars",
|
||||
"serde",
|
||||
"subtle-encoding",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
@@ -819,7 +831,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.32",
|
||||
"syn 2.0.49",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1225,7 +1237,6 @@ dependencies = [
|
||||
"cw4-group",
|
||||
"nym-coconut-dkg-common",
|
||||
"nym-group-contract-common",
|
||||
"rusty-fork",
|
||||
"semver",
|
||||
"serde",
|
||||
"thiserror",
|
||||
@@ -1248,7 +1259,7 @@ dependencies = [
|
||||
name = "nym-contracts-common"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"bs58 0.5.0",
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"schemars",
|
||||
@@ -1260,7 +1271,7 @@ dependencies = [
|
||||
name = "nym-crypto"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"bs58 0.5.0",
|
||||
"ed25519-dalek",
|
||||
"nym-pemstore",
|
||||
"nym-sphinx-types",
|
||||
@@ -1316,7 +1327,7 @@ dependencies = [
|
||||
name = "nym-mixnet-contract"
|
||||
version = "1.5.1"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"bs58 0.4.0",
|
||||
"cosmwasm-derive",
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
@@ -1339,7 +1350,7 @@ dependencies = [
|
||||
name = "nym-mixnet-contract-common"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"bs58 0.4.0",
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cw2",
|
||||
@@ -1374,7 +1385,7 @@ name = "nym-name-service"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bs58",
|
||||
"bs58 0.4.0",
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cw-controllers",
|
||||
@@ -1421,7 +1432,7 @@ name = "nym-service-provider-directory"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bs58",
|
||||
"bs58 0.4.0",
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cw-controllers",
|
||||
@@ -1608,9 +1619,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.63"
|
||||
version = "1.0.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb"
|
||||
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -1646,9 +1657,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.30"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5907a1b7c277254a8b15170f6e7c97cfa60ee7872a3217663bb81151e48184bb"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -1888,9 +1899,9 @@ checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.190"
|
||||
version = "1.0.196"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7"
|
||||
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -1906,13 +1917,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.190"
|
||||
version = "1.0.196"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3"
|
||||
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.32",
|
||||
"syn 2.0.49",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1945,7 +1956,7 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.32",
|
||||
"syn 2.0.49",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2000,7 +2011,7 @@ dependencies = [
|
||||
"aes",
|
||||
"arrayref",
|
||||
"blake2",
|
||||
"bs58",
|
||||
"bs58 0.4.0",
|
||||
"byteorder",
|
||||
"chacha",
|
||||
"curve25519-dalek",
|
||||
@@ -2059,9 +2070,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.32"
|
||||
version = "2.0.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2"
|
||||
checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2098,7 +2109,7 @@ checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.32",
|
||||
"syn 2.0.49",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2447,5 +2458,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.32",
|
||||
"syn 2.0.49",
|
||||
]
|
||||
|
||||
@@ -34,6 +34,7 @@ incremental = false
|
||||
overflow-checks = true
|
||||
|
||||
[workspace.dependencies]
|
||||
bs58 = "0.4.0"
|
||||
cosmwasm-crypto = "=1.3.0"
|
||||
cosmwasm-derive = "=1.3.0"
|
||||
cosmwasm-schema = "=1.3.0"
|
||||
@@ -49,5 +50,6 @@ cw3-fixed-multisig = "=1.1.0"
|
||||
cw4 = "=1.1.0"
|
||||
cw20 = "=1.1.0"
|
||||
semver = "1.0.21"
|
||||
serde = "1.0.196"
|
||||
|
||||
thiserror = "1.0.48"
|
||||
|
||||
@@ -30,7 +30,6 @@ thiserror = { workspace = true }
|
||||
cw-multi-test = { workspace = true }
|
||||
cw4-group = { path = "../multisig/cw4-group" }
|
||||
nym-group-contract-common = { path = "../../common/cosmwasm-smart-contracts/group-contract" }
|
||||
rusty-fork = "0.3"
|
||||
|
||||
[features]
|
||||
schema-gen = ["nym-coconut-dkg-common/schema", "cosmwasm-schema"]
|
||||
|
||||
@@ -180,15 +180,11 @@
|
||||
"commit_dealings_chunk": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"chunk",
|
||||
"resharing"
|
||||
"chunk"
|
||||
],
|
||||
"properties": {
|
||||
"chunk": {
|
||||
"$ref": "#/definitions/PartialContractDealing"
|
||||
},
|
||||
"resharing": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@@ -249,10 +245,10 @@
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"surpassed_threshold"
|
||||
"advance_epoch_state"
|
||||
],
|
||||
"properties": {
|
||||
"surpassed_threshold": {
|
||||
"advance_epoch_state": {
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -262,10 +258,23 @@
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"advance_epoch_state"
|
||||
"trigger_reset"
|
||||
],
|
||||
"properties": {
|
||||
"advance_epoch_state": {
|
||||
"trigger_reset": {
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"trigger_resharing"
|
||||
],
|
||||
"properties": {
|
||||
"trigger_resharing": {
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -368,16 +377,45 @@
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"get_initial_dealers"
|
||||
"can_advance_state"
|
||||
],
|
||||
"properties": {
|
||||
"get_initial_dealers": {
|
||||
"can_advance_state": {
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"get_registered_dealer"
|
||||
],
|
||||
"properties": {
|
||||
"get_registered_dealer": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"dealer_address"
|
||||
],
|
||||
"properties": {
|
||||
"dealer_address": {
|
||||
"type": "string"
|
||||
},
|
||||
"epoch_id": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -431,10 +469,10 @@
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"get_past_dealers"
|
||||
"get_dealer_indices"
|
||||
],
|
||||
"properties": {
|
||||
"get_past_dealers": {
|
||||
"get_dealer_indices": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"limit": {
|
||||
@@ -716,6 +754,215 @@
|
||||
},
|
||||
"sudo": null,
|
||||
"responses": {
|
||||
"can_advance_state": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "StateAdvanceResponse",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"current_state",
|
||||
"is_complete",
|
||||
"progress",
|
||||
"reached_deadline"
|
||||
],
|
||||
"properties": {
|
||||
"current_state": {
|
||||
"$ref": "#/definitions/EpochState"
|
||||
},
|
||||
"deadline": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Timestamp"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"is_complete": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"progress": {
|
||||
"$ref": "#/definitions/StateProgress"
|
||||
},
|
||||
"reached_deadline": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"definitions": {
|
||||
"EpochState": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"waiting_initialisation",
|
||||
"in_progress"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"public_key_submission"
|
||||
],
|
||||
"properties": {
|
||||
"public_key_submission": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"resharing"
|
||||
],
|
||||
"properties": {
|
||||
"resharing": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"dealing_exchange"
|
||||
],
|
||||
"properties": {
|
||||
"dealing_exchange": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"resharing"
|
||||
],
|
||||
"properties": {
|
||||
"resharing": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"verification_key_submission"
|
||||
],
|
||||
"properties": {
|
||||
"verification_key_submission": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"resharing"
|
||||
],
|
||||
"properties": {
|
||||
"resharing": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"verification_key_validation"
|
||||
],
|
||||
"properties": {
|
||||
"verification_key_validation": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"resharing"
|
||||
],
|
||||
"properties": {
|
||||
"resharing": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"verification_key_finalization"
|
||||
],
|
||||
"properties": {
|
||||
"verification_key_finalization": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"resharing"
|
||||
],
|
||||
"properties": {
|
||||
"resharing": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"StateProgress": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"registered_dealers",
|
||||
"registered_resharing_dealers",
|
||||
"submitted_dealings",
|
||||
"submitted_key_shares",
|
||||
"verified_keys"
|
||||
],
|
||||
"properties": {
|
||||
"registered_dealers": {
|
||||
"description": "Counts the number of dealers that have registered in this epoch.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"registered_resharing_dealers": {
|
||||
"description": "Counts the number of resharing dealers that have registered in this epoch. This field is only populated during a resharing exchange. It is always <= registered_dealers.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"submitted_dealings": {
|
||||
"description": "Counts the number of fully received dealings (i.e. full chunks) from all the allowed dealers.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"submitted_key_shares": {
|
||||
"description": "Counts the number of submitted verification key shared from the dealers.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"verified_keys": {
|
||||
"description": "Counts the number of verified key shares.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Timestamp": {
|
||||
"description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Uint64"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Uint64": {
|
||||
"description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"get_c_w2_contract_version": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "ContractVersion",
|
||||
@@ -813,15 +1060,11 @@
|
||||
"required": [
|
||||
"epoch_id",
|
||||
"state",
|
||||
"state_progress",
|
||||
"time_configuration"
|
||||
],
|
||||
"properties": {
|
||||
"epoch_id": {
|
||||
"type": "integer",
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"finish_timestamp": {
|
||||
"deadline": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Timestamp"
|
||||
@@ -831,9 +1074,17 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"epoch_id": {
|
||||
"type": "integer",
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"state": {
|
||||
"$ref": "#/definitions/EpochState"
|
||||
},
|
||||
"state_progress": {
|
||||
"$ref": "#/definitions/StateProgress"
|
||||
},
|
||||
"time_configuration": {
|
||||
"$ref": "#/definitions/TimeConfiguration"
|
||||
}
|
||||
@@ -956,6 +1207,49 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"StateProgress": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"registered_dealers",
|
||||
"registered_resharing_dealers",
|
||||
"submitted_dealings",
|
||||
"submitted_key_shares",
|
||||
"verified_keys"
|
||||
],
|
||||
"properties": {
|
||||
"registered_dealers": {
|
||||
"description": "Counts the number of dealers that have registered in this epoch.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"registered_resharing_dealers": {
|
||||
"description": "Counts the number of resharing dealers that have registered in this epoch. This field is only populated during a resharing exchange. It is always <= registered_dealers.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"submitted_dealings": {
|
||||
"description": "Counts the number of fully received dealings (i.e. full chunks) from all the allowed dealers.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"submitted_key_shares": {
|
||||
"description": "Counts the number of submitted verification key shared from the dealers.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"verified_keys": {
|
||||
"description": "Counts the number of verified key shares.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"TimeConfiguration": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -1154,15 +1448,109 @@
|
||||
"additionalProperties": false
|
||||
},
|
||||
"DealerType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"current",
|
||||
"past",
|
||||
"unknown"
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"unknown"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"current"
|
||||
],
|
||||
"properties": {
|
||||
"current": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"assigned_index"
|
||||
],
|
||||
"properties": {
|
||||
"assigned_index": {
|
||||
"type": "integer",
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"past"
|
||||
],
|
||||
"properties": {
|
||||
"past": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"assigned_index"
|
||||
],
|
||||
"properties": {
|
||||
"assigned_index": {
|
||||
"type": "integer",
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"get_dealer_indices": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "PagedDealerIndexResponse",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"indices"
|
||||
],
|
||||
"properties": {
|
||||
"indices": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"$ref": "#/definitions/Addr"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
}
|
||||
],
|
||||
"maxItems": 2,
|
||||
"minItems": 2
|
||||
}
|
||||
},
|
||||
"start_next_after": {
|
||||
"description": "Field indicating paging information for the following queries if the caller wishes to get further entries.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Addr"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"definitions": {
|
||||
"Addr": {
|
||||
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"get_dealing_chunk": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "DealingChunkResponse",
|
||||
@@ -1455,70 +1843,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"get_initial_dealers": {
|
||||
"get_registered_dealer": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Nullable_InitialReplacementData",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/InitialReplacementData"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"definitions": {
|
||||
"Addr": {
|
||||
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
|
||||
"type": "string"
|
||||
},
|
||||
"InitialReplacementData": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"initial_dealers",
|
||||
"initial_height"
|
||||
],
|
||||
"properties": {
|
||||
"initial_dealers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Addr"
|
||||
}
|
||||
},
|
||||
"initial_height": {
|
||||
"type": "integer",
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"get_past_dealers": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "PagedDealerResponse",
|
||||
"title": "RegisteredDealerDetails",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"dealers",
|
||||
"per_page"
|
||||
],
|
||||
"properties": {
|
||||
"dealers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/DealerDetails"
|
||||
}
|
||||
},
|
||||
"per_page": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"start_next_after": {
|
||||
"description": "Field indicating paging information for the following queries if the caller wishes to get further entries.",
|
||||
"details": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Addr"
|
||||
"$ref": "#/definitions/DealerRegistrationDetails"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
@@ -1528,31 +1861,17 @@
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"definitions": {
|
||||
"Addr": {
|
||||
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
|
||||
"type": "string"
|
||||
},
|
||||
"DealerDetails": {
|
||||
"DealerRegistrationDetails": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"address",
|
||||
"announce_address",
|
||||
"assigned_index",
|
||||
"bte_public_key_with_proof",
|
||||
"ed25519_identity"
|
||||
],
|
||||
"properties": {
|
||||
"address": {
|
||||
"$ref": "#/definitions/Addr"
|
||||
},
|
||||
"announce_address": {
|
||||
"type": "string"
|
||||
},
|
||||
"assigned_index": {
|
||||
"type": "integer",
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"bte_public_key_with_proof": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -91,15 +91,11 @@
|
||||
"commit_dealings_chunk": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"chunk",
|
||||
"resharing"
|
||||
"chunk"
|
||||
],
|
||||
"properties": {
|
||||
"chunk": {
|
||||
"$ref": "#/definitions/PartialContractDealing"
|
||||
},
|
||||
"resharing": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@@ -160,10 +156,10 @@
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"surpassed_threshold"
|
||||
"advance_epoch_state"
|
||||
],
|
||||
"properties": {
|
||||
"surpassed_threshold": {
|
||||
"advance_epoch_state": {
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -173,10 +169,23 @@
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"advance_epoch_state"
|
||||
"trigger_reset"
|
||||
],
|
||||
"properties": {
|
||||
"advance_epoch_state": {
|
||||
"trigger_reset": {
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"trigger_resharing"
|
||||
],
|
||||
"properties": {
|
||||
"trigger_resharing": {
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
}
|
||||
|
||||
@@ -44,16 +44,45 @@
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"get_initial_dealers"
|
||||
"can_advance_state"
|
||||
],
|
||||
"properties": {
|
||||
"get_initial_dealers": {
|
||||
"can_advance_state": {
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"get_registered_dealer"
|
||||
],
|
||||
"properties": {
|
||||
"get_registered_dealer": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"dealer_address"
|
||||
],
|
||||
"properties": {
|
||||
"dealer_address": {
|
||||
"type": "string"
|
||||
},
|
||||
"epoch_id": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -107,10 +136,10 @@
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"get_past_dealers"
|
||||
"get_dealer_indices"
|
||||
],
|
||||
"properties": {
|
||||
"get_past_dealers": {
|
||||
"get_dealer_indices": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"limit": {
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "StateAdvanceResponse",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"current_state",
|
||||
"is_complete",
|
||||
"progress",
|
||||
"reached_deadline"
|
||||
],
|
||||
"properties": {
|
||||
"current_state": {
|
||||
"$ref": "#/definitions/EpochState"
|
||||
},
|
||||
"deadline": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Timestamp"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"is_complete": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"progress": {
|
||||
"$ref": "#/definitions/StateProgress"
|
||||
},
|
||||
"reached_deadline": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"definitions": {
|
||||
"EpochState": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"waiting_initialisation",
|
||||
"in_progress"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"public_key_submission"
|
||||
],
|
||||
"properties": {
|
||||
"public_key_submission": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"resharing"
|
||||
],
|
||||
"properties": {
|
||||
"resharing": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"dealing_exchange"
|
||||
],
|
||||
"properties": {
|
||||
"dealing_exchange": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"resharing"
|
||||
],
|
||||
"properties": {
|
||||
"resharing": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"verification_key_submission"
|
||||
],
|
||||
"properties": {
|
||||
"verification_key_submission": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"resharing"
|
||||
],
|
||||
"properties": {
|
||||
"resharing": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"verification_key_validation"
|
||||
],
|
||||
"properties": {
|
||||
"verification_key_validation": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"resharing"
|
||||
],
|
||||
"properties": {
|
||||
"resharing": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"verification_key_finalization"
|
||||
],
|
||||
"properties": {
|
||||
"verification_key_finalization": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"resharing"
|
||||
],
|
||||
"properties": {
|
||||
"resharing": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"StateProgress": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"registered_dealers",
|
||||
"registered_resharing_dealers",
|
||||
"submitted_dealings",
|
||||
"submitted_key_shares",
|
||||
"verified_keys"
|
||||
],
|
||||
"properties": {
|
||||
"registered_dealers": {
|
||||
"description": "Counts the number of dealers that have registered in this epoch.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"registered_resharing_dealers": {
|
||||
"description": "Counts the number of resharing dealers that have registered in this epoch. This field is only populated during a resharing exchange. It is always <= registered_dealers.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"submitted_dealings": {
|
||||
"description": "Counts the number of fully received dealings (i.e. full chunks) from all the allowed dealers.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"submitted_key_shares": {
|
||||
"description": "Counts the number of submitted verification key shared from the dealers.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"verified_keys": {
|
||||
"description": "Counts the number of verified key shares.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Timestamp": {
|
||||
"description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Uint64"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Uint64": {
|
||||
"description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,15 +5,11 @@
|
||||
"required": [
|
||||
"epoch_id",
|
||||
"state",
|
||||
"state_progress",
|
||||
"time_configuration"
|
||||
],
|
||||
"properties": {
|
||||
"epoch_id": {
|
||||
"type": "integer",
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"finish_timestamp": {
|
||||
"deadline": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Timestamp"
|
||||
@@ -23,9 +19,17 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"epoch_id": {
|
||||
"type": "integer",
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"state": {
|
||||
"$ref": "#/definitions/EpochState"
|
||||
},
|
||||
"state_progress": {
|
||||
"$ref": "#/definitions/StateProgress"
|
||||
},
|
||||
"time_configuration": {
|
||||
"$ref": "#/definitions/TimeConfiguration"
|
||||
}
|
||||
@@ -148,6 +152,49 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"StateProgress": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"registered_dealers",
|
||||
"registered_resharing_dealers",
|
||||
"submitted_dealings",
|
||||
"submitted_key_shares",
|
||||
"verified_keys"
|
||||
],
|
||||
"properties": {
|
||||
"registered_dealers": {
|
||||
"description": "Counts the number of dealers that have registered in this epoch.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"registered_resharing_dealers": {
|
||||
"description": "Counts the number of resharing dealers that have registered in this epoch. This field is only populated during a resharing exchange. It is always <= registered_dealers.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"submitted_dealings": {
|
||||
"description": "Counts the number of fully received dealings (i.e. full chunks) from all the allowed dealers.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"submitted_key_shares": {
|
||||
"description": "Counts the number of submitted verification key shared from the dealers.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"verified_keys": {
|
||||
"description": "Counts the number of verified key shares.",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"TimeConfiguration": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
||||
@@ -57,11 +57,59 @@
|
||||
"additionalProperties": false
|
||||
},
|
||||
"DealerType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"current",
|
||||
"past",
|
||||
"unknown"
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"unknown"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"current"
|
||||
],
|
||||
"properties": {
|
||||
"current": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"assigned_index"
|
||||
],
|
||||
"properties": {
|
||||
"assigned_index": {
|
||||
"type": "integer",
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"past"
|
||||
],
|
||||
"properties": {
|
||||
"past": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"assigned_index"
|
||||
],
|
||||
"properties": {
|
||||
"assigned_index": {
|
||||
"type": "integer",
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "PagedDealerIndexResponse",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"indices"
|
||||
],
|
||||
"properties": {
|
||||
"indices": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"$ref": "#/definitions/Addr"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
}
|
||||
],
|
||||
"maxItems": 2,
|
||||
"minItems": 2
|
||||
}
|
||||
},
|
||||
"start_next_after": {
|
||||
"description": "Field indicating paging information for the following queries if the caller wishes to get further entries.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Addr"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"definitions": {
|
||||
"Addr": {
|
||||
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "RegisteredDealerDetails",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"details": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/DealerRegistrationDetails"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"definitions": {
|
||||
"DealerRegistrationDetails": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"announce_address",
|
||||
"bte_public_key_with_proof",
|
||||
"ed25519_identity"
|
||||
],
|
||||
"properties": {
|
||||
"announce_address": {
|
||||
"type": "string"
|
||||
},
|
||||
"bte_public_key_with_proof": {
|
||||
"type": "string"
|
||||
},
|
||||
"ed25519_identity": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::dealers::queries::{
|
||||
query_current_dealers_paged, query_dealer_details, query_past_dealers_paged,
|
||||
query_current_dealers_paged, query_dealer_details, query_dealers_indices_paged,
|
||||
query_registered_dealer_details,
|
||||
};
|
||||
use crate::dealers::transactions::try_add_dealer;
|
||||
use crate::dealings::queries::{
|
||||
@@ -11,11 +12,11 @@ use crate::dealings::queries::{
|
||||
};
|
||||
use crate::dealings::transactions::{try_commit_dealings_chunk, try_submit_dealings_metadata};
|
||||
use crate::epoch_state::queries::{
|
||||
query_current_epoch, query_current_epoch_threshold, query_initial_dealers,
|
||||
query_can_advance_state, query_current_epoch, query_current_epoch_threshold,
|
||||
};
|
||||
use crate::epoch_state::storage::CURRENT_EPOCH;
|
||||
use crate::epoch_state::transactions::{
|
||||
advance_epoch_state, try_initiate_dkg, try_surpassed_threshold,
|
||||
try_advance_epoch_state, try_initiate_dkg, try_trigger_reset, try_trigger_resharing,
|
||||
};
|
||||
use crate::error::ContractError;
|
||||
use crate::state::queries::query_state;
|
||||
@@ -108,8 +109,8 @@ pub fn execute(
|
||||
chunks,
|
||||
resharing,
|
||||
} => try_submit_dealings_metadata(deps, info, dealing_index, chunks, resharing),
|
||||
ExecuteMsg::CommitDealingsChunk { chunk, resharing } => {
|
||||
try_commit_dealings_chunk(deps, env, info, chunk, resharing)
|
||||
ExecuteMsg::CommitDealingsChunk { chunk } => {
|
||||
try_commit_dealings_chunk(deps, env, info, chunk)
|
||||
}
|
||||
ExecuteMsg::CommitVerificationKeyShare { share, resharing } => {
|
||||
try_commit_verification_key_share(deps, env, info, share, resharing)
|
||||
@@ -117,28 +118,37 @@ pub fn execute(
|
||||
ExecuteMsg::VerifyVerificationKeyShare { owner, resharing } => {
|
||||
try_verify_verification_key_share(deps, info, owner, resharing)
|
||||
}
|
||||
ExecuteMsg::SurpassedThreshold {} => try_surpassed_threshold(deps, env),
|
||||
ExecuteMsg::AdvanceEpochState {} => advance_epoch_state(deps, env),
|
||||
ExecuteMsg::AdvanceEpochState {} => try_advance_epoch_state(deps, env),
|
||||
ExecuteMsg::TriggerReset {} => try_trigger_reset(deps, env, info),
|
||||
ExecuteMsg::TriggerResharing {} => try_trigger_resharing(deps, env, info),
|
||||
}
|
||||
}
|
||||
|
||||
#[entry_point]
|
||||
pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
|
||||
pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
|
||||
let response = match msg {
|
||||
QueryMsg::GetState {} => to_binary(&query_state(deps.storage)?)?,
|
||||
QueryMsg::GetCurrentEpochState {} => to_binary(&query_current_epoch(deps.storage)?)?,
|
||||
QueryMsg::CanAdvanceState {} => to_binary(&query_can_advance_state(deps.storage, env)?)?,
|
||||
QueryMsg::GetCurrentEpochThreshold {} => {
|
||||
to_binary(&query_current_epoch_threshold(deps.storage)?)?
|
||||
}
|
||||
QueryMsg::GetInitialDealers {} => to_binary(&query_initial_dealers(deps.storage)?)?,
|
||||
QueryMsg::GetRegisteredDealer {
|
||||
dealer_address,
|
||||
epoch_id,
|
||||
} => to_binary(&query_registered_dealer_details(
|
||||
deps,
|
||||
dealer_address,
|
||||
epoch_id,
|
||||
)?)?,
|
||||
QueryMsg::GetDealerDetails { dealer_address } => {
|
||||
to_binary(&query_dealer_details(deps, dealer_address)?)?
|
||||
}
|
||||
QueryMsg::GetCurrentDealers { limit, start_after } => {
|
||||
to_binary(&query_current_dealers_paged(deps, start_after, limit)?)?
|
||||
}
|
||||
QueryMsg::GetPastDealers { limit, start_after } => {
|
||||
to_binary(&query_past_dealers_paged(deps, start_after, limit)?)?
|
||||
QueryMsg::GetDealerIndices { limit, start_after } => {
|
||||
to_binary(&query_dealers_indices_paged(deps, start_after, limit)?)?
|
||||
}
|
||||
QueryMsg::GetDealingsMetadata {
|
||||
epoch_id,
|
||||
|
||||
@@ -1,36 +1,34 @@
|
||||
// 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::dealers::storage::{self, IndexedDealersMap};
|
||||
use crate::dealers::storage::{
|
||||
self, get_dealer_details, get_dealer_index, get_registration_details, DEALERS_INDICES,
|
||||
EPOCH_DEALERS_MAP,
|
||||
};
|
||||
use crate::epoch_state::storage::CURRENT_EPOCH;
|
||||
use cosmwasm_std::{Deps, Order, StdResult};
|
||||
use cw_storage_plus::Bound;
|
||||
use nym_coconut_dkg_common::dealer::{DealerDetailsResponse, DealerType, PagedDealerResponse};
|
||||
use nym_coconut_dkg_common::dealer::{
|
||||
DealerDetailsResponse, DealerType, PagedDealerIndexResponse, PagedDealerResponse,
|
||||
RegisteredDealerDetails,
|
||||
};
|
||||
use nym_coconut_dkg_common::types::{DealerDetails, EpochId};
|
||||
|
||||
fn query_dealers(
|
||||
pub fn query_registered_dealer_details(
|
||||
deps: Deps<'_>,
|
||||
start_after: Option<String>,
|
||||
limit: Option<u32>,
|
||||
underlying_map: &IndexedDealersMap<'_>,
|
||||
) -> StdResult<PagedDealerResponse> {
|
||||
let limit = limit
|
||||
.unwrap_or(storage::DEALERS_PAGE_DEFAULT_LIMIT)
|
||||
.min(storage::DEALERS_PAGE_MAX_LIMIT) as usize;
|
||||
dealer_address: String,
|
||||
epoch_id: Option<EpochId>,
|
||||
) -> StdResult<RegisteredDealerDetails> {
|
||||
let addr = deps.api.addr_validate(&dealer_address)?;
|
||||
|
||||
let addr = start_after
|
||||
.map(|addr| deps.api.addr_validate(&addr))
|
||||
.transpose()?;
|
||||
let epoch_id = match epoch_id {
|
||||
Some(epoch_id) => epoch_id,
|
||||
None => CURRENT_EPOCH.load(deps.storage)?.epoch_id,
|
||||
};
|
||||
|
||||
let start = addr.as_ref().map(Bound::exclusive);
|
||||
|
||||
let dealers = underlying_map
|
||||
.range(deps.storage, start, None, Order::Ascending)
|
||||
.take(limit)
|
||||
.map(|res| res.map(|item| item.1))
|
||||
.collect::<StdResult<Vec<_>>>()?;
|
||||
|
||||
let start_next_after = dealers.last().map(|dealer| dealer.address.clone());
|
||||
|
||||
Ok(PagedDealerResponse::new(dealers, limit, start_next_after))
|
||||
Ok(RegisteredDealerDetails {
|
||||
details: get_registration_details(deps.storage, &addr, epoch_id).ok(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn query_dealer_details(
|
||||
@@ -38,32 +36,93 @@ pub fn query_dealer_details(
|
||||
dealer_address: String,
|
||||
) -> StdResult<DealerDetailsResponse> {
|
||||
let addr = deps.api.addr_validate(&dealer_address)?;
|
||||
if let Some(current) = storage::current_dealers().may_load(deps.storage, &addr)? {
|
||||
let current_epoch_id = CURRENT_EPOCH.load(deps.storage)?.epoch_id;
|
||||
|
||||
// if the address has registration data for the current epoch, it means it's an active dealer
|
||||
if let Ok(dealer_details) = get_dealer_details(deps.storage, &addr, current_epoch_id) {
|
||||
let assigned_index = dealer_details.assigned_index;
|
||||
return Ok(DealerDetailsResponse::new(
|
||||
Some(current),
|
||||
DealerType::Current,
|
||||
Some(dealer_details),
|
||||
DealerType::Current { assigned_index },
|
||||
));
|
||||
}
|
||||
if let Some(past) = storage::past_dealers().may_load(deps.storage, &addr)? {
|
||||
return Ok(DealerDetailsResponse::new(Some(past), DealerType::Past));
|
||||
|
||||
// and if has had an assigned index it must have been a dealer at some point in the past
|
||||
if let Ok(assigned_index) = get_dealer_index(deps.storage, &addr, current_epoch_id) {
|
||||
return Ok(DealerDetailsResponse::new(
|
||||
None,
|
||||
DealerType::Past { assigned_index },
|
||||
));
|
||||
}
|
||||
|
||||
Ok(DealerDetailsResponse::new(None, DealerType::Unknown))
|
||||
}
|
||||
|
||||
pub fn query_dealers_indices_paged(
|
||||
deps: Deps<'_>,
|
||||
start_after: Option<String>,
|
||||
limit: Option<u32>,
|
||||
) -> StdResult<PagedDealerIndexResponse> {
|
||||
let limit = limit
|
||||
.unwrap_or(storage::DEALER_INDICES_PAGE_DEFAULT_LIMIT)
|
||||
.min(storage::DEALER_INDICES_PAGE_MAX_LIMIT) as usize;
|
||||
let addr = start_after
|
||||
.map(|addr| deps.api.addr_validate(&addr))
|
||||
.transpose()?;
|
||||
|
||||
let start = addr.as_ref().map(Bound::exclusive);
|
||||
|
||||
let dealers = DEALERS_INDICES
|
||||
.range(deps.storage, start, None, Order::Ascending)
|
||||
.take(limit)
|
||||
.collect::<StdResult<Vec<_>>>()?;
|
||||
|
||||
let start_next_after = dealers.last().map(|dealer| dealer.0.clone());
|
||||
|
||||
Ok(PagedDealerIndexResponse::new(dealers, start_next_after))
|
||||
}
|
||||
|
||||
pub fn query_current_dealers_paged(
|
||||
deps: Deps<'_>,
|
||||
start_after: Option<String>,
|
||||
limit: Option<u32>,
|
||||
) -> StdResult<PagedDealerResponse> {
|
||||
query_dealers(deps, start_after, limit, &storage::current_dealers())
|
||||
}
|
||||
let limit = limit
|
||||
.unwrap_or(storage::DEALERS_PAGE_DEFAULT_LIMIT)
|
||||
.min(storage::DEALERS_PAGE_MAX_LIMIT) as usize;
|
||||
let addr = start_after
|
||||
.map(|addr| deps.api.addr_validate(&addr))
|
||||
.transpose()?;
|
||||
|
||||
pub fn query_past_dealers_paged(
|
||||
deps: Deps<'_>,
|
||||
start_after: Option<String>,
|
||||
limit: Option<u32>,
|
||||
) -> StdResult<PagedDealerResponse> {
|
||||
query_dealers(deps, start_after, limit, &storage::past_dealers())
|
||||
let start = addr.as_ref().map(Bound::exclusive);
|
||||
|
||||
let current_epoch_id = CURRENT_EPOCH.load(deps.storage)?.epoch_id;
|
||||
|
||||
let dealers = EPOCH_DEALERS_MAP
|
||||
.prefix(current_epoch_id)
|
||||
.range(deps.storage, start, None, Order::Ascending)
|
||||
.take(limit)
|
||||
.map(|res| {
|
||||
res.map(|(address, details)| {
|
||||
// SAFETY: if we have DealerRegistrationDetails saved, it means we MUST also have its node index
|
||||
// otherwise some serious invariants have been broken in the contract, and we're in trouble
|
||||
#[allow(clippy::expect_used)]
|
||||
let assigned_index = get_dealer_index(deps.storage, &address, current_epoch_id)
|
||||
.expect("could not retrieve dealer index for a registered dealer");
|
||||
|
||||
DealerDetails {
|
||||
address,
|
||||
bte_public_key_with_proof: details.bte_public_key_with_proof,
|
||||
ed25519_identity: details.ed25519_identity,
|
||||
announce_address: details.announce_address,
|
||||
assigned_index,
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect::<StdResult<Vec<_>>>()?;
|
||||
let start_next_after = dealers.last().map(|dealer| dealer.address.clone());
|
||||
|
||||
Ok(PagedDealerResponse::new(dealers, limit, start_next_after))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -71,24 +130,22 @@ pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::dealers::storage::{DEALERS_PAGE_DEFAULT_LIMIT, DEALERS_PAGE_MAX_LIMIT};
|
||||
use crate::support::tests::fixtures::dealer_details_fixture;
|
||||
use crate::support::tests::helpers::init_contract;
|
||||
use crate::support::tests::helpers::{init_contract, insert_dealer};
|
||||
use cosmwasm_std::DepsMut;
|
||||
|
||||
fn fill_dealers(deps: DepsMut<'_>, mapping: &IndexedDealersMap<'_>, size: usize) {
|
||||
for n in 0..size {
|
||||
let dealer_details = dealer_details_fixture(n as u64);
|
||||
mapping
|
||||
.save(deps.storage, &dealer_details.address, &dealer_details)
|
||||
.unwrap();
|
||||
fn fill_dealers(mut deps: DepsMut<'_>, epoch_id: EpochId, size: usize) {
|
||||
for assigned_index in 0..size {
|
||||
let dealer_details = dealer_details_fixture(assigned_index as u64);
|
||||
insert_dealer(deps.branch(), epoch_id, &dealer_details);
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_dealers(deps: DepsMut<'_>, mapping: &IndexedDealersMap<'_>, size: usize) {
|
||||
for n in 0..size {
|
||||
let dealer_details = dealer_details_fixture(n as u64);
|
||||
mapping
|
||||
.remove(deps.storage, &dealer_details.address)
|
||||
.unwrap();
|
||||
fn remove_dealers(deps: DepsMut<'_>, epoch_id: EpochId, size: usize) {
|
||||
for assigned_index in 0..size {
|
||||
let dealer_details = dealer_details_fixture(assigned_index as u64);
|
||||
DEALERS_INDICES.remove(deps.storage, &dealer_details.address);
|
||||
|
||||
EPOCH_DEALERS_MAP.remove(deps.storage, (epoch_id, &dealer_details.address));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,10 +153,8 @@ pub(crate) mod tests {
|
||||
fn dealers_empty_on_init() {
|
||||
let deps = init_contract();
|
||||
|
||||
for mapping in [storage::current_dealers(), storage::past_dealers()] {
|
||||
let page1 = query_dealers(deps.as_ref(), None, None, &mapping).unwrap();
|
||||
assert_eq!(0, page1.dealers.len() as u32);
|
||||
}
|
||||
let page1 = query_current_dealers_paged(deps.as_ref(), None, None).unwrap();
|
||||
assert_eq!(0, page1.dealers.len() as u32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -107,30 +162,26 @@ pub(crate) mod tests {
|
||||
let mut deps = init_contract();
|
||||
let limit = 2;
|
||||
|
||||
for mapping in [storage::current_dealers(), storage::past_dealers()] {
|
||||
fill_dealers(deps.as_mut(), &mapping, 1000);
|
||||
fill_dealers(deps.as_mut(), 0, 1000);
|
||||
|
||||
let page1 = query_dealers(deps.as_ref(), None, Option::from(limit), &mapping).unwrap();
|
||||
assert_eq!(limit, page1.dealers.len() as u32);
|
||||
let page1 = query_current_dealers_paged(deps.as_ref(), None, Option::from(limit)).unwrap();
|
||||
assert_eq!(limit, page1.dealers.len() as u32);
|
||||
|
||||
remove_dealers(deps.as_mut(), &mapping, 1000);
|
||||
}
|
||||
remove_dealers(deps.as_mut(), 0, 1000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dealers_paged_retrieval_has_default_limit() {
|
||||
let mut deps = init_contract();
|
||||
|
||||
for mapping in [storage::current_dealers(), storage::past_dealers()] {
|
||||
fill_dealers(deps.as_mut(), &mapping, 1000);
|
||||
fill_dealers(deps.as_mut(), 0, 1000);
|
||||
|
||||
// query without explicitly setting a limit
|
||||
let page1 = query_dealers(deps.as_ref(), None, None, &mapping).unwrap();
|
||||
// query without explicitly setting a limit
|
||||
let page1 = query_current_dealers_paged(deps.as_ref(), None, None).unwrap();
|
||||
|
||||
assert_eq!(DEALERS_PAGE_DEFAULT_LIMIT, page1.dealers.len() as u32);
|
||||
assert_eq!(DEALERS_PAGE_DEFAULT_LIMIT, page1.dealers.len() as u32);
|
||||
|
||||
remove_dealers(deps.as_mut(), &mapping, 1000);
|
||||
}
|
||||
remove_dealers(deps.as_mut(), 0, 1000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -140,18 +191,16 @@ pub(crate) mod tests {
|
||||
// query with a crazily high limit in an attempt to use too many resources
|
||||
let crazy_limit = 1000 * DEALERS_PAGE_MAX_LIMIT;
|
||||
|
||||
for mapping in [storage::current_dealers(), storage::past_dealers()] {
|
||||
fill_dealers(deps.as_mut(), &mapping, 1000);
|
||||
fill_dealers(deps.as_mut(), 0, 1000);
|
||||
|
||||
let page1 =
|
||||
query_dealers(deps.as_ref(), None, Option::from(crazy_limit), &mapping).unwrap();
|
||||
let page1 =
|
||||
query_current_dealers_paged(deps.as_ref(), None, Option::from(crazy_limit)).unwrap();
|
||||
|
||||
// we default to a decent sized upper bound instead
|
||||
let expected_limit = DEALERS_PAGE_MAX_LIMIT;
|
||||
assert_eq!(expected_limit, page1.dealers.len() as u32);
|
||||
// we default to a decent sized upper bound instead
|
||||
let expected_limit = DEALERS_PAGE_MAX_LIMIT;
|
||||
assert_eq!(expected_limit, page1.dealers.len() as u32);
|
||||
|
||||
remove_dealers(deps.as_mut(), &mapping, 1000);
|
||||
}
|
||||
remove_dealers(deps.as_mut(), 0, 1000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -160,62 +209,52 @@ pub(crate) mod tests {
|
||||
|
||||
let per_page = 2;
|
||||
|
||||
for mapping in [storage::current_dealers(), storage::past_dealers()] {
|
||||
fill_dealers(deps.as_mut(), &mapping, 1);
|
||||
let page1 =
|
||||
query_dealers(deps.as_ref(), None, Option::from(per_page), &mapping).unwrap();
|
||||
fill_dealers(deps.as_mut(), 0, 1);
|
||||
let page1 =
|
||||
query_current_dealers_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
|
||||
|
||||
// page should have 1 result on it
|
||||
assert_eq!(1, page1.dealers.len());
|
||||
remove_dealers(deps.as_mut(), &mapping, 1);
|
||||
}
|
||||
// page should have 1 result on it
|
||||
assert_eq!(1, page1.dealers.len());
|
||||
remove_dealers(deps.as_mut(), 0, 1);
|
||||
|
||||
for mapping in [storage::current_dealers(), storage::past_dealers()] {
|
||||
fill_dealers(deps.as_mut(), &mapping, 2);
|
||||
// page1 should have 2 results on it
|
||||
let page1 =
|
||||
query_dealers(deps.as_ref(), None, Option::from(per_page), &mapping).unwrap();
|
||||
assert_eq!(2, page1.dealers.len());
|
||||
remove_dealers(deps.as_mut(), &mapping, 2);
|
||||
}
|
||||
fill_dealers(deps.as_mut(), 0, 2);
|
||||
// page1 should have 2 results on it
|
||||
let page1 =
|
||||
query_current_dealers_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
|
||||
assert_eq!(2, page1.dealers.len());
|
||||
remove_dealers(deps.as_mut(), 0, 2);
|
||||
|
||||
for mapping in [storage::current_dealers(), storage::past_dealers()] {
|
||||
fill_dealers(deps.as_mut(), &mapping, 3);
|
||||
// page1 still has 2 results
|
||||
let page1 =
|
||||
query_dealers(deps.as_ref(), None, Option::from(per_page), &mapping).unwrap();
|
||||
assert_eq!(2, page1.dealers.len());
|
||||
fill_dealers(deps.as_mut(), 0, 3);
|
||||
// page1 still has 2 results
|
||||
let page1 =
|
||||
query_current_dealers_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
|
||||
assert_eq!(2, page1.dealers.len());
|
||||
|
||||
// retrieving the next page should start after the last key on this page
|
||||
let start_after = page1.start_next_after.unwrap();
|
||||
let page2 = query_dealers(
|
||||
deps.as_ref(),
|
||||
Option::from(start_after.to_string()),
|
||||
Option::from(per_page),
|
||||
&mapping,
|
||||
)
|
||||
.unwrap();
|
||||
// retrieving the next page should start after the last key on this page
|
||||
let start_after = page1.start_next_after.unwrap();
|
||||
let page2 = query_current_dealers_paged(
|
||||
deps.as_ref(),
|
||||
Option::from(start_after.to_string()),
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(1, page2.dealers.len());
|
||||
remove_dealers(deps.as_mut(), &mapping, 3);
|
||||
}
|
||||
assert_eq!(1, page2.dealers.len());
|
||||
remove_dealers(deps.as_mut(), 0, 3);
|
||||
|
||||
for mapping in [storage::current_dealers(), storage::past_dealers()] {
|
||||
fill_dealers(deps.as_mut(), &mapping, 4);
|
||||
let page1 =
|
||||
query_dealers(deps.as_ref(), None, Option::from(per_page), &mapping).unwrap();
|
||||
let start_after = page1.start_next_after.unwrap();
|
||||
let page2 = query_dealers(
|
||||
deps.as_ref(),
|
||||
Option::from(start_after.to_string()),
|
||||
Option::from(per_page),
|
||||
&mapping,
|
||||
)
|
||||
.unwrap();
|
||||
fill_dealers(deps.as_mut(), 0, 4);
|
||||
let page1 =
|
||||
query_current_dealers_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
|
||||
let start_after = page1.start_next_after.unwrap();
|
||||
let page2 = query_current_dealers_paged(
|
||||
deps.as_ref(),
|
||||
Option::from(start_after.to_string()),
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// now we have 2 pages, with 2 results on the second page
|
||||
assert_eq!(2, page2.dealers.len());
|
||||
remove_dealers(deps.as_mut(), &mapping, 4);
|
||||
}
|
||||
// now we have 2 pages, with 2 results on the second page
|
||||
assert_eq!(2, page2.dealers.len());
|
||||
remove_dealers(deps.as_mut(), 0, 4);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +1,102 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::{Addr, StdResult, Storage};
|
||||
use cw_storage_plus::{Index, IndexList, IndexedMap, Item, UniqueIndex};
|
||||
use nym_coconut_dkg_common::types::{DealerDetails, NodeIndex};
|
||||
use crate::error::ContractError;
|
||||
use crate::Dealer;
|
||||
use cosmwasm_std::{StdResult, Storage};
|
||||
use cw_storage_plus::{Item, Map};
|
||||
use nym_coconut_dkg_common::types::{DealerDetails, DealerRegistrationDetails, EpochId, NodeIndex};
|
||||
|
||||
const CURRENT_DEALERS_PK: &str = "crd";
|
||||
const PAST_DEALERS_PK: &str = "ptd";
|
||||
const DEALERS_NODE_INDEX_IDX_NAMESPACE: &str = "dni";
|
||||
pub(crate) const DEALER_INDICES_PAGE_MAX_LIMIT: u32 = 80;
|
||||
pub(crate) const DEALER_INDICES_PAGE_DEFAULT_LIMIT: u32 = 40;
|
||||
|
||||
pub(crate) const DEALERS_PAGE_MAX_LIMIT: u32 = 75;
|
||||
pub(crate) const DEALERS_PAGE_DEFAULT_LIMIT: u32 = 50;
|
||||
pub(crate) const DEALERS_PAGE_MAX_LIMIT: u32 = 25;
|
||||
pub(crate) const DEALERS_PAGE_DEFAULT_LIMIT: u32 = 10;
|
||||
|
||||
pub(crate) const NODE_INDEX_COUNTER: Item<NodeIndex> = Item::new("node_index_counter");
|
||||
|
||||
pub(crate) type IndexedDealersMap<'a> = IndexedMap<'a, &'a Addr, DealerDetails, DealersIndex<'a>>;
|
||||
pub(crate) const DEALERS_INDICES: Map<Dealer, NodeIndex> = Map::new("dealer_index");
|
||||
|
||||
pub(crate) struct DealersIndex<'a> {
|
||||
pub(crate) node_index: UniqueIndex<'a, NodeIndex, DealerDetails>,
|
||||
}
|
||||
pub(crate) const EPOCH_DEALERS_MAP: Map<(EpochId, Dealer), DealerRegistrationDetails> =
|
||||
Map::new("epoch_dealers");
|
||||
|
||||
impl<'a> IndexList<DealerDetails> for DealersIndex<'a> {
|
||||
fn get_indexes(&'_ self) -> Box<dyn Iterator<Item = &'_ dyn Index<DealerDetails>> + '_> {
|
||||
let v: Vec<&dyn Index<DealerDetails>> = vec![&self.node_index];
|
||||
Box::new(v.into_iter())
|
||||
/// Attempts to retrieve a pre-assign node index associated with given dealer.
|
||||
/// If one doesn't exist, a new one is assigned.
|
||||
pub(crate) fn get_or_assign_index(
|
||||
storage: &mut dyn Storage,
|
||||
dealer: Dealer,
|
||||
) -> StdResult<NodeIndex> {
|
||||
if let Some(index) = DEALERS_INDICES.may_load(storage, dealer)? {
|
||||
return Ok(index);
|
||||
}
|
||||
let index = next_node_index(storage)?;
|
||||
DEALERS_INDICES.save(storage, dealer, &index)?;
|
||||
Ok(index)
|
||||
}
|
||||
|
||||
pub(crate) fn current_dealers<'a>() -> IndexedDealersMap<'a> {
|
||||
let indexes = DealersIndex {
|
||||
node_index: UniqueIndex::new(|d| d.assigned_index, DEALERS_NODE_INDEX_IDX_NAMESPACE),
|
||||
};
|
||||
IndexedMap::new(CURRENT_DEALERS_PK, indexes)
|
||||
pub(crate) fn save_dealer_details_if_not_a_dealer(
|
||||
storage: &mut dyn Storage,
|
||||
dealer: Dealer,
|
||||
epoch_id: EpochId,
|
||||
details: DealerRegistrationDetails,
|
||||
) -> Result<(), ContractError> {
|
||||
if EPOCH_DEALERS_MAP.has(storage, (epoch_id, dealer)) {
|
||||
return Err(ContractError::AlreadyADealer);
|
||||
}
|
||||
EPOCH_DEALERS_MAP.save(storage, (epoch_id, dealer), &details)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn past_dealers<'a>() -> IndexedDealersMap<'a> {
|
||||
let indexes = DealersIndex {
|
||||
node_index: UniqueIndex::new(|d| d.assigned_index, DEALERS_NODE_INDEX_IDX_NAMESPACE),
|
||||
};
|
||||
IndexedMap::new(PAST_DEALERS_PK, indexes)
|
||||
pub(crate) fn ensure_dealer(
|
||||
storage: &dyn Storage,
|
||||
dealer: Dealer,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<(), ContractError> {
|
||||
if !is_dealer(storage, dealer, epoch_id) {
|
||||
return Err(ContractError::NotADealer { epoch_id });
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn is_dealer(storage: &dyn Storage, dealer: Dealer, epoch_id: EpochId) -> bool {
|
||||
EPOCH_DEALERS_MAP.has(storage, (epoch_id, dealer))
|
||||
}
|
||||
|
||||
// note: `epoch_id` is provided purely for the error message. it has nothing to do with storage retrieval
|
||||
pub(crate) fn get_dealer_index(
|
||||
storage: &dyn Storage,
|
||||
dealer: Dealer,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<NodeIndex, ContractError> {
|
||||
DEALERS_INDICES
|
||||
.may_load(storage, dealer)?
|
||||
.ok_or(ContractError::NotADealer { epoch_id })
|
||||
}
|
||||
|
||||
pub(crate) fn get_registration_details(
|
||||
storage: &dyn Storage,
|
||||
dealer: Dealer,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<DealerRegistrationDetails, ContractError> {
|
||||
EPOCH_DEALERS_MAP
|
||||
.may_load(storage, (epoch_id, dealer))?
|
||||
.ok_or(ContractError::NotADealer { epoch_id })
|
||||
}
|
||||
|
||||
pub(crate) fn get_dealer_details(
|
||||
storage: &dyn Storage,
|
||||
dealer: Dealer,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<DealerDetails, ContractError> {
|
||||
let registration_details = get_registration_details(storage, dealer, epoch_id)?;
|
||||
let assigned_index = get_dealer_index(storage, dealer, epoch_id)?;
|
||||
Ok(DealerDetails {
|
||||
address: dealer.to_owned(),
|
||||
bte_public_key_with_proof: registration_details.bte_public_key_with_proof,
|
||||
ed25519_identity: registration_details.ed25519_identity,
|
||||
announce_address: registration_details.announce_address,
|
||||
assigned_index,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn next_node_index(store: &mut dyn Storage) -> StdResult<NodeIndex> {
|
||||
|
||||
@@ -1,34 +1,24 @@
|
||||
// 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::dealers::storage as dealers_storage;
|
||||
use crate::epoch_state::storage::INITIAL_REPLACEMENT_DATA;
|
||||
use crate::dealers::storage::{
|
||||
get_or_assign_index, is_dealer, save_dealer_details_if_not_a_dealer,
|
||||
};
|
||||
use crate::epoch_state::storage::CURRENT_EPOCH;
|
||||
use crate::epoch_state::utils::check_epoch_state;
|
||||
use crate::error::ContractError;
|
||||
use crate::state::storage::STATE;
|
||||
use cosmwasm_std::{Addr, DepsMut, MessageInfo, Response};
|
||||
use nym_coconut_dkg_common::types::{DealerDetails, EncodedBTEPublicKeyWithProof, EpochState};
|
||||
use crate::Dealer;
|
||||
use cosmwasm_std::{Deps, DepsMut, MessageInfo, Response, StdResult};
|
||||
use nym_coconut_dkg_common::dealer::DealerRegistrationDetails;
|
||||
use nym_coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, EpochState};
|
||||
|
||||
// currently we only require that
|
||||
// a) it's part of the signer group
|
||||
// b) it isn't already a dealer
|
||||
fn verify_dealer(deps: DepsMut<'_>, dealer: &Addr, resharing: bool) -> Result<(), ContractError> {
|
||||
if dealers_storage::current_dealers()
|
||||
.may_load(deps.storage, dealer)?
|
||||
.is_some()
|
||||
{
|
||||
return Err(ContractError::AlreadyADealer);
|
||||
}
|
||||
fn ensure_group_member(deps: Deps, dealer: Dealer) -> Result<(), ContractError> {
|
||||
let state = STATE.load(deps.storage)?;
|
||||
|
||||
let height = if resharing {
|
||||
Some(INITIAL_REPLACEMENT_DATA.load(deps.storage)?.initial_height)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
state
|
||||
.group_addr
|
||||
.is_voting_member(&deps.querier, dealer, height)?
|
||||
.is_voting_member(&deps.querier, dealer, None)?
|
||||
.ok_or(ContractError::Unauthorized {})?;
|
||||
|
||||
Ok(())
|
||||
@@ -37,42 +27,57 @@ fn verify_dealer(deps: DepsMut<'_>, dealer: &Addr, resharing: bool) -> Result<()
|
||||
// future optimisation:
|
||||
// for a recurring dealer just let it refresh the keys without having to do all the storage operations
|
||||
pub fn try_add_dealer(
|
||||
mut deps: DepsMut<'_>,
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
bte_key_with_proof: EncodedBTEPublicKeyWithProof,
|
||||
identity_key: String,
|
||||
announce_address: String,
|
||||
resharing: bool,
|
||||
) -> Result<Response, ContractError> {
|
||||
let epoch = CURRENT_EPOCH.load(deps.storage)?;
|
||||
check_epoch_state(deps.storage, EpochState::PublicKeySubmission { resharing })?;
|
||||
|
||||
verify_dealer(deps.branch(), &info.sender, resharing)?;
|
||||
// make sure this potential dealer actually belong to the group
|
||||
ensure_group_member(deps.as_ref(), &info.sender)?;
|
||||
|
||||
// if it was already a dealer in the past, assign the same node index
|
||||
let node_index = if let Some(prior_details) =
|
||||
dealers_storage::past_dealers().may_load(deps.storage, &info.sender)?
|
||||
{
|
||||
// since this dealer is going to become active now, remove it from the past dealers
|
||||
dealers_storage::past_dealers().replace(
|
||||
deps.storage,
|
||||
&info.sender,
|
||||
None,
|
||||
Some(&prior_details),
|
||||
)?;
|
||||
prior_details.assigned_index
|
||||
} else {
|
||||
dealers_storage::next_node_index(deps.storage)?
|
||||
};
|
||||
let node_index = get_or_assign_index(deps.storage, &info.sender)?;
|
||||
|
||||
// save the dealer into the storage
|
||||
let dealer_details = DealerDetails {
|
||||
address: info.sender.clone(),
|
||||
// save the dealer into the storage (if it hasn't already been saved)
|
||||
let dealer_details = DealerRegistrationDetails {
|
||||
bte_public_key_with_proof: bte_key_with_proof,
|
||||
ed25519_identity: identity_key,
|
||||
announce_address,
|
||||
assigned_index: node_index,
|
||||
};
|
||||
dealers_storage::current_dealers().save(deps.storage, &info.sender, &dealer_details)?;
|
||||
save_dealer_details_if_not_a_dealer(
|
||||
deps.storage,
|
||||
&info.sender,
|
||||
epoch.epoch_id,
|
||||
dealer_details,
|
||||
)?;
|
||||
|
||||
// check if it's a resharing dealer
|
||||
|
||||
let is_resharing_dealer = resharing
|
||||
&& is_dealer(
|
||||
deps.storage,
|
||||
&info.sender,
|
||||
epoch
|
||||
.epoch_id
|
||||
.checked_sub(1)
|
||||
.expect("epoch invariant broken: resharing during 0th epoch"),
|
||||
);
|
||||
|
||||
// increment the number of registered dealers
|
||||
CURRENT_EPOCH.update(deps.storage, |epoch| -> StdResult<_> {
|
||||
let mut updated_epoch = epoch;
|
||||
updated_epoch.state_progress.registered_dealers += 1;
|
||||
|
||||
if is_resharing_dealer {
|
||||
updated_epoch.state_progress.registered_resharing_dealers += 1;
|
||||
}
|
||||
|
||||
Ok(updated_epoch)
|
||||
})?;
|
||||
|
||||
Ok(Response::new().add_attribute("node_index", node_index.to_string()))
|
||||
}
|
||||
@@ -80,63 +85,12 @@ pub fn try_add_dealer(
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::dealers::storage::current_dealers;
|
||||
use crate::epoch_state::transactions::{advance_epoch_state, try_initiate_dkg};
|
||||
use crate::support::tests::fixtures::dealer_details_fixture;
|
||||
use crate::epoch_state::transactions::{try_advance_epoch_state, try_initiate_dkg};
|
||||
use crate::support::tests::helpers;
|
||||
use crate::support::tests::helpers::{add_fixture_dealer, ADMIN_ADDRESS, GROUP_MEMBERS};
|
||||
use crate::support::tests::helpers::{add_fixture_dealer, ADMIN_ADDRESS};
|
||||
use cosmwasm_std::testing::{mock_env, mock_info};
|
||||
use cw4::Member;
|
||||
use nym_coconut_dkg_common::types::{InitialReplacementData, TimeConfiguration};
|
||||
use rusty_fork::rusty_fork_test;
|
||||
|
||||
rusty_fork_test! {
|
||||
#[test]
|
||||
fn verification() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let new_dealer = Addr::unchecked("new_dealer");
|
||||
let details1 = dealer_details_fixture(1);
|
||||
let details2 = dealer_details_fixture(2);
|
||||
let details3 = dealer_details_fixture(3);
|
||||
current_dealers()
|
||||
.save(deps.as_mut().storage, &details1.address, &details1)
|
||||
.unwrap();
|
||||
let err = verify_dealer(deps.as_mut(), &details1.address, false).unwrap_err();
|
||||
assert_eq!(err, ContractError::AlreadyADealer);
|
||||
|
||||
INITIAL_REPLACEMENT_DATA
|
||||
.save(
|
||||
deps.as_mut().storage,
|
||||
&InitialReplacementData {
|
||||
initial_dealers: vec![details1.address, details2.address, details3.address],
|
||||
initial_height: 1,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let err = verify_dealer(deps.as_mut(), &new_dealer, false).unwrap_err();
|
||||
assert_eq!(err, ContractError::Unauthorized);
|
||||
|
||||
GROUP_MEMBERS.lock().unwrap().push((
|
||||
Member {
|
||||
addr: new_dealer.to_string(),
|
||||
weight: 10,
|
||||
},
|
||||
2,
|
||||
));
|
||||
verify_dealer(deps.as_mut(), &new_dealer, false).unwrap();
|
||||
|
||||
let err = verify_dealer(deps.as_mut(), &new_dealer, true).unwrap_err();
|
||||
assert_eq!(err, ContractError::Unauthorized);
|
||||
|
||||
INITIAL_REPLACEMENT_DATA
|
||||
.update::<_, ContractError>(deps.as_mut().storage, |mut data| {
|
||||
data.initial_height = 2;
|
||||
Ok(data)
|
||||
})
|
||||
.unwrap();
|
||||
verify_dealer(deps.as_mut(), &new_dealer, true).unwrap();
|
||||
}
|
||||
}
|
||||
use cosmwasm_std::Addr;
|
||||
use nym_coconut_dkg_common::types::TimeConfiguration;
|
||||
|
||||
#[test]
|
||||
fn invalid_state() {
|
||||
@@ -156,7 +110,7 @@ pub(crate) mod tests {
|
||||
.plus_seconds(TimeConfiguration::default().public_key_submission_time_secs);
|
||||
|
||||
add_fixture_dealer(deps.as_mut());
|
||||
advance_epoch_state(deps.as_mut(), env).unwrap();
|
||||
try_advance_epoch_state(deps.as_mut(), env).unwrap();
|
||||
|
||||
let ret = try_add_dealer(
|
||||
deps.as_mut(),
|
||||
|
||||
@@ -2,15 +2,14 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::ContractError;
|
||||
use cosmwasm_std::{Addr, Storage};
|
||||
use crate::Dealer;
|
||||
use cosmwasm_std::Storage;
|
||||
use cw_storage_plus::{Key, Map, Path, PrimaryKey};
|
||||
use nym_coconut_dkg_common::dealing::{DealingMetadata, PartialContractDealing};
|
||||
use nym_coconut_dkg_common::types::{
|
||||
ChunkIndex, ContractSafeBytes, DealingIndex, EpochId, PartialContractDealingData,
|
||||
};
|
||||
|
||||
type Dealer<'a> = &'a Addr;
|
||||
|
||||
/// Metadata for a dealing for given `EpochId`, submitted by particular `Dealer` for given `DealingIndex`.
|
||||
pub(crate) const DEALINGS_METADATA: Map<(EpochId, Dealer, DealingIndex), DealingMetadata> =
|
||||
Map::new("dealings_metadata");
|
||||
@@ -180,7 +179,7 @@ impl StoredDealing {
|
||||
pub(crate) fn unchecked_all_entries(
|
||||
storage: &dyn Storage,
|
||||
) -> Vec<(
|
||||
(EpochId, Addr, (DealingIndex, ChunkIndex)),
|
||||
(EpochId, cosmwasm_std::Addr, (DealingIndex, ChunkIndex)),
|
||||
PartialContractDealingData,
|
||||
)> {
|
||||
use cw_storage_plus::KeyDeserialize;
|
||||
@@ -210,6 +209,7 @@ impl StoredDealing {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::support::tests::helpers::init_contract;
|
||||
use cosmwasm_std::Addr;
|
||||
use cw_storage_plus::Bound;
|
||||
use std::collections::HashMap;
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::dealers::storage as dealers_storage;
|
||||
use crate::dealers::storage::ensure_dealer;
|
||||
use crate::dealings::storage::{
|
||||
metadata_exists, must_read_metadata, store_metadata, StoredDealing,
|
||||
};
|
||||
use crate::epoch_state::storage::{CURRENT_EPOCH, INITIAL_REPLACEMENT_DATA};
|
||||
use crate::epoch_state::storage::CURRENT_EPOCH;
|
||||
use crate::epoch_state::utils::check_epoch_state;
|
||||
use crate::error::ContractError;
|
||||
use crate::state::storage::STATE;
|
||||
@@ -13,31 +13,25 @@ use cosmwasm_std::{Addr, DepsMut, Env, MessageInfo, Response, Storage};
|
||||
use nym_coconut_dkg_common::dealing::{
|
||||
DealingChunkInfo, DealingMetadata, PartialContractDealing, MAX_DEALING_CHUNKS,
|
||||
};
|
||||
use nym_coconut_dkg_common::types::{ChunkIndex, DealingIndex, EpochState};
|
||||
use nym_coconut_dkg_common::types::{ChunkIndex, DealingIndex, EpochId, EpochState};
|
||||
|
||||
// make sure the epoch is in the dealing exchange and the message sender is a valid dealer for this epoch
|
||||
fn ensure_permission(
|
||||
storage: &dyn Storage,
|
||||
sender: &Addr,
|
||||
current_epoch_id: EpochId,
|
||||
resharing: bool,
|
||||
) -> Result<(), ContractError> {
|
||||
check_epoch_state(storage, EpochState::DealingExchange { resharing })?;
|
||||
|
||||
// ensure the sender is a dealer
|
||||
if dealers_storage::current_dealers()
|
||||
.may_load(storage, sender)?
|
||||
.is_none()
|
||||
{
|
||||
return Err(ContractError::NotADealer);
|
||||
}
|
||||
if resharing
|
||||
&& !INITIAL_REPLACEMENT_DATA
|
||||
.load(storage)?
|
||||
.initial_dealers
|
||||
.contains(sender)
|
||||
{
|
||||
return Err(ContractError::NotAnInitialDealer);
|
||||
// ensure the sender is a dealer for this epoch
|
||||
ensure_dealer(storage, sender, current_epoch_id)?;
|
||||
|
||||
// if we're in resharing, make sure this sender has also been a dealer in the previous epoch
|
||||
if resharing {
|
||||
ensure_dealer(storage, sender, current_epoch_id.saturating_sub(1))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -48,10 +42,10 @@ pub fn try_submit_dealings_metadata(
|
||||
chunks: Vec<DealingChunkInfo>,
|
||||
resharing: bool,
|
||||
) -> Result<Response, ContractError> {
|
||||
ensure_permission(deps.storage, &info.sender, resharing)?;
|
||||
|
||||
let state = STATE.load(deps.storage)?;
|
||||
let epoch = CURRENT_EPOCH.load(deps.storage)?;
|
||||
let state = STATE.load(deps.storage)?;
|
||||
|
||||
ensure_permission(deps.storage, &info.sender, epoch.epoch_id, resharing)?;
|
||||
|
||||
// don't allow overwriting existing metadata
|
||||
if metadata_exists(deps.storage, epoch.epoch_id, &info.sender, dealing_index) {
|
||||
@@ -139,11 +133,11 @@ pub fn try_commit_dealings_chunk(
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
chunk: PartialContractDealing,
|
||||
resharing: bool,
|
||||
) -> Result<Response, ContractError> {
|
||||
ensure_permission(deps.storage, &info.sender, resharing)?;
|
||||
// note: checking permissions is implicit as if the metadata exists,
|
||||
// the sender must have been allowed to submit it
|
||||
|
||||
let epoch = CURRENT_EPOCH.load(deps.storage)?;
|
||||
let mut epoch = CURRENT_EPOCH.load(deps.storage)?;
|
||||
|
||||
// read meta
|
||||
let mut metadata = must_read_metadata(
|
||||
@@ -199,6 +193,13 @@ pub fn try_commit_dealings_chunk(
|
||||
// store the dealing
|
||||
StoredDealing::save(deps.storage, epoch.epoch_id, &info.sender, chunk);
|
||||
|
||||
// this is less than ideal since we have to iterate through all the chunks, but realistically,
|
||||
// there won't be a lot of them
|
||||
if metadata.is_complete() {
|
||||
epoch.state_progress.submitted_dealings += 1;
|
||||
CURRENT_EPOCH.save(deps.storage, &epoch)?;
|
||||
}
|
||||
|
||||
Ok(Response::new())
|
||||
}
|
||||
|
||||
@@ -206,18 +207,14 @@ pub fn try_commit_dealings_chunk(
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::epoch_state::storage::CURRENT_EPOCH;
|
||||
use crate::epoch_state::transactions::{advance_epoch_state, try_initiate_dkg};
|
||||
use crate::support::tests::fixtures::{
|
||||
dealer_details_fixture, dealing_metadata_fixture, partial_dealing_fixture,
|
||||
};
|
||||
use crate::epoch_state::transactions::{try_advance_epoch_state, try_initiate_dkg};
|
||||
use crate::support::tests::fixtures::{dealing_metadata_fixture, partial_dealing_fixture};
|
||||
use crate::support::tests::helpers;
|
||||
use crate::support::tests::helpers::{add_fixture_dealer, ADMIN_ADDRESS};
|
||||
use crate::support::tests::helpers::{add_current_dealer, re_register_dealer, ADMIN_ADDRESS};
|
||||
use cosmwasm_std::testing::{mock_env, mock_info};
|
||||
use cosmwasm_std::Addr;
|
||||
use nym_coconut_dkg_common::dealer::DealerDetails;
|
||||
use nym_coconut_dkg_common::types::{
|
||||
ContractSafeBytes, InitialReplacementData, TimeConfiguration,
|
||||
};
|
||||
use nym_coconut_dkg_common::types::{ContractSafeBytes, TimeConfiguration};
|
||||
|
||||
#[test]
|
||||
fn invalid_commit_dealing_chunk() {
|
||||
@@ -227,41 +224,27 @@ pub(crate) mod tests {
|
||||
|
||||
let owner = Addr::unchecked("owner1");
|
||||
let info = mock_info(owner.as_str(), &[]);
|
||||
let dealing = partial_dealing_fixture();
|
||||
let chunk = partial_dealing_fixture();
|
||||
|
||||
let ret = try_commit_dealings_chunk(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
info.clone(),
|
||||
dealing.clone(),
|
||||
false,
|
||||
)
|
||||
.unwrap_err();
|
||||
// no dealing metadata
|
||||
let ret =
|
||||
try_commit_dealings_chunk(deps.as_mut(), env.clone(), info.clone(), chunk.clone())
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
ret,
|
||||
ContractError::IncorrectEpochState {
|
||||
current_state: EpochState::PublicKeySubmission { resharing: false }.to_string(),
|
||||
expected_state: EpochState::DealingExchange { resharing: false }.to_string()
|
||||
ContractError::UnavailableDealingMetadata {
|
||||
epoch_id: 0,
|
||||
dealer: info.sender.clone(),
|
||||
dealing_index: chunk.dealing_index,
|
||||
}
|
||||
);
|
||||
|
||||
// add dealing metadata
|
||||
env.block.time = env
|
||||
.block
|
||||
.time
|
||||
.plus_seconds(TimeConfiguration::default().public_key_submission_time_secs);
|
||||
add_fixture_dealer(deps.as_mut());
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
|
||||
let ret = try_commit_dealings_chunk(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
info.clone(),
|
||||
dealing.clone(),
|
||||
false,
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(ret, ContractError::NotADealer);
|
||||
|
||||
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
let dealer_details = DealerDetails {
|
||||
address: owner.clone(),
|
||||
bte_public_key_with_proof: String::new(),
|
||||
@@ -269,60 +252,12 @@ pub(crate) mod tests {
|
||||
announce_address: String::new(),
|
||||
assigned_index: 1,
|
||||
};
|
||||
dealers_storage::current_dealers()
|
||||
.save(deps.as_mut().storage, &owner, &dealer_details)
|
||||
.unwrap();
|
||||
add_current_dealer(deps.as_mut(), &dealer_details);
|
||||
|
||||
// assume we're in resharing mode
|
||||
CURRENT_EPOCH
|
||||
.update::<_, ContractError>(deps.as_mut().storage, |mut epoch| {
|
||||
epoch.state = EpochState::DealingExchange { resharing: true };
|
||||
Ok(epoch)
|
||||
})
|
||||
.unwrap();
|
||||
INITIAL_REPLACEMENT_DATA
|
||||
.save(
|
||||
deps.as_mut().storage,
|
||||
&InitialReplacementData {
|
||||
initial_dealers: vec![],
|
||||
initial_height: 1,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let ret = try_commit_dealings_chunk(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
info.clone(),
|
||||
dealing.clone(),
|
||||
true,
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(ret, ContractError::NotAnInitialDealer);
|
||||
|
||||
INITIAL_REPLACEMENT_DATA
|
||||
.update::<_, ContractError>(deps.as_mut().storage, |mut data| {
|
||||
data.initial_dealers = vec![dealer_details_fixture(1).address];
|
||||
Ok(data)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// back to 'normal' mode
|
||||
CURRENT_EPOCH
|
||||
.update::<_, ContractError>(deps.as_mut().storage, |mut epoch| {
|
||||
epoch.state = EpochState::DealingExchange { resharing: false };
|
||||
Ok(epoch)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// TODO: test case: no metadata
|
||||
//
|
||||
//
|
||||
|
||||
// add dealing metadata
|
||||
try_submit_dealings_metadata(
|
||||
deps.as_mut(),
|
||||
info.clone(),
|
||||
0,
|
||||
chunk.dealing_index,
|
||||
dealing_metadata_fixture(),
|
||||
false,
|
||||
)
|
||||
@@ -338,7 +273,6 @@ pub(crate) mod tests {
|
||||
chunk_index: 42,
|
||||
data: ContractSafeBytes(vec![1, 2, 3]),
|
||||
},
|
||||
false,
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
@@ -352,24 +286,14 @@ pub(crate) mod tests {
|
||||
);
|
||||
|
||||
// 'good' dealing
|
||||
let ret = try_commit_dealings_chunk(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
info.clone(),
|
||||
dealing.clone(),
|
||||
false,
|
||||
);
|
||||
let ret =
|
||||
try_commit_dealings_chunk(deps.as_mut(), env.clone(), info.clone(), chunk.clone());
|
||||
assert!(ret.is_ok());
|
||||
|
||||
// duplicate dealing
|
||||
let ret = try_commit_dealings_chunk(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
info.clone(),
|
||||
dealing.clone(),
|
||||
false,
|
||||
)
|
||||
.unwrap_err();
|
||||
let ret =
|
||||
try_commit_dealings_chunk(deps.as_mut(), env.clone(), info.clone(), chunk.clone())
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
ret,
|
||||
ContractError::DealingChunkAlreadyCommitted {
|
||||
@@ -388,6 +312,7 @@ pub(crate) mod tests {
|
||||
Ok(epoch)
|
||||
})
|
||||
.unwrap();
|
||||
re_register_dealer(deps.as_mut(), &info.sender);
|
||||
|
||||
try_submit_dealings_metadata(
|
||||
deps.as_mut(),
|
||||
@@ -398,7 +323,7 @@ pub(crate) mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let ret = try_commit_dealings_chunk(deps.as_mut(), env, info, dealing.clone(), false);
|
||||
let ret = try_commit_dealings_chunk(deps.as_mut(), env, info, chunk.clone());
|
||||
assert!(ret.is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user