Feature Noise XKpsk3 : Fix localnet (#4465)

* add custom mixnet option for gateways

* add custom mixnet option for mixnode

* change topology construction as to not miss described_nodes

* adapt localnet scripts to new topology

* add snake_case alias to described_nodes
This commit is contained in:
Simon Wicky
2024-03-19 15:49:29 +01:00
committed by GitHub
parent 5a9c391ba2
commit 4b3dcb6eca
12 changed files with 275 additions and 35 deletions
@@ -294,6 +294,14 @@ impl GeoAwareTopologyProvider {
Ok(gateways) => gateways,
};
let nodes_described = match self.validator_client.get_cached_described_nodes().await {
Err(err) => {
error!("failed to get described nodes - {err}");
return None;
}
Ok(epoch) => epoch,
};
// Also fetch mixnodes cached by explorer-api, with the purpose of getting their
// geolocation.
debug!("Fetching mixnodes from explorer-api...");
@@ -353,7 +361,7 @@ impl GeoAwareTopologyProvider {
.filter(|m| filtered_mixnode_ids.contains(&m.mix_id()))
.collect::<Vec<_>>();
let topology = nym_topology_from_detailed(mixnodes, gateways)
let topology = nym_topology_from_detailed(mixnodes, gateways, nodes_described)
.filter_system_version(&self.client_version);
// TODO: return real error type
@@ -85,8 +85,7 @@ impl NymApiTopologyProvider {
Ok(epoch) => epoch,
};
let topology = nym_topology_from_detailed(mixnodes, gateways)
.with_described_nodes(nodes_described.clone())
let topology = nym_topology_from_detailed(mixnodes, gateways, nodes_described.clone())
.filter_system_version(&self.client_version);
if let Err(err) = self.check_layer_distribution(&topology) {
+18 -8
View File
@@ -119,11 +119,15 @@ pub struct NymTopology {
}
impl NymTopology {
pub fn new(mixes: BTreeMap<MixLayer, Vec<mix::Node>>, gateways: Vec<gateway::Node>) -> Self {
pub fn new(
mixes: BTreeMap<MixLayer, Vec<mix::Node>>,
gateways: Vec<gateway::Node>,
described_nodes: Vec<DescribedNymNode>,
) -> Self {
NymTopology {
mixes: mixes.clone(),
gateways: gateways.clone(),
described_nodes: Vec::new(),
described_nodes: described_nodes.clone(),
}
}
@@ -140,7 +144,11 @@ impl NymTopology {
self
}
pub fn new_unordered(unordered_mixes: Vec<mix::Node>, gateways: Vec<gateway::Node>) -> Self {
pub fn new_unordered(
unordered_mixes: Vec<mix::Node>,
gateways: Vec<gateway::Node>,
described_nodes: Vec<DescribedNymNode>,
) -> Self {
let mut mixes = BTreeMap::new();
for node in unordered_mixes.into_iter() {
let layer = node.layer as MixLayer;
@@ -148,7 +156,7 @@ impl NymTopology {
layer_entry.push(node)
}
NymTopology::new(mixes, gateways)
NymTopology::new(mixes, gateways, described_nodes)
}
#[cfg(feature = "serializable")]
@@ -160,8 +168,9 @@ impl NymTopology {
pub fn from_detailed(
mix_details: Vec<MixNodeDetails>,
gateway_bonds: Vec<GatewayBond>,
described_nodes: Vec<DescribedNymNode>,
) -> Self {
nym_topology_from_detailed(mix_details, gateway_bonds)
nym_topology_from_detailed(mix_details, gateway_bonds, described_nodes)
}
pub fn find_mix(&self, mix_id: MixId) -> Option<&mix::Node> {
@@ -492,6 +501,7 @@ impl IntoGatewayNode for DescribedGateway {
pub fn nym_topology_from_detailed<G>(
mix_details: Vec<MixNodeDetails>,
gateway_bonds: Vec<G>,
described_nodes: Vec<DescribedNymNode>,
) -> NymTopology
where
G: IntoGatewayNode,
@@ -535,7 +545,7 @@ where
}
}
NymTopology::new(mixes, gateways)
NymTopology::new(mixes, gateways, described_nodes)
}
#[cfg(test)]
@@ -582,7 +592,7 @@ mod converting_mixes_to_vec {
mixes.insert(1, vec![node1, node2]);
mixes.insert(2, vec![node3]);
let topology = NymTopology::new(mixes, vec![]);
let topology = NymTopology::new(mixes, vec![], vec![]);
let mixvec = topology.mixes_as_vec();
assert!(mixvec.iter().any(|node| node.owner == "N/A"));
}
@@ -594,7 +604,7 @@ mod converting_mixes_to_vec {
#[test]
fn returns_an_empty_vec() {
let topology = NymTopology::new(BTreeMap::new(), vec![]);
let topology = NymTopology::new(BTreeMap::new(), vec![], vec![]);
let mixvec = topology.mixes_as_vec();
assert!(mixvec.is_empty());
}
+14 -2
View File
@@ -4,6 +4,7 @@
use crate::gateway::GatewayConversionError;
use crate::mix::MixnodeConversionError;
use crate::{gateway, mix, MixLayer, NymTopology};
use nym_api_requests::models::DescribedNymNode;
use nym_config::defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT};
use nym_crypto::asymmetric::{encryption, identity};
use serde::{Deserialize, Serialize};
@@ -53,6 +54,12 @@ impl From<SerializableTopologyError> for JsValue {
pub struct SerializableNymTopology {
pub mixnodes: BTreeMap<MixLayer, Vec<SerializableMixNode>>,
pub gateways: Vec<SerializableGateway>,
//SW NOTE : make this an option to keep backwards compatibility. Noise with fallback needs that to work though
// Once fallback is removed, we only need a list of unfiltered nodes that can be constructed from mixnodes and gateways
// depending on the usecase of this struct
#[serde(alias = "described_nodes")]
pub described_nodes: Option<Vec<DescribedNymNode>>, //DescribedNymNode is already Serialize and Deserialize
}
impl TryFrom<SerializableNymTopology> for NymTopology {
@@ -76,7 +83,11 @@ impl TryFrom<SerializableNymTopology> for NymTopology {
.map(TryInto::try_into)
.collect::<Result<_, _>>()?;
Ok(NymTopology::new(converted_mixes, gateways))
Ok(NymTopology::new(
converted_mixes,
gateways,
value.described_nodes.unwrap_or_default(),
))
}
}
@@ -89,6 +100,7 @@ impl From<NymTopology> for SerializableNymTopology {
.map(|(&l, nodes)| (l, nodes.iter().map(Into::into).collect()))
.collect(),
gateways: value.gateways().iter().map(Into::into).collect(),
described_nodes: Some(value.described_nodes),
}
}
}
@@ -181,7 +193,7 @@ pub struct SerializableGateway {
// optional ip address in the case of host being a hostname that can't be resolved
// (thank you wasm)
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
#[serde(alias = "explicit_ip")]
#[serde(alias = "explicit_ips")]
pub explicit_ips: Option<Vec<IpAddr>>,
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
+11 -7
View File
@@ -58,6 +58,10 @@ pub struct Run {
// the alias here is included for backwards compatibility (1.1.4 and before)
nym_apis: Option<Vec<url::Url>>,
/// Path to .json file containing custom network specification.
#[arg(long, group = "network", hide = true)]
custom_mixnet: Option<PathBuf>,
/// Comma separated list of endpoints of the validator
#[arg(
long,
@@ -127,11 +131,6 @@ pub struct Run {
)]
medium_toggle: bool,
/// Path to .json file containing custom network specification.
/// Only usable when local network requester is enabled.
#[arg(long, group = "network", hide = true)]
custom_mixnet: Option<PathBuf>,
/// Specifies whether this network requester will run using the default ExitPolicy
/// as opposed to the allow list.
/// Note: this setting will become the default in the future releases.
@@ -249,12 +248,17 @@ pub async fn execute(args: Run) -> anyhow::Result<()> {
}
let node_details = node_details(&config)?;
let gateway =
crate::node::create_gateway(config, Some(nr_opts), Some(ip_opts), custom_mixnet).await?;
let mut gateway =
crate::node::create_gateway(config, Some(nr_opts), Some(ip_opts), custom_mixnet.clone())
.await?;
eprintln!(
"\nTo bond your gateway you will need to install the Nym wallet, go to https://nymtech.net/get-involved and select the Download button.\n\
Select the correct version and install it to your machine. You will need to provide some of the following: \n ");
output.to_stdout(&node_details);
if let Some(custom_mixnet) = custom_mixnet {
gateway = gateway.with_stored_topology(custom_mixnet)?;
}
gateway.run().await
}
+9
View File
@@ -39,6 +39,15 @@ pub(crate) enum GatewayError {
source: io::Error,
},
#[error(
"failed to load custom topology using path '{}'. detailed message: {source}", file_path.display()
)]
CustomTopologyLoadFailure {
file_path: PathBuf,
#[source]
source: std::io::Error,
},
#[error(
"failed to load config file for network requester (gateway-id: '{id}') using path '{}'. detailed message: {source}", path.display()
)]
+28 -6
View File
@@ -32,6 +32,7 @@ use nym_node::wireguard::types::GatewayClientRegistry;
use nym_statistics_common::collector::StatisticsSender;
use nym_task::{TaskClient, TaskManager};
use nym_topology::provider_trait::TopologyProvider;
use nym_topology::HardcodedTopologyProvider;
use nym_topology_control::accessor::TopologyAccessor;
use nym_topology_control::nym_api_provider::NymApiTopologyProvider;
use nym_topology_control::TopologyRefresher;
@@ -42,7 +43,7 @@ use rand::seq::SliceRandom;
use rand::thread_rng;
use std::error::Error;
use std::net::SocketAddr;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use url::Url;
@@ -138,6 +139,7 @@ pub(crate) struct Gateway<St = PersistentStorage> {
storage: St,
client_registry: Arc<GatewayClientRegistry>,
custom_topology_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
}
impl<St> Gateway<St> {
@@ -156,6 +158,7 @@ impl<St> Gateway<St> {
network_requester_opts,
ip_packet_router_opts,
client_registry: Arc::new(DashMap::new()),
custom_topology_provider: None,
})
}
@@ -176,9 +179,22 @@ impl<St> Gateway<St> {
sphinx_keypair: Arc::new(sphinx_keypair),
storage,
client_registry: Arc::new(DashMap::new()),
custom_topology_provider: None,
}
}
pub fn with_stored_topology<P: AsRef<Path>>(mut self, file: P) -> Result<Self, GatewayError> {
self.custom_topology_provider = Some(Box::new(
HardcodedTopologyProvider::new_from_file(&file).map_err(|source| {
GatewayError::CustomTopologyLoadFailure {
file_path: file.as_ref().to_path_buf(),
source,
}
})?,
));
Ok(self)
}
fn start_mix_socket_listener(
&self,
ack_sender: MixForwardingSender,
@@ -448,12 +464,15 @@ impl<St> Gateway<St> {
.map_err(Into::into)
}
fn setup_topology_provider(nym_api_urls: Vec<Url>) -> Box<dyn TopologyProvider + Send + Sync> {
fn setup_topology_provider(
custom_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
nym_api_urls: Vec<Url>,
) -> Box<dyn TopologyProvider + Send + Sync> {
// if no custom provider was ... provided ..., create one using nym-api
Box::new(NymApiTopologyProvider::new(
custom_provider.unwrap_or(Box::new(NymApiTopologyProvider::new(
nym_api_urls,
env!("CARGO_PKG_VERSION").to_string(),
))
)))
}
// future responsible for periodically polling directory server and updating
@@ -506,7 +525,7 @@ impl<St> Gateway<St> {
}))
}
pub async fn run(self) -> anyhow::Result<()>
pub async fn run(mut self) -> anyhow::Result<()>
where
St: Storage + Clone + 'static,
{
@@ -523,7 +542,10 @@ impl<St> Gateway<St> {
CoconutVerifier::new(nyxd_client).await
}?;
let topology_provider = Self::setup_topology_provider(self.config.get_nym_api_endpoints());
let topology_provider = Self::setup_topology_provider(
self.custom_topology_provider.take(),
self.config.get_nym_api_endpoints(),
);
let shared_topology_access = TopologyAccessor::new();
+9
View File
@@ -11,6 +11,7 @@ use nym_bin_common::output_format::OutputFormat;
use nym_config::helpers::SPECIAL_ADDRESSES;
use nym_validator_client::nyxd;
use std::net::IpAddr;
use std::path::PathBuf;
#[derive(Args, Clone)]
pub(crate) struct Run {
/// Id of the nym-mixnode we want to run
@@ -42,6 +43,10 @@ pub(crate) struct Run {
#[clap(long, alias = "validators", value_delimiter = ',')]
nym_apis: Option<Vec<url::Url>>,
/// Path to .json file containing custom network specification.
#[arg(long, group = "network", hide = true)]
pub custom_mixnet: Option<PathBuf>,
#[clap(short, long, default_value_t = OutputFormat::default())]
output: OutputFormat,
}
@@ -91,6 +96,10 @@ pub(crate) async fn execute(args: &Run) -> anyhow::Result<()> {
Select the correct version and install it to your machine. You will need to provide the following: \n ");
mixnode.print_node_details(args.output);
if let Some(custom_mixnet) = &args.custom_mixnet {
mixnode = mixnode.with_stored_topology(custom_mixnet)?;
}
mixnode.run().await?;
Ok(())
}
+9
View File
@@ -34,6 +34,15 @@ pub enum MixnodeError {
source: io::Error,
},
#[error(
"failed to load custom topology using path '{}'. detailed message: {source}", file_path.display()
)]
CustomTopologyLoadFailure {
file_path: PathBuf,
#[source]
source: std::io::Error,
},
#[error(
"failed to save config file for id {id} using path '{}'. detailed message: {source}", path.display()
)]
+26 -4
View File
@@ -19,6 +19,7 @@ use nym_crypto::asymmetric::{encryption, identity};
use nym_mixnode_common::verloc::{self, AtomicVerlocResult, VerlocMeasurer};
use nym_task::{TaskClient, TaskManager};
use nym_topology::provider_trait::TopologyProvider;
use nym_topology::HardcodedTopologyProvider;
use nym_topology_control::accessor::TopologyAccessor;
use nym_topology_control::nym_api_provider::NymApiTopologyProvider;
use nym_topology_control::TopologyRefresher;
@@ -27,6 +28,7 @@ use nym_validator_client::NymApiClient;
use rand::seq::SliceRandom;
use rand::thread_rng;
use std::net::SocketAddr;
use std::path::Path;
use std::process;
use std::sync::Arc;
use url::Url;
@@ -44,6 +46,7 @@ pub struct MixNode {
descriptor: NodeDescription,
identity_keypair: Arc<identity::KeyPair>,
sphinx_keypair: Arc<encryption::KeyPair>,
custom_topology_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
}
impl MixNode {
@@ -53,6 +56,7 @@ impl MixNode {
identity_keypair: Arc::new(load_identity_keys(&config)?),
sphinx_keypair: Arc::new(load_sphinx_keys(&config)?),
config,
custom_topology_provider: None,
})
}
@@ -75,6 +79,18 @@ impl MixNode {
println!("{}", output.format(&node_details));
}
pub fn with_stored_topology<P: AsRef<Path>>(mut self, file: P) -> Result<Self, MixnodeError> {
self.custom_topology_provider = Some(Box::new(
HardcodedTopologyProvider::new_from_file(&file).map_err(|source| {
MixnodeError::CustomTopologyLoadFailure {
file_path: file.as_ref().to_path_buf(),
source,
}
})?,
));
Ok(self)
}
fn start_http_api(
&self,
atomic_verloc_result: AtomicVerlocResult,
@@ -207,12 +223,15 @@ impl MixNode {
atomic_verloc_results
}
fn setup_topology_provider(nym_api_urls: Vec<Url>) -> Box<dyn TopologyProvider + Send + Sync> {
fn setup_topology_provider(
custom_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
nym_api_urls: Vec<Url>,
) -> Box<dyn TopologyProvider + Send + Sync> {
// if no custom provider was ... provided ..., create one using nym-api
Box::new(NymApiTopologyProvider::new(
custom_provider.unwrap_or(Box::new(NymApiTopologyProvider::new(
nym_api_urls,
env!("CARGO_PKG_VERSION").to_string(),
))
)))
}
// future responsible for periodically polling directory server and updating
@@ -295,7 +314,10 @@ impl MixNode {
let (node_stats_pointer, node_stats_update_sender) = self
.start_node_stats_controller(shutdown.subscribe().named("node_statistics::Controller"));
let topology_provider = Self::setup_topology_provider(self.config.get_nym_api_endpoints());
let topology_provider = Self::setup_topology_provider(
self.custom_topology_provider.take(),
self.config.get_nym_api_endpoints(),
);
let shared_topology_access = TopologyAccessor::new();
Self::start_topology_refresher(
topology_provider,
+137 -1
View File
@@ -14,6 +14,18 @@ def add_mixnode(base_network, base_dir, mix_id):
base_network["mixnodes"][str(mix_id)][0]["layer"] = mix_id
base_network["mixnodes"][str(mix_id)][0]["mix_id"] = mix_id
base_network["mixnodes"][str(mix_id)][0]["owner"] = "whatever"
#described_node
template = mixnode_template()
template["Mixnode"]["bond"]["mix_node"]["identity_key"] = mix_data["identity_key"]
template["Mixnode"]["bond"]["mix_node"]["sphinx_key"] = mix_data["sphinx_key"]
template["Mixnode"]["bond"]["mix_node"]["mix_port"] = mix_data["mix_port"]
template["Mixnode"]["bond"]["mix_node"]["host"] = mix_data["bind_address"]
template["Mixnode"]["bond"]["layer"] = mix_id
template["Mixnode"]["bond"]["mix_id"] = mix_id
template["Mixnode"]["self_described"]["host_information"]["keys"]["ed25519"] = mix_data["identity_key"]
template["Mixnode"]["self_described"]["host_information"]["keys"]["x25519"] = mix_data["sphinx_key"]
base_network["described_nodes"][mix_id] = template
return base_network
@@ -27,6 +39,17 @@ def add_gateway(base_network, base_dir):
# base_network["gateways"][0]["version"] = gateway_data["version"]
base_network["gateways"][0]["host"] = gateway_data["bind_address"]
base_network["gateways"][0]["owner"] = "whatever"
#described_node
template = gateway_template()
template["Gateway"]["bond"]["gateway"]["identity_key"] = gateway_data["identity_key"]
template["Gateway"]["bond"]["gateway"]["sphinx_key"] = gateway_data["sphinx_key"]
template["Gateway"]["bond"]["gateway"]["mix_port"] = gateway_data["mix_port"]
template["Gateway"]["bond"]["gateway"]["clients_port"] = gateway_data["clients_port"]
template["Gateway"]["bond"]["gateway"]["host"] = gateway_data["bind_address"]
template["Gateway"]["self_described"]["host_information"]["keys"]["ed25519"] = gateway_data["identity_key"]
template["Gateway"]["self_described"]["host_information"]["keys"]["x25519"] = gateway_data["sphinx_key"]
base_network["described_nodes"][0] = template
return base_network
@@ -37,7 +60,8 @@ def main(args):
"2": [{}],
"3": [{}],
},
"gateways": [{}]
"gateways": [{}],
"described_nodes":[{}, {}, {}, {}]
}
base_dir = args[0]
@@ -50,5 +74,117 @@ def main(args):
json.dump(base_network, out, indent=2)
def gateway_template():
return {"Gateway": {
"bond": {
"pledge_amount": {
"denom": "unym",
"amount": "0"
},
"owner": "whatever",
"block_height": 0,
"gateway": {
"host": "TO_BE_FILLED",
"mix_port": "TO_BE_FILLED",
"clients_port": "TO_BE_FILLED",
"location": "whatever",
"sphinx_key": "TO_BE_FILLED",
"identity_key": "TO_BE_FILLED",
"version": "whatever",
},
"proxy": None,
},
"self_described": {
"host_information": {
"ip_address": [
"0.0.0.0"
],
"hostname": None,
"keys": {
"ed25519": "TO_BE_FILLED",
"x25519": "TO_BE_FILLED"
}
},
"build_information": {
"binary_name": "whatever",
"build_timestamp": "whatever",
"build_version": "whatever",
"commit_sha": "whatever",
"commit_timestamp": "whatever",
"commit_branch": "whatever",
"rustc_version": "whatever",
"rustc_channel": "whatever",
"cargo_profile": "whatever"
},
"network_requester": {
"address": "none",
"uses_exit_policy": True
},
"mixnet_websockets": {
"ws_port": 9000,
"wss_port": None
},
"noise_information": {
"supported": True
}
}
}}
def mixnode_template():
return {
"Mixnode": {
"bond": {
"mix_id": "TO_BE_FILLED",
"owner": "whatever",
"original_pledge": {
"denom": "unym",
"amount": "0"
},
"layer": "TO_BE_FILLED",
"mix_node": {
"host": "TO_BE_FILLED",
"mix_port": "TO_BE_FILLED",
"verloc_port": 1790,
"http_api_port": 8000,
"sphinx_key": "TO_BE_FILLED",
"identity_key": "TO_BE_FILLED",
"version": "whatever"
},
"proxy": None,
"bonding_height": 0,
"is_unbonding": False
},
"self_described": {
"host_information": {
"ip_address": [
"0.0.0.0"
],
"hostname": None,
"keys": {
"ed25519": "TO_BE_FILLED",
"x25519": "TO_BE_FILLED"
}
},
"build_information": {
"binary_name": "whatever",
"build_timestamp": "whatever",
"build_version": "whatever",
"commit_sha": "whatever",
"commit_timestamp": "whatever",
"commit_branch": "whatever",
"rustc_version": "whatever",
"rustc_channel": "whatever",
"cargo_profile": "whatever"
},
"network_requester": None,
"ip_packet_router": None,
"mixnet_websockets": None,
"noise_information": {
"supported": True
}
}
}
}
if __name__ == '__main__':
main(sys.argv[1:])
+4 -4
View File
@@ -42,10 +42,10 @@ echo "the full network file is located at $networkfile"
echo "starting the mixnet..."
tmux start-server
tmux new-session -d -s localnet -n Mixnet -d "/usr/bin/env sh -c \" cargo run --release --bin nym-mixnode -- run --id mix1-$suffix \""
tmux split-window -t localnet:0 "/usr/bin/env sh -c \" cargo run --release --bin nym-mixnode -- run --id mix2-$suffix \""
tmux split-window -t localnet:0 "/usr/bin/env sh -c \" cargo run --release --bin nym-mixnode -- run --id mix3-$suffix \""
tmux split-window -t localnet:0 "/usr/bin/env sh -c \" cargo run --release --bin nym-gateway -- run --id gateway-$suffix --local \""
tmux new-session -d -s localnet -n Mixnet -d "/usr/bin/env sh -c \" cargo run --release --bin nym-mixnode -- run --id mix1-$suffix --custom-mixnet \"$networkfile\" \""
tmux split-window -t localnet:0 "/usr/bin/env sh -c \" cargo run --release --bin nym-mixnode -- run --id mix2-$suffix --custom-mixnet \"$networkfile\" \""
tmux split-window -t localnet:0 "/usr/bin/env sh -c \" cargo run --release --bin nym-mixnode -- run --id mix3-$suffix --custom-mixnet \"$networkfile\" \""
tmux split-window -t localnet:0 "/usr/bin/env sh -c \" cargo run --release --bin nym-gateway -- run --id gateway-$suffix --local --custom-mixnet \"$networkfile\" \""
while ! nc -z localhost 9000; do
echo "waiting for nym-gateway to launch on port 9000..."