From 358d2e0c9fcf270ddb42c88542bb56324312d91c Mon Sep 17 00:00:00 2001 From: Joe Date: Fri, 29 Sep 2023 22:03:28 +0200 Subject: [PATCH] move replace functions to helper module --- runtime/src/bank.rs | 214 +-------------------------- runtime/src/bank/replace_account.rs | 222 ++++++++++++++++++++++++++++ runtime/src/bank/tests.rs | 12 +- 3 files changed, 234 insertions(+), 214 deletions(-) create mode 100644 runtime/src/bank/replace_account.rs diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 2e4f4b8479e1b4..98ce1d5f39816f 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -216,6 +216,7 @@ pub mod bank_hash_details; mod builtin_programs; pub mod epoch_accounts_hash_utils; mod metrics; +mod replace_account; mod serde_snapshot; mod sysvar_cache; #[cfg(test)] @@ -8057,7 +8058,8 @@ impl Bank { } if new_feature_activations.contains(&feature_set::programify_feature_gate_program::id()) { - self.replace_empty_account_with_upgradeable_program( + replace_account::replace_empty_account_with_upgradeable_program( + self, &feature::id(), &inline_feature_gate_program::noop_program::id(), "bank-apply_feature_gate_program", @@ -8205,216 +8207,6 @@ impl Bank { } } - /// Moves one account in place of another - /// `source`: the account to replace with - /// `destination`: the account to be replaced - fn move_account( - &mut self, - source_address: &Pubkey, - source_account: &V, - destination_address: &Pubkey, - destination_account: Option<&U>, - ) where - U: ReadableAccount + Sync + ZeroLamport, - V: ReadableAccount + Sync + ZeroLamport, - { - let (destination_lamports, destination_len) = match destination_account { - Some(destination_account) => ( - destination_account.lamports(), - destination_account.data().len(), - ), - None => (0, 0), - }; - - // Burn lamports in the destination account - self.capitalization.fetch_sub(destination_lamports, Relaxed); - - // Transfer source account to destination account - self.store_account(destination_address, source_account); - - // Clear source account - self.store_account(source_address, &AccountSharedData::default()); - - self.calculate_and_update_accounts_data_size_delta_off_chain( - destination_len, - source_account.data().len(), - ); - } - - /// Use to replace non-upgradeable programs by feature activation - /// `source`: the non-upgradeable program account to replace with - /// `destination`: the non-upgradeable program account to be replaced - #[allow(dead_code)] - fn replace_non_upgradeable_program_account( - &mut self, - source_address: &Pubkey, - destination_address: &Pubkey, - datapoint_name: &'static str, - ) { - if let Some(destination_account) = self.get_account_with_fixed_root(destination_address) { - if let Some(source_account) = self.get_account_with_fixed_root(source_address) { - datapoint_info!(datapoint_name, ("slot", self.slot, i64)); - - self.move_account( - source_address, - &source_account, - destination_address, - Some(&destination_account), - ); - - // Unload a program from the bank's cache - self.loaded_programs_cache - .write() - .unwrap() - .remove_programs([*destination_address].into_iter()); - } else { - warn!( - "Unable to find source program {}. Destination: {}", - source_address, destination_address - ); - datapoint_warn!(datapoint_name, ("slot", self.slot(), i64),); - } - } else { - warn!("Unable to find destination program {}", destination_address); - datapoint_warn!(datapoint_name, ("slot", self.slot(), i64),); - } - } - - /// Use to replace an empty account with a program by feature activation - /// Note: The upgradeable program should have both: - /// - Program account - /// - Program data account - /// `source`: the upgradeable program account to replace with - /// `destination`: the empty account to be replaced - fn replace_empty_account_with_upgradeable_program( - &mut self, - source_address: &Pubkey, - destination_address: &Pubkey, - datapoint_name: &'static str, - ) { - // Must be attempting to replace an empty account with a program - // account _and_ data account - if let Some(source_account) = self.get_account_with_fixed_root(source_address) { - let (destination_data_address, _) = Pubkey::find_program_address( - &[destination_address.as_ref()], - &bpf_loader_upgradeable::id(), - ); - let (source_data_address, _) = Pubkey::find_program_address( - &[source_address.as_ref()], - &bpf_loader_upgradeable::id(), - ); - - // Make sure the data within the source account is the PDA of its - // data account. This also means it has at least the necessary - // lamports for rent. - if let Ok(source_state) = - bincode::deserialize::(source_account.data()) - { - match source_state { - UpgradeableLoaderState::Program { - programdata_address: _, - } => { - if let Some(source_data_account) = - self.get_account_with_fixed_root(&source_data_address) - { - // Make sure the destination account is empty - // We aren't going to check that there isn't a data account at - // the known program-derived address (ie. `destination_data_address`), - // because if it exists, it will be overwritten - if self - .get_account_with_fixed_root(destination_address) - .is_none() - { - let state = UpgradeableLoaderState::Program { - programdata_address: destination_data_address, - }; - if let Ok(data) = bincode::serialize(&state) { - let lamports = - self.get_minimum_balance_for_rent_exemption(data.len()); - let created_program_account = Account { - lamports, - data, - owner: bpf_loader_upgradeable::id(), - executable: true, - rent_epoch: source_account.rent_epoch(), - }; - - datapoint_info!(datapoint_name, ("slot", self.slot, i64)); - let change_in_capitalization = - source_account.lamports().saturating_sub(lamports); - - // Replace the destination data account with the source one - // If the destination data account does not exist, it will be created - // If it does exist, it will be overwritten - self.move_account( - &source_data_address, - &source_data_account, - &destination_data_address, - self.get_account_with_fixed_root(&destination_data_address) - .as_ref(), - ); - - // Write the source data account's PDA into the destination program account - self.move_account( - source_address, - &created_program_account, - destination_address, - None::<&AccountSharedData>, - ); - - // Any remaining lamports in the source program account are burnt - self.capitalization - .fetch_sub(change_in_capitalization, Relaxed); - } else { - error!( - "Unable to serialize program account state. \ - Source program: {} Source data account: {} \ - Destination program: {} Destination data account: {}", - source_address, - source_data_address, - destination_address, - destination_data_address, - ); - datapoint_error!(datapoint_name, ("slot", self.slot(), i64),); - } - } - } else { - warn!( - "Unable to find data account for source program. \ - Source program: {} Source data account: {} \ - Destination program: {} Destination data account: {}", - source_address, - source_data_address, - destination_address, - destination_data_address, - ); - datapoint_warn!(datapoint_name, ("slot", self.slot(), i64),); - } - } - _ => { - warn!( - "Source program account is not an upgradeable program. \ - Source program: {} Source data account: {} \ - Destination program: {} Destination data account: {}", - source_address, - source_data_address, - destination_address, - destination_data_address, - ); - datapoint_warn!(datapoint_name, ("slot", self.slot(), i64),); - return; - } - } - } - } else { - warn!( - "Unable to find source program {}. Destination: {}", - source_address, destination_address - ); - datapoint_warn!(datapoint_name, ("slot", self.slot(), i64),); - } - } - /// Get all the accounts for this bank and calculate stats pub fn get_total_accounts_stats(&self) -> ScanResult { let accounts = self.get_all_accounts()?; diff --git a/runtime/src/bank/replace_account.rs b/runtime/src/bank/replace_account.rs new file mode 100644 index 00000000000000..e5bf6855c1b12d --- /dev/null +++ b/runtime/src/bank/replace_account.rs @@ -0,0 +1,222 @@ +use { + super::Bank, + log::*, + solana_accounts_db::accounts_index::ZeroLamport, + solana_sdk::{ + account::{Account, AccountSharedData, ReadableAccount}, + bpf_loader_upgradeable::{self, UpgradeableLoaderState}, + pubkey::Pubkey, + }, + std::sync::atomic::Ordering::Relaxed, +}; + +/// Moves one account in place of another +/// `source`: the account to replace with +/// `destination`: the account to be replaced +fn move_account( + bank: &mut Bank, + source_address: &Pubkey, + source_account: &V, + destination_address: &Pubkey, + destination_account: Option<&U>, +) where + U: ReadableAccount + Sync + ZeroLamport, + V: ReadableAccount + Sync + ZeroLamport, +{ + let (destination_lamports, destination_len) = match destination_account { + Some(destination_account) => ( + destination_account.lamports(), + destination_account.data().len(), + ), + None => (0, 0), + }; + + // Burn lamports in the destination account + bank.capitalization.fetch_sub(destination_lamports, Relaxed); + + // Transfer source account to destination account + bank.store_account(destination_address, source_account); + + // Clear source account + bank.store_account(source_address, &AccountSharedData::default()); + + bank.calculate_and_update_accounts_data_size_delta_off_chain( + destination_len, + source_account.data().len(), + ); +} + +/// Use to replace non-upgradeable programs by feature activation +/// `source`: the non-upgradeable program account to replace with +/// `destination`: the non-upgradeable program account to be replaced +#[allow(dead_code)] +pub(crate) fn replace_non_upgradeable_program_account( + bank: &mut Bank, + source_address: &Pubkey, + destination_address: &Pubkey, + datapoint_name: &'static str, +) { + if let Some(destination_account) = bank.get_account_with_fixed_root(destination_address) { + if let Some(source_account) = bank.get_account_with_fixed_root(source_address) { + datapoint_info!(datapoint_name, ("slot", bank.slot, i64)); + + move_account( + bank, + source_address, + &source_account, + destination_address, + Some(&destination_account), + ); + + // Unload a program from the bank's cache + bank.loaded_programs_cache + .write() + .unwrap() + .remove_programs([*destination_address].into_iter()); + } else { + warn!( + "Unable to find source program {}. Destination: {}", + source_address, destination_address + ); + datapoint_warn!(datapoint_name, ("slot", bank.slot(), i64),); + } + } else { + warn!("Unable to find destination program {}", destination_address); + datapoint_warn!(datapoint_name, ("slot", bank.slot(), i64),); + } +} + +/// Use to replace an empty account with a program by feature activation +/// Note: The upgradeable program should have both: +/// - Program account +/// - Program data account +/// `source`: the upgradeable program account to replace with +/// `destination`: the empty account to be replaced +pub(crate) fn replace_empty_account_with_upgradeable_program( + bank: &mut Bank, + source_address: &Pubkey, + destination_address: &Pubkey, + datapoint_name: &'static str, +) { + // Must be attempting to replace an empty account with a program + // account _and_ data account + if let Some(source_account) = bank.get_account_with_fixed_root(source_address) { + let (destination_data_address, _) = Pubkey::find_program_address( + &[destination_address.as_ref()], + &bpf_loader_upgradeable::id(), + ); + let (source_data_address, _) = + Pubkey::find_program_address(&[source_address.as_ref()], &bpf_loader_upgradeable::id()); + + // Make sure the data within the source account is the PDA of its + // data account. This also means it has at least the necessary + // lamports for rent. + if let Ok(source_state) = + bincode::deserialize::(source_account.data()) + { + match source_state { + UpgradeableLoaderState::Program { + programdata_address: _, + } => { + if let Some(source_data_account) = + bank.get_account_with_fixed_root(&source_data_address) + { + // Make sure the destination account is empty + // We aren't going to check that there isn't a data account at + // the known program-derived address (ie. `destination_data_address`), + // because if it exists, it will be overwritten + if bank + .get_account_with_fixed_root(destination_address) + .is_none() + { + let state = UpgradeableLoaderState::Program { + programdata_address: destination_data_address, + }; + if let Ok(data) = bincode::serialize(&state) { + let lamports = + bank.get_minimum_balance_for_rent_exemption(data.len()); + let created_program_account = Account { + lamports, + data, + owner: bpf_loader_upgradeable::id(), + executable: true, + rent_epoch: source_account.rent_epoch(), + }; + + datapoint_info!(datapoint_name, ("slot", bank.slot, i64)); + let change_in_capitalization = + source_account.lamports().saturating_sub(lamports); + + // Replace the destination data account with the source one + // If the destination data account does not exist, it will be created + // If it does exist, it will be overwritten + move_account( + bank, + &source_data_address, + &source_data_account, + &destination_data_address, + bank.get_account_with_fixed_root(&destination_data_address) + .as_ref(), + ); + + // Write the source data account's PDA into the destination program account + move_account( + bank, + source_address, + &created_program_account, + destination_address, + None::<&AccountSharedData>, + ); + + // Any remaining lamports in the source program account are burnt + bank.capitalization + .fetch_sub(change_in_capitalization, Relaxed); + } else { + error!( + "Unable to serialize program account state. \ + Source program: {} Source data account: {} \ + Destination program: {} Destination data account: {}", + source_address, + source_data_address, + destination_address, + destination_data_address, + ); + datapoint_error!(datapoint_name, ("slot", bank.slot(), i64),); + } + } + } else { + warn!( + "Unable to find data account for source program. \ + Source program: {} Source data account: {} \ + Destination program: {} Destination data account: {}", + source_address, + source_data_address, + destination_address, + destination_data_address, + ); + datapoint_warn!(datapoint_name, ("slot", bank.slot(), i64),); + } + } + _ => { + warn!( + "Source program account is not an upgradeable program. \ + Source program: {} Source data account: {} \ + Destination program: {} Destination data account: {}", + source_address, + source_data_address, + destination_address, + destination_data_address, + ); + datapoint_warn!(datapoint_name, ("slot", bank.slot(), i64),); + return; + } + } + } + } else { + warn!( + "Unable to find source program {}. Destination: {}", + source_address, destination_address + ); + datapoint_warn!(datapoint_name, ("slot", bank.slot(), i64),); + } +} diff --git a/runtime/src/bank/tests.rs b/runtime/src/bank/tests.rs index f23d621bbff3ea..a2672e809d2a2a 100644 --- a/runtime/src/bank/tests.rs +++ b/runtime/src/bank/tests.rs @@ -8,6 +8,9 @@ use { }, crate::{ accounts_background_service::{PrunedBanksRequestHandler, SendDroppedBankCallback}, + bank::replace_account::{ + replace_empty_account_with_upgradeable_program, replace_non_upgradeable_program_account, + }, bank_client::BankClient, epoch_rewards_hasher::hash_rewards_into_partitions, genesis_utils::{ @@ -8063,7 +8066,8 @@ fn test_replace_non_upgradeable_program_account() { let original_capitalization = bank.capitalization(); - bank.replace_non_upgradeable_program_account( + replace_non_upgradeable_program_account( + &mut bank, &source, &destination, "bank-apply_program_replacement", @@ -8179,7 +8183,8 @@ fn test_replace_empty_account_with_upgradeable_program_success( let original_capitalization = bank.capitalization(); // Do the replacement - bank.replace_empty_account_with_upgradeable_program( + replace_empty_account_with_upgradeable_program( + &mut bank, &source, &destination, "bank-apply_empty_account_replacement_for_program", @@ -8309,7 +8314,8 @@ fn test_replace_empty_account_with_upgradeable_program_fail_when_account_exists( let original_capitalization = bank.capitalization(); // Attempt the replacement - bank.replace_empty_account_with_upgradeable_program( + replace_empty_account_with_upgradeable_program( + &mut bank, &source, &destination, "bank-apply_empty_account_replacement_for_program",