Output more inflation calc details in ledger-tool (#13345)

* Output more inflation calc details in ledger-tool

* Fix broken ci...

* Rename confusing variables

* Fix panic by wrapping PointValue with Opiton...

* Minor modifications

* Remove explict needless flush; Drop already does

* Yet another csv field adjustments

* Add data_size and rename epochs to earned_epochs

* Introduce null_tracer

* Unwrap Option in new_from_parent_with_tracer

* Don't shorten identifiers

* Allow irrefutable_let_patterns temporalily

* More null_tracer

* More field adjustments
This commit is contained in:
Ryo Onodera
2020-11-08 16:43:50 +09:00
committed by GitHub
parent 549492954e
commit a81e7e7749
5 changed files with 386 additions and 60 deletions

View File

@ -12,12 +12,14 @@ homepage = "https://solana.com/"
bs58 = "0.3.1"
bytecount = "0.6.0"
clap = "2.33.1"
csv = "1.1.3"
futures = "0.3.5"
futures-util = "0.3.5"
histogram = "*"
itertools = "0.9.0"
log = { version = "0.4.8" }
regex = "1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.56"
serde_yaml = "0.8.13"
solana-clap-utils = { path = "../clap-utils", version = "1.5.0" }

View File

@ -5,6 +5,7 @@ use clap::{
use itertools::Itertools;
use log::*;
use regex::Regex;
use serde::Serialize;
use serde_json::json;
use solana_clap_utils::{
input_parsers::{cluster_type_of, pubkey_of, pubkeys_of},
@ -20,7 +21,7 @@ use solana_ledger::{
rooted_slot_iterator::RootedSlotIterator,
};
use solana_runtime::{
bank::Bank,
bank::{Bank, RewardCalculationEvent},
bank_forks::{BankForks, CompressionType, SnapshotConfig},
hardened_unpack::{open_genesis_config, MAX_GENESIS_ARCHIVE_UNPACKED_SIZE},
snapshot_utils,
@ -38,7 +39,7 @@ use solana_sdk::{
shred_version::compute_shred_version,
system_program,
};
use solana_stake_program::stake_state::{self, StakeState};
use solana_stake_program::stake_state::{self, PointValue, StakeState};
use solana_vote_program::{
self,
vote_state::{self, VoteState},
@ -1192,6 +1193,13 @@ fn main() {
.help("Recalculate capitalization before warping; circumvents \
bank's out-of-sync capitalization"),
)
.arg(
Arg::with_name("csv_filename")
.long("csv-filename")
.value_name("FILENAME")
.takes_value(true)
.help("Output file in the csv format"),
)
).subcommand(
SubCommand::with_name("purge")
.about("Delete a range of slots from the ledger.")
@ -2017,9 +2025,100 @@ fn main() {
base_bank
.lazy_rent_collection
.store(true, std::sync::atomic::Ordering::Relaxed);
let warped_bank =
Bank::new_from_parent(&base_bank, base_bank.collector_id(), next_epoch);
#[derive(Default, Debug)]
struct CalculationDetail {
epochs: usize,
voter: Pubkey,
point: u128,
stake: u128,
total_stake: u64,
rent_exempt_reserve: u64,
credits: u128,
base_rewards: u64,
commission: u8,
vote_rewards: u64,
stake_rewards: u64,
activation_epoch: Epoch,
deactivation_epoch: Option<Epoch>,
point_value: Option<PointValue>,
}
use solana_stake_program::stake_state::InflationPointCalculationEvent;
let mut stake_calcuration_details: HashMap<Pubkey, CalculationDetail> =
HashMap::new();
let mut last_point_value = None;
let tracer = |event: &RewardCalculationEvent| {
// Currently RewardCalculationEvent enum has only Staking variant
// because only staking tracing is supported!
#[allow(irrefutable_let_patterns)]
if let RewardCalculationEvent::Staking(pubkey, event) = event {
let detail = stake_calcuration_details.entry(**pubkey).or_default();
match event {
InflationPointCalculationEvent::CalculatedPoints(
point,
stake,
credits,
) => {
// Don't sum for epochs where no credits are earned
if *credits > 0 {
detail.epochs += 1;
detail.point += *point;
detail.stake += *stake;
detail.credits += *credits;
}
}
InflationPointCalculationEvent::SplitRewards(
all,
voter,
staker,
point_value,
) => {
detail.base_rewards = *all;
detail.vote_rewards = *voter;
detail.stake_rewards = *staker;
detail.point_value = Some(point_value.clone());
// we have duplicate copies of `PointValue`s for possible
// miscalculation; do some minimum sanity check
let point_value = detail.point_value.clone();
if point_value.is_some() {
if last_point_value.is_some() {
assert_eq!(last_point_value, point_value,);
}
last_point_value = point_value;
}
}
InflationPointCalculationEvent::Commission(commission) => {
detail.commission = *commission;
}
InflationPointCalculationEvent::RentExemptReserve(reserve) => {
detail.rent_exempt_reserve = *reserve;
}
InflationPointCalculationEvent::Delegation(delegation) => {
detail.voter = delegation.voter_pubkey;
detail.total_stake = delegation.stake;
detail.activation_epoch = delegation.activation_epoch;
if delegation.deactivation_epoch < Epoch::max_value() {
detail.deactivation_epoch =
Some(delegation.deactivation_epoch);
}
}
}
}
};
let warped_bank = Bank::new_from_parent_with_tracer(
&base_bank,
base_bank.collector_id(),
next_epoch,
tracer,
);
warped_bank.freeze();
let mut csv_writer = if arg_matches.is_present("csv_filename") {
let csv_filename =
value_t_or_exit!(arg_matches, "csv_filename", String);
let file = File::create(&csv_filename).unwrap();
Some(csv::WriterBuilder::new().from_writer(file))
} else {
None
};
println!("Slot: {} => {}", base_bank.slot(), warped_bank.slot());
println!("Epoch: {} => {}", base_bank.epoch(), warped_bank.epoch());
@ -2041,9 +2140,10 @@ fn main() {
);
let mut overall_delta = 0;
let modified_accounts =
warped_bank.get_all_accounts_modified_since_parent();
let mut sorted_accounts = modified_accounts
let mut rewarded_accounts = modified_accounts
.iter()
.map(|(pubkey, account)| {
(
@ -2056,32 +2156,133 @@ fn main() {
)
})
.collect::<Vec<_>>();
sorted_accounts.sort_unstable_by_key(|(pubkey, account, base_lamports)| {
(
account.owner,
*base_lamports,
account.lamports - base_lamports,
*pubkey,
rewarded_accounts.sort_unstable_by_key(
|(pubkey, account, base_lamports)| {
(
account.owner,
*base_lamports,
account.lamports - base_lamports,
*pubkey,
)
},
);
let mut unchanged_accounts = stake_calcuration_details
.keys()
.collect::<HashSet<_>>()
.difference(
&rewarded_accounts
.iter()
.map(|(pubkey, ..)| *pubkey)
.collect(),
)
.map(|pubkey| (**pubkey, warped_bank.get_account(pubkey).unwrap()))
.collect::<Vec<_>>();
unchanged_accounts.sort_unstable_by_key(|(pubkey, account)| {
(account.owner, account.lamports, *pubkey)
});
for (pubkey, warped_account, _) in sorted_accounts {
let unchanged_accounts = unchanged_accounts.into_iter();
let rewarded_accounts = rewarded_accounts
.into_iter()
.map(|(pubkey, account, ..)| (*pubkey, account.clone()));
let all_accounts = unchanged_accounts.chain(rewarded_accounts);
for (pubkey, warped_account) in all_accounts {
// Don't output sysvars; it's always updated but not related to
// inflation.
if solana_sdk::sysvar::is_sysvar_id(&pubkey) {
continue;
}
if let Some(base_account) = base_bank.get_account(&pubkey) {
if base_account.lamports != warped_account.lamports {
let delta = warped_account.lamports - base_account.lamports;
println!(
"{:<45}({}): {} => {} (+{} {:>4.9}%)",
format!("{}", pubkey), // format! is needed to pad/justify correctly.
base_account.owner,
Sol(base_account.lamports),
Sol(warped_account.lamports),
Sol(delta),
((warped_account.lamports as f64)
/ (base_account.lamports as f64)
* 100_f64)
- 100_f64,
);
overall_delta += delta;
let delta = warped_account.lamports - base_account.lamports;
let detail = stake_calcuration_details.get(&pubkey);
println!(
"{:<45}({}): {} => {} (+{} {:>4.9}%) {:?}",
format!("{}", pubkey), // format! is needed to pad/justify correctly.
base_account.owner,
Sol(base_account.lamports),
Sol(warped_account.lamports),
Sol(delta),
((warped_account.lamports as f64)
/ (base_account.lamports as f64)
* 100_f64)
- 100_f64,
detail,
);
if let Some(ref mut csv_writer) = csv_writer {
#[derive(Serialize)]
struct InflationRecord {
account: String,
owner: String,
old_balance: u64,
new_balance: u64,
data_size: usize,
delegation: String,
effective_stake: String,
delegated_stake: String,
rent_exempt_reserve: String,
activation_epoch: String,
deactivation_epoch: String,
earned_epochs: String,
earned_credits: String,
base_rewards: String,
stake_rewards: String,
vote_rewards: String,
commission: String,
cluster_rewards: String,
cluster_points: String,
};
fn format_or_na<T: std::fmt::Display>(
data: Option<T>,
) -> String {
data.map(|data| format!("{}", data))
.unwrap_or_else(|| "N/A".to_owned())
};
let record = InflationRecord {
account: format!("{}", pubkey),
owner: format!("{}", base_account.owner),
old_balance: base_account.lamports,
new_balance: warped_account.lamports,
data_size: base_account.data.len(),
delegation: format_or_na(detail.map(|d| d.voter)),
effective_stake: format_or_na(detail.map(|d| d.stake)),
delegated_stake: format_or_na(
detail.map(|d| d.total_stake),
),
rent_exempt_reserve: format_or_na(
detail.map(|d| d.rent_exempt_reserve),
),
activation_epoch: format_or_na(detail.map(|d| {
if d.activation_epoch < Epoch::max_value() {
d.activation_epoch
} else {
// bootstraped
0
}
})),
deactivation_epoch: format_or_na(
detail.and_then(|d| d.deactivation_epoch),
),
earned_epochs: format_or_na(detail.map(|d| d.epochs)),
earned_credits: format_or_na(detail.map(|d| d.credits)),
base_rewards: format_or_na(detail.map(|d| d.base_rewards)),
stake_rewards: format_or_na(
detail.map(|d| d.stake_rewards),
),
vote_rewards: format_or_na(detail.map(|d| d.vote_rewards)),
commission: format_or_na(detail.map(|d| d.commission)),
cluster_rewards: format_or_na(
last_point_value.as_ref().map(|pv| pv.rewards),
),
cluster_points: format_or_na(
last_point_value.as_ref().map(|pv| pv.points),
),
};
csv_writer.serialize(&record).unwrap();
}
overall_delta += delta;
} else {
error!("new account!?: {}", pubkey);
}