Implement OutputFormat for block in Cli and ledger-tool bigtable (#15524)
* Impl DisplayFormat for solana block * Use DisplayFormat in ledger-tool bigtable block
This commit is contained in:
parent
7cb44b1095
commit
d5f235d997
|
@ -2,10 +2,12 @@ use {
|
|||
crate::{
|
||||
display::{
|
||||
build_balance_message, build_balance_message_with_config, format_labeled_address,
|
||||
unix_timestamp_to_string, writeln_name_value, BuildBalanceMessageConfig,
|
||||
unix_timestamp_to_string, writeln_name_value, writeln_transaction,
|
||||
BuildBalanceMessageConfig,
|
||||
},
|
||||
QuietDisplay, VerboseDisplay,
|
||||
},
|
||||
chrono::{Local, TimeZone},
|
||||
console::{style, Emoji},
|
||||
inflector::cases::titlecase::to_title_case,
|
||||
serde::{Deserialize, Serialize},
|
||||
|
@ -27,6 +29,7 @@ use {
|
|||
transaction::Transaction,
|
||||
},
|
||||
solana_stake_program::stake_state::{Authorized, Lockup},
|
||||
solana_transaction_status::EncodedConfirmedBlock,
|
||||
solana_vote_program::{
|
||||
authorized_voters::AuthorizedVoters,
|
||||
vote_state::{BlockTimestamp, Lockout, MAX_EPOCH_CREDITS_HISTORY, MAX_LOCKOUT_HISTORY},
|
||||
|
@ -1739,6 +1742,100 @@ impl fmt::Display for CliSignatureVerificationStatus {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
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(block_time, 0))?;
|
||||
}
|
||||
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}",
|
||||
"Address", "Type", "Amount", "New Balance", "Percent Change"
|
||||
)?;
|
||||
for reward in rewards {
|
||||
let sign = if reward.lamports < 0 { "-" } else { "" };
|
||||
|
||||
total_rewards += reward.lamports;
|
||||
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,
|
||||
lamports_to_sol(reward.lamports.abs() as u64)
|
||||
),
|
||||
if reward.post_balance == 0 {
|
||||
" - -".to_string()
|
||||
} else {
|
||||
format!(
|
||||
"◎{:<19.9} {:>13.9}%",
|
||||
lamports_to_sol(reward.post_balance),
|
||||
(reward.lamports.abs() as f64
|
||||
/ (reward.post_balance as f64 - reward.lamports as f64))
|
||||
* 100.0
|
||||
)
|
||||
}
|
||||
)?;
|
||||
}
|
||||
|
||||
let sign = if total_rewards < 0 { "-" } else { "" };
|
||||
writeln!(
|
||||
f,
|
||||
"Total Rewards: {}◎{:<12.9}",
|
||||
sign,
|
||||
lamports_to_sol(total_rewards.abs() as u64)
|
||||
)?;
|
||||
}
|
||||
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,
|
||||
" ",
|
||||
None,
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -294,6 +294,30 @@ pub fn println_transaction(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn writeln_transaction(
|
||||
f: &mut dyn fmt::Write,
|
||||
transaction: &Transaction,
|
||||
transaction_status: &Option<UiTransactionStatusMeta>,
|
||||
prefix: &str,
|
||||
sigverify_status: Option<&[CliSignatureVerificationStatus]>,
|
||||
) -> fmt::Result {
|
||||
let mut w = Vec::new();
|
||||
if write_transaction(
|
||||
&mut w,
|
||||
transaction,
|
||||
transaction_status,
|
||||
prefix,
|
||||
sigverify_status,
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
if let Ok(s) = String::from_utf8(w) {
|
||||
write!(f, "{}", s)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Creates a new process bar for processing that will take an unknown amount of time
|
||||
pub fn new_spinner_progress_bar() -> ProgressBar {
|
||||
let progress_bar = ProgressBar::new(42);
|
||||
|
|
|
@ -3,7 +3,6 @@ use crate::{
|
|||
spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
|
||||
stake::is_stake_program_v2_enabled,
|
||||
};
|
||||
use chrono::{Local, TimeZone};
|
||||
use clap::{value_t, value_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand};
|
||||
use console::{style, Emoji};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -896,7 +895,7 @@ pub fn process_leader_schedule(
|
|||
|
||||
pub fn process_get_block(
|
||||
rpc_client: &RpcClient,
|
||||
_config: &CliConfig,
|
||||
config: &CliConfig,
|
||||
slot: Option<Slot>,
|
||||
) -> ProcessResult {
|
||||
let slot = if let Some(slot) = slot {
|
||||
|
@ -905,72 +904,13 @@ pub fn process_get_block(
|
|||
rpc_client.get_slot_with_commitment(CommitmentConfig::finalized())?
|
||||
};
|
||||
|
||||
let mut block =
|
||||
let encoded_confirmed_block =
|
||||
rpc_client.get_confirmed_block_with_encoding(slot, UiTransactionEncoding::Base64)?;
|
||||
|
||||
println!("Slot: {}", slot);
|
||||
println!("Parent Slot: {}", block.parent_slot);
|
||||
println!("Blockhash: {}", block.blockhash);
|
||||
println!("Previous Blockhash: {}", block.previous_blockhash);
|
||||
if let Some(block_time) = block.block_time {
|
||||
println!("Block Time: {:?}", Local.timestamp(block_time, 0));
|
||||
}
|
||||
if !block.rewards.is_empty() {
|
||||
block.rewards.sort_by(|a, b| a.pubkey.cmp(&b.pubkey));
|
||||
let mut total_rewards = 0;
|
||||
println!("Rewards:",);
|
||||
println!(
|
||||
" {:<44} {:^15} {:<15} {:<20} {:>14}",
|
||||
"Address", "Type", "Amount", "New Balance", "Percent Change"
|
||||
);
|
||||
for reward in block.rewards {
|
||||
let sign = if reward.lamports < 0 { "-" } else { "" };
|
||||
|
||||
total_rewards += reward.lamports;
|
||||
println!(
|
||||
" {:<44} {:^15} {:>15} {}",
|
||||
reward.pubkey,
|
||||
if let Some(reward_type) = reward.reward_type {
|
||||
format!("{}", reward_type)
|
||||
} else {
|
||||
"-".to_string()
|
||||
},
|
||||
format!(
|
||||
"{}◎{:<14.9}",
|
||||
sign,
|
||||
lamports_to_sol(reward.lamports.abs() as u64)
|
||||
),
|
||||
if reward.post_balance == 0 {
|
||||
" - -".to_string()
|
||||
} else {
|
||||
format!(
|
||||
"◎{:<19.9} {:>13.9}%",
|
||||
lamports_to_sol(reward.post_balance),
|
||||
(reward.lamports.abs() as f64
|
||||
/ (reward.post_balance as f64 - reward.lamports as f64))
|
||||
* 100.0
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let sign = if total_rewards < 0 { "-" } else { "" };
|
||||
println!(
|
||||
"Total Rewards: {}◎{:<12.9}",
|
||||
sign,
|
||||
lamports_to_sol(total_rewards.abs() as u64)
|
||||
);
|
||||
}
|
||||
for (index, transaction_with_meta) in block.transactions.iter().enumerate() {
|
||||
println!("Transaction {}:", index);
|
||||
println_transaction(
|
||||
&transaction_with_meta.transaction.decode().unwrap(),
|
||||
&transaction_with_meta.meta,
|
||||
" ",
|
||||
None,
|
||||
);
|
||||
}
|
||||
Ok("".to_string())
|
||||
let cli_block = CliBlock {
|
||||
encoded_confirmed_block,
|
||||
slot,
|
||||
};
|
||||
Ok(config.output_format.formatted_string(&cli_block))
|
||||
}
|
||||
|
||||
pub fn process_get_block_time(
|
||||
|
|
|
@ -4,10 +4,10 @@ use solana_clap_utils::{
|
|||
input_parsers::pubkey_of,
|
||||
input_validators::{is_slot, is_valid_pubkey},
|
||||
};
|
||||
use solana_cli_output::display::println_transaction;
|
||||
use solana_cli_output::{display::println_transaction, CliBlock, OutputFormat};
|
||||
use solana_ledger::{blockstore::Blockstore, blockstore_db::AccessType};
|
||||
use solana_sdk::{clock::Slot, pubkey::Pubkey, signature::Signature};
|
||||
use solana_transaction_status::ConfirmedBlock;
|
||||
use solana_transaction_status::{ConfirmedBlock, UiTransactionEncoding};
|
||||
use std::{
|
||||
path::Path,
|
||||
process::exit,
|
||||
|
@ -48,32 +48,18 @@ async fn first_available_block() -> Result<(), Box<dyn std::error::Error>> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn block(slot: Slot) -> Result<(), Box<dyn std::error::Error>> {
|
||||
async fn block(slot: Slot, output_format: OutputFormat) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let bigtable = solana_storage_bigtable::LedgerStorage::new(false, None)
|
||||
.await
|
||||
.map_err(|err| format!("Failed to connect to storage: {:?}", err))?;
|
||||
|
||||
let block = bigtable.get_confirmed_block(slot).await?;
|
||||
|
||||
println!("Slot: {}", slot);
|
||||
println!("Parent Slot: {}", block.parent_slot);
|
||||
println!("Blockhash: {}", block.blockhash);
|
||||
println!("Previous Blockhash: {}", block.previous_blockhash);
|
||||
if block.block_time.is_some() {
|
||||
println!("Block Time: {:?}", block.block_time);
|
||||
}
|
||||
if !block.rewards.is_empty() {
|
||||
println!("Rewards: {:?}", block.rewards);
|
||||
}
|
||||
for (index, transaction_with_meta) in block.transactions.into_iter().enumerate() {
|
||||
println!("Transaction {}:", index);
|
||||
println_transaction(
|
||||
&transaction_with_meta.transaction,
|
||||
&transaction_with_meta.meta.map(|meta| meta.into()),
|
||||
" ",
|
||||
None,
|
||||
);
|
||||
}
|
||||
let cli_block = CliBlock {
|
||||
encoded_confirmed_block: block.encode(UiTransactionEncoding::Base64),
|
||||
slot,
|
||||
};
|
||||
println!("{}", output_format.formatted_string(&cli_block));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -396,6 +382,15 @@ impl BigTableSubCommand for App<'_, '_> {
|
|||
pub fn bigtable_process_command(ledger_path: &Path, matches: &ArgMatches<'_>) {
|
||||
let runtime = tokio::runtime::Runtime::new().unwrap();
|
||||
|
||||
let output_format = matches
|
||||
.value_of("output_format")
|
||||
.map(|value| match value {
|
||||
"json" => OutputFormat::Json,
|
||||
"json-compact" => OutputFormat::JsonCompact,
|
||||
_ => unreachable!(),
|
||||
})
|
||||
.unwrap_or(OutputFormat::Display);
|
||||
|
||||
let future = match matches.subcommand() {
|
||||
("upload", Some(arg_matches)) => {
|
||||
let starting_slot = value_t!(arg_matches, "starting_slot", Slot).unwrap_or(0);
|
||||
|
@ -416,7 +411,7 @@ pub fn bigtable_process_command(ledger_path: &Path, matches: &ArgMatches<'_>) {
|
|||
("first-available-block", Some(_arg_matches)) => runtime.block_on(first_available_block()),
|
||||
("block", Some(arg_matches)) => {
|
||||
let slot = value_t_or_exit!(arg_matches, "slot", Slot);
|
||||
runtime.block_on(block(slot))
|
||||
runtime.block_on(block(slot, output_format))
|
||||
}
|
||||
("blocks", Some(arg_matches)) => {
|
||||
let starting_slot = value_t_or_exit!(arg_matches, "starting_slot", Slot);
|
||||
|
|
|
@ -843,6 +843,15 @@ fn main() {
|
|||
.global(true)
|
||||
.help("Use DIR for ledger location"),
|
||||
)
|
||||
.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, currently only available for bigtable subcommands"),
|
||||
)
|
||||
.bigtable_subcommand()
|
||||
.subcommand(
|
||||
SubCommand::with_name("print")
|
||||
|
|
Loading…
Reference in New Issue