ledger-tool: add show-entries option to bigtable block (#34536)
* Add cli flag to show entry data * Add display structs * Add writeln_entry helper fn * Add entry conversion method * Populate Display for CliBlockWithEntries * Add ctor from flattened block and entries iterator * Support show_entries
This commit is contained in:
parent
def3bc4c4f
commit
cc0e5f7a13
|
@ -1,6 +1,9 @@
|
|||
//! The `bigtable` subcommand
|
||||
use {
|
||||
crate::{ledger_path::canonicalize_ledger_path, output::CliEntries},
|
||||
crate::{
|
||||
ledger_path::canonicalize_ledger_path,
|
||||
output::{CliBlockWithEntries, CliEntries, EncodedConfirmedBlockWithEntries},
|
||||
},
|
||||
clap::{
|
||||
value_t, value_t_or_exit, values_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand,
|
||||
},
|
||||
|
@ -23,8 +26,8 @@ use {
|
|||
solana_sdk::{clock::Slot, pubkey::Pubkey, signature::Signature},
|
||||
solana_storage_bigtable::CredentialType,
|
||||
solana_transaction_status::{
|
||||
BlockEncodingOptions, ConfirmedBlock, EncodeError, TransactionDetails,
|
||||
UiTransactionEncoding, VersionedConfirmedBlock,
|
||||
BlockEncodingOptions, ConfirmedBlock, EncodeError, EncodedConfirmedBlock,
|
||||
TransactionDetails, UiTransactionEncoding, VersionedConfirmedBlock,
|
||||
},
|
||||
std::{
|
||||
cmp::min,
|
||||
|
@ -113,6 +116,7 @@ async fn first_available_block(
|
|||
async fn block(
|
||||
slot: Slot,
|
||||
output_format: OutputFormat,
|
||||
show_entries: bool,
|
||||
config: solana_storage_bigtable::LedgerStorageConfig,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let bigtable = solana_storage_bigtable::LedgerStorage::new_with_config(config)
|
||||
|
@ -134,12 +138,25 @@ async fn block(
|
|||
format!("Failed to process unsupported transaction version ({version}) in block")
|
||||
}
|
||||
})?;
|
||||
let encoded_block: EncodedConfirmedBlock = encoded_block.into();
|
||||
|
||||
let cli_block = CliBlock {
|
||||
encoded_confirmed_block: encoded_block.into(),
|
||||
slot,
|
||||
};
|
||||
println!("{}", output_format.formatted_string(&cli_block));
|
||||
if show_entries {
|
||||
let entries = bigtable.get_entries(slot).await?;
|
||||
let cli_block = CliBlockWithEntries {
|
||||
encoded_confirmed_block: EncodedConfirmedBlockWithEntries::try_from(
|
||||
encoded_block,
|
||||
entries,
|
||||
)?,
|
||||
slot,
|
||||
};
|
||||
println!("{}", output_format.formatted_string(&cli_block));
|
||||
} else {
|
||||
let cli_block = CliBlock {
|
||||
encoded_confirmed_block: encoded_block,
|
||||
slot,
|
||||
};
|
||||
println!("{}", output_format.formatted_string(&cli_block));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -823,6 +840,12 @@ impl BigTableSubCommand for App<'_, '_> {
|
|||
.takes_value(true)
|
||||
.index(1)
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("show_entries")
|
||||
.long("show-entries")
|
||||
.required(false)
|
||||
.help("Display the transactions in their entries"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
|
@ -1117,13 +1140,14 @@ pub fn bigtable_process_command(ledger_path: &Path, matches: &ArgMatches<'_>) {
|
|||
}
|
||||
("block", Some(arg_matches)) => {
|
||||
let slot = value_t_or_exit!(arg_matches, "slot", Slot);
|
||||
let show_entries = arg_matches.is_present("show_entries");
|
||||
let config = solana_storage_bigtable::LedgerStorageConfig {
|
||||
read_only: true,
|
||||
instance_name,
|
||||
app_profile_id,
|
||||
..solana_storage_bigtable::LedgerStorageConfig::default()
|
||||
};
|
||||
runtime.block_on(block(slot, output_format, config))
|
||||
runtime.block_on(block(slot, output_format, show_entries, config))
|
||||
}
|
||||
("entries", Some(arg_matches)) => {
|
||||
let slot = value_t_or_exit!(arg_matches, "slot", Slot);
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
use {
|
||||
chrono::{Local, TimeZone},
|
||||
serde::{Deserialize, Serialize},
|
||||
solana_cli_output::{QuietDisplay, VerboseDisplay},
|
||||
solana_sdk::clock::Slot,
|
||||
solana_transaction_status::EntrySummary,
|
||||
solana_cli_output::{display::writeln_transaction, QuietDisplay, VerboseDisplay},
|
||||
solana_sdk::{
|
||||
clock::{Slot, UnixTimestamp},
|
||||
native_token::lamports_to_sol,
|
||||
},
|
||||
solana_transaction_status::{
|
||||
EncodedConfirmedBlock, EncodedTransactionWithStatusMeta, EntrySummary, Rewards,
|
||||
},
|
||||
std::fmt::{self, Display, Formatter, Result},
|
||||
};
|
||||
|
||||
|
@ -70,6 +76,14 @@ impl Display for SlotBounds<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
fn writeln_entry(f: &mut dyn fmt::Write, i: usize, entry: &CliEntry, prefix: &str) -> fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"{prefix}Entry {} - num_hashes: {}, hash: {}, transactions: {}, starting_transaction_index: {}",
|
||||
i, entry.num_hashes, entry.hash, entry.num_transactions, entry.starting_transaction_index,
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliEntries {
|
||||
|
@ -85,15 +99,7 @@ impl fmt::Display for CliEntries {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f, "Slot {}", self.slot)?;
|
||||
for (i, entry) in self.entries.iter().enumerate() {
|
||||
writeln!(
|
||||
f,
|
||||
" Entry {} - num_hashes: {}, hash: {}, transactions: {}, starting_transaction_index: {}",
|
||||
i,
|
||||
entry.num_hashes,
|
||||
entry.hash,
|
||||
entry.num_transactions,
|
||||
entry.starting_transaction_index,
|
||||
)?;
|
||||
writeln_entry(f, i, entry, " ")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -118,3 +124,182 @@ impl From<EntrySummary> for CliEntry {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&CliPopulatedEntry> for CliEntry {
|
||||
fn from(populated_entry: &CliPopulatedEntry) -> Self {
|
||||
Self {
|
||||
num_hashes: populated_entry.num_hashes,
|
||||
hash: populated_entry.hash.clone(),
|
||||
num_transactions: populated_entry.num_transactions,
|
||||
starting_transaction_index: populated_entry.starting_transaction_index,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliPopulatedEntry {
|
||||
num_hashes: u64,
|
||||
hash: String,
|
||||
num_transactions: u64,
|
||||
starting_transaction_index: usize,
|
||||
transactions: Vec<EncodedTransactionWithStatusMeta>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliBlockWithEntries {
|
||||
#[serde(flatten)]
|
||||
pub encoded_confirmed_block: EncodedConfirmedBlockWithEntries,
|
||||
#[serde(skip_serializing)]
|
||||
pub slot: Slot,
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliBlockWithEntries {}
|
||||
impl VerboseDisplay for CliBlockWithEntries {}
|
||||
|
||||
impl fmt::Display for CliBlockWithEntries {
|
||||
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,
|
||||
lamports_to_sol(reward.lamports.unsigned_abs())
|
||||
),
|
||||
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
|
||||
)
|
||||
},
|
||||
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,
|
||||
lamports_to_sol(total_rewards.unsigned_abs())
|
||||
)?;
|
||||
}
|
||||
for (index, entry) in self.encoded_confirmed_block.entries.iter().enumerate() {
|
||||
writeln_entry(f, index, &entry.into(), "")?;
|
||||
for (index, transaction_with_meta) in entry.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 EncodedConfirmedBlockWithEntries {
|
||||
pub previous_blockhash: String,
|
||||
pub blockhash: String,
|
||||
pub parent_slot: Slot,
|
||||
pub entries: Vec<CliPopulatedEntry>,
|
||||
pub rewards: Rewards,
|
||||
pub block_time: Option<UnixTimestamp>,
|
||||
pub block_height: Option<u64>,
|
||||
}
|
||||
|
||||
impl EncodedConfirmedBlockWithEntries {
|
||||
pub fn try_from(
|
||||
block: EncodedConfirmedBlock,
|
||||
entries_iterator: impl Iterator<Item = EntrySummary>,
|
||||
) -> std::result::Result<Self, String> {
|
||||
let mut entries = vec![];
|
||||
for (i, entry) in entries_iterator.enumerate() {
|
||||
let ending_transaction_index = entry
|
||||
.starting_transaction_index
|
||||
.saturating_add(entry.num_transactions as usize);
|
||||
let transactions = block
|
||||
.transactions
|
||||
.get(entry.starting_transaction_index..ending_transaction_index)
|
||||
.ok_or(format!(
|
||||
"Mismatched entry data and transactions: entry {:?}",
|
||||
i
|
||||
))?;
|
||||
entries.push(CliPopulatedEntry {
|
||||
num_hashes: entry.num_hashes,
|
||||
hash: entry.hash.to_string(),
|
||||
num_transactions: entry.num_transactions,
|
||||
starting_transaction_index: entry.starting_transaction_index,
|
||||
transactions: transactions.to_vec(),
|
||||
});
|
||||
}
|
||||
Ok(Self {
|
||||
previous_blockhash: block.previous_blockhash,
|
||||
blockhash: block.blockhash,
|
||||
parent_slot: block.parent_slot,
|
||||
entries,
|
||||
rewards: block.rewards,
|
||||
block_time: block.block_time,
|
||||
block_height: block.block_height,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue