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