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    /// Pop blocks from the top of the blockchain.
55    PopBlocks { numb_blocks: usize },
56}
57
58/// The log output target.
59#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
60pub enum OutputTarget {
61    /// The stdout logging output.
62    #[default]
63    Stdout,
64    /// The file appender logging output.
65    File,
66}
67
68/// The [`Command`] listener loop.
69pub fn command_listener(incoming_commands: mpsc::Sender<Command>) -> ! {
70    let mut stdin = io::stdin();
71    let mut line = String::new();
72
73    loop {
74        line.clear();
75
76        if let Err(e) = stdin.read_line(&mut line) {
77            eprintln!("Failed to read from stdin: {e}");
78            sleep(Duration::from_secs(1));
79            continue;
80        }
81
82        match Command::try_parse_from(line.split_whitespace()) {
83            Ok(command) => drop(
84                incoming_commands
85                    .blocking_send(command)
86                    .inspect_err(|err| eprintln!("Failed to send command: {err}")),
87            ),
88            Err(err) => err.print().unwrap(),
89        }
90    }
91}
92
93/// The [`Command`] handler loop.
94pub async fn io_loop(
95    mut incoming_commands: mpsc::Receiver<Command>,
96    mut context_service: BlockchainContextService,
97) {
98    loop {
99        let Some(command) = incoming_commands.recv().await else {
100            tracing::warn!("Shutting down io_loop command channel closed.");
101            return;
102        };
103
104        match command {
105            Command::SetLog {
106                level,
107                output_target,
108            } => {
109                let modify_output = |filter: &mut CupratedTracingFilter| {
110                    if let Some(level) = level {
111                        filter.level = level;
112                    }
113                    println!("NEW LOG FILTER: {filter}");
114                };
115
116                match output_target {
117                    OutputTarget::File => logging::modify_file_output(modify_output),
118                    OutputTarget::Stdout => logging::modify_stdout_output(modify_output),
119                }
120            }
121            Command::Status => {
122                let context = context_service.blockchain_context();
123
124                let uptime = statics::START_INSTANT.elapsed().unwrap_or_default();
125
126                let (h, m, s) = secs_to_hms(uptime.as_secs());
127                let height = context.chain_height;
128                let top_hash = hex::encode(context.top_hash);
129
130                println!("STATUS:\n  uptime: {h}h {m}m {s}s,\n  height: {height},\n  top_hash: {top_hash}");
131            }
132            Command::FastSyncStopHeight => {
133                let stop_height = cuprate_fast_sync::fast_sync_stop_height();
134
135                println!("{stop_height}");
136            }
137            Command::PopBlocks { numb_blocks } => {
138                tracing::info!("Popping {numb_blocks} blocks.");
139                let res = crate::blockchain::interface::pop_blocks(numb_blocks).await;
140
141                match res {
142                    Ok(()) => println!("Popped {numb_blocks} blocks."),
143                    Err(e) => println!("Failed to pop blocks: {e}"),
144                }
145            }
146        }
147    }
148}