Skip to content

Commit

Permalink
Add hash stats information to check hashes between validators
Browse files Browse the repository at this point in the history
  • Loading branch information
sakridge committed Jan 14, 2020
1 parent f91ffbb commit c6cb69a
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 17 deletions.
11 changes: 9 additions & 2 deletions runtime/src/accounts.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::accounts_db::{AccountInfo, AccountStorage, AccountsDB, AppendVecId, ErrorCounters};
use crate::accounts_db::{
AccountInfo, AccountStorage, AccountsDB, AppendVecId, BankHashInfo, ErrorCounters,
};
use crate::accounts_index::AccountsIndex;
use crate::append_vec::StoredAccount;
use crate::bank::{HashAgeKind, TransactionProcessResult};
Expand Down Expand Up @@ -487,10 +489,15 @@ impl Accounts {
}

pub fn bank_hash_at(&self, slot_id: Slot) -> BankHash {
self.bank_hash_info_at(slot_id).hash
}

pub fn bank_hash_info_at(&self, slot_id: Slot) -> BankHashInfo {
let bank_hashes = self.accounts_db.bank_hashes.read().unwrap();
*bank_hashes
bank_hashes
.get(&slot_id)
.expect("No bank hash was found for this bank, that should not be possible")
.clone()
}

/// This function will prevent multiple threads from modifying the same account state at the
Expand Down
106 changes: 94 additions & 12 deletions runtime/src/accounts_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,46 @@ impl<'a> Serialize for AccountsDBSerialize<'a> {
}
}

#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
pub struct BankHashStats {
pub num_removed_accounts: u64,
pub num_added_accounts: u64,
pub num_lamports_stored: u64,
pub total_data_len: u64,
pub num_executable_accounts: u64,
}

impl BankHashStats {
pub fn update(&mut self, account: &Account) {
if Hash::default() == account.hash {
self.num_added_accounts += 1;
} else {
self.num_removed_accounts += 1;
}
self.total_data_len = self.total_data_len.wrapping_add(account.data.len() as u64);
if account.executable {
self.num_executable_accounts += 1;
}
self.num_lamports_stored = self.num_lamports_stored.wrapping_add(account.lamports);
}

pub fn merge(&mut self, other: &BankHashStats) {
self.num_removed_accounts += other.num_removed_accounts;
self.num_added_accounts += other.num_added_accounts;
self.total_data_len = self.total_data_len.wrapping_add(other.total_data_len);
self.num_lamports_stored = self
.num_lamports_stored
.wrapping_add(other.num_lamports_stored);
self.num_executable_accounts += other.num_executable_accounts;
}
}

#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
pub struct BankHashInfo {
pub hash: BankHash,
pub stats: BankHashStats,
}

// This structure handles the load/store of the accounts
#[derive(Debug)]
pub struct AccountsDB {
Expand Down Expand Up @@ -385,7 +425,7 @@ pub struct AccountsDB {
/// the accounts
min_num_stores: usize,

pub bank_hashes: RwLock<HashMap<Slot, BankHash>>,
pub bank_hashes: RwLock<HashMap<Slot, BankHashInfo>>,
}

impl Default for AccountsDB {
Expand Down Expand Up @@ -522,7 +562,7 @@ impl AccountsDB {
let version: u64 = deserialize_from(&mut stream)
.map_err(|_| AccountsDB::get_io_error("write version deserialize error"))?;

let (slot, bank_hash): (Slot, BankHash) = deserialize_from(&mut stream)
let (slot, bank_hash): (Slot, BankHashInfo) = deserialize_from(&mut stream)
.map_err(|_| AccountsDB::get_io_error("bank hashes deserialize error"))?;
self.bank_hashes.write().unwrap().insert(slot, bank_hash);

Expand Down Expand Up @@ -716,10 +756,15 @@ impl AccountsDB {

pub fn set_hash(&self, slot: Slot, parent_slot: Slot) {
let mut bank_hashes = self.bank_hashes.write().unwrap();
let hash = *bank_hashes
let hash_info = bank_hashes
.get(&parent_slot)
.expect("accounts_db::set_hash::no parent slot");
bank_hashes.insert(slot, hash);
let hash = hash_info.hash;
let new_hash_info = BankHashInfo {
hash,
stats: BankHashStats::default(),
};
bank_hashes.insert(slot, new_hash_info);
}

pub fn load(
Expand Down Expand Up @@ -1006,8 +1051,8 @@ impl AccountsDB {
calculated_hash.xor(hash);
}
let bank_hashes = self.bank_hashes.read().unwrap();
if let Some(found_hash) = bank_hashes.get(&slot) {
if calculated_hash == *found_hash {
if let Some(found_hash_info) = bank_hashes.get(&slot) {
if calculated_hash == found_hash_info.hash {
Ok(())
} else {
Err(MismatchedBankHash)
Expand All @@ -1017,10 +1062,14 @@ impl AccountsDB {
}
}

pub fn xor_in_hash_state(&self, slot_id: Slot, hash: BankHash) {
pub fn xor_in_hash_state(&self, slot_id: Slot, hash: BankHash, stats: &BankHashStats) {
let mut bank_hashes = self.bank_hashes.write().unwrap();
let bank_hash = bank_hashes.entry(slot_id).or_insert_with(BankHash::default);
bank_hash.xor(hash);
let bank_hash = bank_hashes
.entry(slot_id)
.or_insert_with(BankHashInfo::default);
bank_hash.hash.xor(hash);

bank_hash.stats.merge(stats);
}

fn update_index(
Expand Down Expand Up @@ -1109,11 +1158,13 @@ impl AccountsDB {
fn hash_accounts(&self, slot_id: Slot, accounts: &[(&Pubkey, &Account)]) -> Vec<Hash> {
let mut hash_state = BankHash::default();
let mut had_account = false;
let mut stats = BankHashStats::default();
let hashes: Vec<_> = accounts
.iter()
.map(|(pubkey, account)| {
if !sysvar::check_id(&account.owner) {
let hash = BankHash::from_hash(&account.hash);
stats.update(account);
let new_hash = Self::hash_account(slot_id, account, pubkey);
let new_bank_hash = BankHash::from_hash(&new_hash);
debug!(
Expand All @@ -1135,7 +1186,7 @@ impl AccountsDB {
.collect();

if had_account {
self.xor_in_hash_state(slot_id, hash_state);
self.xor_in_hash_state(slot_id, hash_state, &stats);
}
hashes
}
Expand Down Expand Up @@ -2224,6 +2275,33 @@ pub mod tests {
);
}

#[test]
fn test_bank_hash_stats() {
solana_logger::setup();
let db = AccountsDB::new(Vec::new());

let key = Pubkey::default();
let some_data_len = 5;
let some_slot: Slot = 0;
let account = Account::new(1, some_data_len, &key);
let ancestors = vec![(some_slot, 0)].into_iter().collect();

db.store(some_slot, &[(&key, &account)]);
let mut account = db.load_slow(&ancestors, &key).unwrap().0;
account.lamports += 1;
account.executable = true;
db.store(some_slot, &[(&key, &account)]);
db.add_root(some_slot);

let bank_hashes = db.bank_hashes.read().unwrap();
let bank_hash = bank_hashes.get(&some_slot).unwrap();
assert_eq!(bank_hash.stats.num_removed_accounts, 1);
assert_eq!(bank_hash.stats.num_added_accounts, 1);
assert_eq!(bank_hash.stats.num_lamports_stored, 3);
assert_eq!(bank_hash.stats.total_data_len, 2 * some_data_len as u64);
assert_eq!(bank_hash.stats.num_executable_accounts, 1);
}

#[test]
fn test_verify_bank_hash() {
use BankHashVerificatonError::*;
Expand All @@ -2247,10 +2325,14 @@ pub mod tests {
);

let some_bank_hash = BankHash::from_hash(&Hash::new(&[0xca; HASH_BYTES]));
let bank_hash_info = BankHashInfo {
hash: some_bank_hash,
stats: BankHashStats::default(),
};
db.bank_hashes
.write()
.unwrap()
.insert(some_slot, some_bank_hash);
.insert(some_slot, bank_hash_info);
assert_matches!(
db.verify_bank_hash(some_slot, &ancestors),
Err(MismatchedBankHash)
Expand All @@ -2268,7 +2350,7 @@ pub mod tests {
db.bank_hashes
.write()
.unwrap()
.insert(some_slot, BankHash::default());
.insert(some_slot, BankHashInfo::default());
db.add_root(some_slot);
assert_matches!(db.verify_bank_hash(some_slot, &ancestors), Ok(_));
}
Expand Down
11 changes: 8 additions & 3 deletions runtime/src/bank.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1644,23 +1644,28 @@ impl Bank {
/// of the delta of the ledger since the last vote and up to now
fn hash_internal_state(&self) -> Hash {
// If there are no accounts, return the hash of the previous state and the latest blockhash
let accounts_delta_hash = self.rc.accounts.bank_hash_at(self.slot());
let accounts_delta_hash = self.rc.accounts.bank_hash_info_at(self.slot());
let mut signature_count_buf = [0u8; 8];
LittleEndian::write_u64(&mut signature_count_buf[..], self.signature_count() as u64);
let hash = hashv(&[
self.parent_hash.as_ref(),
accounts_delta_hash.as_ref(),
accounts_delta_hash.hash.as_ref(),
&signature_count_buf,
self.last_blockhash().as_ref(),
]);
info!(
"bank frozen: {} hash: {} accounts_delta: {} signature_count: {} last_blockhash: {}",
self.slot(),
hash,
accounts_delta_hash,
accounts_delta_hash.hash,
self.signature_count(),
self.last_blockhash()
);
info!(
"accounts hash slot: {} stats: {:?}",
self.slot(),
accounts_delta_hash.stats,
);
hash
}

Expand Down

0 comments on commit c6cb69a

Please sign in to comment.