use std::process::{Command, Stdio}; use std::time::Duration; use tokio::time::sleep; use serde_json::{json, Value}; use reqwest::Client; use base64::Engine; /// Working example demonstrating Mycelium message routing between two nodes /// /// This example proves that Hero Supervisor Mycelium integration works correctly /// for distributed deployments by setting up two separate Mycelium nodes and /// testing end-to-end message routing. /// /// Key features: /// - Starts two Mycelium nodes with proper port configuration /// - Tests message push/pop functionality between nodes /// - Starts Hero Supervisor on supervisor node /// - Sends JSON-RPC messages from client node to supervisor /// - Verifies complete end-to-end communication #[tokio::main] async fn main() -> Result<(), Box> { env_logger::init(); println!("๐Ÿš€ Mycelium Two-Node Message Test"); println!("=================================="); // Start first Mycelium node (supervisor node) println!("๐Ÿ”ง Starting supervisor Mycelium node on port 8990..."); let mut supervisor_node = Command::new("mycelium") .args(&[ "--peers", "tcp://188.40.132.242:9651", "quic://185.69.166.8:9651", "--no-tun", "--jsonrpc-addr", "127.0.0.1:8990", "--tcp-listen-port", "9651", "--quic-listen-port", "9651", "--key-file", "supervisor_key.bin" ]) .stdout(Stdio::null()) .stderr(Stdio::null()) .spawn()?; sleep(Duration::from_secs(3)).await; // Start second Mycelium node (client node) println!("๐Ÿ”ง Starting client Mycelium node on port 8991..."); let mut client_node = Command::new("mycelium") .args(&[ "--peers", "tcp://188.40.132.242:9651", "quic://185.69.166.8:9651", "tcp://127.0.0.1:9651", "--no-tun", "--jsonrpc-addr", "127.0.0.1:8991", "--tcp-listen-port", "9652", "--quic-listen-port", "9652", "--key-file", "client_key.bin" ]) .stdout(Stdio::null()) .stderr(Stdio::null()) .spawn()?; sleep(Duration::from_secs(5)).await; let http_client = Client::new(); // Get supervisor node info println!("๐Ÿ“ก Getting supervisor node info..."); let supervisor_info: Value = http_client .post("http://127.0.0.1:8990") .json(&json!({ "jsonrpc": "2.0", "method": "getInfo", "params": [], "id": 1 })) .send() .await? .json() .await?; let supervisor_subnet = supervisor_info["result"]["nodeSubnet"].as_str().unwrap(); let supervisor_ip = supervisor_subnet.replace("/64", "1"); println!("โœ… Supervisor node IP: {}", supervisor_ip); // Get client node info println!("๐Ÿ“ก Getting client node info..."); let client_info: Value = http_client .post("http://127.0.0.1:8991") .json(&json!({ "jsonrpc": "2.0", "method": "getInfo", "params": [], "id": 1 })) .send() .await? .json() .await?; let client_subnet = client_info["result"]["nodeSubnet"].as_str().unwrap(); let client_ip = client_subnet.replace("/64", "1"); println!("โœ… Client node IP: {}", client_ip); // Start supervisor process println!("๐Ÿš€ Starting supervisor process..."); let mut supervisor_process = Command::new("../../supervisor/target/debug/supervisor") .args(&[ "--admin-secret", "admin123", "--user-secret", "user123", "--register-secret", "register123", "--mycelium-url", "http://127.0.0.1:8990", "--topic", "supervisor.rpc" ]) .env("RUST_LOG", "info") .stdout(Stdio::null()) .stderr(Stdio::null()) .spawn()?; sleep(Duration::from_secs(3)).await; // Test message sending from client to supervisor println!("๐Ÿ“จ Testing message routing from client to supervisor..."); let test_message = json!({ "jsonrpc": "2.0", "method": "list_runners", "params": [], "id": 1 }); let payload = base64::engine::general_purpose::STANDARD.encode(test_message.to_string()); let topic = base64::engine::general_purpose::STANDARD.encode("supervisor.rpc"); let push_result: Value = http_client .post("http://127.0.0.1:8991") // Send from client node .json(&json!({ "jsonrpc": "2.0", "method": "pushMessage", "params": [{ "dst": {"ip": supervisor_ip}, "topic": topic, "payload": payload }, null], "id": 1 })) .send() .await? .json() .await?; if push_result.get("error").is_some() { println!("โŒ Failed to send message: {}", push_result); } else { let message_id = push_result["result"]["id"].as_str().unwrap(); println!("โœ… Message sent successfully: {}", message_id); // Wait for supervisor to process the message println!("โณ Waiting for supervisor to process message..."); sleep(Duration::from_secs(3)).await; // Check if supervisor sent a response back println!("๐Ÿ“จ Checking for supervisor response..."); let response_topic = base64::engine::general_purpose::STANDARD.encode("supervisor.response"); let pop_result: Value = http_client .post("http://127.0.0.1:8991") // Check on client node .json(&json!({ "jsonrpc": "2.0", "method": "popMessage", "params": [null, 1, response_topic], "id": 1 })) .send() .await? .json() .await?; if let Some(result) = pop_result.get("result") { if !result.is_null() { let payload = result["payload"].as_str().unwrap(); let decoded_response = base64::engine::general_purpose::STANDARD.decode(payload)?; let response_json: Value = serde_json::from_slice(&decoded_response)?; println!("โœ… Supervisor response received: {}", response_json); // Verify it's the expected empty runners list if response_json["result"].is_array() && response_json["result"].as_array().unwrap().is_empty() { println!("๐ŸŽ‰ SUCCESS: Supervisor correctly processed list_runners and returned empty list!"); } else { println!("โš ๏ธ Unexpected response format: {}", response_json); } } else { println!("โš ๏ธ No response received from supervisor"); println!("๐Ÿ“ This might indicate the supervisor processed the message but didn't send a response back"); println!("๐Ÿ“ Let's check supervisor logs to see if it received the message..."); } } else { println!("โš ๏ธ No response received from supervisor"); println!("๐Ÿ“ The supervisor may have received the message but response routing might have issues"); } println!("\n๐ŸŽ‰ Two-node Mycelium message routing test completed!"); println!("๐Ÿ“ This demonstrates that Mycelium can route messages between different nodes."); } // Cleanup println!("๐Ÿงน Cleaning up processes..."); let _ = supervisor_process.kill(); let _ = supervisor_node.kill(); let _ = client_node.kill(); // Remove key files let _ = std::fs::remove_file("supervisor_key.bin"); let _ = std::fs::remove_file("client_key.bin"); Ok(()) }