Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.

Commit b9cd4cb

Browse files
author
Steven Czabaniuk
committed
Write bank hash components to file when node diverges
Also allow the data to be written from ledger-tool in order to create a file to compare against. The generated files are human-readable (JSON) and diff-friendly.
1 parent ed401ce commit b9cd4cb

File tree

7 files changed

+125
-4
lines changed

7 files changed

+125
-4
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/src/replay_stage.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1500,6 +1500,7 @@ impl ReplayStage {
15001500
let bank = w_bank_forks
15011501
.remove(*slot)
15021502
.expect("BankForks should not have been purged yet");
1503+
bank.write_hash_details_file();
15031504
((*slot, bank.bank_id()), bank)
15041505
})
15051506
.unzip()

ledger-tool/src/main.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1654,6 +1654,13 @@ fn main() {
16541654
.takes_value(false)
16551655
.help("After verifying the ledger, print some information about the account stores"),
16561656
)
1657+
.arg(
1658+
Arg::with_name("write_bank_file")
1659+
.long("write-bank-file")
1660+
.takes_value(false)
1661+
.help("After verifying the ledger, write a file with that contains the \
1662+
information that went into computing the final bank's bank hash"),
1663+
)
16571664
).subcommand(
16581665
SubCommand::with_name("graph")
16591666
.about("Create a Graphviz rendering of the ledger")
@@ -2616,6 +2623,7 @@ fn main() {
26162623
..ProcessOptions::default()
26172624
};
26182625
let print_accounts_stats = arg_matches.is_present("print_accounts_stats");
2626+
let write_bank_file = arg_matches.is_present("write_bank_file");
26192627
let genesis_config = open_genesis_config_by(&ledger_path, arg_matches);
26202628
info!("genesis hash: {}", genesis_config.hash());
26212629

@@ -2641,6 +2649,10 @@ fn main() {
26412649
let working_bank = bank_forks.read().unwrap().working_bank();
26422650
working_bank.print_accounts_stats();
26432651
}
2652+
if write_bank_file {
2653+
let working_bank = bank_forks.read().unwrap().working_bank();
2654+
working_bank.write_hash_details_file();
2655+
}
26442656
exit_signal.store(true, Ordering::Relaxed);
26452657
system_monitor_service.join().unwrap();
26462658
}

programs/sbf/Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

runtime/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ rayon = { workspace = true }
4444
regex = { workspace = true }
4545
serde = { workspace = true, features = ["rc"] }
4646
serde_derive = { workspace = true }
47+
serde_json = { workspace = true }
4748
siphasher = { workspace = true }
4849
solana-address-lookup-table-program = { workspace = true }
4950
solana-bpf-loader-program = { workspace = true }
@@ -62,6 +63,7 @@ solana-rayon-threadlimit = { workspace = true }
6263
solana-sdk = { workspace = true }
6364
solana-stake-program = { workspace = true }
6465
solana-system-program = { workspace = true }
66+
solana-version = { workspace = true }
6567
solana-vote-program = { workspace = true }
6668
solana-zk-token-proof-program = { workspace = true }
6769
solana-zk-token-sdk = { workspace = true }

runtime/src/accounts_db.rs

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,6 +1015,16 @@ struct CleanKeyTimings {
10151015
dirty_ancient_stores: usize,
10161016
}
10171017

1018+
/// Fundamental details of an account
1019+
#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize)]
1020+
pub struct AccountDetails {
1021+
pub pubkey: Pubkey,
1022+
pub lamports: u64,
1023+
pub rent_epoch: Epoch,
1024+
pub executable: bool,
1025+
pub hash: Hash,
1026+
}
1027+
10181028
/// Persistent storage structure holding the accounts
10191029
#[derive(Debug)]
10201030
pub struct AccountStorageEntry {
@@ -2372,8 +2382,8 @@ impl AccountsDb {
23722382
let (base_working_path, accounts_hash_cache_path, temp_accounts_hash_cache_path) =
23732383
match base_working_path {
23742384
Some(base_working_path) => {
2375-
let accounts_hash_cache_path = base_working_path
2376-
.join(Self::ACCOUNTS_HASH_CACHE_DIR);
2385+
let accounts_hash_cache_path =
2386+
base_working_path.join(Self::ACCOUNTS_HASH_CACHE_DIR);
23772387
(base_working_path, accounts_hash_cache_path, None)
23782388
}
23792389
None => {
@@ -2383,8 +2393,8 @@ impl AccountsDb {
23832393
.unwrap()
23842394
.path()
23852395
.to_path_buf();
2386-
let accounts_hash_cache_path = base_working_path
2387-
.join(Self::ACCOUNTS_HASH_CACHE_DIR);
2396+
let accounts_hash_cache_path =
2397+
base_working_path.join(Self::ACCOUNTS_HASH_CACHE_DIR);
23882398
(
23892399
base_working_path,
23902400
accounts_hash_cache_path,
@@ -7822,6 +7832,37 @@ impl AccountsDb {
78227832
(hashes, scan.as_us(), accumulate)
78237833
}
78247834

7835+
/// Return details on all of the accounts for the given `slot`, sorted by pubkey.
7836+
pub(crate) fn get_account_details_for_slot(&self, slot: Slot) -> Vec<AccountDetails> {
7837+
let account_info_func = |loaded_account: &LoadedAccount| -> AccountDetails {
7838+
AccountDetails {
7839+
pubkey: *loaded_account.pubkey(),
7840+
lamports: loaded_account.lamports(),
7841+
rent_epoch: loaded_account.rent_epoch(),
7842+
executable: loaded_account.executable(),
7843+
hash: loaded_account.loaded_hash(),
7844+
}
7845+
};
7846+
7847+
type ScanResult = ScanStorageResult<AccountDetails, Mutex<Vec<AccountDetails>>>;
7848+
let scan_result: ScanResult = self.scan_account_storage(
7849+
slot,
7850+
|loaded_account: LoadedAccount| {
7851+
// Cache only has one version per key, don't need to worry about versioning
7852+
Some(account_info_func(&loaded_account))
7853+
},
7854+
|accum: &Mutex<Vec<AccountDetails>>, loaded_account: LoadedAccount| {
7855+
let account_info = account_info_func(&loaded_account);
7856+
accum.lock().unwrap().push(account_info);
7857+
},
7858+
);
7859+
7860+
match scan_result {
7861+
ScanStorageResult::Cached(cached_result) => cached_result,
7862+
ScanStorageResult::Stored(stored_result) => stored_result.into_inner().unwrap(),
7863+
}
7864+
}
7865+
78257866
/// Calculate accounts delta hash for `slot`
78267867
///
78277868
/// As part of calculating the accounts delta hash, get a list of accounts modified this slot

runtime/src/bank.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ use {
9696
iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator},
9797
ThreadPool, ThreadPoolBuilder,
9898
},
99+
serde_json::json,
99100
solana_bpf_loader_program::syscalls::create_program_runtime_environment,
100101
solana_cost_model::cost_tracker::CostTracker,
101102
solana_measure::{measure, measure::Measure, measure_us},
@@ -8164,6 +8165,66 @@ impl Bank {
81648165
}
81658166
false
81668167
}
8168+
8169+
/// Output the components that comprise bank hash
8170+
// TODO: maybe give ledger-tool a unique path so it can bypass path.exists() check?
8171+
pub fn write_hash_details_file(&self) {
8172+
let slot = self.slot();
8173+
// Only allow generating (and thus comparing) results on frozen (complete) banks
8174+
if !self.is_frozen() {
8175+
warn!("Cannot write bank hash details for non-frozen bank {slot}");
8176+
return;
8177+
}
8178+
8179+
let hash = self.hash();
8180+
let file_name = format!("{slot}.{hash}");
8181+
let parent_dir = self
8182+
.rc
8183+
.accounts
8184+
.accounts_db
8185+
.get_base_working_path()
8186+
.join("bank_hash_details");
8187+
let path = parent_dir.join(file_name);
8188+
// A file with the same name implies the same hash for this slot. Skip
8189+
// rewriting a duplicate file in this scenario
8190+
if path.exists() {
8191+
return;
8192+
}
8193+
info!("writing details of bank {} to {}", slot, path.display());
8194+
8195+
// This bank is frozen; as a result, we know that the state has been
8196+
// hashed which means the delta hash is Some(). So, .unwrap() is safe
8197+
let accounts_delta_hash = self
8198+
.rc
8199+
.accounts
8200+
.accounts_db
8201+
.get_accounts_delta_hash(slot)
8202+
.unwrap()
8203+
.0;
8204+
let mut account_details = self
8205+
.rc
8206+
.accounts
8207+
.accounts_db
8208+
.get_account_details_for_slot(slot);
8209+
account_details.sort_by_key(|details| details.pubkey);
8210+
8211+
let output = json!({
8212+
"version": solana_version::version!(),
8213+
"slot": slot,
8214+
"hash": hash,
8215+
"parent_hash": self.parent_hash(),
8216+
"accounts_delta_hash": accounts_delta_hash,
8217+
"signature_count": self.signature_count(),
8218+
"last_blockhash": self.last_blockhash(),
8219+
"accounts": account_details
8220+
});
8221+
8222+
// std::fs::write may fail (depending on platform) if the full directory
8223+
// path does not exist. So, call std::fs_create_dir_all first.
8224+
// https://doc.rust-lang.org/std/fs/fn.write.html
8225+
_ = std::fs::create_dir_all(parent_dir);
8226+
_ = std::fs::write(path, serde_json::to_vec_pretty(&output).unwrap());
8227+
}
81678228
}
81688229

81698230
/// Compute how much an account has changed size. This function is useful when the data size delta

0 commit comments

Comments
 (0)