Skip to main content

solana_cli/
cluster_query.rs

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