solana_cli/
cluster_query.rs

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