use crate::constants;
use crate::error::{HylixError, HylixResult};
use crate::logging::log_info;
use std::path::PathBuf;
use std::process::Command;
use super::context::{DevnetContext, NodePorts};
pub fn get_node_id(index: u32) -> String {
format!("node-{}", index)
}
pub fn generate_stakers_config(total_nodes: u32, has_local_node: bool) -> String {
let mut stakers = Vec::new();
let docker_start = if has_local_node { 1 } else { 0 };
for i in docker_start..total_nodes {
let node_id = if has_local_node && i == 0 {
constants::containers::NODE_LOCAL.to_string()
} else {
get_node_id(i)
};
stakers.push(format!("\"{}\"=100", node_id));
}
if has_local_node {
stakers.insert(0, format!("\"{}\"=100", constants::containers::NODE_LOCAL));
}
stakers.join(",")
}
pub fn generate_peers_for_node(
node_index: u32,
total_nodes: u32,
has_local_node: bool,
base_config: &crate::config::DevnetConfig,
) -> String {
let mut peers = Vec::new();
for i in 0..total_nodes {
if i == node_index {
continue; }
let ports = if has_local_node && i == 0 {
NodePorts::for_local_node(base_config)
} else {
NodePorts::for_docker_node(i, base_config)
};
let node_name = if has_local_node && i == 0 {
constants::containers::DOCKER_HOST_NAME.to_string()
} else if has_local_node && node_index == 0 {
"127.0.0.1".to_string()
} else {
constants::containers::node_name(i)
};
peers.push(format!("{node_name}:{}", ports.p2p));
}
peers.join(",")
}
pub fn get_local_node_config_dir() -> HylixResult<PathBuf> {
let config_dir =
dirs::config_dir().ok_or_else(|| HylixError::config("Could not find config directory"))?;
Ok(config_dir.join("hylix").join("devnet").join("node-local"))
}
pub fn get_local_node_config_path() -> HylixResult<PathBuf> {
Ok(get_local_node_config_dir()?.join("config.toml"))
}
pub fn generate_local_node_config(context: &DevnetContext) -> HylixResult<()> {
let multi_node = context.multi_node.as_ref().unwrap();
let config_dir = get_local_node_config_dir()?;
std::fs::create_dir_all(&config_dir)?;
let config_path = config_dir.join("config.toml");
let ports = NodePorts::for_local_node(&context.config.devnet);
let peers = generate_peers_for_node(
0, multi_node.total_nodes,
true,
&context.config.devnet,
);
let stakers = generate_stakers_config(multi_node.total_nodes, true);
let host_ip = Command::new("docker")
.args([
"inspect",
"-f",
"{{(index .IPAM.Config 0).Gateway}}",
constants::networks::DEVNET,
])
.output()
.map_err(|e| HylixError::process(format!("Failed to get Docker host IP: {e}")))?
.stdout;
let host_ip = String::from_utf8_lossy(&host_ip).trim().to_string();
let config_content = format!(
r#"# Auto-generated configuration for local node in multi-node devnet
# Generated by: hy devnet up --nodes {}
id = "{}"
data_directory = "data_node_local"
# REST API
run_rest_server = true
rest_server_port = {}
# ADMIN API
run_admin_server = true
admin_server_port = {}
# DA Server
da_server_port = {}
da_public_address = "{host_ip}:{}"
# Indexer (disabled - using shared indexer)
run_indexer = false
run_explorer = false
[p2p]
mode = "FullValidator"
public_address = "{host_ip}:{}"
server_port = {}
peers = [{}]
[consensus]
solo = false
slot_duration = 1000
genesis_timestamp = {}
[genesis]
stakers = {{ {} }}
[websocket]
enabled = true
server_port = 8888
"#,
multi_node.total_nodes,
constants::containers::NODE_LOCAL,
ports.rest,
ports.admin,
ports.da,
ports.da,
ports.p2p,
ports.p2p,
peers
.split(',')
.map(|p| format!("\"{}\"", p))
.collect::<Vec<_>>()
.join(", "),
multi_node.genesis_timestamp,
stakers.replace(',', ", ").replace('=', " = "),
);
std::fs::write(&config_path, config_content)?;
log_info(&format!(
"Local node configuration written to: {}",
config_path.display()
));
Ok(())
}