793 lines
27 KiB
Rust
793 lines
27 KiB
Rust
use {
|
|
crate::ledger_utils::get_program_ids,
|
|
chrono::{Local, TimeZone},
|
|
serde::{
|
|
ser::{Impossible, SerializeSeq, SerializeStruct, Serializer},
|
|
Deserialize, Serialize,
|
|
},
|
|
solana_account_decoder::{UiAccount, UiAccountData, UiAccountEncoding},
|
|
solana_accounts_db::accounts_index::ScanConfig,
|
|
solana_cli_output::{
|
|
display::writeln_transaction, CliAccount, CliAccountNewConfig, OutputFormat, QuietDisplay,
|
|
VerboseDisplay,
|
|
},
|
|
solana_entry::entry::Entry,
|
|
solana_ledger::blockstore::Blockstore,
|
|
solana_runtime::bank::{Bank, TotalAccountsStats},
|
|
solana_sdk::{
|
|
account::{AccountSharedData, ReadableAccount},
|
|
clock::{Slot, UnixTimestamp},
|
|
hash::Hash,
|
|
native_token::lamports_to_sol,
|
|
pubkey::Pubkey,
|
|
},
|
|
solana_transaction_status::{
|
|
EncodedConfirmedBlock, EncodedTransactionWithStatusMeta, EntrySummary, Rewards,
|
|
},
|
|
std::{
|
|
cell::RefCell,
|
|
collections::HashMap,
|
|
fmt::{self, Display, Formatter},
|
|
io::{stdout, Write},
|
|
rc::Rc,
|
|
result::Result,
|
|
sync::Arc,
|
|
},
|
|
};
|
|
|
|
#[derive(Serialize, Debug, Default)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct SlotInfo {
|
|
pub total: usize,
|
|
pub first: Option<u64>,
|
|
pub last: Option<u64>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub num_after_last_root: Option<usize>,
|
|
}
|
|
|
|
#[derive(Serialize, Debug, Default)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct SlotBounds<'a> {
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub all_slots: Option<&'a Vec<u64>>,
|
|
pub slots: SlotInfo,
|
|
pub roots: SlotInfo,
|
|
}
|
|
|
|
impl VerboseDisplay for SlotBounds<'_> {}
|
|
impl QuietDisplay for SlotBounds<'_> {}
|
|
|
|
impl Display for SlotBounds<'_> {
|
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
if self.slots.total > 0 {
|
|
let first = self.slots.first.unwrap();
|
|
let last = self.slots.last.unwrap();
|
|
|
|
if first != last {
|
|
writeln!(
|
|
f,
|
|
"Ledger has data for {:?} slots {:?} to {:?}",
|
|
self.slots.total, first, last
|
|
)?;
|
|
|
|
if let Some(all_slots) = self.all_slots {
|
|
writeln!(f, "Non-empty slots: {all_slots:?}")?;
|
|
}
|
|
} else {
|
|
writeln!(f, "Ledger has data for slot {first:?}")?;
|
|
}
|
|
|
|
if self.roots.total > 0 {
|
|
let first_rooted = self.roots.first.unwrap_or_default();
|
|
let last_rooted = self.roots.last.unwrap_or_default();
|
|
let num_after_last_root = self.roots.num_after_last_root.unwrap_or_default();
|
|
writeln!(
|
|
f,
|
|
" with {:?} rooted slots from {:?} to {:?}",
|
|
self.roots.total, first_rooted, last_rooted
|
|
)?;
|
|
|
|
writeln!(f, " and {num_after_last_root:?} slots past the last root")?;
|
|
} else {
|
|
writeln!(f, " with no rooted slots")?;
|
|
}
|
|
} else {
|
|
writeln!(f, "Ledger is empty")?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
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 {
|
|
pub entries: Vec<CliEntry>,
|
|
#[serde(skip_serializing)]
|
|
pub slot: Slot,
|
|
}
|
|
|
|
impl QuietDisplay for CliEntries {}
|
|
impl VerboseDisplay for CliEntries {}
|
|
|
|
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_entry(f, i, entry, " ")?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct CliEntry {
|
|
num_hashes: u64,
|
|
hash: String,
|
|
num_transactions: u64,
|
|
starting_transaction_index: usize,
|
|
}
|
|
|
|
impl From<EntrySummary> for CliEntry {
|
|
fn from(entry_summary: EntrySummary) -> Self {
|
|
Self {
|
|
num_hashes: entry_summary.num_hashes,
|
|
hash: entry_summary.hash.to_string(),
|
|
num_transactions: entry_summary.num_transactions,
|
|
starting_transaction_index: entry_summary.starting_transaction_index,
|
|
}
|
|
}
|
|
}
|
|
|
|
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>,
|
|
) -> 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,
|
|
})
|
|
}
|
|
}
|
|
|
|
pub fn output_slot_rewards(blockstore: &Blockstore, slot: Slot, method: &OutputFormat) {
|
|
// Note: rewards are not output in JSON yet
|
|
if *method == OutputFormat::Display {
|
|
if let Ok(Some(rewards)) = blockstore.read_rewards(slot) {
|
|
if !rewards.is_empty() {
|
|
println!(" Rewards:");
|
|
println!(
|
|
" {:<44} {:^15} {:<15} {:<20} {:>10}",
|
|
"Address", "Type", "Amount", "New Balance", "Commission",
|
|
);
|
|
|
|
for reward in rewards {
|
|
let sign = if reward.lamports < 0 { "-" } else { "" };
|
|
println!(
|
|
" {:<44} {:^15} {}◎{:<14.9} ◎{:<18.9} {}",
|
|
reward.pubkey,
|
|
if let Some(reward_type) = reward.reward_type {
|
|
format!("{reward_type}")
|
|
} else {
|
|
"-".to_string()
|
|
},
|
|
sign,
|
|
lamports_to_sol(reward.lamports.unsigned_abs()),
|
|
lamports_to_sol(reward.post_balance),
|
|
reward
|
|
.commission
|
|
.map(|commission| format!("{commission:>9}%"))
|
|
.unwrap_or_else(|| " -".to_string())
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn output_entry(
|
|
blockstore: &Blockstore,
|
|
method: &OutputFormat,
|
|
slot: Slot,
|
|
entry_index: usize,
|
|
entry: Entry,
|
|
) {
|
|
match method {
|
|
OutputFormat::Display => {
|
|
println!(
|
|
" Entry {} - num_hashes: {}, hash: {}, transactions: {}",
|
|
entry_index,
|
|
entry.num_hashes,
|
|
entry.hash,
|
|
entry.transactions.len()
|
|
);
|
|
for (transactions_index, transaction) in entry.transactions.into_iter().enumerate() {
|
|
println!(" Transaction {transactions_index}");
|
|
let tx_signature = transaction.signatures[0];
|
|
let tx_status_meta = blockstore
|
|
.read_transaction_status((tx_signature, slot))
|
|
.unwrap_or_else(|err| {
|
|
eprintln!(
|
|
"Failed to read transaction status for {} at slot {}: {}",
|
|
transaction.signatures[0], slot, err
|
|
);
|
|
None
|
|
})
|
|
.map(|meta| meta.into());
|
|
|
|
solana_cli_output::display::println_transaction(
|
|
&transaction,
|
|
tx_status_meta.as_ref(),
|
|
" ",
|
|
None,
|
|
None,
|
|
);
|
|
}
|
|
}
|
|
OutputFormat::Json => {
|
|
// Note: transaction status is not output in JSON yet
|
|
serde_json::to_writer(stdout(), &entry).expect("serialize entry");
|
|
stdout().write_all(b",\n").expect("newline");
|
|
}
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
pub fn output_slot(
|
|
blockstore: &Blockstore,
|
|
slot: Slot,
|
|
allow_dead_slots: bool,
|
|
method: &OutputFormat,
|
|
verbose_level: u64,
|
|
all_program_ids: &mut HashMap<Pubkey, u64>,
|
|
) -> Result<(), String> {
|
|
if blockstore.is_dead(slot) {
|
|
if allow_dead_slots {
|
|
if *method == OutputFormat::Display {
|
|
println!(" Slot is dead");
|
|
}
|
|
} else {
|
|
return Err("Dead slot".to_string());
|
|
}
|
|
}
|
|
|
|
let (entries, num_shreds, is_full) = blockstore
|
|
.get_slot_entries_with_shred_info(slot, 0, allow_dead_slots)
|
|
.map_err(|err| format!("Failed to load entries for slot {slot}: {err:?}"))?;
|
|
|
|
if *method == OutputFormat::Display {
|
|
if let Ok(Some(meta)) = blockstore.meta(slot) {
|
|
if verbose_level >= 1 {
|
|
println!(" {meta:?} is_full: {is_full}");
|
|
} else {
|
|
println!(
|
|
" num_shreds: {}, parent_slot: {:?}, next_slots: {:?}, num_entries: {}, \
|
|
is_full: {}",
|
|
num_shreds,
|
|
meta.parent_slot,
|
|
meta.next_slots,
|
|
entries.len(),
|
|
is_full,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if verbose_level >= 2 {
|
|
for (entry_index, entry) in entries.into_iter().enumerate() {
|
|
output_entry(blockstore, method, slot, entry_index, entry);
|
|
}
|
|
|
|
output_slot_rewards(blockstore, slot, method);
|
|
} else if verbose_level >= 1 {
|
|
let mut transactions = 0;
|
|
let mut num_hashes = 0;
|
|
let mut program_ids = HashMap::new();
|
|
let blockhash = if let Some(entry) = entries.last() {
|
|
entry.hash
|
|
} else {
|
|
Hash::default()
|
|
};
|
|
|
|
for entry in entries {
|
|
transactions += entry.transactions.len();
|
|
num_hashes += entry.num_hashes;
|
|
for transaction in entry.transactions {
|
|
for program_id in get_program_ids(&transaction) {
|
|
*program_ids.entry(*program_id).or_insert(0) += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
println!(" Transactions: {transactions}, hashes: {num_hashes}, block_hash: {blockhash}",);
|
|
for (pubkey, count) in program_ids.iter() {
|
|
*all_program_ids.entry(*pubkey).or_insert(0) += count;
|
|
}
|
|
println!(" Programs:");
|
|
output_sorted_program_ids(program_ids);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn output_ledger(
|
|
blockstore: Blockstore,
|
|
starting_slot: Slot,
|
|
ending_slot: Slot,
|
|
allow_dead_slots: bool,
|
|
method: OutputFormat,
|
|
num_slots: Option<Slot>,
|
|
verbose_level: u64,
|
|
only_rooted: bool,
|
|
) {
|
|
let slot_iterator = blockstore
|
|
.slot_meta_iterator(starting_slot)
|
|
.unwrap_or_else(|err| {
|
|
eprintln!("Failed to load entries starting from slot {starting_slot}: {err:?}");
|
|
std::process::exit(1);
|
|
});
|
|
|
|
if method == OutputFormat::Json {
|
|
stdout().write_all(b"{\"ledger\":[\n").expect("open array");
|
|
}
|
|
|
|
let num_slots = num_slots.unwrap_or(Slot::MAX);
|
|
let mut num_printed = 0;
|
|
let mut all_program_ids = HashMap::new();
|
|
for (slot, slot_meta) in slot_iterator {
|
|
if only_rooted && !blockstore.is_root(slot) {
|
|
continue;
|
|
}
|
|
if slot > ending_slot {
|
|
break;
|
|
}
|
|
|
|
match method {
|
|
OutputFormat::Display => {
|
|
println!("Slot {} root?: {}", slot, blockstore.is_root(slot))
|
|
}
|
|
OutputFormat::Json => {
|
|
serde_json::to_writer(stdout(), &slot_meta).expect("serialize slot_meta");
|
|
stdout().write_all(b",\n").expect("newline");
|
|
}
|
|
_ => unreachable!(),
|
|
}
|
|
|
|
if let Err(err) = output_slot(
|
|
&blockstore,
|
|
slot,
|
|
allow_dead_slots,
|
|
&method,
|
|
verbose_level,
|
|
&mut all_program_ids,
|
|
) {
|
|
eprintln!("{err}");
|
|
}
|
|
num_printed += 1;
|
|
if num_printed >= num_slots as usize {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if method == OutputFormat::Json {
|
|
stdout().write_all(b"\n]}\n").expect("close array");
|
|
} else {
|
|
println!("Summary of Programs:");
|
|
output_sorted_program_ids(all_program_ids);
|
|
}
|
|
}
|
|
|
|
pub fn output_sorted_program_ids(program_ids: HashMap<Pubkey, u64>) {
|
|
let mut program_ids_array: Vec<_> = program_ids.into_iter().collect();
|
|
// Sort descending by count of program id
|
|
program_ids_array.sort_by(|a, b| b.1.cmp(&a.1));
|
|
for (program_id, count) in program_ids_array.iter() {
|
|
println!("{:<44}: {}", program_id.to_string(), count);
|
|
}
|
|
}
|
|
|
|
/// A type to facilitate streaming account information to an output destination
|
|
///
|
|
/// This type scans every account, so streaming is preferred over the simpler
|
|
/// approach of accumulating all the accounts into a Vec and printing or
|
|
/// serializing the Vec directly.
|
|
pub struct AccountsOutputStreamer {
|
|
account_scanner: AccountsScanner,
|
|
total_accounts_stats: Rc<RefCell<TotalAccountsStats>>,
|
|
output_format: OutputFormat,
|
|
}
|
|
|
|
pub enum AccountsOutputMode {
|
|
All,
|
|
Individual(Vec<Pubkey>),
|
|
Program(Pubkey),
|
|
}
|
|
|
|
pub struct AccountsOutputConfig {
|
|
pub mode: AccountsOutputMode,
|
|
pub include_sysvars: bool,
|
|
pub include_account_contents: bool,
|
|
pub include_account_data: bool,
|
|
pub account_data_encoding: UiAccountEncoding,
|
|
}
|
|
|
|
impl AccountsOutputStreamer {
|
|
pub fn new(bank: Arc<Bank>, output_format: OutputFormat, config: AccountsOutputConfig) -> Self {
|
|
let total_accounts_stats = Rc::new(RefCell::new(TotalAccountsStats::default()));
|
|
let account_scanner = AccountsScanner {
|
|
bank,
|
|
total_accounts_stats: total_accounts_stats.clone(),
|
|
config,
|
|
};
|
|
Self {
|
|
account_scanner,
|
|
total_accounts_stats,
|
|
output_format,
|
|
}
|
|
}
|
|
|
|
pub fn output(&self) -> Result<(), String> {
|
|
match self.output_format {
|
|
OutputFormat::Json | OutputFormat::JsonCompact => {
|
|
let mut serializer = serde_json::Serializer::new(stdout());
|
|
let mut struct_serializer = serializer
|
|
.serialize_struct("accountInfo", 2)
|
|
.map_err(|err| format!("unable to start serialization: {err}"))?;
|
|
struct_serializer
|
|
.serialize_field("accounts", &self.account_scanner)
|
|
.map_err(|err| format!("unable to serialize accounts scanner: {err}"))?;
|
|
struct_serializer
|
|
.serialize_field("summary", &*self.total_accounts_stats.borrow())
|
|
.map_err(|err| format!("unable to serialize accounts summary: {err}"))?;
|
|
SerializeStruct::end(struct_serializer)
|
|
.map_err(|err| format!("unable to end serialization: {err}"))?;
|
|
// The serializer doesn't give us a trailing newline so do it ourselves
|
|
println!();
|
|
Ok(())
|
|
}
|
|
_ => {
|
|
// The compiler needs a placeholder type to satisfy the generic
|
|
// SerializeSeq trait on AccountScanner::output(). The type
|
|
// doesn't really matter since we're passing None, so just use
|
|
// serde::ser::Impossible as it already implements SerializeSeq
|
|
self.account_scanner
|
|
.output::<Impossible<(), serde_json::Error>>(&mut None);
|
|
println!("\n{:#?}", self.total_accounts_stats.borrow());
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct AccountsScanner {
|
|
bank: Arc<Bank>,
|
|
total_accounts_stats: Rc<RefCell<TotalAccountsStats>>,
|
|
config: AccountsOutputConfig,
|
|
}
|
|
|
|
impl AccountsScanner {
|
|
/// Returns true if this account should be included in the output
|
|
fn should_process_account(&self, account: &AccountSharedData, pubkey: &Pubkey) -> bool {
|
|
solana_accounts_db::accounts::Accounts::is_loadable(account.lamports())
|
|
&& (self.config.include_sysvars || !solana_sdk::sysvar::is_sysvar_id(pubkey))
|
|
}
|
|
|
|
fn maybe_output_account<S>(
|
|
&self,
|
|
seq_serializer: &mut Option<S>,
|
|
pubkey: &Pubkey,
|
|
account: &AccountSharedData,
|
|
slot: Option<Slot>,
|
|
cli_account_new_config: &CliAccountNewConfig,
|
|
) where
|
|
S: SerializeSeq,
|
|
{
|
|
if self.config.include_account_contents {
|
|
if let Some(serializer) = seq_serializer {
|
|
let cli_account =
|
|
CliAccount::new_with_config(pubkey, account, cli_account_new_config);
|
|
serializer.serialize_element(&cli_account).unwrap();
|
|
} else {
|
|
output_account(
|
|
pubkey,
|
|
account,
|
|
slot,
|
|
self.config.include_account_data,
|
|
self.config.account_data_encoding,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn output<S>(&self, seq_serializer: &mut Option<S>)
|
|
where
|
|
S: SerializeSeq,
|
|
{
|
|
let mut total_accounts_stats = self.total_accounts_stats.borrow_mut();
|
|
let rent_collector = self.bank.rent_collector();
|
|
|
|
let cli_account_new_config = CliAccountNewConfig {
|
|
data_encoding: self.config.account_data_encoding,
|
|
..CliAccountNewConfig::default()
|
|
};
|
|
|
|
let scan_func = |account_tuple: Option<(&Pubkey, AccountSharedData, Slot)>| {
|
|
if let Some((pubkey, account, slot)) = account_tuple
|
|
.filter(|(pubkey, account, _)| self.should_process_account(account, pubkey))
|
|
{
|
|
total_accounts_stats.accumulate_account(pubkey, &account, rent_collector);
|
|
self.maybe_output_account(
|
|
seq_serializer,
|
|
pubkey,
|
|
&account,
|
|
Some(slot),
|
|
&cli_account_new_config,
|
|
);
|
|
}
|
|
};
|
|
|
|
match &self.config.mode {
|
|
AccountsOutputMode::All => {
|
|
self.bank.scan_all_accounts(scan_func).unwrap();
|
|
}
|
|
AccountsOutputMode::Individual(pubkeys) => pubkeys.iter().for_each(|pubkey| {
|
|
if let Some((account, slot)) = self
|
|
.bank
|
|
.get_account_modified_slot_with_fixed_root(pubkey)
|
|
.filter(|(account, _)| self.should_process_account(account, pubkey))
|
|
{
|
|
total_accounts_stats.accumulate_account(pubkey, &account, rent_collector);
|
|
self.maybe_output_account(
|
|
seq_serializer,
|
|
pubkey,
|
|
&account,
|
|
Some(slot),
|
|
&cli_account_new_config,
|
|
);
|
|
}
|
|
}),
|
|
AccountsOutputMode::Program(program_pubkey) => self
|
|
.bank
|
|
.get_program_accounts(program_pubkey, &ScanConfig::default())
|
|
.unwrap()
|
|
.iter()
|
|
.filter(|(pubkey, account)| self.should_process_account(account, pubkey))
|
|
.for_each(|(pubkey, account)| {
|
|
total_accounts_stats.accumulate_account(pubkey, account, rent_collector);
|
|
self.maybe_output_account(
|
|
seq_serializer,
|
|
pubkey,
|
|
account,
|
|
None,
|
|
&cli_account_new_config,
|
|
);
|
|
}),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Serialize for AccountsScanner {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
let mut seq_serializer = Some(serializer.serialize_seq(None)?);
|
|
self.output(&mut seq_serializer);
|
|
seq_serializer.unwrap().end()
|
|
}
|
|
}
|
|
|
|
pub fn output_account(
|
|
pubkey: &Pubkey,
|
|
account: &AccountSharedData,
|
|
modified_slot: Option<Slot>,
|
|
print_account_data: bool,
|
|
encoding: UiAccountEncoding,
|
|
) {
|
|
println!("{pubkey}:");
|
|
println!(" balance: {} SOL", lamports_to_sol(account.lamports()));
|
|
println!(" owner: '{}'", account.owner());
|
|
println!(" executable: {}", account.executable());
|
|
if let Some(slot) = modified_slot {
|
|
println!(" slot: {slot}");
|
|
}
|
|
println!(" rent_epoch: {}", account.rent_epoch());
|
|
println!(" data_len: {}", account.data().len());
|
|
if print_account_data {
|
|
let account_data = UiAccount::encode(pubkey, account, encoding, None, None).data;
|
|
match account_data {
|
|
UiAccountData::Binary(data, data_encoding) => {
|
|
println!(" data: '{data}'");
|
|
println!(
|
|
" encoding: {}",
|
|
serde_json::to_string(&data_encoding).unwrap()
|
|
);
|
|
}
|
|
UiAccountData::Json(account_data) => {
|
|
println!(
|
|
" data: '{}'",
|
|
serde_json::to_string(&account_data).unwrap()
|
|
);
|
|
println!(" encoding: \"jsonParsed\"");
|
|
}
|
|
UiAccountData::LegacyBinary(_) => {}
|
|
};
|
|
}
|
|
}
|