Allow bank hash file to contain details for multiple slots (#34516)

The initial implementation only allowed for outputting details for one
bank/slot. There are several usecases that could take advantage of the
file being able to contain details for multiple banks.

So, this PR adjusts the structure to contain details about multiple
banks. Note that using a basic diff will not work for comparing the old
format to this new format. Instead, a basic transform with jq will be
necessary to do so.
This commit is contained in:
steviez 2023-12-27 12:37:11 -05:00 committed by GitHub
parent 057ad2ab47
commit 1f7c714acf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 99 additions and 50 deletions

View File

@ -23,9 +23,51 @@ use {
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub(crate) struct BankHashDetails {
/// client version
/// The client version
pub version: String,
/// The encoding format for account data buffers
pub account_data_encoding: String,
/// Bank hash details for a collection of banks
pub bank_hash_details: Vec<BankHashSlotDetails>,
}
impl BankHashDetails {
pub fn new(bank_hash_details: Vec<BankHashSlotDetails>) -> Self {
Self {
version: solana_version::version!().to_string(),
account_data_encoding: "base64".to_string(),
bank_hash_details,
}
}
/// Determines a filename given the currently held bank details
pub fn filename(&self) -> Result<String, String> {
if self.bank_hash_details.is_empty() {
return Err("BankHashDetails does not contains details for any banks".to_string());
}
// From here on, .unwrap() on .first() and .second() is safe as
// self.bank_hash_details is known to be non-empty
let (first_slot, first_hash) = {
let details = self.bank_hash_details.first().unwrap();
(details.slot, &details.bank_hash)
};
let filename = if self.bank_hash_details.len() == 1 {
format!("{first_slot}-{first_hash}.json")
} else {
let (last_slot, last_hash) = {
let details = self.bank_hash_details.last().unwrap();
(details.slot, &details.bank_hash)
};
format!("{first_slot}-{first_hash}_{last_slot}-{last_hash}.json")
};
Ok(filename)
}
}
/// The components that go into a bank hash calculation for a single bank/slot.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub(crate) struct BankHashSlotDetails {
pub slot: Slot,
pub bank_hash: String,
pub parent_bank_hash: String,
@ -35,7 +77,7 @@ pub(crate) struct BankHashDetails {
pub accounts: BankHashAccounts,
}
impl BankHashDetails {
impl BankHashSlotDetails {
pub fn new(
slot: Slot,
bank_hash: Hash,
@ -46,8 +88,6 @@ impl BankHashDetails {
accounts: BankHashAccounts,
) -> Self {
Self {
version: solana_version::version!().to_string(),
account_data_encoding: "base64".to_string(),
slot,
bank_hash: bank_hash.to_string(),
parent_bank_hash: parent_bank_hash.to_string(),
@ -59,7 +99,7 @@ impl BankHashDetails {
}
}
impl TryFrom<&Bank> for BankHashDetails {
impl TryFrom<&Bank> for BankHashSlotDetails {
type Error = String;
fn try_from(bank: &Bank) -> Result<Self, Self::Error> {
@ -99,15 +139,16 @@ impl TryFrom<&Bank> for BankHashDetails {
}
}
// Wrap the Vec<...> so we can implement custom Serialize/Deserialize traits on the wrapper type
/// Wrapper around a Vec<_> to facilitate custom Serialize/Deserialize trait
/// implementations.
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct BankHashAccounts {
pub accounts: Vec<PubkeyHashAccount>,
}
#[derive(Deserialize, Serialize)]
/// Used as an intermediate for serializing and deserializing account fields
/// into a human readable format.
#[derive(Deserialize, Serialize)]
struct SerdeAccount {
pubkey: String,
hash: String,
@ -193,24 +234,22 @@ impl<'de> Deserialize<'de> for BankHashAccounts {
}
}
/// Output the components that comprise bank hash
/// Output the components that comprise the overall bank hash for the supplied `Bank`
pub fn write_bank_hash_details_file(bank: &Bank) -> std::result::Result<(), String> {
let details = BankHashDetails::try_from(bank)?;
let slot_details = BankHashSlotDetails::try_from(bank)?;
let details = BankHashDetails::new(vec![slot_details]);
let slot = details.slot;
let hash = &details.bank_hash;
let file_name = format!("{slot}-{hash}.json");
let parent_dir = bank
.rc
.accounts
.accounts_db
.get_base_working_path()
.join("bank_hash_details");
let path = parent_dir.join(file_name);
let path = parent_dir.join(details.filename()?);
// A file with the same name implies the same hash for this slot. Skip
// rewriting a duplicate file in this scenario
if !path.exists() {
info!("writing details of bank {} to {}", slot, path.display());
info!("writing bank hash details file: {}", path.display());
// std::fs::write may fail (depending on platform) if the full directory
// path does not exist. So, call std::fs_create_dir_all first.
@ -228,44 +267,54 @@ pub fn write_bank_hash_details_file(bank: &Bank) -> std::result::Result<(), Stri
pub mod tests {
use super::*;
fn build_details(num_slots: usize) -> BankHashDetails {
use solana_sdk::hash::{hash, hashv};
let slot_details: Vec<_> = (0..num_slots)
.map(|slot| {
let signature_count = 314;
let account = AccountSharedData::from(Account {
lamports: 123_456_789,
data: vec![0, 9, 1, 8, 2, 7, 3, 6, 4, 5],
owner: Pubkey::new_unique(),
executable: true,
rent_epoch: 123,
});
let account_pubkey = Pubkey::new_unique();
let account_hash = AccountHash(hash("account".as_bytes()));
let accounts = BankHashAccounts {
accounts: vec![PubkeyHashAccount {
pubkey: account_pubkey,
hash: account_hash,
account,
}],
};
let bank_hash = hashv(&["bank".as_bytes(), &slot.to_le_bytes()]);
let parent_bank_hash = hash("parent_bank".as_bytes());
let accounts_delta_hash = hash("accounts_delta".as_bytes());
let last_blockhash = hash("last_blockhash".as_bytes());
BankHashSlotDetails::new(
slot as Slot,
bank_hash,
parent_bank_hash,
accounts_delta_hash,
signature_count,
last_blockhash,
accounts,
)
})
.collect();
BankHashDetails::new(slot_details)
}
#[test]
fn test_serde_bank_hash_details() {
use solana_sdk::hash::hash;
let slot = 123_456_789;
let signature_count = 314;
let account = AccountSharedData::from(Account {
lamports: 123_456_789,
data: vec![0, 9, 1, 8, 2, 7, 3, 6, 4, 5],
owner: Pubkey::new_unique(),
executable: true,
rent_epoch: 123,
});
let account_pubkey = Pubkey::new_unique();
let account_hash = AccountHash(hash("account".as_bytes()));
let accounts = BankHashAccounts {
accounts: vec![PubkeyHashAccount {
pubkey: account_pubkey,
hash: account_hash,
account,
}],
};
let bank_hash = hash("bank".as_bytes());
let parent_bank_hash = hash("parent_bank".as_bytes());
let accounts_delta_hash = hash("accounts_delta".as_bytes());
let last_blockhash = hash("last_blockhash".as_bytes());
let bank_hash_details = BankHashDetails::new(
slot,
bank_hash,
parent_bank_hash,
accounts_delta_hash,
signature_count,
last_blockhash,
accounts,
);
let num_slots = 10;
let bank_hash_details = build_details(num_slots);
let serialized_bytes = serde_json::to_vec(&bank_hash_details).unwrap();
let deserialized_bank_hash_details: BankHashDetails =