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
215 changed files with 9926 additions and 7194 deletions
@@ -20,8 +20,6 @@ jobs:
platform: [ubuntu-20.04]
runs-on: ${{ matrix.platform }}
env:
CARGO_TERM_COLOR: always
steps:
- uses: actions/checkout@v3
+3 -4
View File
@@ -3,12 +3,11 @@ name: ci-build-ts
on:
push:
paths:
- "ts-packages/**"
- "sdk/typescript/**"
- 'ts-packages/**'
jobs:
build:
runs-on: ubuntu-20.04-16-core
runs-on: custom-runner-linux
steps:
- uses: actions/checkout@v2
- name: Install rsync
@@ -21,7 +20,7 @@ jobs:
- name: Setup yarn
run: npm install -g yarn
- name: Build
run: yarn && yarn build && yarn build:ci:storybook
run: yarn && yarn build && yarn build:ci
- name: Deploy branch to CI www (storybook)
continue-on-error: true
uses: easingthemes/ssh-deploy@main
@@ -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
+3 -4
View File
@@ -28,14 +28,13 @@ jobs:
command: build
args: --workspace --release --all
- name: Install mdbook
run: (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.4.35" mdbook)
run: (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.4.33" mdbook)
- name: Install mdbook plugins
run: |
cargo install --vers "=0.2.2" mdbook-variables && cargo install \
--vers "^1.8.0" mdbook-admonish --force && cargo install --vers \
--vers "^1.8.0" mdbook-admonish && cargo install --vers \
"^0.1.2" mdbook-last-changed && cargo install --vers "^0.1.2" mdbook-theme \
&& cargo install --vers "^0.7.7" mdbook-linkcheck \
&& mdbook-admonish install
&& cargo install --vers "^0.7.7" mdbook-linkcheck
- name: Build all projects in documentation/ & move to ~/dist/docs/
run: cd documentation && ./build_all_to_dist.sh
continue-on-error: false
+2 -4
View File
@@ -22,7 +22,7 @@ on:
jobs:
build:
runs-on: ubuntu-20.04-16-core
runs-on: custom-runner-linux
steps:
- uses: actions/checkout@v2
- uses: rlespinasse/github-slug-action@v3.x
@@ -39,8 +39,6 @@ jobs:
toolchain: stable
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- name: Install wasm-opt
run: cargo install wasm-opt
- name: Set up Go
uses: actions/setup-go@v4
@@ -51,7 +49,7 @@ jobs:
run: yarn
- name: Build packages
run: yarn build:ci
run: yarn build:ci:sdk
- name: Lint
run: yarn lint
@@ -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: [macos-12-large]
platform: [macos-latest]
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: [macos-12-large]
platform: [macos-latest]
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:
@@ -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 -4
View File
@@ -4,7 +4,7 @@ on:
jobs:
publish:
runs-on: ubuntu-20.04-16-core
runs-on: [custom-runner-linux]
steps:
- uses: actions/checkout@v2
@@ -25,9 +25,6 @@ jobs:
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- name: Install wasm-opt
run: cargo install wasm-opt
- name: Install dependencies
run: yarn
-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
+1142 -966
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]
@@ -74,7 +74,7 @@ impl PartiallyDelegated {
fn route_socket_messages(
ws_msgs: Vec<Message>,
packet_router: &PacketRouter,
packet_router: &mut PacketRouter,
shared_key: &SharedKeys,
) -> Result<(), GatewayClientError> {
let plaintexts = Self::recover_received_plaintexts(ws_msgs, shared_key);
@@ -97,6 +97,7 @@ impl PartiallyDelegated {
let mixnet_receiver_future = async move {
let mut notify_receiver = notify_receiver;
let mut chunk_stream = (&mut stream).ready_chunks(8);
let mut packet_router = packet_router;
let ret_err = loop {
tokio::select! {
@@ -114,7 +115,7 @@ impl PartiallyDelegated {
Ok(msgs) => msgs
};
if let Err(err) = Self::route_socket_messages(ws_msgs, &packet_router, shared_key.as_ref()) {
if let Err(err) = Self::route_socket_messages(ws_msgs, &mut packet_router, shared_key.as_ref()) {
log::warn!("Route socket messages failed: {err}");
}
}
@@ -42,9 +42,7 @@ pub trait GatewayPacketRouter {
}
n if n
== PacketSize::OutfoxRegularPacket
.plaintext_size()
.saturating_sub(outfox_ack_overhead) =>
== PacketSize::OutfoxRegularPacket.plaintext_size() - outfox_ack_overhead =>
{
trace!("received regular outfox packet");
received_messages.push(received_packet);
@@ -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"]}
+13 -14
View File
@@ -3,31 +3,30 @@ use std::fmt::{Display, Formatter};
use bytes::Bytes;
#[allow(unused)]
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum Event {
/// IP packet received from the WireGuard tunnel that should be passed through to the
/// corresponding virtual device/internet.
Wg(Bytes),
/// IP packet received from the WireGuard tunnel that was verified as part of the handshake.
WgVerified(Bytes),
/// 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),
/// IP packet to be sent through the WireGuard tunnel as crafted by the virtual device.
Ip(Bytes),
IpPacket(Bytes),
}
impl Display for Event {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Event::Wg(data) => {
let size = data.len();
write!(f, "Wg{{ size={size} }}")
Event::Dumb => {
write!(f, "Dumb{{}}")
}
Event::WgVerified(data) => {
Event::WgPacket(data) => {
let size = data.len();
write!(f, "WgVerified{{ size={size} }}")
write!(f, "WgPacket{{ size={size} }}")
}
Event::Ip(data) => {
Event::IpPacket(data) => {
let size = data.len();
write!(f, "Ip{{ size={size} }}")
write!(f, "IpPacket{{ size={size} }}")
}
}
}
+125 -40
View File
@@ -1,55 +1,140 @@
#![cfg_attr(not(target_os = "linux"), allow(dead_code))]
// #![warn(clippy::pedantic)]
// #![warn(clippy::expect_used)]
// #![warn(clippy::unwrap_used)]
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 registered_peers;
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)
}
/// Start wireguard UDP listener and TUN device
///
/// # Errors
///
/// This function will return an error if either the UDP listener of the TUN device fails to start.
#[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?);
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, 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!("{TUN_BASE_NAME}%d").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::Ip(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;
-56
View File
@@ -1,56 +0,0 @@
use std::{collections::HashMap, sync::Arc};
use boringtun::x25519;
use ip_network::IpNetwork;
pub(crate) type PeerIdx = u32;
#[derive(Debug)]
pub(crate) struct RegisteredPeer {
pub(crate) public_key: x25519::PublicKey,
pub(crate) index: PeerIdx,
pub(crate) allowed_ips: IpNetwork,
// endpoint: SocketAddr,
}
#[derive(Debug, Default)]
pub(crate) struct RegisteredPeers {
peers: HashMap<x25519::PublicKey, Arc<tokio::sync::Mutex<RegisteredPeer>>>,
peers_by_idx: HashMap<PeerIdx, Arc<tokio::sync::Mutex<RegisteredPeer>>>,
}
impl RegisteredPeers {
pub(crate) async fn insert(
&mut self,
public_key: x25519::PublicKey,
peer: Arc<tokio::sync::Mutex<RegisteredPeer>>,
) {
let peer_idx = { peer.lock().await.index };
self.peers.insert(public_key, Arc::clone(&peer));
self.peers_by_idx.insert(peer_idx, peer);
}
#[allow(unused)]
pub(crate) async fn remove(&mut self, public_key: &x25519::PublicKey) {
if let Some(peer) = self.peers.remove(public_key) {
let peer_idx = peer.lock().await.index;
if self.peers_by_idx.remove(&peer_idx).is_none() {
log::error!("Removed registered peer but no registered index was found");
}
}
}
pub(crate) fn get_by_key(
&self,
public_key: &x25519::PublicKey,
) -> Option<&Arc<tokio::sync::Mutex<RegisteredPeer>>> {
self.peers.get(public_key)
}
pub(crate) fn get_by_idx(
&self,
peer_idx: PeerIdx,
) -> Option<&Arc<tokio::sync::Mutex<RegisteredPeer>>> {
self.peers_by_idx.get(&peer_idx)
}
}
-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!(
"Adding wg 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()
}
+221
View File
@@ -0,0 +1,221 @@
use std::{net::SocketAddr, sync::Arc, time::Duration};
use async_recursion::async_recursion;
use boringtun::{
noise::{errors::WireGuardError, Tunn, TunnResult},
x25519,
};
use bytes::Bytes;
use log::{debug, error, info, warn};
use tap::TapFallible;
use tokio::{
net::UdpSocket,
sync::{broadcast, mpsc},
time::timeout,
};
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
udp_rx: mpsc::UnboundedReceiver<Event>,
// UDP socket used for sending data
udp: Arc<UdpSocket>,
// Peer endpoint
addr: SocketAddr,
// `boringtun` tunnel, used for crypto & WG protocol
wg_tunnel: Arc<tokio::sync::Mutex<Tunn>>,
// Signal close
close_tx: broadcast::Sender<()>,
close_rx: broadcast::Receiver<()>,
}
impl Drop for WireGuardTunnel {
fn drop(&mut self) {
info!("WireGuard tunnel: dropping");
self.close();
}
}
impl WireGuardTunnel {
fn close(&self) {
let _ = self.close_tx.send(());
}
pub fn new(
udp: Arc<UdpSocket>,
addr: SocketAddr,
static_private: x25519::StaticSecret,
peer_static_public: x25519::PublicKey,
) -> (Self, mpsc::UnboundedSender<Event>) {
let preshared_key = None;
let persistent_keepalive = None;
let index = 0;
let rate_limiter = None;
let wg_tunnel = Arc::new(tokio::sync::Mutex::new(
Tunn::new(
static_private,
peer_static_public,
preshared_key,
persistent_keepalive,
index,
rate_limiter,
)
.unwrap(),
));
// Channels with incoming data that is received by the main event loop
let (udp_tx, udp_rx) = mpsc::unbounded_channel();
// Signal close tunnel
let (close_tx, close_rx) = broadcast::channel(1);
let tunnel = WireGuardTunnel {
udp_rx,
udp,
addr,
wg_tunnel,
close_tx,
close_rx,
};
(tunnel, udp_tx)
}
pub async fn spin_off(&mut self) {
loop {
tokio::select! {
_ = self.close_rx.recv() => {
info!("WireGuard tunnel: received msg to close");
break;
},
packet = self.udp_rx.recv() => match packet {
Some(packet) => {
info!("WireGuard tunnel received: {packet}");
match packet {
Event::WgPacket(data) => {
let _ = self.consume_wg(&data)
.await
.tap_err(|err| error!("WireGuard tunnel: consume_wg error: {err}"));
},
Event::IpPacket(data) => self.consume_eth(&data).await,
_ => {},
}
},
None => {
info!("WireGuard tunnel: incoming UDP stream closed, closing tunnel");
break;
},
},
_ = tokio::time::sleep(Duration::from_millis(250)) => {
let _ = self.update_wg_timers()
.await
.map_err(|err| error!("WireGuard tunnel: update_wg_timers error: {err}"));
},
}
}
info!("WireGuard tunnel ({}): closed", self.addr);
}
async fn wg_tunnel_lock(&self) -> Result<tokio::sync::MutexGuard<'_, Tunn>, WgError> {
timeout(Duration::from_millis(100), self.wg_tunnel.lock())
.await
.map_err(|_| WgError::UnableToGetTunnel)
}
async fn consume_wg(&self, data: &[u8]) -> Result<(), WgError> {
let mut send_buf = [0u8; MAX_PACKET];
let mut peer = self.wg_tunnel_lock().await?;
match peer.decapsulate(None, data, &mut send_buf) {
TunnResult::WriteToNetwork(packet) => {
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 peer.decapsulate(None, &[], &mut send_buf) {
TunnResult::WriteToNetwork(packet) => {
if let Err(err) = self.udp.send_to(packet, self.addr).await {
error!("Failed to send decapsulation-instructed packet to WireGuard endpoint: {err:?}");
break;
};
}
_ => {
break;
}
}
}
}
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");
}
TunnResult::Err(err) => {
error!("WireGuard: decapsulate error: {err:?}");
}
}
Ok(())
}
async fn consume_eth(&self, _data: &Bytes) {
info!("WireGuard tunnel: consume_eth");
todo!();
}
async fn update_wg_timers(&mut self) -> Result<(), WgError> {
let mut send_buf = [0u8; MAX_PACKET];
let mut tun = self.wg_tunnel_lock().await?;
let tun_result = tun.update_timers(&mut send_buf);
self.handle_routine_tun_result(tun_result).await;
Ok(())
}
#[async_recursion]
async fn handle_routine_tun_result<'a: 'async_recursion>(&self, result: TunnResult<'a>) {
match result {
TunnResult::WriteToNetwork(packet) => {
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!");
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");
self.close();
return;
};
peer.format_handshake_initiation(&mut buf[..], false);
self.handle_routine_tun_result(result).await
}
TunnResult::Err(err) => {
error!("Failed to prepare routine packet for WireGuard endpoint: {err:?}");
}
TunnResult::Done => {}
other => {
warn!("Unexpected WireGuard routine task state: {other:?}");
}
};
}
}
-204
View File
@@ -1,204 +0,0 @@
use std::{net::SocketAddr, sync::Arc, time::Duration};
use boringtun::{
noise::{self, handshake::parse_handshake_anon, rate_limiter::RateLimiter, TunnResult},
x25519,
};
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,
registered_peers::{RegisteredPeer, RegisteredPeers},
setup::{self, WG_ADDRESS, WG_PORT},
TunTaskTx,
};
const MAX_PACKET: usize = 65535;
// Registered peers
pub(crate) type PeersByIp = NetworkTable<mpsc::UnboundedSender<Event>>;
// Active peers
pub(crate) type ActivePeers = DashMap<x25519::PublicKey, mpsc::UnboundedSender<Event>>;
pub(crate) type PeersByAddr = DashMap<SocketAddr, mpsc::UnboundedSender<Event>>;
async fn add_test_peer(registered_peers: &mut RegisteredPeers) {
let peer_static_public = setup::peer_static_public_key();
let peer_index = 0;
let peer_allowed_ips = setup::peer_allowed_ips();
let test_peer = Arc::new(tokio::sync::Mutex::new(RegisteredPeer {
public_key: peer_static_public,
index: peer_index,
allowed_ips: peer_allowed_ips,
}));
registered_peers.insert(peer_static_public, test_peer).await;
}
pub(crate) async fn start_udp_listener(
tun_task_tx: TunTaskTx,
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 = Arc::new(UdpSocket::bind(wg_address).await?);
// Setup our own keys
let static_private = setup::server_static_private_key();
let static_public = x25519::PublicKey::from(&static_private);
let handshake_max_rate = 100u64;
let rate_limiter = RateLimiter::new(&static_public, handshake_max_rate);
// Create a test peer for dev
let mut registered_peers = RegisteredPeers::default();
add_test_peer(&mut registered_peers).await;
tokio::spawn(async move {
// The set of active tunnels indexed by the peer's address
let active_peers = Arc::new(ActivePeers::new());
let active_peers_by_addr = PeersByAddr::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];
let mut dst_buf = [0u8; MAX_PACKET];
while !task_client.is_shutdown() {
tokio::select! {
() = task_client.recv() => {
log::trace!("WireGuard UDP listener: received shutdown");
break;
}
// Reset the rate limiter every 1 sec
() = tokio::time::sleep(Duration::from_secs(1)) => {
rate_limiter.reset_count();
},
// Handle tunnel closing
Some(public_key) = active_peers_task_handles.next() => {
match public_key {
Ok(public_key) => {
log::info!("Removing peer: {public_key:?}");
active_peers.remove(&public_key);
log::warn!("TODO: remove from peers_by_ip?");
log::warn!("TODO: remove from peers_by_addr");
}
Err(err) => {
error!("WireGuard UDP listener: error receiving shutdown from peer: {err}");
}
}
},
// Handle incoming packets
Ok((len, addr)) = udp.recv_from(&mut buf) => {
log::trace!("udp: received {} bytes from {}", len, addr);
// If this addr has already been encountered, send directly to tunnel
// TODO: optimization opportunity to instead create a connected UDP socket
// inside the wg tunnel, where you can recv the data directly.
if let Some(peer_tx) = active_peers_by_addr.get(&addr) {
log::info!("udp: received {len} bytes from {addr} from known peer");
peer_tx
.send(Event::Wg(buf[..len].to_vec().into()))
.tap_err(|e| log::error!("{e}"))
.ok();
continue;
}
// Verify the incoming packet
let verified_packet = match rate_limiter.verify_packet(Some(addr.ip()), &buf[..len], &mut dst_buf) {
Ok(packet) => packet,
Err(TunnResult::WriteToNetwork(cookie)) => {
log::info!("Send back cookie to: {addr}");
udp.send_to(cookie, addr).await.tap_err(|e| log::error!("{e}")).ok();
continue;
}
Err(err) => {
log::warn!("{err:?}");
continue;
}
};
// Check if this is a registered peer, if not, just skip
let registered_peer = {
let reg_peer = match verified_packet {
noise::Packet::HandshakeInit(ref packet) => {
let Ok(handshake) = parse_handshake_anon(&static_private, &static_public, packet) else {
log::warn!("Handshake failed: {addr}");
continue;
};
registered_peers.get_by_key(&x25519::PublicKey::from(handshake.peer_static_public))
},
noise::Packet::HandshakeResponse(packet) => {
let peer_idx = packet.receiver_idx >> 8;
registered_peers.get_by_idx(peer_idx)
},
noise::Packet::PacketCookieReply(packet) => {
let peer_idx = packet.receiver_idx >> 8;
registered_peers.get_by_idx(peer_idx)
},
noise::Packet::PacketData(packet) => {
let peer_idx = packet.receiver_idx >> 8;
registered_peers.get_by_idx(peer_idx)
},
};
match reg_peer {
Some(reg_peer) => reg_peer.lock().await,
None => {
log::warn!("Peer not registered: {addr}");
continue;
}
}
};
// Look up if the peer is already connected
if let Some(peer_tx) = active_peers.get_mut(&registered_peer.public_key) {
// We found the peer as connected, even though the addr was not known
log::info!("udp: received {len} bytes from {addr} which is a known peer with unknown addr");
peer_tx.send(Event::WgVerified(buf[..len].to_vec().into()))
.tap_err(|err| log::error!("{err}"))
.ok();
} else {
// If it isn't, start a new tunnel
log::info!("udp: received {len} bytes from {addr} from unknown peer, starting tunnel");
// NOTE: we are NOT passing in the existing rate_limiter. Re-visit this
// choice later.
log::warn!("Creating new rate limiter, consider re-using?");
let (join_handle, peer_tx) = crate::wg_tunnel::start_wg_tunnel(
addr,
udp.clone(),
static_private.clone(),
registered_peer.public_key,
registered_peer.index,
registered_peer.allowed_ips,
tun_task_tx.clone(),
);
peers_by_ip.lock().unwrap().insert(registered_peer.allowed_ips, peer_tx.clone());
active_peers_by_addr.insert(addr, peer_tx.clone());
peer_tx.send(Event::Wg(buf[..len].to_vec().into()))
.tap_err(|e| log::error!("{e}"))
.ok();
log::info!("Adding peer: {addr}");
active_peers.insert(registered_peer.public_key, peer_tx);
active_peers_task_handles.push(join_handle);
}
},
}
}
log::info!("WireGuard listener: shutting down");
});
Ok(())
}
-329
View File
@@ -1,329 +0,0 @@
use std::{net::SocketAddr, sync::Arc, time::Duration};
use async_recursion::async_recursion;
use boringtun::{
noise::{errors::WireGuardError, rate_limiter::RateLimiter, Tunn, TunnResult},
x25519,
};
use bytes::Bytes;
use log::{debug, error, info, warn};
use tap::TapFallible;
use tokio::{
net::UdpSocket,
sync::{broadcast, mpsc},
time::timeout,
};
use crate::{
error::WgError, event::Event, network_table::NetworkTable, registered_peers::PeerIdx, TunTaskTx,
};
const HANDSHAKE_MAX_RATE: u64 = 10;
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 socket used for sending data
udp: Arc<UdpSocket>,
// Peer endpoint
endpoint: Arc<tokio::sync::RwLock<SocketAddr>>,
// AllowedIPs for this peer
allowed_ips: NetworkTable<()>,
// `boringtun` tunnel, used for crypto & WG protocol
wg_tunnel: Arc<tokio::sync::Mutex<Tunn>>,
// 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 {
fn drop(&mut self) {
info!("WireGuard tunnel: dropping");
self.close();
}
}
impl WireGuardTunnel {
pub(crate) fn new(
udp: Arc<UdpSocket>,
endpoint: SocketAddr,
static_private: x25519::StaticSecret,
peer_static_public: x25519::PublicKey,
index: PeerIdx,
peer_allowed_ips: ip_network::IpNetwork,
// rate_limiter: Option<RateLimiter>,
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 static_public = x25519::PublicKey::from(&static_private);
let rate_limiter = Some(Arc::new(RateLimiter::new(
&static_public,
HANDSHAKE_MAX_RATE,
)));
let wg_tunnel = Arc::new(tokio::sync::Mutex::new(
Tunn::new(
static_private,
peer_static_public,
preshared_key,
persistent_keepalive,
index,
rate_limiter,
)
.unwrap(),
));
// Channels with incoming data that is received by the main event loop
let (peer_tx, peer_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,
endpoint: Arc::new(tokio::sync::RwLock::new(endpoint)),
allowed_ips,
wg_tunnel,
close_tx,
close_rx,
tun_task_tx: tunnel_tx,
};
(tunnel, peer_tx)
}
fn close(&self) {
let _ = self.close_tx.send(());
}
pub async fn spin_off(&mut self) {
loop {
tokio::select! {
_ = self.close_rx.recv() => {
info!("WireGuard tunnel: received msg to close");
break;
},
packet = self.peer_rx.recv() => match packet {
Some(packet) => {
info!("event loop: {packet}");
match packet {
Event::Wg(data) => {
let _ = self.consume_wg(&data)
.await
.tap_err(|err| error!("WireGuard tunnel: consume_wg error: {err}"));
},
Event::WgVerified(data) => {
let _ = self.consume_verified_wg(&data)
.await
.tap_err(|err| error!("WireGuard tunnel: consume_verified_wg error: {err}"));
}
Event::Ip(data) => self.consume_eth(&data).await,
}
},
None => {
info!("WireGuard tunnel: incoming UDP stream closed, closing tunnel");
break;
},
},
() = tokio::time::sleep(Duration::from_millis(250)) => {
let _ = self.update_wg_timers()
.await
.map_err(|err| error!("WireGuard tunnel: update_wg_timers error: {err}"));
},
}
}
info!("WireGuard tunnel ({}): closed", self.endpoint.read().await);
}
async fn wg_tunnel_lock(&self) -> Result<tokio::sync::MutexGuard<'_, Tunn>, WgError> {
timeout(Duration::from_millis(100), self.wg_tunnel.lock())
.await
.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> {
let mut send_buf = [0u8; MAX_PACKET];
let mut tunnel = self.wg_tunnel_lock().await?;
match tunnel.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 {
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) {
TunnResult::WriteToNetwork(packet) => {
log::info!("udp: send {} bytes to {}", packet.len(), *endpoint);
if let Err(err) = self.udp.send_to(packet, *endpoint).await {
error!("Failed to send decapsulation-instructed packet to WireGuard endpoint: {err:?}");
break;
};
}
_ => {
break;
}
}
}
}
TunnResult::WriteToTunnelV4(packet, 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) => {
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::Done => {
debug!("WireGuard: decapsulate done");
}
TunnResult::Err(err) => {
error!("WireGuard: decapsulate error: {err:?}");
}
}
Ok(())
}
async fn consume_verified_wg(&mut self, data: &[u8]) -> Result<(), WgError> {
// Potentially we could take some shortcuts here in the name of performance, but currently
// I don't see that the needed functions in boringtun is exposed in the public API.
// TODO: make sure we don't put double pressure on the rate limiter!
self.consume_wg(data).await
}
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 update_wg_timers(&mut self) -> Result<(), WgError> {
let mut send_buf = [0u8; MAX_PACKET];
let mut tun = self.wg_tunnel_lock().await?;
let tun_result = tun.update_timers(&mut send_buf);
self.handle_routine_tun_result(tun_result).await;
Ok(())
}
#[async_recursion]
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:?}");
};
}
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");
self.close();
return;
};
peer.format_handshake_initiation(&mut buf[..], false);
self.handle_routine_tun_result(result).await;
}
TunnResult::Err(err) => {
error!("Failed to prepare routine packet for WireGuard endpoint: {err:?}");
}
TunnResult::Done => {}
other => {
warn!("Unexpected WireGuard routine task state: {other:?}");
}
};
}
}
pub(crate) fn start_wg_tunnel(
endpoint: SocketAddr,
udp: Arc<UdpSocket>,
static_private: x25519::StaticSecret,
peer_static_public: x25519::PublicKey,
peer_index: PeerIdx,
peer_allowed_ips: ip_network::IpNetwork,
tunnel_tx: TunTaskTx,
) -> (
tokio::task::JoinHandle<x25519::PublicKey>,
mpsc::UnboundedSender<Event>,
) {
let (mut tunnel, peer_tx) = WireGuardTunnel::new(
udp,
endpoint,
static_private,
peer_static_public,
peer_index,
peer_allowed_ips,
tunnel_tx,
);
let join_handle = tokio::spawn(async move {
tunnel.spin_off().await;
peer_static_public
});
(join_handle, peer_tx)
}
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)
@@ -17,7 +17,8 @@ This is a *reference page*, to see the entire presentation join Max's talk at [H
## SDKs
* [Rust SDK](https://nymtech.net/docs/sdk/rust.html)
* [Typescript SDK](https://sdk.nymtech.net/)
* [Typescript SDK](https://nymtech.net/docs/sdk/typescript.html)
* [Interactive Typescript SDK docs](https://sdk.nymtech.net)
### Rust examples
@@ -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)
+1 -5
View File
@@ -2,8 +2,4 @@
Welcome to the Nym Developer Portal, containing quickstart resources, user manuals, integration information, and tutorials outlining to start building privacy enhanced apps.
For more in-depth information about nodes, network traffic flows, clients, coconut etc check out the [docs](https://nymtech.net/docs).
If you are looking for information and setup guides for the various pieces of Nym mixnet infrastructure (mix nodes, gateways and network requesters) and Nyx blockchain validators see the **new [Operators Guides](https://nymtech.net/operators)** book.
If you're looking for TypeScript/JavaScript related information such as SDKs to build your own tools, step-by-step tutorials, live playgrounds and more, make sure to check out the **new [TS SDK Handbook](https://sdk.nymtech.net/)** !
For more in-depth information about nodes, network traffic flows, clients, coconut etc check out the [docs](https://nymtech.net/docs). If you are looking for information and setup guides for the various pieces of Nym mixnet infrastructure (mix nodes, gateways and network requesters) and Nyx blockchain validators see the **new [Operators Guides](https://nymtech.net/operators)** book.
@@ -1,3 +1,3 @@
# Typescript
Tutorial code in this section is built to interact with a standalone Nym client. You can read about interacting with standalone clients [here](https://nymtech.net/docs/clients/websocket-client.html#connecting-to-the-local-websocket), although it is usually preferable to use the [Typescript SDK](https://sdk.nymtech.net/).
Tutorial code in this section is built to interact with a standalone Nym client. You can read about interacting with standalone clients [here](https://nymtech.net/docs/clients/websocket-client.html#connecting-to-the-local-websocket), although it is usually preferable to use the [Typescript SDK](https://nymtech.net/docs/sdk/typescript.html).
+2 -4
View File
@@ -2,11 +2,9 @@
This is Nym's technical documentation, containing information and setup guides about the various pieces of Nym software such as different mixnet infrastructure nodes, application clients, and existing applications like the desktop wallet and mixnet explorer.
If you are new to Nym and want to learn about the mixnet, explore kickstart options and demos, learn how to integrate with the network, and follow developer tutorials check out the [Developer Portal](https://nymtech.net/developers/) where you can find also our [FAQ section](https://nymtech.net/developers/faq/general-faq.md).
If you are looking for information and setup guides for the various pieces of Nym mixnet infrastructure (mix nodes, gateways and network requesters) and Nyx blockchain validators see the **new [Operators Guides](https://nymtech.net/operators)** book.
If you're specically looking for TypeScript/JavaScript related information such as SDKs to build your own tools, step-by-step tutorials, live playgrounds and more - make sure to check out the **new [TS SDK Handbook](https://sdk.nymtech.net/)** !
If you are new to Nym and want to learn about the mixnet, explore kickstart options and demos, learn how to integrate with the network, and follow developer tutorials check out the [Developer Portal](https://nymtech.net/developers/) where you can find also our [FAQ section](https://nymtech.net/developers/faq/general-faq.md).
## Popular pages
**Network Architecture:**
@@ -14,7 +12,7 @@ If you're specically looking for TypeScript/JavaScript related information such
* [Mixnet Traffic Flow](./architecture/traffic-flow.md)
**SDK examples:**
* [Typescript SDK](https://sdk.nymtech.net/)
* [Typescript SDK](./sdk/typescript.md)
* [Rust SDK](./sdk/rust.md)
**Nyx**
+63 -1
View File
@@ -1,4 +1,66 @@
# Typescript SDK
The Typescript SDK allows developers to start building browser-based mixnet applications quickly, by simply importing the SDK into their code via NPM as they would any other Typescript library.
> If you'd like to learn more, build apps or integrate Nym components using the TS SDK, please visit the **dedicated [TS SDK Handbook](https://sdk.nymtech.net/)** !
You can find the source code [here](https://github.com/nymtech/nym/tree/master/sdk) and the library on NPM [here](https://www.npmjs.com/package/@nymproject/sdk).
Currently developers can use the SDK to do the following **entirely in the browser**:
* Create a client
* Listen for incoming messages and reply to them
* Encrypt text and binary-encoded messages as Sphinx packets and send these through the mixnet
> We will be fleshing out further mixnet-related features in the coming weeks with functionality such as importing/exporting keypairs for developing apps with a retained identity over time.
In the future the SDK will be made up of several components, each of which will allow developers to interact with different parts of Nym's infrastructure.
| Component | Functionality | Released |
| --------- | ------------------------------------------------------------------------------ | -------- |
| Mixnet | Create clients & keypairs, subscribe to Mixnet events, send & receive messages | ✔️ |
| Coconut | Create & verify Coconut credentials | ❌ |
| Validator | Sign & broadcast Nyx blockchain transactions, query the blockchain | ❌ |
### How it works
The SDK can be thought of as a 'wrapper' around the compiled [WebAssembly client](https://github.com/nymtech/nym/tree/master/clients/webassembly) code: it runs the client (a Wasm blob) in a web worker. This allows us to keep the work done by the client - such as the heavy lifting of creating and multiply-encrypting Sphinx packets - in a seperate thread from our UI, enabling you to build reactive frontends without worrying about the work done under the hood by the client eating your processing power.
The SDK exposes an interface that allows developers to interact with the Wasm blob inside the webworker from frontend code.
### Framework Support
Currently, the SDK **only** works with frameworks that use either `Webpack` or `Parcel` as bundlers. If you want to use the SDK with a framework that isn't on this list, such as Angular, or NodeJS, **here be dragons!** These frameworks will probably use a different bundler and you will probably run into problems.
| Bundler | Supported |
| ------- | --------- |
| Webpack | ✔️ |
| Packer | ✔️ |
Support for environments with different bundlers will be added in subsequent releases.
| Environment | Supported |
| ---------------- | --------- |
| Browser | ✔️ |
| Headless NodeJS | ❌ |
| Electron Desktop | ❌ |
### Using the SDK
There are multiple example projects in [`nym/sdk/typescript/examples/`](https://github.com/nymtech/nym/tree/master/sdk/typescript/examples/), each for a different frontend framework.
#### Vanilla HTML
The best place to start if you just want to quickly get a basic frontend up and running with which to experiment is `examples/plain-html`:
```typescript
{{#include ../../../../sdk/typescript/examples/shared/index.ts}}
```
As you can see, all that is required to create an ephemeral keypair and connect to the mixnet is creating a client and then subscribing to the mixnet events coming down the websocket, and adding logic to deal with them.
#### Parcel
If you don't want to use `Webpack` as your app bundler, we have an example with `Parcel` located at [`examples/parcel/`](https://github.com/nymtech/nym/tree/master/sdk/typescript/examples/chat-app/parcel).
#### Create React App
For React developers we have an example which is a basic React app scaffold with the additional logic for creating a client and subscribing to mixnet events in [`examples/react-webpack-with-theme-example/`](https://github.com/nymtech/nym/tree/master/sdk/typescript/examples/chat-app/react-webpack-with-theme-example).
### Developers: think about what you're sending (and importing)!
Think about what information your app sends. That goes for whatever you put into your Sphinx packet messages as well as what your app's environment may leak.
Whenever you write client PEApps using HTML/JavaScript, we recommend that you **do not load external resources from CDNs**. Webapp developers do this all the time, to save load time for common resources, or just for convenience. But when you're writing privacy apps it's better not to make these kinds of requests. **Pack everything locally**.
If you use only local resources within your Electron app or your browser extensions, explicitly encoding request data in a Sphinx packet does protect you from the normal leakage that gets sent in a browser HTTP request. [There's a lot of stuff that leaks when you make an HTTP request from a browser window](https://panopticlick.eff.org/). Luckily, all that metadata and request leakage doesn't happen in Nym, because you're choosing very explicitly what to encode into Sphinx packets, instead of sending a whole browser environment by default.
+2 -2
View File
@@ -42,7 +42,7 @@ turn-off = true
[preprocessor.admonish]
command = "mdbook-admonish"
assets_version = "3.0.0" # do not edit: managed by `mdbook-admonish install`
assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install`
# variables preprocessor: import variables into files
# https://gitlab.com/tglman/mdbook-variables/
@@ -83,7 +83,7 @@ curly-quotes = true
# mathjax-support = false # useful if we want to pull equations in
copy-fonts = true
no-section-label = false
additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css","./custom.css", "./mdbook-admonish.css"]
additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css","./custom.css"]
additional-js = ["theme/pagetoc.js"]
git-repository-url = "https://github.com/nymtech/nym"
git-repository-icon = "fa-github"
+92 -81
View File
@@ -1,18 +1,31 @@
@charset "UTF-8";
:root {
--md-admonition-icon--admonish-note: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z'/></svg>");
--md-admonition-icon--admonish-abstract: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17 9H7V7h10m0 6H7v-2h10m-3 6H7v-2h7M12 3a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m7 0h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z'/></svg>");
--md-admonition-icon--admonish-info: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2z'/></svg>");
--md-admonition-icon--admonish-tip: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17.66 11.2c-.23-.3-.51-.56-.77-.82-.67-.6-1.43-1.03-2.07-1.66C13.33 7.26 13 4.85 13.95 3c-.95.23-1.78.75-2.49 1.32-2.59 2.08-3.61 5.75-2.39 8.9.04.1.08.2.08.33 0 .22-.15.42-.35.5-.23.1-.47.04-.66-.12a.58.58 0 0 1-.14-.17c-1.13-1.43-1.31-3.48-.55-5.12C5.78 10 4.87 12.3 5 14.47c.06.5.12 1 .29 1.5.14.6.41 1.2.71 1.73 1.08 1.73 2.95 2.97 4.96 3.22 2.14.27 4.43-.12 6.07-1.6 1.83-1.66 2.47-4.32 1.53-6.6l-.13-.26c-.21-.46-.77-1.26-.77-1.26m-3.16 6.3c-.28.24-.74.5-1.1.6-1.12.4-2.24-.16-2.9-.82 1.19-.28 1.9-1.16 2.11-2.05.17-.8-.15-1.46-.28-2.23-.12-.74-.1-1.37.17-2.06.19.38.39.76.63 1.06.77 1 1.98 1.44 2.24 2.8.04.14.06.28.06.43.03.82-.33 1.72-.93 2.27z'/></svg>");
--md-admonition-icon--admonish-success: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m9 20.42-6.21-6.21 2.83-2.83L9 14.77l9.88-9.89 2.83 2.83L9 20.42z'/></svg>");
--md-admonition-icon--admonish-question: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m15.07 11.25-.9.92C13.45 12.89 13 13.5 13 15h-2v-.5c0-1.11.45-2.11 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41a2 2 0 0 0-2-2 2 2 0 0 0-2 2H8a4 4 0 0 1 4-4 4 4 0 0 1 4 4 3.2 3.2 0 0 1-.93 2.25M13 19h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10c0-5.53-4.5-10-10-10z'/></svg>");
--md-admonition-icon--admonish-warning: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 14h-2V9h2m0 9h-2v-2h2M1 21h22L12 2 1 21z'/></svg>");
--md-admonition-icon--admonish-failure: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20 6.91 17.09 4 12 9.09 6.91 4 4 6.91 9.09 12 4 17.09 6.91 20 12 14.91 17.09 20 20 17.09 14.91 12 20 6.91z'/></svg>");
--md-admonition-icon--admonish-danger: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M11 15H6l7-14v8h5l-7 14v-8z'/></svg>");
--md-admonition-icon--admonish-bug: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 12h-4v-2h4m0 6h-4v-2h4m6-6h-2.81a5.985 5.985 0 0 0-1.82-1.96L17 4.41 15.59 3l-2.17 2.17a6.002 6.002 0 0 0-2.83 0L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8z'/></svg>");
--md-admonition-icon--admonish-example: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M7 13v-2h14v2H7m0 6v-2h14v2H7M7 7V5h14v2H7M3 8V5H2V4h2v4H3m-1 9v-1h3v4H2v-1h2v-.5H3v-1h1V17H2m2.25-7a.75.75 0 0 1 .75.75c0 .2-.08.39-.21.52L3.12 13H5v1H2v-.92L4 11H2v-1h2.25z'/></svg>");
--md-admonition-icon--admonish-quote: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 17h3l2-4V7h-6v6h3M6 17h3l2-4V7H5v6h3l-2 4z'/></svg>");
--md-details-icon: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M8.59 16.58 13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.42Z'/></svg>");
--md-admonition-icon--note:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z'/></svg>");
--md-admonition-icon--abstract:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17 9H7V7h10m0 6H7v-2h10m-3 6H7v-2h7M12 3a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m7 0h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z'/></svg>");
--md-admonition-icon--info:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2z'/></svg>");
--md-admonition-icon--tip:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17.66 11.2c-.23-.3-.51-.56-.77-.82-.67-.6-1.43-1.03-2.07-1.66C13.33 7.26 13 4.85 13.95 3c-.95.23-1.78.75-2.49 1.32-2.59 2.08-3.61 5.75-2.39 8.9.04.1.08.2.08.33 0 .22-.15.42-.35.5-.23.1-.47.04-.66-.12a.58.58 0 0 1-.14-.17c-1.13-1.43-1.31-3.48-.55-5.12C5.78 10 4.87 12.3 5 14.47c.06.5.12 1 .29 1.5.14.6.41 1.2.71 1.73 1.08 1.73 2.95 2.97 4.96 3.22 2.14.27 4.43-.12 6.07-1.6 1.83-1.66 2.47-4.32 1.53-6.6l-.13-.26c-.21-.46-.77-1.26-.77-1.26m-3.16 6.3c-.28.24-.74.5-1.1.6-1.12.4-2.24-.16-2.9-.82 1.19-.28 1.9-1.16 2.11-2.05.17-.8-.15-1.46-.28-2.23-.12-.74-.1-1.37.17-2.06.19.38.39.76.63 1.06.77 1 1.98 1.44 2.24 2.8.04.14.06.28.06.43.03.82-.33 1.72-.93 2.27z'/></svg>");
--md-admonition-icon--success:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m9 20.42-6.21-6.21 2.83-2.83L9 14.77l9.88-9.89 2.83 2.83L9 20.42z'/></svg>");
--md-admonition-icon--question:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m15.07 11.25-.9.92C13.45 12.89 13 13.5 13 15h-2v-.5c0-1.11.45-2.11 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41a2 2 0 0 0-2-2 2 2 0 0 0-2 2H8a4 4 0 0 1 4-4 4 4 0 0 1 4 4 3.2 3.2 0 0 1-.93 2.25M13 19h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10c0-5.53-4.5-10-10-10z'/></svg>");
--md-admonition-icon--warning:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 14h-2V9h2m0 9h-2v-2h2M1 21h22L12 2 1 21z'/></svg>");
--md-admonition-icon--failure:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20 6.91 17.09 4 12 9.09 6.91 4 4 6.91 9.09 12 4 17.09 6.91 20 12 14.91 17.09 20 20 17.09 14.91 12 20 6.91z'/></svg>");
--md-admonition-icon--danger:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M11 15H6l7-14v8h5l-7 14v-8z'/></svg>");
--md-admonition-icon--bug:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 12h-4v-2h4m0 6h-4v-2h4m6-6h-2.81a5.985 5.985 0 0 0-1.82-1.96L17 4.41 15.59 3l-2.17 2.17a6.002 6.002 0 0 0-2.83 0L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8z'/></svg>");
--md-admonition-icon--example:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M7 13v-2h14v2H7m0 6v-2h14v2H7M7 7V5h14v2H7M3 8V5H2V4h2v4H3m-1 9v-1h3v4H2v-1h2v-.5H3v-1h1V17H2m2.25-7a.75.75 0 0 1 .75.75c0 .2-.08.39-.21.52L3.12 13H5v1H2v-.92L4 11H2v-1h2.25z'/></svg>");
--md-admonition-icon--quote:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 17h3l2-4V7h-6v6h3M6 17h3l2-4V7H5v6h3l-2 4z'/></svg>");
--md-details-icon:
url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M8.59 16.58 13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.42Z'/></svg>");
}
:is(.admonition) {
@@ -62,9 +75,8 @@ a.admonition-anchor-link::before {
content: "§";
}
:is(.admonition-title, summary.admonition-title) {
:is(.admonition-title, summary) {
position: relative;
min-height: 4rem;
margin-block: 0;
margin-inline: -1.6rem -1.2rem;
padding-block: 0.8rem;
@@ -73,13 +85,13 @@ a.admonition-anchor-link::before {
background-color: rgba(68, 138, 255, 0.1);
display: flex;
}
:is(.admonition-title, summary.admonition-title) p {
:is(.admonition-title, summary) p {
margin: 0;
}
html :is(.admonition-title, summary.admonition-title):last-child {
html :is(.admonition-title, summary):last-child {
margin-bottom: 0;
}
:is(.admonition-title, summary.admonition-title)::before {
:is(.admonition-title, summary)::before {
position: absolute;
top: 0.625em;
inset-inline-start: 1.6rem;
@@ -94,7 +106,7 @@ html :is(.admonition-title, summary.admonition-title):last-child {
-webkit-mask-size: contain;
content: "";
}
:is(.admonition-title, summary.admonition-title):hover a.admonition-anchor-link {
:is(.admonition-title, summary):hover a.admonition-anchor-link {
display: initial;
}
@@ -119,204 +131,204 @@ details[open].admonition > summary.admonition-title::after {
transform: rotate(90deg);
}
:is(.admonition):is(.admonish-note) {
:is(.admonition):is(.note) {
border-color: #448aff;
}
:is(.admonish-note) > :is(.admonition-title, summary.admonition-title) {
:is(.note) > :is(.admonition-title, summary) {
background-color: rgba(68, 138, 255, 0.1);
}
:is(.admonish-note) > :is(.admonition-title, summary.admonition-title)::before {
:is(.note) > :is(.admonition-title, summary)::before {
background-color: #448aff;
mask-image: var(--md-admonition-icon--admonish-note);
-webkit-mask-image: var(--md-admonition-icon--admonish-note);
mask-image: var(--md-admonition-icon--note);
-webkit-mask-image: var(--md-admonition-icon--note);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.admonish-abstract, .admonish-summary, .admonish-tldr) {
:is(.admonition):is(.abstract, .summary, .tldr) {
border-color: #00b0ff;
}
:is(.admonish-abstract, .admonish-summary, .admonish-tldr) > :is(.admonition-title, summary.admonition-title) {
:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary) {
background-color: rgba(0, 176, 255, 0.1);
}
:is(.admonish-abstract, .admonish-summary, .admonish-tldr) > :is(.admonition-title, summary.admonition-title)::before {
:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary)::before {
background-color: #00b0ff;
mask-image: var(--md-admonition-icon--admonish-abstract);
-webkit-mask-image: var(--md-admonition-icon--admonish-abstract);
mask-image: var(--md-admonition-icon--abstract);
-webkit-mask-image: var(--md-admonition-icon--abstract);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.admonish-info, .admonish-todo) {
:is(.admonition):is(.info, .todo) {
border-color: #00b8d4;
}
:is(.admonish-info, .admonish-todo) > :is(.admonition-title, summary.admonition-title) {
:is(.info, .todo) > :is(.admonition-title, summary) {
background-color: rgba(0, 184, 212, 0.1);
}
:is(.admonish-info, .admonish-todo) > :is(.admonition-title, summary.admonition-title)::before {
:is(.info, .todo) > :is(.admonition-title, summary)::before {
background-color: #00b8d4;
mask-image: var(--md-admonition-icon--admonish-info);
-webkit-mask-image: var(--md-admonition-icon--admonish-info);
mask-image: var(--md-admonition-icon--info);
-webkit-mask-image: var(--md-admonition-icon--info);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.admonish-tip, .admonish-hint, .admonish-important) {
:is(.admonition):is(.tip, .hint, .important) {
border-color: #00bfa5;
}
:is(.admonish-tip, .admonish-hint, .admonish-important) > :is(.admonition-title, summary.admonition-title) {
:is(.tip, .hint, .important) > :is(.admonition-title, summary) {
background-color: rgba(0, 191, 165, 0.1);
}
:is(.admonish-tip, .admonish-hint, .admonish-important) > :is(.admonition-title, summary.admonition-title)::before {
:is(.tip, .hint, .important) > :is(.admonition-title, summary)::before {
background-color: #00bfa5;
mask-image: var(--md-admonition-icon--admonish-tip);
-webkit-mask-image: var(--md-admonition-icon--admonish-tip);
mask-image: var(--md-admonition-icon--tip);
-webkit-mask-image: var(--md-admonition-icon--tip);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.admonish-success, .admonish-check, .admonish-done) {
:is(.admonition):is(.success, .check, .done) {
border-color: #00c853;
}
:is(.admonish-success, .admonish-check, .admonish-done) > :is(.admonition-title, summary.admonition-title) {
:is(.success, .check, .done) > :is(.admonition-title, summary) {
background-color: rgba(0, 200, 83, 0.1);
}
:is(.admonish-success, .admonish-check, .admonish-done) > :is(.admonition-title, summary.admonition-title)::before {
:is(.success, .check, .done) > :is(.admonition-title, summary)::before {
background-color: #00c853;
mask-image: var(--md-admonition-icon--admonish-success);
-webkit-mask-image: var(--md-admonition-icon--admonish-success);
mask-image: var(--md-admonition-icon--success);
-webkit-mask-image: var(--md-admonition-icon--success);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.admonish-question, .admonish-help, .admonish-faq) {
:is(.admonition):is(.question, .help, .faq) {
border-color: #64dd17;
}
:is(.admonish-question, .admonish-help, .admonish-faq) > :is(.admonition-title, summary.admonition-title) {
:is(.question, .help, .faq) > :is(.admonition-title, summary) {
background-color: rgba(100, 221, 23, 0.1);
}
:is(.admonish-question, .admonish-help, .admonish-faq) > :is(.admonition-title, summary.admonition-title)::before {
:is(.question, .help, .faq) > :is(.admonition-title, summary)::before {
background-color: #64dd17;
mask-image: var(--md-admonition-icon--admonish-question);
-webkit-mask-image: var(--md-admonition-icon--admonish-question);
mask-image: var(--md-admonition-icon--question);
-webkit-mask-image: var(--md-admonition-icon--question);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.admonish-warning, .admonish-caution, .admonish-attention) {
:is(.admonition):is(.warning, .caution, .attention) {
border-color: #ff9100;
}
:is(.admonish-warning, .admonish-caution, .admonish-attention) > :is(.admonition-title, summary.admonition-title) {
:is(.warning, .caution, .attention) > :is(.admonition-title, summary) {
background-color: rgba(255, 145, 0, 0.1);
}
:is(.admonish-warning, .admonish-caution, .admonish-attention) > :is(.admonition-title, summary.admonition-title)::before {
:is(.warning, .caution, .attention) > :is(.admonition-title, summary)::before {
background-color: #ff9100;
mask-image: var(--md-admonition-icon--admonish-warning);
-webkit-mask-image: var(--md-admonition-icon--admonish-warning);
mask-image: var(--md-admonition-icon--warning);
-webkit-mask-image: var(--md-admonition-icon--warning);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.admonish-failure, .admonish-fail, .admonish-missing) {
:is(.admonition):is(.failure, .fail, .missing) {
border-color: #ff5252;
}
:is(.admonish-failure, .admonish-fail, .admonish-missing) > :is(.admonition-title, summary.admonition-title) {
:is(.failure, .fail, .missing) > :is(.admonition-title, summary) {
background-color: rgba(255, 82, 82, 0.1);
}
:is(.admonish-failure, .admonish-fail, .admonish-missing) > :is(.admonition-title, summary.admonition-title)::before {
:is(.failure, .fail, .missing) > :is(.admonition-title, summary)::before {
background-color: #ff5252;
mask-image: var(--md-admonition-icon--admonish-failure);
-webkit-mask-image: var(--md-admonition-icon--admonish-failure);
mask-image: var(--md-admonition-icon--failure);
-webkit-mask-image: var(--md-admonition-icon--failure);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.admonish-danger, .admonish-error) {
:is(.admonition):is(.danger, .error) {
border-color: #ff1744;
}
:is(.admonish-danger, .admonish-error) > :is(.admonition-title, summary.admonition-title) {
:is(.danger, .error) > :is(.admonition-title, summary) {
background-color: rgba(255, 23, 68, 0.1);
}
:is(.admonish-danger, .admonish-error) > :is(.admonition-title, summary.admonition-title)::before {
:is(.danger, .error) > :is(.admonition-title, summary)::before {
background-color: #ff1744;
mask-image: var(--md-admonition-icon--admonish-danger);
-webkit-mask-image: var(--md-admonition-icon--admonish-danger);
mask-image: var(--md-admonition-icon--danger);
-webkit-mask-image: var(--md-admonition-icon--danger);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.admonish-bug) {
:is(.admonition):is(.bug) {
border-color: #f50057;
}
:is(.admonish-bug) > :is(.admonition-title, summary.admonition-title) {
:is(.bug) > :is(.admonition-title, summary) {
background-color: rgba(245, 0, 87, 0.1);
}
:is(.admonish-bug) > :is(.admonition-title, summary.admonition-title)::before {
:is(.bug) > :is(.admonition-title, summary)::before {
background-color: #f50057;
mask-image: var(--md-admonition-icon--admonish-bug);
-webkit-mask-image: var(--md-admonition-icon--admonish-bug);
mask-image: var(--md-admonition-icon--bug);
-webkit-mask-image: var(--md-admonition-icon--bug);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.admonish-example) {
:is(.admonition):is(.example) {
border-color: #7c4dff;
}
:is(.admonish-example) > :is(.admonition-title, summary.admonition-title) {
:is(.example) > :is(.admonition-title, summary) {
background-color: rgba(124, 77, 255, 0.1);
}
:is(.admonish-example) > :is(.admonition-title, summary.admonition-title)::before {
:is(.example) > :is(.admonition-title, summary)::before {
background-color: #7c4dff;
mask-image: var(--md-admonition-icon--admonish-example);
-webkit-mask-image: var(--md-admonition-icon--admonish-example);
mask-image: var(--md-admonition-icon--example);
-webkit-mask-image: var(--md-admonition-icon--example);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.admonish-quote, .admonish-cite) {
:is(.admonition):is(.quote, .cite) {
border-color: #9e9e9e;
}
:is(.admonish-quote, .admonish-cite) > :is(.admonition-title, summary.admonition-title) {
:is(.quote, .cite) > :is(.admonition-title, summary) {
background-color: rgba(158, 158, 158, 0.1);
}
:is(.admonish-quote, .admonish-cite) > :is(.admonition-title, summary.admonition-title)::before {
:is(.quote, .cite) > :is(.admonition-title, summary)::before {
background-color: #9e9e9e;
mask-image: var(--md-admonition-icon--admonish-quote);
-webkit-mask-image: var(--md-admonition-icon--admonish-quote);
mask-image: var(--md-admonition-icon--quote);
-webkit-mask-image: var(--md-admonition-icon--quote);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
@@ -327,8 +339,7 @@ details[open].admonition > summary.admonition-title::after {
background-color: var(--sidebar-bg);
}
.ayu :is(.admonition),
.coal :is(.admonition) {
.ayu :is(.admonition), .coal :is(.admonition) {
background-color: var(--theme-hover);
}
-4
View File
@@ -24,10 +24,6 @@
- [Mix Nodes](./faq/mixnodes-faq.md)
- [Project Smoosh](./faq/smoosh-faq.md)
# Legal Forum
- [Exit Gateway](./legal/exit-gateway.md)
---
# Misc.
- [Code of Conduct](coc.md)
@@ -2,10 +2,9 @@
> We aim on purpose to make minimal changes to reward scheme and software. We're just 'smooshing' together stuff we already debugged and know works.
> -- Harry Halpin, Nym CEO
<p></p>
<p>
This page refer to the changes which are planned to take place over Q3 and Q4 2023. As this is a transition period in the beginning (Q3 2023) the [Mix Nodes FAQ page](./mixnodes-faq.md) holds more answers to the current setup as project Smoosh refers to the eventual setup. As project Smoosh gets progressively implemented the answers on this page will become to be more relevant to the current state and eventually this FAQ page will be merged with the still relevant parts of the main Mix Nodes FAQ page.
</p>
If any questions are not answered or it's not clear for you in which stage project Smoosh is right now, please reach out in Node Operators [Matrix room](https://matrix.to/#/#operators:nymtech.chat).
## Overview
@@ -41,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?
@@ -1,205 +0,0 @@
# Nym operators - Running Exit Gateway
```admonish info
The entire content of this page is under [Creative Commons Attribution 4.0 International Public License](https://creativecommons.org/licenses/by/4.0/).
```
This page is a part of Nym Community Legal Forum and its content is composed by shared advices in [Node Operators Legal Forum](https://matrix.to/#/!YfoUFsJjsXbWmijbPG:nymtech.chat?via=nymtech.chat&via=matrix.org) (Matrix chat) as well as though pull requests done by the node operators directly to our [repository](https://github.com/nymtech/nym/tree/develop/documentation/operators/src), reviewed by Nym DevRels.
This document presents an initiative to further support Nyms mission of allowing privacy for everyone everywhere. This would be achieved with the support of Nym node operators operating gateways and opening these to any online service with the safeguards of the [Tor Null deny list](https://tornull.org/).
```admonish warning
Nym core team cannot provide comprehensive legal advice across all jurisdictions. Knowledge and experience with the legalities are being built up with the help of our counsel and with you, the community of Nym node operators. We encourage Nym node operators to join the operator channels ([Element](https://matrix.to/#/#operators:nymtech.chat), [Discord](https://discord.com/invite/nym), [Telegram](https://t.me/nymchan_help_chat)) to share best practices and experiences.
```
## Summary
* This document outlines a plan to change Nym Gateways from operating with an allow to a deny list to enable broader uptake and usage of the Nym mixnet. It provides operators with an overview of the plan, pros and cons, legal as well as technical advice.
* Nym is committed to ensuring privacy for all users, regardless of their location and for the broadest possible range of online services. In order to achieve this aim, the Nym mixnet needs to increase its usability across a broad range of apps and services.
* Currently, Nym Gateway nodes only enable access to apps and services that are on an allow list that is maintained by the core team.
* To decentralise and enable privacy for a broader range of services, this initiative will have to transition from the current allow list to a deny list (based on the [Tor Null advisory BL](https://tornull.org/)).
* This will enhance the usage and appeal of Nym products for end users. As a result, increased usage will ultimately lead to higher revenues for Nym operators.
* Nym core team cannot provide operators with definitive answers regarding the potential risks of operating open Gateways. However, there is online evidence of operating Tor exit relays:
* From a technical perspective, Nym node operators may need to implement additional controls, such as dedicated hardware and IP usage, or setting up an HTML exit notice on port 80.
* From an operational standpoint, node operators may be expected to actively manage their relationship with their ISP or VPS provider and respond to abuse requests using the proposed templates.
* Legally, exit relays are typically considered "telecommunication networks" and are subject to intermediary liability protection. However, there may be exceptions, particularly in cases involving criminal law and copyright claims. Operators could seek advice from local privacy associations and may consider running nodes under an entity rather than as individuals.
* This document serves as the basis for a consultation with Nym node operators on any concerns or additional support and information you need for this change to be successful and ensure maximum availability, usability and adoption.
## Goal of the initiative
**Nym supports privacy for everyone, everywhere.**
To offer a better and more private everyday experience for its users, Nym would like them to use any online services they please, without limiting its access to a few messaging apps or crypto wallets.
To achieve this, operators running “gateways” would have to “open” their nodes to a wider range of online services, in a similar fashion to Tor exit relays.
## Pros and cons of the initiative
Previous setup: Running nodes supporting strict SOCKS5 app-based traffic
| **Dimension** | **Pros** | **Cons** |
| :--- | :--- | :--- |
| Aspirational | | - Very limited use cases, not supportive of the “Privacy for everyone everywhere” aspiration<br>- Limited appeal to users, low competitiveness in the market, thus low usage |
| Technical | - No changes required in technical setup | |
| Operational | - No impact on operators operations (e.g., relationships with VPS providers)<br>- Low overhead<br>- Can be run as an individual | |
| Legal | - Limited legal risks for operators | |
| Financial | | - Low revenues for operators due to limited product traction |
The new setup: Running nodes supporting traffic of any online service (with safeguards in the form of an denylist)
| **Dimension** | **Pros** | **Cons** |
| :--- | :--- | :--- |
| Aspirational | - Higher market appeal of a fully-fledged product able to answer all users use cases<br>- Relevance in the market, driving higher usage | |
| Technical | - Very limited changes required in the technical setup (changes in the allow -> denylist) | - Increased monitoring required to detect and prevent abuse (e.g. spam) |
| Operational | | - Higher operational overhead, such as dealing with DMCA / abuse complaints, managing the VPS provider questions, or helping the community to maintain the denylist <br>- Administrative overhead if running nodes as a company or an entity |
| Legal | | - Ideally requires to check legal environment with local privacy association or lawyer | Financial | - Higher revenue potential for operators due to the increase in network usage | - If not running VPS with an unlimited bandwidth plan, higher costs due to higher network usage |
## New gateway setup
In our previous technical setup, network requesters acted as a proxy, and only made requests that match an allow list. That was a default IP based list of allowed domains stored at Nym page in a centralised fashion possibly re-defined by any Network requester operator.
This restricts the hosts that the NymConnect app can connect to and has the effect of selectively supporting messaging services (e.g. Telegram, Matrix) or crypto wallets (e.g. Electrum or Monero). Operators of network requesters can have confidence that the infrastructure they run only connects to a limited set of public internet hosts.
In the new setup, the main change is to expand this short allow list to a more permissive setup. An exit policy will constrain the hosts that the users of the Nym Mixnet and Nym VPN can connect to. This will be done in an effort to protect the operators, as Gateways will act both as SOCKS5 Network Requesters, and exit nodes for IP traffic from Nym Mixnet VPN and VPN clients (both wrapped in the same app).
As of now we the gateways will be defaulted to Tornulls (note: Not affiliated with Tor) deny list - reproduction permitted under Creative Commons Attribution 3.0 United States License which is IP-based, e.g., `ExitPolicy reject 5.188.10.0/23:*`. Whether we will stick with this list, do modifications (likely) or compile another one is still a subject of discussion.
<:--
These policies will be either reused without modification from Tor / Tornull (license permitting), or customized and updated in a Nym crowd-sourced community effort.
-->
The Gateways will display an HTML page similar to that suggested by [Tor](https://gitlab.torproject.org/tpo/core/tor/-/raw/HEAD/contrib/operator-tools/tor-exit-notice.html) for exit relays on port 80 and port 443. This will allow the operator to provide information about their Gateway, possibly including the currently configured exit policy, without having to actively communicate with law enforcement or regulatory authorities. It also makes the behaviour of the Gateway transparent and even computable (a possible feature would be to offer a machine readable form of the notice in JSON or YAML).
We also recommend operators to check the technical advice from [Tor](https://community.torproject.org/relay/setup/exit/).
## Tor legal advice
Giving the legal similarity between Nym exit gateways and Tor exit relays, it is helpful to have a look in [Tor community Exit Guidelines](https://community.torproject.org/relay/community-resources/tor-exit-guidelines/). This chapter is an exert of tor page.
Note that Tor states:
> This FAQ is for informational purposes only and does not constitute legal advice.
*Check legal advice prior to running an exit relay*
* Understand the risks associated with running an exit relay; E.g., know legal paragraphs relevant in the country of operations:
- US [DMCA 512](https://www.law.cornell.edu/uscode/text/17/512); see [EFF's Legal FAQ for TOr Operators](https://community.torproject.org/relay/community-resources/eff-tor-legal-faq) (a very good and relevant read for other countries as well)
- Germanys [TeleMedienGesetz 8](http://www.gesetze-im-internet.de/tmg/__8.html) and [15](http://www.gesetze-im-internet.de/tmg/__15.html)
- Netherlands: [Artikel 6:196c BW](http://wetten.overheid.nl/BWBR0005289/Boek6/Titel3/Afdeling4A/Artikel196c/)
- Austria: [E-Commerce-Gesetz 13](http://www.ris.bka.gv.at/Dokument.wxe?Abfrage=Bundesnormen&Dokumentnummer=NOR40025809)
- Sweden: [16-19 2002:562](https://lagen.nu/2002:562#P16S1)
* Top 3 advice
- Have an abuse response letter
- Run relay from a location that is not home
- Read through the legal resources that Tor-supportive lawyers put together: https://www.eff.org/pages/legal-faq-tor-relay-operators or https://www.noisebridge.net/wiki/Noisebridge_Tor/FBI
* Consult a lawyer / local digital rights association / the EFF prior to operating an exit relay, especially in a place where exit relay operators have been harassed or not operating before. Note that Tor DOES NOT provide legal advice for specific countries. It only provides general advice (itself or in partnership), eventually skewed towards [US audiences](https://www.eff.org/pages/legal-faq-tor-relay-operators).
*Run an exit relay within an entity*
As an organisation - it might help from a liability perspective
* Within your university
* With a node operators association (e.g., a Torservers.net partner)
* Within a company
*Be ready to respond to abuse complaints*
* Make your contact details (email, phone, or even fax) available, instead of those of the ISP
* Reply in a timely manner (e.g., 24 hours) using the [provided templates](https://community.torproject.org/relay/community-resources/tor-abuse-templates)
* Note that Tor states: *“We are not aware of any case that made it near a court, and we will do everything in our power to support you if it does.”*
* Document experience with ISPs at [community.torproject.org/relay/community-resources/good-bad-isps](https://community.torproject.org/relay/community-resources/good-bad-isps/)
Useful links:
* Tor abuse templates:
- [community.torproject.org/relay/community-resources/tor-abuse-templates/](https://community.torproject.org/relay/community-resources/tor-abuse-templates/)
- [gitlab.torproject.org/legacy/trac/-/wikis/doc/TorAbuseTemplates](https://gitlab.torproject.org/legacy/trac/-/wikis/doc/TorAbuseTemplates) (from 2020)
- [github.com/coldhakca/abuse-templates/blob/master/generic.template](https://github.com/coldhakca/abuse-templates/blob/master/generic.template)
* DMCA response templates:
- [community.torproject.org/relay/community-resources/eff-tor-legal-faq/tor-dmca-response/](https://community.torproject.org/relay/community-resources/eff-tor-legal-faq/tor-dmca-response/)
- [2019.www.torproject.org/eff/tor-dmca-response.html](https://2019.www.torproject.org/eff/tor-dmca-response.html) (from 2011)
- [github.com/coldhakca/abuse-templates/blob/master/dmca.template](https://github.com/coldhakca/abuse-templates/blob/master/dmca.template)
## Legal environment - Findings from our legal team
```admonish warning
Nym core team cannot provide comprehensive legal advice across all jurisdictions. Knowledge and experience with the legalities are being built up with the help of our counsel and with you, the community of Nym node operators. We encourage Nym node operators to join the operator channels ([Element](https://matrix.to/#/#operators:nymtech.chat), [Discord](https://discord.com/invite/nym), [Telegram](https://t.me/nymchan_help_chat)) to share best practices and experiences.
```
The Swiss legal counsel and US legal counsel have so far provided the following advice:
### Switzerland
TBD soon.
### United States
A US counsel shared the following advice:
The legal risk faced by VPN operators subject to United States jurisdiction depends on various statutes and regulations related to privacy, anonymity, and electronic communications. The key areas to consider are: intermediary liability and exceptions, data protection, copyright infringement, export controls, criminal law, government requests for data and assistance, and third party liability.
As outlined in Part A, the United States treats VPNs as telecommunications networks subject to intermediary liability protection from wrongful conduct that occurs on its network. However, such protections do have exceptions including criminal law and copyright claims that are worth considering. In the United States, I am not aware of an individual ever being prosecuted or convicted for running a node for a dVPN or a Privacy Enhancing Network.
However, as discussed in Part B-C, VPN operators are subject to law enforcement requests for access or assistance in obtaining access to data relevant to an investigation into allegedly unlawful conduct that was facilitated by the network as an intermediary. As shown in Part C, governments may also request assistance from node operators for certain high-level and national security targets.
Finally, as outlined in Parts D-G, VPN operators may also be subject to non-criminal liability including (Part D) failing to respond to notices under the DMCA, (Part E) privacy and data protection law, (Part F) third party lawsuits stemming from wrongful acts committed using the network, and (G) export control violations.
## How to add legal information
Our aim is to establish a strong community network, sharing legal findings with each other. We would like to encourage all the current and future operators to do research about the situation in the jurisdiction they operate and update this page.
First of all, please join our [Node Operators Legal Forum](https://matrix.to/#/!YfoUFsJjsXbWmijbPG:nymtech.chat?via=nymtech.chat&via=matrix.org) (Matrix chat) and post any information or questions there.
To add your information to this book, you can create a pull request directly to our [repository](https://github.com/nymtech/nym/tree/develop/documentation/operators/src), than ping the admins in the [Legal Forum chat](https://matrix.to/#/!YfoUFsJjsXbWmijbPG:nymtech.chat?via=nymtech.chat&via=matrix.org) and we will review it as fast as possible.
To do so, follow the steps below:
1. Write your legal findings down in a text editor (Soon we will share a template)
2. Clone `nymtech/nym` repository and switch to develop branch
```sh
# Clone the repository
git clone https://github.com/nymtech/nym
# Go to the directory nym
cd nym
# Switch to branch develop
git checkout develop
# Update the repository
git pull origin develop
```
3. Make your own branch based off `develop` and switch to it
```sh
git branch operators/legal-forum/<MY_BRANCH_NAME> # choose a descriptive and consiose name without using <>
git checkout operators/legal-forum/<MY_BRANCH_NAME>
# you can double check that you are on the right branch
git branch
```
4. Save your legal findings as `<FILE_NAME>.md` to `/nym/documentation/operators/src/legal`
5. Don't change anything in `SUMMARY.md`, the admins will do it when merging
6. Add, commit and push your changes
```sh
cd documentation/operators/src/legal
git add <FILE_NAME>.md
git commit -am "<describe your changes>"
git push origin operators/legal-forum/<MY_BRANCH_NAME>
```
7. Notify others in the [Node Operators Legal Forum](https://matrix.to/#/!YfoUFsJjsXbWmijbPG:nymtech.chat?via=nymtech.chat&via=matrix.org) (Matrix chat)
+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();
}
+5 -5
View File
@@ -101,7 +101,7 @@ impl ApiCmdProcessor {
}
fn ephemera_config<A: Application>(
ephemera: &Ephemera<A>,
ephemera: &mut Ephemera<A>,
reply: Sender<api::Result<ApiEphemeraConfig>>,
) {
let node_info = ephemera.node_info.clone();
@@ -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()))
+6 -6
View File
@@ -191,10 +191,10 @@ impl<A: Application> EphemeraStarterWithApplication<A> {
let block_manager = self.init_block_manager(&mut storage)?;
let (shutdown_manager, shutdown_handle) = ShutdownManager::init();
let (mut shutdown_manager, shutdown_handle) = ShutdownManager::init();
let mut service_data = ServiceInfo::default();
let services = self.init_services(&mut service_data, &shutdown_manager, provider)?;
let services = self.init_services(&mut service_data, &mut shutdown_manager, provider)?;
Ok(EphemeraStarterWithProvider {
with_application: self,
@@ -237,7 +237,7 @@ impl<A: Application> EphemeraStarterWithApplication<A> {
>(
&mut self,
service_data: &mut ServiceInfo,
shutdown_manager: &ShutdownManager,
shutdown_manager: &mut ShutdownManager,
provider: P,
) -> anyhow::Result<Vec<BoxFuture<'static, anyhow::Result<()>>>> {
let services = vec![
@@ -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"
+4 -4
View File
@@ -1,5 +1,5 @@
import { GatewayResponse, GatewayBond, GatewayReportResponse } from '../typeDefs/explorer-api';
import { toPercentInteger } from '../utils';
import { toPercentIntegerString } from '../utils';
export type GatewayRowType = {
id: string;
@@ -9,7 +9,7 @@ export type GatewayRowType = {
host: string;
location: string;
version: string;
node_performance: number;
node_performance: string;
};
export type GatewayEnrichedRowType = GatewayRowType & {
@@ -30,7 +30,7 @@ export function gatewayToGridRow(arrayOfGateways: GatewayResponse): GatewayRowTy
bond: gw.pledge_amount.amount || 0,
host: gw.gateway.host || '',
version: gw.gateway.version || '',
node_performance: toPercentInteger(gw.node_performance.last_24h),
node_performance: toPercentIntegerString(gw.node_performance.last_24h),
}));
}
@@ -47,6 +47,6 @@ export function gatewayEnrichedToGridRow(gateway: GatewayBond, report: GatewayRe
mixPort: gateway.gateway.mix_port || 0,
routingScore: `${report.most_recent}%`,
avgUptime: `${report.last_day || report.last_hour}%`,
node_performance: toPercentInteger(gateway.node_performance.most_recent),
node_performance: toPercentIntegerString(gateway.node_performance.most_recent),
};
}
+9 -9
View File
@@ -1,6 +1,6 @@
/* eslint-disable camelcase */
import { MixNodeResponse, MixNodeResponseItem, MixnodeStatus } from '../../typeDefs/explorer-api';
import { toPercentInteger, toPercentIntegerString } from '../../utils';
import { MixNodeResponse, MixNodeResponseItem, MixnodeStatus, NodePerformance } from '../../typeDefs/explorer-api';
import { toPercentIntegerString } from '../../utils';
import { unymToNym } from '../../utils/currency';
export type MixnodeRowType = {
@@ -15,11 +15,11 @@ export type MixnodeRowType = {
pledge_amount: number;
host: string;
layer: string;
profit_percentage: number;
profit_percentage: string;
avg_uptime: string;
stake_saturation: React.ReactNode;
operating_cost: number;
node_performance: number;
operating_cost: string;
node_performance: NodePerformance['most_recent'];
blacklisted: boolean;
};
@@ -32,7 +32,7 @@ export function mixNodeResponseItemToMixnodeRowType(item: MixNodeResponseItem):
const delegations = Number(item.total_delegation.amount) || 0;
const totalBond = pledge + delegations;
const selfPercentage = ((pledge * 100) / totalBond).toFixed(2);
const profitPercentage = toPercentInteger(item.profit_margin_percent) || 0;
const profitPercentage = toPercentIntegerString(item.profit_margin_percent) || 0;
const uncappedSaturation = typeof item.uncapped_saturation === 'number' ? item.uncapped_saturation * 100 : 0;
return {
@@ -47,11 +47,11 @@ export function mixNodeResponseItemToMixnodeRowType(item: MixNodeResponseItem):
pledge_amount: pledge,
host: item?.mix_node?.host || '',
layer: item?.layer || '',
profit_percentage: profitPercentage,
profit_percentage: `${profitPercentage}%`,
avg_uptime: `${toPercentIntegerString(item.node_performance.last_24h)}%`,
stake_saturation: Number(uncappedSaturation.toFixed(2)),
operating_cost: Number(unymToNym(item.operating_cost?.amount, 6)) || 0,
node_performance: toPercentInteger(item.node_performance.most_recent),
operating_cost: `${unymToNym(item.operating_cost?.amount, 6)} NYM`,
node_performance: `${toPercentIntegerString(item.node_performance.most_recent)}%`,
blacklisted: item.blacklisted,
};
}
+3 -3
View File
@@ -210,7 +210,7 @@ export const PageMixnodes: FCWithChildren = () => {
component={RRDLink}
to={`/network-components/mixnode/${params.row.mix_id}`}
>
{params.value}%
{params.value}
</MuiLink>
),
},
@@ -233,7 +233,7 @@ export const PageMixnodes: FCWithChildren = () => {
component={RRDLink}
to={`/network-components/mixnode/${params.row.mix_id}`}
>
{params.value} NYM
{params.value}
</MuiLink>
),
},
@@ -256,7 +256,7 @@ export const PageMixnodes: FCWithChildren = () => {
component={RRDLink}
to={`/network-components/mixnode/${params.row.mix_id}`}
>
{params.value}%
{params.value}
</MuiLink>
),
},
-1
View File
@@ -55,7 +55,6 @@ export const splice = (start: number, deleteCount: number, address?: string): st
* @returns A stringified integer
*/
export const toPercentIntegerString = (value: string) => Math.round(Number(value) * 100).toString();
export const toPercentInteger = (value: string) => Math.round(Number(value) * 100);
export const textColour = (value: EconomicsRowsType, field: string, theme: Theme) => {
const progressBarValue = value?.progressBarValue || 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::{
fmt,
hash::{Hash, Hasher},
net::SocketAddr,
ops::Deref,
str::FromStr,
};
use base64::{engine::general_purpose, Engine};
use dashmap::DashMap;
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 = DashMap<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,104 +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 = {
if let Some(nonce) = state.registration_in_progress.get(client.pub_key()) {
*nonce
} else {
return StatusCode::BAD_REQUEST;
}
};
if client
.verify(state.sphinx_key_pair.private_key(), preshared_nonce)
.is_ok()
{
{
state.registration_in_progress.remove(client.pub_key());
}
{
state.client_registry.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(..);
state
.registration_in_progress
.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>>) {
(
StatusCode::OK,
Json(
state
.client_registry
.iter()
.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 clients = state
.client_registry
.iter()
.filter_map(|r| {
let client = r.value();
if client.pub_key() == &pub_key {
Some(client.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;
-205
View File
@@ -1,205 +0,0 @@
use std::sync::Arc;
use axum::{
routing::{get, post},
Router,
};
use dashmap::DashMap;
use log::info;
use nym_crypto::asymmetric::encryption;
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<ClientRegistry>,
sphinx_key_pair: Arc<encryption::KeyPair>,
registration_in_progress: Arc<DashMap<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<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(DashMap::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::sync::Arc;
use axum::body::Body;
use axum::http::Request;
use axum::http::StatusCode;
use dashmap::DashMap;
use hmac::Mac;
use tower::Service;
use tower::ServiceExt;
use nym_crypto::asymmetric::encryption;
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(DashMap::new());
let client_registry = Arc::new(DashMap::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.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.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());
assert_eq!(
client_registry
.iter()
.map(|c| c.value().pub_key().clone())
.collect::<Vec<ClientPublicKey>>(),
clients
)
}
}

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