1use 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#[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 #[command(arg_required_else_help = true)]
35 SetLog {
36 #[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 #[arg(value_enum, default_value_t)]
45 output_target: OutputTarget,
46 },
47
48 Status,
50
51 FastSyncStopHeight,
53
54 PopBlocks { numb_blocks: usize },
56}
57
58#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
60pub enum OutputTarget {
61 #[default]
63 Stdout,
64 File,
66}
67
68pub 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
93pub 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}