solana_cli/
cluster_query.rs

1use {
2    crate::{
3        cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult},
4        compute_budget::{
5            simulate_for_compute_unit_limit, ComputeUnitConfig, WithComputeUnitConfig,
6        },
7        feature::get_feature_activation_epoch,
8        spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
9    },
10    clap::{value_t, value_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand},
11    console::style,
12    crossbeam_channel::unbounded,
13    serde::{Deserialize, Serialize},
14    solana_account::{from_account, state_traits::StateMut, Account},
15    solana_clap_utils::{
16        compute_budget::{compute_unit_price_arg, ComputeUnitLimit, COMPUTE_UNIT_PRICE_ARG},
17        input_parsers::*,
18        input_validators::*,
19        keypair::DefaultSigner,
20        offline::{blockhash_arg, BLOCKHASH_ARG},
21    },
22    solana_cli_output::{
23        cli_version::CliVersion,
24        display::{
25            build_balance_message, format_labeled_address, new_spinner_progress_bar,
26            writeln_name_value,
27        },
28        *,
29    },
30    solana_clock::{self as clock, Clock, Epoch, Slot},
31    solana_commitment_config::CommitmentConfig,
32    solana_hash::Hash,
33    solana_message::Message,
34    solana_nonce::state::State as NonceState,
35    solana_pubkey::Pubkey,
36    solana_pubsub_client::pubsub_client::PubsubClient,
37    solana_remote_wallet::remote_wallet::RemoteWalletManager,
38    solana_rent::Rent,
39    solana_rpc_client::rpc_client::{GetConfirmedSignaturesForAddress2Config, RpcClient},
40    solana_rpc_client_api::{
41        client_error::ErrorKind as ClientErrorKind,
42        config::{
43            RpcAccountInfoConfig, RpcBlockConfig, RpcGetVoteAccountsConfig,
44            RpcLargestAccountsConfig, RpcLargestAccountsFilter, RpcProgramAccountsConfig,
45            RpcTransactionConfig, RpcTransactionLogsConfig, RpcTransactionLogsFilter,
46        },
47        filter::{Memcmp, RpcFilterType},
48        request::DELINQUENT_VALIDATOR_SLOT_DISTANCE,
49        response::{RpcPerfSample, RpcPrioritizationFee, SlotInfo},
50    },
51    solana_sdk_ids::sysvar::{self, stake_history},
52    solana_signature::Signature,
53    solana_slot_history::{self as slot_history, SlotHistory},
54    solana_stake_interface::{self as stake, state::StakeStateV2},
55    solana_system_interface::{instruction as system_instruction, MAX_PERMITTED_DATA_LENGTH},
56    solana_tps_client::TpsClient,
57    solana_transaction::Transaction,
58    solana_transaction_status::{
59        EncodableWithMeta, EncodedConfirmedTransactionWithStatusMeta, UiTransactionEncoding,
60    },
61    solana_vote_program::vote_state::VoteStateV4,
62    std::{
63        collections::{BTreeMap, HashMap, HashSet, VecDeque},
64        fmt,
65        num::Saturating,
66        rc::Rc,
67        str::FromStr,
68        sync::{
69            atomic::{AtomicBool, Ordering},
70            Arc,
71        },
72        thread::sleep,
73        time::{Duration, Instant, SystemTime, UNIX_EPOCH},
74    },
75    thiserror::Error,
76};
77
78const DEFAULT_RPC_PORT_STR: &str = "8899";
79
80pub trait ClusterQuerySubCommands {
81    fn cluster_query_subcommands(self) -> Self;
82}
83
84impl ClusterQuerySubCommands for App<'_, '_> {
85    fn cluster_query_subcommands(self) -> Self {
86        self.subcommand(
87            SubCommand::with_name("block")
88                .about("Get a confirmed block")
89                .arg(
90                    Arg::with_name("slot")
91                        .long("slot")
92                        .validator(is_slot)
93                        .value_name("SLOT")
94                        .takes_value(true)
95                        .index(1),
96                ),
97        )
98        .subcommand(
99            SubCommand::with_name("recent-prioritization-fees")
100                .about("Get recent prioritization fees")
101                .arg(
102                    Arg::with_name("accounts")
103                        .value_name("ACCOUNTS")
104                        .takes_value(true)
105                        .multiple(true)
106                        .index(1)
107                        .help(
108                            "A list of accounts which if provided the fee response will represent \
109                             the fee to land a transaction with those accounts as writable",
110                        ),
111                )
112                .arg(
113                    Arg::with_name("limit_num_slots")
114                        .long("limit-num-slots")
115                        .value_name("SLOTS")
116                        .takes_value(true)
117                        .help("Limit the number of slots to the last <N> slots"),
118                ),
119        )
120        .subcommand(
121            SubCommand::with_name("catchup")
122                .about("Wait for a validator to catch up to the cluster")
123                .arg(pubkey!(
124                    Arg::with_name("node_pubkey")
125                        .index(1)
126                        .value_name("OUR_VALIDATOR_PUBKEY")
127                        .required(false),
128                    "Identity of the validator."
129                ))
130                .arg(
131                    Arg::with_name("node_json_rpc_url")
132                        .index(2)
133                        .value_name("OUR_URL")
134                        .takes_value(true)
135                        .validator(is_url)
136                        .help(
137                            "JSON RPC URL for validator, which is useful for validators with a \
138                             private RPC service",
139                        ),
140                )
141                .arg(
142                    Arg::with_name("follow")
143                        .long("follow")
144                        .takes_value(false)
145                        .help("Continue reporting progress even after the validator has caught up"),
146                )
147                .arg(
148                    Arg::with_name("our_localhost")
149                        .long("our-localhost")
150                        .takes_value(false)
151                        .value_name("PORT")
152                        .default_value(DEFAULT_RPC_PORT_STR)
153                        .validator(is_port)
154                        .help(
155                            "Guess Identity pubkey and validator rpc node assuming local \
156                             (possibly private) validator",
157                        ),
158                )
159                .arg(Arg::with_name("log").long("log").takes_value(false).help(
160                    "Don't update the progress inplace; instead show updates with its own new \
161                     lines",
162                )),
163        )
164        .subcommand(SubCommand::with_name("cluster-date").about(
165            "Get current cluster date, computed from genesis creation time and network time",
166        ))
167        .subcommand(
168            SubCommand::with_name("cluster-version")
169                .about("Get the version of the cluster entrypoint"),
170        )
171        .subcommand(
172            SubCommand::with_name("first-available-block")
173                .about("Get the first available block in the storage"),
174        )
175        .subcommand(
176            SubCommand::with_name("block-time")
177                .about("Get estimated production time of a block")
178                .alias("get-block-time")
179                .arg(
180                    Arg::with_name("slot")
181                        .index(1)
182                        .takes_value(true)
183                        .value_name("SLOT")
184                        .help("Slot number of the block to query"),
185                ),
186        )
187        .subcommand(
188            SubCommand::with_name("leader-schedule")
189                .about("Display leader schedule")
190                .arg(
191                    Arg::with_name("epoch")
192                        .long("epoch")
193                        .takes_value(true)
194                        .value_name("EPOCH")
195                        .validator(is_epoch)
196                        .help("Epoch to show leader schedule for [default: current]"),
197                ),
198        )
199        .subcommand(
200            SubCommand::with_name("epoch-info")
201                .about("Get information about the current epoch")
202                .alias("get-epoch-info"),
203        )
204        .subcommand(
205            SubCommand::with_name("genesis-hash")
206                .about("Get the genesis hash")
207                .alias("get-genesis-hash"),
208        )
209        .subcommand(
210            SubCommand::with_name("slot")
211                .about("Get current slot")
212                .alias("get-slot"),
213        )
214        .subcommand(SubCommand::with_name("block-height").about("Get current block height"))
215        .subcommand(SubCommand::with_name("epoch").about("Get current epoch"))
216        .subcommand(
217            SubCommand::with_name("largest-accounts")
218                .about("Get addresses of largest cluster accounts")
219                .arg(
220                    Arg::with_name("circulating")
221                        .long("circulating")
222                        .takes_value(false)
223                        .help("Filter address list to only circulating accounts"),
224                )
225                .arg(
226                    Arg::with_name("non_circulating")
227                        .long("non-circulating")
228                        .takes_value(false)
229                        .conflicts_with("circulating")
230                        .help("Filter address list to only non-circulating accounts"),
231                ),
232        )
233        .subcommand(
234            SubCommand::with_name("supply")
235                .about("Get information about the cluster supply of SOL")
236                .arg(
237                    Arg::with_name("print_accounts")
238                        .long("print-accounts")
239                        .takes_value(false)
240                        .help("Print list of non-circulating account addresses"),
241                ),
242        )
243        .subcommand(
244            SubCommand::with_name("total-supply")
245                .about("Get total number of SOL")
246                .setting(AppSettings::Hidden),
247        )
248        .subcommand(
249            SubCommand::with_name("transaction-count")
250                .about("Get current transaction count")
251                .alias("get-transaction-count"),
252        )
253        .subcommand(
254            SubCommand::with_name("ping")
255                .about("Submit transactions sequentially")
256                .arg(
257                    Arg::with_name("interval")
258                        .short("i")
259                        .long("interval")
260                        .value_name("SECONDS")
261                        .takes_value(true)
262                        .default_value("2")
263                        .help("Wait interval seconds between submitting the next transaction"),
264                )
265                .arg(
266                    Arg::with_name("count")
267                        .short("c")
268                        .long("count")
269                        .value_name("NUMBER")
270                        .takes_value(true)
271                        .help("Stop after submitting count transactions"),
272                )
273                .arg(
274                    Arg::with_name("print_timestamp")
275                        .short("D")
276                        .long("print-timestamp")
277                        .takes_value(false)
278                        .help(
279                            "Print timestamp (unix time + microseconds as in gettimeofday) before \
280                             each line",
281                        ),
282                )
283                .arg(
284                    Arg::with_name("timeout")
285                        .short("t")
286                        .long("timeout")
287                        .value_name("SECONDS")
288                        .takes_value(true)
289                        .default_value("15")
290                        .help("Wait up to timeout seconds for transaction confirmation"),
291                )
292                .arg(compute_unit_price_arg())
293                .arg(blockhash_arg()),
294        )
295        .subcommand(
296            SubCommand::with_name("live-slots")
297                .about("Show information about the current slot progression"),
298        )
299        .subcommand(
300            SubCommand::with_name("logs")
301                .about("Stream transaction logs")
302                .arg(pubkey!(
303                    Arg::with_name("address").index(1).value_name("ADDRESS"),
304                    "Account to monitor [default: monitor all transactions except for votes]."
305                ))
306                .arg(
307                    Arg::with_name("include_votes")
308                        .long("include-votes")
309                        .takes_value(false)
310                        .conflicts_with("address")
311                        .help("Include vote transactions when monitoring all transactions"),
312                ),
313        )
314        .subcommand(
315            SubCommand::with_name("block-production")
316                .about("Show information about block production")
317                .alias("show-block-production")
318                .arg(
319                    Arg::with_name("epoch")
320                        .long("epoch")
321                        .takes_value(true)
322                        .help("Epoch to show block production for [default: current epoch]"),
323                )
324                .arg(
325                    Arg::with_name("slot_limit")
326                        .long("slot-limit")
327                        .takes_value(true)
328                        .help(
329                            "Limit results to this many slots from the end of the epoch [default: \
330                             full epoch]",
331                        ),
332                ),
333        )
334        .subcommand(
335            SubCommand::with_name("gossip")
336                .about("Show the current gossip network nodes")
337                .alias("show-gossip"),
338        )
339        .subcommand(
340            SubCommand::with_name("stakes")
341                .about("Show stake account information")
342                .arg(
343                    Arg::with_name("lamports")
344                        .long("lamports")
345                        .takes_value(false)
346                        .help("Display balance in lamports instead of SOL"),
347                )
348                .arg(pubkey!(
349                    Arg::with_name("vote_account_pubkeys")
350                        .index(1)
351                        .value_name("VALIDATOR_ACCOUNT_PUBKEYS")
352                        .multiple(true),
353                    "Only show stake accounts delegated to the provided pubkeys. Accepts both \
354                     vote and identity pubkeys."
355                ))
356                .arg(pubkey!(
357                    Arg::with_name("withdraw_authority")
358                        .value_name("PUBKEY")
359                        .long("withdraw-authority"),
360                    "Only show stake accounts with the provided withdraw authority."
361                )),
362        )
363        .subcommand(
364            SubCommand::with_name("validators")
365                .about("Show summary information about the current validators")
366                .alias("show-validators")
367                .arg(
368                    Arg::with_name("lamports")
369                        .long("lamports")
370                        .takes_value(false)
371                        .help("Display balance in lamports instead of SOL"),
372                )
373                .arg(
374                    Arg::with_name("number")
375                        .long("number")
376                        .short("n")
377                        .takes_value(false)
378                        .help("Number the validators"),
379                )
380                .arg(
381                    Arg::with_name("reverse")
382                        .long("reverse")
383                        .short("r")
384                        .takes_value(false)
385                        .help("Reverse order while sorting"),
386                )
387                .arg(
388                    Arg::with_name("sort")
389                        .long("sort")
390                        .takes_value(true)
391                        .possible_values(&[
392                            "delinquent",
393                            "commission",
394                            "credits",
395                            "identity",
396                            "last-vote",
397                            "root",
398                            "skip-rate",
399                            "stake",
400                            "version",
401                            "vote-account",
402                        ])
403                        .default_value("stake")
404                        .help("Sort order (does not affect JSON output)"),
405                )
406                .arg(
407                    Arg::with_name("keep_unstaked_delinquents")
408                        .long("keep-unstaked-delinquents")
409                        .takes_value(false)
410                        .help("Don't discard unstaked, delinquent validators"),
411                )
412                .arg(
413                    Arg::with_name("delinquent_slot_distance")
414                        .long("delinquent-slot-distance")
415                        .takes_value(true)
416                        .value_name("SLOT_DISTANCE")
417                        .validator(is_slot)
418                        .help(concatcp!(
419                            "Minimum slot distance from the tip to consider a validator \
420                             delinquent [default: ",
421                            DELINQUENT_VALIDATOR_SLOT_DISTANCE,
422                            "]",
423                        )),
424                ),
425        )
426        .subcommand(
427            SubCommand::with_name("transaction-history")
428                .about(
429                    "Show historical transactions affecting the given address from newest to \
430                     oldest",
431                )
432                .arg(pubkey!(
433                    Arg::with_name("address")
434                        .index(1)
435                        .value_name("ADDRESS")
436                        .required(true),
437                    "Account to query for transactions."
438                ))
439                .arg(
440                    Arg::with_name("limit")
441                        .long("limit")
442                        .takes_value(true)
443                        .value_name("LIMIT")
444                        .validator(is_slot)
445                        .default_value("1000")
446                        .help("Maximum number of transaction signatures to return"),
447                )
448                .arg(
449                    Arg::with_name("before")
450                        .long("before")
451                        .value_name("TRANSACTION_SIGNATURE")
452                        .takes_value(true)
453                        .help("Start with the first signature older than this one"),
454                )
455                .arg(
456                    Arg::with_name("until")
457                        .long("until")
458                        .value_name("TRANSACTION_SIGNATURE")
459                        .takes_value(true)
460                        .help(
461                            "List until this transaction signature, if found before limit reached",
462                        ),
463                )
464                .arg(
465                    Arg::with_name("show_transactions")
466                        .long("show-transactions")
467                        .takes_value(false)
468                        .help("Display the full transactions"),
469                ),
470        )
471        .subcommand(
472            SubCommand::with_name("wait-for-max-stake")
473                .about(
474                    "Wait for the max stake of any one node to drop below a percentage of total.",
475                )
476                .arg(
477                    Arg::with_name("max_percent")
478                        .long("max-percent")
479                        .value_name("PERCENT")
480                        .takes_value(true)
481                        .index(1),
482                ),
483        )
484        .subcommand(
485            SubCommand::with_name("rent")
486                .about("Calculate rent-exempt-minimum value for a given account data field length.")
487                .arg(
488                    Arg::with_name("data_length")
489                        .index(1)
490                        .value_name("DATA_LENGTH_OR_MONIKER")
491                        .required(true)
492                        .validator(|s| {
493                            RentLengthValue::from_str(&s)
494                                .map(|_| ())
495                                .map_err(|e| e.to_string())
496                        })
497                        .help(
498                            "Length of data field in the account to calculate rent for, or \
499                             moniker: [nonce, stake, system, vote]",
500                        ),
501                )
502                .arg(
503                    Arg::with_name("lamports")
504                        .long("lamports")
505                        .takes_value(false)
506                        .help("Display rent in lamports instead of SOL"),
507                ),
508        )
509    }
510}
511
512pub fn parse_catchup(
513    matches: &ArgMatches<'_>,
514    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
515) -> Result<CliCommandInfo, CliError> {
516    let node_pubkey = pubkey_of_signer(matches, "node_pubkey", wallet_manager)?;
517    let mut our_localhost_port = value_t!(matches, "our_localhost", u16).ok();
518    // if there is no explicitly specified --our-localhost,
519    // disable the guess mode (= our_localhost_port)
520    if matches.occurrences_of("our_localhost") == 0 {
521        our_localhost_port = None
522    }
523    let node_json_rpc_url = value_t!(matches, "node_json_rpc_url", String).ok();
524    // requirement of node_pubkey is relaxed only if our_localhost_port
525    if our_localhost_port.is_none() && node_pubkey.is_none() {
526        return Err(CliError::BadParameter(
527            "OUR_VALIDATOR_PUBKEY (and possibly OUR_URL) must be specified unless --our-localhost \
528             is given"
529                .into(),
530        ));
531    }
532    let follow = matches.is_present("follow");
533    let log = matches.is_present("log");
534    Ok(CliCommandInfo::without_signers(CliCommand::Catchup {
535        node_pubkey,
536        node_json_rpc_url,
537        follow,
538        our_localhost_port,
539        log,
540    }))
541}
542
543pub fn parse_cluster_ping(
544    matches: &ArgMatches<'_>,
545    default_signer: &DefaultSigner,
546    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
547) -> Result<CliCommandInfo, CliError> {
548    let interval = Duration::from_secs(value_t_or_exit!(matches, "interval", u64));
549    let count = if matches.is_present("count") {
550        Some(value_t_or_exit!(matches, "count", u64))
551    } else {
552        None
553    };
554    let timeout = Duration::from_secs(value_t_or_exit!(matches, "timeout", u64));
555    let blockhash = value_of(matches, BLOCKHASH_ARG.name);
556    let print_timestamp = matches.is_present("print_timestamp");
557    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
558    Ok(CliCommandInfo {
559        command: CliCommand::Ping {
560            interval,
561            count,
562            timeout,
563            blockhash,
564            print_timestamp,
565            compute_unit_price,
566        },
567        signers: vec![default_signer.signer_from_path(matches, wallet_manager)?],
568    })
569}
570
571pub fn parse_get_block(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
572    let slot = value_of(matches, "slot");
573    Ok(CliCommandInfo::without_signers(CliCommand::GetBlock {
574        slot,
575    }))
576}
577
578pub fn parse_get_recent_prioritization_fees(
579    matches: &ArgMatches<'_>,
580) -> Result<CliCommandInfo, CliError> {
581    let accounts = values_of(matches, "accounts").unwrap_or(vec![]);
582    let limit_num_slots = value_of(matches, "limit_num_slots");
583    Ok(CliCommandInfo::without_signers(
584        CliCommand::GetRecentPrioritizationFees {
585            accounts,
586            limit_num_slots,
587        },
588    ))
589}
590
591pub fn parse_get_block_time(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
592    let slot = value_of(matches, "slot");
593    Ok(CliCommandInfo::without_signers(CliCommand::GetBlockTime {
594        slot,
595    }))
596}
597
598pub fn parse_get_epoch(_matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
599    Ok(CliCommandInfo::without_signers(CliCommand::GetEpoch))
600}
601
602pub fn parse_get_epoch_info(_matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
603    Ok(CliCommandInfo::without_signers(CliCommand::GetEpochInfo))
604}
605
606pub fn parse_get_slot(_matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
607    Ok(CliCommandInfo::without_signers(CliCommand::GetSlot))
608}
609
610pub fn parse_get_block_height(_matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
611    Ok(CliCommandInfo::without_signers(CliCommand::GetBlockHeight))
612}
613
614pub fn parse_largest_accounts(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
615    let filter = if matches.is_present("circulating") {
616        Some(RpcLargestAccountsFilter::Circulating)
617    } else if matches.is_present("non_circulating") {
618        Some(RpcLargestAccountsFilter::NonCirculating)
619    } else {
620        None
621    };
622    Ok(CliCommandInfo::without_signers(
623        CliCommand::LargestAccounts { filter },
624    ))
625}
626
627pub fn parse_supply(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
628    let print_accounts = matches.is_present("print_accounts");
629    Ok(CliCommandInfo::without_signers(CliCommand::Supply {
630        print_accounts,
631    }))
632}
633
634pub fn parse_total_supply(_matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
635    Ok(CliCommandInfo::without_signers(CliCommand::TotalSupply))
636}
637
638pub fn parse_get_transaction_count(_matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
639    Ok(CliCommandInfo::without_signers(
640        CliCommand::GetTransactionCount,
641    ))
642}
643
644pub fn parse_show_stakes(
645    matches: &ArgMatches<'_>,
646    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
647) -> Result<CliCommandInfo, CliError> {
648    let use_lamports_unit = matches.is_present("lamports");
649    let vote_account_pubkeys =
650        pubkeys_of_multiple_signers(matches, "vote_account_pubkeys", wallet_manager)?;
651    let withdraw_authority = pubkey_of(matches, "withdraw_authority");
652    Ok(CliCommandInfo::without_signers(CliCommand::ShowStakes {
653        use_lamports_unit,
654        vote_account_pubkeys,
655        withdraw_authority,
656    }))
657}
658
659pub fn parse_show_validators(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
660    let use_lamports_unit = matches.is_present("lamports");
661    let number_validators = matches.is_present("number");
662    let reverse_sort = matches.is_present("reverse");
663    let keep_unstaked_delinquents = matches.is_present("keep_unstaked_delinquents");
664    let delinquent_slot_distance = value_of(matches, "delinquent_slot_distance");
665
666    let sort_order = match value_t_or_exit!(matches, "sort", String).as_str() {
667        "delinquent" => CliValidatorsSortOrder::Delinquent,
668        "commission" => CliValidatorsSortOrder::Commission,
669        "credits" => CliValidatorsSortOrder::EpochCredits,
670        "identity" => CliValidatorsSortOrder::Identity,
671        "last-vote" => CliValidatorsSortOrder::LastVote,
672        "root" => CliValidatorsSortOrder::Root,
673        "skip-rate" => CliValidatorsSortOrder::SkipRate,
674        "stake" => CliValidatorsSortOrder::Stake,
675        "vote-account" => CliValidatorsSortOrder::VoteAccount,
676        "version" => CliValidatorsSortOrder::Version,
677        _ => unreachable!(),
678    };
679
680    Ok(CliCommandInfo::without_signers(
681        CliCommand::ShowValidators {
682            use_lamports_unit,
683            sort_order,
684            reverse_sort,
685            number_validators,
686            keep_unstaked_delinquents,
687            delinquent_slot_distance,
688        },
689    ))
690}
691
692pub fn parse_transaction_history(
693    matches: &ArgMatches<'_>,
694    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
695) -> Result<CliCommandInfo, CliError> {
696    let address = pubkey_of_signer(matches, "address", wallet_manager)?.unwrap();
697
698    let before = match matches.value_of("before") {
699        Some(signature) => Some(
700            signature
701                .parse()
702                .map_err(|err| CliError::BadParameter(format!("Invalid signature: {err}")))?,
703        ),
704        None => None,
705    };
706    let until = match matches.value_of("until") {
707        Some(signature) => Some(
708            signature
709                .parse()
710                .map_err(|err| CliError::BadParameter(format!("Invalid signature: {err}")))?,
711        ),
712        None => None,
713    };
714    let limit = value_t_or_exit!(matches, "limit", usize);
715    let show_transactions = matches.is_present("show_transactions");
716
717    Ok(CliCommandInfo::without_signers(
718        CliCommand::TransactionHistory {
719            address,
720            before,
721            until,
722            limit,
723            show_transactions,
724        },
725    ))
726}
727
728pub fn process_catchup(
729    rpc_client: &RpcClient,
730    config: &CliConfig,
731    node_pubkey: Option<Pubkey>,
732    mut node_json_rpc_url: Option<String>,
733    follow: bool,
734    our_localhost_port: Option<u16>,
735    log: bool,
736) -> ProcessResult {
737    let sleep_interval = Duration::from_secs(2);
738
739    let progress_bar = new_spinner_progress_bar();
740    progress_bar.set_message("Connecting...");
741
742    if let Some(our_localhost_port) = our_localhost_port {
743        let gussed_default = Some(format!("http://localhost:{our_localhost_port}"));
744        if node_json_rpc_url.is_some() && node_json_rpc_url != gussed_default {
745            // go to new line to leave this message on console
746            println!(
747                "Preferring explicitly given rpc ({}) as us, although --our-localhost is given\n",
748                node_json_rpc_url.as_ref().unwrap()
749            );
750        } else {
751            node_json_rpc_url = gussed_default;
752        }
753    }
754
755    let (node_client, node_pubkey) = if our_localhost_port.is_some() {
756        let client = RpcClient::new(node_json_rpc_url.unwrap());
757        let guessed_default = Some(client.get_identity()?);
758        (
759            client,
760            (if node_pubkey.is_some() && node_pubkey != guessed_default {
761                // go to new line to leave this message on console
762                println!(
763                    "Preferring explicitly given node pubkey ({}) as us, although --our-localhost \
764                     is given\n",
765                    node_pubkey.unwrap()
766                );
767                node_pubkey
768            } else {
769                guessed_default
770            })
771            .unwrap(),
772        )
773    } else if let Some(node_pubkey) = node_pubkey {
774        if let Some(node_json_rpc_url) = node_json_rpc_url {
775            (RpcClient::new(node_json_rpc_url), node_pubkey)
776        } else {
777            let rpc_addr = loop {
778                let cluster_nodes = rpc_client.get_cluster_nodes()?;
779                if let Some(contact_info) = cluster_nodes
780                    .iter()
781                    .find(|contact_info| contact_info.pubkey == node_pubkey.to_string())
782                {
783                    if let Some(rpc_addr) = contact_info.rpc {
784                        break rpc_addr;
785                    }
786                    progress_bar.set_message(format!("RPC service not found for {node_pubkey}"));
787                } else {
788                    progress_bar
789                        .set_message(format!("Contact information not found for {node_pubkey}"));
790                }
791                sleep(sleep_interval);
792            };
793
794            (RpcClient::new_socket(rpc_addr), node_pubkey)
795        }
796    } else {
797        unreachable!()
798    };
799
800    let reported_node_pubkey = loop {
801        match node_client.get_identity() {
802            Ok(reported_node_pubkey) => break reported_node_pubkey,
803            Err(err) => {
804                if let ClientErrorKind::Reqwest(err) = err.kind() {
805                    progress_bar.set_message(format!("Connection failed: {err}"));
806                    sleep(sleep_interval);
807                    continue;
808                }
809                return Err(Box::new(err));
810            }
811        }
812    };
813
814    if reported_node_pubkey != node_pubkey {
815        return Err(format!(
816            "The identity reported by node RPC URL does not match.  Expected: {node_pubkey:?}.  \
817             Reported: {reported_node_pubkey:?}"
818        )
819        .into());
820    }
821
822    if rpc_client.get_identity()? == node_pubkey {
823        return Err(
824            "Both RPC URLs reference the same node, unable to monitor for catchup.  Try a \
825             different --url"
826                .into(),
827        );
828    }
829
830    let mut previous_rpc_slot = i64::MAX;
831    let mut previous_slot_distance: i64 = 0;
832    let mut retry_count: u64 = 0;
833    let max_retry_count = 5;
834    let mut get_slot_while_retrying = |client: &RpcClient| {
835        loop {
836            match client.get_slot_with_commitment(config.commitment) {
837                Ok(r) => {
838                    retry_count = 0;
839                    return Ok(r);
840                }
841                Err(e) => {
842                    if retry_count >= max_retry_count {
843                        return Err(e);
844                    }
845                    retry_count = retry_count.saturating_add(1);
846                    if log {
847                        // go to new line to leave this message on console
848                        println!("Retrying({retry_count}/{max_retry_count}): {e}\n");
849                    }
850                    sleep(Duration::from_secs(1));
851                }
852            };
853        }
854    };
855
856    let start_node_slot: i64 = get_slot_while_retrying(&node_client)?.try_into()?;
857    let start_rpc_slot: i64 = get_slot_while_retrying(rpc_client)?.try_into()?;
858    let start_slot_distance = start_rpc_slot.saturating_sub(start_node_slot);
859    let mut total_sleep_interval = Duration::ZERO;
860    loop {
861        // humbly retry; the reference node (rpc_client) could be spotty,
862        // especially if pointing to api.meinnet-beta.solana.com at times
863        let rpc_slot: i64 = get_slot_while_retrying(rpc_client)?.try_into()?;
864        let node_slot: i64 = get_slot_while_retrying(&node_client)?.try_into()?;
865        if !follow && node_slot > std::cmp::min(previous_rpc_slot, rpc_slot) {
866            progress_bar.finish_and_clear();
867            return Ok(format!(
868                "{node_pubkey} has caught up (us:{node_slot} them:{rpc_slot})",
869            ));
870        }
871
872        let slot_distance = rpc_slot.saturating_sub(node_slot);
873        let slots_per_second = previous_slot_distance.saturating_sub(slot_distance) as f64
874            / sleep_interval.as_secs_f64();
875
876        let average_time_remaining = if slot_distance == 0 || total_sleep_interval.is_zero() {
877            "".to_string()
878        } else {
879            let distance_delta = start_slot_distance.saturating_sub(slot_distance);
880            let average_catchup_slots_per_second =
881                distance_delta as f64 / total_sleep_interval.as_secs_f64();
882            let average_time_remaining =
883                (slot_distance as f64 / average_catchup_slots_per_second).round();
884            if !average_time_remaining.is_normal() {
885                "".to_string()
886            } else if average_time_remaining < 0.0 {
887                format!(" (AVG: {average_catchup_slots_per_second:.1} slots/second (falling))")
888            } else {
889                // important not to miss next scheduled lead slots
890                let total_node_slot_delta = node_slot.saturating_sub(start_node_slot);
891                let average_node_slots_per_second =
892                    total_node_slot_delta as f64 / total_sleep_interval.as_secs_f64();
893                let expected_finish_slot = (node_slot as f64
894                    + average_time_remaining * average_node_slots_per_second)
895                    .round();
896                format!(
897                    " (AVG: {:.1} slots/second, ETA: slot {} in {})",
898                    average_catchup_slots_per_second,
899                    expected_finish_slot,
900                    humantime::format_duration(Duration::from_secs_f64(average_time_remaining))
901                )
902            }
903        };
904
905        progress_bar.set_message(format!(
906            "{} slot(s) {} (us:{} them:{}){}",
907            slot_distance.abs(),
908            if slot_distance >= 0 {
909                "behind"
910            } else {
911                "ahead"
912            },
913            node_slot,
914            rpc_slot,
915            if slot_distance == 0 || previous_rpc_slot == i64::MAX {
916                "".to_string()
917            } else {
918                format!(
919                    ", {} node is {} at {:.1} slots/second{}",
920                    if slot_distance >= 0 { "our" } else { "their" },
921                    if slots_per_second < 0.0 {
922                        "falling behind"
923                    } else {
924                        "gaining"
925                    },
926                    slots_per_second,
927                    average_time_remaining
928                )
929            },
930        ));
931        if log {
932            println!();
933        }
934
935        sleep(sleep_interval);
936        previous_rpc_slot = rpc_slot;
937        previous_slot_distance = slot_distance;
938        total_sleep_interval = total_sleep_interval.saturating_add(sleep_interval);
939    }
940}
941
942pub fn process_cluster_date(rpc_client: &RpcClient, config: &CliConfig) -> ProcessResult {
943    let result = rpc_client.get_account_with_commitment(&sysvar::clock::id(), config.commitment)?;
944    if let Some(clock_account) = result.value {
945        let clock: Clock = from_account(&clock_account).ok_or_else(|| {
946            CliError::RpcRequestError("Failed to deserialize clock sysvar".to_string())
947        })?;
948        let block_time = CliBlockTime {
949            slot: result.context.slot,
950            timestamp: clock.unix_timestamp,
951        };
952        Ok(config.output_format.formatted_string(&block_time))
953    } else {
954        Err(format!("AccountNotFound: pubkey={}", sysvar::clock::id()).into())
955    }
956}
957
958pub fn process_cluster_version(rpc_client: &RpcClient, config: &CliConfig) -> ProcessResult {
959    let remote_version = rpc_client.get_version()?;
960
961    if config.verbose {
962        Ok(format!("{remote_version:?}"))
963    } else {
964        Ok(remote_version.to_string())
965    }
966}
967
968pub fn process_first_available_block(rpc_client: &RpcClient) -> ProcessResult {
969    let first_available_block = rpc_client.get_first_available_block()?;
970    Ok(format!("{first_available_block}"))
971}
972
973pub fn parse_leader_schedule(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
974    let epoch = value_of(matches, "epoch");
975    Ok(CliCommandInfo::without_signers(
976        CliCommand::LeaderSchedule { epoch },
977    ))
978}
979
980pub fn process_leader_schedule(
981    rpc_client: &RpcClient,
982    config: &CliConfig,
983    epoch: Option<Epoch>,
984) -> ProcessResult {
985    let epoch_info = rpc_client.get_epoch_info()?;
986    let epoch = epoch.unwrap_or(epoch_info.epoch);
987    if epoch > epoch_info.epoch.saturating_add(1) {
988        return Err(format!("Epoch {epoch} is more than one epoch in the future").into());
989    }
990
991    let epoch_schedule = rpc_client.get_epoch_schedule()?;
992    let first_slot_in_epoch = epoch_schedule.get_first_slot_in_epoch(epoch);
993
994    let leader_schedule = rpc_client.get_leader_schedule(Some(first_slot_in_epoch))?;
995    if leader_schedule.is_none() {
996        return Err(
997            format!("Unable to fetch leader schedule for slot {first_slot_in_epoch}").into(),
998        );
999    }
1000    let leader_schedule = leader_schedule.unwrap();
1001
1002    let mut leader_per_slot_index = Vec::new();
1003    for (pubkey, leader_slots) in leader_schedule.iter() {
1004        for slot_index in leader_slots.iter() {
1005            if *slot_index >= leader_per_slot_index.len() {
1006                leader_per_slot_index.resize(slot_index.saturating_add(1), "?");
1007            }
1008            leader_per_slot_index[*slot_index] = pubkey;
1009        }
1010    }
1011
1012    let mut leader_schedule_entries = vec![];
1013    for (slot_index, leader) in leader_per_slot_index.iter().enumerate() {
1014        leader_schedule_entries.push(CliLeaderScheduleEntry {
1015            slot: first_slot_in_epoch.saturating_add(slot_index as u64),
1016            leader: leader.to_string(),
1017        });
1018    }
1019
1020    Ok(config.output_format.formatted_string(&CliLeaderSchedule {
1021        epoch,
1022        leader_schedule_entries,
1023    }))
1024}
1025
1026pub fn process_get_recent_priority_fees(
1027    rpc_client: &RpcClient,
1028    config: &CliConfig,
1029    accounts: &[Pubkey],
1030    limit_num_slots: Option<Slot>,
1031) -> ProcessResult {
1032    let fees = rpc_client.get_recent_prioritization_fees(accounts)?;
1033    let mut min = u64::MAX;
1034    let mut max = 0;
1035    let mut total = Saturating(0);
1036    let fees_len: u64 = fees.len().try_into().unwrap();
1037    let num_slots = limit_num_slots.unwrap_or(fees_len).min(fees_len).max(1);
1038
1039    let mut cli_fees = Vec::with_capacity(fees.len());
1040    for RpcPrioritizationFee {
1041        slot,
1042        prioritization_fee,
1043    } in fees
1044        .into_iter()
1045        .skip(fees_len.saturating_sub(num_slots) as usize)
1046    {
1047        min = min.min(prioritization_fee);
1048        max = max.max(prioritization_fee);
1049        total += prioritization_fee;
1050        cli_fees.push(CliPrioritizationFee {
1051            slot,
1052            prioritization_fee,
1053        });
1054    }
1055    Ok(config
1056        .output_format
1057        .formatted_string(&CliPrioritizationFeeStats {
1058            fees: cli_fees,
1059            min,
1060            max,
1061            average: total.0.checked_div(num_slots).unwrap_or(0),
1062            num_slots,
1063        }))
1064}
1065
1066pub fn process_get_block(
1067    rpc_client: &RpcClient,
1068    config: &CliConfig,
1069    slot: Option<Slot>,
1070) -> ProcessResult {
1071    let slot = if let Some(slot) = slot {
1072        slot
1073    } else {
1074        rpc_client.get_slot_with_commitment(CommitmentConfig::finalized())?
1075    };
1076
1077    let encoded_confirmed_block = rpc_client
1078        .get_block_with_config(
1079            slot,
1080            RpcBlockConfig {
1081                encoding: Some(UiTransactionEncoding::Base64),
1082                commitment: Some(CommitmentConfig::confirmed()),
1083                max_supported_transaction_version: Some(0),
1084                ..RpcBlockConfig::default()
1085            },
1086        )?
1087        .into();
1088    let cli_block = CliBlock {
1089        encoded_confirmed_block,
1090        slot,
1091    };
1092    Ok(config.output_format.formatted_string(&cli_block))
1093}
1094
1095pub fn process_get_block_time(
1096    rpc_client: &RpcClient,
1097    config: &CliConfig,
1098    slot: Option<Slot>,
1099) -> ProcessResult {
1100    let slot = if let Some(slot) = slot {
1101        slot
1102    } else {
1103        rpc_client.get_slot_with_commitment(CommitmentConfig::finalized())?
1104    };
1105    let timestamp = rpc_client.get_block_time(slot)?;
1106    let block_time = CliBlockTime { slot, timestamp };
1107    Ok(config.output_format.formatted_string(&block_time))
1108}
1109
1110pub fn process_get_epoch(rpc_client: &RpcClient, _config: &CliConfig) -> ProcessResult {
1111    let epoch_info = rpc_client.get_epoch_info()?;
1112    Ok(epoch_info.epoch.to_string())
1113}
1114
1115pub fn process_get_epoch_info(rpc_client: &RpcClient, config: &CliConfig) -> ProcessResult {
1116    let epoch_info = rpc_client.get_epoch_info()?;
1117    let epoch_completed_percent =
1118        epoch_info.slot_index as f64 / epoch_info.slots_in_epoch as f64 * 100_f64;
1119    let mut cli_epoch_info = CliEpochInfo {
1120        epoch_info,
1121        epoch_completed_percent,
1122        average_slot_time_ms: 0,
1123        start_block_time: None,
1124        current_block_time: None,
1125    };
1126    match config.output_format {
1127        OutputFormat::Json | OutputFormat::JsonCompact => {}
1128        _ => {
1129            let epoch_info = &cli_epoch_info.epoch_info;
1130            let average_slot_time_ms = rpc_client
1131                .get_recent_performance_samples(Some(60))
1132                .ok()
1133                .and_then(|samples| {
1134                    let (slots, secs) = samples.iter().fold(
1135                        (0, 0u64),
1136                        |(slots, secs): (u64, u64),
1137                         RpcPerfSample {
1138                             num_slots,
1139                             sample_period_secs,
1140                             ..
1141                         }| {
1142                            (
1143                                slots.saturating_add(*num_slots),
1144                                secs.saturating_add((*sample_period_secs).into()),
1145                            )
1146                        },
1147                    );
1148                    secs.saturating_mul(1000).checked_div(slots)
1149                })
1150                .unwrap_or(clock::DEFAULT_MS_PER_SLOT);
1151            let epoch_expected_start_slot = epoch_info
1152                .absolute_slot
1153                .saturating_sub(epoch_info.slot_index);
1154            let first_block_in_epoch = rpc_client
1155                .get_blocks_with_limit(epoch_expected_start_slot, 1)
1156                .ok()
1157                .and_then(|slot_vec| slot_vec.first().cloned())
1158                .unwrap_or(epoch_expected_start_slot);
1159            let start_block_time =
1160                rpc_client
1161                    .get_block_time(first_block_in_epoch)
1162                    .ok()
1163                    .map(|time| {
1164                        time.saturating_sub(
1165                            first_block_in_epoch
1166                                .saturating_sub(epoch_expected_start_slot)
1167                                .saturating_mul(average_slot_time_ms)
1168                                .saturating_div(1000) as i64,
1169                        )
1170                    });
1171            let current_block_time = rpc_client.get_block_time(epoch_info.absolute_slot).ok();
1172
1173            cli_epoch_info.average_slot_time_ms = average_slot_time_ms;
1174            cli_epoch_info.start_block_time = start_block_time;
1175            cli_epoch_info.current_block_time = current_block_time;
1176        }
1177    }
1178    Ok(config.output_format.formatted_string(&cli_epoch_info))
1179}
1180
1181pub fn process_get_genesis_hash(rpc_client: &RpcClient) -> ProcessResult {
1182    let genesis_hash = rpc_client.get_genesis_hash()?;
1183    Ok(genesis_hash.to_string())
1184}
1185
1186pub fn process_get_slot(rpc_client: &RpcClient, _config: &CliConfig) -> ProcessResult {
1187    let slot = rpc_client.get_slot()?;
1188    Ok(slot.to_string())
1189}
1190
1191pub fn process_get_block_height(rpc_client: &RpcClient, _config: &CliConfig) -> ProcessResult {
1192    let block_height = rpc_client.get_block_height()?;
1193    Ok(block_height.to_string())
1194}
1195
1196pub fn parse_show_block_production(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
1197    let epoch = value_t!(matches, "epoch", Epoch).ok();
1198    let slot_limit = value_t!(matches, "slot_limit", u64).ok();
1199
1200    Ok(CliCommandInfo::without_signers(
1201        CliCommand::ShowBlockProduction { epoch, slot_limit },
1202    ))
1203}
1204
1205pub fn process_show_block_production(
1206    rpc_client: &RpcClient,
1207    config: &CliConfig,
1208    epoch: Option<Epoch>,
1209    slot_limit: Option<u64>,
1210) -> ProcessResult {
1211    let epoch_schedule = rpc_client.get_epoch_schedule()?;
1212    let epoch_info = rpc_client.get_epoch_info_with_commitment(CommitmentConfig::finalized())?;
1213
1214    let epoch = epoch.unwrap_or(epoch_info.epoch);
1215    if epoch > epoch_info.epoch {
1216        return Err(format!("Epoch {epoch} is in the future").into());
1217    }
1218
1219    let first_slot_in_epoch = epoch_schedule.get_first_slot_in_epoch(epoch);
1220    let end_slot = std::cmp::min(
1221        epoch_info.absolute_slot,
1222        epoch_schedule.get_last_slot_in_epoch(epoch),
1223    );
1224
1225    let mut start_slot = if let Some(slot_limit) = slot_limit {
1226        std::cmp::max(end_slot.saturating_sub(slot_limit), first_slot_in_epoch)
1227    } else {
1228        first_slot_in_epoch
1229    };
1230
1231    let progress_bar = new_spinner_progress_bar();
1232    progress_bar.set_message(format!(
1233        "Fetching confirmed blocks between slots {start_slot} and {end_slot}..."
1234    ));
1235
1236    let slot_history_account = rpc_client
1237        .get_account_with_commitment(&sysvar::slot_history::id(), CommitmentConfig::finalized())?
1238        .value
1239        .unwrap();
1240
1241    let slot_history: SlotHistory = from_account(&slot_history_account).ok_or_else(|| {
1242        CliError::RpcRequestError("Failed to deserialize slot history".to_string())
1243    })?;
1244
1245    let (confirmed_blocks, start_slot) =
1246        if start_slot >= slot_history.oldest() && end_slot <= slot_history.newest() {
1247            // Fast, more reliable path using the SlotHistory sysvar
1248
1249            let confirmed_blocks: Vec<_> = (start_slot..=end_slot)
1250                .filter(|slot| slot_history.check(*slot) == slot_history::Check::Found)
1251                .collect();
1252            (confirmed_blocks, start_slot)
1253        } else {
1254            // Slow, less reliable path using `getBlocks`.
1255            //
1256            // "less reliable" because if the RPC node has holds in its ledger then the block production data will be
1257            // incorrect.  This condition currently can't be detected over RPC
1258            //
1259
1260            let minimum_ledger_slot = rpc_client.minimum_ledger_slot()?;
1261            if minimum_ledger_slot > end_slot {
1262                return Err(format!(
1263                    "Ledger data not available for slots {start_slot} to {end_slot} (minimum \
1264                     ledger slot is {minimum_ledger_slot})"
1265                )
1266                .into());
1267            }
1268
1269            if minimum_ledger_slot > start_slot {
1270                progress_bar.println(format!(
1271                    "{}",
1272                    style(format!(
1273                        "Note: Requested start slot was {start_slot} but minimum ledger slot is \
1274                         {minimum_ledger_slot}"
1275                    ))
1276                    .italic(),
1277                ));
1278                start_slot = minimum_ledger_slot;
1279            }
1280
1281            let confirmed_blocks = rpc_client.get_blocks(start_slot, Some(end_slot))?;
1282            (confirmed_blocks, start_slot)
1283        };
1284
1285    let start_slot_index = start_slot.saturating_sub(first_slot_in_epoch) as usize;
1286    let end_slot_index = end_slot.saturating_sub(first_slot_in_epoch) as usize;
1287    let total_slots = end_slot_index
1288        .saturating_sub(start_slot_index)
1289        .saturating_add(1);
1290    let total_blocks_produced = confirmed_blocks.len();
1291    assert!(total_blocks_produced <= total_slots);
1292    let total_slots_skipped = total_slots.saturating_sub(total_blocks_produced);
1293    let mut leader_slot_count = HashMap::new();
1294    let mut leader_skipped_slots = HashMap::new();
1295
1296    progress_bar.set_message(format!("Fetching leader schedule for epoch {epoch}..."));
1297    let leader_schedule = rpc_client
1298        .get_leader_schedule_with_commitment(Some(start_slot), CommitmentConfig::finalized())?;
1299    if leader_schedule.is_none() {
1300        return Err(format!("Unable to fetch leader schedule for slot {start_slot}").into());
1301    }
1302    let leader_schedule = leader_schedule.unwrap();
1303
1304    let mut leader_per_slot_index = Vec::new();
1305    leader_per_slot_index.resize(total_slots, "?".to_string());
1306    for (pubkey, leader_slots) in leader_schedule.iter() {
1307        let pubkey = format_labeled_address(pubkey, &config.address_labels);
1308        for slot_index in leader_slots.iter() {
1309            if *slot_index >= start_slot_index && *slot_index <= end_slot_index {
1310                leader_per_slot_index[slot_index.saturating_sub(start_slot_index)]
1311                    .clone_from(&pubkey);
1312            }
1313        }
1314    }
1315
1316    progress_bar.set_message(format!(
1317        "Processing {total_slots} slots containing {total_blocks_produced} blocks and \
1318         {total_slots_skipped} empty slots..."
1319    ));
1320
1321    let mut confirmed_blocks_index = 0;
1322    let mut individual_slot_status = vec![];
1323    for (leader, slot_index) in leader_per_slot_index.iter().zip(0u64..) {
1324        let slot = start_slot.saturating_add(slot_index);
1325        let slot_count: &mut u64 = leader_slot_count.entry(leader).or_insert(0);
1326        *slot_count = slot_count.saturating_add(1);
1327        let skipped_slots: &mut u64 = leader_skipped_slots.entry(leader).or_insert(0);
1328
1329        loop {
1330            if confirmed_blocks_index < confirmed_blocks.len() {
1331                let slot_of_next_confirmed_block = confirmed_blocks[confirmed_blocks_index];
1332                if slot_of_next_confirmed_block < slot {
1333                    confirmed_blocks_index = confirmed_blocks_index.saturating_add(1);
1334                    continue;
1335                }
1336                if slot_of_next_confirmed_block == slot {
1337                    individual_slot_status.push(CliSlotStatus {
1338                        slot,
1339                        leader: (*leader).to_string(),
1340                        skipped: false,
1341                    });
1342                    break;
1343                }
1344            }
1345            *skipped_slots = skipped_slots.saturating_add(1);
1346            individual_slot_status.push(CliSlotStatus {
1347                slot,
1348                leader: (*leader).to_string(),
1349                skipped: true,
1350            });
1351            break;
1352        }
1353    }
1354
1355    progress_bar.finish_and_clear();
1356
1357    let mut leaders: Vec<CliBlockProductionEntry> = leader_slot_count
1358        .iter()
1359        .map(|(leader, leader_slots)| {
1360            let skipped_slots = *leader_skipped_slots.get(leader).unwrap();
1361            let blocks_produced = leader_slots.saturating_sub(skipped_slots);
1362            CliBlockProductionEntry {
1363                identity_pubkey: (**leader).to_string(),
1364                leader_slots: *leader_slots,
1365                blocks_produced,
1366                skipped_slots,
1367            }
1368        })
1369        .collect();
1370    leaders.sort_by(|a, b| a.identity_pubkey.partial_cmp(&b.identity_pubkey).unwrap());
1371    let block_production = CliBlockProduction {
1372        epoch,
1373        start_slot,
1374        end_slot,
1375        total_slots,
1376        total_blocks_produced,
1377        total_slots_skipped,
1378        leaders,
1379        individual_slot_status,
1380        verbose: config.verbose,
1381    };
1382    Ok(config.output_format.formatted_string(&block_production))
1383}
1384
1385pub fn process_largest_accounts(
1386    rpc_client: &RpcClient,
1387    config: &CliConfig,
1388    filter: Option<RpcLargestAccountsFilter>,
1389) -> ProcessResult {
1390    let accounts = rpc_client
1391        .get_largest_accounts_with_config(RpcLargestAccountsConfig {
1392            commitment: Some(config.commitment),
1393            filter,
1394            sort_results: None,
1395        })?
1396        .value;
1397    let largest_accounts = CliAccountBalances { accounts };
1398    Ok(config.output_format.formatted_string(&largest_accounts))
1399}
1400
1401pub fn process_supply(
1402    rpc_client: &RpcClient,
1403    config: &CliConfig,
1404    print_accounts: bool,
1405) -> ProcessResult {
1406    let supply_response = rpc_client.supply()?;
1407    let mut supply: CliSupply = supply_response.value.into();
1408    supply.print_accounts = print_accounts;
1409    Ok(config.output_format.formatted_string(&supply))
1410}
1411
1412pub fn process_total_supply(rpc_client: &RpcClient, _config: &CliConfig) -> ProcessResult {
1413    let supply = rpc_client.supply()?.value;
1414    Ok(format!(
1415        "{} SOL",
1416        build_balance_message(supply.total, false, false)
1417    ))
1418}
1419
1420pub fn process_get_transaction_count(rpc_client: &RpcClient, _config: &CliConfig) -> ProcessResult {
1421    let transaction_count = rpc_client.get_transaction_count()?;
1422    Ok(transaction_count.to_string())
1423}
1424
1425pub fn process_ping(
1426    tps_client: &Arc<dyn TpsClient>,
1427    config: &CliConfig,
1428    interval: &Duration,
1429    count: &Option<u64>,
1430    timeout: &Duration,
1431    fixed_blockhash: &Option<Hash>,
1432    print_timestamp: bool,
1433    compute_unit_price: Option<u64>,
1434    rpc_client: &RpcClient,
1435) -> ProcessResult {
1436    let (signal_sender, signal_receiver) = unbounded();
1437    let handler = move || {
1438        let _ = signal_sender.send(());
1439    };
1440    match ctrlc::try_set_handler(handler) {
1441        // It's possible to set the ctrl-c handler more than once in testing
1442        // situations, so let that case through
1443        Err(ctrlc::Error::MultipleHandlers) => {}
1444        result => result.expect("Error setting Ctrl-C handler"),
1445    }
1446
1447    let mut cli_pings = vec![];
1448
1449    let mut submit_count: u32 = 0;
1450    let mut confirmed_count: u32 = 0;
1451    let mut confirmation_time: VecDeque<u64> = VecDeque::with_capacity(1024);
1452
1453    let mut blockhash = tps_client.get_latest_blockhash()?;
1454    let mut lamports: u64 = 0;
1455    let mut blockhash_acquired = Instant::now();
1456    let mut blockhash_from_cluster = false;
1457    if let Some(fixed_blockhash) = fixed_blockhash {
1458        if *fixed_blockhash != Hash::default() {
1459            blockhash = *fixed_blockhash;
1460        } else {
1461            blockhash_from_cluster = true;
1462        }
1463    }
1464
1465    let to = config.signers[0].pubkey();
1466    let compute_unit_limit = if compute_unit_price.is_some() {
1467        let ixs = vec![system_instruction::transfer(
1468            &config.signers[0].pubkey(),
1469            &to,
1470            lamports,
1471        )]
1472        .with_compute_unit_config(&ComputeUnitConfig {
1473            compute_unit_price,
1474            compute_unit_limit: ComputeUnitLimit::Simulated,
1475        });
1476        let message = Message::new(&ixs, Some(&config.signers[0].pubkey()));
1477        ComputeUnitLimit::Static(simulate_for_compute_unit_limit(rpc_client, &message)?)
1478    } else {
1479        ComputeUnitLimit::Default
1480    };
1481
1482    'mainloop: for seq in 0..count.unwrap_or(u64::MAX) {
1483        let now = Instant::now();
1484        if fixed_blockhash.is_none() && now.duration_since(blockhash_acquired).as_secs() > 60 {
1485            // Fetch a new blockhash every minute
1486            let new_blockhash = tps_client.get_new_latest_blockhash(&blockhash)?;
1487            blockhash = new_blockhash;
1488            lamports = 0;
1489            blockhash_acquired = Instant::now();
1490        }
1491
1492        lamports = lamports.saturating_add(1);
1493
1494        let build_message = |lamports| {
1495            let ixs = vec![system_instruction::transfer(
1496                &config.signers[0].pubkey(),
1497                &to,
1498                lamports,
1499            )]
1500            .with_compute_unit_config(&ComputeUnitConfig {
1501                compute_unit_price,
1502                compute_unit_limit,
1503            });
1504            Message::new(&ixs, Some(&config.signers[0].pubkey()))
1505        };
1506        let (message, _) = resolve_spend_tx_and_check_account_balance(
1507            rpc_client,
1508            false,
1509            SpendAmount::Some(lamports),
1510            &blockhash,
1511            &config.signers[0].pubkey(),
1512            compute_unit_limit,
1513            build_message,
1514            config.commitment,
1515        )?;
1516        let mut tx = Transaction::new_unsigned(message);
1517        tx.try_sign(&config.signers, blockhash)?;
1518
1519        let timestamp = || {
1520            let micros = SystemTime::now()
1521                .duration_since(UNIX_EPOCH)
1522                .unwrap()
1523                .as_micros();
1524            format!("[{}.{:06}] ", micros / 1_000_000, micros % 1_000_000)
1525        };
1526
1527        match tps_client.send_transaction(tx) {
1528            Ok(signature) => {
1529                let transaction_sent = Instant::now();
1530                loop {
1531                    let signature_status = tps_client.get_signature_status(&signature)?;
1532                    let elapsed_time = Instant::now().duration_since(transaction_sent);
1533                    if let Some(transaction_status) = signature_status {
1534                        match transaction_status {
1535                            Ok(()) => {
1536                                let elapsed_time_millis = elapsed_time.as_millis() as u64;
1537                                confirmation_time.push_back(elapsed_time_millis);
1538                                let cli_ping_data = CliPingData {
1539                                    success: true,
1540                                    signature: Some(signature.to_string()),
1541                                    ms: Some(elapsed_time_millis),
1542                                    error: None,
1543                                    timestamp: timestamp(),
1544                                    print_timestamp,
1545                                    sequence: seq,
1546                                    lamports: Some(lamports),
1547                                };
1548                                eprint!("{cli_ping_data}");
1549                                cli_pings.push(cli_ping_data);
1550                                confirmed_count = confirmed_count.saturating_add(1);
1551                            }
1552                            Err(err) => {
1553                                let cli_ping_data = CliPingData {
1554                                    success: false,
1555                                    signature: Some(signature.to_string()),
1556                                    ms: None,
1557                                    error: Some(err.to_string()),
1558                                    timestamp: timestamp(),
1559                                    print_timestamp,
1560                                    sequence: seq,
1561                                    lamports: None,
1562                                };
1563                                eprint!("{cli_ping_data}");
1564                                cli_pings.push(cli_ping_data);
1565                            }
1566                        }
1567                        break;
1568                    }
1569
1570                    if elapsed_time >= *timeout {
1571                        let cli_ping_data = CliPingData {
1572                            success: false,
1573                            signature: Some(signature.to_string()),
1574                            ms: None,
1575                            error: None,
1576                            timestamp: timestamp(),
1577                            print_timestamp,
1578                            sequence: seq,
1579                            lamports: None,
1580                        };
1581                        eprint!("{cli_ping_data}");
1582                        cli_pings.push(cli_ping_data);
1583                        break;
1584                    }
1585
1586                    // Sleep for half a slot
1587                    if signal_receiver
1588                        .recv_timeout(Duration::from_millis(clock::DEFAULT_MS_PER_SLOT / 2))
1589                        .is_ok()
1590                    {
1591                        break 'mainloop;
1592                    }
1593                }
1594            }
1595            Err(err) => {
1596                let cli_ping_data = CliPingData {
1597                    success: false,
1598                    signature: None,
1599                    ms: None,
1600                    error: Some(err.to_string()),
1601                    timestamp: timestamp(),
1602                    print_timestamp,
1603                    sequence: seq,
1604                    lamports: None,
1605                };
1606                eprint!("{cli_ping_data}");
1607                cli_pings.push(cli_ping_data);
1608            }
1609        }
1610        submit_count = submit_count.saturating_add(1);
1611
1612        if signal_receiver.recv_timeout(*interval).is_ok() {
1613            break 'mainloop;
1614        }
1615    }
1616
1617    let transaction_stats = CliPingTxStats {
1618        num_transactions: submit_count,
1619        num_transaction_confirmed: confirmed_count,
1620    };
1621    let confirmation_stats = if !confirmation_time.is_empty() {
1622        let samples: Vec<f64> = confirmation_time.iter().map(|t| *t as f64).collect();
1623        let dist = criterion_stats::Distribution::from(samples.into_boxed_slice());
1624        let mean = dist.mean();
1625        Some(CliPingConfirmationStats {
1626            min: dist.min(),
1627            mean,
1628            max: dist.max(),
1629            std_dev: dist.std_dev(Some(mean)),
1630        })
1631    } else {
1632        None
1633    };
1634
1635    let cli_ping = CliPing {
1636        source_pubkey: config.signers[0].pubkey().to_string(),
1637        fixed_blockhash: fixed_blockhash.map(|_| blockhash.to_string()),
1638        blockhash_from_cluster,
1639        pings: cli_pings,
1640        transaction_stats,
1641        confirmation_stats,
1642    };
1643
1644    Ok(config.output_format.formatted_string(&cli_ping))
1645}
1646
1647pub fn parse_logs(
1648    matches: &ArgMatches<'_>,
1649    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
1650) -> Result<CliCommandInfo, CliError> {
1651    let address = pubkey_of_signer(matches, "address", wallet_manager)?;
1652    let include_votes = matches.is_present("include_votes");
1653
1654    let filter = match address {
1655        None => {
1656            if include_votes {
1657                RpcTransactionLogsFilter::AllWithVotes
1658            } else {
1659                RpcTransactionLogsFilter::All
1660            }
1661        }
1662        Some(address) => RpcTransactionLogsFilter::Mentions(vec![address.to_string()]),
1663    };
1664
1665    Ok(CliCommandInfo::without_signers(CliCommand::Logs { filter }))
1666}
1667
1668pub fn process_logs(config: &CliConfig, filter: &RpcTransactionLogsFilter) -> ProcessResult {
1669    println!(
1670        "Streaming transaction logs{}. {:?} commitment",
1671        match filter {
1672            RpcTransactionLogsFilter::All => "".into(),
1673            RpcTransactionLogsFilter::AllWithVotes => " (including votes)".into(),
1674            RpcTransactionLogsFilter::Mentions(addresses) =>
1675                format!(" mentioning {}", addresses.join(",")),
1676        },
1677        config.commitment.commitment
1678    );
1679
1680    let (_client, receiver) = PubsubClient::logs_subscribe(
1681        &config.websocket_url,
1682        filter.clone(),
1683        RpcTransactionLogsConfig {
1684            commitment: Some(config.commitment),
1685        },
1686    )?;
1687
1688    loop {
1689        match receiver.recv() {
1690            Ok(logs) => {
1691                println!("Transaction executed in slot {}:", logs.context.slot);
1692                println!("  Signature: {}", logs.value.signature);
1693                println!(
1694                    "  Status: {}",
1695                    logs.value
1696                        .err
1697                        .map(|err| err.to_string())
1698                        .unwrap_or_else(|| "Ok".to_string())
1699                );
1700                println!("  Log Messages:");
1701                for log in logs.value.logs {
1702                    println!("    {log}");
1703                }
1704            }
1705            Err(err) => {
1706                return Ok(format!("Disconnected: {err}"));
1707            }
1708        }
1709    }
1710}
1711
1712pub fn process_live_slots(config: &CliConfig) -> ProcessResult {
1713    let exit = Arc::new(AtomicBool::new(false));
1714
1715    let mut current: Option<SlotInfo> = None;
1716    let mut message = "".to_string();
1717
1718    let slot_progress = new_spinner_progress_bar();
1719    slot_progress.set_message("Connecting...");
1720    let (mut client, receiver) = PubsubClient::slot_subscribe(&config.websocket_url)?;
1721    slot_progress.set_message("Connected.");
1722
1723    let spacer = "|";
1724    slot_progress.println(spacer);
1725
1726    let mut last_root = u64::MAX;
1727    let mut last_root_update = Instant::now();
1728    let mut slots_per_second = f64::NAN;
1729    loop {
1730        if exit.load(Ordering::Relaxed) {
1731            eprintln!("{message}");
1732            client.shutdown().unwrap();
1733            break;
1734        }
1735
1736        match receiver.recv() {
1737            Ok(new_info) => {
1738                if last_root == u64::MAX {
1739                    last_root = new_info.root;
1740                    last_root_update = Instant::now();
1741                }
1742                if last_root_update.elapsed().as_secs() >= 5 {
1743                    let root = new_info.root;
1744                    slots_per_second = root.saturating_sub(last_root) as f64
1745                        / last_root_update.elapsed().as_secs() as f64;
1746                    last_root_update = Instant::now();
1747                    last_root = root;
1748                }
1749
1750                message = if slots_per_second.is_nan() {
1751                    format!("{new_info:?}")
1752                } else {
1753                    format!(
1754                        "{new_info:?} | root slot advancing at {slots_per_second:.2} slots/second"
1755                    )
1756                };
1757                slot_progress.set_message(message.clone());
1758
1759                if let Some(previous) = current {
1760                    let slot_delta = (new_info.slot as i64).saturating_sub(previous.slot as i64);
1761                    let root_delta = (new_info.root as i64).saturating_sub(previous.root as i64);
1762
1763                    //
1764                    // if slot has advanced out of step with the root, we detect
1765                    // a mismatch and output the slot information
1766                    //
1767                    if slot_delta != root_delta {
1768                        let prev_root = format!(
1769                            "|<--- {} <- … <- {} <- {}   (prev)",
1770                            previous.root, previous.parent, previous.slot
1771                        );
1772                        slot_progress.println(&prev_root);
1773
1774                        let new_root = format!(
1775                            "|  '- {} <- … <- {} <- {}   (next)",
1776                            new_info.root, new_info.parent, new_info.slot
1777                        );
1778
1779                        slot_progress.println(prev_root);
1780                        slot_progress.println(new_root);
1781                        slot_progress.println(spacer);
1782                    }
1783                }
1784                current = Some(new_info);
1785            }
1786            Err(err) => {
1787                eprintln!("disconnected: {err}");
1788                break;
1789            }
1790        }
1791    }
1792
1793    Ok("".to_string())
1794}
1795
1796pub fn process_show_gossip(rpc_client: &RpcClient, config: &CliConfig) -> ProcessResult {
1797    let cluster_nodes = rpc_client.get_cluster_nodes()?;
1798
1799    let nodes: Vec<_> = cluster_nodes
1800        .into_iter()
1801        .map(|node| CliGossipNode::new(node, &config.address_labels))
1802        .collect();
1803
1804    Ok(config
1805        .output_format
1806        .formatted_string(&CliGossipNodes(nodes)))
1807}
1808
1809pub fn process_show_stakes(
1810    rpc_client: &RpcClient,
1811    config: &CliConfig,
1812    use_lamports_unit: bool,
1813    vote_account_pubkeys: Option<&[Pubkey]>,
1814    withdraw_authority_pubkey: Option<&Pubkey>,
1815) -> ProcessResult {
1816    use crate::stake::build_stake_state;
1817
1818    // Both vote and identity pubkeys are supported to identify validator stakes.
1819    // For identity pubkeys, fetch corresponding vote pubkey.
1820    let vote_account_pubkeys = match vote_account_pubkeys {
1821        Some(pubkeys) => {
1822            let vote_account_progress_bar = new_spinner_progress_bar();
1823            vote_account_progress_bar.set_message("Searching for matching vote accounts...");
1824
1825            let vote_accounts = rpc_client.get_vote_accounts()?;
1826
1827            let mut pubkeys: HashSet<String> =
1828                pubkeys.iter().map(|pubkey| pubkey.to_string()).collect();
1829
1830            let vote_account_pubkeys: HashSet<String> = vote_accounts
1831                .current
1832                .into_iter()
1833                .chain(vote_accounts.delinquent)
1834                .filter_map(|vote_acc| {
1835                    (pubkeys.remove(&vote_acc.node_pubkey) || pubkeys.remove(&vote_acc.vote_pubkey))
1836                        .then_some(vote_acc.vote_pubkey)
1837                })
1838                .collect();
1839
1840            if !pubkeys.is_empty() {
1841                return Err(CliError::RpcRequestError(format!(
1842                    "Failed to retrieve matching vote account for {pubkeys:?}."
1843                ))
1844                .into());
1845            }
1846            vote_account_progress_bar.finish_and_clear();
1847            vote_account_pubkeys
1848        }
1849        None => HashSet::new(),
1850    };
1851
1852    let mut program_accounts_config = RpcProgramAccountsConfig {
1853        account_config: RpcAccountInfoConfig {
1854            encoding: Some(solana_account_decoder::UiAccountEncoding::Base64),
1855            ..RpcAccountInfoConfig::default()
1856        },
1857        ..RpcProgramAccountsConfig::default()
1858    };
1859
1860    let stake_account_progress_bar = new_spinner_progress_bar();
1861    stake_account_progress_bar.set_message("Fetching stake accounts...");
1862
1863    // Use server-side filtering if only one vote account is provided
1864    if vote_account_pubkeys.len() == 1 {
1865        program_accounts_config.filters = Some(vec![
1866            // Filter by `StakeStateV2::Stake(_, _)`
1867            RpcFilterType::Memcmp(Memcmp::new_base58_encoded(0, &[2, 0, 0, 0])),
1868            // Filter by `Delegation::voter_pubkey`, which begins at byte offset 124
1869            RpcFilterType::Memcmp(Memcmp::new_base58_encoded(
1870                124,
1871                Pubkey::from_str(vote_account_pubkeys.iter().next().unwrap())
1872                    .unwrap()
1873                    .as_ref(),
1874            )),
1875        ]);
1876    }
1877
1878    if let Some(withdraw_authority_pubkey) = withdraw_authority_pubkey {
1879        // withdrawer filter
1880        let withdrawer_filter = RpcFilterType::Memcmp(Memcmp::new_base58_encoded(
1881            44,
1882            withdraw_authority_pubkey.as_ref(),
1883        ));
1884        let filters = program_accounts_config.filters.get_or_insert(vec![]);
1885        filters.push(withdrawer_filter);
1886    }
1887
1888    let all_stake_accounts = rpc_client
1889        .get_program_ui_accounts_with_config(&stake::program::id(), program_accounts_config)?;
1890    let stake_history_account = rpc_client.get_account(&stake_history::id())?;
1891    let clock_account = rpc_client.get_account(&sysvar::clock::id())?;
1892    let clock: Clock = from_account(&clock_account).ok_or_else(|| {
1893        CliError::RpcRequestError("Failed to deserialize clock sysvar".to_string())
1894    })?;
1895    let stake_history = from_account(&stake_history_account).ok_or_else(|| {
1896        CliError::RpcRequestError("Failed to deserialize stake history".to_string())
1897    })?;
1898    let new_rate_activation_epoch = get_feature_activation_epoch(
1899        rpc_client,
1900        &agave_feature_set::reduce_stake_warmup_cooldown::id(),
1901    )?;
1902    stake_account_progress_bar.finish_and_clear();
1903
1904    let mut stake_accounts: Vec<CliKeyedStakeState> = vec![];
1905    for (stake_pubkey, stake_ui_account) in all_stake_accounts {
1906        let stake_account: Account = stake_ui_account.decode().expect(
1907            "It should be impossible at this point for the account data not to be decodable. \
1908             Ensure that the account was fetched using a binary encoding.",
1909        );
1910        if let Ok(stake_state) = stake_account.state() {
1911            match stake_state {
1912                StakeStateV2::Initialized(_) => {
1913                    if vote_account_pubkeys.is_empty() {
1914                        stake_accounts.push(CliKeyedStakeState {
1915                            stake_pubkey: stake_pubkey.to_string(),
1916                            stake_state: build_stake_state(
1917                                stake_account.lamports,
1918                                &stake_state,
1919                                use_lamports_unit,
1920                                &stake_history,
1921                                &clock,
1922                                new_rate_activation_epoch,
1923                                false,
1924                            ),
1925                        });
1926                    }
1927                }
1928                StakeStateV2::Stake(_, stake, _) => {
1929                    if vote_account_pubkeys.is_empty()
1930                        || vote_account_pubkeys.contains(&stake.delegation.voter_pubkey.to_string())
1931                    {
1932                        stake_accounts.push(CliKeyedStakeState {
1933                            stake_pubkey: stake_pubkey.to_string(),
1934                            stake_state: build_stake_state(
1935                                stake_account.lamports,
1936                                &stake_state,
1937                                use_lamports_unit,
1938                                &stake_history,
1939                                &clock,
1940                                new_rate_activation_epoch,
1941                                false,
1942                            ),
1943                        });
1944                    }
1945                }
1946                _ => {}
1947            }
1948        }
1949    }
1950    if stake_accounts.is_empty() {
1951        Ok("No stake accounts found".into())
1952    } else {
1953        Ok(config
1954            .output_format
1955            .formatted_string(&CliStakeVec::new(stake_accounts)))
1956    }
1957}
1958
1959pub fn process_wait_for_max_stake(
1960    rpc_client: &RpcClient,
1961    config: &CliConfig,
1962    max_stake_percent: f32,
1963) -> ProcessResult {
1964    let now = std::time::Instant::now();
1965    rpc_client.wait_for_max_stake(config.commitment, max_stake_percent)?;
1966    Ok(format!("Done waiting, took: {}s", now.elapsed().as_secs()))
1967}
1968
1969pub fn process_show_validators(
1970    rpc_client: &RpcClient,
1971    config: &CliConfig,
1972    use_lamports_unit: bool,
1973    validators_sort_order: CliValidatorsSortOrder,
1974    validators_reverse_sort: bool,
1975    number_validators: bool,
1976    keep_unstaked_delinquents: bool,
1977    delinquent_slot_distance: Option<Slot>,
1978) -> ProcessResult {
1979    let progress_bar = new_spinner_progress_bar();
1980    progress_bar.set_message("Fetching vote accounts...");
1981    let epoch_info = rpc_client.get_epoch_info()?;
1982    let vote_accounts = rpc_client.get_vote_accounts_with_config(RpcGetVoteAccountsConfig {
1983        keep_unstaked_delinquents: Some(keep_unstaked_delinquents),
1984        delinquent_slot_distance,
1985        ..RpcGetVoteAccountsConfig::default()
1986    })?;
1987
1988    progress_bar.set_message("Fetching block production...");
1989    let skip_rate: HashMap<_, _> = rpc_client
1990        .get_block_production()?
1991        .value
1992        .by_identity
1993        .into_iter()
1994        .map(|(identity, (leader_slots, blocks_produced))| {
1995            (
1996                identity,
1997                100. * (leader_slots.saturating_sub(blocks_produced)) as f64 / leader_slots as f64,
1998            )
1999        })
2000        .collect();
2001
2002    progress_bar.set_message("Fetching version information...");
2003    let mut node_version = HashMap::new();
2004    for contact_info in rpc_client.get_cluster_nodes()? {
2005        node_version.insert(
2006            contact_info.pubkey,
2007            contact_info
2008                .version
2009                .and_then(|version| CliVersion::from_str(&version).ok())
2010                .unwrap_or_else(CliVersion::unknown_version),
2011        );
2012    }
2013
2014    progress_bar.finish_and_clear();
2015
2016    let total_active_stake = vote_accounts
2017        .current
2018        .iter()
2019        .chain(vote_accounts.delinquent.iter())
2020        .map(|vote_account| vote_account.activated_stake)
2021        .sum::<u64>();
2022
2023    let total_delinquent_stake = vote_accounts
2024        .delinquent
2025        .iter()
2026        .map(|vote_account| vote_account.activated_stake)
2027        .sum();
2028    let total_current_stake = total_active_stake.saturating_sub(total_delinquent_stake);
2029
2030    let current_validators: Vec<CliValidator> = vote_accounts
2031        .current
2032        .iter()
2033        .map(|vote_account| {
2034            CliValidator::new(
2035                vote_account,
2036                epoch_info.epoch,
2037                node_version
2038                    .get(&vote_account.node_pubkey)
2039                    .cloned()
2040                    .unwrap_or_else(CliVersion::unknown_version),
2041                skip_rate.get(&vote_account.node_pubkey).cloned(),
2042                &config.address_labels,
2043            )
2044        })
2045        .collect();
2046    let delinquent_validators: Vec<CliValidator> = vote_accounts
2047        .delinquent
2048        .iter()
2049        .map(|vote_account| {
2050            CliValidator::new_delinquent(
2051                vote_account,
2052                epoch_info.epoch,
2053                node_version
2054                    .get(&vote_account.node_pubkey)
2055                    .cloned()
2056                    .unwrap_or_else(CliVersion::unknown_version),
2057                skip_rate.get(&vote_account.node_pubkey).cloned(),
2058                &config.address_labels,
2059            )
2060        })
2061        .collect();
2062
2063    let mut stake_by_version: BTreeMap<CliVersion, CliValidatorsStakeByVersion> = BTreeMap::new();
2064    for validator in current_validators.iter() {
2065        let CliValidatorsStakeByVersion {
2066            current_validators,
2067            current_active_stake,
2068            ..
2069        } = stake_by_version
2070            .entry(validator.version.clone())
2071            .or_default();
2072        *current_validators = current_validators.saturating_add(1);
2073        *current_active_stake = current_active_stake.saturating_add(validator.activated_stake);
2074    }
2075    for validator in delinquent_validators.iter() {
2076        let CliValidatorsStakeByVersion {
2077            delinquent_validators,
2078            delinquent_active_stake,
2079            ..
2080        } = stake_by_version
2081            .entry(validator.version.clone())
2082            .or_default();
2083        *delinquent_validators = delinquent_validators.saturating_add(1);
2084        *delinquent_active_stake =
2085            delinquent_active_stake.saturating_add(validator.activated_stake);
2086    }
2087
2088    let validators: Vec<_> = current_validators
2089        .into_iter()
2090        .chain(delinquent_validators)
2091        .collect();
2092
2093    let (average_skip_rate, average_stake_weighted_skip_rate) = {
2094        let mut skip_rate_len: u64 = 0;
2095        let mut skip_rate_sum = 0.;
2096        let mut skip_rate_weighted_sum = 0.;
2097        for validator in validators.iter() {
2098            if let Some(skip_rate) = validator.skip_rate {
2099                skip_rate_sum += skip_rate;
2100                skip_rate_len = skip_rate_len.saturating_add(1);
2101                skip_rate_weighted_sum += skip_rate * validator.activated_stake as f64;
2102            }
2103        }
2104
2105        if skip_rate_len > 0 && total_active_stake > 0 {
2106            (
2107                skip_rate_sum / skip_rate_len as f64,
2108                skip_rate_weighted_sum / total_active_stake as f64,
2109            )
2110        } else {
2111            (100., 100.) // Impossible?
2112        }
2113    };
2114
2115    let cli_validators = CliValidators {
2116        total_active_stake,
2117        total_current_stake,
2118        total_delinquent_stake,
2119        validators,
2120        average_skip_rate,
2121        average_stake_weighted_skip_rate,
2122        validators_sort_order,
2123        validators_reverse_sort,
2124        number_validators,
2125        stake_by_version,
2126        use_lamports_unit,
2127    };
2128    Ok(config.output_format.formatted_string(&cli_validators))
2129}
2130
2131pub fn process_transaction_history(
2132    rpc_client: &RpcClient,
2133    config: &CliConfig,
2134    address: &Pubkey,
2135    before: Option<Signature>,
2136    until: Option<Signature>,
2137    limit: usize,
2138    show_transactions: bool,
2139) -> ProcessResult {
2140    let results = rpc_client.get_signatures_for_address_with_config(
2141        address,
2142        GetConfirmedSignaturesForAddress2Config {
2143            before,
2144            until,
2145            limit: Some(limit),
2146            commitment: Some(CommitmentConfig::confirmed()),
2147        },
2148    )?;
2149
2150    if !show_transactions {
2151        let cli_signatures: Vec<_> = results
2152            .into_iter()
2153            .map(|result| {
2154                let mut signature = CliHistorySignature {
2155                    signature: result.signature,
2156                    ..CliHistorySignature::default()
2157                };
2158                if config.verbose {
2159                    signature.verbose = Some(CliHistoryVerbose {
2160                        slot: result.slot,
2161                        block_time: result.block_time,
2162                        err: result.err,
2163                        confirmation_status: result.confirmation_status,
2164                        memo: result.memo,
2165                    });
2166                }
2167                signature
2168            })
2169            .collect();
2170        Ok(config
2171            .output_format
2172            .formatted_string(&CliHistorySignatureVec::new(cli_signatures)))
2173    } else {
2174        let mut cli_transactions = vec![];
2175        for result in results {
2176            if let Ok(signature) = result.signature.parse::<Signature>() {
2177                let mut transaction = None;
2178                let mut get_transaction_error = None;
2179                match rpc_client.get_transaction_with_config(
2180                    &signature,
2181                    RpcTransactionConfig {
2182                        encoding: Some(UiTransactionEncoding::Base64),
2183                        commitment: Some(CommitmentConfig::confirmed()),
2184                        max_supported_transaction_version: Some(0),
2185                    },
2186                ) {
2187                    Ok(confirmed_transaction) => {
2188                        let EncodedConfirmedTransactionWithStatusMeta {
2189                            block_time,
2190                            slot,
2191                            transaction: transaction_with_meta,
2192                        } = confirmed_transaction;
2193
2194                        let decoded_transaction =
2195                            transaction_with_meta.transaction.decode().unwrap();
2196                        let json_transaction = decoded_transaction.json_encode();
2197
2198                        transaction = Some(CliTransaction {
2199                            transaction: json_transaction,
2200                            meta: transaction_with_meta.meta,
2201                            block_time,
2202                            slot: Some(slot),
2203                            decoded_transaction,
2204                            prefix: "  ".to_string(),
2205                            sigverify_status: vec![],
2206                        });
2207                    }
2208                    Err(err) => {
2209                        get_transaction_error = Some(format!("{err:?}"));
2210                    }
2211                };
2212                cli_transactions.push(CliTransactionConfirmation {
2213                    confirmation_status: result.confirmation_status,
2214                    transaction,
2215                    get_transaction_error,
2216                    err: result.err,
2217                });
2218            }
2219        }
2220        Ok(config
2221            .output_format
2222            .formatted_string(&CliHistoryTransactionVec::new(cli_transactions)))
2223    }
2224}
2225
2226#[derive(Serialize, Deserialize)]
2227#[serde(rename_all = "camelCase")]
2228struct CliRentCalculation {
2229    // lamports_per_* fields are deprecated since all accounts must be rent
2230    // exempt; however, they are kept here for the sake of compatibility.
2231    pub lamports_per_byte_year: u64,
2232    pub lamports_per_epoch: u64,
2233    pub rent_exempt_minimum_lamports: u64,
2234    #[serde(skip)]
2235    pub use_lamports_unit: bool,
2236}
2237
2238impl CliRentCalculation {
2239    fn build_balance_message(&self, lamports: u64) -> String {
2240        build_balance_message(lamports, self.use_lamports_unit, true)
2241    }
2242}
2243
2244impl fmt::Display for CliRentCalculation {
2245    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2246        let exempt_minimum = self.build_balance_message(self.rent_exempt_minimum_lamports);
2247        writeln_name_value(f, "Rent-exempt minimum:", &exempt_minimum)
2248    }
2249}
2250
2251impl QuietDisplay for CliRentCalculation {}
2252impl VerboseDisplay for CliRentCalculation {}
2253
2254#[derive(Debug, PartialEq, Eq)]
2255pub enum RentLengthValue {
2256    Nonce,
2257    Stake,
2258    System,
2259    Vote,
2260    Bytes(usize),
2261}
2262
2263impl RentLengthValue {
2264    pub fn length(&self) -> usize {
2265        match self {
2266            Self::Nonce => NonceState::size(),
2267            Self::Stake => StakeStateV2::size_of(),
2268            Self::System => 0,
2269            Self::Vote => VoteStateV4::size_of(),
2270            Self::Bytes(l) => *l,
2271        }
2272    }
2273}
2274
2275#[derive(Debug, Error)]
2276#[error("expected number or moniker, got \"{0}\"")]
2277pub struct RentLengthValueError(pub String);
2278
2279impl FromStr for RentLengthValue {
2280    type Err = RentLengthValueError;
2281    fn from_str(s: &str) -> Result<Self, Self::Err> {
2282        let s = s.to_ascii_lowercase();
2283        match s.as_str() {
2284            "nonce" => Ok(Self::Nonce),
2285            "stake" => Ok(Self::Stake),
2286            "system" => Ok(Self::System),
2287            "vote" => Ok(Self::Vote),
2288            _ => usize::from_str(&s)
2289                .map(Self::Bytes)
2290                .map_err(|_| RentLengthValueError(s)),
2291        }
2292    }
2293}
2294
2295pub fn process_calculate_rent(
2296    rpc_client: &RpcClient,
2297    config: &CliConfig,
2298    data_length: usize,
2299    use_lamports_unit: bool,
2300) -> ProcessResult {
2301    if data_length > MAX_PERMITTED_DATA_LENGTH.try_into().unwrap() {
2302        eprintln!(
2303            "Warning: Maximum account size is {MAX_PERMITTED_DATA_LENGTH} bytes, {data_length} \
2304             provided"
2305        );
2306    }
2307    let rent_account = rpc_client.get_account(&sysvar::rent::id())?;
2308    let rent: Rent = rent_account.deserialize_data()?;
2309    let rent_exempt_minimum_lamports = rent.minimum_balance(data_length);
2310    let cli_rent_calculation = CliRentCalculation {
2311        lamports_per_byte_year: 0,
2312        lamports_per_epoch: 0,
2313        rent_exempt_minimum_lamports,
2314        use_lamports_unit,
2315    };
2316
2317    Ok(config.output_format.formatted_string(&cli_rent_calculation))
2318}
2319
2320#[cfg(test)]
2321mod tests {
2322    use {
2323        super::*,
2324        crate::{clap_app::get_clap_app, cli::parse_command},
2325        solana_keypair::{write_keypair, Keypair},
2326        std::str::FromStr,
2327        tempfile::NamedTempFile,
2328    };
2329
2330    fn make_tmp_file() -> (String, NamedTempFile) {
2331        let tmp_file = NamedTempFile::new().unwrap();
2332        (String::from(tmp_file.path().to_str().unwrap()), tmp_file)
2333    }
2334
2335    #[test]
2336    fn test_parse_command() {
2337        let test_commands = get_clap_app("test", "desc", "version");
2338        let default_keypair = Keypair::new();
2339        let (default_keypair_file, mut tmp_file) = make_tmp_file();
2340        write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
2341        let default_signer = DefaultSigner::new("", default_keypair_file);
2342
2343        let test_cluster_version = test_commands
2344            .clone()
2345            .get_matches_from(vec!["test", "cluster-date"]);
2346        assert_eq!(
2347            parse_command(&test_cluster_version, &default_signer, &mut None).unwrap(),
2348            CliCommandInfo::without_signers(CliCommand::ClusterDate)
2349        );
2350
2351        let test_cluster_version = test_commands
2352            .clone()
2353            .get_matches_from(vec!["test", "cluster-version"]);
2354        assert_eq!(
2355            parse_command(&test_cluster_version, &default_signer, &mut None).unwrap(),
2356            CliCommandInfo::without_signers(CliCommand::ClusterVersion)
2357        );
2358
2359        let slot = 100;
2360        let test_get_block_time =
2361            test_commands
2362                .clone()
2363                .get_matches_from(vec!["test", "block-time", &slot.to_string()]);
2364        assert_eq!(
2365            parse_command(&test_get_block_time, &default_signer, &mut None).unwrap(),
2366            CliCommandInfo::without_signers(CliCommand::GetBlockTime { slot: Some(slot) })
2367        );
2368
2369        let test_get_epoch = test_commands
2370            .clone()
2371            .get_matches_from(vec!["test", "epoch"]);
2372        assert_eq!(
2373            parse_command(&test_get_epoch, &default_signer, &mut None).unwrap(),
2374            CliCommandInfo::without_signers(CliCommand::GetEpoch)
2375        );
2376
2377        let test_get_epoch_info = test_commands
2378            .clone()
2379            .get_matches_from(vec!["test", "epoch-info"]);
2380        assert_eq!(
2381            parse_command(&test_get_epoch_info, &default_signer, &mut None).unwrap(),
2382            CliCommandInfo::without_signers(CliCommand::GetEpochInfo)
2383        );
2384
2385        let test_get_genesis_hash = test_commands
2386            .clone()
2387            .get_matches_from(vec!["test", "genesis-hash"]);
2388        assert_eq!(
2389            parse_command(&test_get_genesis_hash, &default_signer, &mut None).unwrap(),
2390            CliCommandInfo::without_signers(CliCommand::GetGenesisHash)
2391        );
2392
2393        let test_get_slot = test_commands.clone().get_matches_from(vec!["test", "slot"]);
2394        assert_eq!(
2395            parse_command(&test_get_slot, &default_signer, &mut None).unwrap(),
2396            CliCommandInfo::without_signers(CliCommand::GetSlot)
2397        );
2398
2399        let test_total_supply = test_commands
2400            .clone()
2401            .get_matches_from(vec!["test", "total-supply"]);
2402        assert_eq!(
2403            parse_command(&test_total_supply, &default_signer, &mut None).unwrap(),
2404            CliCommandInfo::without_signers(CliCommand::TotalSupply)
2405        );
2406
2407        let test_transaction_count = test_commands
2408            .clone()
2409            .get_matches_from(vec!["test", "transaction-count"]);
2410        assert_eq!(
2411            parse_command(&test_transaction_count, &default_signer, &mut None).unwrap(),
2412            CliCommandInfo::without_signers(CliCommand::GetTransactionCount)
2413        );
2414
2415        let test_ping = test_commands.clone().get_matches_from(vec![
2416            "test",
2417            "ping",
2418            "-i",
2419            "1",
2420            "-c",
2421            "2",
2422            "-t",
2423            "3",
2424            "-D",
2425            "--blockhash",
2426            "4CCNp28j6AhGq7PkjPDP4wbQWBS8LLbQin2xV5n8frKX",
2427        ]);
2428        assert_eq!(
2429            parse_command(&test_ping, &default_signer, &mut None).unwrap(),
2430            CliCommandInfo {
2431                command: CliCommand::Ping {
2432                    interval: Duration::from_secs(1),
2433                    count: Some(2),
2434                    timeout: Duration::from_secs(3),
2435                    blockhash: Some(
2436                        Hash::from_str("4CCNp28j6AhGq7PkjPDP4wbQWBS8LLbQin2xV5n8frKX").unwrap()
2437                    ),
2438                    print_timestamp: true,
2439                    compute_unit_price: None,
2440                },
2441                signers: vec![Box::new(default_keypair)],
2442            }
2443        );
2444    }
2445}