add query to check for feegrant inclusion
This commit is contained in:
@@ -228,117 +228,3 @@ impl From<NymNodeDescriptionV1> 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,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,3 +279,118 @@ impl From<NymNodeAuxiliaryDetailsV3> 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,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<DisplayRole>,
|
||||
|
||||
#[schema(value_type = Option<CoinSchema>)]
|
||||
pub on_chain_balance: Option<Coin>,
|
||||
pub chain_interaction_capabilities: Option<ChainInteractionCapabilitiesDetailed>,
|
||||
|
||||
pub detailed_performance: DetailedNodePerformanceV2,
|
||||
}
|
||||
|
||||
+16
-11
@@ -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<Coin>) -> bool {
|
||||
let Some(chain_balance) = chain_balance else {
|
||||
fn has_sufficient_tokens(
|
||||
minimum_balance: &Coin,
|
||||
capabilities: &Option<ChainInteractionCapabilitiesDetailed>,
|
||||
) -> 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<Coin>,
|
||||
chain_capabilities: &Option<ChainInteractionCapabilitiesDetailed>,
|
||||
) -> 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(
|
||||
|
||||
+7
-4
@@ -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<HashMap<NodeId, Option<cosmwasm_std::Coin>>, NodeStatusCacheError> {
|
||||
) -> Result<HashMap<NodeId, Option<ChainInteractionCapabilitiesDetailed>>, 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::<HashMap<_, _>>())
|
||||
}
|
||||
}
|
||||
|
||||
+63
-33
@@ -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<HashMap<NodeId, Option<Coin>>, NodeStatusCacheError> {
|
||||
) -> Result<HashMap<NodeId, Option<ChainInteractionCapabilitiesDetailed>>, 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::<NodeId, Option<Coin>>::new());
|
||||
let capabilities = std::sync::Mutex::new(HashMap::<
|
||||
NodeId,
|
||||
Option<ChainInteractionCapabilitiesDetailed>,
|
||||
>::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<NodeId, Option<Coin>>,
|
||||
chain_capabilities: HashMap<NodeId, Option<ChainInteractionCapabilitiesDetailed>>,
|
||||
) -> HashMap<NodeId, NodeAnnotationV2> {
|
||||
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<ChainInteractionCapabilitiesDetailed> {
|
||||
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() {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user