diff --git a/Cargo.lock b/Cargo.lock index 4d6c25b..c9a455f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -853,6 +853,7 @@ dependencies = [ "async-stream", "axum", "bytes", + "dirs 5.0.1", "evmlib", "flate2", "fs2", diff --git a/ant-core/Cargo.toml b/ant-core/Cargo.toml index 6d3aa04..18ad382 100644 --- a/ant-core/Cargo.toml +++ b/ant-core/Cargo.toml @@ -57,11 +57,16 @@ serial_test = "3" anyhow = "1" alloy = { version = "1.6", features = ["node-bindings"] } tokio-test = "0.4" +dirs = "5" [[example]] name = "start-local-devnet" path = "examples/start-local-devnet.rs" +[[example]] +name = "start-devnet-sepolia" +path = "examples/start-devnet-sepolia.rs" + [[test]] name = "e2e_chunk" path = "tests/e2e_chunk.rs" diff --git a/ant-core/examples/start-devnet-sepolia.rs b/ant-core/examples/start-devnet-sepolia.rs new file mode 100644 index 0000000..1a39024 --- /dev/null +++ b/ant-core/examples/start-devnet-sepolia.rs @@ -0,0 +1,120 @@ +//! Start a local devnet with 25 nodes using Arbitrum Sepolia for payments. +//! +//! Uses the existing deployed contracts on Arbitrum Sepolia. +//! Nodes verify payments against the real Sepolia PaymentVault. +//! +//! Writes a manifest to the ant-gui config directory so the GUI +//! auto-detects Sepolia mode on startup. +//! +//! # Usage +//! +//! ```bash +//! cargo run --release --example start-devnet-sepolia +//! ``` + +use ant_core::data::EvmNetwork; +use ant_node::devnet::{Devnet, DevnetConfig}; +use std::path::PathBuf; + +fn gui_manifest_path() -> PathBuf { + let config_dir = dirs::config_dir() + .unwrap_or_else(|| PathBuf::from(".")) + .join("autonomi") + .join("ant-gui"); + std::fs::create_dir_all(&config_dir).ok(); + config_dir.join("devnet-manifest.json") +} + +fn main() -> Result<(), Box> { + tracing_subscriber::fmt() + .with_env_filter( + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")), + ) + .with_writer(std::io::stderr) + .init(); + + let runtime = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .thread_stack_size(8 * 1024 * 1024) + .build()?; + + runtime.block_on(async { + let evm_network = EvmNetwork::ArbitrumSepoliaTest; + + let rpc_url = evm_network.rpc_url().to_string(); + let token_addr = format!("{}", evm_network.payment_token_address()); + let vault_addr = format!("{}", evm_network.payment_vault_address()); + + println!("Starting Sepolia devnet..."); + println!("RPC: {rpc_url}"); + println!("Token: {token_addr}"); + println!("Vault: {vault_addr}"); + + let config = DevnetConfig { + evm_network: Some(evm_network), + ..DevnetConfig::default() + }; + + println!("Starting {} nodes...", config.node_count); + + let mut devnet = Devnet::new(config).await?; + devnet.start().await?; + + let bootstrap_addrs: Vec = devnet + .bootstrap_addrs() + .iter() + .filter_map(|ma| { + let s = ma.to_string(); + let parts: Vec<&str> = s.split('/').collect(); + let ip = parts + .iter() + .position(|&p| p == "ip4") + .and_then(|i| parts.get(i + 1))?; + let port = parts + .iter() + .position(|&p| p == "udp") + .and_then(|i| parts.get(i + 1))?; + Some(format!("{ip}:{port}")) + }) + .collect(); + + let manifest = serde_json::json!({ + "base_port": 0, + "node_count": devnet.config().node_count, + "bootstrap": devnet.bootstrap_addrs().iter().map(|a| a.to_string()).collect::>(), + "data_dir": devnet.config().data_dir.to_string_lossy(), + "created_at": "", + "evm": { + "rpc_url": rpc_url, + "wallet_private_key": "", + "payment_token_address": token_addr, + "payment_vault_address": vault_addr, + } + }); + + let manifest_path = gui_manifest_path(); + std::fs::write(&manifest_path, serde_json::to_string_pretty(&manifest)?)?; + + println!(); + println!("=== Sepolia Devnet is running! ==="); + println!(); + println!("Nodes: {}", devnet.config().node_count); + println!("Bootstrap peers: {:?}", bootstrap_addrs); + println!("Manifest: {}", manifest_path.display()); + println!(); + println!("Start ant-gui with: npm run tauri:dev"); + println!("Import wallet key in Settings > Advanced > Direct Wallet."); + println!(); + println!("Press Ctrl+C to stop."); + + tokio::signal::ctrl_c().await?; + println!("Shutting down..."); + + if manifest_path.exists() { + std::fs::remove_file(&manifest_path).ok(); + } + + Ok(()) + }) +} diff --git a/ant-core/examples/start-local-devnet.rs b/ant-core/examples/start-local-devnet.rs index 5867e5d..2a1f001 100644 --- a/ant-core/examples/start-local-devnet.rs +++ b/ant-core/examples/start-local-devnet.rs @@ -1,20 +1,31 @@ -//! Start a local devnet with EVM payments. +//! Start a local devnet with 25 nodes and EVM payments. //! -//! Launches a minimal Autonomi network (5 nodes) with an embedded Anvil -//! blockchain, writes a manifest to `/tmp/ant-devnet-manifest.json`, -//! and waits for Ctrl+C. +//! Launches an Autonomi network with an embedded Anvil blockchain, +//! writes a manifest to the ant-gui config directory, and waits for Ctrl+C. +//! +//! The ant-gui desktop app detects the manifest on startup and automatically +//! enters devnet mode. //! //! # Usage //! //! ```bash -//! cargo run --example start-local-devnet +//! cargo run --release --example start-local-devnet //! ``` use ant_core::data::LocalDevnet; +use ant_node::devnet::DevnetConfig; use std::path::PathBuf; -#[tokio::main] -async fn main() -> Result<(), Box> { +fn gui_manifest_path() -> PathBuf { + let config_dir = dirs::config_dir() + .unwrap_or_else(|| PathBuf::from(".")) + .join("autonomi") + .join("ant-gui"); + std::fs::create_dir_all(&config_dir).ok(); + config_dir.join("devnet-manifest.json") +} + +fn main() -> Result<(), Box> { tracing_subscriber::fmt() .with_env_filter( tracing_subscriber::EnvFilter::try_from_default_env() @@ -23,36 +34,46 @@ async fn main() -> Result<(), Box> { .with_writer(std::io::stderr) .init(); - // Start a minimal local devnet with EVM payments - let devnet = LocalDevnet::start_minimal().await?; - - // Write manifest so the CLI example can use it - let manifest_path = PathBuf::from("/tmp/ant-devnet-manifest.json"); - devnet.write_manifest(&manifest_path).await?; - - println!(); - println!("=== Devnet is running! ==="); - println!(); - println!("Bootstrap peers: {:?}", devnet.bootstrap_addrs()); - println!("Wallet key: {}", devnet.wallet_private_key()); - println!("Manifest: {}", manifest_path.display()); - println!(); - println!("# Upload a file:"); - println!( - "cargo run --example cli -- --devnet-manifest {} upload --file ", - manifest_path.display() - ); - println!(); - println!("# Download it back:"); - println!( - "cargo run --example cli -- --devnet-manifest {} download --datamap --output ", - manifest_path.display() - ); - println!(); - println!("Press Ctrl+C to stop."); - - tokio::signal::ctrl_c().await?; - println!("Shutting down..."); - - Ok(()) + let runtime = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .thread_stack_size(8 * 1024 * 1024) + .build()?; + + runtime.block_on(async { + let config = DevnetConfig::default(); // 25 nodes, 3 bootstrap + println!("Starting devnet with {} nodes...", config.node_count); + + let devnet = LocalDevnet::start(config).await?; + + let path = gui_manifest_path(); + devnet.write_manifest(&path).await?; + + let manifest = devnet.manifest(); + let evm = manifest.evm.as_ref().expect("EVM info present"); + + println!(); + println!("=== Devnet is running! ==="); + println!(); + println!("Nodes: {}", manifest.node_count); + println!("Bootstrap peers: {:?}", devnet.bootstrap_addrs()); + println!("Manifest: {}", path.display()); + println!(); + println!("EVM RPC: {}", evm.rpc_url); + println!("Token contract: {}", evm.payment_token_address); + println!("Vault contract: {}", evm.payment_vault_address); + println!(); + println!("Start ant-gui with: $env:VITE_DEVNET=\"1\"; npm run tauri:dev"); + println!(); + println!("Press Ctrl+C to stop."); + + tokio::signal::ctrl_c().await?; + println!("Shutting down..."); + + if path.exists() { + std::fs::remove_file(&path).ok(); + println!("Removed manifest: {}", path.display()); + } + + Ok(()) + }) }