Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/force refresh node #5101

Merged
merged 5 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions common/client-libs/validator-client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ use nym_api_requests::models::{LegacyDescribedGateway, MixNodeBondAnnotated};
use nym_api_requests::nym_nodes::SkimmedNode;
use nym_coconut_dkg_common::types::EpochId;
use nym_http_api_client::UserAgent;
use nym_mixnet_contract_common::NymNodeDetails;
use nym_network_defaults::NymNetworkDetails;
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,
};
Expand Down Expand Up @@ -330,10 +330,10 @@ impl NymApiClient {
NymApiClient { nym_api }
}

pub fn new_with_user_agent(api_url: Url, user_agent: UserAgent) -> Self {
pub fn new_with_user_agent(api_url: Url, user_agent: impl Into<UserAgent>) -> Self {
let nym_api = nym_api::Client::builder::<_, ValidatorClientError>(api_url)
.expect("invalid api url")
.with_user_agent(user_agent)
.with_user_agent(user_agent.into())
.build::<ValidatorClientError>()
.expect("failed to build nym api client");

Expand Down
14 changes: 13 additions & 1 deletion common/client-libs/validator-client/src/nym_api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use nym_api_requests::ecash::models::{
use nym_api_requests::ecash::VerificationKeyResponse;
use nym_api_requests::models::{
AnnotationResponse, ApiHealthResponse, LegacyDescribedMixNode, NodePerformanceResponse,
NymNodeDescription,
NodeRefreshBody, NymNodeDescription,
};
use nym_api_requests::nym_nodes::PaginatedCachedNodesResponse;
use nym_api_requests::pagination::PaginatedResponse;
Expand Down Expand Up @@ -934,6 +934,18 @@ pub trait NymApiClientExt: ApiClient {
.await
}

async fn force_refresh_describe_cache(
&self,
request: &NodeRefreshBody,
) -> Result<(), NymAPIError> {
self.post_json(
&[routes::API_VERSION, "nym-nodes", "refresh-described"],
NO_PARAMS,
request,
)
.await
}

#[instrument(level = "debug", skip(self))]
async fn epoch_credentials(
&self,
Expand Down
62 changes: 62 additions & 0 deletions nym-api/nym-api-requests/src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

use crate::helpers::unix_epoch;
use crate::helpers::PlaceholderJsonSchemaImpl;
use crate::legacy::{
LegacyGatewayBondWithId, LegacyMixNodeBondWithLayer, LegacyMixNodeDetailsWithLayer,
};
Expand Down Expand Up @@ -1143,6 +1144,67 @@ pub struct NoiseDetails {
pub ip_addresses: Vec<IpAddr>,
}

#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
pub struct NodeRefreshBody {
#[serde(with = "bs58_ed25519_pubkey")]
#[schemars(with = "String")]
pub node_identity: ed25519::PublicKey,

// a poor man's nonce
pub request_timestamp: i64,

#[schemars(with = "PlaceholderJsonSchemaImpl")]
pub signature: ed25519::Signature,
}

impl NodeRefreshBody {
pub fn plaintext(node_identity: ed25519::PublicKey, request_timestamp: i64) -> Vec<u8> {
node_identity
.to_bytes()
.into_iter()
.chain(request_timestamp.to_be_bytes())
.chain(b"describe-cache-refresh-request".iter().copied())
.collect()
}

pub fn new(private_key: &ed25519::PrivateKey) -> Self {
let node_identity = private_key.public_key();
let request_timestamp = OffsetDateTime::now_utc().unix_timestamp();
let signature = private_key.sign(Self::plaintext(node_identity, request_timestamp));
NodeRefreshBody {
node_identity,
request_timestamp,
signature,
}
}

pub fn verify_signature(&self) -> bool {
self.node_identity
.verify(
Self::plaintext(self.node_identity, self.request_timestamp),
&self.signature,
)
.is_ok()
}

pub fn is_stale(&self) -> bool {
let Ok(encoded) = OffsetDateTime::from_unix_timestamp(self.request_timestamp) else {
return true;
};
let now = OffsetDateTime::now_utc();

if encoded > now {
return true;
}

if (encoded + Duration::from_secs(30)) < now {
return true;
}

false
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
1 change: 1 addition & 0 deletions nym-api/src/ecash/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1261,6 +1261,7 @@ struct TestFixture {
impl TestFixture {
fn build_app_state(storage: NymApiStorage) -> AppState {
AppState {
forced_refresh: Default::default(),
nym_contract_cache: NymContractCache::new(),
node_status_cache: NodeStatusCache::new(),
circulating_supply_cache: CirculatingSupplyCache::new("unym".to_owned()),
Expand Down
71 changes: 49 additions & 22 deletions nym-api/src/node_describe_cache/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ use crate::support::config;
use crate::support::config::DEFAULT_NODE_DESCRIBE_BATCH_SIZE;
use async_trait::async_trait;
use futures::{stream, StreamExt};
use nym_api_requests::legacy::{LegacyGatewayBondWithId, LegacyMixNodeDetailsWithLayer};
use nym_api_requests::models::{DescribedNodeType, NymNodeData, NymNodeDescription};
use nym_config::defaults::DEFAULT_NYM_NODE_HTTP_PORT;
use nym_mixnet_contract_common::{LegacyMixLayer, NodeId};
use nym_mixnet_contract_common::{LegacyMixLayer, NodeId, NymNodeDetails};
use nym_node_requests::api::client::{NymNodeApiClientError, NymNodeApiClientExt};
use nym_topology::gateway::GatewayConversionError;
use nym_topology::mix::MixnodeConversionError;
Expand Down Expand Up @@ -151,6 +152,10 @@ pub struct DescribedNodes {
}

impl DescribedNodes {
pub fn force_update(&mut self, node: NymNodeDescription) {
self.nodes.insert(node.node_id, node);
}

pub fn get_description(&self, node_id: &NodeId) -> Option<&NymNodeData> {
self.nodes.get(node_id).map(|n| &n.description)
}
Expand Down Expand Up @@ -292,14 +297,47 @@ async fn try_get_description(
}

#[derive(Debug)]
struct RefreshData {
pub(crate) struct RefreshData {
host: String,
node_id: NodeId,
node_type: DescribedNodeType,

port: Option<u16>,
}

impl<'a> From<&'a LegacyMixNodeDetailsWithLayer> for RefreshData {
fn from(node: &'a LegacyMixNodeDetailsWithLayer) -> Self {
RefreshData::new(
&node.bond_information.mix_node.host,
DescribedNodeType::LegacyMixnode,
node.mix_id(),
Some(node.bond_information.mix_node.http_api_port),
)
}
}

impl<'a> From<&'a LegacyGatewayBondWithId> for RefreshData {
fn from(node: &'a LegacyGatewayBondWithId) -> Self {
RefreshData::new(
&node.bond.gateway.host,
DescribedNodeType::LegacyGateway,
node.node_id,
None,
)
}
}

impl<'a> From<&'a NymNodeDetails> for RefreshData {
fn from(node: &'a NymNodeDetails) -> Self {
RefreshData::new(
&node.bond_information.node.host,
DescribedNodeType::NymNode,
node.node_id(),
node.bond_information.node.custom_http_port,
)
}
}

impl RefreshData {
pub fn new(
host: impl Into<String>,
Expand All @@ -315,7 +353,11 @@ impl RefreshData {
}
}

async fn try_refresh(self) -> Option<NymNodeDescription> {
pub(crate) fn node_id(&self) -> NodeId {
self.node_id
}

pub(crate) async fn try_refresh(self) -> Option<NymNodeDescription> {
match try_get_description(self).await {
Ok(description) => Some(description),
Err(err) => {
Expand All @@ -341,18 +383,13 @@ impl CacheItemProvider for NodeDescriptionProvider {
// - legacy gateways (because they might already be running nym-nodes, but haven't updated contract info)
// - nym-nodes

let mut nodes_to_query = Vec::new();
let mut nodes_to_query: Vec<RefreshData> = Vec::new();

match self.contract_cache.all_cached_legacy_mixnodes().await {
None => error!("failed to obtain mixnodes information from the cache"),
Some(legacy_mixnodes) => {
for node in &**legacy_mixnodes {
nodes_to_query.push(RefreshData::new(
&node.bond_information.mix_node.host,
DescribedNodeType::LegacyMixnode,
node.mix_id(),
Some(node.bond_information.mix_node.http_api_port),
))
nodes_to_query.push(node.into())
}
}
}
Expand All @@ -361,12 +398,7 @@ impl CacheItemProvider for NodeDescriptionProvider {
None => error!("failed to obtain gateways information from the cache"),
Some(legacy_gateways) => {
for node in &**legacy_gateways {
nodes_to_query.push(RefreshData::new(
&node.bond.gateway.host,
DescribedNodeType::LegacyGateway,
node.node_id,
None,
))
nodes_to_query.push(node.into())
}
}
}
Expand All @@ -375,12 +407,7 @@ impl CacheItemProvider for NodeDescriptionProvider {
None => error!("failed to obtain nym-nodes information from the cache"),
Some(nym_nodes) => {
for node in &**nym_nodes {
nodes_to_query.push(RefreshData::new(
&node.bond_information.node.host,
DescribedNodeType::NymNode,
node.node_id(),
node.bond_information.node.custom_http_port,
))
nodes_to_query.push(node.into())
}
}
}
Expand Down
14 changes: 14 additions & 0 deletions nym-api/src/node_status_api/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,13 @@ impl AxumErrorResponse {
}
}

pub(crate) fn unauthorised(msg: impl Display) -> Self {
Self {
message: RequestError::new(msg.to_string()),
status: StatusCode::UNAUTHORIZED,
}
}

pub(crate) fn unprocessable_entity(msg: impl Display) -> Self {
Self {
message: RequestError::new(msg.to_string()),
Expand All @@ -375,6 +382,13 @@ impl AxumErrorResponse {
status: StatusCode::BAD_REQUEST,
}
}

pub(crate) fn too_many(msg: impl Display) -> Self {
Self {
message: RequestError::new(msg.to_string()),
status: StatusCode::TOO_MANY_REQUESTS,
}
}
}

impl From<UninitialisedCache> for AxumErrorResponse {
Expand Down
44 changes: 44 additions & 0 deletions nym-api/src/nym_contract_cache/cache/mod.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
// Copyright 2023 - Nym Technologies SA <[email protected]>
// SPDX-License-Identifier: GPL-3.0-only

use crate::node_describe_cache::RefreshData;
use crate::nym_contract_cache::cache::data::CachedContractsInfo;
use crate::support::caching::Cache;
use data::ValidatorCacheData;
use nym_api_requests::legacy::{
LegacyGatewayBondWithId, LegacyMixNodeBondWithLayer, LegacyMixNodeDetailsWithLayer,
};
use nym_api_requests::models::MixnodeStatus;
use nym_crypto::asymmetric::ed25519;
use nym_mixnet_contract_common::{Interval, NodeId, NymNodeDetails, RewardedSet, RewardingParams};
use std::{
collections::HashSet,
Expand Down Expand Up @@ -352,6 +354,48 @@ impl NymContractCache {
self.legacy_mixnode_details(mix_id).await.1
}

pub async fn get_node_refresh_data(
&self,
node_identity: ed25519::PublicKey,
) -> Option<RefreshData> {
if !self.initialised() {
return None;
}

let inner = self.inner.read().await;

let encoded_identity = node_identity.to_base58_string();

// 1. check nymnodes
if let Some(nym_node) = inner
.nym_nodes
.iter()
.find(|n| n.bond_information.identity() == encoded_identity)
{
return Some(nym_node.into());
}

// 2. check legacy mixnodes
if let Some(mixnode) = inner
.legacy_mixnodes
.iter()
.find(|n| n.bond_information.identity() == encoded_identity)
{
return Some(mixnode.into());
}

// 3. check legacy gateways
if let Some(gateway) = inner
.legacy_gateways
.iter()
.find(|n| n.identity() == &encoded_identity)
{
return Some(gateway.into());
}

None
}

pub fn initialised(&self) -> bool {
self.initialised.load(Ordering::Relaxed)
}
Expand Down
Loading
Loading