diff --git a/nym-api/nym-api-requests/src/models/described/v2.rs b/nym-api/nym-api-requests/src/models/described/v2.rs index 465663dc2f..30e3de22d9 100644 --- a/nym-api/nym-api-requests/src/models/described/v2.rs +++ b/nym-api/nym-api-requests/src/models/described/v2.rs @@ -228,117 +228,3 @@ impl From for NymNodeDescriptionV2 { } } } - -#[cfg(any(test, feature = "mock-fixtures"))] -pub fn mock_nym_node_description(seed: u64) -> NymNodeDescriptionV2 { - use nym_node_requests::api::v1::lewes_protocol::models::{LPHashFunction, LPKEM}; - use nym_test_utils::helpers::{u64_seeded_rng, RngCore}; - - let mut rng = u64_seeded_rng(seed); - - let ed25519 = nym_crypto::asymmetric::ed25519::KeyPair::new(&mut rng); - - // just reuse the same x25519 key for everything - this is just a data mock - let x25519 = nym_crypto::asymmetric::x25519::KeyPair::new(&mut rng); - - let mut dummy_kems = std::collections::BTreeMap::new(); - for kem in [LPKEM::McEliece, LPKEM::McEliece] { - let mut kem_digests = std::collections::BTreeMap::new(); - for (i, sf) in [ - LPHashFunction::Blake3, - LPHashFunction::Shake128, - LPHashFunction::Shake256, - LPHashFunction::Sha256, - ] - .iter() - .enumerate() - { - kem_digests.insert(*sf, hex::encode([((seed + i as u64) % 256) as u8; 32])); - } - dummy_kems.insert(kem, kem_digests); - } - - // make sure the serialisation stays the same and signature is still valid - let dummy_lp = nym_node_requests::api::v1::lewes_protocol::models::LewesProtocol { - enabled: false, - control_port: 123, - data_port: 345, - x25519: (*x25519.public_key()).into(), - kem_keys: dummy_kems, - }; - let dummy_signed_lp = - nym_node_requests::api::SignedLewesProtocol::new(dummy_lp, ed25519.private_key()).unwrap(); - - NymNodeDescriptionV2 { - node_id: rng.next_u32(), - contract_node_type: DescribedNodeTypeV1::NymNode, - description: NymNodeDataV2 { - last_polled: time::OffsetDateTime::from_unix_timestamp(1767225600) - .unwrap() - .into(), - host_information: HostInformationV2 { - ip_address: vec![ - std::net::IpAddr::V4(std::net::Ipv4Addr::new(1, 2, 3, (seed % 255) as u8)), - ], - hostname: Some(format!("my-awesome-node-{seed}.com")), - keys: HostKeysV2 { - ed25519: *ed25519.public_key(), - x25519: *x25519.public_key(), - current_x25519_sphinx_key: SphinxKeyV2 { - rotation_id: 123, - public_key: *x25519.public_key(), - }, - pre_announced_x25519_sphinx_key: None, - x25519_versioned_noise: Some(VersionedNoiseKeyV2 { - supported_version: nym_noise_keys::NoiseVersion::V1, - x25519_pubkey: *x25519.public_key(), - }), - }, - }, - declared_role: DeclaredRolesV2 { - mixnode: false, - entry: true, - exit_nr: true, - exit_ipr: true, - }, - auxiliary_details: NymNodeAuxiliaryDetailsV2 { - location: Some(celes::Country::switzerland()), - announce_ports: Default::default(), - accepted_operator_terms_and_conditions: true, - }, - build_information: BinaryBuildInformationOwned { - binary_name: "dummy-node".to_string(), - build_timestamp: "2021-02-23T20:14:46.558472672+00:00".to_string(), - build_version: "0.1.0-9-g46f83e1".to_string(), - commit_sha: "46f83e112520533338245862d366f6a02cef07d4".to_string(), - commit_timestamp: "2021-02-23T08:08:02-05:00".to_string(), - commit_branch: "master".to_string(), - rustc_version: "1.52.0-nightly".to_string(), - rustc_channel: "nightly".to_string(), - cargo_profile: "release".to_string(), - cargo_triple: "wasm32-unknown-unknown".to_string(), - }, - network_requester: Some(NetworkRequesterDetailsV2 { - address: "FhtkzizQg2JbZ19kGkRKXdjV2QnFbT5ww88ZAKaD4nkF.7Remi4UVYzn1yL3qYtEcQBGh6tzTYxMdYB4uqyHVc5Z4@62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve".to_string(), - uses_exit_policy: true, - }), - ip_packet_router: Some(IpPacketRouterDetailsV2 { - address: "FhtkzizQg2JbZ19kGkRKXdjV2QnFbT5ww88ZAKaD4nkF.7Remi4UVYzn1yL3qYtEcQBGh6tzTYxMdYB4uqyHVc5Z4@62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve".to_string(), - }), - authenticator: Some(AuthenticatorDetailsV2 { - address: "FhtkzizQg2JbZ19kGkRKXdjV2QnFbT5ww88ZAKaD4nkF.7Remi4UVYzn1yL3qYtEcQBGh6tzTYxMdYB4uqyHVc5Z4@62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve".to_string(), - }), - wireguard: Some(WireguardDetailsV2 { - port: 123, - tunnel_port: 234, - metadata_port: 456, - public_key: x25519.public_key().to_base58_string(), - }), - lewes_protocol: Some(dummy_signed_lp.into()), - mixnet_websockets: WebSocketsV2 { - ws_port: 9000, - wss_port: None, - }, - }, - } -} diff --git a/nym-api/nym-api-requests/src/models/described/v3.rs b/nym-api/nym-api-requests/src/models/described/v3.rs index fb0a208fe4..ffca6ca6aa 100644 --- a/nym-api/nym-api-requests/src/models/described/v3.rs +++ b/nym-api/nym-api-requests/src/models/described/v3.rs @@ -279,3 +279,118 @@ impl From for NymNodeAuxiliaryDetailsV2 { } } } + +#[cfg(any(test, feature = "mock-fixtures"))] +pub fn mock_nym_node_description(seed: u64) -> NymNodeDescriptionV3 { + use nym_node_requests::api::v1::lewes_protocol::models::{LPHashFunction, LPKEM}; + use nym_test_utils::helpers::{u64_seeded_rng, RngCore}; + + let mut rng = u64_seeded_rng(seed); + + let ed25519 = nym_crypto::asymmetric::ed25519::KeyPair::new(&mut rng); + + // just reuse the same x25519 key for everything - this is just a data mock + let x25519 = nym_crypto::asymmetric::x25519::KeyPair::new(&mut rng); + + let mut dummy_kems = std::collections::BTreeMap::new(); + for kem in [LPKEM::McEliece, LPKEM::McEliece] { + let mut kem_digests = std::collections::BTreeMap::new(); + for (i, sf) in [ + LPHashFunction::Blake3, + LPHashFunction::Shake128, + LPHashFunction::Shake256, + LPHashFunction::Sha256, + ] + .iter() + .enumerate() + { + kem_digests.insert(*sf, hex::encode([((seed + i as u64) % 256) as u8; 32])); + } + dummy_kems.insert(kem, kem_digests); + } + + // make sure the serialisation stays the same and signature is still valid + let dummy_lp = nym_node_requests::api::v1::lewes_protocol::models::LewesProtocol { + enabled: false, + control_port: 123, + data_port: 345, + x25519: (*x25519.public_key()).into(), + kem_keys: dummy_kems, + }; + let dummy_signed_lp = + nym_node_requests::api::SignedLewesProtocol::new(dummy_lp, ed25519.private_key()).unwrap(); + + NymNodeDescriptionV3 { + node_id: rng.next_u32(), + contract_node_type: DescribedNodeTypeV3::NymNode, + description: NymNodeDataV3 { + last_polled: time::OffsetDateTime::from_unix_timestamp(1767225600) + .unwrap() + .into(), + host_information: HostInformationV3 { + ip_address: vec![ + std::net::IpAddr::V4(std::net::Ipv4Addr::new(1, 2, 3, (seed % 255) as u8)), + ], + hostname: Some(format!("my-awesome-node-{seed}.com")), + keys: HostKeysV3 { + ed25519: *ed25519.public_key(), + x25519: *x25519.public_key(), + current_x25519_sphinx_key: SphinxKeyV3 { + rotation_id: 123, + public_key: *x25519.public_key(), + }, + pre_announced_x25519_sphinx_key: None, + x25519_versioned_noise: Some(VersionedNoiseKeyV3 { + supported_version: nym_noise_keys::NoiseVersion::V1, + x25519_pubkey: *x25519.public_key(), + }), + }, + }, + declared_role: DeclaredRolesV3 { + mixnode: false, + entry: true, + exit_nr: true, + exit_ipr: true, + }, + auxiliary_details: NymNodeAuxiliaryDetailsV3 { + location: Some(celes::Country::switzerland()), + address: Some("n1jw6mp7d5xqc7w6xm79lha27glmd0vdt3l9artf".to_string()), + announce_ports: Default::default(), + accepted_operator_terms_and_conditions: true, + }, + build_information: BinaryBuildInformationOwned { + binary_name: "dummy-node".to_string(), + build_timestamp: "2021-02-23T20:14:46.558472672+00:00".to_string(), + build_version: "0.1.0-9-g46f83e1".to_string(), + commit_sha: "46f83e112520533338245862d366f6a02cef07d4".to_string(), + commit_timestamp: "2021-02-23T08:08:02-05:00".to_string(), + commit_branch: "master".to_string(), + rustc_version: "1.52.0-nightly".to_string(), + rustc_channel: "nightly".to_string(), + cargo_profile: "release".to_string(), + cargo_triple: "wasm32-unknown-unknown".to_string(), + }, + network_requester: Some(NetworkRequesterDetailsV3 { + address: "FhtkzizQg2JbZ19kGkRKXdjV2QnFbT5ww88ZAKaD4nkF.7Remi4UVYzn1yL3qYtEcQBGh6tzTYxMdYB4uqyHVc5Z4@62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve".to_string(), + uses_exit_policy: true, + }), + ip_packet_router: Some(IpPacketRouterDetailsV3 { + address: "FhtkzizQg2JbZ19kGkRKXdjV2QnFbT5ww88ZAKaD4nkF.7Remi4UVYzn1yL3qYtEcQBGh6tzTYxMdYB4uqyHVc5Z4@62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve".to_string(), + }), + authenticator: Some(AuthenticatorDetailsV3 { + address: "FhtkzizQg2JbZ19kGkRKXdjV2QnFbT5ww88ZAKaD4nkF.7Remi4UVYzn1yL3qYtEcQBGh6tzTYxMdYB4uqyHVc5Z4@62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve".to_string(), + }), + wireguard: Some(WireguardDetailsV3 { + port: 123, + tunnel_port: 234, + metadata_port: 456, + public_key: x25519.public_key().to_base58_string(), + }), + lewes_protocol: Some(dummy_signed_lp.into()), + mixnet_websockets: WebSocketsV3 { + ws_port: 9000, + wss_port: None, + }, + }, + } +} diff --git a/nym-api/nym-api-requests/src/models/node_status.rs b/nym-api/nym-api-requests/src/models/node_status.rs index a45aa01aec..280aa5144f 100644 --- a/nym-api/nym-api-requests/src/models/node_status.rs +++ b/nym-api/nym-api-requests/src/models/node_status.rs @@ -419,12 +419,21 @@ pub struct NodeAnnotationV1 { pub detailed_performance: DetailedNodePerformanceV1, } +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] +pub struct ChainInteractionCapabilitiesDetailed { + #[schema(value_type = CoinSchema)] + pub on_chain_balance: Coin, + + // later to be expanded with information on whether the grant would cover + // cosmwasm executemsg, but for now we assume any feegrant is sufficient + pub is_feegrant_grantee: bool, +} + #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, ToSchema)] pub struct NodeAnnotationV2 { pub current_role: Option, - #[schema(value_type = Option)] - pub on_chain_balance: Option, + pub chain_interaction_capabilities: Option, pub detailed_performance: DetailedNodePerformanceV2, } diff --git a/nym-api/src/node_status_api/cache/config_score.rs b/nym-api/src/node_status_api/cache/config_score.rs index 246534d5ec..b7eb983c9f 100644 --- a/nym-api/src/node_status_api/cache/config_score.rs +++ b/nym-api/src/node_status_api/cache/config_score.rs @@ -4,10 +4,11 @@ use crate::mixnet_contract_cache::cache::data::ConfigScoreData; use cosmwasm_std::Coin; use nym_api_requests::models::described::v3::NymNodeDescriptionV3; -use nym_api_requests::models::{ChainInteractionCapabilities, ConfigScoreV2}; +use nym_api_requests::models::{ + ChainInteractionCapabilities, ChainInteractionCapabilitiesDetailed, ConfigScoreV2, +}; use nym_contracts_common::NaiveFloat; use nym_mixnet_contract_common::VersionScoreFormulaParams; -use tracing::warn; fn versions_behind_factor_to_config_score( versions_behind: u32, @@ -20,10 +21,15 @@ fn versions_behind_factor_to_config_score( penalty.powf((versions_behind as f64).powf(scaling)) } -fn has_sufficient_tokens(minimum_balance: &Coin, chain_balance: &Option) -> bool { - let Some(chain_balance) = chain_balance else { +fn has_sufficient_tokens( + minimum_balance: &Coin, + capabilities: &Option, +) -> bool { + let Some(capabilities) = capabilities else { return false; }; + let chain_balance = &capabilities.on_chain_balance; + if chain_balance.denom != minimum_balance.denom { return false; } @@ -34,7 +40,7 @@ pub(crate) fn calculate_config_score( minimum_balance: &Coin, config_score_data: &ConfigScoreData, described_data: Option<&NymNodeDescriptionV3>, - chain_balance: &Option, + chain_capabilities: &Option, ) -> ConfigScoreV2 { let Some(described) = described_data else { return ConfigScoreV2::unavailable(); @@ -69,13 +75,12 @@ pub(crate) fn calculate_config_score( ) }; - let TODO = ""; - warn!("unimplemented check for feegrant"); - let chain_interaction = ChainInteractionCapabilities { - has_sufficient_tokens: has_sufficient_tokens(minimum_balance, chain_balance), - // TODO: implement this - is_fee_grant_grantee: false, + has_sufficient_tokens: has_sufficient_tokens(minimum_balance, chain_capabilities), + is_fee_grant_grantee: chain_capabilities + .as_ref() + .map(|c| c.is_feegrant_grantee) + .unwrap_or_default(), }; ConfigScoreV2::new( diff --git a/nym-api/src/node_status_api/cache/mod.rs b/nym-api/src/node_status_api/cache/mod.rs index 1a81402083..755d71297f 100644 --- a/nym-api/src/node_status_api/cache/mod.rs +++ b/nym-api/src/node_status_api/cache/mod.rs @@ -5,7 +5,7 @@ use self::data::NodeStatusCacheData; use crate::node_performance::provider::PerformanceRetrievalFailure; use crate::support::caching::cache::{SharedCache, UninitialisedCache}; use crate::support::caching::Cache; -use nym_api_requests::models::NodeAnnotationV2; +use nym_api_requests::models::{ChainInteractionCapabilitiesDetailed, NodeAnnotationV2}; use nym_mixnet_contract_common::NodeId; use std::collections::HashMap; use std::path::Path; @@ -104,15 +104,18 @@ impl NodeStatusCache { self.get(|c| &c.node_annotations).await } - async fn node_balances( + async fn chain_information( &self, - ) -> Result>, NodeStatusCacheError> { + ) -> Result>, NodeStatusCacheError> + { Ok(self .cache() .await? .node_annotations .iter() - .map(|(node_id, annotation)| (*node_id, annotation.on_chain_balance.clone())) + .map(|(node_id, annotation)| { + (*node_id, annotation.chain_interaction_capabilities.clone()) + }) .collect::>()) } } diff --git a/nym-api/src/node_status_api/cache/refresher.rs b/nym-api/src/node_status_api/cache/refresher.rs index eaa4b05b5f..6f26d70998 100644 --- a/nym-api/src/node_status_api/cache/refresher.rs +++ b/nym-api/src/node_status_api/cache/refresher.rs @@ -17,13 +17,16 @@ use crate::{ node_status_api::cache::NodeStatusCacheError, support::caching::CacheNotification, }; use ::time::OffsetDateTime; -use cosmwasm_std::Coin; +use cosmwasm_std::{coin, Coin}; use futures::StreamExt; use nym_api_requests::models::described::v3::NymNodeDescriptionV3; -use nym_api_requests::models::{DetailedNodePerformanceV2, NodeAnnotationV2}; +use nym_api_requests::models::{ + ChainInteractionCapabilitiesDetailed, DetailedNodePerformanceV2, NodeAnnotationV2, +}; use nym_mixnet_contract_common::{NodeId, NymNodeDetails}; use nym_task::ShutdownToken; use nym_topology::CachedEpochRewardedSet; +use nym_validator_client::nyxd::module_traits::feegrant::query::FeegrantQueryClient; use nym_validator_client::nyxd::{AccountId, CosmWasmClient}; use nym_validator_client::QueryHttpRpcNyxdClient; use std::collections::HashMap; @@ -38,9 +41,9 @@ pub(crate) struct NodeStatusCacheConfig { pub(crate) minimum_on_chain_balance: Coin, pub(crate) balance_retrieval_concurrency: usize, - /// Indicates how often should the chain balances of known nodes be refreshed. + /// Indicates how often should the chain balances (and feegrants) of known nodes be refreshed. /// (it is an overkill to do it every single iteration) - pub(crate) chain_balances_refresh_interval: Duration, + pub(crate) chain_capabilities_refresh_interval: Duration, pub(crate) fallback_caching_interval: Duration, @@ -232,10 +235,11 @@ impl NodeStatusCacheRefresher { // SAFETY: unwrap is fine as if the mutex got poisoned we'd be experiencing some UB anyway #[allow(clippy::unwrap_used)] - async fn retrieve_balances( + async fn retrieve_chain_info( &self, nodes: &DescribedNodes, - ) -> Result>, NodeStatusCacheError> { + ) -> Result>, NodeStatusCacheError> + { let denom = self.config.minimum_on_chain_balance.denom.clone(); // create an iterator of node ids with valid associated account addresses @@ -260,30 +264,24 @@ impl NodeStatusCacheRefresher { let concurrency = self.config.balance_retrieval_concurrency.max(1); // std Mutex is fine because we don't hold it across await points - let balances = std::sync::Mutex::new(HashMap::>::new()); + let capabilities = std::sync::Mutex::new(HashMap::< + NodeId, + Option, + >::new()); futures::stream::iter(to_check) .for_each_concurrent(concurrency, |(node_id, account_id)| { let denom = denom.clone(); let query_client = &self.query_client; - let balances = &balances; + let capabilities = &capabilities; async move { - match query_client.get_balance(&account_id, denom).await { - Ok(balance) => { - balances - .lock() - .unwrap() - .insert(node_id, balance.map(Into::into)); - } - Err(err) => { - warn!(node_id, %err, "failed to retrieve node balance"); - } - } + let chain_info = + retrieve_chain_capabilities(query_client, node_id, account_id, denom).await; + capabilities.lock().unwrap().insert(node_id, chain_info); } }) .await; - let balances = balances.into_inner().unwrap(); - Ok(balances) + Ok(capabilities.into_inner().unwrap()) } #[allow(clippy::too_many_arguments)] @@ -295,7 +293,7 @@ impl NodeStatusCacheRefresher { nym_nodes: &[NymNodeDetails], rewarded_set: &CachedEpochRewardedSet, described_nodes: &DescribedNodes, - balances: HashMap>, + chain_capabilities: HashMap>, ) -> HashMap { let mut annotations = HashMap::new(); if nym_nodes.is_empty() { @@ -336,13 +334,13 @@ impl NodeStatusCacheRefresher { let described = described_nodes.get_node(&node_id); let routing_score = routing_scores.get_or_log(node_id); let stress_testing_score = stress_testing_scores.get_or_log(node_id); - let on_chain_balance = balances.get(&node_id).unwrap_or(&None).clone(); + let node_chain_cap = chain_capabilities.get(&node_id).unwrap_or(&None).clone(); let config_score = calculate_config_score( minimum_balance, config_score_data, described, - &on_chain_balance, + &node_chain_cap, ); // a node only takes the stress-testing component if it is actually stress-tested (i.e. @@ -360,7 +358,7 @@ impl NodeStatusCacheRefresher { node_id, NodeAnnotationV2 { current_role: rewarded_set.role(node_id).map(|r| r.into()), - on_chain_balance, + chain_interaction_capabilities: node_chain_cap, detailed_performance: DetailedNodePerformanceV2::new( performance, routing_score, @@ -374,11 +372,11 @@ impl NodeStatusCacheRefresher { annotations } - fn should_refresh_balances(&self) -> bool { + fn should_refresh_chain_interaction(&self) -> bool { let Some(last_refresh) = self.last_refreshed_chain_balances else { return true; }; - last_refresh.elapsed() > self.config.chain_balances_refresh_interval + last_refresh.elapsed() > self.config.chain_capabilities_refresh_interval } /// Refreshes the node status cache by fetching the latest data from the contract cache @@ -417,13 +415,13 @@ impl NodeStatusCacheRefresher { // decide whether to refresh cache of node balances - let balances = if self.should_refresh_balances() { - let balances = self.retrieve_balances(&described).await?; + let chain_info = if self.should_refresh_chain_interaction() { + let info = self.retrieve_chain_info(&described).await?; self.last_refreshed_chain_balances = Some(Instant::now()); - balances + info } else { // use the currently cached values instead - self.cache.node_balances().await? + self.cache.chain_information().await? }; // Create annotated data @@ -435,7 +433,7 @@ impl NodeStatusCacheRefresher { &nym_nodes, &rewarded_set, &described, - balances, + chain_info, ) .await; @@ -454,6 +452,38 @@ impl NodeStatusCacheRefresher { } } +async fn retrieve_chain_capabilities( + query_client: &QueryHttpRpcNyxdClient, + node_id: NodeId, + account_id: AccountId, + balance_denom: String, +) -> Option { + let on_chain_balance = match query_client + .get_balance(&account_id, balance_denom.clone()) + .await + { + Ok(balance) => balance.map(Into::into).unwrap_or(coin(0, balance_denom)), + Err(err) => { + warn!(node_id, %err, "failed to retrieve node balance"); + return None; + } + }; + + let is_feegrant_grantee = match query_client.allowances(account_id, None).await { + Ok(allowances) => !allowances.allowances.is_empty(), + Err(err) => { + warn!(node_id, %err, "failed to retrieve node feegrant allowances"); + // if there was a network blip, at least preserve the balance information + false + } + }; + + Some(ChainInteractionCapabilitiesDetailed { + on_chain_balance, + is_feegrant_grantee, + }) +} + /// Whether `node` is currently in scope for stress testing, and therefore expected to have a /// stress-test sample. This is the single source of truth for stress-test scope and must stay in /// sync with the orchestrator's test-target selection (`NodeType::from_roles`, which keys off the @@ -506,7 +536,7 @@ fn node_performance( #[cfg(test)] mod tests { use super::*; - use nym_api_requests::models::mock_nym_node_description; + use nym_api_requests::models::described::v3::mock_nym_node_description; #[test] fn ineligible_nodes_are_not_penalised_for_missing_stress_data() { diff --git a/nym-api/src/node_status_api/mod.rs b/nym-api/src/node_status_api/mod.rs index 66d16da149..11cd8457c8 100644 --- a/nym-api/src/node_status_api/mod.rs +++ b/nym-api/src/node_status_api/mod.rs @@ -60,10 +60,10 @@ pub(crate) async fn start_cache_refresh( .node_status_api .debug .node_balance_retrieval_concurrency, - chain_balances_refresh_interval: config + chain_capabilities_refresh_interval: config .node_status_api .debug - .chain_balance_refresh_interval, + .chain_capabilities_refresh_interval, fallback_caching_interval: config.node_status_api.debug.caching_interval, use_stress_testing_data: config.performance_provider.debug.use_stress_testing_data, diff --git a/nym-api/src/support/config/mod.rs b/nym-api/src/support/config/mod.rs index a8dc057857..5e70b7b68c 100644 --- a/nym-api/src/support/config/mod.rs +++ b/nym-api/src/support/config/mod.rs @@ -728,13 +728,13 @@ pub struct NodeStatusAPIDebug { pub node_balance_retrieval_concurrency: usize, #[serde(with = "humantime_serde")] - pub chain_balance_refresh_interval: Duration, + pub chain_capabilities_refresh_interval: Duration, } impl NodeStatusAPIDebug { const DEFAULT_NODE_STATUS_CACHE_REFRESH_INTERVAL: Duration = Duration::from_secs(305); const DEFAULT_NODE_BALANCE_RETRIEVAL_CONCURRENCY: usize = 8; - const DEFAULT_CHAIN_BALANCE_REFRESH_INTERVAL: Duration = Duration::from_secs(24 * 60 * 60); // once a day is more than enough + const DEFAULT_CHAIN_CAPABILITIES_REFRESH_INTERVAL: Duration = Duration::from_secs(24 * 60 * 60); // once a day is more than enough const DEFAULT_CHAIN_BALANCE_REFRESH_THRESHOLD: u128 = 1_000000; // 1 nym is enough for all tx fees for quite some time } @@ -744,7 +744,7 @@ impl Default for NodeStatusAPIDebug { caching_interval: Self::DEFAULT_NODE_STATUS_CACHE_REFRESH_INTERVAL, minimum_on_chain_balance_amount: Self::DEFAULT_CHAIN_BALANCE_REFRESH_THRESHOLD, node_balance_retrieval_concurrency: Self::DEFAULT_NODE_BALANCE_RETRIEVAL_CONCURRENCY, - chain_balance_refresh_interval: Self::DEFAULT_CHAIN_BALANCE_REFRESH_INTERVAL, + chain_capabilities_refresh_interval: Self::DEFAULT_CHAIN_CAPABILITIES_REFRESH_INTERVAL, } } }