diff --git a/accounts-bench/src/main.rs b/accounts-bench/src/main.rs index 3abb883d66..6bfbea7a30 100644 --- a/accounts-bench/src/main.rs +++ b/accounts-bench/src/main.rs @@ -98,7 +98,10 @@ fn main() { } else { let mut pubkeys: Vec = vec![]; let mut time = Measure::start("hash"); - let hash = accounts.accounts_db.update_accounts_hash(0, &ancestors).0; + let hash = accounts + .accounts_db + .update_accounts_hash(0, &ancestors, true) + .0; time.stop(); println!("hash: {} {}", hash, time); create_test_accounts(&accounts, &mut pubkeys, 1, 0); diff --git a/ledger-tool/src/main.rs b/ledger-tool/src/main.rs index 6bd7d8cd8a..6b77ee525b 100644 --- a/ledger-tool/src/main.rs +++ b/ledger-tool/src/main.rs @@ -1217,6 +1217,13 @@ fn main() { .help("Enable stake program v2 (several inflation-related staking \ bugs are feature-gated behind this)"), ) + .arg( + Arg::with_name("enable_simple_capitalization") + .required(false) + .long("enable-simple-capitalization") + .takes_value(false) + .help("Enable simple capitalization to test hardcoded cap adjustments"), + ) .arg( Arg::with_name("recalculate_capitalization") .required(false) @@ -2115,11 +2122,47 @@ fn main() { .lazy_rent_collection .store(true, std::sync::atomic::Ordering::Relaxed); + let feature_account_balance = std::cmp::max( + genesis_config.rent.minimum_balance(Feature::size_of()), + 1, + ); + if arg_matches.is_present("enable_simple_capitalization") { + if base_bank + .get_account(&feature_set::simple_capitalization::id()) + .is_none() + { + base_bank.store_account( + &feature_set::simple_capitalization::id(), + &feature::create_account( + &Feature { activated_at: None }, + feature_account_balance, + ), + ); + if base_bank + .get_account(&feature_set::cumulative_rent_related_fixes::id()) + .is_some() + { + // steal some lamports from the pretty old feature not to affect + // capitalizaion, which doesn't affect inflation behavior! + base_bank.store_account( + &feature_set::cumulative_rent_related_fixes::id(), + &Account::default(), + ); + } else { + let old_cap = base_bank.set_capitalization(); + let new_cap = base_bank.capitalization(); + warn!( + "Skewing capitalization a bit to enable simple capitalization as \ + requested: increasing {} from {} to {}", + feature_account_balance, old_cap, new_cap, + ); + assert_eq!(old_cap + feature_account_balance, new_cap); + } + } else { + warn!("Already simple_capitalization is activated (or scheduled)"); + } + } if arg_matches.is_present("enable_stake_program_v2") { - let feature_account_balance = std::cmp::max( - genesis_config.rent.minimum_balance(Feature::size_of()), - 1, - ); let mut force_enabled_count = 0; if base_bank .get_account(&feature_set::stake_program_v2::id()) diff --git a/runtime/benches/accounts.rs b/runtime/benches/accounts.rs index 83f6a6e4bb..7fd41e7364 100644 --- a/runtime/benches/accounts.rs +++ b/runtime/benches/accounts.rs @@ -94,8 +94,12 @@ fn test_accounts_hash_bank_hash(bencher: &mut Bencher) { let slot = 0; create_test_accounts(&accounts, &mut pubkeys, num_accounts, slot); let ancestors = vec![(0, 0)].into_iter().collect(); - let (_, total_lamports) = accounts.accounts_db.update_accounts_hash(0, &ancestors); - bencher.iter(|| assert!(accounts.verify_bank_hash_and_lamports(0, &ancestors, total_lamports))); + let (_, total_lamports) = accounts + .accounts_db + .update_accounts_hash(0, &ancestors, true); + bencher.iter(|| { + assert!(accounts.verify_bank_hash_and_lamports(0, &ancestors, total_lamports, true)) + }); } #[bench] @@ -109,7 +113,9 @@ fn test_update_accounts_hash(bencher: &mut Bencher) { create_test_accounts(&accounts, &mut pubkeys, 50_000, 0); let ancestors = vec![(0, 0)].into_iter().collect(); bencher.iter(|| { - accounts.accounts_db.update_accounts_hash(0, &ancestors); + accounts + .accounts_db + .update_accounts_hash(0, &ancestors, true); }); } diff --git a/runtime/src/accounts.rs b/runtime/src/accounts.rs index 0febd441d7..51f9e96ed2 100644 --- a/runtime/src/accounts.rs +++ b/runtime/src/accounts.rs @@ -460,7 +460,11 @@ impl Accounts { accounts_balances } - pub fn calculate_capitalization(&self, ancestors: &Ancestors) -> u64 { + pub fn calculate_capitalization( + &self, + ancestors: &Ancestors, + simple_capitalization_enabled: bool, + ) -> u64 { let balances = self.load_all_unchecked(ancestors) .into_iter() @@ -469,6 +473,7 @@ impl Accounts { account.lamports, &account.owner, account.executable, + simple_capitalization_enabled, ) }); @@ -481,11 +486,14 @@ impl Accounts { slot: Slot, ancestors: &Ancestors, total_lamports: u64, + simple_capitalization_enabled: bool, ) -> bool { - if let Err(err) = - self.accounts_db - .verify_bank_hash_and_lamports(slot, ancestors, total_lamports) - { + if let Err(err) = self.accounts_db.verify_bank_hash_and_lamports( + slot, + ancestors, + total_lamports, + simple_capitalization_enabled, + ) { warn!("verify_bank_hash failed: {:?}", err); false } else { diff --git a/runtime/src/accounts_db.rs b/runtime/src/accounts_db.rs index 07f315b3cc..295c00fe46 100644 --- a/runtime/src/accounts_db.rs +++ b/runtime/src/accounts_db.rs @@ -2246,7 +2246,12 @@ impl AccountsDB { lamports: u64, owner: &Pubkey, executable: bool, + simple_capitalization_enabled: bool, ) -> u64 { + if simple_capitalization_enabled { + return lamports; + } + let is_specially_retained = (solana_sdk::native_loader::check_id(owner) && executable) || solana_sdk::sysvar::check_id(owner); @@ -2265,6 +2270,7 @@ impl AccountsDB { slot: Slot, ancestors: &Ancestors, check_hash: bool, + simple_capitalization_enabled: bool, ) -> Result<(Hash, u64), BankHashVerificationError> { use BankHashVerificationError::*; let mut scan = Measure::start("scan"); @@ -2295,6 +2301,7 @@ impl AccountsDB { account_info.lamports, &account.account_meta.owner, account.account_meta.executable, + simple_capitalization_enabled, ); if check_hash { @@ -2353,9 +2360,14 @@ impl AccountsDB { bank_hash_info.snapshot_hash } - pub fn update_accounts_hash(&self, slot: Slot, ancestors: &Ancestors) -> (Hash, u64) { + pub fn update_accounts_hash( + &self, + slot: Slot, + ancestors: &Ancestors, + simple_capitalization_enabled: bool, + ) -> (Hash, u64) { let (hash, total_lamports) = self - .calculate_accounts_hash(slot, ancestors, false) + .calculate_accounts_hash(slot, ancestors, false, simple_capitalization_enabled) .unwrap(); let mut bank_hashes = self.bank_hashes.write().unwrap(); let mut bank_hash_info = bank_hashes.get_mut(&slot).unwrap(); @@ -2368,11 +2380,12 @@ impl AccountsDB { slot: Slot, ancestors: &Ancestors, total_lamports: u64, + simple_capitalization_enabled: bool, ) -> Result<(), BankHashVerificationError> { use BankHashVerificationError::*; let (calculated_hash, calculated_lamports) = - self.calculate_accounts_hash(slot, ancestors, true)?; + self.calculate_accounts_hash(slot, ancestors, true, simple_capitalization_enabled)?; if calculated_lamports != total_lamports { warn!( @@ -4015,8 +4028,8 @@ pub mod tests { let ancestors = linear_ancestors(latest_slot); assert_eq!( - daccounts.update_accounts_hash(latest_slot, &ancestors), - accounts.update_accounts_hash(latest_slot, &ancestors) + daccounts.update_accounts_hash(latest_slot, &ancestors, true), + accounts.update_accounts_hash(latest_slot, &ancestors, true) ); } @@ -4169,12 +4182,12 @@ pub mod tests { let ancestors = linear_ancestors(current_slot); info!("ancestors: {:?}", ancestors); - let hash = accounts.update_accounts_hash(current_slot, &ancestors); + let hash = accounts.update_accounts_hash(current_slot, &ancestors, true); accounts.clean_accounts(None); assert_eq!( - accounts.update_accounts_hash(current_slot, &ancestors), + accounts.update_accounts_hash(current_slot, &ancestors, true), hash ); @@ -4291,7 +4304,7 @@ pub mod tests { accounts.add_root(current_slot); accounts.print_accounts_stats("pre_f"); - accounts.update_accounts_hash(4, &HashMap::default()); + accounts.update_accounts_hash(4, &HashMap::default(), true); let accounts = f(accounts, current_slot); @@ -4303,7 +4316,7 @@ pub mod tests { assert_load_account(&accounts, current_slot, dummy_pubkey, dummy_lamport); accounts - .verify_bank_hash_and_lamports(4, &HashMap::default(), 1222) + .verify_bank_hash_and_lamports(4, &HashMap::default(), 1222, true) .unwrap(); } @@ -4700,15 +4713,15 @@ pub mod tests { db.store(some_slot, &[(&key, &account)]); db.add_root(some_slot); - db.update_accounts_hash(some_slot, &ancestors); + db.update_accounts_hash(some_slot, &ancestors, true); assert_matches!( - db.verify_bank_hash_and_lamports(some_slot, &ancestors, 1), + db.verify_bank_hash_and_lamports(some_slot, &ancestors, 1, true), Ok(_) ); db.bank_hashes.write().unwrap().remove(&some_slot).unwrap(); assert_matches!( - db.verify_bank_hash_and_lamports(some_slot, &ancestors, 1), + db.verify_bank_hash_and_lamports(some_slot, &ancestors, 1, true), Err(MissingBankHash) ); @@ -4723,7 +4736,7 @@ pub mod tests { .unwrap() .insert(some_slot, bank_hash_info); assert_matches!( - db.verify_bank_hash_and_lamports(some_slot, &ancestors, 1), + db.verify_bank_hash_and_lamports(some_slot, &ancestors, 1, true), Err(MismatchedBankHash) ); } @@ -4742,9 +4755,9 @@ pub mod tests { db.store(some_slot, &[(&key, &account)]); db.add_root(some_slot); - db.update_accounts_hash(some_slot, &ancestors); + db.update_accounts_hash(some_slot, &ancestors, true); assert_matches!( - db.verify_bank_hash_and_lamports(some_slot, &ancestors, 1), + db.verify_bank_hash_and_lamports(some_slot, &ancestors, 1, true), Ok(_) ); @@ -4756,15 +4769,19 @@ pub mod tests { &solana_sdk::native_loader::create_loadable_account("foo", 1), )], ); - db.update_accounts_hash(some_slot, &ancestors); + db.update_accounts_hash(some_slot, &ancestors, true); assert_matches!( - db.verify_bank_hash_and_lamports(some_slot, &ancestors, 1), + db.verify_bank_hash_and_lamports(some_slot, &ancestors, 1, false), + Ok(_) + ); + assert_matches!( + db.verify_bank_hash_and_lamports(some_slot, &ancestors, 2, true), Ok(_) ); assert_matches!( - db.verify_bank_hash_and_lamports(some_slot, &ancestors, 10), - Err(MismatchedTotalLamports(expected, actual)) if expected == 1 && actual == 10 + db.verify_bank_hash_and_lamports(some_slot, &ancestors, 10, true), + Err(MismatchedTotalLamports(expected, actual)) if expected == 2 && actual == 10 ); } @@ -4781,9 +4798,9 @@ pub mod tests { .unwrap() .insert(some_slot, BankHashInfo::default()); db.add_root(some_slot); - db.update_accounts_hash(some_slot, &ancestors); + db.update_accounts_hash(some_slot, &ancestors, true); assert_matches!( - db.verify_bank_hash_and_lamports(some_slot, &ancestors, 0), + db.verify_bank_hash_and_lamports(some_slot, &ancestors, 0, true), Ok(_) ); } @@ -4808,7 +4825,7 @@ pub mod tests { db.store_accounts_default(some_slot, accounts, &[some_hash]); db.add_root(some_slot); assert_matches!( - db.verify_bank_hash_and_lamports(some_slot, &ancestors, 1), + db.verify_bank_hash_and_lamports(some_slot, &ancestors, 1, true), Err(MismatchedAccountHash) ); } @@ -5284,14 +5301,14 @@ pub mod tests { ); let no_ancestors = HashMap::default(); - accounts.update_accounts_hash(current_slot, &no_ancestors); + accounts.update_accounts_hash(current_slot, &no_ancestors, true); accounts - .verify_bank_hash_and_lamports(current_slot, &no_ancestors, 22300) + .verify_bank_hash_and_lamports(current_slot, &no_ancestors, 22300, true) .unwrap(); let accounts = reconstruct_accounts_db_via_serialization(&accounts, current_slot); accounts - .verify_bank_hash_and_lamports(current_slot, &no_ancestors, 22300) + .verify_bank_hash_and_lamports(current_slot, &no_ancestors, 22300, true) .unwrap(); // repeating should be no-op @@ -5488,7 +5505,7 @@ pub mod tests { fn test_account_balance_for_capitalization_normal() { // system accounts assert_eq!( - AccountsDB::account_balance_for_capitalization(10, &Pubkey::default(), false), + AccountsDB::account_balance_for_capitalization(10, &Pubkey::default(), false, true), 10 ); // any random program data accounts @@ -5496,7 +5513,17 @@ pub mod tests { AccountsDB::account_balance_for_capitalization( 10, &solana_sdk::pubkey::new_rand(), - false + false, + true, + ), + 10 + ); + assert_eq!( + AccountsDB::account_balance_for_capitalization( + 10, + &solana_sdk::pubkey::new_rand(), + false, + false, ), 10 ); @@ -5512,16 +5539,40 @@ pub mod tests { AccountsDB::account_balance_for_capitalization( normal_sysvar.lamports, &normal_sysvar.owner, - normal_sysvar.executable + normal_sysvar.executable, + false, ), 0 ); + assert_eq!( + AccountsDB::account_balance_for_capitalization( + normal_sysvar.lamports, + &normal_sysvar.owner, + normal_sysvar.executable, + true, + ), + 1 + ); // currently transactions can send any lamports to sysvars although this is not sensible. assert_eq!( - AccountsDB::account_balance_for_capitalization(10, &solana_sdk::sysvar::id(), false), + AccountsDB::account_balance_for_capitalization( + 10, + &solana_sdk::sysvar::id(), + false, + false + ), 9 ); + assert_eq!( + AccountsDB::account_balance_for_capitalization( + 10, + &solana_sdk::sysvar::id(), + false, + true + ), + 10 + ); } #[test] @@ -5531,20 +5582,40 @@ pub mod tests { AccountsDB::account_balance_for_capitalization( normal_native_program.lamports, &normal_native_program.owner, - normal_native_program.executable + normal_native_program.executable, + false, ), 0 ); + assert_eq!( + AccountsDB::account_balance_for_capitalization( + normal_native_program.lamports, + &normal_native_program.owner, + normal_native_program.executable, + true, + ), + 1 + ); // test maliciously assigned bogus native loader account assert_eq!( AccountsDB::account_balance_for_capitalization( 1, &solana_sdk::native_loader::id(), - false + false, + false, ), 1 - ) + ); + assert_eq!( + AccountsDB::account_balance_for_capitalization( + 1, + &solana_sdk::native_loader::id(), + false, + true, + ), + 1 + ); } #[test] diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 7ab392e2ac..dfdc91a56f 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -1272,7 +1272,12 @@ impl Bank { { let old_account = self.get_sysvar_account(pubkey); let new_account = updater(&old_account); - self.store_account(pubkey, &new_account); + + if !self.simple_capitalization_enabled() { + self.store_account(pubkey, &new_account); + } else { + self.store_account_and_update_capitalization(pubkey, &new_account); + } } fn inherit_specially_retained_account_balance(&self, old_account: &Option) -> u64 { @@ -2020,7 +2025,6 @@ impl Bank { // Bootstrap validator collects fees until `new_from_parent` is called. self.fee_rate_governor = genesis_config.fee_rate_governor.clone(); self.fee_calculator = self.fee_rate_governor.create_fee_calculator(); - self.update_fees(); for (pubkey, account) in genesis_config.accounts.iter() { if self.get_account(&pubkey).is_some() { @@ -2029,6 +2033,9 @@ impl Bank { self.store_account(pubkey, account); self.capitalization.fetch_add(account.lamports, Relaxed); } + // updating sysvars (the fees sysvar in this case) now depends on feature activations in + // genesis_config.accounts above + self.update_fees(); for (pubkey, account) in genesis_config.rewards_pools.iter() { if self.get_account(&pubkey).is_some() { @@ -2148,7 +2155,11 @@ impl Bank { name, self.inherit_specially_retained_account_balance(&existing_genuine_program), ); - self.store_account(&program_id, &account); + if !self.simple_capitalization_enabled() { + self.store_account(&program_id, &account); + } else { + self.store_account_and_update_capitalization(&program_id, &account); + } debug!("Added native program {} under {:?}", name, program_id); } @@ -3765,7 +3776,6 @@ impl Bank { } } - #[cfg(test)] fn store_account_and_update_capitalization(&self, pubkey: &Pubkey, new_account: &Account) { if let Some(old_account) = self.get_account(&pubkey) { match new_account.lamports.cmp(&old_account.lamports) { @@ -4071,6 +4081,7 @@ impl Bank { self.slot(), &self.ancestors, self.capitalization(), + self.simple_capitalization_enabled(), ) } @@ -4101,7 +4112,9 @@ impl Bank { } pub fn calculate_capitalization(&self) -> u64 { - self.rc.accounts.calculate_capitalization(&self.ancestors) + self.rc + .accounts + .calculate_capitalization(&self.ancestors, self.simple_capitalization_enabled()) } pub fn calculate_and_verify_capitalization(&self) -> bool { @@ -4132,11 +4145,11 @@ impl Bank { } pub fn update_accounts_hash(&self) -> Hash { - let (hash, total_lamports) = self - .rc - .accounts - .accounts_db - .update_accounts_hash(self.slot(), &self.ancestors); + let (hash, total_lamports) = self.rc.accounts.accounts_db.update_accounts_hash( + self.slot(), + &self.ancestors, + self.simple_capitalization_enabled(), + ); assert_eq!(total_lamports, self.capitalization()); hash } @@ -4445,6 +4458,28 @@ impl Bank { .is_active(&feature_set::stake_program_v2::id()) } + pub fn simple_capitalization_enabled(&self) -> bool { + self.simple_capitalization_enabled_at_genesis() + || self + .feature_set + .is_active(&feature_set::simple_capitalization::id()) + } + + fn simple_capitalization_enabled_at_genesis(&self) -> bool { + // genesis builtin initialization codepath is called even before the initial + // feature activation, so we need to peek this flag at very early bank + // initialization phase for the development genesis case + if let Some(account) = self.get_account(&feature_set::simple_capitalization::id()) { + if let Some(feature) = feature::from_account(&account) { + if feature.activated_at == Some(0) { + return true; + } + } + } + + false + } + // This is called from snapshot restore AND for each epoch boundary // The entire code path herein must be idempotent fn apply_feature_activations(&mut self, init_finish_or_warp: bool) { @@ -4476,6 +4511,10 @@ impl Bank { self.rewrite_stakes(); } + if new_feature_activations.contains(&feature_set::simple_capitalization::id()) { + self.adjust_capitalization_for_existing_specially_retained_accounts(); + } + self.ensure_feature_builtins(init_finish_or_warp, &new_feature_activations); self.reconfigure_token2_native_mint(); self.ensure_no_storage_rewards_pool(); @@ -4560,6 +4599,39 @@ impl Bank { } } + fn adjust_capitalization_for_existing_specially_retained_accounts(&self) { + use solana_sdk::{bpf_loader, bpf_loader_deprecated, secp256k1_program}; + let mut existing_sysvar_account_count = 8; + let mut existing_native_program_account_count = 4; + + if self.get_account(&sysvar::rewards::id()).is_some() { + existing_sysvar_account_count += 1; + } + + if self.get_account(&bpf_loader::id()).is_some() { + existing_native_program_account_count += 1; + } + + if self.get_account(&bpf_loader_deprecated::id()).is_some() { + existing_native_program_account_count += 1; + } + + if self.get_account(&secp256k1_program::id()).is_some() { + existing_native_program_account_count += 1; + } + + info!( + "Adjusted capitalization for existing {} sysvars and {} native programs from {}", + existing_sysvar_account_count, + existing_native_program_account_count, + self.capitalization() + ); + self.capitalization.fetch_add( + existing_sysvar_account_count + existing_native_program_account_count, + Relaxed, + ); + } + fn reconfigure_token2_native_mint(&mut self) { let reconfigure_token2_native_mint = match self.cluster_type() { ClusterType::Development => true, @@ -5223,6 +5295,19 @@ pub(crate) mod tests { assert_eq!(bank.capitalization(), bank.calculate_capitalization()); } + fn assert_capitalization_diff_with_new_bank( + bank: &Bank, + updater: impl Fn() -> Bank, + asserter: impl Fn(u64, u64), + ) -> Bank { + let old = bank.capitalization(); + let bank = updater(); + let new = bank.capitalization(); + asserter(old, new); + assert_eq!(bank.capitalization(), bank.calculate_capitalization()); + bank + } + #[test] fn test_store_account_and_update_capitalization_missing() { let (genesis_config, _mint_keypair) = create_genesis_config(0); @@ -5425,6 +5510,7 @@ pub(crate) mod tests { burn_percent: 10, }; + genesis_config.disable_cap_altering_features_for_preciseness(); let mut bank = Bank::new(&genesis_config); // Enable rent collection bank.rent_collector.epoch = 5; @@ -6416,6 +6502,11 @@ pub(crate) mod tests { .map(|(slot, _)| *slot) .collect::>() } + + fn first_slot_in_next_epoch(&self) -> Slot { + self.epoch_schedule() + .get_first_slot_in_epoch(self.epoch() + 1) + } } #[test] @@ -7121,6 +7212,7 @@ pub(crate) mod tests { let (expected_fee_collected, expected_fee_burned) = genesis_config.fee_rate_governor.burn(expected_fee_paid); + genesis_config.disable_cap_altering_features_for_preciseness(); let mut bank = Bank::new(&genesis_config); let capitalization = bank.capitalization(); @@ -7919,18 +8011,22 @@ pub(crate) mod tests { assert_eq!(None, bank3.get_account_modified_since_parent(&pubkey)); } - #[test] - fn test_bank_update_sysvar_account() { + fn do_test_bank_update_sysvar_account(simple_capitalization_enabled: bool) { use sysvar::clock::Clock; let dummy_clock_id = solana_sdk::pubkey::new_rand(); - let (genesis_config, _mint_keypair) = create_genesis_config(500); + let (mut genesis_config, _mint_keypair) = create_genesis_config(500); let expected_previous_slot = 3; let expected_next_slot = expected_previous_slot + 1; // First, initialize the clock sysvar + if simple_capitalization_enabled { + activate_all_features(&mut genesis_config); + } let bank1 = Arc::new(Bank::new(&genesis_config)); + assert_eq!(bank1.calculate_capitalization(), bank1.capitalization()); + assert_capitalization_diff( &bank1, || { @@ -7952,7 +8048,12 @@ pub(crate) mod tests { ); }, |old, new| { - assert_eq!(old, new); + // only if simple_capitalization_enabled, cap should increment + if simple_capitalization_enabled { + assert_eq!(old + 1, new); + } else { + assert_eq!(old, new); + } }, ); @@ -8040,6 +8141,16 @@ pub(crate) mod tests { ); } + #[test] + fn test_bank_update_sysvar_account_with_simple_capitalization_disabled() { + do_test_bank_update_sysvar_account(false) + } + + #[test] + fn test_bank_update_sysvar_account_with_simple_capitalization_enabled() { + do_test_bank_update_sysvar_account(true); + } + #[test] fn test_bank_epoch_vote_accounts() { let leader_pubkey = solana_sdk::pubkey::new_rand(); @@ -10101,9 +10212,11 @@ pub(crate) mod tests { assert_eq!(bank.get_account_modified_slot(&loader_id).unwrap().1, slot); } - #[test] - fn test_add_native_program_no_overwrite() { - let (genesis_config, _mint_keypair) = create_genesis_config(100_000); + fn do_test_add_native_program(simple_capitalization_enabled: bool) { + let (mut genesis_config, _mint_keypair) = create_genesis_config(100_000); + if simple_capitalization_enabled { + activate_all_features(&mut genesis_config); + } let slot = 123; let program_id = solana_sdk::pubkey::new_rand(); @@ -10119,7 +10232,11 @@ pub(crate) mod tests { &bank, || bank.add_native_program("mock_program", &program_id, false), |old, new| { - assert_eq!(old, new); + if simple_capitalization_enabled { + assert_eq!(old + 1, new); + } else { + assert_eq!(old, new); + } }, ); @@ -10162,6 +10279,16 @@ pub(crate) mod tests { ); } + #[test] + fn test_add_native_program_with_simple_capitalization_disabled() { + do_test_add_native_program(false); + } + + #[test] + fn test_add_native_program_with_simple_capitalization_enabled() { + do_test_add_native_program(true); + } + #[test] fn test_add_native_program_inherited_cap_while_replacing() { let (genesis_config, mint_keypair) = create_genesis_config(100_000); @@ -10323,6 +10450,7 @@ pub(crate) mod tests { reward_pubkey, Account::new(u64::MAX, 0, &solana_sdk::pubkey::new_rand()), ); + genesis_config.disable_cap_altering_features_for_preciseness(); let bank0 = Bank::new(&genesis_config); // because capitalization has been reset with bogus capitalization calculation allowing overflows, // deliberately substract 1 lamport to simulate it @@ -10743,6 +10871,113 @@ pub(crate) mod tests { ); } + #[test] + fn test_simple_capitalization_adjustment_minimum_genesis_set() { + solana_logger::setup(); + + let (mut genesis_config, _mint_keypair) = create_genesis_config(0); + let feature_balance = + std::cmp::max(genesis_config.rent.minimum_balance(Feature::size_of()), 1); + + // inhibit deprecated rewards sysvar creation altogether + genesis_config.accounts.insert( + feature_set::deprecate_rewards_sysvar::id(), + feature::create_account( + &Feature { + activated_at: Some(0), + }, + feature_balance, + ), + ); + + let bank0 = Bank::new(&genesis_config); + let bank1 = Arc::new(new_from_parent(&Arc::new(bank0))); + + // schedule activation of simple capitalization + bank1.store_account_and_update_capitalization( + &feature_set::simple_capitalization::id(), + &feature::create_account(&Feature { activated_at: None }, feature_balance), + ); + + // 12 is minimum adjusted cap increase in adjust_capitalization_for_existing_specially_retained_accounts + assert_capitalization_diff_with_new_bank( + &bank1, + || Bank::new_from_parent(&bank1, &Pubkey::default(), bank1.first_slot_in_next_epoch()), + |old, new| assert_eq!(old + 12, new), + ); + } + + #[test] + fn test_simple_capitalization_adjustment_full_set() { + solana_logger::setup(); + + let (mut genesis_config, _mint_keypair) = create_genesis_config(0); + let feature_balance = + std::cmp::max(genesis_config.rent.minimum_balance(Feature::size_of()), 1); + + // activate all features but simple capitalization + activate_all_features(&mut genesis_config); + genesis_config + .accounts + .remove(&feature_set::simple_capitalization::id()); + // intentionally create deprecated rewards sysvar creation + genesis_config + .accounts + .remove(&feature_set::deprecate_rewards_sysvar::id()); + + // intentionally create bogus native programs + fn mock_process_instruction( + _program_id: &Pubkey, + _keyed_accounts: &[KeyedAccount], + _data: &[u8], + _invoke_context: &mut dyn InvokeContext, + ) -> std::result::Result<(), solana_sdk::instruction::InstructionError> { + Ok(()) + } + let builtins = Builtins { + genesis_builtins: vec![ + Builtin::new( + "mock bpf", + solana_sdk::bpf_loader::id(), + mock_process_instruction, + ), + Builtin::new( + "mock bpf", + solana_sdk::bpf_loader_deprecated::id(), + mock_process_instruction, + ), + ], + feature_builtins: (vec![]), + }; + + let bank0 = Arc::new(Bank::new_with_paths( + &genesis_config, + Vec::new(), + &[], + None, + Some(&builtins), + )); + // move to next epoch to create now deprecated rewards sysvar intentionally + let bank1 = Arc::new(Bank::new_from_parent( + &bank0, + &Pubkey::default(), + bank0.first_slot_in_next_epoch(), + )); + + // schedule activation of simple capitalization + bank1.store_account_and_update_capitalization( + &feature_set::simple_capitalization::id(), + &feature::create_account(&Feature { activated_at: None }, feature_balance), + ); + + // 16 is maximum adjusted cap increase in adjust_capitalization_for_existing_specially_retained_accounts + assert_capitalization_diff_with_new_bank( + &bank1, + || Bank::new_from_parent(&bank1, &Pubkey::default(), bank1.first_slot_in_next_epoch()), + |old, new| assert_eq!(old + 16, new), + ); + } + #[test] fn test_timestamp_bounding_feature() { let leader_pubkey = solana_sdk::pubkey::new_rand(); diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index 391f55573d..00e3b21c12 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -98,6 +98,10 @@ pub mod filter_stake_delegation_accounts { solana_sdk::declare_id!("GE7fRxmW46K6EmCD9AMZSbnaJ2e3LfqCZzdHi9hmYAgi"); } +pub mod simple_capitalization { + solana_sdk::declare_id!("9r69RnnxABmpcPFfj1yhg4n9YFR2MNaLdKJCC6v3Speb"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -124,6 +128,7 @@ lazy_static! { (stake_program_v2::id(), "solana_stake_program v2"), (rewrite_stake::id(), "rewrite stake"), (filter_stake_delegation_accounts::id(), "filter stake_delegation_accounts #14062"), + (simple_capitalization::id(), "simple capitalization"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter() diff --git a/sdk/src/genesis_config.rs b/sdk/src/genesis_config.rs index 078d302113..58601b6c76 100644 --- a/sdk/src/genesis_config.rs +++ b/sdk/src/genesis_config.rs @@ -149,6 +149,11 @@ impl GenesisConfig { hash(&serialized) } + pub fn disable_cap_altering_features_for_preciseness(&mut self) { + self.accounts + .remove(&crate::feature_set::simple_capitalization::id()); + } + fn genesis_filename(ledger_path: &Path) -> PathBuf { Path::new(ledger_path).join("genesis.bin") }