c1ddcc75cf
* announce KEM key hashes and use generated value within LpStateMachine * added digest of remote KEM key into LpSession * changed constructor to LpSession to take explicit key materials for local and remote this makes it easier to change keys required by each party without having to change all the interfaces everywhere again * extended the changes to LpStateMachine constructor * modify the interface to LpRegistrationHandler and LpListener * gateway probe fixes * temp nym-lp-client fixes * review nits * remove network test * introduced v2/nym-nodes/described endpoint for returning nodes description alongside LP data * missed V1 -> V2 description replacements * removed deprecated call within mix-fetch * use old v1 call in network stats
167 lines
5.3 KiB
Rust
167 lines
5.3 KiB
Rust
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
use crate::node_describe_cache::query_helpers::query_for_described_data;
|
|
use crate::node_describe_cache::NodeDescribeCacheError;
|
|
use nym_api_requests::models::{DescribedNodeTypeV2, NymNodeDescriptionV2};
|
|
use nym_bin_common::bin_info;
|
|
use nym_config::defaults::DEFAULT_NYM_NODE_HTTP_PORT;
|
|
use nym_crypto::asymmetric::ed25519;
|
|
use nym_mixnet_contract_common::{NodeId, NymNodeDetails};
|
|
use nym_node_requests::api::client::NymNodeApiClientExt;
|
|
use nym_validator_client::UserAgent;
|
|
use std::time::Duration;
|
|
use tracing::debug;
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) struct RefreshData {
|
|
host: String,
|
|
node_id: NodeId,
|
|
expected_identity: ed25519::PublicKey,
|
|
node_type: DescribedNodeTypeV2,
|
|
|
|
port: Option<u16>,
|
|
}
|
|
|
|
impl<'a> TryFrom<&'a NymNodeDetails> for RefreshData {
|
|
type Error = ed25519::Ed25519RecoveryError;
|
|
|
|
fn try_from(node: &'a NymNodeDetails) -> Result<Self, Self::Error> {
|
|
Ok(RefreshData::new(
|
|
&node.bond_information.node.host,
|
|
node.bond_information.identity().parse()?,
|
|
DescribedNodeTypeV2::NymNode,
|
|
node.node_id(),
|
|
node.bond_information.node.custom_http_port,
|
|
))
|
|
}
|
|
}
|
|
|
|
impl RefreshData {
|
|
pub fn new(
|
|
host: impl Into<String>,
|
|
expected_identity: ed25519::PublicKey,
|
|
node_type: DescribedNodeTypeV2,
|
|
node_id: NodeId,
|
|
port: Option<u16>,
|
|
) -> Self {
|
|
RefreshData {
|
|
host: host.into(),
|
|
node_id,
|
|
expected_identity,
|
|
node_type,
|
|
port,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn node_id(&self) -> NodeId {
|
|
self.node_id
|
|
}
|
|
|
|
pub(crate) async fn try_refresh(self, allow_all_ips: bool) -> Option<NymNodeDescriptionV2> {
|
|
match try_get_description(self, allow_all_ips).await {
|
|
Ok(description) => Some(description),
|
|
Err(err) => {
|
|
debug!("failed to obtain node self-described data: {err}");
|
|
None
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn try_get_client(
|
|
host: &str,
|
|
node_id: NodeId,
|
|
custom_port: Option<u16>,
|
|
) -> Result<nym_node_requests::api::Client, NodeDescribeCacheError> {
|
|
// first try the standard port in case the operator didn't put the node behind the proxy,
|
|
// then default https (443)
|
|
// finally default http (80)
|
|
let mut addresses_to_try = vec![
|
|
format!("http://{host}:{DEFAULT_NYM_NODE_HTTP_PORT}"), // 'standard' nym-node
|
|
format!("https://{host}"), // node behind https proxy (443)
|
|
format!("http://{host}"), // node behind http proxy (80)
|
|
];
|
|
|
|
// note: I removed 'standard' legacy mixnode port because it should now be automatically pulled via
|
|
// the 'custom_port' since it should have been present in the contract.
|
|
|
|
if let Some(port) = custom_port {
|
|
addresses_to_try.insert(0, format!("http://{host}:{port}"));
|
|
}
|
|
|
|
for address in addresses_to_try {
|
|
// if provided host was malformed, no point in continuing
|
|
let client = match nym_node_requests::api::Client::builder(address).and_then(|b| {
|
|
b.with_timeout(Duration::from_secs(5))
|
|
.no_hickory_dns()
|
|
.with_user_agent(UserAgent::from(bin_info!()))
|
|
.build()
|
|
}) {
|
|
Ok(client) => client,
|
|
Err(err) => {
|
|
return Err(NodeDescribeCacheError::MalformedHost {
|
|
host: host.to_string(),
|
|
node_id,
|
|
source: Box::new(err),
|
|
});
|
|
}
|
|
};
|
|
|
|
if let Ok(health) = client.get_health().await {
|
|
if health.status.is_up() {
|
|
return Ok(client);
|
|
}
|
|
}
|
|
}
|
|
|
|
Err(NodeDescribeCacheError::NoHttpPortsAvailable {
|
|
host: host.to_string(),
|
|
node_id,
|
|
})
|
|
}
|
|
|
|
async fn try_get_description(
|
|
data: RefreshData,
|
|
allow_all_ips: bool,
|
|
) -> Result<NymNodeDescriptionV2, NodeDescribeCacheError> {
|
|
let client = try_get_client(&data.host, data.node_id, data.port).await?;
|
|
|
|
let map_query_err = |err| NodeDescribeCacheError::ApiFailure {
|
|
node_id: data.node_id,
|
|
source: Box::new(err),
|
|
};
|
|
|
|
let host_info = client.get_host_information().await.map_err(map_query_err)?;
|
|
|
|
// check if the identity key matches the information provided during bonding
|
|
if data.expected_identity != host_info.keys.ed25519_identity {
|
|
return Err(NodeDescribeCacheError::MismatchedIdentity {
|
|
node_id: data.node_id,
|
|
expected: data.expected_identity.to_base58_string(),
|
|
got: host_info.keys.ed25519_identity.to_base58_string(),
|
|
});
|
|
}
|
|
|
|
if !host_info.verify_host_information() {
|
|
return Err(NodeDescribeCacheError::MissignedHostInformation {
|
|
node_id: data.node_id,
|
|
});
|
|
}
|
|
|
|
if !allow_all_ips && !host_info.data.check_ips() {
|
|
return Err(NodeDescribeCacheError::IllegalIpAddress {
|
|
node_id: data.node_id,
|
|
});
|
|
}
|
|
|
|
let node_info = query_for_described_data(&client, data.node_id).await?;
|
|
let description = node_info.into_node_description(host_info.data);
|
|
|
|
Ok(NymNodeDescriptionV2 {
|
|
node_id: data.node_id,
|
|
contract_node_type: data.node_type,
|
|
description,
|
|
})
|
|
}
|