miraland_cli/
cluster_query.rs

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