diff --git a/entry/src/poh.rs b/entry/src/poh.rs index 9716425b0c8293..4bde44217f7a43 100644 --- a/entry/src/poh.rs +++ b/entry/src/poh.rs @@ -8,7 +8,7 @@ use { pub struct Poh { pub hash: Hash, num_hashes: u64, - hashes_per_tick: u64, + pub hashes_per_tick: u64, remaining_hashes: u64, tick_number: u64, slot_start_time: Instant, diff --git a/poh/src/poh_recorder.rs b/poh/src/poh_recorder.rs index f588d57c4f8ccc..fc6e3986e3a856 100644 --- a/poh/src/poh_recorder.rs +++ b/poh/src/poh_recorder.rs @@ -225,7 +225,6 @@ pub struct PohRecorder { id: Pubkey, blockstore: Arc, leader_schedule_cache: Arc, - poh_config: PohConfig, ticks_per_slot: u64, target_ns_per_tick: u64, record_lock_contention_us: u64, @@ -460,13 +459,11 @@ impl PohRecorder { )) } - // synchronize PoH with a bank - pub fn reset(&mut self, reset_bank: Arc, next_leader_slot: Option<(Slot, Slot)>) { - self.clear_bank(); + fn reset_poh(&mut self, reset_bank: Arc, reset_start_bank: bool) { let blockhash = reset_bank.last_blockhash(); let poh_hash = { let mut poh = self.poh.lock().unwrap(); - poh.reset(blockhash, self.poh_config.hashes_per_tick); + poh.reset(blockhash, *reset_bank.hashes_per_tick()); poh.hash }; info!( @@ -479,9 +476,17 @@ impl PohRecorder { ); self.tick_cache = vec![]; - self.start_bank = reset_bank; + if reset_start_bank { + self.start_bank = reset_bank; + } self.tick_height = (self.start_slot() + 1) * self.ticks_per_slot; self.start_tick_height = self.tick_height + 1; + } + + // synchronize PoH with a bank + pub fn reset(&mut self, reset_bank: Arc, next_leader_slot: Option<(Slot, Slot)>) { + self.clear_bank(); + self.reset_poh(reset_bank, true); if let Some(ref sender) = self.poh_timing_point_sender { // start_slot() is the parent slot. current slot is start_slot() + 1. @@ -513,6 +518,17 @@ impl PohRecorder { }; trace!("new working bank"); assert_eq!(working_bank.bank.ticks_per_slot(), self.ticks_per_slot()); + if let Some(hashes_per_tick) = *working_bank.bank.hashes_per_tick() { + if self.poh.lock().unwrap().hashes_per_tick != hashes_per_tick { + // Reset poh when changing hashes per tick so that we ensure all ticks + // for this slot are created with the proper hashes per tick + info!( + "resetting poh due to hashes per tick change detected at {}", + working_bank.bank.slot() + ); + self.reset_poh(working_bank.clone().bank, false); + } + } self.working_bank = Some(working_bank); // send poh slot start timing point @@ -865,7 +881,6 @@ impl PohRecorder { leader_schedule_cache: leader_schedule_cache.clone(), ticks_per_slot, target_ns_per_tick, - poh_config: poh_config.clone(), record_lock_contention_us: 0, flush_cache_tick_us: 0, flush_cache_no_tick_us: 0, diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 31397292b39d71..8734e0c00b570e 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -108,9 +108,10 @@ use { account_utils::StateMut, bpf_loader_upgradeable::{self, UpgradeableLoaderState}, clock::{ - BankId, Epoch, Slot, SlotCount, SlotIndex, UnixTimestamp, DEFAULT_TICKS_PER_SECOND, - INITIAL_RENT_EPOCH, MAX_PROCESSING_AGE, MAX_TRANSACTION_FORWARDING_DELAY, - MAX_TRANSACTION_FORWARDING_DELAY_GPU, SECONDS_PER_DAY, + BankId, Epoch, Slot, SlotCount, SlotIndex, UnixTimestamp, DEFAULT_HASHES_PER_TICK, + DEFAULT_TICKS_PER_SECOND, INITIAL_RENT_EPOCH, MAX_PROCESSING_AGE, + MAX_TRANSACTION_FORWARDING_DELAY, MAX_TRANSACTION_FORWARDING_DELAY_GPU, + SECONDS_PER_DAY, }, ed25519_program, epoch_info::EpochInfo, @@ -1661,7 +1662,8 @@ impl Bank { let (_, apply_feature_activations_time) = measure!( new.apply_feature_activations( ApplyFeatureActivationsCaller::NewFromParent, - false + false, + vec![false; FeatureActivation::NumVariants as usize], ), "apply_feature_activation", ); @@ -1888,7 +1890,11 @@ impl Bank { let parent_timestamp = parent.clock().unix_timestamp; let mut new = Bank::new_from_parent(parent, collector_id, slot); - new.apply_feature_activations(ApplyFeatureActivationsCaller::WarpFromParent, false); + new.apply_feature_activations( + ApplyFeatureActivationsCaller::WarpFromParent, + false, + vec![false; FeatureActivation::NumVariants as usize], + ); new.update_epoch_stakes(new.epoch_schedule().get_epoch(slot)); new.tick_height.store(new.max_tick_height(), Relaxed); @@ -2015,10 +2021,6 @@ impl Bank { "Bank snapshot genesis creation time does not match genesis.bin creation time.\ The snapshot and genesis.bin might pertain to different clusters" ); - assert_eq!( - bank.hashes_per_tick, - genesis_config.poh_config.hashes_per_tick - ); assert_eq!(bank.ticks_per_slot, genesis_config.ticks_per_slot); assert_eq!( bank.ns_per_slot, @@ -6426,6 +6428,7 @@ impl Bank { self.apply_feature_activations( ApplyFeatureActivationsCaller::FinishInit, debug_do_not_add_builtins, + vec![false; FeatureActivation::NumVariants as usize], ); if self @@ -7452,10 +7455,12 @@ impl Bank { // This is called from snapshot restore AND for each epoch boundary // The entire code path herein must be idempotent - fn apply_feature_activations( + pub fn apply_feature_activations( &mut self, caller: ApplyFeatureActivationsCaller, debug_do_not_add_builtins: bool, + // This vector is for forcing a feature to get activated for testing purposes + features_activated_test: Vec, ) { use ApplyFeatureActivationsCaller::*; let allow_new_activations = match caller { @@ -7507,6 +7512,21 @@ impl Bank { const ACCOUNTS_DATA_LEN: u64 = 50_000_000_000; self.accounts_data_size_initial = ACCOUNTS_DATA_LEN; } + + if new_feature_activations.contains(&feature_set::configurable_hashes_per_tick::id()) + || features_activated_test[FeatureActivation::ConfigurablePohTicksPerSecond as usize] + { + self.apply_updated_hashes_per_tick(DEFAULT_HASHES_PER_TICK); + } + } + + fn apply_updated_hashes_per_tick(&mut self, hashes_per_tick: u64) { + info!( + "Activating configurable_hashes_per_tick {} at slot {}", + hashes_per_tick, + self.slot(), + ); + self.hashes_per_tick = Some(hashes_per_tick); } fn adjust_sysvar_balance_for_rent(&self, account: &mut AccountSharedData) { @@ -7797,12 +7817,19 @@ fn calculate_data_size_delta(old_data_size: usize, new_data_size: usize) -> i64 /// Since `apply_feature_activations()` has different behavior depending on its caller, enumerate /// those callers explicitly. #[derive(Debug, Copy, Clone, Eq, PartialEq)] -enum ApplyFeatureActivationsCaller { +pub enum ApplyFeatureActivationsCaller { FinishInit, NewFromParent, WarpFromParent, } +/// Allows for force enabling feature activations for testing. +pub enum FeatureActivation { + ConfigurablePohTicksPerSecond, + // Add new feature activation enums here + NumVariants, +} + /// Return the computed values from `collect_rent_from_accounts()` /// /// Since `collect_rent_from_accounts()` is running in parallel, instead of updating the @@ -20157,4 +20184,47 @@ pub(crate) mod tests { )), ); } + + #[test] + fn test_feature_activation_idempotent() { + let GenesisConfigInfo { + mut genesis_config, .. + } = genesis_utils::create_genesis_config_with_leader( + 1_000_000 * LAMPORTS_PER_SOL, + &Pubkey::new_unique(), + 100 * LAMPORTS_PER_SOL, + ); + genesis_config.rent = Rent::default(); + const HASHES_PER_TICK_START: u64 = 3; + genesis_config.poh_config.hashes_per_tick = Some(HASHES_PER_TICK_START); + + let mut bank = Bank::new_for_tests(&genesis_config); + assert_eq!(bank.hashes_per_tick, Some(HASHES_PER_TICK_START)); + + // Don't activate feature + let mut test_activation = vec![false; FeatureActivation::NumVariants as usize]; + bank.apply_feature_activations( + ApplyFeatureActivationsCaller::NewFromParent, + false, + test_activation.clone(), + ); + assert_eq!(bank.hashes_per_tick, Some(HASHES_PER_TICK_START)); + + // Activate feature + test_activation[FeatureActivation::ConfigurablePohTicksPerSecond as usize] = true; + bank.apply_feature_activations( + ApplyFeatureActivationsCaller::NewFromParent, + false, + test_activation.clone(), + ); + assert_eq!(bank.hashes_per_tick, Some(DEFAULT_HASHES_PER_TICK)); + + // Activate feature "again" + bank.apply_feature_activations( + ApplyFeatureActivationsCaller::NewFromParent, + false, + test_activation, + ); + assert_eq!(bank.hashes_per_tick, Some(DEFAULT_HASHES_PER_TICK)); + } } diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index 019b178e8791a8..1d42c8f6ffeb43 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -554,6 +554,10 @@ pub mod enable_program_redeployment_cooldown { solana_sdk::declare_id!("J4HFT8usBxpcF63y46t1upYobJgChmKyZPm5uTBRg25Z"); } +pub mod configurable_hashes_per_tick { + solana_sdk::declare_id!("3uFHb9oKdGfgZGJK9EHaAXN4USvnQtAFC13Fh5gGFS5B"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -687,6 +691,7 @@ lazy_static! { (cap_transaction_accounts_data_size::id(), "cap transaction accounts data size up to its compute unit limits #27839"), (enable_alt_bn128_syscall::id(), "add alt_bn128 syscalls #27961"), (enable_program_redeployment_cooldown::id(), "enable program redeployment cooldown #29135"), + (configurable_hashes_per_tick::id(), "Configure desired hashes per tick to update on epoch boundary"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter()