Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Verifies accounts lt hash at startup #3145

Merged
merged 2 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 92 additions & 4 deletions accounts-db/src/accounts_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ use {
seqlock::SeqLock,
smallvec::SmallVec,
solana_lattice_hash::lt_hash::LtHash,
solana_measure::{measure::Measure, measure_us},
solana_measure::{meas_dur, measure::Measure, measure_us},
solana_nohash_hasher::{IntMap, IntSet},
solana_rayon_threadlimit::get_thread_count,
solana_sdk::{
Expand Down Expand Up @@ -654,6 +654,9 @@ pub enum ScanStorageResult<R, B> {
pub struct IndexGenerationInfo {
pub accounts_data_len: u64,
pub rent_paying_accounts_by_partition: RentPayingAccountsByPartition,
/// The lt hash of the old/duplicate accounts identified during index generation.
/// Will be used when verifying the accounts lt hash, after rebuilding a Bank.
pub duplicates_lt_hash: Box<DuplicatesLtHash>,
}

#[derive(Debug, Default)]
Expand All @@ -666,6 +669,21 @@ struct SlotIndexGenerationInfo {
rent_paying_accounts_by_partition: Vec<Pubkey>,
}

/// The lt hash of old/duplicate accounts
///
/// Accumulation of all the duplicate accounts found during index generation.
/// These accounts need to have their lt hashes mixed *out*.
/// This is the final value, that when applied to all the storages at startup,
/// will produce the correct accounts lt hash.
#[derive(Debug, Clone)]
pub struct DuplicatesLtHash(pub LtHash);

impl Default for DuplicatesLtHash {
fn default() -> Self {
Self(LtHash::identity())
}
}

#[derive(Default, Debug)]
struct GenerateIndexTimings {
pub total_time_us: u64,
Expand All @@ -687,6 +705,7 @@ struct GenerateIndexTimings {
pub populate_duplicate_keys_us: u64,
pub total_slots: u64,
pub slots_to_clean: u64,
pub par_duplicates_lt_hash_us: AtomicU64,
}

#[derive(Default, Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -763,6 +782,11 @@ impl GenerateIndexTimings {
startup_stats.copy_data_us.swap(0, Ordering::Relaxed),
i64
),
(
"par_duplicates_lt_hash_us",
self.par_duplicates_lt_hash_us.load(Ordering::Relaxed),
i64
),
);
}
}
Expand Down Expand Up @@ -6467,7 +6491,7 @@ impl AccountsDb {
///
/// Only intended to be called at startup (or by tests).
/// Only intended to be used while testing the experimental accumulator hash.
pub fn calculate_accounts_lt_hash_at_startup(
pub fn calculate_accounts_lt_hash_at_startup_from_index(
&self,
ancestors: &Ancestors,
startup_slot: Slot,
Expand Down Expand Up @@ -6528,6 +6552,39 @@ impl AccountsDb {
AccountsLtHash(lt_hash)
}

/// Calculates the accounts lt hash
///
/// Intended to be used to verify the accounts lt hash at startup.
///
/// The `duplicates_lt_hash` is the old/duplicate accounts to mix *out* of the storages.
/// This value comes from index generation.
pub fn calculate_accounts_lt_hash_at_startup_from_storages(
&self,
storages: &[Arc<AccountStorageEntry>],
duplicates_lt_hash: &DuplicatesLtHash,
) -> AccountsLtHash {
debug_assert!(self.is_experimental_accumulator_hash_enabled());

let mut lt_hash = storages
.par_iter()
.fold(LtHash::identity, |mut accum, storage| {
storage.accounts.scan_accounts(|stored_account_meta| {
let account_lt_hash =
Self::lt_hash_account(&stored_account_meta, stored_account_meta.pubkey());
accum.mix_in(&account_lt_hash.0);
});
accum
})
.reduce(LtHash::identity, |mut accum, elem| {
accum.mix_in(&elem);
accum
});

lt_hash.mix_out(&duplicates_lt_hash.0);

AccountsLtHash(lt_hash)
}

/// This is only valid to call from tests.
/// run the accounts hash calculation and store the results
pub fn update_accounts_hash_for_tests(
Expand Down Expand Up @@ -8351,6 +8408,7 @@ impl AccountsDb {

let rent_paying_accounts_by_partition =
Mutex::new(RentPayingAccountsByPartition::new(schedule));
let mut outer_duplicates_lt_hash = None;

// pass == 0 always runs and generates the index
// pass == 1 only runs if verify == true.
Expand Down Expand Up @@ -8554,6 +8612,7 @@ impl AccountsDb {
accounts_data_len_from_duplicates: u64,
num_duplicate_accounts: u64,
uncleaned_roots: IntSet<Slot>,
duplicates_lt_hash: Box<DuplicatesLtHash>,
HaoranYi marked this conversation as resolved.
Show resolved Hide resolved
}
impl DuplicatePubkeysVisitedInfo {
fn reduce(mut a: Self, mut b: Self) -> Self {
Expand All @@ -8570,6 +8629,9 @@ impl AccountsDb {
other.accounts_data_len_from_duplicates;
self.num_duplicate_accounts += other.num_duplicate_accounts;
self.uncleaned_roots.extend(other.uncleaned_roots);
self.duplicates_lt_hash
.0
.mix_in(&other.duplicates_lt_hash.0);
}
}

Expand All @@ -8580,6 +8642,7 @@ impl AccountsDb {
accounts_data_len_from_duplicates,
num_duplicate_accounts,
uncleaned_roots,
duplicates_lt_hash,
} = unique_pubkeys_by_bin
.par_iter()
.fold(
Expand All @@ -8592,6 +8655,7 @@ impl AccountsDb {
accounts_data_len_from_duplicates,
accounts_duplicates_num,
uncleaned_roots,
duplicates_lt_hash,
) = self.visit_duplicate_pubkeys_during_startup(
pubkeys,
&rent_collector,
Expand All @@ -8601,6 +8665,7 @@ impl AccountsDb {
accounts_data_len_from_duplicates,
num_duplicate_accounts: accounts_duplicates_num,
uncleaned_roots,
duplicates_lt_hash,
};
DuplicatePubkeysVisitedInfo::reduce(accum, intermediate)
})
Expand All @@ -8623,6 +8688,8 @@ impl AccountsDb {
self.accounts_index
.add_uncleaned_roots(uncleaned_roots.into_iter());
accounts_data_len.fetch_sub(accounts_data_len_from_duplicates, Ordering::Relaxed);
let old_val = outer_duplicates_lt_hash.replace(duplicates_lt_hash);
assert!(old_val.is_none());
info!(
"accounts data len: {}",
accounts_data_len.load(Ordering::Relaxed)
Expand All @@ -8649,6 +8716,7 @@ impl AccountsDb {
rent_paying_accounts_by_partition: rent_paying_accounts_by_partition
.into_inner()
.unwrap(),
duplicates_lt_hash: outer_duplicates_lt_hash.unwrap(),
}
}

Expand Down Expand Up @@ -8678,20 +8746,28 @@ impl AccountsDb {
/// 1. get the _duplicate_ accounts data len from the given pubkeys
/// 2. get the slots that contained duplicate pubkeys
/// 3. update rent stats
/// 4. build up the duplicates lt hash
///
/// Note this should only be used when ALL entries in the accounts index are roots.
/// returns (data len sum of all older duplicates, number of duplicate accounts, slots that contained duplicate pubkeys)
///
/// returns tuple of:
/// - data len sum of all older duplicates
/// - number of duplicate accounts
/// - slots that contained duplicate pubkeys
/// - lt hash of duplicates
fn visit_duplicate_pubkeys_during_startup(
&self,
pubkeys: &[Pubkey],
rent_collector: &RentCollector,
timings: &GenerateIndexTimings,
) -> (u64, u64, IntSet<Slot>) {
) -> (u64, u64, IntSet<Slot>, Box<DuplicatesLtHash>) {
let mut accounts_data_len_from_duplicates = 0;
let mut num_duplicate_accounts = 0_u64;
let mut uncleaned_slots = IntSet::default();
let mut duplicates_lt_hash = Box::new(DuplicatesLtHash::default());
let mut removed_rent_paying = 0;
let mut removed_top_off = 0;
let mut lt_hash_time = Duration::default();
self.accounts_index.scan(
pubkeys.iter(),
|pubkey, slots_refs, _entry| {
Expand Down Expand Up @@ -8730,6 +8806,14 @@ impl AccountsDb {
removed_rent_paying += 1;
removed_top_off += lamports_to_top_off;
}
if self.is_experimental_accumulator_hash_enabled() {
let (_, duration) = meas_dur!({
let account_lt_hash =
Self::lt_hash_account(&loaded_account, pubkey);
duplicates_lt_hash.0.mix_in(&account_lt_hash.0);
});
lt_hash_time += duration;
}
});
});
}
Expand All @@ -8746,10 +8830,14 @@ impl AccountsDb {
timings
.amount_to_top_off_rent
.fetch_sub(removed_top_off, Ordering::Relaxed);
timings
.par_duplicates_lt_hash_us
.fetch_add(lt_hash_time.as_micros() as u64, Ordering::Relaxed);
(
accounts_data_len_from_duplicates as u64,
num_duplicate_accounts,
uncleaned_slots,
duplicates_lt_hash,
)
}

Expand Down
5 changes: 1 addition & 4 deletions accounts-db/src/accounts_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,10 +210,7 @@ impl AccountsFile {
}

/// Iterate over all accounts and call `callback` with each account.
pub(crate) fn scan_accounts(
&self,
callback: impl for<'local> FnMut(StoredAccountMeta<'local>),
) {
pub fn scan_accounts(&self, callback: impl for<'local> FnMut(StoredAccountMeta<'local>)) {
match self {
Self::AppendVec(av) => av.scan_accounts(callback),
Self::TieredStorage(ts) => {
Expand Down
Loading
Loading