cuprated/
commands.rs

1//! Commands
2//!
3//! `cuprated` [`Command`] definition and handling.
4use std::{io, thread::sleep, time::Duration};
5
6use clap::{builder::TypedValueParser, Parser, ValueEnum};
7use tokio::sync::mpsc;
8use tower::{Service, ServiceExt};
9use tracing::level_filters::LevelFilter;
10
11use cuprate_consensus_context::{
12    BlockChainContextRequest, BlockChainContextResponse, BlockchainContextService,
13};
14use cuprate_helper::time::secs_to_hms;
15
16use crate::{
17    constants::PANIC_CRITICAL_SERVICE_ERROR,
18    logging::{self, CupratedTracingFilter},
19    statics,
20};
21
22/// A command received from [`io::stdin`].
23#[derive(Debug, Parser)]
24#[command(
25    multicall = true,
26    subcommand_required = true,
27    rename_all = "snake_case",
28    help_template = "{all-args}",
29    arg_required_else_help = true,
30    disable_help_flag = true
31)]
32pub enum Command {
33    /// Change the log output.
34    #[command(arg_required_else_help = true)]
35    SetLog {
36        /// The minimum log level that will be displayed.
37        #[arg(
38          short, long,
39          value_parser = clap::builder::PossibleValuesParser::new(["off", "trace", "debug", "info", "warn", "error"])
40            .map(|s| s.parse::<LevelFilter>().unwrap()),
41        )]
42        level: Option<LevelFilter>,
43        /// The logging output target to change.
44        #[arg(value_enum, default_value_t)]
45        output_target: OutputTarget,
46    },
47
48    /// Print status information on `cuprated`.
49    Status,
50
51    /// Print the height of first block not contained in the fast sync hashes.
52    FastSyncStopHeight,
53}
54
55/// The log output target.
56#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
57pub enum OutputTarget {
58    /// The stdout logging output.
59    #[default]
60    Stdout,
61    /// The file appender logging output.
62    File,
63}
64
65/// The [`Command`] listener loop.
66pub fn command_listener(incoming_commands: mpsc::Sender<Command>) -> ! {
67    let mut stdin = io::stdin();
68    let mut line = String::new();
69
70    loop {
71        line.clear();
72
73        if let Err(e) = stdin.read_line(&mut line) {
74            eprintln!("Failed to read from stdin: {e}");
75            sleep(Duration::from_secs(1));
76            continue;
77        }
78
79        match Command::try_parse_from(line.split_whitespace()) {
80            Ok(command) => drop(
81                incoming_commands
82                    .blocking_send(command)
83                    .inspect_err(|err| eprintln!("Failed to send command: {err}")),
84            ),
85            Err(err) => err.print().unwrap(),
86        }
87    }
88}
89
90/// The [`Command`] handler loop.
91pub async fn io_loop(
92    mut incoming_commands: mpsc::Receiver<Command>,
93    mut context_service: BlockchainContextService,
94) {
95    loop {
96        let Some(command) = incoming_commands.recv().await else {
97            tracing::warn!("Shutting down io_loop command channel closed.");
98            return;
99        };
100
101        match command {
102            Command::SetLog {
103                level,
104                output_target,
105            } => {
106                let modify_output = |filter: &mut CupratedTracingFilter| {
107                    if let Some(level) = level {
108                        filter.level = level;
109                    }
110                    println!("NEW LOG FILTER: {filter}");
111                };
112
113                match output_target {
114                    OutputTarget::File => logging::modify_file_output(modify_output),
115                    OutputTarget::Stdout => logging::modify_stdout_output(modify_output),
116                }
117            }
118            Command::Status => {
119                let context = context_service.blockchain_context();
120
121                let uptime = statics::START_INSTANT.elapsed().unwrap_or_default();
122
123                let (h, m, s) = secs_to_hms(uptime.as_secs());
124                let height = context.chain_height;
125                let top_hash = hex::encode(context.top_hash);
126
127                println!("STATUS:\n  uptime: {h}h {m}m {s}s,\n  height: {height},\n  top_hash: {top_hash}");
128            }
129            Command::FastSyncStopHeight => {
130                let stop_height = cuprate_fast_sync::fast_sync_stop_height();
131
132                println!("{stop_height}");
133            }
134        }
135    }
136}