Skip to content

Commit

Permalink
bank: migrate_builtin: add bpf upgradeable migration function
Browse files Browse the repository at this point in the history
  • Loading branch information
buffalojoec committed Jan 26, 2024
1 parent e2998fa commit 78f4734
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 5 deletions.
1 change: 0 additions & 1 deletion runtime/src/bank/migrate_builtin/bpf_upgradeable.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#![allow(dead_code)] // TODO: Removed in future commit
use {
super::error::MigrateBuiltinError,
crate::bank::Bank,
Expand Down
1 change: 0 additions & 1 deletion runtime/src/bank/migrate_builtin/builtin.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#![allow(dead_code)] // TODO: Removed in future commit
use {
super::error::MigrateBuiltinError,
crate::{bank::Bank, builtins::Builtin},
Expand Down
3 changes: 3 additions & 0 deletions runtime/src/bank/migrate_builtin/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,7 @@ pub enum MigrateBuiltinError {
/// Arithmetic overflow
#[error("Arithmetic overflow")]
ArithmeticOverflow,
/// Failed to serialize new program account
#[error("Failed to serialize new program account")]
FailedToSerialize,
}
81 changes: 80 additions & 1 deletion runtime/src/bank/migrate_builtin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@ pub(crate) mod error;
use {
crate::{bank::Bank, builtins::Builtin},
bpf::BpfConfig,
bpf_upgradeable::BpfUpgradeableConfig,
builtin::BuiltinConfig,
error::MigrateBuiltinError,
solana_sdk::{account::AccountSharedData, pubkey::Pubkey},
solana_sdk::{
account::{Account, AccountSharedData},
bpf_loader_upgradeable::{UpgradeableLoaderState, ID as BPF_LOADER_UPGRADEABLE_ID},
pubkey::Pubkey,
},
std::sync::atomic::Ordering::Relaxed,
};

Expand Down Expand Up @@ -54,3 +59,77 @@ pub(crate) fn migrate_builtin_to_bpf(

Ok(())
}

/// Create a new `Account` with a pointer to the target's new data account.
///
/// Note the pointer is created manually, as well as the owner and
/// executable values. The rest is inherited from the source program
/// account, including the lamports.
fn create_new_target_program_account(
target: &BuiltinConfig,
source: &BpfUpgradeableConfig,
) -> Result<AccountSharedData, MigrateBuiltinError> {
let state = UpgradeableLoaderState::Program {
programdata_address: target.program_data_address,
};
let data = bincode::serialize(&state).map_err(|_| MigrateBuiltinError::FailedToSerialize)?;
let account = Account {
data,
owner: BPF_LOADER_UPGRADEABLE_ID,
executable: true,
..source.program_account
};
Ok(AccountSharedData::from(account))
}

/// Migrate a built-in program to an upgradeable BPF program using a BPF
/// version of the program deployed at some arbitrary address.
///
/// Note!!!: This function should be used within a feature activation, and the
/// and the feature ID used to activate the feature _must_ also be added to the
/// corresponding builtin's `disabled_feature_id` field.
/// See `runtime/src/builtin.rs`.
#[allow(dead_code)] // Code is off the hot path until a migration is due
pub(crate) fn migrate_builtin_to_bpf_upgradeable(
bank: &Bank,
target_program: &Builtin,
source_program_address: &Pubkey,
datapoint_name: &'static str,
) -> Result<(), MigrateBuiltinError> {
datapoint_info!(datapoint_name, ("slot", bank.slot, i64));

let target = BuiltinConfig::new_checked(bank, target_program)?;
let source = BpfUpgradeableConfig::new_checked(bank, source_program_address)?;

// Attempt serialization first before touching the bank
let new_target_program_account = create_new_target_program_account(&target, &source)?;

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

// Replace the native program account with the created to point to the new data
// account and clear the source program account
bank.store_account(&target.program_address, &new_target_program_account);
bank.store_account(&source.program_address, &AccountSharedData::default());

// Copy the upgradeable BPF program's data account into the native
// program's data address, which is checked to be empty, then clear the
// upgradeable BPF program's data account.
bank.store_account(&target.program_data_address, &source.program_data_account);
bank.store_account(&source.program_data_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(())
}
112 changes: 110 additions & 2 deletions runtime/src/bank/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use {
},
crate::{
accounts_background_service::{PrunedBanksRequestHandler, SendDroppedBankCallback},
bank::migrate_builtin::migrate_builtin_to_bpf,
bank::migrate_builtin::{migrate_builtin_to_bpf, migrate_builtin_to_bpf_upgradeable},
bank_client::BankClient,
bank_forks::BankForks,
builtins::Builtin,
Expand Down Expand Up @@ -58,7 +58,7 @@ use {
},
account_utils::StateMut,
bpf_loader,
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
bpf_loader_upgradeable::{self, get_program_data_address, UpgradeableLoaderState},
client::SyncClient,
clock::{
BankId, Epoch, Slot, UnixTimestamp, DEFAULT_HASHES_PER_TICK, DEFAULT_SLOTS_PER_EPOCH,
Expand Down Expand Up @@ -8087,6 +8087,114 @@ fn test_migrate_builtin_to_bpf(target: Builtin) {
assert_eq!(bank.capitalization(), expected_capitalization);
}

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

let target_program_address = target.program_id();
let (target_program_data_address, _) = get_program_data_address(&target_program_address);

let source_program_address = Pubkey::new_unique();
let (source_program_data_address, _) = get_program_data_address(&source_program_address);

let source_upgrade_authority = Pubkey::new_unique();

setup_program_account_for_tests(
&bank,
&source_program_address,
(
&UpgradeableLoaderState::Program {
programdata_address: source_program_data_address,
},
None,
),
&bpf_loader_upgradeable::id(),
true,
);
setup_program_account_for_tests(
&bank,
&source_program_data_address,
(
&UpgradeableLoaderState::ProgramData {
slot: 0,
upgrade_authority_address: Some(source_upgrade_authority),
},
Some(&[4u8; 200]),
),
&bpf_loader_upgradeable::id(),
false,
);

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

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

// Assert the new target account holds a pointer to its data account
let expected_data = bincode::serialize(&UpgradeableLoaderState::Program {
programdata_address: target_program_data_address,
})
.unwrap();
let post_migration_target_account = bank.get_account(&target_program_address).unwrap();
assert_eq!(post_migration_target_account.data(), &expected_data);

// Assert the target data account is the same as the original source data account
let post_migration_target_data_account =
bank.get_account(&target_program_data_address).unwrap();
assert_eq!(
original_source_program_data_account,
post_migration_target_data_account
);

// Assert the upgrade authority was preserved
let programdata_data_offset = UpgradeableLoaderState::size_of_programdata_metadata();
assert_eq!(
bincode::deserialize::<UpgradeableLoaderState>(
&post_migration_target_data_account.data()[..programdata_data_offset]
)
.unwrap(),
UpgradeableLoaderState::ProgramData {
slot: 0,
upgrade_authority_address: Some(source_upgrade_authority),
}
);

// 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 source data account was cleared
let post_migration_source_data_account = bank.get_account(&source_program_data_address);
assert!(post_migration_source_data_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 78f4734

Please sign in to comment.