Compare commits

...

4 Commits

Author SHA1 Message Date
dependabot[bot] e587d20a65 Bump joi from 17.13.3 to 18.2.1
Bumps [joi](https://github.com/hapijs/joi) from 17.13.3 to 18.2.1.
- [Commits](https://github.com/hapijs/joi/compare/v17.13.3...v18.2.1)

---
updated-dependencies:
- dependency-name: joi
  dependency-version: 18.2.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-11 23:38:11 +00:00
import this 56846fee77 fix gw landing page (#6873)
* fix landing page image

* use better url
2026-06-10 16:32:12 +02:00
Jędrzej Stuczyński 52949f825a chore: add retries for retrieving chain data (#6847)
* chore: add retries for retrieving chain data

* added retry backoff
2026-06-10 09:54:36 +01:00
Jędrzej Stuczyński 2705330595 feat: introduce node families contract query for Config retrieval (#6870) 2026-06-10 09:54:13 +01:00
11 changed files with 157 additions and 64 deletions
@@ -7,12 +7,12 @@ use crate::nyxd::error::NyxdError;
use crate::nyxd::CosmWasmClient;
use async_trait::async_trait;
use cosmrs::AccountId;
use nym_mixnet_contract_common::NodeId;
use serde::Deserialize;
use nym_mixnet_contract_common::NodeId;
pub use nym_node_families_contract_common::{
msg::QueryMsg as NodeFamiliesQueryMsg, AllFamilyMembersPagedResponse,
AllPastFamilyInvitationsPagedResponse, FamiliesPagedResponse, FamilyMemberRecord,
AllPastFamilyInvitationsPagedResponse, Config, FamiliesPagedResponse, FamilyMemberRecord,
FamilyMembersPagedResponse, GlobalPastFamilyInvitationCursor, NodeFamily,
NodeFamilyByNameResponse, NodeFamilyByOwnerResponse, NodeFamilyId,
NodeFamilyMembershipResponse, NodeFamilyResponse, PastFamilyInvitation,
@@ -35,6 +35,11 @@ pub trait NodeFamiliesQueryClient {
where
for<'a> T: Deserialize<'a>;
async fn get_config(&self) -> Result<Config, NyxdError> {
self.query_node_families_contract(NodeFamiliesQueryMsg::GetConfig {})
.await
}
async fn get_family_by_id(
&self,
family_id: NodeFamilyId,
@@ -360,6 +365,7 @@ mod tests {
msg: NodeFamiliesQueryMsg,
) {
match msg {
NodeFamiliesQueryMsg::GetConfig {} => client.get_config().ignore(),
NodeFamiliesQueryMsg::GetFamilyById { family_id } => {
client.get_family_by_id(family_id).ignore()
}
@@ -96,6 +96,10 @@ pub enum ExecuteMsg {
#[cw_serde]
#[cfg_attr(feature = "schema", derive(cosmwasm_schema::QueryResponses))]
pub enum QueryMsg {
/// Retrieve current contract configuration values
#[cfg_attr(feature = "schema", returns(Config))]
GetConfig {},
/// Look up a single family by its id.
#[cfg_attr(feature = "schema", returns(NodeFamilyResponse))]
GetFamilyById { family_id: NodeFamilyId },
+70 -6
View File
@@ -10,6 +10,7 @@ use crate::{Any, MessageRegistry, ParsedTransactionDetails, default_message_regi
use futures::StreamExt;
use futures::future::join3;
use std::collections::BTreeMap;
use std::future::Future;
use std::sync::Arc;
use tendermint::{Block, Hash};
use tendermint_rpc::endpoint::{block, block_results, tx, validators};
@@ -18,6 +19,38 @@ use tokio::sync::Mutex;
use tracing::{debug, instrument, warn};
use url::Url;
const MAX_QUERY_ATTEMPTS: usize = 3;
/// Runs `op` up to `max_attempts` times (at least once), returning the first success or, on full
/// exhaustion, the last error encountered.
async fn query_with_retries<F, Fut, T>(mut max_attempts: usize, op: F) -> Result<T, ScraperError>
where
F: Fn() -> Fut,
Fut: Future<Output = Result<T, ScraperError>>,
{
if max_attempts == 0 {
max_attempts = 1;
}
let mut last_err = None;
for i in 0..max_attempts {
match op().await {
Ok(result) => return Ok(result),
Err(err) => {
debug!("query failed, retrying {}/{max_attempts} - {err}", i + 1);
last_err = Some(err);
}
}
tokio::time::sleep(std::time::Duration::from_millis(300 * (i as u64 + 1))).await;
}
// SAFETY: max_attempts >= 1, so we only reach here after at least one recorded failure
#[allow(clippy::unwrap_used)]
Err(last_err.unwrap())
}
#[derive(Debug, Clone, Copy)]
pub struct RetrievalConfig {
pub get_validators: bool,
@@ -173,13 +206,24 @@ impl RpcClient {
})
}
#[instrument(skip(self), err(Display))]
async fn get_block_results_with_retries(
&self,
height: u32,
max_attempts: usize,
) -> Result<block_results::Response, ScraperError> {
query_with_retries(max_attempts, || self.get_block_results(height)).await
}
async fn maybe_get_block_results(
&self,
height: u32,
retrieve: bool,
) -> Result<Option<block_results::Response>, ScraperError> {
if retrieve {
self.get_block_results(height).await.map(Some)
self.get_block_results_with_retries(height, MAX_QUERY_ATTEMPTS)
.await
.map(Some)
} else {
Ok(None)
}
@@ -219,8 +263,6 @@ impl RpcClient {
// "Data is just a wrapper for a list of transactions, where transactions are arbitrary byte arrays"
// source: https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md#data
//
// I hate that zip as much as you, dear reader, but for some reason the compiler didn't let me remove the `move`
futures::stream::iter(
raw.iter()
.map(tx_hash)
@@ -228,12 +270,14 @@ impl RpcClient {
.zip(std::iter::repeat(ordered_results.clone())),
)
.for_each_concurrent(4, |((id, tx_hash), ordered_results)| async move {
let res = self.get_transaction_result(tx_hash).await;
let res = self
.get_transaction_result_with_retries(tx_hash, MAX_QUERY_ATTEMPTS)
.await;
ordered_results.lock().await.insert(id, res);
})
.await;
// safety the futures have completed so we MUST have the only arc reference
// safety: the futures have completed so we MUST have the only arc reference
#[allow(clippy::unwrap_used)]
let inner = Arc::into_inner(ordered_results).unwrap().into_inner();
@@ -266,6 +310,15 @@ impl RpcClient {
})
}
#[instrument(skip(self, tx_hash), fields(tx_hash = %tx_hash), err(Display))]
async fn get_transaction_result_with_retries(
&self,
tx_hash: Hash,
max_attempts: usize,
) -> Result<tx::Response, ScraperError> {
query_with_retries(max_attempts, || self.get_transaction_result(tx_hash)).await
}
#[instrument(skip(self))]
pub async fn get_validators_details(
&self,
@@ -282,13 +335,24 @@ impl RpcClient {
})
}
#[instrument(skip(self), err(Display))]
async fn get_validators_details_with_retries(
&self,
height: u32,
max_attempts: usize,
) -> Result<validators::Response, ScraperError> {
query_with_retries(max_attempts, || self.get_validators_details(height)).await
}
async fn maybe_get_validators_details(
&self,
height: u32,
retrieve: bool,
) -> Result<Option<validators::Response>, ScraperError> {
if retrieve {
self.get_validators_details(height).await.map(Some)
self.get_validators_details_with_retries(height, MAX_QUERY_ATTEMPTS)
.await
.map(Some)
} else {
Ok(None)
}
+1 -2
View File
@@ -1037,7 +1037,7 @@ dependencies = [
[[package]]
name = "node-families"
version = "0.1.0"
version = "0.1.1"
dependencies = [
"anyhow",
"cosmwasm-schema",
@@ -1052,7 +1052,6 @@ dependencies = [
"nym-mixnet-contract",
"nym-mixnet-contract-common",
"nym-node-families-contract-common",
"serde",
]
[[package]]
+1 -1
View File
@@ -87,7 +87,7 @@ cw3-flex-multisig = { version = "2.0.0", path = "multisig/cw3-flex-multisig" }
cw4-group = { version = "2.0.0", path = "multisig/cw4-group" }
nym-mixnet-contract = { version = "1.5.1", path = "mixnet" }
nym-vesting-contract = { version = "1.4.1", path = "vesting" }
node-families = { version = "0.1.0", path = "node-families" }
node-families = { version = "0.1.1", path = "node-families" }
[workspace.lints.clippy]
unwrap_used = "deny"
+1 -2
View File
@@ -1,7 +1,7 @@
[package]
name = "node-families"
description = "Nym Node Families contract"
version = "0.1.0"
version = "0.1.1"
authors.workspace = true
edition.workspace = true
license.workspace = true
@@ -25,7 +25,6 @@ cosmwasm-std = { workspace = true }
cw2 = { workspace = true }
cw-storage-plus = { workspace = true }
cw-controllers = { workspace = true }
serde = { workspace = true }
cosmwasm-schema = { workspace = true, optional = true }
cw-utils = { workspace = true }
+2 -1
View File
@@ -5,7 +5,7 @@
use crate::queries::{
query_all_family_members_paged, query_all_past_invitations_paged,
query_all_pending_invitations_paged, query_families_paged, query_family_by_id,
query_all_pending_invitations_paged, query_config, query_families_paged, query_family_by_id,
query_family_by_name, query_family_by_owner, query_family_members_paged,
query_family_membership, query_past_invitations_for_family_paged,
query_past_invitations_for_node_paged, query_past_members_for_family_paged,
@@ -100,6 +100,7 @@ pub fn execute(
#[entry_point]
pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result<Binary, NodeFamiliesContractError> {
match msg {
QueryMsg::GetConfig {} => Ok(to_json_binary(&query_config(deps)?)?),
QueryMsg::GetFamilyById { family_id } => {
Ok(to_json_binary(&query_family_by_id(deps, family_id)?)?)
}
+15 -10
View File
@@ -7,18 +7,23 @@ use cosmwasm_std::{Deps, Env, Order, StdResult};
use cw_storage_plus::Bound;
use nym_mixnet_contract_common::NodeId;
use nym_node_families_contract_common::{
AllFamilyMembersPagedResponse, AllPastFamilyInvitationsPagedResponse, FamiliesPagedResponse,
FamilyMemberRecord, FamilyMembersPagedResponse, GlobalPastFamilyInvitationCursor,
NodeFamiliesContractError, NodeFamilyByNameResponse, NodeFamilyByOwnerResponse, NodeFamilyId,
NodeFamilyMembershipResponse, NodeFamilyResponse, PastFamilyInvitationCursor,
PastFamilyInvitationForNodeCursor, PastFamilyInvitationsForNodePagedResponse,
PastFamilyInvitationsPagedResponse, PastFamilyMemberCursor, PastFamilyMemberForNodeCursor,
PastFamilyMembersForNodePagedResponse, PastFamilyMembersPagedResponse,
PendingFamilyInvitationDetails, PendingFamilyInvitationResponse,
PendingFamilyInvitationsPagedResponse, PendingInvitationsForNodePagedResponse,
PendingInvitationsPagedResponse,
AllFamilyMembersPagedResponse, AllPastFamilyInvitationsPagedResponse, Config,
FamiliesPagedResponse, FamilyMemberRecord, FamilyMembersPagedResponse,
GlobalPastFamilyInvitationCursor, NodeFamiliesContractError, NodeFamilyByNameResponse,
NodeFamilyByOwnerResponse, NodeFamilyId, NodeFamilyMembershipResponse, NodeFamilyResponse,
PastFamilyInvitationCursor, PastFamilyInvitationForNodeCursor,
PastFamilyInvitationsForNodePagedResponse, PastFamilyInvitationsPagedResponse,
PastFamilyMemberCursor, PastFamilyMemberForNodeCursor, PastFamilyMembersForNodePagedResponse,
PastFamilyMembersPagedResponse, PendingFamilyInvitationDetails,
PendingFamilyInvitationResponse, PendingFamilyInvitationsPagedResponse,
PendingInvitationsForNodePagedResponse, PendingInvitationsPagedResponse,
};
/// Retrieve current contract configuration values
pub fn query_config(deps: Deps) -> Result<Config, NodeFamiliesContractError> {
Ok(NodeFamiliesStorage::new().config.load(deps.storage)?)
}
/// Resolve a single family by its id. Returns `family: None` if no family
/// with that id exists.
pub fn query_family_by_id(
+51 -36
View File
@@ -379,8 +379,8 @@ catalogs:
specifier: ^27.1.0
version: 27.5.1
joi:
specifier: ^17.11.0
version: 17.13.3
specifier: ^18.2.1
version: 18.2.1
localforage:
specifier: ^1.10.0
version: 1.10.0
@@ -939,7 +939,7 @@ importers:
version: 2.0.3
joi:
specifier: 'catalog:'
version: 17.13.3
version: 18.2.1
localforage:
specifier: 'catalog:'
version: 1.10.0
@@ -3135,11 +3135,25 @@ packages:
'@gar/promisify@1.1.3':
resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==}
'@hapi/hoek@9.3.0':
resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==}
'@hapi/address@5.1.1':
resolution: {integrity: sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA==}
engines: {node: '>=14.0.0'}
'@hapi/topo@5.1.0':
resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==}
'@hapi/formula@3.0.2':
resolution: {integrity: sha512-hY5YPNXzw1He7s0iqkRQi+uMGh383CGdyyIGYtB+W5N3KHPXoqychklvHhKCC9M3Xtv0OCs/IHw+r4dcHtBYWw==}
'@hapi/hoek@11.0.7':
resolution: {integrity: sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==}
'@hapi/pinpoint@2.0.1':
resolution: {integrity: sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q==}
'@hapi/tlds@1.1.7':
resolution: {integrity: sha512-MgNjRwy9Ti92yVAixLmDc8dd1bJIKwO9qlWCfFQRwRmUEDPQHYn4G6hwPFvFGUTzAa0FsS+inMjLin7GnyBRhA==}
engines: {node: '>=14.0.0'}
'@hapi/topo@6.0.2':
resolution: {integrity: sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==}
'@hookform/resolvers@2.9.11':
resolution: {integrity: sha512-bA3aZ79UgcHj7tFV7RlgThzwSSHZgvfbt2wprldRkYBcMopdMvHyO17Wwp/twcJasNFischFfS7oz8Katz8DdQ==}
@@ -4298,15 +4312,6 @@ packages:
'@scure/starknet@1.1.0':
resolution: {integrity: sha512-83g3M6Ix2qRsPN4wqLDqiRZ2GBNbjVWfboJE/9UjfG+MHr6oDSu/CWgy8hsBSJejr09DkkL+l0Ze4KVrlCIdtQ==}
'@sideway/address@4.1.5':
resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==}
'@sideway/formula@3.0.1':
resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==}
'@sideway/pinpoint@2.0.0':
resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==}
'@sigstore/bundle@1.1.0':
resolution: {integrity: sha512-PFutXEy0SmQxYI4texPw3dd2KewuNqv7OuK1ZFtY2fM754yhvG2KdgwIhRnoEE2uHdtdGNQ8s0lb94dW9sELog==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
@@ -4338,6 +4343,9 @@ packages:
'@sinonjs/fake-timers@8.1.0':
resolution: {integrity: sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==}
'@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
'@starknet-io/types-js@0.8.4':
resolution: {integrity: sha512-0RZ3TZHcLsUTQaq1JhDSCM8chnzO4/XNsSCozwDET64JK5bjFDIf2ZUkta+tl5Nlbf4usoU7uZiDI/Q57kt2SQ==}
@@ -9673,8 +9681,9 @@ packages:
node-notifier:
optional: true
joi@17.13.3:
resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==}
joi@18.2.1:
resolution: {integrity: sha512-2/OKlogiESf2Nh3TFCrRjrr9z1DRHeW0I+KReF67+4J0Ns+8hBtHRmoWAZ2OFU6I5+TWLEe6sVlSdXPjHm5UbQ==}
engines: {node: '>= 20'}
js-sha3@0.8.0:
resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==}
@@ -15873,11 +15882,21 @@ snapshots:
'@gar/promisify@1.1.3': {}
'@hapi/hoek@9.3.0': {}
'@hapi/topo@5.1.0':
'@hapi/address@5.1.1':
dependencies:
'@hapi/hoek': 9.3.0
'@hapi/hoek': 11.0.7
'@hapi/formula@3.0.2': {}
'@hapi/hoek@11.0.7': {}
'@hapi/pinpoint@2.0.1': {}
'@hapi/tlds@1.1.7': {}
'@hapi/topo@6.0.2':
dependencies:
'@hapi/hoek': 11.0.7
'@hookform/resolvers@2.9.11(react-hook-form@7.75.0(react@19.2.6))':
dependencies:
@@ -17424,14 +17443,6 @@ snapshots:
'@noble/curves': 1.7.0
'@noble/hashes': 1.6.1
'@sideway/address@4.1.5':
dependencies:
'@hapi/hoek': 9.3.0
'@sideway/formula@3.0.1': {}
'@sideway/pinpoint@2.0.0': {}
'@sigstore/bundle@1.1.0':
dependencies:
'@sigstore/protobuf-specs': 0.2.1
@@ -17471,6 +17482,8 @@ snapshots:
dependencies:
'@sinonjs/commons': 1.8.6
'@standard-schema/spec@1.1.0': {}
'@starknet-io/types-js@0.8.4': {}
'@starknet-io/types-js@0.9.2': {}
@@ -25301,13 +25314,15 @@ snapshots:
- supports-color
- ts-node
joi@17.13.3:
joi@18.2.1:
dependencies:
'@hapi/hoek': 9.3.0
'@hapi/topo': 5.1.0
'@sideway/address': 4.1.5
'@sideway/formula': 3.0.1
'@sideway/pinpoint': 2.0.0
'@hapi/address': 5.1.1
'@hapi/formula': 3.0.2
'@hapi/hoek': 11.0.7
'@hapi/pinpoint': 2.0.1
'@hapi/tlds': 1.1.7
'@hapi/topo': 6.0.2
'@standard-schema/spec': 1.1.0
js-sha3@0.8.0: {}
+1 -1
View File
@@ -230,7 +230,7 @@ catalog:
"flat": "^5.0.2"
"glob": "^10.5.0"
"hex-rgb": "^4.3.0"
"joi": "^17.11.0"
"joi": "^18.2.1"
"localforage": "^1.10.0"
"lodash": "^4.17.21"
"lodash.padend": "^4.6.1"
+3 -3
View File
@@ -95,7 +95,7 @@ a:hover {
You are most likely accessing this website because you've had some issue with
the traffic coming from this IP. This router is part of the <a
href="https://nym.com/">NYM project</a>, which is
dedicated to <a href="https://nym.com/about/mission">create</a> outstanding
dedicated to <a href="https://nym.com/features">create</a> outstanding
privacy software that is legally compliant without sacrificing integrity or
having any backdoors.
This router IP should be generating no other traffic, unless it has been
@@ -168,7 +168,7 @@ a:hover {
</svg>
</p>
<p><a href="https://nym.com/about/mixnet">Read more about how Nym works.</a></p>
<p><a href="https://nym.com/docs/network/overview">Read more about how Nym works.</a></p>
<p>
Nym relies on a growing ecosystem of users, developers and researcher partners
@@ -227,7 +227,7 @@ a:hover {
<p style="text-align:center">
<img
class="logo"
src="https://raw.githubusercontent.com/nymtech/websites/main/www/nym.com/public/images/Nym_meta_Image.png"
src="https://raw.githubusercontent.com/nymtech/nym/develop/explorer-v2/public/images/Network.webp"
alt=""
style="max-width:320px;width:100%;height:auto"
onerror="this.onerror=null;this.src='/images/nym_logo.png';"