| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553 |
- #![allow(clippy::to_string_in_format_args)]
- use {
- crate::{
- cli_version::CliVersion,
- display::{
- build_balance_message, build_balance_message_with_config, format_labeled_address,
- unix_timestamp_to_string, writeln_name_value, writeln_transaction,
- BuildBalanceMessageConfig,
- },
- QuietDisplay, VerboseDisplay,
- },
- base64::{prelude::BASE64_STANDARD, Engine},
- chrono::{Local, TimeZone, Utc},
- clap::ArgMatches,
- console::{style, Emoji},
- inflector::cases::titlecase::to_title_case,
- serde::{Deserialize, Serialize},
- serde_json::{Map, Value},
- solana_account::ReadableAccount,
- solana_account_decoder::{
- encode_ui_account, parse_account_data::AccountAdditionalDataV3,
- parse_token::UiTokenAccount, UiAccountEncoding, UiDataSliceConfig,
- },
- solana_clap_utils::keypair::SignOnly,
- solana_clock::{Epoch, Slot, UnixTimestamp},
- solana_epoch_info::EpochInfo,
- solana_hash::Hash,
- solana_pubkey::Pubkey,
- solana_rpc_client_api::response::{
- RpcAccountBalance, RpcContactInfo, RpcInflationGovernor, RpcInflationRate, RpcKeyedAccount,
- RpcSupply, RpcVoteAccountInfo,
- },
- solana_signature::Signature,
- solana_stake_interface::{
- stake_history::StakeHistoryEntry,
- state::{Authorized, Lockup},
- },
- solana_transaction::{versioned::VersionedTransaction, Transaction},
- solana_transaction_status::{
- EncodedConfirmedBlock, EncodedTransaction, TransactionConfirmationStatus,
- UiTransactionStatusMeta,
- },
- solana_transaction_status_client_types::UiTransactionError,
- solana_vote_program::{
- authorized_voters::AuthorizedVoters,
- vote_state::{BlockTimestamp, LandedVote, MAX_EPOCH_CREDITS_HISTORY, MAX_LOCKOUT_HISTORY},
- },
- std::{
- collections::{BTreeMap, HashMap},
- fmt,
- str::FromStr,
- time::Duration,
- },
- };
- static CHECK_MARK: Emoji = Emoji("✅ ", "");
- static CROSS_MARK: Emoji = Emoji("❌ ", "");
- static WARNING: Emoji = Emoji("⚠️", "!");
- #[derive(Clone, Debug, PartialEq, Eq)]
- pub enum OutputFormat {
- Display,
- Json,
- JsonCompact,
- DisplayQuiet,
- DisplayVerbose,
- }
- impl OutputFormat {
- pub fn formatted_string<T>(&self, item: &T) -> String
- where
- T: Serialize + fmt::Display + QuietDisplay + VerboseDisplay,
- {
- match self {
- OutputFormat::Display => format!("{item}"),
- OutputFormat::DisplayQuiet => {
- let mut s = String::new();
- QuietDisplay::write_str(item, &mut s).unwrap();
- s
- }
- OutputFormat::DisplayVerbose => {
- let mut s = String::new();
- VerboseDisplay::write_str(item, &mut s).unwrap();
- s
- }
- OutputFormat::Json => serde_json::to_string_pretty(item).unwrap(),
- OutputFormat::JsonCompact => serde_json::to_value(item).unwrap().to_string(),
- }
- }
- pub fn from_matches(matches: &ArgMatches<'_>, output_name: &str, verbose: bool) -> Self {
- matches
- .value_of(output_name)
- .map(|value| match value {
- "json" => OutputFormat::Json,
- "json-compact" => OutputFormat::JsonCompact,
- _ => unreachable!(),
- })
- .unwrap_or(if verbose {
- OutputFormat::DisplayVerbose
- } else {
- OutputFormat::Display
- })
- }
- }
- #[derive(Serialize)]
- pub struct CliPrioritizationFeeStats {
- pub fees: Vec<CliPrioritizationFee>,
- pub min: u64,
- pub max: u64,
- pub average: u64,
- pub num_slots: u64,
- }
- impl QuietDisplay for CliPrioritizationFeeStats {}
- impl VerboseDisplay for CliPrioritizationFeeStats {
- fn write_str(&self, f: &mut dyn std::fmt::Write) -> fmt::Result {
- writeln!(f, "{:<11} prioritization_fee", "slot")?;
- for fee in &self.fees {
- write!(f, "{}", fee)?;
- }
- write!(f, "{}", self)
- }
- }
- impl fmt::Display for CliPrioritizationFeeStats {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(
- f,
- "Fees in recent {} slots: Min: {} Max: {} Average: {}",
- self.num_slots, self.min, self.max, self.average
- )
- }
- }
- #[derive(Serialize)]
- pub struct CliPrioritizationFee {
- pub slot: Slot,
- pub prioritization_fee: u64,
- }
- impl QuietDisplay for CliPrioritizationFee {}
- impl VerboseDisplay for CliPrioritizationFee {}
- impl fmt::Display for CliPrioritizationFee {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(f, "{:<11} {}", self.slot, self.prioritization_fee)
- }
- }
- #[derive(Serialize, Deserialize)]
- pub struct CliAccount {
- #[serde(flatten)]
- pub keyed_account: RpcKeyedAccount,
- #[serde(skip_serializing, skip_deserializing)]
- pub use_lamports_unit: bool,
- }
- pub struct CliAccountNewConfig {
- pub data_encoding: UiAccountEncoding,
- pub additional_data: Option<AccountAdditionalDataV3>,
- pub data_slice_config: Option<UiDataSliceConfig>,
- pub use_lamports_unit: bool,
- }
- impl Default for CliAccountNewConfig {
- fn default() -> Self {
- Self {
- data_encoding: UiAccountEncoding::Base64,
- additional_data: None,
- data_slice_config: None,
- use_lamports_unit: false,
- }
- }
- }
- impl CliAccount {
- pub fn new<T: ReadableAccount>(address: &Pubkey, account: &T, use_lamports_unit: bool) -> Self {
- Self::new_with_config(
- address,
- account,
- &CliAccountNewConfig {
- use_lamports_unit,
- ..CliAccountNewConfig::default()
- },
- )
- }
- pub fn new_with_config<T: ReadableAccount>(
- address: &Pubkey,
- account: &T,
- config: &CliAccountNewConfig,
- ) -> Self {
- let CliAccountNewConfig {
- data_encoding,
- additional_data,
- data_slice_config,
- use_lamports_unit,
- } = *config;
- Self {
- keyed_account: RpcKeyedAccount {
- pubkey: address.to_string(),
- account: encode_ui_account(
- address,
- account,
- data_encoding,
- additional_data,
- data_slice_config,
- ),
- },
- use_lamports_unit,
- }
- }
- }
- impl QuietDisplay for CliAccount {}
- impl VerboseDisplay for CliAccount {}
- impl fmt::Display for CliAccount {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(f)?;
- writeln_name_value(f, "Public Key:", &self.keyed_account.pubkey)?;
- writeln_name_value(
- f,
- "Balance:",
- &build_balance_message(
- self.keyed_account.account.lamports,
- self.use_lamports_unit,
- true,
- ),
- )?;
- writeln_name_value(f, "Owner:", &self.keyed_account.account.owner)?;
- writeln_name_value(
- f,
- "Executable:",
- &self.keyed_account.account.executable.to_string(),
- )?;
- writeln_name_value(
- f,
- "Rent Epoch:",
- &self.keyed_account.account.rent_epoch.to_string(),
- )?;
- Ok(())
- }
- }
- #[derive(Default, Serialize, Deserialize)]
- pub struct CliBlockProduction {
- pub epoch: Epoch,
- pub start_slot: Slot,
- pub end_slot: Slot,
- pub total_slots: usize,
- pub total_blocks_produced: usize,
- pub total_slots_skipped: usize,
- pub leaders: Vec<CliBlockProductionEntry>,
- pub individual_slot_status: Vec<CliSlotStatus>,
- #[serde(skip_serializing)]
- pub verbose: bool,
- }
- impl QuietDisplay for CliBlockProduction {}
- impl VerboseDisplay for CliBlockProduction {}
- impl fmt::Display for CliBlockProduction {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(f)?;
- writeln!(
- f,
- "{}",
- style(format!(
- " {:<44} {:>15} {:>15} {:>15} {:>15}",
- "Identity", "Leader Slots", "Blocks Produced", "Skipped Slots", "Skip Rate",
- ))
- .bold()
- )?;
- for leader in &self.leaders {
- writeln!(
- f,
- " {:<44} {:>15} {:>15} {:>15} {:>22.2}%",
- leader.identity_pubkey,
- leader.leader_slots,
- leader.blocks_produced,
- leader.skipped_slots,
- leader.skipped_slots as f64 / leader.leader_slots as f64 * 100.
- )?;
- }
- writeln!(f)?;
- writeln!(
- f,
- " {:<44} {:>15} {:>15} {:>15} {:>22.2}%",
- format!("Epoch {} total:", self.epoch),
- self.total_slots,
- self.total_blocks_produced,
- self.total_slots_skipped,
- self.total_slots_skipped as f64 / self.total_slots as f64 * 100.
- )?;
- writeln!(
- f,
- " (using data from {} slots: {} to {})",
- self.total_slots, self.start_slot, self.end_slot
- )?;
- if self.verbose {
- writeln!(f)?;
- writeln!(f)?;
- writeln!(
- f,
- "{}",
- style(format!(" {:<15} {:<44}", "Slot", "Identity Pubkey")).bold(),
- )?;
- for status in &self.individual_slot_status {
- if status.skipped {
- writeln!(
- f,
- "{}",
- style(format!(
- " {:<15} {:<44} SKIPPED",
- status.slot, status.leader
- ))
- .red()
- )?;
- } else {
- writeln!(
- f,
- "{}",
- style(format!(" {:<15} {:<44}", status.slot, status.leader))
- )?;
- }
- }
- }
- Ok(())
- }
- }
- #[derive(Default, Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliBlockProductionEntry {
- pub identity_pubkey: String,
- pub leader_slots: u64,
- pub blocks_produced: u64,
- pub skipped_slots: u64,
- }
- #[derive(Default, Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliSlotStatus {
- pub slot: Slot,
- pub leader: String,
- pub skipped: bool,
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliEpochInfo {
- #[serde(flatten)]
- pub epoch_info: EpochInfo,
- pub epoch_completed_percent: f64,
- #[serde(skip)]
- pub average_slot_time_ms: u64,
- #[serde(skip)]
- pub start_block_time: Option<UnixTimestamp>,
- #[serde(skip)]
- pub current_block_time: Option<UnixTimestamp>,
- }
- impl QuietDisplay for CliEpochInfo {}
- impl VerboseDisplay for CliEpochInfo {}
- impl fmt::Display for CliEpochInfo {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(f)?;
- writeln_name_value(
- f,
- "Block height:",
- &self.epoch_info.block_height.to_string(),
- )?;
- writeln_name_value(f, "Slot:", &self.epoch_info.absolute_slot.to_string())?;
- writeln_name_value(f, "Epoch:", &self.epoch_info.epoch.to_string())?;
- if let Some(transaction_count) = &self.epoch_info.transaction_count {
- writeln_name_value(f, "Transaction Count:", &transaction_count.to_string())?;
- }
- let start_slot = self.epoch_info.absolute_slot - self.epoch_info.slot_index;
- let end_slot = start_slot + self.epoch_info.slots_in_epoch;
- writeln_name_value(
- f,
- "Epoch Slot Range:",
- &format!("[{start_slot}..{end_slot})"),
- )?;
- writeln_name_value(
- f,
- "Epoch Completed Percent:",
- &format!("{:>3.3}%", self.epoch_completed_percent),
- )?;
- let remaining_slots_in_epoch = self.epoch_info.slots_in_epoch - self.epoch_info.slot_index;
- writeln_name_value(
- f,
- "Epoch Completed Slots:",
- &format!(
- "{}/{} ({} remaining)",
- self.epoch_info.slot_index,
- self.epoch_info.slots_in_epoch,
- remaining_slots_in_epoch
- ),
- )?;
- let (time_elapsed, annotation) = if let (Some(start_block_time), Some(current_block_time)) =
- (self.start_block_time, self.current_block_time)
- {
- (
- Duration::from_secs((current_block_time - start_block_time) as u64),
- None,
- )
- } else {
- (
- slot_to_duration(self.epoch_info.slot_index, self.average_slot_time_ms),
- Some("* estimated based on current slot durations"),
- )
- };
- let time_remaining = slot_to_duration(remaining_slots_in_epoch, self.average_slot_time_ms);
- writeln_name_value(
- f,
- "Epoch Completed Time:",
- &format!(
- "{}{}/{} ({} remaining)",
- humantime::format_duration(time_elapsed),
- if annotation.is_some() { "*" } else { "" },
- humantime::format_duration(time_elapsed + time_remaining),
- humantime::format_duration(time_remaining),
- ),
- )?;
- if let Some(annotation) = annotation {
- writeln!(f)?;
- writeln!(f, "{annotation}")?;
- }
- Ok(())
- }
- }
- fn slot_to_duration(slot: Slot, slot_time_ms: u64) -> Duration {
- Duration::from_secs((slot * slot_time_ms) / 1000)
- }
- #[derive(Serialize, Deserialize, Default)]
- #[serde(rename_all = "camelCase")]
- pub struct CliValidatorsStakeByVersion {
- pub current_validators: usize,
- pub delinquent_validators: usize,
- pub current_active_stake: u64,
- pub delinquent_active_stake: u64,
- }
- #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
- pub enum CliValidatorsSortOrder {
- Delinquent,
- Commission,
- EpochCredits,
- Identity,
- LastVote,
- Root,
- SkipRate,
- Stake,
- VoteAccount,
- Version,
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliValidators {
- pub total_active_stake: u64,
- pub total_current_stake: u64,
- pub total_delinquent_stake: u64,
- pub validators: Vec<CliValidator>,
- pub average_skip_rate: f64,
- pub average_stake_weighted_skip_rate: f64,
- #[serde(skip_serializing)]
- pub validators_sort_order: CliValidatorsSortOrder,
- #[serde(skip_serializing)]
- pub validators_reverse_sort: bool,
- #[serde(skip_serializing)]
- pub number_validators: bool,
- pub stake_by_version: BTreeMap<CliVersion, CliValidatorsStakeByVersion>,
- #[serde(skip_serializing)]
- pub use_lamports_unit: bool,
- }
- impl QuietDisplay for CliValidators {}
- impl VerboseDisplay for CliValidators {}
- impl fmt::Display for CliValidators {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- fn write_vote_account(
- f: &mut fmt::Formatter,
- validator: &CliValidator,
- total_active_stake: u64,
- use_lamports_unit: bool,
- highest_last_vote: u64,
- highest_root: u64,
- ) -> fmt::Result {
- fn non_zero_or_dash(v: u64, max_v: u64) -> String {
- if v == 0 {
- " - ".into()
- } else if v == max_v {
- format!("{v:>9} ( 0)")
- } else if v > max_v.saturating_sub(100) {
- format!("{:>9} ({:>3})", v, -(max_v.saturating_sub(v) as isize))
- } else {
- format!("{v:>9} ")
- }
- }
- writeln!(
- f,
- "{} {:<44} {:<44} {:>3}% {:>14} {:>14} {:>7} {:>8} {:>7} {:>22} ({:.2}%)",
- if validator.delinquent {
- WARNING.to_string()
- } else {
- "\u{a0}".to_string()
- },
- validator.identity_pubkey,
- validator.vote_account_pubkey,
- validator.commission,
- non_zero_or_dash(validator.last_vote, highest_last_vote),
- non_zero_or_dash(validator.root_slot, highest_root),
- if let Some(skip_rate) = validator.skip_rate {
- format!("{skip_rate:.2}%")
- } else {
- "- ".to_string()
- },
- validator.epoch_credits,
- // convert to a string so that fill/alignment works correctly
- validator.version.to_string(),
- build_balance_message_with_config(
- validator.activated_stake,
- &BuildBalanceMessageConfig {
- use_lamports_unit,
- trim_trailing_zeros: false,
- ..BuildBalanceMessageConfig::default()
- }
- ),
- 100. * validator.activated_stake as f64 / total_active_stake as f64,
- )
- }
- let padding = if self.number_validators {
- ((self.validators.len() + 1) as f64).log10().floor() as usize + 1
- } else {
- 0
- };
- let header = style(format!(
- "{:padding$} {:<44} {:<38} {} {} {} {} {} {} {:>22}",
- " ",
- "Identity",
- "Vote Account",
- "Commission",
- "Last Vote ",
- "Root Slot ",
- "Skip Rate",
- "Credits",
- "Version",
- "Active Stake",
- padding = padding + 2
- ))
- .bold();
- writeln!(f, "{header}")?;
- let mut sorted_validators = self.validators.clone();
- match self.validators_sort_order {
- CliValidatorsSortOrder::Delinquent => {
- sorted_validators.sort_by_key(|a| a.delinquent);
- }
- CliValidatorsSortOrder::Commission => {
- sorted_validators.sort_by_key(|a| a.commission);
- }
- CliValidatorsSortOrder::EpochCredits => {
- sorted_validators.sort_by_key(|a| a.epoch_credits);
- }
- CliValidatorsSortOrder::Identity => {
- sorted_validators.sort_by(|a, b| a.identity_pubkey.cmp(&b.identity_pubkey));
- }
- CliValidatorsSortOrder::LastVote => {
- sorted_validators.sort_by_key(|a| a.last_vote);
- }
- CliValidatorsSortOrder::Root => {
- sorted_validators.sort_by_key(|a| a.root_slot);
- }
- CliValidatorsSortOrder::VoteAccount => {
- sorted_validators.sort_by(|a, b| a.vote_account_pubkey.cmp(&b.vote_account_pubkey));
- }
- CliValidatorsSortOrder::SkipRate => {
- sorted_validators.sort_by(|a, b| {
- use std::cmp::Ordering;
- match (a.skip_rate, b.skip_rate) {
- (None, None) => Ordering::Equal,
- (None, Some(_)) => Ordering::Greater,
- (Some(_), None) => Ordering::Less,
- (Some(a), Some(b)) => a.partial_cmp(&b).unwrap_or(Ordering::Equal),
- }
- });
- }
- CliValidatorsSortOrder::Stake => {
- sorted_validators.sort_by_key(|a| a.activated_stake);
- }
- CliValidatorsSortOrder::Version => {
- sorted_validators.sort_by(|a, b| {
- (&a.version, a.activated_stake).cmp(&(&b.version, b.activated_stake))
- });
- }
- }
- if self.validators_reverse_sort {
- sorted_validators.reverse();
- }
- let highest_root = sorted_validators
- .iter()
- .map(|v| v.root_slot)
- .max()
- .unwrap_or_default();
- let highest_last_vote = sorted_validators
- .iter()
- .map(|v| v.last_vote)
- .max()
- .unwrap_or_default();
- for (i, validator) in sorted_validators.iter().enumerate() {
- if padding > 0 {
- let num = if self.validators_reverse_sort {
- i + 1
- } else {
- sorted_validators.len() - i
- };
- write!(f, "{num:padding$} ")?;
- }
- write_vote_account(
- f,
- validator,
- self.total_active_stake,
- self.use_lamports_unit,
- highest_last_vote,
- highest_root,
- )?;
- }
- // The actual header has long scrolled away. Print the header once more as a footer
- if self.validators.len() > 100 {
- writeln!(f, "{header}")?;
- }
- writeln!(f)?;
- writeln_name_value(
- f,
- "Average Stake-Weighted Skip Rate:",
- &format!("{:.2}%", self.average_stake_weighted_skip_rate,),
- )?;
- writeln_name_value(
- f,
- "Average Unweighted Skip Rate: ",
- &format!("{:.2}%", self.average_skip_rate),
- )?;
- writeln!(f)?;
- writeln_name_value(
- f,
- "Active Stake:",
- &build_balance_message(self.total_active_stake, self.use_lamports_unit, true),
- )?;
- if self.total_delinquent_stake > 0 {
- writeln_name_value(
- f,
- "Current Stake:",
- &format!(
- "{} ({:0.2}%)",
- &build_balance_message(self.total_current_stake, self.use_lamports_unit, true),
- 100. * self.total_current_stake as f64 / self.total_active_stake as f64
- ),
- )?;
- writeln_name_value(
- f,
- "Delinquent Stake:",
- &format!(
- "{} ({:0.2}%)",
- &build_balance_message(
- self.total_delinquent_stake,
- self.use_lamports_unit,
- true
- ),
- 100. * self.total_delinquent_stake as f64 / self.total_active_stake as f64
- ),
- )?;
- }
- writeln!(f)?;
- writeln!(f, "{}", style("Stake By Version:").bold())?;
- for (version, info) in self.stake_by_version.iter().rev() {
- writeln!(
- f,
- "{:<7} - {:4} current validators ({:>5.2}%){}",
- // convert to a string so that fill/alignment works correctly
- version.to_string(),
- info.current_validators,
- 100. * info.current_active_stake as f64 / self.total_active_stake as f64,
- if info.delinquent_validators > 0 {
- format!(
- " {:3} delinquent validators ({:>5.2}%)",
- info.delinquent_validators,
- 100. * info.delinquent_active_stake as f64 / self.total_active_stake as f64
- )
- } else {
- "".to_string()
- },
- )?;
- }
- Ok(())
- }
- }
- #[derive(Serialize, Deserialize, Clone)]
- #[serde(rename_all = "camelCase")]
- pub struct CliValidator {
- pub identity_pubkey: String,
- pub vote_account_pubkey: String,
- pub commission: u8,
- pub last_vote: u64,
- pub root_slot: u64,
- pub credits: u64, // lifetime credits
- pub epoch_credits: u64, // credits earned in the current epoch
- pub activated_stake: u64,
- pub version: CliVersion,
- pub delinquent: bool,
- pub skip_rate: Option<f64>,
- }
- impl CliValidator {
- pub fn new(
- vote_account: &RpcVoteAccountInfo,
- current_epoch: Epoch,
- version: CliVersion,
- skip_rate: Option<f64>,
- address_labels: &HashMap<String, String>,
- ) -> Self {
- Self::_new(
- vote_account,
- current_epoch,
- version,
- skip_rate,
- address_labels,
- false,
- )
- }
- pub fn new_delinquent(
- vote_account: &RpcVoteAccountInfo,
- current_epoch: Epoch,
- version: CliVersion,
- skip_rate: Option<f64>,
- address_labels: &HashMap<String, String>,
- ) -> Self {
- Self::_new(
- vote_account,
- current_epoch,
- version,
- skip_rate,
- address_labels,
- true,
- )
- }
- fn _new(
- vote_account: &RpcVoteAccountInfo,
- current_epoch: Epoch,
- version: CliVersion,
- skip_rate: Option<f64>,
- address_labels: &HashMap<String, String>,
- delinquent: bool,
- ) -> Self {
- let (credits, epoch_credits) = vote_account
- .epoch_credits
- .iter()
- .find_map(|(epoch, credits, pre_credits)| {
- if *epoch == current_epoch {
- Some((*credits, credits.saturating_sub(*pre_credits)))
- } else {
- None
- }
- })
- .unwrap_or((0, 0));
- Self {
- identity_pubkey: format_labeled_address(&vote_account.node_pubkey, address_labels),
- vote_account_pubkey: format_labeled_address(&vote_account.vote_pubkey, address_labels),
- commission: vote_account.commission,
- last_vote: vote_account.last_vote,
- root_slot: vote_account.root_slot,
- credits,
- epoch_credits,
- activated_stake: vote_account.activated_stake,
- version,
- delinquent,
- skip_rate,
- }
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliHistorySignatureVec(Vec<CliHistorySignature>);
- impl CliHistorySignatureVec {
- pub fn new(list: Vec<CliHistorySignature>) -> Self {
- Self(list)
- }
- }
- impl QuietDisplay for CliHistorySignatureVec {}
- impl VerboseDisplay for CliHistorySignatureVec {
- fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
- for signature in &self.0 {
- VerboseDisplay::write_str(signature, w)?;
- }
- writeln!(w, "{} transactions found", self.0.len())
- }
- }
- impl fmt::Display for CliHistorySignatureVec {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- for signature in &self.0 {
- write!(f, "{signature}")?;
- }
- writeln!(f, "{} transactions found", self.0.len())
- }
- }
- #[derive(Serialize, Deserialize, Default)]
- #[serde(rename_all = "camelCase")]
- pub struct CliHistorySignature {
- pub signature: String,
- #[serde(flatten, skip_serializing_if = "Option::is_none")]
- pub verbose: Option<CliHistoryVerbose>,
- }
- impl QuietDisplay for CliHistorySignature {}
- impl VerboseDisplay for CliHistorySignature {
- fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
- let verbose = self
- .verbose
- .as_ref()
- .expect("should have verbose.is_some()");
- writeln!(
- w,
- "{} [slot={} {}status={}] {}",
- self.signature,
- verbose.slot,
- match verbose.block_time {
- None => "".to_string(),
- Some(block_time) => format!("timestamp={} ", unix_timestamp_to_string(block_time)),
- },
- if let Some(err) = &verbose.err {
- format!("Failed: {err:?}")
- } else {
- match &verbose.confirmation_status {
- None => "Finalized".to_string(),
- Some(status) => format!("{status:?}"),
- }
- },
- verbose.memo.clone().unwrap_or_default(),
- )
- }
- }
- impl fmt::Display for CliHistorySignature {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(f, "{}", self.signature)
- }
- }
- #[derive(Serialize, Deserialize, Default)]
- #[serde(rename_all = "camelCase")]
- pub struct CliHistoryVerbose {
- pub slot: Slot,
- pub block_time: Option<UnixTimestamp>,
- pub err: Option<UiTransactionError>,
- pub confirmation_status: Option<TransactionConfirmationStatus>,
- pub memo: Option<String>,
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliHistoryTransactionVec(Vec<CliTransactionConfirmation>);
- impl CliHistoryTransactionVec {
- pub fn new(list: Vec<CliTransactionConfirmation>) -> Self {
- Self(list)
- }
- }
- impl QuietDisplay for CliHistoryTransactionVec {}
- impl VerboseDisplay for CliHistoryTransactionVec {}
- impl fmt::Display for CliHistoryTransactionVec {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- for transaction in &self.0 {
- VerboseDisplay::write_str(transaction, f)?;
- writeln!(f)?;
- }
- writeln!(f, "{} transactions found", self.0.len())
- }
- }
- #[derive(Default, Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliNonceAccount {
- pub balance: u64,
- pub minimum_balance_for_rent_exemption: u64,
- pub nonce: Option<String>,
- pub lamports_per_signature: Option<u64>,
- pub authority: Option<String>,
- #[serde(skip_serializing)]
- pub use_lamports_unit: bool,
- }
- impl QuietDisplay for CliNonceAccount {}
- impl VerboseDisplay for CliNonceAccount {}
- impl fmt::Display for CliNonceAccount {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(
- f,
- "Balance: {}",
- build_balance_message(self.balance, self.use_lamports_unit, true)
- )?;
- writeln!(
- f,
- "Minimum Balance Required: {}",
- build_balance_message(
- self.minimum_balance_for_rent_exemption,
- self.use_lamports_unit,
- true
- )
- )?;
- let nonce = self.nonce.as_deref().unwrap_or("uninitialized");
- writeln!(f, "Nonce blockhash: {nonce}")?;
- if let Some(fees) = self.lamports_per_signature {
- writeln!(f, "Fee: {fees} lamports per signature")?;
- } else {
- writeln!(f, "Fees: uninitialized")?;
- }
- let authority = self.authority.as_deref().unwrap_or("uninitialized");
- writeln!(f, "Authority: {authority}")
- }
- }
- #[derive(Serialize, Deserialize)]
- pub struct CliStakeVec(Vec<CliKeyedStakeState>);
- impl CliStakeVec {
- pub fn new(list: Vec<CliKeyedStakeState>) -> Self {
- Self(list)
- }
- }
- impl QuietDisplay for CliStakeVec {}
- impl VerboseDisplay for CliStakeVec {
- fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
- for state in &self.0 {
- writeln!(w)?;
- VerboseDisplay::write_str(state, w)?;
- }
- Ok(())
- }
- }
- impl fmt::Display for CliStakeVec {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- for state in &self.0 {
- writeln!(f)?;
- write!(f, "{state}")?;
- }
- Ok(())
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliKeyedStakeState {
- pub stake_pubkey: String,
- #[serde(flatten)]
- pub stake_state: CliStakeState,
- }
- impl QuietDisplay for CliKeyedStakeState {}
- impl VerboseDisplay for CliKeyedStakeState {
- fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
- writeln!(w, "Stake Pubkey: {}", self.stake_pubkey)?;
- VerboseDisplay::write_str(&self.stake_state, w)
- }
- }
- impl fmt::Display for CliKeyedStakeState {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(f, "Stake Pubkey: {}", self.stake_pubkey)?;
- write!(f, "{}", self.stake_state)
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliEpochReward {
- pub epoch: Epoch,
- pub effective_slot: Slot,
- pub amount: u64, // lamports
- pub post_balance: u64, // lamports
- pub percent_change: f64,
- pub apr: Option<f64>,
- pub commission: Option<u8>,
- pub block_time: UnixTimestamp,
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliKeyedEpochReward {
- pub address: String,
- pub reward: Option<CliEpochReward>,
- }
- #[derive(Serialize, Deserialize, Default)]
- #[serde(rename_all = "camelCase")]
- pub struct CliEpochRewardsMetadata {
- pub epoch: Epoch,
- #[deprecated(
- since = "2.2.0",
- note = "Please use CliEpochReward::effective_slot per reward"
- )]
- pub effective_slot: Slot,
- #[deprecated(
- since = "2.2.0",
- note = "Please use CliEpochReward::block_time per reward"
- )]
- pub block_time: UnixTimestamp,
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliKeyedEpochRewards {
- #[serde(flatten, skip_serializing_if = "Option::is_none")]
- pub epoch_metadata: Option<CliEpochRewardsMetadata>,
- pub rewards: Vec<CliKeyedEpochReward>,
- }
- impl QuietDisplay for CliKeyedEpochRewards {}
- impl VerboseDisplay for CliKeyedEpochRewards {
- fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
- if self.rewards.is_empty() {
- writeln!(w, "No rewards found in epoch")?;
- return Ok(());
- }
- if let Some(metadata) = &self.epoch_metadata {
- writeln!(w, "Epoch: {}", metadata.epoch)?;
- }
- writeln!(w, "Epoch Rewards:")?;
- writeln!(
- w,
- " {:<44} {:<11} {:<23} {:<18} {:<20} {:>14} {:>7} {:>10}",
- "Address",
- "Reward Slot",
- "Time",
- "Amount",
- "New Balance",
- "Percent Change",
- "APR",
- "Commission"
- )?;
- for keyed_reward in &self.rewards {
- match &keyed_reward.reward {
- Some(reward) => {
- writeln!(
- w,
- " {:<44} {:<11} {:<23} ◎{:<17.9} ◎{:<19.9} {:>13.9}% {:>7} {:>10}",
- keyed_reward.address,
- reward.effective_slot,
- Utc.timestamp_opt(reward.block_time, 0).unwrap(),
- build_balance_message(reward.amount, false, false),
- build_balance_message(reward.post_balance, false, false),
- reward.percent_change,
- reward
- .apr
- .map(|apr| format!("{apr:.2}%"))
- .unwrap_or_default(),
- reward
- .commission
- .map(|commission| format!("{commission}%"))
- .unwrap_or_else(|| "-".to_string()),
- )?;
- }
- None => {
- writeln!(w, " {:<44} No rewards in epoch", keyed_reward.address,)?;
- }
- }
- }
- Ok(())
- }
- }
- impl fmt::Display for CliKeyedEpochRewards {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- if self.rewards.is_empty() {
- writeln!(f, "No rewards found in epoch")?;
- return Ok(());
- }
- if let Some(metadata) = &self.epoch_metadata {
- writeln!(f, "Epoch: {}", metadata.epoch)?;
- }
- writeln!(f, "Epoch Rewards:")?;
- writeln!(
- f,
- " {:<44} {:<18} {:<18} {:>14} {:>7} {:>10}",
- "Address", "Amount", "New Balance", "Percent Change", "APR", "Commission"
- )?;
- for keyed_reward in &self.rewards {
- match &keyed_reward.reward {
- Some(reward) => {
- writeln!(
- f,
- " {:<44} ◎{:<17.9} ◎{:<17.9} {:>13.9}% {:>7} {:>10}",
- keyed_reward.address,
- build_balance_message(reward.amount, false, false),
- build_balance_message(reward.post_balance, false, false),
- reward.percent_change,
- reward
- .apr
- .map(|apr| format!("{apr:.2}%"))
- .unwrap_or_default(),
- reward
- .commission
- .map(|commission| format!("{commission}%"))
- .unwrap_or_else(|| "-".to_string())
- )?;
- }
- None => {
- writeln!(f, " {:<44} No rewards in epoch", keyed_reward.address,)?;
- }
- }
- }
- Ok(())
- }
- }
- fn show_votes_and_credits(
- f: &mut fmt::Formatter,
- votes: &[CliLandedVote],
- epoch_voting_history: &[CliEpochVotingHistory],
- ) -> fmt::Result {
- if votes.is_empty() {
- return Ok(());
- }
- // Existence of this should guarantee the occurrence of vote truncation
- let newest_history_entry = epoch_voting_history.iter().next_back();
- writeln!(
- f,
- "{} Votes (using {}/{} entries):",
- (if newest_history_entry.is_none() {
- "All"
- } else {
- "Recent"
- }),
- votes.len(),
- MAX_LOCKOUT_HISTORY
- )?;
- for vote in votes.iter().rev() {
- write!(
- f,
- "- slot: {} (confirmation count: {})",
- vote.slot, vote.confirmation_count
- )?;
- if vote.latency == 0 {
- writeln!(f)?;
- } else {
- writeln!(f, " (latency {})", vote.latency)?;
- }
- }
- if let Some(newest) = newest_history_entry {
- writeln!(
- f,
- "- ... (truncated {} rooted votes, which have been credited)",
- newest.credits
- )?;
- }
- if !epoch_voting_history.is_empty() {
- writeln!(
- f,
- "{} Epoch Voting History (using {}/{} entries):",
- (if epoch_voting_history.len() < MAX_EPOCH_CREDITS_HISTORY {
- "All"
- } else {
- "Recent"
- }),
- epoch_voting_history.len(),
- MAX_EPOCH_CREDITS_HISTORY
- )?;
- writeln!(
- f,
- "* missed credits include slots unavailable to vote on due to delinquent leaders",
- )?;
- }
- for entry in epoch_voting_history.iter().rev() {
- writeln!(
- f, // tame fmt so that this will be folded like following
- "- epoch: {}",
- entry.epoch
- )?;
- writeln!(
- f,
- " credits range: ({}..{}]",
- entry.prev_credits, entry.credits
- )?;
- writeln!(
- f,
- " credits/max credits: {}/{}",
- entry.credits_earned,
- entry.slots_in_epoch * u64::from(entry.max_credits_per_slot)
- )?;
- }
- if let Some(oldest) = epoch_voting_history.iter().next() {
- if oldest.prev_credits > 0 {
- // Oldest entry doesn't start with 0. so history must be truncated...
- // count of this combined pseudo credits range: (0..=oldest.prev_credits] like the above
- // (or this is just [1..=oldest.prev_credits] for human's simpler minds)
- let count = oldest.prev_credits;
- writeln!(
- f,
- "- ... (omitting {count} past rooted votes, which have already been credited)"
- )?;
- }
- }
- Ok(())
- }
- enum Format {
- Csv,
- Human,
- }
- macro_rules! format_as {
- ($target:expr, $fmt1:expr, $fmt2:expr, $which_fmt:expr, $($arg:tt)*) => {
- match $which_fmt {
- Format::Csv => {
- writeln!(
- $target,
- $fmt1,
- $($arg)*
- )
- },
- Format::Human => {
- writeln!(
- $target,
- $fmt2,
- $($arg)*
- )
- }
- }
- };
- }
- fn show_epoch_rewards(
- f: &mut fmt::Formatter,
- epoch_rewards: &Option<Vec<CliEpochReward>>,
- use_csv: bool,
- ) -> fmt::Result {
- if let Some(epoch_rewards) = epoch_rewards {
- if epoch_rewards.is_empty() {
- return Ok(());
- }
- writeln!(f, "Epoch Rewards:")?;
- let fmt = if use_csv { Format::Csv } else { Format::Human };
- format_as!(
- f,
- "{},{},{},{},{},{},{},{}",
- " {:<6} {:<11} {:<26} {:<18} {:<18} {:>14} {:>14} {:>10}",
- fmt,
- "Epoch",
- "Reward Slot",
- "Time",
- "Amount",
- "New Balance",
- "Percent Change",
- "APR",
- "Commission",
- )?;
- for reward in epoch_rewards {
- format_as!(
- f,
- "{},{},{},{},{},{}%,{},{}",
- " {:<6} {:<11} {:<26} ◎{:<17.11} ◎{:<17.11} {:>13.3}% {:>14} {:>10}",
- fmt,
- reward.epoch,
- reward.effective_slot,
- Utc.timestamp_opt(reward.block_time, 0).unwrap(),
- build_balance_message(reward.amount, false, false),
- build_balance_message(reward.post_balance, false, false),
- reward.percent_change,
- reward
- .apr
- .map(|apr| format!("{apr:.2}%"))
- .unwrap_or_default(),
- reward
- .commission
- .map(|commission| format!("{commission}%"))
- .unwrap_or_else(|| "-".to_string())
- )?;
- }
- }
- Ok(())
- }
- #[derive(Default, Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliStakeState {
- pub stake_type: CliStakeType,
- pub account_balance: u64,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub credits_observed: Option<u64>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub delegated_stake: Option<u64>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub delegated_vote_account_address: Option<String>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub activation_epoch: Option<Epoch>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub deactivation_epoch: Option<Epoch>,
- #[serde(flatten, skip_serializing_if = "Option::is_none")]
- pub authorized: Option<CliAuthorized>,
- #[serde(flatten, skip_serializing_if = "Option::is_none")]
- pub lockup: Option<CliLockup>,
- #[serde(skip_serializing)]
- pub use_lamports_unit: bool,
- #[serde(skip_serializing)]
- pub current_epoch: Epoch,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub rent_exempt_reserve: Option<u64>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub active_stake: Option<u64>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub activating_stake: Option<u64>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub deactivating_stake: Option<u64>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub epoch_rewards: Option<Vec<CliEpochReward>>,
- #[serde(skip_serializing)]
- pub use_csv: bool,
- }
- impl QuietDisplay for CliStakeState {}
- impl VerboseDisplay for CliStakeState {
- fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
- write!(w, "{self}")?;
- if let Some(credits) = self.credits_observed {
- writeln!(w, "Credits Observed: {credits}")?;
- }
- Ok(())
- }
- }
- fn show_inactive_stake(
- me: &CliStakeState,
- f: &mut fmt::Formatter,
- delegated_stake: u64,
- ) -> fmt::Result {
- if let Some(deactivation_epoch) = me.deactivation_epoch {
- if me.current_epoch > deactivation_epoch {
- let deactivating_stake = me.deactivating_stake.or(me.active_stake);
- if let Some(deactivating_stake) = deactivating_stake {
- writeln!(
- f,
- "Inactive Stake: {}",
- build_balance_message(
- delegated_stake - deactivating_stake,
- me.use_lamports_unit,
- true
- ),
- )?;
- writeln!(
- f,
- "Deactivating Stake: {}",
- build_balance_message(deactivating_stake, me.use_lamports_unit, true),
- )?;
- }
- }
- writeln!(
- f,
- "Stake deactivates starting from epoch: {deactivation_epoch}"
- )?;
- }
- if let Some(delegated_vote_account_address) = &me.delegated_vote_account_address {
- writeln!(
- f,
- "Delegated Vote Account Address: {delegated_vote_account_address}"
- )?;
- }
- Ok(())
- }
- fn show_active_stake(
- me: &CliStakeState,
- f: &mut fmt::Formatter,
- delegated_stake: u64,
- ) -> fmt::Result {
- if me
- .deactivation_epoch
- .map(|d| me.current_epoch <= d)
- .unwrap_or(true)
- {
- let active_stake = me.active_stake.unwrap_or(0);
- writeln!(
- f,
- "Active Stake: {}",
- build_balance_message(active_stake, me.use_lamports_unit, true),
- )?;
- let activating_stake = me.activating_stake.or_else(|| {
- if me.active_stake.is_none() {
- Some(delegated_stake)
- } else {
- None
- }
- });
- if let Some(activating_stake) = activating_stake {
- writeln!(
- f,
- "Activating Stake: {}",
- build_balance_message(activating_stake, me.use_lamports_unit, true),
- )?;
- writeln!(
- f,
- "Stake activates starting from epoch: {}",
- me.activation_epoch.unwrap()
- )?;
- }
- }
- Ok(())
- }
- impl fmt::Display for CliStakeState {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- fn show_authorized(f: &mut fmt::Formatter, authorized: &CliAuthorized) -> fmt::Result {
- writeln!(f, "Stake Authority: {}", authorized.staker)?;
- writeln!(f, "Withdraw Authority: {}", authorized.withdrawer)?;
- Ok(())
- }
- fn show_lockup(f: &mut fmt::Formatter, lockup: Option<&CliLockup>) -> fmt::Result {
- if let Some(lockup) = lockup {
- if lockup.unix_timestamp != UnixTimestamp::default() {
- writeln!(
- f,
- "Lockup Timestamp: {}",
- unix_timestamp_to_string(lockup.unix_timestamp)
- )?;
- }
- if lockup.epoch != Epoch::default() {
- writeln!(f, "Lockup Epoch: {}", lockup.epoch)?;
- }
- writeln!(f, "Lockup Custodian: {}", lockup.custodian)?;
- }
- Ok(())
- }
- writeln!(
- f,
- "Balance: {}",
- build_balance_message(self.account_balance, self.use_lamports_unit, true)
- )?;
- if let Some(rent_exempt_reserve) = self.rent_exempt_reserve {
- writeln!(
- f,
- "Rent Exempt Reserve: {}",
- build_balance_message(rent_exempt_reserve, self.use_lamports_unit, true)
- )?;
- }
- match self.stake_type {
- CliStakeType::RewardsPool => writeln!(f, "Stake account is a rewards pool")?,
- CliStakeType::Uninitialized => writeln!(f, "Stake account is uninitialized")?,
- CliStakeType::Initialized => {
- writeln!(f, "Stake account is undelegated")?;
- show_authorized(f, self.authorized.as_ref().unwrap())?;
- show_lockup(f, self.lockup.as_ref())?;
- }
- CliStakeType::Stake => {
- let show_delegation = {
- self.active_stake.is_some()
- || self.activating_stake.is_some()
- || self.deactivating_stake.is_some()
- || self
- .deactivation_epoch
- .map(|de| de > self.current_epoch)
- .unwrap_or(true)
- };
- if show_delegation {
- let delegated_stake = self.delegated_stake.unwrap();
- writeln!(
- f,
- "Delegated Stake: {}",
- build_balance_message(delegated_stake, self.use_lamports_unit, true)
- )?;
- show_active_stake(self, f, delegated_stake)?;
- show_inactive_stake(self, f, delegated_stake)?;
- } else {
- writeln!(f, "Stake account is undelegated")?;
- }
- show_authorized(f, self.authorized.as_ref().unwrap())?;
- show_lockup(f, self.lockup.as_ref())?;
- show_epoch_rewards(f, &self.epoch_rewards, self.use_csv)?
- }
- }
- Ok(())
- }
- }
- #[derive(Serialize, Deserialize, PartialEq, Eq)]
- pub enum CliStakeType {
- Stake,
- RewardsPool,
- Uninitialized,
- Initialized,
- }
- impl Default for CliStakeType {
- fn default() -> Self {
- Self::Uninitialized
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliStakeHistory {
- pub entries: Vec<CliStakeHistoryEntry>,
- #[serde(skip_serializing)]
- pub use_lamports_unit: bool,
- }
- impl QuietDisplay for CliStakeHistory {}
- impl VerboseDisplay for CliStakeHistory {}
- impl fmt::Display for CliStakeHistory {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(f)?;
- writeln!(
- f,
- "{}",
- style(format!(
- " {:<5} {:>20} {:>20} {:>20}",
- "Epoch", "Effective Stake", "Activating Stake", "Deactivating Stake",
- ))
- .bold()
- )?;
- let config = BuildBalanceMessageConfig {
- use_lamports_unit: self.use_lamports_unit,
- show_unit: false,
- trim_trailing_zeros: false,
- };
- for entry in &self.entries {
- writeln!(
- f,
- " {:>5} {:>20} {:>20} {:>20} {}",
- entry.epoch,
- build_balance_message_with_config(entry.effective_stake, &config),
- build_balance_message_with_config(entry.activating_stake, &config),
- build_balance_message_with_config(entry.deactivating_stake, &config),
- if self.use_lamports_unit {
- "lamports"
- } else {
- "SOL"
- }
- )?;
- }
- Ok(())
- }
- }
- impl From<&(Epoch, StakeHistoryEntry)> for CliStakeHistoryEntry {
- fn from((epoch, entry): &(Epoch, StakeHistoryEntry)) -> Self {
- Self {
- epoch: *epoch,
- effective_stake: entry.effective,
- activating_stake: entry.activating,
- deactivating_stake: entry.deactivating,
- }
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliStakeHistoryEntry {
- pub epoch: Epoch,
- pub effective_stake: u64,
- pub activating_stake: u64,
- pub deactivating_stake: u64,
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliAuthorized {
- pub staker: String,
- pub withdrawer: String,
- }
- impl From<&Authorized> for CliAuthorized {
- fn from(authorized: &Authorized) -> Self {
- Self {
- staker: authorized.staker.to_string(),
- withdrawer: authorized.withdrawer.to_string(),
- }
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliLockup {
- pub unix_timestamp: UnixTimestamp,
- pub epoch: Epoch,
- pub custodian: String,
- }
- impl From<&Lockup> for CliLockup {
- fn from(lockup: &Lockup) -> Self {
- Self {
- unix_timestamp: lockup.unix_timestamp,
- epoch: lockup.epoch,
- custodian: lockup.custodian.to_string(),
- }
- }
- }
- #[derive(Serialize, Deserialize)]
- pub struct CliValidatorInfoVec(Vec<CliValidatorInfo>);
- impl CliValidatorInfoVec {
- pub fn new(list: Vec<CliValidatorInfo>) -> Self {
- Self(list)
- }
- }
- impl QuietDisplay for CliValidatorInfoVec {}
- impl VerboseDisplay for CliValidatorInfoVec {}
- impl fmt::Display for CliValidatorInfoVec {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- if self.0.is_empty() {
- writeln!(f, "No validator info accounts found")?;
- }
- for validator_info in &self.0 {
- writeln!(f)?;
- write!(f, "{validator_info}")?;
- }
- Ok(())
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliValidatorInfo {
- pub identity_pubkey: String,
- pub info_pubkey: String,
- pub info: Map<String, Value>,
- }
- impl QuietDisplay for CliValidatorInfo {}
- impl VerboseDisplay for CliValidatorInfo {}
- impl fmt::Display for CliValidatorInfo {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln_name_value(f, "Validator Identity:", &self.identity_pubkey)?;
- writeln_name_value(f, " Info Address:", &self.info_pubkey)?;
- for (key, value) in self.info.iter() {
- writeln_name_value(
- f,
- &format!(" {}:", to_title_case(key)),
- value.as_str().unwrap_or("?"),
- )?;
- }
- Ok(())
- }
- }
- #[derive(Default, Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliVoteAccount {
- pub account_balance: u64,
- pub validator_identity: String,
- #[serde(flatten)]
- pub authorized_voters: CliAuthorizedVoters,
- pub authorized_withdrawer: String,
- pub credits: u64,
- pub commission: u8,
- pub root_slot: Option<Slot>,
- pub recent_timestamp: BlockTimestamp,
- pub votes: Vec<CliLandedVote>,
- pub epoch_voting_history: Vec<CliEpochVotingHistory>,
- #[serde(skip_serializing)]
- pub use_lamports_unit: bool,
- #[serde(skip_serializing)]
- pub use_csv: bool,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub epoch_rewards: Option<Vec<CliEpochReward>>,
- }
- impl QuietDisplay for CliVoteAccount {}
- impl VerboseDisplay for CliVoteAccount {}
- impl fmt::Display for CliVoteAccount {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(
- f,
- "Account Balance: {}",
- build_balance_message(self.account_balance, self.use_lamports_unit, true)
- )?;
- writeln!(f, "Validator Identity: {}", self.validator_identity)?;
- writeln!(f, "Vote Authority: {}", self.authorized_voters)?;
- writeln!(f, "Withdraw Authority: {}", self.authorized_withdrawer)?;
- writeln!(f, "Credits: {}", self.credits)?;
- writeln!(f, "Commission: {}%", self.commission)?;
- writeln!(
- f,
- "Root Slot: {}",
- match self.root_slot {
- Some(slot) => slot.to_string(),
- None => "~".to_string(),
- }
- )?;
- writeln!(
- f,
- "Recent Timestamp: {} from slot {}",
- unix_timestamp_to_string(self.recent_timestamp.timestamp),
- self.recent_timestamp.slot
- )?;
- show_votes_and_credits(f, &self.votes, &self.epoch_voting_history)?;
- show_epoch_rewards(f, &self.epoch_rewards, self.use_csv)?;
- Ok(())
- }
- }
- #[derive(Default, Debug, Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliAuthorizedVoters {
- authorized_voters: BTreeMap<Epoch, String>,
- }
- impl QuietDisplay for CliAuthorizedVoters {}
- impl VerboseDisplay for CliAuthorizedVoters {}
- impl fmt::Display for CliAuthorizedVoters {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- if let Some((_epoch, current_authorized_voter)) = self.authorized_voters.first_key_value() {
- write!(f, "{current_authorized_voter}")?;
- } else {
- write!(f, "None")?;
- }
- if self.authorized_voters.len() > 1 {
- let (epoch, upcoming_authorized_voter) = self
- .authorized_voters
- .last_key_value()
- .expect("CliAuthorizedVoters::authorized_voters.len() > 1");
- writeln!(f)?;
- write!(
- f,
- " New Vote Authority as of Epoch {epoch}: {upcoming_authorized_voter}"
- )?;
- }
- Ok(())
- }
- }
- impl From<&AuthorizedVoters> for CliAuthorizedVoters {
- fn from(authorized_voters: &AuthorizedVoters) -> Self {
- let mut voter_map: BTreeMap<Epoch, String> = BTreeMap::new();
- for (epoch, voter) in authorized_voters.iter() {
- voter_map.insert(*epoch, voter.to_string());
- }
- Self {
- authorized_voters: voter_map,
- }
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliEpochVotingHistory {
- pub epoch: Epoch,
- pub slots_in_epoch: u64,
- pub credits_earned: u64,
- pub credits: u64,
- pub prev_credits: u64,
- pub max_credits_per_slot: u8,
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliLandedVote {
- pub latency: u8,
- pub slot: Slot,
- pub confirmation_count: u32,
- }
- impl From<&LandedVote> for CliLandedVote {
- fn from(landed_vote: &LandedVote) -> Self {
- Self {
- latency: landed_vote.latency,
- slot: landed_vote.slot(),
- confirmation_count: landed_vote.confirmation_count(),
- }
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliBlockTime {
- pub slot: Slot,
- pub timestamp: UnixTimestamp,
- }
- impl QuietDisplay for CliBlockTime {}
- impl VerboseDisplay for CliBlockTime {}
- impl fmt::Display for CliBlockTime {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln_name_value(f, "Block:", &self.slot.to_string())?;
- writeln_name_value(f, "Date:", &unix_timestamp_to_string(self.timestamp))
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliLeaderSchedule {
- pub epoch: Epoch,
- pub leader_schedule_entries: Vec<CliLeaderScheduleEntry>,
- }
- impl QuietDisplay for CliLeaderSchedule {}
- impl VerboseDisplay for CliLeaderSchedule {}
- impl fmt::Display for CliLeaderSchedule {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- for entry in &self.leader_schedule_entries {
- writeln!(f, " {:<15} {:<44}", entry.slot, entry.leader)?;
- }
- Ok(())
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliLeaderScheduleEntry {
- pub slot: Slot,
- pub leader: String,
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliInflation {
- pub governor: RpcInflationGovernor,
- pub current_rate: RpcInflationRate,
- }
- impl QuietDisplay for CliInflation {}
- impl VerboseDisplay for CliInflation {}
- impl fmt::Display for CliInflation {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(f, "{}", style("Inflation Governor:").bold())?;
- if (self.governor.initial - self.governor.terminal).abs() < f64::EPSILON {
- writeln!(
- f,
- "Fixed rate: {:>5.2}%",
- self.governor.terminal * 100.
- )?;
- } else {
- writeln!(
- f,
- "Initial rate: {:>5.2}%",
- self.governor.initial * 100.
- )?;
- writeln!(
- f,
- "Terminal rate: {:>5.2}%",
- self.governor.terminal * 100.
- )?;
- writeln!(
- f,
- "Rate reduction per year: {:>5.2}%",
- self.governor.taper * 100.
- )?;
- writeln!(
- f,
- "* Rate reduction is derived using the target slot time in genesis config"
- )?;
- }
- if self.governor.foundation_term > 0. {
- writeln!(
- f,
- "Foundation percentage: {:>5.2}%",
- self.governor.foundation
- )?;
- writeln!(
- f,
- "Foundation term: {:.1} years",
- self.governor.foundation_term
- )?;
- }
- writeln!(
- f,
- "\n{}",
- style(format!("Inflation for Epoch {}:", self.current_rate.epoch)).bold()
- )?;
- writeln!(
- f,
- "Total rate: {:>5.2}%",
- self.current_rate.total * 100.
- )?;
- writeln!(
- f,
- "Staking rate: {:>5.2}%",
- self.current_rate.validator * 100.
- )?;
- if self.current_rate.foundation > 0. {
- writeln!(
- f,
- "Foundation rate: {:>5.2}%",
- self.current_rate.foundation * 100.
- )?;
- }
- Ok(())
- }
- }
- #[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq)]
- #[serde(rename_all = "camelCase")]
- pub struct CliSignOnlyData {
- pub blockhash: String,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub message: Option<String>,
- #[serde(skip_serializing_if = "Vec::is_empty", default)]
- pub signers: Vec<String>,
- #[serde(skip_serializing_if = "Vec::is_empty", default)]
- pub absent: Vec<String>,
- #[serde(skip_serializing_if = "Vec::is_empty", default)]
- pub bad_sig: Vec<String>,
- }
- impl QuietDisplay for CliSignOnlyData {}
- impl VerboseDisplay for CliSignOnlyData {}
- impl fmt::Display for CliSignOnlyData {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(f)?;
- writeln_name_value(f, "Blockhash:", &self.blockhash)?;
- if let Some(message) = self.message.as_ref() {
- writeln_name_value(f, "Transaction Message:", message)?;
- }
- if !self.signers.is_empty() {
- writeln!(f, "{}", style("Signers (Pubkey=Signature):").bold())?;
- for signer in self.signers.iter() {
- writeln!(f, " {signer}")?;
- }
- }
- if !self.absent.is_empty() {
- writeln!(f, "{}", style("Absent Signers (Pubkey):").bold())?;
- for pubkey in self.absent.iter() {
- writeln!(f, " {pubkey}")?;
- }
- }
- if !self.bad_sig.is_empty() {
- writeln!(f, "{}", style("Bad Signatures (Pubkey):").bold())?;
- for pubkey in self.bad_sig.iter() {
- writeln!(f, " {pubkey}")?;
- }
- }
- Ok(())
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliSignature {
- pub signature: String,
- }
- impl QuietDisplay for CliSignature {}
- impl VerboseDisplay for CliSignature {}
- impl fmt::Display for CliSignature {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(f)?;
- writeln_name_value(f, "Signature:", &self.signature)?;
- Ok(())
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliAccountBalances {
- pub accounts: Vec<RpcAccountBalance>,
- }
- impl QuietDisplay for CliAccountBalances {}
- impl VerboseDisplay for CliAccountBalances {}
- impl fmt::Display for CliAccountBalances {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(
- f,
- "{}",
- style(format!("{:<44} {}", "Address", "Balance")).bold()
- )?;
- for account in &self.accounts {
- writeln!(
- f,
- "{:<44} {}",
- account.address,
- &format!(
- "{} SOL",
- build_balance_message(account.lamports, false, false)
- ),
- )?;
- }
- Ok(())
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliSupply {
- pub total: u64,
- pub circulating: u64,
- pub non_circulating: u64,
- pub non_circulating_accounts: Vec<String>,
- #[serde(skip_serializing)]
- pub print_accounts: bool,
- }
- impl From<RpcSupply> for CliSupply {
- fn from(rpc_supply: RpcSupply) -> Self {
- Self {
- total: rpc_supply.total,
- circulating: rpc_supply.circulating,
- non_circulating: rpc_supply.non_circulating,
- non_circulating_accounts: rpc_supply.non_circulating_accounts,
- print_accounts: false,
- }
- }
- }
- impl QuietDisplay for CliSupply {}
- impl VerboseDisplay for CliSupply {}
- impl fmt::Display for CliSupply {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln_name_value(
- f,
- "Total:",
- &format!("{} SOL", build_balance_message(self.total, false, false)),
- )?;
- writeln_name_value(
- f,
- "Circulating:",
- &format!(
- "{} SOL",
- build_balance_message(self.circulating, false, false)
- ),
- )?;
- writeln_name_value(
- f,
- "Non-Circulating:",
- &format!(
- "{} SOL",
- build_balance_message(self.non_circulating, false, false)
- ),
- )?;
- if self.print_accounts {
- writeln!(f)?;
- writeln_name_value(f, "Non-Circulating Accounts:", " ")?;
- for account in &self.non_circulating_accounts {
- writeln!(f, " {account}")?;
- }
- }
- Ok(())
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliFeesInner {
- pub slot: Slot,
- pub blockhash: String,
- pub lamports_per_signature: u64,
- pub last_valid_slot: Option<Slot>,
- pub last_valid_block_height: Option<Slot>,
- }
- impl QuietDisplay for CliFeesInner {}
- impl VerboseDisplay for CliFeesInner {}
- impl fmt::Display for CliFeesInner {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln_name_value(f, "Blockhash:", &self.blockhash)?;
- writeln_name_value(
- f,
- "Lamports per signature:",
- &self.lamports_per_signature.to_string(),
- )?;
- let last_valid_block_height = self
- .last_valid_block_height
- .map(|s| s.to_string())
- .unwrap_or_default();
- writeln_name_value(f, "Last valid block height:", &last_valid_block_height)
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliFees {
- #[serde(flatten, skip_serializing_if = "Option::is_none")]
- pub inner: Option<CliFeesInner>,
- }
- impl QuietDisplay for CliFees {}
- impl VerboseDisplay for CliFees {}
- impl fmt::Display for CliFees {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self.inner.as_ref() {
- Some(inner) => write!(f, "{inner}"),
- None => write!(f, "Fees unavailable"),
- }
- }
- }
- impl CliFees {
- pub fn some(
- slot: Slot,
- blockhash: Hash,
- lamports_per_signature: u64,
- last_valid_slot: Option<Slot>,
- last_valid_block_height: Option<Slot>,
- ) -> Self {
- Self {
- inner: Some(CliFeesInner {
- slot,
- blockhash: blockhash.to_string(),
- lamports_per_signature,
- last_valid_slot,
- last_valid_block_height,
- }),
- }
- }
- pub fn none() -> Self {
- Self { inner: None }
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliTokenAccount {
- pub address: String,
- #[serde(flatten)]
- pub token_account: UiTokenAccount,
- }
- impl QuietDisplay for CliTokenAccount {}
- impl VerboseDisplay for CliTokenAccount {}
- impl fmt::Display for CliTokenAccount {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(f)?;
- writeln_name_value(f, "Address:", &self.address)?;
- let account = &self.token_account;
- writeln_name_value(
- f,
- "Balance:",
- &account.token_amount.real_number_string_trimmed(),
- )?;
- let mint = format!(
- "{}{}",
- account.mint,
- if account.is_native { " (native)" } else { "" }
- );
- writeln_name_value(f, "Mint:", &mint)?;
- writeln_name_value(f, "Owner:", &account.owner)?;
- writeln_name_value(f, "State:", &format!("{:?}", account.state))?;
- if let Some(delegate) = &account.delegate {
- writeln!(f, "Delegation:")?;
- writeln_name_value(f, " Delegate:", delegate)?;
- let allowance = account.delegated_amount.as_ref().unwrap();
- writeln_name_value(f, " Allowance:", &allowance.real_number_string_trimmed())?;
- }
- writeln_name_value(
- f,
- "Close authority:",
- account.close_authority.as_ref().unwrap_or(&String::new()),
- )?;
- Ok(())
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliProgramId {
- pub program_id: String,
- pub signature: Option<String>,
- }
- impl QuietDisplay for CliProgramId {}
- impl VerboseDisplay for CliProgramId {}
- impl fmt::Display for CliProgramId {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln_name_value(f, "Program Id:", &self.program_id)?;
- if let Some(ref signature) = self.signature {
- writeln!(f)?;
- writeln_name_value(f, "Signature:", signature)?;
- }
- Ok(())
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliProgramBuffer {
- pub buffer: String,
- }
- impl QuietDisplay for CliProgramBuffer {}
- impl VerboseDisplay for CliProgramBuffer {}
- impl fmt::Display for CliProgramBuffer {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln_name_value(f, "Buffer:", &self.buffer)
- }
- }
- #[derive(Debug, Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub enum CliProgramAccountType {
- Buffer,
- Program,
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliProgramAuthority {
- pub authority: String,
- pub account_type: CliProgramAccountType,
- }
- impl QuietDisplay for CliProgramAuthority {}
- impl VerboseDisplay for CliProgramAuthority {}
- impl fmt::Display for CliProgramAuthority {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln_name_value(f, "Account Type:", &format!("{:?}", self.account_type))?;
- writeln_name_value(f, "Authority:", &self.authority)
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliProgram {
- pub program_id: String,
- pub owner: String,
- pub data_len: usize,
- }
- impl QuietDisplay for CliProgram {}
- impl VerboseDisplay for CliProgram {}
- impl fmt::Display for CliProgram {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(f)?;
- writeln_name_value(f, "Program Id:", &self.program_id)?;
- writeln_name_value(f, "Owner:", &self.owner)?;
- writeln_name_value(
- f,
- "Data Length:",
- &format!("{:?} ({:#x?}) bytes", self.data_len, self.data_len),
- )?;
- Ok(())
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliProgramV4 {
- pub program_id: String,
- pub owner: String,
- pub authority: String,
- pub last_deploy_slot: u64,
- pub status: String,
- pub data_len: usize,
- }
- impl QuietDisplay for CliProgramV4 {}
- impl VerboseDisplay for CliProgramV4 {}
- impl fmt::Display for CliProgramV4 {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(f)?;
- writeln_name_value(f, "Program Id:", &self.program_id)?;
- writeln_name_value(f, "Owner:", &self.owner)?;
- writeln_name_value(f, "Authority:", &self.authority)?;
- writeln_name_value(
- f,
- "Last Deployed In Slot:",
- &self.last_deploy_slot.to_string(),
- )?;
- writeln_name_value(f, "Status:", &self.status)?;
- writeln_name_value(
- f,
- "Data Length:",
- &format!("{:?} ({:#x?}) bytes", self.data_len, self.data_len),
- )?;
- Ok(())
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliProgramsV4 {
- pub programs: Vec<CliProgramV4>,
- }
- impl QuietDisplay for CliProgramsV4 {}
- impl VerboseDisplay for CliProgramsV4 {}
- impl fmt::Display for CliProgramsV4 {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(f)?;
- writeln!(
- f,
- "{}",
- style(format!(
- "{:<44} | {:<9} | {:<44} | {:<10}",
- "Program Id", "Slot", "Authority", "Status"
- ))
- .bold()
- )?;
- for program in self.programs.iter() {
- writeln!(
- f,
- "{}",
- &format!(
- "{:<44} | {:<9} | {:<44} | {:<10}",
- program.program_id, program.last_deploy_slot, program.authority, program.status,
- )
- )?;
- }
- Ok(())
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliUpgradeableProgram {
- pub program_id: String,
- pub owner: String,
- pub programdata_address: String,
- pub authority: String,
- pub last_deploy_slot: u64,
- pub data_len: usize,
- pub lamports: u64,
- #[serde(skip_serializing)]
- pub use_lamports_unit: bool,
- }
- impl QuietDisplay for CliUpgradeableProgram {}
- impl VerboseDisplay for CliUpgradeableProgram {}
- impl fmt::Display for CliUpgradeableProgram {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(f)?;
- writeln_name_value(f, "Program Id:", &self.program_id)?;
- writeln_name_value(f, "Owner:", &self.owner)?;
- writeln_name_value(f, "ProgramData Address:", &self.programdata_address)?;
- writeln_name_value(f, "Authority:", &self.authority)?;
- writeln_name_value(
- f,
- "Last Deployed In Slot:",
- &self.last_deploy_slot.to_string(),
- )?;
- writeln_name_value(
- f,
- "Data Length:",
- &format!("{:?} ({:#x?}) bytes", self.data_len, self.data_len),
- )?;
- writeln_name_value(
- f,
- "Balance:",
- &build_balance_message(self.lamports, self.use_lamports_unit, true),
- )?;
- Ok(())
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliUpgradeablePrograms {
- pub programs: Vec<CliUpgradeableProgram>,
- #[serde(skip_serializing)]
- pub use_lamports_unit: bool,
- }
- impl QuietDisplay for CliUpgradeablePrograms {}
- impl VerboseDisplay for CliUpgradeablePrograms {}
- impl fmt::Display for CliUpgradeablePrograms {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(f)?;
- writeln!(
- f,
- "{}",
- style(format!(
- "{:<44} | {:<9} | {:<44} | {}",
- "Program Id", "Slot", "Authority", "Balance"
- ))
- .bold()
- )?;
- for program in self.programs.iter() {
- writeln!(
- f,
- "{}",
- &format!(
- "{:<44} | {:<9} | {:<44} | {}",
- program.program_id,
- program.last_deploy_slot,
- program.authority,
- build_balance_message(program.lamports, self.use_lamports_unit, true)
- )
- )?;
- }
- Ok(())
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliUpgradeableProgramClosed {
- pub program_id: String,
- pub lamports: u64,
- #[serde(skip_serializing)]
- pub use_lamports_unit: bool,
- }
- impl QuietDisplay for CliUpgradeableProgramClosed {}
- impl VerboseDisplay for CliUpgradeableProgramClosed {}
- impl fmt::Display for CliUpgradeableProgramClosed {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(f)?;
- writeln!(
- f,
- "Closed Program Id {}, {} reclaimed",
- &self.program_id,
- &build_balance_message(self.lamports, self.use_lamports_unit, true)
- )?;
- Ok(())
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliUpgradeableProgramExtended {
- pub program_id: String,
- pub additional_bytes: u32,
- }
- impl QuietDisplay for CliUpgradeableProgramExtended {}
- impl VerboseDisplay for CliUpgradeableProgramExtended {}
- impl fmt::Display for CliUpgradeableProgramExtended {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(f)?;
- writeln!(
- f,
- "Extended Program Id {} by {} bytes",
- &self.program_id, self.additional_bytes,
- )?;
- Ok(())
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliUpgradeableProgramMigrated {
- pub program_id: String,
- }
- impl QuietDisplay for CliUpgradeableProgramMigrated {}
- impl VerboseDisplay for CliUpgradeableProgramMigrated {}
- impl fmt::Display for CliUpgradeableProgramMigrated {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(f)?;
- writeln!(
- f,
- "Migrated Program Id {} from loader-v3 to loader-v4",
- &self.program_id,
- )?;
- Ok(())
- }
- }
- #[derive(Clone, Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliUpgradeableBuffer {
- pub address: String,
- pub authority: String,
- pub data_len: usize,
- pub lamports: u64,
- #[serde(skip_serializing)]
- pub use_lamports_unit: bool,
- }
- impl QuietDisplay for CliUpgradeableBuffer {}
- impl VerboseDisplay for CliUpgradeableBuffer {}
- impl fmt::Display for CliUpgradeableBuffer {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(f)?;
- writeln_name_value(f, "Buffer Address:", &self.address)?;
- writeln_name_value(f, "Authority:", &self.authority)?;
- writeln_name_value(
- f,
- "Balance:",
- &build_balance_message(self.lamports, self.use_lamports_unit, true),
- )?;
- writeln_name_value(
- f,
- "Data Length:",
- &format!("{:?} ({:#x?}) bytes", self.data_len, self.data_len),
- )?;
- Ok(())
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliUpgradeableBuffers {
- pub buffers: Vec<CliUpgradeableBuffer>,
- #[serde(skip_serializing)]
- pub use_lamports_unit: bool,
- }
- impl QuietDisplay for CliUpgradeableBuffers {}
- impl VerboseDisplay for CliUpgradeableBuffers {}
- impl fmt::Display for CliUpgradeableBuffers {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(f)?;
- writeln!(
- f,
- "{}",
- style(format!(
- "{:<44} | {:<44} | {}",
- "Buffer Address", "Authority", "Balance"
- ))
- .bold()
- )?;
- for buffer in self.buffers.iter() {
- writeln!(
- f,
- "{}",
- &format!(
- "{:<44} | {:<44} | {}",
- buffer.address,
- buffer.authority,
- build_balance_message(buffer.lamports, self.use_lamports_unit, true)
- )
- )?;
- }
- Ok(())
- }
- }
- #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
- #[serde(rename_all = "camelCase")]
- pub struct CliAddressLookupTable {
- pub lookup_table_address: String,
- pub authority: Option<String>,
- pub deactivation_slot: u64,
- pub last_extended_slot: u64,
- pub addresses: Vec<String>,
- }
- impl QuietDisplay for CliAddressLookupTable {}
- impl VerboseDisplay for CliAddressLookupTable {}
- impl fmt::Display for CliAddressLookupTable {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(f)?;
- writeln_name_value(f, "Lookup Table Address:", &self.lookup_table_address)?;
- if let Some(authority) = &self.authority {
- writeln_name_value(f, "Authority:", authority)?;
- } else {
- writeln_name_value(f, "Authority:", "None (frozen)")?;
- }
- if self.deactivation_slot == u64::MAX {
- writeln_name_value(f, "Deactivation Slot:", "None (still active)")?;
- } else {
- writeln_name_value(f, "Deactivation Slot:", &self.deactivation_slot.to_string())?;
- }
- if self.last_extended_slot == 0 {
- writeln_name_value(f, "Last Extended Slot:", "None (empty)")?;
- } else {
- writeln_name_value(
- f,
- "Last Extended Slot:",
- &self.last_extended_slot.to_string(),
- )?;
- }
- if self.addresses.is_empty() {
- writeln_name_value(f, "Address Table Entries:", "None (empty)")?;
- } else {
- writeln!(f, "{}", style("Address Table Entries:".to_string()).bold())?;
- writeln!(f)?;
- writeln!(
- f,
- "{}",
- style(format!(" {:<5} {}", "Index", "Address")).bold()
- )?;
- for (index, address) in self.addresses.iter().enumerate() {
- writeln!(f, " {index:<5} {address}")?;
- }
- }
- Ok(())
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliAddressLookupTableCreated {
- pub lookup_table_address: String,
- pub signature: String,
- }
- impl QuietDisplay for CliAddressLookupTableCreated {}
- impl VerboseDisplay for CliAddressLookupTableCreated {}
- impl fmt::Display for CliAddressLookupTableCreated {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(f)?;
- writeln_name_value(f, "Signature:", &self.signature)?;
- writeln_name_value(f, "Lookup Table Address:", &self.lookup_table_address)?;
- Ok(())
- }
- }
- #[derive(Debug, Default)]
- pub struct ReturnSignersConfig {
- pub dump_transaction_message: bool,
- }
- pub fn return_signers(
- tx: &Transaction,
- output_format: &OutputFormat,
- ) -> Result<String, Box<dyn std::error::Error>> {
- return_signers_with_config(tx, output_format, &ReturnSignersConfig::default())
- }
- pub fn return_signers_with_config(
- tx: &Transaction,
- output_format: &OutputFormat,
- config: &ReturnSignersConfig,
- ) -> Result<String, Box<dyn std::error::Error>> {
- let cli_command = return_signers_data(tx, config);
- Ok(output_format.formatted_string(&cli_command))
- }
- pub fn return_signers_data(tx: &Transaction, config: &ReturnSignersConfig) -> CliSignOnlyData {
- let verify_results = tx.verify_with_results();
- let mut signers = Vec::new();
- let mut absent = Vec::new();
- let mut bad_sig = Vec::new();
- tx.signatures
- .iter()
- .zip(tx.message.account_keys.iter())
- .zip(verify_results)
- .for_each(|((sig, key), res)| {
- if res {
- signers.push(format!("{key}={sig}"))
- } else if *sig == Signature::default() {
- absent.push(key.to_string());
- } else {
- bad_sig.push(key.to_string());
- }
- });
- let message = if config.dump_transaction_message {
- let message_data = tx.message_data();
- Some(BASE64_STANDARD.encode(message_data))
- } else {
- None
- };
- CliSignOnlyData {
- blockhash: tx.message.recent_blockhash.to_string(),
- message,
- signers,
- absent,
- bad_sig,
- }
- }
- pub fn parse_sign_only_reply_string(reply: &str) -> SignOnly {
- let object: Value = serde_json::from_str(reply).unwrap();
- let blockhash_str = object.get("blockhash").unwrap().as_str().unwrap();
- let blockhash = blockhash_str.parse::<Hash>().unwrap();
- let mut present_signers: Vec<(Pubkey, Signature)> = Vec::new();
- let signer_strings = object.get("signers");
- if let Some(sig_strings) = signer_strings {
- present_signers = sig_strings
- .as_array()
- .unwrap()
- .iter()
- .map(|signer_string| {
- let mut signer = signer_string.as_str().unwrap().split('=');
- let key = Pubkey::from_str(signer.next().unwrap()).unwrap();
- let sig = Signature::from_str(signer.next().unwrap()).unwrap();
- (key, sig)
- })
- .collect();
- }
- let mut absent_signers: Vec<Pubkey> = Vec::new();
- let signer_strings = object.get("absent");
- if let Some(sig_strings) = signer_strings {
- absent_signers = sig_strings
- .as_array()
- .unwrap()
- .iter()
- .map(|val| {
- let s = val.as_str().unwrap();
- Pubkey::from_str(s).unwrap()
- })
- .collect();
- }
- let mut bad_signers: Vec<Pubkey> = Vec::new();
- let signer_strings = object.get("badSig");
- if let Some(sig_strings) = signer_strings {
- bad_signers = sig_strings
- .as_array()
- .unwrap()
- .iter()
- .map(|val| {
- let s = val.as_str().unwrap();
- Pubkey::from_str(s).unwrap()
- })
- .collect();
- }
- let message = object
- .get("message")
- .and_then(|o| o.as_str())
- .map(|m| m.to_string());
- SignOnly {
- blockhash,
- message,
- present_signers,
- absent_signers,
- bad_signers,
- }
- }
- #[derive(Debug, Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub enum CliSignatureVerificationStatus {
- None,
- Pass,
- Fail,
- }
- impl CliSignatureVerificationStatus {
- pub fn verify_transaction(tx: &VersionedTransaction) -> Vec<Self> {
- tx.verify_with_results()
- .iter()
- .zip(&tx.signatures)
- .map(|(stat, sig)| match stat {
- true => CliSignatureVerificationStatus::Pass,
- false if sig == &Signature::default() => CliSignatureVerificationStatus::None,
- false => CliSignatureVerificationStatus::Fail,
- })
- .collect()
- }
- }
- impl fmt::Display for CliSignatureVerificationStatus {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- Self::None => write!(f, "none"),
- Self::Pass => write!(f, "pass"),
- Self::Fail => write!(f, "fail"),
- }
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliBlock {
- #[serde(flatten)]
- pub encoded_confirmed_block: EncodedConfirmedBlock,
- #[serde(skip_serializing)]
- pub slot: Slot,
- }
- impl QuietDisplay for CliBlock {}
- impl VerboseDisplay for CliBlock {}
- impl fmt::Display for CliBlock {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(f, "Slot: {}", self.slot)?;
- writeln!(
- f,
- "Parent Slot: {}",
- self.encoded_confirmed_block.parent_slot
- )?;
- writeln!(f, "Blockhash: {}", self.encoded_confirmed_block.blockhash)?;
- writeln!(
- f,
- "Previous Blockhash: {}",
- self.encoded_confirmed_block.previous_blockhash
- )?;
- if let Some(block_time) = self.encoded_confirmed_block.block_time {
- writeln!(
- f,
- "Block Time: {:?}",
- Local.timestamp_opt(block_time, 0).unwrap()
- )?;
- }
- if let Some(block_height) = self.encoded_confirmed_block.block_height {
- writeln!(f, "Block Height: {block_height:?}")?;
- }
- if !self.encoded_confirmed_block.rewards.is_empty() {
- let mut rewards = self.encoded_confirmed_block.rewards.clone();
- rewards.sort_by(|a, b| a.pubkey.cmp(&b.pubkey));
- let mut total_rewards = 0;
- writeln!(f, "Rewards:")?;
- writeln!(
- f,
- " {:<44} {:^15} {:<15} {:<20} {:>14} {:>10}",
- "Address", "Type", "Amount", "New Balance", "Percent Change", "Commission"
- )?;
- for reward in rewards {
- let sign = if reward.lamports < 0 { "-" } else { "" };
- total_rewards += reward.lamports;
- #[allow(clippy::format_in_format_args)]
- writeln!(
- f,
- " {:<44} {:^15} {:>15} {} {}",
- reward.pubkey,
- if let Some(reward_type) = reward.reward_type {
- format!("{reward_type}")
- } else {
- "-".to_string()
- },
- format!(
- "{}◎{:<14.9}",
- sign,
- build_balance_message(reward.lamports.unsigned_abs(), false, false)
- ),
- if reward.post_balance == 0 {
- " - -".to_string()
- } else {
- format!(
- "◎{:<19.9} {:>13.9}%",
- build_balance_message(reward.post_balance, false, false),
- (reward.lamports.abs() as f64
- / (reward.post_balance as f64 - reward.lamports as f64))
- * 100.0
- )
- },
- reward
- .commission
- .map(|commission| format!("{commission:>9}%"))
- .unwrap_or_else(|| " -".to_string())
- )?;
- }
- let sign = if total_rewards < 0 { "-" } else { "" };
- writeln!(
- f,
- "Total Rewards: {}◎{:<12.9}",
- sign,
- build_balance_message(total_rewards.unsigned_abs(), false, false)
- )?;
- }
- for (index, transaction_with_meta) in
- self.encoded_confirmed_block.transactions.iter().enumerate()
- {
- writeln!(f, "Transaction {index}:")?;
- writeln_transaction(
- f,
- &transaction_with_meta.transaction.decode().unwrap(),
- transaction_with_meta.meta.as_ref(),
- " ",
- None,
- None,
- )?;
- }
- Ok(())
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliTransaction {
- pub transaction: EncodedTransaction,
- pub meta: Option<UiTransactionStatusMeta>,
- pub block_time: Option<UnixTimestamp>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub slot: Option<Slot>,
- #[serde(skip_serializing)]
- pub decoded_transaction: VersionedTransaction,
- #[serde(skip_serializing)]
- pub prefix: String,
- #[serde(skip_serializing_if = "Vec::is_empty")]
- pub sigverify_status: Vec<CliSignatureVerificationStatus>,
- }
- impl QuietDisplay for CliTransaction {}
- impl VerboseDisplay for CliTransaction {}
- impl fmt::Display for CliTransaction {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln_transaction(
- f,
- &self.decoded_transaction,
- self.meta.as_ref(),
- &self.prefix,
- if !self.sigverify_status.is_empty() {
- Some(&self.sigverify_status)
- } else {
- None
- },
- self.block_time,
- )
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliTransactionConfirmation {
- pub confirmation_status: Option<TransactionConfirmationStatus>,
- #[serde(flatten, skip_serializing_if = "Option::is_none")]
- pub transaction: Option<CliTransaction>,
- #[serde(skip_serializing)]
- pub get_transaction_error: Option<String>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub err: Option<UiTransactionError>,
- }
- impl QuietDisplay for CliTransactionConfirmation {}
- impl VerboseDisplay for CliTransactionConfirmation {
- fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
- if let Some(transaction) = &self.transaction {
- writeln!(
- w,
- "\nTransaction executed in slot {}:",
- transaction.slot.expect("slot should exist")
- )?;
- write!(w, "{transaction}")?;
- } else if let Some(confirmation_status) = &self.confirmation_status {
- if confirmation_status != &TransactionConfirmationStatus::Finalized {
- writeln!(w)?;
- writeln!(
- w,
- "Unable to get finalized transaction details: not yet finalized"
- )?;
- } else if let Some(err) = &self.get_transaction_error {
- writeln!(w)?;
- writeln!(w, "Unable to get finalized transaction details: {err}")?;
- }
- }
- writeln!(w)?;
- write!(w, "{self}")
- }
- }
- impl fmt::Display for CliTransactionConfirmation {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match &self.confirmation_status {
- None => write!(f, "Not found"),
- Some(confirmation_status) => {
- if let Some(err) = &self.err {
- write!(f, "Transaction failed: {err}")
- } else {
- write!(f, "{confirmation_status:?}")
- }
- }
- }
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliGossipNode {
- #[serde(skip_serializing_if = "Option::is_none")]
- pub ip_address: Option<String>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub identity_label: Option<String>,
- pub identity_pubkey: String,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub gossip_port: Option<u16>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub tpu_port: Option<u16>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub rpc_host: Option<String>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub pubsub_host: Option<String>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub version: Option<String>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub feature_set: Option<u32>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub tpu_quic_port: Option<u16>,
- }
- impl CliGossipNode {
- pub fn new(info: RpcContactInfo, labels: &HashMap<String, String>) -> Self {
- Self {
- ip_address: info.gossip.map(|addr| addr.ip().to_string()),
- identity_label: labels.get(&info.pubkey).cloned(),
- identity_pubkey: info.pubkey,
- gossip_port: info.gossip.map(|addr| addr.port()),
- tpu_port: info.tpu.map(|addr| addr.port()),
- rpc_host: info.rpc.map(|addr| addr.to_string()),
- pubsub_host: info.pubsub.map(|addr| addr.to_string()),
- version: info.version,
- feature_set: info.feature_set,
- tpu_quic_port: info.tpu_quic.map(|addr| addr.port()),
- }
- }
- }
- fn unwrap_to_string_or_none<T>(option: Option<T>) -> String
- where
- T: std::string::ToString,
- {
- unwrap_to_string_or_default(option, "none")
- }
- fn unwrap_to_string_or_default<T>(option: Option<T>, default: &str) -> String
- where
- T: std::string::ToString,
- {
- option
- .as_ref()
- .map(|v| v.to_string())
- .unwrap_or_else(|| default.to_string())
- }
- impl fmt::Display for CliGossipNode {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(
- f,
- "{:15} | {:44} | {:6} | {:5} | {:8} | {:21} | {:8}| {}",
- unwrap_to_string_or_none(self.ip_address.as_ref()),
- self.identity_label
- .as_ref()
- .unwrap_or(&self.identity_pubkey),
- unwrap_to_string_or_none(self.gossip_port.as_ref()),
- unwrap_to_string_or_none(self.tpu_port.as_ref()),
- unwrap_to_string_or_none(self.tpu_quic_port.as_ref()),
- unwrap_to_string_or_none(self.rpc_host.as_ref()),
- unwrap_to_string_or_default(self.version.as_ref(), "unknown"),
- unwrap_to_string_or_default(self.feature_set.as_ref(), "unknown"),
- )
- }
- }
- impl QuietDisplay for CliGossipNode {}
- impl VerboseDisplay for CliGossipNode {}
- #[derive(Serialize, Deserialize)]
- pub struct CliGossipNodes(pub Vec<CliGossipNode>);
- impl fmt::Display for CliGossipNodes {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(
- f,
- "IP Address | Identity \
- | Gossip | TPU | TPU-QUIC | RPC Address | Version | Feature Set\n\
- ----------------+----------------------------------------------+\
- --------+-------+----------+-----------------------+---------+----------------",
- )?;
- for node in self.0.iter() {
- writeln!(f, "{node}")?;
- }
- writeln!(f, "Nodes: {}", self.0.len())
- }
- }
- impl QuietDisplay for CliGossipNodes {}
- impl VerboseDisplay for CliGossipNodes {}
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliPing {
- pub source_pubkey: String,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub fixed_blockhash: Option<String>,
- #[serde(skip_serializing)]
- pub blockhash_from_cluster: bool,
- pub pings: Vec<CliPingData>,
- pub transaction_stats: CliPingTxStats,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub confirmation_stats: Option<CliPingConfirmationStats>,
- }
- impl fmt::Display for CliPing {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(f)?;
- writeln_name_value(f, "Source Account:", &self.source_pubkey)?;
- if let Some(fixed_blockhash) = &self.fixed_blockhash {
- let blockhash_origin = if self.blockhash_from_cluster {
- "fetched from cluster"
- } else {
- "supplied from cli arguments"
- };
- writeln!(
- f,
- "Fixed blockhash is used: {fixed_blockhash} ({blockhash_origin})"
- )?;
- }
- writeln!(f)?;
- for ping in &self.pings {
- write!(f, "{ping}")?;
- }
- writeln!(f)?;
- writeln!(f, "--- transaction statistics ---")?;
- write!(f, "{}", self.transaction_stats)?;
- if let Some(confirmation_stats) = &self.confirmation_stats {
- write!(f, "{confirmation_stats}")?;
- }
- Ok(())
- }
- }
- impl QuietDisplay for CliPing {}
- impl VerboseDisplay for CliPing {}
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliPingData {
- pub success: bool,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub signature: Option<String>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub ms: Option<u64>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub error: Option<String>,
- #[serde(skip_serializing)]
- pub print_timestamp: bool,
- pub timestamp: String,
- pub sequence: u64,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub lamports: Option<u64>,
- }
- impl fmt::Display for CliPingData {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- let (mark, msg) = if let Some(signature) = &self.signature {
- if self.success {
- (
- CHECK_MARK,
- format!(
- "{} lamport(s) transferred: seq={:<3} time={:>4}ms signature={}",
- self.lamports.unwrap(),
- self.sequence,
- self.ms.unwrap(),
- signature
- ),
- )
- } else if let Some(error) = &self.error {
- (
- CROSS_MARK,
- format!(
- "Transaction failed: seq={:<3} error={:?} signature={}",
- self.sequence, error, signature
- ),
- )
- } else {
- (
- CROSS_MARK,
- format!(
- "Confirmation timeout: seq={:<3} signature={}",
- self.sequence, signature
- ),
- )
- }
- } else {
- (
- CROSS_MARK,
- format!(
- "Submit failed: seq={:<3} error={:?}",
- self.sequence,
- self.error.as_ref().unwrap(),
- ),
- )
- };
- writeln!(
- f,
- "{}{}{}",
- if self.print_timestamp {
- &self.timestamp
- } else {
- ""
- },
- mark,
- msg
- )
- }
- }
- impl QuietDisplay for CliPingData {}
- impl VerboseDisplay for CliPingData {}
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliPingTxStats {
- pub num_transactions: u32,
- pub num_transaction_confirmed: u32,
- }
- impl fmt::Display for CliPingTxStats {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(
- f,
- "{} transactions submitted, {} transactions confirmed, {:.1}% transaction loss",
- self.num_transactions,
- self.num_transaction_confirmed,
- (100.
- - f64::from(self.num_transaction_confirmed) / f64::from(self.num_transactions)
- * 100.)
- )
- }
- }
- impl QuietDisplay for CliPingTxStats {}
- impl VerboseDisplay for CliPingTxStats {}
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliPingConfirmationStats {
- pub min: f64,
- pub mean: f64,
- pub max: f64,
- pub std_dev: f64,
- }
- impl fmt::Display for CliPingConfirmationStats {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(
- f,
- "confirmation min/mean/max/stddev = {:.0}/{:.0}/{:.0}/{:.0} ms",
- self.min, self.mean, self.max, self.std_dev,
- )
- }
- }
- impl QuietDisplay for CliPingConfirmationStats {}
- impl VerboseDisplay for CliPingConfirmationStats {}
- #[derive(Serialize, Deserialize, Debug)]
- #[serde(rename_all = "camelCase")]
- pub struct CliBalance {
- pub lamports: u64,
- #[serde(skip)]
- pub config: BuildBalanceMessageConfig,
- }
- impl QuietDisplay for CliBalance {
- fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
- let config = BuildBalanceMessageConfig {
- show_unit: false,
- trim_trailing_zeros: true,
- ..self.config
- };
- let balance_message = build_balance_message_with_config(self.lamports, &config);
- write!(w, "{balance_message}")
- }
- }
- impl VerboseDisplay for CliBalance {
- fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
- let config = BuildBalanceMessageConfig {
- show_unit: true,
- trim_trailing_zeros: false,
- ..self.config
- };
- let balance_message = build_balance_message_with_config(self.lamports, &config);
- write!(w, "{balance_message}")
- }
- }
- impl fmt::Display for CliBalance {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- let balance_message = build_balance_message_with_config(self.lamports, &self.config);
- write!(f, "{balance_message}")
- }
- }
- #[derive(Serialize, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct CliFindProgramDerivedAddress {
- pub address: String,
- pub bump_seed: u8,
- }
- impl QuietDisplay for CliFindProgramDerivedAddress {}
- impl VerboseDisplay for CliFindProgramDerivedAddress {}
- impl fmt::Display for CliFindProgramDerivedAddress {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(f, "{}", self.address)?;
- Ok(())
- }
- }
- #[cfg(test)]
- mod tests {
- use {
- super::*,
- clap::{App, Arg},
- solana_keypair::keypair_from_seed,
- solana_message::Message,
- solana_pubkey::Pubkey,
- solana_signature::Signature,
- solana_signer::{null_signer::NullSigner, Signer, SignerError},
- solana_system_interface::instruction::transfer,
- solana_transaction::Transaction,
- };
- #[test]
- fn test_return_signers() {
- struct BadSigner {
- pubkey: Pubkey,
- }
- impl BadSigner {
- pub fn new(pubkey: Pubkey) -> Self {
- Self { pubkey }
- }
- }
- impl Signer for BadSigner {
- fn try_pubkey(&self) -> Result<Pubkey, SignerError> {
- Ok(self.pubkey)
- }
- fn try_sign_message(&self, _message: &[u8]) -> Result<Signature, SignerError> {
- Ok(Signature::from([1u8; 64]))
- }
- fn is_interactive(&self) -> bool {
- false
- }
- }
- let present: Box<dyn Signer> = Box::new(keypair_from_seed(&[2u8; 32]).unwrap());
- let absent: Box<dyn Signer> = Box::new(NullSigner::new(&Pubkey::from([3u8; 32])));
- let bad: Box<dyn Signer> = Box::new(BadSigner::new(Pubkey::from([4u8; 32])));
- let to = Pubkey::from([5u8; 32]);
- let nonce = Pubkey::from([6u8; 32]);
- let from = present.pubkey();
- let fee_payer = absent.pubkey();
- let nonce_auth = bad.pubkey();
- let mut tx = Transaction::new_unsigned(Message::new_with_nonce(
- vec![transfer(&from, &to, 42)],
- Some(&fee_payer),
- &nonce,
- &nonce_auth,
- ));
- let signers = vec![present.as_ref(), absent.as_ref(), bad.as_ref()];
- let blockhash = Hash::new_from_array([7u8; 32]);
- tx.try_partial_sign(&signers, blockhash).unwrap();
- let res = return_signers(&tx, &OutputFormat::JsonCompact).unwrap();
- let sign_only = parse_sign_only_reply_string(&res);
- assert_eq!(sign_only.blockhash, blockhash);
- assert_eq!(sign_only.message, None);
- assert_eq!(sign_only.present_signers[0].0, present.pubkey());
- assert_eq!(sign_only.absent_signers[0], absent.pubkey());
- assert_eq!(sign_only.bad_signers[0], bad.pubkey());
- let res_data = return_signers_data(&tx, &ReturnSignersConfig::default());
- assert_eq!(
- res_data,
- CliSignOnlyData {
- blockhash: blockhash.to_string(),
- message: None,
- signers: vec![format!("{}={}", present.pubkey(), tx.signatures[1])],
- absent: vec![absent.pubkey().to_string()],
- bad_sig: vec![bad.pubkey().to_string()],
- }
- );
- let expected_msg = "AwECBwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDgTl3Dqh9\
- F19Wo1Rmw0x+zMuNipG07jeiXfYPW4/Js5QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE\
- BAQEBAUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBgYGBgYGBgYGBgYGBgYGBgYG\
- BgYGBgYGBgYGBgYGBgYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAan1RcZLFaO\
- 4IqEX3PSl4jPA1wxRbIas0TYBi6pQAAABwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcH\
- BwcCBQMEBgIEBAAAAAUCAQMMAgAAACoAAAAAAAAA"
- .to_string();
- let config = ReturnSignersConfig {
- dump_transaction_message: true,
- };
- let res = return_signers_with_config(&tx, &OutputFormat::JsonCompact, &config).unwrap();
- let sign_only = parse_sign_only_reply_string(&res);
- assert_eq!(sign_only.blockhash, blockhash);
- assert_eq!(sign_only.message, Some(expected_msg.clone()));
- assert_eq!(sign_only.present_signers[0].0, present.pubkey());
- assert_eq!(sign_only.absent_signers[0], absent.pubkey());
- assert_eq!(sign_only.bad_signers[0], bad.pubkey());
- let res_data = return_signers_data(&tx, &config);
- assert_eq!(
- res_data,
- CliSignOnlyData {
- blockhash: blockhash.to_string(),
- message: Some(expected_msg),
- signers: vec![format!("{}={}", present.pubkey(), tx.signatures[1])],
- absent: vec![absent.pubkey().to_string()],
- bad_sig: vec![bad.pubkey().to_string()],
- }
- );
- }
- #[test]
- fn test_verbose_quiet_output_formats() {
- #[derive(Deserialize, Serialize)]
- struct FallbackToDisplay {}
- impl std::fmt::Display for FallbackToDisplay {
- fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
- write!(f, "display")
- }
- }
- impl QuietDisplay for FallbackToDisplay {}
- impl VerboseDisplay for FallbackToDisplay {}
- let f = FallbackToDisplay {};
- assert_eq!(&OutputFormat::Display.formatted_string(&f), "display");
- assert_eq!(&OutputFormat::DisplayQuiet.formatted_string(&f), "display");
- assert_eq!(
- &OutputFormat::DisplayVerbose.formatted_string(&f),
- "display"
- );
- #[derive(Deserialize, Serialize)]
- struct DiscreteVerbosityDisplay {}
- impl std::fmt::Display for DiscreteVerbosityDisplay {
- fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
- write!(f, "display")
- }
- }
- impl QuietDisplay for DiscreteVerbosityDisplay {
- fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
- write!(w, "quiet")
- }
- }
- impl VerboseDisplay for DiscreteVerbosityDisplay {
- fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
- write!(w, "verbose")
- }
- }
- let f = DiscreteVerbosityDisplay {};
- assert_eq!(&OutputFormat::Display.formatted_string(&f), "display");
- assert_eq!(&OutputFormat::DisplayQuiet.formatted_string(&f), "quiet");
- assert_eq!(
- &OutputFormat::DisplayVerbose.formatted_string(&f),
- "verbose"
- );
- }
- #[test]
- fn test_output_format_from_matches() {
- let app = App::new("test").arg(
- Arg::with_name("output_format")
- .long("output")
- .value_name("FORMAT")
- .global(true)
- .takes_value(true)
- .possible_values(&["json", "json-compact"])
- .help("Return information in specified output format"),
- );
- let matches = app
- .clone()
- .get_matches_from(vec!["test", "--output", "json"]);
- assert_eq!(
- OutputFormat::from_matches(&matches, "output_format", false),
- OutputFormat::Json
- );
- assert_eq!(
- OutputFormat::from_matches(&matches, "output_format", true),
- OutputFormat::Json
- );
- let matches = app
- .clone()
- .get_matches_from(vec!["test", "--output", "json-compact"]);
- assert_eq!(
- OutputFormat::from_matches(&matches, "output_format", false),
- OutputFormat::JsonCompact
- );
- assert_eq!(
- OutputFormat::from_matches(&matches, "output_format", true),
- OutputFormat::JsonCompact
- );
- let matches = app.clone().get_matches_from(vec!["test"]);
- assert_eq!(
- OutputFormat::from_matches(&matches, "output_format", false),
- OutputFormat::Display
- );
- assert_eq!(
- OutputFormat::from_matches(&matches, "output_format", true),
- OutputFormat::DisplayVerbose
- );
- }
- #[test]
- fn test_format_vote_account() {
- let epoch_rewards = vec![
- CliEpochReward {
- percent_change: 11.0,
- post_balance: 100,
- commission: Some(1),
- effective_slot: 100,
- epoch: 1,
- amount: 10,
- block_time: 0,
- apr: Some(10.0),
- },
- CliEpochReward {
- percent_change: 11.0,
- post_balance: 100,
- commission: Some(1),
- effective_slot: 200,
- epoch: 2,
- amount: 12,
- block_time: 1_000_000,
- apr: Some(13.0),
- },
- ];
- let mut c = CliVoteAccount {
- account_balance: 10000,
- validator_identity: Pubkey::default().to_string(),
- epoch_rewards: Some(epoch_rewards),
- recent_timestamp: BlockTimestamp::default(),
- ..CliVoteAccount::default()
- };
- let s = format!("{c}");
- assert_eq!(s, "Account Balance: 0.00001 SOL\nValidator Identity: 11111111111111111111111111111111\nVote Authority: None\nWithdraw Authority: \nCredits: 0\nCommission: 0%\nRoot Slot: ~\nRecent Timestamp: 1970-01-01T00:00:00Z from slot 0\nEpoch Rewards:\n Epoch Reward Slot Time Amount New Balance Percent Change APR Commission\n 1 100 1970-01-01 00:00:00 UTC ◎0.00000001 ◎0.0000001 11.000% 10.00% 1%\n 2 200 1970-01-12 13:46:40 UTC ◎0.000000012 ◎0.0000001 11.000% 13.00% 1%\n");
- println!("{s}");
- c.use_csv = true;
- let s = format!("{c}");
- assert_eq!(s, "Account Balance: 0.00001 SOL\nValidator Identity: 11111111111111111111111111111111\nVote Authority: None\nWithdraw Authority: \nCredits: 0\nCommission: 0%\nRoot Slot: ~\nRecent Timestamp: 1970-01-01T00:00:00Z from slot 0\nEpoch Rewards:\nEpoch,Reward Slot,Time,Amount,New Balance,Percent Change,APR,Commission\n1,100,1970-01-01 00:00:00 UTC,0.00000001,0.0000001,11%,10.00%,1%\n2,200,1970-01-12 13:46:40 UTC,0.000000012,0.0000001,11%,13.00%,1%\n");
- println!("{s}");
- }
- }
|