use std::{ net::{IpAddr, SocketAddr}, path::PathBuf, }; use anyhow::Result; use clap::Parser; use log::{error, info}; use tokio_util::sync::CancellationToken; #[derive(Debug, Parser)] #[clap(version)] struct Args { #[clap(name = "config")] config_path: PathBuf, #[clap(short, long, default_value = "3040")] port: u16, /// Interface the RPC server binds to. The RPC has no caller auth — /// bind loopback unless the port is firewalled. #[clap(long, default_value = "0.0.0.0")] listen_address: IpAddr, /// Override the config's home directory (`RocksDB` + bedrock signing key), /// so multiple instances can share one config file. #[clap(long)] home: Option, } #[tokio::main] #[expect( clippy::integer_division_remainder_used, reason = "Generated by select! macro, can't be easily rewritten to avoid this lint" )] async fn main() -> Result<()> { env_logger::init(); let Args { config_path, port, listen_address, home, } = Args::parse(); // TODO: handle this cancellation token more gracefully within Sequencer service // similar to how we do in Indexer let cancellation_token = listen_for_shutdown_signal(); let mut config = sequencer_service::SequencerConfig::from_path(&config_path)?; if let Some(home) = home { config.home = home; } let sequencer_handle = sequencer_service::run(config, SocketAddr::new(listen_address, port)).await?; tokio::select! { () = cancellation_token.cancelled() => { info!("Shutting down sequencer..."); } Err(err) = sequencer_handle.failed() => { error!("Sequencer failed unexpectedly: {err}"); } } info!("Sequencer shutdown complete"); Ok(()) } fn listen_for_shutdown_signal() -> CancellationToken { let cancellation_token = CancellationToken::new(); let cancellation_token_clone = cancellation_token.clone(); tokio::spawn(async move { if let Err(err) = tokio::signal::ctrl_c().await { error!("Failed to listen for Ctrl-C signal: {err}"); return; } info!("Received Ctrl-C signal"); cancellation_token_clone.cancel(); }); cancellation_token }