Compare commits

...

48 Commits

Author SHA1 Message Date
dynco-nym 452ce3da8c self described field not null 2024-10-30 17:37:53 +01:00
dynco-nym f6105c9483 Point to internal deps + rebase + v0.1.5 2024-10-30 17:33:31 +01:00
dynco-nym 52004b3e13 Compatible with directory v2 2024-10-30 16:54:04 +01:00
dynco-nym f46332d24a Sqlx offline query data + clippy 2024-10-30 16:54:04 +01:00
dynco-nym 7629c5b3a0 Agent 0.1.4 2024-10-30 16:54:04 +01:00
dynco-nym 4d22ddd75a Agent 0.1.3 2024-10-30 16:54:04 +01:00
dynco-nym e21b37b1a9 Testrun stores gw identity key instead of gw pk 2024-10-30 16:54:04 +01:00
dynco-nym 493aaf7bb4 Better logging on agent 2024-10-30 16:54:04 +01:00
dynco-nym bdecab0205 Clean up stale testruns & logging
- log gw identity key
- better agent testrun logging
- log responses
- change response code for agents
2024-10-30 16:54:04 +01:00
dynco-nym 19d1939fb7 Use unstable explorer client 2024-10-30 16:54:04 +01:00
Jędrzej Stuczyński 16de47ba57 Merge pull request #5063 from nymtech/merge2/release/2024.13-magura
Merge2/release/2024.13 magura
2024-10-30 14:30:11 +00:00
Jędrzej Stuczyński 54a823311b Merge branch 'release/2024.13-magura' into develop 2024-10-30 14:16:07 +00:00
Jędrzej Stuczyński 753a21f8ca bugfix/feature: added NymApiClient method to get all skimmed nodes (#5062)
* bugfix/feature: added NymApiClient method to get all skimmed nodes

* wasm

* helper: utility method for getting ed25519 identity directly from node description
2024-10-30 12:21:27 +00:00
Jędrzej Stuczyński 76da4ab532 bugfix: mark migrated gateways as rewarded in the previous epoch in case theyre in the rewarded set (#5049) 2024-10-30 09:11:13 +00:00
dependabot[bot] 2ca7c7a252 build(deps): bump lazy_static from 1.4.0 to 1.5.0 (#4913) 2024-10-30 07:07:39 +01:00
dependabot[bot] e680e8dc49 build(deps): bump once_cell from 1.19.0 to 1.20.2 (#4952)
Bumps [once_cell](https://github.com/matklad/once_cell) from 1.19.0 to 1.20.2.
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.19.0...v1.20.2)

---
updated-dependencies:
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-29 23:27:10 +01:00
Jon Häggblad 242bc93807 Merge pull request #5027 from nymtech/jon/integrate-credential-proxy-into-workspace
Integrate nym-credential-proxy into workspace
2024-10-29 20:47:07 +01:00
dynco-nym 94c6cdc7b2 Type coercion into time::Date 2024-10-29 17:46:35 +01:00
Jon Häggblad fce322c789 Remove unused workflow 2024-10-29 17:46:35 +01:00
Jon Häggblad ac5baab693 Add to default workspace 2024-10-29 17:46:35 +01:00
Jon Häggblad 23da0f4d8e Workspace updates 2024-10-29 17:46:35 +01:00
Jon Häggblad 25e3b4cd83 Delete old Cargo files 2024-10-29 17:46:35 +01:00
Jon Häggblad 8e4d72a565 Update for rebase 2024-10-29 17:46:34 +01:00
Jon Häggblad ad84a6d85d Add nym-vpn-api crates to main workspace 2024-10-29 17:45:56 +01:00
Jędrzej Stuczyński 34c5f23684 Merge pull request #5061 from nymtech/merge1/release/2024.13-magura
checkpoint merge release/2024.13-magura into develop
2024-10-29 16:17:16 +00:00
Jędrzej Stuczyński 000f2f1c29 Merge branch 'release/2024.13-magura' into develop 2024-10-29 15:31:51 +00:00
Jędrzej Stuczyński 317f7fffa9 added hacky routes to return nymnodes alongside legacy nodes (#5051)
* added hacky routes to return nymnodes alongside legacy nodes

* fixed mixing role

* Update client (#5054)

* removed hacky mixnodes endpoint for its not used

* construct explorer-api client with timeout

---------

Co-authored-by: Dinko Zdravac <173912580+dynco-nym@users.noreply.github.com>
2024-10-29 08:35:07 +00:00
Jędrzej Stuczyński 4396def133 bugfix: adjust runtime storage migration (#5047) 2024-10-28 10:07:51 +00:00
Jędrzej Stuczyński a56a318a7f bugfix: supersede 'cb13be27f8f61d9ae74d924e85d2e6787895eb14' by using query parameters (#5046) 2024-10-28 09:57:14 +00:00
Jędrzej Stuczyński 4d08047c57 bugfix: restore default http port for nym-api (#5045)
when it was run under 'rocket' server the port used was 8000. let's restore that value
2024-10-28 09:28:47 +00:00
Jędrzej Stuczyński cb13be27f8 bugfix: fix ecash handlers routes (#5043) 2024-10-28 09:12:40 +00:00
Jędrzej Stuczyński fa392169c1 bugfix: use human readable roles for annotations (#5036)
* bugfix: use human readable roles for annotations

* update the wallet code to use 'DisplayRole'
2024-10-28 09:08:17 +00:00
Jędrzej Stuczyński 3167fb34e6 bugfix: don't assign exit gateways to standby set (#5041) 2024-10-25 16:53:51 +01:00
Jędrzej Stuczyński 9ca6301e1c bugfix: make sure nym-nodes are also tested by network monitor (#5040) 2024-10-25 15:20:39 +01:00
Jędrzej Stuczyński e16a73338e bugfix: use bonded nym-nodes for determining initial network monitor nodes (#5039) 2024-10-25 12:34:25 +01:00
Bogdan-Ștefan Neacşu bfa3825d70 Pass the Poisson flag on authenticator config (#5037) 2024-10-25 14:08:52 +03:00
Jędrzej Stuczyński d626e7689f bugfix: make gateways insert themselves into [local] topology (#5038)
* added explicit SP suffix to started tasks

* added 'GatewayTopologyProvider' that always injects itself into the network

* use the new topology provider to bypass described bootstrapping problem
2024-10-25 12:06:16 +01:00
Jędrzej Stuczyński 9234474565 bugfix: use old name for 'epoch_role' in SkimmedNode (#5034)
* bugfix: use old name for 'epoch_role' in SkimmedNode

* clippy
2024-10-25 09:29:37 +01:00
Jędrzej Stuczyński 29f8386b50 bugfix: make sure to use correct highest node id when assigning role (#5032)
* bugfix: make sure to use correct highest node id when assigning role

* make sure nym-api provides sorted values for older contracts
2024-10-24 17:47:57 +01:00
Jędrzej Stuczyński 0edb9631a6 feature: use axum_client_ip for attempting to extract source ip (#5031) 2024-10-24 17:38:32 +01:00
Jędrzej Stuczyński 4b0153f5f2 bugfix: fixed backwards incompatibility for /gateways/described endpoint (#5030) 2024-10-24 15:37:41 +01:00
Jędrzej Stuczyński c09a17b66d bugfix: verifying signed information of legacy nodes (#5029)
* Added new legacy variant of HostInformation

* fixed 'option_bs58_x25519_pubkey' for empty string

* 'Debug' impl for x25519 and ed25519 to use human-readable representation

* HttpClient to use explicit 'serde_json' conversion for better errors

* additional 'Debug' derives
2024-10-24 15:00:34 +01:00
Jędrzej Stuczyński d18ddcdc11 bugfix: introduce 'LegacyPendingMixNodeChanges' that does not contain 'cost_params_change' (#5028)
* bugfix: introduce 'LegacyPendingMixNodeChanges' that does not contain 'cost_params_change'

* updated schema files due to removal of '#[serde(deny_unknown_fields)]'
2024-10-24 10:54:00 +01:00
Jędrzej Stuczyński d2df542280 bugfix: missing #[serde(default)] for announce port (#5024) 2024-10-23 16:52:17 +01:00
Jędrzej Stuczyński 6fafd8c03a bugfix: directory v2.1 get_all_avg_gateway_reliability_in_interval query (#5023)
* log full storage errors on failures

* use query_as! macro
2024-10-23 16:36:21 +01:00
Jędrzej Stuczyński 38e66f6ddf added 'get_all_described_nodes' to NymApiClient and adjusted return type on api itself (#5016) 2024-10-23 09:48:25 +01:00
Bogdan-Ștefan Neacşu b9fbe0b8f3 Reapply fixes to new branch (#5014) 2024-10-22 18:33:18 +03:00
Bogdan-Ștefan Neacşu daafb5cae4 Consume only positive bandwidth (#5013) 2024-10-22 17:46:46 +03:00
133 changed files with 2692 additions and 6576 deletions
@@ -1,45 +0,0 @@
name: ci-nym-credential-proxy
on:
pull_request:
paths:
- 'common/**'
- 'nym-credential-proxy/**'
- '.github/workspace/ci-nym-credential-proxy.yml'
workflow_dispatch:
jobs:
build:
runs-on: arc-ubuntu-22.04
env:
CARGO_TERM_COLOR: always
MANIFEST_PATH: "--manifest-path nym-credential-proxy/Cargo.toml"
steps:
- name: Check out repository code
uses: actions/checkout@v4
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
components: rustfmt, clippy
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: ${{ env.MANIFEST_PATH }} --all -- --check
- name: Build
uses: actions-rs/cargo@v1
with:
command: build
args: ${{ env.MANIFEST_PATH }} --workspace --all-targets
- name: Clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: ${{ env.MANIFEST_PATH }} --workspace --all-targets -- -D warnings
Generated
+450 -719
View File
File diff suppressed because it is too large Load Diff
+22 -21
View File
@@ -19,33 +19,33 @@ members = [
"clients/native",
"clients/native/websocket-requests",
"clients/socks5",
"common/authenticator-requests",
"common/async-file-watcher",
"common/authenticator-requests",
"common/bandwidth-controller",
"common/bin-common",
"common/client-core",
"common/client-core/config-types",
"common/client-core/surb-storage",
"common/client-core/gateways-storage",
"common/client-core/surb-storage",
"common/client-libs/gateway-client",
"common/client-libs/mixnet-client",
"common/client-libs/validator-client",
"common/commands",
"common/config",
"common/cosmwasm-smart-contracts/coconut-bandwidth-contract",
"common/cosmwasm-smart-contracts/ecash-contract",
"common/cosmwasm-smart-contracts/coconut-dkg",
"common/cosmwasm-smart-contracts/contracts-common",
"common/cosmwasm-smart-contracts/ecash-contract",
"common/cosmwasm-smart-contracts/group-contract",
"common/cosmwasm-smart-contracts/mixnet-contract",
"common/cosmwasm-smart-contracts/multisig-contract",
"common/cosmwasm-smart-contracts/vesting-contract",
"common/country-group",
"common/credential-storage",
"common/credentials",
"common/credential-utils",
"common/credentials-interface",
"common/credential-verification",
"common/credentials",
"common/credentials-interface",
"common/crypto",
"common/dkg",
"common/ecash-double-spending",
@@ -65,10 +65,10 @@ members = [
"common/network-defaults",
"common/node-tester-utils",
"common/nonexhaustive-delayqueue",
"common/nymcoconut",
"common/nym_offline_compact_ecash",
"common/nym-id",
"common/nym-metrics",
"common/nym_offline_compact_ecash",
"common/nymcoconut",
"common/nymsphinx",
"common/nymsphinx/acknowledgements",
"common/nymsphinx/addressing",
@@ -104,20 +104,23 @@ members = [
"gateway",
"integrations/bity",
"mixnode",
"sdk/ffi/cpp",
"sdk/ffi/go",
"sdk/ffi/shared",
"sdk/lib/socks5-listener",
"sdk/rust/nym-sdk",
"sdk/ffi/shared",
"sdk/ffi/go",
"sdk/ffi/cpp",
"service-providers/authenticator",
"service-providers/common",
"service-providers/ip-packet-router",
"service-providers/network-requester",
"nym-network-monitor",
"nym-api",
"nym-browser-extension/storage",
"nym-api/nym-api-requests",
"nym-browser-extension/storage",
"nym-credential-proxy/nym-credential-proxy",
"nym-credential-proxy/nym-credential-proxy-requests",
"nym-credential-proxy/vpn-api-lib-wasm",
"nym-data-observatory",
"nym-network-monitor",
"nym-node",
"nym-node/nym-node-http-api",
"nym-node/nym-node-requests",
@@ -140,11 +143,11 @@ members = [
"wasm/mix-fetch",
"wasm/node-tester",
"wasm/zknym-lib",
"tools/internal/testnet-manager",
"tools/internal/testnet-manager/dkg-bypass-contract",
"tools/echo-server",
"tools/internal/contract-state-importer/importer-cli",
"tools/internal/contract-state-importer/importer-contract",
"tools/internal/testnet-manager",
"tools/internal/testnet-manager/dkg-bypass-contract",
]
default-members = [
@@ -155,6 +158,7 @@ default-members = [
"gateway",
"mixnode",
"nym-api",
"nym-credential-proxy/nym-credential-proxy",
"nym-data-observatory",
"nym-node",
"nym-node-status-api",
@@ -193,16 +197,14 @@ aead = "0.5.2"
anyhow = "1.0.90"
argon2 = "0.5.0"
async-trait = "0.1.83"
axum-client-ip = "0.6.1"
axum = "0.7.5"
axum-extra = "0.9.4"
base64 = "0.22.1"
bincode = "1.3.3"
bip39 = { version = "2.0.0", features = ["zeroize"] }
# can we unify those?
bit-vec = "0.7.0"
bit-vec = "0.7.0" # can we unify those?
bitvec = "1.0.0"
blake3 = "1.5.4"
bloomfilter = "1.0.14"
bs58 = "0.5.1"
@@ -269,7 +271,7 @@ ipnetwork = "0.20"
isocountry = "0.3.2"
itertools = "0.13.0"
k256 = "0.13"
lazy_static = "1.4.0"
lazy_static = "1.5.0"
ledger-transport = "0.10.0"
ledger-transport-hid = "0.10.0"
log = "0.4"
@@ -279,7 +281,7 @@ moka = { version = "0.12", features = ["future"] }
nix = "0.27.1"
notify = "5.1.0"
okapi = "0.7.0"
once_cell = "1.7.2"
once_cell = "1.20.2"
opentelemetry = "0.19.0"
opentelemetry-jaeger = "0.18.0"
parking_lot = "0.12.3"
@@ -407,7 +409,6 @@ wasm-bindgen-futures = "0.4.45"
wasmtimer = "0.2.0"
web-sys = "0.3.72"
# Profile settings for individual crates
# Compile-time verified queries do quite a bit of work at compile time. Incremental
@@ -112,7 +112,7 @@ impl GeoAwareTopologyProvider {
async fn get_topology(&self) -> Option<NymTopology> {
let mixnodes = match self
.validator_client
.get_basic_active_mixing_assigned_nodes(Some(self.client_version.clone()))
.get_all_basic_active_mixing_assigned_nodes(Some(self.client_version.clone()))
.await
{
Err(err) => {
@@ -6,7 +6,6 @@ pub(crate) use accessor::{TopologyAccessor, TopologyReadPermit};
use futures::StreamExt;
use log::*;
use nym_sphinx::addressing::nodes::NodeIdentity;
use nym_topology::provider_trait::TopologyProvider;
use nym_topology::NymTopologyError;
use std::time::Duration;
@@ -18,7 +17,11 @@ use wasmtimer::tokio::sleep;
mod accessor;
pub mod geo_aware_provider;
pub(crate) mod nym_api_provider;
pub mod nym_api_provider;
pub use geo_aware_provider::GeoAwareTopologyProvider;
pub use nym_api_provider::{Config as NymApiTopologyProviderConfig, NymApiTopologyProvider};
pub use nym_topology::provider_trait::TopologyProvider;
// TODO: move it to config later
const MAX_FAILURE_COUNT: usize = 10;
@@ -14,9 +14,10 @@ use url::Url;
pub const DEFAULT_MIN_MIXNODE_PERFORMANCE: u8 = 50;
pub const DEFAULT_MIN_GATEWAY_PERFORMANCE: u8 = 50;
pub(crate) struct Config {
pub(crate) min_mixnode_performance: u8,
pub(crate) min_gateway_performance: u8,
#[derive(Debug)]
pub struct Config {
pub min_mixnode_performance: u8,
pub min_gateway_performance: u8,
}
impl Default for Config {
@@ -29,7 +30,7 @@ impl Default for Config {
}
}
pub(crate) struct NymApiTopologyProvider {
pub struct NymApiTopologyProvider {
config: Config,
validator_client: nym_validator_client::client::NymApiClient,
@@ -40,7 +41,7 @@ pub(crate) struct NymApiTopologyProvider {
}
impl NymApiTopologyProvider {
pub(crate) fn new(
pub fn new(
config: Config,
mut nym_api_urls: Vec<Url>,
client_version: String,
@@ -98,7 +99,7 @@ impl NymApiTopologyProvider {
async fn get_current_compatible_topology(&mut self) -> Option<NymTopology> {
let mixnodes = match self
.validator_client
.get_basic_active_mixing_assigned_nodes(Some(self.client_version.clone()))
.get_all_basic_active_mixing_assigned_nodes(Some(self.client_version.clone()))
.await
{
Err(err) => {
+3 -1
View File
@@ -121,7 +121,9 @@ pub async fn current_mixnodes<R: Rng>(
log::trace!("Fetching list of mixnodes from: {nym_api}");
let mixnodes = client.get_basic_active_mixing_assigned_nodes(None).await?;
let mixnodes = client
.get_all_basic_active_mixing_assigned_nodes(None)
.await?;
let valid_mixnodes = mixnodes
.iter()
.filter_map(|mixnode| mixnode.try_into().ok())
@@ -19,7 +19,7 @@ use nym_api_requests::ecash::{
};
use nym_api_requests::models::{
GatewayCoreStatusResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse,
RewardEstimationResponse, StakeSaturationResponse,
NymNodeDescription, RewardEstimationResponse, StakeSaturationResponse,
};
use nym_api_requests::models::{LegacyDescribedGateway, MixNodeBondAnnotated};
use nym_api_requests::nym_nodes::SkimmedNode;
@@ -30,10 +30,10 @@ use time::Date;
use url::Url;
pub use crate::nym_api::NymApiClientExt;
use nym_mixnet_contract_common::NymNodeDetails;
pub use nym_mixnet_contract_common::{
mixnode::MixNodeDetails, GatewayBond, IdentityKey, IdentityKeyRef, NodeId,
};
// re-export the type to not break existing imports
pub use crate::coconut::EcashApiClient;
@@ -106,7 +106,9 @@ impl Config {
pub struct Client<C, S = NoSigner> {
// ideally they would have been read-only, but unfortunately rust doesn't have such features
// #[deprecated(note = "please use `nym_api_client` instead")]
pub nym_api: nym_api::Client,
// pub nym_api_client: NymApiClient,
pub nyxd: NyxdClient<C, S>,
}
@@ -243,6 +245,50 @@ impl<C, S> Client<C, S> {
Ok(self.nym_api.get_gateways().await?)
}
// TODO: combine with NymApiClient...
pub async fn get_all_cached_described_nodes(
&self,
) -> Result<Vec<NymNodeDescription>, ValidatorClientError> {
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
let mut page = 0;
let mut descriptions = Vec::new();
loop {
let mut res = self.nym_api.get_nodes_described(Some(page), None).await?;
descriptions.append(&mut res.data);
if descriptions.len() < res.pagination.total {
page += 1
} else {
break;
}
}
Ok(descriptions)
}
// TODO: combine with NymApiClient...
pub async fn get_all_cached_bonded_nym_nodes(
&self,
) -> Result<Vec<NymNodeDetails>, ValidatorClientError> {
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
let mut page = 0;
let mut bonds = Vec::new();
loop {
let mut res = self.nym_api.get_nym_nodes(Some(page), None).await?;
bonds.append(&mut res.data);
if bonds.len() < res.pagination.total {
page += 1
} else {
break;
}
}
Ok(bonds)
}
pub async fn blind_sign(
&self,
request_body: &BlindSignRequestBody,
@@ -290,7 +336,7 @@ impl NymApiClient {
self.nym_api.change_base_url(new_endpoint);
}
#[deprecated(note = "use get_basic_active_mixing_assigned_nodes instead")]
#[deprecated(note = "use get_all_basic_active_mixing_assigned_nodes instead")]
pub async fn get_basic_mixnodes(
&self,
semver_compatibility: Option<String>,
@@ -327,7 +373,7 @@ impl NymApiClient {
loop {
let mut res = self
.nym_api
.get_all_basic_entry_assigned_nodes(
.get_basic_entry_assigned_nodes(
semver_compatibility.clone(),
false,
Some(page),
@@ -348,7 +394,7 @@ impl NymApiClient {
/// retrieve basic information for nodes that got assigned 'mixing' node in this epoch
/// this includes legacy mixnodes and nym-nodes
pub async fn get_basic_active_mixing_assigned_nodes(
pub async fn get_all_basic_active_mixing_assigned_nodes(
&self,
semver_compatibility: Option<String>,
) -> Result<Vec<SkimmedNode>, ValidatorClientError> {
@@ -378,6 +424,32 @@ impl NymApiClient {
Ok(nodes)
}
/// retrieve basic information for all bonded nodes on the network
pub async fn get_all_basic_nodes(
&self,
semver_compatibility: Option<String>,
) -> Result<Vec<SkimmedNode>, ValidatorClientError> {
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
let mut page = 0;
let mut nodes = Vec::new();
loop {
let mut res = self
.nym_api
.get_basic_nodes(semver_compatibility.clone(), false, Some(page), None)
.await?;
nodes.append(&mut res.nodes.data);
if nodes.len() < res.nodes.pagination.total {
page += 1
} else {
break;
}
}
Ok(nodes)
}
pub async fn get_cached_active_mixnodes(
&self,
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
@@ -404,6 +476,48 @@ impl NymApiClient {
Ok(self.nym_api.get_gateways_described().await?)
}
pub async fn get_all_described_nodes(
&self,
) -> Result<Vec<NymNodeDescription>, ValidatorClientError> {
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
let mut page = 0;
let mut descriptions = Vec::new();
loop {
let mut res = self.nym_api.get_nodes_described(Some(page), None).await?;
descriptions.append(&mut res.data);
if descriptions.len() < res.pagination.total {
page += 1
} else {
break;
}
}
Ok(descriptions)
}
pub async fn get_all_bonded_nym_nodes(
&self,
) -> Result<Vec<NymNodeDetails>, ValidatorClientError> {
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
let mut page = 0;
let mut bonds = Vec::new();
loop {
let mut res = self.nym_api.get_nym_nodes(Some(page), None).await?;
bonds.append(&mut res.data);
if bonds.len() < res.pagination.total {
page += 1
} else {
break;
}
}
Ok(bonds)
}
pub async fn get_gateway_core_status_count(
&self,
identity: IdentityKeyRef<'_>,
@@ -11,9 +11,10 @@ use nym_api_requests::ecash::models::{
};
use nym_api_requests::ecash::VerificationKeyResponse;
use nym_api_requests::models::{
AnnotationResponse, LegacyDescribedMixNode, NodePerformanceResponse,
AnnotationResponse, LegacyDescribedMixNode, NodePerformanceResponse, NymNodeDescription,
};
use nym_api_requests::nym_nodes::PaginatedCachedNodesResponse;
use nym_api_requests::pagination::PaginatedResponse;
pub use nym_api_requests::{
ecash::{
models::{
@@ -38,7 +39,7 @@ use nym_contracts_common::IdentityKey;
pub use nym_http_api_client::Client;
use nym_http_api_client::{ApiClient, NO_PARAMS};
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, NodeId};
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, NodeId, NymNodeDetails};
use time::format_description::BorrowedFormatItem;
use time::Date;
use tracing::instrument;
@@ -127,6 +128,44 @@ pub trait NymApiClientExt: ApiClient {
.await
}
async fn get_nodes_described(
&self,
page: Option<u32>,
per_page: Option<u32>,
) -> Result<PaginatedResponse<NymNodeDescription>, NymAPIError> {
let mut params = Vec::new();
if let Some(page) = page {
params.push(("page", page.to_string()))
}
if let Some(per_page) = per_page {
params.push(("per_page", per_page.to_string()))
}
self.get_json(&[routes::API_VERSION, "nym-nodes", "described"], &params)
.await
}
async fn get_nym_nodes(
&self,
page: Option<u32>,
per_page: Option<u32>,
) -> Result<PaginatedResponse<NymNodeDetails>, NymAPIError> {
let mut params = Vec::new();
if let Some(page) = page {
params.push(("page", page.to_string()))
}
if let Some(per_page) = per_page {
params.push(("per_page", per_page.to_string()))
}
self.get_json(&[routes::API_VERSION, "nym-nodes", "bonded"], &params)
.await
}
#[tracing::instrument(level = "debug", skip_all)]
async fn get_basic_mixnodes(
&self,
@@ -178,7 +217,7 @@ pub trait NymApiClientExt: ApiClient {
/// retrieve basic information for nodes are capable of operating as an entry gateway
/// this includes legacy gateways and nym-nodes
#[instrument(level = "debug", skip(self))]
async fn get_all_basic_entry_assigned_nodes(
async fn get_basic_entry_assigned_nodes(
&self,
semver_compatibility: Option<String>,
no_legacy: bool,
@@ -259,6 +298,38 @@ pub trait NymApiClientExt: ApiClient {
.await
}
async fn get_basic_nodes(
&self,
semver_compatibility: Option<String>,
no_legacy: bool,
page: Option<u32>,
per_page: Option<u32>,
) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> {
let mut params = Vec::new();
if let Some(arg) = &semver_compatibility {
params.push(("semver_compatibility", arg.clone()))
}
if no_legacy {
params.push(("no_legacy", "true".to_string()))
}
if let Some(page) = page {
params.push(("page", page.to_string()))
}
if let Some(per_page) = per_page {
params.push(("per_page", per_page.to_string()))
}
self.get_json(
&[routes::API_VERSION, "unstable", "nym-nodes", "skimmed"],
&params,
)
.await
}
#[instrument(level = "debug", skip(self))]
async fn get_active_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
self.get_json(
@@ -17,6 +17,7 @@ use crate::{
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Addr, Coin, Decimal, StdResult, Uint128};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
/// Full details associated with given mixnode.
@@ -647,14 +648,39 @@ impl From<LegacyMixLayer> for u8 {
export_to = "ts-packages/types/src/types/rust/PendingMixnodeChanges.ts"
)
)]
#[cw_serde]
#[derive(Default, Copy)]
// note: we had to remove `#[cw_serde]` as it enforces `#[serde(deny_unknown_fields)]` which we do not want
// with the addition of .cost_params_change field
#[derive(
::cosmwasm_schema::serde::Serialize,
::cosmwasm_schema::serde::Deserialize,
::std::clone::Clone,
::std::fmt::Debug,
::std::cmp::PartialEq,
::cosmwasm_schema::schemars::JsonSchema,
Default,
Copy,
)]
#[schemars(crate = "::cosmwasm_schema::schemars")]
pub struct PendingMixNodeChanges {
pub pledge_change: Option<EpochEventId>,
#[serde(default)]
pub cost_params_change: Option<IntervalEventId>,
}
#[derive(Default, Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct LegacyPendingMixNodeChanges {
pub pledge_change: Option<EpochEventId>,
}
impl From<PendingMixNodeChanges> for LegacyPendingMixNodeChanges {
fn from(value: PendingMixNodeChanges) -> Self {
LegacyPendingMixNodeChanges {
pledge_change: value.pledge_change,
}
}
}
impl PendingMixNodeChanges {
pub fn new_empty() -> PendingMixNodeChanges {
PendingMixNodeChanges {
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
use std::fmt::{self, Display, Formatter};
use std::fmt::{self, Debug, Display, Formatter};
use std::str::FromStr;
use thiserror::Error;
use zeroize::{Zeroize, ZeroizeOnDrop};
@@ -112,12 +112,18 @@ impl PemStorableKeyPair for KeyPair {
}
}
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
#[derive(PartialEq, Eq, Hash, Copy, Clone)]
pub struct PublicKey(x25519_dalek::PublicKey);
impl Display for PublicKey {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_base58_string())
Display::fmt(&self.to_base58_string(), f)
}
}
impl Debug for PublicKey {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Debug::fmt(&self.to_base58_string(), f)
}
}
@@ -31,8 +31,16 @@ pub mod option_bs58_x25519_pubkey {
pub fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Option<PublicKey>, D::Error> {
let s = Option::<String>::deserialize(deserializer)?;
s.map(|s| PublicKey::from_base58_string(&s).map_err(serde::de::Error::custom))
.transpose()
match Option::<String>::deserialize(deserializer)? {
None => Ok(None),
Some(s) => {
if s.is_empty() {
Ok(None)
} else {
Some(PublicKey::from_base58_string(&s).map_err(serde::de::Error::custom))
.transpose()
}
}
}
}
}
+9 -3
View File
@@ -5,7 +5,7 @@ pub use ed25519_dalek::SignatureError;
use ed25519_dalek::{Signer, SigningKey};
pub use ed25519_dalek::{Verifier, PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, SIGNATURE_LENGTH};
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
use std::fmt::{self, Display, Formatter};
use std::fmt::{self, Debug, Display, Formatter};
use std::str::FromStr;
use thiserror::Error;
use zeroize::{Zeroize, ZeroizeOnDrop};
@@ -119,12 +119,18 @@ impl PemStorableKeyPair for KeyPair {
}
/// ed25519 EdDSA Public Key
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[derive(Copy, Clone, Eq, PartialEq)]
pub struct PublicKey(ed25519_dalek::VerifyingKey);
impl Display for PublicKey {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_base58_string())
Display::fmt(&self.to_base58_string(), f)
}
}
impl Debug for PublicKey {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Debug::fmt(&self.to_base58_string(), f)
}
}
+13 -12
View File
@@ -35,6 +35,9 @@ pub enum HttpClientError<E: Display = String> {
source: reqwest::Error,
},
#[error("failed to deserialise received response: {source}")]
ResponseDeserialisationFailure { source: serde_json::Error },
#[error("provided url is malformed: {source}")]
MalformedUrl {
#[from]
@@ -531,19 +534,17 @@ where
}
if res.status().is_success() {
#[cfg(debug_assertions)]
{
let text = res.text().await.inspect_err(|err| {
tracing::error!("Couldn't even get response text: {err}");
})?;
tracing::trace!("Result:\n{:#?}", text);
serde_json::from_str(&text)
.map_err(|err| HttpClientError::GenericRequestFailure(err.to_string()))
let text = res.text().await?;
match serde_json::from_str(&text) {
Ok(res) => Ok(res),
Err(source) => {
#[cfg(debug_assertions)]
{
tracing::trace!("Result:\n{:#?}", text);
}
Err(HttpClientError::ResponseDeserialisationFailure { source })
}
}
#[cfg(not(debug_assertions))]
Ok(res.json().await?)
} else if res.status() == StatusCode::NOT_FOUND {
Err(HttpClientError::NotFound)
} else {
+1
View File
@@ -11,6 +11,7 @@ license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
axum-client-ip.workspace = true
axum.workspace = true
bytes = { workspace = true }
colored.workspace = true
+3 -3
View File
@@ -1,18 +1,18 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use axum::extract::{ConnectInfo, Request};
use axum::extract::Request;
use axum::http::header::{HOST, USER_AGENT};
use axum::http::HeaderValue;
use axum::middleware::Next;
use axum::response::IntoResponse;
use axum_client_ip::InsecureClientIp;
use colored::Colorize;
use std::net::SocketAddr;
use std::time::Instant;
use tracing::info;
pub async fn logger(
ConnectInfo(addr): ConnectInfo<SocketAddr>,
InsecureClientIp(addr): InsecureClientIp,
request: Request,
next: Next,
) -> impl IntoResponse {
+1 -2
View File
@@ -2,7 +2,6 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct TestrunAssignment {
/// has nothing to do with GW identity key. This is PK from `gateways` table
pub testrun_id: i64,
pub gateway_pk_id: i64,
pub gateway_identity_key: String,
}
+4
View File
@@ -286,6 +286,10 @@ impl NymTopology {
self.get_gateway(gateway_identity).is_some()
}
pub fn insert_gateway(&mut self, gateway: gateway::LegacyNode) {
self.gateways.push(gateway)
}
pub fn set_gateways(&mut self, gateways: Vec<gateway::LegacyNode>) {
self.gateways = gateways
}
+1 -1
View File
@@ -116,7 +116,7 @@ impl<'a> TryFrom<&'a SkimmedNode> for LegacyNode {
});
}
let layer = match value.epoch_role {
let layer = match value.role {
NodeRole::Mixnode { layer } => layer
.try_into()
.map_err(|_| MixnodeConversionError::InvalidLayer)?,
+1 -1
View File
@@ -68,7 +68,7 @@ pub async fn current_network_topology_async(
let api_client = NymApiClient::new(url);
let mixnodes = api_client
.get_basic_active_mixing_assigned_nodes(None)
.get_all_basic_active_mixing_assigned_nodes(None)
.await?;
let gateways = api_client.get_all_basic_entry_assigned_nodes(None).await?;
+5 -2
View File
@@ -158,10 +158,13 @@ impl<St: Storage + Clone + 'static> PeerController<St> {
.ok_or(Error::MissingClientBandwidthEntry)?
.client_id
{
storage.create_bandwidth_entry(client_id).await?;
let bandwidth = storage
.get_available_bandwidth(client_id)
.await?
.ok_or(Error::MissingClientBandwidthEntry)?;
Ok(Some(BandwidthStorageManager::new(
storage,
ClientBandwidth::new(Default::default()),
ClientBandwidth::new(bandwidth.into()),
client_id,
BandwidthFlushingBehaviourConfig::default(),
true,
+7 -6
View File
@@ -84,12 +84,13 @@ impl<St: Storage + Clone + 'static> PeerHandle<St> {
.ok_or(Error::InconsistentConsumedBytes)?
.try_into()
.map_err(|_| Error::InconsistentConsumedBytes)?;
if bandwidth_manager
.write()
.await
.try_use_bandwidth(spent_bandwidth)
.await
.is_err()
if spent_bandwidth > 0
&& bandwidth_manager
.write()
.await
.try_use_bandwidth(spent_bandwidth)
.await
.is_err()
{
let success = self.remove_peer().await?;
return Ok(!success);
+15 -15
View File
@@ -433,7 +433,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.59",
"syn 2.0.85",
]
[[package]]
@@ -1483,9 +1483,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.81"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
dependencies = [
"unicode-ident",
]
@@ -1660,7 +1660,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"syn 2.0.59",
"syn 2.0.85",
]
[[package]]
@@ -1702,9 +1702,9 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.210"
version = "1.0.214"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
dependencies = [
"serde_derive",
]
@@ -1729,13 +1729,13 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.210"
version = "1.0.214"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.59",
"syn 2.0.85",
]
[[package]]
@@ -1746,7 +1746,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.59",
"syn 2.0.85",
]
[[package]]
@@ -1769,7 +1769,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.59",
"syn 2.0.85",
]
[[package]]
@@ -1928,9 +1928,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.59"
version = "2.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a6531ffc7b071655e4ce2e04bd464c4830bb585a61cabb96cf808f05172615a"
checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56"
dependencies = [
"proc-macro2",
"quote",
@@ -1954,7 +1954,7 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.59",
"syn 2.0.85",
]
[[package]]
@@ -2120,5 +2120,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.59",
"syn 2.0.85",
]
@@ -3689,8 +3689,7 @@
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
}
},
"Percent": {
"description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)",
@@ -5239,8 +5238,7 @@
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
}
},
"Percent": {
"description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)",
@@ -5575,8 +5573,7 @@
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
}
},
"Percent": {
"description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)",
@@ -7595,8 +7592,7 @@
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
}
},
"Percent": {
"description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)",
@@ -315,8 +315,7 @@
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
}
},
"Percent": {
"description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)",
@@ -323,8 +323,7 @@
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
}
},
"Percent": {
"description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)",
@@ -317,8 +317,7 @@
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
}
},
"Percent": {
"description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)",
@@ -319,8 +319,7 @@
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
}
},
"Percent": {
"description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)",
@@ -4,6 +4,7 @@
use super::helpers::must_get_gateway_bond_by_owner;
use super::storage;
use crate::constants::default_node_costs;
use crate::interval::storage as interval_storage;
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::nodes::helpers::save_new_nymnode_with_id;
use crate::nodes::transactions::add_nym_node_inner;
@@ -115,6 +116,10 @@ pub fn try_migrate_to_nymnode(
comment: "legacy gateway did not have a pre-assigned node id".to_string(),
})?;
let current_epoch =
interval_storage::current_interval(deps.storage)?.current_epoch_absolute_id();
let previous_epoch = current_epoch.saturating_sub(1);
// create nym-node entry
// for gateways it's quite straightforward as there are no delegations or rewards to worry about
save_new_nymnode_with_id(
@@ -125,6 +130,7 @@ pub fn try_migrate_to_nymnode(
cost_params,
info.sender.clone(),
gateway_bond.pledge_amount,
previous_epoch,
)?;
storage::PREASSIGNED_LEGACY_IDS.remove(deps.storage, gateway_identity.clone());
+6 -3
View File
@@ -22,6 +22,8 @@ pub(crate) fn save_new_nymnode(
pledge: Coin,
) -> Result<NodeId, MixnetContractError> {
let node_id = next_nymnode_id_counter(storage)?;
let current_epoch = interval_storage::current_interval(storage)?.current_epoch_absolute_id();
save_new_nymnode_with_id(
storage,
node_id,
@@ -30,11 +32,13 @@ pub(crate) fn save_new_nymnode(
cost_params,
owner,
pledge,
current_epoch,
)?;
Ok(node_id)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn save_new_nymnode_with_id(
storage: &mut dyn Storage,
node_id: NodeId,
@@ -43,10 +47,9 @@ pub(crate) fn save_new_nymnode_with_id(
cost_params: NodeCostParams,
owner: Addr,
pledge: Coin,
last_rewarding_epoch: u32,
) -> Result<(), MixnetContractError> {
let current_epoch = interval_storage::current_interval(storage)?.current_epoch_absolute_id();
let node_rewarding = NodeRewarding::initialise_new(cost_params, &pledge, current_epoch)?;
let node_rewarding = NodeRewarding::initialise_new(cost_params, &pledge, last_rewarding_epoch)?;
let node_bond = NymNodeBond::new(node_id, owner, pledge, node, bonding_height);
// save node bond data
+32 -2
View File
@@ -52,8 +52,8 @@ pub(crate) fn save_assignment(
// update metadata
let mut metadata = ROLES_METADATA.load(storage, inactive)?;
let last = assignment.nodes.last().copied().unwrap_or_default();
metadata.set_highest_id(last, assignment.role);
let highest_id = assignment.nodes.iter().max().copied().unwrap_or_default();
metadata.set_highest_id(highest_id, assignment.role);
metadata.set_role_count(assignment.role, assignment.nodes.len() as u32);
if assignment.is_final_assignment() {
metadata.fully_assigned = true
@@ -140,6 +140,7 @@ pub(crate) fn initialise_storage(storage: &mut dyn Storage) -> Result<(), Mixnet
mod tests {
use super::*;
use crate::support::tests::test_helpers;
use crate::support::tests::test_helpers::TestSetup;
#[test]
fn next_id() {
@@ -149,4 +150,33 @@ mod tests {
assert_eq!(i, next_nymnode_id_counter(deps.as_mut().storage).unwrap());
}
}
#[test]
fn assigning_role_uses_highest_id_even_if_not_sorted() {
let mut test = TestSetup::new();
let deps = test.deps_mut();
let sorted = RoleAssignment {
role: Role::EntryGateway,
nodes: vec![1, 2, 3],
};
let unsorted = RoleAssignment {
role: Role::Layer1,
nodes: vec![8, 5, 4],
};
save_assignment(deps.storage, sorted).unwrap();
save_assignment(deps.storage, unsorted).unwrap();
let storage = deps.as_ref().storage;
let active_bucket = ACTIVE_ROLES_BUCKET.load(storage).unwrap();
let inactive = active_bucket.other() as u8;
let metadata = ROLES_METADATA.load(storage, inactive).unwrap();
assert_eq!(metadata.entry_gateway_metadata.highest_id, 3);
assert_eq!(metadata.layer1_metadata.highest_id, 8);
assert_eq!(metadata.highest_rewarded_id(), 8)
}
}
+9
View File
@@ -13,6 +13,8 @@ pub use nym_explorer_api_requests::{
// Paths
const API_VERSION: &str = "v1";
const TMP: &str = "tmp";
const UNSTABLE: &str = "unstable";
const MIXNODES: &str = "mix-nodes";
const GATEWAYS: &str = "gateways";
@@ -96,6 +98,13 @@ impl ExplorerClient {
pub async fn get_gateways(&self) -> Result<Vec<PrettyDetailedGatewayBond>, ExplorerApiError> {
self.query_explorer_api(&[API_VERSION, GATEWAYS]).await
}
pub async fn unstable_get_gateways(
&self,
) -> Result<Vec<PrettyDetailedGatewayBond>, ExplorerApiError> {
self.query_explorer_api(&[API_VERSION, TMP, UNSTABLE, GATEWAYS])
.await
}
}
fn combine_url(mut base_url: Url, paths: &[&str]) -> Result<Url, ExplorerApiError> {
@@ -4,6 +4,7 @@
use crate::state::ExplorerApiStateContext;
use log::{info, warn};
use nym_explorer_api_requests::Location;
use nym_network_defaults::DEFAULT_NYM_NODE_HTTP_PORT;
use nym_task::TaskClient;
pub(crate) struct GeoLocateTask {
@@ -25,6 +26,7 @@ impl GeoLocateTask {
_ = interval_timer.tick() => {
self.locate_mix_nodes().await;
self.locate_gateways().await;
self.locate_nym_nodes().await;
}
_ = self.shutdown.recv() => {
trace!("Listener: Received shutdown");
@@ -109,6 +111,83 @@ impl GeoLocateTask {
trace!("All mix nodes located");
}
async fn locate_nym_nodes(&mut self) {
// I'm unwrapping to the default value to get rid of an extra indentation level from the `if let Some(...) = ...`
// If the value is None, we'll unwrap to an empty hashmap and the `values()` loop won't do any work anyway
let nym_nodes = self.state.inner.nymnodes.get_bonded_nymnodes().await;
let geo_ip = self.state.inner.geo_ip.0.clone();
for (i, cache_item) in nym_nodes.values().enumerate() {
if self
.state
.inner
.nymnodes
.is_location_valid(cache_item.node_id())
.await
{
// when the cached location is valid, don't locate and continue to next mix node
continue;
}
let bonded_host = &cache_item.bond_information.node.host;
match geo_ip.query(
bonded_host,
Some(
cache_item
.bond_information
.node
.custom_http_port
.unwrap_or(DEFAULT_NYM_NODE_HTTP_PORT),
),
) {
Ok(opt) => match opt {
Some(location) => {
let location: Location = location.into();
trace!(
"{} mix nodes already located. host {} is located in {:#?}",
i,
bonded_host,
location.three_letter_iso_country_code,
);
if i > 0 && (i % 100) == 0 {
info!("Located {} nym-nodes...", i + 1,);
}
self.state
.inner
.nymnodes
.set_location(cache_item.node_id(), Some(location))
.await;
// one node has been located, so return out of the loop
return;
}
None => {
warn!("❌ Location for {bonded_host} not found.");
self.state
.inner
.nymnodes
.set_location(cache_item.node_id(), None)
.await;
}
},
Err(_e) => {
// warn!(
// "❌ Oh no! Location for {} failed. Error: {:#?}",
// cache_item.mix_node().host,
// e
// );
}
};
}
trace!("All nym-nodes nodes located");
}
async fn locate_gateways(&mut self) {
let gateways = self.state.inner.gateways.get_gateways().await;
+2
View File
@@ -10,6 +10,7 @@ use crate::gateways::http::gateways_make_default_routes;
use crate::http::swagger::get_docs;
use crate::mix_node::http::mix_node_make_default_routes;
use crate::mix_nodes::http::mix_nodes_make_default_routes;
use crate::nym_nodes::http::unstable_temp_nymnodes_make_default_routes;
use crate::overview::http::overview_make_default_routes;
use crate::ping::http::ping_make_default_routes;
use crate::service_providers::http::service_providers_make_default_routes;
@@ -58,6 +59,7 @@ fn configure_rocket(state: ExplorerApiStateContext) -> Rocket<Build> {
"/ping" => ping_make_default_routes(&openapi_settings),
"/validators" => validators_make_default_routes(&openapi_settings),
"/service-providers" => service_providers_make_default_routes(&openapi_settings),
"/tmp/unstable" => unstable_temp_nymnodes_make_default_routes(&openapi_settings),
};
building_rocket
+1
View File
@@ -22,6 +22,7 @@ mod http;
mod location;
mod mix_node;
pub(crate) mod mix_nodes;
mod nym_nodes;
mod overview;
mod ping;
pub(crate) mod service_providers;
+26
View File
@@ -0,0 +1,26 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::state::ExplorerApiStateContext;
use nym_explorer_api_requests::PrettyDetailedGatewayBond;
use okapi::openapi3::OpenApi;
use rocket::serde::json::Json;
use rocket::{Route, State};
use rocket_okapi::settings::OpenApiSettings;
pub fn unstable_temp_nymnodes_make_default_routes(
settings: &OpenApiSettings,
) -> (Vec<Route>, OpenApi) {
openapi_get_routes_spec![settings: all_gateways]
}
#[openapi(tag = "UNSTABLE")]
#[get("/gateways")]
pub(crate) async fn all_gateways(
state: &State<ExplorerApiStateContext>,
) -> Json<Vec<PrettyDetailedGatewayBond>> {
let mut gateways = state.inner.gateways.get_detailed_gateways().await;
gateways.append(&mut state.inner.nymnodes.pretty_gateways().await);
Json(gateways)
}
+8
View File
@@ -0,0 +1,8 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_mixnet_contract_common::NodeId;
use crate::location::LocationCache;
pub(crate) type NymNodeLocationCache = LocationCache<NodeId>;
+10
View File
@@ -0,0 +1,10 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::time::Duration;
pub(crate) mod http;
pub(crate) mod location;
pub(crate) mod models;
pub(crate) const CACHE_ENTRY_TTL: Duration = Duration::from_secs(1200);
+154
View File
@@ -0,0 +1,154 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::location::{LocationCache, LocationCacheItem};
use crate::nym_nodes::location::NymNodeLocationCache;
use crate::nym_nodes::CACHE_ENTRY_TTL;
use nym_explorer_api_requests::{Location, PrettyDetailedGatewayBond};
use nym_mixnet_contract_common::{Gateway, NodeId, NymNodeDetails};
use nym_validator_client::models::NymNodeDescription;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::{Duration, SystemTime};
use tokio::sync::{RwLock, RwLockReadGuard};
pub(crate) struct NymNodesCache {
pub(crate) valid_until: SystemTime,
pub(crate) bonded_nym_nodes: HashMap<NodeId, NymNodeDetails>,
pub(crate) described_nodes: HashMap<NodeId, NymNodeDescription>,
}
impl NymNodesCache {
fn new() -> Self {
NymNodesCache {
valid_until: SystemTime::now() - Duration::from_secs(60), // in the past
bonded_nym_nodes: Default::default(),
described_nodes: Default::default(),
}
}
// fn is_valid(&self) -> bool {
// self.valid_until >= SystemTime::now()
// }
}
#[derive(Clone)]
pub(crate) struct ThreadSafeNymNodesCache {
nymnodes: Arc<RwLock<NymNodesCache>>,
locations: Arc<RwLock<LocationCache<NodeId>>>,
}
impl ThreadSafeNymNodesCache {
pub(crate) fn new() -> Self {
ThreadSafeNymNodesCache {
nymnodes: Arc::new(RwLock::new(NymNodesCache::new())),
locations: Arc::new(RwLock::new(NymNodeLocationCache::new())),
}
}
pub(crate) fn new_with_location_cache(locations: NymNodeLocationCache) -> Self {
ThreadSafeNymNodesCache {
nymnodes: Arc::new(RwLock::new(NymNodesCache::new())),
locations: Arc::new(RwLock::new(locations)),
}
}
pub(crate) async fn is_location_valid(&self, node_id: NodeId) -> bool {
self.locations
.read()
.await
.get(&node_id)
.map_or(false, |cache_item| {
cache_item.valid_until > SystemTime::now()
})
}
pub(crate) async fn get_bonded_nymnodes(
&self,
) -> RwLockReadGuard<HashMap<NodeId, NymNodeDetails>> {
let guard = self.nymnodes.read().await;
RwLockReadGuard::map(guard, |n| &n.bonded_nym_nodes)
}
pub(crate) async fn get_locations(&self) -> NymNodeLocationCache {
self.locations.read().await.clone()
}
pub(crate) async fn set_location(&self, node_id: NodeId, location: Option<Location>) {
// cache the location for this mix node so that it can be used when the mix node list is refreshed
self.locations
.write()
.await
.insert(node_id, LocationCacheItem::new_from_location(location));
}
pub(crate) async fn update_cache(
&self,
all_bonds: Vec<NymNodeDetails>,
descriptions: Vec<NymNodeDescription>,
) {
let mut guard = self.nymnodes.write().await;
guard.bonded_nym_nodes = all_bonds
.into_iter()
.map(|details| (details.node_id(), details))
.collect();
guard.described_nodes = descriptions
.into_iter()
.map(|description| (description.node_id, description))
.collect();
guard.valid_until = SystemTime::now() + CACHE_ENTRY_TTL;
}
pub(crate) async fn pretty_gateways(&self) -> Vec<PrettyDetailedGatewayBond> {
let nodes_guard = self.nymnodes.read().await;
let location_guard = self.locations.read().await;
let mut pretty_gateways = vec![];
for (node_id, native_nymnode) in &nodes_guard.bonded_nym_nodes {
let Some(description) = nodes_guard.described_nodes.get(node_id) else {
continue;
};
if description.description.declared_role.entry {
let location = location_guard.get(node_id);
let bond = &native_nymnode.bond_information;
pretty_gateways.push(PrettyDetailedGatewayBond {
pledge_amount: bond.original_pledge.clone(),
owner: bond.owner.clone(),
block_height: bond.bonding_height,
gateway: Gateway {
host: bond.node.host.clone(),
mix_port: description.description.mix_port(),
clients_port: description.description.mixnet_websockets.ws_port,
location: description
.description
.auxiliary_details
.location
.as_ref()
.map(|l| l.to_string())
.unwrap_or_default(),
sphinx_key: description
.description
.host_information
.keys
.x25519
.to_base58_string(),
identity_key: bond.node.identity_key.clone(),
version: description
.description
.build_information
.build_version
.clone(),
},
proxy: None,
location: location.and_then(|l| l.location.clone()),
})
}
}
pretty_gateways
}
}
+9
View File
@@ -18,6 +18,8 @@ use crate::gateways::models::ThreadsafeGatewayCache;
use crate::mix_node::models::ThreadsafeMixNodeCache;
use crate::mix_nodes::location::MixnodeLocationCache;
use crate::mix_nodes::models::ThreadsafeMixNodesCache;
use crate::nym_nodes::location::NymNodeLocationCache;
use crate::nym_nodes::models::ThreadSafeNymNodesCache;
use crate::ping::models::ThreadsafePingCache;
use crate::validators::models::ThreadsafeValidatorCache;
@@ -30,6 +32,7 @@ pub struct ExplorerApiState {
pub(crate) gateways: ThreadsafeGatewayCache,
pub(crate) mixnode: ThreadsafeMixNodeCache,
pub(crate) mixnodes: ThreadsafeMixNodesCache,
pub(crate) nymnodes: ThreadSafeNymNodesCache,
pub(crate) ping: ThreadsafePingCache,
pub(crate) validators: ThreadsafeValidatorCache,
pub(crate) geo_ip: ThreadsafeGeoIp,
@@ -49,6 +52,7 @@ pub struct ExplorerApiStateOnDisk {
pub(crate) country_node_distribution: CountryNodesDistribution,
pub(crate) mixnode_location_cache: MixnodeLocationCache,
pub(crate) gateway_location_cache: GatewayLocationCache,
pub(crate) nymnode_location_cache: NymNodeLocationCache,
pub(crate) as_at: DateTime<Utc>,
}
@@ -85,6 +89,9 @@ impl ExplorerApiStateContext {
mixnodes: ThreadsafeMixNodesCache::new_with_location_cache(
state.mixnode_location_cache,
),
nymnodes: ThreadSafeNymNodesCache::new_with_location_cache(
state.nymnode_location_cache,
),
ping: ThreadsafePingCache::new(),
validators: ThreadsafeValidatorCache::new(),
validator_client: ThreadsafeValidatorClient::new(),
@@ -101,6 +108,7 @@ impl ExplorerApiStateContext {
gateways: ThreadsafeGatewayCache::new(),
mixnode: ThreadsafeMixNodeCache::new(),
mixnodes: ThreadsafeMixNodesCache::new(),
nymnodes: ThreadSafeNymNodesCache::new(),
ping: ThreadsafePingCache::new(),
validators: ThreadsafeValidatorCache::new(),
validator_client: ThreadsafeValidatorClient::new(),
@@ -117,6 +125,7 @@ impl ExplorerApiStateContext {
country_node_distribution: self.inner.country_node_distribution.get_all().await,
mixnode_location_cache: self.inner.mixnodes.get_locations().await,
gateway_location_cache: self.inner.gateways.get_locations().await,
nymnode_location_cache: self.inner.nymnodes.get_locations().await,
as_at: Utc::now(),
};
serde_json::to_writer(file, &state).expect("error writing state to disk");
+54 -5
View File
@@ -1,16 +1,16 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_mixnet_contract_common::GatewayBond;
use crate::mix_nodes::CACHE_REFRESH_RATE;
use crate::state::ExplorerApiStateContext;
use nym_mixnet_contract_common::{GatewayBond, NymNodeDetails};
use nym_task::TaskClient;
use nym_validator_client::models::MixNodeBondAnnotated;
use nym_validator_client::models::{MixNodeBondAnnotated, NymNodeDescription};
use nym_validator_client::nyxd::error::NyxdError;
use nym_validator_client::nyxd::{Paging, TendermintRpcClient, ValidatorResponse};
use nym_validator_client::{QueryHttpRpcValidatorClient, ValidatorClientError};
use std::future::Future;
use crate::mix_nodes::CACHE_REFRESH_RATE;
use crate::state::ExplorerApiStateContext;
use tokio::time::MissedTickBehavior;
pub(crate) struct ExplorerApiTasks {
state: ExplorerApiStateContext,
@@ -39,6 +39,28 @@ impl ExplorerApiTasks {
bonds
}
async fn retrieve_bonded_nymnodes(&self) -> Result<Vec<NymNodeDetails>, ValidatorClientError> {
info!("About to retrieve all nymnode bonds...");
self.state
.inner
.validator_client
.0
.get_all_cached_bonded_nym_nodes()
.await
}
async fn retrieve_node_descriptions(
&self,
) -> Result<Vec<NymNodeDescription>, ValidatorClientError> {
info!("About to retrieve node descriptions...");
self.state
.inner
.validator_client
.0
.get_all_cached_described_nodes()
.await
}
async fn retrieve_all_mixnodes(&self) -> Vec<MixNodeBondAnnotated> {
info!("About to retrieve all mixnode bonds...");
self.retrieve_mixnodes(
@@ -130,10 +152,33 @@ impl ExplorerApiTasks {
}
}
async fn update_nymnodes_cache(&self) {
let nym_node_bonds = self.retrieve_bonded_nymnodes().await.unwrap_or_else(|err| {
error!("failed to retrieve nym node bonds: {err}");
Vec::new()
});
let all_descriptions = self
.retrieve_node_descriptions()
.await
.unwrap_or_else(|err| {
error!("failed to retrieve node descriptions: {err}");
Vec::new()
});
self.state
.inner
.nymnodes
.update_cache(nym_node_bonds, all_descriptions)
.await
}
pub(crate) fn start(mut self) {
info!("Spawning mix nodes task runner...");
tokio::spawn(async move {
let mut interval_timer = tokio::time::interval(CACHE_REFRESH_RATE);
interval_timer.set_missed_tick_behavior(MissedTickBehavior::Skip);
while !self.shutdown.is_shutdown() {
tokio::select! {
_ = interval_timer.tick() => {
@@ -147,6 +192,10 @@ impl ExplorerApiTasks {
info!("Updating mix node cache...");
self.update_mixnode_cache().await;
info!("Updating nymnode cache...");
self.update_nymnodes_cache().await;
info!("Done");
}
_ = self.shutdown.recv() => {
trace!("Listener: Received shutdown");
+2
View File
@@ -77,9 +77,11 @@ nym-network-defaults = { path = "../common/network-defaults" }
nym-network-requester = { path = "../service-providers/network-requester" }
nym-node-http-api = { path = "../nym-node/nym-node-http-api" }
nym-pemstore = { path = "../common/pemstore" }
nym-sdk = { path = "../sdk/rust/nym-sdk" }
nym-sphinx = { path = "../common/nymsphinx" }
nym-statistics-common = { path = "../common/statistics" }
nym-task = { path = "../common/task" }
nym-topology = { path = "../common/topology" }
nym-types = { path = "../common/types" }
nym-validator-client = { path = "../common/client-libs/validator-client" }
nym-ip-packet-router = { path = "../service-providers/ip-packet-router" }
+60 -2
View File
@@ -3,14 +3,19 @@
use crate::config::Config;
use crate::error::GatewayError;
use async_trait::async_trait;
use nym_crypto::asymmetric::encryption;
use nym_gateway_stats_storage::PersistentStatsStorage;
use nym_gateway_storage::PersistentStorage;
use nym_pemstore::traits::PemStorableKeyPair;
use nym_pemstore::KeyPairPath;
use nym_sdk::{NymApiTopologyProvider, NymApiTopologyProviderConfig, UserAgent};
use nym_topology::{gateway, NymTopology, TopologyProvider};
use std::path::Path;
use std::sync::Arc;
use tokio::sync::Mutex;
use tracing::debug;
use url::Url;
pub async fn load_network_requester_config<P: AsRef<Path>>(
id: &str,
@@ -102,3 +107,56 @@ pub(crate) fn load_sphinx_keys(config: &Config) -> Result<encryption::KeyPair, G
);
load_keypair(sphinx_paths, "gateway sphinx")
}
#[derive(Clone)]
pub struct GatewayTopologyProvider {
inner: Arc<Mutex<GatewayTopologyProviderInner>>,
}
impl GatewayTopologyProvider {
pub fn new(
gateway_node: gateway::LegacyNode,
user_agent: UserAgent,
nym_api_url: Vec<Url>,
) -> GatewayTopologyProvider {
GatewayTopologyProvider {
inner: Arc::new(Mutex::new(GatewayTopologyProviderInner {
inner: NymApiTopologyProvider::new(
NymApiTopologyProviderConfig {
min_mixnode_performance: 50,
min_gateway_performance: 0,
},
nym_api_url,
env!("CARGO_PKG_VERSION").to_string(),
Some(user_agent),
),
gateway_node,
})),
}
}
}
struct GatewayTopologyProviderInner {
inner: NymApiTopologyProvider,
gateway_node: gateway::LegacyNode,
}
#[async_trait]
impl TopologyProvider for GatewayTopologyProvider {
async fn get_new_topology(&mut self) -> Option<NymTopology> {
let mut guard = self.inner.lock().await;
match guard.inner.get_new_topology().await {
None => None,
Some(mut base) => {
if !base.gateway_exists(&guard.gateway_node.identity_key) {
debug!(
"{} didn't exist in topology. inserting it.",
guard.gateway_node.identity_key
);
base.insert_gateway(guard.gateway_node.clone());
}
Some(base)
}
}
}
}
+50 -1
View File
@@ -14,9 +14,11 @@ use crate::node::client_handling::embedded_clients::{LocalEmbeddedClientHandle,
use crate::node::client_handling::websocket;
use crate::node::helpers::{
initialise_main_storage, initialise_stats_storage, load_network_requester_config,
GatewayTopologyProvider,
};
use crate::node::mixnet_handling::receiver::connection_handler::ConnectionHandler;
use futures::channel::{mpsc, oneshot};
use nym_bin_common::bin_info;
use nym_credential_verification::ecash::{
credential_sender::CredentialHandlerConfig, EcashManager,
};
@@ -27,13 +29,15 @@ use nym_network_requester::{LocalGateway, NRServiceProviderBuilder, RequestFilte
use nym_node_http_api::state::metrics::SharedSessionStats;
use nym_statistics_common::events::{self, StatsEventSender};
use nym_task::{TaskClient, TaskHandle, TaskManager};
use nym_topology::NetworkAddress;
use nym_types::gateway::GatewayNodeDetailsResponse;
use nym_validator_client::client::NodeId;
use nym_validator_client::nyxd::{Coin, CosmWasmClient};
use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient};
use rand::seq::SliceRandom;
use rand::thread_rng;
use statistics::GatewayStatisticsCollector;
use std::net::SocketAddr;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::path::PathBuf;
use std::sync::Arc;
use tracing::*;
@@ -236,6 +240,39 @@ impl<St> Gateway<St> {
crate::helpers::node_details(&self.config).await
}
fn gateway_topology_provider(&self) -> GatewayTopologyProvider {
GatewayTopologyProvider::new(
self.as_topology_node(),
bin_info!().into(),
self.config.gateway.nym_api_urls.clone(),
)
}
fn as_topology_node(&self) -> nym_topology::gateway::LegacyNode {
let ip = self
.config
.host
.public_ips
.first()
.copied()
.unwrap_or(IpAddr::V4(Ipv4Addr::LOCALHOST));
let mix_host = SocketAddr::new(ip, self.config.gateway.mix_port);
nym_topology::gateway::LegacyNode {
// those fields are irrelevant for the purposes of routing so it's fine if they're inaccurate.
// the only thing that matters is the identity key (and maybe version)
node_id: NodeId::MAX,
mix_host,
host: NetworkAddress::IpAddr(ip),
clients_ws_port: self.config.gateway.clients_port,
clients_wss_port: self.config.gateway.clients_wss_port,
sphinx_key: *self.sphinx_keypair.public_key(),
identity_key: *self.identity_keypair.public_key(),
version: env!("CARGO_PKG_VERSION").into(),
}
}
fn start_mix_socket_listener(
&self,
ack_sender: MixForwardingSender,
@@ -268,6 +305,7 @@ impl<St> Gateway<St> {
async fn start_authenticator(
&mut self,
forwarding_channel: MixForwardingSender,
topology_provider: GatewayTopologyProvider,
shutdown: TaskClient,
ecash_verifier: Arc<EcashManager<St>>,
) -> Result<StartedAuthenticator, Box<dyn std::error::Error + Send + Sync>>
@@ -315,6 +353,7 @@ impl<St> Gateway<St> {
.with_shutdown(shutdown.fork("authenticator"))
.with_wait_for_gateway(true)
.with_minimum_gateway_performance(0)
.with_custom_topology_provider(Box::new(topology_provider))
.with_on_start(on_start_tx);
if let Some(custom_mixnet) = &opts.custom_mixnet_path {
@@ -363,6 +402,7 @@ impl<St> Gateway<St> {
async fn start_authenticator(
&self,
_forwarding_channel: MixForwardingSender,
_topology_provider: GatewayTopologyProvider,
_shutdown: TaskClient,
_ecash_verifier: Arc<EcashManager<St>>,
) -> Result<StartedAuthenticator, Box<dyn std::error::Error + Send + Sync>> {
@@ -435,6 +475,7 @@ impl<St> Gateway<St> {
async fn start_network_requester(
&self,
forwarding_channel: MixForwardingSender,
topology_provider: GatewayTopologyProvider,
shutdown: TaskClient,
) -> Result<StartedNetworkRequester, GatewayError> {
info!("Starting network requester...");
@@ -462,6 +503,7 @@ impl<St> Gateway<St> {
.with_custom_gateway_transceiver(Box::new(transceiver))
.with_wait_for_gateway(true)
.with_minimum_gateway_performance(0)
.with_custom_topology_provider(Box::new(topology_provider))
.with_on_start(on_start_tx);
if let Some(custom_mixnet) = &nr_opts.custom_mixnet_path {
@@ -499,6 +541,7 @@ impl<St> Gateway<St> {
async fn start_ip_packet_router(
&self,
forwarding_channel: MixForwardingSender,
topology_provider: GatewayTopologyProvider,
shutdown: TaskClient,
) -> Result<LocalEmbeddedClientHandle, GatewayError> {
info!("Starting IP packet provider...");
@@ -527,6 +570,7 @@ impl<St> Gateway<St> {
.with_custom_gateway_transceiver(Box::new(transceiver))
.with_wait_for_gateway(true)
.with_minimum_gateway_performance(0)
.with_custom_topology_provider(Box::new(topology_provider))
.with_on_start(on_start_tx);
if let Some(custom_mixnet) = &ip_opts.custom_mixnet_path {
@@ -643,6 +687,8 @@ impl<St> Gateway<St> {
shutdown.fork("statistics::GatewayStatisticsCollector"),
);
let topology_provider = self.gateway_topology_provider();
let handler_config = CredentialHandlerConfig {
revocation_bandwidth_penalty: self
.config
@@ -691,6 +737,7 @@ impl<St> Gateway<St> {
let embedded_nr = self
.start_network_requester(
mix_forwarding_channel.clone(),
topology_provider.clone(),
shutdown.fork("NetworkRequester"),
)
.await?;
@@ -706,6 +753,7 @@ impl<St> Gateway<St> {
let embedded_ip_sp = self
.start_ip_packet_router(
mix_forwarding_channel.clone(),
topology_provider.clone(),
shutdown.fork("ip_service_provider"),
)
.await?;
@@ -718,6 +766,7 @@ impl<St> Gateway<St> {
let embedded_auth = self
.start_authenticator(
mix_forwarding_channel,
topology_provider,
shutdown.fork("authenticator"),
ecash_verifier,
)
+3 -15
View File
@@ -2,10 +2,8 @@
// SPDX-License-Identifier: GPL-3.0-only
use cosmwasm_std::Decimal;
use nym_mixnet_contract_common::mixnode::PendingMixNodeChanges;
use nym_mixnet_contract_common::{
GatewayBond, LegacyMixLayer, MixNodeBond, MixNodeDetails, NodeId, NodeRewarding,
};
use nym_mixnet_contract_common::mixnode::LegacyPendingMixNodeChanges;
use nym_mixnet_contract_common::{GatewayBond, LegacyMixLayer, MixNodeBond, NodeId, NodeRewarding};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::ops::Deref;
@@ -64,7 +62,7 @@ pub struct LegacyMixNodeDetailsWithLayer {
/// Adjustments to the mixnode that are ought to happen during future epoch transitions.
#[serde(default)]
pub pending_changes: PendingMixNodeChanges,
pub pending_changes: LegacyPendingMixNodeChanges,
}
impl LegacyMixNodeDetailsWithLayer {
@@ -80,13 +78,3 @@ impl LegacyMixNodeDetailsWithLayer {
self.bond_information.is_unbonding
}
}
impl From<LegacyMixNodeDetailsWithLayer> for MixNodeDetails {
fn from(value: LegacyMixNodeDetailsWithLayer) -> Self {
MixNodeDetails {
bond_information: value.bond_information.into(),
rewarding_details: value.rewarding_details,
pending_changes: value.pending_changes,
}
}
}
+53 -7
View File
@@ -16,7 +16,7 @@ use nym_crypto::asymmetric::x25519::{
use nym_mixnet_contract_common::nym_node::Role;
use nym_mixnet_contract_common::reward_params::{Performance, RewardingParams};
use nym_mixnet_contract_common::rewarding::RewardEstimate;
use nym_mixnet_contract_common::{IdentityKey, Interval, MixNode, NodeId, Percent};
use nym_mixnet_contract_common::{GatewayBond, IdentityKey, Interval, MixNode, NodeId, Percent};
use nym_network_defaults::{DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT};
use nym_node_requests::api::v1::authenticator::models::Authenticator;
use nym_node_requests::api::v1::gateway::models::Wireguard;
@@ -138,6 +138,48 @@ pub struct NodePerformance {
pub last_24h: Performance,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Hash, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export, export_to = "ts-packages/types/src/types/rust/DisplayRole.ts")
)]
pub enum DisplayRole {
EntryGateway,
Layer1,
Layer2,
Layer3,
ExitGateway,
Standby,
}
impl From<Role> for DisplayRole {
fn from(role: Role) -> Self {
match role {
Role::EntryGateway => DisplayRole::EntryGateway,
Role::Layer1 => DisplayRole::Layer1,
Role::Layer2 => DisplayRole::Layer2,
Role::Layer3 => DisplayRole::Layer3,
Role::ExitGateway => DisplayRole::ExitGateway,
Role::Standby => DisplayRole::Standby,
}
}
}
impl From<DisplayRole> for Role {
fn from(role: DisplayRole) -> Self {
match role {
DisplayRole::EntryGateway => Role::EntryGateway,
DisplayRole::Layer1 => Role::Layer1,
DisplayRole::Layer2 => Role::Layer2,
DisplayRole::Layer3 => Role::Layer3,
DisplayRole::ExitGateway => Role::ExitGateway,
DisplayRole::Standby => Role::Standby,
}
}
}
// imo for now there's no point in exposing more than that,
// nym-api shouldn't be calculating apy or stake saturation for you.
// it should just return its own metrics (performance) and then you can do with it as you wish
@@ -153,7 +195,7 @@ pub struct NodePerformance {
pub struct NodeAnnotation {
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
pub last_24h_performance: Performance,
pub current_role: Option<Role>,
pub current_role: Option<DisplayRole>,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, ToSchema)]
@@ -286,7 +328,7 @@ impl MixNodeBondAnnotated {
.sphinx_key
.parse()
.map_err(|_| MalformedNodeBond::InvalidX25519Key)?,
epoch_role: role,
role,
supported_roles: DeclaredRoles {
mixnode: true,
entry: false,
@@ -345,7 +387,7 @@ impl GatewayBondAnnotated {
.sphinx_key
.parse()
.map_err(|_| MalformedNodeBond::InvalidX25519Key)?,
epoch_role: role,
role,
supported_roles: DeclaredRoles {
mixnode: false,
entry: true,
@@ -810,6 +852,10 @@ impl NymNodeDescription {
}
}
pub fn ed25519_identity_key(&self) -> ed25519::PublicKey {
self.description.host_information.keys.ed25519
}
pub fn to_skimmed_node(&self, role: NodeRole, performance: Performance) -> SkimmedNode {
let keys = &self.description.host_information.keys;
let entry = if self.description.declared_role.entry {
@@ -827,7 +873,7 @@ impl NymNodeDescription {
// we can't use the declared roles, we have to take whatever was provided in the contract.
// why? say this node COULD operate as an exit, but it might be the case the contract decided
// to assign it an ENTRY role only. we have to use that one instead.
epoch_role: role,
role,
supported_roles: self.description.declared_role,
entry,
performance,
@@ -935,14 +981,14 @@ impl NymNodeData {
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
pub struct LegacyDescribedGateway {
pub bond: LegacyGatewayBondWithId,
pub bond: GatewayBond,
pub self_described: Option<NymNodeData>,
}
impl From<LegacyGatewayBondWithId> for LegacyDescribedGateway {
fn from(bond: LegacyGatewayBondWithId) -> Self {
LegacyDescribedGateway {
bond,
bond: bond.bond,
self_described: None,
}
}
+3 -3
View File
@@ -141,8 +141,8 @@ pub struct SkimmedNode {
#[schemars(with = "String")]
pub x25519_sphinx_pubkey: x25519::PublicKey,
#[serde(alias = "role")]
pub epoch_role: NodeRole,
#[serde(alias = "epoch_role")]
pub role: NodeRole,
// needed for the purposes of sending appropriate test packets
#[serde(default)]
@@ -157,7 +157,7 @@ pub struct SkimmedNode {
impl SkimmedNode {
pub fn get_mix_layer(&self) -> Option<u8> {
match self.epoch_role {
match self.role {
NodeRole::Mixnode { layer } => Some(layer),
_ => None,
}
+6 -6
View File
@@ -24,21 +24,21 @@ use utoipa::IntoParams;
pub(crate) fn aggregation_routes(ecash_state: Arc<EcashState>) -> Router<AppState> {
Router::new()
.route(
"/master-verification-key:epoch_id",
"/master-verification-key",
axum::routing::get({
let ecash_state = Arc::clone(&ecash_state);
|epoch_id| master_verification_key(epoch_id, ecash_state)
}),
)
.route(
"/aggregated-expiration-date-signatures:expiration_date",
"/aggregated-expiration-date-signatures",
axum::routing::get({
let ecash_state = Arc::clone(&ecash_state);
|expiration_date| expiration_date_signatures(expiration_date, ecash_state)
}),
)
.route(
"/aggregated-coin-indices-signatures:epoch_id",
"/aggregated-coin-indices-signatures",
axum::routing::get({
let ecash_state = Arc::clone(&ecash_state);
|epoch_id| coin_indices_signatures(epoch_id, ecash_state)
@@ -52,7 +52,7 @@ pub(crate) fn aggregation_routes(ecash_state: Arc<EcashState>) -> Router<AppStat
params(
EpochIdParam
),
path = "/v1/ecash/master-verification-key/{epoch_id}",
path = "/v1/ecash/master-verification-key",
responses(
(status = 200, body = VerificationKeyResponse)
)
@@ -83,7 +83,7 @@ struct ExpirationDateParam {
params(
ExpirationDateParam
),
path = "/v1/ecash/aggregated-expiration-date-signatures/{epoch_id}",
path = "/v1/ecash/aggregated-expiration-date-signatures",
responses(
(status = 200, body = AggregatedExpirationDateSignatureResponse)
)
@@ -120,7 +120,7 @@ async fn expiration_date_signatures(
params(
EpochIdParam
),
path = "/v1/ecash/aggregated-coin-indices-signatures/{epoch_id}",
path = "/v1/ecash/aggregated-coin-indices-signatures",
responses(
(status = 200, body = AggregatedCoinIndicesSignatureResponse)
)
@@ -32,14 +32,14 @@ pub(crate) fn partial_signing_routes(ecash_state: Arc<EcashState>) -> Router<App
}),
)
.route(
"/partial-expiration-date-signatures:expiration_date",
"/partial-expiration-date-signatures",
axum::routing::get({
let ecash_state = Arc::clone(&ecash_state);
|expiration_date| partial_expiration_date_signatures(expiration_date, ecash_state)
}),
)
.route(
"/partial-coin-indices-signatures:epoch_id",
"/partial-coin-indices-signatures",
axum::routing::get({
let ecash_state = Arc::clone(&ecash_state);
|epoch_id| partial_coin_indices_signatures(epoch_id, ecash_state)
@@ -127,7 +127,7 @@ struct ExpirationDateParam {
params(
ExpirationDateParam
),
path = "/v1/ecash/partial-expiration-date-signatures/{expiration_date}",
path = "/v1/ecash/partial-expiration-date-signatures",
responses(
(status = 200, body = PartialExpirationDateSignatureResponse),
(status = 400, body = ErrorResponse, description = "this nym-api is not an ecash signer in the current epoch"),
@@ -165,7 +165,7 @@ async fn partial_expiration_date_signatures(
params(
EpochIdParam
),
path = "/v1/ecash/partial-coin-indices-signatures/{epoch_id}",
path = "/v1/ecash/partial-coin-indices-signatures",
responses(
(status = 200, body = PartialExpirationDateSignatureResponse),
(status = 400, body = ErrorResponse, description = "this nym-api is not an ecash signer in the current epoch"),
+2
View File
@@ -20,6 +20,7 @@ use time::macros::time;
use time::{OffsetDateTime, Time};
use tracing::{error, warn};
#[allow(deprecated)]
pub(crate) fn spending_routes(ecash_state: Arc<EcashState>) -> Router<AppState> {
Router::new()
.route(
@@ -242,6 +243,7 @@ async fn batch_redeem_tickets(
(status = 500, body = ErrorResponse, description = "bloomfilters got disabled"),
)
)]
#[deprecated]
async fn double_spending_filter_v1(
_state: Arc<EcashState>,
) -> AxumResult<Json<SpentCredentialsResponse>> {
@@ -169,7 +169,7 @@ impl EpochAdvancer {
let standby_eligible = all_choices
.iter()
.filter(|node| {
exit_gateways.contains(&node.0.node_id)
!exit_gateways.contains(&node.0.node_id)
&& !entry_gateways.contains(&node.0.node_id)
&& !mixnodes.contains(&node.0.node_id)
})
@@ -228,14 +228,24 @@ impl EpochAdvancer {
)
}
Ok(RewardedSet {
let mut rewarded_set = RewardedSet {
entry_gateways: entry_gateways.into_iter().collect(),
exit_gateways: exit_gateways.into_iter().collect(),
layer1,
layer2,
layer3,
standby,
})
};
// make sure to sort the rewarded set values
rewarded_set.entry_gateways.sort();
rewarded_set.exit_gateways.sort();
rewarded_set.layer1.sort();
rewarded_set.layer2.sort();
rewarded_set.layer3.sort();
rewarded_set.standby.sort();
Ok(rewarded_set)
}
async fn attach_performance_to_eligible_nodes(
+2 -2
View File
@@ -274,8 +274,8 @@ impl<R: MessageReceiver + Send> Monitor<R> {
info!("Received {}/{} packets", total_received, total_sent);
let summary = self.summary_producer.produce_summary(
prepared_packets.tested_mixnodes,
prepared_packets.tested_gateways,
prepared_packets.mixnodes_under_test,
prepared_packets.gateways_under_test,
received,
prepared_packets.invalid_mixnodes,
prepared_packets.invalid_gateways,
+46 -31
View File
@@ -25,7 +25,7 @@ use std::collections::{HashMap, HashSet};
use std::fmt::{self, Display, Formatter};
use std::sync::Arc;
use std::time::Duration;
use tracing::{error, info, trace};
use tracing::{debug, error, info, trace};
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(200);
const DEFAULT_AVERAGE_ACK_DELAY: Duration = Duration::from_millis(200);
@@ -59,10 +59,10 @@ pub(crate) struct PreparedPackets {
pub(super) packets: Vec<GatewayPackets>,
/// Vector containing list of public keys and owners of all nodes mixnodes being tested.
pub(super) tested_mixnodes: Vec<TestableNode>,
pub(super) mixnodes_under_test: Vec<TestableNode>,
/// Vector containing list of public keys and owners of all gateways being tested.
pub(super) tested_gateways: Vec<TestableNode>,
pub(super) gateways_under_test: Vec<TestableNode>,
/// All mixnodes that failed to get parsed correctly or were not version compatible.
/// They will be marked to the validator as being down for the test.
@@ -151,34 +151,38 @@ impl PacketPreparer {
self.contract_cache.wait_for_initial_values().await;
self.described_cache.naive_wait_for_initial_values().await;
let described_nodes = self
.described_cache
.get()
.await
.expect("the self-describe cache should have been initialised!");
// now wait for at least `minimum_full_routes` mixnodes per layer and `minimum_full_routes` gateway to be online
info!("Waiting for minimal topology to be online");
let initialisation_backoff = Duration::from_secs(30);
loop {
let gateways = self.contract_cache.legacy_gateways_all().await;
let mixnodes = self.contract_cache.legacy_mixnodes_all_basic().await;
let nym_nodes = self.contract_cache.nym_nodes().await;
if gateways.len() < minimum_full_routes {
self.topology_wait_backoff(initialisation_backoff).await;
continue;
}
let mut gateways_count = gateways.len();
let mut mixnodes_count = mixnodes.len();
let mut layer1_count = 0;
let mut layer2_count = 0;
let mut layer3_count = 0;
for mix in mixnodes {
match mix.layer {
LegacyMixLayer::One => layer1_count += 1,
LegacyMixLayer::Two => layer2_count += 1,
LegacyMixLayer::Three => layer3_count += 1,
for nym_node in nym_nodes {
if let Some(described) = described_nodes.get_description(&nym_node.node_id()) {
if described.declared_role.mixnode {
mixnodes_count += 1;
} else if described.declared_role.entry {
gateways_count += 1;
}
}
}
if layer1_count >= minimum_full_routes
&& layer2_count >= minimum_full_routes
&& layer3_count >= minimum_full_routes
{
debug!(
"we have {mixnodes_count} possible mixnodes and {gateways_count} possible gateways"
);
if gateways_count >= minimum_full_routes && mixnodes_count * 3 >= minimum_full_routes {
break;
}
@@ -529,30 +533,41 @@ impl PacketPreparer {
let mixing_nym_nodes = descriptions.mixing_nym_nodes();
let gateway_capable_nym_nodes = descriptions.entry_capable_nym_nodes();
let (mixnodes, invalid_mixnodes) = self.filter_outdated_and_malformed_mixnodes(mixnodes);
let (gateways, invalid_gateways) = self.filter_outdated_and_malformed_gateways(gateways);
let (mut mixnodes_to_test_details, invalid_mixnodes) =
self.filter_outdated_and_malformed_mixnodes(mixnodes);
let (mut gateways_to_test_details, invalid_gateways) =
self.filter_outdated_and_malformed_gateways(gateways);
let mut tested_mixnodes = mixnodes.iter().map(|node| node.into()).collect::<Vec<_>>();
let mut tested_gateways = gateways.iter().map(|node| node.into()).collect::<Vec<_>>();
// summary of nodes that got tested
let mut mixnodes_under_test = mixnodes_to_test_details
.iter()
.map(|node| node.into())
.collect::<Vec<_>>();
let mut gateways_under_test = gateways_to_test_details
.iter()
.map(|node| node.into())
.collect::<Vec<_>>();
// try to add nym-nodes into the fold
if let Some(rewarded_set) = rewarded_set {
let mut rng = thread_rng();
for mix in mixing_nym_nodes {
if let Some(parsed) = self.nym_node_to_legacy_mix(&mut rng, &rewarded_set, mix) {
tested_mixnodes.push(TestableNode::from(&parsed));
mixnodes_under_test.push(TestableNode::from(&parsed));
mixnodes_to_test_details.push(parsed);
}
}
}
for gateway in gateway_capable_nym_nodes {
if let Some(parsed) = self.nym_node_to_legacy_gateway(gateway) {
tested_gateways.push((&parsed, gateway.node_id).into())
gateways_under_test.push((&parsed, gateway.node_id).into());
gateways_to_test_details.push((parsed, gateway.node_id));
}
}
let packets_to_create = (test_routes.len() * self.per_node_test_packets)
* (tested_mixnodes.len() + tested_gateways.len());
* (mixnodes_under_test.len() + gateways_under_test.len());
info!("Need to create {} mix packets", packets_to_create);
let mut all_gateway_packets = HashMap::new();
@@ -574,7 +589,7 @@ impl PacketPreparer {
#[allow(clippy::unwrap_used)]
let mixnode_test_packets = mix_tester
.mixnodes_test_packets(
&mixnodes,
&mixnodes_to_test_details,
route_ext,
self.per_node_test_packets as u32,
None,
@@ -588,7 +603,7 @@ impl PacketPreparer {
gateway_packets.push_packets(mix_packets);
// and generate test packets for gateways (note the variable recipient)
for (gateway, node_id) in &gateways {
for (gateway, node_id) in &gateways_to_test_details {
let recipient = self.create_packet_sender(gateway);
let gateway_identity = gateway.identity_key;
let gateway_address = gateway.clients_address();
@@ -624,8 +639,8 @@ impl PacketPreparer {
PreparedPackets {
packets,
tested_mixnodes,
tested_gateways,
mixnodes_under_test,
gateways_under_test,
invalid_mixnodes,
invalid_gateways,
}
+2
View File
@@ -145,6 +145,7 @@ impl NodeDescriptionTopologyExt for NymNodeDescription {
}
}
#[derive(Debug, Clone)]
pub struct DescribedNodes {
nodes: HashMap<NodeId, NymNodeDescription>,
}
@@ -290,6 +291,7 @@ async fn try_get_description(
})
}
#[derive(Debug)]
struct RefreshData {
host: String,
node_id: NodeId,
@@ -210,6 +210,7 @@ impl ResolvedNodeDescribedInfo {
}
}
#[derive(Debug)]
pub(crate) struct UnwrappedResolvedNodeDescribedInfo {
pub(crate) build_info: BinaryBuildInformationOwned,
pub(crate) roles: DeclaredRoles,
+3 -3
View File
@@ -209,7 +209,7 @@ pub(crate) async fn produce_node_annotations(
legacy_mix.mix_id(),
NodeAnnotation {
last_24h_performance: perf,
current_role: rewarded_set.role(legacy_mix.mix_id()),
current_role: rewarded_set.role(legacy_mix.mix_id()).map(|r| r.into()),
},
);
}
@@ -229,7 +229,7 @@ pub(crate) async fn produce_node_annotations(
legacy_gateway.node_id,
NodeAnnotation {
last_24h_performance: perf,
current_role: rewarded_set.role(legacy_gateway.node_id),
current_role: rewarded_set.role(legacy_gateway.node_id).map(|r| r.into()),
},
);
}
@@ -249,7 +249,7 @@ pub(crate) async fn produce_node_annotations(
nym_node.node_id(),
NodeAnnotation {
last_24h_performance: perf,
current_role: rewarded_set.role(nym_node.node_id()),
current_role: rewarded_set.role(nym_node.node_id()).map(|r| r.into()),
},
);
}
+10 -1
View File
@@ -16,6 +16,7 @@ use nym_serde_helpers::date::DATE_FORMAT;
use reqwest::StatusCode;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use sqlx::Error;
use std::fmt::Display;
use thiserror::Error;
use time::{Date, OffsetDateTime};
@@ -438,7 +439,7 @@ pub enum NymApiStorageError {
// I don't think we want to expose errors to the user about what really happened
#[error("experienced internal database error")]
InternalDatabaseError(#[from] sqlx::Error),
InternalDatabaseError(sqlx::Error),
// the same is true here (also note that the message is subtly different so we would be able to distinguish them)
#[error("experienced internal storage error")]
@@ -449,6 +450,14 @@ pub enum NymApiStorageError {
StartupMigrationFailure(#[from] sqlx::migrate::MigrateError),
}
impl From<sqlx::Error> for NymApiStorageError {
fn from(err: Error) -> Self {
// those should realistically never be happening so an `error!` is warranted
error!("storage failure: {err}");
NymApiStorageError::InternalDatabaseError(err)
}
}
impl NymApiStorageError {
pub fn database_inconsistency<S: Into<String>>(reason: S) -> NymApiStorageError {
NymApiStorageError::DatabaseInconsistency {
+1 -1
View File
@@ -173,7 +173,7 @@ impl NymContractCacheRefresher {
layer,
},
rewarding_details: detail.rewarding_details,
pending_changes: detail.pending_changes,
pending_changes: detail.pending_changes.into(),
})
}
+1 -1
View File
@@ -49,7 +49,7 @@ async fn get_gateways_described(
.into_iter()
.map(|bond| LegacyDescribedGateway {
self_described: self_descriptions.get_description(&bond.node_id).cloned(),
bond,
bond: bond.bond,
})
.collect(),
)
+7 -12
View File
@@ -9,7 +9,7 @@ use axum::routing::get;
use axum::{Json, Router};
use nym_api_requests::models::{
AnnotationResponse, NodeDatePerformanceResponse, NodePerformanceResponse, NoiseDetails,
NymNodeData, PerformanceHistoryResponse, UptimeHistoryResponse,
NymNodeDescription, PerformanceHistoryResponse, UptimeHistoryResponse,
};
use nym_api_requests::pagination::{PaginatedResponse, Pagination};
use nym_contracts_common::NaiveFloat;
@@ -125,32 +125,27 @@ async fn get_bonded_nodes(
path = "/described",
context_path = "/v1/nym-nodes",
responses(
(status = 200, body = PaginatedResponse<NymNodeData>)
(status = 200, body = PaginatedResponse<NymNodeDescription>)
),
params(PaginationRequest)
)]
async fn get_described_nodes(
State(state): State<AppState>,
Query(pagination): Query<PaginationRequest>,
) -> AxumResult<Json<PaginatedResponse<NymNodeData>>> {
) -> AxumResult<Json<PaginatedResponse<NymNodeDescription>>> {
// TODO: implement it
let _ = pagination;
let cache = state.described_nodes_cache.get().await?;
let descriptions = cache.all_nodes();
let data = descriptions
.map(|n| &n.description)
.cloned()
.collect::<Vec<_>>();
let descriptions = cache.all_nodes().cloned().collect::<Vec<_>>();
Ok(Json(PaginatedResponse {
pagination: Pagination {
total: data.len(),
total: descriptions.len(),
page: 0,
size: data.len(),
size: descriptions.len(),
},
data,
data: descriptions,
}))
}
+2 -2
View File
@@ -234,9 +234,9 @@ impl Config {
fn default_http_socket_addr() -> SocketAddr {
cfg_if::cfg_if! {
if #[cfg(debug_assertions)] {
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8080)
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8000)
} else {
SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 8080)
SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 8000)
}
}
}
+4 -5
View File
@@ -119,9 +119,8 @@ impl StorageManager {
start_ts_secs: i64,
end_ts_secs: i64,
) -> Result<Vec<AvgGatewayReliability>, sqlx::Error> {
// we can't use `query_as!` macro because we don't apply all required table changes during sqlx migrations.
// some (like v3 directory) happens at runtime
let result = sqlx::query_as(
let result = sqlx::query_as!(
AvgGatewayReliability,
r#"
SELECT
d.node_id as "node_id: NodeId",
@@ -135,9 +134,9 @@ impl StorageManager {
timestamp <= ?
GROUP BY 1
"#,
start_ts_secs,
end_ts_secs
)
.bind(start_ts_secs)
.bind(end_ts_secs)
.fetch_all(&self.connection_pool)
.await?;
Ok(result)
-4
View File
@@ -30,10 +30,6 @@ pub async fn migrate_v3_database(
let contract_gateways = nyxd_client.get_gateways().await?;
let nym_nodes = nyxd_client.get_nymnodes().await?;
if preassigned_ids.len() != contract_gateways.len() {
bail!("CONTRACT DATA CORRUPTION: THE NUMBER OF PREASSIGNED GATEWAY IDS IS DIFFERENT THAN THE NUMBER OF GATEWAYS")
}
// assign node_id to every gateway
let all_known = storage.get_all_known_gateways().await?;
for gateway in all_known {
-5388
View File
File diff suppressed because it is too large Load Diff
-62
View File
@@ -1,62 +0,0 @@
[profile.release]
panic = "abort"
opt-level = "s"
overflow-checks = true
[profile.dev]
panic = "abort"
[workspace]
resolver = "2"
members = [
"nym-credential-proxy",
"nym-credential-proxy-requests",
"vpn-api-lib-wasm"
]
[workspace.package]
authors = ["Nym Technologies SA"]
repository = "https://github.com/nymtech/nym"
homepage = "https://nymtech.net"
documentation = "https://nymtech.net"
edition = "2021"
license = "GPL-3.0"
[workspace.dependencies]
async-trait = "0.1.80"
axum = "0.7.5"
anyhow = "1.0.71"
bip39 = "2.0.0"
bs58 = "0.5.1"
colored = "2.1.0"
cfg-if = "1.0.0"
clap = "4.5.4"
dotenv = "0.15.0"
futures = "0.3.30"
humantime = "2.1.0"
thiserror = "1.0.59"
rand = "0.8.5"
reqwest = "0.12.4"
schemars = "0.8.17"
strum = "0.26.3"
strum_macros = "0.26.4"
serde = "1.0.200"
serde_json = "1.0.117"
sqlx = "0.7.4"
tempfile = "3.12.0"
time = "0.3.36"
tracing = "0.1.40"
tsify = "0.4.5"
tokio = "1.37.0"
tokio-util = "0.7.10"
tower = "0.5.0"
tower-http = "0.5.2"
uuid = "1.8.0"
url = "2.5.2"
utoipa = "4.2.0"
utoipa-swagger-ui = "7.0.1"
zeroize = "1.6.0"
wasm-bindgen = "0.2.93"
wasmtimer = "0.2.0"
@@ -27,7 +27,7 @@ utoipa = { workspace = true, optional = true }
nym-credentials = { path = "../../common/credentials" }
nym-credentials-interface = { path = "../../common/credentials-interface" }
nym-http-api-common = { path = "../../common/http-api-common", optional = true }
nym-http-api-client = { path = "../../common/http-api-client" }
nym-http-api-client = { path = "../../common/http-api-client" }
nym-serde-helpers = { path = "../../common/serde-helpers", features = ["bs58"] }
[target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer]
@@ -19,7 +19,7 @@ bs58.workspace = true
cfg-if = { workspace = true }
colored.workspace = true
clap = { workspace = true, features = ["derive", "env"] }
dotenv.workspace = true
dotenvy.workspace = true
futures.workspace = true
humantime.workspace = true
rand.workspace = true
@@ -40,7 +40,7 @@ impl SqliteStorageManager {
sqlx::query_as!(
MinimalWalletShare,
r#"
SELECT t1.node_id, t1.blinded_signature, t1.epoch_id, t1.expiration_date
SELECT t1.node_id, t1.blinded_signature, t1.epoch_id, t1.expiration_date as "expiration_date!: Date"
FROM partial_blinded_wallet as t1
JOIN ticketbook_deposit as t2
on t1.corresponding_deposit = t2.deposit_id
@@ -79,10 +79,11 @@ impl SqliteStorageManager {
device_id: &str,
credential_id: &str,
) -> Result<Vec<MinimalWalletShare>, sqlx::Error> {
// https://docs.rs/sqlx/latest/sqlx/macro.query.html#force-a-differentcustom-type
sqlx::query_as!(
MinimalWalletShare,
r#"
SELECT t1.node_id, t1.blinded_signature, t1.epoch_id, t1.expiration_date
SELECT t1.node_id, t1.blinded_signature, t1.epoch_id, t1.expiration_date as "expiration_date!: Date"
FROM partial_blinded_wallet as t1
JOIN ticketbook_deposit as t2
on t1.corresponding_deposit = t2.deposit_id
+1 -1
View File
@@ -4,7 +4,7 @@
[package]
name = "nym-node-status-agent"
version = "0.1.0"
version = "0.1.4"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
+10 -4
View File
@@ -2,7 +2,11 @@
set -eu
export RUST_LOG=${RUST_LOG:-debug}
environment="qa"
source ../envs/${environment}.env
export RUST_LOG="debug"
crate_root=$(dirname $(realpath "$0"))
gateway_probe_src=$(dirname $(dirname "$crate_root"))/nym-vpn-client/nym-vpn-core
@@ -14,6 +18,8 @@ export NODE_STATUS_AGENT_PROBE_PATH="$crate_root/nym-gateway-probe"
# build & copy over GW probe
function copy_gw_probe() {
pushd $gateway_probe_src
git switch main
git pull
cargo build --release --package nym-gateway-probe
cp target/release/nym-gateway-probe "$crate_root"
$crate_root/nym-gateway-probe --version
@@ -30,8 +36,8 @@ function swarm() {
build_agent
for ((i=1; i<=$workers; i++)); do
../target/release/nym-node-status-agent run-probe &
for ((i = 1; i <= $workers; i++)); do
../target/release/nym-node-status-agent run-probe &
done
wait
@@ -44,6 +50,6 @@ export NODE_STATUS_AGENT_SERVER_PORT="8000"
copy_gw_probe
swarm 30
swarm 8
# cargo run -- run-probe
+4 -8
View File
@@ -31,24 +31,19 @@ pub(crate) enum Command {
/// path of binary to run
#[arg(long, env = "NODE_STATUS_AGENT_PROBE_PATH")]
probe_path: String,
#[arg(short, long, env = "NODE_STATUS_AGENT_GATEWAY_ID")]
gateway_id: Option<String>,
},
}
impl Args {
pub(crate) async fn execute(&self) -> anyhow::Result<()> {
match &self.command {
Command::RunProbe {
probe_path,
gateway_id,
} => self.run_probe(probe_path, gateway_id).await?,
Command::RunProbe { probe_path } => self.run_probe(probe_path).await?,
}
Ok(())
}
async fn run_probe(&self, probe_path: &str, gateway_id: &Option<String>) -> anyhow::Result<()> {
async fn run_probe(&self, probe_path: &str) -> anyhow::Result<()> {
let server_address = format!("{}:{}", &self.server_address, self.server_port);
let probe = GwProbe::new(probe_path.to_string());
@@ -58,7 +53,7 @@ impl Args {
let testrun = request_testrun(&server_address).await?;
let log = probe.run_and_get_log(gateway_id);
let log = probe.run_and_get_log(&Some(testrun.gateway_identity_key));
submit_results(&server_address, testrun.testrun_id, log).await?;
@@ -97,6 +92,7 @@ async fn submit_results(
) -> anyhow::Result<()> {
let target_url = format!("{}/{}/{}", server_addr, URL_BASE, testrun_id);
let client = reqwest::Client::new();
let res = client
.post(target_url)
.body(probe_outcome)
+6
View File
@@ -40,6 +40,12 @@ impl GwProbe {
match command.spawn() {
Ok(child) => {
if let Ok(output) = child.wait_with_output() {
if !output.status.success() {
let out = String::from_utf8_lossy(&output.stdout);
let err = String::from_utf8_lossy(&output.stderr);
tracing::error!("Probe exited with {:?}:\n{}\n{}", output.status, out, err);
}
return String::from_utf8(output.stdout)
.unwrap_or("Unable to get log from test run".to_string());
}
@@ -0,0 +1,20 @@
{
"db_name": "SQLite",
"query": "SELECT\n gateway_identity_key\n FROM\n gateways\n WHERE\n id = ?",
"describe": {
"columns": [
{
"name": "gateway_identity_key",
"ordinal": 0,
"type_info": "Text"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false
]
},
"hash": "06b17d1e5f61201a1b7542896ba55c69cd5c1a7e7d87073c94600c783a0a3984"
}
@@ -0,0 +1,32 @@
{
"db_name": "SQLite",
"query": "SELECT\n key as \"key!\",\n value_json as \"value_json!\",\n last_updated_utc as \"last_updated_utc!\"\n FROM summary",
"describe": {
"columns": [
{
"name": "key!",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "value_json!",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "last_updated_utc!",
"ordinal": 2,
"type_info": "Int64"
}
],
"parameters": {
"Right": 0
},
"nullable": [
true,
true,
false
]
},
"hash": "1327b5118f9144dddbcf8edb11f7dc549cf503409fd6dfedcdc02dbcd61d5454"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "UPDATE mixnodes\n SET bonded = ?, last_updated_utc = ?\n WHERE id = ?;",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "18abc8fde56cf86baed7b4afa38f2c63cdf90f2f3b6d81afb9000bb0968dcaea"
}
@@ -0,0 +1,26 @@
{
"db_name": "SQLite",
"query": "\n SELECT\n id,\n gateway_identity_key\n FROM gateways\n WHERE id = ?\n LIMIT 1",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Int64"
},
{
"name": "gateway_identity_key",
"ordinal": 1,
"type_info": "Text"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
false
]
},
"hash": "2236299f9f691376db54cbd58ec5ceb89b9925cba46efcf4ed79ef0759a01129"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "INSERT INTO testruns (gateway_id, status, ip_address, timestamp_utc, log) VALUES (?, ?, ?, ?, ?)",
"describe": {
"columns": [],
"parameters": {
"Right": 5
},
"nullable": []
},
"hash": "3c584e211d07c511644c8079187965acf3bcfb3f84ba8d24ed645d79976cf784"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "UPDATE gateways\n SET bonded = ?, last_updated_utc = ?\n WHERE id = ?;",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "3d3a1fa429e3090741c6b6a8e82e692afc04b51e8782bcbf59f1eb4116112536"
}
@@ -0,0 +1,38 @@
{
"db_name": "SQLite",
"query": "SELECT\n id as \"id!\",\n gateway_identity_key as \"gateway_identity_key!\",\n self_described as \"self_described?\",\n explorer_pretty_bond as \"explorer_pretty_bond?\"\n FROM gateways\n WHERE gateway_identity_key = ?\n ORDER BY gateway_identity_key\n LIMIT 1",
"describe": {
"columns": [
{
"name": "id!",
"ordinal": 0,
"type_info": "Int64"
},
{
"name": "gateway_identity_key!",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "self_described?",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "explorer_pretty_bond?",
"ordinal": 3,
"type_info": "Text"
}
],
"parameters": {
"Right": 1
},
"nullable": [
true,
false,
true,
true
]
},
"hash": "3d5fc502f976f5081f01352856b8632c29c81bfafb043bb8744129cf9e0266ad"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "UPDATE testruns SET status = ? WHERE id = ?",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "418944f2eccb838cb3882f34469203c8569f03fdd39ce09d7b74177896e52a8c"
}
@@ -0,0 +1,50 @@
{
"db_name": "SQLite",
"query": "SELECT\n id as \"id!\",\n gateway_id as \"gateway_id!\",\n status as \"status!\",\n timestamp_utc as \"timestamp_utc!\",\n ip_address as \"ip_address!\",\n log as \"log!\"\n FROM testruns\n WHERE gateway_id = ? AND status != 2\n ORDER BY id DESC\n LIMIT 1",
"describe": {
"columns": [
{
"name": "id!",
"ordinal": 0,
"type_info": "Int64"
},
{
"name": "gateway_id!",
"ordinal": 1,
"type_info": "Int64"
},
{
"name": "status!",
"ordinal": 2,
"type_info": "Int64"
},
{
"name": "timestamp_utc!",
"ordinal": 3,
"type_info": "Int64"
},
{
"name": "ip_address!",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "log!",
"ordinal": 5,
"type_info": "Text"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
false,
false,
false,
false,
false
]
},
"hash": "46d76bc6d3fba2dae3b21511a36289dd776749dd7a20cda61b0480f2fba60889"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "UPDATE gateways SET last_probe_log = ? WHERE id = ?",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "4afcc6673890f795c2793f1e2f8570ee787fc7daf00fcb916f18d1cb7d6c8f08"
}
@@ -0,0 +1,32 @@
{
"db_name": "SQLite",
"query": "SELECT\n id as \"id!\",\n gateway_identity_key as \"identity_key!\",\n bonded as \"bonded: bool\"\n FROM gateways\n WHERE bonded = ?",
"describe": {
"columns": [
{
"name": "id!",
"ordinal": 0,
"type_info": "Int64"
},
{
"name": "identity_key!",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "bonded: bool",
"ordinal": 2,
"type_info": "Int64"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
false,
false
]
},
"hash": "4b61a4bc32333c92a8f5ad4ad0017b40dc01845f554b5479f37855d89b309e6f"
}
@@ -0,0 +1,20 @@
{
"db_name": "SQLite",
"query": "SELECT count(id) FROM mixnodes",
"describe": {
"columns": [
{
"name": "count(id)",
"ordinal": 0,
"type_info": "Int"
}
],
"parameters": {
"Right": 0
},
"nullable": [
false
]
},
"hash": "670b7ed7d57a6986181b24be24ca667e8cacdf677ccb906415b3fe92be0c436b"
}
@@ -0,0 +1,50 @@
{
"db_name": "SQLite",
"query": "SELECT\n id as \"id!\",\n gateway_id as \"gateway_id!\",\n status as \"status!\",\n timestamp_utc as \"timestamp_utc!\",\n ip_address as \"ip_address!\",\n log as \"log!\"\n FROM testruns\n WHERE\n id = ?\n AND\n status = ?\n ORDER BY timestamp_utc",
"describe": {
"columns": [
{
"name": "id!",
"ordinal": 0,
"type_info": "Int64"
},
{
"name": "gateway_id!",
"ordinal": 1,
"type_info": "Int64"
},
{
"name": "status!",
"ordinal": 2,
"type_info": "Int64"
},
{
"name": "timestamp_utc!",
"ordinal": 3,
"type_info": "Int64"
},
{
"name": "ip_address!",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "log!",
"ordinal": 5,
"type_info": "Text"
}
],
"parameters": {
"Right": 2
},
"nullable": [
false,
false,
false,
false,
false,
false
]
},
"hash": "6d7967b831b355d5f2c77950abc56f816956b0824c66a25da611dce688105d36"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "INSERT INTO mixnodes\n (mix_id, identity_key, bonded, total_stake,\n host, http_api_port, blacklisted, full_details,\n self_described, last_updated_utc, is_dp_delegatee)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(mix_id) DO UPDATE SET\n bonded=excluded.bonded,\n total_stake=excluded.total_stake, host=excluded.host,\n http_api_port=excluded.http_api_port,blacklisted=excluded.blacklisted,\n full_details=excluded.full_details,self_described=excluded.self_described,\n last_updated_utc=excluded.last_updated_utc,\n is_dp_delegatee = excluded.is_dp_delegatee;",
"describe": {
"columns": [],
"parameters": {
"Right": 11
},
"nullable": []
},
"hash": "6eb1a682cf13205cf701590021cdf795147ac3724e89df5b2f24f7215d87dce1"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "UPDATE gateways SET last_probe_result = ? WHERE id = ?",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "6ef3efde571d46961244cd90420f3de5949a5ff2083453cb879af8a1689efe2f"
}
@@ -0,0 +1,98 @@
{
"db_name": "SQLite",
"query": "SELECT\n gw.gateway_identity_key as \"gateway_identity_key!\",\n gw.bonded as \"bonded: bool\",\n gw.blacklisted as \"blacklisted: bool\",\n gw.performance as \"performance!\",\n gw.self_described as \"self_described?\",\n gw.explorer_pretty_bond as \"explorer_pretty_bond?\",\n gw.last_probe_result as \"last_probe_result?\",\n gw.last_probe_log as \"last_probe_log?\",\n gw.last_testrun_utc as \"last_testrun_utc?\",\n gw.last_updated_utc as \"last_updated_utc!\",\n COALESCE(gd.moniker, \"NA\") as \"moniker!\",\n COALESCE(gd.website, \"NA\") as \"website!\",\n COALESCE(gd.security_contact, \"NA\") as \"security_contact!\",\n COALESCE(gd.details, \"NA\") as \"details!\"\n FROM gateways gw\n LEFT JOIN gateway_description gd\n ON gw.gateway_identity_key = gd.gateway_identity_key\n ORDER BY gw.gateway_identity_key",
"describe": {
"columns": [
{
"name": "gateway_identity_key!",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "bonded: bool",
"ordinal": 1,
"type_info": "Int64"
},
{
"name": "blacklisted: bool",
"ordinal": 2,
"type_info": "Int64"
},
{
"name": "performance!",
"ordinal": 3,
"type_info": "Int64"
},
{
"name": "self_described?",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "explorer_pretty_bond?",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "last_probe_result?",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "last_probe_log?",
"ordinal": 7,
"type_info": "Text"
},
{
"name": "last_testrun_utc?",
"ordinal": 8,
"type_info": "Int64"
},
{
"name": "last_updated_utc!",
"ordinal": 9,
"type_info": "Int64"
},
{
"name": "moniker!",
"ordinal": 10,
"type_info": "Text"
},
{
"name": "website!",
"ordinal": 11,
"type_info": "Text"
},
{
"name": "security_contact!",
"ordinal": 12,
"type_info": "Text"
},
{
"name": "details!",
"ordinal": 13,
"type_info": "Text"
}
],
"parameters": {
"Right": 0
},
"nullable": [
false,
false,
false,
false,
true,
true,
true,
true,
true,
false,
false,
false,
false,
false
]
},
"hash": "71a455c705f9c25d3843ff2fb8629d1320a5eb10797cdb5a435455d22c6aeac1"
}
@@ -0,0 +1,38 @@
{
"db_name": "SQLite",
"query": "SELECT\n id as \"id!\",\n date as \"date!\",\n timestamp_utc as \"timestamp_utc!\",\n value_json as \"value_json!\"\n FROM summary_history\n ORDER BY date DESC\n LIMIT 30",
"describe": {
"columns": [
{
"name": "id!",
"ordinal": 0,
"type_info": "Int64"
},
{
"name": "date!",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "timestamp_utc!",
"ordinal": 2,
"type_info": "Int64"
},
{
"name": "value_json!",
"ordinal": 3,
"type_info": "Text"
}
],
"parameters": {
"Right": 0
},
"nullable": [
true,
false,
false,
true
]
},
"hash": "7600823da7ce80b8ffda933608603a2752e28df775d1af8fd943a5fc8d7dc00d"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "INSERT INTO summary_history\n (date, timestamp_utc, value_json)\n VALUES (?, ?, ?)\n ON CONFLICT(date) DO UPDATE SET\n timestamp_utc=excluded.timestamp_utc,\n value_json=excluded.value_json;",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "788515c34588aec352773df4b6e6c5e41f3c0bb56a27648b5e25466b8634a578"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "UPDATE gateways\n SET blacklisted = true\n WHERE gateway_identity_key = ?;",
"describe": {
"columns": [],
"parameters": {
"Right": 1
},
"nullable": []
},
"hash": "8571faad2f66e08f24acfbfe036d17ca6eb090df7f6d52ef89c5d51564f8b45c"
}
@@ -0,0 +1,20 @@
{
"db_name": "SQLite",
"query": "SELECT count(id) FROM gateways",
"describe": {
"columns": [
{
"name": "count(id)",
"ordinal": 0,
"type_info": "Int"
}
],
"parameters": {
"Right": 0
},
"nullable": [
false
]
},
"hash": "86ff64db477a1d6235179b0b88d86b86d1b9be62336c9eac0eef44987a5451b5"
}
@@ -0,0 +1,26 @@
{
"db_name": "SQLite",
"query": "SELECT\n gateway_identity_key as \"gateway_identity_key!\",\n bonded as \"bonded: bool\"\n FROM gateways\n ORDER BY last_testrun_utc",
"describe": {
"columns": [
{
"name": "gateway_identity_key!",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "bonded: bool",
"ordinal": 1,
"type_info": "Int64"
}
],
"parameters": {
"Right": 0
},
"nullable": [
false,
false
]
},
"hash": "930a41e612b4e964ae214843da190f6c66c14d4267a2cc2ca73354becc2c8bb8"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "UPDATE gateways SET last_testrun_utc = ?, last_updated_utc = ? WHERE id = ?",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "c214c001acbbf79fa499816f36ec586c4c29c03efb4cf0c40b73a5c76159cf5c"
}
@@ -0,0 +1,44 @@
{
"db_name": "SQLite",
"query": "\n SELECT\n date_utc as \"date_utc!\",\n packets_received as \"total_packets_received!: i64\",\n packets_sent as \"total_packets_sent!: i64\",\n packets_dropped as \"total_packets_dropped!: i64\",\n total_stake as \"total_stake!: i64\"\n FROM (\n SELECT\n date_utc,\n SUM(packets_received) as packets_received,\n SUM(packets_sent) as packets_sent,\n SUM(packets_dropped) as packets_dropped,\n SUM(total_stake) as total_stake\n FROM mixnode_daily_stats\n GROUP BY date_utc\n ORDER BY date_utc DESC\n LIMIT 30\n )\n GROUP BY date_utc\n ORDER BY date_utc\n ",
"describe": {
"columns": [
{
"name": "date_utc!",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "total_packets_received!: i64",
"ordinal": 1,
"type_info": "Int64"
},
{
"name": "total_packets_sent!: i64",
"ordinal": 2,
"type_info": "Int64"
},
{
"name": "total_packets_dropped!: i64",
"ordinal": 3,
"type_info": "Int64"
},
{
"name": "total_stake!: i64",
"ordinal": 4,
"type_info": "Int64"
}
],
"parameters": {
"Right": 0
},
"nullable": [
false,
true,
true,
true,
false
]
},
"hash": "c5e3cd7284b334df5aa979b1627ea1f6dc2aed00cedde25f2be3567e47064351"
}
@@ -0,0 +1,32 @@
{
"db_name": "SQLite",
"query": "SELECT\n id as \"id!\",\n identity_key as \"identity_key!\",\n bonded as \"bonded: bool\"\n FROM mixnodes\n WHERE bonded = ?",
"describe": {
"columns": [
{
"name": "id!",
"ordinal": 0,
"type_info": "Int64"
},
{
"name": "identity_key!",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "bonded: bool",
"ordinal": 2,
"type_info": "Int64"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
false,
false
]
},
"hash": "c7ba2621becb9ac4b5dee0ce303dadfcf19095935867a51cbd5b8362d1505fcc"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "INSERT INTO gateways\n (gateway_identity_key, bonded, blacklisted,\n self_described, explorer_pretty_bond,\n last_updated_utc, performance)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(gateway_identity_key) DO UPDATE SET\n bonded=excluded.bonded,\n blacklisted=excluded.blacklisted,\n self_described=excluded.self_described,\n explorer_pretty_bond=excluded.explorer_pretty_bond,\n last_updated_utc=excluded.last_updated_utc,\n performance = excluded.performance;",
"describe": {
"columns": [],
"parameters": {
"Right": 7
},
"nullable": []
},
"hash": "d8ea93e781666e6267902170709ee2aa37f6163525bbdce1a4cebef4a285f8d9"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "INSERT INTO summary\n (key, value_json, last_updated_utc)\n VALUES (?, ?, ?)\n ON CONFLICT(key) DO UPDATE SET\n value_json=excluded.value_json,\n last_updated_utc=excluded.last_updated_utc;",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "e0c76a959276e3b0f44c720af9c74a5bf4912ee73468e62e7d0d96b1d9074cbe"
}
@@ -0,0 +1,86 @@
{
"db_name": "SQLite",
"query": "SELECT\n mn.mix_id as \"mix_id!\",\n mn.bonded as \"bonded: bool\",\n mn.blacklisted as \"blacklisted: bool\",\n mn.is_dp_delegatee as \"is_dp_delegatee: bool\",\n mn.total_stake as \"total_stake!\",\n mn.full_details as \"full_details!\",\n mn.self_described as \"self_described\",\n mn.last_updated_utc as \"last_updated_utc!\",\n COALESCE(md.moniker, \"NA\") as \"moniker!\",\n COALESCE(md.website, \"NA\") as \"website!\",\n COALESCE(md.security_contact, \"NA\") as \"security_contact!\",\n COALESCE(md.details, \"NA\") as \"details!\"\n FROM mixnodes mn\n LEFT JOIN mixnode_description md ON mn.mix_id = md.mix_id\n ORDER BY mn.mix_id",
"describe": {
"columns": [
{
"name": "mix_id!",
"ordinal": 0,
"type_info": "Int64"
},
{
"name": "bonded: bool",
"ordinal": 1,
"type_info": "Int64"
},
{
"name": "blacklisted: bool",
"ordinal": 2,
"type_info": "Int64"
},
{
"name": "is_dp_delegatee: bool",
"ordinal": 3,
"type_info": "Int64"
},
{
"name": "total_stake!",
"ordinal": 4,
"type_info": "Int64"
},
{
"name": "full_details!",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "self_described",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "last_updated_utc!",
"ordinal": 7,
"type_info": "Int64"
},
{
"name": "moniker!",
"ordinal": 8,
"type_info": "Text"
},
{
"name": "website!",
"ordinal": 9,
"type_info": "Text"
},
{
"name": "security_contact!",
"ordinal": 10,
"type_info": "Text"
},
{
"name": "details!",
"ordinal": 11,
"type_info": "Text"
}
],
"parameters": {
"Right": 0
},
"nullable": [
false,
false,
false,
false,
false,
true,
true,
false,
false,
false,
false,
false
]
},
"hash": "f0a4316081d1be9444a87b95d933d31cb4bcc4071d31d8d2f7755e2d2c2e3e35"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "UPDATE\n testruns\n SET\n status = ?\n WHERE\n status = ?\n AND\n timestamp_utc < ?\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "f5048d9926a5f5329f7f3b96d43b925e033ceec4f8112258feb4ac9e96fc5924"
}

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