hylix 0.10.1

Build, test & deploy verifiable apps on Hyli
Documentation
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};

/// Get the node ID for a given index
pub fn get_node_id(index: u32) -> String {
    format!("node-{}", index)
}

/// Generate stakers configuration for genesis
pub fn generate_stakers_config(total_nodes: u32, has_local_node: bool) -> String {
    let mut stakers = Vec::new();

    // Add Docker nodes
    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));
    }

    // Add local node if enabled
    if has_local_node {
        stakers.insert(0, format!("\"{}\"=100", constants::containers::NODE_LOCAL));
    }

    stakers.join(",")
}

/// Generate the list of peer addresses for a node
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();

    // Add all other nodes as peers
    for i in 0..total_nodes {
        if i == node_index {
            continue; // Skip self
        }

        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 {
            // Local node connecting to Docker exposed ports
            "127.0.0.1".to_string()
        } else {
            // Docker node connecting to other Docker nodes
            constants::containers::node_name(i)
        };

        peers.push(format!("{node_name}:{}", ports.p2p));
    }

    peers.join(",")
}

/// Get the directory for local node configuration
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"))
}

/// Get the path to the local node configuration file
pub fn get_local_node_config_path() -> HylixResult<PathBuf> {
    Ok(get_local_node_config_dir()?.join("config.toml"))
}

/// Generate configuration file for the local node
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, // Local node is index 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();

    // Generate TOML configuration
    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(())
}