Check account hashes in snapshot (#7559)

* Check for incorrect hash value

* Finish up checking for incorrect hash value

* Fix comment a bit

Co-authored-by: sakridge <sakridge@gmail.com>
This commit is contained in:
Ryo Onodera 2019-12-20 09:39:30 +09:00 committed by GitHub
parent 37eaa6e4f9
commit 3c361eb759
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 136 additions and 34 deletions

View File

@ -59,12 +59,12 @@ fn test_accounts_squash(bencher: &mut Bencher) {
}
#[bench]
fn test_accounts_hash_internal_state(bencher: &mut Bencher) {
fn test_accounts_hash_bank_hash(bencher: &mut Bencher) {
let accounts = Accounts::new(vec![PathBuf::from("bench_accounts_hash_internal")]);
let mut pubkeys: Vec<Pubkey> = vec![];
create_test_accounts(&accounts, &mut pubkeys, 60000, 0);
let ancestors = vec![(0, 0)].into_iter().collect();
bencher.iter(|| {
accounts.verify_hash_internal_state(0, &ancestors);
accounts.verify_bank_hash(0, &ancestors);
});
}

View File

@ -345,8 +345,8 @@ impl Accounts {
})
}
pub fn verify_hash_internal_state(&self, slot: Slot, ancestors: &HashMap<Slot, usize>) -> bool {
self.accounts_db.verify_hash_internal_state(slot, ancestors)
pub fn verify_bank_hash(&self, slot: Slot, ancestors: &HashMap<Slot, usize>) -> bool {
self.accounts_db.verify_bank_hash(slot, ancestors).is_ok()
}
pub fn load_by_program(
@ -476,7 +476,7 @@ impl Accounts {
}
}
pub fn hash_internal_state(&self, slot_id: Slot) -> BankHash {
pub fn bank_hash_at(&self, slot_id: Slot) -> BankHash {
let slot_hashes = self.accounts_db.slot_hashes.read().unwrap();
*slot_hashes
.get(&slot_id)
@ -1164,17 +1164,17 @@ mod tests {
#[test]
#[should_panic]
fn test_accounts_empty_hash_internal_state() {
fn test_accounts_empty_bank_hash() {
let accounts = Accounts::new(Vec::new());
accounts.hash_internal_state(0);
accounts.bank_hash_at(0);
}
#[test]
#[should_panic]
fn test_accounts_empty_account_hash_internal_state() {
fn test_accounts_empty_account_bank_hash() {
let accounts = Accounts::new(Vec::new());
accounts.store_slow(0, &Pubkey::default(), &Account::new(1, 0, &sysvar::id()));
accounts.hash_internal_state(0);
accounts.bank_hash_at(0);
}
fn check_accounts(accounts: &Accounts, pubkeys: &Vec<Pubkey>, num: usize) {
@ -1221,10 +1221,7 @@ mod tests {
.accounts_from_stream(&mut reader, &daccounts_paths, copied_accounts.path())
.is_ok());
check_accounts(&daccounts, &pubkeys, 100);
assert_eq!(
accounts.hash_internal_state(0),
daccounts.hash_internal_state(0)
);
assert_eq!(accounts.bank_hash_at(0), daccounts.bank_hash_at(0));
}
#[test]

View File

@ -175,6 +175,13 @@ pub enum AccountStorageStatus {
Candidate = 2,
}
#[derive(Debug)]
pub enum BankHashVerificatonError {
MismatchedAccountHash,
MismatchedBankHash,
MissingBankHash,
}
/// Persistent storage structure holding the accounts
#[derive(Debug, Serialize, Deserialize)]
pub struct AccountStorageEntry {
@ -354,13 +361,11 @@ pub struct AccountsDB {
/// Keeps tracks of index into AppendVec on a per slot basis
pub accounts_index: RwLock<AccountsIndex<AccountInfo>>,
/// Account storage
pub storage: RwLock<AccountStorage>,
/// distribute the accounts across storage lists
pub next_id: AtomicUsize,
/// write version
write_version: AtomicUsize,
/// Set of storage paths to pick from
@ -379,7 +384,6 @@ pub struct AccountsDB {
/// the accounts
min_num_stores: usize,
/// slot to BankHash and a status flag to indicate if the hash has been initialized or not
pub slot_hashes: RwLock<HashMap<Slot, BankHash>>,
}
@ -969,28 +973,49 @@ impl AccountsDB {
datapoint_info!("accounts_db-stores", ("total_count", total_count, i64));
}
pub fn verify_hash_internal_state(&self, slot: Slot, ancestors: &HashMap<Slot, usize>) -> bool {
let mut hash_state = BankHash::default();
let hashes: Vec<_> = self.scan_accounts(
pub fn verify_bank_hash(
&self,
slot: Slot,
ancestors: &HashMap<Slot, usize>,
) -> Result<(), BankHashVerificatonError> {
use BankHashVerificatonError::*;
let (hashes, mismatch_found) = self.scan_accounts(
ancestors,
|collector: &mut Vec<BankHash>, option: Option<(&Pubkey, Account, Slot)>| {
|(collector, mismatch_found): &mut (Vec<BankHash>, bool),
option: Option<(&Pubkey, Account, Slot)>| {
if let Some((pubkey, account, slot)) = option {
if !sysvar::check_id(&account.owner) {
let hash = BankHash::from_hash(&Self::hash_account(slot, &account, pubkey));
let hash = Self::hash_account(slot, &account, pubkey);
if hash != account.hash {
*mismatch_found = true;
}
if *mismatch_found {
return;
}
let hash = BankHash::from_hash(&hash);
debug!("xoring..{} key: {}", hash, pubkey);
collector.push(hash);
}
}
},
);
if mismatch_found {
return Err(MismatchedAccountHash);
}
let mut calculated_hash = BankHash::default();
for hash in hashes {
hash_state.xor(hash);
calculated_hash.xor(hash);
}
let slot_hashes = self.slot_hashes.read().unwrap();
if let Some(state) = slot_hashes.get(&slot) {
hash_state == *state
if let Some(found_hash) = slot_hashes.get(&slot) {
if calculated_hash == *found_hash {
Ok(())
} else {
Err(MismatchedBankHash)
}
} else {
false
Err(BankHashVerificatonError::MissingBankHash)
}
}
@ -1120,9 +1145,12 @@ impl AccountsDB {
/// Store the account update.
pub fn store(&self, slot_id: Slot, accounts: &[(&Pubkey, &Account)]) {
let hashes = self.hash_accounts(slot_id, accounts);
self.store_with_hashes(slot_id, accounts, &hashes);
}
fn store_with_hashes(&self, slot_id: Slot, accounts: &[(&Pubkey, &Account)], hashes: &[Hash]) {
let mut store_accounts = Measure::start("store::store_accounts");
let infos = self.store_accounts(slot_id, accounts, &hashes);
let infos = self.store_accounts(slot_id, accounts, hashes);
store_accounts.stop();
let mut update_index = Measure::start("store::update_index");
@ -1234,9 +1262,11 @@ pub mod tests {
// TODO: all the bank tests are bank specific, issue: 2194
use super::*;
use crate::append_vec::AccountMeta;
use assert_matches::assert_matches;
use bincode::serialize_into;
use rand::{thread_rng, Rng};
use solana_sdk::account::Account;
use solana_sdk::hash::HASH_BYTES;
use std::fs;
use std::str::FromStr;
use tempfile::TempDir;
@ -2195,4 +2225,78 @@ pub mod tests {
"Account-based hashing must be consistent with StoredAccount-based one."
);
}
#[test]
fn test_verify_bank_hash() {
use BankHashVerificatonError::*;
solana_logger::setup();
let db = AccountsDB::new(Vec::new());
let key = Pubkey::default();
let some_data_len = 0;
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)]);
db.add_root(some_slot);
assert_matches!(db.verify_bank_hash(some_slot, &ancestors), Ok(_));
db.slot_hashes.write().unwrap().remove(&some_slot).unwrap();
assert_matches!(
db.verify_bank_hash(some_slot, &ancestors),
Err(MissingBankHash)
);
let some_bank_hash = BankHash::from_hash(&Hash::new(&[0xca; HASH_BYTES]));
db.slot_hashes
.write()
.unwrap()
.insert(some_slot, some_bank_hash);
assert_matches!(
db.verify_bank_hash(some_slot, &ancestors),
Err(MismatchedBankHash)
);
}
#[test]
fn test_verify_bank_hash_no_account() {
solana_logger::setup();
let db = AccountsDB::new(Vec::new());
let some_slot: Slot = 0;
let ancestors = vec![(some_slot, 0)].into_iter().collect();
db.slot_hashes
.write()
.unwrap()
.insert(some_slot, BankHash::default());
db.add_root(some_slot);
assert_matches!(db.verify_bank_hash(some_slot, &ancestors), Ok(_));
}
#[test]
fn test_verify_bank_hash_bad_account_hash() {
use BankHashVerificatonError::*;
solana_logger::setup();
let db = AccountsDB::new(Vec::new());
let key = Pubkey::default();
let some_data_len = 0;
let some_slot: Slot = 0;
let account = Account::new(1, some_data_len, &key);
let ancestors = vec![(some_slot, 0)].into_iter().collect();
let accounts = &[(&key, &account)];
// update AccountsDB's bank hash but discard real account hashes
db.hash_accounts(some_slot, accounts);
// provide bogus account hashes
let some_hash = Hash::new(&[0xca; HASH_BYTES]);
db.store_with_hashes(some_slot, accounts, &vec![some_hash]);
db.add_root(some_slot);
assert_matches!(
db.verify_bank_hash(some_slot, &ancestors),
Err(MismatchedAccountHash)
);
}
}

View File

@ -1595,7 +1595,7 @@ 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.hash_internal_state(self.slot());
let accounts_delta_hash = self.rc.accounts.bank_hash_at(self.slot());
let mut signature_count_buf = [0u8; 8];
LittleEndian::write_u64(&mut signature_count_buf[..], self.signature_count() as u64);
hashv(&[
@ -1611,7 +1611,7 @@ impl Bank {
pub fn verify_hash_internal_state(&self) -> bool {
self.rc
.accounts
.verify_hash_internal_state(self.slot(), &self.ancestors)
.verify_bank_hash(self.slot(), &self.ancestors)
}
/// A snapshot bank should be purged of 0 lamport accounts which are not part of the hash
@ -1620,7 +1620,7 @@ impl Bank {
self.purge_zero_lamport_accounts();
self.rc
.accounts
.verify_hash_internal_state(self.slot(), &self.ancestors)
.verify_bank_hash(self.slot(), &self.ancestors)
}
/// Return the number of hashes per tick
@ -1806,8 +1806,8 @@ impl Bank {
let dsc = dbank.src.status_cache.read().unwrap();
assert_eq!(*sc, *dsc);
assert_eq!(
self.rc.accounts.hash_internal_state(self.slot),
dbank.rc.accounts.hash_internal_state(dbank.slot)
self.rc.accounts.bank_hash_at(self.slot),
dbank.rc.accounts.bank_hash_at(dbank.slot)
);
}

View File

@ -7,9 +7,10 @@ use std::fmt;
use std::mem;
use std::str::FromStr;
pub const HASH_BYTES: usize = 32;
#[derive(Serialize, Deserialize, Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[repr(transparent)]
pub struct Hash([u8; 32]);
pub struct Hash([u8; HASH_BYTES]);
#[derive(Clone, Default)]
pub struct Hasher {
@ -28,7 +29,7 @@ impl Hasher {
pub fn result(self) -> Hash {
// At the time of this writing, the sha2 library is stuck on an old version
// of generic_array (0.9.0). Decouple ourselves with a clone to our version.
Hash(<[u8; 32]>::try_from(self.hasher.result().as_slice()).unwrap())
Hash(<[u8; HASH_BYTES]>::try_from(self.hasher.result().as_slice()).unwrap())
}
}
@ -73,7 +74,7 @@ impl FromStr for Hash {
impl Hash {
pub fn new(hash_slice: &[u8]) -> Self {
Hash(<[u8; 32]>::try_from(hash_slice).unwrap())
Hash(<[u8; HASH_BYTES]>::try_from(hash_slice).unwrap())
}
}