Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4c449797eb | |||
| fcdf0fb580 | |||
| 66d0296f47 | |||
| 03bbbf44e9 | |||
| 0a48fa6172 | |||
| 18d9d807f2 | |||
| 3a7393d316 | |||
| 6ce5f707c6 | |||
| 766a1d4497 | |||
| 35c83f0a31 | |||
| 01dd4a7972 | |||
| c2e335557e | |||
| 40e1cbc7a9 | |||
| c133e0e88b | |||
| 5b716633de | |||
| 834538300d | |||
| bd0d70f7cd | |||
| 979485c582 | |||
| d95f66bd90 | |||
| 906dfb2fb0 | |||
| 7daa726626 | |||
| 067f492ad6 | |||
| ed73ec9ce6 | |||
| 61606630bd | |||
| 2d3deeb424 | |||
| 3827dc357d | |||
| d01867ca8d | |||
| 502c63b291 |
@@ -21,7 +21,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ arc-linux-latest ]
|
||||
platform: [ arc-ubuntu-22.04 ]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
env:
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ arc-ubuntu-22.04, custom-windows-11, custom-macos-15 ]
|
||||
os: [ arc-linux-latest, custom-windows-11, custom-macos-15 ]
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
@@ -46,9 +46,9 @@ jobs:
|
||||
RUSTUP_PERMIT_COPY_RENAME: 1
|
||||
steps:
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools protobuf-compiler
|
||||
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools protobuf-compiler cmake
|
||||
continue-on-error: true
|
||||
if: contains(matrix.os, 'ubuntu')
|
||||
if: contains(matrix.os, 'linux')
|
||||
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v4
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
|
||||
# To avoid running out of disk space, skip generating debug symbols
|
||||
- name: Set debug to false (unix)
|
||||
if: contains(matrix.os, 'ubuntu') || contains(matrix.os, 'mac')
|
||||
if: contains(matrix.os, 'linux') || contains(matrix.os, 'mac')
|
||||
run: |
|
||||
sed -i.bak 's/\[profile.dev\]/\[profile.dev\]\ndebug = false/' Cargo.toml
|
||||
git diff
|
||||
@@ -93,14 +93,14 @@ jobs:
|
||||
command: build
|
||||
|
||||
- name: Build all examples
|
||||
if: contains(matrix.os, 'ubuntu')
|
||||
if: contains(matrix.os, 'linux')
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --workspace --examples
|
||||
|
||||
- name: Run all tests
|
||||
if: contains(matrix.os, 'ubuntu')
|
||||
if: contains(matrix.os, 'linux')
|
||||
uses: actions-rs/cargo@v1
|
||||
env:
|
||||
NYM_API: https://sandbox-nym-api1.nymtech.net/api
|
||||
@@ -109,7 +109,7 @@ jobs:
|
||||
args: --workspace
|
||||
|
||||
- name: Run expensive tests
|
||||
if: (github.ref == 'refs/heads/develop' || github.event.pull_request.base.ref == 'develop' || github.event.pull_request.base.ref == 'master') && contains(matrix.os, 'ubuntu')
|
||||
if: (github.ref == 'refs/heads/develop' || github.event.pull_request.base.ref == 'develop' || github.event.pull_request.base.ref == 'master') && contains(matrix.os, 'linux')
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
|
||||
@@ -40,7 +40,8 @@ jobs:
|
||||
- name: Get version from cargo.toml
|
||||
id: get_version
|
||||
run: |
|
||||
yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
VERSION=$(yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml)
|
||||
echo "result=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: cleanup-gateway-probe-ref
|
||||
id: cleanup_gateway_probe_ref
|
||||
@@ -52,13 +53,16 @@ jobs:
|
||||
- name: Set GIT_TAG variable
|
||||
run: echo "GIT_TAG=${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set RELEASE_TAG variable
|
||||
- name: Initialize RELEASE_TAG
|
||||
run: echo "RELEASE_TAG=" >> $GITHUB_ENV
|
||||
|
||||
- name: Set RELEASE_TAG for release
|
||||
if: github.event.inputs.release_image == 'true'
|
||||
run: echo "RELEASE_TAG=golden-" >> $GITHUB_ENV
|
||||
|
||||
|
||||
- name: Set IMAGE_NAME_AND_TAGS variable
|
||||
run: echo "IMAGE_NAME_AND_TAGS=${{ env.CONTAINER_NAME }}:${{ env.RELEASE_TAG }}${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}" >> $GITHUB_ENV
|
||||
|
||||
|
||||
- name: New env vars
|
||||
run: echo "RELEASE_TAG='$RELEASE_TAG' GIT_TAG='$GIT_TAG' IMAGE_NAME_AND_TAGS='$IMAGE_NAME_AND_TAGS'"
|
||||
|
||||
|
||||
@@ -34,18 +34,22 @@ jobs:
|
||||
- name: Get version from cargo.toml
|
||||
id: get_version
|
||||
run: |
|
||||
yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
VERSION=$(yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml)
|
||||
echo "result=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Set GIT_TAG variable
|
||||
run: echo "GIT_TAG=${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set RELEASE_TAG variable
|
||||
- name: Initialise RELEASE_TAG
|
||||
run: echo "RELEASE_TAG=" >> $GITHUB_ENV
|
||||
|
||||
- name: Set RELEASE_TAG for release
|
||||
if: github.event.inputs.release_image == 'true'
|
||||
run: echo "RELEASE_TAG=golden-" >> $GITHUB_ENV
|
||||
|
||||
|
||||
- name: Set IMAGE_NAME_AND_TAGS variable
|
||||
run: echo "IMAGE_NAME_AND_TAGS=${{ env.CONTAINER_NAME }}:${{ env.RELEASE_TAG }}${{ steps.get_version.outputs.result }}" >> $GITHUB_ENV
|
||||
|
||||
|
||||
- name: New env vars
|
||||
run: echo "RELEASE_TAG='$RELEASE_TAG' GIT_TAG='$GIT_TAG' IMAGE_NAME_AND_TAGS='$IMAGE_NAME_AND_TAGS'"
|
||||
|
||||
@@ -65,6 +69,6 @@ jobs:
|
||||
|
||||
- name: BuildAndPushImageOnHarbor
|
||||
run: |
|
||||
docker build -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.IMAGE_NAME_AND_TAGS }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:latest
|
||||
docker build -f ${{ env.WORKING_DIRECTORY }}/Dockerfile-sqlite . -t harbor.nymte.ch/nym/${{ env.IMAGE_NAME_AND_TAGS }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:latest
|
||||
docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags
|
||||
|
||||
|
||||
@@ -4,6 +4,42 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2025.15-gruyere] (2025-08-20)
|
||||
|
||||
- Migrate strum to 0.27.2 ([#5960])
|
||||
- WG exit policy scripts update ([#5921])
|
||||
- Make DNS Resolver fallback optional ([#5920])
|
||||
- nym-node debug command to reset providers db ([#5914])
|
||||
- basic zulip client for sending messages ([#5913])
|
||||
- chore: allow compatibility with 'CDLA-Permissive-2.0' ([#5910])
|
||||
- feat: ecash liveness check ([#5890])
|
||||
- Remove old free credential handle ([#5864])
|
||||
|
||||
[#5960]: https://github.com/nymtech/nym/pull/5960
|
||||
[#5921]: https://github.com/nymtech/nym/pull/5921
|
||||
[#5920]: https://github.com/nymtech/nym/pull/5920
|
||||
[#5914]: https://github.com/nymtech/nym/pull/5914
|
||||
[#5913]: https://github.com/nymtech/nym/pull/5913
|
||||
[#5910]: https://github.com/nymtech/nym/pull/5910
|
||||
[#5890]: https://github.com/nymtech/nym/pull/5890
|
||||
[#5864]: https://github.com/nymtech/nym/pull/5864
|
||||
|
||||
## [2025.14-feta] (2025-08-05)
|
||||
|
||||
- chore: nym node tokio console ([#5909])
|
||||
- Feature/dkg snapshot epoch ([#5900])
|
||||
- Feature/dkg epoch dealers query ([#5899])
|
||||
- sqlx-pool-guard: allocate more memory on windows ([#5896])
|
||||
- Support mnemonic in the NS agent ([#5883])
|
||||
- Allow PG database backend ([#5880])
|
||||
|
||||
[#5909]: https://github.com/nymtech/nym/pull/5909
|
||||
[#5900]: https://github.com/nymtech/nym/pull/5900
|
||||
[#5899]: https://github.com/nymtech/nym/pull/5899
|
||||
[#5896]: https://github.com/nymtech/nym/pull/5896
|
||||
[#5883]: https://github.com/nymtech/nym/pull/5883
|
||||
[#5880]: https://github.com/nymtech/nym/pull/5880
|
||||
|
||||
## [2025.13-emmental] (2025-07-22)
|
||||
|
||||
- fix: don't allow mixnode running in exit mode ([#5898])
|
||||
|
||||
Generated
+544
-1296
File diff suppressed because it is too large
Load Diff
+5
-5
@@ -92,7 +92,7 @@ members = [
|
||||
"common/socks5/requests",
|
||||
"common/statistics",
|
||||
"common/store-cipher",
|
||||
"common/task",
|
||||
"common/task", "common/test-utils",
|
||||
"common/ticketbooks-merkle",
|
||||
"common/topology",
|
||||
"common/tun",
|
||||
@@ -102,7 +102,8 @@ members = [
|
||||
"common/wasm/storage",
|
||||
"common/wasm/utils",
|
||||
"common/wireguard",
|
||||
"common/wireguard-types", "common/zulip-client",
|
||||
"common/wireguard-types",
|
||||
"common/zulip-client",
|
||||
"documentation/autodoc",
|
||||
"gateway",
|
||||
"nym-api",
|
||||
@@ -320,8 +321,8 @@ si-scale = "0.2.3"
|
||||
snow = "0.9.6"
|
||||
sphinx-packet = "=0.6.0"
|
||||
sqlx = "0.8.6"
|
||||
strum = "0.26"
|
||||
strum_macros = "0.26"
|
||||
strum = "0.27.2"
|
||||
strum_macros = "0.27.2"
|
||||
subtle-encoding = "0.5"
|
||||
syn = "1"
|
||||
sysinfo = "0.33.0"
|
||||
@@ -450,4 +451,3 @@ exit = "deny"
|
||||
panic = "deny"
|
||||
unimplemented = "deny"
|
||||
unreachable = "deny"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.60"
|
||||
version = "1.1.61"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.60"
|
||||
version = "1.1.61"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
|
||||
@@ -207,7 +207,7 @@ where
|
||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
if let Some(stored) = storage
|
||||
.get_expiration_date_signatures(expiration_date)
|
||||
.get_expiration_date_signatures(expiration_date, epoch_id)
|
||||
.await
|
||||
.map_err(BandwidthControllerError::credential_storage_error)?
|
||||
{
|
||||
@@ -220,7 +220,7 @@ where
|
||||
ecash_apis,
|
||||
|api| async move {
|
||||
api.api_client
|
||||
.global_expiration_date_signatures(Some(expiration_date))
|
||||
.global_expiration_date_signatures(Some(expiration_date), Some(epoch_id))
|
||||
.await
|
||||
},
|
||||
format!("aggregated coin index signatures for date {expiration_date}"),
|
||||
|
||||
@@ -96,35 +96,59 @@ impl MixTrafficController {
|
||||
mut mix_packets: Vec<MixPacket>,
|
||||
) -> Result<(), ErasedGatewayError> {
|
||||
debug_assert!(!mix_packets.is_empty());
|
||||
|
||||
let result = if mix_packets.len() == 1 {
|
||||
let send_future = if mix_packets.len() == 1 {
|
||||
// SAFETY: we just checked we have one packet
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let mix_packet = mix_packets.pop().unwrap();
|
||||
self.gateway_transceiver.send_mix_packet(mix_packet).await
|
||||
self.gateway_transceiver.send_mix_packet(mix_packet)
|
||||
} else {
|
||||
self.gateway_transceiver
|
||||
.batch_send_mix_packets(mix_packets)
|
||||
.await
|
||||
self.gateway_transceiver.batch_send_mix_packets(mix_packets)
|
||||
};
|
||||
|
||||
if result.is_err() {
|
||||
self.consecutive_gateway_failure_count += 1;
|
||||
} else {
|
||||
trace!("We *might* have managed to forward sphinx packet(s) to the gateway!");
|
||||
self.consecutive_gateway_failure_count = 0;
|
||||
}
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = self.task_client.recv() => {
|
||||
trace!("received shutdown while handling messages");
|
||||
Ok(())
|
||||
}
|
||||
result = send_future => {
|
||||
if result.is_err() {
|
||||
self.consecutive_gateway_failure_count += 1;
|
||||
} else {
|
||||
trace!("We *might* have managed to forward sphinx packet(s) to the gateway!");
|
||||
self.consecutive_gateway_failure_count = 0;
|
||||
}
|
||||
|
||||
result
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_client_request(&mut self, client_request: ClientRequest) {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = self.task_client.recv() => {
|
||||
trace!("received shutdown while handling client request");
|
||||
}
|
||||
result = self.gateway_transceiver.send_client_request(client_request) => {
|
||||
if let Err(err) = result {
|
||||
error!("Failed to send client request: {err}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(mut self) {
|
||||
spawn_future!(
|
||||
async move {
|
||||
debug!("Started MixTrafficController with graceful shutdown support");
|
||||
|
||||
while !self.task_client.is_shutdown() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = self.task_client.recv() => {
|
||||
tracing::trace!("MixTrafficController: Received shutdown");
|
||||
break;
|
||||
}
|
||||
mix_packets = self.mix_rx.recv() => match mix_packets {
|
||||
Some(mix_packets) => {
|
||||
if let Err(err) = self.on_messages(mix_packets).await {
|
||||
@@ -147,23 +171,15 @@ impl MixTrafficController {
|
||||
},
|
||||
client_request = self.client_rx.recv() => match client_request {
|
||||
Some(client_request) => {
|
||||
match self.gateway_transceiver.send_client_request(client_request).await {
|
||||
Ok(_) => (),
|
||||
Err(e) => error!("Failed to send client request: {e}"),
|
||||
};
|
||||
self.on_client_request(client_request).await;
|
||||
},
|
||||
None => {
|
||||
tracing::trace!("MixTrafficController, client request channel closed");
|
||||
}
|
||||
},
|
||||
_ = self.task_client.recv() => {
|
||||
tracing::trace!("MixTrafficController: Received shutdown");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.task_client.recv_timeout().await;
|
||||
|
||||
tracing::debug!("MixTrafficController: Exiting");
|
||||
},
|
||||
"MixTrafficController"
|
||||
|
||||
+6
-3
@@ -226,6 +226,11 @@ where
|
||||
|
||||
while !self.task_client.is_shutdown() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = self.task_client.recv() => {
|
||||
tracing::trace!("InputMessageListener: Received shutdown");
|
||||
break;
|
||||
}
|
||||
input_msg = self.input_receiver.recv() => match input_msg {
|
||||
Some(input_msg) => {
|
||||
self.on_input_message(input_msg).await;
|
||||
@@ -235,9 +240,7 @@ where
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = self.task_client.recv() => {
|
||||
tracing::trace!("InputMessageListener: Received shutdown");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
self.task_client.recv_timeout().await;
|
||||
|
||||
+6
-3
@@ -179,6 +179,11 @@ where
|
||||
|
||||
while !self.task_client.is_shutdown() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = self.task_client.recv() => {
|
||||
tracing::trace!("RetransmissionRequestListener: Received shutdown");
|
||||
break;
|
||||
}
|
||||
timed_out_ack = self.request_receiver.next() => match timed_out_ack {
|
||||
Some(timed_out_ack) => self.on_retransmission_request(timed_out_ack, packet_type).await,
|
||||
None => {
|
||||
@@ -186,9 +191,7 @@ where
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = self.task_client.recv() => {
|
||||
tracing::trace!("RetransmissionRequestListener: Received shutdown");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
self.task_client.recv_timeout().await;
|
||||
|
||||
@@ -548,6 +548,7 @@ where
|
||||
pending_acks.push(pending_ack);
|
||||
}
|
||||
|
||||
drop(topology_permit);
|
||||
self.insert_pending_acks(pending_acks);
|
||||
self.forward_messages(real_messages, lane).await;
|
||||
|
||||
@@ -730,17 +731,21 @@ where
|
||||
|
||||
// tells real message sender (with the poisson timer) to send this to the mix network
|
||||
pub(crate) async fn forward_messages(
|
||||
&self,
|
||||
&mut self,
|
||||
messages: Vec<RealMessage>,
|
||||
transmission_lane: TransmissionLane,
|
||||
) {
|
||||
if let Err(err) = self
|
||||
.real_message_sender
|
||||
.send((messages, transmission_lane))
|
||||
.await
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("Failed to forward messages to the real message sender: {err}");
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = self.task_client.recv() => {
|
||||
trace!("received shutdown while attempting to forward mixnet messages");
|
||||
}
|
||||
sending_res = self.real_message_sender.send((messages, transmission_lane)) => {
|
||||
if sending_res.is_err() {
|
||||
error!(
|
||||
"failed to forward mixnet messages due to closed channel (outside of shutdown!)"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,17 +280,33 @@ where
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = self.mix_tx.send(vec![next_message]).await {
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
tracing::error!("Failed to send: {err}");
|
||||
let sending_res = tokio::select! {
|
||||
biased;
|
||||
_ = self.task_client.recv() => {
|
||||
trace!("received shutdown signal while attempting to send mix message");
|
||||
return
|
||||
}
|
||||
sending_res = self.mix_tx.send(vec![next_message]) => {
|
||||
sending_res
|
||||
}
|
||||
};
|
||||
|
||||
match sending_res {
|
||||
Err(_) => {
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
tracing::error!(
|
||||
"failed to send mixnet packet due to closed channel (outside of shutdown!)"
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(_) => {
|
||||
let event = if fragment_id.is_some() {
|
||||
PacketStatisticsEvent::RealPacketSent(packet_size)
|
||||
} else {
|
||||
PacketStatisticsEvent::CoverPacketSent(packet_size)
|
||||
};
|
||||
self.stats_tx.report(event.into());
|
||||
}
|
||||
} else {
|
||||
let event = if fragment_id.is_some() {
|
||||
PacketStatisticsEvent::RealPacketSent(packet_size)
|
||||
} else {
|
||||
PacketStatisticsEvent::CoverPacketSent(packet_size)
|
||||
};
|
||||
self.stats_tx.report(event.into());
|
||||
}
|
||||
|
||||
// notify ack controller about sending our message only after we actually managed to push it
|
||||
|
||||
@@ -126,7 +126,7 @@ impl TopologyAccessor {
|
||||
.map(|p| p.topology.clone())
|
||||
}
|
||||
|
||||
pub async fn current_route_provider(&self) -> Option<RwLockReadGuard<NymRouteProvider>> {
|
||||
pub async fn current_route_provider(&self) -> Option<RwLockReadGuard<'_, NymRouteProvider>> {
|
||||
let provider = self.inner.topology.read().await;
|
||||
if provider.topology.is_empty() {
|
||||
None
|
||||
|
||||
@@ -148,7 +148,7 @@ async fn connect(endpoint: &str) -> Result<WsConn, ClientCoreError> {
|
||||
JSWebsocket::new(endpoint).map_err(|_| ClientCoreError::GatewayJsConnectionFailure)
|
||||
}
|
||||
|
||||
async fn measure_latency<G>(gateway: &G) -> Result<GatewayWithLatency<G>, ClientCoreError>
|
||||
async fn measure_latency<G>(gateway: &G) -> Result<GatewayWithLatency<'_, G>, ClientCoreError>
|
||||
where
|
||||
G: ConnectableGateway,
|
||||
{
|
||||
|
||||
@@ -201,7 +201,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub async fn establish_connection(&mut self) -> Result<(), GatewayClientError> {
|
||||
debug!(
|
||||
"Attemting to establish connection to gateway at: {}",
|
||||
"Attempting to establish connection to gateway at: {}",
|
||||
self.gateway_address
|
||||
);
|
||||
let (ws_stream, _) = connect_async(
|
||||
|
||||
@@ -337,7 +337,7 @@ impl PartiallyDelegatedHandle {
|
||||
// check if the split stream didn't error out
|
||||
let receive_res = stream_receiver
|
||||
.try_recv()
|
||||
.expect("stream sender was somehow dropped without sending anything!");
|
||||
.map_err(|_| GatewayClientError::ConnectionAbruptlyClosed)?;
|
||||
|
||||
if let Some(res) = receive_res {
|
||||
let _res = res?;
|
||||
|
||||
@@ -719,10 +719,11 @@ impl NymApiClient {
|
||||
pub async fn partial_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Option<Date>,
|
||||
epoch_id: Option<EpochId>,
|
||||
) -> Result<PartialExpirationDateSignatureResponse, ValidatorClientError> {
|
||||
Ok(self
|
||||
.nym_api
|
||||
.partial_expiration_date_signatures(expiration_date)
|
||||
.partial_expiration_date_signatures(expiration_date, epoch_id)
|
||||
.await?)
|
||||
}
|
||||
|
||||
@@ -739,10 +740,11 @@ impl NymApiClient {
|
||||
pub async fn global_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Option<Date>,
|
||||
epoch_id: Option<EpochId>,
|
||||
) -> Result<AggregatedExpirationDateSignatureResponse, ValidatorClientError> {
|
||||
Ok(self
|
||||
.nym_api
|
||||
.global_expiration_date_signatures(expiration_date)
|
||||
.global_expiration_date_signatures(expiration_date, epoch_id)
|
||||
.await?)
|
||||
}
|
||||
|
||||
|
||||
@@ -1103,8 +1103,9 @@ pub trait NymApiClientExt: ApiClient {
|
||||
async fn partial_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Option<Date>,
|
||||
epoch_id: Option<EpochId>,
|
||||
) -> Result<PartialExpirationDateSignatureResponse, NymAPIError> {
|
||||
let params = match expiration_date {
|
||||
let mut params = match expiration_date {
|
||||
None => Vec::new(),
|
||||
Some(exp) => vec![(
|
||||
ecash::EXPIRATION_DATE_PARAM,
|
||||
@@ -1112,6 +1113,10 @@ pub trait NymApiClientExt: ApiClient {
|
||||
)],
|
||||
};
|
||||
|
||||
if let Some(epoch_id) = epoch_id {
|
||||
params.push((ecash::EPOCH_ID_PARAM, epoch_id.to_string()));
|
||||
}
|
||||
|
||||
self.get_json(
|
||||
&[
|
||||
routes::V1_API_VERSION,
|
||||
@@ -1148,8 +1153,9 @@ pub trait NymApiClientExt: ApiClient {
|
||||
async fn global_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Option<Date>,
|
||||
epoch_id: Option<EpochId>,
|
||||
) -> Result<AggregatedExpirationDateSignatureResponse, NymAPIError> {
|
||||
let params = match expiration_date {
|
||||
let mut params = match expiration_date {
|
||||
None => Vec::new(),
|
||||
Some(exp) => vec![(
|
||||
ecash::EXPIRATION_DATE_PARAM,
|
||||
@@ -1157,6 +1163,10 @@ pub trait NymApiClientExt: ApiClient {
|
||||
)],
|
||||
};
|
||||
|
||||
if let Some(epoch_id) = epoch_id {
|
||||
params.push((ecash::EPOCH_ID_PARAM, epoch_id.to_string()));
|
||||
}
|
||||
|
||||
self.get_json(
|
||||
&[
|
||||
routes::V1_API_VERSION,
|
||||
|
||||
@@ -86,7 +86,7 @@ pub async fn execute(args: Args) -> anyhow::Result<()> {
|
||||
anyhow!("ticketbook got incorrectly imported - the master verification key is missing")
|
||||
})?;
|
||||
let expiration_signatures = persistent_storage
|
||||
.get_expiration_date_signatures(expiration_date)
|
||||
.get_expiration_date_signatures(expiration_date, epoch_id)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
anyhow!(
|
||||
|
||||
@@ -120,7 +120,7 @@ async fn issue_to_file(args: Args, client: SigningClient) -> anyhow::Result<()>
|
||||
|
||||
if args.include_expiration_date_signatures {
|
||||
let signatures = credentials_store
|
||||
.get_expiration_date_signatures(expiration_date)
|
||||
.get_expiration_date_signatures(expiration_date, epoch_id)
|
||||
.await?
|
||||
.ok_or(anyhow!("missing expiration date signatures!"))?;
|
||||
|
||||
|
||||
@@ -241,7 +241,7 @@ pub async fn delegate_to_multiple_mixnodes(args: Args, client: SigningClient) {
|
||||
let node_id = row.node_id.clone().parse::<u32>().unwrap();
|
||||
let coins: Vec<Coin> = vec![];
|
||||
undelegation_msgs.push((ExecuteMsg::Undelegate { node_id }, coins));
|
||||
undelegation_table.add_row(&[row.node_id.clone()]);
|
||||
undelegation_table.add_row(std::slice::from_ref(&row.node_id));
|
||||
|
||||
if row.amount.amount > 0 {
|
||||
delegation_msgs
|
||||
|
||||
@@ -188,7 +188,7 @@ impl<C> ContractTesterBuilder<C> {
|
||||
*self.app.api()
|
||||
}
|
||||
|
||||
pub fn querier(&self) -> QuerierWrapper {
|
||||
pub fn querier(&self) -> QuerierWrapper<'_> {
|
||||
self.app.wrap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ pub trait NodeBond {
|
||||
|
||||
fn is_unbonding(&self) -> bool;
|
||||
|
||||
fn identity(&self) -> IdentityKeyRef;
|
||||
fn identity(&self) -> IdentityKeyRef<'_>;
|
||||
|
||||
fn original_pledge(&self) -> &Coin;
|
||||
|
||||
@@ -125,7 +125,7 @@ impl NodeBond for MixNodeBond {
|
||||
self.is_unbonding
|
||||
}
|
||||
|
||||
fn identity(&self) -> IdentityKeyRef {
|
||||
fn identity(&self) -> IdentityKeyRef<'_> {
|
||||
self.identity()
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ impl NodeBond for NymNodeBond {
|
||||
self.is_unbonding
|
||||
}
|
||||
|
||||
fn identity(&self) -> IdentityKeyRef {
|
||||
fn identity(&self) -> IdentityKeyRef<'_> {
|
||||
self.identity()
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ impl<'a> PrimaryKey<'a> for Role {
|
||||
type Suffix = <u8 as PrimaryKey<'a>>::Suffix;
|
||||
type SuperSuffix = <u8 as PrimaryKey<'a>>::SuperSuffix;
|
||||
|
||||
fn key(&self) -> Vec<Key> {
|
||||
fn key(&self) -> Vec<Key<'_>> {
|
||||
// I'm not sure why it wasn't possible to delegate the call to
|
||||
// `(*self as u8).key()` directly...
|
||||
// I guess because of the `Key::Ref(&'a [u8])` variant?
|
||||
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
-- 1. add temporary `epoch_id` column
|
||||
ALTER TABLE pending_issuance
|
||||
ADD COLUMN epoch_id INTEGER;
|
||||
|
||||
-- 2. populate the value
|
||||
UPDATE pending_issuance
|
||||
SET epoch_id = (SELECT epoch_id
|
||||
FROM expiration_date_signatures
|
||||
WHERE expiration_date_signatures.expiration_date = pending_issuance.expiration_date);
|
||||
|
||||
-- 3. create new expiration_date_signatures table (with changed constraints)
|
||||
CREATE TABLE expiration_date_signatures_new
|
||||
(
|
||||
expiration_date DATE NOT NULL,
|
||||
|
||||
epoch_id INTEGER NOT NULL,
|
||||
|
||||
serialization_revision INTEGER NOT NULL,
|
||||
|
||||
-- combined signatures for all tuples issued for given day
|
||||
serialised_signatures BLOB NOT NULL,
|
||||
|
||||
PRIMARY KEY (epoch_id, expiration_date)
|
||||
);
|
||||
|
||||
-- 4. migrate the data
|
||||
INSERT INTO expiration_date_signatures_new (expiration_date, epoch_id, serialization_revision, serialised_signatures)
|
||||
SELECT expiration_date, epoch_id, serialization_revision, serialised_signatures
|
||||
FROM expiration_date_signatures;
|
||||
|
||||
-- 5. drop and recreate the table references (due to new FK)
|
||||
|
||||
-- 5.1.
|
||||
-- (data for ticketbooks that have an associated deposit, but failed to get issued)
|
||||
CREATE TABLE pending_issuance_new
|
||||
(
|
||||
deposit_id INTEGER NOT NULL PRIMARY KEY,
|
||||
|
||||
-- introduce a way for us to introduce breaking changes in serialization of data
|
||||
serialization_revision INTEGER NOT NULL,
|
||||
|
||||
pending_ticketbook_data BLOB NOT NULL UNIQUE,
|
||||
|
||||
-- for each ticketbook we MUST have corresponding expiration date signatures
|
||||
expiration_date DATE NOT NULL,
|
||||
epoch_id INTEGER NOT NULL,
|
||||
|
||||
-- for each ticketbook we MUST have corresponding expiration date signatures
|
||||
FOREIGN KEY (epoch_id, expiration_date) REFERENCES expiration_date_signatures_new (epoch_id, expiration_date)
|
||||
);
|
||||
|
||||
INSERT INTO pending_issuance_new (deposit_id, serialization_revision, pending_ticketbook_data, expiration_date,
|
||||
epoch_id)
|
||||
SELECT deposit_id, serialization_revision, pending_ticketbook_data, expiration_date, epoch_id
|
||||
FROM pending_issuance;
|
||||
|
||||
|
||||
-- 5.2.
|
||||
CREATE TABLE ecash_ticketbook_new
|
||||
(
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
-- introduce a way for us to introduce breaking changes in serialization of data
|
||||
serialization_revision INTEGER NOT NULL,
|
||||
|
||||
-- the type of the associated ticketbook
|
||||
ticketbook_type TEXT NOT NULL,
|
||||
|
||||
-- the actual crypto data of the ticketbook (wallet, keys, etc.)
|
||||
ticketbook_data BLOB NOT NULL UNIQUE,
|
||||
|
||||
-- for each ticketbook we MUST have corresponding expiration date signatures
|
||||
expiration_date DATE NOT NULL,
|
||||
|
||||
-- for each ticketbook we MUST have corresponding coin index signatures
|
||||
epoch_id INTEGER NOT NULL,
|
||||
|
||||
-- the initial number of tickets the wallet has been created for
|
||||
total_tickets INTEGER NOT NULL,
|
||||
|
||||
-- how many tickets have been used so far (the `l` value of the wallet)
|
||||
used_tickets INTEGER NOT NULL,
|
||||
|
||||
|
||||
-- FOREIGN KEYS:
|
||||
|
||||
-- for each ticketbook we MUST have corresponding coin index signatures
|
||||
FOREIGN KEY (epoch_id) REFERENCES coin_indices_signatures (epoch_id),
|
||||
|
||||
-- for each ticketbook we MUST have corresponding expiration date signatures
|
||||
FOREIGN KEY (expiration_date, epoch_id) REFERENCES expiration_date_signatures_new (expiration_date, epoch_id)
|
||||
);
|
||||
|
||||
INSERT INTO ecash_ticketbook_new (id, serialization_revision, ticketbook_type, ticketbook_data, expiration_date,
|
||||
epoch_id, total_tickets, used_tickets)
|
||||
SELECT id,
|
||||
serialization_revision,
|
||||
ticketbook_type,
|
||||
ticketbook_data,
|
||||
expiration_date,
|
||||
epoch_id,
|
||||
total_tickets,
|
||||
used_tickets
|
||||
FROM ecash_ticketbook;
|
||||
|
||||
-- 6. finally swap out the old tables
|
||||
-- drop old tables
|
||||
DROP TABLE expiration_date_signatures;
|
||||
DROP TABLE pending_issuance;
|
||||
DROP TABLE ecash_ticketbook;
|
||||
|
||||
-- rename new tables
|
||||
ALTER TABLE expiration_date_signatures_new
|
||||
RENAME TO expiration_date_signatures;
|
||||
ALTER TABLE pending_issuance_new
|
||||
RENAME TO pending_issuance;
|
||||
ALTER TABLE ecash_ticketbook_new
|
||||
RENAME TO ecash_ticketbook;
|
||||
@@ -28,7 +28,7 @@ struct EcashCredentialManagerInner {
|
||||
pending: HashMap<i64, RetrievedPendingTicketbook>,
|
||||
master_vk: HashMap<u64, VerificationKeyAuth>,
|
||||
coin_indices_sigs: HashMap<u64, Vec<AnnotatedCoinIndexSignature>>,
|
||||
expiration_date_sigs: HashMap<Date, Vec<AnnotatedExpirationDateSignature>>,
|
||||
expiration_date_sigs: HashMap<(u64, Date), Vec<AnnotatedExpirationDateSignature>>,
|
||||
_next_id: i64,
|
||||
}
|
||||
|
||||
@@ -242,10 +242,14 @@ impl MemoryEcachTicketbookManager {
|
||||
pub(crate) async fn get_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: u64,
|
||||
) -> Option<Vec<AnnotatedExpirationDateSignature>> {
|
||||
let guard = self.inner.read().await;
|
||||
|
||||
guard.expiration_date_sigs.get(&expiration_date).cloned()
|
||||
guard
|
||||
.expiration_date_sigs
|
||||
.get(&(epoch_id, expiration_date))
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_expiration_date_signatures(
|
||||
@@ -254,8 +258,9 @@ impl MemoryEcachTicketbookManager {
|
||||
) {
|
||||
let mut guard = self.inner.write().await;
|
||||
|
||||
guard
|
||||
.expiration_date_sigs
|
||||
.insert(sigs.expiration_date, sigs.signatures.clone());
|
||||
guard.expiration_date_sigs.insert(
|
||||
(sigs.epoch_id, sigs.expiration_date),
|
||||
sigs.signatures.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ impl SqliteEcashTicketbookManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn begin_storage_tx(&self) -> Result<Transaction<Sqlite>, sqlx::Error> {
|
||||
pub(crate) async fn begin_storage_tx(&self) -> Result<Transaction<'_, Sqlite>, sqlx::Error> {
|
||||
self.connection_pool.begin().await
|
||||
}
|
||||
|
||||
@@ -260,15 +260,17 @@ impl SqliteEcashTicketbookManager {
|
||||
pub(crate) async fn get_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: i64,
|
||||
) -> Result<Option<RawExpirationDateSignatures>, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
RawExpirationDateSignatures,
|
||||
r#"
|
||||
SELECT epoch_id as "epoch_id: u32", serialised_signatures, serialization_revision as "serialization_revision: u8"
|
||||
SELECT serialised_signatures, serialization_revision as "serialization_revision: u8"
|
||||
FROM expiration_date_signatures
|
||||
WHERE expiration_date = ?
|
||||
WHERE expiration_date = ? AND epoch_id = ?
|
||||
"#,
|
||||
expiration_date
|
||||
expiration_date,
|
||||
epoch_id
|
||||
)
|
||||
.fetch_optional(&*self.connection_pool)
|
||||
.await
|
||||
|
||||
@@ -166,10 +166,11 @@ impl Storage for EphemeralStorage {
|
||||
async fn get_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: u64,
|
||||
) -> Result<Option<Vec<AnnotatedExpirationDateSignature>>, Self::StorageError> {
|
||||
Ok(self
|
||||
.storage_manager
|
||||
.get_expiration_date_signatures(expiration_date)
|
||||
.get_expiration_date_signatures(expiration_date, epoch_id)
|
||||
.await)
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,6 @@ pub struct StoredPendingTicketbook {
|
||||
|
||||
#[cfg_attr(not(target_arch = "wasm32"), derive(sqlx::FromRow))]
|
||||
pub struct RawExpirationDateSignatures {
|
||||
pub epoch_id: u32,
|
||||
pub serialised_signatures: Vec<u8>,
|
||||
pub serialization_revision: u8,
|
||||
}
|
||||
|
||||
@@ -325,10 +325,11 @@ impl Storage for PersistentStorage {
|
||||
async fn get_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: u64,
|
||||
) -> Result<Option<Vec<AnnotatedExpirationDateSignature>>, Self::StorageError> {
|
||||
let Some(raw) = self
|
||||
.storage_manager
|
||||
.get_expiration_date_signatures(expiration_date)
|
||||
.get_expiration_date_signatures(expiration_date, epoch_id as i64)
|
||||
.await?
|
||||
else {
|
||||
return Ok(None);
|
||||
|
||||
@@ -92,6 +92,7 @@ pub trait Storage: Clone + Send + Sync {
|
||||
async fn get_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: u64,
|
||||
) -> Result<Option<Vec<AnnotatedExpirationDateSignature>>, Self::StorageError>;
|
||||
|
||||
async fn insert_expiration_date_signatures(
|
||||
|
||||
@@ -39,7 +39,7 @@ impl traits::EcashManager for EcashManager {
|
||||
async fn verification_key(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<RwLockReadGuard<VerificationKeyAuth>, EcashTicketError> {
|
||||
) -> Result<RwLockReadGuard<'_, VerificationKeyAuth>, EcashTicketError> {
|
||||
self.shared_state.verification_key(epoch_id).await
|
||||
}
|
||||
|
||||
@@ -231,7 +231,7 @@ impl traits::EcashManager for MockEcashManager {
|
||||
async fn verification_key(
|
||||
&self,
|
||||
_epoch_id: EpochId,
|
||||
) -> Result<RwLockReadGuard<VerificationKeyAuth>, EcashTicketError> {
|
||||
) -> Result<RwLockReadGuard<'_, VerificationKeyAuth>, EcashTicketError> {
|
||||
Ok(self.verfication_key.read().await)
|
||||
}
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ impl SharedState {
|
||||
async fn set_epoch_data(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<RwLockWriteGuard<BTreeMap<EpochId, EpochState>>, EcashTicketError> {
|
||||
) -> Result<RwLockWriteGuard<'_, BTreeMap<EpochId, EpochState>>, EcashTicketError> {
|
||||
let Some(threshold) = self.threshold(epoch_id).await? else {
|
||||
return Err(EcashTicketError::DKGThresholdUnavailable { epoch_id });
|
||||
};
|
||||
@@ -186,7 +186,7 @@ impl SharedState {
|
||||
pub(crate) async fn api_clients(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<RwLockReadGuard<Vec<EcashApiClient>>, EcashTicketError> {
|
||||
) -> Result<RwLockReadGuard<'_, Vec<EcashApiClient>>, EcashTicketError> {
|
||||
let guard = self.epoch_data.read().await;
|
||||
|
||||
// the key was already in the map
|
||||
@@ -212,7 +212,7 @@ impl SharedState {
|
||||
pub(crate) async fn verification_key(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<RwLockReadGuard<VerificationKeyAuth>, EcashTicketError> {
|
||||
) -> Result<RwLockReadGuard<'_, VerificationKeyAuth>, EcashTicketError> {
|
||||
let guard = self.epoch_data.read().await;
|
||||
|
||||
// the key was already in the map
|
||||
@@ -235,11 +235,11 @@ impl SharedState {
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) async fn start_tx(&self) -> RwLockWriteGuard<DirectSigningHttpRpcNyxdClient> {
|
||||
pub(crate) async fn start_tx(&self) -> RwLockWriteGuard<'_, DirectSigningHttpRpcNyxdClient> {
|
||||
self.nyxd_client.write().await
|
||||
}
|
||||
|
||||
pub(crate) async fn start_query(&self) -> RwLockReadGuard<DirectSigningHttpRpcNyxdClient> {
|
||||
pub(crate) async fn start_query(&self) -> RwLockReadGuard<'_, DirectSigningHttpRpcNyxdClient> {
|
||||
self.nyxd_client.read().await
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ pub trait EcashManager {
|
||||
async fn verification_key(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<RwLockReadGuard<VerificationKeyAuth>, EcashTicketError>;
|
||||
) -> Result<RwLockReadGuard<'_, VerificationKeyAuth>, EcashTicketError>;
|
||||
fn storage(&self) -> Box<dyn BandwidthGatewayStorage + Send + Sync>;
|
||||
async fn check_payment(
|
||||
&self,
|
||||
|
||||
@@ -15,6 +15,7 @@ bls12_381 = { workspace = true, default-features = false }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
thiserror = { workspace = true }
|
||||
strum = { workspace = true, features = ["derive"] }
|
||||
strum_macros = { workspace = true }
|
||||
time = { workspace = true, features = ["serde"] }
|
||||
utoipa = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
|
||||
@@ -229,9 +229,9 @@ impl From<PayInfo> for NymPayInfo {
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Hash,
|
||||
strum::Display,
|
||||
strum::EnumString,
|
||||
strum::EnumIter,
|
||||
strum_macros::Display,
|
||||
strum_macros::EnumString,
|
||||
strum_macros::EnumIter,
|
||||
)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[strum(serialize_all = "kebab-case")]
|
||||
|
||||
@@ -51,7 +51,7 @@ pub async fn obtain_expiration_date_signatures(
|
||||
for ecash_api_client in ecash_api_clients.iter() {
|
||||
match ecash_api_client
|
||||
.api_client
|
||||
.partial_expiration_date_signatures(None)
|
||||
.partial_expiration_date_signatures(None, None)
|
||||
.await
|
||||
{
|
||||
Ok(signature) => {
|
||||
|
||||
@@ -47,4 +47,7 @@ workspace = true
|
||||
default-features = false
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
nym-compact-ecash = { path = "../nym_offline_compact_ecash" } # we need specific imports in tests
|
||||
nym-test-utils = { path = "../test-utils" }
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
|
||||
@@ -109,3 +109,85 @@ GATEWAY -> CLIENT
|
||||
DONE(status)
|
||||
|
||||
*/
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ClientControlRequest;
|
||||
use futures::StreamExt;
|
||||
use nym_test_utils::helpers::u64_seeded_rng;
|
||||
use nym_test_utils::mocks::stream_sink::mock_streams;
|
||||
use nym_test_utils::traits::{Leak, Timeboxed, TimeboxedSpawnable};
|
||||
use tokio::join;
|
||||
use tungstenite::Message;
|
||||
|
||||
#[tokio::test]
|
||||
async fn basic_handshake() -> anyhow::Result<()> {
|
||||
use anyhow::Context as _;
|
||||
|
||||
// solve the lifetime issue by just leaking the contents of the boxes
|
||||
// which is perfectly fine in test
|
||||
let client_rng = u64_seeded_rng(42).leak();
|
||||
let gateway_rng = u64_seeded_rng(69).leak();
|
||||
|
||||
let client_keys = ed25519::KeyPair::new(client_rng).leak();
|
||||
let gateway_keys = ed25519::KeyPair::new(gateway_rng).leak();
|
||||
|
||||
let (client_ws, gateway_ws) = mock_streams::<Message>();
|
||||
|
||||
// we need streams that return Result<Message, WsError>
|
||||
let client_ws = client_ws.map(Ok);
|
||||
let gateway_ws = gateway_ws.map(Ok);
|
||||
|
||||
let client_ws = client_ws.leak();
|
||||
let gateway_ws = gateway_ws.leak();
|
||||
|
||||
let handshake_client = client_handshake(
|
||||
client_rng,
|
||||
client_ws,
|
||||
client_keys,
|
||||
*gateway_keys.public_key(),
|
||||
false,
|
||||
true,
|
||||
TaskClient::dummy(),
|
||||
);
|
||||
|
||||
let client_fut = handshake_client.spawn_timeboxed();
|
||||
|
||||
// we need to receive the first message so that it could be propagated to the gateway side of the handshake
|
||||
let ClientControlRequest::RegisterHandshakeInitRequest {
|
||||
protocol_version: _,
|
||||
data,
|
||||
} = (gateway_ws.next())
|
||||
.timeboxed()
|
||||
.await
|
||||
.context("timeout")?
|
||||
.context("no message!")??
|
||||
.into_text()?
|
||||
.parse::<ClientControlRequest>()?
|
||||
else {
|
||||
panic!("bad message")
|
||||
};
|
||||
|
||||
let init_msg = data;
|
||||
|
||||
let handshake_gateway = gateway_handshake(
|
||||
gateway_rng,
|
||||
gateway_ws,
|
||||
gateway_keys,
|
||||
init_msg,
|
||||
TaskClient::dummy(),
|
||||
);
|
||||
|
||||
let gateway_fut = handshake_gateway.spawn_timeboxed();
|
||||
let (client, gateway) = join!(client_fut, gateway_fut);
|
||||
|
||||
let client_key = client???;
|
||||
let gateway_key = gateway???;
|
||||
|
||||
// ensure the created keys are the same
|
||||
assert_eq!(client_key, gateway_key);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,6 +173,10 @@ mod path;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use dns::{HickoryDnsError, HickoryDnsResolver};
|
||||
|
||||
// helper for generating user agent based on binary information
|
||||
#[doc(hidden)]
|
||||
pub use nym_bin_common::bin_info;
|
||||
|
||||
/// Default HTTP request connection timeout.
|
||||
///
|
||||
/// The timeout is relatively high as we are often making requests over the mixnet, where latency is
|
||||
@@ -608,6 +612,7 @@ impl Client {
|
||||
current_idx: Arc::new(Default::default()),
|
||||
reqwest_client: self.reqwest_client.clone(),
|
||||
|
||||
#[cfg(feature = "tunneling")]
|
||||
front: self.front.clone(),
|
||||
retry_limit: self.retry_limit,
|
||||
|
||||
|
||||
@@ -20,6 +20,16 @@ pub struct UserAgent {
|
||||
pub git_commit: String,
|
||||
}
|
||||
|
||||
/// Create `UserAgent` based on the caller's crate information
|
||||
// we can't use normal function as then `application` and `version` would correspond
|
||||
// of that of `nym-http-api-client` lib
|
||||
#[macro_export]
|
||||
macro_rules! generate_user_agent {
|
||||
() => {
|
||||
$crate::UserAgent::from($crate::bin_info!())
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, thiserror::Error)]
|
||||
#[error("invalid user agent string: {0}")]
|
||||
pub struct UserAgentError(String);
|
||||
|
||||
@@ -25,8 +25,8 @@ pub enum NymIdError {
|
||||
#[error("attempted to import an expired credential (it expired on {expiration})")]
|
||||
ExpiredCredentialImport { expiration: Date },
|
||||
|
||||
#[error("could not import ticketbook expiring at {date} since we do not have corresponding expiration date signatures")]
|
||||
MissingExpirationDateSignatures { date: Date },
|
||||
#[error("could not import ticketbook expiring at {date} for epoch {epoch_id} since we do not have corresponding expiration date signatures")]
|
||||
MissingExpirationDateSignatures { date: Date, epoch_id: u64 },
|
||||
|
||||
#[error("could not import ticketbook for epoch {epoch_id} since we do not have corresponding coin index signatures")]
|
||||
MissingCoinIndexSignatures { epoch_id: u64 },
|
||||
|
||||
@@ -99,7 +99,7 @@ where
|
||||
|
||||
// in order to import the ticketbook we MUST have the appropriate signatures in the storage already
|
||||
if credentials_store
|
||||
.get_expiration_date_signatures(ticketbook.expiration_date())
|
||||
.get_expiration_date_signatures(ticketbook.expiration_date(), ticketbook.epoch_id())
|
||||
.await
|
||||
.map_err(|source| NymIdError::StorageError {
|
||||
source: Box::new(source),
|
||||
@@ -108,6 +108,7 @@ where
|
||||
{
|
||||
return Err(NymIdError::MissingExpirationDateSignatures {
|
||||
date: ticketbook.expiration_date(),
|
||||
epoch_id: ticketbook.epoch_id(),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ pin-project = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
snow = { workspace = true }
|
||||
strum = { workspace = true, features = ["derive"] }
|
||||
strum_macros = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["net", "io-util", "time"] }
|
||||
tokio-util = { workspace = true, features = ["codec"] }
|
||||
@@ -27,6 +28,7 @@ anyhow = { workspace = true }
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
rand_chacha = { workspace = true }
|
||||
nym-crypto = { path = "../crypto", features = ["rand"] }
|
||||
nym-test-utils = { path = "../test-utils" }
|
||||
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -13,7 +13,7 @@ use nym_crypto::asymmetric::x25519;
|
||||
use nym_noise_keys::{NoiseVersion, VersionedNoiseKey};
|
||||
use snow::params::NoiseParams;
|
||||
|
||||
use strum::{EnumIter, FromRepr};
|
||||
use strum_macros::{EnumIter, FromRepr};
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy, EnumIter, FromRepr, Eq, PartialEq)]
|
||||
#[repr(u8)]
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::config::NoisePattern;
|
||||
use crate::error::NoiseError;
|
||||
use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||
use nym_noise_keys::NoiseVersion;
|
||||
use strum::FromRepr;
|
||||
use strum_macros::FromRepr;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NymNoiseFrame {
|
||||
|
||||
@@ -411,122 +411,21 @@ where
|
||||
mod tests {
|
||||
use super::*;
|
||||
use nym_crypto::asymmetric::x25519;
|
||||
use rand_chacha::rand_core::SeedableRng;
|
||||
use std::io::Error;
|
||||
use std::mem;
|
||||
use nym_test_utils::helpers::deterministic_rng;
|
||||
use nym_test_utils::mocks::async_read_write::mock_io_streams;
|
||||
use nym_test_utils::traits::{Timeboxed, TimeboxedSpawnable};
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Waker};
|
||||
use std::time::Duration;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::join;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::time::timeout;
|
||||
|
||||
fn mock_streams() -> (MockStream, MockStream) {
|
||||
let ch1 = Arc::new(Mutex::new(Default::default()));
|
||||
let ch2 = Arc::new(Mutex::new(Default::default()));
|
||||
|
||||
(
|
||||
MockStream {
|
||||
inner: MockStreamInner {
|
||||
tx: ch1.clone(),
|
||||
rx: ch2.clone(),
|
||||
},
|
||||
},
|
||||
MockStream {
|
||||
inner: MockStreamInner { tx: ch2, rx: ch1 },
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
struct MockStream {
|
||||
inner: MockStreamInner,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl MockStream {
|
||||
fn unchecked_tx_data(&self) -> Vec<u8> {
|
||||
self.inner.tx.try_lock().unwrap().data.clone()
|
||||
}
|
||||
|
||||
fn unchecked_rx_data(&self) -> Vec<u8> {
|
||||
self.inner.rx.try_lock().unwrap().data.clone()
|
||||
}
|
||||
}
|
||||
|
||||
struct MockStreamInner {
|
||||
tx: Arc<Mutex<DataWrapper>>,
|
||||
rx: Arc<Mutex<DataWrapper>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct DataWrapper {
|
||||
data: Vec<u8>,
|
||||
waker: Option<Waker>,
|
||||
}
|
||||
|
||||
impl AsyncRead for MockStream {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
let mut inner = self.inner.rx.try_lock().unwrap();
|
||||
let data = mem::take(&mut inner.data);
|
||||
if data.is_empty() {
|
||||
inner.waker = Some(cx.waker().clone());
|
||||
return Poll::Pending;
|
||||
}
|
||||
|
||||
if let Some(waker) = inner.waker.take() {
|
||||
waker.wake();
|
||||
}
|
||||
|
||||
buf.put_slice(&data);
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for MockStream {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<Result<usize, Error>> {
|
||||
let mut inner = self.inner.tx.try_lock().unwrap();
|
||||
let len = buf.len();
|
||||
|
||||
if !inner.data.is_empty() {
|
||||
assert!(inner.waker.is_none());
|
||||
inner.waker = Some(cx.waker().clone());
|
||||
return Poll::Pending;
|
||||
}
|
||||
|
||||
inner.data.extend_from_slice(buf);
|
||||
if let Some(waker) = inner.waker.take() {
|
||||
waker.wake();
|
||||
}
|
||||
Poll::Ready(Ok(len))
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn noise_handshake() -> anyhow::Result<()> {
|
||||
let dummy_seed = [42u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
let mut rng = deterministic_rng();
|
||||
|
||||
let initiator_keys = Arc::new(x25519::KeyPair::new(&mut rng));
|
||||
let responder_keys = Arc::new(x25519::KeyPair::new(&mut rng));
|
||||
|
||||
let (initiator_stream, responder_stream) = mock_streams();
|
||||
let (initiator_stream, responder_stream) = mock_io_streams();
|
||||
|
||||
let psk = generate_psk(*responder_keys.public_key(), NoiseVersion::V1)?;
|
||||
let pattern = NoisePattern::default();
|
||||
@@ -547,14 +446,8 @@ mod tests {
|
||||
*responder_keys.public_key(),
|
||||
);
|
||||
|
||||
let initiator_fut =
|
||||
tokio::spawn(
|
||||
async move { timeout(Duration::from_millis(200), stream_initiator).await },
|
||||
);
|
||||
let responder_fut =
|
||||
tokio::spawn(
|
||||
async move { timeout(Duration::from_millis(200), stream_responder).await },
|
||||
);
|
||||
let initiator_fut = stream_initiator.spawn_timeboxed();
|
||||
let responder_fut = stream_responder.spawn_timeboxed();
|
||||
|
||||
let (initiator, responder) = join!(initiator_fut, responder_fut);
|
||||
|
||||
@@ -563,14 +456,13 @@ mod tests {
|
||||
|
||||
let msg = b"hello there";
|
||||
// if noise was successful we should be able to write a proper message across
|
||||
timeout(Duration::from_millis(200), initiator.write_all(msg)).await??;
|
||||
|
||||
initiator.write_all(msg).timeboxed().await??;
|
||||
initiator.inner_stream.flush().await?;
|
||||
|
||||
let inner_buf = initiator.inner_stream.get_ref().unchecked_tx_data();
|
||||
|
||||
let mut buf = [0u8; 11];
|
||||
timeout(Duration::from_millis(200), responder.read(&mut buf)).await??;
|
||||
responder.read(&mut buf).timeboxed().await??;
|
||||
|
||||
assert_eq!(&buf[..], msg);
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ time = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
si-scale = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
strum_macros = { workspace = true }
|
||||
|
||||
nym-crypto = { path = "../crypto" }
|
||||
nym-sphinx = { path = "../nymsphinx" }
|
||||
|
||||
@@ -7,8 +7,8 @@ use serde::{Deserialize, Serialize};
|
||||
PartialEq,
|
||||
Copy,
|
||||
Clone,
|
||||
strum::Display,
|
||||
strum::EnumString,
|
||||
strum_macros::Display,
|
||||
strum_macros::EnumString,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Default,
|
||||
|
||||
@@ -10,6 +10,7 @@ where
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[track_caller]
|
||||
pub fn spawn<F>(future: F)
|
||||
where
|
||||
F: Future + Send + 'static,
|
||||
@@ -18,6 +19,7 @@ where
|
||||
tokio::spawn(future);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn spawn_with_report_error<F, T, E>(future: F, mut shutdown: TaskClient)
|
||||
where
|
||||
F: Future<Output = Result<T, E>> + Send + 'static,
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "nym-test-utils"
|
||||
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]
|
||||
anyhow = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
rand_chacha = { workspace = true }
|
||||
tokio = { workspace = true, features = ["sync", "time", "rt"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::traits::Timeboxed;
|
||||
use rand_chacha::rand_core::SeedableRng;
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
use std::future::Future;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio::time::error::Elapsed;
|
||||
|
||||
pub fn leak<T>(val: T) -> &'static mut T {
|
||||
Box::leak(Box::new(val))
|
||||
}
|
||||
|
||||
pub fn spawn_timeboxed<F>(fut: F) -> JoinHandle<Result<F::Output, Elapsed>>
|
||||
where
|
||||
F: Future + Send + 'static,
|
||||
<F as Future>::Output: Send,
|
||||
{
|
||||
tokio::spawn(async move { fut.timeboxed().await })
|
||||
}
|
||||
|
||||
pub fn deterministic_rng() -> ChaCha20Rng {
|
||||
seeded_rng([42u8; 32])
|
||||
}
|
||||
|
||||
pub fn seeded_rng(seed: [u8; 32]) -> ChaCha20Rng {
|
||||
ChaCha20Rng::from_seed(seed)
|
||||
}
|
||||
|
||||
pub fn u64_seeded_rng(seed: u64) -> ChaCha20Rng {
|
||||
ChaCha20Rng::seed_from_u64(seed)
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod helpers;
|
||||
pub mod mocks;
|
||||
pub mod traits;
|
||||
@@ -0,0 +1,161 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::mocks::shared::InnerWrapper;
|
||||
use futures::ready;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
|
||||
// sending buffer of the first stream is the receiving buffer of the second stream
|
||||
// and vice versa
|
||||
pub fn mock_io_streams() -> (MockIOStream, MockIOStream) {
|
||||
let ch1 = MockIOStream::default();
|
||||
let ch2 = ch1.make_connection();
|
||||
|
||||
(ch1, ch2)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MockIOStream {
|
||||
// messages to send
|
||||
tx: InnerWrapper<Vec<u8>>,
|
||||
|
||||
// messages to receive
|
||||
rx: InnerWrapper<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl MockIOStream {
|
||||
fn make_connection(&self) -> Self {
|
||||
MockIOStream {
|
||||
tx: self.rx.cloned_buffer(),
|
||||
rx: self.tx.cloned_buffer(),
|
||||
}
|
||||
}
|
||||
|
||||
// unwrap in test code is fine
|
||||
#[allow(clippy::unwrap_used)]
|
||||
pub fn unchecked_tx_data(&self) -> Vec<u8> {
|
||||
self.tx.buffer.try_lock().unwrap().content.clone()
|
||||
}
|
||||
|
||||
// unwrap in test code is fine
|
||||
#[allow(clippy::unwrap_used)]
|
||||
pub fn unchecked_rx_data(&self) -> Vec<u8> {
|
||||
self.rx.buffer.try_lock().unwrap().content.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for MockIOStream {
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
ready!(Pin::new(&mut self.rx).poll_guard_ready(cx));
|
||||
|
||||
// SAFETY: guard is ready
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let guard = self.rx.guard().unwrap();
|
||||
|
||||
let data = guard.take_content();
|
||||
if data.is_empty() {
|
||||
// nothing to retrieve - store the waiter so that the sender could trigger it
|
||||
guard.waker = Some(cx.waker().clone());
|
||||
|
||||
// drop the guard so that the sender could actually put messages in
|
||||
self.rx.transition_to_idle();
|
||||
return Poll::Pending;
|
||||
}
|
||||
|
||||
// if let Some(waker) = guard.waker.take() {
|
||||
// waker.wake();
|
||||
// }
|
||||
|
||||
self.rx.transition_to_idle();
|
||||
|
||||
buf.put_slice(&data);
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for MockIOStream {
|
||||
fn poll_write(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
// wait until we transition to the locked state
|
||||
ready!(Pin::new(&mut self.tx).poll_guard_ready(cx));
|
||||
|
||||
// SAFETY: guard is ready
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let guard = self.tx.guard().unwrap();
|
||||
|
||||
let len = buf.len();
|
||||
guard.content.extend_from_slice(buf);
|
||||
|
||||
// TODO: if we wanted the behaviour of always reading everything before writing anything extra
|
||||
// if !guard.content.is_empty() {
|
||||
// // sanity check
|
||||
// assert!(guard.waker.is_none());
|
||||
// guard.waker = Some(cx.waker().clone());
|
||||
// self.tx.transition_to_idle();
|
||||
// return Poll::Pending;
|
||||
// }
|
||||
|
||||
Poll::Ready(Ok(len))
|
||||
}
|
||||
|
||||
fn poll_flush(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
let Some(guard) = self.tx.guard() else {
|
||||
return Poll::Ready(Err(io::Error::other(
|
||||
"invalid lock state to send/flush messages",
|
||||
)));
|
||||
};
|
||||
|
||||
if let Some(waker) = guard.waker.take() {
|
||||
// notify the receiver if it was waiting for messages
|
||||
waker.wake();
|
||||
}
|
||||
|
||||
// release the guard
|
||||
self.tx.transition_to_idle();
|
||||
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn poll_shutdown(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
// make sure our guard is always dropped on close
|
||||
self.tx.transition_to_idle();
|
||||
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
|
||||
#[tokio::test]
|
||||
async fn basic() {
|
||||
let (mut stream1, mut stream2) = mock_io_streams();
|
||||
stream1.write_all(&[1, 2, 3, 4, 5]).await.unwrap();
|
||||
stream1.flush().await.unwrap();
|
||||
|
||||
let mut buf = [0u8; 5];
|
||||
let read = stream2.read(&mut buf).await.unwrap();
|
||||
assert_eq!(read, 5);
|
||||
assert_eq!(&buf[0..5], &[1, 2, 3, 4, 5]);
|
||||
|
||||
let mut buf = [0u8; 5];
|
||||
stream2.write_all(&[6, 7, 8, 9, 10]).await.unwrap();
|
||||
stream2.flush().await.unwrap();
|
||||
|
||||
let read = stream1.read(&mut buf).await.unwrap();
|
||||
assert_eq!(read, 5);
|
||||
assert_eq!(&buf[0..5], &[6, 7, 8, 9, 10]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod async_read_write;
|
||||
mod shared;
|
||||
pub mod stream_sink;
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use futures::future::BoxFuture;
|
||||
use futures::{ready, FutureExt};
|
||||
use std::mem;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll, Waker};
|
||||
use tokio::sync::{Mutex, OwnedMutexGuard};
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct InnerWrapper<T: 'static> {
|
||||
pub(crate) buffer: Arc<Mutex<ContentWrapper<T>>>,
|
||||
lock_state: LockState<T>,
|
||||
}
|
||||
|
||||
impl<T: Send> InnerWrapper<T> {
|
||||
pub(crate) fn clone_buffer(&self) -> Arc<Mutex<ContentWrapper<T>>> {
|
||||
Arc::clone(&self.buffer)
|
||||
}
|
||||
|
||||
pub(crate) fn cloned_buffer(&self) -> Self {
|
||||
assert!(matches!(self.lock_state, LockState::Idle));
|
||||
InnerWrapper {
|
||||
buffer: self.clone_buffer(),
|
||||
lock_state: LockState::Idle,
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: it's responsibility of the caller to ensure the guard is released and state transitions to idle!
|
||||
pub(crate) fn poll_guard_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
|
||||
match &mut self.lock_state {
|
||||
LockState::Idle => {
|
||||
// 1. first try to obtain the guard without locking
|
||||
let Ok(guard) = self.buffer.clone().try_lock_owned() else {
|
||||
// 2. if that fails, create the future for obtaining it
|
||||
self.lock_state =
|
||||
LockState::TryingToLock(self.buffer.clone().lock_owned().boxed());
|
||||
return Poll::Pending;
|
||||
};
|
||||
|
||||
// correctly transition to locked state and poll ourselves again
|
||||
self.lock_state = LockState::Locked(guard);
|
||||
cx.waker().wake_by_ref();
|
||||
Poll::Ready(())
|
||||
}
|
||||
|
||||
LockState::TryingToLock(lock_fut) => {
|
||||
// see if the guard future has resolved, if so, transition to locked state and schedule for another poll
|
||||
let guard = ready!(lock_fut.as_mut().poll(cx));
|
||||
self.lock_state = LockState::Locked(guard);
|
||||
cx.waker().wake_by_ref();
|
||||
Poll::Pending
|
||||
}
|
||||
|
||||
LockState::Locked(_) => Poll::Ready(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn guard(&mut self) -> Option<&mut OwnedMutexGuard<ContentWrapper<T>>> {
|
||||
match &mut self.lock_state {
|
||||
LockState::Locked(guard) => Some(guard),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn transition_to_idle(&mut self) {
|
||||
self.lock_state = LockState::Idle
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) enum LockState<T> {
|
||||
// We haven’t started locking yet
|
||||
#[default]
|
||||
Idle,
|
||||
|
||||
// Waiting for the mutex lock future to resolve
|
||||
TryingToLock(BoxFuture<'static, OwnedMutexGuard<ContentWrapper<T>>>),
|
||||
|
||||
// We hold the mutex guard
|
||||
Locked(OwnedMutexGuard<ContentWrapper<T>>),
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ContentWrapper<T> {
|
||||
pub(crate) content: T,
|
||||
pub(crate) waker: Option<Waker>,
|
||||
}
|
||||
|
||||
impl<T> ContentWrapper<T> {
|
||||
pub fn into_content(self) -> T {
|
||||
self.content
|
||||
}
|
||||
|
||||
pub fn content(&self) -> &T {
|
||||
&self.content
|
||||
}
|
||||
|
||||
pub(crate) fn take_content(&mut self) -> T
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
mem::take(&mut self.content)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> LockState<T> {}
|
||||
@@ -0,0 +1,181 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::mocks::shared::{ContentWrapper, InnerWrapper};
|
||||
use anyhow::{anyhow, bail};
|
||||
use futures::{ready, Sink, Stream};
|
||||
use std::collections::VecDeque;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
// sending buffer of the first stream is the receiving buffer of the second stream
|
||||
// and vice versa
|
||||
pub fn mock_streams<T>() -> (MockStream<T>, MockStream<T>)
|
||||
where
|
||||
T: Send,
|
||||
{
|
||||
let ch1 = MockStream::default();
|
||||
let ch2 = ch1.make_connection();
|
||||
|
||||
(ch1, ch2)
|
||||
}
|
||||
|
||||
pub struct MockStream<T: 'static> {
|
||||
// messages to send
|
||||
tx: InnerWrapper<VecDeque<T>>,
|
||||
|
||||
// messages to receive
|
||||
rx: InnerWrapper<VecDeque<T>>,
|
||||
}
|
||||
|
||||
impl<T> MockStream<T> {
|
||||
pub fn clone_tx_buffer(&self) -> Arc<Mutex<ContentWrapper<VecDeque<T>>>>
|
||||
where
|
||||
T: Send,
|
||||
{
|
||||
self.tx.clone_buffer()
|
||||
}
|
||||
|
||||
pub fn clone_rx_buffer(&self) -> Arc<Mutex<ContentWrapper<VecDeque<T>>>>
|
||||
where
|
||||
T: Send,
|
||||
{
|
||||
self.rx.clone_buffer()
|
||||
}
|
||||
|
||||
fn make_connection(&self) -> Self
|
||||
where
|
||||
T: Send,
|
||||
{
|
||||
MockStream {
|
||||
tx: self.rx.cloned_buffer(),
|
||||
rx: self.tx.cloned_buffer(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for MockStream<T> {
|
||||
fn default() -> Self {
|
||||
MockStream {
|
||||
tx: InnerWrapper::default(),
|
||||
rx: InnerWrapper::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Stream for MockStream<T>
|
||||
where
|
||||
T: Send,
|
||||
{
|
||||
type Item = T;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
ready!(Pin::new(&mut self.rx).poll_guard_ready(cx));
|
||||
|
||||
// SAFETY: guard is ready
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let guard = self.rx.guard().unwrap();
|
||||
|
||||
let Some(next) = guard.content.pop_front() else {
|
||||
// nothing to retrieve - store the waiter so that the sender could trigger it
|
||||
guard.waker = Some(cx.waker().clone());
|
||||
|
||||
// drop the guard so that the sender could actually put messages in
|
||||
self.rx.transition_to_idle();
|
||||
return Poll::Pending;
|
||||
};
|
||||
|
||||
// there are more messages buffered waiting for us to retrieve
|
||||
// keep the guard!
|
||||
if !guard.content.is_empty() {
|
||||
cx.waker().wake_by_ref();
|
||||
} else {
|
||||
// no more messages, drop the guard
|
||||
self.rx.transition_to_idle();
|
||||
}
|
||||
|
||||
Poll::Ready(Some(next))
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
// that's just a minor optimisation, so don't sweat about it too much,
|
||||
// if we can obtain the mutex, give precise information, otherwise return default values
|
||||
let Ok(guard) = self.rx.buffer.try_lock() else {
|
||||
return (0, None);
|
||||
};
|
||||
let items = guard.content.len();
|
||||
(items, Some(items))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Sink<T> for MockStream<T>
|
||||
where
|
||||
T: Send,
|
||||
{
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
// wait until we transition to the locked state
|
||||
ready!(Pin::new(&mut self.tx).poll_guard_ready(cx));
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn start_send(mut self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> {
|
||||
let Some(guard) = self.tx.guard() else {
|
||||
bail!("invalid lock state to send messages");
|
||||
};
|
||||
guard.content.push_back(item);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn poll_flush(
|
||||
mut self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<Result<(), Self::Error>> {
|
||||
let Some(guard) = self.tx.guard() else {
|
||||
return Poll::Ready(Err(anyhow!("invalid lock state to send/flush messages")));
|
||||
};
|
||||
|
||||
if let Some(waker) = guard.waker.take() {
|
||||
// notify the receiver if it was waiting for messages
|
||||
waker.wake();
|
||||
}
|
||||
|
||||
// release the guard
|
||||
self.tx.transition_to_idle();
|
||||
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn poll_close(
|
||||
mut self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<Result<(), Self::Error>> {
|
||||
// make sure our guard is always dropped on close
|
||||
self.tx.transition_to_idle();
|
||||
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
|
||||
#[tokio::test]
|
||||
async fn basic() {
|
||||
let (mut stream1, mut stream2) = mock_streams();
|
||||
stream1.send("foomp").await.unwrap();
|
||||
|
||||
let received = stream2.next().await.unwrap();
|
||||
assert_eq!(received, "foomp");
|
||||
|
||||
stream2.send("bar").await.unwrap();
|
||||
let received = stream1.next().await.unwrap();
|
||||
assert_eq!(received, "bar");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::helpers::{leak, spawn_timeboxed};
|
||||
use std::future::{Future, IntoFuture};
|
||||
use std::time::Duration;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio::time::error::Elapsed;
|
||||
|
||||
// a helper trait for use in tests to easily convert `T` into `&'static mut T`
|
||||
pub trait Leak {
|
||||
fn leak(self) -> &'static mut Self;
|
||||
}
|
||||
|
||||
impl<T> Leak for T {
|
||||
fn leak(self) -> &'static mut T {
|
||||
leak(self)
|
||||
}
|
||||
}
|
||||
|
||||
// those are internal testing traits so we're not concerned about auto traits
|
||||
#[allow(async_fn_in_trait)]
|
||||
pub trait Timeboxed: IntoFuture + Sized {
|
||||
async fn timeboxed(self) -> Result<Self::Output, Elapsed> {
|
||||
self.execute_with_deadline(Duration::from_millis(200)).await
|
||||
}
|
||||
|
||||
async fn execute_with_deadline(self, timeout: Duration) -> Result<Self::Output, Elapsed> {
|
||||
tokio::time::timeout(timeout, self).await
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Timeboxed for T where T: IntoFuture + Sized {}
|
||||
|
||||
// those are internal testing traits so we're not concerned about auto traits
|
||||
#[allow(async_fn_in_trait)]
|
||||
pub trait Spawnable: Future + Sized + Send + 'static {
|
||||
fn spawn(self) -> JoinHandle<Self::Output>
|
||||
where
|
||||
<Self as Future>::Output: Send + 'static,
|
||||
{
|
||||
tokio::spawn(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Spawnable for T where T: Future + Sized + Send + 'static {}
|
||||
|
||||
pub trait TimeboxedSpawnable: Timeboxed + Spawnable {
|
||||
fn spawn_timeboxed(self) -> JoinHandle<Result<<Self as Future>::Output, Elapsed>>
|
||||
where
|
||||
<Self as Future>::Output: Send,
|
||||
{
|
||||
spawn_timeboxed(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TimeboxedSpawnable for T where T: Spawnable + Future + Send {}
|
||||
@@ -19,6 +19,7 @@ serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
strum = { workspace = true, features = ["derive"] }
|
||||
strum_macros = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
ts-rs = { workspace = true }
|
||||
url = { workspace = true }
|
||||
|
||||
@@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use strum::{Display, EnumString, VariantNames};
|
||||
use strum_macros::{Display, EnumString, VariantNames};
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
|
||||
@@ -32,9 +32,11 @@ time = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
nym-authenticator-requests = { path = "../authenticator-requests" }
|
||||
nym-credentials-interface = { path = "../credentials-interface" }
|
||||
nym-credential-verification = { path = "../credential-verification" }
|
||||
nym-crypto = { path = "../crypto", features = ["asymmetric"] }
|
||||
nym-gateway-storage = { path = "../gateway-storage" }
|
||||
nym-gateway-requests = { path = "../gateway-requests" }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
nym-task = { path = "../task" }
|
||||
nym-wireguard-types = { path = "../wireguard-types" }
|
||||
@@ -46,4 +48,3 @@ nym-gateway-storage = { path = "../gateway-storage", features = ["mock"] }
|
||||
[features]
|
||||
default = []
|
||||
mock = ["nym-gateway-storage/mock"]
|
||||
|
||||
|
||||
@@ -21,3 +21,5 @@ pub enum Error {
|
||||
#[error("{0}")]
|
||||
SystemTime(#[from] std::time::SystemTimeError),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
// #![warn(clippy::unwrap_used)]
|
||||
|
||||
use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask, WGApi, WireguardInterfaceApi};
|
||||
use nym_crypto::asymmetric::x25519::KeyPair;
|
||||
#[cfg(target_os = "linux")]
|
||||
use nym_gateway_storage::GatewayStorage;
|
||||
use nym_credential_verification::ecash::EcashManager;
|
||||
use nym_crypto::asymmetric::x25519::KeyPair;
|
||||
use nym_wireguard_types::Config;
|
||||
use peer_controller::PeerControlRequest;
|
||||
use std::sync::Arc;
|
||||
@@ -158,7 +158,7 @@ pub struct WireguardData {
|
||||
/// Start wireguard device
|
||||
#[cfg(target_os = "linux")]
|
||||
pub async fn start_wireguard(
|
||||
storage: GatewayStorage,
|
||||
ecash_manager: Arc<EcashManager>,
|
||||
metrics: nym_node_metrics::NymNodeMetrics,
|
||||
peers: Vec<Peer>,
|
||||
task_client: nym_task::TaskClient,
|
||||
@@ -167,6 +167,7 @@ pub async fn start_wireguard(
|
||||
use base64::{prelude::BASE64_STANDARD, Engine};
|
||||
use defguard_wireguard_rs::{InterfaceConfiguration, WireguardInterfaceApi};
|
||||
use ip_network::IpNetwork;
|
||||
use nym_credential_verification::ecash::traits::EcashManager;
|
||||
use peer_controller::PeerController;
|
||||
use std::collections::HashMap;
|
||||
use tokio::sync::RwLock;
|
||||
@@ -178,7 +179,7 @@ pub async fn start_wireguard(
|
||||
|
||||
for peer in peers.iter() {
|
||||
let bandwidth_manager = Arc::new(RwLock::new(
|
||||
PeerController::generate_bandwidth_manager(Box::new(storage.clone()), &peer.public_key)
|
||||
PeerController::generate_bandwidth_manager(ecash_manager.storage(), &peer.public_key)
|
||||
.await?,
|
||||
));
|
||||
peer_bandwidth_managers.insert(peer.public_key.clone(), (bandwidth_manager, peer.clone()));
|
||||
@@ -233,7 +234,7 @@ pub async fn start_wireguard(
|
||||
let host = wg_api.read_interface_data()?;
|
||||
let wg_api = std::sync::Arc::new(WgApiWrapper::new(wg_api));
|
||||
let mut controller = PeerController::new(
|
||||
Box::new(storage),
|
||||
ecash_manager,
|
||||
metrics,
|
||||
wg_api.clone(),
|
||||
host,
|
||||
|
||||
@@ -9,9 +9,11 @@ use defguard_wireguard_rs::{
|
||||
use futures::channel::oneshot;
|
||||
use log::info;
|
||||
use nym_credential_verification::{
|
||||
bandwidth_storage_manager::BandwidthStorageManager, BandwidthFlushingBehaviourConfig,
|
||||
ClientBandwidth,
|
||||
bandwidth_storage_manager::BandwidthStorageManager, ecash::traits::EcashManager,
|
||||
BandwidthFlushingBehaviourConfig, ClientBandwidth, CredentialVerifier,
|
||||
};
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_gateway_requests::models::CredentialSpendingRequest;
|
||||
use nym_gateway_storage::traits::BandwidthGatewayStorage;
|
||||
use nym_node_metrics::NymNodeMetrics;
|
||||
use nym_wireguard_types::DEFAULT_PEER_TIMEOUT_CHECK;
|
||||
@@ -20,7 +22,10 @@ use std::{collections::HashMap, sync::Arc};
|
||||
use tokio::sync::{mpsc, RwLock};
|
||||
use tokio_stream::{wrappers::IntervalStream, StreamExt};
|
||||
|
||||
use crate::{error::Error, peer_handle::SharedBandwidthStorageManager};
|
||||
use crate::{
|
||||
error::{Error, Result},
|
||||
peer_handle::SharedBandwidthStorageManager,
|
||||
};
|
||||
use crate::{peer_handle::PeerHandle, peer_storage_manager::CachedPeerManager};
|
||||
|
||||
pub enum PeerControlRequest {
|
||||
@@ -40,27 +45,21 @@ pub enum PeerControlRequest {
|
||||
key: Key,
|
||||
response_tx: oneshot::Sender<GetClientBandwidthControlResponse>,
|
||||
},
|
||||
GetVerifier {
|
||||
key: Key,
|
||||
credential: Box<CredentialSpendingData>,
|
||||
response_tx: oneshot::Sender<QueryVerifierControlResponse>,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct AddPeerControlResponse {
|
||||
pub success: bool,
|
||||
}
|
||||
|
||||
pub struct RemovePeerControlResponse {
|
||||
pub success: bool,
|
||||
}
|
||||
|
||||
pub struct QueryPeerControlResponse {
|
||||
pub success: bool,
|
||||
pub peer: Option<Peer>,
|
||||
}
|
||||
|
||||
pub struct GetClientBandwidthControlResponse {
|
||||
pub client_bandwidth: Option<ClientBandwidth>,
|
||||
}
|
||||
pub type AddPeerControlResponse = Result<()>;
|
||||
pub type RemovePeerControlResponse = Result<()>;
|
||||
pub type QueryPeerControlResponse = Result<Option<Peer>>;
|
||||
pub type GetClientBandwidthControlResponse = Result<ClientBandwidth>;
|
||||
pub type QueryVerifierControlResponse = Result<CredentialVerifier>;
|
||||
|
||||
pub struct PeerController {
|
||||
storage: Box<dyn BandwidthGatewayStorage + Send + Sync>,
|
||||
ecash_verifier: Arc<dyn EcashManager + Send + Sync>,
|
||||
|
||||
// we have "all" metrics of a node, but they're behind a single Arc pointer,
|
||||
// so the overhead is minimal
|
||||
@@ -79,7 +78,7 @@ pub struct PeerController {
|
||||
impl PeerController {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
storage: Box<dyn BandwidthGatewayStorage + Send + Sync>,
|
||||
ecash_verifier: Arc<dyn EcashManager + Send + Sync>,
|
||||
metrics: NymNodeMetrics,
|
||||
wg_api: Arc<dyn WireguardInterfaceApi + Send + Sync>,
|
||||
initial_host_information: Host,
|
||||
@@ -114,7 +113,7 @@ impl PeerController {
|
||||
.collect();
|
||||
|
||||
PeerController {
|
||||
storage,
|
||||
ecash_verifier,
|
||||
wg_api,
|
||||
host_information,
|
||||
bw_storage_managers,
|
||||
@@ -127,8 +126,11 @@ impl PeerController {
|
||||
}
|
||||
|
||||
// Function that should be used for peer removal, to handle both storage and kernel interaction
|
||||
pub async fn remove_peer(&mut self, key: &Key) -> Result<(), Error> {
|
||||
self.storage.remove_wireguard_peer(&key.to_string()).await?;
|
||||
pub async fn remove_peer(&mut self, key: &Key) -> Result<()> {
|
||||
self.ecash_verifier
|
||||
.storage()
|
||||
.remove_wireguard_peer(&key.to_string())
|
||||
.await?;
|
||||
self.bw_storage_managers.remove(key);
|
||||
let ret = self.wg_api.remove_peer(key);
|
||||
if ret.is_err() {
|
||||
@@ -140,7 +142,7 @@ impl PeerController {
|
||||
pub async fn generate_bandwidth_manager(
|
||||
storage: Box<dyn BandwidthGatewayStorage + Send + Sync>,
|
||||
public_key: &Key,
|
||||
) -> Result<BandwidthStorageManager, Error> {
|
||||
) -> Result<BandwidthStorageManager> {
|
||||
let client_id = storage
|
||||
.get_wireguard_peer(&public_key.to_string())
|
||||
.await?
|
||||
@@ -161,14 +163,11 @@ impl PeerController {
|
||||
))
|
||||
}
|
||||
|
||||
async fn handle_add_request(&mut self, peer: &Peer) -> Result<(), Error> {
|
||||
async fn handle_add_request(&mut self, peer: &Peer) -> Result<()> {
|
||||
self.wg_api.configure_peer(peer)?;
|
||||
let bandwidth_storage_manager = Arc::new(RwLock::new(
|
||||
Self::generate_bandwidth_manager(
|
||||
dyn_clone::clone_box(&*self.storage),
|
||||
&peer.public_key,
|
||||
)
|
||||
.await?,
|
||||
Self::generate_bandwidth_manager(self.ecash_verifier.storage(), &peer.public_key)
|
||||
.await?,
|
||||
));
|
||||
let cached_peer_manager = CachedPeerManager::new(peer);
|
||||
let mut handle = PeerHandle::new(
|
||||
@@ -193,21 +192,52 @@ impl PeerController {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_query_peer(&self, key: &Key) -> Result<Option<Peer>, Error> {
|
||||
async fn handle_query_peer(&self, key: &Key) -> Result<Option<Peer>> {
|
||||
Ok(self
|
||||
.storage
|
||||
.ecash_verifier
|
||||
.storage()
|
||||
.get_wireguard_peer(&key.to_string())
|
||||
.await?
|
||||
.map(Peer::try_from)
|
||||
.transpose()?)
|
||||
}
|
||||
|
||||
async fn handle_get_client_bandwidth(&self, key: &Key) -> Option<ClientBandwidth> {
|
||||
if let Some(bandwidth_storage_manager) = self.bw_storage_managers.get(key) {
|
||||
Some(bandwidth_storage_manager.read().await.client_bandwidth())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
async fn handle_get_client_bandwidth(&self, key: &Key) -> Result<ClientBandwidth> {
|
||||
let bandwidth_storage_manager = self
|
||||
.bw_storage_managers
|
||||
.get(key)
|
||||
.ok_or(Error::MissingClientBandwidthEntry)?;
|
||||
|
||||
Ok(bandwidth_storage_manager.read().await.client_bandwidth())
|
||||
}
|
||||
|
||||
async fn handle_query_verifier(
|
||||
&self,
|
||||
key: &Key,
|
||||
credential: CredentialSpendingData,
|
||||
) -> Result<CredentialVerifier> {
|
||||
let storage = self.ecash_verifier.storage();
|
||||
let client_id = storage
|
||||
.get_wireguard_peer(&key.to_string())
|
||||
.await?
|
||||
.ok_or(Error::MissingClientBandwidthEntry)?
|
||||
.client_id;
|
||||
let Some(bandwidth_storage_manager) = self.bw_storage_managers.get(key) else {
|
||||
return Err(Error::MissingClientBandwidthEntry);
|
||||
};
|
||||
let client_bandwidth = bandwidth_storage_manager.read().await.client_bandwidth();
|
||||
let verifier = CredentialVerifier::new(
|
||||
CredentialSpendingRequest::new(credential),
|
||||
self.ecash_verifier.clone(),
|
||||
BandwidthStorageManager::new(
|
||||
storage,
|
||||
client_bandwidth,
|
||||
client_id,
|
||||
BandwidthFlushingBehaviourConfig::default(),
|
||||
true,
|
||||
),
|
||||
);
|
||||
Ok(verifier)
|
||||
}
|
||||
|
||||
async fn update_metrics(&self, new_host: &Host) {
|
||||
@@ -304,28 +334,19 @@ impl PeerController {
|
||||
msg = self.request_rx.recv() => {
|
||||
match msg {
|
||||
Some(PeerControlRequest::AddPeer { peer, response_tx }) => {
|
||||
let ret = self.handle_add_request(&peer).await;
|
||||
if ret.is_ok() {
|
||||
response_tx.send(AddPeerControlResponse { success: true }).ok();
|
||||
} else {
|
||||
response_tx.send(AddPeerControlResponse { success: false }).ok();
|
||||
}
|
||||
response_tx.send(self.handle_add_request(&peer).await).ok();
|
||||
}
|
||||
Some(PeerControlRequest::RemovePeer { key, response_tx }) => {
|
||||
let success = self.remove_peer(&key).await.is_ok();
|
||||
response_tx.send(RemovePeerControlResponse { success }).ok();
|
||||
response_tx.send(self.remove_peer(&key).await).ok();
|
||||
}
|
||||
Some(PeerControlRequest::QueryPeer { key, response_tx }) => {
|
||||
let ret = self.handle_query_peer(&key).await;
|
||||
if let Ok(peer) = ret {
|
||||
response_tx.send(QueryPeerControlResponse { success: true, peer }).ok();
|
||||
} else {
|
||||
response_tx.send(QueryPeerControlResponse { success: false, peer: None }).ok();
|
||||
}
|
||||
response_tx.send(self.handle_query_peer(&key).await).ok();
|
||||
}
|
||||
Some(PeerControlRequest::GetClientBandwidth { key, response_tx }) => {
|
||||
let client_bandwidth = self.handle_get_client_bandwidth(&key).await;
|
||||
response_tx.send(GetClientBandwidthControlResponse { client_bandwidth }).ok();
|
||||
response_tx.send(self.handle_get_client_bandwidth(&key).await).ok();
|
||||
}
|
||||
Some(PeerControlRequest::GetVerifier { key, credential, response_tx }) => {
|
||||
response_tx.send(self.handle_query_verifier(&key, *credential).await).ok();
|
||||
}
|
||||
None => {
|
||||
log::trace!("PeerController [main loop]: stopping since channel closed");
|
||||
@@ -349,21 +370,21 @@ struct MockWgApi {
|
||||
impl WireguardInterfaceApi for MockWgApi {
|
||||
fn create_interface(
|
||||
&self,
|
||||
) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> {
|
||||
) -> std::result::Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn assign_address(
|
||||
&self,
|
||||
_address: &defguard_wireguard_rs::net::IpAddrMask,
|
||||
) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> {
|
||||
) -> std::result::Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn configure_peer_routing(
|
||||
&self,
|
||||
_peers: &[Peer],
|
||||
) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> {
|
||||
) -> std::result::Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
@@ -371,7 +392,7 @@ impl WireguardInterfaceApi for MockWgApi {
|
||||
fn configure_interface(
|
||||
&self,
|
||||
_config: &defguard_wireguard_rs::InterfaceConfiguration,
|
||||
) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> {
|
||||
) -> std::result::Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
@@ -380,20 +401,20 @@ impl WireguardInterfaceApi for MockWgApi {
|
||||
&self,
|
||||
_config: &defguard_wireguard_rs::InterfaceConfiguration,
|
||||
_dns: &[std::net::IpAddr],
|
||||
) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> {
|
||||
) -> std::result::Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn remove_interface(
|
||||
&self,
|
||||
) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> {
|
||||
) -> std::result::Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn configure_peer(
|
||||
&self,
|
||||
peer: &Peer,
|
||||
) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> {
|
||||
) -> std::result::Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> {
|
||||
self.peers
|
||||
.write()
|
||||
.unwrap()
|
||||
@@ -404,14 +425,14 @@ impl WireguardInterfaceApi for MockWgApi {
|
||||
fn remove_peer(
|
||||
&self,
|
||||
peer_pubkey: &Key,
|
||||
) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> {
|
||||
) -> std::result::Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> {
|
||||
self.peers.write().unwrap().remove(peer_pubkey);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_interface_data(
|
||||
&self,
|
||||
) -> Result<Host, defguard_wireguard_rs::error::WireguardInterfaceError> {
|
||||
) -> std::result::Result<Host, defguard_wireguard_rs::error::WireguardInterfaceError> {
|
||||
let mut host = Host::default();
|
||||
host.peers = self.peers.read().unwrap().clone();
|
||||
Ok(host)
|
||||
@@ -420,7 +441,7 @@ impl WireguardInterfaceApi for MockWgApi {
|
||||
fn configure_dns(
|
||||
&self,
|
||||
_dns: &[std::net::IpAddr],
|
||||
) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> {
|
||||
) -> std::result::Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
@@ -433,13 +454,18 @@ pub fn start_controller(
|
||||
Arc<RwLock<nym_gateway_storage::traits::mock::MockGatewayStorage>>,
|
||||
nym_task::TaskManager,
|
||||
) {
|
||||
use std::sync::Arc;
|
||||
|
||||
let storage = Arc::new(RwLock::new(
|
||||
nym_gateway_storage::traits::mock::MockGatewayStorage::default(),
|
||||
));
|
||||
let ecash_manager = Arc::new(nym_credential_verification::ecash::MockEcashManager::new(
|
||||
Box::new(storage.clone()),
|
||||
));
|
||||
let wg_api = Arc::new(MockWgApi::default());
|
||||
let task_manager = nym_task::TaskManager::default();
|
||||
let mut peer_controller = PeerController::new(
|
||||
Box::new(storage.clone()),
|
||||
ecash_manager,
|
||||
Default::default(),
|
||||
wg_api,
|
||||
Default::default(),
|
||||
|
||||
@@ -62,7 +62,8 @@ impl PeerHandle {
|
||||
let success = response_rx
|
||||
.await
|
||||
.map_err(|_| Error::Internal("peer controller didn't respond".to_string()))?
|
||||
.success;
|
||||
.inspect_err(|err| tracing::error!("Could not remove peer: {err:?}"))
|
||||
.is_ok();
|
||||
Ok(success)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
},
|
||||
"mixmining_reserve": {
|
||||
"denom": "unym",
|
||||
"amount": "185782781887190"
|
||||
"amount": "184339094131786"
|
||||
},
|
||||
"vesting_tokens": {
|
||||
"denom": "unym",
|
||||
@@ -13,6 +13,6 @@
|
||||
},
|
||||
"circulating_supply": {
|
||||
"denom": "unym",
|
||||
"amount": "814217218112810"
|
||||
"amount": "815660905868214"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
814_217_218
|
||||
815_660_905
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
5_160
|
||||
5_120
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
0.73%
|
||||
0.74%
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
39.437
|
||||
38.479
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1_038_975
|
||||
1_040_817
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
249_354_023
|
||||
249_796_152
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
249_354_023
|
||||
249_796_152
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
| **Item** | **Description** | **Amount in NYM** |
|
||||
|:-------------------|:------------------------------------------------------|--------------------:|
|
||||
| Total Supply | Maximum amount of NYM token in existence | 1_000_000_000 |
|
||||
| Mixmining Reserve | Tokens releasing for operators rewards | 185_782_781 |
|
||||
| Mixmining Reserve | Tokens releasing for operators rewards | 184_339_094 |
|
||||
| Vesting Tokens | Tokens locked outside of cicrulation for future claim | 0 |
|
||||
| Circulating Supply | Amount of unlocked tokens | 814_217_218 |
|
||||
| Stake Saturation | Optimal size of node self-bond + delegation | 1_038_975 |
|
||||
| Circulating Supply | Amount of unlocked tokens | 815_660_905 |
|
||||
| Stake Saturation | Optimal size of node self-bond + delegation | 1_040_817 |
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"interval": {
|
||||
"reward_pool": "185782781887190.195531020579422623",
|
||||
"staking_supply": "249354023047048.00261862494755182",
|
||||
"reward_pool": "184339094131786.145886263466367605",
|
||||
"staking_supply": "249796152422140.492822331813424919",
|
||||
"staking_supply_scale_factor": "0.30625",
|
||||
"epoch_reward_budget": "5160632830.199727653639460539",
|
||||
"stake_saturation_point": "1038975096029.366677577603948132",
|
||||
"epoch_reward_budget": "5120530392.54961516350731851",
|
||||
"stake_saturation_point": "1040817301758.918720093049222603",
|
||||
"sybil_resistance": "0.3",
|
||||
"active_set_work_factor": "10",
|
||||
"interval_pool_emission": "0.02"
|
||||
|
||||
@@ -1 +1 @@
|
||||
Wednesday, July 30th 2025, 09:32:50 UTC
|
||||
Friday, August 22nd 2025, 10:15:08 UTC
|
||||
@@ -41,3 +41,4 @@
|
||||
[Dataclub](https://www.dataclub.eu/),"Latvia, Sweden, Netherlands",Yes,,,07/2027
|
||||
[Privex](https://www.privex.io/tor-exit-policy/),"USA, Germany, Sweden",Yes,Yes,,07/2025
|
||||
[Svea](https://svea.net/vps),Sweden,Yes,,,07/2025
|
||||
[Hostraha](https://hostraha.com),"Kenya and other African countries", "No, but advertised otherwise", "Yes, USDT TRC20", "Don't recommend. Unresponsive technical and billing support, never provided IPv6 even though advertised and paid for. When VPS cancelled, company still tried to bill the credit card on file multiple times.", 08/2025
|
||||
|
||||
|
@@ -39,6 +39,8 @@ export const LoadEndpointInfo = () => (
|
||||
</div>
|
||||
);
|
||||
|
||||
{/*
|
||||
|
||||
# Changelog
|
||||
|
||||
This page displays a full list of all the changes during our release cycle from `v2024.3-eclipse` onward. Operators can find here the newest updates together with links to relevant documentation. The list is sorted so that the newest changes appear first.
|
||||
@@ -47,7 +49,101 @@ This page displays a full list of all the changes during our release cycle from
|
||||
|
||||
<VarInfo />
|
||||
|
||||
## `v2025.15-gruyere`
|
||||
|
||||
- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.15-gruyere)
|
||||
- [`nym-node`](nodes/nym-node.mdx) version `1.17.0`
|
||||
|
||||
```sh
|
||||
nym-node
|
||||
Binary Name: nym-node
|
||||
Build Timestamp: 2025-08-20T10:37:05.965300480Z
|
||||
Build Version: 1.17.0
|
||||
Commit SHA: 40e1cbc7a9f518eafbb5649c383626b096dd167d
|
||||
Commit Date: 2025-08-20T12:34:04.000000000+02:00
|
||||
Commit Branch: HEAD
|
||||
rustc Version: 1.86.0
|
||||
rustc Channel: stable
|
||||
cargo Profile: release
|
||||
```
|
||||
|
||||
### Operators Updates & Tools
|
||||
|
||||
- [**NIP-3: Nym Exit Policy Update**](https://forum.nym.com/t/nip-3-nym-exit-policy-update/1462/2) resulted by operators [governance](https://governator.nym.com/proposal/prop-d0c0d398-43bd-4a6f-b008-1921b64ae4ed) is implemented.
|
||||
|
||||
- If you operate a node routing wireguard, please re-run [these steps](nodes/nym-node/configuration#wireguard-exit-policy-configuration) to update your wireguard exit policy through IP tables rules.
|
||||
|
||||
- [**NIP-2: Changing stake saturation**](https://governator.nym.com/proposal/prop-8dfa4a28-55b1-45a5-968f-f2897c2670df) will be implemented soon, with that we are going to adjust rules of Delegation Program. **Join [Community call ](https://www.youtube.com/watch?v=KaO6-WLWzMo) on Thursday August 21st, 14:00UTC** to find out more info.
|
||||
|
||||
### Features
|
||||
|
||||
- [Remove old free credential handle](https://github.com/nymtech/nym/pull/5864): And create some traits for unit testing purposes.
|
||||
|
||||
- [Ecash liveness check](https://github.com/nymtech/nym/pull/5890)
|
||||
|
||||
- [Basic zulip client for sending messages](https://github.com/nymtech/nym/pull/5913): In order to be able to send zulip notifications about *emergency* upgrade mode being activated, we need some sort of client. Unfortunately there isn't any rust library that's maintained (the only one had last commit 4 years ago). This simple thing now currently only supports message sending
|
||||
|
||||
- [`nym-node` debug command to reset providers db](https://github.com/nymtech/nym/pull/5914)
|
||||
|
||||
- [Make DNS Resolver fallback optional](https://github.com/nymtech/nym/pull/5920): Default to no dns system fallback, but keep support in the custom hickory dns resolver used for resolving internal domains.
|
||||
|
||||
- [WG exit policy scripts update](https://github.com/nymtech/nym/pull/5921): This PR modifies the scripts `wireguard-exit-policy-manager.sh` and `exit-policy-tests.sh` supporting operators to easily configure and test their IP tables rules in order to have same exit policy for WG as the one for NR, adding the ports decided to be enabled in [NIP-3](https://governator.nym.com/proposal/prop-d0c0d398-43bd-4a6f-b008-1921b64ae4ed) protocol upgrade.
|
||||
|
||||
### Refactors & Maintenance
|
||||
|
||||
- [Allow compatibility with 'CDLA-Permissive-2.0'](https://github.com/nymtech/nym/pull/5910): This license is present in the included `webpki-roots`
|
||||
|
||||
- [Migrate strum to `0.27.2`](https://github.com/nymtech/nym/pull/5960): This PR migrates strum to the latest. Notably all macros' were moved into `strum_macros`. The rest stays the same.
|
||||
|
||||
*/}
|
||||
|
||||
## `v2025.14-feta`
|
||||
|
||||
- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.14-feta)
|
||||
- [`nym-node`](nodes/nym-node.mdx) version `1.16.0`
|
||||
|
||||
```sh
|
||||
nym-node
|
||||
Binary Name: nym-node
|
||||
Build Timestamp: 2025-08-05T09:14:30.322593213Z
|
||||
Build Version: 1.16.0
|
||||
Commit SHA: 7f97f13799342f864e1b106e8cafc9f6d6c24c0f
|
||||
Commit Date: 2025-07-24T11:00:58.000000000+01:00
|
||||
Commit Branch: HEAD
|
||||
rustc Version: 1.86.0
|
||||
rustc Channel: stable
|
||||
cargo Profile: release
|
||||
```
|
||||
|
||||
### Operators Updates & Tools
|
||||
|
||||
- Stark Industries is on a sanction list by EU. IP addresses managed by Stark Ind. and their subsidies (ASN 44477 / ASN 33993) had been put on [spamhaus.org](http://spamhaus.org/) [list](https://www.spamhaus.org/drop/asndrop.json). The effect on NymVPN user experience is that Exit Gateways IPs hosted on Stark Ind. are seen as a spam proxies by many online services.
|
||||
|
||||
- We ask operators - especially Exit Gateways - to consider moving to another ISP. Visit an updated [ISP list](community-counsel/isp-list) and feel free to add more providers, following [these steps](community-counsel/add-content).
|
||||
|
||||
### Features
|
||||
|
||||
- [Allow PG database backend](https://github.com/nymtech/nym/pull/5880):
|
||||
- Added PostgreSQL database support alongside existing SQLite through Cargo feature flags
|
||||
- Implemented runtime query conversion from SQLite `?` placeholders to PostgreSQL `$1`, `$2`, ... format
|
||||
- Single codebase now supports both databases without query duplication
|
||||
|
||||
- [Support mnemonic in the NS agent](https://github.com/nymtech/nym/pull/5883)
|
||||
|
||||
- [`sqlx-pool-guard`: allocate more memory on windows](https://github.com/nymtech/nym/pull/5896):
|
||||
- Allocate 1.5x more memory than reported by the system to provide a safety margin
|
||||
- Increase number of retry attempts to 5
|
||||
|
||||
|
||||
- [dkg epoch dealers query](https://github.com/nymtech/nym/pull/5899)
|
||||
|
||||
- [dkg snapshot epoch](https://github.com/nymtech/nym/pull/5900): In order to determine if signer quorum has been down at particular height, we need to know with certainty the dkg epoch id corresponding to given block height. This PR makes it possible. Every time epoch state is changed (due to DKG progress), snapshot is saved and can be queried. This doesn't work for past data, but given mainnet has only had a single DKG instance, that's not an issue.
|
||||
|
||||
- [`sqlx-pool-guard`: obtain filename from connect options](https://github.com/nymtech/nym/pull/5905):
|
||||
|
||||
### Refactors & Maintenance
|
||||
|
||||
- [Nym node tokio console](https://github.com/nymtech/nym/pull/5909)
|
||||
|
||||
## `v2025.13-emmental`
|
||||
|
||||
|
||||
@@ -20,10 +20,10 @@ This documentation page provides a guide on how to set up and run a [NYM NODE](.
|
||||
```sh
|
||||
nym-node
|
||||
Binary Name: nym-node
|
||||
Build Timestamp: 2025-07-22T09:24:35.790560275Z
|
||||
Build Version: 1.15.0
|
||||
Commit SHA: 578c9b0567656d86812aa21eb0b4c93b5a7235bd
|
||||
Commit Date: 2025-07-22T11:09:35.000000000+02:00
|
||||
Build Timestamp: 2025-08-20T10:37:05.965300480Z
|
||||
Build Version: 1.17.0
|
||||
Commit SHA: 40e1cbc7a9f518eafbb5649c383626b096dd167d
|
||||
Commit Date: 2025-08-20T12:34:04.000000000+02:00
|
||||
Commit Branch: HEAD
|
||||
rustc Version: 1.86.0
|
||||
rustc Channel: stable
|
||||
|
||||
@@ -486,8 +486,14 @@ impl GatewayTasksBuilder {
|
||||
);
|
||||
};
|
||||
|
||||
let Some(ecash_manager) = self.ecash_manager.clone() else {
|
||||
return Err(
|
||||
GatewayError::InternalWireguardError("ecash manager not set".to_string()).into(),
|
||||
);
|
||||
};
|
||||
|
||||
let wg_handle = nym_wireguard::start_wireguard(
|
||||
self.storage.clone(),
|
||||
ecash_manager,
|
||||
self.metrics.clone(),
|
||||
all_peers,
|
||||
self.shutdown.fork("wireguard"),
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
[package]
|
||||
name = "nym-api"
|
||||
license = "GPL-3.0"
|
||||
version = "1.1.63"
|
||||
version = "1.1.64"
|
||||
authors.workspace = true
|
||||
edition = "2021"
|
||||
rust-version.workspace = true
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
-- Change performed in this migration:
|
||||
-- remove PK on expiration_date and instead use composite (epoch_id, expiration_date) PK
|
||||
|
||||
|
||||
CREATE TABLE global_expiration_date_signatures_new
|
||||
(
|
||||
expiration_date DATE NOT NULL,
|
||||
|
||||
epoch_id INTEGER NOT NULL,
|
||||
|
||||
-- combined signatures for all tuples issued for given day
|
||||
serialised_signatures BLOB NOT NULL,
|
||||
|
||||
PRIMARY KEY (epoch_id, expiration_date)
|
||||
);
|
||||
|
||||
CREATE TABLE partial_expiration_date_signatures_new
|
||||
(
|
||||
expiration_date DATE NOT NULL,
|
||||
|
||||
epoch_id INTEGER NOT NULL,
|
||||
|
||||
serialised_signatures BLOB NOT NULL,
|
||||
|
||||
PRIMARY KEY (epoch_id, expiration_date)
|
||||
);
|
||||
|
||||
-- global
|
||||
INSERT INTO global_expiration_date_signatures_new
|
||||
SELECT *
|
||||
FROM global_expiration_date_signatures;
|
||||
|
||||
DROP TABLE global_expiration_date_signatures;
|
||||
|
||||
ALTER TABLE global_expiration_date_signatures_new
|
||||
RENAME TO global_expiration_date_signatures;
|
||||
|
||||
-- partial
|
||||
INSERT INTO partial_expiration_date_signatures_new
|
||||
SELECT *
|
||||
FROM partial_expiration_date_signatures;
|
||||
|
||||
DROP TABLE partial_expiration_date_signatures;
|
||||
|
||||
ALTER TABLE partial_expiration_date_signatures_new
|
||||
RENAME TO partial_expiration_date_signatures;
|
||||
@@ -12,6 +12,7 @@ use nym_api_requests::ecash::models::{
|
||||
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
|
||||
};
|
||||
use nym_api_requests::ecash::VerificationKeyResponse;
|
||||
use nym_coconut_dkg_common::types::EpochId;
|
||||
use nym_ecash_time::{cred_exp_date, EcashTime};
|
||||
use nym_http_api_common::{FormattedResponse, Output};
|
||||
use nym_validator_client::nym_api::rfc_3339_date;
|
||||
@@ -71,6 +72,7 @@ async fn master_verification_key(
|
||||
#[derive(Deserialize, IntoParams)]
|
||||
struct ExpirationDateParam {
|
||||
expiration_date: Option<String>,
|
||||
epoch_id: Option<EpochId>,
|
||||
output: Option<Output>,
|
||||
}
|
||||
|
||||
@@ -93,6 +95,7 @@ async fn expiration_date_signatures(
|
||||
State(state): State<Arc<EcashState>>,
|
||||
Query(ExpirationDateParam {
|
||||
expiration_date,
|
||||
epoch_id,
|
||||
output,
|
||||
}): Query<ExpirationDateParam>,
|
||||
) -> AxumResult<FormattedResponse<AggregatedExpirationDateSignatureResponse>> {
|
||||
@@ -108,8 +111,13 @@ async fn expiration_date_signatures(
|
||||
// see if we're not in the middle of new dkg
|
||||
state.ensure_dkg_not_in_progress().await?;
|
||||
|
||||
let epoch_id = match epoch_id {
|
||||
Some(epoch_id) => epoch_id,
|
||||
None => state.current_dkg_epoch().await?,
|
||||
};
|
||||
|
||||
let expiration_date_signatures = state
|
||||
.master_expiration_date_signatures(expiration_date)
|
||||
.master_expiration_date_signatures(expiration_date, epoch_id)
|
||||
.await?;
|
||||
|
||||
Ok(
|
||||
|
||||
@@ -13,6 +13,7 @@ use nym_api_requests::ecash::{
|
||||
BlindSignRequestBody, BlindedSignatureResponse, PartialCoinIndicesSignatureResponse,
|
||||
PartialExpirationDateSignatureResponse,
|
||||
};
|
||||
use nym_coconut_dkg_common::types::EpochId;
|
||||
use nym_ecash_time::{cred_exp_date, EcashTime};
|
||||
use nym_http_api_common::{FormattedResponse, Output, OutputParams};
|
||||
use nym_validator_client::nym_api::rfc_3339_date;
|
||||
@@ -114,6 +115,7 @@ async fn post_blind_sign(
|
||||
#[derive(Deserialize, IntoParams)]
|
||||
struct ExpirationDateParam {
|
||||
expiration_date: Option<String>,
|
||||
epoch_id: Option<EpochId>,
|
||||
output: Option<Output>,
|
||||
}
|
||||
|
||||
@@ -137,6 +139,7 @@ async fn partial_expiration_date_signatures(
|
||||
State(state): State<Arc<EcashState>>,
|
||||
Query(ExpirationDateParam {
|
||||
expiration_date,
|
||||
epoch_id,
|
||||
output,
|
||||
}): Query<ExpirationDateParam>,
|
||||
) -> AxumResult<FormattedResponse<PartialExpirationDateSignatureResponse>> {
|
||||
@@ -152,8 +155,13 @@ async fn partial_expiration_date_signatures(
|
||||
// see if we're not in the middle of new dkg
|
||||
state.ensure_dkg_not_in_progress().await?;
|
||||
|
||||
let epoch_id = match epoch_id {
|
||||
Some(epoch_id) => epoch_id,
|
||||
None => state.current_dkg_epoch().await?,
|
||||
};
|
||||
|
||||
let expiration_date_signatures = state
|
||||
.partial_expiration_date_signatures(expiration_date)
|
||||
.partial_expiration_date_signatures(expiration_date, epoch_id)
|
||||
.await?;
|
||||
|
||||
Ok(output.to_response(PartialExpirationDateSignatureResponse {
|
||||
|
||||
@@ -83,7 +83,7 @@ impl QueryCommunicationChannel {
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_epoch_cache(&self) -> Result<RwLockWriteGuard<CachedEpoch>> {
|
||||
async fn update_epoch_cache(&self) -> Result<RwLockWriteGuard<'_, CachedEpoch>> {
|
||||
let mut guard = self.cached_epoch.write().await;
|
||||
|
||||
let epoch = ecash::client::Client::get_current_epoch(&self.nyxd_client).await?;
|
||||
|
||||
@@ -100,7 +100,7 @@ where
|
||||
&self,
|
||||
key: K,
|
||||
f: F,
|
||||
) -> Result<RwLockReadGuard<V>, EcashError>
|
||||
) -> Result<RwLockReadGuard<'_, V>, EcashError>
|
||||
where
|
||||
F: FnOnce() -> U,
|
||||
U: Future<Output = Result<V, EcashError>>,
|
||||
|
||||
@@ -65,13 +65,13 @@ impl KeyPair {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn keys(&self) -> Result<RwLockReadGuard<KeyPairWithEpoch>, EcashError> {
|
||||
pub async fn keys(&self) -> Result<RwLockReadGuard<'_, KeyPairWithEpoch>, EcashError> {
|
||||
let keypair_guard = self.get().await.ok_or(EcashError::KeyPairNotDerivedYet)?;
|
||||
RwLockReadGuard::try_map(keypair_guard, |keypair| keypair.as_ref())
|
||||
.map_err(|_| EcashError::KeyPairNotDerivedYet)
|
||||
}
|
||||
|
||||
pub async fn signing_key(&self) -> Result<RwLockReadGuard<SecretKeyAuth>, EcashError> {
|
||||
pub async fn signing_key(&self) -> Result<RwLockReadGuard<'_, SecretKeyAuth>, EcashError> {
|
||||
let keypair_guard = self.get().await.ok_or(EcashError::KeyPairNotDerivedYet)?;
|
||||
|
||||
RwLockReadGuard::try_map(keypair_guard, |keypair| {
|
||||
@@ -80,7 +80,7 @@ impl KeyPair {
|
||||
.map_err(|_| EcashError::KeyPairNotDerivedYet)
|
||||
}
|
||||
|
||||
pub async fn verification_key(&self) -> Option<RwLockReadGuard<VerificationKeyAuth>> {
|
||||
pub async fn verification_key(&self) -> Option<RwLockReadGuard<'_, VerificationKeyAuth>> {
|
||||
RwLockReadGuard::try_map(self.get().await?, |maybe_keys| {
|
||||
maybe_keys.as_ref().map(|k| k.keys.verification_key_ref())
|
||||
})
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::ecash::helpers::{
|
||||
CachedImmutableEpochItem, CachedImmutableItems, IssuedCoinIndicesSignatures,
|
||||
IssuedExpirationDateSignatures,
|
||||
};
|
||||
use nym_coconut_dkg_common::types::EpochId;
|
||||
use nym_compact_ecash::VerificationKeyAuth;
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use time::Date;
|
||||
@@ -18,7 +19,7 @@ pub(crate) struct GlobalEcachState {
|
||||
pub(crate) coin_index_signatures: CachedImmutableEpochItem<IssuedCoinIndicesSignatures>,
|
||||
|
||||
pub(crate) expiration_date_signatures:
|
||||
CachedImmutableItems<Date, IssuedExpirationDateSignatures>,
|
||||
CachedImmutableItems<(EpochId, Date), IssuedExpirationDateSignatures>,
|
||||
}
|
||||
|
||||
impl GlobalEcachState {
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::ecash::helpers::{
|
||||
use crate::ecash::keys::KeyPair;
|
||||
use crate::ecash::storage::models::IssuedHash;
|
||||
use nym_api_requests::ecash::models::{CommitedDeposit, DepositId};
|
||||
use nym_coconut_dkg_common::types::EpochId;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_ticketbooks_merkle::{
|
||||
IssuedTicketbook, IssuedTicketbooksFullMerkleProof, IssuedTicketbooksMerkleTree, MerkleLeaf,
|
||||
@@ -143,7 +144,7 @@ pub(crate) struct LocalEcashState {
|
||||
|
||||
pub(crate) partial_coin_index_signatures: CachedImmutableEpochItem<IssuedCoinIndicesSignatures>,
|
||||
pub(crate) partial_expiration_date_signatures:
|
||||
CachedImmutableItems<Date, IssuedExpirationDateSignatures>,
|
||||
CachedImmutableItems<(EpochId, Date), IssuedExpirationDateSignatures>,
|
||||
|
||||
// merkle trees for ticketbooks issued for particular expiration dates
|
||||
pub(crate) issued_merkle_trees: Arc<RwLock<HashMap<Date, DailyMerkleTree>>>,
|
||||
|
||||
@@ -198,21 +198,21 @@ impl EcashState {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn ecash_signing_key(&self) -> Result<RwLockReadGuard<SecretKeyAuth>> {
|
||||
pub(crate) async fn ecash_signing_key(&self) -> Result<RwLockReadGuard<'_, SecretKeyAuth>> {
|
||||
self.local.ecash_keypair.signing_key().await
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) async fn current_master_verification_key(
|
||||
&self,
|
||||
) -> Result<RwLockReadGuard<VerificationKeyAuth>> {
|
||||
) -> Result<RwLockReadGuard<'_, VerificationKeyAuth>> {
|
||||
self.master_verification_key(None).await
|
||||
}
|
||||
|
||||
pub(crate) async fn master_verification_key(
|
||||
&self,
|
||||
epoch_id: Option<EpochId>,
|
||||
) -> Result<RwLockReadGuard<VerificationKeyAuth>> {
|
||||
) -> Result<RwLockReadGuard<'_, VerificationKeyAuth>> {
|
||||
let epoch_id = match epoch_id {
|
||||
Some(id) => id,
|
||||
None => self.aux.current_epoch().await?,
|
||||
@@ -258,7 +258,7 @@ impl EcashState {
|
||||
pub(crate) async fn master_coin_index_signatures(
|
||||
&self,
|
||||
epoch_id: Option<EpochId>,
|
||||
) -> Result<RwLockReadGuard<IssuedCoinIndicesSignatures>> {
|
||||
) -> Result<RwLockReadGuard<'_, IssuedCoinIndicesSignatures>> {
|
||||
let epoch_id = match epoch_id {
|
||||
Some(id) => id,
|
||||
None => self.aux.current_epoch().await?,
|
||||
@@ -344,7 +344,7 @@ impl EcashState {
|
||||
pub(crate) async fn partial_coin_index_signatures(
|
||||
&self,
|
||||
epoch_id: Option<EpochId>,
|
||||
) -> Result<RwLockReadGuard<IssuedCoinIndicesSignatures>> {
|
||||
) -> Result<RwLockReadGuard<'_, IssuedCoinIndicesSignatures>> {
|
||||
let epoch_id = match epoch_id {
|
||||
Some(id) => id,
|
||||
None => self.aux.current_epoch().await?,
|
||||
@@ -401,10 +401,11 @@ impl EcashState {
|
||||
pub(crate) async fn master_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
) -> Result<RwLockReadGuard<IssuedExpirationDateSignatures>> {
|
||||
epoch_id: EpochId,
|
||||
) -> Result<RwLockReadGuard<'_, IssuedExpirationDateSignatures>> {
|
||||
self.global
|
||||
.expiration_date_signatures
|
||||
.get_or_init(expiration_date, || async {
|
||||
.get_or_init((epoch_id, expiration_date), || async {
|
||||
// 1. sanity check to see if the expiration_date is not nonsense
|
||||
ensure_sane_expiration_date(expiration_date)?;
|
||||
|
||||
@@ -412,7 +413,7 @@ impl EcashState {
|
||||
if let Some(master_sigs) = self
|
||||
.aux
|
||||
.storage
|
||||
.get_master_expiration_date_signatures(expiration_date)
|
||||
.get_master_expiration_date_signatures(expiration_date, epoch_id)
|
||||
.await?
|
||||
{
|
||||
return Ok(master_sigs);
|
||||
@@ -435,13 +436,16 @@ impl EcashState {
|
||||
// check if we're attempting to query ourselves, in that case just get local signature
|
||||
// rather than making the http query
|
||||
let partial = if Some(api.cosmos_address) == cosmos_address {
|
||||
self.partial_expiration_date_signatures(expiration_date)
|
||||
self.partial_expiration_date_signatures(expiration_date, epoch_id)
|
||||
.await?
|
||||
.signatures
|
||||
.clone()
|
||||
} else {
|
||||
api.api_client
|
||||
.partial_expiration_date_signatures(Some(expiration_date))
|
||||
.partial_expiration_date_signatures(
|
||||
Some(expiration_date),
|
||||
Some(epoch_id),
|
||||
)
|
||||
.await?
|
||||
.signatures
|
||||
};
|
||||
@@ -480,10 +484,11 @@ impl EcashState {
|
||||
pub(crate) async fn partial_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
) -> Result<RwLockReadGuard<IssuedExpirationDateSignatures>> {
|
||||
epoch_id: EpochId,
|
||||
) -> Result<RwLockReadGuard<'_, IssuedExpirationDateSignatures>> {
|
||||
self.local
|
||||
.partial_expiration_date_signatures
|
||||
.get_or_init(expiration_date, || async {
|
||||
.get_or_init((epoch_id, expiration_date), || async {
|
||||
// 1. sanity check to see if the expiration_date is not nonsense
|
||||
ensure_sane_expiration_date(expiration_date)?;
|
||||
|
||||
@@ -491,7 +496,7 @@ impl EcashState {
|
||||
if let Some(partial_sigs) = self
|
||||
.aux
|
||||
.storage
|
||||
.get_partial_expiration_date_signatures(expiration_date)
|
||||
.get_partial_expiration_date_signatures(expiration_date, epoch_id)
|
||||
.await?
|
||||
{
|
||||
return Ok(partial_sigs);
|
||||
@@ -721,7 +726,7 @@ impl EcashState {
|
||||
async fn get_updated_merkle_read(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
) -> Result<RwLockReadGuard<DailyMerkleTree>> {
|
||||
) -> Result<RwLockReadGuard<'_, DailyMerkleTree>> {
|
||||
let write_guard = self.get_updated_full_write(expiration_date).await?;
|
||||
|
||||
// SAFETY: the entry was either not empty or we just inserted data in there, whilst never dropping the lock
|
||||
@@ -735,7 +740,7 @@ impl EcashState {
|
||||
async fn get_updated_full_write(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
) -> Result<RwLockWriteGuard<HashMap<Date, DailyMerkleTree>>> {
|
||||
) -> Result<RwLockWriteGuard<'_, HashMap<Date, DailyMerkleTree>>> {
|
||||
let mut write_guard = self.local.issued_merkle_trees.write().await;
|
||||
|
||||
// double check if it's still empty in case another task has already grabbed the write lock and performed the update
|
||||
|
||||
@@ -128,6 +128,7 @@ pub trait EcashStorageManagerExt {
|
||||
async fn get_partial_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: i64,
|
||||
) -> Result<Option<RawExpirationDateSignatures>, sqlx::Error>;
|
||||
async fn insert_partial_expiration_date_signatures(
|
||||
&self,
|
||||
@@ -139,6 +140,7 @@ pub trait EcashStorageManagerExt {
|
||||
async fn get_master_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: i64,
|
||||
) -> Result<Option<RawExpirationDateSignatures>, sqlx::Error>;
|
||||
async fn insert_master_expiration_date_signatures(
|
||||
&self,
|
||||
@@ -501,15 +503,17 @@ impl EcashStorageManagerExt for StorageManager {
|
||||
async fn get_partial_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: i64,
|
||||
) -> Result<Option<RawExpirationDateSignatures>, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
RawExpirationDateSignatures,
|
||||
r#"
|
||||
SELECT epoch_id as "epoch_id: u32", serialised_signatures
|
||||
FROM partial_expiration_date_signatures
|
||||
WHERE expiration_date = ?
|
||||
WHERE expiration_date = ? AND epoch_id = ?
|
||||
"#,
|
||||
expiration_date
|
||||
expiration_date,
|
||||
epoch_id
|
||||
)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await
|
||||
@@ -535,15 +539,17 @@ impl EcashStorageManagerExt for StorageManager {
|
||||
async fn get_master_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: i64,
|
||||
) -> Result<Option<RawExpirationDateSignatures>, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
RawExpirationDateSignatures,
|
||||
r#"
|
||||
SELECT epoch_id as "epoch_id: u32", serialised_signatures
|
||||
FROM global_expiration_date_signatures
|
||||
WHERE expiration_date = ?
|
||||
WHERE expiration_date = ? AND epoch_id = ?
|
||||
"#,
|
||||
expiration_date
|
||||
expiration_date,
|
||||
epoch_id
|
||||
)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await
|
||||
|
||||
@@ -143,6 +143,7 @@ pub trait EcashStorageExt {
|
||||
async fn get_partial_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<Option<IssuedExpirationDateSignatures>, NymApiStorageError>;
|
||||
|
||||
async fn insert_partial_expiration_date_signatures(
|
||||
@@ -154,6 +155,7 @@ pub trait EcashStorageExt {
|
||||
async fn get_master_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<Option<IssuedExpirationDateSignatures>, NymApiStorageError>;
|
||||
|
||||
async fn insert_master_expiration_date_signatures(
|
||||
@@ -456,10 +458,11 @@ impl EcashStorageExt for NymApiStorage {
|
||||
async fn get_partial_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<Option<IssuedExpirationDateSignatures>, NymApiStorageError> {
|
||||
let Some(raw) = self
|
||||
.manager
|
||||
.get_partial_expiration_date_signatures(expiration_date)
|
||||
.get_partial_expiration_date_signatures(expiration_date, epoch_id as i64)
|
||||
.await?
|
||||
else {
|
||||
return Ok(None);
|
||||
@@ -491,10 +494,11 @@ impl EcashStorageExt for NymApiStorage {
|
||||
async fn get_master_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<Option<IssuedExpirationDateSignatures>, NymApiStorageError> {
|
||||
let Some(raw) = self
|
||||
.manager
|
||||
.get_master_expiration_date_signatures(expiration_date)
|
||||
.get_master_expiration_date_signatures(expiration_date, epoch_id as i64)
|
||||
.await?
|
||||
else {
|
||||
return Ok(None);
|
||||
|
||||
@@ -75,7 +75,7 @@ pub(super) fn stake_to_f64(stake: Decimal) -> f64 {
|
||||
|
||||
impl EpochAdvancer {
|
||||
fn load_performance(
|
||||
status_cache: &Option<RwLockReadGuard<Cache<HashMap<NodeId, NodeAnnotation>>>>,
|
||||
status_cache: &Option<RwLockReadGuard<'_, Cache<HashMap<NodeId, NodeAnnotation>>>>,
|
||||
node_id: NodeId,
|
||||
) -> NodeWithPerformance {
|
||||
let Some(status_cache) = status_cache.as_ref() else {
|
||||
|
||||
+4
-4
@@ -66,7 +66,7 @@ impl MixnetContractCache {
|
||||
|
||||
pub async fn all_cached_legacy_mixnodes(
|
||||
&self,
|
||||
) -> Option<RwLockReadGuard<Vec<LegacyMixNodeDetailsWithLayer>>> {
|
||||
) -> Option<RwLockReadGuard<'_, Vec<LegacyMixNodeDetailsWithLayer>>> {
|
||||
self.get(|c| &c.legacy_mixnodes).await.ok()
|
||||
}
|
||||
|
||||
@@ -84,11 +84,11 @@ impl MixnetContractCache {
|
||||
|
||||
pub async fn all_cached_legacy_gateways(
|
||||
&self,
|
||||
) -> Option<RwLockReadGuard<Vec<LegacyGatewayBondWithId>>> {
|
||||
) -> Option<RwLockReadGuard<'_, Vec<LegacyGatewayBondWithId>>> {
|
||||
self.get(|c| &c.legacy_gateways).await.ok()
|
||||
}
|
||||
|
||||
pub async fn all_cached_nym_nodes(&self) -> Option<RwLockReadGuard<Vec<NymNodeDetails>>> {
|
||||
pub async fn all_cached_nym_nodes(&self) -> Option<RwLockReadGuard<'_, Vec<NymNodeDetails>>> {
|
||||
self.get(|c| &c.nym_nodes).await.ok()
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ impl MixnetContractCache {
|
||||
Ok(Cache::as_mapped(&cache, |c| c.rewarded_set.clone()))
|
||||
}
|
||||
|
||||
pub async fn rewarded_set(&self) -> Option<RwLockReadGuard<CachedEpochRewardedSet>> {
|
||||
pub async fn rewarded_set(&self) -> Option<RwLockReadGuard<'_, CachedEpochRewardedSet>> {
|
||||
self.get(|c| &c.rewarded_set).await.ok()
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -112,7 +112,7 @@ impl NodeStatusCache {
|
||||
|
||||
pub(crate) async fn node_annotations(
|
||||
&self,
|
||||
) -> Option<RwLockReadGuard<Cache<HashMap<NodeId, NodeAnnotation>>>> {
|
||||
) -> Option<RwLockReadGuard<'_, Cache<HashMap<NodeId, NodeAnnotation>>>> {
|
||||
self.get(|c| &c.node_annotations).await
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ impl NodeStatusCache {
|
||||
|
||||
pub(crate) async fn annotated_legacy_mixnodes(
|
||||
&self,
|
||||
) -> Option<RwLockReadGuard<Cache<HashMap<NodeId, MixNodeBondAnnotated>>>> {
|
||||
) -> Option<RwLockReadGuard<'_, Cache<HashMap<NodeId, MixNodeBondAnnotated>>>> {
|
||||
self.get(|c| &c.mixnodes_annotated).await
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ impl NodeStatusCache {
|
||||
|
||||
pub(crate) async fn annotated_legacy_gateways(
|
||||
&self,
|
||||
) -> Option<RwLockReadGuard<Cache<HashMap<NodeId, GatewayBondAnnotated>>>> {
|
||||
) -> Option<RwLockReadGuard<'_, Cache<HashMap<NodeId, GatewayBondAnnotated>>>> {
|
||||
self.get(|c| &c.gateways_annotated).await
|
||||
}
|
||||
|
||||
|
||||
@@ -8,31 +8,13 @@ use crate::{MixnetContractCache, NodeStatusCache};
|
||||
use nym_api_requests::models::{
|
||||
ComputeRewardEstParam, GatewayBondAnnotated, GatewayCoreStatusResponse,
|
||||
GatewayStatusReportResponse, GatewayUptimeHistoryResponse, GatewayUptimeResponse,
|
||||
MixNodeBondAnnotated, MixnodeCoreStatusResponse, MixnodeStatus, MixnodeStatusReportResponse,
|
||||
MixNodeBondAnnotated, MixnodeCoreStatusResponse, MixnodeStatusReportResponse,
|
||||
MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse,
|
||||
StakeSaturationResponse, UptimeResponse,
|
||||
};
|
||||
use nym_mixnet_contract_common::rewarding::RewardEstimate;
|
||||
use nym_mixnet_contract_common::NodeId;
|
||||
|
||||
pub(crate) enum RewardedSetStatus {
|
||||
Active,
|
||||
Standby,
|
||||
Inactive,
|
||||
}
|
||||
|
||||
impl From<MixnodeStatus> for RewardedSetStatus {
|
||||
fn from(value: MixnodeStatus) -> Self {
|
||||
match value {
|
||||
MixnodeStatus::Active => RewardedSetStatus::Active,
|
||||
MixnodeStatus::Standby => RewardedSetStatus::Standby,
|
||||
// for all intents and purposes, missing node is treated as inactive for rewarding (since it wouldn't get anything
|
||||
MixnodeStatus::Inactive => RewardedSetStatus::Inactive,
|
||||
MixnodeStatus::NotFound => RewardedSetStatus::Inactive,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn gateway_identity_to_node_id(
|
||||
cache: &NodeStatusCache,
|
||||
identity: &str,
|
||||
|
||||
@@ -148,7 +148,7 @@ impl AppState {
|
||||
impl AppState {
|
||||
pub(crate) async fn describe_nodes_cache_data(
|
||||
&self,
|
||||
) -> Result<RwLockReadGuard<Cache<DescribedNodes>>, AxumErrorResponse> {
|
||||
) -> Result<RwLockReadGuard<'_, Cache<DescribedNodes>>, AxumErrorResponse> {
|
||||
Ok(self.described_nodes_cache().get().await?)
|
||||
}
|
||||
|
||||
@@ -160,7 +160,8 @@ impl AppState {
|
||||
|
||||
pub(crate) async fn node_annotations(
|
||||
&self,
|
||||
) -> Result<RwLockReadGuard<Cache<HashMap<NodeId, NodeAnnotation>>>, AxumErrorResponse> {
|
||||
) -> Result<RwLockReadGuard<'_, Cache<HashMap<NodeId, NodeAnnotation>>>, AxumErrorResponse>
|
||||
{
|
||||
self.node_status_cache()
|
||||
.node_annotations()
|
||||
.await
|
||||
@@ -169,7 +170,7 @@ impl AppState {
|
||||
|
||||
pub(crate) async fn legacy_mixnode_annotations(
|
||||
&self,
|
||||
) -> Result<RwLockReadGuard<Cache<HashMap<NodeId, MixNodeBondAnnotated>>>, AxumErrorResponse>
|
||||
) -> Result<RwLockReadGuard<'_, Cache<HashMap<NodeId, MixNodeBondAnnotated>>>, AxumErrorResponse>
|
||||
{
|
||||
self.node_status_cache()
|
||||
.annotated_legacy_mixnodes()
|
||||
@@ -179,7 +180,7 @@ impl AppState {
|
||||
|
||||
pub(crate) async fn legacy_gateways_annotations(
|
||||
&self,
|
||||
) -> Result<RwLockReadGuard<Cache<HashMap<NodeId, GatewayBondAnnotated>>>, AxumErrorResponse>
|
||||
) -> Result<RwLockReadGuard<'_, Cache<HashMap<NodeId, GatewayBondAnnotated>>>, AxumErrorResponse>
|
||||
{
|
||||
self.node_status_cache()
|
||||
.annotated_legacy_gateways()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user