Skip to content

Commit

Permalink
bank: add migrate_native_program_to_bpf_non_upgradeable
Browse files Browse the repository at this point in the history
  • Loading branch information
buffalojoec committed Jan 11, 2024
1 parent e0bd018 commit 2cb4cbd
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 18 deletions.
12 changes: 5 additions & 7 deletions runtime/src/bank/migrate_native_program/bpf_program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,16 @@ use {
///
/// This struct is used to validate the BPF (non-upgradeable) program's account
/// and data account before the migration is performed.
#[allow(dead_code)] // TODO: Removed in future commit
#[derive(Debug)]
struct BpfProgramConfig {
program_address: Pubkey,
program_account: Account,
total_data_size: usize,
pub struct BpfProgramConfig {
pub program_address: Pubkey,
pub program_account: Account,
pub total_data_size: usize,
}
#[allow(dead_code)] // TODO: Removed in future commit
impl BpfProgramConfig {
/// Creates a new migration config for the given BPF (non-upgradeable)
/// program, validating the BPF program's account and data account
fn new_checked(bank: &Bank, address: &Pubkey) -> Result<Self, MigrateNativeProgramError> {
pub fn new_checked(bank: &Bank, address: &Pubkey) -> Result<Self, MigrateNativeProgramError> {
let program_address = *address;
let program_account: Account = bank
.get_account_with_fixed_root(&program_address)
Expand Down
51 changes: 48 additions & 3 deletions runtime/src/bank/migrate_native_program/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@ mod bpf_upgradeable_program;
pub(crate) mod error;
mod native_program;

use solana_sdk::pubkey::Pubkey;
use {
self::{
bpf_program::BpfProgramConfig, error::MigrateNativeProgramError,
native_program::NativeProgramConfig,
},
super::Bank,
solana_sdk::{account::AccountSharedData, pubkey::Pubkey},
std::sync::atomic::Ordering::Relaxed,
};

/// Enum representing the native programs that can be migrated to BPF
/// programs
Expand All @@ -25,10 +33,9 @@ pub(crate) enum NativeProgram {
Vote,
ZkTokenProof,
}
#[allow(dead_code)] // Code is off the hot path until a migration is due
impl NativeProgram {
/// The program ID of the native program
fn id(&self) -> Pubkey {
pub(crate) fn id(&self) -> Pubkey {
match self {
Self::AddressLookupTable => solana_sdk::address_lookup_table::program::id(),
Self::BpfLoader => solana_sdk::bpf_loader::id(),
Expand Down Expand Up @@ -61,3 +68,41 @@ impl NativeProgram {
}
}
}

/// Migrate a native program to a BPF (non-upgradeable) program using a BPF
/// version of the program deployed at some arbitrary address.
#[allow(dead_code)] // Code is off the hot path until a migration is due
pub(crate) fn migrate_native_program_to_bpf_non_upgradeable(
bank: &Bank,
target_program: NativeProgram,
source_program_address: &Pubkey,
datapoint_name: &'static str,
) -> Result<(), MigrateNativeProgramError> {
datapoint_info!(datapoint_name, ("slot", bank.slot, i64));

let target = NativeProgramConfig::new_checked(bank, target_program)?;
let source = BpfProgramConfig::new_checked(bank, source_program_address)?;

// Burn lamports from the target program account
bank.capitalization
.fetch_sub(target.program_account.lamports, Relaxed);

// Copy the non-upgradeable BPF program's account into the native program's
// address, then clear the source BPF program account
bank.store_account(&target.program_address, &source.program_account);
bank.store_account(&source.program_address, &AccountSharedData::default());

// Update the account data size delta
bank.calculate_and_update_accounts_data_size_delta_off_chain(
target.total_data_size,
source.total_data_size,
);

// Unload the programs from the bank's cache
bank.loaded_programs_cache
.write()
.unwrap()
.remove_programs([*source_program_address, target.program_address].into_iter());

Ok(())
}
14 changes: 6 additions & 8 deletions runtime/src/bank/migrate_native_program/native_program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,17 @@ use {
///
/// This struct is used to validate the native program's account and data
/// account before the migration is performed.
#[allow(dead_code)] // TODO: Removed in future commit
#[derive(Debug)]
struct NativeProgramConfig {
program_address: Pubkey,
program_account: Account,
program_data_address: Pubkey,
total_data_size: usize,
pub struct NativeProgramConfig {
pub program_address: Pubkey,
pub program_account: Account,
pub program_data_address: Pubkey,
pub total_data_size: usize,
}
#[allow(dead_code)] // TODO: Removed in future commit
impl NativeProgramConfig {
/// Creates a new migration config for the given native program,
/// validating the native program's account and data account
fn new_checked(
pub fn new_checked(
bank: &Bank,
native_program: NativeProgram,
) -> Result<Self, MigrateNativeProgramError> {
Expand Down
82 changes: 82 additions & 0 deletions runtime/src/bank/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ use {
},
crate::{
accounts_background_service::{PrunedBanksRequestHandler, SendDroppedBankCallback},
bank::migrate_native_program::{
migrate_native_program_to_bpf_non_upgradeable, NativeProgram,
},
bank_client::BankClient,
bank_forks::BankForks,
epoch_rewards_hasher::hash_rewards_into_partitions,
Expand Down Expand Up @@ -7967,6 +7970,85 @@ fn min_rent_exempt_balance_for_sysvars(bank: &Bank, sysvar_ids: &[Pubkey]) -> u6
.sum()
}

fn setup_program_account_for_tests<T: serde::Serialize>(
bank: &Bank,
address: &Pubkey,
state: &T,
owner: &Pubkey,
executable: bool,
) -> AccountSharedData {
let data = bincode::serialize(&state).unwrap();
let data_len = data.len();
let lamports = bank.get_minimum_balance_for_rent_exemption(data_len);
let account = AccountSharedData::from(Account {
lamports,
owner: *owner,
executable,
data,
..Account::default()
});
bank.store_account_and_update_capitalization(&address, &account);
account
}

#[test_case(NativeProgram::AddressLookupTable)]
#[test_case(NativeProgram::BpfLoader)]
#[test_case(NativeProgram::BpfLoaderUpgradeable)]
#[test_case(NativeProgram::ComputeBudget)]
#[test_case(NativeProgram::Config)]
#[test_case(NativeProgram::Ed25519)]
#[test_case(NativeProgram::FeatureGate)]
#[test_case(NativeProgram::LoaderV4)]
#[test_case(NativeProgram::NativeLoader)]
#[test_case(NativeProgram::Secp256k1)]
#[test_case(NativeProgram::Stake)]
#[test_case(NativeProgram::System)]
#[test_case(NativeProgram::Vote)]
#[test_case(NativeProgram::ZkTokenProof)]
fn test_migrate_native_program_to_bpf_non_upgradeable(target: NativeProgram) {
let bank = create_simple_test_bank(0);

let target_program_address = target.id();
let source_program_address = Pubkey::new_unique();
setup_program_account_for_tests(
&bank,
&source_program_address,
&vec![4u8; 300],
&bpf_loader::id(),
true,
);

let original_source_program_account = bank.get_account(&source_program_address).unwrap();
let expected_capitalization = bank.capitalization()
- bank
.get_account(&target_program_address)
.map(|account| account.lamports())
.unwrap_or_default();

// Perform the migration
migrate_native_program_to_bpf_non_upgradeable(
&bank,
target,
&source_program_address,
"migrate_native_program",
)
.unwrap();

// Assert the new target account is the same as the original source account
let post_migration_target_account = bank.get_account(&target_program_address).unwrap();
assert_eq!(
original_source_program_account,
post_migration_target_account
);

// Assert the source account was cleared
let post_migration_source_account = bank.get_account(&source_program_address);
assert!(post_migration_source_account.is_none());

// Assert the lamports of the target account were burnt
assert_eq!(bank.capitalization(), expected_capitalization);
}

#[test]
fn test_adjust_sysvar_balance_for_rent() {
let bank = create_simple_test_bank(0);
Expand Down

0 comments on commit 2cb4cbd

Please sign in to comment.