Compare commits

..

1 Commits

Author SHA1 Message Date
Jon Häggblad f9fb3f6988 Upgrade to latest sqlite everywhere 2023-10-03 11:25:10 +02:00
114 changed files with 5041 additions and 2352 deletions
@@ -20,8 +20,6 @@ jobs:
platform: [ubuntu-20.04]
runs-on: ${{ matrix.platform }}
env:
CARGO_TERM_COLOR: always
steps:
- uses: actions/checkout@v3
@@ -42,8 +42,6 @@ jobs:
platform: [ubuntu-20.04]
runs-on: ${{ matrix.platform }}
env:
CARGO_TERM_COLOR: always
# a push event from the origin repo, or a PR from external repo
if: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name != 'nymtech/nym' }}
steps:
+8 -20
View File
@@ -37,17 +37,10 @@ on:
- 'tools/nym-nr-query/**'
- 'tools/ts-rs-cli/**'
- 'Cargo.toml'
workflow_dispatch:
jobs:
build:
strategy:
fail-fast: false
matrix:
os: [custom-linux, custom-runner-mac-m1]
runs-on: ${{ matrix.os }}
env:
CARGO_TERM_COLOR: always
runs-on: [ self-hosted, custom-linux ]
# Enable sccache via environment variable
# env:
# RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache
@@ -55,7 +48,6 @@ jobs:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools protobuf-compiler
continue-on-error: true
if: matrix.os == 'custom-linux'
- name: Check out repository code
uses: actions/checkout@v2
@@ -78,40 +70,36 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: build
# Enable wireguard by default on linux only
args: --workspace --features wireguard
args: --workspace
- name: Build all examples
if: matrix.os == 'custom-linux'
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace --examples --features wireguard
args: --workspace --examples
- name: Run all tests
if: matrix.os == 'custom-linux'
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --features wireguard
args: --workspace
- name: Run expensive tests
if: (github.ref == 'refs/heads/develop' || github.event.pull_request.base.ref == 'develop' || github.event.pull_request.base.ref == 'master') && matrix.os == 'custom-linux'
if: github.ref == 'refs/heads/develop' || github.event.pull_request.base.ref == 'develop' || github.event.pull_request.base.ref == 'master'
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --features wireguard -- --ignored
args: --workspace -- --ignored
- name: Annotate with clippy checks
if: matrix.os == 'custom-linux'
uses: actions-rs/clippy-check@v1
continue-on-error: true
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --workspace --features wireguard
args: --workspace
- name: Clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: --workspace --all-targets --features wireguard -- -D warnings
args: --workspace --all-targets -- -D warnings
+2 -3
View File
@@ -15,8 +15,6 @@ jobs:
check-schema:
name: Generate and check schema
runs-on: custom-runner-linux
env:
CARGO_TERM_COLOR: always
steps:
- name: Check out repository code
uses: actions/checkout@v2
@@ -26,8 +24,9 @@ jobs:
with:
toolchain: stable
- name: Generate the schema
run: make contract-schema
- name: Check for diff
run: git diff --exit-code -- contracts/**/schema
run: git diff --exit-code -- contracts/*/schema
@@ -22,10 +22,6 @@ jobs:
platform: [ubuntu-20.04]
runs-on: ${{ matrix.platform }}
env:
CARGO_TERM_COLOR: always
# a push event from the origin repo, or a PR from external repo
if: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name != 'nymtech/nym' }}
steps:
- uses: actions/checkout@v3
-2
View File
@@ -27,8 +27,6 @@ jobs:
# since it's going to be compiled into wasm, there's absolutely
# no point in running CI on different OS-es
runs-on: ubuntu-20.04
env:
CARGO_TERM_COLOR: always
needs: matrix_prep
strategy:
fail-fast: false
@@ -25,8 +25,6 @@ on:
jobs:
build:
runs-on: [self-hosted, custom-linux]
env:
CARGO_TERM_COLOR: always
# env:
# RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache
steps:
+1 -3
View File
@@ -17,8 +17,6 @@ on:
jobs:
build:
runs-on: [ self-hosted, custom-linux ]
env:
CARGO_TERM_COLOR: always
# env:
# RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache
steps:
@@ -33,7 +31,7 @@ jobs:
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
toolchain: 1.71.0
override: true
components: rustfmt, clippy
+1 -3
View File
@@ -10,15 +10,13 @@ on:
jobs:
wasm:
runs-on: [custom-runner-linux]
env:
CARGO_TERM_COLOR: always
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v3
with:
node-version: 18
- uses: actions-rs/toolchain@v1
with:
profile: minimal
-2
View File
@@ -12,8 +12,6 @@ jobs:
rust: [stable, beta]
os: [custom-linux, windows10, custom-runner-mac-m1]
runs-on: ${{ matrix.os }}
env:
CARGO_TERM_COLOR: always
continue-on-error: true
steps:
- name: Install Dependencies (Linux)
@@ -14,10 +14,8 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [custom-ubuntu-20.04, macos-latest, windows10]
os: [custom-linux, macos-latest, windows10]
runs-on: ${{ matrix.os }}
env:
CARGO_TERM_COLOR: always
continue-on-error: true
steps:
- name: Check out repository code
@@ -31,7 +29,8 @@ jobs:
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
# There is an issue with 1.72.0 where clippy crashes on nym-wallet-types. Pin to 1.71.0 for now
toolchain: 1.71.0
override: true
components: rustfmt, clippy
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
strategy:
fail-fast: false
matrix:
platform: [custom-ubuntu-20.04]
platform: [custom-runner-linux]
runs-on: ${{ matrix.platform }}
outputs:
@@ -14,7 +14,7 @@ jobs:
strategy:
fail-fast: false
matrix:
platform: [custom-ubuntu-20.04]
platform: [custom-runner-linux]
runs-on: ${{ matrix.platform }}
outputs:
+1 -1
View File
@@ -7,7 +7,7 @@ on:
jobs:
build:
if: ${{ (startsWith(github.ref, 'refs/tags/nym-contracts-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
runs-on: [self-hosted, custom-ubuntu-20.04]
runs-on: [self-hosted, custom-runner-linux]
steps:
- uses: actions/checkout@v2
@@ -14,7 +14,7 @@ jobs:
strategy:
fail-fast: false
matrix:
platform: [custom-ubuntu-20.04]
platform: [custom-runner-linux]
runs-on: ${{ matrix.platform }}
outputs:
@@ -12,7 +12,7 @@ on:
jobs:
build:
name: Build APK
runs-on: custom-ubuntu-20.04
runs-on: custom-runner-linux
env:
ANDROID_HOME: ${{ github.workspace }}/android-sdk
NDK_VERSION: 25.2.9519653
+1 -1
View File
@@ -4,7 +4,7 @@ on:
jobs:
publish:
runs-on: [custom-ubuntu-20.04]
runs-on: [custom-runner-linux]
steps:
- uses: actions/checkout@v2
-25
View File
@@ -3,31 +3,6 @@
Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
- add client registry to Gateway ([#3955])
- add HTTP API to Gateway ([#3955])
- add `/client/<pub-key>`, `clients` and `register` routes to the gateway ([#3955])
## [2023.1-milka] (2023-09-24)
- custom Debug impl for mix::Node and gateway::Node ([#3930])
- added forceTls argument to 'MixFetchOptsSimple' ([#3907])
- Enable loop cover traffic by default in NR ([#3904])
- Fix all the cargo warnings ([#3899])
- [Issue] nym-socks5-client crash on UDP request ([#3898])
- Feature/gateway inbuilt nr ([#3877])
- removed queued mixnet migration that was already run ([#3872])
- [feat] Socks5 and Native client: run with hardcoded topology ([#3866])
- Introduce a local network requester directly inside a gateway ([#3838])
[#3930]: https://github.com/nymtech/nym/pull/3930
[#3907]: https://github.com/nymtech/nym/pull/3907
[#3904]: https://github.com/nymtech/nym/pull/3904
[#3899]: https://github.com/nymtech/nym/pull/3899
[#3898]: https://github.com/nymtech/nym/issues/3898
[#3877]: https://github.com/nymtech/nym/pull/3877
[#3872]: https://github.com/nymtech/nym/pull/3872
[#3866]: https://github.com/nymtech/nym/pull/3866
[#3838]: https://github.com/nymtech/nym/issues/3838
## [v1.1.31-kitkat] (2023-09-12)
Generated
+577 -358
View File
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -160,12 +160,11 @@ rand = "0.8.5"
reqwest = "0.11.18"
serde = "1.0.152"
serde_json = "1.0.91"
sqlx = "0.7.2"
tap = "1.0.1"
tendermint-rpc = "0.32" # same version as used by cosmrs
thiserror = "1.0.38"
tokio = "1.24.1"
tokio-tungstenite = "0.20.1"
tungstenite = { version = "0.20.1", default-features = false }
ts-rs = "7.0.0"
url = "2.4"
zeroize = "1.6.0"
+19 -15
View File
@@ -46,9 +46,21 @@ clippy:
# -----------------------------------------------------------------------------
define add_cargo_workspace
clippy-$(1):
cargo $$($(1)_CLIPPY_TOOLCHAIN) clippy --manifest-path $(2)/Cargo.toml --workspace $(3) -- -D warnings
clippy-extra-$(1):
cargo $$($(1)_CLIPPY_TOOLCHAIN) clippy --manifest-path $(2)/Cargo.toml --workspace --examples --tests -- -D warnings
check-$(1):
cargo check --manifest-path $(2)/Cargo.toml --workspace $(3)
test-$(1):
cargo test --manifest-path $(2)/Cargo.toml --workspace
test-expensive-$(1):
cargo test --manifest-path $(2)/Cargo.toml --workspace -- --ignored
build-$(1):
cargo build --manifest-path $(2)/Cargo.toml --workspace $(3)
@@ -58,27 +70,15 @@ build-extra-$(1):
build-release-$(1):
$(4) cargo $$($(1)_BUILD_RELEASE_TOOLCHAIN) build --manifest-path $(2)/Cargo.toml --workspace --release $(3)
test-$(1):
cargo test --manifest-path $(2)/Cargo.toml --workspace
test-expensive-$(1):
cargo test --manifest-path $(2)/Cargo.toml --workspace -- --ignored
clippy-$(1):
cargo $$($(1)_CLIPPY_TOOLCHAIN) clippy --manifest-path $(2)/Cargo.toml --workspace $(3) -- -D warnings
clippy-extra-$(1):
cargo $$($(1)_CLIPPY_TOOLCHAIN) clippy --manifest-path $(2)/Cargo.toml --workspace --examples --tests -- -D warnings
fmt-$(1):
cargo fmt --manifest-path $(2)/Cargo.toml --all
clippy: clippy-$(1) clippy-extra-$(1)
check: check-$(1)
build: build-$(1) build-extra-$(1)
build-release-all: build-release-$(1)
cargo-test: test-$(1)
cargo-test-expensive: test-expensive-$(1)
clippy: clippy-$(1) clippy-extra-$(1)
build: build-$(1) build-extra-$(1)
build-release-all: build-release-$(1)
fmt: fmt-$(1)
endef
@@ -93,6 +93,10 @@ $(eval $(call add_cargo_workspace,contracts,contracts,--lib --target wasm32-unkn
$(eval $(call add_cargo_workspace,wallet,nym-wallet))
$(eval $(call add_cargo_workspace,connect,nym-connect/desktop))
# OVERRIDE: there is an issue where clippy crashes on nym-wallet-types with the latest
# stable toolchain. So pin to 1.71.0 until that is resolved.
wallet_CLIPPY_TOOLCHAIN := +1.71.0
# OVERRIDE: wasm-opt fails if the binary has been built with the latest rustc.
# Pin to the last working version.
contracts_BUILD_RELEASE_TOOLCHAIN := +1.69.0
+3 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.30"
version = "1.1.29"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
@@ -30,8 +30,8 @@ serde = { workspace = true, features = ["derive"] } # for config serialization/d
serde_json = { workspace = true }
thiserror = { workspace = true }
tap = "1.0.1"
tokio = { workspace = true, features = ["rt-multi-thread", "net", "signal"] } # async runtime
tokio-tungstenite = { workspace = true }
tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal"] } # async runtime
tokio-tungstenite = "0.14" # websocket
## internal
nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.30"
version = "1.1.29"
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"
+1 -6
View File
@@ -64,12 +64,7 @@ pub(crate) struct Init {
nyxd_urls: Option<Vec<url::Url>>,
/// Comma separated list of rest endpoints of the API validators
#[clap(
long,
alias = "api_validators",
value_delimiter = ',',
group = "network"
)]
#[clap(long, alias = "api_validators", value_delimiter = ',')]
// the alias here is included for backwards compatibility (1.1.4 and before)
nym_apis: Option<Vec<url::Url>>,
+4 -4
View File
@@ -24,7 +24,7 @@ sha2 = "0.10.6"
tap = "1.0.1"
thiserror = { workspace = true }
url = { workspace = true, features = ["serde"] }
tungstenite = { workspace = true, default-features = false }
tungstenite = { version = "0.13.0", default-features = false }
tokio = { workspace = true, features = ["macros"]}
time = "0.3.17"
zeroize = { workspace = true }
@@ -54,10 +54,10 @@ workspace = true
features = ["time"]
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-tungstenite]
version = "0.20.1"
version = "0.14"
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
version = "0.6.2"
workspace = true
features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"]
optional = true
@@ -88,7 +88,7 @@ tempfile = "3.1.0"
[build-dependencies]
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
sqlx = { version = "0.6.2", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
[features]
default = []
@@ -47,7 +47,11 @@ impl<T> PersistedGatewayDetails<T> {
pub fn validate(&self, shared_key: Option<&SharedKeys>) -> Result<(), ClientCoreError> {
match self {
PersistedGatewayDetails::Default(details) => {
if !details.verify(shared_key.ok_or(ClientCoreError::UnavailableSharedKey)?) {
if !details.verify(
shared_key
.ok_or(ClientCoreError::UnavailableSharedKey)?
.deref(),
) {
Err(ClientCoreError::MismatchedGatewayDetails {
gateway_id: details.details.gateway_id.clone(),
})
@@ -30,11 +30,11 @@ impl StorageManager {
})?;
}
let mut opts = sqlx::sqlite::SqliteConnectOptions::new()
let opts = sqlx::sqlite::SqliteConnectOptions::new()
.filename(database_path)
.create_if_missing(fresh);
opts.disable_statement_logging();
let opts = opts.disable_statement_logging();
let connection_pool = match sqlx::SqlitePool::connect_with(opts).await {
Ok(pool) => pool,
@@ -199,7 +199,7 @@ fn group_mixnodes_by_country_code(
if let Some(ref location) = m.location {
let country_code = location.two_letter_iso_country_code.clone();
let group_code = CountryGroup::new(country_code.as_str());
let mixnodes = acc.entry(group_code).or_default();
let mixnodes = acc.entry(group_code).or_insert_with(Vec::new);
mixnodes.push(m.mix_id);
}
acc
+2 -2
View File
@@ -31,7 +31,7 @@ serde = { workspace = true, features = ["derive"] }
[dependencies.tungstenite]
workspace = true
version = "0.13"
default-features = false
# non-wasm-only dependencies
@@ -44,7 +44,7 @@ version = "0.1.11"
features = ["net", "sync", "time"]
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-tungstenite]
workspace = true
version = "0.14"
# wasm-only dependencies
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen]
@@ -86,5 +86,4 @@ required-features = ["http-client"]
default = ["http-client"]
http-client = ["cosmrs/rpc", "openssl"]
generate-ts = []
contract-testing = ["nym-mixnet-contract-common/contract-testing"]
@@ -683,14 +683,13 @@ pub trait MixnetSigningClient {
.await
}
#[cfg(feature = "contract-testing")]
#[cfg(feature = "nym_mixnet_contract_common/contract-testing")]
async fn testing_resolve_all_pending_events(
&self,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::TestingResolveAllPendingEvents { limit: None },
MixnetExecuteMsg::TestingResolveAllPendingEvents {},
vec![],
)
.await
@@ -929,8 +928,8 @@ mod tests {
.withdraw_delegator_reward_on_behalf(owner.parse().unwrap(), mix_id, None)
.ignore(),
#[cfg(feature = "contract-testing")]
MixnetExecuteMsg::TestingResolveAllPendingEvents { .. } => {
#[cfg(feature = "nym_mixnet_contract_common/contract-testing")]
MixnetExecuteMsg::TestingResolveAllPendingEvents {} => {
client.testing_resolve_all_pending_events(None).ignore()
}
};
@@ -572,7 +572,7 @@ mod tests {
let env = mock_env();
// epoch just begun
let mut interval = Interval {
let interval = Interval {
id: 0,
epochs_in_interval: 100,
current_epoch_start: OffsetDateTime::from_unix_timestamp(
@@ -586,16 +586,19 @@ mod tests {
assert!(!interval.is_current_epoch_over(&env));
// current time == current epoch start
let mut interval = interval;
interval.current_epoch_start =
OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64).unwrap();
assert!(!interval.is_current_epoch_over(&env));
// epoch HASN'T yet begun (weird edge case, but can happen if we decide to manually adjust things)
let mut interval = interval;
interval.current_epoch_start =
OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64 + 100).unwrap();
assert!(!interval.is_current_epoch_over(&env));
// current_time = EXACTLY end of the epoch
let mut interval = interval;
interval.current_epoch_start =
OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64).unwrap()
- interval.epoch_length;
+2 -2
View File
@@ -13,7 +13,7 @@ thiserror = "1.0"
tokio = { version = "1.24.1", features = ["sync"]}
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
version = "0.5"
workspace = true
features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"]
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio]
@@ -22,5 +22,5 @@ features = [ "rt-multi-thread", "net", "signal", "fs" ]
[build-dependencies]
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] }
@@ -29,11 +29,11 @@ impl PersistentStorage {
database_path.as_ref().as_os_str()
);
let mut opts = sqlx::sqlite::SqliteConnectOptions::new()
let opts = sqlx::sqlite::SqliteConnectOptions::new()
.filename(database_path)
.create_if_missing(true);
opts.disable_statement_logging();
let opts = opts.disable_statement_logging();
let connection_pool = match sqlx::SqlitePool::connect_with(opts).await {
Ok(db) => db,
+1 -1
View File
@@ -80,7 +80,7 @@ pub fn number_of_required_fragments(
let max_linked = linked_fragment_payload_max_len(plaintext_per_fragment);
match set::total_number_of_sets(message_len, plaintext_per_fragment) {
1 => {
n if n == 1 => {
// is if it's a single fragment message
if message_len < max_unlinked {
return (1, max_unlinked - message_len);
+1 -1
View File
@@ -89,7 +89,7 @@ pub enum PacketSize {
impl PartialOrd for PacketSize {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
// order them by actual packet size
Some(self.cmp(other))
self.size().partial_cmp(&other.size())
}
}
@@ -193,7 +193,10 @@ impl Controller {
}
} else if !self.recently_closed.contains(&hdr.connection_id) {
debug!("Received a 'Send' before 'Connect' - going to buffer the data");
let pending = self.pending_messages.entry(hdr.connection_id).or_default();
let pending = self
.pending_messages
.entry(hdr.connection_id)
.or_insert_with(Vec::new);
pending.push(message);
} else if !hdr.local_socket_closed {
error!(
+1 -1
View File
@@ -14,6 +14,6 @@ log = { workspace = true }
reqwest = { workspace = true, features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1"
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "chrono"]}
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "chrono"]}
thiserror = "1"
tokio = { version = "1.24.1", features = [ "time" ] }
+2 -2
View File
@@ -175,8 +175,8 @@ impl WasmStorage {
K: wasm_bindgen::JsCast,
{
match self.key_count(store, key).await? {
0 => Ok(false),
1 => Ok(true),
n if n == 0 => Ok(false),
n if n == 1 => Ok(true),
n => Err(StorageError::DuplicateKey { count: n }),
}
}
+1 -1
View File
@@ -18,7 +18,7 @@ gloo-net = { version = "0.3.1", features = ["websocket"], optional = true }
# we don't want entire tokio-tungstenite, tungstenite itself is just fine - we just want message and error enums
[dependencies.tungstenite]
workspace = true
version = "0.13"
default-features = false
optional = true
+1 -8
View File
@@ -19,16 +19,9 @@ base64 = "0.21.3"
#boringtun = "0.6.0"
boringtun = { git = "https://github.com/cloudflare/boringtun", rev = "e1d6360d6ab4529fc942a078e4c54df107abe2ba" }
bytes = "1.5.0"
dashmap = "5.5.3"
etherparse = "0.13.0"
futures = "0.3.28"
ip_network = "0.4.1"
ip_network_table = "0.2.0"
log.workspace = true
nym-task = { path = "../task" }
tap.workspace = true
thiserror.workspace = true
tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util"] }
[target.'cfg(target_os = "linux")'.dependencies]
tokio-tun = "0.9.0"
tokio = { workspace = true, features = ["rt-multi-thread", "net"]}
+5
View File
@@ -5,6 +5,8 @@ use bytes::Bytes;
#[allow(unused)]
#[derive(Debug, Clone)]
pub enum Event {
/// Dumb event with no data.
Dumb,
/// IP packet received from the WireGuard tunnel that should be passed through to the corresponding virtual device/internet.
/// Original implementation also has protocol here since it understands it, but we'll have to infer it downstream
WgPacket(Bytes),
@@ -15,6 +17,9 @@ pub enum Event {
impl Display for Event {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Event::Dumb => {
write!(f, "Dumb{{}}")
}
Event::WgPacket(data) => {
let size = data.len();
write!(f, "WgPacket{{ size={size} }}")
+125 -33
View File
@@ -1,48 +1,140 @@
#![cfg_attr(not(target_os = "linux"), allow(dead_code))]
use std::{collections::HashMap, net::SocketAddr, sync::Arc};
use base64::{engine::general_purpose, Engine as _};
use boringtun::x25519;
use futures::StreamExt;
use log::{error, info};
use nym_task::TaskClient;
use tap::TapFallible;
use tokio::{net::UdpSocket, sync::mpsc, task::JoinHandle};
use tun::WireGuardTunnel;
use crate::event::Event;
pub use error::WgError;
mod error;
mod event;
mod network_table;
mod platform;
mod setup;
mod udp_listener;
mod wg_tunnel;
mod tun;
// Currently the module related to setting up the virtual network device is platform specific.
#[cfg(target_os = "linux")]
use platform::linux::tun_device;
//const WG_ADDRESS = "0.0.0.0:51820";
const WG_ADDRESS: &str = "0.0.0.0:51822";
#[derive(Clone)]
struct TunTaskTx(tokio::sync::mpsc::UnboundedSender<Vec<u8>>);
// The private key of the listener
// Corresponding public key: "WM8s8bYegwMa0TJ+xIwhk+dImk2IpDUKslDBCZPizlE="
const PRIVATE_KEY: &str = "AEqXrLFT4qjYq3wmX0456iv94uM6nDj5ugp6Jedcflg=";
impl TunTaskTx {
fn send(&self, packet: Vec<u8>) -> Result<(), tokio::sync::mpsc::error::SendError<Vec<u8>>> {
self.0.send(packet)
}
// The public keys of the registered peers (clients)
const PEERS: &[&str; 1] = &[
// Corresponding private key: "ILeN6gEh6vJ3Ju8RJ3HVswz+sPgkcKtAYTqzQRhTtlo="
"NCIhkgiqxFx1ckKl3Zuh595DzIFl8mxju1Vg995EZhI=", // "mxV/mw7WZTe+0Msa0kvJHMHERDA/cSskiZWQce+TdEs=",
];
const MAX_PACKET: usize = 65535;
fn init_static_dev_keys() -> (x25519::StaticSecret, x25519::PublicKey) {
// TODO: this is a temporary solution for development
let static_private_bytes: [u8; 32] = general_purpose::STANDARD
.decode(PRIVATE_KEY)
.unwrap()
.try_into()
.unwrap();
let static_private = x25519::StaticSecret::try_from(static_private_bytes).unwrap();
let static_public = x25519::PublicKey::from(&static_private);
info!(
"wg public key: {}",
general_purpose::STANDARD.encode(static_public)
);
// TODO: A single static public key is used for all peers during development
let peer_static_public_bytes: [u8; 32] = general_purpose::STANDARD
.decode(PEERS[0])
.unwrap()
.try_into()
.unwrap();
let peer_static_public = x25519::PublicKey::try_from(peer_static_public_bytes).unwrap();
(static_private, peer_static_public)
}
#[cfg(target_os = "linux")]
pub async fn start_wireguard(
task_client: nym_task::TaskClient,
fn start_wg_tunnel(
addr: SocketAddr,
udp: Arc<UdpSocket>,
static_private: x25519::StaticSecret,
peer_static_public: x25519::PublicKey,
) -> (JoinHandle<SocketAddr>, mpsc::UnboundedSender<Event>) {
let (mut tunnel, peer_tx) = WireGuardTunnel::new(udp, addr, static_private, peer_static_public);
let join_handle = tokio::spawn(async move {
tunnel.spin_off().await;
addr
});
(join_handle, peer_tx)
}
pub async fn start_wg_listener(
mut task_client: TaskClient,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
use std::sync::Arc;
log::info!("Starting wireguard listener on {}", WG_ADDRESS);
let udp_socket = Arc::new(UdpSocket::bind(WG_ADDRESS).await?);
// The set of active tunnels indexed by the peer's address
let active_peers = Arc::new(udp_listener::ActivePeers::new());
let peers_by_ip = Arc::new(std::sync::Mutex::new(network_table::NetworkTable::new()));
// Setup some static keys for development
let (static_private, peer_static_public) = init_static_dev_keys();
// Start the tun device that is used to relay traffic outbound
let tun_task_tx = tun_device::start_tun_device(peers_by_ip.clone());
tokio::spawn(async move {
// The set of active tunnels indexed by the peer's address
let mut active_peers: HashMap<SocketAddr, mpsc::UnboundedSender<Event>> = HashMap::new();
// Each tunnel is run in its own task, and the task handle is stored here so we can remove
// it from `active_peers` when the tunnel is closed
let mut active_peers_task_handles = futures::stream::FuturesUnordered::new();
let mut buf = [0u8; MAX_PACKET];
// Start the UDP listener that clients connect to
udp_listener::start_udp_listener(tun_task_tx, active_peers, peers_by_ip, task_client).await?;
while !task_client.is_shutdown() {
tokio::select! {
_ = task_client.recv() => {
log::trace!("WireGuard listener: received shutdown");
break;
}
// Handle tunnel closing
Some(addr) = active_peers_task_handles.next() => {
match addr {
Ok(addr) => {
info!("WireGuard listener: closed {addr:?}");
active_peers.remove(&addr);
}
Err(err) => {
error!("WireGuard listener: error receiving shutdown from peer: {err}");
}
}
}
// Handle incoming packets
Ok((len, addr)) = udp_socket.recv_from(&mut buf) => {
log::info!("Received {} bytes from {}", len, addr);
if let Some(peer_tx) = active_peers.get_mut(&addr) {
log::info!("WireGuard listener: received packet from known peer");
peer_tx.send(Event::WgPacket(buf[..len].to_vec().into()))
.tap_err(|err| log::error!("{err}"))
.unwrap();
} else {
log::info!("WireGuard listener: received packet from unknown peer, starting tunnel");
let (join_handle, peer_tx) = start_wg_tunnel(
addr,
udp_socket.clone(),
static_private.clone(),
peer_static_public
);
peer_tx.send(Event::WgPacket(buf[..len].to_vec().into()))
.tap_err(|err| log::error!("{err}"))
.unwrap();
active_peers.insert(addr, peer_tx);
active_peers_task_handles.push(join_handle);
}
}
}
}
log::info!("WireGuard listener: shutting down");
});
Ok(())
}
#[cfg(not(target_os = "linux"))]
pub async fn start_wireguard(
_task_client: nym_task::TaskClient,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
todo!("WireGuard is currently only supported on Linux")
}
-25
View File
@@ -1,25 +0,0 @@
use std::net::IpAddr;
use ip_network::IpNetwork;
use ip_network_table::IpNetworkTable;
#[derive(Default)]
pub(crate) struct NetworkTable<T> {
ips: IpNetworkTable<T>,
}
impl<T> NetworkTable<T> {
pub(crate) fn new() -> Self {
Self {
ips: IpNetworkTable::new(),
}
}
pub fn insert<N: Into<IpNetwork>>(&mut self, network: N, data: T) -> Option<T> {
self.ips.insert(network, data)
}
pub fn longest_match<I: Into<IpAddr>>(&self, ip: I) -> Option<(IpNetwork, &T)> {
self.ips.longest_match(ip)
}
}
@@ -1 +0,0 @@
pub(crate) mod tun_device;
@@ -1,99 +0,0 @@
use std::{net::Ipv4Addr, sync::Arc};
use etherparse::{InternetSlice, SlicedPacket};
use tap::TapFallible;
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
sync::mpsc::{self},
};
use crate::{
event::Event,
setup::{TUN_BASE_NAME, TUN_DEVICE_ADDRESS, TUN_DEVICE_NETMASK},
udp_listener::PeersByIp,
TunTaskTx,
};
fn setup_tokio_tun_device(name: &str, address: Ipv4Addr, netmask: Ipv4Addr) -> tokio_tun::Tun {
log::info!("Creating TUN device with: address={address}, netmask={netmask}");
tokio_tun::Tun::builder()
.name(name)
.tap(false)
.packet_info(false)
.mtu(1350)
.up()
.address(address)
.netmask(netmask)
.try_build()
.expect("Failed to setup tun device, do you have permission?")
}
pub(crate) fn start_tun_device(peers_by_ip: Arc<std::sync::Mutex<PeersByIp>>) -> TunTaskTx {
let tun = setup_tokio_tun_device(
format!("{}%d", TUN_BASE_NAME).as_str(),
TUN_DEVICE_ADDRESS.parse().unwrap(),
TUN_DEVICE_NETMASK.parse().unwrap(),
);
log::info!("Created TUN device: {}", tun.name());
let (mut tun_device_rx, mut tun_device_tx) = tokio::io::split(tun);
// Channels to communicate with the other tasks
let (tun_task_tx, mut tun_task_rx) = mpsc::unbounded_channel::<Vec<u8>>();
let tun_task_tx = TunTaskTx(tun_task_tx);
tokio::spawn(async move {
let mut buf = [0u8; 1024];
loop {
tokio::select! {
// Reading from the TUN device
len = tun_device_rx.read(&mut buf) => match len {
Ok(len) => {
let packet = &buf[..len];
let dst_addr = boringtun::noise::Tunn::dst_address(packet).unwrap();
let headers = SlicedPacket::from_ip(packet).unwrap();
let src_addr = match headers.ip.unwrap() {
InternetSlice::Ipv4(ip, _) => ip.source_addr().to_string(),
InternetSlice::Ipv6(ip, _) => ip.source_addr().to_string(),
};
log::info!("iface: read Packet({src_addr} -> {dst_addr}, {len} bytes)");
// Route packet to the correct peer.
if let Some(peer_tx) = peers_by_ip.lock().unwrap().longest_match(dst_addr).map(|(_, tx)| tx) {
log::info!("Forward packet to wg tunnel");
peer_tx
.send(Event::IpPacket(packet.to_vec().into()))
.tap_err(|err| log::error!("{err}"))
.unwrap();
} else {
log::info!("No peer found, packet dropped");
}
},
Err(err) => {
log::info!("iface: read error: {err}");
break;
}
},
// Writing to the TUN device
Some(data) = tun_task_rx.recv() => {
let headers = SlicedPacket::from_ip(&data).unwrap();
let (source_addr, destination_addr) = match headers.ip.unwrap() {
InternetSlice::Ipv4(ip, _) => (ip.source_addr(), ip.destination_addr()),
InternetSlice::Ipv6(_, _) => unimplemented!(),
};
log::info!(
"iface: write Packet({source_addr} -> {destination_addr}, {} bytes)",
data.len()
);
// log::info!("iface: writing {} bytes", data.len());
tun_device_tx.write_all(&data).await.unwrap();
}
}
}
log::info!("TUN device shutting down");
});
tun_task_tx
}
-2
View File
@@ -1,2 +0,0 @@
#[cfg(target_os = "linux")]
pub(crate) mod linux;
-63
View File
@@ -1,63 +0,0 @@
use std::net::IpAddr;
use base64::{engine::general_purpose, Engine as _};
use boringtun::x25519;
use log::info;
// The wireguard UDP listener
pub const WG_ADDRESS: &str = "0.0.0.0";
pub const WG_PORT: u16 = 51822;
// The interface used to route traffic
pub const TUN_BASE_NAME: &str = "nymtun";
pub const TUN_DEVICE_ADDRESS: &str = "10.0.0.1";
pub const TUN_DEVICE_NETMASK: &str = "255.255.255.0";
// The private key of the listener
// Corresponding public key: "WM8s8bYegwMa0TJ+xIwhk+dImk2IpDUKslDBCZPizlE="
const PRIVATE_KEY: &str = "AEqXrLFT4qjYq3wmX0456iv94uM6nDj5ugp6Jedcflg=";
// The public keys of the registered peer (clients)
// Corresponding private key: "ILeN6gEh6vJ3Ju8RJ3HVswz+sPgkcKtAYTqzQRhTtlo="
const PEER: &str = "NCIhkgiqxFx1ckKl3Zuh595DzIFl8mxju1Vg995EZhI=";
// The AllowedIPs for the connected peer, which is one a single IP and the same as the IP that the
// peer has configured on their side.
const ALLOWED_IPS: &str = "10.0.0.2";
fn decode_base64_key(base64_key: &str) -> [u8; 32] {
general_purpose::STANDARD
.decode(base64_key)
.unwrap()
.try_into()
.unwrap()
}
pub fn server_static_private_key() -> x25519::StaticSecret {
// TODO: this is a temporary solution for development
let static_private_bytes: [u8; 32] = decode_base64_key(PRIVATE_KEY);
let static_private = x25519::StaticSecret::try_from(static_private_bytes).unwrap();
let static_public = x25519::PublicKey::from(&static_private);
info!(
"wg public key: {}",
general_purpose::STANDARD.encode(static_public)
);
static_private
}
pub fn peer_static_public_key() -> x25519::PublicKey {
// A single static public key is used during development
let peer_static_public_bytes: [u8; 32] = decode_base64_key(PEER);
let peer_static_public = x25519::PublicKey::try_from(peer_static_public_bytes).unwrap();
info!(
"peer public key: {}",
general_purpose::STANDARD.encode(peer_static_public)
);
peer_static_public
}
pub fn peer_allowed_ips() -> ip_network::IpNetwork {
let key: IpAddr = ALLOWED_IPS.parse().unwrap();
let cidr = 0u8;
ip_network::IpNetwork::new_truncate(key, cidr).unwrap()
}
@@ -14,22 +14,19 @@ use tokio::{
time::timeout,
};
use crate::{error::WgError, event::Event, network_table::NetworkTable, TunTaskTx};
use crate::{event::Event, WgError};
const MAX_PACKET: usize = 65535;
pub struct WireGuardTunnel {
// Incoming data from the UDP socket received in the main event loop
peer_rx: mpsc::UnboundedReceiver<Event>,
udp_rx: mpsc::UnboundedReceiver<Event>,
// UDP socket used for sending data
udp: Arc<UdpSocket>,
// Peer endpoint
endpoint: Arc<tokio::sync::RwLock<SocketAddr>>,
// AllowedIPs for this peer
allowed_ips: NetworkTable<()>,
addr: SocketAddr,
// `boringtun` tunnel, used for crypto & WG protocol
wg_tunnel: Arc<tokio::sync::Mutex<Tunn>>,
@@ -37,9 +34,6 @@ pub struct WireGuardTunnel {
// Signal close
close_tx: broadcast::Sender<()>,
close_rx: broadcast::Receiver<()>,
// Send data to the task that handles sending data through the tun device
tun_task_tx: TunTaskTx,
}
impl Drop for WireGuardTunnel {
@@ -50,18 +44,16 @@ impl Drop for WireGuardTunnel {
}
impl WireGuardTunnel {
pub(crate) fn new(
fn close(&self) {
let _ = self.close_tx.send(());
}
pub fn new(
udp: Arc<UdpSocket>,
endpoint: SocketAddr,
addr: SocketAddr,
static_private: x25519::StaticSecret,
peer_static_public: x25519::PublicKey,
peer_allowed_ips: ip_network::IpNetwork,
tunnel_tx: TunTaskTx,
) -> (Self, mpsc::UnboundedSender<Event>) {
let local_addr = udp.local_addr().unwrap();
let peer_addr = udp.peer_addr();
log::info!("New wg tunnel: endpoint: {endpoint}, local_addr: {local_addr}, peer_addr: {peer_addr:?}");
let preshared_key = None;
let persistent_keepalive = None;
let index = 0;
@@ -80,30 +72,21 @@ impl WireGuardTunnel {
));
// Channels with incoming data that is received by the main event loop
let (peer_tx, peer_rx) = mpsc::unbounded_channel();
let (udp_tx, udp_rx) = mpsc::unbounded_channel();
// Signal close tunnel
let (close_tx, close_rx) = broadcast::channel(1);
let mut allowed_ips = NetworkTable::new();
allowed_ips.insert(peer_allowed_ips, ());
let tunnel = WireGuardTunnel {
peer_rx,
udp_rx,
udp,
endpoint: Arc::new(tokio::sync::RwLock::new(endpoint)),
allowed_ips,
addr,
wg_tunnel,
close_tx,
close_rx,
tun_task_tx: tunnel_tx,
};
(tunnel, peer_tx)
}
fn close(&self) {
let _ = self.close_tx.send(());
(tunnel, udp_tx)
}
pub async fn spin_off(&mut self) {
@@ -113,9 +96,9 @@ impl WireGuardTunnel {
info!("WireGuard tunnel: received msg to close");
break;
},
packet = self.peer_rx.recv() => match packet {
packet = self.udp_rx.recv() => match packet {
Some(packet) => {
info!("event loop: {packet}");
info!("WireGuard tunnel received: {packet}");
match packet {
Event::WgPacket(data) => {
let _ = self.consume_wg(&data)
@@ -123,6 +106,7 @@ impl WireGuardTunnel {
.tap_err(|err| error!("WireGuard tunnel: consume_wg error: {err}"));
},
Event::IpPacket(data) => self.consume_eth(&data).await,
_ => {},
}
},
None => {
@@ -137,7 +121,7 @@ impl WireGuardTunnel {
},
}
}
info!("WireGuard tunnel ({}): closed", self.endpoint.read().await);
info!("WireGuard tunnel ({}): closed", self.addr);
}
async fn wg_tunnel_lock(&self) -> Result<tokio::sync::MutexGuard<'_, Tunn>, WgError> {
@@ -146,31 +130,21 @@ impl WireGuardTunnel {
.map_err(|_| WgError::UnableToGetTunnel)
}
#[allow(unused)]
async fn set_endpoint(&self, addr: SocketAddr) {
if *self.endpoint.read().await != addr {
log::info!("wg tunnel update endpoint: {addr}");
*self.endpoint.write().await = addr;
}
}
async fn consume_wg(&mut self, data: &[u8]) -> Result<(), WgError> {
async fn consume_wg(&self, data: &[u8]) -> Result<(), WgError> {
let mut send_buf = [0u8; MAX_PACKET];
let mut tunnel = self.wg_tunnel_lock().await?;
match tunnel.decapsulate(None, data, &mut send_buf) {
let mut peer = self.wg_tunnel_lock().await?;
match peer.decapsulate(None, data, &mut send_buf) {
TunnResult::WriteToNetwork(packet) => {
let endpoint = self.endpoint.read().await;
log::info!("udp: send {} bytes to {}", packet.len(), *endpoint);
if let Err(err) = self.udp.send_to(packet, *endpoint).await {
debug!("WireGuard: writing to network");
if let Err(err) = self.udp.send_to(packet, self.addr).await {
error!("Failed to send decapsulation-instructed packet to WireGuard endpoint: {err:?}");
};
// Flush pending queue
loop {
let mut send_buf = [0u8; MAX_PACKET];
match tunnel.decapsulate(None, &[], &mut send_buf) {
match peer.decapsulate(None, &[], &mut send_buf) {
TunnResult::WriteToNetwork(packet) => {
log::info!("udp: send {} bytes to {}", packet.len(), *endpoint);
if let Err(err) = self.udp.send_to(packet, *endpoint).await {
if let Err(err) = self.udp.send_to(packet, self.addr).await {
error!("Failed to send decapsulation-instructed packet to WireGuard endpoint: {err:?}");
break;
};
@@ -181,23 +155,13 @@ impl WireGuardTunnel {
}
}
}
TunnResult::WriteToTunnelV4(packet, addr) => {
// TODO: once the flow is redone, we should add updating the endpoint dynamically
// self.set_endpoint(addr);
if self.allowed_ips.longest_match(addr).is_some() {
self.tun_task_tx.send(packet.to_vec()).unwrap();
} else {
warn!("Packet from {addr} not in allowed_ips");
}
}
TunnResult::WriteToTunnelV6(packet, addr) => {
// TODO: once the flow is redone, we should add updating the endpoint dynamically
// self.set_endpoint(addr);
if self.allowed_ips.longest_match(addr).is_some() {
self.tun_task_tx.send(packet.to_vec()).unwrap();
} else {
warn!("Packet (v6) from {addr} not in allowed_ips");
}
TunnResult::WriteToTunnelV4(packet, _) | TunnResult::WriteToTunnelV6(packet, _) => {
debug!("WireGuard: writing to tunnel");
info!(
"WireGuard endpoint sent IP packet of {} bytes (not yet implemented)",
packet.len()
);
// TODO
}
TunnResult::Done => {
debug!("WireGuard: decapsulate done");
@@ -209,36 +173,9 @@ impl WireGuardTunnel {
Ok(())
}
async fn consume_eth(&self, data: &Bytes) {
info!("consume_eth: raw packet size: {}", data.len());
let encapsulated_packet = self.encapsulate_packet(data).await;
info!(
"consume_eth: after encapsulate: {}",
encapsulated_packet.len()
);
let endpoint = self.endpoint.read().await;
info!("consume_eth: send to {}: {}", *endpoint, data.len());
self.udp
.send_to(&encapsulated_packet, *endpoint)
.await
.unwrap();
}
async fn encapsulate_packet(&self, payload: &[u8]) -> Vec<u8> {
// TODO: use fixed dst and src buffers that we can reuse
let len = 148.max(payload.len() + 32);
let mut dst = vec![0; len];
let mut wg_tunnel = self.wg_tunnel_lock().await.unwrap();
match wg_tunnel.encapsulate(payload, &mut dst) {
TunnResult::WriteToNetwork(packet) => packet.to_vec(),
unexpected => {
error!("{:?}", unexpected);
vec![]
}
}
async fn consume_eth(&self, _data: &Bytes) {
info!("WireGuard tunnel: consume_eth");
todo!();
}
async fn update_wg_timers(&mut self) -> Result<(), WgError> {
@@ -253,15 +190,16 @@ impl WireGuardTunnel {
async fn handle_routine_tun_result<'a: 'async_recursion>(&self, result: TunnResult<'a>) {
match result {
TunnResult::WriteToNetwork(packet) => {
let endpoint = self.endpoint.read().await;
log::info!("routine: write to network: {}: {}", endpoint, packet.len());
if let Err(err) = self.udp.send_to(packet, *endpoint).await {
error!("routine: failed to send packet: {err:?}");
info!(
"Sending routine packet of {} bytes to WireGuard endpoint",
packet.len()
);
if let Err(err) = self.udp.send_to(packet, self.addr).await {
error!("Failed to send routine packet to WireGuard endpoint: {err:?}",);
};
}
TunnResult::Err(WireGuardError::ConnectionExpired) => {
warn!("Wireguard handshake has expired!");
// WIP(JON): consider just closing the tunnel here
let mut buf = vec![0u8; MAX_PACKET];
let Ok(mut peer) = self.wg_tunnel_lock().await else {
warn!("Failed to lock WireGuard peer, closing tunnel");
@@ -281,29 +219,3 @@ impl WireGuardTunnel {
};
}
}
pub(crate) fn start_wg_tunnel(
endpoint: SocketAddr,
udp: Arc<UdpSocket>,
static_private: x25519::StaticSecret,
peer_static_public: x25519::PublicKey,
peer_allowed_ips: ip_network::IpNetwork,
tunnel_tx: TunTaskTx,
) -> (
tokio::task::JoinHandle<SocketAddr>,
mpsc::UnboundedSender<Event>,
) {
let (mut tunnel, peer_tx) = WireGuardTunnel::new(
udp,
endpoint,
static_private,
peer_static_public,
peer_allowed_ips,
tunnel_tx,
);
let join_handle = tokio::spawn(async move {
tunnel.spin_off().await;
endpoint
});
(join_handle, peer_tx)
}
-109
View File
@@ -1,109 +0,0 @@
use std::{net::SocketAddr, sync::Arc};
use dashmap::DashMap;
use futures::StreamExt;
use log::error;
use nym_task::TaskClient;
use tap::TapFallible;
use tokio::{
net::UdpSocket,
sync::mpsc::{self},
};
use crate::{
event::Event,
network_table::NetworkTable,
setup::{self, WG_ADDRESS, WG_PORT},
TunTaskTx,
};
const MAX_PACKET: usize = 65535;
pub(crate) type ActivePeers = DashMap<SocketAddr, mpsc::UnboundedSender<Event>>;
pub(crate) type PeersByIp = NetworkTable<mpsc::UnboundedSender<Event>>;
pub(crate) async fn start_udp_listener(
tun_task_tx: TunTaskTx,
active_peers: Arc<ActivePeers>,
peers_by_ip: Arc<std::sync::Mutex<PeersByIp>>,
mut task_client: TaskClient,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
let wg_address = SocketAddr::new(WG_ADDRESS.parse().unwrap(), WG_PORT);
log::info!("Starting wireguard UDP listener on {wg_address}");
let udp_socket = Arc::new(UdpSocket::bind(wg_address).await?);
// Setup some static keys for development
let static_private = setup::server_static_private_key();
let peer_static_public = setup::peer_static_public_key();
let peer_allowed_ips = setup::peer_allowed_ips();
tokio::spawn(async move {
// Each tunnel is run in its own task, and the task handle is stored here so we can remove
// it from `active_peers` when the tunnel is closed
let mut active_peers_task_handles = futures::stream::FuturesUnordered::new();
let mut buf = [0u8; MAX_PACKET];
while !task_client.is_shutdown() {
tokio::select! {
_ = task_client.recv() => {
log::trace!("WireGuard UDP listener: received shutdown");
break;
}
// Handle tunnel closing
Some(addr) = active_peers_task_handles.next() => {
match addr {
Ok(addr) => {
log::info!("Removing peer: {addr:?}");
active_peers.remove(&addr);
// TODO: remove from peers_by_ip
}
Err(err) => {
error!("WireGuard UDP listener: error receiving shutdown from peer: {err}");
}
}
},
// Handle incoming packets
Ok((len, addr)) = udp_socket.recv_from(&mut buf) => {
log::trace!("udp: received {} bytes from {}", len, addr);
if let Some(peer_tx) = active_peers.get_mut(&addr) {
log::info!("udp: received {len} bytes from {addr} from known peer");
peer_tx.send(Event::WgPacket(buf[..len].to_vec().into()))
.tap_err(|err| log::error!("{err}"))
.unwrap();
} else {
log::info!("udp: received {len} bytes from {addr} from unknown peer, starting tunnel");
// TODO: this is a temporary solution for development since this
// assumes we know the peer_static_public this corresponds to.
// TODO: rework this before production! This is likely not secure!
log::warn!("Assuming peer_static_public is known");
log::warn!("SECURITY: Rework me to do proper handshake before creating the tunnel!");
let (join_handle, peer_tx) = crate::wg_tunnel::start_wg_tunnel(
addr,
udp_socket.clone(),
static_private.clone(),
peer_static_public,
peer_allowed_ips,
tun_task_tx.clone(),
);
peers_by_ip.lock().unwrap().insert(peer_allowed_ips, peer_tx.clone());
peer_tx.send(Event::WgPacket(buf[..len].to_vec().into()))
.tap_err(|err| log::error!("{err}"))
.unwrap();
// WIP(JON): active peers should probably be keyed by peer_static_public
// instead. Does this current setup lead to any issues?
log::info!("Adding peer: {addr}");
active_peers.insert(addr, peer_tx);
active_peers_task_handles.push(join_handle);
}
},
}
}
log::info!("WireGuard listener: shutting down");
});
Ok(())
}
File diff suppressed because it is too large Load Diff
@@ -234,6 +234,67 @@
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"staking"
],
"properties": {
"staking": {
"$ref": "#/definitions/StakingMsg"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"distribution"
],
"properties": {
"distribution": {
"$ref": "#/definitions/DistributionMsg"
}
},
"additionalProperties": false
},
{
"description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)",
"type": "object",
"required": [
"stargate"
],
"properties": {
"stargate": {
"type": "object",
"required": [
"type_url",
"value"
],
"properties": {
"type_url": {
"type": "string"
},
"value": {
"$ref": "#/definitions/Binary"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"ibc"
],
"properties": {
"ibc": {
"$ref": "#/definitions/IbcMsg"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
@@ -245,6 +306,67 @@
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"gov"
],
"properties": {
"gov": {
"$ref": "#/definitions/GovMsg"
}
},
"additionalProperties": false
}
]
},
"DistributionMsg": {
"description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto",
"oneOf": [
{
"description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.",
"type": "object",
"required": [
"set_withdraw_address"
],
"properties": {
"set_withdraw_address": {
"type": "object",
"required": [
"address"
],
"properties": {
"address": {
"description": "The `withdraw_address`",
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.",
"type": "object",
"required": [
"withdraw_delegator_reward"
],
"properties": {
"withdraw_delegator_reward": {
"type": "object",
"required": [
"validator"
],
"properties": {
"validator": {
"description": "The `validator_address`",
"type": "string"
}
}
}
},
"additionalProperties": false
}
]
},
@@ -299,6 +421,196 @@
}
]
},
"GovMsg": {
"description": "This message type allows the contract interact with the [x/gov] module in order to cast votes.\n\n[x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov\n\n## Examples\n\nCast a simple vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); use cosmwasm_std::{GovMsg, VoteOption};\n\n#[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result<Response, StdError> { // ... Ok(Response::new().add_message(GovMsg::Vote { proposal_id: 4, vote: VoteOption::Yes, })) } ```\n\nCast a weighted vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); # #[cfg(feature = \"cosmwasm_1_2\")] use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};\n\n# #[cfg(feature = \"cosmwasm_1_2\")] #[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result<Response, StdError> { // ... Ok(Response::new().add_message(GovMsg::VoteWeighted { proposal_id: 4, options: vec![ WeightedVoteOption { option: VoteOption::Yes, weight: Decimal::percent(65), }, WeightedVoteOption { option: VoteOption::Abstain, weight: Decimal::percent(35), }, ], })) } ```",
"oneOf": [
{
"description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.",
"type": "object",
"required": [
"vote"
],
"properties": {
"vote": {
"type": "object",
"required": [
"proposal_id",
"vote"
],
"properties": {
"proposal_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"vote": {
"description": "The vote option.\n\nThis should be called \"option\" for consistency with Cosmos SDK. Sorry for that. See <https://github.com/CosmWasm/cosmwasm/issues/1571>.",
"allOf": [
{
"$ref": "#/definitions/VoteOption"
}
]
}
}
}
},
"additionalProperties": false
}
]
},
"IbcMsg": {
"description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)",
"oneOf": [
{
"description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.",
"type": "object",
"required": [
"transfer"
],
"properties": {
"transfer": {
"type": "object",
"required": [
"amount",
"channel_id",
"timeout",
"to_address"
],
"properties": {
"amount": {
"description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20",
"allOf": [
{
"$ref": "#/definitions/Coin"
}
]
},
"channel_id": {
"description": "exisiting channel to send the tokens over",
"type": "string"
},
"timeout": {
"description": "when packet times out, measured on remote chain",
"allOf": [
{
"$ref": "#/definitions/IbcTimeout"
}
]
},
"to_address": {
"description": "address on the remote chain to receive these tokens",
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.",
"type": "object",
"required": [
"send_packet"
],
"properties": {
"send_packet": {
"type": "object",
"required": [
"channel_id",
"data",
"timeout"
],
"properties": {
"channel_id": {
"type": "string"
},
"data": {
"$ref": "#/definitions/Binary"
},
"timeout": {
"description": "when packet times out, measured on remote chain",
"allOf": [
{
"$ref": "#/definitions/IbcTimeout"
}
]
}
}
}
},
"additionalProperties": false
},
{
"description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port",
"type": "object",
"required": [
"close_channel"
],
"properties": {
"close_channel": {
"type": "object",
"required": [
"channel_id"
],
"properties": {
"channel_id": {
"type": "string"
}
}
}
},
"additionalProperties": false
}
]
},
"IbcTimeout": {
"description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.",
"type": "object",
"properties": {
"block": {
"anyOf": [
{
"$ref": "#/definitions/IbcTimeoutBlock"
},
{
"type": "null"
}
]
},
"timestamp": {
"anyOf": [
{
"$ref": "#/definitions/Timestamp"
},
{
"type": "null"
}
]
}
}
},
"IbcTimeoutBlock": {
"description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)",
"type": "object",
"required": [
"height",
"revision"
],
"properties": {
"height": {
"description": "block height after which the packet times out. the height within the given revision",
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"revision": {
"description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)",
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
}
},
"MemberChangedHookMsg": {
"description": "MemberChangedHookMsg should be de/serialized under `MemberChangedHook()` variant in a ExecuteMsg. This contains a list of all diffs on the given transaction.",
"type": "object",
@@ -344,6 +656,90 @@
},
"additionalProperties": false
},
"StakingMsg": {
"description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto",
"oneOf": [
{
"description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.",
"type": "object",
"required": [
"delegate"
],
"properties": {
"delegate": {
"type": "object",
"required": [
"amount",
"validator"
],
"properties": {
"amount": {
"$ref": "#/definitions/Coin"
},
"validator": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.",
"type": "object",
"required": [
"undelegate"
],
"properties": {
"undelegate": {
"type": "object",
"required": [
"amount",
"validator"
],
"properties": {
"amount": {
"$ref": "#/definitions/Coin"
},
"validator": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.",
"type": "object",
"required": [
"redelegate"
],
"properties": {
"redelegate": {
"type": "object",
"required": [
"amount",
"dst_validator",
"src_validator"
],
"properties": {
"amount": {
"$ref": "#/definitions/Coin"
},
"dst_validator": {
"type": "string"
},
"src_validator": {
"type": "string"
}
}
}
},
"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": [
@@ -392,6 +788,15 @@
}
]
},
"VoteOption": {
"type": "string",
"enum": [
"yes",
"no",
"abstain",
"no_with_veto"
]
},
"WasmMsg": {
"description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto",
"oneOf": [
@@ -121,6 +121,67 @@
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"staking"
],
"properties": {
"staking": {
"$ref": "#/definitions/StakingMsg"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"distribution"
],
"properties": {
"distribution": {
"$ref": "#/definitions/DistributionMsg"
}
},
"additionalProperties": false
},
{
"description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)",
"type": "object",
"required": [
"stargate"
],
"properties": {
"stargate": {
"type": "object",
"required": [
"type_url",
"value"
],
"properties": {
"type_url": {
"type": "string"
},
"value": {
"$ref": "#/definitions/Binary"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"ibc"
],
"properties": {
"ibc": {
"$ref": "#/definitions/IbcMsg"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
@@ -132,6 +193,18 @@
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"gov"
],
"properties": {
"gov": {
"$ref": "#/definitions/GovMsg"
}
},
"additionalProperties": false
}
]
},
@@ -199,6 +272,55 @@
},
"additionalProperties": false
},
"DistributionMsg": {
"description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto",
"oneOf": [
{
"description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.",
"type": "object",
"required": [
"set_withdraw_address"
],
"properties": {
"set_withdraw_address": {
"type": "object",
"required": [
"address"
],
"properties": {
"address": {
"description": "The `withdraw_address`",
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.",
"type": "object",
"required": [
"withdraw_delegator_reward"
],
"properties": {
"withdraw_delegator_reward": {
"type": "object",
"required": [
"validator"
],
"properties": {
"validator": {
"description": "The `validator_address`",
"type": "string"
}
}
}
},
"additionalProperties": false
}
]
},
"Empty": {
"description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)",
"type": "object"
@@ -250,6 +372,196 @@
}
]
},
"GovMsg": {
"description": "This message type allows the contract interact with the [x/gov] module in order to cast votes.\n\n[x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov\n\n## Examples\n\nCast a simple vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); use cosmwasm_std::{GovMsg, VoteOption};\n\n#[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result<Response, StdError> { // ... Ok(Response::new().add_message(GovMsg::Vote { proposal_id: 4, vote: VoteOption::Yes, })) } ```\n\nCast a weighted vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); # #[cfg(feature = \"cosmwasm_1_2\")] use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};\n\n# #[cfg(feature = \"cosmwasm_1_2\")] #[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result<Response, StdError> { // ... Ok(Response::new().add_message(GovMsg::VoteWeighted { proposal_id: 4, options: vec![ WeightedVoteOption { option: VoteOption::Yes, weight: Decimal::percent(65), }, WeightedVoteOption { option: VoteOption::Abstain, weight: Decimal::percent(35), }, ], })) } ```",
"oneOf": [
{
"description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.",
"type": "object",
"required": [
"vote"
],
"properties": {
"vote": {
"type": "object",
"required": [
"proposal_id",
"vote"
],
"properties": {
"proposal_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"vote": {
"description": "The vote option.\n\nThis should be called \"option\" for consistency with Cosmos SDK. Sorry for that. See <https://github.com/CosmWasm/cosmwasm/issues/1571>.",
"allOf": [
{
"$ref": "#/definitions/VoteOption"
}
]
}
}
}
},
"additionalProperties": false
}
]
},
"IbcMsg": {
"description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)",
"oneOf": [
{
"description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.",
"type": "object",
"required": [
"transfer"
],
"properties": {
"transfer": {
"type": "object",
"required": [
"amount",
"channel_id",
"timeout",
"to_address"
],
"properties": {
"amount": {
"description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20",
"allOf": [
{
"$ref": "#/definitions/Coin"
}
]
},
"channel_id": {
"description": "exisiting channel to send the tokens over",
"type": "string"
},
"timeout": {
"description": "when packet times out, measured on remote chain",
"allOf": [
{
"$ref": "#/definitions/IbcTimeout"
}
]
},
"to_address": {
"description": "address on the remote chain to receive these tokens",
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.",
"type": "object",
"required": [
"send_packet"
],
"properties": {
"send_packet": {
"type": "object",
"required": [
"channel_id",
"data",
"timeout"
],
"properties": {
"channel_id": {
"type": "string"
},
"data": {
"$ref": "#/definitions/Binary"
},
"timeout": {
"description": "when packet times out, measured on remote chain",
"allOf": [
{
"$ref": "#/definitions/IbcTimeout"
}
]
}
}
}
},
"additionalProperties": false
},
{
"description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port",
"type": "object",
"required": [
"close_channel"
],
"properties": {
"close_channel": {
"type": "object",
"required": [
"channel_id"
],
"properties": {
"channel_id": {
"type": "string"
}
}
}
},
"additionalProperties": false
}
]
},
"IbcTimeout": {
"description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.",
"type": "object",
"properties": {
"block": {
"anyOf": [
{
"$ref": "#/definitions/IbcTimeoutBlock"
},
{
"type": "null"
}
]
},
"timestamp": {
"anyOf": [
{
"$ref": "#/definitions/Timestamp"
},
{
"type": "null"
}
]
}
}
},
"IbcTimeoutBlock": {
"description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)",
"type": "object",
"required": [
"height",
"revision"
],
"properties": {
"height": {
"description": "block height after which the packet times out. the height within the given revision",
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"revision": {
"description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)",
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
}
},
"ProposalResponse_for_Empty": {
"description": "Note, if you are storing custom messages in the proposal, the querier needs to know what possible custom message types those are in order to parse the response",
"type": "object",
@@ -311,6 +623,90 @@
},
"additionalProperties": false
},
"StakingMsg": {
"description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto",
"oneOf": [
{
"description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.",
"type": "object",
"required": [
"delegate"
],
"properties": {
"delegate": {
"type": "object",
"required": [
"amount",
"validator"
],
"properties": {
"amount": {
"$ref": "#/definitions/Coin"
},
"validator": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.",
"type": "object",
"required": [
"undelegate"
],
"properties": {
"undelegate": {
"type": "object",
"required": [
"amount",
"validator"
],
"properties": {
"amount": {
"$ref": "#/definitions/Coin"
},
"validator": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.",
"type": "object",
"required": [
"redelegate"
],
"properties": {
"redelegate": {
"type": "object",
"required": [
"amount",
"dst_validator",
"src_validator"
],
"properties": {
"amount": {
"$ref": "#/definitions/Coin"
},
"dst_validator": {
"type": "string"
},
"src_validator": {
"type": "string"
}
}
}
},
"additionalProperties": false
}
]
},
"Status": {
"oneOf": [
{
@@ -461,6 +857,15 @@
"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"
},
"VoteOption": {
"type": "string",
"enum": [
"yes",
"no",
"abstain",
"no_with_veto"
]
},
"WasmMsg": {
"description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto",
"oneOf": [
@@ -167,6 +167,67 @@
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"staking"
],
"properties": {
"staking": {
"$ref": "#/definitions/StakingMsg"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"distribution"
],
"properties": {
"distribution": {
"$ref": "#/definitions/DistributionMsg"
}
},
"additionalProperties": false
},
{
"description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)",
"type": "object",
"required": [
"stargate"
],
"properties": {
"stargate": {
"type": "object",
"required": [
"type_url",
"value"
],
"properties": {
"type_url": {
"type": "string"
},
"value": {
"$ref": "#/definitions/Binary"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"ibc"
],
"properties": {
"ibc": {
"$ref": "#/definitions/IbcMsg"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
@@ -178,6 +239,18 @@
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"gov"
],
"properties": {
"gov": {
"$ref": "#/definitions/GovMsg"
}
},
"additionalProperties": false
}
]
},
@@ -245,6 +318,55 @@
},
"additionalProperties": false
},
"DistributionMsg": {
"description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto",
"oneOf": [
{
"description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.",
"type": "object",
"required": [
"set_withdraw_address"
],
"properties": {
"set_withdraw_address": {
"type": "object",
"required": [
"address"
],
"properties": {
"address": {
"description": "The `withdraw_address`",
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.",
"type": "object",
"required": [
"withdraw_delegator_reward"
],
"properties": {
"withdraw_delegator_reward": {
"type": "object",
"required": [
"validator"
],
"properties": {
"validator": {
"description": "The `validator_address`",
"type": "string"
}
}
}
},
"additionalProperties": false
}
]
},
"Empty": {
"description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)",
"type": "object"
@@ -296,6 +418,280 @@
}
]
},
"GovMsg": {
"description": "This message type allows the contract interact with the [x/gov] module in order to cast votes.\n\n[x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov\n\n## Examples\n\nCast a simple vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); use cosmwasm_std::{GovMsg, VoteOption};\n\n#[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result<Response, StdError> { // ... Ok(Response::new().add_message(GovMsg::Vote { proposal_id: 4, vote: VoteOption::Yes, })) } ```\n\nCast a weighted vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); # #[cfg(feature = \"cosmwasm_1_2\")] use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};\n\n# #[cfg(feature = \"cosmwasm_1_2\")] #[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result<Response, StdError> { // ... Ok(Response::new().add_message(GovMsg::VoteWeighted { proposal_id: 4, options: vec![ WeightedVoteOption { option: VoteOption::Yes, weight: Decimal::percent(65), }, WeightedVoteOption { option: VoteOption::Abstain, weight: Decimal::percent(35), }, ], })) } ```",
"oneOf": [
{
"description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.",
"type": "object",
"required": [
"vote"
],
"properties": {
"vote": {
"type": "object",
"required": [
"proposal_id",
"vote"
],
"properties": {
"proposal_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"vote": {
"description": "The vote option.\n\nThis should be called \"option\" for consistency with Cosmos SDK. Sorry for that. See <https://github.com/CosmWasm/cosmwasm/issues/1571>.",
"allOf": [
{
"$ref": "#/definitions/VoteOption"
}
]
}
}
}
},
"additionalProperties": false
}
]
},
"IbcMsg": {
"description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)",
"oneOf": [
{
"description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.",
"type": "object",
"required": [
"transfer"
],
"properties": {
"transfer": {
"type": "object",
"required": [
"amount",
"channel_id",
"timeout",
"to_address"
],
"properties": {
"amount": {
"description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20",
"allOf": [
{
"$ref": "#/definitions/Coin"
}
]
},
"channel_id": {
"description": "exisiting channel to send the tokens over",
"type": "string"
},
"timeout": {
"description": "when packet times out, measured on remote chain",
"allOf": [
{
"$ref": "#/definitions/IbcTimeout"
}
]
},
"to_address": {
"description": "address on the remote chain to receive these tokens",
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.",
"type": "object",
"required": [
"send_packet"
],
"properties": {
"send_packet": {
"type": "object",
"required": [
"channel_id",
"data",
"timeout"
],
"properties": {
"channel_id": {
"type": "string"
},
"data": {
"$ref": "#/definitions/Binary"
},
"timeout": {
"description": "when packet times out, measured on remote chain",
"allOf": [
{
"$ref": "#/definitions/IbcTimeout"
}
]
}
}
}
},
"additionalProperties": false
},
{
"description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port",
"type": "object",
"required": [
"close_channel"
],
"properties": {
"close_channel": {
"type": "object",
"required": [
"channel_id"
],
"properties": {
"channel_id": {
"type": "string"
}
}
}
},
"additionalProperties": false
}
]
},
"IbcTimeout": {
"description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.",
"type": "object",
"properties": {
"block": {
"anyOf": [
{
"$ref": "#/definitions/IbcTimeoutBlock"
},
{
"type": "null"
}
]
},
"timestamp": {
"anyOf": [
{
"$ref": "#/definitions/Timestamp"
},
{
"type": "null"
}
]
}
}
},
"IbcTimeoutBlock": {
"description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)",
"type": "object",
"required": [
"height",
"revision"
],
"properties": {
"height": {
"description": "block height after which the packet times out. the height within the given revision",
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"revision": {
"description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)",
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
}
},
"StakingMsg": {
"description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto",
"oneOf": [
{
"description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.",
"type": "object",
"required": [
"delegate"
],
"properties": {
"delegate": {
"type": "object",
"required": [
"amount",
"validator"
],
"properties": {
"amount": {
"$ref": "#/definitions/Coin"
},
"validator": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.",
"type": "object",
"required": [
"undelegate"
],
"properties": {
"undelegate": {
"type": "object",
"required": [
"amount",
"validator"
],
"properties": {
"amount": {
"$ref": "#/definitions/Coin"
},
"validator": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.",
"type": "object",
"required": [
"redelegate"
],
"properties": {
"redelegate": {
"type": "object",
"required": [
"amount",
"dst_validator",
"src_validator"
],
"properties": {
"amount": {
"$ref": "#/definitions/Coin"
},
"dst_validator": {
"type": "string"
},
"src_validator": {
"type": "string"
}
}
}
},
"additionalProperties": false
}
]
},
"Status": {
"oneOf": [
{
@@ -446,6 +842,15 @@
"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"
},
"VoteOption": {
"type": "string",
"enum": [
"yes",
"no",
"abstain",
"no_with_veto"
]
},
"WasmMsg": {
"description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto",
"oneOf": [
@@ -121,6 +121,67 @@
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"staking"
],
"properties": {
"staking": {
"$ref": "#/definitions/StakingMsg"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"distribution"
],
"properties": {
"distribution": {
"$ref": "#/definitions/DistributionMsg"
}
},
"additionalProperties": false
},
{
"description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)",
"type": "object",
"required": [
"stargate"
],
"properties": {
"stargate": {
"type": "object",
"required": [
"type_url",
"value"
],
"properties": {
"type_url": {
"type": "string"
},
"value": {
"$ref": "#/definitions/Binary"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"ibc"
],
"properties": {
"ibc": {
"$ref": "#/definitions/IbcMsg"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
@@ -132,6 +193,18 @@
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"gov"
],
"properties": {
"gov": {
"$ref": "#/definitions/GovMsg"
}
},
"additionalProperties": false
}
]
},
@@ -199,6 +272,55 @@
},
"additionalProperties": false
},
"DistributionMsg": {
"description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto",
"oneOf": [
{
"description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.",
"type": "object",
"required": [
"set_withdraw_address"
],
"properties": {
"set_withdraw_address": {
"type": "object",
"required": [
"address"
],
"properties": {
"address": {
"description": "The `withdraw_address`",
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.",
"type": "object",
"required": [
"withdraw_delegator_reward"
],
"properties": {
"withdraw_delegator_reward": {
"type": "object",
"required": [
"validator"
],
"properties": {
"validator": {
"description": "The `validator_address`",
"type": "string"
}
}
}
},
"additionalProperties": false
}
]
},
"Empty": {
"description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)",
"type": "object"
@@ -250,6 +372,196 @@
}
]
},
"GovMsg": {
"description": "This message type allows the contract interact with the [x/gov] module in order to cast votes.\n\n[x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov\n\n## Examples\n\nCast a simple vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); use cosmwasm_std::{GovMsg, VoteOption};\n\n#[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result<Response, StdError> { // ... Ok(Response::new().add_message(GovMsg::Vote { proposal_id: 4, vote: VoteOption::Yes, })) } ```\n\nCast a weighted vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); # #[cfg(feature = \"cosmwasm_1_2\")] use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};\n\n# #[cfg(feature = \"cosmwasm_1_2\")] #[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result<Response, StdError> { // ... Ok(Response::new().add_message(GovMsg::VoteWeighted { proposal_id: 4, options: vec![ WeightedVoteOption { option: VoteOption::Yes, weight: Decimal::percent(65), }, WeightedVoteOption { option: VoteOption::Abstain, weight: Decimal::percent(35), }, ], })) } ```",
"oneOf": [
{
"description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.",
"type": "object",
"required": [
"vote"
],
"properties": {
"vote": {
"type": "object",
"required": [
"proposal_id",
"vote"
],
"properties": {
"proposal_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"vote": {
"description": "The vote option.\n\nThis should be called \"option\" for consistency with Cosmos SDK. Sorry for that. See <https://github.com/CosmWasm/cosmwasm/issues/1571>.",
"allOf": [
{
"$ref": "#/definitions/VoteOption"
}
]
}
}
}
},
"additionalProperties": false
}
]
},
"IbcMsg": {
"description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)",
"oneOf": [
{
"description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.",
"type": "object",
"required": [
"transfer"
],
"properties": {
"transfer": {
"type": "object",
"required": [
"amount",
"channel_id",
"timeout",
"to_address"
],
"properties": {
"amount": {
"description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20",
"allOf": [
{
"$ref": "#/definitions/Coin"
}
]
},
"channel_id": {
"description": "exisiting channel to send the tokens over",
"type": "string"
},
"timeout": {
"description": "when packet times out, measured on remote chain",
"allOf": [
{
"$ref": "#/definitions/IbcTimeout"
}
]
},
"to_address": {
"description": "address on the remote chain to receive these tokens",
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.",
"type": "object",
"required": [
"send_packet"
],
"properties": {
"send_packet": {
"type": "object",
"required": [
"channel_id",
"data",
"timeout"
],
"properties": {
"channel_id": {
"type": "string"
},
"data": {
"$ref": "#/definitions/Binary"
},
"timeout": {
"description": "when packet times out, measured on remote chain",
"allOf": [
{
"$ref": "#/definitions/IbcTimeout"
}
]
}
}
}
},
"additionalProperties": false
},
{
"description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port",
"type": "object",
"required": [
"close_channel"
],
"properties": {
"close_channel": {
"type": "object",
"required": [
"channel_id"
],
"properties": {
"channel_id": {
"type": "string"
}
}
}
},
"additionalProperties": false
}
]
},
"IbcTimeout": {
"description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.",
"type": "object",
"properties": {
"block": {
"anyOf": [
{
"$ref": "#/definitions/IbcTimeoutBlock"
},
{
"type": "null"
}
]
},
"timestamp": {
"anyOf": [
{
"$ref": "#/definitions/Timestamp"
},
{
"type": "null"
}
]
}
}
},
"IbcTimeoutBlock": {
"description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)",
"type": "object",
"required": [
"height",
"revision"
],
"properties": {
"height": {
"description": "block height after which the packet times out. the height within the given revision",
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"revision": {
"description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)",
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
}
},
"ProposalResponse_for_Empty": {
"description": "Note, if you are storing custom messages in the proposal, the querier needs to know what possible custom message types those are in order to parse the response",
"type": "object",
@@ -311,6 +623,90 @@
},
"additionalProperties": false
},
"StakingMsg": {
"description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto",
"oneOf": [
{
"description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.",
"type": "object",
"required": [
"delegate"
],
"properties": {
"delegate": {
"type": "object",
"required": [
"amount",
"validator"
],
"properties": {
"amount": {
"$ref": "#/definitions/Coin"
},
"validator": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.",
"type": "object",
"required": [
"undelegate"
],
"properties": {
"undelegate": {
"type": "object",
"required": [
"amount",
"validator"
],
"properties": {
"amount": {
"$ref": "#/definitions/Coin"
},
"validator": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.",
"type": "object",
"required": [
"redelegate"
],
"properties": {
"redelegate": {
"type": "object",
"required": [
"amount",
"dst_validator",
"src_validator"
],
"properties": {
"amount": {
"$ref": "#/definitions/Coin"
},
"dst_validator": {
"type": "string"
},
"src_validator": {
"type": "string"
}
}
}
},
"additionalProperties": false
}
]
},
"Status": {
"oneOf": [
{
@@ -461,6 +857,15 @@
"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"
},
"VoteOption": {
"type": "string",
"enum": [
"yes",
"no",
"abstain",
"no_with_veto"
]
},
"WasmMsg": {
"description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto",
"oneOf": [
-2
View File
@@ -51,8 +51,6 @@
# Events
- [Web3Privacy Now](./events/web3-privacy.md)
- [HCPP23-serinko](./events/hcpp23-serinko.md)
- [HCPP23-max](./events/hcpp23-max.md)
@@ -1,38 +0,0 @@
# Web3 Privacy Now - Nym for Ethereum validator privacy
Serinko's presentation on [Web3Privacy Now: Community 1st](https://lu.ma/web3privacynow_rome) introduces ***why network privacy matters*** for ETH 2.0 validators' security and decentralisation.
This page serves as an accessible list of references mentioned during the talk.
## References
### Mixnet architecture
* [Mixnet motivations](https://nymtech.net/developers/infrastructure/nym.html)
* [Mixnet architecture overview](https://nymtech.net/docs/architecture/network-overview.html)
* [Mixnet traffic flow](https://nymtech.net/docs/architecture/traffic-flow.html)
* [Tor + VPN comparison](https://nymtech.net/developers/infrastructure/nym-vs-others.html)
* [Addressing system](https://nymtech.net/docs/clients/addressing-system.html)
### Nym \<\> ETH 2.0
* [Chainsafe Rust libp2p Nym intergration repo](https://github.com/ChainSafe/rust-libp2p-nym)
* [rust-libp2p-nym Transport Specification](https://hackmd.io/@nZ-twauPRISEa6G9zg3XRw/HkE8sHuns)
* [Lighthouse PoC](https://github.com/ChainSafe/lighthouse/blob/nym/USE_NYM.md)
* [Nym \<\> Aztec partnership](https://blog.nymtech.net/nym-partners-with-aztec-to-provide-integral-infrastructure-privacy-in-ethereum-chains-694963c55192)
### Rust Examples
* [Dev tutorial: chain service](https://nymtech.net/developers/tutorials/cosmos-service/intro.html)
### Clients
* [Clients overview](https://nymtech.net/docs/clients/overview.html)
### Nym Docs
* [Nym Developer Portal](https://nymtech.net/developers)
* [Nym Operators Guide](https://nymtech.net/operators)
* [Nym technical Documentation](https://nymtech.net/docs)
@@ -40,7 +40,7 @@ Yes, to run a mix node only is an option. However it will be less rewarded as no
### What are the incentives for the node operator?
In the original setup there were no incentives to run a `network-requester`. After the transition all the users will buy multiple tickets of zkNyms credentials and use those as [anonymous e-cash](https://arxiv.org/abs/2303.08221) to pay for their data traffic ([`Nym API`](https://github.com/nymtech/nym/tree/master/nym-api) will do the do cryptographical checks to prevent double-spending). All collected fees get distributed to all active nodes proportionally to their work by the end of each epoch.
In the original setup there were no incentives to run a `network-requester`. After the transition all the users will buy multiple tickets of zkNyms credentials and use those as [anonymous e-cash](https://arxiv.org/abs/2303.08221) to pay for their data traffic (`[Nym API](https://github.com/nymtech/nym/tree/master/nym-api)` will do the do cryptographical checks to prevent double-spending). All collected fees get distributed to all active nodes proportionally to their work by the end of each epoch.
### How does this change the token economics?
+4 -8
View File
@@ -37,16 +37,14 @@ nym-ephemera-common = { path = "../common/cosmwasm-smart-contracts/ephemera" }
pretty_env_logger = "0.4"
refinery = { version = "0.8.7", features = ["rusqlite"], optional = true }
reqwest = { version = "0.11.6", features = ["json"] }
# Rocksdb kills compilation times and we're not currently using it. The reason
# we comment it out is that rust-analyzer runs with --all-features
#rocksdb = { version = "0.21.0", optional = true }
rusqlite = { version = "0.27.0", features = ["bundled"], optional = true }
rocksdb = { version = "0.21.0", optional = true }
rusqlite = { version = "0.29.0", features = ["bundled"], optional = true }
serde = { version = "1.0", features = ["derive"] }
serde_derive = "1.0.149"
serde_json = "1.0.91"
thiserror = "1.0.37"
tokio = { version = "1", features = ["macros", "net","rt-multi-thread"] }
tokio-tungstenite = { workspace = true }
tokio-tungstenite = "0.18.0"
tokio-util = { version = "0.7.4", features = ["full"] }
toml = "0.7.0"
unsigned-varint = "0.7.1"
@@ -63,7 +61,5 @@ rand = "0.8.5"
[features]
default = ["sqlite_storage"]
# Rocksdb kills compilation times and we're not currently using it. The reason
# we comment it out is that rust-analyzer runs with --all-features
#rocksdb_storage = ["rocksdb"]
rocksdb_storage = ["rocksdb"]
sqlite_storage = ["rusqlite", "refinery"]
+2 -2
View File
@@ -20,7 +20,7 @@ pub(crate) async fn submit_message(
api: web::Data<CommandExecutor>,
) -> HttpResponse {
match api.send_ephemera_message(message.into_inner()).await {
Ok(()) => HttpResponse::Ok().json("Message submitted"),
Ok(_) => HttpResponse::Ok().json("Message submitted"),
Err(err) => {
if let ApiError::DuplicateMessage = err {
debug!("Message already submitted {err:?}");
@@ -53,7 +53,7 @@ pub(crate) async fn store_in_dht(
let value = request.value();
match api.store_in_dht(key, value).await {
Ok(()) => HttpResponse::Ok().json("Store request submitted"),
Ok(_) => HttpResponse::Ok().json("Store request submitted"),
Err(err) => {
error!("Error storing in dht: {}", err);
HttpResponse::InternalServerError().json("Server failed to process request")
+1 -1
View File
@@ -278,7 +278,7 @@ impl BlockManager {
}
match self.message_pool.remove_messages(&block.messages) {
Ok(()) => {
Ok(_) => {
self.block_chain_state
.mark_last_produced_block_as_committed();
}
+4 -4
View File
@@ -132,7 +132,7 @@ impl ApiCmdProcessor {
.send_ephemera_event(EphemeraEvent::StoreInDht { key, value })
.await
{
Ok(()) => Ok(()),
Ok(_) => Ok(()),
Err(err) => {
error!("Error sending StoreInDht to network: {:?}", err);
Err(ApiError::Internal("Failed to store in DHT".to_string()))
@@ -153,7 +153,7 @@ impl ApiCmdProcessor {
.send_ephemera_event(EphemeraEvent::QueryDht { key: key.clone() })
.await
{
Ok(()) => {
Ok(_) => {
//Save the reply channel in a map and send the reply when we get the response from the network
ephemera
.api_cmd_processor
@@ -278,7 +278,7 @@ impl ApiCmdProcessor {
// Send to BlockManager to verify it and put into memory pool
let ephemera_msg: message::EphemeraMessage = (*api_msg).into();
match ephemera.block_manager.on_new_message(ephemera_msg.clone()) {
Ok(()) => {
Ok(_) => {
//Gossip to network for other nodes to receive
match ephemera
.to_network
@@ -287,7 +287,7 @@ impl ApiCmdProcessor {
))
.await
{
Ok(()) => Ok(()),
Ok(_) => Ok(()),
Err(err) => {
error!("Error sending EphemeraMessage to network: {:?}", err);
Err(ApiError::Internal("Failed to submit message".to_string()))
+3 -3
View File
@@ -267,7 +267,7 @@ impl<A: Application> EphemeraStarterWithApplication<A> {
}
ws_stopped = websocket.run() => {
match ws_stopped {
Ok(()) => info!("Websocket stopped unexpectedly"),
Ok(_) => info!("Websocket stopped unexpectedly"),
Err(e) => error!("Websocket stopped with error: {}", e),
}
}
@@ -293,7 +293,7 @@ impl<A: Application> EphemeraStarterWithApplication<A> {
}
http_stopped = http => {
match http_stopped {
Ok(()) => info!("Http server stopped unexpectedly"),
Ok(_) => info!("Http server stopped unexpectedly"),
Err(e) => error!("Http server stopped with error: {}", e),
}
}
@@ -330,7 +330,7 @@ impl<A: Application> EphemeraStarterWithApplication<A> {
}
nw_stopped = network.start() => {
match nw_stopped {
Ok(()) => info!("Network stopped unexpectedly"),
Ok(_) => info!("Network stopped unexpectedly"),
Err(e) => error!("Network stopped with error: {e}",),
}
}
+1 -1
View File
@@ -120,7 +120,7 @@ impl<A: Application> Ephemera<A> {
while !shutdown.is_shutdown() {
tokio::select! {
biased;
() = shutdown.recv() => {
_ = shutdown.recv() => {
trace!("UpdateHandler: Received shutdown");
self.shutdown_manager.stop().await;
break;
+1 -1
View File
@@ -57,7 +57,7 @@ impl ShutdownManager {
.map(|(i, h)| (i + 1, h))
{
match handle.await.unwrap() {
Ok(()) => info!("Task {i} finished successfully"),
Ok(_) => info!("Task {i} finished successfully"),
Err(e) => info!("Task {i} finished with error: {e}",),
}
}
@@ -309,7 +309,7 @@ impl ConnectionHandler for Handler {
match event {
ConnectionEvent::FullyNegotiatedInbound(FullyNegotiatedInbound {
protocol: stream,
info: (),
info: _,
}) => {
if self.inbound_substream_attempts > MAX_SUBSTREAM_ATTEMPTS {
log::warn!("Too many inbound substream attempts, refusing stream");
@@ -320,7 +320,7 @@ impl ConnectionHandler for Handler {
}
ConnectionEvent::FullyNegotiatedOutbound(FullyNegotiatedOutbound {
protocol,
info: (),
info: _,
}) => {
if self.outbound_substream_attempts > MAX_SUBSTREAM_ATTEMPTS {
log::warn!("Too many outbound substream attempts, refusing stream");
+2 -2
View File
@@ -1,12 +1,12 @@
[package]
name = "explorer-api"
version = "1.1.30"
version = "1.1.29"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
chrono = { version = "0.4.31", features = ["serde"] }
chrono = { version = "0.4.19", features = ["serde"] }
clap = { version = "4.0", features = ["cargo", "derive"] }
dotenvy = "0.15.6"
humantime-serde = "1.0"
+7 -30
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-gateway"
version = "1.1.30"
version = "1.1.29"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
@@ -15,13 +15,6 @@ rust-version = "1.56"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
axum = "0.6.20"
sha2 = "0.10.8"
hmac = "0.12.1"
axum-macros = "0.3.8" # Useful for debugging axum Handler trait errors
fastrand = "2"
x25519-dalek = { version = "2.0.0", features = ["static_secrets"] }
base64 = "0.21.4"
anyhow = { workspace = true }
async-trait = { workspace = true }
atty = "0.2"
@@ -41,23 +34,12 @@ pretty_env_logger = "0.4"
rand = "0.7"
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
sqlx = { version = "0.5", features = [
"runtime-tokio-rustls",
"sqlite",
"macros",
"migrate",
] }
sqlx = { workspace = true, features = [ "runtime-tokio-rustls", "sqlite", "macros", "migrate", ] }
subtle-encoding = { version = "0.5", features = ["bech32-preview"] }
thiserror = "1"
tokio = { workspace = true, features = [
"rt-multi-thread",
"net",
"signal",
"fs",
"time",
] }
tokio = { workspace = true, features = [ "rt-multi-thread", "net", "signal", "fs", "time" ] }
tokio-stream = { version = "0.1.11", features = ["fs"] }
tokio-tungstenite = { version = "0.20.1" }
tokio-tungstenite = "0.14"
tokio-util = { version = "0.7.4", features = ["codec"] }
url = { version = "2.2", features = ["serde"] }
zeroize = { workspace = true }
@@ -80,16 +62,11 @@ nym-statistics-common = { path = "../common/statistics" }
nym-task = { path = "../common/task" }
nym-types = { path = "../common/types" }
nym-validator-client = { path = "../common/client-libs/validator-client" }
nym-wireguard = { path = "../common/wireguard", optional = true }
[dev-dependencies]
tower = "0.4.13"
rand = "0.8.5"
hyper = "0.14.27"
nym-wireguard = { path = "../common/wireguard" }
[build-dependencies]
tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] }
sqlx = { version = "0.5", features = [
sqlx = { workspace = true, features = [
"runtime-tokio-rustls",
"sqlite",
"macros",
@@ -97,4 +74,4 @@ sqlx = { version = "0.5", features = [
] }
[features]
wireguard = ["nym-wireguard"]
wireguard = []
+1 -1
View File
@@ -28,6 +28,6 @@ nym-coconut-interface = { path = "../../common/coconut-interface" }
nym-credentials = { path = "../../common/credentials" }
[dependencies.tungstenite]
workspace = true
version = "0.13.0"
default-features = false
-10
View File
@@ -106,16 +106,6 @@ pub(crate) enum GatewayError {
#[from]
source: NyxdError,
},
#[error("Error verifying hmac digest")]
HmacDigestError {
#[from]
source: hmac::digest::MacError,
},
#[error("Invalid hmac length")]
HmacInvalidLength {
#[from]
source: hmac::digest::InvalidLength,
},
}
impl From<ClientCoreError> for GatewayError {
@@ -1,181 +0,0 @@
use std::{
collections::HashMap,
fmt,
hash::{Hash, Hasher},
net::SocketAddr,
ops::Deref,
str::FromStr,
};
use base64::{engine::general_purpose, Engine};
use hmac::{Hmac, Mac};
use nym_crypto::asymmetric::encryption::PrivateKey;
use serde::{Deserialize, Serialize};
use sha2::Sha256;
use x25519_dalek::StaticSecret;
use crate::error::GatewayError;
#[derive(Debug, Clone, Deserialize, Serialize)]
pub(crate) enum ClientMessage {
Init(InitMessage),
Final(Client),
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub(crate) struct InitMessage {
pub_key: ClientPublicKey,
}
impl InitMessage {
pub fn pub_key(&self) -> &ClientPublicKey {
&self.pub_key
}
#[allow(dead_code)]
pub fn new(pub_key: ClientPublicKey) -> Self {
InitMessage { pub_key }
}
}
// Client that wants to register sends its PublicKey and SocketAddr bytes mac digest encrypted with a DH shared secret.
// Gateway can then verify pub_key payload using the sme process
#[derive(Debug, Clone, Deserialize, Serialize)]
pub(crate) struct Client {
// base64 encoded public key, using x25519-dalek for impl
pub(crate) pub_key: ClientPublicKey,
pub(crate) socket: SocketAddr,
pub(crate) mac: ClientMac,
}
pub type HmacSha256 = Hmac<Sha256>;
impl Client {
// Reusable secret should be gateways Wireguard PK
// Client should perform this step when generating its payload, using its own WG PK
pub fn verify(&self, gateway_key: &PrivateKey, nonce: u64) -> Result<(), GatewayError> {
#[allow(clippy::expect_used)]
let static_secret =
StaticSecret::try_from(gateway_key.to_bytes()).expect("This is infalliable");
let dh = static_secret.diffie_hellman(&self.pub_key);
let mut mac = HmacSha256::new_from_slice(dh.as_bytes())?;
mac.update(self.pub_key.as_bytes());
mac.update(self.socket.ip().to_string().as_bytes());
mac.update(self.socket.port().to_string().as_bytes());
mac.update(&nonce.to_le_bytes());
Ok(mac.verify_slice(&self.mac)?)
}
pub fn pub_key(&self) -> &ClientPublicKey {
&self.pub_key
}
pub fn socket(&self) -> SocketAddr {
self.socket
}
}
// This should go into nym-wireguard crate
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ClientPublicKey(x25519_dalek::PublicKey);
#[derive(Debug, Clone)]
pub(crate) struct ClientMac(Vec<u8>);
impl ClientMac {
#[allow(dead_code)]
pub fn new(mac: Vec<u8>) -> Self {
ClientMac(mac)
}
}
impl Deref for ClientMac {
type Target = Vec<u8>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl fmt::Display for ClientPublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", general_purpose::STANDARD.encode(self.0.as_bytes()))
}
}
impl Hash for ClientPublicKey {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.as_bytes().hash(state)
}
}
impl FromStr for ClientMac {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mac_bytes: Vec<u8> = general_purpose::STANDARD
.decode(s)
.map_err(|_| "Could not base64 decode public key mac representation".to_string())?;
Ok(ClientMac(mac_bytes))
}
}
impl Serialize for ClientMac {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let encoded_key = general_purpose::STANDARD.encode(self.0.clone());
serializer.serialize_str(&encoded_key)
}
}
impl<'de> Deserialize<'de> for ClientMac {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let encoded_key = String::deserialize(deserializer)?;
ClientMac::from_str(&encoded_key).map_err(serde::de::Error::custom)
}
}
impl ClientPublicKey {
#[allow(dead_code)]
pub fn new(key: x25519_dalek::PublicKey) -> Self {
ClientPublicKey(key)
}
pub fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
}
impl Deref for ClientPublicKey {
type Target = x25519_dalek::PublicKey;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl FromStr for ClientPublicKey {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let key_bytes: [u8; 32] = general_purpose::STANDARD
.decode(s)
.map_err(|_| "Could not base64 decode public key representation".to_string())?
.try_into()
.map_err(|_| "Invalid key length".to_string())?;
Ok(ClientPublicKey(x25519_dalek::PublicKey::from(key_bytes)))
}
}
impl Serialize for ClientPublicKey {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let encoded_key = general_purpose::STANDARD.encode(self.0.as_bytes());
serializer.serialize_str(&encoded_key)
}
}
impl<'de> Deserialize<'de> for ClientPublicKey {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let encoded_key = String::deserialize(deserializer)?;
Ok(ClientPublicKey::from_str(&encoded_key).map_err(serde::de::Error::custom))?
}
}
pub(crate) type ClientRegistry = HashMap<SocketAddr, Client>;
-1
View File
@@ -3,7 +3,6 @@
pub(crate) mod active_clients;
mod bandwidth;
pub(crate) mod client_registration;
pub(crate) mod embedded_network_requester;
pub(crate) mod websocket;
@@ -91,7 +91,7 @@ impl CoconutVerifier {
let req = nym_api_requests::coconut::VerifyCredentialBody::new(
credential.clone(),
proposal_id,
self.nyxd_client.address(),
self.nyxd_client.address().clone(),
);
for client in api_clients {
self.nyxd_client
@@ -19,11 +19,11 @@ mod authenticated;
pub(crate) mod coconut;
mod fresh;
// TODO: note for my future self to consider the following idea:
// split the socket connection into sink and stream
// stream will be for reading explicit requests
// and sink for pumping responses AND mix traffic
// but as byproduct this might (or might not) break the clean "SocketStream" enum here
//// TODO: note for my future self to consider the following idea:
//// split the socket connection into sink and stream
//// stream will be for reading explicit requests
//// and sink for pumping responses AND mix traffic
//// but as byproduct this might (or might not) break the clean "SocketStream" enum here
pub(crate) enum SocketStream<S> {
RawTcp(S),
-1
View File
@@ -1 +0,0 @@
pub(crate) mod v1;
@@ -1,105 +0,0 @@
use axum::extract::Path;
use std::sync::Arc;
use axum::http::StatusCode;
use axum::{extract::State, Json};
use std::str::FromStr;
// use axum_macros::debug_handler;
use crate::node::client_handling::client_registration::{
Client, ClientMessage, ClientPublicKey, InitMessage,
};
use crate::node::http::ApiState;
async fn process_final_message(client: Client, state: Arc<ApiState>) -> StatusCode {
let preshared_nonce = {
let in_progress_ro = state.registration_in_progress.read().await;
if let Some(nonce) = in_progress_ro.get(client.pub_key()) {
*nonce
} else {
return StatusCode::BAD_REQUEST;
}
};
if client
.verify(state.sphinx_key_pair.private_key(), preshared_nonce)
.is_ok()
{
{
let mut in_progress_rw = state.registration_in_progress.write().await;
in_progress_rw.remove(client.pub_key());
}
{
let mut registry_rw = state.client_registry.write().await;
registry_rw.insert(client.socket(), client);
}
return StatusCode::OK;
}
StatusCode::BAD_REQUEST
}
async fn process_init_message(init_message: InitMessage, state: Arc<ApiState>) -> u64 {
let nonce: u64 = fastrand::u64(..);
let mut registry_rw = state.registration_in_progress.write().await;
registry_rw.insert(init_message.pub_key().clone(), nonce);
nonce
}
// #[debug_handler]
pub(crate) async fn register_client(
State(state): State<Arc<ApiState>>,
Json(payload): Json<ClientMessage>,
) -> (StatusCode, Json<Option<u64>>) {
match payload {
ClientMessage::Init(i) => (
StatusCode::OK,
Json(Some(process_init_message(i, Arc::clone(&state)).await)),
),
ClientMessage::Final(client) => (
process_final_message(client, Arc::clone(&state)).await,
Json(None),
),
}
}
pub(crate) async fn get_all_clients(
State(state): State<Arc<ApiState>>,
) -> (StatusCode, Json<Vec<ClientPublicKey>>) {
let registry_ro = state.client_registry.read().await;
(
StatusCode::OK,
Json(
registry_ro
.values()
.map(|c| c.pub_key().clone())
.collect::<Vec<ClientPublicKey>>(),
),
)
}
pub(crate) async fn get_client(
Path(pub_key): Path<String>,
State(state): State<Arc<ApiState>>,
) -> (StatusCode, Json<Vec<Client>>) {
let pub_key = match ClientPublicKey::from_str(&pub_key) {
Ok(pub_key) => pub_key,
Err(_) => return (StatusCode::BAD_REQUEST, Json(vec![])),
};
let registry_ro = state.client_registry.read().await;
let clients = registry_ro
.iter()
.filter_map(|(_, c)| {
if c.pub_key() == &pub_key {
Some(c.clone())
} else {
None
}
})
.collect::<Vec<Client>>();
if clients.is_empty() {
return (StatusCode::NOT_FOUND, Json(clients));
}
(StatusCode::OK, Json(clients))
}
-1
View File
@@ -1 +0,0 @@
pub(crate) mod client_registry;
-206
View File
@@ -1,206 +0,0 @@
use std::{collections::HashMap, sync::Arc};
use axum::{
routing::{get, post},
Router,
};
use log::info;
use nym_crypto::asymmetric::encryption;
use tokio::sync::RwLock;
mod api;
use api::v1::client_registry::*;
use super::client_handling::client_registration::{ClientPublicKey, ClientRegistry};
const ROUTE_PREFIX: &str = "/api/v1/gateway/client-interfaces/wireguard";
pub struct ApiState {
client_registry: Arc<RwLock<ClientRegistry>>,
sphinx_key_pair: Arc<encryption::KeyPair>,
registration_in_progress: Arc<RwLock<HashMap<ClientPublicKey, u64>>>,
}
fn router_with_state(state: Arc<ApiState>) -> Router {
Router::new()
.route(&format!("{}/clients", ROUTE_PREFIX), get(get_all_clients))
.route(&format!("{}/client", ROUTE_PREFIX), post(register_client))
.route(
&format!("{}/client/:pub_key", ROUTE_PREFIX),
get(get_client),
)
.with_state(state)
}
pub(crate) async fn start_http_api(
client_registry: Arc<RwLock<ClientRegistry>>,
sphinx_key_pair: Arc<encryption::KeyPair>,
) {
// Port should be 80 post smoosh
let port = 8000;
info!("Started HTTP API on port {}", port);
let client_registry = Arc::clone(&client_registry);
let state = Arc::new(ApiState {
client_registry,
sphinx_key_pair,
registration_in_progress: Arc::new(RwLock::new(HashMap::new())),
});
let routes = router_with_state(state);
#[allow(clippy::unwrap_used)]
axum::Server::bind(&format!("0.0.0.0:{}", port).parse().unwrap())
.serve(routes.into_make_service())
.await
.unwrap();
}
#[cfg(test)]
mod test {
use std::net::SocketAddr;
use std::str::FromStr;
use std::{collections::HashMap, sync::Arc};
use axum::body::Body;
use axum::http::Request;
use axum::http::StatusCode;
use hmac::Mac;
use tower::Service;
use tower::ServiceExt;
use nym_crypto::asymmetric::encryption;
use tokio::sync::RwLock;
use x25519_dalek::{PublicKey, StaticSecret};
use crate::node::client_handling::client_registration::{
Client, ClientMac, ClientMessage, InitMessage,
};
use crate::node::client_handling::client_registration::{ClientPublicKey, HmacSha256};
use crate::node::http::{router_with_state, ApiState, ROUTE_PREFIX};
#[tokio::test]
async fn registration() {
// 1. Provision random keys for gateway and client
// 2. Generate DH shared secret
// 3. Client submits its public key to the gateway to start the handshake process, gateway responds with nonce
// 4. Client generates mac digest using DH shared secret, its own public key, socket address and port, and nonce
// 5. Client sends its public key, socket address and port, nonce and mac digest to the gateway
// 6. Gateway verifies mac digest and nonce, and stores client's public key and socket address and port
let mut rng = rand::thread_rng();
let gateway_key_pair = encryption::KeyPair::new(&mut rng);
let client_key_pair = encryption::KeyPair::new(&mut rng);
let gateway_static_public =
PublicKey::try_from(gateway_key_pair.public_key().to_bytes()).unwrap();
let client_static_private =
StaticSecret::try_from(client_key_pair.private_key().to_bytes()).unwrap();
let client_static_public =
PublicKey::try_from(client_key_pair.public_key().to_bytes()).unwrap();
let client_dh = client_static_private.diffie_hellman(&gateway_static_public);
let registration_in_progress = Arc::new(RwLock::new(HashMap::new()));
let client_registry = Arc::new(RwLock::new(HashMap::new()));
let state = Arc::new(ApiState {
client_registry: Arc::clone(&client_registry),
sphinx_key_pair: Arc::new(gateway_key_pair),
registration_in_progress: Arc::clone(&registration_in_progress),
});
// `Router` implements `tower::Service<Request<Body>>` so we can
// call it like any tower service, no need to run an HTTP server.
let mut app = router_with_state(state);
let init_message =
ClientMessage::Init(InitMessage::new(ClientPublicKey::new(client_static_public)));
let init_request = Request::builder()
.method("POST")
.uri(format!("{}/client", ROUTE_PREFIX))
.header("Content-type", "application/json")
.body(Body::from(serde_json::to_vec(&init_message).unwrap()))
.unwrap();
let response = ServiceExt::<Request<Body>>::ready(&mut app)
.await
.unwrap()
.call(init_request)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
assert!(!registration_in_progress.read().await.is_empty());
let nonce: Option<u64> =
serde_json::from_slice(&hyper::body::to_bytes(response.into_body()).await.unwrap())
.unwrap();
assert!(nonce.is_some());
let mut mac = HmacSha256::new_from_slice(client_dh.as_bytes()).unwrap();
mac.update(client_static_public.as_bytes());
mac.update("127.0.0.1".as_bytes());
mac.update("8080".as_bytes());
mac.update(&nonce.unwrap().to_le_bytes());
let mac = mac.finalize().into_bytes();
let finalized_message = ClientMessage::Final(Client {
pub_key: ClientPublicKey::new(client_static_public),
socket: SocketAddr::from_str("127.0.0.1:8080").unwrap(),
mac: ClientMac::new(mac.as_slice().to_vec()),
});
let final_request = Request::builder()
.method("POST")
.uri(format!("{}/client", ROUTE_PREFIX))
.header("Content-type", "application/json")
.body(Body::from(serde_json::to_vec(&finalized_message).unwrap()))
.unwrap();
let response = ServiceExt::<Request<Body>>::ready(&mut app)
.await
.unwrap()
.call(final_request)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
assert!(!client_registry.read().await.is_empty());
let clients_request = Request::builder()
.method("GET")
.uri(format!("{}/clients", ROUTE_PREFIX))
.body(Body::empty())
.unwrap();
let response = ServiceExt::<Request<Body>>::ready(&mut app)
.await
.unwrap()
.call(clients_request)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let clients: Vec<ClientPublicKey> =
serde_json::from_slice(&hyper::body::to_bytes(response.into_body()).await.unwrap())
.unwrap();
assert!(!clients.is_empty());
let ro_clients = client_registry.read().await.clone();
assert_eq!(
ro_clients
.values()
.map(|c| c.pub_key().clone())
.collect::<Vec<ClientPublicKey>>(),
clients
)
}
}
+1 -16
View File
@@ -1,7 +1,6 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use self::client_handling::client_registration::ClientRegistry;
use self::storage::PersistentStorage;
use crate::commands::helpers::{override_network_requester_config, OverrideNetworkRequesterConfig};
use crate::config::Config;
@@ -13,7 +12,6 @@ use crate::node::client_handling::embedded_network_requester::{
use crate::node::client_handling::websocket;
use crate::node::client_handling::websocket::connection_handler::coconut::CoconutVerifier;
use crate::node::helpers::{initialise_main_storage, load_network_requester_config};
use crate::node::http::start_http_api;
use crate::node::mixnet_handling::receiver::connection_handler::ConnectionHandler;
use crate::node::statistics::collector::GatewayStatisticsCollector;
use crate::node::storage::Storage;
@@ -29,16 +27,13 @@ use nym_task::{TaskClient, TaskManager};
use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient};
use rand::seq::SliceRandom;
use rand::thread_rng;
use std::collections::HashMap;
use std::error::Error;
use std::net::SocketAddr;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::RwLock;
pub(crate) mod client_handling;
pub(crate) mod helpers;
pub(crate) mod http;
pub(crate) mod mixnet_handling;
pub(crate) mod statistics;
pub(crate) mod storage;
@@ -90,8 +85,6 @@ pub(crate) struct Gateway<St = PersistentStorage> {
/// x25519 keypair used for Diffie-Hellman. Currently only used for sphinx key derivation.
sphinx_keypair: Arc<encryption::KeyPair>,
storage: St,
client_registry: Arc<RwLock<ClientRegistry>>,
}
impl<St> Gateway<St> {
@@ -107,7 +100,6 @@ impl<St> Gateway<St> {
sphinx_keypair: Arc::new(helpers::load_sphinx_keys(&config)?),
config,
network_requester_opts,
client_registry: Arc::new(RwLock::new(HashMap::new())),
})
}
@@ -125,7 +117,6 @@ impl<St> Gateway<St> {
identity_keypair: Arc::new(identity_keypair),
sphinx_keypair: Arc::new(sphinx_keypair),
storage,
client_registry: Arc::new(RwLock::new(HashMap::new())),
}
}
@@ -379,17 +370,11 @@ impl<St> Gateway<St> {
// Once this is a bit more mature, make this a commandline flag instead of a compile time
// flag
#[cfg(feature = "wireguard")]
if let Err(err) = nym_wireguard::start_wireguard(shutdown.subscribe()).await {
if let Err(err) = nym_wireguard::start_wg_listener(shutdown.subscribe()).await {
// that's a nasty workaround, but anyhow errors are generally nicer, especially on exit
bail!("{err}")
}
// This should likely be wireguard feature gated, but its easier to test if it hangs in here
tokio::spawn(start_http_api(
Arc::clone(&self.client_registry),
Arc::clone(&self.sphinx_keypair),
));
info!("Finished nym gateway startup procedure - it should now be able to receive mix and client traffic!");
if let Err(err) = Self::wait_for_interrupt(shutdown).await {
+2 -2
View File
@@ -162,13 +162,13 @@ impl PersistentStorage {
// TODO: we can inject here more stuff based on our gateway global config
// struct. Maybe different pool size or timeout intervals?
let mut opts = sqlx::sqlite::SqliteConnectOptions::new()
let opts = sqlx::sqlite::SqliteConnectOptions::new()
.filename(database_path)
.create_if_missing(true);
// TODO: do we want auto_vacuum ?
opts.disable_statement_logging();
let opts = opts.disable_statement_logging();
let connection_pool = match sqlx::SqlitePool::connect_with(opts).await {
Ok(db) => db,
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-mixnode"
version = "1.1.31"
version = "1.1.30"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
+3 -3
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-api"
version = "1.1.31"
version = "1.1.30"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
@@ -52,7 +52,7 @@ ts-rs = { workspace = true, optional = true}
anyhow = "1.0"
getset = "0.1.1"
sqlx = { version = "0.6.2", features = [
sqlx = { workspace = true, features = [
"runtime-tokio-rustls",
"sqlite",
"macros",
@@ -113,7 +113,7 @@ generate-ts = ["ts-rs"]
[build-dependencies]
tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] }
sqlx = { version = "0.6.2", features = [
sqlx = { workspace = true, features = [
"runtime-tokio-rustls",
"sqlite",
"macros",
+1 -1
View File
@@ -48,7 +48,7 @@ impl Epoch {
let start_time = NaiveDateTime::from_timestamp_opt(info.start_time, 0)
.ok_or("Invalid start time")
.unwrap();
let start_time: DateTime<Utc> = DateTime::from_naive_utc_and_offset(start_time, Utc);
let start_time: DateTime<Utc> = DateTime::from_utc(start_time, Utc);
let interval = tokio::time::interval(std::time::Duration::from_secs(info.duration));
Self {
start_time,
+2 -2
View File
@@ -31,13 +31,13 @@ impl NymApiStorage {
pub async fn init<P: AsRef<Path>>(database_path: P) -> Result<Self, NymApiStorageError> {
// TODO: we can inject here more stuff based on our nym-api global config
// struct. Maybe different pool size or timeout intervals?
let mut opts = sqlx::sqlite::SqliteConnectOptions::new()
let opts = sqlx::sqlite::SqliteConnectOptions::new()
.filename(database_path)
.create_if_missing(true);
// TODO: do we want auto_vacuum ?
opts.disable_statement_logging();
let opts = opts.disable_statement_logging();
let connection_pool = match sqlx::SqlitePool::connect_with(opts).await {
Ok(db) => db,
+814
View File
@@ -0,0 +1,814 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aead"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
dependencies = [
"crypto-common",
"generic-array",
]
[[package]]
name = "aes"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241"
dependencies = [
"cfg-if 1.0.0",
"cipher",
"cpufeatures",
]
[[package]]
name = "aes-gcm"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82e1366e0c69c9f927b1fa5ce2c7bf9eafc8f9268c0b9800729e8b267612447c"
dependencies = [
"aead",
"aes",
"cipher",
"ctr",
"ghash",
"subtle",
]
[[package]]
name = "argon2"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95c2fcf79ad1932ac6269a738109997a83c227c09b75842ae564dc8ede6a861c"
dependencies = [
"base64ct",
"blake2",
"password-hash",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "base64ct"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "bip39"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f"
dependencies = [
"bitcoin_hashes",
"serde",
"unicode-normalization",
"zeroize",
]
[[package]]
name = "bitcoin_hashes"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4"
[[package]]
name = "blake2"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
dependencies = [
"digest",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "bumpalo"
version = "3.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]]
name = "console_error_panic_hook"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
dependencies = [
"cfg-if 1.0.0",
"wasm-bindgen",
]
[[package]]
name = "cpufeatures"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"rand_core",
"typenum",
]
[[package]]
name = "ctr"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
dependencies = [
"cipher",
]
[[package]]
name = "digest"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
dependencies = [
"block-buffer",
"crypto-common",
"subtle",
]
[[package]]
name = "extension-storage"
version = "0.1.0"
dependencies = [
"bip39",
"console_error_panic_hook",
"js-sys",
"serde-wasm-bindgen",
"thiserror",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-utils",
"wee_alloc",
"zeroize",
]
[[package]]
name = "futures"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
[[package]]
name = "futures-executor"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
[[package]]
name = "futures-macro"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.16",
]
[[package]]
name = "futures-sink"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
[[package]]
name = "futures-task"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
[[package]]
name = "futures-util"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
"zeroize",
]
[[package]]
name = "getrandom"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
dependencies = [
"cfg-if 1.0.0",
"js-sys",
"libc",
"wasi",
"wasm-bindgen",
]
[[package]]
name = "ghash"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40"
dependencies = [
"opaque-debug",
"polyval",
]
[[package]]
name = "indexed_db_futures"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfbcff6ae46750b15cc594bfd277b188cbddcfdc1817848f97f03f26f8625b9e"
dependencies = [
"cfg-if 1.0.0",
"js-sys",
"uuid",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "inout"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
dependencies = [
"generic-array",
]
[[package]]
name = "itoa"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
[[package]]
name = "js-sys"
version = "0.3.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.144"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memory_units"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3"
[[package]]
name = "nym-store-cipher"
version = "0.1.0"
dependencies = [
"aes-gcm",
"argon2",
"generic-array",
"getrandom",
"rand",
"serde",
"serde_json",
"thiserror",
"zeroize",
]
[[package]]
name = "once_cell"
version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "password-hash"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
dependencies = [
"base64ct",
"rand_core",
"subtle",
]
[[package]]
name = "pin-project-lite"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "polyval"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef234e08c11dfcb2e56f79fd70f6f2eb7f025c0ce2333e82f4f0518ecad30c6"
dependencies = [
"cfg-if 1.0.0",
"cpufeatures",
"opaque-debug",
"universal-hash",
]
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro2"
version = "1.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "ryu"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
[[package]]
name = "serde"
version = "1.0.163"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde-wasm-bindgen"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e"
dependencies = [
"js-sys",
"serde",
"wasm-bindgen",
]
[[package]]
name = "serde_derive"
version = "1.0.163"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.16",
]
[[package]]
name = "serde_json"
version = "1.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "slab"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
dependencies = [
"autocfg",
]
[[package]]
name = "subtle"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.16",
]
[[package]]
name = "tinyvec"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "typenum"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]]
name = "unicode-ident"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
[[package]]
name = "unicode-normalization"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
dependencies = [
"tinyvec",
]
[[package]]
name = "universal-hash"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5"
dependencies = [
"crypto-common",
"subtle",
]
[[package]]
name = "uuid"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2"
dependencies = [
"getrandom",
"wasm-bindgen",
]
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
dependencies = [
"cfg-if 1.0.0",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn 1.0.109",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d"
dependencies = [
"cfg-if 1.0.0",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
[[package]]
name = "wasm-utils"
version = "0.1.0"
dependencies = [
"futures",
"indexed_db_futures",
"js-sys",
"nym-store-cipher",
"serde",
"serde-wasm-bindgen",
"thiserror",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "web-sys"
version = "0.3.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "wee_alloc"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e"
dependencies = [
"cfg-if 0.1.10",
"libc",
"memory_units",
"winapi",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "zeroize"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9"
dependencies = [
"zeroize_derive",
]
[[package]]
name = "zeroize_derive"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.16",
]
+35 -17
View File
@@ -1524,12 +1524,6 @@ dependencies = [
"parking_lot_core 0.9.8",
]
[[package]]
name = "data-encoding"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
[[package]]
name = "debugid"
version = "0.8.0"
@@ -3217,6 +3211,15 @@ dependencies = [
"generic-array 0.14.7",
]
[[package]]
name = "input_buffer"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413"
dependencies = [
"bytes",
]
[[package]]
name = "instant"
version = "0.1.12"
@@ -5804,7 +5807,7 @@ dependencies = [
"log",
"ring",
"sct 0.7.0",
"webpki 0.22.2",
"webpki 0.22.0",
]
[[package]]
@@ -6302,6 +6305,19 @@ dependencies = [
"stable_deref_trait",
]
[[package]]
name = "sha-1"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6"
dependencies = [
"block-buffer 0.9.0",
"cfg-if",
"cpufeatures",
"digest 0.9.0",
"opaque-debug 0.3.0",
]
[[package]]
name = "sha1"
version = "0.10.5"
@@ -7415,7 +7431,7 @@ checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
dependencies = [
"rustls 0.20.8",
"tokio",
"webpki 0.22.2",
"webpki 0.22.0",
]
[[package]]
@@ -7444,12 +7460,13 @@ dependencies = [
[[package]]
name = "tokio-tungstenite"
version = "0.20.1"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c"
checksum = "1e96bb520beab540ab664bd5a9cfeaa1fcd846fa68c830b42e2c8963071251d2"
dependencies = [
"futures-util",
"log",
"pin-project",
"tokio",
"tungstenite",
]
@@ -7620,18 +7637,19 @@ dependencies = [
[[package]]
name = "tungstenite"
version = "0.20.1"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9"
checksum = "5fe8dada8c1a3aeca77d6b51a4f1314e0f4b8e438b7b1b71e3ddaca8080e4093"
dependencies = [
"base64 0.13.1",
"byteorder",
"bytes",
"data-encoding",
"http",
"httparse",
"input_buffer",
"log",
"rand 0.8.5",
"sha1",
"sha-1",
"thiserror",
"url",
"utf-8",
@@ -8072,9 +8090,9 @@ dependencies = [
[[package]]
name = "webpki"
version = "0.22.2"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07ecc0cd7cac091bf682ec5efa18b1cff79d617b84181f38b3951dbe135f607f"
checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
dependencies = [
"ring",
"untrusted",
@@ -8095,7 +8113,7 @@ version = "0.22.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87"
dependencies = [
"webpki 0.22.2",
"webpki 0.22.0",
]
[[package]]
+1 -1
View File
@@ -31,7 +31,7 @@
"@nymproject/mui-theme": "^1.0.0",
"@nymproject/react": "^1.0.0",
"@nymproject/types": "^1.0.0",
"@nymproject/node-tester": ">=1.2.0-rc.8",
"@nymproject/node-tester": ">=1.2.0-rc.5",
"@storybook/react": "^6.5.15",
"@tauri-apps/api": "^1.2.0",
"@tauri-apps/tauri-forage": "^1.0.0-beta.2",
+1 -2
View File
@@ -42,18 +42,17 @@ fn main() {
mixnet::account::connect_with_mnemonic,
mixnet::account::create_new_mnemonic,
mixnet::account::create_password,
mixnet::account::update_password,
mixnet::account::does_password_file_exist,
mixnet::account::get_balance,
mixnet::account::list_accounts,
mixnet::account::logout,
mixnet::account::remove_account_for_password,
mixnet::account::remove_password,
mixnet::account::rename_account_for_password,
mixnet::account::show_mnemonic_for_account_in_password,
mixnet::account::sign_in_with_password,
mixnet::account::sign_in_with_password_and_account_id,
mixnet::account::switch_network,
mixnet::account::update_password,
mixnet::account::validate_mnemonic,
mixnet::admin::get_contract_settings,
mixnet::admin::update_contract_settings,
@@ -484,8 +484,7 @@ pub async fn add_account_for_password(
})
}
// Set the tauri state with all the accounts in the wallet.
// NOTE: the first `AccoundId` when converting is the `LoginId` for the entry that was loaded.
// The first `AccoundId` when converting is the `LoginId` for the entry that was loaded.
async fn set_state_with_all_accounts(
stored_login: wallet_storage::StoredLogin,
first_id_when_converting: wallet_storage::AccountId,
@@ -548,28 +547,6 @@ pub async fn remove_account_for_password(
set_state_with_all_accounts(stored_login, first_account_id_when_converting, state).await
}
#[tauri::command]
pub async fn rename_account_for_password(
password: UserPassword,
account_id: &str,
new_account_id: &str,
state: tauri::State<'_, WalletState>,
) -> Result<(), BackendError> {
log::info!("Renaming account: {account_id} to {new_account_id}");
// Currently we only support a single, default, id in the wallet
let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string());
let account_id = wallet_storage::AccountId::new(account_id.to_string());
let new_account_id = wallet_storage::AccountId::new(new_account_id.to_string());
wallet_storage::rename_account_in_login(&login_id, &account_id, &new_account_id, &password)?;
// Load from storage to reset the internal tuari state
let stored_login = wallet_storage::load_existing_login(&login_id, &password)?;
// NOTE: Since we removed from a multi-account login, this id shouldn't be needed, but setting
// the state is supposed to be a general function
let first_account_id_when_converting = login_id.into();
set_state_with_all_accounts(stored_login, first_account_id_when_converting, state).await
}
fn derive_address(
mnemonic: bip39::Mnemonic,
prefix: &str,
@@ -226,10 +226,6 @@ impl MultipleAccounts {
self.accounts.iter().find(|account| &account.id == id)
}
pub(crate) fn get_account_mut(&mut self, id: &AccountId) -> Option<&mut WalletAccount> {
self.accounts.iter_mut().find(|account| &account.id == id)
}
pub(crate) fn get_account_with_mnemonic(
&self,
mnemonic: &bip39::Mnemonic,
@@ -277,21 +273,6 @@ impl MultipleAccounts {
self.accounts.retain(|accounts| &accounts.id != id);
Ok(())
}
pub(crate) fn rename(
&mut self,
id: &AccountId,
new_id: &AccountId,
) -> Result<(), BackendError> {
if self.get_account(new_id).is_some() {
return Err(BackendError::WalletAccountIdAlreadyExistsInWalletLogin);
}
let account = self
.get_account_mut(id)
.ok_or(BackendError::WalletNoSuchAccountIdInWalletLogin)?;
account.rename_id(new_id.clone());
Ok(())
}
}
impl From<Vec<WalletAccount>> for MultipleAccounts {
@@ -319,10 +300,6 @@ impl WalletAccount {
&self.id
}
pub(crate) fn rename_id(&mut self, new_id: AccountId) {
self.id = new_id;
}
pub(crate) fn mnemonic(&self) -> &bip39::Mnemonic {
match self.account {
AccountData::Mnemonic(ref account) => account.mnemonic(),
+1 -292
View File
@@ -425,49 +425,6 @@ fn remove_account_from_login_at_file(
}
}
/// Rename an account inside the encrypted login.
/// - If the encrypted login is just a single account, abort to be on the safe side.
/// - If the name already exists, abort.
pub(crate) fn rename_account_in_login(
id: &LoginId,
account_id: &AccountId,
new_account_id: &AccountId,
password: &UserPassword,
) -> Result<(), BackendError> {
let store_dir = get_storage_directory()?;
let filepath = store_dir.join(WALLET_INFO_FILENAME);
rename_account_in_login_at_file(&filepath, id, account_id, new_account_id, password)
}
fn rename_account_in_login_at_file(
filepath: &Path,
id: &LoginId,
account_id: &AccountId,
new_account_id: &AccountId,
password: &UserPassword,
) -> Result<(), BackendError> {
log::info!("Renaming associated account in login account: {id}");
let mut stored_wallet = load_existing_wallet_at_file(filepath)?;
let mut decrypted_login = stored_wallet.decrypt_login(id, password)?;
// Rename the account
match decrypted_login {
StoredLogin::Mnemonic(_) => {
log::warn!("Encountered mnemonic login instead of list of accounts, aborting");
return Err(BackendError::WalletUnexpectedMnemonicAccount);
}
StoredLogin::Multiple(ref mut accounts) => {
accounts.rename(account_id, new_account_id)?;
}
};
// Encrypt the new updated login and write to file
let encrypted_accounts = EncryptedLogin::encrypt(id.clone(), &decrypted_login, password)?;
stored_wallet.replace_encrypted_login(encrypted_accounts)?;
write_to_file(filepath, &stored_wallet)
}
#[cfg(test)]
mod tests {
use crate::wallet_storage::account_data::{MnemonicAccount, WalletAccount};
@@ -1556,61 +1513,11 @@ mod tests {
.unwrap();
assert!(matches!(
append_account_to_login_at_file(&wallet_file, account1, hd_path, id1, id2, &password),
append_account_to_login_at_file(&wallet_file, account1, hd_path, id1, id2, &password,),
Err(BackendError::WalletMnemonicAlreadyExistsInWalletLogin),
))
}
#[test]
fn append_the_same_account_name_twice_fails() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let mnemonic1 = Mnemonic::generate(24).unwrap();
let mnemonic2 = Mnemonic::generate(24).unwrap();
let mnemonic3 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
// The top-level login id. NOTE: the first account id is always set to default.
let login_id = LoginId::new("my_login_id".to_string());
// Store the first account under login_id. The first account id is always set to default
// name.
store_login_with_multiple_accounts_at_file(
&wallet_file,
mnemonic1.clone(),
hd_path.clone(),
login_id.clone(),
&password,
)
.unwrap();
// Append another account (account2) to the same login (login_id)
let account2 = AccountId::new("account_2".to_string());
append_account_to_login_at_file(
&wallet_file,
mnemonic2.clone(),
hd_path.clone(),
login_id.clone(),
account2.clone(),
&password,
)
.unwrap();
// Appending the third account, with same account id will fail
assert!(matches!(
append_account_to_login_at_file(
&wallet_file,
mnemonic3.clone(),
hd_path,
login_id,
account2,
&password,
),
Err(BackendError::WalletAccountIdAlreadyExistsInWalletLogin),
))
}
#[test]
fn delete_the_same_account_twice_for_a_login_fails() {
let store_dir = tempdir().unwrap();
@@ -1934,204 +1841,6 @@ mod tests {
assert_eq!(account.hd_path(), &hd_path);
}
#[test]
fn rename_first_account_in_login() {
let store_dir = tempdir().unwrap();
let wallet = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let login_id = LoginId::new("first".to_string());
store_login_with_multiple_accounts_at_file(
&wallet,
account1.clone(),
hd_path.clone(),
login_id.clone(),
&password,
)
.unwrap();
let loaded_login = load_existing_login_at_file(&wallet, &login_id, &password).unwrap();
let loaded_accounts = loaded_login.as_multiple_accounts().unwrap();
let expected = vec![WalletAccount::new(
DEFAULT_FIRST_ACCOUNT_NAME.into(),
MnemonicAccount::new(account1.clone(), hd_path.clone()),
)]
.into();
assert_eq!(loaded_accounts, &expected);
let renamed_account = AccountId::new("new_first".to_string());
rename_account_in_login_at_file(
&wallet,
&login_id,
&DEFAULT_FIRST_ACCOUNT_NAME.into(),
&renamed_account.clone(),
&password,
)
.unwrap();
let loaded_login = load_existing_login_at_file(&wallet, &login_id, &password).unwrap();
let loaded_accounts = loaded_login.as_multiple_accounts().unwrap();
let expected = vec![WalletAccount::new(
renamed_account.clone(),
MnemonicAccount::new(account1.clone(), hd_path.clone()),
)]
.into();
assert_eq!(loaded_accounts, &expected);
}
#[test]
fn rename_one_account_in_login_with_two_accounts() {
let store_dir = tempdir().unwrap();
let wallet = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = Mnemonic::generate(24).unwrap();
let account2 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let login_id = LoginId::new("first".to_string());
let account_id2 = AccountId::new("second".to_string());
let renamed_account_id2 = AccountId::new("new_second".to_string());
store_login_with_multiple_accounts_at_file(
&wallet,
account1.clone(),
hd_path.clone(),
login_id.clone(),
&password,
)
.unwrap();
append_account_to_login_at_file(
&wallet,
account2.clone(),
hd_path.clone(),
login_id.clone(),
account_id2.clone(),
&password,
)
.unwrap();
// Load and confirm
let loaded_login = load_existing_login_at_file(&wallet, &login_id, &password).unwrap();
let loaded_accounts = loaded_login.as_multiple_accounts().unwrap();
let expected = vec![
WalletAccount::new(
DEFAULT_FIRST_ACCOUNT_NAME.into(),
MnemonicAccount::new(account1.clone(), hd_path.clone()),
),
WalletAccount::new(
account_id2.clone(),
MnemonicAccount::new(account2.clone(), hd_path.clone()),
),
]
.into();
assert_eq!(loaded_accounts, &expected);
// Rename the second account to a new name
rename_account_in_login_at_file(
&wallet,
&login_id,
&account_id2,
&renamed_account_id2,
&password,
)
.unwrap();
// Load and confirm
let loaded_login = load_existing_login_at_file(&wallet, &login_id, &password).unwrap();
let loaded_accounts = loaded_login.as_multiple_accounts().unwrap();
let expected = vec![
WalletAccount::new(
DEFAULT_FIRST_ACCOUNT_NAME.into(),
MnemonicAccount::new(account1, hd_path.clone()),
),
WalletAccount::new(
renamed_account_id2.clone(),
MnemonicAccount::new(account2, hd_path.clone()),
),
]
.into();
assert_eq!(loaded_accounts, &expected);
}
#[test]
fn rename_account_into_existing_account_id_fails() {
let store_dir = tempdir().unwrap();
let wallet = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = Mnemonic::generate(24).unwrap();
let account2 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let login_id = LoginId::new("first".to_string());
let account_id2 = AccountId::new("second".to_string());
let renamed_account_id2 = DEFAULT_FIRST_ACCOUNT_NAME.into();
store_login_with_multiple_accounts_at_file(
&wallet,
account1.clone(),
hd_path.clone(),
login_id.clone(),
&password,
)
.unwrap();
append_account_to_login_at_file(
&wallet,
account2.clone(),
hd_path.clone(),
login_id.clone(),
account_id2.clone(),
&password,
)
.unwrap();
// Load and confirm
let loaded_login = load_existing_login_at_file(&wallet, &login_id, &password).unwrap();
let loaded_accounts = loaded_login.as_multiple_accounts().unwrap();
let expected = vec![
WalletAccount::new(
DEFAULT_FIRST_ACCOUNT_NAME.into(),
MnemonicAccount::new(account1.clone(), hd_path.clone()),
),
WalletAccount::new(
account_id2.clone(),
MnemonicAccount::new(account2.clone(), hd_path.clone()),
),
]
.into();
assert_eq!(loaded_accounts, &expected);
// Rename the second account to the name of the first one fails
assert!(matches!(
rename_account_in_login_at_file(
&wallet,
&login_id,
&account_id2,
&renamed_account_id2,
&password,
),
Err(BackendError::WalletAccountIdAlreadyExistsInWalletLogin)
));
// Load and confirm nothing was changed
let loaded_login = load_existing_login_at_file(&wallet, &login_id, &password).unwrap();
let loaded_accounts = loaded_login.as_multiple_accounts().unwrap();
let expected = vec![
WalletAccount::new(
DEFAULT_FIRST_ACCOUNT_NAME.into(),
MnemonicAccount::new(account1, hd_path.clone()),
),
WalletAccount::new(
account_id2.clone(),
MnemonicAccount::new(account2, hd_path.clone()),
),
]
.into();
assert_eq!(loaded_accounts, &expected);
}
// Test to that decrypts a stored file from the repo, to make sure we are able to decrypt stored
// wallets created with older versions.
#[test]
@@ -1,15 +1,5 @@
import React, { useContext } from 'react';
import EditIcon from '@mui/icons-material/Create';
import {
Box,
IconButton,
ListItem,
ListItemAvatar,
ListItemButton,
ListItemText,
Tooltip,
Typography,
} from '@mui/material';
import { Box, ListItem, ListItemAvatar, ListItemButton, ListItemText, Tooltip, Typography } from '@mui/material';
import { useClipboard } from 'use-clipboard-copy';
import { AccountsContext } from 'src/context';
import { AccountAvatar } from './AccountAvatar';
@@ -23,24 +13,13 @@ export const AccountItem = ({
address: string;
onSelectAccount: () => void;
}) => {
const { selectedAccount, setDialogToDisplay, setAccountMnemonic, handleAccountToEdit } = useContext(AccountsContext);
const { selectedAccount, setDialogToDisplay, setAccountMnemonic } = useContext(AccountsContext);
const { copy, copied } = useClipboard({ copiedTimeout: 1000 });
return (
<ListItem
disablePadding
disableGutters
sx={selectedAccount?.id === name ? { bgcolor: 'rgba(33, 208, 115, 0.1)' } : {}}
secondaryAction={
<IconButton
sx={{ mr: 2, color: 'nym.text.dark' }}
onClick={() => {
handleAccountToEdit(name);
setDialogToDisplay('Edit');
}}
>
<EditIcon fontSize="small" />
</IconButton>
}
>
<ListItemButton disableRipple onClick={onSelectAccount}>
<ListItemAvatar sx={{ minWidth: 0, mr: 2 }}>
@@ -80,6 +59,17 @@ export const AccountItem = ({
</Box>
}
/>
{/* edit and remove accounts todo */}
{/* <ListItemIcon>
<IconButton
onClick={(e) => {
e.stopPropagation();
handleAccountToEdit(name);
}}
>
<Edit />
</IconButton>
</ListItemIcon> */}
</ListItemButton>
</ListItem>
);
@@ -33,9 +33,7 @@ export const AccountsModal = () => {
if (accountToSwitchTo)
return (
<ConfirmPasswordModal
modalTitle="Switch account"
accountName={accountToSwitchTo}
buttonTitle="Switch account"
onClose={() => {
handleClose();
setDialogToDisplay('Accounts');
@@ -6,14 +6,10 @@ import { AccountsContext } from 'src/context';
export const ConfirmPasswordModal = ({
accountName,
modalTitle,
onClose,
onConfirm,
buttonTitle,
}: {
accountName?: string;
modalTitle: string;
buttonTitle: string;
onClose: () => void;
onConfirm: (password: string) => Promise<void>;
}) => {
@@ -31,7 +27,7 @@ export const ConfirmPasswordModal = ({
>
<Paper>
<DialogTitle>
<Typography variant="h6">{modalTitle}</Typography>
<Typography variant="h6">Switch account</Typography>
<Typography fontSize="small" sx={{ color: 'grey.600' }}>
Confirm password
</Typography>
@@ -40,7 +36,7 @@ export const ConfirmPasswordModal = ({
onConfirm={onConfirm}
error={error}
isLoading={isLoading}
buttonTitle={buttonTitle}
buttonTitle="Switch account"
onCancel={onClose}
/>
</Paper>
@@ -14,59 +14,22 @@ import {
import { Close } from '@mui/icons-material';
import { useTheme } from '@mui/material/styles';
import { AccountsContext } from 'src/context';
import { StyledBackButton } from 'src/components/StyledBackButton';
import { ConfirmPasswordModal } from './ConfirmPasswordModal';
export const EditAccountModal = () => {
const { accountToEdit, dialogToDisplay, setDialogToDisplay, handleEditAccount, handleAccountToEdit, setError } =
useContext(AccountsContext);
const [accountName, setAccountName] = useState('');
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const { accountToEdit, dialogToDisplay, setDialogToDisplay, handleEditAccount } = useContext(AccountsContext);
const theme = useTheme();
useEffect(() => {
if (accountToEdit) {
setAccountName(accountToEdit.id);
}
setAccountName(accountToEdit ? accountToEdit?.id : '');
}, [accountToEdit]);
const handleClose = () => {
handleAccountToEdit(undefined);
setDialogToDisplay('Accounts');
};
const onConfirmPassword = async (password: string) => {
if (accountToEdit) {
try {
await handleEditAccount({ account: accountToEdit, newAccountName: accountName, password });
setShowConfirmPassword(false);
} catch (e) {
setError(`Error editing account: ${e}`);
}
}
};
if (showConfirmPassword) {
return (
<ConfirmPasswordModal
modalTitle="Rename account"
accountName={accountToEdit?.id}
buttonTitle="Confirm"
onClose={() => {
setShowConfirmPassword(false);
setError(undefined);
}}
onConfirm={onConfirmPassword}
/>
);
}
return (
<Dialog
open={dialogToDisplay === 'Edit'}
onClose={handleClose}
onClose={() => setDialogToDisplay('Accounts')}
fullWidth
PaperProps={{
style: { border: `1px solid ${theme.palette.nym.nymWallet.modal.border}` },
@@ -75,15 +38,17 @@ export const EditAccountModal = () => {
<Paper>
<DialogTitle>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography variant="h6">Rename account</Typography>
<IconButton onClick={handleClose}>
<Typography variant="h6">Edit account name</Typography>
<IconButton onClick={() => setDialogToDisplay('Accounts')}>
<Close />
</IconButton>
</Box>
<Typography fontSize="small" sx={{ color: 'grey.600' }}>
New wallet address
</Typography>
</DialogTitle>
<DialogContent sx={{ p: 0 }}>
<Box sx={{ px: 3, mt: 1 }}>
<Typography sx={{ mb: 2 }}>Type the new name for your account</Typography>
<TextField
label="Account name"
fullWidth
@@ -94,19 +59,21 @@ export const EditAccountModal = () => {
/>
</Box>
</DialogContent>
<DialogActions sx={{ p: 3, gap: 2 }}>
<StyledBackButton onBack={handleClose} />
<DialogActions sx={{ p: 3 }}>
<Button
fullWidth
disableElevation
variant="contained"
size="large"
onClick={() => {
setShowConfirmPassword(true);
if (accountToEdit) {
handleEditAccount({ ...accountToEdit, id: accountName });
setDialogToDisplay('Accounts');
}
}}
disabled={!accountName?.length}
>
Rename
Edit
</Button>
</DialogActions>
</Paper>
@@ -48,7 +48,7 @@ export const MnemonicModal = () => {
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography variant="h6">Display mnemonic</Typography>
</Box>
<Typography fontSize="small" sx={{ color: 'grey.600' }}>
<Typography variant="body1" sx={{ color: (theme) => theme.palette.text.disabled }}>
{`Display mnemonic for: ${accountMnemonic?.accountName}`}
</Typography>
</DialogTitle>
+6 -37
View File
@@ -1,6 +1,6 @@
import React, { createContext, Dispatch, SetStateAction, useContext, useEffect, useMemo, useState } from 'react';
import { AccountEntry } from '@nymproject/types';
import { addAccount as addAccountRequest, renameAccount, showMnemonicForAccount } from 'src/requests';
import { addAccount as addAccountRequest, showMnemonicForAccount } from 'src/requests';
import { useSnackbar } from 'notistack';
import { AppContext } from './main';
@@ -17,16 +17,8 @@ type TAccounts = {
handleAddAccount: (data: { accountName: string; mnemonic: string; password: string }) => void;
setDialogToDisplay: (dialog?: TAccountsDialog) => void;
handleSelectAccount: (data: { accountName: string; password: string }) => Promise<boolean>;
handleAccountToEdit: (accountId: string | undefined) => void;
handleEditAccount: ({
account,
newAccountName,
password,
}: {
account: AccountEntry;
newAccountName: string;
password: string;
}) => Promise<void>;
handleAccountToEdit: (accountId: string) => void;
handleEditAccount: (account: AccountEntry) => void;
handleImportAccount: (account: AccountEntry) => void;
handleGetAccountMnemonic: (data: { password: string; accountName: string }) => void;
};
@@ -75,35 +67,12 @@ export const AccountsProvider: FCWithChildren = ({ children }) => {
setIsLoading(false);
}
};
const handleEditAccount = async ({
account,
newAccountName,
password,
}: {
account: AccountEntry;
newAccountName: string;
password: string;
}) => {
setIsLoading(true);
try {
await renameAccount({ accountName: account.id, newAccountName, password });
setAccounts((accs) =>
accs?.map((acc) => (acc.address === account.address ? { ...acc, id: newAccountName } : acc)),
);
if (selectedAccount?.id === account.id) {
setSelectedAccount({ ...selectedAccount, id: newAccountName });
}
setDialogToDisplay('Accounts');
} catch (e) {
throw new Error(`Error editing account: ${e}`);
} finally {
setIsLoading(false);
}
};
const handleEditAccount = (account: AccountEntry) =>
setAccounts((accs) => accs?.map((acc) => (acc.address === account.address ? account : acc)));
const handleImportAccount = (account: AccountEntry) => setAccounts((accs) => [...(accs ? [...accs] : []), account]);
const handleAccountToEdit = (accountName: string | undefined) =>
const handleAccountToEdit = (accountName: string) =>
setAccountToEdit(accounts?.find((acc) => acc.id === accountName));
const handleSelectAccount = async ({ accountName, password }: { accountName: string; password: string }) => {
+3 -22
View File
@@ -28,31 +28,12 @@ export const MockAccountsProvider: FCWithChildren = ({ children }) => {
setIsLoading(false);
}
};
const handleEditAccount = async ({
password,
account,
newAccountName,
}: {
password: string;
account: AccountEntry;
newAccountName: string;
}) => {
if (password) {
setIsLoading(true);
try {
setAccounts((accs) => accs.map((acc) => (acc.id === account.id ? { ...acc, id: newAccountName } : acc)));
setDialogToDisplay('Accounts');
} catch (e) {
setError(`Error adding account: ${e}`);
} finally {
setIsLoading(false);
}
}
};
const handleEditAccount = (account: AccountEntry) =>
setAccounts((accs) => accs?.map((acc) => (acc.address === account.address ? account : acc)));
const handleImportAccount = (account: AccountEntry) => setAccounts((accs) => [...(accs ? [...accs] : []), account]);
const handleAccountToEdit = (accountName: string | undefined) =>
const handleAccountToEdit = (accountName: string) =>
setAccountToEdit(accounts?.find((acc) => acc.id === accountName));
const handleSelectAccount = async ({ accountName }: { accountName: string; password: string }) => {
-15
View File
@@ -50,18 +50,3 @@ export const archiveWalletFile = async () => invokeWrapper<void>('archive_wallet
export const showMnemonicForAccount = async ({ password, accountName }: { password: string; accountName: string }) =>
invokeWrapper<string>('show_mnemonic_for_account_in_password', { password, accountId: accountName });
export const renameAccount = async ({
password,
accountName,
newAccountName,
}: {
password: string;
accountName: string;
newAccountName: string;
}) =>
invokeWrapper<AccountEntry>('rename_account_for_password', {
password,
accountId: accountName,
newAccountId: newAccountName,
});
-54
View File
@@ -1,54 +0,0 @@
import json
import os.path
import sys
def add_mixnode(base_network, base_dir, mix_id):
with open(os.path.join(base_dir, "mix" + str(mix_id) + ".json"), "r") as json_blob:
mix_data = json.load(json_blob)
base_network["mixnodes"][str(mix_id)][0]["identity_key"] = mix_data["identity_key"]
base_network["mixnodes"][str(mix_id)][0]["sphinx_key"] = mix_data["sphinx_key"]
base_network["mixnodes"][str(mix_id)][0]["mix_port"] = mix_data["mix_port"]
base_network["mixnodes"][str(mix_id)][0]["version"] = mix_data["version"]
base_network["mixnodes"][str(mix_id)][0]["host"] = mix_data["bind_address"]
base_network["mixnodes"][str(mix_id)][0]["layer"] = mix_id
base_network["mixnodes"][str(mix_id)][0]["mix_id"] = mix_id
base_network["mixnodes"][str(mix_id)][0]["owner"] = "whatever"
return base_network
def add_gateway(base_network, base_dir):
with open(os.path.join(base_dir, "gateway.json"), "r") as json_blob:
gateway_data = json.load(json_blob)
base_network["gateways"][0]["identity_key"] = gateway_data["identity_key"]
base_network["gateways"][0]["sphinx_key"] = gateway_data["sphinx_key"]
base_network["gateways"][0]["mix_port"] = gateway_data["mix_port"]
base_network["gateways"][0]["clients_port"] = gateway_data["clients_port"]
# base_network["gateways"][0]["version"] = gateway_data["version"]
base_network["gateways"][0]["host"] = gateway_data["bind_address"]
base_network["gateways"][0]["owner"] = "whatever"
return base_network
def main(args):
base_network = {
"mixnodes": {
"1": [{}],
"2": [{}],
"3": [{}],
},
"gateways": [{}]
}
base_dir = args[0]
base_network = add_mixnode(base_network, base_dir, 1)
base_network = add_mixnode(base_network, base_dir, 2)
base_network = add_mixnode(base_network, base_dir, 3)
base_network = add_gateway(base_network, base_dir)
with open(os.path.join(base_dir, "network.json"), "w") as out:
json.dump(base_network, out, indent=2)
if __name__ == '__main__':
main(sys.argv[1:])

Some files were not shown because too many files have changed in this diff Show More