Compare commits

..

2 Commits

Author SHA1 Message Date
Jędrzej Stuczyński 308de3a7cb Revert "chore: workspace global panic preventing lints (#5512)"
This reverts commit 6ac6747cc7.
2025-02-26 10:30:22 +00:00
Jędrzej Stuczyński 6ac6747cc7 chore: workspace global panic preventing lints (#5512)
* add vercel config file: turn off autodeploy on master (#5490)

* Add extra args for the probe (#5499)

* setup workspace global lints to prevent needless panics

* removed sources of panic in nym-crypto, nym-node and nym-api

* adjusted test code

---------

Co-authored-by: mfahampshire <maxhampshire@pm.me>
Co-authored-by: dynco-nym <173912580+dynco-nym@users.noreply.github.com>
2025-02-26 10:30:14 +00:00
159 changed files with 1411 additions and 5728 deletions
@@ -82,7 +82,6 @@ jobs:
target/release/nym-cli
target/release/nymvisor
target/release/nym-node
target/release/foomp
retention-days: 30
# If this was a pull_request or nightly, upload to build server
@@ -101,7 +100,6 @@ jobs:
cp target/release/nym-node $OUTPUT_DIR
cp target/release/nym-cli $OUTPUT_DIR
cp target/release/explorer-api $OUTPUT_DIR
cp target/release/foomp $OUTPUT_DIR
if [ ${{ github.event_name == 'workflow_dispatch' && inputs.enable_deb == true }} = true ]; then
cp target/debian/*.deb $OUTPUT_DIR
fi
-90
View File
@@ -4,96 +4,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [2025.4-dorina] (2025-03-04)
- fixed sphinx version metrics registration ([#5546])
- Feature/chain status api ([#5539])
- Add SURBs soft threshold ([#5535])
- Simplify IPR v8 ([#5532])
- Shared instance for DNS AsyncResolver ([#5523])
- merge #5512 again after reverting due to incorrect rebase ([#5520])
- cherry-pick 17d3ff2d775f61aee381d90a304ed416c08f33fc onto dorina ([#5519])
- cherry-pick 6e5d0dac1b75413c5f09122b0d953f8ec6ef48df onto dorina ([#5518])
- chore: workspace global panic preventing lints ([#5512])
- bugfix: dont query for ecash apis unless necessary when spending ticketbooks ([#5508])
- bugfix: bound check when recovering a reply SURB ([#5502])
- chore: removed all old coconut code ([#5500])
- IPR request types v8 ([#5498])
- Support static routes for HTTP requests ([#5487])
- build(deps): bump the patch-updates group across 1 directory with 3 updates ([#5482])
- added missing import to doctest ([#5480])
- adjusted TestSetup::new_complex to ensure bonded node's existence ([#5478])
- Trigger contracts CI on main workspace Cargo changes ([#5477])
- build(deps): bump http from 1.1.0 to 1.2.0 ([#5472])
- build(deps): bump utoipa-swagger-ui from 8.0.3 to 8.1.0 ([#5471])
- build(deps): bump colored from 2.1.0 to 2.2.0 ([#5470])
- build(deps): bump celes from 2.4.0 to 2.5.0 ([#5469])
- build(deps): bump the patch-updates group with 2 updates ([#5467])
- build(deps): bump elliptic from 6.5.4 to 6.6.1 in /docker/typescript_client/upload_contract ([#5463])
- Run cargo autoinherit ([#5460])
- Fix clippy::precedence ([#5457])
- Provide Interval context with node descriptor endpoints ([#5456])
- fix: update fx average rate calcs to ignore 0 values ([#5454])
- Feature/add gbp currency ([#5453])
- Add helper to extract a list of sqlite files with journal files wal/shm ([#5452])
- Add a middleware layer to the nym api allowing for data compression ([#5451])
- Condense core API functionalities and enable gzip decompression for reqwest payloads ([#5450])
- build(deps): bump uniffi_build from 0.25.3 to 0.29.0 ([#5448])
- Upgrade tower to 0.5.2 ([#5446])
- build(deps): bump hickory-proto from 0.24.2 to 0.24.3 ([#5444])
- Seedable clients ([#5440])
- build(deps): bump the patch-updates group across 1 directory with 10 updates ([#5439])
- Remove all recv_with_delay and add shutdown condition to loops in client-core ([#5435])
- Disable the test for checking the remaining bandwidth in nym-node-status-api ([#5425])
- Dz nym node stats ([#5418])
- build(deps): bump hyper from 1.4.1 to 1.6.0 ([#5416])
- build(deps): bump publicsuffix from 2.2.3 to 2.3.0 ([#5367])
- Nymnode entrypoint docker ([#5300])
[#5546]: https://github.com/nymtech/nym/pull/5546
[#5539]: https://github.com/nymtech/nym/pull/5539
[#5535]: https://github.com/nymtech/nym/pull/5535
[#5532]: https://github.com/nymtech/nym/pull/5532
[#5523]: https://github.com/nymtech/nym/pull/5523
[#5520]: https://github.com/nymtech/nym/pull/5520
[#5519]: https://github.com/nymtech/nym/pull/5519
[#5518]: https://github.com/nymtech/nym/pull/5518
[#5512]: https://github.com/nymtech/nym/pull/5512
[#5508]: https://github.com/nymtech/nym/pull/5508
[#5502]: https://github.com/nymtech/nym/pull/5502
[#5500]: https://github.com/nymtech/nym/pull/5500
[#5498]: https://github.com/nymtech/nym/pull/5498
[#5487]: https://github.com/nymtech/nym/pull/5487
[#5482]: https://github.com/nymtech/nym/pull/5482
[#5480]: https://github.com/nymtech/nym/pull/5480
[#5478]: https://github.com/nymtech/nym/pull/5478
[#5477]: https://github.com/nymtech/nym/pull/5477
[#5472]: https://github.com/nymtech/nym/pull/5472
[#5471]: https://github.com/nymtech/nym/pull/5471
[#5470]: https://github.com/nymtech/nym/pull/5470
[#5469]: https://github.com/nymtech/nym/pull/5469
[#5467]: https://github.com/nymtech/nym/pull/5467
[#5463]: https://github.com/nymtech/nym/pull/5463
[#5460]: https://github.com/nymtech/nym/pull/5460
[#5457]: https://github.com/nymtech/nym/pull/5457
[#5456]: https://github.com/nymtech/nym/pull/5456
[#5454]: https://github.com/nymtech/nym/pull/5454
[#5453]: https://github.com/nymtech/nym/pull/5453
[#5452]: https://github.com/nymtech/nym/pull/5452
[#5451]: https://github.com/nymtech/nym/pull/5451
[#5450]: https://github.com/nymtech/nym/pull/5450
[#5448]: https://github.com/nymtech/nym/pull/5448
[#5446]: https://github.com/nymtech/nym/pull/5446
[#5444]: https://github.com/nymtech/nym/pull/5444
[#5440]: https://github.com/nymtech/nym/pull/5440
[#5439]: https://github.com/nymtech/nym/pull/5439
[#5435]: https://github.com/nymtech/nym/pull/5435
[#5425]: https://github.com/nymtech/nym/pull/5425
[#5418]: https://github.com/nymtech/nym/pull/5418
[#5416]: https://github.com/nymtech/nym/pull/5416
[#5367]: https://github.com/nymtech/nym/pull/5367
[#5300]: https://github.com/nymtech/nym/pull/5300
## [2025.3-ruta] (2025-02-10)
- Push down forget me to client configs ([#5431])
Generated
+10 -29
View File
@@ -234,9 +234,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.97"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
[[package]]
name = "arbitrary"
@@ -2597,16 +2597,6 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foomp"
version = "0.1.0"
dependencies = [
"anyhow",
"nym-http-api-client",
"nym-validator-client",
"tokio",
]
[[package]]
name = "form_urlencoded"
version = "1.2.1"
@@ -3172,9 +3162,9 @@ dependencies = [
[[package]]
name = "hickory-resolver"
version = "0.24.4"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbb117a1ca520e111743ab2f6688eddee69db4e0ea242545a604dce8a66fd22e"
checksum = "dcf287bde7b776e85d7188e6e5db7cf410a2f9531fe82817eb87feed034c8d14"
dependencies = [
"cfg-if",
"futures-util",
@@ -4757,7 +4747,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
[[package]]
name = "nym-api"
version = "1.1.52"
version = "1.1.51"
dependencies = [
"anyhow",
"async-trait",
@@ -4824,7 +4814,6 @@ dependencies = [
"sha2 0.9.9",
"sqlx",
"tempfile",
"tendermint 0.40.1",
"thiserror 2.0.11",
"time",
"tokio",
@@ -4849,7 +4838,6 @@ dependencies = [
"cosmwasm-std",
"ecdsa",
"getset",
"humantime-serde",
"nym-compact-ecash",
"nym-config",
"nym-contracts-common",
@@ -5128,7 +5116,6 @@ dependencies = [
name = "nym-client-core"
version = "1.1.15"
dependencies = [
"anyhow",
"async-trait",
"base64 0.22.1",
"bs58",
@@ -5771,16 +5758,13 @@ dependencies = [
"nym-credentials-interface",
"nym-crypto",
"nym-pemstore",
"nym-serde-helpers",
"nym-sphinx",
"nym-task",
"rand 0.8.5",
"serde",
"serde_json",
"strum 0.26.3",
"subtle 2.5.0",
"thiserror 2.0.11",
"time",
"tokio",
"tracing",
"tungstenite 0.20.1",
@@ -5929,7 +5913,6 @@ dependencies = [
"bytes",
"nym-bin-common",
"nym-crypto",
"nym-service-provider-requests-common",
"nym-sphinx",
"rand 0.8.5",
"serde",
@@ -5961,7 +5944,6 @@ dependencies = [
"nym-network-defaults",
"nym-network-requester",
"nym-sdk",
"nym-service-provider-requests-common",
"nym-service-providers-common",
"nym-sphinx",
"nym-task",
@@ -5973,6 +5955,7 @@ dependencies = [
"reqwest 0.12.4",
"serde",
"serde_json",
"tap",
"thiserror 2.0.11",
"time",
"tokio",
@@ -6182,7 +6165,7 @@ dependencies = [
[[package]]
name = "nym-node"
version = "1.6.1"
version = "1.6.0"
dependencies = [
"anyhow",
"arc-swap",
@@ -6306,7 +6289,7 @@ dependencies = [
[[package]]
name = "nym-node-status-api"
version = "1.0.2"
version = "1.0.0-rc.8"
dependencies = [
"ammonia",
"anyhow",
@@ -6535,9 +6518,7 @@ dependencies = [
name = "nym-service-provider-requests-common"
version = "0.1.0"
dependencies = [
"bincode",
"serde",
"thiserror 2.0.11",
]
[[package]]
@@ -9932,9 +9913,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tar"
version = "0.4.44"
version = "0.4.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a"
checksum = "c65998313f8e17d0d553d28f91a0df93e4dbbbf770279c7bc21ca0f09ea1a1f6"
dependencies = [
"filetime",
"libc",
+3 -14
View File
@@ -100,7 +100,7 @@ members = [
"documentation/autodoc",
"explorer-api",
"explorer-api/explorer-api-requests",
"explorer-api/explorer-client", "foomp",
"explorer-api/explorer-client",
"gateway",
"integrations/bity",
"nym-api",
@@ -151,7 +151,6 @@ members = [
]
default-members = [
"foomp",
"clients/native",
"clients/socks5",
"explorer-api",
@@ -257,7 +256,7 @@ handlebars = "3.5.5"
headers = "0.4.0"
hex = "0.4.3"
hex-literal = "0.3.3"
hickory-resolver = "0.24.4"
hickory-resolver = "0.24.3"
hkdf = "0.12.3"
hmac = "0.12.1"
http = "1"
@@ -330,7 +329,7 @@ subtle-encoding = "0.5"
syn = "1"
sysinfo = "0.33.0"
tap = "1.0.1"
tar = "0.4.44"
tar = "0.4.43"
tempfile = "3.15"
thiserror = "2.0"
time = "0.3.37"
@@ -438,13 +437,3 @@ opt-level = 'z'
[profile.release.package.mix-fetch-wasm]
# lto = true
opt-level = 'z'
[workspace.lints.clippy]
unwrap_used = "deny"
expect_used = "deny"
todo = "deny"
dbg_macro = "deny"
exit = "deny"
panic = "deny"
unimplemented = "deny"
unreachable = "deny"
-1
View File
@@ -1,3 +1,2 @@
allow-unwrap-in-tests = true
allow-expect-in-tests = true
allow-panic-in-tests = true
+2 -3
View File
@@ -6,15 +6,14 @@ pub mod v1;
pub mod v2;
pub mod v3;
pub mod v4;
pub mod v5;
mod error;
mod util;
pub use error::Error;
pub use v5 as latest;
pub use v4 as latest;
pub const CURRENT_VERSION: u8 = 5;
pub const CURRENT_VERSION: u8 = 4;
fn make_bincode_serializer() -> impl bincode::Options {
use bincode::Options;
+20 -92
View File
@@ -8,8 +8,8 @@ use nym_sphinx::addressing::clients::Recipient;
use nym_wireguard_types::PeerPublicKey;
use crate::{
v1, v2, v3, v4,
v5::{self, registration::IpPair},
v1, v2, v3,
v4::{self, registration::IpPair},
Error,
};
@@ -19,7 +19,6 @@ pub enum AuthenticatorVersion {
V2,
V3,
V4,
V5,
UNKNOWN,
}
@@ -35,8 +34,6 @@ impl From<Protocol> for AuthenticatorVersion {
AuthenticatorVersion::V3
} else if value.version == v4::VERSION {
AuthenticatorVersion::V4
} else if value.version == v5::VERSION {
AuthenticatorVersion::V5
} else {
AuthenticatorVersion::UNKNOWN
}
@@ -71,12 +68,6 @@ impl InitMessage for v4::registration::InitMessage {
}
}
impl InitMessage for v5::registration::InitMessage {
fn pub_key(&self) -> PeerPublicKey {
self.pub_key
}
}
pub trait FinalMessage {
fn pub_key(&self) -> PeerPublicKey;
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error>;
@@ -147,24 +138,6 @@ impl FinalMessage for v4::registration::FinalMessage {
self.gateway_client.verify(private_key, nonce)
}
fn private_ips(&self) -> IpPair {
self.gateway_client.private_ips.into()
}
fn credential(&self) -> Option<CredentialSpendingData> {
self.credential.clone()
}
}
impl FinalMessage for v5::registration::FinalMessage {
fn pub_key(&self) -> PeerPublicKey {
self.gateway_client.pub_key
}
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
self.gateway_client.verify(private_key, nonce)
}
fn private_ips(&self) -> IpPair {
self.gateway_client.private_ips
}
@@ -209,39 +182,29 @@ impl TopUpMessage for v4::topup::TopUpMessage {
}
}
impl TopUpMessage for v5::topup::TopUpMessage {
fn pub_key(&self) -> PeerPublicKey {
self.pub_key
}
fn credential(&self) -> CredentialSpendingData {
self.credential.clone()
}
}
pub enum AuthenticatorRequest {
Initial {
msg: Box<dyn InitMessage + Send + Sync + 'static>,
protocol: Protocol,
reply_to: Option<Recipient>,
reply_to: Recipient,
request_id: u64,
},
Final {
msg: Box<dyn FinalMessage + Send + Sync + 'static>,
protocol: Protocol,
reply_to: Option<Recipient>,
reply_to: Recipient,
request_id: u64,
},
QueryBandwidth {
msg: Box<dyn QueryBandwidthMessage + Send + Sync + 'static>,
protocol: Protocol,
reply_to: Option<Recipient>,
reply_to: Recipient,
request_id: u64,
},
TopUpBandwidth {
msg: Box<dyn TopUpMessage + Send + Sync + 'static>,
protocol: Protocol,
reply_to: Option<Recipient>,
reply_to: Recipient,
request_id: u64,
},
}
@@ -255,7 +218,7 @@ impl From<v1::request::AuthenticatorRequest> for AuthenticatorRequest {
version: value.version,
service_provider_type: ServiceProviderType::Authenticator,
},
reply_to: Some(value.reply_to),
reply_to: value.reply_to,
request_id: value.request_id,
},
v1::request::AuthenticatorRequestData::Final(gateway_client) => Self::Final {
@@ -264,7 +227,7 @@ impl From<v1::request::AuthenticatorRequest> for AuthenticatorRequest {
version: value.version,
service_provider_type: ServiceProviderType::Authenticator,
},
reply_to: Some(value.reply_to),
reply_to: value.reply_to,
request_id: value.request_id,
},
v1::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
@@ -274,7 +237,7 @@ impl From<v1::request::AuthenticatorRequest> for AuthenticatorRequest {
version: value.version,
service_provider_type: ServiceProviderType::Authenticator,
},
reply_to: Some(value.reply_to),
reply_to: value.reply_to,
request_id: value.request_id,
}
}
@@ -288,20 +251,20 @@ impl From<v2::request::AuthenticatorRequest> for AuthenticatorRequest {
v2::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
msg: Box::new(init_message),
protocol: value.protocol,
reply_to: Some(value.reply_to),
reply_to: value.reply_to,
request_id: value.request_id,
},
v2::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
msg: final_message,
protocol: value.protocol,
reply_to: Some(value.reply_to),
reply_to: value.reply_to,
request_id: value.request_id,
},
v2::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
Self::QueryBandwidth {
msg: Box::new(peer_public_key),
protocol: value.protocol,
reply_to: Some(value.reply_to),
reply_to: value.reply_to,
request_id: value.request_id,
}
}
@@ -315,20 +278,20 @@ impl From<v3::request::AuthenticatorRequest> for AuthenticatorRequest {
v3::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
msg: Box::new(init_message),
protocol: value.protocol,
reply_to: Some(value.reply_to),
reply_to: value.reply_to,
request_id: value.request_id,
},
v3::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
msg: final_message,
protocol: value.protocol,
reply_to: Some(value.reply_to),
reply_to: value.reply_to,
request_id: value.request_id,
},
v3::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
Self::QueryBandwidth {
msg: Box::new(peer_public_key),
protocol: value.protocol,
reply_to: Some(value.reply_to),
reply_to: value.reply_to,
request_id: value.request_id,
}
}
@@ -336,7 +299,7 @@ impl From<v3::request::AuthenticatorRequest> for AuthenticatorRequest {
Self::TopUpBandwidth {
msg: top_up_message,
protocol: value.protocol,
reply_to: Some(value.reply_to),
reply_to: value.reply_to,
request_id: value.request_id,
}
}
@@ -350,20 +313,20 @@ impl From<v4::request::AuthenticatorRequest> for AuthenticatorRequest {
v4::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
msg: Box::new(init_message),
protocol: value.protocol,
reply_to: Some(value.reply_to),
reply_to: value.reply_to,
request_id: value.request_id,
},
v4::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
msg: final_message,
protocol: value.protocol,
reply_to: Some(value.reply_to),
reply_to: value.reply_to,
request_id: value.request_id,
},
v4::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
Self::QueryBandwidth {
msg: Box::new(peer_public_key),
protocol: value.protocol,
reply_to: Some(value.reply_to),
reply_to: value.reply_to,
request_id: value.request_id,
}
}
@@ -371,42 +334,7 @@ impl From<v4::request::AuthenticatorRequest> for AuthenticatorRequest {
Self::TopUpBandwidth {
msg: top_up_message,
protocol: value.protocol,
reply_to: Some(value.reply_to),
request_id: value.request_id,
}
}
}
}
}
impl From<v5::request::AuthenticatorRequest> for AuthenticatorRequest {
fn from(value: v5::request::AuthenticatorRequest) -> Self {
match value.data {
v5::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
msg: Box::new(init_message),
protocol: value.protocol,
reply_to: None,
request_id: value.request_id,
},
v5::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
msg: final_message,
protocol: value.protocol,
reply_to: None,
request_id: value.request_id,
},
v5::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
Self::QueryBandwidth {
msg: Box::new(peer_public_key),
protocol: value.protocol,
reply_to: None,
request_id: value.request_id,
}
}
v5::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => {
Self::TopUpBandwidth {
msg: top_up_message,
protocol: value.protocol,
reply_to: None,
reply_to: value.reply_to,
request_id: value.request_id,
}
}
@@ -1,478 +0,0 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
use crate::{v4, v5};
impl From<v4::request::AuthenticatorRequest> for v5::request::AuthenticatorRequest {
fn from(authenticator_request: v4::request::AuthenticatorRequest) -> Self {
Self {
protocol: Protocol {
version: 5,
service_provider_type: ServiceProviderType::Authenticator,
},
data: authenticator_request.data.into(),
request_id: authenticator_request.request_id,
}
}
}
impl From<v4::request::AuthenticatorRequestData> for v5::request::AuthenticatorRequestData {
fn from(authenticator_request_data: v4::request::AuthenticatorRequestData) -> Self {
match authenticator_request_data {
v4::request::AuthenticatorRequestData::Initial(init_msg) => {
v5::request::AuthenticatorRequestData::Initial(init_msg.into())
}
v4::request::AuthenticatorRequestData::Final(final_msg) => {
v5::request::AuthenticatorRequestData::Final(Box::new((*final_msg).into()))
}
v4::request::AuthenticatorRequestData::QueryBandwidth(pub_key) => {
v5::request::AuthenticatorRequestData::QueryBandwidth(pub_key)
}
v4::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => {
v5::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message.into())
}
}
}
}
impl From<v4::registration::InitMessage> for v5::registration::InitMessage {
fn from(init_msg: v4::registration::InitMessage) -> Self {
Self {
pub_key: init_msg.pub_key,
}
}
}
impl From<v4::registration::FinalMessage> for v5::registration::FinalMessage {
fn from(final_msg: v4::registration::FinalMessage) -> Self {
Self {
gateway_client: final_msg.gateway_client.into(),
credential: final_msg.credential,
}
}
}
impl From<v4::registration::GatewayClient> for v5::registration::GatewayClient {
fn from(gateway_client: v4::registration::GatewayClient) -> Self {
Self {
pub_key: gateway_client.pub_key,
private_ips: gateway_client.private_ips.into(),
mac: gateway_client.mac.into(),
}
}
}
impl From<v5::registration::GatewayClient> for v4::registration::GatewayClient {
fn from(gateway_client: v5::registration::GatewayClient) -> Self {
Self {
pub_key: gateway_client.pub_key,
private_ips: gateway_client.private_ips.into(),
mac: gateway_client.mac.into(),
}
}
}
impl From<v4::registration::ClientMac> for v5::registration::ClientMac {
fn from(client_mac: v4::registration::ClientMac) -> Self {
Self::new((*client_mac).clone())
}
}
impl From<v5::registration::ClientMac> for v4::registration::ClientMac {
fn from(client_mac: v5::registration::ClientMac) -> Self {
Self::new((*client_mac).clone())
}
}
impl From<Box<v4::topup::TopUpMessage>> for Box<v5::topup::TopUpMessage> {
fn from(top_up_message: Box<v4::topup::TopUpMessage>) -> Self {
Box::new(v5::topup::TopUpMessage {
pub_key: top_up_message.pub_key,
credential: top_up_message.credential,
})
}
}
impl From<v4::response::AuthenticatorResponse> for v5::response::AuthenticatorResponse {
fn from(value: v4::response::AuthenticatorResponse) -> Self {
Self {
protocol: Protocol {
version: 5,
service_provider_type: value.protocol.service_provider_type,
},
data: value.data.into(),
}
}
}
impl From<v4::response::AuthenticatorResponseData> for v5::response::AuthenticatorResponseData {
fn from(authenticator_response_data: v4::response::AuthenticatorResponseData) -> Self {
match authenticator_response_data {
v4::response::AuthenticatorResponseData::PendingRegistration(pending_response) => {
v5::response::AuthenticatorResponseData::PendingRegistration(
pending_response.into(),
)
}
v4::response::AuthenticatorResponseData::Registered(registered_response) => {
v5::response::AuthenticatorResponseData::Registered(registered_response.into())
}
v4::response::AuthenticatorResponseData::RemainingBandwidth(
remaining_bandwidth_response,
) => v5::response::AuthenticatorResponseData::RemainingBandwidth(
remaining_bandwidth_response.into(),
),
v4::response::AuthenticatorResponseData::TopUpBandwidth(top_up_response) => {
v5::response::AuthenticatorResponseData::TopUpBandwidth(top_up_response.into())
}
}
}
}
impl From<v4::response::RegisteredResponse> for v5::response::RegisteredResponse {
fn from(value: v4::response::RegisteredResponse) -> Self {
Self {
request_id: value.request_id,
reply: value.reply.into(),
}
}
}
impl From<v4::response::PendingRegistrationResponse> for v5::response::PendingRegistrationResponse {
fn from(value: v4::response::PendingRegistrationResponse) -> Self {
Self {
request_id: value.request_id,
reply: value.reply.into(),
}
}
}
impl From<v4::registration::RegistrationData> for v5::registration::RegistrationData {
fn from(value: v4::registration::RegistrationData) -> Self {
Self {
nonce: value.nonce,
gateway_data: value.gateway_data.into(),
wg_port: value.wg_port,
}
}
}
impl From<v5::registration::RegistrationData> for v4::registration::RegistrationData {
fn from(value: v5::registration::RegistrationData) -> Self {
Self {
nonce: value.nonce,
gateway_data: value.gateway_data.into(),
wg_port: value.wg_port,
}
}
}
impl From<v4::response::RemainingBandwidthResponse> for v5::response::RemainingBandwidthResponse {
fn from(value: v4::response::RemainingBandwidthResponse) -> Self {
Self {
request_id: value.request_id,
reply: value.reply.map(Into::into),
}
}
}
impl From<v4::response::TopUpBandwidthResponse> for v5::response::TopUpBandwidthResponse {
fn from(value: v4::response::TopUpBandwidthResponse) -> Self {
Self {
request_id: value.request_id,
reply: value.reply.into(),
}
}
}
impl From<v4::registration::RegistredData> for v5::registration::RegistredData {
fn from(value: v4::registration::RegistredData) -> Self {
Self {
pub_key: value.pub_key,
private_ips: value.private_ips.into(),
wg_port: value.wg_port,
}
}
}
impl From<v4::registration::RemainingBandwidthData> for v5::registration::RemainingBandwidthData {
fn from(value: v4::registration::RemainingBandwidthData) -> Self {
Self {
available_bandwidth: value.available_bandwidth,
}
}
}
impl From<v4::registration::IpPair> for v5::registration::IpPair {
fn from(value: v4::registration::IpPair) -> Self {
Self {
ipv4: value.ipv4,
ipv6: value.ipv6,
}
}
}
impl From<v5::registration::IpPair> for v4::registration::IpPair {
fn from(value: v5::registration::IpPair) -> Self {
Self {
ipv4: value.ipv4,
ipv6: value.ipv6,
}
}
}
#[cfg(test)]
mod tests {
use std::{
net::{Ipv4Addr, Ipv6Addr},
str::FromStr,
};
use nym_credentials_interface::CredentialSpendingData;
use nym_crypto::asymmetric::encryption::PrivateKey;
use nym_sphinx::addressing::Recipient;
use nym_wireguard_types::PeerPublicKey;
use x25519_dalek::PublicKey;
use super::*;
use crate::{
util::tests::{CREDENTIAL_BYTES, RECIPIENT},
v4,
};
#[test]
fn upgrade_initial_req() {
let pub_key = PeerPublicKey::new(PublicKey::from([0; 32]));
let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap();
let (msg, _) = v4::request::AuthenticatorRequest::new_initial_request(
v4::registration::InitMessage::new(pub_key),
reply_to,
);
let upgraded_msg = v5::request::AuthenticatorRequest::from(msg);
assert_eq!(
upgraded_msg.protocol,
Protocol {
version: 5,
service_provider_type: ServiceProviderType::Authenticator
}
);
assert_eq!(
upgraded_msg.data,
v5::request::AuthenticatorRequestData::Initial(v5::registration::InitMessage {
pub_key
})
);
}
#[test]
fn upgrade_final_req() {
let mut rng = rand::thread_rng();
let local_secret = PrivateKey::new(&mut rng);
let remote_secret = x25519_dalek::StaticSecret::random_from_rng(&mut rng);
let ipv4 = Ipv4Addr::from_str("10.10.10.10").unwrap();
let ipv6 = Ipv6Addr::from_str("fc01::a0a").unwrap();
let ips = v4::registration::IpPair::new(ipv4, ipv6);
let nonce = 42;
let gateway_client = v4::registration::GatewayClient::new(
&local_secret,
(&remote_secret).into(),
ips,
nonce,
);
let credential = Some(CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap());
let final_message = v4::registration::FinalMessage {
gateway_client: gateway_client.clone(),
credential: credential.clone(),
};
let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap();
let (msg, _) =
v4::request::AuthenticatorRequest::new_final_request(final_message, reply_to);
let upgraded_msg = v5::request::AuthenticatorRequest::from(msg);
assert_eq!(
upgraded_msg.protocol,
Protocol {
version: 5,
service_provider_type: ServiceProviderType::Authenticator
}
);
assert_eq!(
upgraded_msg.data,
v5::request::AuthenticatorRequestData::Final(Box::new(
v5::registration::FinalMessage {
gateway_client: v5::registration::GatewayClient::new(
&local_secret,
(&remote_secret).into(),
v5::registration::IpPair::new(ipv4, ipv6),
nonce
),
credential
}
))
);
}
#[test]
fn upgrade_query_req() {
let pub_key = PeerPublicKey::new(PublicKey::from([0; 32]));
let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap();
let (msg, _) = v4::request::AuthenticatorRequest::new_query_request(pub_key, reply_to);
let upgraded_msg = v5::request::AuthenticatorRequest::from(msg);
assert_eq!(
upgraded_msg.protocol,
Protocol {
version: 5,
service_provider_type: ServiceProviderType::Authenticator
}
);
assert_eq!(
upgraded_msg.data,
v5::request::AuthenticatorRequestData::QueryBandwidth(pub_key)
);
}
#[test]
fn upgrade_pending_reg_resp() {
let mut rng = rand::thread_rng();
let local_secret = PrivateKey::new(&mut rng);
let remote_secret = x25519_dalek::StaticSecret::random_from_rng(&mut rng);
let ipv4 = Ipv4Addr::from_str("10.10.10.10").unwrap();
let ipv6 = Ipv6Addr::from_str("fc01::a0a").unwrap();
let ips = v4::registration::IpPair::new(ipv4, ipv6);
let nonce = 42;
let wg_port = 51822;
let gateway_data = v4::registration::GatewayClient::new(
&local_secret,
(&remote_secret).into(),
ips,
nonce,
);
let registration_data = v4::registration::RegistrationData {
nonce,
gateway_data,
wg_port,
};
let request_id = 123;
let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap();
let msg = v4::response::AuthenticatorResponse::new_pending_registration_success(
registration_data,
request_id,
reply_to,
);
let upgraded_msg = v5::response::AuthenticatorResponse::from(msg);
assert_eq!(
upgraded_msg.protocol,
Protocol {
version: 5,
service_provider_type: ServiceProviderType::Authenticator
}
);
assert_eq!(
upgraded_msg.data,
v5::response::AuthenticatorResponseData::PendingRegistration(
v5::response::PendingRegistrationResponse {
request_id,
reply: v5::registration::RegistrationData {
nonce,
gateway_data: v5::registration::GatewayClient::new(
&local_secret,
(&remote_secret).into(),
v5::registration::IpPair::new(ipv4, ipv6),
nonce
),
wg_port
}
}
)
);
}
#[test]
fn upgrade_registered_resp() {
let pub_key = PeerPublicKey::new(PublicKey::from([0; 32]));
let ipv4 = Ipv4Addr::from_str("10.1.10.10").unwrap();
let ipv6 = Ipv6Addr::from_str("fc01::a0a").unwrap();
let private_ips = v4::registration::IpPair::new(ipv4, ipv6);
let wg_port = 51822;
let registred_data = v4::registration::RegistredData {
pub_key,
private_ips,
wg_port,
};
let request_id = 123;
let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap();
let msg = v4::response::AuthenticatorResponse::new_registered(
registred_data,
reply_to,
request_id,
);
let upgraded_msg = v5::response::AuthenticatorResponse::from(msg);
assert_eq!(
upgraded_msg.protocol,
Protocol {
version: 5,
service_provider_type: ServiceProviderType::Authenticator
}
);
assert_eq!(
upgraded_msg.data,
v5::response::AuthenticatorResponseData::Registered(v5::response::RegisteredResponse {
request_id,
reply: v5::registration::RegistredData {
wg_port,
pub_key,
private_ips: v5::registration::IpPair::new(ipv4, ipv6)
}
})
);
}
#[test]
fn upgrade_remaining_bandwidth_resp() {
let available_bandwidth = 42;
let remaining_bandwidth_data = Some(v4::registration::RemainingBandwidthData {
available_bandwidth,
});
let request_id = 123;
let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap();
let msg = v4::response::AuthenticatorResponse::new_remaining_bandwidth(
remaining_bandwidth_data,
reply_to,
request_id,
);
let upgraded_msg = v5::response::AuthenticatorResponse::from(msg);
assert_eq!(
upgraded_msg.protocol,
Protocol {
version: 5,
service_provider_type: ServiceProviderType::Authenticator
}
);
assert_eq!(
upgraded_msg.data,
v5::response::AuthenticatorResponseData::RemainingBandwidth(
v5::response::RemainingBandwidthResponse {
request_id,
reply: Some(v5::registration::RemainingBandwidthData {
available_bandwidth,
})
}
)
);
}
}
@@ -1,10 +0,0 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod conversion;
pub mod registration;
pub mod request;
pub mod response;
pub mod topup;
pub const VERSION: u8 = 5;
@@ -1,287 +0,0 @@
// -2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::Error;
use base64::{engine::general_purpose, Engine};
use nym_credentials_interface::CredentialSpendingData;
use nym_network_defaults::constants::{WG_TUN_DEVICE_IP_ADDRESS_V4, WG_TUN_DEVICE_IP_ADDRESS_V6};
use nym_wireguard_types::PeerPublicKey;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::time::SystemTime;
use std::{fmt, ops::Deref, str::FromStr};
#[cfg(feature = "verify")]
use hmac::{Hmac, Mac};
#[cfg(feature = "verify")]
use nym_crypto::asymmetric::encryption::PrivateKey;
#[cfg(feature = "verify")]
use sha2::Sha256;
pub type PendingRegistrations = HashMap<PeerPublicKey, RegistrationData>;
pub type PrivateIPs = HashMap<IpPair, Taken>;
#[cfg(feature = "verify")]
pub type HmacSha256 = Hmac<Sha256>;
pub type Nonce = u64;
pub type Taken = Option<SystemTime>;
pub const BANDWIDTH_CAP_PER_DAY: u64 = 250 * 1024 * 1024 * 1024; // 250 GB
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct IpPair {
pub ipv4: Ipv4Addr,
pub ipv6: Ipv6Addr,
}
impl IpPair {
pub fn new(ipv4: Ipv4Addr, ipv6: Ipv6Addr) -> Self {
IpPair { ipv4, ipv6 }
}
}
impl From<(Ipv4Addr, Ipv6Addr)> for IpPair {
fn from((ipv4, ipv6): (Ipv4Addr, Ipv6Addr)) -> Self {
IpPair { ipv4, ipv6 }
}
}
impl fmt::Display for IpPair {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {})", self.ipv4, self.ipv6)
}
}
impl From<IpAddr> for IpPair {
fn from(value: IpAddr) -> Self {
let (before_last_byte, last_byte) = match value {
std::net::IpAddr::V4(ipv4_addr) => (ipv4_addr.octets()[2], ipv4_addr.octets()[3]),
std::net::IpAddr::V6(ipv6_addr) => (ipv6_addr.octets()[14], ipv6_addr.octets()[15]),
};
let last_bytes = ((before_last_byte as u16) << 8) | last_byte as u16;
let ipv4 = Ipv4Addr::new(
WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[0],
WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[1],
before_last_byte,
last_byte,
);
let ipv6 = Ipv6Addr::new(
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[0],
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[1],
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[2],
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[3],
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[4],
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[5],
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[6],
last_bytes,
);
IpPair::new(ipv4, ipv6)
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct InitMessage {
/// Base64 encoded x25519 public key
pub pub_key: PeerPublicKey,
}
impl InitMessage {
pub fn new(pub_key: PeerPublicKey) -> Self {
InitMessage { pub_key }
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct FinalMessage {
/// Gateway client data
pub gateway_client: GatewayClient,
/// Ecash credential
pub credential: Option<CredentialSpendingData>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct RegistrationData {
pub nonce: u64,
pub gateway_data: GatewayClient,
pub wg_port: u16,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct RegistredData {
pub pub_key: PeerPublicKey,
pub private_ips: IpPair,
pub wg_port: u16,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct RemainingBandwidthData {
pub available_bandwidth: i64,
}
/// Client that wants to register sends its PublicKey bytes mac digest encrypted with a DH shared secret.
/// Gateway/Nym node can then verify pub_key payload using the same process
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct GatewayClient {
/// Base64 encoded x25519 public key
pub pub_key: PeerPublicKey,
/// Assigned private IPs (v4 and v6)
pub private_ips: IpPair,
/// Sha256 hmac on the data (alongside the prior nonce)
pub mac: ClientMac,
}
impl GatewayClient {
#[cfg(feature = "verify")]
pub fn new(
local_secret: &PrivateKey,
remote_public: x25519_dalek::PublicKey,
private_ips: IpPair,
nonce: u64,
) -> Self {
// convert from 1.0 x25519-dalek private key into 2.0 x25519-dalek
#[allow(clippy::expect_used)]
let static_secret = x25519_dalek::StaticSecret::from(local_secret.to_bytes());
let local_public: x25519_dalek::PublicKey = (&static_secret).into();
let dh = static_secret.diffie_hellman(&remote_public);
// TODO: change that to use our nym_crypto::hmac module instead
#[allow(clippy::expect_used)]
let mut mac = HmacSha256::new_from_slice(dh.as_bytes())
.expect("x25519 shared secret is always 32 bytes long");
mac.update(local_public.as_bytes());
mac.update(private_ips.to_string().as_bytes());
mac.update(&nonce.to_le_bytes());
GatewayClient {
pub_key: PeerPublicKey::new(local_public),
private_ips,
mac: ClientMac(mac.finalize().into_bytes().to_vec()),
}
}
// Reusable secret should be gateways Wireguard PK
// Client should perform this step when generating its payload, using its own WG PK
#[cfg(feature = "verify")]
pub fn verify(&self, gateway_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
// convert from 1.0 x25519-dalek private key into 2.0 x25519-dalek
#[allow(clippy::expect_used)]
let static_secret = x25519_dalek::StaticSecret::from(gateway_key.to_bytes());
let dh = static_secret.diffie_hellman(&self.pub_key);
// TODO: change that to use our nym_crypto::hmac module instead
#[allow(clippy::expect_used)]
let mut mac = HmacSha256::new_from_slice(dh.as_bytes())
.expect("x25519 shared secret is always 32 bytes long");
mac.update(self.pub_key.as_bytes());
mac.update(self.private_ips.to_string().as_bytes());
mac.update(&nonce.to_le_bytes());
mac.verify_slice(&self.mac)
.map_err(|source| Error::FailedClientMacVerification {
client: self.pub_key.to_string(),
source,
})
}
pub fn pub_key(&self) -> PeerPublicKey {
self.pub_key
}
}
// TODO: change the inner type into generic array of size HmacSha256::OutputSize
// TODO2: rely on our internal crypto/hmac
#[derive(Debug, Clone, PartialEq)]
pub struct ClientMac(Vec<u8>);
impl fmt::Display for ClientMac {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", general_purpose::STANDARD.encode(&self.0))
}
}
impl ClientMac {
#[allow(dead_code)]
pub fn new(mac: Vec<u8>) -> Self {
ClientMac(mac)
}
}
impl Deref for ClientMac {
type Target = Vec<u8>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl FromStr for ClientMac {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mac_bytes: Vec<u8> =
general_purpose::STANDARD
.decode(s)
.map_err(|source| Error::MalformedClientMac {
mac: s.to_string(),
source,
})?;
Ok(ClientMac(mac_bytes))
}
}
impl Serialize for ClientMac {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let encoded_key = general_purpose::STANDARD.encode(self.0.clone());
serializer.serialize_str(&encoded_key)
}
}
impl<'de> Deserialize<'de> for ClientMac {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let encoded_key = String::deserialize(deserializer)?;
ClientMac::from_str(&encoded_key).map_err(serde::de::Error::custom)
}
}
#[cfg(test)]
mod tests {
use super::*;
use nym_crypto::asymmetric::encryption;
#[test]
fn create_ip_pair() {
let ipv4: IpAddr = Ipv4Addr::from_str("10.1.10.50").unwrap().into();
let ipv6: IpAddr = Ipv6Addr::from_str("fc01::0a32").unwrap().into();
assert_eq!(IpPair::from(ipv4), IpPair::from(ipv6));
}
#[test]
#[cfg(feature = "verify")]
fn client_request_roundtrip() {
let mut rng = rand::thread_rng();
let gateway_key_pair = encryption::KeyPair::new(&mut rng);
let client_key_pair = encryption::KeyPair::new(&mut rng);
let nonce = 1234567890;
let client = GatewayClient::new(
client_key_pair.private_key(),
x25519_dalek::PublicKey::from(gateway_key_pair.public_key().to_bytes()),
IpPair::new("10.0.0.42".parse().unwrap(), "fc00::42".parse().unwrap()),
nonce,
);
assert!(client.verify(gateway_key_pair.private_key(), nonce).is_ok())
}
}
@@ -1,132 +0,0 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::{
registration::{FinalMessage, InitMessage},
topup::TopUpMessage,
};
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
use nym_wireguard_types::PeerPublicKey;
use serde::{Deserialize, Serialize};
use crate::make_bincode_serializer;
use super::VERSION;
fn generate_random() -> u64 {
use rand::RngCore;
let mut rng = rand::rngs::OsRng;
rng.next_u64()
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct AuthenticatorRequest {
pub protocol: Protocol,
pub data: AuthenticatorRequestData,
pub request_id: u64,
}
impl AuthenticatorRequest {
pub fn from_reconstructed_message(
message: &nym_sphinx::receiver::ReconstructedMessage,
) -> Result<Self, bincode::Error> {
use bincode::Options;
make_bincode_serializer().deserialize(&message.message)
}
pub fn new_initial_request(init_message: InitMessage) -> (Self, u64) {
let request_id = generate_random();
(
Self {
protocol: Protocol {
service_provider_type: ServiceProviderType::Authenticator,
version: VERSION,
},
data: AuthenticatorRequestData::Initial(init_message),
request_id,
},
request_id,
)
}
pub fn new_final_request(final_message: FinalMessage) -> (Self, u64) {
let request_id = generate_random();
(
Self {
protocol: Protocol {
service_provider_type: ServiceProviderType::Authenticator,
version: VERSION,
},
data: AuthenticatorRequestData::Final(Box::new(final_message)),
request_id,
},
request_id,
)
}
pub fn new_query_request(peer_public_key: PeerPublicKey) -> (Self, u64) {
let request_id = generate_random();
(
Self {
protocol: Protocol {
service_provider_type: ServiceProviderType::Authenticator,
version: VERSION,
},
data: AuthenticatorRequestData::QueryBandwidth(peer_public_key),
request_id,
},
request_id,
)
}
pub fn new_topup_request(top_up_message: TopUpMessage) -> (Self, u64) {
let request_id = generate_random();
(
Self {
protocol: Protocol {
service_provider_type: ServiceProviderType::Authenticator,
version: VERSION,
},
data: AuthenticatorRequestData::TopUpBandwidth(Box::new(top_up_message)),
request_id,
},
request_id,
)
}
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
make_bincode_serializer().serialize(self)
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum AuthenticatorRequestData {
Initial(InitMessage),
Final(Box<FinalMessage>),
QueryBandwidth(PeerPublicKey),
TopUpBandwidth(Box<TopUpMessage>),
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
#[test]
fn check_first_bytes_protocol() {
let version = 5;
let data = AuthenticatorRequest {
protocol: Protocol {
version,
service_provider_type: ServiceProviderType::Authenticator,
},
data: AuthenticatorRequestData::Initial(InitMessage::new(
PeerPublicKey::from_str("yvNUDpT5l7W/xDhiu6HkqTHDQwbs/B3J5UrLmORl1EQ=").unwrap(),
)),
request_id: 1,
};
let bytes = *data.to_bytes().unwrap().first_chunk::<2>().unwrap();
assert_eq!(bytes, [version, ServiceProviderType::Authenticator as u8]);
}
}
@@ -1,132 +0,0 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::registration::{RegistrationData, RegistredData, RemainingBandwidthData};
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
use serde::{Deserialize, Serialize};
use crate::make_bincode_serializer;
use super::VERSION;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct AuthenticatorResponse {
pub protocol: Protocol,
pub data: AuthenticatorResponseData,
}
impl AuthenticatorResponse {
pub fn new_pending_registration_success(
registration_data: RegistrationData,
request_id: u64,
) -> Self {
Self {
protocol: Protocol {
service_provider_type: ServiceProviderType::Authenticator,
version: VERSION,
},
data: AuthenticatorResponseData::PendingRegistration(PendingRegistrationResponse {
reply: registration_data,
request_id,
}),
}
}
pub fn new_registered(registred_data: RegistredData, request_id: u64) -> Self {
Self {
protocol: Protocol {
service_provider_type: ServiceProviderType::Authenticator,
version: VERSION,
},
data: AuthenticatorResponseData::Registered(RegisteredResponse {
reply: registred_data,
request_id,
}),
}
}
pub fn new_remaining_bandwidth(
remaining_bandwidth_data: Option<RemainingBandwidthData>,
request_id: u64,
) -> Self {
Self {
protocol: Protocol {
service_provider_type: ServiceProviderType::Authenticator,
version: VERSION,
},
data: AuthenticatorResponseData::RemainingBandwidth(RemainingBandwidthResponse {
reply: remaining_bandwidth_data,
request_id,
}),
}
}
pub fn new_topup_bandwidth(
remaining_bandwidth_data: RemainingBandwidthData,
request_id: u64,
) -> Self {
Self {
protocol: Protocol {
service_provider_type: ServiceProviderType::Authenticator,
version: VERSION,
},
data: AuthenticatorResponseData::TopUpBandwidth(TopUpBandwidthResponse {
reply: remaining_bandwidth_data,
request_id,
}),
}
}
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
make_bincode_serializer().serialize(self)
}
pub fn from_reconstructed_message(
message: &nym_sphinx::receiver::ReconstructedMessage,
) -> Result<Self, bincode::Error> {
use bincode::Options;
make_bincode_serializer().deserialize(&message.message)
}
pub fn id(&self) -> Option<u64> {
match &self.data {
AuthenticatorResponseData::PendingRegistration(response) => Some(response.request_id),
AuthenticatorResponseData::Registered(response) => Some(response.request_id),
AuthenticatorResponseData::RemainingBandwidth(response) => Some(response.request_id),
AuthenticatorResponseData::TopUpBandwidth(response) => Some(response.request_id),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum AuthenticatorResponseData {
PendingRegistration(PendingRegistrationResponse),
Registered(RegisteredResponse),
RemainingBandwidth(RemainingBandwidthResponse),
TopUpBandwidth(TopUpBandwidthResponse),
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct PendingRegistrationResponse {
pub request_id: u64,
pub reply: RegistrationData,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct RegisteredResponse {
pub request_id: u64,
pub reply: RegistredData,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct RemainingBandwidthResponse {
pub request_id: u64,
pub reply: Option<RemainingBandwidthData>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct TopUpBandwidthResponse {
pub request_id: u64,
pub reply: RemainingBandwidthData,
}
@@ -1,15 +0,0 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_credentials_interface::CredentialSpendingData;
use nym_wireguard_types::PeerPublicKey;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct TopUpMessage {
/// Base64 encoded x25519 public key
pub pub_key: PeerPublicKey,
/// Ecash credential
pub credential: CredentialSpendingData,
}
-2
View File
@@ -118,8 +118,6 @@ features = ["wasm-bindgen"]
[dev-dependencies]
tempfile = { workspace = true }
tokio = { workspace = true, features = ["full"] }
anyhow = "1.0.96"
[features]
default = []
+1 -7
View File
@@ -45,12 +45,11 @@ const DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO: f64 = 0.70;
// clients/client-core/src/client/replies/reply_storage/surb_storage.rs
const DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 10;
const DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 200;
const DEFAULT_MINIMUM_REPLY_SURB_THRESHOLD_BUFFER: usize = 0;
// define how much to request at once
// clients/client-core/src/client/replies/reply_controller.rs
const DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 10;
const DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 30;
const DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 100;
const DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE: u32 = 500;
@@ -622,10 +621,6 @@ pub struct ReplySurbs {
/// Defines the maximum number of reply surbs the client wants to keep in its storage at any times.
pub maximum_reply_surb_storage_threshold: usize,
/// Defines the soft threshold ontop of the minimum reply surb storage threshold for when the client
/// should proactively request additional reply surbs.
pub minimum_reply_surb_threshold_buffer: usize,
/// Defines the minimum number of reply surbs the client would request.
pub minimum_reply_surb_request_size: u32,
@@ -665,7 +660,6 @@ impl Default for ReplySurbs {
ReplySurbs {
minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD,
maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD,
minimum_reply_surb_threshold_buffer: DEFAULT_MINIMUM_REPLY_SURB_THRESHOLD_BUFFER,
minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE,
maximum_reply_surb_request_size: DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE,
maximum_allowed_reply_surb_request_size: DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE,
@@ -181,7 +181,6 @@ impl From<ConfigV5> for Config {
maximum_reply_surb_age: value.debug.reply_surbs.maximum_reply_surb_age,
maximum_reply_key_age: value.debug.reply_surbs.maximum_reply_key_age,
surb_mix_hops: value.debug.reply_surbs.surb_mix_hops,
..Default::default()
},
..Default::default()
},
@@ -33,12 +33,10 @@ pub enum PreparationError {
#[error(transparent)]
NymTopologyError(#[from] NymTopologyError),
#[error("message too long for a single SURB, splitting into {fragments} fragments.")]
#[error("The received message cannot be sent using a single reply surb. It ended up getting split into {fragments} fragments.")]
MessageTooLongForSingleSurb { fragments: usize },
#[error(
"not enough reply SURBs to send the message, available: {available} required: {required}."
)]
#[error("Not enough reply SURBs to send the message. We have {available} available and require at least {required}.")]
NotEnoughSurbs { available: usize, required: usize },
}
@@ -517,25 +517,17 @@ where
use crate::error::ClientCoreStatusMessage;
let packets = self.transmission_buffer.total_size();
let lanes = self.transmission_buffer.lanes();
let backlog = self.transmission_buffer.total_size_in_bytes() as f64 / 1024.0;
let lanes = self.transmission_buffer.num_lanes();
let mult = self.sending_delay_controller.current_multiplier();
let delay = self.current_average_message_sending_delay().as_millis();
let lane_status = lanes
.iter()
.map(|lane_name| {
let lane_length = self.transmission_buffer.lane_length(lane_name).unwrap_or(0);
format!("{lane_name:?}: {lane_length}")
})
.collect::<Vec<String>>()
.join(", ");
let status_str = if self.config.traffic.disable_main_poisson_packet_distribution {
format!("Packet backlog: {lane_status}, no delay")
format!("Packet backlog: {backlog:.2} kiB ({packets}), {lanes} lanes, no delay")
} else {
format!("Packet backlog: {lane_status}, avg delay: {delay}ms ({mult})")
format!(
"Packet backlog: {backlog:.2} kiB ({packets}), {lanes} lanes, avg delay: {delay}ms ({mult})"
)
};
if packets > 1000 {
log::warn!("{status_str}");
} else if packets > 0 {
@@ -102,7 +102,6 @@ where
fragments: I,
lane: TransmissionLane,
) {
trace!("buffering pending replies for {recipient}");
self.pending_replies
.entry(*recipient)
.or_insert_with(TransmissionBuffer::new)
@@ -114,7 +113,6 @@ where
recipient: &AnonymousSenderTag,
fragments: Vec<(TransmissionLane, Fragment)>,
) {
trace!("re-inserting pending replies for {recipient}");
// the buffer should ALWAYS exist at this point, if it doesn't, it's a bug...
self.pending_replies
.entry(*recipient)
@@ -127,7 +125,6 @@ where
recipient: &AnonymousSenderTag,
data: Vec<Arc<PendingAcknowledgement>>,
) {
trace!("re-inserting pending retransmissions for {recipient}");
// the underlying entry MUST exist as we've just got data from there
let map_entry = self
.pending_retransmissions
@@ -145,7 +142,7 @@ where
}
fn should_request_more_surbs(&self, target: &AnonymousSenderTag) -> bool {
trace!("checking if we should request more surbs from {target}");
trace!("checking if we should request more surbs from {:?}", target);
let pending_queue_size = self
.pending_replies
@@ -161,6 +158,11 @@ where
let total_queue = pending_queue_size + retransmission_queue;
// simple as that - there's absolutely nothing to retransmit
if total_queue == 0 {
return false;
}
let available_surbs = self
.full_reply_storage
.surbs_storage_ref()
@@ -177,27 +179,11 @@ where
.full_reply_storage
.surbs_storage_ref()
.max_surb_threshold();
let min_surbs_threshold_buffer =
self.config.reply_surbs.minimum_reply_surb_threshold_buffer;
// After clearing the queue, we want to have at least `min_surbs_threshold` surbs available
// and reserved for requesting additional surbs, and in addition to that we also want to
// have `min_surbs_threshold_buffer` surbs available proactively.
let target_surbs_after_clearing_queue = min_surbs_threshold + min_surbs_threshold_buffer;
debug!("total queue size: {total_queue} = pending data {pending_queue_size} + pending retransmission {retransmission_queue}, available surbs: {available_surbs} pending surbs: {pending_surbs} threshold range: {min_surbs_threshold}..{max_surbs_threshold}");
// Check if we have enough surbs to handle the total queue and maintain minimum thresholds
let total_required_surbs = total_queue + target_surbs_after_clearing_queue;
let total_available_surbs = pending_surbs + available_surbs;
debug!("total queue size: {total_queue} = pending data {pending_queue_size} + pending retransmission {retransmission_queue}, available surbs: {available_surbs} pending surbs: {pending_surbs} threshold range: {min_surbs_threshold}..+{min_surbs_threshold_buffer}..{max_surbs_threshold}");
// We should request more surbs if:
// 1. We haven't hit the maximum surb threshold, and
// 2. We don't have enough surbs to handle the queue plus minimum thresholds
let is_below_max_threshold = total_available_surbs < max_surbs_threshold;
let is_below_required_surbs = total_available_surbs < total_required_surbs;
is_below_max_threshold && is_below_required_surbs
(pending_surbs + available_surbs) < max_surbs_threshold
&& (pending_surbs + available_surbs) < (total_queue + min_surbs_threshold)
}
async fn handle_send_reply(
@@ -258,10 +244,6 @@ where
&recipient_tag,
);
warn!("failed to send reply to {recipient_tag}: {err}");
info!(
"buffering {no_fragments} fragments for {recipient_tag}",
no_fragments = to_send.len()
);
self.insert_pending_replies(&recipient_tag, to_send, lane);
}
}
@@ -269,13 +251,6 @@ where
// if there's leftover data we didn't send because we didn't have enough (or any) surbs - buffer it
if !fragments.is_empty() {
// Ideally we should have enough surbs above the minimum threshold to handle sending
// new replies without having to first request more surbs. That's why I'd like to log
// these cases as they might indicate a problem with the surb management.
debug!(
"buffering {no_fragments} fragments for {recipient_tag}",
no_fragments = fragments.len()
);
self.insert_pending_replies(&recipient_tag, fragments, lane);
}
@@ -290,7 +265,6 @@ where
target: AnonymousSenderTag,
amount: u32,
) -> Result<(), PreparationError> {
debug!("requesting {amount} additional reply surbs for {target}");
let reply_surb = self
.full_reply_storage
.surbs_storage_ref()
@@ -712,7 +686,7 @@ where
// it should take into consideration the average latency, sending rate and queue size.
// it should request as many surbs as it takes to saturate its sending rate before next batch arrives
async fn request_reply_surbs_for_queue_clearing(&mut self, target: AnonymousSenderTag) {
trace!("requesting surbs for queue clearing");
trace!("requesting surbs for queues clearing");
let pending_queue_size = self
.pending_replies
@@ -726,18 +700,17 @@ where
.map(|pending_queue| pending_queue.len())
.unwrap_or_default();
let min_surbs_buffer = self.config.reply_surbs.minimum_reply_surb_threshold_buffer as u32;
let total_queue = (pending_queue_size + retransmission_queue) as u32;
// To proactively request additional surbs, we aim to have a buffer of extra surbs in our
// storage.
let total_queue_with_buffer = total_queue + min_surbs_buffer;
if total_queue == 0 {
trace!("the pending queues for {:?} are already empty", target);
return;
}
let request_size = min(
self.config.reply_surbs.maximum_reply_surb_request_size,
max(
total_queue_with_buffer,
total_queue,
self.config.reply_surbs.minimum_reply_surb_request_size,
),
);
@@ -746,7 +719,7 @@ where
.request_additional_reply_surbs(target, request_size)
.await
{
info!("{err}")
warn!("failed to request additional surbs... - {err}")
}
}
@@ -171,33 +171,3 @@ impl TopologyProvider for NymApiTopologyProvider {
Some(topology)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn foomp() -> anyhow::Result<()> {
let provider = NymApiTopologyProvider::new(
Config {
min_mixnode_performance: 0,
min_gateway_performance: 0,
use_extended_topology: false,
ignore_egress_epoch_role: false,
},
vec!["https://validator.nymtech.net/api/".parse()?],
None,
);
let a = provider
.validator_client
.get_current_rewarded_set()
.await
.unwrap();
println!("{a:#?}");
panic!("");
Ok(())
}
}
@@ -58,8 +58,8 @@ impl<T> TransmissionBuffer<T> {
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn lanes(&self) -> Vec<TransmissionLane> {
self.buffer.keys().cloned().collect()
pub(crate) fn num_lanes(&self) -> usize {
self.buffer.keys().count()
}
pub(crate) fn lane_length(&self, lane: &TransmissionLane) -> Option<usize> {
@@ -83,7 +83,6 @@ impl<T> TransmissionBuffer<T> {
}
#[cfg(not(target_arch = "wasm32"))]
#[allow(unused)]
pub(crate) fn total_size_in_bytes(&self) -> usize
where
T: SizedData,
@@ -20,8 +20,8 @@ use nym_credentials_interface::TicketType;
use nym_crypto::asymmetric::identity;
use nym_gateway_requests::registration::handshake::client_handshake;
use nym_gateway_requests::{
BinaryRequest, ClientControlRequest, ClientRequest, GatewayProtocolVersionExt,
SensitiveServerResponse, ServerResponse, SharedGatewayKey, SharedSymmetricKey,
BinaryRequest, ClientControlRequest, ClientRequest, SensitiveServerResponse, ServerResponse,
SharedGatewayKey, SharedSymmetricKey, AES_GCM_SIV_PROTOCOL_VERSION,
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, CURRENT_PROTOCOL_VERSION,
};
use nym_sphinx::forwarding::packet::MixPacket;
@@ -563,10 +563,28 @@ impl<C, St> GatewayClient<C, St> {
Ok(zeroizing_updated_key)
}
async fn send_authenticate_request_and_handle_response(
&mut self,
msg: ClientControlRequest,
) -> Result<(), GatewayClientError> {
async fn authenticate(&mut self) -> Result<(), GatewayClientError> {
let Some(shared_key) = self.shared_key.as_ref() else {
return Err(GatewayClientError::NoSharedKeyAvailable);
};
if !self.connection.is_established() {
return Err(GatewayClientError::ConnectionNotEstablished);
}
debug!("authenticating with gateway");
let self_address = self
.local_identity
.as_ref()
.public_key()
.derive_destination_address();
let msg = ClientControlRequest::new_authenticate(
self_address,
shared_key,
self.cfg.bandwidth.require_tickets,
)?;
match self.send_websocket_message(msg).await? {
ServerResponse::Authenticate {
protocol_version,
@@ -590,51 +608,6 @@ impl<C, St> GatewayClient<C, St> {
}
}
async fn authenticate_v1(&mut self) -> Result<(), GatewayClientError> {
debug!("using v1 authentication");
let Some(shared_key) = self.shared_key.as_ref() else {
return Err(GatewayClientError::NoSharedKeyAvailable);
};
let self_address = self
.local_identity
.public_key()
.derive_destination_address();
let msg = ClientControlRequest::new_authenticate(
self_address,
shared_key,
self.cfg.bandwidth.require_tickets,
)?;
self.send_authenticate_request_and_handle_response(msg)
.await
}
async fn authenticate_v2(&mut self) -> Result<(), GatewayClientError> {
debug!("using v2 authentication");
let Some(shared_key) = self.shared_key.as_ref() else {
return Err(GatewayClientError::NoSharedKeyAvailable);
};
let msg = ClientControlRequest::new_authenticate_v2(shared_key, &self.local_identity)?;
self.send_authenticate_request_and_handle_response(msg)
.await
}
async fn authenticate(&mut self, use_v2: bool) -> Result<(), GatewayClientError> {
if !self.connection.is_established() {
return Err(GatewayClientError::ConnectionNotEstablished);
}
debug!("authenticating with gateway");
if use_v2 {
self.authenticate_v2().await
} else {
self.authenticate_v1().await
}
}
/// Helper method to either call register or authenticate based on self.shared_key value
#[instrument(skip_all,
fields(
@@ -650,25 +623,19 @@ impl<C, St> GatewayClient<C, St> {
}
// 1. check gateway's protocol version
let gw_protocol = match self.get_gateway_protocol().await {
Ok(protocol) => Some(protocol),
let supports_aes_gcm_siv = match self.get_gateway_protocol().await {
Ok(protocol) => protocol >= AES_GCM_SIV_PROTOCOL_VERSION,
Err(_) => {
// if we failed to send the request, it means the gateway is running the old binary,
// so it has reset our connection - we have to reconnect
self.establish_connection().await?;
None
false
}
};
let supports_aes_gcm_siv = gw_protocol.supports_aes256_gcm_siv();
let supports_auth_v2 = gw_protocol.supports_authenticate_v2();
if !supports_aes_gcm_siv {
warn!("this gateway is on an old version that doesn't support AES256-GCM-SIV");
}
if !supports_auth_v2 {
warn!("this gateway is on an old version that doesn't support authentication v2")
}
if self.authenticated {
debug!("Already authenticated");
@@ -683,7 +650,7 @@ impl<C, St> GatewayClient<C, St> {
}
if self.shared_key.is_some() {
self.authenticate(supports_auth_v2).await?;
self.authenticate().await?;
if self.authenticated {
// if we are authenticated it means we MUST have an associated shared_key
@@ -1016,8 +983,7 @@ impl<C, St> GatewayClient<C, St> {
}
// if we're reconnecting, because we lost connection, we need to re-authenticate the connection
self.authenticate(self.negotiated_protocol.supports_authenticate_v2())
.await?;
self.authenticate().await?;
// this call is NON-blocking
self.start_listening_for_mixnet_messages()?;
@@ -28,6 +28,7 @@ use nym_network_defaults::{ChainDetails, NymNetworkDetails};
use serde::{de::DeserializeOwned, Serialize};
use std::fmt::Debug;
use std::time::SystemTime;
use tendermint_rpc::endpoint::block::Response as BlockResponse;
use tendermint_rpc::endpoint::*;
use tendermint_rpc::{Error as TendermintRpcError, Order};
use url::Url;
@@ -62,7 +63,6 @@ pub use cw3;
pub use cw4;
pub use cw_controllers;
pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment};
pub use tendermint_rpc::endpoint::block::Response as BlockResponse;
pub use tendermint_rpc::{
endpoint::{tx::Response as TxResponse, validators::Response as ValidatorResponse},
query::Query,
+1 -4
View File
@@ -43,7 +43,4 @@ serde = ["dep:serde", "serde_bytes", "ed25519-dalek/serde", "x25519-dalek/serde"
asymmetric = ["x25519-dalek", "ed25519-dalek", "zeroize"]
hashing = ["blake3", "digest", "hkdf", "hmac", "generic-array", "sha2"]
stream_cipher = ["aes", "ctr", "cipher", "generic-array"]
sphinx = ["nym-sphinx-types/sphinx"]
[lints]
workspace = true
sphinx = ["nym-sphinx-types/sphinx"]
+4 -10
View File
@@ -16,11 +16,8 @@ pub fn compute_keyed_hmac<D>(key: &[u8], data: &[u8]) -> HmacOutput<D>
where
D: Digest + BlockSizeUser,
{
// SAFETY: hmac is fine with keys of any size; if they're smaller than the block size of the underlying
// digest, they're padded with 0. if they're larger they're hashed and padded
// the reason for `Result` return type is due to the trait definition
#[allow(clippy::unwrap_used)]
let mut hmac = SimpleHmac::<D>::new_from_slice(key).unwrap();
let mut hmac = SimpleHmac::<D>::new_from_slice(key)
.expect("HMAC was instantiated with a key of an invalid size!");
hmac.update(data);
hmac.finalize()
}
@@ -30,11 +27,8 @@ pub fn recompute_keyed_hmac_and_verify_tag<D>(key: &[u8], data: &[u8], tag: &[u8
where
D: Digest + BlockSizeUser,
{
// SAFETY: hmac is fine with keys of any size; if they're smaller than the block size of the underlying
// digest, they're padded with 0. if they're larger they're hashed and padded
// the reason for `Result` return type is due to the trait definition
#[allow(clippy::unwrap_used)]
let mut hmac = SimpleHmac::<D>::new_from_slice(key).unwrap();
let mut hmac = SimpleHmac::<D>::new_from_slice(key)
.expect("HMAC was instantiated with a key of an invalid size!");
hmac.update(data);
let tag_arr = Output::<D>::from_slice(tag);
+5 -14
View File
@@ -27,16 +27,12 @@ where
// after performing diffie-hellman we don't care about the private component anymore
let dh_result = ephemeral_keypair.private_key().diffie_hellman(remote_key);
// SAFETY: while this is a relatively weak assumption, it's unlikely that any stream cipher has `C::key_size()`
// larger than 255 * chunk_size of the digest (so for example keys larger than 8160 bytes if sh256 is used)
#[allow(clippy::expect_used)]
// there is no reason for this to fail as our okm is expected to be only C::KeySize bytes
let okm = hkdf::extract_then_expand::<D>(None, &dh_result, None, C::key_size())
.expect("somehow too long okm was provided");
// SAFETY: the generated okm has exactly `C::key_size()` elements,
// so this call is safe
#[allow(clippy::unwrap_used)]
let derived_shared_key = Key::<C>::from_exact_iter(okm).unwrap();
let derived_shared_key =
Key::<C>::from_exact_iter(okm).expect("okm was expanded to incorrect length!");
(ephemeral_keypair, derived_shared_key)
}
@@ -52,14 +48,9 @@ where
{
let dh_result = local_key.diffie_hellman(remote_key);
// SAFETY: while this is a relatively weak assumption, it's unlikely that any stream cipher has `C::key_size()`
// larger than 255 * chunk_size of the digest (so for example keys larger than 8160 bytes if sh256 is used)
#[allow(clippy::expect_used)]
// there is no reason for this to fail as our okm is expected to be only C::KeySize bytes
let okm = hkdf::extract_then_expand::<D>(None, &dh_result, None, C::key_size())
.expect("somehow too long okm was provided");
// SAFETY: the generated okm has exactly `C::key_size()` elements,
// so this call is safe
#[allow(clippy::unwrap_used)]
Key::<C>::from_exact_iter(okm).unwrap()
Key::<C>::from_exact_iter(okm).expect("okm was expanded to incorrect length!")
}
+9 -4
View File
@@ -60,15 +60,20 @@ where
Iv::<C>::default()
}
pub fn try_iv_from_slice<C>(b: &[u8]) -> Option<&IV<C>>
pub fn iv_from_slice<C>(b: &[u8]) -> &IV<C>
where
C: IvSizeUser,
{
if b.len() != C::iv_size() {
None
} else {
Some(IV::<C>::from_slice(b))
// `from_slice` would have caused a panic about this issue anyway.
// Now we at least have slightly more information
panic!(
"Tried to convert {} bytes to IV. Expected {}",
b.len(),
C::iv_size()
)
}
IV::<C>::from_slice(b)
}
// TODO: there's really no way to use more parts of the keystream if it was required at some point.
-3
View File
@@ -20,14 +20,11 @@ serde_json = { workspace = true }
strum = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true, features = ["log"] }
time = { workspace = true }
subtle = { workspace = true }
zeroize = { workspace = true }
nym-crypto = { path = "../crypto", features = ["aead", "hashing"] }
nym-pemstore = { path = "../pemstore" }
nym-sphinx = { path = "../nymsphinx" }
nym-serde-helpers = { path = "../serde-helpers", features = ["base64"] }
nym-task = { path = "../task" }
nym-credentials = { path = "../credentials" }
@@ -15,12 +15,6 @@ use thiserror::Error;
// this is no longer constant size due to the differences in ciphertext between aes128ctr and aes256gcm-siv (inclusion of tag)
pub struct EncryptedAddressBytes(Vec<u8>);
impl From<Vec<u8>> for EncryptedAddressBytes {
fn from(encrypted_address: Vec<u8>) -> Self {
EncryptedAddressBytes(encrypted_address)
}
}
#[derive(Debug, Error)]
pub enum EncryptedAddressConversionError {
#[error("Failed to decode the encrypted address - {0}")]
+1 -20
View File
@@ -19,7 +19,7 @@ pub use shared_key::{
SharedGatewayKey, SharedKeyConversionError, SharedKeyUsageError, SharedSymmetricKey,
};
pub const CURRENT_PROTOCOL_VERSION: u8 = AUTHENTICATE_V2_PROTOCOL_VERSION;
pub const CURRENT_PROTOCOL_VERSION: u8 = AES_GCM_SIV_PROTOCOL_VERSION;
/// Defines the current version of the communication protocol between gateway and clients.
/// It has to be incremented for any breaking change.
@@ -27,29 +27,10 @@ pub const CURRENT_PROTOCOL_VERSION: u8 = AUTHENTICATE_V2_PROTOCOL_VERSION;
// 1 - initial release
// 2 - changes to client credentials structure
// 3 - change to AES-GCM-SIV and non-zero IVs
// 4 - introduction of v2 authentication protocol to prevent reply attacks
pub const INITIAL_PROTOCOL_VERSION: u8 = 1;
pub const CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION: u8 = 2;
pub const AES_GCM_SIV_PROTOCOL_VERSION: u8 = 3;
pub const AUTHENTICATE_V2_PROTOCOL_VERSION: u8 = 4;
// TODO: could using `Mac` trait here for OutputSize backfire?
// Should hmac itself be exposed, imported and used instead?
pub type LegacyGatewayMacSize = <GatewayIntegrityHmacAlgorithm as OutputSizeUser>::OutputSize;
pub trait GatewayProtocolVersionExt {
fn supports_aes256_gcm_siv(&self) -> bool;
fn supports_authenticate_v2(&self) -> bool;
}
impl GatewayProtocolVersionExt for Option<u8> {
fn supports_aes256_gcm_siv(&self) -> bool {
let Some(protocol) = *self else { return false };
protocol >= AES_GCM_SIV_PROTOCOL_VERSION
}
fn supports_authenticate_v2(&self) -> bool {
let Some(protocol) = *self else { return false };
protocol >= AUTHENTICATE_V2_PROTOCOL_VERSION
}
}
@@ -3,7 +3,6 @@
use crate::SharedKeyUsageError;
use nym_credentials_interface::CompactEcashError;
use nym_crypto::asymmetric::ed25519::SignatureError;
use nym_sphinx::addressing::nodes::NymNodeRoutingAddressError;
use nym_sphinx::forwarding::packet::MixPacketFormattingError;
use nym_sphinx::params::packet_sizes::PacketSize;
@@ -93,34 +92,7 @@ pub enum GatewayRequestsError {
#[error("the provided [v1] credential has invalid number of parameters - {0}")]
InvalidNumberOfEmbededParameters(u32),
#[error("failed to authenticate the client: {0}")]
Authentication(#[from] AuthenticationFailure),
// variant to catch legacy errors
#[error("{0}")]
Other(String),
}
#[derive(Debug, Error)]
pub enum AuthenticationFailure {
#[error(transparent)]
KeyUsageFailure(#[from] SharedKeyUsageError),
#[error("failed to verify provided address ciphertext")]
MalformedCiphertext,
#[error("failed to verify request signature")]
InvalidSignature(#[from] SignatureError),
#[error("provided request timestamp is in the future")]
RequestTimestampInFuture,
#[error("the client is not registered")]
NotRegistered,
#[error("the provided request is too stale to process")]
StaleRequest,
#[error("the provided request timestamp is smaller or equal to a one previously used")]
RequestReuse,
}
@@ -2,21 +2,16 @@
// SPDX-License-Identifier: Apache-2.0
use crate::models::CredentialSpendingRequest;
use crate::text_request::authenticate::AuthenticateRequest;
use crate::{
GatewayRequestsError, SharedGatewayKey, SymmetricKey, AES_GCM_SIV_PROTOCOL_VERSION,
AUTHENTICATE_V2_PROTOCOL_VERSION, CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION,
INITIAL_PROTOCOL_VERSION,
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, INITIAL_PROTOCOL_VERSION,
};
use nym_credentials_interface::CredentialSpendingData;
use nym_crypto::asymmetric::ed25519;
use nym_sphinx::DestinationAddressBytes;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use tungstenite::Message;
pub mod authenticate;
// wrapper for all encrypted requests for ease of use
#[derive(Serialize, Deserialize, Debug, Clone)]
#[non_exhaustive]
@@ -73,9 +68,6 @@ pub enum ClientControlRequest {
enc_address: String,
iv: String,
},
AuthenticateV2(Box<AuthenticateRequest>),
#[serde(alias = "handshakePayload")]
RegisterHandshakeInitRequest {
#[serde(default)]
@@ -131,22 +123,9 @@ impl ClientControlRequest {
})
}
pub fn new_authenticate_v2(
shared_key: &SharedGatewayKey,
identity_keys: &ed25519::KeyPair,
) -> Result<Self, GatewayRequestsError> {
// if we're using v2 authentication, we must announce at least that protocol version
let protocol_version = AUTHENTICATE_V2_PROTOCOL_VERSION;
Ok(ClientControlRequest::AuthenticateV2(Box::new(
AuthenticateRequest::new(protocol_version, shared_key, identity_keys)?,
)))
}
pub fn name(&self) -> String {
match self {
ClientControlRequest::Authenticate { .. } => "Authenticate".to_string(),
ClientControlRequest::AuthenticateV2(..) => "AuthenticateV2".to_string(),
ClientControlRequest::RegisterHandshakeInitRequest { .. } => {
"RegisterHandshakeInitRequest".to_string()
}
@@ -1,142 +0,0 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::{AuthenticationFailure, GatewayRequestsError, SharedGatewayKey};
use nym_crypto::asymmetric::ed25519;
use serde::{Deserialize, Serialize};
use std::iter;
use std::time::Duration;
use subtle::ConstantTimeEq;
use time::OffsetDateTime;
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct AuthenticateRequest {
#[serde(flatten)]
pub content: AuthenticateRequestContent,
pub request_signature: ed25519::Signature,
}
impl AuthenticateRequest {
pub fn new(
protocol_version: u8,
shared_key: &SharedGatewayKey,
identity_keys: &ed25519::KeyPair,
) -> Result<AuthenticateRequest, GatewayRequestsError> {
let content = AuthenticateRequestContent::new(
protocol_version,
shared_key,
*identity_keys.public_key(),
)?;
let plaintext = content.plaintext();
let request_signature = identity_keys.private_key().sign(&plaintext);
Ok(AuthenticateRequest {
content,
request_signature,
})
}
pub fn verify_timestamp(&self, max_request_age: Duration) -> Result<(), AuthenticationFailure> {
let now = OffsetDateTime::now_utc();
if self.content.request_timestamp() + max_request_age < now {
return Err(AuthenticationFailure::StaleRequest);
}
if self.content.request_timestamp() > now {
return Err(AuthenticationFailure::RequestTimestampInFuture);
}
Ok(())
}
pub fn ensure_timestamp_not_reused(
&self,
previous: OffsetDateTime,
) -> Result<(), AuthenticationFailure> {
if self.content.request_timestamp() <= previous {
return Err(AuthenticationFailure::RequestReuse);
}
Ok(())
}
pub fn verify_ciphertext(
&self,
shared_key: &SharedGatewayKey,
) -> Result<(), AuthenticationFailure> {
let expected = shared_key.encrypt(
self.content
.client_identity
.derive_destination_address()
.as_bytes_ref(),
Some(&self.content.nonce),
)?;
if !bool::from(expected.ct_eq(&self.content.address_ciphertext)) {
return Err(AuthenticationFailure::MalformedCiphertext);
}
Ok(())
}
pub fn verify_signature(&self) -> Result<(), AuthenticationFailure> {
let plaintext = self.content.plaintext();
self.content
.client_identity
.verify(plaintext, &self.request_signature)
.map_err(Into::into)
}
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct AuthenticateRequestContent {
pub protocol_version: u8,
// this is identical to the client's address
pub client_identity: ed25519::PublicKey,
#[serde(with = "nym_serde_helpers::base64")]
pub address_ciphertext: Vec<u8>,
#[serde(with = "nym_serde_helpers::base64")]
pub nonce: Vec<u8>,
pub request_unix_timestamp: u64,
}
impl AuthenticateRequestContent {
fn new(
protocol_version: u8,
shared_key: &SharedGatewayKey,
client_identity: ed25519::PublicKey,
) -> Result<AuthenticateRequestContent, GatewayRequestsError> {
let nonce = shared_key.random_nonce_or_iv();
let destination_address = client_identity.derive_destination_address();
let address_ciphertext =
shared_key.encrypt(destination_address.as_bytes_ref(), Some(&nonce))?;
let now = OffsetDateTime::now_utc();
Ok(AuthenticateRequestContent {
protocol_version,
client_identity,
address_ciphertext,
nonce,
request_unix_timestamp: now.unix_timestamp() as u64, // SAFETY: we're running this in post 1970...
})
}
}
impl AuthenticateRequestContent {
pub fn plaintext(&self) -> Vec<u8> {
iter::once(self.protocol_version)
.chain(self.client_identity.to_bytes())
.chain(self.address_ciphertext.iter().copied())
.chain(self.nonce.iter().copied())
.chain(self.request_unix_timestamp.to_be_bytes())
.collect()
}
pub fn request_timestamp(&self) -> OffsetDateTime {
OffsetDateTime::from_unix_timestamp(self.request_unix_timestamp as i64)
.unwrap_or(OffsetDateTime::UNIX_EPOCH)
}
}
@@ -1,7 +0,0 @@
/*
* Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
* SPDX-License-Identifier: GPL-3.0-only
*/
ALTER TABLE shared_keys
ADD COLUMN last_used_authentication TIMESTAMP WITHOUT TIME ZONE;
-14
View File
@@ -200,20 +200,6 @@ impl GatewayStorage {
Ok(())
}
pub async fn update_last_used_authentication_timestamp(
&self,
client_id: i64,
last_used_authentication_timestamp: OffsetDateTime,
) -> Result<(), GatewayStorageError> {
self.shared_key_manager
.update_last_used_authentication_timestamp(
client_id,
last_used_authentication_timestamp,
)
.await?;
Ok(())
}
pub async fn get_client(&self, client_id: i64) -> Result<Option<Client>, GatewayStorageError> {
let client = self.client_manager.get_client(client_id).await?;
Ok(client)
+1 -1
View File
@@ -14,13 +14,13 @@ pub struct Client {
#[derive(FromRow)]
pub struct PersistedSharedKeys {
#[allow(dead_code)]
pub client_id: i64,
#[allow(dead_code)]
pub client_address_bs58: String,
pub derived_aes128_ctr_blake3_hmac_keys_bs58: Option<String>,
pub derived_aes256_gcm_siv_key: Option<Vec<u8>>,
pub last_used_authentication: Option<OffsetDateTime>,
}
impl TryFrom<PersistedSharedKeys> for SharedGatewayKey {
+7 -21
View File
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::models::PersistedSharedKeys;
use time::OffsetDateTime;
#[derive(Clone)]
pub(crate) struct SharedKeysManager {
@@ -69,22 +68,6 @@ impl SharedKeysManager {
Ok(())
}
pub(crate) async fn update_last_used_authentication_timestamp(
&self,
client_id: i64,
last_used: OffsetDateTime,
) -> Result<(), sqlx::Error> {
sqlx::query!(
"UPDATE shared_keys SET last_used_authentication = ? WHERE client_id = ?;",
last_used,
client_id
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
/// Tries to retrieve shared keys stored for the particular client.
///
/// # Arguments
@@ -94,10 +77,13 @@ impl SharedKeysManager {
&self,
client_address_bs58: &str,
) -> Result<Option<PersistedSharedKeys>, sqlx::Error> {
sqlx::query_as("SELECT * FROM shared_keys WHERE client_address_bs58 = ?")
.bind(client_address_bs58)
.fetch_optional(&self.connection_pool)
.await
sqlx::query_as!(
PersistedSharedKeys,
"SELECT * FROM shared_keys WHERE client_address_bs58 = ?",
client_address_bs58
)
.fetch_optional(&self.connection_pool)
.await
}
/// Removes from the database shared keys derived with the particular client.
+11 -89
View File
@@ -13,18 +13,15 @@
use crate::ClientBuilder;
use std::{
net::SocketAddr,
sync::{Arc, LazyLock},
};
use std::{net::SocketAddr, sync::Arc};
use hickory_resolver::lookup_ip::LookupIp;
use hickory_resolver::{
config::{LookupIpStrategy, NameServerConfigGroup, ResolverConfig, ResolverOpts},
error::ResolveError,
lookup_ip::LookupIpIntoIter,
TokioAsyncResolver,
};
use hickory_resolver::{error::ResolveErrorKind, lookup_ip::LookupIp};
use once_cell::sync::OnceCell;
use reqwest::dns::{Addrs, Name, Resolve, Resolving};
use tracing::warn;
@@ -41,14 +38,6 @@ struct SocketAddrs {
iter: LookupIpIntoIter,
}
// n.b. static items do not call [`Drop`] on program termination, so this won't be deallocated.
// this is fine, as the OS can deallocate the terminated program faster than we can free memory
// but tools like valgrind might report "memory leaks" as it isn't obvious this is intentional.
static SHARED_RESOLVER: LazyLock<HickoryDnsResolver> = LazyLock::new(|| {
tracing::debug!("Initializing shared DNS resolver");
HickoryDnsResolver::default()
});
#[derive(Debug, thiserror::Error)]
#[error("hickory-dns resolver error: {hickory_error}")]
/// Error occurring while resolving a hostname into an IP address.
@@ -58,62 +47,29 @@ pub struct HickoryDnsError {
}
/// Wrapper around an `AsyncResolver`, which implements the `Resolve` trait.
///
/// Typical use involves instantiating using the `Default` implementation and then resolving using
/// methods or trait implementations.
///
/// The default initialization uses a shared underlying `AsyncResolver`. If a thread local resolver
/// is required use `thread_resolver()` to build a resolver with an independently instantiated
/// internal `AsyncResolver`.
#[derive(Debug, Default, Clone)]
pub struct HickoryDnsResolver {
// Since we might not have been called in the context of a
// Tokio Runtime in initialization, so we must delay the actual
// construction of the resolver.
/// Since we might not have been called in the context of a
/// Tokio Runtime in initialization, so we must delay the actual
/// construction of the resolver.
state: Arc<OnceCell<TokioAsyncResolver>>,
fallback: Arc<OnceCell<TokioAsyncResolver>>,
dont_use_shared: bool,
}
impl Resolve for HickoryDnsResolver {
fn resolve(&self, name: Name) -> Resolving {
let resolver = self.state.clone();
let fallback = self.fallback.clone();
let independent = self.dont_use_shared;
Box::pin(async move {
let resolver = resolver.get_or_try_init(|| {
// using a closure here is slightly gross, but this makes sure that if the
// lazy-init returns an error it can be handled by the client
if independent {
new_resolver()
} else {
Ok(SHARED_RESOLVER.state.get_or_try_init(new_resolver)?.clone())
}
})?;
let resolver = resolver.get_or_try_init(new_resolver)?;
// try the primary DNS resolver that we set up (DoH or DoT or whatever)
let lookup = match resolver.lookup_ip(name.as_str()).await {
Ok(res) => res,
Err(e) => {
// on failure use the fall back system configured DNS resolver
match e.kind() {
ResolveErrorKind::NoRecordsFound { .. } => {}
_ => {
warn!("primary DNS failed w/ error {e}: using system fallback");
}
}
let resolver = fallback.get_or_try_init(|| {
// using a closure here is slightly gross, but this makes sure that if the
// lazy-init returns an error it can be handled by the client
if independent {
new_resolver_system()
} else {
Ok(SHARED_RESOLVER
.fallback
.get_or_try_init(new_resolver_system)?
.clone())
}
})?;
warn!("primary DNS failed w/ error {e}: using system fallback");
let resolver = fallback.get_or_try_init(new_resolver_system)?;
resolver.lookup_ip(name.as_str()).await?
}
};
@@ -137,55 +93,21 @@ impl Iterator for SocketAddrs {
impl HickoryDnsResolver {
/// Attempt to resolve a domain name to a set of ['IpAddr']s
pub async fn resolve_str(&self, name: &str) -> Result<LookupIp, HickoryDnsError> {
let resolver = self.state.get_or_try_init(|| self.new_resolver())?;
let resolver = self.state.get_or_try_init(new_resolver)?;
// try the primary DNS resolver that we set up (DoH or DoT or whatever)
let lookup = match resolver.lookup_ip(name).await {
Ok(res) => res,
Err(e) => {
// on failure use the fall back system configured DNS resolver
match e.kind() {
ResolveErrorKind::NoRecordsFound { .. } => {}
_ => {
warn!("primary DNS failed w/ error {e}: using system fallback");
}
}
let resolver = self
.fallback
.get_or_try_init(|| self.new_resolver_system())?;
warn!("primary DNS failed w/ error {e}: using system fallback");
let resolver = self.fallback.get_or_try_init(new_resolver_system)?;
resolver.lookup_ip(name).await?
}
};
Ok(lookup)
}
/// Create a (lazy-initialized) resolver that is not shared across threads.
pub fn thread_resolver() -> Self {
Self {
dont_use_shared: true,
..Default::default()
}
}
fn new_resolver(&self) -> Result<TokioAsyncResolver, HickoryDnsError> {
if self.dont_use_shared {
new_resolver()
} else {
Ok(SHARED_RESOLVER.state.get_or_try_init(new_resolver)?.clone())
}
}
fn new_resolver_system(&self) -> Result<TokioAsyncResolver, HickoryDnsError> {
if self.dont_use_shared {
new_resolver_system()
} else {
Ok(SHARED_RESOLVER
.fallback
.get_or_try_init(new_resolver_system)?
.clone())
}
}
}
/// Create a new resolver with a custom DoT based configuration. The options are overridden to look
+12 -19
View File
@@ -210,12 +210,6 @@ pub enum HttpClientError<E: Display = String> {
#[error("failed to resolve request. status: '{status}', additional error message: {error}")]
EndpointFailure { status: StatusCode, error: E },
#[error("failed to decode response body: {source} from {content}")]
ResponseDecodeFailure {
source: serde_json::Error,
content: String,
},
#[cfg(target_arch = "wasm32")]
#[error("the request has timed out")]
RequestTimeout,
@@ -871,21 +865,20 @@ where
}
}
println!("response:\n{res:#?}");
if res.status().is_success() {
let text = res.text().await?;
println!("response text:\n{}", text);
// internally reqwest is first retrieving bytes and then performing parsing via serde_json
// (and similarly does the same thing for text())
// let full = res.bytes().await?;
match serde_json::from_str(&text) {
Ok(data) => Ok(data),
Err(err) => Err(HttpClientError::ResponseDecodeFailure {
source: err,
content: text.clone(),
}),
#[cfg(debug_assertions)]
{
let text = res.text().await.inspect_err(|err| {
tracing::error!("Couldn't even get response text: {err}");
})?;
tracing::trace!("Result:\n{:#?}", text);
serde_json::from_str(&text)
.map_err(|err| HttpClientError::GenericRequestFailure(err.to_string()))
}
#[cfg(not(debug_assertions))]
Ok(res.json().await?)
} else if res.status() == StatusCode::NOT_FOUND {
Err(HttpClientError::NotFound)
} else {
-1
View File
@@ -13,7 +13,6 @@ bincode = { workspace = true }
bytes = { workspace = true }
nym-bin-common = { path = "../bin-common" }
nym-crypto = { path = "../crypto" }
nym-service-provider-requests-common = { path = "../service-provider-requests-common" }
nym-sphinx = { path = "../nymsphinx" }
rand = { workspace = true }
serde = { workspace = true, features = ["derive"] }
+9 -9
View File
@@ -2,18 +2,24 @@ use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
use std::net::{Ipv4Addr, Ipv6Addr};
// The current version of the protocol.
// The idea here is that we add new request response types at least one version before we start
// using them.
// Also, depending on the version in the client connect message the IPR could respond with a
// matching older version.
pub use v6::request;
pub use v6::response;
pub mod codec;
pub mod sign;
pub mod v6;
pub mod v7;
pub mod v8;
// version 3: initial version
// version 4: IPv6 support
// version 5: Add severity level to info response
// version 6: Increase the available IPs
// version 7: Add signature support (for the future)
// version 8: Anonymous sends
pub const CURRENT_VERSION: u8 = 6;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct IpPair {
@@ -39,9 +45,3 @@ fn make_bincode_serializer() -> impl bincode::Options {
.with_big_endian()
.with_varint_encoding()
}
fn generate_random() -> u64 {
use rand::RngCore;
let mut rng = rand::rngs::OsRng;
rng.next_u64()
}
@@ -0,0 +1,69 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{v6, v7};
impl From<v7::response::StaticConnectFailureReason> for v6::response::StaticConnectFailureReason {
fn from(failure: v7::response::StaticConnectFailureReason) -> Self {
match failure {
v7::response::StaticConnectFailureReason::RequestedIpAlreadyInUse => {
v6::response::StaticConnectFailureReason::RequestedIpAlreadyInUse
}
v7::response::StaticConnectFailureReason::RequestedNymAddressAlreadyInUse => {
v6::response::StaticConnectFailureReason::RequestedNymAddressAlreadyInUse
}
v7::response::StaticConnectFailureReason::OutOfDateTimestamp => {
v6::response::StaticConnectFailureReason::Other("out of date timestamp".to_string())
}
v7::response::StaticConnectFailureReason::Other(reason) => {
v6::response::StaticConnectFailureReason::Other(reason)
}
}
}
}
impl From<v7::response::DynamicConnectFailureReason> for v6::response::DynamicConnectFailureReason {
fn from(failure: v7::response::DynamicConnectFailureReason) -> Self {
match failure {
v7::response::DynamicConnectFailureReason::RequestedNymAddressAlreadyInUse => {
v6::response::DynamicConnectFailureReason::RequestedNymAddressAlreadyInUse
}
v7::response::DynamicConnectFailureReason::NoAvailableIp => {
v6::response::DynamicConnectFailureReason::NoAvailableIp
}
v7::response::DynamicConnectFailureReason::Other(err) => {
v6::response::DynamicConnectFailureReason::Other(err)
}
}
}
}
impl From<v7::response::InfoResponseReply> for v6::response::InfoResponseReply {
fn from(reply: v7::response::InfoResponseReply) -> Self {
match reply {
v7::response::InfoResponseReply::Generic { msg } => {
v6::response::InfoResponseReply::Generic { msg }
}
v7::response::InfoResponseReply::VersionMismatch {
request_version,
response_version,
} => v6::response::InfoResponseReply::VersionMismatch {
request_version,
response_version,
},
v7::response::InfoResponseReply::ExitPolicyFilterCheckFailed { dst } => {
v6::response::InfoResponseReply::ExitPolicyFilterCheckFailed { dst }
}
}
}
}
impl From<v7::response::InfoLevel> for v6::response::InfoLevel {
fn from(level: v7::response::InfoLevel) -> Self {
match level {
v7::response::InfoLevel::Info => v6::response::InfoLevel::Info,
v7::response::InfoLevel::Warn => v6::response::InfoLevel::Warn,
v7::response::InfoLevel::Error => v6::response::InfoLevel::Error,
}
}
}
+1
View File
@@ -1,3 +1,4 @@
pub mod conversion;
pub mod request;
pub mod response;
@@ -0,0 +1,125 @@
use time::OffsetDateTime;
use crate::{v6, v7};
impl From<v6::request::IpPacketRequest> for v7::request::IpPacketRequest {
fn from(ip_packet_request: v6::request::IpPacketRequest) -> Self {
Self {
version: 7,
data: ip_packet_request.data.into(),
}
}
}
impl From<v6::request::IpPacketRequestData> for v7::request::IpPacketRequestData {
fn from(ip_packet_request_data: v6::request::IpPacketRequestData) -> Self {
match ip_packet_request_data {
v6::request::IpPacketRequestData::StaticConnect(r) => {
v7::request::IpPacketRequestData::StaticConnect(
v7::request::SignedStaticConnectRequest {
request: r.into(),
signature: None,
},
)
}
v6::request::IpPacketRequestData::DynamicConnect(r) => {
v7::request::IpPacketRequestData::DynamicConnect(
v7::request::SignedDynamicConnectRequest {
request: r.into(),
signature: None,
},
)
}
v6::request::IpPacketRequestData::Disconnect(r) => {
v7::request::IpPacketRequestData::Disconnect(v7::request::SignedDisconnectRequest {
request: r.into(),
signature: None,
})
}
v6::request::IpPacketRequestData::Data(r) => {
v7::request::IpPacketRequestData::Data(r.into())
}
v6::request::IpPacketRequestData::Ping(r) => {
v7::request::IpPacketRequestData::Ping(r.into())
}
v6::request::IpPacketRequestData::Health(r) => {
v7::request::IpPacketRequestData::Health(r.into())
}
}
}
}
impl From<v6::request::StaticConnectRequest> for v7::request::StaticConnectRequest {
fn from(static_connect_request: v6::request::StaticConnectRequest) -> Self {
Self {
request_id: static_connect_request.request_id,
ips: static_connect_request.ips,
reply_to: static_connect_request.reply_to,
reply_to_hops: static_connect_request.reply_to_hops,
reply_to_avg_mix_delays: static_connect_request.reply_to_avg_mix_delays,
buffer_timeout: static_connect_request.buffer_timeout,
timestamp: OffsetDateTime::now_utc(),
}
}
}
#[allow(deprecated)]
impl From<v6::request::DynamicConnectRequest> for v7::request::DynamicConnectRequest {
fn from(dynamic_connect_request: v6::request::DynamicConnectRequest) -> Self {
Self {
request_id: dynamic_connect_request.request_id,
reply_to: dynamic_connect_request.reply_to,
reply_to_hops: dynamic_connect_request.reply_to_hops,
reply_to_avg_mix_delays: dynamic_connect_request.reply_to_avg_mix_delays,
buffer_timeout: dynamic_connect_request.buffer_timeout,
timestamp: OffsetDateTime::now_utc(),
}
}
}
impl From<v6::request::DisconnectRequest> for v7::request::SignedDisconnectRequest {
fn from(disconnect_request: v6::request::DisconnectRequest) -> Self {
Self {
request: disconnect_request.into(),
signature: None,
}
}
}
impl From<v6::request::DisconnectRequest> for v7::request::DisconnectRequest {
fn from(disconnect_request: v6::request::DisconnectRequest) -> Self {
Self {
request_id: disconnect_request.request_id,
reply_to: disconnect_request.reply_to,
timestamp: OffsetDateTime::now_utc(),
}
}
}
impl From<v6::request::DataRequest> for v7::request::DataRequest {
fn from(data_request: v6::request::DataRequest) -> Self {
Self {
ip_packets: data_request.ip_packets,
}
}
}
impl From<v6::request::PingRequest> for v7::request::PingRequest {
fn from(ping_request: v6::request::PingRequest) -> Self {
Self {
request_id: ping_request.request_id,
reply_to: ping_request.reply_to,
timestamp: OffsetDateTime::now_utc(),
}
}
}
impl From<v6::request::HealthRequest> for v7::request::HealthRequest {
fn from(health_request: v6::request::HealthRequest) -> Self {
Self {
request_id: health_request.request_id,
reply_to: health_request.reply_to,
timestamp: OffsetDateTime::now_utc(),
}
}
}
+2
View File
@@ -1,4 +1,6 @@
pub mod conversion;
pub mod request;
pub mod response;
pub mod signature;
pub const VERSION: u8 = 7;
+33 -53
View File
@@ -1,18 +1,22 @@
use std::fmt;
use nym_crypto::asymmetric::identity;
use nym_sphinx::addressing::clients::Recipient;
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
use crate::{
sign::{SignatureError, SignedRequest},
IpPair,
use crate::{make_bincode_serializer, IpPair};
use super::{
signature::{SignatureError, SignedRequest},
VERSION,
};
use super::VERSION;
fn generate_random() -> u64 {
use rand::RngCore;
let mut rng = rand::rngs::OsRng;
rng.next_u64()
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct IpPacketRequest {
pub version: u8,
pub data: IpPacketRequestData,
@@ -26,7 +30,7 @@ impl IpPacketRequest {
reply_to_avg_mix_delays: Option<f64>,
buffer_timeout: Option<u64>,
) -> (Self, u64) {
let request_id = crate::generate_random();
let request_id = generate_random();
(
Self {
version: VERSION,
@@ -54,7 +58,7 @@ impl IpPacketRequest {
reply_to_avg_mix_delays: Option<f64>,
buffer_timeout: Option<u64>,
) -> (Self, u64) {
let request_id = crate::generate_random();
let request_id = generate_random();
(
Self {
version: VERSION,
@@ -75,7 +79,7 @@ impl IpPacketRequest {
}
pub fn new_disconnect_request(reply_to: Recipient) -> (Self, u64) {
let request_id = crate::generate_random();
let request_id = generate_random();
(
Self {
version: VERSION,
@@ -100,7 +104,7 @@ impl IpPacketRequest {
}
pub fn new_ping(reply_to: Recipient) -> (Self, u64) {
let request_id = crate::generate_random();
let request_id = generate_random();
(
Self {
version: VERSION,
@@ -115,7 +119,7 @@ impl IpPacketRequest {
}
pub fn new_health_request(reply_to: Recipient) -> (Self, u64) {
let request_id = crate::generate_random();
let request_id = generate_random();
(
Self {
version: VERSION,
@@ -151,27 +155,16 @@ impl IpPacketRequest {
}
}
pub fn verify(&self) -> Result<(), SignatureError> {
match &self.data {
IpPacketRequestData::StaticConnect(request) => request.verify(),
IpPacketRequestData::DynamicConnect(request) => request.verify(),
IpPacketRequestData::Disconnect(request) => request.verify(),
IpPacketRequestData::Data(_) => Ok(()),
IpPacketRequestData::Ping(_) => Ok(()),
IpPacketRequestData::Health(_) => Ok(()),
}
}
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
crate::make_bincode_serializer().serialize(self)
make_bincode_serializer().serialize(self)
}
pub fn from_reconstructed_message(
message: &nym_sphinx::receiver::ReconstructedMessage,
) -> Result<Self, bincode::Error> {
use bincode::Options;
crate::make_bincode_serializer().deserialize(&message.message)
make_bincode_serializer().deserialize(&message.message)
}
}
@@ -186,19 +179,6 @@ pub enum IpPacketRequestData {
Health(HealthRequest),
}
impl fmt::Display for IpPacketRequestData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
IpPacketRequestData::StaticConnect(_) => write!(f, "StaticConnect"),
IpPacketRequestData::DynamicConnect(_) => write!(f, "DynamicConnect"),
IpPacketRequestData::Disconnect(_) => write!(f, "Disconnect"),
IpPacketRequestData::Data(_) => write!(f, "Data"),
IpPacketRequestData::Ping(_) => write!(f, "Ping"),
IpPacketRequestData::Health(_) => write!(f, "Health"),
}
}
}
impl IpPacketRequestData {
pub fn add_signature(&mut self, signature: identity::Signature) -> Option<identity::Signature> {
match self {
@@ -222,9 +202,9 @@ impl IpPacketRequestData {
pub fn signable_request(&self) -> Option<Result<Vec<u8>, SignatureError>> {
match self {
IpPacketRequestData::StaticConnect(request) => Some(request.request_as_bytes()),
IpPacketRequestData::DynamicConnect(request) => Some(request.request_as_bytes()),
IpPacketRequestData::Disconnect(request) => Some(request.request_as_bytes()),
IpPacketRequestData::StaticConnect(request) => Some(request.request()),
IpPacketRequestData::DynamicConnect(request) => Some(request.request()),
IpPacketRequestData::Disconnect(request) => Some(request.request()),
IpPacketRequestData::Data(_) => None,
IpPacketRequestData::Ping(_) => None,
IpPacketRequestData::Health(_) => None,
@@ -262,7 +242,7 @@ pub struct StaticConnectRequest {
impl StaticConnectRequest {
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
crate::make_bincode_serializer().serialize(self)
make_bincode_serializer().serialize(self)
}
}
@@ -273,11 +253,11 @@ pub struct SignedStaticConnectRequest {
}
impl SignedRequest for SignedStaticConnectRequest {
fn identity(&self) -> Option<&identity::PublicKey> {
Some(self.request.reply_to.identity())
fn identity(&self) -> &identity::PublicKey {
self.request.reply_to.identity()
}
fn request_as_bytes(&self) -> Result<Vec<u8>, SignatureError> {
fn request(&self) -> Result<Vec<u8>, SignatureError> {
self.request
.to_bytes()
.map_err(|error| SignatureError::RequestSerializationError {
@@ -326,7 +306,7 @@ pub struct DynamicConnectRequest {
impl DynamicConnectRequest {
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
crate::make_bincode_serializer().serialize(self)
make_bincode_serializer().serialize(self)
}
}
@@ -337,11 +317,11 @@ pub struct SignedDynamicConnectRequest {
}
impl SignedRequest for SignedDynamicConnectRequest {
fn identity(&self) -> Option<&identity::PublicKey> {
Some(self.request.reply_to.identity())
fn identity(&self) -> &identity::PublicKey {
self.request.reply_to.identity()
}
fn request_as_bytes(&self) -> Result<Vec<u8>, SignatureError> {
fn request(&self) -> Result<Vec<u8>, SignatureError> {
self.request
.to_bytes()
.map_err(|error| SignatureError::RequestSerializationError {
@@ -375,7 +355,7 @@ pub struct DisconnectRequest {
impl DisconnectRequest {
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
crate::make_bincode_serializer().serialize(self)
make_bincode_serializer().serialize(self)
}
}
@@ -386,11 +366,11 @@ pub struct SignedDisconnectRequest {
}
impl SignedRequest for SignedDisconnectRequest {
fn identity(&self) -> Option<&identity::PublicKey> {
Some(self.request.reply_to.identity())
fn identity(&self) -> &identity::PublicKey {
self.request.reply_to.identity()
}
fn request_as_bytes(&self) -> Result<Vec<u8>, SignatureError> {
fn request(&self) -> Result<Vec<u8>, SignatureError> {
self.request
.to_bytes()
.map_err(|error| SignatureError::RequestSerializationError {
@@ -1,7 +1,6 @@
use std::time::Duration;
use nym_crypto::asymmetric::ed25519;
use time::OffsetDateTime;
use nym_crypto::asymmetric::identity;
// For reply protection, if a request is older than this, it will be rejected
const MAX_REQUEST_AGE: Duration = Duration::from_secs(10);
@@ -23,37 +22,29 @@ pub enum SignatureError {
#[error("signature verification failed")]
VerificationFailed {
message: String,
error: ed25519::SignatureError,
error: identity::SignatureError,
},
}
pub trait SignedRequest {
fn identity(&self) -> Option<&ed25519::PublicKey>;
fn identity(&self) -> &identity::PublicKey;
fn request_as_bytes(&self) -> Result<Vec<u8>, SignatureError>;
fn request(&self) -> Result<Vec<u8>, SignatureError>;
fn signature(&self) -> Option<&ed25519::Signature>;
fn signature(&self) -> Option<&identity::Signature>;
fn timestamp(&self) -> OffsetDateTime;
fn timestamp(&self) -> time::OffsetDateTime;
fn verify(&self) -> Result<(), SignatureError> {
let identity = match self.identity() {
Some(identity) => identity,
None => {
// If we are not revealing our identity, we don't need to verify anything
return Ok(());
}
};
if let Some(signature) = self.signature() {
// First check that the request is recent enough
if OffsetDateTime::now_utc() - self.timestamp() > MAX_REQUEST_AGE {
if time::OffsetDateTime::now_utc() - self.timestamp() > MAX_REQUEST_AGE {
return Err(SignatureError::RequestOutOfDate);
}
let request_as_bytes = self.request_as_bytes()?;
let request_as_bytes = self.request()?;
identity
self.identity()
.verify(request_as_bytes, signature)
.map_err(|error| SignatureError::VerificationFailed {
message: "signature verification failed".to_string(),
-4
View File
@@ -1,4 +0,0 @@
pub mod request;
pub mod response;
pub const VERSION: u8 = 8;
-304
View File
@@ -1,304 +0,0 @@
use std::fmt;
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
use super::VERSION;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct IpPacketRequest {
pub protocol: Protocol,
pub data: IpPacketRequestData,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum IpPacketRequestData {
Data(DataRequest),
Control(Box<ControlRequest>),
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum ControlRequest {
Connect(ConnectRequest),
Disconnect(DisconnectRequest),
Ping(PingRequest),
Health(HealthRequest),
}
// A data request is when the client wants to send an IP packet to a destination.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct DataRequest {
pub ip_packets: bytes::Bytes,
}
// A dynamic connect request is when the client does not provide the internal IP address it will use
// on the ip packet router, and instead requests one to be assigned to it.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct ConnectRequest {
pub request_id: u64,
// The maximum time in milliseconds the IPR should wait when filling up a mix packet
// with ip packets.
pub buffer_timeout: Option<u64>,
// Timestamp of when the request was sent by the client.
pub timestamp: OffsetDateTime,
}
// A disconnect request is when the client wants to disconnect from the ip packet router and free
// up the allocated IP address.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct DisconnectRequest {
pub request_id: u64,
// Timestamp of when the request was sent by the client.
pub timestamp: OffsetDateTime,
}
// A ping request is when the client wants to check if the ip packet router is still alive.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct PingRequest {
pub request_id: u64,
// Timestamp of when the request was sent by the client.
pub timestamp: OffsetDateTime,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct HealthRequest {
pub request_id: u64,
// Timestamp of when the request was sent by the client.
pub timestamp: OffsetDateTime,
}
impl IpPacketRequest {
pub fn new_connect_request(buffer_timeout: Option<u64>) -> (Self, u64) {
let protocol = Protocol {
version: VERSION,
service_provider_type: ServiceProviderType::IpPacketRouter,
};
let request_id = rand::random();
let timestamp = OffsetDateTime::now_utc();
let connect = ConnectRequest {
request_id,
buffer_timeout,
timestamp,
};
let request = Self {
protocol,
data: IpPacketRequestData::Control(Box::new(ControlRequest::Connect(connect))),
};
(request, request_id)
}
pub fn new_disconnect_request() -> (Self, u64) {
let protocol = Protocol {
version: VERSION,
service_provider_type: ServiceProviderType::IpPacketRouter,
};
let request_id = rand::random();
let timestamp = OffsetDateTime::now_utc();
let disconnect = DisconnectRequest {
request_id,
timestamp,
};
let request = Self {
protocol,
data: IpPacketRequestData::Control(Box::new(ControlRequest::Disconnect(disconnect))),
};
(request, request_id)
}
pub fn new_data_request(ip_packets: bytes::Bytes) -> Self {
Self {
protocol: Protocol {
version: VERSION,
service_provider_type: ServiceProviderType::IpPacketRouter,
},
data: IpPacketRequestData::Data(DataRequest { ip_packets }),
}
}
pub fn new_ping() -> (Self, u64) {
let protocol = Protocol {
version: VERSION,
service_provider_type: ServiceProviderType::IpPacketRouter,
};
let request_id = rand::random();
let timestamp = OffsetDateTime::now_utc();
let ping_request = PingRequest {
request_id,
timestamp,
};
let request = Self {
protocol,
data: IpPacketRequestData::Control(Box::new(ControlRequest::Ping(ping_request))),
};
(request, request_id)
}
pub fn new_health_request() -> (Self, u64) {
let protocol = Protocol {
version: VERSION,
service_provider_type: ServiceProviderType::IpPacketRouter,
};
let request_id = rand::random();
let timestamp = OffsetDateTime::now_utc();
let health_request = HealthRequest {
request_id,
timestamp,
};
let request = Self {
protocol,
data: IpPacketRequestData::Control(Box::new(ControlRequest::Health(health_request))),
};
(request, request_id)
}
pub fn id(&self) -> Option<u64> {
match self.data {
IpPacketRequestData::Control(ref c) => Some(c.id()),
IpPacketRequestData::Data(_) => None,
}
}
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
crate::make_bincode_serializer().serialize(self)
}
pub fn from_reconstructed_message(
message: &nym_sphinx::receiver::ReconstructedMessage,
) -> Result<Self, bincode::Error> {
use bincode::Options;
crate::make_bincode_serializer().deserialize(&message.message)
}
}
impl fmt::Display for IpPacketRequest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"IpPacketRequest {{ version: {}, data: {} }}",
self.protocol.version, self.data
)
}
}
impl fmt::Display for IpPacketRequestData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
IpPacketRequestData::Data(_) => write!(f, "Data"),
IpPacketRequestData::Control(c) => write!(f, "Control({})", c),
}
}
}
impl ControlRequest {
fn id(&self) -> u64 {
match self {
ControlRequest::Connect(request) => request.request_id,
ControlRequest::Disconnect(request) => request.request_id,
ControlRequest::Ping(request) => request.request_id,
ControlRequest::Health(request) => request.request_id,
}
}
}
impl fmt::Display for ControlRequest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ControlRequest::Connect(_) => write!(f, "Connect"),
ControlRequest::Disconnect(_) => write!(f, "Disconnect"),
ControlRequest::Ping(_) => write!(f, "Ping"),
ControlRequest::Health(_) => write!(f, "Health"),
}
}
}
impl ConnectRequest {
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
crate::make_bincode_serializer().serialize(self)
}
}
impl DisconnectRequest {
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
crate::make_bincode_serializer().serialize(self)
}
}
#[cfg(test)]
mod tests {
use time::macros::datetime;
use super::*;
#[test]
fn check_size_of_request() {
let connect = IpPacketRequest {
protocol: Protocol {
version: 4,
service_provider_type: ServiceProviderType::IpPacketRouter,
},
data: IpPacketRequestData::Control(Box::new(ControlRequest::Connect(ConnectRequest {
request_id: 123,
buffer_timeout: None,
timestamp: datetime!(2024-01-01 12:59:59.5 UTC),
}))),
};
assert_eq!(connect.to_bytes().unwrap().len(), 21);
}
#[test]
fn check_size_of_data() {
let data = IpPacketRequest {
protocol: Protocol {
version: 4,
service_provider_type: ServiceProviderType::IpPacketRouter,
},
data: IpPacketRequestData::Data(DataRequest {
ip_packets: bytes::Bytes::from(vec![1u8; 32]),
}),
};
assert_eq!(data.to_bytes().unwrap().len(), 36);
}
#[test]
fn serialize_and_deserialize_data_request() {
let data = IpPacketRequest {
protocol: Protocol {
version: 4,
service_provider_type: ServiceProviderType::IpPacketRouter,
},
data: IpPacketRequestData::Data(DataRequest {
ip_packets: bytes::Bytes::from(vec![1, 2, 4, 2, 5]),
}),
};
let serialized = data.to_bytes().unwrap();
let deserialized = IpPacketRequest::from_reconstructed_message(
&nym_sphinx::receiver::ReconstructedMessage {
message: serialized,
sender_tag: None,
},
)
.unwrap();
assert_eq!(deserialized.protocol.version, 4);
assert_eq!(
deserialized.protocol.service_provider_type,
ServiceProviderType::IpPacketRouter
);
assert_eq!(
deserialized.data,
IpPacketRequestData::Data(DataRequest {
ip_packets: bytes::Bytes::from(vec![1, 2, 4, 2, 5]),
})
);
}
}
@@ -1,219 +0,0 @@
use nym_bin_common::build_information::BinaryBuildInformationOwned;
use serde::{Deserialize, Serialize};
use crate::{make_bincode_serializer, IpPair};
use super::VERSION;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct IpPacketResponse {
pub version: u8,
pub data: IpPacketResponseData,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum IpPacketResponseData {
Data(DataResponse),
Control(Box<ControlResponse>),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DataResponse {
pub ip_packet: bytes::Bytes,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ControlResponse {
// Response for a connect request
Connect(ConnectResponse),
// Response for a disconnect initiqated by the client
Disconnect(DisconnectResponse),
// Message from the server that the client got disconnected without the client initiating it
UnrequestedDisconnect(UnrequestedDisconnect),
// Response to ping request
Pong(PongResponse),
// Response for a health request
Health(Box<HealthResponse>),
// Info response. This can be anything from informative messages to errors
Info(InfoResponse),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ConnectResponse {
pub request_id: u64,
pub reply: ConnectResponseReply,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ConnectResponseReply {
Success(ConnectSuccess),
Failure(ConnectFailureReason),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ConnectSuccess {
pub ips: IpPair,
}
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
pub enum ConnectFailureReason {
#[error("client is already connected")]
ClientAlreadyConnected,
#[error("no available ip address")]
NoAvailableIp,
#[error("{0}")]
Other(String),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DisconnectResponse {
pub request_id: u64,
pub reply: DisconnectResponseReply,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum DisconnectResponseReply {
Success,
Failure(DisconnectFailureReason),
}
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
pub enum DisconnectFailureReason {
#[error("client is not connected")]
ClientNotConnected,
#[error("{0}")]
Other(String),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct UnrequestedDisconnect {
pub reason: UnrequestedDisconnectReason,
}
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
pub enum UnrequestedDisconnectReason {
#[error("client mixnet traffic timeout")]
ClientMixnetTrafficTimeout,
#[error("client tun traffic timeout")]
ClientTunTrafficTimeout,
#[error("{0}")]
Other(String),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PongResponse {
pub request_id: u64,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct HealthResponse {
pub request_id: u64,
pub reply: HealthResponseReply,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct HealthResponseReply {
// Return the binary build information of the IPR
pub build_info: BinaryBuildInformationOwned,
// Return if the IPR has performed a successful routing test.
pub routable: Option<bool>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct InfoResponse {
pub request_id: u64,
pub reply: InfoResponseReply,
pub level: InfoLevel,
}
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
pub enum InfoResponseReply {
#[error("{msg}")]
Generic { msg: String },
#[error(
"version mismatch: response is v{request_version} and response is v{response_version}"
)]
VersionMismatch {
request_version: u8,
response_version: u8,
},
#[error("destination failed exit policy filter check: {dst}")]
ExitPolicyFilterCheckFailed { dst: String },
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum InfoLevel {
Info,
Warn,
Error,
}
impl IpPacketResponse {
pub fn new_ip_packet(ip_packet: bytes::Bytes) -> Self {
Self {
version: VERSION,
data: IpPacketResponseData::Data(DataResponse { ip_packet }),
}
}
pub fn id(&self) -> Option<u64> {
match &self.data {
IpPacketResponseData::Data(_) => None,
IpPacketResponseData::Control(response) => response.id(),
}
}
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
make_bincode_serializer().serialize(self)
}
pub fn from_reconstructed_message(
message: &nym_sphinx::receiver::ReconstructedMessage,
) -> Result<Self, bincode::Error> {
use bincode::Options;
make_bincode_serializer().deserialize(&message.message)
}
}
impl IpPacketResponseData {
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
make_bincode_serializer().serialize(self)
}
}
impl ControlResponse {
fn id(&self) -> Option<u64> {
match self {
ControlResponse::Connect(response) => Some(response.request_id),
ControlResponse::Disconnect(response) => Some(response.request_id),
ControlResponse::UnrequestedDisconnect(_) => None,
ControlResponse::Pong(response) => Some(response.request_id),
ControlResponse::Health(response) => Some(response.request_id),
ControlResponse::Info(response) => Some(response.request_id),
}
}
}
impl ConnectResponseReply {
pub fn is_success(&self) -> bool {
match self {
ConnectResponseReply::Success(_) => true,
ConnectResponseReply::Failure(_) => false,
}
}
}
@@ -2,9 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::AckKey;
use nym_crypto::symmetric::stream_cipher::{
self, encrypt, random_iv, try_iv_from_slice, IvSizeUser,
};
use nym_crypto::symmetric::stream_cipher::{self, encrypt, iv_from_slice, random_iv, IvSizeUser};
use nym_sphinx_params::{AckEncryptionAlgorithm, SerializedFragmentIdentifier, FRAG_ID_LEN};
use rand::{CryptoRng, RngCore};
@@ -27,11 +25,7 @@ pub fn recover_identifier(
iv_id_ciphertext: &[u8],
) -> Option<SerializedFragmentIdentifier> {
let iv_size = AckEncryptionAlgorithm::iv_size();
if iv_id_ciphertext.len() < FRAG_ID_LEN + iv_size {
return None;
}
let iv = try_iv_from_slice::<AckEncryptionAlgorithm>(&iv_id_ciphertext[..iv_size])?;
let iv = iv_from_slice::<AckEncryptionAlgorithm>(&iv_id_ciphertext[..iv_size]);
let id = stream_cipher::decrypt::<AckEncryptionAlgorithm>(
key.inner(),
@@ -12,7 +12,3 @@ readme.workspace = true
[dependencies]
serde = { workspace = true, features = ["derive"] }
thiserror.workspace = true
[dev-dependencies]
bincode.workspace = true
@@ -1,16 +1,8 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::fmt;
use serde::{Deserialize, Serialize};
#[derive(thiserror::Error, Debug)]
pub enum ProtocolError {
#[error("invalid service provider type: {0}")]
InvalidServiceProviderType(u8),
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[repr(u8)]
pub enum ServiceProviderType {
@@ -19,103 +11,8 @@ pub enum ServiceProviderType {
Authenticator = 2,
}
impl fmt::Display for ServiceProviderType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NetworkRequester => write!(f, "NetworkRequester"),
Self::IpPacketRouter => write!(f, "IpPacketRouter"),
Self::Authenticator => write!(f, "Authenticator"),
}
}
}
impl TryFrom<u8> for ServiceProviderType {
type Error = ProtocolError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::NetworkRequester),
1 => Ok(Self::IpPacketRouter),
2 => Ok(Self::Authenticator),
_ => Err(ProtocolError::InvalidServiceProviderType(value)),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct Protocol {
pub version: u8,
pub service_provider_type: ServiceProviderType,
}
impl TryFrom<&[u8; 2]> for Protocol {
type Error = ProtocolError;
fn try_from(bytes: &[u8; 2]) -> Result<Self, Self::Error> {
let version = bytes[0];
let service_provider_type = ServiceProviderType::try_from(bytes[1])
.map_err(|_| ProtocolError::InvalidServiceProviderType(bytes[1]))?;
Ok(Self {
version,
service_provider_type,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use bincode::Options;
fn make_bincode_serializer() -> impl bincode::Options {
bincode::DefaultOptions::new()
.with_big_endian()
.with_varint_encoding()
}
#[test]
fn protocol_serialization() {
let protocol = Protocol {
version: 4,
service_provider_type: ServiceProviderType::NetworkRequester,
};
let serialized = make_bincode_serializer().serialize(&protocol).unwrap();
let deserialized: Protocol = make_bincode_serializer().deserialize(&serialized).unwrap();
assert_eq!(protocol, deserialized);
}
#[test]
fn compact_serialization() {
let protocol = Protocol {
version: 4,
service_provider_type: ServiceProviderType::NetworkRequester,
};
let serialized = make_bincode_serializer().serialize(&protocol).unwrap();
assert_eq!(serialized.len(), 2);
}
#[test]
fn protocol_deserialization() {
let bytes = [4, ServiceProviderType::NetworkRequester as u8];
let deserialized = Protocol::try_from(&bytes).unwrap();
let expected = Protocol {
version: 4,
service_provider_type: ServiceProviderType::NetworkRequester,
};
assert_eq!(expected, deserialized);
}
#[test]
fn invalid_protocol_deserialization() {
let bytes = [4, 3];
let deserialized = Protocol::try_from(&bytes);
assert!(deserialized.is_err());
}
}
@@ -462,10 +462,6 @@ pub struct ReplySurbsWasm {
/// Defines the maximum number of reply surbs the client wants to keep in its storage at any times.
pub maximum_reply_surb_storage_threshold: usize,
/// Defines the soft threshold ontop of the minimum reply surb storage threshold for when the client
/// should proactively request additional reply surbs.
pub minimum_reply_surb_threshold_buffer: usize,
/// Defines the minimum number of reply surbs the client would request.
pub minimum_reply_surb_request_size: u32,
@@ -507,7 +503,6 @@ impl From<ReplySurbsWasm> for ConfigReplySurbs {
ConfigReplySurbs {
minimum_reply_surb_storage_threshold: reply_surbs.minimum_reply_surb_storage_threshold,
maximum_reply_surb_storage_threshold: reply_surbs.maximum_reply_surb_storage_threshold,
minimum_reply_surb_threshold_buffer: reply_surbs.minimum_reply_surb_threshold_buffer,
minimum_reply_surb_request_size: reply_surbs.minimum_reply_surb_request_size,
maximum_reply_surb_request_size: reply_surbs.maximum_reply_surb_request_size,
maximum_allowed_reply_surb_request_size: reply_surbs
@@ -534,7 +529,6 @@ impl From<ConfigReplySurbs> for ReplySurbsWasm {
ReplySurbsWasm {
minimum_reply_surb_storage_threshold: reply_surbs.minimum_reply_surb_storage_threshold,
maximum_reply_surb_storage_threshold: reply_surbs.maximum_reply_surb_storage_threshold,
minimum_reply_surb_threshold_buffer: reply_surbs.minimum_reply_surb_threshold_buffer,
minimum_reply_surb_request_size: reply_surbs.minimum_reply_surb_request_size,
maximum_reply_surb_request_size: reply_surbs.maximum_reply_surb_request_size,
maximum_allowed_reply_surb_request_size: reply_surbs
@@ -339,11 +339,6 @@ pub struct ReplySurbsWasmOverride {
#[tsify(optional)]
pub maximum_reply_surb_storage_threshold: Option<usize>,
/// Defines the soft threshold ontop of the minimum reply surb storage threshold for when the client
/// should proactively request additional reply surbs.
#[tsify(optional)]
pub minimum_reply_surb_threshold_buffer: Option<usize>,
/// Defines the minimum number of reply surbs the client would request.
#[tsify(optional)]
pub minimum_reply_surb_request_size: Option<u32>,
@@ -391,9 +386,6 @@ impl From<ReplySurbsWasmOverride> for ReplySurbsWasm {
maximum_reply_surb_storage_threshold: value
.maximum_reply_surb_storage_threshold
.unwrap_or(def.maximum_reply_surb_storage_threshold),
minimum_reply_surb_threshold_buffer: value
.minimum_reply_surb_threshold_buffer
.unwrap_or(def.minimum_reply_surb_threshold_buffer),
minimum_reply_surb_request_size: value
.minimum_reply_surb_request_size
.unwrap_or(def.minimum_reply_surb_request_size),
+1 -1
View File
@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "ahash"
-1
View File
@@ -122,7 +122,6 @@ exceptions = [
{ allow = ["GPL-3.0"], crate = "nym-network-requester" },
{ allow = ["GPL-3.0"], crate = "nym-node" },
{ allow = ["GPL-3.0"], crate = "nym-validator-rewarder" },
{ allow = ["GPL-3.0"], crate = "nym-ip-packet-router" },
]
# Some crates don't have (easily) machine readable licensing information,
-21
View File
@@ -1,21 +0,0 @@
[package]
name = "foomp"
version = "0.1.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
readme.workspace = true
[dependencies]
tokio = { workspace = true, features = ["full"] }
anyhow.workspace = true
nym-http-api-client = { path = "../common/http-api-client" }
nym-validator-client = { path = "../common/client-libs/validator-client", features = ["http-client"] }
[lints]
workspace = true
-15
View File
@@ -1,15 +0,0 @@
use nym_http_api_client::{ApiClient, Client};
use nym_validator_client::nym_api::NymApiClientExt;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let url = "https://validator.nymtech.net/api/";
let client = Client::new(url.parse()?, None);
let res = client.get_rewarded_set().await;
println!("{:?}", res);
println!("Hello, world!");
Ok(())
}
-3
View File
@@ -101,9 +101,6 @@ pub struct Debug {
pub maximum_open_connections: usize,
pub zk_nym_tickets: ZkNymTicketHandlerDebug,
/// Defines the maximum age of a signed authentication request before it's deemed too stale to process.
pub maximum_auth_request_age: Duration,
}
#[derive(Debug, Clone)]
@@ -6,20 +6,11 @@ use crate::node::client_handling::embedded_clients::LocalEmbeddedClientHandle;
use dashmap::DashMap;
use nym_sphinx::DestinationAddressBytes;
use std::sync::Arc;
use time::OffsetDateTime;
use tracing::warn;
#[derive(Clone)]
pub(crate) struct RemoteClientData {
// note, this does **NOT** indicate timestamp of when client connected
// it is (for v2 auth) timestamp the client **signed** when it created the request
pub(crate) session_request_timestamp: OffsetDateTime,
pub(crate) channels: ClientIncomingChannels,
}
enum ActiveClient {
/// Handle to a remote client connected via a network socket.
Remote(RemoteClientData),
Remote(ClientIncomingChannels),
/// Handle to a locally (inside the same process) running client.
Embedded(LocalEmbeddedClientHandle),
@@ -28,14 +19,14 @@ enum ActiveClient {
impl ActiveClient {
fn get_sender_ref(&self) -> &MixMessageSender {
match self {
ActiveClient::Remote(remote) => &remote.channels.mix_message_sender,
ActiveClient::Remote(remote) => &remote.mix_message_sender,
ActiveClient::Embedded(embedded) => &embedded.mix_message_sender,
}
}
fn get_sender(&self) -> MixMessageSender {
match self {
ActiveClient::Remote(remote) => remote.channels.mix_message_sender.clone(),
ActiveClient::Remote(remote) => remote.mix_message_sender.clone(),
ActiveClient::Embedded(embedded) => embedded.mix_message_sender.clone(),
}
}
@@ -87,18 +78,18 @@ impl ActiveClientsStore {
pub(crate) fn get_remote_client(
&self,
address: DestinationAddressBytes,
) -> Option<RemoteClientData> {
) -> Option<ClientIncomingChannels> {
let entry = self.inner.get(&address)?;
let handle = entry.value();
let ActiveClient::Remote(remote) = handle else {
let ActiveClient::Remote(channels) = handle else {
warn!("attempted to get a remote handle to a embedded network requester");
return None;
};
// if the entry is stale, remove it from the map
if !remote.channels.mix_message_sender.is_closed() {
Some(remote.clone())
if !channels.mix_message_sender.is_closed() {
Some(channels.clone())
} else {
// drop the reference to the map to prevent deadlocks
drop(entry);
@@ -146,14 +137,10 @@ impl ActiveClientsStore {
client: DestinationAddressBytes,
handle: MixMessageSender,
is_active_request_sender: IsActiveRequestSender,
session_request_timestamp: OffsetDateTime,
) {
let entry = ActiveClient::Remote(RemoteClientData {
session_request_timestamp,
channels: ClientIncomingChannels {
mix_message_sender: handle,
is_active_request_sender,
},
let entry = ActiveClient::Remote(ClientIncomingChannels {
mix_message_sender: handle,
is_active_request_sender,
});
if self.inner.insert(client, entry).is_some() {
panic!("inserted a duplicate remote client")
@@ -9,23 +9,15 @@ use nym_mixnet_client::forwarder::MixForwardingSender;
use nym_node_metrics::events::MetricEventsSender;
use nym_node_metrics::NymNodeMetrics;
use std::sync::Arc;
use std::time::Duration;
#[derive(Clone)]
pub(crate) struct Config {
pub(crate) enforce_zk_nym: bool,
pub(crate) max_auth_request_age: Duration,
pub(crate) bandwidth: BandwidthFlushingBehaviourConfig,
}
// I can see this being possible expanded with say storage or client store
#[derive(Clone)]
pub(crate) struct CommonHandlerState {
pub(crate) cfg: Config,
pub(crate) ecash_verifier: Arc<EcashManager>,
pub(crate) storage: GatewayStorage,
pub(crate) local_identity: Arc<identity::KeyPair>,
pub(crate) only_coconut_credentials: bool,
pub(crate) bandwidth_cfg: BandwidthFlushingBehaviourConfig,
pub(crate) metrics: NymNodeMetrics,
pub(crate) metrics_sender: MetricEventsSender,
pub(crate) outbound_mix_sender: MixForwardingSender,
@@ -194,8 +194,8 @@ impl<R, S> AuthenticatedHandler<R, S> {
fresh.shared_state.storage.clone(),
ClientBandwidth::new(bandwidth.into()),
client.id,
fresh.shared_state.cfg.bandwidth,
fresh.shared_state.cfg.enforce_zk_nym,
fresh.shared_state.bandwidth_cfg,
fresh.shared_state.only_coconut_credentials,
),
inner: fresh,
client,
@@ -1,13 +1,11 @@
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::node::client_handling::active_clients::RemoteClientData;
use crate::node::client_handling::websocket::common_state::CommonHandlerState;
use crate::node::client_handling::websocket::connection_handler::helpers::KeyWithAuthTimestamp;
use crate::node::client_handling::websocket::connection_handler::INITIAL_MESSAGE_TIMEOUT;
use crate::node::client_handling::websocket::{
connection_handler::{AuthenticatedHandler, ClientDetails, InitialAuthResult, SocketStream},
message_receiver::IsActive,
message_receiver::{IsActive, IsActiveRequestSender},
};
use futures::{
channel::{mpsc, oneshot},
@@ -15,16 +13,14 @@ use futures::{
};
use nym_credentials_interface::AvailableBandwidth;
use nym_crypto::aes::cipher::crypto_common::rand_core::RngCore;
use nym_crypto::asymmetric::ed25519;
use nym_gateway_requests::authenticate::AuthenticateRequest;
use nym_crypto::asymmetric::identity;
use nym_gateway_requests::authentication::encrypted_address::{
EncryptedAddressBytes, EncryptedAddressConversionError,
};
use nym_gateway_requests::{
registration::handshake::{error::HandshakeError, gateway_handshake},
types::{ClientControlRequest, ServerResponse},
AuthenticationFailure, BinaryResponse, SharedGatewayKey, CURRENT_PROTOCOL_VERSION,
INITIAL_PROTOCOL_VERSION,
BinaryResponse, SharedGatewayKey, CURRENT_PROTOCOL_VERSION, INITIAL_PROTOCOL_VERSION,
};
use nym_gateway_storage::error::GatewayStorageError;
use nym_node_metrics::events::MetricsEvent;
@@ -34,7 +30,6 @@ use rand::CryptoRng;
use std::net::SocketAddr;
use std::time::Duration;
use thiserror::Error;
use time::OffsetDateTime;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio::time::timeout;
use tokio_tungstenite::tungstenite::{protocol::Message, Error as WsError};
@@ -42,12 +37,6 @@ use tracing::*;
#[derive(Debug, Error)]
pub(crate) enum InitialAuthenticationError {
#[error(transparent)]
AuthenticationFailure(#[from] AuthenticationFailure),
#[error("attempted to overwrite client session with a stale authentication")]
StaleSessionOverwrite,
#[error("Internal gateway storage error")]
StorageError(#[from] GatewayStorageError),
@@ -301,15 +290,15 @@ impl<R, S> FreshHandler<R, S> {
// of doing full parse of the init_data elsewhere
fn extract_remote_identity_from_register_init(
init_data: &[u8],
) -> Result<ed25519::PublicKey, InitialAuthenticationError> {
if init_data.len() < ed25519::PUBLIC_KEY_LENGTH {
) -> Result<identity::PublicKey, InitialAuthenticationError> {
if init_data.len() < identity::PUBLIC_KEY_LENGTH {
Err(InitialAuthenticationError::HandshakeError(
HandshakeError::MalformedRequest,
))
} else {
ed25519::PublicKey::from_bytes(&init_data[..ed25519::PUBLIC_KEY_LENGTH]).map_err(|_| {
InitialAuthenticationError::HandshakeError(HandshakeError::MalformedRequest)
})
identity::PublicKey::from_bytes(&init_data[..identity::PUBLIC_KEY_LENGTH]).map_err(
|_| InitialAuthenticationError::HandshakeError(HandshakeError::MalformedRequest),
)
}
}
@@ -362,21 +351,6 @@ impl<R, S> FreshHandler<R, S> {
Ok(())
}
async fn retrieve_shared_key(
&self,
client: DestinationAddressBytes,
) -> Result<Option<KeyWithAuthTimestamp>, InitialAuthenticationError> {
let shared_keys = self.shared_state.storage.get_shared_keys(client).await?;
let Some(stored_shared_keys) = shared_keys else {
return Ok(None);
};
let keys = KeyWithAuthTimestamp::try_from_stored(stored_shared_keys, client)?;
Ok(Some(keys))
}
/// Checks whether the stored shared keys match the received data, i.e. whether the upon decryption
/// the provided encrypted address matches the expected unencrypted address.
///
@@ -387,18 +361,31 @@ impl<R, S> FreshHandler<R, S> {
/// * `client_address`: address of the client.
/// * `encrypted_address`: encrypted address of the client, presumably encrypted using the shared keys.
/// * `iv`: nonce/iv created for this particular encryption.
async fn auth_v1_verify_stored_shared_key(
async fn verify_stored_shared_key(
&self,
client_address: DestinationAddressBytes,
encrypted_address: EncryptedAddressBytes,
nonce: &[u8],
) -> Result<Option<KeyWithAuthTimestamp>, InitialAuthenticationError> {
let Some(keys) = self.retrieve_shared_key(client_address).await? else {
) -> Result<Option<SharedGatewayKey>, InitialAuthenticationError> {
let shared_keys = self
.shared_state
.storage
.get_shared_keys(client_address)
.await?;
let Some(stored_shared_keys) = shared_keys else {
return Ok(None);
};
let keys = SharedGatewayKey::try_from(stored_shared_keys).map_err(|source| {
InitialAuthenticationError::MalformedStoredSharedKey {
client_id: client_address.as_base58_string(),
source,
}
})?;
// LEGACY ISSUE: we're not verifying HMAC key
if encrypted_address.verify(&client_address, &keys.key, nonce) {
if encrypted_address.verify(&client_address, &keys, nonce) {
Ok(Some(keys))
} else {
Ok(None)
@@ -441,19 +428,49 @@ impl<R, S> FreshHandler<R, S> {
}
}
/// Using the received challenge data, i.e. client's address as well the ciphertext of it plus
/// a fresh IV, attempts to authenticate the client by checking whether the ciphertext matches
/// the expected value if encrypted with the shared key.
///
/// Finally, upon completion, all previously stored messages are pushed back to the client.
///
/// # Arguments
///
/// * `client_address`: address of the client wishing to authenticate.
/// * `encrypted_address`: ciphertext of the address of the client wishing to authenticate.
/// * `iv`: fresh nonce/IV received with the request.
async fn authenticate_client(
&mut self,
client_address: DestinationAddressBytes,
encrypted_address: EncryptedAddressBytes,
nonce: &[u8],
) -> Result<Option<SharedGatewayKey>, InitialAuthenticationError>
where
S: AsyncRead + AsyncWrite + Unpin,
{
debug!(
"Processing authenticate client request for: {}",
client_address.as_base58_string()
);
let shared_keys = self
.verify_stored_shared_key(client_address, encrypted_address, nonce)
.await?;
if let Some(shared_keys) = shared_keys {
self.push_stored_messages_to_client(client_address, &shared_keys)
.await?;
Ok(Some(shared_keys))
} else {
Ok(None)
}
}
async fn handle_duplicate_client(
&mut self,
address: DestinationAddressBytes,
remote_client_data: RemoteClientData,
new_session_start: OffsetDateTime,
mut is_active_request_tx: IsActiveRequestSender,
) -> Result<(), InitialAuthenticationError> {
let mut is_active_request_tx = remote_client_data.channels.is_active_request_sender;
// new session must **always** be explicitly more recent
if new_session_start <= remote_client_data.session_request_timestamp {
return Err(InitialAuthenticationError::StaleSessionOverwrite);
}
// Ask the other connection to ping if they are still active.
// Use a oneshot channel to return the result to us
let (ping_result_sender, ping_result_receiver) = oneshot::channel();
@@ -502,32 +519,6 @@ impl<R, S> FreshHandler<R, S> {
Ok(())
}
#[allow(dead_code)]
async fn get_registered_client_id(
&self,
client_address: DestinationAddressBytes,
) -> Result<i64, InitialAuthenticationError> {
self.shared_state
.storage
.get_mixnet_client_id(client_address)
.await
.map_err(Into::into)
}
async fn get_registered_available_bandwidth(
&self,
client_id: i64,
) -> Result<AvailableBandwidth, InitialAuthenticationError> {
let available_bandwidth: AvailableBandwidth = self
.shared_state
.storage
.get_available_bandwidth(client_id)
.await?
.map(From::from)
.unwrap_or_default();
Ok(available_bandwidth)
}
/// Tries to handle the received authentication request by checking correctness of the received data.
///
/// # Arguments
@@ -540,7 +531,7 @@ impl<R, S> FreshHandler<R, S> {
address = %address,
)
)]
async fn handle_legacy_authenticate(
async fn handle_authenticate(
&mut self,
client_protocol_version: Option<u8>,
address: String,
@@ -550,7 +541,7 @@ impl<R, S> FreshHandler<R, S> {
where
S: AsyncRead + AsyncWrite + Unpin,
{
debug!("handling client authentication (v1)");
debug!("handling client registration");
let negotiated_protocol = self.negotiate_client_protocol(client_protocol_version)?;
// populate the negotiated protocol for future uses
@@ -563,124 +554,38 @@ impl<R, S> FreshHandler<R, S> {
.into_vec()
.map_err(InitialAuthenticationError::MalformedIV)?;
// validate the shared key
// Check for duplicate clients
if let Some(client_tx) = self
.shared_state
.active_clients_store
.get_remote_client(address)
{
warn!("Detected duplicate connection for client: {address}");
self.handle_duplicate_client(address, client_tx.is_active_request_sender)
.await?;
}
let Some(shared_keys) = self
.auth_v1_verify_stored_shared_key(address, encrypted_address, &nonce)
.authenticate_client(address, encrypted_address, &nonce)
.await?
else {
// it feels weird to be returning an 'Ok' here, but I didn't want to change the existing behaviour
return Ok(InitialAuthResult::new_failed(Some(negotiated_protocol)));
};
// in v1 we don't have explicit data so we have to use current timestamp
// (which does nothing but just allows us to use the same codepath)
let session_request_start = OffsetDateTime::now_utc();
// Check for duplicate clients
if let Some(remote_client_data) = self
let client_id = self
.shared_state
.active_clients_store
.get_remote_client(address)
{
warn!("Detected duplicate connection for client: {address}");
self.handle_duplicate_client(address, remote_client_data, session_request_start)
.await?;
}
let client_id = shared_keys.client_id;
// if applicable, push stored messages
self.push_stored_messages_to_client(address, &shared_keys.key)
.await?;
// check the bandwidth
let available_bandwidth = self.get_registered_available_bandwidth(client_id).await?;
let bandwidth_remaining = if available_bandwidth.expired() {
self.shared_state.storage.reset_bandwidth(client_id).await?;
0
} else {
available_bandwidth.bytes
};
Ok(InitialAuthResult::new(
Some(ClientDetails::new(
client_id,
address,
shared_keys.key,
session_request_start,
)),
ServerResponse::Authenticate {
protocol_version: Some(negotiated_protocol),
status: true,
bandwidth_remaining,
},
))
}
async fn handle_authenticate_v2(
&mut self,
request: Box<AuthenticateRequest>,
) -> Result<InitialAuthResult, InitialAuthenticationError>
where
S: AsyncRead + AsyncWrite + Unpin,
{
debug!("handling client authentication (v2)");
let negotiated_protocol =
self.negotiate_client_protocol(Some(request.content.protocol_version))?;
// populate the negotiated protocol for future uses
self.negotiated_protocol = Some(negotiated_protocol);
let address = request.content.client_identity.derive_destination_address();
// do cheap checks first
// is the provided timestamp relatively recent (and not in the future?)
request.verify_timestamp(self.shared_state.cfg.max_auth_request_age)?;
// does the message signature verify?
request.verify_signature()?;
// retrieve the actually stored key and check if the ciphertext matches
let Some(shared_key) = self.retrieve_shared_key(address).await? else {
return Err(AuthenticationFailure::NotRegistered)?;
};
request.verify_ciphertext(&shared_key.key)?;
let session_request_start = request.content.request_timestamp();
// if the client has already authenticated in the past, make sure this authentication timestamp
// is different and greater than the old one (in case it was replayed)
if let Some(prior_usage) = shared_key.last_used_authentication {
request.ensure_timestamp_not_reused(prior_usage)?;
}
// check for duplicate clients
if let Some(client_data) = self
.shared_state
.active_clients_store
.get_remote_client(address)
{
warn!("Detected duplicate connection for client: {address}");
self.handle_duplicate_client(address, client_data, session_request_start)
.await?;
}
let client_id = shared_key.client_id;
// update the auth timestamp for future uses
self.shared_state
.storage
.update_last_used_authentication_timestamp(client_id, session_request_start)
.get_mixnet_client_id(address)
.await?;
// push any old stored messages to the client
// (this will be removed soon)
self.push_stored_messages_to_client(address, &shared_key.key)
.await?;
// finally check and retrieve client's bandwidth
let available_bandwidth = self.get_registered_available_bandwidth(client_id).await?;
let available_bandwidth: AvailableBandwidth = self
.shared_state
.storage
.get_available_bandwidth(client_id)
.await?
.map(From::from)
.unwrap_or_default();
let bandwidth_remaining = if available_bandwidth.expired() {
self.shared_state.storage.reset_bandwidth(client_id).await?;
@@ -690,12 +595,7 @@ impl<R, S> FreshHandler<R, S> {
};
Ok(InitialAuthResult::new(
Some(ClientDetails::new(
client_id,
address,
shared_key.key,
session_request_start,
)),
Some(ClientDetails::new(client_id, address, shared_keys)),
ServerResponse::Authenticate {
protocol_version: Some(negotiated_protocol),
status: true,
@@ -788,12 +688,7 @@ impl<R, S> FreshHandler<R, S> {
debug!(client_id = %client_id, "managed to finalize client registration");
let client_details = ClientDetails::new(
client_id,
remote_address,
shared_keys,
OffsetDateTime::now_utc(),
);
let client_details = ClientDetails::new(client_id, remote_address, shared_keys);
Ok(InitialAuthResult::new(
Some(client_details),
@@ -839,10 +734,9 @@ impl<R, S> FreshHandler<R, S> {
enc_address,
iv,
} => {
self.handle_legacy_authenticate(protocol_version, address, enc_address, iv)
self.handle_authenticate(protocol_version, address, enc_address, iv)
.await
}
ClientControlRequest::AuthenticateV2(req) => self.handle_authenticate_v2(req).await,
ClientControlRequest::RegisterHandshakeInitRequest {
protocol_version,
data,
@@ -933,7 +827,6 @@ impl<R, S> FreshHandler<R, S> {
registration_details.address,
mix_sender,
is_active_request_sender,
registration_details.session_request_timestamp,
);
return AuthenticatedHandler::upgrade(
@@ -1,37 +0,0 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::node::client_handling::websocket::connection_handler::fresh::InitialAuthenticationError;
use nym_gateway_requests::SharedGatewayKey;
use nym_gateway_storage::models::PersistedSharedKeys;
use nym_sphinx::DestinationAddressBytes;
use time::OffsetDateTime;
pub(crate) struct KeyWithAuthTimestamp {
pub(crate) client_id: i64,
pub(crate) key: SharedGatewayKey,
pub(crate) last_used_authentication: Option<OffsetDateTime>,
}
impl KeyWithAuthTimestamp {
pub(crate) fn try_from_stored(
stored_shared_keys: PersistedSharedKeys,
client: DestinationAddressBytes,
) -> Result<Self, InitialAuthenticationError> {
let last_used_authentication = stored_shared_keys.last_used_authentication;
let client_id = stored_shared_keys.client_id;
let key = SharedGatewayKey::try_from(stored_shared_keys).map_err(|source| {
InitialAuthenticationError::MalformedStoredSharedKey {
client_id: client.as_base58_string(),
source,
}
})?;
Ok(KeyWithAuthTimestamp {
client_id,
key,
last_used_authentication,
})
}
}
@@ -8,17 +8,16 @@ use nym_gateway_requests::ServerResponse;
use nym_sphinx::DestinationAddressBytes;
use rand::{CryptoRng, Rng};
use std::time::Duration;
use time::OffsetDateTime;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio_tungstenite::WebSocketStream;
use tracing::{debug, instrument, trace, warn};
use zeroize::{Zeroize, ZeroizeOnDrop};
pub(crate) use self::authenticated::AuthenticatedHandler;
pub(crate) use self::fresh::FreshHandler;
pub(crate) mod authenticated;
mod fresh;
pub(crate) mod helpers;
const WEBSOCKET_HANDSHAKE_TIMEOUT: Duration = Duration::from_millis(1_500);
const INITIAL_MESSAGE_TIMEOUT: Duration = Duration::from_millis(10_000);
@@ -41,13 +40,12 @@ impl<S> SocketStream<S> {
}
}
#[derive(Zeroize, ZeroizeOnDrop)]
pub(crate) struct ClientDetails {
#[zeroize(skip)]
pub(crate) address: DestinationAddressBytes,
pub(crate) id: i64,
pub(crate) shared_keys: SharedGatewayKey,
// note, this does **NOT ALWAYS** indicate timestamp of when client connected
// it is (for v2 auth) timestamp the client **signed** when it created the request
pub(crate) session_request_timestamp: OffsetDateTime,
}
impl ClientDetails {
@@ -55,13 +53,11 @@ impl ClientDetails {
id: i64,
address: DestinationAddressBytes,
shared_keys: SharedGatewayKey,
session_request_timestamp: OffsetDateTime,
) -> Self {
ClientDetails {
address,
id,
shared_keys,
session_request_timestamp,
}
}
}
@@ -8,4 +8,4 @@ pub(crate) mod connection_handler;
pub(crate) mod listener;
pub(crate) mod message_receiver;
pub(crate) use common_state::{CommonHandlerState, Config};
pub(crate) use common_state::CommonHandlerState;
+2 -5
View File
@@ -249,14 +249,11 @@ impl GatewayTasksBuilder {
active_clients_store: ActiveClientsStore,
) -> Result<websocket::Listener, GatewayError> {
let shared_state = websocket::CommonHandlerState {
cfg: websocket::Config {
enforce_zk_nym: self.config.gateway.enforce_zk_nyms,
max_auth_request_age: self.config.debug.maximum_auth_request_age,
bandwidth: (&self.config).into(),
},
ecash_verifier: self.ecash_manager().await?,
storage: self.storage.clone(),
local_identity: Arc::clone(&self.identity_keypair),
only_coconut_credentials: self.config.gateway.enforce_zk_nyms,
bandwidth_cfg: (&self.config).into(),
metrics: self.metrics.clone(),
metrics_sender: self.metrics_sender.clone(),
outbound_mix_sender: self.mix_packet_sender.clone(),
+1 -5
View File
@@ -4,7 +4,7 @@
[package]
name = "nym-api"
license = "GPL-3.0"
version = "1.1.52"
version = "1.1.51"
authors.workspace = true
edition = "2021"
rust-version.workspace = true
@@ -46,7 +46,6 @@ tokio-stream = { workspace = true }
tokio-util = { workspace = true }
url = { workspace = true }
tendermint = { workspace = true }
ts-rs = { workspace = true, optional = true }
anyhow = { workspace = true }
@@ -142,6 +141,3 @@ cw3 = { workspace = true }
cw-utils = { workspace = true }
rand_chacha = { workspace = true }
sha2 = "0.9"
[lints]
workspace = true
-3
View File
@@ -1,9 +1,6 @@
use sqlx::{Connection, FromRow, SqliteConnection};
use std::env;
// it's fine if compilation fails
#[allow(clippy::unwrap_used)]
#[allow(clippy::expect_used)]
#[tokio::main]
async fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
-1
View File
@@ -13,7 +13,6 @@ cosmwasm-std = { workspace = true }
getset = { workspace = true }
schemars = { workspace = true, features = ["preserve_order"] }
serde = { workspace = true, features = ["derive"] }
humantime-serde = { workspace = true }
serde_json = { workspace = true }
sha2.workspace = true
tendermint = { workspace = true }
-16
View File
@@ -1215,7 +1215,6 @@ impl From<Wireguard> for WireguardDetails {
#[derive(Clone, Copy, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
pub struct ApiHealthResponse {
pub status: ApiStatus,
pub chain_status: ChainStatus,
pub uptime: u64,
}
@@ -1225,25 +1224,10 @@ pub enum ApiStatus {
Up,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum ChainStatus {
Synced,
Unknown,
Stalled {
#[serde(
serialize_with = "humantime_serde::serialize",
deserialize_with = "humantime_serde::deserialize"
)]
approximate_amount: Duration,
},
}
impl ApiHealthResponse {
pub fn new_healthy(uptime: Duration) -> Self {
ApiHealthResponse {
status: ApiStatus::Up,
chain_status: ChainStatus::Synced,
uptime: uptime.as_secs(),
}
}
+1 -3
View File
@@ -47,8 +47,6 @@ impl CachedEpoch {
let now = OffsetDateTime::now_utc();
let validity_duration = if let Some(epoch_finish) = epoch.deadline {
// SAFETY: values set in our contract are valid unix timestamps
#[allow(clippy::unwrap_used)]
let state_end =
OffsetDateTime::from_unix_timestamp(epoch_finish.seconds() as i64).unwrap();
let until_epoch_state_end = state_end - now;
@@ -105,7 +103,7 @@ impl APICommunicationChannel for QueryCommunicationChannel {
drop(guard);
let guard = self.update_epoch_cache().await?;
Ok(guard.current_epoch.epoch_id)
return Ok(guard.current_epoch.epoch_id);
}
// TODO: perhaps this should be returning a ReadGuard instead?
+1 -4
View File
@@ -189,10 +189,7 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
self.state.clear_previous_epoch(epoch_id);
// SAFETY: we just accessed this item in an immutable way, thus it MUST exist so the unwrap is fine
#[allow(clippy::unwrap_used)]
{
self.state.in_progress_state_mut(epoch_id).unwrap().entered = true;
}
self.state.in_progress_state_mut(epoch_id).unwrap().entered = true;
}
// so at this point we don't need to be polling the contract so often anymore, but we can't easily
+1 -2
View File
@@ -204,9 +204,8 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
chunk_dealing(*dealing_index, dealing.to_bytes(), Self::DEALING_CHUNK_SIZE);
for chunk_index in needs_resubmission {
// this is a hard failure, panic level, actually.
// because we have already committed to dealings of particular size,
// because we have already committed to dealings of particular size
// yet we don't have relevant chunks after chunking
#[allow(clippy::expect_used)]
let chunk = chunks
.remove(chunk_index)
.expect("chunking specification has changed mid-exchange!");
-4
View File
@@ -177,8 +177,6 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
// SAFETY:
// since this share appears as 'verified' on the chain, it means the consensus of dealers confirmed its validity
// and thus they must have been able to parse it, so the unwrap/expect here is fine
// (unless quorum of validators is malicious, but at that point we have bigger problems...)
#[allow(clippy::expect_used)]
Ok(Some(
VerificationKeyAuth::try_from_bs58(&share.share)
.expect("failed to deserialize VERIFIED key"),
@@ -417,7 +415,6 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
// SAFETY: combining shares can only fail if we have different number shares and indices
// however, we returned an explicit error if decryption of any share failed and thus we know those values must match
#[allow(clippy::unwrap_used)]
let secret = combine_shares(shares, &all_dealers).unwrap();
if derived_x.is_none() {
derived_x = Some(secret)
@@ -429,7 +426,6 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
// SAFETY:
// we know we had a non-empty map of dealings and thus, at the very least, we must have derived a single secret
// (i.e. the x-element)
#[allow(clippy::unwrap_used)]
let sk = SecretKeyAuth::create_from_raw(derived_x.unwrap(), derived_secrets);
let derived_vk = sk.verification_key();
+2 -5
View File
@@ -33,17 +33,14 @@ impl DailyMerkleTree {
.into_iter()
.map(|l| (l.merkle_index, l))
.collect();
let total_leaves = leaves.len();
let mut sorted_leaves = Vec::new();
for i in 0..leaves.len() {
if let Some(next_leaf) = leaves.remove(&i) {
sorted_leaves.push(next_leaf);
} else {
let lost = total_leaves - i + 1;
error!("failed to produce consistent merkle tree. there was no leaf with index {i}. at least {lost} leaves got lost");
// we have to drop all data above that height because we can't rebuild the full tree
break;
let lost = leaves.len() - i + 1;
error!("failed to produce consistent merkle tree. there was no leaf with index {i}. at least {lost} leaves got lost")
}
}
+2 -3
View File
@@ -140,9 +140,6 @@ impl EcashState {
}
}
// whilst we normally don't want to panic, this one would only occur at startup,
// if some logical invariants got broken (which have to be fixed in code anyway)
#[allow(clippy::panic)]
pub(crate) fn spawn_background_cleaner(&mut self) {
match std::mem::take(&mut self.background_cleaner_state) {
BackgroundCleanerState::WaitingStartup(cleaner) => {
@@ -150,6 +147,8 @@ impl EcashState {
_handle: cleaner.start(),
}
}
// whilst we normally don't want to panic, this one would only occur at startup,
// if some logical invariants got broken (which have to be fixed in code anyway)
_ => panic!("attempted to spawn background cleaner more than once"),
}
}
-6
View File
@@ -13,9 +13,6 @@ struct StorageBorrowedSerdeWrapper<'a, T>(&'a T);
#[derive(Serialize, Deserialize)]
struct StorageSerdeWrapper<T>(T);
// SAFETY: we're not using custom serialiser for AnnotatedCoinIndexSignature
// and we're within bound limits
#[allow(clippy::unwrap_used)]
pub(crate) fn serialise_coin_index_signatures(sigs: &[AnnotatedCoinIndexSignature]) -> Vec<u8> {
storage_serialiser()
.serialize(&StorageBorrowedSerdeWrapper(&sigs))
@@ -31,9 +28,6 @@ pub(crate) fn deserialise_coin_index_signatures(
Ok(de.0)
}
// SAFETY: we're not using custom serialiser for AnnotatedExpirationDateSignature
// and we're within bound limits
#[allow(clippy::unwrap_used)]
pub(crate) fn serialise_expiration_date_signatures(
sigs: &[AnnotatedExpirationDateSignature],
) -> Vec<u8> {
+18 -36
View File
@@ -14,8 +14,7 @@ use crate::nym_contract_cache::cache::NymContractCache;
use crate::status::ApiStatusState;
use crate::support::caching::cache::SharedCache;
use crate::support::config;
use crate::support::http::state::{AppState, ChainStatusCache, ForcedRefresh};
use crate::support::nyxd::Client;
use crate::support::http::state::{AppState, ForcedRefresh};
use crate::support::storage::NymApiStorage;
use async_trait::async_trait;
use axum::Router;
@@ -47,7 +46,7 @@ use nym_coconut_dkg_common::types::{
use nym_coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare};
use nym_compact_ecash::BlindedSignature;
use nym_compact_ecash::{ttp_keygen, VerificationKeyAuth};
use nym_config::defaults::{NymNetworkDetails, ValidatorDetails};
use nym_config::defaults::NymNetworkDetails;
use nym_contracts_common::IdentityKey;
use nym_credentials::IssuanceTicketBook;
use nym_credentials_interface::TicketType;
@@ -71,7 +70,6 @@ use std::ops::Deref;
use std::str::FromStr;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tempfile::{tempdir, TempDir};
use time::Date;
use tokio::sync::RwLock;
@@ -430,27 +428,27 @@ impl FakeChainState {
);
let epoch_id = self.dkg_contract.epoch.epoch_id;
let Some(shares) = self.dkg_contract.verification_shares.get_mut(&epoch_id) else {
panic!("no shares for epoch")
unimplemented!("no shares for epoch")
};
let Some(share) = shares.get_mut(owner.as_str()) else {
panic!("no shares for owner")
unimplemented!("no shares for owner")
};
share.verified = true
}
other => panic!("unimplemented exec of {other:?}"),
other => unimplemented!("unimplemented exec of {other:?}"),
}
}
// TODO: make it return a result
fn execute_contract_msg(&mut self, contract: &String, msg: &Binary, sender: MessageInfo) {
if contract == &self.group_contract.address {
panic!("group contract exec")
unimplemented!("group contract exec")
}
if contract == &self.multisig_contract.address {
panic!("multisig contract exec")
unimplemented!("multisig contract exec")
}
if contract == &self.ecash_contract.address {
panic!("bandwidth contract exec")
unimplemented!("bandwidth contract exec")
}
if contract == self.dkg_contract.address.as_ref() {
return self.execute_dkg_contract(sender, msg);
@@ -469,7 +467,7 @@ impl FakeChainState {
let sender = mock_info(sender_address.as_ref(), funds);
self.execute_contract_msg(contract_addr, msg, sender)
}
other => panic!("unimplemented wasm proposal for {other:?}"),
other => unimplemented!("unimplemented wasm proposal for {other:?}"),
}
}
@@ -479,7 +477,7 @@ impl FakeChainState {
CosmosMsg::Wasm(wasm_msg) => {
self.execute_wasm_msg(wasm_msg, Addr::unchecked(sender_address.as_ref()))
}
other => panic!("unimplemented proposal for {other:?}"),
other => unimplemented!("unimplemented proposal for {other:?}"),
};
}
}
@@ -909,7 +907,7 @@ impl super::client::Client for DummyClient {
};
if proposal.status != cw3::Status::Passed {
panic!("proposal hasn't been passed")
unimplemented!("proposal hasn't been passed")
}
proposal.status = cw3::Status::Executed;
@@ -956,7 +954,7 @@ impl super::client::Client for DummyClient {
if !epoch_dealers.contains_key(self.validator_address.as_ref()) {
epoch_dealers.insert(self.validator_address.to_string(), dealer_details);
} else {
panic!("already registered")
unimplemented!("already registered")
}
let transaction_hash = guard._counters.next_tx_hash();
@@ -1266,14 +1264,8 @@ struct TestFixture {
}
impl TestFixture {
fn build_app_state(
storage: NymApiStorage,
ecash_state: EcashState,
nyxd_client: Client,
) -> AppState {
fn build_app_state(storage: NymApiStorage, ecash_state: EcashState) -> AppState {
AppState {
nyxd_client,
chain_status_cache: ChainStatusCache::new(Duration::from_secs(42)),
forced_refresh: ForcedRefresh::new(true),
nym_contract_cache: NymContractCache::new(),
node_status_cache: NodeStatusCache::new(),
@@ -1345,22 +1337,12 @@ impl TestFixture {
TaskClient::dummy(),
);
// ideally this would have been generic, but that's way too much work
// since then `AppState` would have had to be made generic
// also, this is such a disgusting workaround to make it 'work'. yuck
let mut dummy = NymNetworkDetails::new_empty();
dummy.endpoints = vec![ValidatorDetails::new(
"http://127.0.0.1:26657",
Some("http://why-do-we-even-need-api-url-set-here.wtf"),
None,
)];
dummy.export_to_env();
let another_fake_nyxd_client = Client::new(&config).unwrap();
TestFixture {
axum: TestServer::new(Router::new().nest("/v1/ecash", ecash_routes()).with_state(
Self::build_app_state(storage.clone(), ecash_state, another_fake_nyxd_client),
))
axum: TestServer::new(
Router::new()
.nest("/v1/ecash", ecash_routes())
.with_state(Self::build_app_state(storage.clone(), ecash_state)),
)
.unwrap(),
storage,
chain_state,
-3
View File
@@ -105,11 +105,8 @@ impl EpochAdvancer {
let standby_node_work_factor = global_rewarding_params.standby_node_work();
// SANITY CHECK:
// SAFETY: 0 decimal places is within the range of `Decimal`
#[allow(clippy::unwrap_used)]
let standby_share = Decimal::from_atomics(nodes.standby.len() as u128, 0).unwrap()
* standby_node_work_factor;
#[allow(clippy::unwrap_used)]
let active_share = Decimal::from_atomics(nodes.active_set_size() as u128, 0).unwrap()
* active_node_work_factor;
let total_work = standby_share + active_share;
@@ -139,7 +139,6 @@ impl EpochAdvancer {
let mut layer2 = Vec::new();
let mut layer3 = Vec::new();
#[allow(clippy::panic)]
for (i, mix) in mixnodes_vec.iter().enumerate() {
match i % 3 {
0 => layer1.push(*mix),
@@ -208,7 +207,6 @@ impl EpochAdvancer {
let mut with_performance = Vec::new();
// SAFETY: the cache MUST HAVE been initialised before now
#[allow(clippy::unwrap_used)]
let described_cache = self.described_cache.get().await.unwrap();
let Some(status_cache) = self.status_cache.node_annotations().await else {
+2 -27
View File
@@ -1,11 +1,9 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::network::models::{ChainStatusResponse, ContractInformation, NetworkDetails};
use crate::node_status_api::models::AxumResult;
use crate::network::models::{ContractInformation, NetworkDetails};
use crate::support::http::state::AppState;
use axum::extract::State;
use axum::{extract, Json, Router};
use axum::{extract, Router};
use nym_contracts_common::ContractBuildInformation;
use std::collections::HashMap;
use tower_http::compression::CompressionLayer;
@@ -14,7 +12,6 @@ use utoipa::ToSchema;
pub(crate) fn nym_network_routes() -> Router<AppState> {
Router::new()
.route("/details", axum::routing::get(network_details))
.route("/chain-status", axum::routing::get(chain_status))
.route("/nym-contracts", axum::routing::get(nym_contracts))
.route(
"/nym-contracts-detailed",
@@ -37,28 +34,6 @@ async fn network_details(
state.network_details().to_owned().into()
}
#[utoipa::path(
tag = "network",
get,
path = "/v1/network/chain-status",
responses(
(status = 200, body = ChainStatusResponse)
)
)]
async fn chain_status(State(state): State<AppState>) -> AxumResult<Json<ChainStatusResponse>> {
let chain_status = state
.chain_status_cache
.get_or_refresh(&state.nyxd_client)
.await?;
let connected_nyxd = state.network_details.connected_nyxd;
Ok(Json(ChainStatusResponse {
connected_nyxd,
status: chain_status,
}))
}
// it's used for schema generation so dead_code is fine
#[allow(dead_code)]
#[derive(ToSchema)]
-291
View File
@@ -1,9 +1,7 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::network::models::tendermint_types::{AbciInfo, BlockHeader, BlockId};
use nym_config::defaults::NymNetworkDetails;
use nym_validator_client::nyxd::BlockResponse;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
@@ -29,292 +27,3 @@ pub struct ContractInformation<T> {
pub(crate) address: Option<String>,
pub(crate) details: Option<T>,
}
#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct ChainStatusResponse {
pub connected_nyxd: String,
pub status: ChainStatus,
}
#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct ChainStatus {
pub abci: AbciInfo,
pub latest_block: BlockInfo,
}
#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct BlockInfo {
pub block_id: BlockId,
pub block: FullBlockInfo,
// if necessary we might put block data here later too
}
impl From<BlockResponse> for BlockInfo {
fn from(value: BlockResponse) -> Self {
BlockInfo {
block_id: value.block_id.into(),
block: FullBlockInfo {
header: value.block.header.into(),
},
}
}
}
#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct FullBlockInfo {
pub header: BlockHeader,
}
// copy tendermint types definitions whilst deriving schema types on them and dropping unwanted fields
pub mod tendermint_types {
use nym_validator_client::nyxd::Hash;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use tendermint::abci::response::Info;
use tendermint::block;
use tendermint::block::header::Version;
use utoipa::ToSchema;
#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct AbciInfo {
/// Some arbitrary information.
pub data: String,
/// The application software semantic version.
pub version: String,
/// The application protocol version.
pub app_version: u64,
/// The latest block for which the app has called [`Commit`].
pub last_block_height: u64,
/// The latest result of [`Commit`].
pub last_block_app_hash: String,
}
impl From<Info> for AbciInfo {
fn from(value: Info) -> Self {
AbciInfo {
data: value.data,
version: value.version,
app_version: value.app_version,
last_block_height: value.last_block_height.value(),
last_block_app_hash: value.last_block_app_hash.to_string(),
}
}
}
/// `Version` contains the protocol version for the blockchain and the
/// application.
///
/// <https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md#version>
#[derive(
Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, ToSchema,
)]
pub struct HeaderVersion {
/// Block version
pub block: u64,
/// App version
pub app: u64,
}
impl From<tendermint::block::header::Version> for HeaderVersion {
fn from(value: Version) -> Self {
HeaderVersion {
block: value.block,
app: value.app,
}
}
}
/// Block identifiers which contain two distinct Merkle roots of the block,
/// as well as the number of parts in the block.
///
/// <https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md#blockid>
///
/// Default implementation is an empty Id as defined by the Go implementation in
/// <https://github.com/tendermint/tendermint/blob/1635d1339c73ae6a82e062cd2dc7191b029efa14/types/block.go#L1204>.
///
/// If the Hash is empty in BlockId, the BlockId should be empty (encoded to None).
/// This is implemented outside of this struct. Use the Default trait to check for an empty BlockId.
/// See: <https://github.com/informalsystems/tendermint-rs/issues/663>
#[derive(
Serialize,
Deserialize,
Copy,
Clone,
Debug,
Default,
Hash,
Eq,
PartialEq,
PartialOrd,
Ord,
JsonSchema,
ToSchema,
)]
pub struct BlockId {
/// The block's main hash is the Merkle root of all the fields in the
/// block header.
#[schemars(with = "String")]
#[schema(value_type = String)]
pub hash: Hash,
/// Parts header (if available) is used for secure gossipping of the block
/// during consensus. It is the Merkle root of the complete serialized block
/// cut into parts.
///
/// PartSet is used to split a byteslice of data into parts (pieces) for
/// transmission. By splitting data into smaller parts and computing a
/// Merkle root hash on the list, you can verify that a part is
/// legitimately part of the complete data, and the part can be forwarded
/// to other peers before all the parts are known. In short, it's a fast
/// way to propagate a large file over a gossip network.
///
/// <https://github.com/tendermint/tendermint/wiki/Block-Structure#partset>
///
/// PartSetHeader in protobuf is defined as never nil using the gogoproto
/// annotations. This does not translate to Rust, but we can indicate this
/// in the domain type.
pub part_set_header: PartSetHeader,
}
impl From<block::Id> for BlockId {
fn from(value: block::Id) -> Self {
BlockId {
hash: value.hash,
part_set_header: value.part_set_header.into(),
}
}
}
/// Block parts header
#[derive(
Clone,
Copy,
Debug,
Default,
Hash,
Eq,
PartialEq,
PartialOrd,
Ord,
Deserialize,
Serialize,
JsonSchema,
ToSchema,
)]
#[non_exhaustive]
pub struct PartSetHeader {
/// Number of parts in this block
pub total: u32,
/// Hash of the parts set header,
#[schemars(with = "String")]
#[schema(value_type = String)]
pub hash: Hash,
}
impl From<tendermint::block::parts::Header> for PartSetHeader {
fn from(value: block::parts::Header) -> Self {
PartSetHeader {
total: value.total,
hash: value.hash,
}
}
}
/// Block `Header` values contain metadata about the block and about the
/// consensus, as well as commitments to the data in the current block, the
/// previous block, and the results returned by the application.
///
/// <https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md#header>
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct BlockHeader {
/// Header version
pub version: HeaderVersion,
/// Chain ID
pub chain_id: String,
/// Current block height
pub height: u64,
/// Current timestamp
#[schemars(with = "String")]
#[schema(value_type = String)]
pub time: tendermint::Time,
/// Previous block info
pub last_block_id: Option<BlockId>,
/// Commit from validators from the last block
#[schemars(with = "Option<String>")]
#[schema(value_type = Option<String>)]
pub last_commit_hash: Option<Hash>,
/// Merkle root of transaction hashes
#[schemars(with = "Option<String>")]
#[schema(value_type = Option<String>)]
pub data_hash: Option<Hash>,
/// Validators for the current block
#[schemars(with = "String")]
#[schema(value_type = String)]
pub validators_hash: Hash,
/// Validators for the next block
#[schemars(with = "String")]
#[schema(value_type = String)]
pub next_validators_hash: Hash,
/// Consensus params for the current block
#[schemars(with = "String")]
#[schema(value_type = String)]
pub consensus_hash: Hash,
/// State after txs from the previous block
#[schemars(with = "String")]
#[schema(value_type = String)]
pub app_hash: Hash,
/// Root hash of all results from the txs from the previous block
#[schemars(with = "Option<String>")]
#[schema(value_type = Option<String>)]
pub last_results_hash: Option<Hash>,
/// Hash of evidence included in the block
#[schemars(with = "Option<String>")]
#[schema(value_type = Option<String>)]
pub evidence_hash: Option<Hash>,
/// Original proposer of the block
#[serde(with = "nym_serde_helpers::hex")]
#[schemars(with = "String")]
#[schema(value_type = String)]
pub proposer_address: Vec<u8>,
}
impl From<block::Header> for BlockHeader {
fn from(value: block::Header) -> Self {
BlockHeader {
version: value.version.into(),
chain_id: value.chain_id.to_string(),
height: value.height.value(),
time: value.time,
last_block_id: value.last_block_id.map(Into::into),
last_commit_hash: value.last_commit_hash,
data_hash: value.data_hash,
validators_hash: value.validators_hash,
next_validators_hash: value.next_validators_hash,
consensus_hash: value.consensus_hash,
app_hash: Hash::try_from(value.app_hash.as_bytes().to_vec()).unwrap_or_default(),
last_results_hash: value.last_results_hash,
evidence_hash: value.evidence_hash,
proposer_address: value.proposer_address.as_bytes().to_vec(),
}
}
}
}
@@ -229,8 +229,6 @@ impl<R: MessageReceiver + Send + Sync> Monitor<R> {
// ideally we would blacklist all nodes regardless of the result so we would not use them anymore
// however, currently we have huge imbalance of gateways to mixnodes so we might accidentally
// discard working gateway because it was paired with broken mixnode
// SAFETY: the results is subset of candidates so the entry must exist
#[allow(clippy::unwrap_used)]
if *results.get(&candidate.id()).unwrap() {
// if the path is fully working, blacklist those nodes so we wouldn't construct
// any other path through any of those nodes
@@ -157,7 +157,6 @@ impl PacketPreparer {
self.contract_cache.wait_for_initial_values().await;
self.described_cache.naive_wait_for_initial_values().await;
#[allow(clippy::expect_used)]
let described_nodes = self
.described_cache
.get()
@@ -375,7 +374,6 @@ impl PacketPreparer {
.collect::<Vec<_>>();
// the unwrap on `min()` is fine as we know the iterator is not empty
#[allow(clippy::unwrap_used)]
let most_available = *[
rand_l1.len(),
rand_l2.len(),
@@ -429,7 +427,6 @@ impl PacketPreparer {
// 1. the topology is definitely valid (otherwise we wouldn't be here)
// 2. the recipient is specified (by calling **mix**_tester)
// 3. the test message is not too long, i.e. when serialized it will fit in a single sphinx packet
#[allow(clippy::unwrap_used)]
let mix_packets = plaintexts
.into_iter()
.map(|p| tester.wrap_plaintext_data(p, &topology, None).unwrap())
@@ -495,7 +492,6 @@ impl PacketPreparer {
) -> PreparedPackets {
let (mixnodes, gateways) = self.all_legacy_mixnodes_and_gateways().await;
#[allow(clippy::expect_used)]
let descriptions = self
.described_cache
.get()
@@ -169,9 +169,6 @@ where
where
R: Sync + Send + 'static,
{
// this panic could only be triggered by incorrect startup sequence and shouldn't affect
// the binary beyond that
#[allow(clippy::expect_used)]
let mut receiver_task = self
.receiver_task
.take()
@@ -8,7 +8,7 @@ use futures::StreamExt;
use nym_crypto::asymmetric::identity;
use nym_gateway_client::{AcknowledgementReceiver, MixnetMessageReceiver};
use nym_task::TaskClient;
use tracing::{error, trace};
use tracing::trace;
pub(crate) type GatewayClientUpdateSender = mpsc::UnboundedSender<GatewayClientUpdate>;
pub(crate) type GatewayClientUpdateReceiver = mpsc::UnboundedReceiver<GatewayClientUpdate>;
@@ -52,9 +52,9 @@ impl PacketReceiver {
}
fn process_gateway_messages(&self, messages: GatewayMessages) {
if self.processor_sender.unbounded_send(messages).is_err() {
error!("packet processor seems to have crashed!")
}
self.processor_sender
.unbounded_send(messages)
.expect("packet processor seems to have crashed!");
}
pub(crate) async fn run(&mut self, mut shutdown: TaskClient) {
@@ -66,13 +66,7 @@ impl PacketReceiver {
}
// unwrap here is fine as it can only return a `None` if the PacketSender has died
// and if that was the case, then the entire monitor is already in an undefined state
update = self.clients_updater.next() => {
if let Some(update) = update {
self.process_gateway_update(update)
} else {
error!("UpdateHandler: Client stream ended!");
}
},
update = self.clients_updater.next() => self.process_gateway_update(update.unwrap()),
Some((_gateway_id, messages)) = self.gateways_reader.next() => {
self.process_gateway_messages(messages)
}
@@ -246,8 +246,6 @@ impl PacketSender {
trace!("Sending {} packets...", mix_packets.len());
if mix_packets.len() == 1 {
// SAFETY: we just explicitly checked we have 1 message
#[allow(clippy::unwrap_used)]
client.send_mix_packet(mix_packets.pop().unwrap()).await?;
} else {
client.batch_send_mix_packets(mix_packets).await?;
@@ -104,7 +104,6 @@ impl TestRoute {
// the unwrap here is fine as the failure can only occur due to serialization and we're not
// using any custom implementations
#[allow(clippy::unwrap_used)]
NymApiTestMessageExt::new(self.id, ROUTE_TESTING_TEST_NONCE)
.mix_plaintexts(mix, count as u32)
.unwrap()
-15
View File
@@ -14,7 +14,6 @@ use nym_contracts_common::NaiveFloat;
use nym_mixnet_contract_common::reward_params::Performance;
use nym_mixnet_contract_common::{IdentityKey, NodeId};
use nym_serde_helpers::date::DATE_FORMAT;
use nym_validator_client::nyxd::error::NyxdError;
use reqwest::StatusCode;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -126,8 +125,6 @@ impl TryFrom<i64> for Uptime {
impl From<Uptime> for Performance {
fn from(uptime: Uptime) -> Self {
// SAFETY: uptime has a valid range to be transformed into a `Performance`
#[allow(clippy::unwrap_used)]
Performance::from_percentage_value(uptime.0 as u64).unwrap()
}
}
@@ -453,15 +450,6 @@ impl From<RedemptionError> for AxumErrorResponse {
}
}
impl From<NyxdError> for AxumErrorResponse {
fn from(value: NyxdError) -> Self {
Self {
message: RequestError::new(value.to_string()),
status: StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
#[derive(Debug, Error)]
pub enum NymApiStorageError {
#[error("could not find status report associated with mixnode {mix_id}")]
@@ -493,9 +481,6 @@ pub enum NymApiStorageError {
// this one would never be returned to users since it's only possible on startup
#[error("failed to perform startup SQL migration - {0}")]
StartupMigrationFailure(#[from] sqlx::migrate::MigrateError),
#[error("{value} is not a valid unix timestamp")]
InvalidTimestampProvided { value: i64 },
}
impl From<sqlx::Error> for NymApiStorageError {
@@ -8,8 +8,7 @@ use crate::node_status_api::ONE_DAY;
use crate::storage::NymApiStorage;
use nym_task::{TaskClient, TaskManager};
use std::time::Duration;
use time::macros::time;
use time::{OffsetDateTime, PrimitiveDateTime};
use time::{OffsetDateTime, PrimitiveDateTime, Time};
use tokio::time::{interval_at, Instant};
use tracing::error;
use tracing::{info, trace, warn};
@@ -76,20 +75,18 @@ impl HistoricalUptimeUpdater {
// nodes update for different days
// the unwrap is fine as 23:00:00 is a valid time
let update_time = time!(23:00:00);
let update_time = Time::from_hms(23, 0, 0).unwrap();
let now = OffsetDateTime::now_utc();
// is the current time within 0:00 - 22:59:59 or 23:00 - 23:59:59 ?
let update_date = if now.hour() < 23 {
now.date()
} else {
// the unwrap is fine as (**PRESUMABLY**) we're not running this code in the year 9999
#[allow(clippy::unwrap_used)]
now.date().next_day().unwrap()
};
let update_datetime = PrimitiveDateTime::new(update_date, update_time).assume_utc();
// the unwrap here is fine as we're certain `update_datetime` is in the future and thus the
// resultant Duration is positive
#[allow(clippy::unwrap_used)]
let time_left: Duration = (update_datetime - now).try_into().unwrap();
info!(
-1
View File
@@ -109,7 +109,6 @@ impl NodeUptimes {
// the unwraps in Uptime::from_ratio are fine because it's impossible for us to have more "up" results
// than total test runs as we just bounded them
#[allow(clippy::unwrap_used)]
NodeUptimes {
most_recent: most_recent.try_into().unwrap(),
last_hour: Uptime::from_uptime_sum(last_hour_sum, last_hour_test_runs).unwrap(),
-1
View File
@@ -128,7 +128,6 @@ impl NymContractCacheRefresher {
.collect();
let mut gateways = Vec::with_capacity(gateway_bonds.len());
#[allow(clippy::panic)]
for bond in gateway_bonds {
// we explicitly panic here because that value MUST exist.
// if it doesn't, we messed up the migration and we have big problems
@@ -47,8 +47,5 @@ impl LegacyAnnotation for GatewayBondAnnotated {
pub(crate) fn refreshed_at(
iter: impl IntoIterator<Item = OffsetDateTime>,
) -> OffsetDateTimeJsonSchemaWrapper {
iter.into_iter()
.min()
.unwrap_or(OffsetDateTime::UNIX_EPOCH)
.into()
iter.into_iter().min().unwrap().into()
}
+4 -33
View File
@@ -7,13 +7,9 @@ use crate::support::http::state::AppState;
use axum::extract::State;
use axum::Json;
use axum::Router;
use nym_api_requests::models::{
ApiHealthResponse, ApiStatus, ChainStatus, SignerInformationResponse,
};
use nym_api_requests::models::{ApiHealthResponse, SignerInformationResponse};
use nym_bin_common::build_information::BinaryBuildInformationOwned;
use nym_compact_ecash::Base58;
use std::time::Duration;
use time::OffsetDateTime;
pub(crate) fn api_status_routes() -> Router<AppState> {
Router::new()
@@ -33,34 +29,9 @@ pub(crate) fn api_status_routes() -> Router<AppState> {
(status = 200, body = ApiHealthResponse)
)
)]
async fn health(State(state): State<AppState>) -> Json<ApiHealthResponse> {
const CHAIN_STALL_THRESHOLD: Duration = Duration::from_secs(5 * 60);
let uptime = state.api_status.startup_time.elapsed();
let chain_status = match state
.chain_status_cache
.get_or_refresh(&state.nyxd_client)
.await
{
Ok(res) => {
let now = OffsetDateTime::now_utc();
let block_time: OffsetDateTime = res.latest_block.block.header.time.into();
let diff = now - block_time;
if diff > CHAIN_STALL_THRESHOLD {
ChainStatus::Stalled {
approximate_amount: diff.unsigned_abs(),
}
} else {
ChainStatus::Synced
}
}
Err(_) => ChainStatus::Unknown,
};
let health = ApiHealthResponse {
status: ApiStatus::Up,
chain_status,
uptime: uptime.as_secs(),
};
async fn health(State(state): State<ApiStatusState>) -> Json<ApiHealthResponse> {
let uptime = state.startup_time.elapsed();
let health = ApiHealthResponse::new_healthy(uptime);
Json(health)
}
+3 -5
View File
@@ -19,9 +19,9 @@ use crate::nym_contract_cache::cache::NymContractCache;
use crate::status::{ApiStatusState, SignerState};
use crate::support::caching::cache::SharedCache;
use crate::support::config::helpers::try_load_current_config;
use crate::support::config::{Config, DEFAULT_CHAIN_STATUS_CACHE_TTL};
use crate::support::config::Config;
use crate::support::http::state::{
AppState, ChainStatusCache, ForcedRefresh, ShutdownHandles, TASK_MANAGER_TIMEOUT_S,
AppState, ForcedRefresh, ShutdownHandles, TASK_MANAGER_TIMEOUT_S,
};
use crate::support::http::RouterBuilder;
use crate::support::nyxd;
@@ -108,7 +108,7 @@ pub(crate) struct Args {
async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result<ShutdownHandles> {
let task_manager = TaskManager::new(TASK_MANAGER_TIMEOUT_S);
let nyxd_client = nyxd::Client::new(config)?;
let nyxd_client = nyxd::Client::new(config);
let connected_nyxd = config.get_nyxd_url();
let nym_network_details = NymNetworkDetails::new_from_env();
let network_details = NetworkDetails::new(connected_nyxd.to_string(), nym_network_details);
@@ -191,8 +191,6 @@ async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result<ShutdownHan
ecash_state.spawn_background_cleaner();
let router = router.with_state(AppState {
nyxd_client: nyxd_client.clone(),
chain_status_cache: ChainStatusCache::new(DEFAULT_CHAIN_STATUS_CACHE_TTL),
forced_refresh: ForcedRefresh::new(
config.topology_cacher.debug.node_describe_allow_illegal_ips,
),
-5
View File
@@ -56,9 +56,6 @@ const DEFAULT_CIRCULATING_SUPPLY_CACHE_INTERVAL: Duration = Duration::from_secs(
pub(crate) const DEFAULT_NODE_DESCRIBE_CACHE_INTERVAL: Duration = Duration::from_secs(4500);
pub(crate) const DEFAULT_NODE_DESCRIBE_BATCH_SIZE: usize = 50;
// TODO: make it configurable
pub(crate) const DEFAULT_CHAIN_STATUS_CACHE_TTL: Duration = Duration::from_secs(60);
const DEFAULT_MONITOR_THRESHOLD: u8 = 60;
const DEFAULT_MIN_MIXNODE_RELIABILITY: u8 = 50;
const DEFAULT_MIN_GATEWAY_RELIABILITY: u8 = 20;
@@ -271,8 +268,6 @@ pub struct Base {
impl Base {
pub fn new_default<S: Into<String>>(id: S) -> Self {
// SAFETY: the provided hardcoded value is well-formed
#[allow(clippy::expect_used)]
let default_validator: Url = DEFAULT_LOCAL_VALIDATOR
.parse()
.expect("default local validator is malformed!");
+1 -89
View File
@@ -3,7 +3,7 @@
use crate::circulating_supply_api::cache::CirculatingSupplyCache;
use crate::ecash::state::EcashState;
use crate::network::models::{ChainStatus, NetworkDetails};
use crate::network::models::NetworkDetails;
use crate::node_describe_cache::DescribedNodes;
use crate::node_status_api::handlers::unstable;
use crate::node_status_api::models::AxumErrorResponse;
@@ -12,7 +12,6 @@ use crate::nym_contract_cache::cache::NymContractCache;
use crate::status::ApiStatusState;
use crate::support::caching::cache::SharedCache;
use crate::support::caching::Cache;
use crate::support::nyxd::Client;
use crate::support::storage;
use axum::extract::FromRef;
use nym_api_requests::models::{GatewayBondAnnotated, MixNodeBondAnnotated, NodeAnnotation};
@@ -21,7 +20,6 @@ use nym_task::TaskManager;
use nym_topology::CachedEpochRewardedSet;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use time::OffsetDateTime;
use tokio::sync::{RwLock, RwLockReadGuard};
use tokio::task::JoinHandle;
@@ -78,11 +76,6 @@ type AxumJoinHandle = JoinHandle<std::io::Result<()>>;
#[derive(Clone)]
pub(crate) struct AppState {
// ideally this would have been made generic to make tests easier,
// however, it'd be a way bigger change (I tried)
pub(crate) nyxd_client: Client,
pub(crate) chain_status_cache: ChainStatusCache,
pub(crate) forced_refresh: ForcedRefresh,
pub(crate) nym_contract_cache: NymContractCache,
pub(crate) node_status_cache: NodeStatusCache,
@@ -134,87 +127,6 @@ impl ForcedRefresh {
}
}
#[derive(Clone)]
pub(crate) struct ChainStatusCache {
cache_ttl: Duration,
inner: Arc<RwLock<Option<ChainStatusCacheInner>>>,
}
impl ChainStatusCache {
pub(crate) fn new(cache_ttl: Duration) -> Self {
ChainStatusCache {
cache_ttl,
inner: Arc::new(Default::default()),
}
}
}
struct ChainStatusCacheInner {
last_refreshed_at: OffsetDateTime,
cache_value: ChainStatus,
}
impl ChainStatusCacheInner {
fn is_valid(&self, ttl: Duration) -> bool {
if self.last_refreshed_at + ttl > OffsetDateTime::now_utc() {
return true;
}
false
}
}
impl ChainStatusCache {
pub(crate) async fn get_or_refresh(
&self,
client: &Client,
) -> Result<ChainStatus, AxumErrorResponse> {
if let Some(cached) = self.check_cache().await {
return Ok(cached);
}
self.refresh(client).await
}
async fn check_cache(&self) -> Option<ChainStatus> {
let guard = self.inner.read().await;
let inner = guard.as_ref()?;
if inner.is_valid(self.cache_ttl) {
return Some(inner.cache_value.clone());
}
None
}
async fn refresh(&self, client: &Client) -> Result<ChainStatus, AxumErrorResponse> {
// 1. attempt to get write lock permit
let mut guard = self.inner.write().await;
// 2. check if another task hasn't already updated the cache whilst we were waiting for the permit
if let Some(cached) = guard.as_ref() {
if cached.is_valid(self.cache_ttl) {
return Ok(cached.cache_value.clone());
}
}
// 3. attempt to query the chain for the chain data
let abci = client.abci_info().await?;
let block = client
.block_info(abci.last_block_height.value() as u32)
.await?;
let status = ChainStatus {
abci: abci.into(),
latest_block: block.into(),
};
*guard = Some(ChainStatusCacheInner {
last_refreshed_at: OffsetDateTime::now_utc(),
cache_value: status.clone(),
});
Ok(status)
}
}
impl AppState {
pub(crate) fn nym_contract_cache(&self) -> &NymContractCache {
&self.nym_contract_cache
+9 -19
View File
@@ -4,7 +4,7 @@
use crate::ecash::error::EcashError;
use crate::epoch_operations::RewardedNodeWithParams;
use crate::support::config::Config;
use anyhow::{Context, Result};
use anyhow::Result;
use async_trait::async_trait;
use cw3::{ProposalResponse, VoteResponse};
use cw4::MemberResponse;
@@ -44,7 +44,7 @@ use nym_validator_client::nyxd::{
PagedMixnetQueryClient, PagedMultisigQueryClient, PagedVestingQueryClient,
},
cosmwasm_client::types::ExecuteResult,
BlockResponse, CosmWasmClient, Fee, TendermintRpcClient,
CosmWasmClient, Fee,
};
use nym_validator_client::nyxd::{
hash::{Hash, SHA256_HASH_SIZE},
@@ -56,7 +56,6 @@ use nym_validator_client::{
use nym_vesting_contract_common::AccountVestingCoins;
use serde::Deserialize;
use std::sync::Arc;
use tendermint::abci::response::Info;
use tokio::sync::{RwLock, RwLockReadGuard};
#[macro_export]
@@ -100,13 +99,12 @@ pub enum ClientInner {
}
impl Client {
pub(crate) fn new(config: &Config) -> anyhow::Result<Self> {
pub(crate) fn new(config: &Config) -> Self {
let details = NymNetworkDetails::new_from_env();
let nyxd_url = config.get_nyxd_url();
let client_config = nyxd::Config::try_from_nym_network_details(&details).context(
"failed to construct valid validator client config with the provided network",
)?;
let client_config = nyxd::Config::try_from_nym_network_details(&details)
.expect("failed to construct valid validator client config with the provided network");
let inner = if let Some(mnemonic) = config.get_mnemonic() {
ClientInner::Signing(
@@ -115,32 +113,24 @@ impl Client {
nyxd_url.as_str(),
mnemonic.clone(),
)
.context("Failed to connect to nyxd!")?,
.expect("Failed to connect to nyxd!"),
)
} else {
ClientInner::Query(
QueryHttpRpcNyxdClient::connect(client_config, nyxd_url.as_str())
.context("Failed to connect to nyxd!")?,
.expect("Failed to connect to nyxd!"),
)
};
Ok(Client {
Client {
inner: Arc::new(RwLock::new(inner)),
})
}
}
pub(crate) async fn read(&self) -> RwLockReadGuard<'_, ClientInner> {
self.inner.read().await
}
pub(crate) async fn abci_info(&self) -> Result<Info, NyxdError> {
Ok(nyxd_query!(self, abci_info().await?))
}
pub(crate) async fn block_info(&self, height: u32) -> Result<BlockResponse, NyxdError> {
Ok(nyxd_query!(self, block(height).await?))
}
pub(crate) async fn client_address(&self) -> Option<AccountId> {
let guard = self.inner.read().await;
match &*guard {

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