Compare commits

..

2 Commits

Author SHA1 Message Date
Jon Häggblad 8cd9b99d72 wip 2023-11-16 11:22:05 +00:00
Jon Häggblad f7093cdc5a Rework error handling in tun device 2023-11-15 12:33:45 +00:00
18 changed files with 229 additions and 353 deletions
Generated
+1
View File
@@ -7593,6 +7593,7 @@ dependencies = [
"tap",
"thiserror",
"tokio",
"tokio-stream",
"tokio-tun",
]
@@ -4,7 +4,6 @@
use clap::{Args, Subcommand};
pub mod update_config;
pub mod update_cost_params;
pub mod vesting_update_config;
#[derive(Debug, Args)]
@@ -21,5 +20,7 @@ pub enum MixnetOperatorsMixnodeSettingsCommands {
/// Update mixnode configuration for a mixnode bonded with locked tokens
VestingUpdateConfig(vesting_update_config::Args),
/// Update mixnode cost parameters
UpdateCostParameters(update_cost_params::Args),
UpdateCostParameters,
/// Update mixnode cost parameters for a mixnode bonded with locked tokens
VestingUpdateCostParameters,
}
@@ -1,48 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use cosmwasm_std::Uint128;
use log::info;
use nym_mixnet_contract_common::{MixNodeCostParams, Percent};
use nym_validator_client::nyxd::contract_traits::MixnetSigningClient;
use nym_validator_client::nyxd::CosmWasmCoin;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(
long,
help = "input your profit margin as follows; (so it would be 10, rather than 0.1)"
)]
pub profit_margin_percent: Option<u8>,
#[clap(
long,
help = "operating cost in current DENOMINATION (so it would be 'unym', rather than 'nym')"
)]
pub interval_operating_cost: Option<u128>,
}
pub async fn update_cost_params(args: Args, client: SigningClient) {
let denom = client.current_chain_details().mix_denom.base.as_str();
let cost_params = MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(
args.profit_margin_percent.unwrap_or(10) as u64,
)
.unwrap(),
interval_operating_cost: CosmWasmCoin {
denom: denom.into(),
amount: Uint128::new(args.interval_operating_cost.unwrap_or(40_000_000)),
},
};
info!("Starting mixnode params updating!");
let res = client
.update_mixnode_cost_params(cost_params, None)
.await
.expect("failed to update cost params");
info!("Cost params result: {:?}", res)
}
+2 -10
View File
@@ -25,20 +25,12 @@ pub const DEFAULT_CONFIG_FILENAME: &str = "config.toml";
#[cfg(feature = "dirs")]
pub fn must_get_home() -> PathBuf {
if let Some(home_dir) = std::env::var_os("NYM_HOME_DIR") {
home_dir.into()
} else {
dirs::home_dir().expect("Failed to evaluate $HOME value")
}
dirs::home_dir().expect("Failed to evaluate $HOME value")
}
#[cfg(feature = "dirs")]
pub fn may_get_home() -> Option<PathBuf> {
if let Some(home_dir) = std::env::var_os("NYM_HOME_DIR") {
Some(home_dir.into())
} else {
dirs::home_dir()
}
dirs::home_dir()
}
pub trait NymConfigTemplate: Serialize {
+1
View File
@@ -32,6 +32,7 @@ serde = { workspace = true, features = ["derive"] }
tap.workspace = true
thiserror.workspace = true
tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util"] }
tokio-stream = { version = "0.1.11" }
[target.'cfg(target_os = "linux")'.dependencies]
tokio-tun = "0.9.0"
+24 -4
View File
@@ -1,11 +1,14 @@
use std::net::SocketAddr;
use std::{net::SocketAddr, time::Duration};
use boringtun::x25519;
use dashmap::{
mapref::one::{Ref, RefMut},
DashMap,
};
use tokio::sync::mpsc::{self};
use tokio::{
sync::mpsc::{self},
time::{error::Elapsed, timeout},
};
use crate::event::Event;
@@ -14,9 +17,26 @@ use crate::event::Event;
pub struct PeerEventSender(mpsc::Sender<Event>);
pub(crate) struct PeerEventReceiver(mpsc::Receiver<Event>);
#[derive(thiserror::Error, Debug)]
pub enum PeerEventSenderError {
#[error("timeout")]
Timeout {
#[from]
source: Elapsed,
},
#[error("send failed: {source}")]
SendError {
#[from]
source: mpsc::error::SendError<Event>,
},
}
impl PeerEventSender {
pub(crate) async fn send(&self, event: Event) -> Result<(), mpsc::error::SendError<Event>> {
self.0.send(event).await
pub(crate) async fn send(&self, event: Event) -> Result<(), PeerEventSenderError> {
timeout(Duration::from_millis(1000), self.0.send(event))
.await?
.map_err(|err| err.into())
}
}
+136 -94
View File
@@ -2,21 +2,61 @@ use std::{
collections::HashMap,
net::{IpAddr, Ipv4Addr},
sync::Arc,
time::Duration,
};
use etherparse::{InternetSlice, SlicedPacket};
use tap::TapFallible;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
time::timeout,
};
use crate::{
active_peers::PeerEventSenderError,
event::Event,
tun_task_channel::{
tun_task_channel, tun_task_response_channel, TunTaskPayload, TunTaskResponseRx,
TunTaskResponseTx, TunTaskRx, TunTaskTx,
TunTaskResponseSendError, TunTaskResponseTx, TunTaskRx, TunTaskTx,
},
udp_listener::PeersByIp,
};
#[derive(thiserror::Error, Debug)]
pub enum TunDeviceError {
#[error("iface: timeout writing to tun device, dropping packet")]
TunWriteTimeout,
#[error("iface: failed forwarding packet to peer: {source}")]
ForwardToPeerFailed {
#[from]
source: PeerEventSenderError,
},
#[error("iface: failed to forward responding packet with tag: {source}")]
ForwardNatResponseFailed {
#[from]
source: TunTaskResponseSendError,
},
#[error("iface: error writing to tun device: {source}")]
TunWriteError { source: std::io::Error },
#[error("unable to parse destination address from packet")]
UnableToParseDstAdddress,
#[error("unable to parse source address from packet")]
UnableToParseSrcAddress {
#[from]
source: etherparse::ReadError,
},
#[error("unable to parse source address from packet: ip header missing")]
UnableToParseSrcAddressIpHeaderMissing,
#[error("unable to lock peer mutex")]
FailedToLockPeer,
}
fn setup_tokio_tun_device(name: &str, address: Ipv4Addr, netmask: Ipv4Addr) -> tokio_tun::Tun {
log::info!("Creating TUN device with: address={address}, netmask={netmask}");
// Read MTU size from env variable NYM_MTU_SIZE, else default to 1420.
@@ -38,10 +78,10 @@ fn setup_tokio_tun_device(name: &str, address: Ipv4Addr, netmask: Ipv4Addr) -> t
pub struct TunDevice {
// The TUN device that we read/write to, to send/receive packets
tun: tokio_tun::Tun,
tun: Option<tokio_tun::Tun>,
// Incoming data that we should send
tun_task_rx: TunTaskRx,
tun_task_rx: Option<TunTaskRx>,
// And when we get replies, this is where we should send it
tun_task_response_tx: TunTaskResponseTx,
@@ -74,6 +114,14 @@ pub struct AllowedIpsInner {
peers_by_ip: Arc<tokio::sync::Mutex<PeersByIp>>,
}
impl AllowedIpsInner {
async fn lock(&self) -> Result<tokio::sync::MutexGuard<PeersByIp>, TunDeviceError> {
timeout(Duration::from_millis(200), self.peers_by_ip.as_ref().lock())
.await
.map_err(|_| TunDeviceError::FailedToLockPeer)
}
}
pub struct NatInner {
nat_table: HashMap<IpAddr, u64>,
}
@@ -104,9 +152,9 @@ impl TunDevice {
let (tun_task_response_tx, tun_task_response_rx) = tun_task_response_channel();
let tun_device = TunDevice {
tun_task_rx,
tun_task_rx: Some(tun_task_rx),
tun_task_response_tx,
tun,
tun: Some(tun),
routing_mode,
};
@@ -114,47 +162,35 @@ impl TunDevice {
}
// Send outbound packets out on the wild internet
async fn handle_tun_write(&mut self, data: TunTaskPayload) {
let (tag, packet) = data;
let Some(dst_addr) = boringtun::noise::Tunn::dst_address(&packet) else {
log::error!("Unable to parse dst_address in packet that was supposed to be written to tun device");
return;
};
let Some(src_addr) = parse_src_address(&packet) else {
log::error!("Unable to parse src_address in packet that was supposed to be written to tun device");
return;
};
log::info!(
"iface: write Packet({src_addr} -> {dst_addr}, {} bytes)",
packet.len()
);
async fn handle_tun_write(&mut self, data: TunTaskPayload) -> Result<(), TunDeviceError> {
{
let (tag, ref packet) = data;
let dst_addr = boringtun::noise::Tunn::dst_address(packet)
.ok_or_else(|| TunDeviceError::UnableToParseDstAdddress)?;
// TODO: expire old entries
if let RoutingMode::Nat(nat_table) = &mut self.routing_mode {
nat_table.nat_table.insert(src_addr, tag);
let src_addr = parse_src_address(packet)?;
log::info!(
"iface: write Packet({src_addr} -> {dst_addr}, {} bytes)",
packet.len()
);
// TODO: expire old entries
if let RoutingMode::Nat(nat_table) = &mut self.routing_mode {
nat_table.nat_table.insert(src_addr, tag);
}
}
tokio::time::timeout(
std::time::Duration::from_millis(1000),
self.tun.write_all(&packet),
)
.await
.tap_err(|err| {
log::error!("iface: write error: {err}");
})
.ok();
// timeout(Duration::from_millis(1000), self.tun.write_all(&data.1))
// .await
// .map_err(|_| TunDeviceError::TunWriteTimeout)?
// .map_err(|err| TunDeviceError::TunWriteError { source: err })
}
// Receive reponse packets from the wild internet
async fn handle_tun_read(&self, packet: &[u8]) {
let Some(dst_addr) = boringtun::noise::Tunn::dst_address(packet) else {
log::error!("Unable to parse dst_address in packet that was read from tun device");
return;
};
let Some(src_addr) = parse_src_address(packet) else {
log::error!("Unable to parse src_address in packet that was read from tun device");
return;
};
async fn handle_tun_read(&self, packet: &[u8]) -> Result<(), TunDeviceError> {
let dst_addr = boringtun::noise::Tunn::dst_address(packet)
.ok_or(TunDeviceError::UnableToParseDstAdddress)?;
let src_addr = parse_src_address(packet)?;
log::info!(
"iface: read Packet({src_addr} -> {dst_addr}, {} bytes)",
packet.len(),
@@ -165,64 +201,72 @@ impl TunDevice {
match self.routing_mode {
// This is how wireguard does it, by consulting the AllowedIPs table.
RoutingMode::AllowedIps(ref peers_by_ip) => {
let Ok(peers) = tokio::time::timeout(
std::time::Duration::from_millis(1000),
peers_by_ip.peers_by_ip.as_ref().lock(),
)
.await
else {
log::error!("Failed to lock peer");
return;
};
let peers = peers_by_ip.lock().await?;
if let Some(peer_tx) = peers.longest_match(dst_addr).map(|(_, tx)| tx) {
log::info!("Forward packet to wg tunnel");
tokio::time::timeout(
std::time::Duration::from_millis(1000),
peer_tx.send(Event::Ip(packet.to_vec().into())),
)
.await
.tap_err(|err| log::error!("Failed to forward packet to wg tunnel: {err}"))
.ok();
return;
return peer_tx
.send(Event::Ip(packet.to_vec().into()))
.await
.map_err(|err| err.into());
}
}
// But we can also do it by consulting the NAT table.
RoutingMode::Nat(ref nat_table) => {
if let Some(tag) = nat_table.nat_table.get(&dst_addr) {
log::info!("Forward packet with tag: {tag}");
tokio::time::timeout(
std::time::Duration::from_millis(1000),
self.tun_task_response_tx.send((*tag, packet.to_vec())),
)
.await
.tap_err(|err| log::error!("Failed to foward packet with tag: {err}"))
.ok();
return;
log::info!("Forward packet with NAT tag: {tag}");
return self
.tun_task_response_tx
.send((*tag, packet.to_vec()))
.await
.map_err(|err| err.into());
}
}
}
log::info!("No peer found, packet dropped");
Ok(())
}
pub async fn run(mut self) {
let mut buf = [0u8; 65535];
let tun_task_rx_stream =
tokio_stream::wrappers::ReceiverStream::new(self.tun_task_rx.take().unwrap().0);
use futures::StreamExt;
let tun_task_rx_stream = tun_task_rx_stream.map(|data| {
//{
// let (tag, ref packet) = data;
// let dst_addr = boringtun::noise::Tunn::dst_address(packet).unwrap();
// // .ok_or_else(|| TunDeviceError::UnableToParseDstAdddress)?;
// let src_addr = parse_src_address(packet).unwrap();
// log::info!(
// "iface: write Packet({src_addr} -> {dst_addr}, {} bytes)",
// packet.len()
// );
// // TODO: expire old entries
// // if let RoutingMode::Nat(nat_table) = &mut self.routing_mode {
// // nat_table.nat_table.insert(src_addr, tag);
// // }
//}
// data.1
4
});
let (mut tun_read, tun_write) = tokio::io::split(self.tun);
loop {
tokio::select! {
// Reading from the TUN device
len = self.tun.read(&mut buf) => match len {
// len = self.tun.read(&mut buf) => match len {
len = tun_read.read(&mut buf) => match len {
Ok(len) => {
let packet = &buf[..len];
tokio::time::timeout(
std::time::Duration::from_millis(1000),
self.handle_tun_read(packet)
)
.await
.tap_err(|_err| log::error!("Failed: handle_tun_read timeout"))
.ok();
if let Err(err) = self.handle_tun_read(packet).await {
log::error!("iface: handle_tun_read failed: {err}")
}
},
Err(err) => {
log::info!("iface: read error: {err}");
@@ -230,15 +274,14 @@ impl TunDevice {
}
},
// Writing to the TUN device
Some(data) = self.tun_task_rx.recv() => {
tokio::time::timeout(
std::time::Duration::from_millis(1000),
self.handle_tun_write(data)
)
.await
.tap_err(|_err| log::error!("Failed: handle_tun_write timeout"))
.ok();
}
//Some(data) = self.tun_task_rx.recv() => {
// if let Err(err) = self.handle_tun_write(data).await {
// log::error!("ifcae: handle_tun_write failed: {err}");
// }
//}
// res = self.tun.send_all(&mut tun_task_rx_stream) => {
// log::error!("finished");
// }
}
}
// log::info!("TUN device shutting down");
@@ -249,12 +292,11 @@ impl TunDevice {
}
}
fn parse_src_address(packet: &[u8]) -> Option<IpAddr> {
let headers = SlicedPacket::from_ip(packet)
.tap_err(|err| log::error!("Unable to parse IP packet: {err:?}"))
.ok()?;
Some(match headers.ip? {
InternetSlice::Ipv4(ip, _) => ip.source_addr().into(),
InternetSlice::Ipv6(ip, _) => ip.source_addr().into(),
})
fn parse_src_address(packet: &[u8]) -> Result<IpAddr, TunDeviceError> {
let headers = SlicedPacket::from_ip(packet)?;
match headers.ip {
Some(InternetSlice::Ipv4(ip, _)) => Ok(ip.source_addr().into()),
Some(InternetSlice::Ipv6(ip, _)) => Ok(ip.source_addr().into()),
None => Err(TunDeviceError::UnableToParseSrcAddressIpHeaderMissing),
}
}
+22 -7
View File
@@ -1,10 +1,17 @@
use tokio::sync::mpsc;
use std::time::Duration;
use tokio::{
sync::mpsc::{self, error::SendError},
time::{error::Elapsed, timeout},
};
pub(crate) type TunTaskPayload = (u64, Vec<u8>);
#[derive(Clone)]
pub struct TunTaskTx(mpsc::Sender<TunTaskPayload>);
pub(crate) struct TunTaskRx(mpsc::Receiver<TunTaskPayload>);
pub(crate) struct TunTaskRx(pub(crate) mpsc::Receiver<TunTaskPayload>);
pub(crate) struct TunTaskRxStream(pub(crate) tokio_stream::wrappers::ReceiverStream<TunTaskPayload>);
impl TunTaskTx {
pub async fn send(
@@ -30,12 +37,20 @@ pub(crate) fn tun_task_channel() -> (TunTaskTx, TunTaskRx) {
pub(crate) struct TunTaskResponseTx(mpsc::Sender<TunTaskPayload>);
pub struct TunTaskResponseRx(mpsc::Receiver<TunTaskPayload>);
#[derive(thiserror::Error, Debug)]
pub enum TunTaskResponseSendError {
#[error("failed to send: timeout")]
Timeout(#[from] Elapsed),
#[error("failed to send: {0}")]
SendError(#[from] SendError<TunTaskPayload>),
}
impl TunTaskResponseTx {
pub(crate) async fn send(
&self,
data: TunTaskPayload,
) -> Result<(), tokio::sync::mpsc::error::SendError<TunTaskPayload>> {
self.0.send(data).await
pub(crate) async fn send(&self, data: TunTaskPayload) -> Result<(), TunTaskResponseSendError> {
timeout(Duration::from_millis(1000), self.0.send(data))
.await?
.map_err(|err| err.into())
}
}
-5
View File
@@ -81,8 +81,3 @@ cpucycles = [
"opentelemetry",
"nym-bin-common/tracing",
]
[package.metadata.deb]
name = "nym-mixnode"
maintainer-scripts = "debian"
systemd-units = { enable = false }
-24
View File
@@ -14,27 +14,3 @@ A Rust mixnode implementation.
* `nym-mixnode run --layer 1 --host x.x.x.x` will start the mixnode in layer 1 and bind to the specified host IP address. Coordinate with other people in your network to find out which layer needs coverage.
By default, the Nym Mixnode will start on port 1789. If desired, you can change the port using the `--port` option.
## Build debian package
```bash
# cargo install cargo-deb
# Build package
cargo deb -p nym-mixnode
# Install
# This will init the mixnode to `/etc/nym` as `nym` user, and create a systemd service
sudo dpkg -i target/debian/<PACKAGE>
# Run
sudo systemctl start nym-mixnode
# Check status
sudo systemctl status nym-mixnode
# Logs
journalctl -f -u nym-mixnode
```
-6
View File
@@ -1,6 +0,0 @@
#DEBHELPER#
useradd nym
mkdir -p /etc/nym
chown -R nym /etc/nym
su nym -c 'NYM_HOME_DIR=/etc/nym nym-mixnode init --host 0.0.0.0 --id nym-mixnode'
-11
View File
@@ -1,11 +0,0 @@
[Unit]
Description=Nym Mixnode
After=network-online.target
[Service]
ExecStart=/usr/bin/nym-mixnode run --id nym-mixnode
User=nym
Environment="NYM_HOME_DIR=/etc/nym"
[Install]
WantedBy=multi-user.target
+1 -1
View File
@@ -13,7 +13,7 @@ This is the application UI layer for the next NymVPN clients.
Some system libraries are required depending on the host platform.
Follow the instructions for your specific OS [here](https://tauri.app/v1/guides/getting-started/prerequisites)
To install:
To install run
```
yarn
@@ -16,8 +16,6 @@ in combination with their own configuration. If you are trying to access somethi
3. If you are using `mixFetch` in a web app with HTTPS you will need to use a gateway that has Secure Websockets to
avoid getting a [mixed content](https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content) error.
4. For now, mixfetch doesn't work with SURBS, altough this may change in the future.
Read [this article](https://blog.nymtech.net/mixfetch-like-the-fetch-api-but-via-the-mixnet-82acfd435c62) to learn more about mixFetch.
+13 -14
View File
@@ -44,7 +44,7 @@ pub(crate) trait ReleasePackage: Sized {
Ok(())
}
fn update_nym_dependencies(&mut self, _: &HashSet<String>, _: bool) -> anyhow::Result<()> {
fn update_nym_dependencies(&mut self, _: &HashSet<String>) -> anyhow::Result<()> {
Ok(())
}
}
@@ -57,29 +57,28 @@ pub(crate) trait VersionBumpExt: Sized {
fn try_remove_prerelease(&self) -> anyhow::Result<Self>;
}
pub(crate) fn try_bump_raw_prerelease(raw: &str) -> anyhow::Result<Prerelease> {
// ugh that's disgusting
let (rc_prefix, pre_version) = raw
.split_once('.')
.context("the prerelease version does not contain a valid rc.X suffix")?;
let parsed_version: u32 = pre_version.parse()?;
let updated_version = parsed_version + 1;
Ok(format!("{rc_prefix}.{updated_version}").parse()?)
}
impl VersionBumpExt for Version {
fn try_bump_prerelease(&self) -> anyhow::Result<Self> {
if self.pre.is_empty() {
bail!("the current version ({self}) does not have pre-release data set - are you sure you followed the release process correctly?")
}
// ugh that's disgusting
let (rc_prefix, pre_version) = self
.pre
.as_str()
.split_once('.')
.context("the prerelease version does not contain a valid rc.X suffix")?;
let parsed_version: u32 = pre_version.parse()?;
let updated_version = parsed_version + 1;
let pre = format!("{rc_prefix}.{updated_version}").parse()?;
Ok(Version {
major: self.major,
minor: self.minor,
patch: self.patch,
pre: try_bump_raw_prerelease(self.pre.as_str())?,
pre,
build: self.build.clone(),
})
}
+11 -48
View File
@@ -1,7 +1,6 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::helpers::try_bump_raw_prerelease;
use crate::json_types::DepsSet;
use crate::{json_types, ReleasePackage};
use anyhow::{bail, Context};
@@ -15,18 +14,10 @@ pub struct PackageJson {
inner: json_types::Package,
}
fn update_dependencies(
deps: &mut DepsSet,
names: &HashSet<String>,
pre_release: bool,
) -> anyhow::Result<()> {
fn update_dependencies(deps: &mut DepsSet, names: &HashSet<String>) -> anyhow::Result<()> {
for (package, version) in deps.iter_mut() {
if names.contains(package) {
let updated = if pre_release {
try_bump_prerelease_version_req(version)?
} else {
try_bump_minor_version_req(version)?
};
let updated = try_bump_minor_version_req(version)?;
println!("\t\t>>> updating '{package}' from {version} to {updated}");
*version = updated
@@ -61,25 +52,21 @@ impl ReleasePackage for PackageJson {
self.inner.version = version.to_string()
}
fn update_nym_dependencies(
&mut self,
names: &HashSet<String>,
pre_release: bool,
) -> anyhow::Result<()> {
fn update_nym_dependencies(&mut self, names: &HashSet<String>) -> anyhow::Result<()> {
println!("\t>>> updating @nymproject dependencies...");
update_dependencies(&mut self.inner.dependencies, names, pre_release)?;
update_dependencies(&mut self.inner.dependencies, names)?;
println!("\t>>> updating @nymproject peerDependencies...");
update_dependencies(&mut self.inner.peer_dependencies, names, pre_release)?;
update_dependencies(&mut self.inner.peer_dependencies, names)?;
println!("\t>>> updating @nymproject devDependencies...");
update_dependencies(&mut self.inner.dev_dependencies, names, pre_release)?;
update_dependencies(&mut self.inner.dev_dependencies, names)?;
println!("\t>>> updating @nymproject optionalDependencies...");
update_dependencies(&mut self.inner.optional_dependencies, names, pre_release)?;
update_dependencies(&mut self.inner.optional_dependencies, names)?;
println!("\t>>> updating @nymproject bundledDependencies...");
update_dependencies(&mut self.inner.bundled_dependencies, names, pre_release)?;
update_dependencies(&mut self.inner.bundled_dependencies, names)?;
Ok(())
}
@@ -114,9 +101,9 @@ pub(crate) fn find_package_path(dir: &Path) -> anyhow::Result<PathBuf> {
// expected structure: `>=X.Y.Z-rc.W || ^X`
fn try_bump_minor_version_req(raw_req: &str) -> anyhow::Result<String> {
let (req, major) = raw_req.split_once("||").context(format!(
"'{raw_req}' is not a valid semver version requirement - we expect '`>=X.Y.Z-rc.W || ^X`'"
))?;
let (req, major) = raw_req
.split_once("||")
.context("invalid version requirement")?;
let parsed_req = VersionReq::parse(req)?;
let parsed_major = VersionReq::parse(major)?;
if parsed_req.comparators.len() != 1 {
@@ -136,30 +123,6 @@ fn try_bump_minor_version_req(raw_req: &str) -> anyhow::Result<String> {
Ok(format!("{updated} || {parsed_major}"))
}
// expected structure: `>=X.Y.Z-rc.W || ^X`
fn try_bump_prerelease_version_req(raw_req: &str) -> anyhow::Result<String> {
let (req, major) = raw_req.split_once("||").context(format!(
"'{raw_req}' is not a valid semver version requirement - we expect '`>=X.Y.Z-rc.W || ^X`'"
))?;
let parsed_req = VersionReq::parse(req)?;
let parsed_major = VersionReq::parse(major)?;
if parsed_req.comparators.len() != 1 {
bail!("wrong number of version requirements present in {parsed_req}")
}
let updated = VersionReq {
comparators: vec![Comparator {
op: parsed_req.comparators[0].op,
major: parsed_req.comparators[0].major,
minor: parsed_req.comparators[0].minor,
patch: parsed_req.comparators[0].patch,
pre: try_bump_raw_prerelease(parsed_req.comparators[0].pre.as_str())?,
}],
};
Ok(format!("{updated} || {parsed_major}"))
}
#[cfg(test)]
mod tests {
use super::*;
+15 -74
View File
@@ -5,7 +5,7 @@ use crate::cargo::CargoPackage;
use crate::helpers::ReleasePackage;
use crate::json::PackageJson;
use clap::{Parser, Subcommand};
use std::collections::{HashMap, HashSet};
use std::collections::HashSet;
use std::env;
use std::path::{Path, PathBuf};
@@ -18,48 +18,6 @@ fn default_root() -> PathBuf {
env::current_dir().unwrap()
}
struct Summary {
cargo_results: HashMap<String, anyhow::Result<()>>,
json_results: HashMap<String, anyhow::Result<()>>,
}
impl Summary {
fn new(
cargo_results: HashMap<String, anyhow::Result<()>>,
json_results: HashMap<String, anyhow::Result<()>>,
) -> Self {
Summary {
cargo_results,
json_results,
}
}
fn print(&self) {
let cargo_ok = self.cargo_results.values().filter(|p| p.is_ok()).count();
let json_ok = self.json_results.values().filter(|p| p.is_ok()).count();
println!("SUMMARY");
println!("inspected {} cargo packages", self.cargo_results.len());
println!("updated {cargo_ok} cargo packages");
for (package, res) in &self.cargo_results {
if let Err(err) = res {
println!(
"\t>>> ❌ FAILURE: cargo package '{package}' failed to get updated: {err}"
);
}
}
println!("inspected {} json packages", self.json_results.len());
println!("updated {json_ok} json packages");
for (package, res) in &self.json_results {
if let Err(err) = res {
println!("\t>>> ❌ FAILURE: json package '{package}' failed to get updated: {err}");
}
}
}
}
#[derive(Parser)]
struct Args {
#[arg(default_value=default_root().into_os_string())]
@@ -78,13 +36,12 @@ enum Commands {
/// It will also update the `@nymproject/...` dependencies from `">=X.Y.Z-rc.0 || ^X"` to `">=X.Y.(Z+1)-rc.0 || ^X"`
BumpVersion {
#[arg(long)]
/// If enabled, the packages will only have their rc version bumped and the dependencies
/// will get updated from `">=X.Y.Z-rc.W || ^X"` to `">=X.Y.Z-rc.(W+1) || ^X"`
/// If enabled, the packages will only have their rc version bumped and the dependencies won't get updated at all
pre_release: bool,
},
}
fn remove_suffix<Pkg: ReleasePackage>(root: &Path, path: impl AsRef<Path>) -> anyhow::Result<()> {
fn remove_suffix<Pkg: ReleasePackage>(root: &Path, path: impl AsRef<Path>) {
let path = root.join(path);
println!(
">>> [{}] UPDATING PACKAGE {}: ",
@@ -94,10 +51,8 @@ fn remove_suffix<Pkg: ReleasePackage>(root: &Path, path: impl AsRef<Path>) -> an
if let Err(err) = { remove_suffix_inner::<Pkg>(path) } {
println!("\t>>> ❌ FAILURE: {err}");
Err(err)
} else {
println!("\t>>> ✅ SUCCESS");
Ok(())
}
}
@@ -115,7 +70,7 @@ fn bump_version<Pkg: ReleasePackage>(
path: impl AsRef<Path>,
dependencies_to_update: &HashSet<String>,
pre_release: bool,
) -> anyhow::Result<()> {
) {
let path = root.join(path);
println!(
">>> [{}] UPDATING PACKAGE {}: ",
@@ -124,10 +79,8 @@ fn bump_version<Pkg: ReleasePackage>(
);
if let Err(err) = { bump_version_inner::<Pkg>(path, dependencies_to_update, pre_release) } {
println!("\t>>> ❌ FAILURE: {err}");
Err(err)
} else {
println!("\t>>> ✅ SUCCESS");
Ok(())
}
}
@@ -140,7 +93,9 @@ fn bump_version_inner<Pkg: ReleasePackage>(
let mut package = Pkg::open(path)?;
package.bump_version(pre_release)?;
package.update_nym_dependencies(dependencies_to_update, pre_release)?;
if !pre_release {
package.update_nym_dependencies(dependencies_to_update)?;
}
println!("\t>>> saving the package file...");
package.save_changes()
@@ -177,46 +132,34 @@ impl InternalPackages {
self.internal_js_dependencies.insert(name.into());
}
pub fn remove_suffix(&self) -> Summary {
let mut cargo_results = HashMap::new();
pub fn remove_suffix(&self) {
for cargo_package in &self.cargo {
let res = remove_suffix::<CargoPackage>(&self.root, cargo_package);
cargo_results.insert(cargo_package.clone(), res);
remove_suffix::<CargoPackage>(&self.root, cargo_package);
}
let mut json_results = HashMap::new();
for package_json in &self.json {
let res = remove_suffix::<PackageJson>(&self.root, package_json);
json_results.insert(package_json.clone(), res);
remove_suffix::<PackageJson>(&self.root, package_json);
}
Summary::new(cargo_results, json_results)
}
pub fn bump_version(&self, pre_release: bool) -> Summary {
let mut cargo_results = HashMap::new();
pub fn bump_version(&self, pre_release: bool) {
for cargo_package in &self.cargo {
let res = bump_version::<CargoPackage>(
bump_version::<CargoPackage>(
&self.root,
cargo_package,
&Default::default(),
pre_release,
);
cargo_results.insert(cargo_package.clone(), res);
}
let mut json_results = HashMap::new();
for package_json in &self.json {
let res = bump_version::<PackageJson>(
bump_version::<PackageJson>(
&self.root,
package_json,
&self.internal_js_dependencies,
pre_release,
);
json_results.insert(package_json.clone(), res);
}
Summary::new(cargo_results, json_results)
}
}
@@ -283,12 +226,10 @@ fn main() -> anyhow::Result<()> {
let args = Args::parse();
let packages = initialise_internal_packages(args.root);
let summary = match args.command {
match args.command {
Commands::RemoveSuffix => packages.remove_suffix(),
Commands::BumpVersion { pre_release } => packages.bump_version(pre_release),
};
summary.print();
}
Ok(())
}
@@ -13,9 +13,6 @@ pub(crate) async fn execute(
nym_cli_commands::validator::mixnet::operators::mixnode::settings::MixnetOperatorsMixnodeSettingsCommands::UpdateConfig(args) => {
nym_cli_commands::validator::mixnet::operators::mixnode::settings::update_config::update_config(args, create_signing_client(global_args, network_details)?).await
}
nym_cli_commands::validator::mixnet::operators::mixnode::settings::MixnetOperatorsMixnodeSettingsCommands::UpdateCostParameters(args) => {
nym_cli_commands::validator::mixnet::operators::mixnode::settings::update_cost_params::update_cost_params(args, create_signing_client(global_args, network_details)?).await
}
_ => unreachable!(),
}
Ok(())