Skip to content

Commit

Permalink
ledger-tool: add show-entries option to bigtable block (solana-labs#3…
Browse files Browse the repository at this point in the history
…4536)

* 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
  • Loading branch information
CriesofCarrots authored Dec 20, 2023
1 parent def3bc4 commit cc0e5f7
Show file tree
Hide file tree
Showing 2 changed files with 231 additions and 22 deletions.
44 changes: 34 additions & 10 deletions ledger-tool/src/bigtable.rs
Original file line number Diff line number Diff line change
@@ -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,
},
Expand All @@ -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,
Expand Down Expand Up @@ -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)
Expand All @@ -134,12 +138,25 @@ async fn block(
format!("Failed to process unsupported transaction version ({version}) in block")
}
})?;

let cli_block = CliBlock {
encoded_confirmed_block: encoded_block.into(),
slot,
};
println!("{}", output_format.formatted_string(&cli_block));
let encoded_block: EncodedConfirmedBlock = encoded_block.into();

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(())
}

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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);
Expand Down
209 changes: 197 additions & 12 deletions ledger-tool/src/output.rs
Original file line number Diff line number Diff line change
@@ -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},
};

Expand Down Expand Up @@ -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 {
Expand All @@ -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(())
}
Expand All @@ -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,
})
}
}

0 comments on commit cc0e5f7

Please sign in to comment.