Check for incorrect account hash value on snapshot ingest (v2)#7559
Check for incorrect account hash value on snapshot ingest (v2)#7559ryoqun merged 3 commits intosolana-labs:masterfrom
Conversation
| use std::mem; | ||
| use std::str::FromStr; | ||
|
|
||
| pub const HASH_BYTES: usize = 32; |
There was a problem hiding this comment.
A tiny nicety. This aligns with BANK_HASH_BYTES in bank_hash.rs.
| self.store_with_hashes(slot_id, accounts, &hashes); | ||
| } | ||
|
|
||
| fn store_with_hashes(&self, slot_id: Slot, accounts: &[(&Pubkey, &Account)], hashes: &[Hash]) { |
There was a problem hiding this comment.
Extracted into new fn purely for the purpose of unit-testing.
| /// the accounts | ||
| min_num_stores: usize, | ||
|
|
||
| /// slot to BankHash and a status flag to indicate if the hash has been initialized or not |
There was a problem hiding this comment.
A bit aggressive, but delete this comment as it's outdated and now adds no additional information by the comment.
- This comment is introduced at 19ae556 (Bank hash xor #5573)
- This comment is forgot to be updated at 42da1ce (Fix bank hash not changing when no internal state has changed #7052)
| /// distribute the accounts across storage lists | ||
| pub next_id: AtomicUsize, | ||
|
|
||
| /// write version |
There was a problem hiding this comment.
A bit aggressive here, but remove this comment as it adds no additional information to the commented code itself. use fewer comments and remove otherwise as it's too common for engineers to forget to maintain them up-to-date (including me!) as this is the case.
| /// Keeps tracks of index into AppendVec on a per slot basis | ||
| pub accounts_index: RwLock<AccountsIndex<AccountInfo>>, | ||
|
|
||
| /// Account storage |
| } | ||
|
|
||
| #[derive(Debug)] | ||
| pub enum BankHashVerificatonError { |
There was a problem hiding this comment.
In my opinion, the naming here is very clear after all the hustle of naming changes of this PR. :)
| 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; |
There was a problem hiding this comment.
Originally assigned false, but this didn't work because the Default::default() of bool is false... So I inverted the boolean value.
| *mismatch_found = true; | ||
| } | ||
| if *mismatch_found { | ||
| return; |
There was a problem hiding this comment.
Added by me, bail out early as the PR description explains.
| } | ||
|
|
||
| pub fn verify_hash_internal_state(&self, slot: Slot, ancestors: &HashMap<Slot, usize>) -> bool { | ||
| let mut hash_state = BankHash::default(); |
There was a problem hiding this comment.
Moved to its use site and renamed.
| } | ||
|
|
||
| #[test] | ||
| fn test_verify_bank_hash() { |
There was a problem hiding this comment.
As the coverage report from the CI will reveal, these tests does the 100% coverage. :p
| let ancestors = vec![(some_slot, 0)].into_iter().collect(); | ||
|
|
||
| let accounts = &[(&key, &account)]; | ||
| // update AccountsDB's hash state but discard real account hashes |
There was a problem hiding this comment.
oops, hash state should be written as bank hash to reflect new naming...
| } | ||
| } | ||
|
|
||
| pub fn hash_internal_state(&self, slot_id: Slot) -> BankHash { |
There was a problem hiding this comment.
Changed from verb_object to noun_preposition style here, as it's rather a getter fn, not doing actual calculating here.
| 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() |
There was a problem hiding this comment.
At the very layer boundary of "Bank<=>AccountsDB", we discard detailed error and encapsulate the result into a bool.
| self.rc | ||
| .accounts | ||
| .verify_hash_internal_state(self.slot(), &self.ancestors) | ||
| .verify_bank_hash(self.slot(), &self.ancestors) |
There was a problem hiding this comment.
At this place, we semantically translate verify-ing Bank's internal_state into AccountDB's bank_hash. So outwards rename propagation from AccountsDB ends here. :)
Codecov Report
@@ Coverage Diff @@
## master #7559 +/- ##
=========================================
- Coverage 77.7% 65.5% -12.3%
=========================================
Files 244 245 +1
Lines 52151 61966 +9815
=========================================
+ Hits 40556 40601 +45
- Misses 11595 21365 +9770 |
Pull request has been modified.
(This PR is based on @sakridge 's #7427)
Problem
No checking on snapshot ingestion that data which is hashed matches the hash value in the append vec.
Summary of Changes
enum, not bool to make it easy to unit test for individual bad cases. I think this is a desired prudent approach here because these are corner cases which is rarely touched on the production so it's quite possible for bit rot and silent false-{positive, negative}s.enum, I did some naming clarification for logically clear naming for each member of theenumbecause otherwise the new and radical naming would be stranger to the other parts of code or the traditional naming based on the status-quo would be too confusing.Naming changes
1.
internal_state=>bank_hashat the AccountDB layer:The word
internal_stateis originated frombankmodule'sBank::{hash,verify_hash}_internal_state, and used only here. The names for banks are fine as it is, because it is calculating (=hash-ing, verb) thestateofBank, which isinternalfrom the other layers, andverify-ing the calculation (=hash, noun) itself.But the
internal_stateisn't clear-cut fit forAccountsDB. The same word is used for different things. AccountsDB calls its returning BankHash its "internal_state" while bank calls its returning Hash including AccountDB's BankHash its "internal_state". It's confusing and AccountsDB should just use thebank_hashname. Because of following section's reasoning, I chose it over the other candidate (=slot_hash).In my opinion, using exactly same names for different layers is a bit confusing unless they are perfectly delegating work and forwarding args and concepts or they are well-defined concepts across different layers and components. These are not true for the
internal_state.Finally by introducing
bank_hash, it cleanly align with theaccount.hash(=account_hash) in the newenum.2.
slot_hashes=>bank_hashesat the AccountsDB APISadly,
SlotHashes/slot_hashesare already taken and established as a sysvar. While both components use the same word "slot hashes", yet its definitions are different. Bank/AccountsDB's slot_hash is one of inputs for the sysvar's "slot hash". It's confusing at best. So, just use the straight naming derived from type name ofBankHashhere.I avoided renaming the
AccontsDB::slot_hashesfield toAccountsDB::bank_hashesas it will introduce too noise for this PR, which is already a bit sizable. I'll do a quick dumb-replace PR after this PR.Part of #7167