Skip to content

Commit

Permalink
feat(merod): implement generic config edit (#962)
Browse files Browse the repository at this point in the history
  • Loading branch information
fbozic authored Nov 12, 2024
1 parent 455a6d0 commit 57b43a4
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 181 deletions.
2 changes: 1 addition & 1 deletion contracts/proxy-lib/tests/common/proxy_lib_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use calimero_context_config::{
use ed25519_dalek::{Signer, SigningKey};
use near_sdk::AccountId;
use near_workspaces::result::{ExecutionFinalResult, ViewResultDetails};
use near_workspaces::{Account, Contract};
use near_workspaces::Account;
use serde_json::json;

pub const PROXY_CONTRACT_WASM: &str = "./res/proxy_lib.wasm";
Expand Down
2 changes: 1 addition & 1 deletion crates/config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use eyre::{Result as EyreResult, WrapErr};
use multiaddr::Multiaddr;
use serde::{Deserialize, Serialize};

const CONFIG_FILE: &str = "config.toml";
pub const CONFIG_FILE: &str = "config.toml";

#[derive(Debug, Deserialize, Serialize)]
#[non_exhaustive]
Expand Down
4 changes: 2 additions & 2 deletions crates/merod/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ pub struct RootCommand {

#[derive(Debug, Subcommand)]
pub enum SubCommands {
Init(InitCommand),
Config(ConfigCommand),
Init(InitCommand),
#[command(alias = "up")]
Run(RunCommand),
#[command(alias = "call")]
Expand All @@ -70,8 +70,8 @@ pub struct RootArgs {
impl RootCommand {
pub async fn run(self) -> EyreResult<()> {
match self.action {
SubCommands::Init(init) => init.run(self.args),
SubCommands::Config(config) => config.run(&self.args),
SubCommands::Init(init) => init.run(self.args),
SubCommands::Run(run) => run.run(self.args).await,
SubCommands::Relay(relay) => relay.run(self.args).await,
}
Expand Down
228 changes: 54 additions & 174 deletions crates/merod/src/cli/config.rs
Original file line number Diff line number Diff line change
@@ -1,209 +1,76 @@
#![allow(unused_results, reason = "Occurs in macro")]

use core::net::IpAddr;
use std::env::temp_dir;
use std::fs::{read_to_string, write};
use std::str::FromStr;

use calimero_network::config::BootstrapNodes;
use clap::{Args, Parser, ValueEnum};
use eyre::{eyre, Result as EyreResult};
use multiaddr::{Multiaddr, Protocol};
use toml_edit::{DocumentMut, Value};
use calimero_config::{ConfigFile, CONFIG_FILE};
use camino::Utf8PathBuf;
use clap::{value_parser, Parser};
use eyre::{bail, eyre, Result as EyreResult};
use toml_edit::{Item, Value};
use tracing::info;

use crate::cli;

#[derive(Copy, Clone, Debug, ValueEnum)]
pub enum ConfigProtocol {
Near,
Starknet,
}

impl ConfigProtocol {
pub fn as_str(&self) -> &str {
match self {
ConfigProtocol::Near => "near",
ConfigProtocol::Starknet => "starknet",
}
}
}

/// Configure the node
#[derive(Debug, Parser)]
pub struct ConfigCommand {
/// List of bootstrap nodes
#[arg(long, value_name = "ADDR", conflicts_with = "boot_network")]
pub boot_nodes: Vec<Multiaddr>,

/// Use nodes from a known network
#[arg(long, value_name = "NETWORK", conflicts_with = "boot_nodes")]
pub boot_network: Option<BootstrapNetwork>,

/// Host to listen on
#[arg(long, value_name = "HOST", use_value_delimiter = true)]
pub swarm_host: Vec<IpAddr>,

/// Port to listen on
#[arg(long, value_name = "PORT")]
pub swarm_port: Option<u16>,

/// Host to listen on for RPC
#[arg(long, value_name = "HOST", use_value_delimiter = true)]
pub server_host: Vec<IpAddr>,

/// Port to listen on for RPC
#[arg(long, value_name = "PORT")]
pub server_port: Option<u16>,

#[command(flatten)]
pub mdns: Option<MdnsArgs>,
/// Key-value pairs to be added or updated in the TOML file
#[clap(short, long, value_parser = value_parser!(KeyValuePair))]
arg: Vec<KeyValuePair>,
}

/// Print the config file
#[arg(long, short)]
pub print: bool,
#[derive(Clone, Debug)]
struct KeyValuePair {
key: String,
value: Value,
}

#[derive(Args, Debug)]
#[group(multiple = false)]
pub struct MdnsArgs {
/// Enable mDNS discovery
#[arg(long)]
pub mdns: bool,
impl FromStr for KeyValuePair {
type Err = String;

#[arg(long, hide = true)]
pub _no_mdns: bool,
}
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parts = s.splitn(2, '=');
let key = parts.next().ok_or("Missing key")?.to_owned();

let value = parts.next().ok_or("Missing value")?;
let value = Value::from_str(value).map_err(|e| e.to_string())?;

#[derive(Clone, Debug, ValueEnum)]
pub enum BootstrapNetwork {
CalimeroDev,
Ipfs,
Ok(Self { key, value })
}
}

#[warn(unused_results)]
impl ConfigCommand {
// TODO: Consider splitting this long function into multiple parts.
#[expect(clippy::too_many_lines, reason = "TODO: Will be refactored")]
pub fn run(self, root_args: &cli::RootArgs) -> EyreResult<()> {
let path = root_args
.home
.join(&root_args.node_name)
.join("config.toml");
let path = root_args.home.join(&root_args.node_name);

if !ConfigFile::exists(&path) {
bail!("Node is not initialized in {:?}", path);
}

let path = path.join(CONFIG_FILE);

// Load the existing TOML file
let toml_str =
read_to_string(&path).map_err(|_| eyre!("Node is not initialized in {:?}", path))?;
let mut doc = toml_str.parse::<DocumentMut>()?;
let mut doc = toml_str.parse::<toml_edit::DocumentMut>()?;

#[expect(clippy::print_stdout, reason = "Acceptable for CLI")]
if self.print {
println!("{doc}");
return Ok(());
}
// Update the TOML document
for kv in self.arg.iter() {
let key_parts: Vec<&str> = kv.key.split('.').collect();

let (ipv4_host, ipv6_host) =
self.swarm_host
.iter()
.fold((None, None), |(v4, v6), ip| match ip {
IpAddr::V4(v4_addr) => (Some(*v4_addr), v6),
IpAddr::V6(v6_addr) => (v4, Some(*v6_addr)),
});

// Update swarm listen addresses
if !self.swarm_host.is_empty() || self.swarm_port.is_some() {
let listen_array = doc["swarm"]["listen"]
.as_array_mut()
.ok_or(eyre!("No swarm table in config.toml"))?;

for item in listen_array.iter_mut() {
let addr: Multiaddr = item
.as_str()
.ok_or(eyre!("Value can't be parsed as string"))?
.parse()?;
let mut new_addr = Multiaddr::empty();

for protocol in &addr {
match (&protocol, ipv4_host, ipv6_host, self.swarm_port) {
(Protocol::Ip4(_), Some(ipv4_host), _, _) => {
new_addr.push(Protocol::Ip4(ipv4_host));
}
(Protocol::Ip6(_), _, Some(ipv6_host), _) => {
new_addr.push(Protocol::Ip6(ipv6_host));
}
(Protocol::Tcp(_) | Protocol::Udp(_), _, _, Some(new_port)) => {
#[expect(clippy::wildcard_enum_match_arm, reason = "Acceptable here")]
new_addr.push(match &protocol {
Protocol::Tcp(_) => Protocol::Tcp(new_port),
Protocol::Udp(_) => Protocol::Udp(new_port),
_ => unreachable!(),
});
}
_ => new_addr.push(protocol),
}
}

*item = Value::from(new_addr.to_string());
}
}
let mut current = doc.as_item_mut();

// Update server listen addresses
if !self.server_host.is_empty() || self.server_port.is_some() {
let listen_array = doc["server"]["listen"]
.as_array_mut()
.ok_or(eyre!("No server table in config.toml"))?;

for item in listen_array.iter_mut() {
let addr: Multiaddr = item
.as_str()
.ok_or(eyre!("Value can't be parsed as string"))?
.parse()?;
let mut new_addr = Multiaddr::empty();

for protocol in &addr {
match (&protocol, ipv4_host, ipv6_host, self.server_port) {
(Protocol::Ip4(_), Some(ipv4_host), _, _) => {
new_addr.push(Protocol::Ip4(ipv4_host));
}
(Protocol::Ip6(_), _, Some(ipv6_host), _) => {
new_addr.push(Protocol::Ip6(ipv6_host));
}
(Protocol::Tcp(_), _, _, Some(new_port)) => {
new_addr.push(Protocol::Tcp(new_port));
}
_ => new_addr.push(protocol),
}
}

*item = Value::from(new_addr.to_string());
for key in &key_parts[..key_parts.len() - 1] {
current = &mut current[key];
}
}

// Update boot nodes if provided
if !self.boot_nodes.is_empty() {
let list_array = doc["bootstrap"]["nodes"]
.as_array_mut()
.ok_or(eyre!("No swarm table in config.toml"))?;
list_array.clear();
for node in self.boot_nodes {
list_array.push(node.to_string());
}
} else if let Some(network) = self.boot_network {
let list_array = doc["bootstrap"]["nodes"]
.as_array_mut()
.ok_or(eyre!("No swarm table in config.toml"))?;
list_array.clear();
let new_nodes = match network {
BootstrapNetwork::CalimeroDev => BootstrapNodes::calimero_dev().list,
BootstrapNetwork::Ipfs => BootstrapNodes::ipfs().list,
};
for node in new_nodes {
list_array.push(node.to_string());
}
current[key_parts[key_parts.len() - 1]] = Item::Value(kv.value.clone());
}

// Update mDNS setting if provided
if let Some(opts) = self.mdns {
doc["discovery"]["mdns"] = toml_edit::value(opts.mdns);
}
self.validate_toml(&doc)?;

// Save the updated TOML back to the file
write(&path, doc.to_string())?;
Expand All @@ -212,4 +79,17 @@ impl ConfigCommand {

Ok(())
}

pub fn validate_toml(self, doc: &toml_edit::DocumentMut) -> EyreResult<()> {
let tmp_dir = temp_dir();
let tmp_path = tmp_dir.join(CONFIG_FILE);

write(&tmp_path, doc.to_string())?;

let tmp_path_utf8 = Utf8PathBuf::try_from(tmp_dir)?;

drop(ConfigFile::load(&tmp_path_utf8)?);

Ok(())
}
}
16 changes: 15 additions & 1 deletion crates/merod/src/cli/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ use calimero_store::config::StoreConfig;
use calimero_store::db::RocksDB;
use calimero_store::Store;
use clap::{Parser, ValueEnum};
use cli::config::ConfigProtocol;
use eyre::{bail, Result as EyreResult, WrapErr};
use libp2p::identity::Keypair;
use multiaddr::{Multiaddr, Protocol};
Expand All @@ -35,6 +34,21 @@ use url::Url;

use crate::{cli, defaults};

#[derive(Copy, Clone, Debug, ValueEnum)]
pub enum ConfigProtocol {
Near,
Starknet,
}

impl ConfigProtocol {
pub fn as_str(&self) -> &str {
match self {
ConfigProtocol::Near => "near",
ConfigProtocol::Starknet => "starknet",
}
}
}

/// Initialize node configuration
#[derive(Debug, Parser)]
pub struct InitCommand {
Expand Down
2 changes: 1 addition & 1 deletion crates/node/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ impl Node {
// TODO: move this into the config
// TODO: also this would be nice to have global default with per application customization
fn get_runtime_limits() -> EyreResult<VMLimits> {
Ok(calimero_runtime::logic::VMLimits {
Ok(VMLimits {
max_memory_pages: 1 << 10, // 1 KiB
max_stack_size: 200 << 10, // 200 KiB
max_registers: 100,
Expand Down
2 changes: 1 addition & 1 deletion crates/server/src/admin/handlers/proposals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::vec;

use axum::extract::Path;
use axum::response::IntoResponse;
use axum::{Extension, Json};
use axum::Extension;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use tower_sessions::Session;
Expand Down

0 comments on commit 57b43a4

Please sign in to comment.