agave_validator/commands/exit/
mod.rs

1use {
2    crate::{
3        admin_rpc_service,
4        commands::{monitor, wait_for_restart_window, FromClapArgMatches, Result},
5    },
6    clap::{value_t_or_exit, App, Arg, ArgMatches, SubCommand},
7    solana_clap_utils::input_validators::{is_parsable, is_valid_percentage},
8    std::path::Path,
9};
10
11const COMMAND: &str = "exit";
12
13const DEFAULT_MIN_IDLE_TIME: &str = "10";
14const DEFAULT_MAX_DELINQUENT_STAKE: &str = "5";
15
16#[derive(Debug, PartialEq)]
17pub struct ExitArgs {
18    pub force: bool,
19    pub monitor: bool,
20    pub min_idle_time: usize,
21    pub max_delinquent_stake: u8,
22    pub skip_new_snapshot_check: bool,
23    pub skip_health_check: bool,
24}
25
26impl FromClapArgMatches for ExitArgs {
27    fn from_clap_arg_match(matches: &ArgMatches) -> Result<Self> {
28        Ok(ExitArgs {
29            force: matches.is_present("force"),
30            monitor: matches.is_present("monitor"),
31            min_idle_time: value_t_or_exit!(matches, "min_idle_time", usize),
32            max_delinquent_stake: value_t_or_exit!(matches, "max_delinquent_stake", u8),
33            skip_new_snapshot_check: matches.is_present("skip_new_snapshot_check"),
34            skip_health_check: matches.is_present("skip_health_check"),
35        })
36    }
37}
38
39pub fn command<'a>() -> App<'a, 'a> {
40    SubCommand::with_name(COMMAND)
41        .about("Send an exit request to the validator")
42        .arg(
43            Arg::with_name("force")
44                .short("f")
45                .long("force")
46                .takes_value(false)
47                .help(
48                    "Request the validator exit immediately instead of waiting for a restart window",
49                ),
50        )
51        .arg(
52            Arg::with_name("monitor")
53                .short("m")
54                .long("monitor")
55                .takes_value(false)
56                .help("Monitor the validator after sending the exit request"),
57        )
58        .arg(
59            Arg::with_name("min_idle_time")
60                .long("min-idle-time")
61                .takes_value(true)
62                .validator(is_parsable::<usize>)
63                .value_name("MINUTES")
64                .default_value(DEFAULT_MIN_IDLE_TIME)
65                .help(
66                    "Minimum time that the validator should not be leader before restarting",
67                ),
68        )
69        .arg(
70            Arg::with_name("max_delinquent_stake")
71                .long("max-delinquent-stake")
72                .takes_value(true)
73                .validator(is_valid_percentage)
74                .default_value(DEFAULT_MAX_DELINQUENT_STAKE)
75                .value_name("PERCENT")
76                .help("The maximum delinquent stake % permitted for an exit"),
77        )
78        .arg(
79            Arg::with_name("skip_new_snapshot_check")
80                .long("skip-new-snapshot-check")
81                .help("Skip check for a new snapshot"),
82        )
83        .arg(
84            Arg::with_name("skip_health_check")
85                .long("skip-health-check")
86                .help("Skip health check"),
87        )
88}
89
90pub fn execute(matches: &ArgMatches, ledger_path: &Path) -> Result<()> {
91    let exit_args = ExitArgs::from_clap_arg_match(matches)?;
92
93    if !exit_args.force {
94        wait_for_restart_window::wait_for_restart_window(
95            ledger_path,
96            None,
97            exit_args.min_idle_time,
98            exit_args.max_delinquent_stake,
99            exit_args.skip_new_snapshot_check,
100            exit_args.skip_health_check,
101        )?;
102    }
103
104    let admin_client = admin_rpc_service::connect(ledger_path);
105    admin_rpc_service::runtime().block_on(async move { admin_client.await?.exit().await })?;
106    println!("Exit request sent");
107
108    if exit_args.monitor {
109        monitor::execute(matches, ledger_path)?;
110    }
111
112    Ok(())
113}
114
115#[cfg(test)]
116mod tests {
117    use {super::*, crate::commands::tests::verify_args_struct_by_command};
118
119    impl Default for ExitArgs {
120        fn default() -> Self {
121            ExitArgs {
122                min_idle_time: DEFAULT_MIN_IDLE_TIME
123                    .parse()
124                    .expect("invalid DEFAULT_MIN_IDLE_TIME"),
125                max_delinquent_stake: DEFAULT_MAX_DELINQUENT_STAKE
126                    .parse()
127                    .expect("invalid DEFAULT_MAX_DELINQUENT_STAKE"),
128                force: false,
129                monitor: false,
130                skip_new_snapshot_check: false,
131                skip_health_check: false,
132            }
133        }
134    }
135
136    #[test]
137    fn verify_args_struct_by_command_exit_default() {
138        verify_args_struct_by_command(command(), vec![COMMAND], ExitArgs::default());
139    }
140
141    #[test]
142    fn verify_args_struct_by_command_exit_with_force() {
143        verify_args_struct_by_command(
144            command(),
145            vec![COMMAND, "--force"],
146            ExitArgs {
147                force: true,
148                ..ExitArgs::default()
149            },
150        );
151    }
152
153    #[test]
154    fn verify_args_struct_by_command_exit_with_monitor() {
155        verify_args_struct_by_command(
156            command(),
157            vec![COMMAND, "--monitor"],
158            ExitArgs {
159                monitor: true,
160                ..ExitArgs::default()
161            },
162        );
163    }
164
165    #[test]
166    fn verify_args_struct_by_command_exit_with_min_idle_time() {
167        verify_args_struct_by_command(
168            command(),
169            vec![COMMAND, "--min-idle-time", "60"],
170            ExitArgs {
171                min_idle_time: 60,
172                ..ExitArgs::default()
173            },
174        );
175    }
176
177    #[test]
178    fn verify_args_struct_by_command_exit_with_max_delinquent_stake() {
179        verify_args_struct_by_command(
180            command(),
181            vec![COMMAND, "--max-delinquent-stake", "10"],
182            ExitArgs {
183                max_delinquent_stake: 10,
184                ..ExitArgs::default()
185            },
186        );
187    }
188
189    #[test]
190    fn verify_args_struct_by_command_exit_with_skip_new_snapshot_check() {
191        verify_args_struct_by_command(
192            command(),
193            vec![COMMAND, "--skip-new-snapshot-check"],
194            ExitArgs {
195                skip_new_snapshot_check: true,
196                ..ExitArgs::default()
197            },
198        );
199    }
200
201    #[test]
202    fn verify_args_struct_by_command_exit_with_skip_health_check() {
203        verify_args_struct_by_command(
204            command(),
205            vec![COMMAND, "--skip-health-check"],
206            ExitArgs {
207                skip_health_check: true,
208                ..ExitArgs::default()
209            },
210        );
211    }
212}