Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0d420fb0a5 | |||
| fce195fdba | |||
| 3a528d3b89 | |||
| e9bb9792ab | |||
| 88d4a9b111 | |||
| 4c67f01efb | |||
| b69c2e1e94 | |||
| d27e3b49db | |||
| ac12455f97 | |||
| 0b92a59f1a | |||
| 474eff67fa | |||
| 1c6db86259 | |||
| 4a1ce8154a | |||
| e126c1f7f1 | |||
| 31772019cd | |||
| aca98ab04f | |||
| f925c6caf0 | |||
| 5369e5eab9 | |||
| 2e634c59a7 | |||
| d7383d74f3 | |||
| e98d60d7ce | |||
| f47650d6c8 |
@@ -13,6 +13,7 @@ on:
|
||||
- 'nym-network-monitor/**'
|
||||
- 'nym-node/**'
|
||||
- 'nym-node-status-api/**'
|
||||
- 'nym-statistics-api/**'
|
||||
- 'nym-outfox/**'
|
||||
- 'nym-validator-rewarder/**'
|
||||
- 'nyx-chain-watcher/**'
|
||||
|
||||
@@ -20,6 +20,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTUP_PERMIT_COPY_RENAME: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -27,7 +28,8 @@ jobs:
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
# pinned due to issues building contracts
|
||||
toolchain: 1.86.0
|
||||
target: wasm32-unknown-unknown
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
toolchain: 1.86.0
|
||||
override: true
|
||||
|
||||
- name: Build all binaries
|
||||
@@ -66,7 +66,6 @@ jobs:
|
||||
with:
|
||||
name: my-artifact
|
||||
path: |
|
||||
target/release/explorer-api
|
||||
target/release/nym-client
|
||||
target/release/nym-socks5-client
|
||||
target/release/nym-api
|
||||
@@ -82,7 +81,6 @@ jobs:
|
||||
if: github.event_name == 'release'
|
||||
with:
|
||||
files: |
|
||||
target/release/explorer-api
|
||||
target/release/nym-client
|
||||
target/release/nym-socks5-client
|
||||
target/release/nym-api
|
||||
|
||||
@@ -62,4 +62,3 @@ nym-api/redocly/formatted-openapi.json
|
||||
|
||||
**/settings.sql
|
||||
**/enter_db.sh
|
||||
CLAUDE.md
|
||||
@@ -4,6 +4,82 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2025.11-cheddar] (2025-06-10)
|
||||
|
||||
- No autoremoval of peers ([#5831])
|
||||
- Set cached storage counters to 0 ([#5812])
|
||||
- hack: temporarily use next.config.js instead of next.config.ts ([#5805])
|
||||
- chore: resolve 1.87 clippy warnings ([#5802])
|
||||
- Nym Statistics API ([#5800])
|
||||
- QoL: RequestPath trait for http-api-client ([#5788])
|
||||
- Fix contains ticketbook function that always returned true ([#5787])
|
||||
- swap a decode into a fromrow to please future postgres feature ([#5785])
|
||||
- Make address cache configurable ([#5784])
|
||||
- Track wireguard credential retries ([#5783])
|
||||
|
||||
[#5831]: https://github.com/nymtech/nym/pull/5831
|
||||
[#5812]: https://github.com/nymtech/nym/pull/5812
|
||||
[#5805]: https://github.com/nymtech/nym/pull/5805
|
||||
[#5802]: https://github.com/nymtech/nym/pull/5802
|
||||
[#5800]: https://github.com/nymtech/nym/pull/5800
|
||||
[#5788]: https://github.com/nymtech/nym/pull/5788
|
||||
[#5787]: https://github.com/nymtech/nym/pull/5787
|
||||
[#5785]: https://github.com/nymtech/nym/pull/5785
|
||||
[#5784]: https://github.com/nymtech/nym/pull/5784
|
||||
[#5783]: https://github.com/nymtech/nym/pull/5783
|
||||
|
||||
## [2025.10-brie] (2025-05-27)
|
||||
|
||||
- Backport PR 5779 ([#5801])
|
||||
- Expanded Accept Encoding for `reqwest` ([#5779])
|
||||
- Teach HttpClientError how to report its status code and timeout ([#5770])
|
||||
- Skip refreshing the topology on startup as we already have an initial set ([#5768])
|
||||
- Fetch the topology from the nym-api concurrently ([#5767])
|
||||
- feat: use bincode by default in NymApiClient + remove feature-lock ([#5761])
|
||||
- Instrument create_request ([#5760])
|
||||
- Add node_bonded field to delegations ([#5759])
|
||||
- build(deps): bump mikefarah/yq from 4.45.1 to 4.45.4 ([#5758])
|
||||
- Raw route submissions ([#5756])
|
||||
- feat: expires header for `/active` nym-api responses ([#5755])
|
||||
- Decrease default average packet delay to 15 ms ([#5754])
|
||||
- build(deps): bump the patch-updates group across 1 directory with 12 updates ([#5753])
|
||||
- Remove pretty_env_logger and switch remaining crates to use tracing ([#5749])
|
||||
- Update pretty_env_logger to latest to not depend on unmaintained crate atty ([#5748])
|
||||
- Upgrade prometheus crate to fix security warning ([#5747])
|
||||
- Downgrade deranged crate to 0.4.0 ([#5746])
|
||||
- feat: nym-api bincode + yaml support ([#5745])
|
||||
- fix parallel feature in ecash crate with send + sync ([#5744])
|
||||
- Remove old test directory - Update validator docker ([#5743])
|
||||
- [Feature] `RememberMe` is the new don't `ForgetMe` ([#5742])
|
||||
- build(deps): bump ammonia from 4.0.0 to 4.1.0 ([#5739])
|
||||
- build(deps): bump base-x from 3.0.9 to 3.0.11 in /testnet-faucet ([#5737])
|
||||
- build(deps): bump http-proxy-middleware from 2.0.8 to 2.0.9 ([#5730])
|
||||
|
||||
[#5801]: https://github.com/nymtech/nym/pull/5801
|
||||
[#5779]: https://github.com/nymtech/nym/pull/5779
|
||||
[#5770]: https://github.com/nymtech/nym/pull/5770
|
||||
[#5768]: https://github.com/nymtech/nym/pull/5768
|
||||
[#5767]: https://github.com/nymtech/nym/pull/5767
|
||||
[#5761]: https://github.com/nymtech/nym/pull/5761
|
||||
[#5760]: https://github.com/nymtech/nym/pull/5760
|
||||
[#5759]: https://github.com/nymtech/nym/pull/5759
|
||||
[#5758]: https://github.com/nymtech/nym/pull/5758
|
||||
[#5756]: https://github.com/nymtech/nym/pull/5756
|
||||
[#5755]: https://github.com/nymtech/nym/pull/5755
|
||||
[#5754]: https://github.com/nymtech/nym/pull/5754
|
||||
[#5753]: https://github.com/nymtech/nym/pull/5753
|
||||
[#5749]: https://github.com/nymtech/nym/pull/5749
|
||||
[#5748]: https://github.com/nymtech/nym/pull/5748
|
||||
[#5747]: https://github.com/nymtech/nym/pull/5747
|
||||
[#5746]: https://github.com/nymtech/nym/pull/5746
|
||||
[#5745]: https://github.com/nymtech/nym/pull/5745
|
||||
[#5744]: https://github.com/nymtech/nym/pull/5744
|
||||
[#5743]: https://github.com/nymtech/nym/pull/5743
|
||||
[#5742]: https://github.com/nymtech/nym/pull/5742
|
||||
[#5739]: https://github.com/nymtech/nym/pull/5739
|
||||
[#5737]: https://github.com/nymtech/nym/pull/5737
|
||||
[#5730]: https://github.com/nymtech/nym/pull/5730
|
||||
|
||||
## [2025.9-appenzeller] (2025-05-13)
|
||||
|
||||
- build(deps): bump clap from 4.5.36 to 4.5.37 in the patch-updates group ([#5722])
|
||||
|
||||
Generated
+841
-800
File diff suppressed because it is too large
Load Diff
@@ -112,6 +112,7 @@ members = [
|
||||
"nym-node/nym-node-metrics",
|
||||
"nym-node/nym-node-requests",
|
||||
"nym-outfox",
|
||||
"nym-statistics-api",
|
||||
"nym-validator-rewarder",
|
||||
"nyx-chain-watcher",
|
||||
"sdk/ffi/cpp",
|
||||
@@ -152,6 +153,7 @@ default-members = [
|
||||
"nym-node",
|
||||
"nym-node-status-api/nym-node-status-agent",
|
||||
"nym-node-status-api/nym-node-status-api",
|
||||
"nym-statistics-api",
|
||||
"nym-validator-rewarder",
|
||||
"nyx-chain-watcher",
|
||||
"service-providers/authenticator",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.55"
|
||||
version = "1.1.57"
|
||||
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.55"
|
||||
version = "1.1.57"
|
||||
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"
|
||||
|
||||
@@ -456,7 +456,7 @@ where
|
||||
log::error!("Could not authenticate and start up the gateway connection - {err}");
|
||||
ClientCoreError::GatewayClientError {
|
||||
gateway_id: details.gateway_id.to_base58_string(),
|
||||
source: err,
|
||||
source: Box::new(err),
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -36,9 +36,6 @@ pub trait GatewayTransceiver: GatewaySender + GatewayReceiver {
|
||||
&mut self,
|
||||
message: ClientRequest,
|
||||
) -> Result<(), GatewayClientError>;
|
||||
|
||||
/// Check if the websocket connection to the gateway is alive
|
||||
fn is_connection_alive(&self) -> bool;
|
||||
}
|
||||
|
||||
/// This trait defines the functionality of sending `MixPacket` into the mixnet,
|
||||
@@ -93,11 +90,6 @@ impl<G: GatewayTransceiver + ?Sized + Send> GatewayTransceiver for Box<G> {
|
||||
log::debug!("Sent client request: {:?}", message);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_connection_alive(&self) -> bool {
|
||||
(**self).is_connection_alive()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
@@ -155,10 +147,6 @@ where
|
||||
) -> Result<(), GatewayClientError> {
|
||||
self.gateway_client.send_client_request(message).await
|
||||
}
|
||||
|
||||
fn is_connection_alive(&self) -> bool {
|
||||
self.gateway_client.is_connection_alive()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
@@ -246,11 +234,6 @@ mod nonwasm_sealed {
|
||||
) -> Result<(), GatewayClientError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_connection_alive(&self) -> bool {
|
||||
// LocalGateway is always "connected" since it's in-process
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -333,9 +316,4 @@ impl GatewayTransceiver for MockGateway {
|
||||
) -> Result<(), GatewayClientError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_connection_alive(&self) -> bool {
|
||||
// MockGateway is always "connected" for testing purposes
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ pub enum ClientCoreError {
|
||||
#[error("gateway client error ({gateway_id}): {source}")]
|
||||
GatewayClientError {
|
||||
gateway_id: String,
|
||||
source: GatewayClientError,
|
||||
source: Box<GatewayClientError>,
|
||||
},
|
||||
|
||||
#[error("custom gateway client error: {source}")]
|
||||
@@ -88,10 +88,7 @@ pub enum ClientCoreError {
|
||||
},
|
||||
|
||||
#[error("failed to establish connection to gateway: {source}")]
|
||||
GatewayConnectionFailure {
|
||||
#[from]
|
||||
source: tungstenite::Error,
|
||||
},
|
||||
GatewayConnectionFailure { source: Box<tungstenite::Error> },
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[error("failed to establish gateway connection (wasm)")]
|
||||
@@ -166,9 +163,6 @@ pub enum ClientCoreError {
|
||||
#[error("there are no gateways supporting the wss protocol available")]
|
||||
NoWssGateways,
|
||||
|
||||
#[error("there are no gateways with compatible protocol versions available")]
|
||||
NoGatewaysWithCompatibleProtocol,
|
||||
|
||||
#[error("the specified gateway '{gateway}' does not support the wss protocol")]
|
||||
UnsupportedWssProtocol { gateway: String },
|
||||
|
||||
@@ -230,6 +224,14 @@ pub enum ClientCoreError {
|
||||
HkdfDerivationError {},
|
||||
}
|
||||
|
||||
impl From<tungstenite::Error> for ClientCoreError {
|
||||
fn from(err: tungstenite::Error) -> ClientCoreError {
|
||||
ClientCoreError::GatewayConnectionFailure {
|
||||
source: Box::new(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Set of messages that the client can send to listeners via the task manager
|
||||
#[derive(Debug)]
|
||||
pub enum ClientCoreStatusMessage {
|
||||
|
||||
@@ -7,7 +7,6 @@ use futures::{SinkExt, StreamExt};
|
||||
use log::{debug, info, trace, warn};
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_gateway_client::GatewayClient;
|
||||
use nym_gateway_requests::{ClientControlRequest, ServerResponse, CURRENT_PROTOCOL_VERSION};
|
||||
use nym_topology::node::RoutingNode;
|
||||
use nym_validator_client::client::IdentityKeyRef;
|
||||
use nym_validator_client::UserAgent;
|
||||
@@ -132,63 +131,6 @@ pub async fn gateways_for_init<R: Rng>(
|
||||
Ok(valid_gateways)
|
||||
}
|
||||
|
||||
pub async fn gateways_for_init_with_protocol_validation<R: Rng>(
|
||||
rng: &mut R,
|
||||
nym_apis: &[Url],
|
||||
user_agent: Option<UserAgent>,
|
||||
minimum_performance: u8,
|
||||
ignore_epoch_roles: bool,
|
||||
) -> Result<Vec<RoutingNode>, ClientCoreError> {
|
||||
// First get the initial list of gateways
|
||||
let gateways = gateways_for_init(
|
||||
rng,
|
||||
nym_apis,
|
||||
user_agent,
|
||||
minimum_performance,
|
||||
ignore_epoch_roles,
|
||||
)
|
||||
.await?;
|
||||
|
||||
info!(
|
||||
"Checking protocol compatibility for {} gateways...",
|
||||
gateways.len()
|
||||
);
|
||||
|
||||
// Filter out gateways with invalid protocols concurrently
|
||||
let validated_gateways = Arc::new(tokio::sync::Mutex::new(Vec::new()));
|
||||
|
||||
futures::stream::iter(&gateways)
|
||||
.for_each_concurrent(CONCURRENT_GATEWAYS_MEASURED, |gateway| async {
|
||||
let id = gateway.identity();
|
||||
trace!("validating protocol compatibility with {id}...");
|
||||
|
||||
match validate_gateway_protocol(gateway).await {
|
||||
Ok(()) => {
|
||||
debug!("{id}: protocol check successful");
|
||||
validated_gateways.lock().await.push(gateway.clone());
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("failed to check protocol for {id}: {err}");
|
||||
}
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
let validated_gateways = validated_gateways.lock().await;
|
||||
|
||||
info!(
|
||||
"Protocol check complete: {}/{} gateways responded successfully",
|
||||
validated_gateways.len(),
|
||||
gateways.len()
|
||||
);
|
||||
|
||||
if validated_gateways.is_empty() {
|
||||
return Err(ClientCoreError::NoGatewaysWithCompatibleProtocol);
|
||||
}
|
||||
|
||||
Ok(validated_gateways.clone())
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
async fn connect(endpoint: &str) -> Result<WsConn, ClientCoreError> {
|
||||
match tokio::time::timeout(CONN_TIMEOUT, connect_async(endpoint)).await {
|
||||
@@ -268,132 +210,6 @@ where
|
||||
Ok(GatewayWithLatency::new(gateway, avg))
|
||||
}
|
||||
|
||||
async fn validate_gateway_protocol<G>(gateway: &G) -> Result<(), ClientCoreError>
|
||||
where
|
||||
G: ConnectableGateway,
|
||||
{
|
||||
let Some(addr) = gateway.clients_address(false) else {
|
||||
return Err(ClientCoreError::UnsupportedEntry {
|
||||
id: gateway.node_id(),
|
||||
identity: gateway.identity().to_string(),
|
||||
});
|
||||
};
|
||||
|
||||
trace!(
|
||||
"validating protocol compatibility with {} ({addr})...",
|
||||
gateway.identity(),
|
||||
);
|
||||
|
||||
let mut stream = connect(&addr).await?;
|
||||
|
||||
// Send protocol version request
|
||||
let protocol_request = ClientControlRequest::SupportedProtocol {};
|
||||
|
||||
// Send the request as JSON text message
|
||||
stream.send(Message::from(protocol_request)).await?;
|
||||
|
||||
// Wait for response with timeout
|
||||
let protocol_timeout = Duration::from_millis(2000);
|
||||
let response_future = stream.next();
|
||||
|
||||
match tokio::time::timeout(protocol_timeout, response_future).await {
|
||||
Err(_) => {
|
||||
warn!("Gateway {} protocol check timed out", gateway.identity());
|
||||
Err(ClientCoreError::GatewayConnectionTimeout)
|
||||
}
|
||||
Ok(Some(Ok(Message::Text(response_text)))) => {
|
||||
// Try to deserialize the response
|
||||
let response = ServerResponse::try_from(response_text).map_err(|_| {
|
||||
ClientCoreError::GatewayClientError {
|
||||
gateway_id: gateway.identity().to_base58_string(),
|
||||
source: *Box::new(
|
||||
nym_gateway_client::error::GatewayClientError::MalformedResponse,
|
||||
),
|
||||
}
|
||||
})?;
|
||||
|
||||
match response {
|
||||
ServerResponse::SupportedProtocol { version } => {
|
||||
debug!(
|
||||
"Gateway {} supports protocol version {}, ours: {}",
|
||||
gateway.identity(),
|
||||
version,
|
||||
CURRENT_PROTOCOL_VERSION
|
||||
);
|
||||
|
||||
// Check protocol compatibility
|
||||
if version > CURRENT_PROTOCOL_VERSION {
|
||||
warn!(
|
||||
"Gateway {} uses newer protocol version {} (client supports {}). \
|
||||
Gateway should gracefully degrade, but consider updating your client.",
|
||||
gateway.identity(),
|
||||
version,
|
||||
CURRENT_PROTOCOL_VERSION
|
||||
);
|
||||
}
|
||||
|
||||
trace!(
|
||||
"Gateway {} protocol validation successful (gateway: v{}, client: v{})",
|
||||
gateway.identity(),
|
||||
version,
|
||||
CURRENT_PROTOCOL_VERSION
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
ServerResponse::Error { message } => {
|
||||
warn!(
|
||||
"Gateway {} returned error during protocol check: {}",
|
||||
gateway.identity(),
|
||||
message
|
||||
);
|
||||
Err(ClientCoreError::GatewayClientError {
|
||||
gateway_id: gateway.identity().to_base58_string(),
|
||||
source: *Box::new(
|
||||
nym_gateway_client::error::GatewayClientError::GatewayError(message),
|
||||
),
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
warn!(
|
||||
"Gateway {} returned unexpected response during protocol check",
|
||||
gateway.identity()
|
||||
);
|
||||
Err(ClientCoreError::GatewayClientError {
|
||||
gateway_id: gateway.identity().to_base58_string(),
|
||||
source: *Box::new(
|
||||
nym_gateway_client::error::GatewayClientError::UnexpectedResponse {
|
||||
name: response.name().to_string(),
|
||||
},
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Some(Ok(_))) => {
|
||||
warn!(
|
||||
"Gateway {} sent non-text response during protocol check",
|
||||
gateway.identity()
|
||||
);
|
||||
Err(ClientCoreError::GatewayConnectionAbruptlyClosed)
|
||||
}
|
||||
Ok(Some(Err(e))) => {
|
||||
warn!(
|
||||
"WebSocket error during protocol check with {}: {}",
|
||||
gateway.identity(),
|
||||
e
|
||||
);
|
||||
Err(e.into())
|
||||
}
|
||||
Ok(None) => {
|
||||
warn!(
|
||||
"Gateway {} closed connection during protocol check",
|
||||
gateway.identity()
|
||||
);
|
||||
Err(ClientCoreError::GatewayConnectionAbruptlyClosed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn choose_gateway_by_latency<R: Rng, G: ConnectableGateway + Clone>(
|
||||
rng: &mut R,
|
||||
gateways: &[G],
|
||||
@@ -513,7 +329,7 @@ pub(super) async fn register_with_gateway(
|
||||
log::warn!("Failed to establish connection with gateway!");
|
||||
ClientCoreError::GatewayClientError {
|
||||
gateway_id: gateway_id.to_base58_string(),
|
||||
source: err,
|
||||
source: Box::new(err),
|
||||
}
|
||||
})?;
|
||||
let auth_response = gateway_client
|
||||
@@ -523,7 +339,7 @@ pub(super) async fn register_with_gateway(
|
||||
log::warn!("Failed to register with the gateway {gateway_id}: {err}");
|
||||
ClientCoreError::GatewayClientError {
|
||||
gateway_id: gateway_id.to_base58_string(),
|
||||
source: err,
|
||||
source: Box::new(err),
|
||||
}
|
||||
})?;
|
||||
|
||||
|
||||
@@ -232,7 +232,7 @@ where
|
||||
} => {
|
||||
log::debug!("GatewaySetup::ReuseConnection");
|
||||
Ok(reuse_gateway_connection(
|
||||
authenticated_ephemeral_client,
|
||||
*authenticated_ephemeral_client,
|
||||
*gateway_details,
|
||||
managed_keys,
|
||||
))
|
||||
|
||||
@@ -218,7 +218,7 @@ pub enum GatewaySetup {
|
||||
|
||||
ReuseConnection {
|
||||
/// The authenticated ephemeral client that was created during `init`
|
||||
authenticated_ephemeral_client: InitGatewayClient,
|
||||
authenticated_ephemeral_client: Box<InitGatewayClient>,
|
||||
|
||||
// Details of this pre-initialised client (i.e. gateway and keys)
|
||||
gateway_details: Box<GatewayRegistration>,
|
||||
@@ -261,7 +261,7 @@ impl GatewaySetup {
|
||||
pub fn try_reuse_connection(init_res: InitialisationResult) -> Result<Self, ClientCoreError> {
|
||||
if let Some(authenticated_ephemeral_client) = init_res.authenticated_ephemeral_client {
|
||||
Ok(GatewaySetup::ReuseConnection {
|
||||
authenticated_ephemeral_client,
|
||||
authenticated_ephemeral_client: Box::new(authenticated_ephemeral_client),
|
||||
gateway_details: Box::new(init_res.gateway_registration),
|
||||
client_keys: init_res.client_keys,
|
||||
})
|
||||
|
||||
@@ -27,6 +27,7 @@ nym-credential-storage = { path = "../../credential-storage" }
|
||||
nym-credentials-interface = { path = "../../credentials-interface" }
|
||||
nym-crypto = { path = "../../crypto" }
|
||||
nym-gateway-requests = { path = "../../gateway-requests" }
|
||||
nym-http-api-client = { path = "../../http-api-client" }
|
||||
nym-network-defaults = { path = "../../network-defaults" }
|
||||
nym-sphinx = { path = "../../nymsphinx" }
|
||||
nym-statistics-common = { path = "../../statistics" }
|
||||
|
||||
@@ -165,24 +165,6 @@ impl<C, St> GatewayClient<C, St> {
|
||||
self.bandwidth.remaining()
|
||||
}
|
||||
|
||||
pub fn is_connection_established(&self) -> bool {
|
||||
self.connection.is_established()
|
||||
}
|
||||
|
||||
/// Check if the websocket connection is actually alive at the socket level
|
||||
pub fn is_connection_alive(&self) -> bool {
|
||||
// First check if we have an established connection
|
||||
if !self.connection.is_established() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the file descriptor and check if the socket is alive
|
||||
match self.ws_fd() {
|
||||
Some(fd) => socket_is_alive(fd),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
async fn _close_connection(&mut self) -> Result<(), GatewayClientError> {
|
||||
match std::mem::replace(&mut self.connection, SocketState::NotConnected) {
|
||||
@@ -1146,49 +1128,3 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a socket file descriptor is alive and responsive
|
||||
///
|
||||
/// This function performs socket-level checks to determine if the connection is actually alive.
|
||||
/// It's cross-platform compatible and works on both Unix and non-Unix systems.
|
||||
fn socket_is_alive(fd: RawFd) -> bool {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::io::ErrorKind;
|
||||
use std::net::TcpStream;
|
||||
use std::os::unix::io::FromRawFd;
|
||||
|
||||
unsafe {
|
||||
// Create a TcpStream from the raw fd to perform socket operations
|
||||
let stream = TcpStream::from_raw_fd(fd);
|
||||
|
||||
// Try to peek at the socket to see if it's still connected
|
||||
// We peek with a zero-length buffer to avoid consuming data
|
||||
let mut buf = [0u8; 0];
|
||||
let result = match stream.peek(&mut buf) {
|
||||
Ok(_) => true, // Socket is alive and readable
|
||||
Err(e) => match e.kind() {
|
||||
ErrorKind::WouldBlock => true, // Socket is alive but no data available
|
||||
ErrorKind::ConnectionReset
|
||||
| ErrorKind::ConnectionAborted
|
||||
| ErrorKind::BrokenPipe
|
||||
| ErrorKind::NotConnected => false, // Socket is clearly dead
|
||||
_ => true, // Other errors might be temporary, assume alive
|
||||
},
|
||||
};
|
||||
|
||||
// Prevent the TcpStream from closing the fd when it's dropped
|
||||
// since we don't own the fd
|
||||
std::mem::forget(stream);
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
// On non-Unix systems, we can't easily check socket state
|
||||
// Fall back to assuming the connection is alive if we have an fd
|
||||
fd != 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::error::GatewayClientError;
|
||||
|
||||
use nym_http_api_client::HickoryDnsResolver;
|
||||
#[cfg(unix)]
|
||||
use std::{
|
||||
os::fd::{AsRawFd, RawFd},
|
||||
@@ -19,6 +20,7 @@ pub(crate) async fn connect_async(
|
||||
) -> Result<(WebSocketStream<MaybeTlsStream<TcpStream>>, Response), GatewayClientError> {
|
||||
use tokio::net::TcpSocket;
|
||||
|
||||
let resolver = HickoryDnsResolver::default();
|
||||
let uri =
|
||||
Url::parse(endpoint).map_err(|_| GatewayClientError::InvalidUrl(endpoint.to_owned()))?;
|
||||
let port: u16 = uri.port_or_known_default().unwrap_or(443);
|
||||
@@ -27,18 +29,18 @@ pub(crate) async fn connect_async(
|
||||
.host()
|
||||
.ok_or(GatewayClientError::InvalidUrl(endpoint.to_owned()))?;
|
||||
|
||||
// Get address for tcp connection, using system DNS resolver
|
||||
// Get address for tcp connection, if a domain is provided use our preferred resolver rather than
|
||||
// the default std resolve
|
||||
let sock_addrs: Vec<SocketAddr> = match host {
|
||||
Host::Ipv4(addr) => vec![SocketAddr::new(addr.into(), port)],
|
||||
Host::Ipv6(addr) => vec![SocketAddr::new(addr.into(), port)],
|
||||
Host::Domain(domain) => {
|
||||
// Do a DNS lookup for the domain using system DNS resolver
|
||||
tokio::net::lookup_host((domain, port))
|
||||
.await
|
||||
.map_err(|err| GatewayClientError::NetworkConnectionFailed {
|
||||
address: endpoint.to_owned(),
|
||||
source: err.into(),
|
||||
})?
|
||||
// Do a DNS lookup for the domain using our custom DNS resolver
|
||||
resolver
|
||||
.resolve_str(domain)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|a| SocketAddr::new(a, port))
|
||||
.collect()
|
||||
}
|
||||
};
|
||||
@@ -54,7 +56,7 @@ pub(crate) async fn connect_async(
|
||||
}
|
||||
.map_err(|err| GatewayClientError::NetworkConnectionFailed {
|
||||
address: endpoint.to_owned(),
|
||||
source: err.into(),
|
||||
source: Box::new(tungstenite::Error::from(err)),
|
||||
})?;
|
||||
|
||||
#[cfg(unix)]
|
||||
@@ -70,7 +72,7 @@ pub(crate) async fn connect_async(
|
||||
Err(err) => {
|
||||
stream = Err(GatewayClientError::NetworkConnectionFailed {
|
||||
address: endpoint.to_owned(),
|
||||
source: err.into(),
|
||||
source: Box::new(tungstenite::Error::from(err)),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
@@ -81,6 +83,6 @@ pub(crate) async fn connect_async(
|
||||
.await
|
||||
.map_err(|error| GatewayClientError::NetworkConnectionFailed {
|
||||
address: endpoint.to_owned(),
|
||||
source: error,
|
||||
source: Box::new(error),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ pub enum GatewayClientError {
|
||||
RequestError(#[from] GatewayRequestsError),
|
||||
|
||||
#[error("There was a network error: {0}")]
|
||||
NetworkError(#[from] WsError),
|
||||
NetworkError(Box<WsError>),
|
||||
|
||||
#[error("failed to upgrade our shared key - the gateway sent malformed response")]
|
||||
FatalKeyUpgradeFailure,
|
||||
@@ -41,7 +41,10 @@ pub enum GatewayClientError {
|
||||
NetworkErrorWasm(#[from] JsError),
|
||||
|
||||
#[error("connection failed: {address}: {source}")]
|
||||
NetworkConnectionFailed { address: String, source: WsError },
|
||||
NetworkConnectionFailed {
|
||||
address: String,
|
||||
source: Box<WsError>,
|
||||
},
|
||||
|
||||
#[error("no socket address for endpoint: {address}")]
|
||||
NoEndpointForConnection { address: String },
|
||||
@@ -49,6 +52,10 @@ pub enum GatewayClientError {
|
||||
#[error("Invalid URL: {0}")]
|
||||
InvalidUrl(String),
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[error("resolution failed: {0}")]
|
||||
ResolutionFailed(#[from] nym_http_api_client::HickoryDnsError),
|
||||
|
||||
#[error("No shared key was provided or obtained")]
|
||||
NoSharedKeyAvailable,
|
||||
|
||||
@@ -123,10 +130,16 @@ pub enum GatewayClientError {
|
||||
ShutdownInProgress,
|
||||
}
|
||||
|
||||
impl From<WsError> for GatewayClientError {
|
||||
fn from(error: WsError) -> Self {
|
||||
GatewayClientError::NetworkError(Box::new(error))
|
||||
}
|
||||
}
|
||||
|
||||
impl GatewayClientError {
|
||||
pub fn is_closed_connection(&self) -> bool {
|
||||
match self {
|
||||
GatewayClientError::NetworkError(ws_err) => match ws_err {
|
||||
GatewayClientError::NetworkError(ws_err) => match ws_err.as_ref() {
|
||||
WsError::AlreadyClosed | WsError::ConnectionClosed => true,
|
||||
WsError::Io(io_err) => matches!(
|
||||
io_err.kind(),
|
||||
|
||||
@@ -28,7 +28,7 @@ pub(crate) fn cleanup_socket_message(
|
||||
msg: Option<Result<Message, WsError>>,
|
||||
) -> Result<Message, GatewayClientError> {
|
||||
match msg {
|
||||
Some(msg) => msg.map_err(GatewayClientError::NetworkError),
|
||||
Some(msg) => msg.map_err(GatewayClientError::from),
|
||||
None => Err(GatewayClientError::ConnectionAbruptlyClosed),
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ pub(crate) fn cleanup_socket_messages(
|
||||
match msgs {
|
||||
Some(msgs) => msgs
|
||||
.into_iter()
|
||||
.map(|msg| msg.map_err(GatewayClientError::NetworkError))
|
||||
.map(|msg| msg.map_err(GatewayClientError::from))
|
||||
.collect(),
|
||||
None => Err(GatewayClientError::ConnectionAbruptlyClosed),
|
||||
}
|
||||
|
||||
@@ -73,7 +73,6 @@ pub const STAKE_SATURATION: &str = "stake-saturation";
|
||||
pub const INCLUSION_CHANCE: &str = "inclusion-probability";
|
||||
pub const SUBMIT_GATEWAY: &str = "submit-gateway-monitoring-results";
|
||||
pub const SUBMIT_NODE: &str = "submit-node-monitoring-results";
|
||||
pub const SUBMIT_ROUTE: &str = "submit-route-monitoring-results";
|
||||
|
||||
pub const SERVICE_PROVIDERS: &str = "services";
|
||||
|
||||
|
||||
@@ -48,8 +48,7 @@ pub mod nym_config {
|
||||
log::trace!("Loading from file: {:#?}", filepath.as_ref().to_owned());
|
||||
let config_contents = fs::read_to_string(filepath)?;
|
||||
|
||||
toml::from_str(&config_contents)
|
||||
.map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err))
|
||||
toml::from_str(&config_contents).map_err(io::Error::other)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,8 +151,7 @@ where
|
||||
let content = fs::read_to_string(path)?;
|
||||
|
||||
// TODO: should we be preserving original error type instead?
|
||||
deserialize_config_from_toml_str(&content)
|
||||
.map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err))
|
||||
deserialize_config_from_toml_str(&content).map_err(io::Error::other)
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -138,7 +138,12 @@
|
||||
|
||||
pub use reqwest::{IntoUrl, StatusCode};
|
||||
|
||||
use crate::path::RequestPath;
|
||||
use async_trait::async_trait;
|
||||
use bytes::Bytes;
|
||||
use http::header::CONTENT_TYPE;
|
||||
use http::HeaderMap;
|
||||
use mime::Mime;
|
||||
use reqwest::header::HeaderValue;
|
||||
use reqwest::{RequestBuilder, Response};
|
||||
use serde::de::DeserializeOwned;
|
||||
@@ -149,10 +154,6 @@ use thiserror::Error;
|
||||
use tracing::{debug, instrument, warn};
|
||||
use url::Url;
|
||||
|
||||
use bytes::Bytes;
|
||||
use http::header::CONTENT_TYPE;
|
||||
use http::HeaderMap;
|
||||
use mime::Mime;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use std::net::SocketAddr;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -163,6 +164,8 @@ pub use user_agent::UserAgent;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod dns;
|
||||
mod path;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use dns::{HickoryDnsError, HickoryDnsResolver};
|
||||
|
||||
@@ -454,14 +457,15 @@ impl Client {
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait ApiClientCore {
|
||||
/// Create an HTTP request using the host configured in this client.
|
||||
fn create_request<B, K, V>(
|
||||
fn create_request<P, B, K, V>(
|
||||
&self,
|
||||
method: reqwest::Method,
|
||||
path: PathSegments<'_>,
|
||||
path: P,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: Option<&B>,
|
||||
) -> RequestBuilder
|
||||
where
|
||||
P: RequestPath,
|
||||
B: Serialize + ?Sized,
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>;
|
||||
@@ -512,7 +516,7 @@ pub trait ApiClientCore {
|
||||
};
|
||||
let params: Vec<(String, String)> = standin_url.query_pairs().into_owned().collect();
|
||||
|
||||
self.create_request(method, &path, ¶ms, json_body)
|
||||
self.create_request(method, path.as_slice(), ¶ms, json_body)
|
||||
}
|
||||
|
||||
/// Send a created HTTP request.
|
||||
@@ -525,14 +529,15 @@ pub trait ApiClientCore {
|
||||
E: Display;
|
||||
|
||||
/// Create and send a created HTTP request.
|
||||
async fn send_request<B, K, V, E>(
|
||||
async fn send_request<P, B, K, V, E>(
|
||||
&self,
|
||||
method: reqwest::Method,
|
||||
path: PathSegments<'_>,
|
||||
path: P,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: Option<&B>,
|
||||
) -> Result<Response, HttpClientError<E>>
|
||||
where
|
||||
P: RequestPath + Send + Sync,
|
||||
B: Serialize + ?Sized + Sync,
|
||||
K: AsRef<str> + Sync,
|
||||
V: AsRef<str> + Sync,
|
||||
@@ -547,14 +552,15 @@ pub trait ApiClientCore {
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl ApiClientCore for Client {
|
||||
#[instrument(level = "debug", skip_all, fields(path=?path))]
|
||||
fn create_request<B, K, V>(
|
||||
fn create_request<P, B, K, V>(
|
||||
&self,
|
||||
method: reqwest::Method,
|
||||
path: PathSegments<'_>,
|
||||
path: P,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: Option<&B>,
|
||||
) -> RequestBuilder
|
||||
where
|
||||
P: RequestPath,
|
||||
B: Serialize + ?Sized,
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
@@ -597,12 +603,9 @@ impl ApiClientCore for Client {
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait ApiClient: ApiClientCore {
|
||||
/// Create an HTTP GET Request with the provided path and parameters
|
||||
fn create_get_request<K, V>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
) -> RequestBuilder
|
||||
fn create_get_request<P, K, V>(&self, path: P, params: Params<'_, K, V>) -> RequestBuilder
|
||||
where
|
||||
P: RequestPath,
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
{
|
||||
@@ -610,13 +613,14 @@ pub trait ApiClient: ApiClientCore {
|
||||
}
|
||||
|
||||
/// Create an HTTP POST Request with the provided path, parameters, and json body
|
||||
fn create_post_request<B, K, V>(
|
||||
fn create_post_request<P, B, K, V>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
path: P,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: &B,
|
||||
) -> RequestBuilder
|
||||
where
|
||||
P: RequestPath,
|
||||
B: Serialize + ?Sized,
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
@@ -625,12 +629,9 @@ pub trait ApiClient: ApiClientCore {
|
||||
}
|
||||
|
||||
/// Create an HTTP DELETE Request with the provided path and parameters
|
||||
fn create_delete_request<K, V>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
) -> RequestBuilder
|
||||
fn create_delete_request<P, K, V>(&self, path: P, params: Params<'_, K, V>) -> RequestBuilder
|
||||
where
|
||||
P: RequestPath,
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
{
|
||||
@@ -638,13 +639,14 @@ pub trait ApiClient: ApiClientCore {
|
||||
}
|
||||
|
||||
/// Create an HTTP PATCH Request with the provided path, parameters, and json body
|
||||
fn create_patch_request<B, K, V>(
|
||||
fn create_patch_request<P, B, K, V>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
path: P,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: &B,
|
||||
) -> RequestBuilder
|
||||
where
|
||||
P: RequestPath,
|
||||
B: Serialize + ?Sized,
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
@@ -654,12 +656,13 @@ pub trait ApiClient: ApiClientCore {
|
||||
|
||||
/// Create and send an HTTP GET Request with the provided path and parameters
|
||||
#[instrument(level = "debug", skip_all, fields(path=?path))]
|
||||
async fn send_get_request<K, V, E>(
|
||||
async fn send_get_request<P, K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
path: P,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Result<Response, HttpClientError<E>>
|
||||
where
|
||||
P: RequestPath + Send + Sync,
|
||||
K: AsRef<str> + Sync,
|
||||
V: AsRef<str> + Sync,
|
||||
E: Display,
|
||||
@@ -669,13 +672,14 @@ pub trait ApiClient: ApiClientCore {
|
||||
}
|
||||
|
||||
/// Create and send an HTTP POST Request with the provided path, parameters, and json data
|
||||
async fn send_post_request<B, K, V, E>(
|
||||
async fn send_post_request<P, B, K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
path: P,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: &B,
|
||||
) -> Result<Response, HttpClientError<E>>
|
||||
where
|
||||
P: RequestPath + Send + Sync,
|
||||
B: Serialize + ?Sized + Sync,
|
||||
K: AsRef<str> + Sync,
|
||||
V: AsRef<str> + Sync,
|
||||
@@ -686,12 +690,13 @@ pub trait ApiClient: ApiClientCore {
|
||||
}
|
||||
|
||||
/// Create and send an HTTP DELETE Request with the provided path and parameters
|
||||
async fn send_delete_request<K, V, E>(
|
||||
async fn send_delete_request<P, K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
path: P,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Result<Response, HttpClientError<E>>
|
||||
where
|
||||
P: RequestPath + Send + Sync,
|
||||
K: AsRef<str> + Sync,
|
||||
V: AsRef<str> + Sync,
|
||||
E: Display,
|
||||
@@ -701,13 +706,14 @@ pub trait ApiClient: ApiClientCore {
|
||||
}
|
||||
|
||||
/// Create and send an HTTP PATCH Request with the provided path, parameters, and json data
|
||||
async fn send_patch_request<B, K, V, E>(
|
||||
async fn send_patch_request<P, B, K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
path: P,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: &B,
|
||||
) -> Result<Response, HttpClientError<E>>
|
||||
where
|
||||
P: RequestPath + Send + Sync,
|
||||
B: Serialize + ?Sized + Sync,
|
||||
K: AsRef<str> + Sync,
|
||||
V: AsRef<str> + Sync,
|
||||
@@ -722,12 +728,13 @@ pub trait ApiClient: ApiClientCore {
|
||||
/// into the provided type `T`.
|
||||
#[instrument(level = "debug", skip_all, fields(path=?path))]
|
||||
// TODO: deprecate in favour of get_response that works based on mime type in the response
|
||||
async fn get_json<T, K, V, E>(
|
||||
async fn get_json<P, T, K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
path: P,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
P: RequestPath + Send + Sync,
|
||||
for<'a> T: Deserialize<'a>,
|
||||
K: AsRef<str> + Sync,
|
||||
V: AsRef<str> + Sync,
|
||||
@@ -739,12 +746,13 @@ pub trait ApiClient: ApiClientCore {
|
||||
/// 'get' data from the segment-defined path, e.g. `["api", "v1", "mixnodes"]`, with tuple
|
||||
/// defined key-value parameters, e.g. `[("since", "12345")]`. Attempt to parse the response
|
||||
/// into the provided type `T` based on the content type header
|
||||
async fn get_response<T, K, V, E>(
|
||||
async fn get_response<P, T, K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
path: P,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
P: RequestPath + Send + Sync,
|
||||
for<'a> T: Deserialize<'a>,
|
||||
K: AsRef<str> + Sync,
|
||||
V: AsRef<str> + Sync,
|
||||
@@ -759,13 +767,14 @@ pub trait ApiClient: ApiClientCore {
|
||||
/// 'post' json data to the segment-defined path, e.g. `["api", "v1", "mixnodes"]`, with tuple
|
||||
/// defined key-value parameters, e.g. `[("since", "12345")]`. Attempt to parse the response
|
||||
/// into the provided type `T`.
|
||||
async fn post_json<B, T, K, V, E>(
|
||||
async fn post_json<P, B, T, K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
path: P,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: &B,
|
||||
) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
P: RequestPath + Send + Sync,
|
||||
B: Serialize + ?Sized + Sync,
|
||||
for<'a> T: Deserialize<'a>,
|
||||
K: AsRef<str> + Sync,
|
||||
@@ -781,12 +790,13 @@ pub trait ApiClient: ApiClientCore {
|
||||
/// 'delete' json data from the segment-defined path, e.g. `["api", "v1", "mixnodes"]`, with
|
||||
/// tuple defined key-value parameters, e.g. `[("since", "12345")]`. Attempt to parse the
|
||||
/// response into the provided type `T`.
|
||||
async fn delete_json<T, K, V, E>(
|
||||
async fn delete_json<P, T, K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
path: P,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
P: RequestPath + Send + Sync,
|
||||
for<'a> T: Deserialize<'a>,
|
||||
K: AsRef<str> + Sync,
|
||||
V: AsRef<str> + Sync,
|
||||
@@ -801,13 +811,14 @@ pub trait ApiClient: ApiClientCore {
|
||||
/// 'patch' json data at the segment-defined path, e.g. `["api", "v1", "mixnodes"]`, with tuple
|
||||
/// defined key-value parameters, e.g. `[("since", "12345")]`. Attempt to parse the response
|
||||
/// into the provided type `T`.
|
||||
async fn patch_json<B, T, K, V, E>(
|
||||
async fn patch_json<P, B, T, K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
path: P,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: &B,
|
||||
) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
P: RequestPath + Send + Sync,
|
||||
B: Serialize + ?Sized + Sync,
|
||||
for<'a> T: Deserialize<'a>,
|
||||
K: AsRef<str> + Sync,
|
||||
@@ -890,7 +901,7 @@ impl<C> ApiClient for C where C: ApiClientCore + Sync {}
|
||||
/// utility function that should solve the double slash problem in API urls forever.
|
||||
fn sanitize_url<K: AsRef<str>, V: AsRef<str>>(
|
||||
base: &Url,
|
||||
segments: PathSegments<'_>,
|
||||
request_path: impl RequestPath,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Url {
|
||||
let mut url = base.clone();
|
||||
@@ -900,10 +911,7 @@ fn sanitize_url<K: AsRef<str>, V: AsRef<str>>(
|
||||
|
||||
path_segments.pop_if_empty();
|
||||
|
||||
for segment in segments {
|
||||
let segment = segment.strip_prefix('/').unwrap_or(segment);
|
||||
let segment = segment.strip_suffix('/').unwrap_or(segment);
|
||||
|
||||
for segment in request_path.to_sanitized_segments() {
|
||||
path_segments.push(segment);
|
||||
}
|
||||
|
||||
@@ -1048,6 +1056,18 @@ mod tests {
|
||||
fn sanitizing_urls() {
|
||||
let base_url: Url = "http://foomp.com".parse().unwrap();
|
||||
|
||||
// works with a full string
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo/bar",
|
||||
sanitize_url(&base_url, "/foo//bar/", NO_PARAMS).as_str()
|
||||
);
|
||||
|
||||
// (and leading slash doesn't matter)
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo/bar",
|
||||
sanitize_url(&base_url, "foo//bar/", NO_PARAMS).as_str()
|
||||
);
|
||||
|
||||
// works with 1 segment
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo",
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Collection of URL Path Segments
|
||||
pub type PathSegments<'a> = &'a [&'a str];
|
||||
|
||||
fn sanitize_fragment(segment: &str) -> &str {
|
||||
segment.trim_matches(|c: char| c.is_whitespace() || c == '/')
|
||||
}
|
||||
|
||||
pub trait RequestPath: Debug {
|
||||
fn to_sanitized_segments(&self) -> Vec<&str>;
|
||||
}
|
||||
|
||||
macro_rules! impl_stringified_sanitized_segments {
|
||||
($frag_iter:expr) => {{
|
||||
let mut path_segments = Vec::new();
|
||||
|
||||
for segment in $frag_iter {
|
||||
if !segment.is_empty() {
|
||||
path_segments.push(sanitize_fragment(segment));
|
||||
}
|
||||
}
|
||||
|
||||
path_segments
|
||||
}};
|
||||
}
|
||||
|
||||
impl RequestPath for PathSegments<'_> {
|
||||
fn to_sanitized_segments(&self) -> Vec<&str> {
|
||||
impl_stringified_sanitized_segments!(self.iter())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> RequestPath for &[&str; N] {
|
||||
fn to_sanitized_segments(&self) -> Vec<&str> {
|
||||
impl_stringified_sanitized_segments!(self.iter())
|
||||
}
|
||||
}
|
||||
|
||||
impl RequestPath for &str {
|
||||
fn to_sanitized_segments(&self) -> Vec<&str> {
|
||||
impl_stringified_sanitized_segments!(self.split('/'))
|
||||
}
|
||||
}
|
||||
|
||||
impl RequestPath for String {
|
||||
fn to_sanitized_segments(&self) -> Vec<&str> {
|
||||
impl_stringified_sanitized_segments!(self.split('/'))
|
||||
}
|
||||
}
|
||||
|
||||
impl RequestPath for &String {
|
||||
fn to_sanitized_segments(&self) -> Vec<&str> {
|
||||
impl_stringified_sanitized_segments!(self.split('/'))
|
||||
}
|
||||
}
|
||||
@@ -124,7 +124,7 @@ impl ReconstructionBuffer {
|
||||
// TODO: what to do in that case? give up on the message? overwrite it? panic?
|
||||
// it *might* be due to lock ack-packet, but let's keep the `warn` level in case
|
||||
// it could be somehow exploited
|
||||
debug!(
|
||||
warn!(
|
||||
"duplicate fragment received! - frag - {} (set id: {})",
|
||||
fragment.current_fragment(),
|
||||
fragment.id()
|
||||
|
||||
@@ -26,54 +26,54 @@ pub enum ScraperError {
|
||||
WebSocketConnectionFailure {
|
||||
url: String,
|
||||
#[source]
|
||||
source: tendermint_rpc::Error,
|
||||
source: Box<tendermint_rpc::Error>,
|
||||
},
|
||||
|
||||
#[error("failed to establish rpc connection to {url}: {source}")]
|
||||
HttpConnectionFailure {
|
||||
url: String,
|
||||
#[source]
|
||||
source: tendermint_rpc::Error,
|
||||
source: Box<tendermint_rpc::Error>,
|
||||
},
|
||||
|
||||
#[error("failed to create chain subscription: {source}")]
|
||||
ChainSubscriptionFailure {
|
||||
#[source]
|
||||
source: tendermint_rpc::Error,
|
||||
source: Box<tendermint_rpc::Error>,
|
||||
},
|
||||
|
||||
#[error("could not obtain basic block information at height: {height}: {source}")]
|
||||
BlockQueryFailure {
|
||||
height: u32,
|
||||
#[source]
|
||||
source: tendermint_rpc::Error,
|
||||
source: Box<tendermint_rpc::Error>,
|
||||
},
|
||||
|
||||
#[error("could not obtain block results information at height: {height}: {source}")]
|
||||
BlockResultsQueryFailure {
|
||||
height: u32,
|
||||
#[source]
|
||||
source: tendermint_rpc::Error,
|
||||
source: Box<tendermint_rpc::Error>,
|
||||
},
|
||||
|
||||
#[error("could not obtain validators information at height: {height}: {source}")]
|
||||
ValidatorsQueryFailure {
|
||||
height: u32,
|
||||
#[source]
|
||||
source: tendermint_rpc::Error,
|
||||
source: Box<tendermint_rpc::Error>,
|
||||
},
|
||||
|
||||
#[error("could not obtain tx results for tx: {hash}: {source}")]
|
||||
TxResultsQueryFailure {
|
||||
hash: Hash,
|
||||
#[source]
|
||||
source: tendermint_rpc::Error,
|
||||
source: Box<tendermint_rpc::Error>,
|
||||
},
|
||||
|
||||
#[error("could not obtain current abci info: {source}")]
|
||||
AbciInfoQueryFailure {
|
||||
#[source]
|
||||
source: tendermint_rpc::Error,
|
||||
source: Box<tendermint_rpc::Error>,
|
||||
},
|
||||
|
||||
#[error("could not parse tx {hash}: {source}")]
|
||||
|
||||
@@ -29,7 +29,7 @@ impl RpcClient {
|
||||
let http_client = HttpClient::new(url.as_str()).map_err(|source| {
|
||||
ScraperError::HttpConnectionFailure {
|
||||
url: url.to_string(),
|
||||
source,
|
||||
source: Box::new(source),
|
||||
}
|
||||
})?;
|
||||
|
||||
@@ -90,7 +90,10 @@ impl RpcClient {
|
||||
self.inner
|
||||
.block(height)
|
||||
.await
|
||||
.map_err(|source| ScraperError::BlockQueryFailure { height, source })
|
||||
.map_err(|source| ScraperError::BlockQueryFailure {
|
||||
height,
|
||||
source: Box::new(source),
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(skip(self), err(Display))]
|
||||
@@ -100,31 +103,37 @@ impl RpcClient {
|
||||
) -> Result<block_results::Response, ScraperError> {
|
||||
debug!("getting block results");
|
||||
|
||||
self.inner
|
||||
.block_results(height)
|
||||
.await
|
||||
.map_err(|source| ScraperError::BlockResultsQueryFailure { height, source })
|
||||
self.inner.block_results(height).await.map_err(|source| {
|
||||
ScraperError::BlockResultsQueryFailure {
|
||||
height,
|
||||
source: Box::new(source),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn current_block_height(&self) -> Result<u64, ScraperError> {
|
||||
debug!("getting current block height");
|
||||
|
||||
let info = self
|
||||
.inner
|
||||
.abci_info()
|
||||
.await
|
||||
.map_err(|source| ScraperError::AbciInfoQueryFailure { source })?;
|
||||
let info =
|
||||
self.inner
|
||||
.abci_info()
|
||||
.await
|
||||
.map_err(|source| ScraperError::AbciInfoQueryFailure {
|
||||
source: Box::new(source),
|
||||
})?;
|
||||
Ok(info.last_block_height.value())
|
||||
}
|
||||
|
||||
pub(crate) async fn earliest_available_block_height(&self) -> Result<u64, ScraperError> {
|
||||
debug!("getting earliest available block height");
|
||||
|
||||
let status = self
|
||||
.inner
|
||||
.status()
|
||||
.await
|
||||
.map_err(|source| ScraperError::AbciInfoQueryFailure { source })?;
|
||||
let status =
|
||||
self.inner
|
||||
.status()
|
||||
.await
|
||||
.map_err(|source| ScraperError::AbciInfoQueryFailure {
|
||||
source: Box::new(source),
|
||||
})?;
|
||||
Ok(status.sync_info.earliest_block_height.value())
|
||||
}
|
||||
|
||||
@@ -167,7 +176,7 @@ impl RpcClient {
|
||||
.await
|
||||
.map_err(|source| ScraperError::TxResultsQueryFailure {
|
||||
hash: tx_hash,
|
||||
source,
|
||||
source: Box::new(source),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -181,6 +190,9 @@ impl RpcClient {
|
||||
self.inner
|
||||
.validators(height, Paging::All)
|
||||
.await
|
||||
.map_err(|source| ScraperError::ValidatorsQueryFailure { height, source })
|
||||
.map_err(|source| ScraperError::ValidatorsQueryFailure {
|
||||
height,
|
||||
source: Box::new(source),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ impl ChainSubscriber {
|
||||
let websocket_url = websocket_endpoint.as_str().try_into().map_err(|source| {
|
||||
ScraperError::WebSocketConnectionFailure {
|
||||
url: websocket_endpoint.to_string(),
|
||||
source,
|
||||
source: Box::new(source),
|
||||
}
|
||||
})?;
|
||||
|
||||
@@ -52,7 +52,7 @@ impl ChainSubscriber {
|
||||
.await
|
||||
.map_err(|source| ScraperError::WebSocketConnectionFailure {
|
||||
url: websocket_endpoint.to_string(),
|
||||
source,
|
||||
source: Box::new(source),
|
||||
})?;
|
||||
|
||||
Ok(ChainSubscriber {
|
||||
@@ -83,7 +83,7 @@ impl ChainSubscriber {
|
||||
.await
|
||||
.map_err(|source| ScraperError::WebSocketConnectionFailure {
|
||||
url: self.websocket_endpoint.to_string(),
|
||||
source,
|
||||
source: Box::new(source),
|
||||
})?;
|
||||
self.websocket_client = client;
|
||||
self.websocket_driver = Some(driver);
|
||||
@@ -121,7 +121,9 @@ impl ChainSubscriber {
|
||||
.websocket_client
|
||||
.subscribe(EventType::NewBlock.into())
|
||||
.await
|
||||
.map_err(|source| ScraperError::ChainSubscriptionFailure { source })?;
|
||||
.map_err(|source| ScraperError::ChainSubscriptionFailure {
|
||||
source: Box::new(source),
|
||||
})?;
|
||||
|
||||
let mut failures = 0;
|
||||
|
||||
|
||||
@@ -54,14 +54,11 @@ where
|
||||
let key_pem = read_pem_file(path)?;
|
||||
|
||||
if T::pem_type() != key_pem.tag {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!(
|
||||
"unexpected key pem tag. Got '{}', expected: '{}'",
|
||||
key_pem.tag,
|
||||
T::pem_type()
|
||||
),
|
||||
));
|
||||
return Err(io::Error::other(format!(
|
||||
"unexpected key pem tag. Got '{}', expected: '{}'",
|
||||
key_pem.tag,
|
||||
T::pem_type()
|
||||
)));
|
||||
}
|
||||
|
||||
let key = match T::from_bytes(&key_pem.contents) {
|
||||
@@ -84,7 +81,7 @@ fn read_pem_file<P: AsRef<Path>>(filepath: P) -> io::Result<Pem> {
|
||||
let mut pem_bytes = File::open(filepath)?;
|
||||
let mut buf = Vec::new();
|
||||
pem_bytes.read_to_end(&mut buf)?;
|
||||
pem::parse(&buf).map_err(|e| io::Error::new(io::ErrorKind::Other, e))
|
||||
pem::parse(&buf).map_err(io::Error::other)
|
||||
}
|
||||
|
||||
fn write_pem_file<P: AsRef<Path>>(filepath: P, data: Vec<u8>, tag: &str) -> io::Result<()> {
|
||||
|
||||
@@ -28,5 +28,11 @@ nym-credentials-interface = { path = "../credentials-interface" }
|
||||
nym-metrics = { path = "../nym-metrics" }
|
||||
nym-task = { path = "../task" }
|
||||
|
||||
utoipa = { workspace = true, optional = true }
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer]
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
openapi = ["dep:utoipa"]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::report::{ClientStatsReport, OsInformation};
|
||||
use crate::report::client::{ClientStatsReport, OsInformation};
|
||||
|
||||
use nym_task::TaskClient;
|
||||
use time::{OffsetDateTime, Time};
|
||||
|
||||
@@ -26,11 +26,16 @@ pub mod report;
|
||||
pub mod types;
|
||||
|
||||
const CLIENT_ID_PREFIX: &str = "client_stats_id";
|
||||
const VPN_CLIENT_ID_PREFIX: &str = "vpnclient_stats_id";
|
||||
|
||||
pub fn generate_client_stats_id(id_key: ed25519::PublicKey) -> String {
|
||||
generate_stats_id(CLIENT_ID_PREFIX, id_key.to_base58_string())
|
||||
}
|
||||
|
||||
pub fn generate_vpn_client_stats_id<M: AsRef<[u8]>>(seed: M) -> String {
|
||||
generate_stats_id(VPN_CLIENT_ID_PREFIX, seed)
|
||||
}
|
||||
|
||||
fn generate_stats_id<M: AsRef<[u8]>>(prefix: &str, id_seed: M) -> String {
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
hasher.update(prefix);
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::clients::{
|
||||
nym_api_statistics::NymApiStats, packet_statistics::PacketStatistics,
|
||||
};
|
||||
|
||||
use super::error::StatsError;
|
||||
use crate::error::StatsError;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sysinfo::System;
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod client;
|
||||
pub mod vpn_client;
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const KIND: &str = "vpn_client_stats_report";
|
||||
const VERSION: &str = "v1";
|
||||
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct VpnClientStatsReport {
|
||||
pub kind: String,
|
||||
pub api_version: String,
|
||||
pub stats_id: String,
|
||||
pub static_information: StaticInformationReport,
|
||||
//SW called it basic so we can swap it easily down the line for more data
|
||||
pub basic_usage: Option<UsageReport>,
|
||||
}
|
||||
|
||||
impl VpnClientStatsReport {
|
||||
pub fn new(stats_id: String, static_information: StaticInformationReport) -> Self {
|
||||
VpnClientStatsReport {
|
||||
kind: KIND.into(),
|
||||
api_version: VERSION.into(),
|
||||
stats_id,
|
||||
static_information,
|
||||
basic_usage: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_usage_report(mut self, usage_report: UsageReport) -> Self {
|
||||
self.basic_usage = Some(usage_report);
|
||||
self
|
||||
}
|
||||
}
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct StaticInformationReport {
|
||||
pub os_type: String,
|
||||
pub os_version: Option<String>,
|
||||
pub os_arch: String,
|
||||
pub app_version: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct UsageReport {
|
||||
pub connection_time_ms: Option<i32>,
|
||||
pub two_hop: bool,
|
||||
}
|
||||
@@ -224,10 +224,6 @@ impl NymTopology {
|
||||
serde_json::from_reader(file).map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn node_details(&self) -> &HashMap<NodeId, RoutingNode> {
|
||||
&self.node_details
|
||||
}
|
||||
|
||||
pub fn add_skimmed_nodes(&mut self, nodes: &[SkimmedNode]) {
|
||||
self.add_additional_nodes(nodes.iter())
|
||||
}
|
||||
|
||||
@@ -11,27 +11,6 @@ static NETWORK_MONITORS: LazyLock<HashSet<String>> = LazyLock::new(|| {
|
||||
nm
|
||||
});
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, ToSchema)]
|
||||
pub struct RouteResult {
|
||||
pub layer1: u32,
|
||||
pub layer2: u32,
|
||||
pub layer3: u32,
|
||||
pub gw: u32,
|
||||
pub success: bool,
|
||||
}
|
||||
|
||||
impl RouteResult {
|
||||
pub fn new(layer1: u32, layer2: u32, layer3: u32, gw: u32, success: bool) -> Self {
|
||||
RouteResult {
|
||||
layer1,
|
||||
layer2,
|
||||
layer3,
|
||||
gw,
|
||||
success,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, ToSchema)]
|
||||
pub struct NodeResult {
|
||||
#[schema(value_type = u32)]
|
||||
@@ -50,23 +29,23 @@ impl NodeResult {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(untagged)]
|
||||
pub enum MonitorResults {
|
||||
Node(Vec<NodeResult>),
|
||||
Route(Vec<RouteResult>),
|
||||
Mixnode(Vec<NodeResult>),
|
||||
Gateway(Vec<NodeResult>),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
pub struct MonitorMessage {
|
||||
results: MonitorResults,
|
||||
results: Vec<NodeResult>,
|
||||
signature: String,
|
||||
signer: String,
|
||||
timestamp: i64,
|
||||
}
|
||||
|
||||
impl MonitorMessage {
|
||||
fn message_to_sign(results: &MonitorResults, timestamp: i64) -> Vec<u8> {
|
||||
fn message_to_sign(results: &[NodeResult], timestamp: i64) -> Vec<u8> {
|
||||
let mut msg = serde_json::to_vec(results).unwrap_or_default();
|
||||
msg.extend_from_slice(×tamp.to_le_bytes());
|
||||
msg
|
||||
@@ -81,7 +60,7 @@ impl MonitorMessage {
|
||||
now - self.timestamp < 5
|
||||
}
|
||||
|
||||
pub fn new(results: MonitorResults, private_key: &PrivateKey) -> Self {
|
||||
pub fn new(results: Vec<NodeResult>, private_key: &PrivateKey) -> Self {
|
||||
let timestamp = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.expect("Time went backwards")
|
||||
@@ -103,7 +82,7 @@ impl MonitorMessage {
|
||||
NETWORK_MONITORS.contains(&self.signer)
|
||||
}
|
||||
|
||||
pub fn results(&self) -> &MonitorResults {
|
||||
pub fn results(&self) -> &[NodeResult] {
|
||||
&self.results
|
||||
}
|
||||
|
||||
|
||||
@@ -25,10 +25,8 @@ fn map_ws_error(err: WebSocketError) -> WsError {
|
||||
// TODO: are we preserving correct semantics?
|
||||
WebSocketError::ConnectionError => WsError::ConnectionClosed,
|
||||
WebSocketError::ConnectionClose(_event) => WsError::ConnectionClosed,
|
||||
WebSocketError::MessageSendError(err) => {
|
||||
WsError::Io(io::Error::new(io::ErrorKind::Other, err.to_string()))
|
||||
}
|
||||
_ => WsError::Io(io::Error::new(io::ErrorKind::Other, "new websocket error")),
|
||||
WebSocketError::MessageSendError(err) => WsError::Io(io::Error::other(err.to_string())),
|
||||
_ => WsError::Io(io::Error::other("new websocket error")),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,12 +13,10 @@ use nym_gateway_storage::models::WireguardPeer;
|
||||
use nym_task::TaskClient;
|
||||
use nym_wireguard_types::DEFAULT_PEER_TIMEOUT_CHECK;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use tokio::sync::{mpsc, RwLock};
|
||||
use tokio_stream::{wrappers::IntervalStream, StreamExt};
|
||||
|
||||
pub(crate) type SharedBandwidthStorageManager = Arc<RwLock<BandwidthStorageManager>>;
|
||||
const AUTO_REMOVE_AFTER: Duration = Duration::from_secs(60 * 60); // 1 hour
|
||||
|
||||
pub struct PeerHandle {
|
||||
public_key: Key,
|
||||
@@ -28,7 +26,6 @@ pub struct PeerHandle {
|
||||
request_tx: mpsc::Sender<PeerControlRequest>,
|
||||
timeout_check_interval: IntervalStream,
|
||||
task_client: TaskClient,
|
||||
startup_timestamp: SystemTime,
|
||||
}
|
||||
|
||||
impl PeerHandle {
|
||||
@@ -53,7 +50,6 @@ impl PeerHandle {
|
||||
request_tx,
|
||||
timeout_check_interval,
|
||||
task_client,
|
||||
startup_timestamp: SystemTime::now(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,26 +69,49 @@ impl PeerHandle {
|
||||
Ok(success)
|
||||
}
|
||||
|
||||
async fn active_peer(
|
||||
&mut self,
|
||||
storage_peer: &WireguardPeer,
|
||||
kernel_peer: &Peer,
|
||||
) -> Result<bool, Error> {
|
||||
if let Some(bandwidth_manager) = &self.bandwidth_storage_manager {
|
||||
if kernel_peer.last_handshake.is_none()
|
||||
&& SystemTime::now().duration_since(self.startup_timestamp)? >= AUTO_REMOVE_AFTER
|
||||
{
|
||||
let success = self.remove_peer().await?;
|
||||
self.peer_storage_manager.remove_peer();
|
||||
tracing::debug!(
|
||||
"Peer {} has not been active for more then {} seconds, removing it",
|
||||
kernel_peer.public_key.to_string(),
|
||||
AUTO_REMOVE_AFTER.as_secs()
|
||||
fn compute_spent_bandwidth(kernel_peer: &Peer, storage_peer: &WireguardPeer) -> Option<u64> {
|
||||
let storage_peer_rx_bytes = u64::try_from(storage_peer.rx_bytes)
|
||||
.inspect_err(|e| tracing::error!("Storage rx bytes could not be converted: {e}"))
|
||||
.ok()?;
|
||||
let storage_peer_tx_bytes = u64::try_from(storage_peer.tx_bytes)
|
||||
.inspect_err(|e| tracing::error!("Storage tx bytes could not be converted: {e}"))
|
||||
.ok()?;
|
||||
|
||||
let kernel_total = kernel_peer
|
||||
.rx_bytes
|
||||
.checked_add(kernel_peer.tx_bytes)
|
||||
.or_else(|| {
|
||||
tracing::error!(
|
||||
"Overflow on kernel adding bytes: {} + {}",
|
||||
kernel_peer.rx_bytes,
|
||||
kernel_peer.tx_bytes
|
||||
);
|
||||
return Ok(!success);
|
||||
}
|
||||
let spent_bandwidth = (kernel_peer.rx_bytes + kernel_peer.tx_bytes)
|
||||
.checked_sub(storage_peer.rx_bytes as u64 + storage_peer.tx_bytes as u64)
|
||||
None
|
||||
})?;
|
||||
let storage_total = storage_peer_rx_bytes
|
||||
.checked_add(storage_peer_tx_bytes)
|
||||
.or_else(|| {
|
||||
tracing::error!("Overflow on storage adding bytes: {storage_peer_rx_bytes} + {storage_peer_tx_bytes}");
|
||||
None
|
||||
})?;
|
||||
|
||||
kernel_total.checked_sub(storage_total).or_else(|| {
|
||||
tracing::error!("Overflow on spent bandwidth subtraction: kernel - storage = {kernel_total} - {storage_total}");
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
async fn active_peer(&mut self, kernel_peer: &Peer) -> Result<bool, Error> {
|
||||
let Some(storage_peer) = self.peer_storage_manager.get_peer() else {
|
||||
log::debug!(
|
||||
"Peer {:?} not in storage anymore, shutting down handle",
|
||||
self.public_key
|
||||
);
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
if let Some(bandwidth_manager) = &self.bandwidth_storage_manager {
|
||||
let spent_bandwidth = Self::compute_spent_bandwidth(kernel_peer, &storage_peer)
|
||||
.unwrap_or_else(|| {
|
||||
// if gateway restarted, the kernel values restart from 0
|
||||
// and we should restart from 0 in storage as well
|
||||
@@ -100,8 +119,10 @@ impl PeerHandle {
|
||||
self.peer_storage_manager.peer_information.as_mut()
|
||||
{
|
||||
peer_information.force_sync = true;
|
||||
peer_information.peer.rx_bytes = kernel_peer.rx_bytes;
|
||||
peer_information.peer.tx_bytes = kernel_peer.tx_bytes;
|
||||
}
|
||||
kernel_peer.rx_bytes + kernel_peer.tx_bytes
|
||||
0
|
||||
})
|
||||
.try_into()
|
||||
.map_err(|_| Error::InconsistentConsumedBytes)?;
|
||||
@@ -124,14 +145,6 @@ impl PeerHandle {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if SystemTime::now().duration_since(self.startup_timestamp)? >= AUTO_REMOVE_AFTER {
|
||||
log::debug!(
|
||||
"Peer {} has been present for 30 days, removing it",
|
||||
self.public_key
|
||||
);
|
||||
let success = self.remove_peer().await?;
|
||||
return Ok(!success);
|
||||
}
|
||||
let spent_bandwidth = kernel_peer.rx_bytes + kernel_peer.tx_bytes;
|
||||
if spent_bandwidth >= BANDWIDTH_CAP_PER_DAY {
|
||||
log::debug!(
|
||||
@@ -158,14 +171,7 @@ impl PeerHandle {
|
||||
// the host information hasn't beed updated yet
|
||||
return Ok(true);
|
||||
};
|
||||
let Some(storage_peer) = self.peer_storage_manager.get_peer() else {
|
||||
log::debug!(
|
||||
"Peer {:?} not in storage anymore, shutting down handle",
|
||||
self.public_key
|
||||
);
|
||||
return Ok(false);
|
||||
};
|
||||
if !self.active_peer(&storage_peer, &kernel_peer).await? {
|
||||
if !self.active_peer(&kernel_peer).await? {
|
||||
log::debug!(
|
||||
"Peer {:?} is not active anymore, shutting down handle",
|
||||
self.public_key
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// @ts-check
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
|
||||
basePath: "/explorer",
|
||||
assetPrefix: "/explorer",
|
||||
trailingSlash: false,
|
||||
|
||||
async redirects() {
|
||||
return [
|
||||
// Change the basePath to /explorer
|
||||
{
|
||||
source: "/",
|
||||
destination: "/explorer",
|
||||
basePath: false,
|
||||
permanent: true,
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = nextConfig
|
||||
@@ -1,23 +0,0 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
reactStrictMode: true,
|
||||
|
||||
basePath: "/explorer",
|
||||
assetPrefix: "/explorer",
|
||||
trailingSlash: false,
|
||||
|
||||
async redirects() {
|
||||
return [
|
||||
// Change the basePath to /explorer
|
||||
{
|
||||
source: "/",
|
||||
destination: "/explorer",
|
||||
basePath: false,
|
||||
permanent: true,
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
+1
-1
@@ -50,7 +50,7 @@ nym-api-requests = { path = "../nym-api/nym-api-requests" }
|
||||
nym-credentials = { path = "../common/credentials" }
|
||||
nym-credentials-interface = { path = "../common/credentials-interface" }
|
||||
nym-credential-verification = { path = "../common/credential-verification" }
|
||||
nym-crypto = { path = "../common/crypto" }
|
||||
nym-crypto = { path = "../common/crypto", features = ["sphinx"] }
|
||||
nym-gateway-storage = { path = "../common/gateway-storage" }
|
||||
nym-gateway-stats-storage = { path = "../common/gateway-stats-storage" }
|
||||
nym-gateway-requests = { path = "../common/gateway-requests" }
|
||||
|
||||
@@ -69,10 +69,7 @@ pub enum GatewayError {
|
||||
},
|
||||
|
||||
#[error("there was an issue with the local authenticator: {source}")]
|
||||
AuthenticatorFailure {
|
||||
#[from]
|
||||
source: AuthenticatorError,
|
||||
},
|
||||
AuthenticatorFailure { source: Box<AuthenticatorError> },
|
||||
|
||||
#[error("failed to startup local {typ}")]
|
||||
ServiceProviderStartupFailure { typ: &'static str },
|
||||
@@ -138,3 +135,11 @@ impl From<ClientCoreError> for GatewayError {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AuthenticatorError> for GatewayError {
|
||||
fn from(error: AuthenticatorError) -> Self {
|
||||
GatewayError::AuthenticatorFailure {
|
||||
source: Box::new(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ enum ActiveClient {
|
||||
Remote(RemoteClientData),
|
||||
|
||||
/// Handle to a locally (inside the same process) running client.
|
||||
Embedded(LocalEmbeddedClientHandle),
|
||||
Embedded(Box<LocalEmbeddedClientHandle>),
|
||||
}
|
||||
|
||||
impl ActiveClient {
|
||||
@@ -163,7 +163,7 @@ impl ActiveClientsStore {
|
||||
/// Inserts a handle to the embedded client
|
||||
pub fn insert_embedded(&self, local_client_handle: LocalEmbeddedClientHandle) {
|
||||
let key = local_client_handle.client_destination();
|
||||
let entry = ActiveClient::Embedded(local_client_handle);
|
||||
let entry = ActiveClient::Embedded(Box::new(local_client_handle));
|
||||
if self.inner.insert(key, entry).is_some() {
|
||||
// this is literally impossible since we're starting the local embedded client before
|
||||
// even spawning the websocket listener task
|
||||
|
||||
@@ -83,7 +83,7 @@ pub(crate) enum InitialAuthenticationError {
|
||||
UnexpectedMessageType { typ: String },
|
||||
|
||||
#[error("Experienced connection error: {0}")]
|
||||
ConnectionError(#[from] WsError),
|
||||
ConnectionError(Box<WsError>),
|
||||
|
||||
#[error("Attempted to negotiate connection with client using incompatible protocol version. Ours is {current} and the client reports {client:?}")]
|
||||
IncompatibleProtocol { client: Option<u8>, current: u8 },
|
||||
@@ -91,7 +91,7 @@ pub(crate) enum InitialAuthenticationError {
|
||||
#[error("failed to send authentication response: {source}")]
|
||||
ResponseSendFailure {
|
||||
#[source]
|
||||
source: WsError,
|
||||
source: Box<WsError>,
|
||||
},
|
||||
|
||||
#[error("possibly received a sphinx packet without prior authentication. Request is going to be ignored")]
|
||||
@@ -103,7 +103,7 @@ pub(crate) enum InitialAuthenticationError {
|
||||
#[error("failed to obtain message from websocket stream: {source}")]
|
||||
FailedToReadMessage {
|
||||
#[source]
|
||||
source: WsError,
|
||||
source: Box<WsError>,
|
||||
},
|
||||
|
||||
#[error("timed out while waiting for initial data")]
|
||||
@@ -113,6 +113,12 @@ pub(crate) enum InitialAuthenticationError {
|
||||
EmptyClientDetails,
|
||||
}
|
||||
|
||||
impl From<WsError> for InitialAuthenticationError {
|
||||
fn from(error: WsError) -> Self {
|
||||
InitialAuthenticationError::ConnectionError(Box::new(error))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct FreshHandler<R, S> {
|
||||
rng: R,
|
||||
pub(crate) shared_state: CommonHandlerState,
|
||||
@@ -163,7 +169,7 @@ impl<R, S> FreshHandler<R, S> {
|
||||
SocketStream::RawTcp(conn) => {
|
||||
// TODO: perhaps in the future, rather than panic here (and uncleanly shut tcp stream)
|
||||
// return a result with an error?
|
||||
let ws_stream = tokio_tungstenite::accept_async(conn).await?;
|
||||
let ws_stream = Box::new(tokio_tungstenite::accept_async(conn).await?);
|
||||
SocketStream::UpgradedWebSocket(ws_stream)
|
||||
}
|
||||
other => other,
|
||||
@@ -342,7 +348,7 @@ impl<R, S> FreshHandler<R, S> {
|
||||
// push them to the client
|
||||
if let Err(err) = self.push_packets_to_client(shared_keys, messages).await {
|
||||
warn!("We failed to send stored messages to fresh client - {err}",);
|
||||
return Err(InitialAuthenticationError::ConnectionError(err));
|
||||
return Err(InitialAuthenticationError::ConnectionError(Box::new(err)));
|
||||
} else {
|
||||
// if it was successful - remove them from the store
|
||||
self.shared_state.storage.remove_messages(ids).await?;
|
||||
@@ -880,7 +886,9 @@ impl<R, S> FreshHandler<R, S> {
|
||||
.await
|
||||
{
|
||||
debug!("failed to send authentication response: {source}");
|
||||
return Err(InitialAuthenticationError::ResponseSendFailure { source });
|
||||
return Err(InitialAuthenticationError::ResponseSendFailure {
|
||||
source: Box::new(source),
|
||||
});
|
||||
}
|
||||
|
||||
let Some(client_details) = auth_result.client_details else {
|
||||
@@ -963,7 +971,9 @@ impl<R, S> FreshHandler<R, S> {
|
||||
Ok(Some(Ok(msg))) => msg,
|
||||
Ok(Some(Err(source))) => {
|
||||
debug!("failed to obtain message from websocket stream! stopping connection handler: {source}");
|
||||
return Err(InitialAuthenticationError::FailedToReadMessage { source });
|
||||
return Err(InitialAuthenticationError::FailedToReadMessage {
|
||||
source: Box::new(source),
|
||||
});
|
||||
}
|
||||
Ok(None) => return Err(InitialAuthenticationError::ClosedConnection),
|
||||
Err(_timeout) => return Err(InitialAuthenticationError::Timeout),
|
||||
|
||||
@@ -31,7 +31,7 @@ const INITIAL_MESSAGE_TIMEOUT: Duration = Duration::from_millis(10_000);
|
||||
|
||||
pub(crate) enum SocketStream<S> {
|
||||
RawTcp(S),
|
||||
UpgradedWebSocket(WebSocketStream<S>),
|
||||
UpgradedWebSocket(Box<WebSocketStream<S>>),
|
||||
Invalid,
|
||||
}
|
||||
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT epoch_id as \"epoch_id: u32\", serialised_signatures, serialization_revision as \"serialization_revision: u8\"\n FROM expiration_date_signatures\n WHERE expiration_date = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "epoch_id: u32",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "serialised_signatures",
|
||||
"ordinal": 1,
|
||||
"type_info": "Blob"
|
||||
},
|
||||
{
|
||||
"name": "serialization_revision: u8",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "00d857b624e7edab1198114b17cbad1e16988a3f9989d135840500e1143ce5e5"
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT epoch_id as \"epoch_id: u32\", serialised_key, serialization_revision as \"serialization_revision: u8\"\n FROM master_verification_key WHERE epoch_id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "epoch_id: u32",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "serialised_key",
|
||||
"ordinal": 1,
|
||||
"type_info": "Blob"
|
||||
},
|
||||
{
|
||||
"name": "serialization_revision: u8",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "0112296b190328a3856d1adf51aafa2525da6c0b871633aad80ad555db9cf47c"
|
||||
}
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT COUNT(*) as count FROM simulated_node_performance WHERE simulated_epoch_id = ?",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "count",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "0557e64c547e147ef7eef713b49c62afde62b3b04ff817ae372ffe9fbeaee6b8"
|
||||
}
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT COUNT(*) as count FROM simulated_reward_epochs",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "count",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "1411660a6456f6f5ca141db10d9a54857fc8b4a661e09ae6719d84ac2e77cf6d"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT OR IGNORE INTO expiration_date_signatures(expiration_date, epoch_id, serialised_signatures, serialization_revision)\n VALUES (?, ?, ?, ?);\n UPDATE expiration_date_signatures\n SET\n serialised_signatures = ?,\n serialization_revision = ?\n WHERE expiration_date = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 7
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "16d10f0ac0ed9ce4239937f46df3797a6a9ee7db2aab9f1b5e55f7c13c53bcc1"
|
||||
}
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT id as \"id!\", epoch_id as \"epoch_id!: u32\", calculation_method as \"calculation_method!\", \n start_timestamp as \"start_timestamp!\", end_timestamp as \"end_timestamp!\", \n description, created_at as \"created_at!\"\n FROM simulated_reward_epochs\n WHERE id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "epoch_id!: u32",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "calculation_method!",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "start_timestamp!",
|
||||
"ordinal": 3,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "end_timestamp!",
|
||||
"ordinal": 4,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"ordinal": 5,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created_at!",
|
||||
"ordinal": 6,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "1e4daea4d5f87938cf98cb2b6facc5802e79b06f3d0d41f6467844265b26d0f7"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO ecash_ticketbook\n (serialization_revision, ticketbook_data, expiration_date, ticketbook_type, epoch_id, total_tickets, used_tickets)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 7
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "284b3ceae42f9320c30323dde47765854899103fd3c0fa670eb6809492270e02"
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO simulated_reward_epochs \n (epoch_id, calculation_method, start_timestamp, end_timestamp, description)\n VALUES (?, ?, ?, ?, ?)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 5
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "2bb062a25129bfc6b060f68592e74aa91d8c82b8b912009fce14f9c19fd4464c"
|
||||
}
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT id as \"id!\", simulated_epoch_id as \"simulated_epoch_id!\", \n node_id as \"node_id!: NodeId\", calculation_method as \"calculation_method!\", \n performance_rank as \"performance_rank!\", performance_percentile as \"performance_percentile!\",\n calculated_at as \"calculated_at!\"\n FROM simulated_performance_rankings\n WHERE simulated_epoch_id = ? AND calculation_method = ?\n ORDER BY performance_rank\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "simulated_epoch_id!",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "node_id!: NodeId",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "calculation_method!",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "performance_rank!",
|
||||
"ordinal": 4,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "performance_percentile!",
|
||||
"ordinal": 5,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "calculated_at!",
|
||||
"ordinal": 6,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "3024d5e589b1f50f95f5e7bf09e31161beca74e137e340d2a6a02a589ad251cb"
|
||||
}
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT id as \"id!\", simulated_epoch_id as \"simulated_epoch_id!\", \n node_id as \"node_id!: NodeId\", calculation_method as \"calculation_method!\", \n performance_rank as \"performance_rank!\", performance_percentile as \"performance_percentile!\",\n calculated_at as \"calculated_at!\"\n FROM simulated_performance_rankings\n WHERE simulated_epoch_id = ?\n ORDER BY calculation_method, performance_rank\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "simulated_epoch_id!",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "node_id!: NodeId",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "calculation_method!",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "performance_rank!",
|
||||
"ordinal": 4,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "performance_percentile!",
|
||||
"ordinal": 5,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "calculated_at!",
|
||||
"ordinal": 6,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "33108405de0328977ac5d05d89f484bdb127f9cba0ba5370c931e1225983d74a"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "DELETE FROM ecash_ticketbook WHERE expiration_date <= ?",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "37f82c9ec26b53d01601a2d6df82038a77ec37cca9f9aef18008dcd03030c2c4"
|
||||
}
|
||||
-74
@@ -1,74 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT sra.id as \"id!\", sra.simulated_epoch_id as \"simulated_epoch_id!\", \n sra.calculation_method as \"calculation_method!\", sra.total_routes_analyzed as \"total_routes_analyzed!: u32\", \n sra.successful_routes as \"successful_routes!: u32\", sra.failed_routes as \"failed_routes!: u32\", \n sra.average_route_reliability, sra.time_window_hours as \"time_window_hours!: u32\", \n sra.analysis_parameters, sra.calculated_at as \"calculated_at!\"\n FROM simulated_route_analysis sra\n JOIN simulated_reward_epochs sre ON sra.simulated_epoch_id = sre.id\n WHERE sre.epoch_id = ? AND sra.calculation_method = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "simulated_epoch_id!",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "calculation_method!",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "total_routes_analyzed!: u32",
|
||||
"ordinal": 3,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "successful_routes!: u32",
|
||||
"ordinal": 4,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "failed_routes!: u32",
|
||||
"ordinal": 5,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "average_route_reliability",
|
||||
"ordinal": 6,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "time_window_hours!: u32",
|
||||
"ordinal": 7,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "analysis_parameters",
|
||||
"ordinal": 8,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "calculated_at!",
|
||||
"ordinal": 9,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "4e2338362058e4356ac639fdf806e827761f879cab723cb2a649bc76b176fdad"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "DELETE FROM pending_issuance WHERE deposit_id = ?",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "5c5d4bfabf18bc6fa56e76a9b98e38b7f6ceb8e9191a7b9201922efcf6b07966"
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT OR IGNORE INTO routes (layer1, layer2, layer3, gw, success) VALUES (?, ?, ?, ?, ?);\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 5
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "66109c1d856e1ca2b5126e4bf4c58c7a27b8c303bfa079cf74909354202dcc49"
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n d.node_id as \"node_id: NodeId\",\n CASE WHEN count(*) > 3 THEN AVG(reliability) ELSE 100 END as \"value: f32\"\n FROM\n gateway_details d\n JOIN\n gateway_status s on d.id = s.gateway_details_id\n WHERE\n timestamp >= ? AND\n timestamp <= ?\n GROUP BY 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "node_id: NodeId",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "value: f32",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "676299beb2004ab89f7b38cf21ffb84ab5e7d7435297573523e2532560c2e302"
|
||||
}
|
||||
-80
@@ -1,80 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT id as \"id!\", simulated_epoch_id as \"simulated_epoch_id!\", node_id as \"node_id!: NodeId\", \n node_type as \"node_type!\", performance_score as \"performance_score!\", \n work_factor as \"work_factor!\", calculation_method as \"calculation_method!\", \n positive_samples as \"positive_samples?\", negative_samples as \"negative_samples?\",\n route_success_rate as \"route_success_rate?\", calculated_at as \"calculated_at!\"\n FROM simulated_performance_comparisons\n WHERE simulated_epoch_id = ?\n ORDER BY calculation_method, node_id\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "simulated_epoch_id!",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "node_id!: NodeId",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "node_type!",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "performance_score!",
|
||||
"ordinal": 4,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "work_factor!",
|
||||
"ordinal": 5,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "calculation_method!",
|
||||
"ordinal": 6,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "positive_samples?",
|
||||
"ordinal": 7,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "negative_samples?",
|
||||
"ordinal": 8,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "route_success_rate?",
|
||||
"ordinal": 9,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "calculated_at!",
|
||||
"ordinal": 10,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "6a7b25d2c580298bd44fcf74e4d2c550537c64dbfb2c17a6c91bd5345b9a3e6a"
|
||||
}
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n layer1 as \"layer1\",\n layer2 as \"layer2\",\n layer3 as \"layer3\",\n gw as \"gw\",\n success\n FROM routes\n WHERE timestamp >= ? AND timestamp <= ?\n ORDER BY timestamp ASC\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "layer1",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "layer2",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "layer3",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "gw",
|
||||
"ordinal": 3,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "success",
|
||||
"ordinal": 4,
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "6b2479c02cf1ef5ae674ce0ab4d027595b91739f3579e1f289b0c722ea91bbcc"
|
||||
}
|
||||
-80
@@ -1,80 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT snp.id as \"id!\", snp.simulated_epoch_id as \"simulated_epoch_id!\", \n snp.node_id as \"node_id!: NodeId\", snp.node_type as \"node_type!\", \n snp.identity_key, snp.reliability_score as \"reliability_score!\", \n snp.positive_samples as \"positive_samples!: u32\", snp.negative_samples as \"negative_samples!: u32\", \n snp.work_factor, snp.calculation_method as \"calculation_method!\", snp.calculated_at as \"calculated_at!\"\n FROM simulated_node_performance snp\n JOIN simulated_reward_epochs sre ON snp.simulated_epoch_id = sre.id\n WHERE snp.node_id = ?\n ORDER BY sre.epoch_id DESC, snp.calculation_method\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "simulated_epoch_id!",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "node_id!: NodeId",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "node_type!",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "identity_key",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "reliability_score!",
|
||||
"ordinal": 5,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "positive_samples!: u32",
|
||||
"ordinal": 6,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "negative_samples!: u32",
|
||||
"ordinal": 7,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "work_factor",
|
||||
"ordinal": 8,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "calculation_method!",
|
||||
"ordinal": 9,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "calculated_at!",
|
||||
"ordinal": 10,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "6fa6967e77886b69b049c68b664cdf8f4b97daa3734036db16e49915fd89073a"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO pending_issuance\n (deposit_id, serialization_revision, pending_ticketbook_data, expiration_date)\n VALUES (?, ?, ?, ?)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 4
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "81a12a8a419c88b1c28a5533fde4d63462e9ea0049e2edafea1dc3f8476b33e4"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "UPDATE ecash_ticketbook SET used_tickets = used_tickets + ? WHERE id = ?",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "84cad8b1078a4000830835e6349de3eb76fed954b7336530401db72cd008aff3"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT OR IGNORE INTO master_verification_key(epoch_id, serialised_key, serialization_revision) VALUES (?, ?, ?);\n UPDATE master_verification_key\n SET\n serialised_key = ?,\n serialization_revision = ?\n WHERE epoch_id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 6
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a5b18e66d77ff802e274623605e15dcfcffb502ba8398caefd56c481f44eb84e"
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT epoch_id as \"epoch_id: u32\", serialised_signatures, serialization_revision as \"serialization_revision: u8\"\n FROM coin_indices_signatures WHERE epoch_id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "epoch_id: u32",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "serialised_signatures",
|
||||
"ordinal": 1,
|
||||
"type_info": "Blob"
|
||||
},
|
||||
{
|
||||
"name": "serialization_revision: u8",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "ba96344db31b0f2155e2af53eaaeafc9b5f64061b6c9a829e2912945b6cffc82"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n UPDATE ecash_ticketbook\n SET used_tickets = used_tickets - ?\n WHERE id = ?\n AND used_tickets = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "bc823c54143e2dc590b91347cd089dde284b38a3a4960afed758206d03ca1cf4"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT OR IGNORE INTO coin_indices_signatures(epoch_id, serialised_signatures, serialization_revision) VALUES (?, ?, ?);\n UPDATE coin_indices_signatures\n SET\n serialised_signatures = ?,\n serialization_revision = ?\n WHERE epoch_id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 6
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "bd1973696121b6128bd75ae80fab253c071e04eb853d4b0f3b21782ea57c2f68"
|
||||
}
|
||||
-80
@@ -1,80 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT id as \"id!\", simulated_epoch_id as \"simulated_epoch_id!\", node_id as \"node_id!: NodeId\", \n node_type as \"node_type!\", identity_key, reliability_score as \"reliability_score!\", \n positive_samples as \"positive_samples!: u32\", negative_samples as \"negative_samples!: u32\", \n work_factor, calculation_method as \"calculation_method!\", calculated_at as \"calculated_at!\"\n FROM simulated_node_performance\n WHERE simulated_epoch_id = ?\n ORDER BY calculation_method, node_id\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "simulated_epoch_id!",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "node_id!: NodeId",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "node_type!",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "identity_key",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "reliability_score!",
|
||||
"ordinal": 5,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "positive_samples!: u32",
|
||||
"ordinal": 6,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "negative_samples!: u32",
|
||||
"ordinal": 7,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "work_factor",
|
||||
"ordinal": 8,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "calculation_method!",
|
||||
"ordinal": 9,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "calculated_at!",
|
||||
"ordinal": 10,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "c04c33631eea6959267dd441f50c2bd876ba951e32e8b8bee398cf98fec5e7bf"
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n d.mix_id as \"mix_id: NodeId\",\n AVG(s.reliability) as \"value: f32\"\n FROM\n mixnode_details d\n JOIN\n mixnode_status s on d.id = s.mixnode_details_id\n WHERE\n timestamp >= ? AND\n timestamp <= ?\n GROUP BY 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "mix_id: NodeId",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "value: f32",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "c19e1b3768bf2929407599e6e8783ead09f4d7319b7997fa2a9bb628f9404166"
|
||||
}
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT DISTINCT calculation_method FROM simulated_reward_epochs WHERE epoch_id = ?",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "calculation_method",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "c9796e61c49a29ebd42dec46fb047f7a7e2f80aa4db4f963146cf56864a62347"
|
||||
}
|
||||
-80
@@ -1,80 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT id as \"id!\", simulated_epoch_id as \"simulated_epoch_id!\", node_id as \"node_id!: NodeId\", \n node_type as \"node_type!\", identity_key, reliability_score as \"reliability_score!\", \n positive_samples as \"positive_samples!: u32\", negative_samples as \"negative_samples!: u32\", \n work_factor, calculation_method as \"calculation_method!\", calculated_at as \"calculated_at!\"\n FROM simulated_node_performance\n WHERE simulated_epoch_id = ? AND calculation_method = ?\n ORDER BY node_id\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "simulated_epoch_id!",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "node_id!: NodeId",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "node_type!",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "identity_key",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "reliability_score!",
|
||||
"ordinal": 5,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "positive_samples!: u32",
|
||||
"ordinal": 6,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "negative_samples!: u32",
|
||||
"ordinal": 7,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "work_factor",
|
||||
"ordinal": 8,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "calculation_method!",
|
||||
"ordinal": 9,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "calculated_at!",
|
||||
"ordinal": 10,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "d76e131fff79978284f31509e70d42d20ea2e95e5a2f2d574d33ad8d2a7f2a19"
|
||||
}
|
||||
-74
@@ -1,74 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT id as \"id!\", simulated_epoch_id as \"simulated_epoch_id!\", \n calculation_method as \"calculation_method!\", total_routes_analyzed as \"total_routes_analyzed!: u32\", \n successful_routes as \"successful_routes!: u32\", failed_routes as \"failed_routes!: u32\", \n average_route_reliability, time_window_hours as \"time_window_hours!: u32\", \n analysis_parameters, calculated_at as \"calculated_at!\"\n FROM simulated_route_analysis\n WHERE simulated_epoch_id = ?\n ORDER BY calculation_method\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "simulated_epoch_id!",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "calculation_method!",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "total_routes_analyzed!: u32",
|
||||
"ordinal": 3,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "successful_routes!: u32",
|
||||
"ordinal": 4,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "failed_routes!: u32",
|
||||
"ordinal": 5,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "average_route_reliability",
|
||||
"ordinal": 6,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "time_window_hours!: u32",
|
||||
"ordinal": 7,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "analysis_parameters",
|
||||
"ordinal": 8,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "calculated_at!",
|
||||
"ordinal": 9,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "db2b71d1dd33022d5c960053d6a0f5d7ccf52de6775d4f57744d1401d44fee62"
|
||||
}
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT id as \"id!\", epoch_id as \"epoch_id!: u32\", calculation_method as \"calculation_method!\", \n start_timestamp as \"start_timestamp!\", end_timestamp as \"end_timestamp!\", \n description, created_at as \"created_at!\"\n FROM simulated_reward_epochs \n ORDER BY created_at DESC \n LIMIT ? OFFSET ?",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "epoch_id!: u32",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "calculation_method!",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "start_timestamp!",
|
||||
"ordinal": 3,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "end_timestamp!",
|
||||
"ordinal": 4,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"ordinal": 5,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created_at!",
|
||||
"ordinal": 6,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "dbc52e443ab600516b4f96ab2904d6b6379c06cfa1ff8598c29b67a2fe37055d"
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO simulated_route_analysis\n (simulated_epoch_id, calculation_method, total_routes_analyzed, successful_routes,\n failed_routes, average_route_reliability, time_window_hours, analysis_parameters)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 8
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "df9013912c0e4177625267776656cb139cf3ac8dd4cd4c5146005b055aefd228"
|
||||
}
|
||||
-80
@@ -1,80 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT snp.id as \"id!\", snp.simulated_epoch_id as \"simulated_epoch_id!\", \n snp.node_id as \"node_id!: NodeId\", snp.node_type as \"node_type!\", \n snp.identity_key, snp.reliability_score as \"reliability_score!\", \n snp.positive_samples as \"positive_samples!: u32\", snp.negative_samples as \"negative_samples!: u32\", \n snp.work_factor, snp.calculation_method as \"calculation_method!\", snp.calculated_at as \"calculated_at!\"\n FROM simulated_node_performance snp\n JOIN simulated_reward_epochs sre ON snp.simulated_epoch_id = sre.id\n WHERE sre.epoch_id = ? AND snp.calculation_method = ?\n ORDER BY snp.node_id\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "simulated_epoch_id!",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "node_id!: NodeId",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "node_type!",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "identity_key",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "reliability_score!",
|
||||
"ordinal": 5,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "positive_samples!: u32",
|
||||
"ordinal": 6,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "negative_samples!: u32",
|
||||
"ordinal": 7,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "work_factor",
|
||||
"ordinal": 8,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "calculation_method!",
|
||||
"ordinal": 9,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "calculated_at!",
|
||||
"ordinal": 10,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "e2a8ebf260e0e3dd25d614a3c9f70715d0b593cf19d0a5f5c7972ac5925151c3"
|
||||
}
|
||||
-80
@@ -1,80 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT id as \"id!\", simulated_epoch_id as \"simulated_epoch_id!\", node_id as \"node_id!: NodeId\", \n node_type as \"node_type!\", performance_score as \"performance_score!\", \n work_factor as \"work_factor!\", calculation_method as \"calculation_method!\", \n positive_samples as \"positive_samples?\", negative_samples as \"negative_samples?\",\n route_success_rate as \"route_success_rate?\", calculated_at as \"calculated_at!\"\n FROM simulated_performance_comparisons\n WHERE simulated_epoch_id = ? AND calculation_method = ?\n ORDER BY node_id\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "simulated_epoch_id!",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "node_id!: NodeId",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "node_type!",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "performance_score!",
|
||||
"ordinal": 4,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "work_factor!",
|
||||
"ordinal": 5,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "calculation_method!",
|
||||
"ordinal": 6,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "positive_samples?",
|
||||
"ordinal": 7,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "negative_samples?",
|
||||
"ordinal": 8,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "route_success_rate?",
|
||||
"ordinal": 9,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "calculated_at!",
|
||||
"ordinal": 10,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "e8a88007b46202c6dcd87c2f964e366fa2b6fe0c0fd71ce48a544d51c679a9c8"
|
||||
}
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
[package]
|
||||
name = "nym-api"
|
||||
license = "GPL-3.0"
|
||||
version = "1.1.67"
|
||||
version = "1.1.60"
|
||||
authors.workspace = true
|
||||
edition = "2021"
|
||||
rust-version.workspace = true
|
||||
|
||||
@@ -2,11 +2,4 @@
|
||||
|
||||
set -e
|
||||
|
||||
# Optional: delete existing config and force reinit
|
||||
if [ "$NYM_API_RESET_CONFIG" = "true" ]; then
|
||||
echo "RESET_CONFIG enabled - removing existing configuration..."
|
||||
rm -rf ~/.nym/nym-api/default/*
|
||||
fi
|
||||
|
||||
# Init can fail if the mounted volume already has a config
|
||||
/usr/src/nym/target/release/nym-api init --mnemonic "$MNEMONIC" || true && /usr/src/nym/target/release/nym-api run --mnemonic "$MNEMONIC"
|
||||
/usr/src/nym/target/release/nym-api init && /usr/src/nym/target/release/nym-api run
|
||||
@@ -1,17 +0,0 @@
|
||||
-- Add routes table for storing route metrics data
|
||||
CREATE TABLE IF NOT EXISTS routes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
layer1 INTEGER NOT NULL, -- NodeId of layer 1 mixnode
|
||||
layer2 INTEGER NOT NULL, -- NodeId of layer 2 mixnode
|
||||
layer3 INTEGER NOT NULL, -- NodeId of layer 3 mixnode
|
||||
gw INTEGER NOT NULL, -- NodeId of gateway
|
||||
success BOOLEAN NOT NULL, -- Whether the packet was delivered successfully
|
||||
timestamp INTEGER NOT NULL DEFAULT (unixepoch()) -- When the measurement was taken
|
||||
);
|
||||
|
||||
-- Add indexes for efficient querying
|
||||
CREATE INDEX IF NOT EXISTS idx_routes_timestamp ON routes(timestamp);
|
||||
CREATE INDEX IF NOT EXISTS idx_routes_layer1 ON routes(layer1);
|
||||
CREATE INDEX IF NOT EXISTS idx_routes_layer2 ON routes(layer2);
|
||||
CREATE INDEX IF NOT EXISTS idx_routes_layer3 ON routes(layer3);
|
||||
CREATE INDEX IF NOT EXISTS idx_routes_gw ON routes(gw);
|
||||
@@ -1,100 +0,0 @@
|
||||
-- Migration: Add performance methodology comparison tables
|
||||
-- This migration adds support for comparing performance calculation methodologies:
|
||||
-- old (24h cache-based) vs new (1h route-based) without affecting actual rewards
|
||||
|
||||
-- Simulated reward epochs track each simulation run
|
||||
CREATE TABLE IF NOT EXISTS simulated_reward_epochs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
epoch_id INTEGER NOT NULL,
|
||||
calculation_method TEXT NOT NULL CHECK (calculation_method IN ('old', 'new', 'comparison', 'test')),
|
||||
start_timestamp INTEGER NOT NULL,
|
||||
end_timestamp INTEGER NOT NULL,
|
||||
description TEXT,
|
||||
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
||||
);
|
||||
|
||||
-- Node performance data calculated from different methodologies
|
||||
CREATE TABLE IF NOT EXISTS simulated_node_performance (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
simulated_epoch_id INTEGER NOT NULL,
|
||||
node_id INTEGER NOT NULL,
|
||||
node_type TEXT NOT NULL CHECK (node_type IN ('mixnode', 'gateway')),
|
||||
identity_key TEXT,
|
||||
reliability_score REAL NOT NULL CHECK (reliability_score >= 0.0 AND reliability_score <= 100.0),
|
||||
positive_samples INTEGER NOT NULL DEFAULT 0,
|
||||
negative_samples INTEGER NOT NULL DEFAULT 0,
|
||||
work_factor REAL CHECK (work_factor >= 0.0 AND work_factor <= 1.0),
|
||||
calculation_method TEXT NOT NULL CHECK (calculation_method IN ('old', 'new', 'test')),
|
||||
calculated_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
||||
FOREIGN KEY (simulated_epoch_id) REFERENCES simulated_reward_epochs(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Performance comparison data for analyzing methodology differences
|
||||
CREATE TABLE IF NOT EXISTS simulated_performance_comparisons (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
simulated_epoch_id INTEGER NOT NULL,
|
||||
node_id INTEGER NOT NULL,
|
||||
node_type TEXT NOT NULL CHECK (node_type IN ('mixnode', 'gateway')),
|
||||
-- Performance scores from each methodology
|
||||
performance_score REAL NOT NULL CHECK (performance_score >= 0.0 AND performance_score <= 100.0),
|
||||
work_factor REAL NOT NULL CHECK (work_factor >= 0.0),
|
||||
calculation_method TEXT NOT NULL CHECK (calculation_method IN ('old', 'new', 'test')),
|
||||
-- Additional metrics for analysis
|
||||
positive_samples INTEGER DEFAULT 0,
|
||||
negative_samples INTEGER DEFAULT 0,
|
||||
route_success_rate REAL CHECK (route_success_rate >= 0.0 AND route_success_rate <= 100.0),
|
||||
calculated_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
||||
FOREIGN KEY (simulated_epoch_id) REFERENCES simulated_reward_epochs(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Route analysis metadata for each simulation run
|
||||
CREATE TABLE IF NOT EXISTS simulated_route_analysis (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
simulated_epoch_id INTEGER NOT NULL,
|
||||
calculation_method TEXT NOT NULL CHECK (calculation_method IN ('old', 'new', 'test')),
|
||||
total_routes_analyzed INTEGER NOT NULL DEFAULT 0,
|
||||
successful_routes INTEGER NOT NULL DEFAULT 0,
|
||||
failed_routes INTEGER NOT NULL DEFAULT 0,
|
||||
average_route_reliability REAL CHECK (average_route_reliability >= 0.0 AND average_route_reliability <= 100.0),
|
||||
time_window_hours INTEGER NOT NULL DEFAULT 1, -- New method uses 1 hour, old uses 24
|
||||
analysis_parameters TEXT, -- JSON with additional analysis configuration
|
||||
calculated_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
||||
FOREIGN KEY (simulated_epoch_id) REFERENCES simulated_reward_epochs(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Performance rankings and comparison analytics
|
||||
CREATE TABLE IF NOT EXISTS simulated_performance_rankings (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
simulated_epoch_id INTEGER NOT NULL,
|
||||
node_id INTEGER NOT NULL,
|
||||
calculation_method TEXT NOT NULL CHECK (calculation_method IN ('old', 'new', 'test')),
|
||||
performance_rank INTEGER NOT NULL,
|
||||
performance_percentile REAL NOT NULL CHECK (performance_percentile >= 0.0 AND performance_percentile <= 100.0),
|
||||
calculated_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
||||
FOREIGN KEY (simulated_epoch_id) REFERENCES simulated_reward_epochs(id) ON DELETE CASCADE,
|
||||
UNIQUE(simulated_epoch_id, node_id, calculation_method)
|
||||
);
|
||||
|
||||
-- Indexes for efficient querying
|
||||
CREATE INDEX IF NOT EXISTS idx_simulated_reward_epochs_epoch_id ON simulated_reward_epochs(epoch_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_simulated_reward_epochs_calculation_method ON simulated_reward_epochs(calculation_method);
|
||||
CREATE INDEX IF NOT EXISTS idx_simulated_reward_epochs_created_at ON simulated_reward_epochs(created_at);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_simulated_node_performance_simulated_epoch_id ON simulated_node_performance(simulated_epoch_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_simulated_node_performance_node_id ON simulated_node_performance(node_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_simulated_node_performance_calculation_method ON simulated_node_performance(calculation_method);
|
||||
CREATE INDEX IF NOT EXISTS idx_simulated_node_performance_node_type ON simulated_node_performance(node_type);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_simulated_performance_comparisons_simulated_epoch_id ON simulated_performance_comparisons(simulated_epoch_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_simulated_performance_comparisons_node_id ON simulated_performance_comparisons(node_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_simulated_performance_comparisons_calculation_method ON simulated_performance_comparisons(calculation_method);
|
||||
CREATE INDEX IF NOT EXISTS idx_simulated_performance_comparisons_node_type ON simulated_performance_comparisons(node_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_simulated_performance_comparisons_performance_score ON simulated_performance_comparisons(performance_score);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_simulated_route_analysis_simulated_epoch_id ON simulated_route_analysis(simulated_epoch_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_simulated_route_analysis_calculation_method ON simulated_route_analysis(calculation_method);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_simulated_performance_rankings_simulated_epoch_id ON simulated_performance_rankings(simulated_epoch_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_simulated_performance_rankings_node_id ON simulated_performance_rankings(node_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_simulated_performance_rankings_calculation_method ON simulated_performance_rankings(calculation_method);
|
||||
CREATE INDEX IF NOT EXISTS idx_simulated_performance_rankings_performance_rank ON simulated_performance_rankings(performance_rank);
|
||||
@@ -16,7 +16,7 @@ pub enum DkgError {
|
||||
StatePersistenceFailure {
|
||||
path: PathBuf,
|
||||
#[source]
|
||||
source: EcashError,
|
||||
source: Box<EcashError>,
|
||||
},
|
||||
|
||||
#[error("failed to query for the current DKG epoch state: {source}")]
|
||||
|
||||
@@ -73,7 +73,7 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
persistent_state.save_to_file(save_path).map_err(|source| {
|
||||
DkgError::StatePersistenceFailure {
|
||||
path: save_path.to_path_buf(),
|
||||
source,
|
||||
source: Box::new(source),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1275,7 +1275,7 @@ impl TestFixture {
|
||||
AppState {
|
||||
nyxd_client,
|
||||
chain_status_cache: ChainStatusCache::new(Duration::from_secs(42)),
|
||||
address_info_cache: AddressInfoCache::new(),
|
||||
address_info_cache: AddressInfoCache::new(Duration::from_secs(42), 1000),
|
||||
forced_refresh: ForcedRefresh::new(true),
|
||||
nym_contract_cache: NymContractCache::new(),
|
||||
node_status_cache: NodeStatusCache::new(),
|
||||
|
||||
@@ -59,9 +59,6 @@ pub enum RewardingError {
|
||||
#[error("could not obtain the current interval rewarding parameters")]
|
||||
RewardingParamsRetrievalFailure,
|
||||
|
||||
#[error("Database operation failed: {source}")]
|
||||
DatabaseError { source: anyhow::Error },
|
||||
|
||||
#[error("{0}")]
|
||||
GenericError(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ use error::RewardingError;
|
||||
pub(crate) use helpers::RewardedNodeWithParams;
|
||||
use nym_mixnet_contract_common::{CurrentIntervalResponse, Interval};
|
||||
use nym_task::{TaskClient, TaskManager};
|
||||
use simulation::SimulationConfig;
|
||||
use std::collections::HashSet;
|
||||
use std::time::Duration;
|
||||
use tokio::time::sleep;
|
||||
@@ -33,7 +32,6 @@ mod event_reconciliation;
|
||||
mod helpers;
|
||||
mod rewarded_set_assignment;
|
||||
mod rewarding;
|
||||
pub(crate) mod simulation;
|
||||
mod transition_beginning;
|
||||
|
||||
// naming things is difficult, ok?
|
||||
@@ -44,7 +42,6 @@ pub struct EpochAdvancer {
|
||||
described_cache: SharedCache<DescribedNodes>,
|
||||
status_cache: NodeStatusCache,
|
||||
storage: NymApiStorage,
|
||||
simulation_config: Option<SimulationConfig>,
|
||||
}
|
||||
|
||||
impl EpochAdvancer {
|
||||
@@ -60,7 +57,6 @@ impl EpochAdvancer {
|
||||
status_cache: NodeStatusCache,
|
||||
described_cache: SharedCache<DescribedNodes>,
|
||||
storage: NymApiStorage,
|
||||
simulation_config: Option<SimulationConfig>,
|
||||
) -> Self {
|
||||
EpochAdvancer {
|
||||
nyxd_client,
|
||||
@@ -68,7 +64,6 @@ impl EpochAdvancer {
|
||||
described_cache,
|
||||
status_cache,
|
||||
storage,
|
||||
simulation_config,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,81 +136,29 @@ impl EpochAdvancer {
|
||||
|
||||
if epoch_status.being_advanced_by.as_str() != address.as_ref() {
|
||||
// another nym-api is already handling
|
||||
// In simulation mode, we want to proceed anyway since we're not actually advancing the epoch
|
||||
if self.simulation_config.is_some() {
|
||||
info!("Another nym-api ({}) is advancing the epoch, but we're in simulation mode so proceeding anyway", epoch_status.being_advanced_by);
|
||||
} else {
|
||||
error!("another nym-api ({}) is already advancing the epoch... but we shouldn't have other nym-apis yet!", epoch_status.being_advanced_by);
|
||||
return Ok(());
|
||||
}
|
||||
error!("another nym-api ({}) is already advancing the epoch... but we shouldn't have other nym-apis yet!", epoch_status.being_advanced_by);
|
||||
return Ok(());
|
||||
} else {
|
||||
warn!("we seem to have crashed mid-epoch advancement...");
|
||||
}
|
||||
} else {
|
||||
// In simulation mode, skip trying to begin epoch transition
|
||||
if self.simulation_config.is_none() {
|
||||
let should_continue = self.begin_epoch_transition().await?;
|
||||
if !should_continue {
|
||||
return Ok(());
|
||||
}
|
||||
} else {
|
||||
info!("Skipping epoch transition in simulation mode - proceeding to performance calculations");
|
||||
let should_continue = self.begin_epoch_transition().await?;
|
||||
if !should_continue {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// Run simulation if enabled (before actual rewarding)
|
||||
if let Some(simulation_config) = &self.simulation_config {
|
||||
info!(
|
||||
"Running reward simulation for epoch {}",
|
||||
interval.current_epoch_absolute_id()
|
||||
);
|
||||
let rewarded_set = match self.nyxd_client.get_rewarded_set_nodes().await {
|
||||
Ok(rewarded_set) => rewarded_set,
|
||||
Err(err) => {
|
||||
warn!("Failed to obtain current rewarded set for simulation: {err}. Falling back to cached version");
|
||||
self.nym_contract_cache
|
||||
.rewarded_set_owned()
|
||||
.await
|
||||
.into_inner()
|
||||
.into()
|
||||
}
|
||||
};
|
||||
// Reward all the nodes in the still current, soon to be previous rewarded set
|
||||
info!("Rewarding the current rewarded set...");
|
||||
self.reward_current_rewarded_set(rewards, interval).await?;
|
||||
|
||||
if let Some(reward_params) = self
|
||||
.nym_contract_cache
|
||||
.interval_reward_params()
|
||||
.await
|
||||
.into_inner()
|
||||
{
|
||||
let _ = self
|
||||
.run_simulation_if_enabled(
|
||||
&rewarded_set,
|
||||
reward_params,
|
||||
interval.current_epoch_absolute_id(),
|
||||
simulation_config.clone(),
|
||||
)
|
||||
.await;
|
||||
} else {
|
||||
warn!("Could not obtain reward parameters for simulation");
|
||||
}
|
||||
}
|
||||
// note: those operations don't really have to be atomic, so it's fine to send them
|
||||
// as separate transactions
|
||||
self.reconcile_epoch_events().await?;
|
||||
self.update_rewarded_set_and_advance_epoch(&nym_nodes)
|
||||
.await?;
|
||||
|
||||
// Skip actual rewarding operations in simulation mode
|
||||
if self.simulation_config.is_none() {
|
||||
// Reward all the nodes in the still current, soon to be previous rewarded set
|
||||
info!("Rewarding the current rewarded set...");
|
||||
self.reward_current_rewarded_set(rewards, interval).await?;
|
||||
|
||||
// note: those operations don't really have to be atomic, so it's fine to send them
|
||||
// as separate transactions
|
||||
self.reconcile_epoch_events().await?;
|
||||
self.update_rewarded_set_and_advance_epoch(&nym_nodes)
|
||||
.await?;
|
||||
} else {
|
||||
info!("Skipping actual reward distribution in simulation mode");
|
||||
}
|
||||
|
||||
info!("Purging old data (node statuses and routes) from the storage...");
|
||||
info!("Purging old node statuses from the storage...");
|
||||
let cutoff = (epoch_end - 2 * ONE_DAY).unix_timestamp();
|
||||
self.storage.purge_old_statuses(cutoff).await?;
|
||||
|
||||
@@ -354,7 +297,6 @@ impl EpochAdvancer {
|
||||
status_cache: &NodeStatusCache,
|
||||
described_cache: SharedCache<DescribedNodes>,
|
||||
storage: &NymApiStorage,
|
||||
simulation_config: Option<SimulationConfig>,
|
||||
shutdown: &TaskManager,
|
||||
) {
|
||||
let mut rewarded_set_updater = EpochAdvancer::new(
|
||||
@@ -363,7 +305,6 @@ impl EpochAdvancer {
|
||||
status_cache.to_owned(),
|
||||
described_cache,
|
||||
storage.to_owned(),
|
||||
simulation_config,
|
||||
);
|
||||
let shutdown_listener = shutdown.subscribe();
|
||||
tokio::spawn(async move { rewarded_set_updater.run(shutdown_listener).await });
|
||||
|
||||
@@ -1,700 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
//! Performance methodology comparison system
|
||||
//!
|
||||
//! This module provides functionality to compare different performance calculation
|
||||
//! methodologies without affecting actual rewards, enabling analysis of:
|
||||
//! - Old method: 24-hour cache-based performance calculation
|
||||
//! - New method: 1-hour route-based performance calculation
|
||||
//!
|
||||
//! The system focuses on performance metrics and rankings rather than reward amounts,
|
||||
//! as actual rewards are calculated on-chain based on factors not available to the API.
|
||||
|
||||
use crate::epoch_operations::error::RewardingError;
|
||||
use crate::epoch_operations::helpers::RewardedNodeWithParams;
|
||||
use crate::storage::models::{
|
||||
SimulatedNodePerformance, SimulatedPerformanceComparison, SimulatedPerformanceRanking,
|
||||
SimulatedRouteAnalysis,
|
||||
};
|
||||
use crate::support::storage::NymApiStorage;
|
||||
use crate::EpochAdvancer;
|
||||
use nym_contracts_common::types::NaiveFloat;
|
||||
use nym_mixnet_contract_common::reward_params::Performance;
|
||||
use nym_mixnet_contract_common::{EpochRewardedSet, NodeId, RewardingParams};
|
||||
use std::collections::HashMap;
|
||||
use time::OffsetDateTime;
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
/// Configuration for simulation runs
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SimulationConfig {
|
||||
/// Time window in hours for new method calculation (default: 1)
|
||||
pub new_method_time_window_hours: u32,
|
||||
/// Whether to run both old and new methods (default: true)
|
||||
pub run_both_methods: bool,
|
||||
/// Description for this simulation run
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for SimulationConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
new_method_time_window_hours: 1,
|
||||
run_both_methods: true,
|
||||
description: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Main simulation coordinator
|
||||
pub struct SimulationCoordinator<'a> {
|
||||
storage: &'a NymApiStorage,
|
||||
config: SimulationConfig,
|
||||
}
|
||||
|
||||
impl<'a> SimulationCoordinator<'a> {
|
||||
pub fn new(storage: &'a NymApiStorage, config: SimulationConfig) -> Self {
|
||||
Self { storage, config }
|
||||
}
|
||||
|
||||
/// Run a complete simulation comparing old vs new rewarding methods
|
||||
pub async fn run_simulation(
|
||||
&self,
|
||||
rewarded_set: &EpochRewardedSet,
|
||||
reward_params: RewardingParams,
|
||||
current_epoch_id: u32,
|
||||
) -> Result<(), RewardingError> {
|
||||
let now = OffsetDateTime::now_utc();
|
||||
let end_timestamp = now.unix_timestamp();
|
||||
let start_timestamp = end_timestamp - (24 * 3600); // 24 hours ago for baseline
|
||||
|
||||
info!(
|
||||
"Starting simulation for epoch {} with time window {}h",
|
||||
current_epoch_id, self.config.new_method_time_window_hours
|
||||
);
|
||||
|
||||
// Create simulation epoch record
|
||||
let epoch_db_id = self
|
||||
.storage
|
||||
.manager
|
||||
.create_simulated_reward_epoch(
|
||||
current_epoch_id,
|
||||
"comparison",
|
||||
start_timestamp,
|
||||
end_timestamp,
|
||||
self.config.description.as_deref(),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| RewardingError::DatabaseError { source: e.into() })?;
|
||||
|
||||
// Run old method simulation (24h cache-based)
|
||||
if self.config.run_both_methods {
|
||||
match self
|
||||
.run_old_method_simulation(rewarded_set, reward_params, epoch_db_id, end_timestamp)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
info!("Old method simulation completed successfully");
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Old method simulation failed: {}", e);
|
||||
// Continue with new method even if old fails
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run new method simulation (1h route-based)
|
||||
match self
|
||||
.run_new_method_simulation(rewarded_set, reward_params, epoch_db_id, end_timestamp)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
info!("New method simulation completed successfully");
|
||||
}
|
||||
Err(e) => {
|
||||
error!("New method simulation failed: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
info!("Simulation completed for epoch {}", current_epoch_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run simulation using old method (24h cache-based)
|
||||
async fn run_old_method_simulation(
|
||||
&self,
|
||||
rewarded_set: &EpochRewardedSet,
|
||||
reward_params: RewardingParams,
|
||||
epoch_db_id: i64,
|
||||
end_timestamp: i64,
|
||||
) -> Result<(), RewardingError> {
|
||||
debug!("Running old method simulation (24h cache-based)");
|
||||
|
||||
// Get 24h performance data using existing cache-based method
|
||||
let mixnode_reliabilities = self
|
||||
.storage
|
||||
.get_all_avg_mix_reliability_in_last_24hr(end_timestamp)
|
||||
.await
|
||||
.map_err(|e| RewardingError::DatabaseError { source: e.into() })?;
|
||||
|
||||
let gateway_reliabilities = self
|
||||
.storage
|
||||
.get_all_avg_gateway_reliability_in_last_24hr(end_timestamp)
|
||||
.await
|
||||
.map_err(|e| RewardingError::DatabaseError { source: e.into() })?;
|
||||
|
||||
// Convert to performance map
|
||||
let mut performance_map = HashMap::new();
|
||||
|
||||
for mix_reliability in mixnode_reliabilities {
|
||||
performance_map.insert(
|
||||
mix_reliability.mix_id(),
|
||||
Performance::from_percentage_value((mix_reliability.value() * 100.0) as u64)
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
}
|
||||
|
||||
for gateway_reliability in gateway_reliabilities {
|
||||
performance_map.insert(
|
||||
gateway_reliability.node_id(),
|
||||
Performance::from_percentage_value((gateway_reliability.value() * 100.0) as u64)
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
}
|
||||
|
||||
// Calculate rewards using old method logic
|
||||
let rewarded_nodes =
|
||||
self.calculate_rewards_for_nodes(rewarded_set, reward_params, &performance_map);
|
||||
|
||||
// Convert to simulation data structures
|
||||
let node_performance = self
|
||||
.convert_to_simulated_performance(
|
||||
&rewarded_nodes,
|
||||
rewarded_set,
|
||||
reward_params,
|
||||
epoch_db_id,
|
||||
"old",
|
||||
None, // Old method doesn't have route sample data
|
||||
)
|
||||
.await;
|
||||
|
||||
let performance_comparisons = self
|
||||
.convert_to_performance_comparisons(
|
||||
&rewarded_nodes,
|
||||
rewarded_set,
|
||||
reward_params,
|
||||
epoch_db_id,
|
||||
"old",
|
||||
)
|
||||
.await;
|
||||
|
||||
// Calculate average reliability for old method (mean of all node reliabilities)
|
||||
let node_reliabilities: Vec<f64> = performance_map
|
||||
.values()
|
||||
.map(|p| p.naive_to_f64() * 100.0)
|
||||
.filter(|&r| r > 0.0) // Only include nodes with non-zero reliability
|
||||
.collect();
|
||||
|
||||
let (mean_reliability, median_reliability) = if !node_reliabilities.is_empty() {
|
||||
let mean = node_reliabilities.iter().sum::<f64>() / node_reliabilities.len() as f64;
|
||||
let median = calculate_median(&node_reliabilities);
|
||||
(
|
||||
Some((mean * 100.0).round() / 100.0),
|
||||
Some((median * 100.0).round() / 100.0),
|
||||
)
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
// Create route analysis for old method
|
||||
let route_analysis = SimulatedRouteAnalysis {
|
||||
id: 0, // Will be set by database
|
||||
simulated_epoch_id: epoch_db_id,
|
||||
calculation_method: "old".to_string(),
|
||||
total_routes_analyzed: 0, // Old method doesn't use route data
|
||||
successful_routes: 0,
|
||||
failed_routes: 0,
|
||||
average_route_reliability: mean_reliability,
|
||||
time_window_hours: 24, // Old method uses 24h
|
||||
analysis_parameters: Some(format!(
|
||||
"{{\"method\":\"cache_based\",\"data_source\":\"status_cache\",\"median_reliability\":{},\"nodes_analyzed\":{}}}",
|
||||
median_reliability.unwrap_or(0.0),
|
||||
node_reliabilities.len()
|
||||
)),
|
||||
calculated_at: OffsetDateTime::now_utc().unix_timestamp(),
|
||||
};
|
||||
|
||||
// Store results in database
|
||||
self.storage
|
||||
.manager
|
||||
.insert_simulated_node_performance(&node_performance)
|
||||
.await
|
||||
.map_err(|e| RewardingError::DatabaseError { source: e.into() })?;
|
||||
|
||||
self.storage
|
||||
.manager
|
||||
.insert_simulated_performance_comparisons(&performance_comparisons)
|
||||
.await
|
||||
.map_err(|e| RewardingError::DatabaseError { source: e.into() })?;
|
||||
|
||||
// Calculate and store performance rankings
|
||||
let rankings =
|
||||
self.calculate_performance_rankings(&performance_comparisons, epoch_db_id, "old");
|
||||
self.storage
|
||||
.manager
|
||||
.insert_simulated_performance_rankings(&rankings)
|
||||
.await
|
||||
.map_err(|e| RewardingError::DatabaseError { source: e.into() })?;
|
||||
|
||||
self.storage
|
||||
.manager
|
||||
.insert_simulated_route_analysis(&route_analysis)
|
||||
.await
|
||||
.map_err(|e| RewardingError::DatabaseError { source: e.into() })?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run simulation using new method (1h route-based)
|
||||
async fn run_new_method_simulation(
|
||||
&self,
|
||||
rewarded_set: &EpochRewardedSet,
|
||||
reward_params: RewardingParams,
|
||||
epoch_db_id: i64,
|
||||
end_timestamp: i64,
|
||||
) -> Result<(), RewardingError> {
|
||||
debug!(
|
||||
"Running new method simulation ({}h route-based)",
|
||||
self.config.new_method_time_window_hours
|
||||
);
|
||||
|
||||
let time_window_secs = (self.config.new_method_time_window_hours as i64) * 3600;
|
||||
let start_timestamp = end_timestamp - time_window_secs;
|
||||
|
||||
// Get route-based performance data using new method
|
||||
let corrected_reliabilities = self
|
||||
.storage
|
||||
.manager
|
||||
.calculate_corrected_node_reliabilities_for_interval(start_timestamp, end_timestamp)
|
||||
.await
|
||||
.map_err(|e| RewardingError::DatabaseError { source: e })?;
|
||||
|
||||
// Convert to performance map and build route reliability data
|
||||
let mut performance_map = HashMap::new();
|
||||
let mut route_reliability_map = HashMap::new();
|
||||
let mut total_routes = 0u32;
|
||||
let mut successful_routes = 0u32;
|
||||
|
||||
for node_reliability in &corrected_reliabilities {
|
||||
let total_samples =
|
||||
node_reliability.pos_samples_in_interval + node_reliability.neg_samples_in_interval;
|
||||
total_routes += total_samples;
|
||||
successful_routes += node_reliability.pos_samples_in_interval;
|
||||
performance_map.insert(
|
||||
node_reliability.node_id,
|
||||
Performance::naive_try_from_f64(node_reliability.reliability / 100.0)
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
|
||||
// Store sample counts for detailed performance records
|
||||
route_reliability_map.insert(
|
||||
node_reliability.node_id,
|
||||
(
|
||||
node_reliability.pos_samples_in_interval,
|
||||
node_reliability.neg_samples_in_interval,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Calculate rewards using new method logic
|
||||
let rewarded_nodes =
|
||||
self.calculate_rewards_for_nodes(rewarded_set, reward_params, &performance_map);
|
||||
|
||||
// Convert to simulation data structures
|
||||
let node_performance = self
|
||||
.convert_to_simulated_performance(
|
||||
&rewarded_nodes,
|
||||
rewarded_set,
|
||||
reward_params,
|
||||
epoch_db_id,
|
||||
"new",
|
||||
Some(&route_reliability_map), // Pass route sample data for new method
|
||||
)
|
||||
.await;
|
||||
|
||||
let performance_comparisons = self
|
||||
.convert_to_performance_comparisons(
|
||||
&rewarded_nodes,
|
||||
rewarded_set,
|
||||
reward_params,
|
||||
epoch_db_id,
|
||||
"new",
|
||||
)
|
||||
.await;
|
||||
|
||||
// Calculate average reliability for new method (mean of all node reliabilities)
|
||||
let node_reliabilities: Vec<f64> = corrected_reliabilities
|
||||
.iter()
|
||||
.filter(|n| n.pos_samples_in_interval + n.neg_samples_in_interval > 0)
|
||||
.map(|n| n.reliability)
|
||||
.collect();
|
||||
|
||||
let (mean_reliability, median_reliability) = if !node_reliabilities.is_empty() {
|
||||
let mean = node_reliabilities.iter().sum::<f64>() / node_reliabilities.len() as f64;
|
||||
let median = calculate_median(&node_reliabilities);
|
||||
(
|
||||
Some((mean * 100.0).round() / 100.0),
|
||||
Some((median * 100.0).round() / 100.0),
|
||||
)
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
// Create route analysis for new method
|
||||
let route_analysis = SimulatedRouteAnalysis {
|
||||
id: 0, // Will be set by database
|
||||
simulated_epoch_id: epoch_db_id,
|
||||
calculation_method: "new".to_string(),
|
||||
total_routes_analyzed: total_routes,
|
||||
successful_routes,
|
||||
failed_routes: total_routes - successful_routes,
|
||||
average_route_reliability: mean_reliability,
|
||||
time_window_hours: self.config.new_method_time_window_hours,
|
||||
analysis_parameters: Some(format!(
|
||||
"{{\"method\":\"route_based\",\"time_window_hours\":{},\"corrected_routes\":{},\"median_reliability\":{},\"nodes_analyzed\":{}}}",
|
||||
self.config.new_method_time_window_hours,
|
||||
corrected_reliabilities.len(),
|
||||
median_reliability.unwrap_or(0.0),
|
||||
node_reliabilities.len()
|
||||
)),
|
||||
calculated_at: OffsetDateTime::now_utc().unix_timestamp(),
|
||||
};
|
||||
|
||||
// Store results in database
|
||||
self.storage
|
||||
.manager
|
||||
.insert_simulated_node_performance(&node_performance)
|
||||
.await
|
||||
.map_err(|e| RewardingError::DatabaseError { source: e.into() })?;
|
||||
|
||||
self.storage
|
||||
.manager
|
||||
.insert_simulated_performance_comparisons(&performance_comparisons)
|
||||
.await
|
||||
.map_err(|e| RewardingError::DatabaseError { source: e.into() })?;
|
||||
|
||||
// Calculate and store performance rankings
|
||||
let rankings =
|
||||
self.calculate_performance_rankings(&performance_comparisons, epoch_db_id, "new");
|
||||
self.storage
|
||||
.manager
|
||||
.insert_simulated_performance_rankings(&rankings)
|
||||
.await
|
||||
.map_err(|e| RewardingError::DatabaseError { source: e.into() })?;
|
||||
|
||||
self.storage
|
||||
.manager
|
||||
.insert_simulated_route_analysis(&route_analysis)
|
||||
.await
|
||||
.map_err(|e| RewardingError::DatabaseError { source: e.into() })?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Calculate rewards for nodes using the provided performance data
|
||||
/// This mirrors the logic from helpers.rs but uses simulation performance data
|
||||
fn calculate_rewards_for_nodes(
|
||||
&self,
|
||||
rewarded_set: &EpochRewardedSet,
|
||||
reward_params: RewardingParams,
|
||||
performance_map: &HashMap<NodeId, Performance>,
|
||||
) -> Vec<RewardedNodeWithParams> {
|
||||
let nodes = &rewarded_set.assignment;
|
||||
let active_node_work_factor = reward_params.active_node_work();
|
||||
let standby_node_work_factor = reward_params.standby_node_work();
|
||||
|
||||
let mut rewarded_nodes = Vec::with_capacity(nodes.rewarded_set_size());
|
||||
|
||||
// Process active set mixnodes (layers 1, 2, 3)
|
||||
for &node_id in nodes
|
||||
.layer1
|
||||
.iter()
|
||||
.chain(nodes.layer2.iter())
|
||||
.chain(nodes.layer3.iter())
|
||||
{
|
||||
let performance = performance_map.get(&node_id).copied().unwrap_or_default();
|
||||
rewarded_nodes.push(RewardedNodeWithParams {
|
||||
node_id,
|
||||
params: nym_mixnet_contract_common::reward_params::NodeRewardingParameters {
|
||||
performance,
|
||||
work_factor: active_node_work_factor,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Process active set gateways
|
||||
for &node_id in nodes
|
||||
.entry_gateways
|
||||
.iter()
|
||||
.chain(nodes.exit_gateways.iter())
|
||||
{
|
||||
let performance = performance_map.get(&node_id).copied().unwrap_or_default();
|
||||
rewarded_nodes.push(RewardedNodeWithParams {
|
||||
node_id,
|
||||
params: nym_mixnet_contract_common::reward_params::NodeRewardingParameters {
|
||||
performance,
|
||||
work_factor: active_node_work_factor,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Process standby nodes
|
||||
for &node_id in &nodes.standby {
|
||||
let performance = performance_map.get(&node_id).copied().unwrap_or_default();
|
||||
rewarded_nodes.push(RewardedNodeWithParams {
|
||||
node_id,
|
||||
params: nym_mixnet_contract_common::reward_params::NodeRewardingParameters {
|
||||
performance,
|
||||
work_factor: standby_node_work_factor,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
rewarded_nodes
|
||||
}
|
||||
|
||||
/// Determine node type and work factor from rewarded set position
|
||||
fn determine_node_info(
|
||||
&self,
|
||||
node_id: NodeId,
|
||||
rewarded_set: &EpochRewardedSet,
|
||||
reward_params: RewardingParams,
|
||||
) -> (String, f64) {
|
||||
let nodes = &rewarded_set.assignment;
|
||||
|
||||
// Check if node is in active mixnode layers
|
||||
if nodes.layer1.contains(&node_id)
|
||||
|| nodes.layer2.contains(&node_id)
|
||||
|| nodes.layer3.contains(&node_id)
|
||||
{
|
||||
return (
|
||||
"mixnode".to_string(),
|
||||
reward_params.active_node_work().naive_to_f64(),
|
||||
);
|
||||
}
|
||||
|
||||
// Check if node is in active gateways
|
||||
if nodes.entry_gateways.contains(&node_id) || nodes.exit_gateways.contains(&node_id) {
|
||||
return (
|
||||
"gateway".to_string(),
|
||||
reward_params.active_node_work().naive_to_f64(),
|
||||
);
|
||||
}
|
||||
|
||||
// Check if node is in standby (could be mixnode or gateway)
|
||||
if nodes.standby.contains(&node_id) {
|
||||
// Note: We cannot determine if standby nodes are mixnodes or gateways from the
|
||||
// rewarded set data alone. This limitation exists in both old and new calculation
|
||||
// methods and doesn't significantly impact the simulation since:
|
||||
// 1. All standby nodes receive the same work factor regardless of type
|
||||
// 2. The gateway 3-sample rule can't be applied to standby nodes in either method
|
||||
// For consistency, we label all standby nodes as "mixnode" in the simulation data
|
||||
return (
|
||||
"mixnode".to_string(),
|
||||
reward_params.standby_node_work().naive_to_f64(),
|
||||
);
|
||||
}
|
||||
|
||||
// Default case (shouldn't happen)
|
||||
("unknown".to_string(), 0.0)
|
||||
}
|
||||
|
||||
/// Convert rewarded nodes to simulated performance records
|
||||
async fn convert_to_simulated_performance(
|
||||
&self,
|
||||
rewarded_nodes: &[RewardedNodeWithParams],
|
||||
rewarded_set: &EpochRewardedSet,
|
||||
reward_params: RewardingParams,
|
||||
epoch_db_id: i64,
|
||||
method: &str,
|
||||
route_reliability_map: Option<&HashMap<NodeId, (u32, u32)>>, // (positive_samples, negative_samples)
|
||||
) -> Vec<SimulatedNodePerformance> {
|
||||
let now = OffsetDateTime::now_utc().unix_timestamp();
|
||||
|
||||
let mut performance_records = Vec::with_capacity(rewarded_nodes.len());
|
||||
|
||||
// First collect all node IDs for batch identity key lookups
|
||||
let all_node_ids: Vec<NodeId> = rewarded_nodes.iter().map(|node| node.node_id).collect();
|
||||
|
||||
// Batch fetch identity keys for both mixnodes and gateways
|
||||
let mixnode_identities = self
|
||||
.storage
|
||||
.manager
|
||||
.get_mixnode_identity_keys_batch(&all_node_ids)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let gateway_identities = self
|
||||
.storage
|
||||
.manager
|
||||
.get_gateway_identity_keys_batch(&all_node_ids)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
for node in rewarded_nodes {
|
||||
let (node_type, _) =
|
||||
self.determine_node_info(node.node_id, rewarded_set, reward_params);
|
||||
|
||||
// Get identity key from our batch results
|
||||
let identity_key = match node_type.as_str() {
|
||||
"mixnode" => mixnode_identities.get(&node.node_id).cloned(),
|
||||
"gateway" => gateway_identities.get(&node.node_id).cloned(),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
// Extract sample counts from route reliability if available
|
||||
let (positive_samples, negative_samples) = route_reliability_map
|
||||
.and_then(|map| map.get(&node.node_id))
|
||||
.copied()
|
||||
.unwrap_or((0, 0));
|
||||
|
||||
performance_records.push(SimulatedNodePerformance {
|
||||
id: 0, // Will be set by database
|
||||
simulated_epoch_id: epoch_db_id,
|
||||
node_id: node.node_id,
|
||||
node_type,
|
||||
identity_key,
|
||||
reliability_score: node.params.performance.naive_to_f64() * 100.0,
|
||||
positive_samples,
|
||||
negative_samples,
|
||||
work_factor: Some(node.params.work_factor.naive_to_f64()),
|
||||
calculation_method: method.to_string(),
|
||||
calculated_at: now,
|
||||
});
|
||||
}
|
||||
|
||||
performance_records
|
||||
}
|
||||
|
||||
/// Convert rewarded nodes to performance comparison records
|
||||
async fn convert_to_performance_comparisons(
|
||||
&self,
|
||||
rewarded_nodes: &[RewardedNodeWithParams],
|
||||
rewarded_set: &EpochRewardedSet,
|
||||
reward_params: RewardingParams,
|
||||
epoch_db_id: i64,
|
||||
method: &str,
|
||||
) -> Vec<SimulatedPerformanceComparison> {
|
||||
let now = OffsetDateTime::now_utc().unix_timestamp();
|
||||
|
||||
let mut performance_records = Vec::with_capacity(rewarded_nodes.len());
|
||||
|
||||
for node in rewarded_nodes {
|
||||
let (node_type, _) =
|
||||
self.determine_node_info(node.node_id, rewarded_set, reward_params);
|
||||
|
||||
performance_records.push(SimulatedPerformanceComparison {
|
||||
id: 0, // Will be set by database
|
||||
simulated_epoch_id: epoch_db_id,
|
||||
node_id: node.node_id,
|
||||
node_type,
|
||||
performance_score: node.params.performance.naive_to_f64() * 100.0,
|
||||
work_factor: node.params.work_factor.naive_to_f64(),
|
||||
calculation_method: method.to_string(),
|
||||
positive_samples: None, // Will be populated from node performance data
|
||||
negative_samples: None,
|
||||
route_success_rate: None,
|
||||
calculated_at: now,
|
||||
});
|
||||
}
|
||||
|
||||
performance_records
|
||||
}
|
||||
|
||||
/// Calculate performance rankings for a set of performance comparisons
|
||||
fn calculate_performance_rankings(
|
||||
&self,
|
||||
performance_comparisons: &[SimulatedPerformanceComparison],
|
||||
epoch_db_id: i64,
|
||||
method: &str,
|
||||
) -> Vec<SimulatedPerformanceRanking> {
|
||||
let now = OffsetDateTime::now_utc().unix_timestamp();
|
||||
let mut rankings = Vec::with_capacity(performance_comparisons.len());
|
||||
|
||||
// Sort by performance score descending
|
||||
let mut sorted_comparisons: Vec<_> = performance_comparisons
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, comp)| (idx, comp.performance_score))
|
||||
.collect();
|
||||
sorted_comparisons
|
||||
.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
|
||||
|
||||
let total_nodes = sorted_comparisons.len() as f64;
|
||||
|
||||
for (rank, (original_idx, _score)) in sorted_comparisons.iter().enumerate() {
|
||||
let comparison = &performance_comparisons[*original_idx];
|
||||
let percentile = ((total_nodes - rank as f64 - 1.0) / total_nodes) * 100.0;
|
||||
|
||||
rankings.push(SimulatedPerformanceRanking {
|
||||
id: 0, // Will be set by database
|
||||
simulated_epoch_id: epoch_db_id,
|
||||
node_id: comparison.node_id,
|
||||
calculation_method: method.to_string(),
|
||||
performance_rank: (rank + 1) as i64,
|
||||
performance_percentile: percentile,
|
||||
calculated_at: now,
|
||||
});
|
||||
}
|
||||
|
||||
rankings
|
||||
}
|
||||
}
|
||||
|
||||
impl EpochAdvancer {
|
||||
/// Run simulation during epoch operations if simulation mode is enabled
|
||||
pub async fn run_simulation_if_enabled(
|
||||
&self,
|
||||
rewarded_set: &EpochRewardedSet,
|
||||
reward_params: RewardingParams,
|
||||
current_epoch_id: u32,
|
||||
simulation_config: SimulationConfig,
|
||||
) -> Result<(), RewardingError> {
|
||||
let coordinator = SimulationCoordinator::new(&self.storage, simulation_config);
|
||||
|
||||
match coordinator
|
||||
.run_simulation(rewarded_set, reward_params, current_epoch_id)
|
||||
.await
|
||||
{
|
||||
Ok(()) => {
|
||||
info!(
|
||||
"Simulation completed successfully for epoch {}",
|
||||
current_epoch_id
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Simulation failed for epoch {}: {}", current_epoch_id, e);
|
||||
// Don't fail the entire epoch operation due to simulation failure
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate median of a vector of f64 values
|
||||
fn calculate_median(values: &[f64]) -> f64 {
|
||||
if values.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let mut sorted = values.to_vec();
|
||||
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
|
||||
|
||||
let len = sorted.len();
|
||||
if len % 2 == 0 {
|
||||
(sorted[len / 2 - 1] + sorted[len / 2]) / 2.0
|
||||
} else {
|
||||
sorted[len / 2]
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,6 @@ pub(crate) mod node_describe_cache;
|
||||
pub(crate) mod node_status_api;
|
||||
pub(crate) mod nym_contract_cache;
|
||||
pub(crate) mod nym_nodes;
|
||||
pub(crate) mod simulation_api;
|
||||
mod status;
|
||||
pub(crate) mod support;
|
||||
mod unstable_routes;
|
||||
|
||||
@@ -20,7 +20,7 @@ use nym_api_requests::models::{
|
||||
};
|
||||
use nym_http_api_common::{FormattedResponse, OutputParams};
|
||||
use nym_mixnet_contract_common::NodeId;
|
||||
use nym_types::monitoring::{MonitorMessage, MonitorResults};
|
||||
use nym_types::monitoring::MonitorMessage;
|
||||
use tracing::error;
|
||||
|
||||
pub(super) fn mandatory_routes() -> Router<AppState> {
|
||||
@@ -33,10 +33,6 @@ pub(super) fn mandatory_routes() -> Router<AppState> {
|
||||
"/submit-node-monitoring-results",
|
||||
post(submit_node_monitoring_results),
|
||||
)
|
||||
.route(
|
||||
"/submit-route-monitoring-results",
|
||||
post(submit_route_monitoring_results),
|
||||
)
|
||||
.nest(
|
||||
"/mixnode/:mix_id",
|
||||
Router::new()
|
||||
@@ -62,53 +58,6 @@ pub(super) fn mandatory_routes() -> Router<AppState> {
|
||||
)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "status",
|
||||
post,
|
||||
path = "/v1/status/submit-route-monitoring-results",
|
||||
responses(
|
||||
(status = 200),
|
||||
(status = 400, body = String, description = "TBD"),
|
||||
(status = 403, body = String, description = "TBD"),
|
||||
(status = 500, body = String, description = "TBD"),
|
||||
),
|
||||
)]
|
||||
pub(crate) async fn submit_route_monitoring_results(
|
||||
State(state): State<AppState>,
|
||||
Json(message): Json<MonitorMessage>,
|
||||
) -> AxumResult<()> {
|
||||
if !message.is_in_allowed() {
|
||||
return Err(AxumErrorResponse::forbidden(
|
||||
"Monitor not registered to submit results",
|
||||
));
|
||||
}
|
||||
|
||||
if !message.timely() {
|
||||
return Err(AxumErrorResponse::bad_request("Message is too old"));
|
||||
}
|
||||
|
||||
if !message.verify() {
|
||||
return Err(AxumErrorResponse::bad_request("invalid signature"));
|
||||
}
|
||||
|
||||
match message.results() {
|
||||
MonitorResults::Route(results) => {
|
||||
match state.storage.submit_route_monitoring_results(results).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
error!("failed to submit node monitoring results: {err}");
|
||||
Err(AxumErrorResponse::internal_msg(
|
||||
"failed to submit node monitoring results",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
MonitorResults::Node(_results) => Err(AxumErrorResponse::bad_request(
|
||||
"Node monitoring results not supported for this endpoint",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "status",
|
||||
post,
|
||||
@@ -138,21 +87,18 @@ pub(crate) async fn submit_gateway_monitoring_results(
|
||||
return Err(AxumErrorResponse::bad_request("invalid signature"));
|
||||
}
|
||||
|
||||
match message.results() {
|
||||
MonitorResults::Node(results) => {
|
||||
match state.storage.submit_gateway_statuses_v2(results).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
error!("failed to submit node monitoring results: {err}");
|
||||
Err(AxumErrorResponse::internal_msg(
|
||||
"failed to submit node monitoring results",
|
||||
))
|
||||
}
|
||||
}
|
||||
match state
|
||||
.storage
|
||||
.submit_gateway_statuses_v2(message.results())
|
||||
.await
|
||||
{
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
error!("failed to submit gateway monitoring results: {err}");
|
||||
Err(AxumErrorResponse::internal_msg(
|
||||
"failed to submit gateway monitoring results",
|
||||
))
|
||||
}
|
||||
MonitorResults::Route(_results) => Err(AxumErrorResponse::bad_request(
|
||||
"Gateway monitoring results not supported for this endpoint",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,21 +131,18 @@ pub(crate) async fn submit_node_monitoring_results(
|
||||
return Err(AxumErrorResponse::bad_request("invalid signature"));
|
||||
}
|
||||
|
||||
match message.results() {
|
||||
MonitorResults::Node(results) => {
|
||||
match state.storage.submit_mixnode_statuses_v2(results).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
error!("failed to submit node monitoring results: {err}");
|
||||
Err(AxumErrorResponse::internal_msg(
|
||||
"failed to submit node monitoring results",
|
||||
))
|
||||
}
|
||||
}
|
||||
match state
|
||||
.storage
|
||||
.submit_mixnode_statuses_v2(message.results())
|
||||
.await
|
||||
{
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
error!("failed to submit node monitoring results: {err}");
|
||||
Err(AxumErrorResponse::internal_msg(
|
||||
"failed to submit node monitoring results",
|
||||
))
|
||||
}
|
||||
MonitorResults::Route(_results) => Err(AxumErrorResponse::bad_request(
|
||||
"Node monitoring results not supported for this endpoint",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
//! Simulation API for reward calculation analysis
|
||||
//!
|
||||
//! This module provides REST endpoints for accessing and analyzing
|
||||
//! simulated reward calculation data comparing old vs new methodologies.
|
||||
|
||||
pub(crate) mod handlers;
|
||||
pub(crate) mod models;
|
||||
@@ -1,285 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
//! API models for simulation data responses
|
||||
|
||||
use crate::storage::models::{
|
||||
SimulatedNodePerformance, SimulatedPerformanceComparison, SimulatedRewardEpoch,
|
||||
SimulatedRouteAnalysis,
|
||||
};
|
||||
use nym_mixnet_contract_common::NodeId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::OffsetDateTime;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
/// Response for listing simulation epochs
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug, Clone)]
|
||||
pub struct SimulationEpochsResponse {
|
||||
pub epochs: Vec<SimulationEpochSummary>,
|
||||
pub total_count: usize,
|
||||
}
|
||||
|
||||
/// Summary information about a simulation epoch
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug, Clone)]
|
||||
pub struct SimulationEpochSummary {
|
||||
pub id: i64,
|
||||
pub epoch_id: u32,
|
||||
pub calculation_method: String,
|
||||
pub start_timestamp: i64,
|
||||
pub end_timestamp: i64,
|
||||
pub description: Option<String>,
|
||||
pub created_at: i64,
|
||||
/// Number of nodes that had performance calculated
|
||||
pub nodes_analyzed: usize,
|
||||
/// Available calculation methods for this epoch
|
||||
pub available_methods: Vec<String>,
|
||||
}
|
||||
|
||||
impl From<SimulatedRewardEpoch> for SimulationEpochSummary {
|
||||
fn from(epoch: SimulatedRewardEpoch) -> Self {
|
||||
Self {
|
||||
id: epoch.id,
|
||||
epoch_id: epoch.epoch_id,
|
||||
calculation_method: epoch.calculation_method,
|
||||
start_timestamp: epoch.start_timestamp,
|
||||
end_timestamp: epoch.end_timestamp,
|
||||
description: epoch.description,
|
||||
created_at: epoch.created_at,
|
||||
nodes_analyzed: 0, // Will be populated by handler
|
||||
available_methods: vec![], // Will be populated by handler
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Detailed simulation epoch with all data
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug, Clone)]
|
||||
pub struct SimulationEpochDetails {
|
||||
pub epoch: SimulationEpochSummary,
|
||||
pub node_performance: Vec<NodePerformanceData>,
|
||||
pub performance_comparisons: Vec<PerformanceComparisonData>,
|
||||
pub route_analysis: Vec<RouteAnalysisData>,
|
||||
}
|
||||
|
||||
/// Node performance data for API responses
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug, Clone)]
|
||||
pub struct NodePerformanceData {
|
||||
pub node_id: NodeId,
|
||||
pub node_type: String,
|
||||
pub identity_key: Option<String>,
|
||||
pub reliability_score: f64,
|
||||
pub positive_samples: u32,
|
||||
pub negative_samples: u32,
|
||||
pub work_factor: Option<f64>,
|
||||
pub calculation_method: String,
|
||||
pub calculated_at: i64,
|
||||
}
|
||||
|
||||
impl From<SimulatedNodePerformance> for NodePerformanceData {
|
||||
fn from(perf: SimulatedNodePerformance) -> Self {
|
||||
Self {
|
||||
node_id: perf.node_id,
|
||||
node_type: perf.node_type,
|
||||
identity_key: perf.identity_key,
|
||||
reliability_score: (perf.reliability_score * 100.0).round() / 100.0,
|
||||
positive_samples: perf.positive_samples,
|
||||
negative_samples: perf.negative_samples,
|
||||
work_factor: perf.work_factor.map(|w| (w * 100.0).round() / 100.0),
|
||||
calculation_method: perf.calculation_method,
|
||||
calculated_at: perf.calculated_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Performance comparison data for API responses
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug, Clone)]
|
||||
pub struct PerformanceComparisonData {
|
||||
pub node_id: NodeId,
|
||||
pub node_type: String,
|
||||
pub performance_score: f64,
|
||||
pub work_factor: f64,
|
||||
pub calculation_method: String,
|
||||
pub positive_samples: Option<i64>,
|
||||
pub negative_samples: Option<i64>,
|
||||
pub route_success_rate: Option<f64>,
|
||||
pub calculated_at: i64,
|
||||
}
|
||||
|
||||
impl From<SimulatedPerformanceComparison> for PerformanceComparisonData {
|
||||
fn from(comparison: SimulatedPerformanceComparison) -> Self {
|
||||
Self {
|
||||
node_id: comparison.node_id,
|
||||
node_type: comparison.node_type,
|
||||
performance_score: (comparison.performance_score * 100.0).round() / 100.0,
|
||||
work_factor: (comparison.work_factor * 100.0).round() / 100.0,
|
||||
calculation_method: comparison.calculation_method,
|
||||
positive_samples: comparison.positive_samples,
|
||||
negative_samples: comparison.negative_samples,
|
||||
route_success_rate: comparison
|
||||
.route_success_rate
|
||||
.map(|r| (r * 100.0).round() / 100.0),
|
||||
calculated_at: comparison.calculated_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Route analysis data for API responses
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug, Clone)]
|
||||
pub struct RouteAnalysisData {
|
||||
pub calculation_method: String,
|
||||
pub total_routes_analyzed: u32,
|
||||
pub successful_routes: u32,
|
||||
pub failed_routes: u32,
|
||||
pub average_route_reliability: Option<f64>,
|
||||
pub median_route_reliability: Option<f64>,
|
||||
pub time_window_hours: u32,
|
||||
pub analysis_parameters: Option<String>,
|
||||
pub calculated_at: i64,
|
||||
}
|
||||
|
||||
impl From<SimulatedRouteAnalysis> for RouteAnalysisData {
|
||||
fn from(analysis: SimulatedRouteAnalysis) -> Self {
|
||||
// Extract median from analysis_parameters JSON if available
|
||||
let median_route_reliability = analysis
|
||||
.analysis_parameters
|
||||
.as_ref()
|
||||
.and_then(|params| serde_json::from_str::<serde_json::Value>(params).ok())
|
||||
.and_then(|json| json.get("median_reliability").and_then(|v| v.as_f64()))
|
||||
.map(|m| (m * 100.0).round() / 100.0);
|
||||
|
||||
Self {
|
||||
calculation_method: analysis.calculation_method,
|
||||
total_routes_analyzed: analysis.total_routes_analyzed,
|
||||
successful_routes: analysis.successful_routes,
|
||||
failed_routes: analysis.failed_routes,
|
||||
average_route_reliability: analysis
|
||||
.average_route_reliability
|
||||
.map(|a| (a * 100.0).round() / 100.0),
|
||||
median_route_reliability,
|
||||
time_window_hours: analysis.time_window_hours,
|
||||
analysis_parameters: analysis.analysis_parameters,
|
||||
calculated_at: analysis.calculated_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Comparison between old and new methods for a specific epoch
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug, Clone)]
|
||||
pub struct MethodComparisonResponse {
|
||||
pub epoch_id: u32,
|
||||
pub simulation_epoch_id: i64,
|
||||
pub node_comparisons: Vec<NodeMethodComparison>,
|
||||
pub summary_statistics: ComparisonSummaryStats,
|
||||
pub route_analysis_comparison: RouteAnalysisComparison,
|
||||
}
|
||||
|
||||
/// Comparison data for a single node between old and new methods
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug, Clone)]
|
||||
pub struct NodeMethodComparison {
|
||||
pub node_id: NodeId,
|
||||
pub node_type: String,
|
||||
pub identity_key: Option<String>,
|
||||
pub old_method: Option<NodePerformanceData>,
|
||||
pub new_method: Option<NodePerformanceData>,
|
||||
pub reliability_difference: Option<f64>, // new - old
|
||||
pub performance_delta_percentage: Option<f64>, // (new - old) / old * 100
|
||||
pub ranking_old_method: Option<i64>,
|
||||
pub ranking_new_method: Option<i64>,
|
||||
pub ranking_delta: Option<i64>, // new ranking - old ranking (negative is improvement)
|
||||
}
|
||||
|
||||
/// Distribution of nodes by reliability score ranges
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug, Clone)]
|
||||
pub struct ReliabilityDistribution {
|
||||
pub excellent: usize, // >95%
|
||||
pub very_good: usize, // 90-95%
|
||||
pub good: usize, // 75-90%
|
||||
pub moderate: usize, // 50-75%
|
||||
pub poor: usize, // 25-50%
|
||||
pub very_poor: usize, // <25%
|
||||
}
|
||||
|
||||
/// Summary statistics comparing old vs new methods
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug, Clone)]
|
||||
pub struct ComparisonSummaryStats {
|
||||
pub total_nodes_compared: usize,
|
||||
pub nodes_improved: usize, // nodes with better performance in new method
|
||||
pub nodes_degraded: usize, // nodes with worse performance in new method
|
||||
pub nodes_unchanged: usize, // nodes with same performance
|
||||
pub average_reliability_old: f64,
|
||||
pub average_reliability_new: f64,
|
||||
pub median_reliability_old: f64,
|
||||
pub median_reliability_new: f64,
|
||||
pub reliability_std_dev_old: f64,
|
||||
pub reliability_std_dev_new: f64,
|
||||
pub max_improvement: f64, // highest positive delta
|
||||
pub max_degradation: f64, // highest negative delta
|
||||
pub distribution_old: ReliabilityDistribution,
|
||||
pub distribution_new: ReliabilityDistribution,
|
||||
}
|
||||
|
||||
/// Comparison of route analysis between methods
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug, Clone)]
|
||||
pub struct RouteAnalysisComparison {
|
||||
pub old_method: Option<RouteAnalysisData>,
|
||||
pub new_method: Option<RouteAnalysisData>,
|
||||
pub time_window_difference_hours: i32, // new - old
|
||||
pub route_coverage_difference: i32, // new total routes - old total routes
|
||||
pub success_rate_difference: Option<f64>, // new average reliability - old average reliability
|
||||
}
|
||||
|
||||
/// Export format options
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug, Clone)]
|
||||
pub enum ExportFormat {
|
||||
#[serde(rename = "json")]
|
||||
Json,
|
||||
#[serde(rename = "csv")]
|
||||
Csv,
|
||||
}
|
||||
|
||||
/// Query parameters for simulation listings
|
||||
#[derive(Deserialize, ToSchema, Debug, utoipa::IntoParams)]
|
||||
pub struct SimulationListQuery {
|
||||
/// Limit number of results (default: 50, max: 1000)
|
||||
pub limit: Option<usize>,
|
||||
/// Offset for pagination (default: 0)
|
||||
pub offset: Option<usize>,
|
||||
}
|
||||
|
||||
/// Query parameters for node-specific performance comparison
|
||||
#[derive(Deserialize, ToSchema, Debug, utoipa::IntoParams)]
|
||||
pub struct NodeComparisonQuery {
|
||||
/// Specific node ID to analyze
|
||||
pub node_id: Option<NodeId>,
|
||||
/// Node type filter (mixnode, gateway)
|
||||
pub node_type: Option<String>,
|
||||
/// Minimum reliability difference threshold for filtering
|
||||
pub min_delta: Option<f64>,
|
||||
/// Maximum reliability difference threshold for filtering
|
||||
pub max_delta: Option<f64>,
|
||||
}
|
||||
|
||||
/// Error response for simulation API
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug)]
|
||||
pub struct SimulationApiError {
|
||||
pub error: String,
|
||||
pub details: Option<String>,
|
||||
pub timestamp: i64,
|
||||
}
|
||||
|
||||
impl SimulationApiError {
|
||||
pub fn new(error: &str) -> Self {
|
||||
Self {
|
||||
error: error.to_string(),
|
||||
details: None,
|
||||
timestamp: OffsetDateTime::now_utc().unix_timestamp(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_details(error: &str, details: &str) -> Self {
|
||||
Self {
|
||||
error: error.to_string(),
|
||||
details: Some(details.to_string()),
|
||||
timestamp: OffsetDateTime::now_utc().unix_timestamp(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ use crate::ecash::dkg::controller::keys::{
|
||||
};
|
||||
use crate::ecash::dkg::controller::DkgController;
|
||||
use crate::ecash::state::EcashState;
|
||||
use crate::epoch_operations::{simulation::SimulationConfig, EpochAdvancer};
|
||||
use crate::epoch_operations::EpochAdvancer;
|
||||
use crate::network::models::NetworkDetails;
|
||||
use crate::node_describe_cache::DescribedNodes;
|
||||
use crate::node_status_api::handlers::unstable;
|
||||
@@ -61,22 +61,10 @@ pub(crate) struct Args {
|
||||
long,
|
||||
requires = "enable_monitor",
|
||||
requires = "mnemonic",
|
||||
conflicts_with = "simulate_rewarding",
|
||||
env = "NYMAPI_ENABLE_REWARDING_ARG"
|
||||
)]
|
||||
pub(crate) enable_rewarding: Option<bool>,
|
||||
|
||||
/// Enable simulated rewarding mode to compare old vs new reward calculations
|
||||
/// Requires monitoring but does NOT require mnemonic (no blockchain transactions)
|
||||
/// default: None - config value will be used instead
|
||||
#[clap(
|
||||
long,
|
||||
requires = "enable_monitor",
|
||||
conflicts_with = "enable_rewarding",
|
||||
env = "NYMAPI_SIMULATE_REWARDING_ARG"
|
||||
)]
|
||||
pub(crate) simulate_rewarding: Option<bool>,
|
||||
|
||||
/// Endpoint to nyxd instance used for contract information.
|
||||
/// default: None - config value will be used instead
|
||||
#[clap(long, env = "NYMAPI_NYXD_VALIDATOR_ARG")]
|
||||
@@ -114,6 +102,15 @@ pub(crate) struct Args {
|
||||
#[clap(long)]
|
||||
pub(crate) bind_address: Option<SocketAddr>,
|
||||
|
||||
/// account/address cache TTL: should be lower than epoch length (1 hour)
|
||||
/// because, at worst, data will be stale for <epoch_length> + <cache_ttl> seconds
|
||||
#[clap(long, env = "ADDRESS_CACHE_REFRESH_INTERVAL_S")]
|
||||
pub(crate) address_cache_ttl_seconds: Option<u64>,
|
||||
|
||||
/// number of addresses that are cached on account/address endpoint
|
||||
#[clap(long, env = "ADDRESS_CACHE_CAPACITY")]
|
||||
pub(crate) address_cache_capacity: Option<u64>,
|
||||
|
||||
#[clap(hide = true, long, default_value_t = false)]
|
||||
pub(crate) allow_illegal_ips: bool,
|
||||
}
|
||||
@@ -206,7 +203,10 @@ async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result<ShutdownHan
|
||||
let router = router.with_state(AppState {
|
||||
nyxd_client: nyxd_client.clone(),
|
||||
chain_status_cache: ChainStatusCache::new(DEFAULT_CHAIN_STATUS_CACHE_TTL),
|
||||
address_info_cache: AddressInfoCache::new(),
|
||||
address_info_cache: AddressInfoCache::new(
|
||||
config.address_cache.time_to_live,
|
||||
config.address_cache.capacity,
|
||||
),
|
||||
forced_refresh: ForcedRefresh::new(
|
||||
config.topology_cacher.debug.node_describe_allow_illegal_ips,
|
||||
),
|
||||
@@ -288,34 +288,15 @@ async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result<ShutdownHan
|
||||
|
||||
HistoricalUptimeUpdater::start(storage.to_owned(), &task_manager);
|
||||
|
||||
// start 'rewarding' or 'simulation' if enabled
|
||||
if config.rewarding.enabled || config.rewarding.simulation_mode {
|
||||
// Only check rewarding permission for real rewarding, not simulation
|
||||
if config.rewarding.enabled && !config.rewarding.simulation_mode {
|
||||
epoch_operations::ensure_rewarding_permission(&nyxd_client).await?;
|
||||
}
|
||||
|
||||
// Create simulation config if simulation mode is enabled
|
||||
let simulation_config = if config.rewarding.simulation_mode {
|
||||
Some(SimulationConfig {
|
||||
new_method_time_window_hours: config
|
||||
.rewarding
|
||||
.debug
|
||||
.simulation_new_method_time_window_hours,
|
||||
run_both_methods: config.rewarding.debug.simulation_run_both_methods,
|
||||
description: Some("Simulation run at epoch advancement".to_string()),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// start 'rewarding' if its enabled
|
||||
if config.rewarding.enabled {
|
||||
epoch_operations::ensure_rewarding_permission(&nyxd_client).await?;
|
||||
EpochAdvancer::start(
|
||||
nyxd_client,
|
||||
&nym_contract_cache_state,
|
||||
&node_status_cache_state,
|
||||
described_nodes_cache.clone(),
|
||||
&storage,
|
||||
simulation_config,
|
||||
&task_manager,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -54,6 +54,9 @@ const DEFAULT_TOPOLOGY_CACHE_INTERVAL: Duration = Duration::from_secs(30);
|
||||
const DEFAULT_NODE_STATUS_CACHE_INTERVAL: Duration = Duration::from_secs(120);
|
||||
const DEFAULT_CIRCULATING_SUPPLY_CACHE_INTERVAL: Duration = Duration::from_secs(3600);
|
||||
|
||||
pub(crate) const DEFAULT_ADDRESS_CACHE_TTL: Duration = Duration::from_secs(60 * 15);
|
||||
pub(crate) const DEFAULT_ADDRESS_CACHE_CAPACITY: u64 = 1000;
|
||||
|
||||
pub(crate) const DEFAULT_NODE_DESCRIBE_CACHE_INTERVAL: Duration = Duration::from_secs(4500);
|
||||
pub(crate) const DEFAULT_NODE_DESCRIBE_BATCH_SIZE: usize = 50;
|
||||
|
||||
@@ -111,6 +114,9 @@ pub struct Config {
|
||||
|
||||
#[serde(alias = "coconut_signer")]
|
||||
pub ecash_signer: EcashSigner,
|
||||
|
||||
#[serde(skip)]
|
||||
pub address_cache: AddressCacheConfig,
|
||||
}
|
||||
|
||||
impl NymConfigTemplate for Config {
|
||||
@@ -130,36 +136,21 @@ impl Config {
|
||||
circulating_supply_cacher: Default::default(),
|
||||
rewarding: Default::default(),
|
||||
ecash_signer: EcashSigner::new_default(id.as_ref()),
|
||||
address_cache: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> anyhow::Result<()> {
|
||||
let can_sign = self.base.mnemonic.is_some();
|
||||
|
||||
// Real rewarding requires mnemonic, but simulation mode does not
|
||||
if !can_sign && self.rewarding.enabled && !self.rewarding.simulation_mode {
|
||||
if !can_sign && self.rewarding.enabled {
|
||||
bail!("can't enable rewarding without providing a mnemonic")
|
||||
}
|
||||
|
||||
// Simulation mode and real rewarding are mutually exclusive
|
||||
if self.rewarding.enabled && self.rewarding.simulation_mode {
|
||||
bail!("cannot enable both real rewarding and simulation mode simultaneously")
|
||||
}
|
||||
|
||||
if !can_sign && self.ecash_signer.enabled {
|
||||
bail!("can't enable coconut signer without providing a mnemonic")
|
||||
}
|
||||
|
||||
// Validate simulation-specific settings
|
||||
if self.rewarding.simulation_mode {
|
||||
if self.rewarding.debug.simulation_new_method_time_window_hours == 0 {
|
||||
bail!("simulation_new_method_time_window_hours must be greater than 0")
|
||||
}
|
||||
if self.rewarding.debug.simulation_new_method_time_window_hours > 24 {
|
||||
bail!("simulation_new_method_time_window_hours should not exceed 24 hours")
|
||||
}
|
||||
}
|
||||
|
||||
self.ecash_signer.validate()?;
|
||||
|
||||
Ok(())
|
||||
@@ -174,9 +165,6 @@ impl Config {
|
||||
if let Some(enable_rewarding) = args.enable_rewarding {
|
||||
self.rewarding.enabled = enable_rewarding;
|
||||
}
|
||||
if let Some(simulate_rewarding) = args.simulate_rewarding {
|
||||
self.rewarding.simulation_mode = simulate_rewarding;
|
||||
}
|
||||
if let Some(nyxd_upstream) = args.nyxd_validator {
|
||||
self.base.local_validator = nyxd_upstream;
|
||||
}
|
||||
@@ -198,6 +186,12 @@ impl Config {
|
||||
if args.allow_illegal_ips {
|
||||
self.topology_cacher.debug.node_describe_allow_illegal_ips = true
|
||||
}
|
||||
if let Some(address_cache_ttl) = args.address_cache_ttl {
|
||||
self.address_cache.time_to_live = address_cache_ttl;
|
||||
}
|
||||
if let Some(address_cache_capacity) = args.address_cache_capacity {
|
||||
self.address_cache.capacity = address_cache_capacity;
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
@@ -311,6 +305,21 @@ impl Base {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct AddressCacheConfig {
|
||||
pub time_to_live: Duration,
|
||||
pub capacity: u64,
|
||||
}
|
||||
|
||||
impl Default for AddressCacheConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
time_to_live: DEFAULT_ADDRESS_CACHE_TTL,
|
||||
capacity: DEFAULT_ADDRESS_CACHE_CAPACITY,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this got separated into 2 structs so that we could have a sane `default` implementation for the latter
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
pub struct NetworkMonitor {
|
||||
@@ -518,9 +527,6 @@ pub struct Rewarding {
|
||||
/// Specifies whether rewarding service is enabled in this process.
|
||||
pub enabled: bool,
|
||||
|
||||
/// Specifies whether to run in simulation mode (compare old vs new calculations without blockchain transactions)
|
||||
pub simulation_mode: bool,
|
||||
|
||||
// this should really be a thing too...
|
||||
// pub paths: RewardingPathfinder,
|
||||
#[serde(default)]
|
||||
@@ -532,7 +538,6 @@ impl Default for Rewarding {
|
||||
fn default() -> Self {
|
||||
Rewarding {
|
||||
enabled: false,
|
||||
simulation_mode: false,
|
||||
debug: Default::default(),
|
||||
}
|
||||
}
|
||||
@@ -545,21 +550,12 @@ pub struct RewardingDebug {
|
||||
/// distribute rewards for given interval.
|
||||
/// Note, only values in range 0-100 are valid
|
||||
pub minimum_interval_monitor_threshold: u8,
|
||||
|
||||
/// Time window in hours for new route-based performance calculation (default: 1 hour)
|
||||
/// Old method always uses 24 hours for comparison
|
||||
pub simulation_new_method_time_window_hours: u32,
|
||||
|
||||
/// Whether to run both old and new calculations in simulation mode
|
||||
pub simulation_run_both_methods: bool,
|
||||
}
|
||||
|
||||
impl Default for RewardingDebug {
|
||||
fn default() -> Self {
|
||||
RewardingDebug {
|
||||
minimum_interval_monitor_threshold: DEFAULT_MONITOR_THRESHOLD,
|
||||
simulation_new_method_time_window_hours: 1, // New method uses 1 hour
|
||||
simulation_run_both_methods: true, // Default to running both for comparison
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::support::cli::{init, run};
|
||||
use std::net::SocketAddr;
|
||||
use std::{net::SocketAddr, time::Duration};
|
||||
|
||||
// Configuration that can be overridden.
|
||||
pub(crate) struct OverrideConfig {
|
||||
@@ -12,9 +12,6 @@ pub(crate) struct OverrideConfig {
|
||||
/// Specifies whether network rewarding is enabled on this API
|
||||
pub(crate) enable_rewarding: Option<bool>,
|
||||
|
||||
/// Specifies whether simulated rewarding is enabled on this API
|
||||
pub(crate) simulate_rewarding: Option<bool>,
|
||||
|
||||
/// Endpoint to nyxd instance used for contract information.
|
||||
pub(crate) nyxd_validator: Option<url::Url>,
|
||||
|
||||
@@ -35,6 +32,9 @@ pub(crate) struct OverrideConfig {
|
||||
/// default: `127.0.0.1:8080` in `debug` builds and `0.0.0.0:8080` in `release`
|
||||
pub(crate) bind_address: Option<SocketAddr>,
|
||||
|
||||
pub(crate) address_cache_ttl: Option<Duration>,
|
||||
pub(crate) address_cache_capacity: Option<u64>,
|
||||
|
||||
pub(crate) allow_illegal_ips: bool,
|
||||
}
|
||||
|
||||
@@ -43,13 +43,15 @@ impl From<init::Args> for OverrideConfig {
|
||||
OverrideConfig {
|
||||
enable_monitor: Some(args.enable_monitor),
|
||||
enable_rewarding: Some(args.enable_rewarding),
|
||||
simulate_rewarding: None, // Not available during initialization
|
||||
nyxd_validator: args.nyxd_validator,
|
||||
mnemonic: args.mnemonic,
|
||||
enable_zk_nym: Some(args.enable_zk_nym),
|
||||
announce_address: args.announce_address,
|
||||
monitor_credentials_mode: Some(args.monitor_credentials_mode),
|
||||
bind_address: args.bind_address,
|
||||
// irrelevant for --init command because we set the value in --run
|
||||
address_cache_ttl: None,
|
||||
address_cache_capacity: None,
|
||||
allow_illegal_ips: args.allow_illegal_ips,
|
||||
}
|
||||
}
|
||||
@@ -60,13 +62,14 @@ impl From<run::Args> for OverrideConfig {
|
||||
OverrideConfig {
|
||||
enable_monitor: args.enable_monitor,
|
||||
enable_rewarding: args.enable_rewarding,
|
||||
simulate_rewarding: args.simulate_rewarding,
|
||||
nyxd_validator: args.nyxd_validator,
|
||||
mnemonic: args.mnemonic,
|
||||
enable_zk_nym: args.enable_zk_nym,
|
||||
announce_address: args.announce_address,
|
||||
monitor_credentials_mode: args.monitor_credentials_mode,
|
||||
bind_address: args.bind_address,
|
||||
address_cache_ttl: args.address_cache_ttl_seconds.map(Duration::from_secs),
|
||||
address_cache_capacity: args.address_cache_capacity,
|
||||
allow_illegal_ips: args.allow_illegal_ips,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ use crate::node_status_api::handlers::status_routes;
|
||||
use crate::nym_contract_cache::handlers::nym_contract_cache_routes;
|
||||
use crate::nym_nodes::handlers::legacy::legacy_nym_node_routes;
|
||||
use crate::nym_nodes::handlers::nym_node_routes;
|
||||
use crate::simulation_api::handlers::simulation_routes;
|
||||
use crate::status;
|
||||
use crate::support::http::openapi::ApiDoc;
|
||||
use crate::support::http::state::AppState;
|
||||
@@ -65,7 +64,6 @@ impl RouterBuilder {
|
||||
.nest("/api-status", status::handlers::api_status_routes())
|
||||
.nest("/nym-nodes", nym_node_routes())
|
||||
.nest("/ecash", ecash_routes())
|
||||
.nest("/simulation", simulation_routes())
|
||||
.nest("/unstable", unstable_routes()), // CORS layer needs to be "outside" of routes
|
||||
);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user