Skip to content

Commit

Permalink
Verifies accounts lt hash at startup
Browse files Browse the repository at this point in the history
  • Loading branch information
brooksprumo committed Oct 11, 2024
1 parent b81d197 commit 296319f
Show file tree
Hide file tree
Showing 8 changed files with 496 additions and 59 deletions.
114 changes: 103 additions & 11 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 @@ -685,6 +703,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 @@ -751,6 +770,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 @@ -6442,7 +6466,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 @@ -6503,6 +6527,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 @@ -8326,6 +8383,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 @@ -8516,6 +8574,7 @@ impl AccountsDb {
struct DuplicatePubkeysVisitedInfo {
accounts_data_len_from_duplicates: u64,
uncleaned_roots: IntSet<Slot>,
duplicates_lt_hash: DuplicatesLtHash,
}
impl DuplicatePubkeysVisitedInfo {
fn reduce(mut a: Self, mut b: Self) -> Self {
Expand All @@ -8531,6 +8590,9 @@ impl AccountsDb {
self.accounts_data_len_from_duplicates +=
other.accounts_data_len_from_duplicates;
self.uncleaned_roots.extend(other.uncleaned_roots);
self.duplicates_lt_hash
.0
.mix_in(&other.duplicates_lt_hash.0);
}
}

Expand All @@ -8540,6 +8602,7 @@ impl AccountsDb {
let DuplicatePubkeysVisitedInfo {
accounts_data_len_from_duplicates,
uncleaned_roots,
duplicates_lt_hash,
} = unique_pubkeys_by_bin
.par_iter()
.fold(
Expand All @@ -8548,15 +8611,19 @@ impl AccountsDb {
let intermediate = pubkeys_by_bin
.par_chunks(4096)
.fold(DuplicatePubkeysVisitedInfo::default, |accum, pubkeys| {
let (accounts_data_len_from_duplicates, uncleaned_roots) = self
.visit_duplicate_pubkeys_during_startup(
pubkeys,
&rent_collector,
&timings,
);
let (
accounts_data_len_from_duplicates,
uncleaned_roots,
duplicates_lt_hash,
) = self.visit_duplicate_pubkeys_during_startup(
pubkeys,
&rent_collector,
&timings,
);
let intermediate = DuplicatePubkeysVisitedInfo {
accounts_data_len_from_duplicates,
uncleaned_roots,
duplicates_lt_hash,
};
DuplicatePubkeysVisitedInfo::reduce(accum, intermediate)
})
Expand All @@ -8578,6 +8645,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 @@ -8604,6 +8673,7 @@ impl AccountsDb {
rent_paying_accounts_by_partition: rent_paying_accounts_by_partition
.into_inner()
.unwrap(),
duplicates_lt_hash: Box::new(outer_duplicates_lt_hash.unwrap()),
}
}

Expand Down Expand Up @@ -8633,19 +8703,26 @@ 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, slots that contained duplicate pubkeys)
///
/// returns tuple of:
/// - data len sum of all older duplicates
/// - slots that contained duplicate pubkeys
/// - lt hash of duplicates
fn visit_duplicate_pubkeys_during_startup(
&self,
pubkeys: &[Pubkey],
rent_collector: &RentCollector,
timings: &GenerateIndexTimings,
) -> (u64, IntSet<Slot>) {
) -> (u64, IntSet<Slot>, DuplicatesLtHash) {
let mut accounts_data_len_from_duplicates = 0;
let mut uncleaned_slots = IntSet::default();
let mut duplicates_lt_hash = 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 @@ -8683,6 +8760,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 @@ -8699,7 +8784,14 @@ impl AccountsDb {
timings
.amount_to_top_off_rent
.fetch_sub(removed_top_off, Ordering::Relaxed);
(accounts_data_len_from_duplicates as u64, uncleaned_slots)
timings
.par_duplicates_lt_hash_us
.fetch_add(lt_hash_time.as_micros() as u64, Ordering::Relaxed);
(
accounts_data_len_from_duplicates as u64,
uncleaned_slots,
duplicates_lt_hash,
)
}

fn set_storage_count_and_alive_bytes(
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

0 comments on commit 296319f

Please sign in to comment.