diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 416666a208..29d7423ec7 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -1600,13 +1600,18 @@ impl Bank { // `pico_inflation` be enabled 2nd, the incorrect start slot provided here should have no // effect on the inflation calculation. fn get_inflation_start_slot(&self) -> Slot { - self.feature_set - .activated_slot(&feature_set::full_inflation::id()) - .unwrap_or_else(|| { - self.feature_set - .activated_slot(&feature_set::pico_inflation::id()) - .unwrap_or(0) - }) + let mut slots = self + .feature_set + .full_inflation_features_enabled() + .iter() + .filter_map(|id| self.feature_set.activated_slot(&id)) + .collect::>(); + slots.sort_unstable(); + slots.get(0).cloned().unwrap_or_else(|| { + self.feature_set + .activated_slot(&feature_set::pico_inflation::id()) + .unwrap_or(0) + }) } fn get_inflation_num_slots(&self) -> u64 { @@ -4669,7 +4674,8 @@ impl Bank { self.rent_collector.rent.burn_percent = 50; // 50% rent burn } - if new_feature_activations.contains(&feature_set::full_inflation::id()) { + if !new_feature_activations.is_disjoint(&self.feature_set.full_inflation_features_enabled()) + { *self.inflation.write().unwrap() = Inflation::full(); self.fee_rate_governor.burn_percent = 50; // 50% fee burn self.rent_collector.rent.burn_percent = 50; // 50% rent burn @@ -11863,7 +11869,7 @@ pub(crate) mod tests { } #[test] - fn test_get_inflation_start_slot() { + fn test_get_inflation_start_slot_devnet_testnet() { let GenesisConfigInfo { mut genesis_config, .. } = create_genesis_config_with_leader(42, &solana_sdk::pubkey::new_rand(), 42); @@ -11873,51 +11879,156 @@ pub(crate) mod tests { .unwrap(); genesis_config .accounts - .remove(&feature_set::full_inflation::id()) + .remove(&feature_set::full_inflation::devnet_and_testnet::id()) .unwrap(); + for pair in feature_set::FULL_INFLATION_FEATURE_PAIRS.iter() { + genesis_config.accounts.remove(&pair.vote_id).unwrap(); + genesis_config.accounts.remove(&pair.enable_id).unwrap(); + } + let bank = Bank::new(&genesis_config); - // Advance to slot 1 + // Advance slot let mut bank = new_from_parent(&Arc::new(bank)); bank = new_from_parent(&Arc::new(bank)); assert_eq!(bank.get_inflation_start_slot(), 0); + assert_eq!(bank.slot(), 2); - // Request `full_inflation` activation - let pico_inflation_activation_slot = 1; + // Request `pico_inflation` activation bank.store_account( &feature_set::pico_inflation::id(), &feature::create_account( &Feature { - activated_at: Some(pico_inflation_activation_slot), + activated_at: Some(1), }, 42, ), ); bank.compute_active_feature_set(true); - assert_eq!( - bank.get_inflation_start_slot(), - pico_inflation_activation_slot - ); + assert_eq!(bank.get_inflation_start_slot(), 1); - // Advance to slot 2 + // Advance slot bank = new_from_parent(&Arc::new(bank)); + assert_eq!(bank.slot(), 3); - // Request `full_inflation` activation, which takes priority over pico_inflation - let full_inflation_activation_slot = 2; + // Request `full_inflation::devnet_and_testnet` activation, which takes priority over pico_inflation bank.store_account( - &feature_set::full_inflation::id(), + &feature_set::full_inflation::devnet_and_testnet::id(), &feature::create_account( &Feature { - activated_at: Some(full_inflation_activation_slot), + activated_at: Some(2), }, 42, ), ); bank.compute_active_feature_set(true); - assert_eq!( - bank.get_inflation_start_slot(), - full_inflation_activation_slot + assert_eq!(bank.get_inflation_start_slot(), 2); + + // Request `full_inflation::candidate_example` activation, which should have no effect on `get_inflation_start_slot` + bank.store_account( + &feature_set::full_inflation::candidate_example::vote::id(), + &feature::create_account( + &Feature { + activated_at: Some(3), + }, + 42, + ), ); + bank.store_account( + &feature_set::full_inflation::candidate_example::enable::id(), + &feature::create_account( + &Feature { + activated_at: Some(3), + }, + 42, + ), + ); + bank.compute_active_feature_set(true); + assert_eq!(bank.get_inflation_start_slot(), 2); + } + + #[test] + fn test_get_inflation_start_slot_mainnet() { + let GenesisConfigInfo { + mut genesis_config, .. + } = create_genesis_config_with_leader(42, &solana_sdk::pubkey::new_rand(), 42); + genesis_config + .accounts + .remove(&feature_set::pico_inflation::id()) + .unwrap(); + genesis_config + .accounts + .remove(&feature_set::full_inflation::devnet_and_testnet::id()) + .unwrap(); + for pair in feature_set::FULL_INFLATION_FEATURE_PAIRS.iter() { + genesis_config.accounts.remove(&pair.vote_id).unwrap(); + genesis_config.accounts.remove(&pair.enable_id).unwrap(); + } + + let bank = Bank::new(&genesis_config); + + // Advance slot + let mut bank = new_from_parent(&Arc::new(bank)); + bank = new_from_parent(&Arc::new(bank)); + assert_eq!(bank.get_inflation_start_slot(), 0); + assert_eq!(bank.slot(), 2); + + // Request `pico_inflation` activation + bank.store_account( + &feature_set::pico_inflation::id(), + &feature::create_account( + &Feature { + activated_at: Some(1), + }, + 42, + ), + ); + bank.compute_active_feature_set(true); + assert_eq!(bank.get_inflation_start_slot(), 1); + + // Advance slot + bank = new_from_parent(&Arc::new(bank)); + assert_eq!(bank.slot(), 3); + + // Request `full_inflation::candidate_example` activation, which takes priority over pico_inflation + bank.store_account( + &feature_set::full_inflation::candidate_example::vote::id(), + &feature::create_account( + &Feature { + activated_at: Some(2), + }, + 42, + ), + ); + bank.store_account( + &feature_set::full_inflation::candidate_example::enable::id(), + &feature::create_account( + &Feature { + activated_at: Some(2), + }, + 42, + ), + ); + bank.compute_active_feature_set(true); + assert_eq!(bank.get_inflation_start_slot(), 2); + + // Advance slot + bank = new_from_parent(&Arc::new(bank)); + assert_eq!(bank.slot(), 4); + + // Request `full_inflation::devnet_and_testnet` activation, which should have no effect on + // `get_inflation_start_slot` + bank.store_account( + &feature_set::full_inflation::devnet_and_testnet::id(), + &feature::create_account( + &Feature { + activated_at: Some(bank.slot()), + }, + 42, + ), + ); + bank.compute_active_feature_set(true); + assert_eq!(bank.get_inflation_start_slot(), 2); } #[test] @@ -11933,8 +12044,13 @@ pub(crate) mod tests { .unwrap(); genesis_config .accounts - .remove(&feature_set::full_inflation::id()) + .remove(&feature_set::full_inflation::devnet_and_testnet::id()) .unwrap(); + for pair in feature_set::FULL_INFLATION_FEATURE_PAIRS.iter() { + genesis_config.accounts.remove(&pair.vote_id).unwrap(); + genesis_config.accounts.remove(&pair.enable_id).unwrap(); + } + let mut bank = Bank::new(&genesis_config); assert_eq!(bank.get_inflation_num_slots(), 0); for _ in 0..2 * slots_per_epoch { @@ -11960,10 +12076,10 @@ pub(crate) mod tests { } assert_eq!(bank.get_inflation_num_slots(), 2 * slots_per_epoch); - // Activate full_inflation + // Activate full_inflation::devnet_and_testnet let full_inflation_activation_slot = bank.slot(); bank.store_account( - &feature_set::full_inflation::id(), + &feature_set::full_inflation::devnet_and_testnet::id(), &feature::create_account( &Feature { activated_at: Some(full_inflation_activation_slot), diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index 8514c1beb7..65b60f70ac 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -27,7 +27,27 @@ pub mod pico_inflation { } pub mod full_inflation { - solana_sdk::declare_id!("DT4n6ABDqs6w4bnfwrXT9rsprcPf6cdDga1egctaPkLC"); + pub mod devnet_and_testnet { + solana_sdk::declare_id!("DT4n6ABDqs6w4bnfwrXT9rsprcPf6cdDga1egctaPkLC"); + } + + // `candidate_example` is an example to follow by a candidate that wishes to enable full + // inflation. There are multiple references to `candidate_example` in this file that need to + // be touched in addition to the following block. + // + // The candidate provides the `enable::id` address and contacts the Solana Foundation to + // receive a `vote::id` address. + // + pub mod candidate_example { + pub mod vote { + // The private key for this address is held by the Solana Foundation + solana_sdk::declare_id!("DummyVoteAddress111111111111111111111111111"); + } + pub mod enable { + // The private key for this address is held by candidate_example + solana_sdk::declare_id!("DummyEnab1eAddress1111111111111111111111111"); + } + } } pub mod spl_token_v2_multisig_fix { @@ -153,8 +173,8 @@ lazy_static! { (secp256k1_program_enabled::id(), "secp256k1 program"), (consistent_recent_blockhashes_sysvar::id(), "consistent recentblockhashes sysvar"), (deprecate_rewards_sysvar::id(), "deprecate unused rewards sysvar"), - (pico_inflation::id(), "pico-inflation"), - (full_inflation::id(), "full-inflation"), + (pico_inflation::id(), "pico inflation"), + (full_inflation::devnet_and_testnet::id(), "full inflation on devnet and testnet"), (spl_token_v2_multisig_fix::id(), "spl-token multisig fix"), (bpf_loader2_program::id(), "bpf_loader2 program"), (bpf_compute_budget_balancing::id(), "compute budget balancing"), @@ -184,6 +204,8 @@ lazy_static! { (use_loaded_executables::id(), "Use loaded executable accounts"), (turbine_retransmit_peers_patch::id(), "turbine retransmit peers patch #14631"), (prevent_upgrade_and_invoke::id(), "Prevent upgrade and invoke in same tx batch"), + (full_inflation::candidate_example::vote::id(), "Community vote allowing candidate_example to enable full inflation"), + (full_inflation::candidate_example::enable::id(), "Full inflation enabled by candidate_example"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter() @@ -202,6 +224,25 @@ lazy_static! { }; } +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct FullInflationFeaturePair { + pub vote_id: Pubkey, // Feature that grants the candidate the ability to enable full inflation + pub enable_id: Pubkey, // Feature to enable full inflation by the candidate +} + +lazy_static! { + /// Set of feature pairs that once enabled will trigger full inflation + pub static ref FULL_INFLATION_FEATURE_PAIRS: HashSet = [ + FullInflationFeaturePair { + vote_id: full_inflation::candidate_example::vote::id(), + enable_id: full_inflation::candidate_example::enable::id(), + }, + ] + .iter() + .cloned() + .collect(); +} + /// `FeatureSet` holds the set of currently active/inactive runtime features #[derive(AbiExample, Debug, Clone)] pub struct FeatureSet { @@ -230,6 +271,25 @@ impl FeatureSet { self.is_active(&cumulative_rent_related_fixes::id()) } + /// List of enabled features that trigger full inflation + pub fn full_inflation_features_enabled(&self) -> HashSet { + let mut hash_set = FULL_INFLATION_FEATURE_PAIRS + .iter() + .filter_map(|pair| { + if self.is_active(&pair.vote_id) && self.is_active(&pair.enable_id) { + Some(pair.enable_id) + } else { + None + } + }) + .collect::>(); + + if self.is_active(&full_inflation::devnet_and_testnet::id()) { + hash_set.insert(full_inflation::devnet_and_testnet::id()); + } + hash_set + } + /// All features enabled, useful for testing pub fn all_enabled() -> Self { Self { @@ -238,3 +298,63 @@ impl FeatureSet { } } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_full_inflation_features_enabled_devnet_and_testnet() { + let mut feature_set = FeatureSet::default(); + assert!(feature_set.full_inflation_features_enabled().is_empty()); + feature_set + .active + .insert(full_inflation::devnet_and_testnet::id(), 42); + assert_eq!( + feature_set.full_inflation_features_enabled(), + [full_inflation::devnet_and_testnet::id()] + .iter() + .cloned() + .collect() + ); + } + + #[test] + fn test_full_inflation_features_enabled() { + // Normal sequence: vote_id then enable_id + let mut feature_set = FeatureSet::default(); + assert!(feature_set.full_inflation_features_enabled().is_empty()); + feature_set + .active + .insert(full_inflation::candidate_example::vote::id(), 42); + assert!(feature_set.full_inflation_features_enabled().is_empty()); + feature_set + .active + .insert(full_inflation::candidate_example::enable::id(), 42); + assert_eq!( + feature_set.full_inflation_features_enabled(), + [full_inflation::candidate_example::enable::id()] + .iter() + .cloned() + .collect() + ); + + // Backwards sequence: enable_id and then vote_id + let mut feature_set = FeatureSet::default(); + assert!(feature_set.full_inflation_features_enabled().is_empty()); + feature_set + .active + .insert(full_inflation::candidate_example::enable::id(), 42); + assert!(feature_set.full_inflation_features_enabled().is_empty()); + feature_set + .active + .insert(full_inflation::candidate_example::vote::id(), 42); + assert_eq!( + feature_set.full_inflation_features_enabled(), + [full_inflation::candidate_example::enable::id()] + .iter() + .cloned() + .collect() + ); + } +}