Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions feature-set/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1199,6 +1199,10 @@ pub mod replace_spl_token_with_p_token {
Pubkey::from_str_const("ptokNfvuU7terQ2r2452RzVXB3o4GT33yPWo1fUkkZ2");
}

pub mod relax_programdata_account_check_migration {
solana_pubkey::declare_id!("rexav5eNTUSNT1K2N7cfRjnthwhcP5BC25v2tA4rW4h");
}

pub static FEATURE_NAMES: LazyLock<AHashMap<Pubkey, &'static str>> = LazyLock::new(|| {
[
(secp256k1_program_enabled::id(), "secp256k1 program"),
Expand Down Expand Up @@ -2150,6 +2154,10 @@ pub static FEATURE_NAMES: LazyLock<AHashMap<Pubkey, &'static str>> = LazyLock::n
replace_spl_token_with_p_token::id(),
"SIMD-0266: Efficient Token program",
),
(
relax_programdata_account_check_migration::id(),
"SIMD-0444: Relax program data account check in migration",
),
/*************** ADD NEW FEATURES HERE ***************/
]
.iter()
Expand Down
15 changes: 11 additions & 4 deletions runtime/src/bank.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ use {
accounts_lt_hash::{CacheValue as AccountsLtHashCacheValue, Stats as AccountsLtHashStats},
agave_feature_set::{
self as feature_set, increase_cpi_account_info_limit, raise_cpi_nesting_limit_to_8,
FeatureSet,
relax_programdata_account_check_migration, FeatureSet,
},
agave_precompiles::{get_precompile, get_precompiles, is_precompile},
agave_reserved_account_keys::ReservedAccountKeys,
Expand Down Expand Up @@ -5386,6 +5386,8 @@ impl Bank {
if let Err(e) = self.upgrade_loader_v2_program_with_loader_v3_program(
&feature_set::replace_spl_token_with_p_token::SPL_TOKEN_PROGRAM_ID,
&feature_set::replace_spl_token_with_p_token::PTOKEN_PROGRAM_BUFFER,
self.feature_set
.is_active(&relax_programdata_account_check_migration::id()),
"replace_spl_token_with_p_token",
) {
warn!(
Expand Down Expand Up @@ -5420,9 +5422,12 @@ impl Bank {
// activation, perform the migration which will remove it from
// the builtins list and the cache.
if new_feature_activations.contains(&core_bpf_migration_config.feature_id) {
if let Err(e) = self
.migrate_builtin_to_core_bpf(&builtin.program_id, core_bpf_migration_config)
{
if let Err(e) = self.migrate_builtin_to_core_bpf(
&builtin.program_id,
core_bpf_migration_config,
self.feature_set
.is_active(&relax_programdata_account_check_migration::id()),
) {
warn!(
"Failed to migrate builtin {} to Core BPF: {}",
builtin.name, e
Expand All @@ -5441,6 +5446,8 @@ impl Bank {
if let Err(e) = self.migrate_builtin_to_core_bpf(
&stateless_builtin.program_id,
core_bpf_migration_config,
self.feature_set
.is_active(&relax_programdata_account_check_migration::id()),
) {
warn!(
"Failed to migrate stateless builtin {} to Core BPF: {}",
Expand Down
201 changes: 190 additions & 11 deletions runtime/src/bank/builtins/core_bpf_migration/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,11 +216,16 @@ impl Bank {
&mut self,
builtin_program_id: &Pubkey,
config: &CoreBpfMigrationConfig,
allow_prefunded: bool,
) -> Result<(), CoreBpfMigrationError> {
datapoint_info!(config.datapoint_name, ("slot", self.slot, i64));

let target =
TargetBuiltin::new_checked(self, builtin_program_id, &config.migration_target)?;
let target = TargetBuiltin::new_checked(
self,
builtin_program_id,
&config.migration_target,
allow_prefunded,
)?;
let source = if let Some(expected_hash) = config.verified_build_hash {
SourceBuffer::new_checked_with_verified_build_hash(
self,
Expand Down Expand Up @@ -261,12 +266,15 @@ impl Bank {

// Calculate the lamports to burn.
// The target program account will be replaced, so burn its lamports.
// The target program data account might have lamports if it existed,
// so burn its lamports if any.
// The source buffer account will be cleared, so burn its lamports.
// The two new program accounts will need to be funded.
let lamports_to_burn = checked_add(
target.program_account.lamports(),
source.buffer_account.lamports(),
)?;
)
.and_then(|v| checked_add(v, target.program_data_account_lamports))?;
let lamports_to_fund = checked_add(
new_target_program_account.lamports(),
new_target_program_data_account.lamports(),
Expand Down Expand Up @@ -378,6 +386,7 @@ impl Bank {
/// self.upgrade_loader_v2_program_with_loader_v3_program(
/// &bpf_loader_v2_program_address,
/// &source_buffer_address,
/// true,
/// "test_upgrade_loader_v2_program_with_loader_v3_program",
/// );
/// }
Expand All @@ -389,11 +398,13 @@ impl Bank {
&mut self,
loader_v2_bpf_program_address: &Pubkey,
source_buffer_address: &Pubkey,
allow_prefunded: bool,
datapoint_name: &'static str,
) -> Result<(), CoreBpfMigrationError> {
datapoint_info!(datapoint_name, ("slot", self.slot, i64));

let target = TargetBpfV2::new_checked(self, loader_v2_bpf_program_address)?;
let target =
TargetBpfV2::new_checked(self, loader_v2_bpf_program_address, allow_prefunded)?;
let source = SourceBuffer::new_checked(self, source_buffer_address)?;

// Attempt serialization first before modifying the bank.
Expand Down Expand Up @@ -428,12 +439,15 @@ impl Bank {

// Calculate the lamports to burn.
// The target program account will be replaced, so burn its lamports.
// The target program data account might have lamports if it existed,
// so burn its lamports if any.
// The source buffer account will be cleared, so burn its lamports.
// The two new program accounts will need to be funded.
let lamports_to_burn = checked_add(
target.program_account.lamports(),
source.buffer_account.lamports(),
)?;
)
.and_then(|v| checked_add(v, target.program_data_account_lamports))?;
let lamports_to_fund = checked_add(
new_target_program_account.lamports(),
new_target_program_data_account.lamports(),
Expand Down Expand Up @@ -511,7 +525,7 @@ pub(crate) mod tests {
solana_native_token::LAMPORTS_PER_SOL,
solana_program_runtime::loaded_programs::{ProgramCacheEntry, ProgramCacheEntryType},
solana_pubkey::Pubkey,
solana_sdk_ids::{bpf_loader, bpf_loader_upgradeable, native_loader},
solana_sdk_ids::{bpf_loader, bpf_loader_upgradeable, native_loader, system_program},
solana_signer::Signer,
solana_transaction::Transaction,
std::{fs::File, io::Read, sync::Arc},
Expand Down Expand Up @@ -786,7 +800,7 @@ pub(crate) mod tests {

// Perform the migration.
let migration_slot = bank.slot();
bank.migrate_builtin_to_core_bpf(&builtin_id, &core_bpf_migration_config)
bank.migrate_builtin_to_core_bpf(&builtin_id, &core_bpf_migration_config, true)
.unwrap();

// Run the post-migration program checks.
Expand Down Expand Up @@ -851,7 +865,7 @@ pub(crate) mod tests {

// Perform the migration.
let migration_slot = bank.slot();
bank.migrate_builtin_to_core_bpf(&builtin_id, &core_bpf_migration_config)
bank.migrate_builtin_to_core_bpf(&builtin_id, &core_bpf_migration_config, true)
.unwrap();

// Run the post-migration program checks.
Expand Down Expand Up @@ -918,7 +932,7 @@ pub(crate) mod tests {
};

assert_matches!(
bank.migrate_builtin_to_core_bpf(&builtin_id, &core_bpf_migration_config)
bank.migrate_builtin_to_core_bpf(&builtin_id, &core_bpf_migration_config, true)
.unwrap_err(),
CoreBpfMigrationError::UpgradeAuthorityMismatch(_, _)
)
Expand Down Expand Up @@ -972,7 +986,7 @@ pub(crate) mod tests {
};

assert_matches!(
bank.migrate_builtin_to_core_bpf(&builtin_id, &core_bpf_migration_config)
bank.migrate_builtin_to_core_bpf(&builtin_id, &core_bpf_migration_config, true)
.unwrap_err(),
CoreBpfMigrationError::BuildHashMismatch(_, _)
)
Expand Down Expand Up @@ -1034,7 +1048,7 @@ pub(crate) mod tests {
datapoint_name: "test_migrate_builtin",
};

bank.migrate_builtin_to_core_bpf(&builtin_id, &core_bpf_migration_config)
bank.migrate_builtin_to_core_bpf(&builtin_id, &core_bpf_migration_config, true)
.unwrap();

let program_data_address = get_program_data_address(&builtin_id);
Expand Down Expand Up @@ -1804,6 +1818,7 @@ pub(crate) mod tests {
bank.upgrade_loader_v2_program_with_loader_v3_program(
&bpf_loader_v2_program_address,
&source_buffer_address,
true,
"test_upgrade_loader_v2_program_with_loader_v3_program",
)
.unwrap();
Expand Down Expand Up @@ -1884,6 +1899,7 @@ pub(crate) mod tests {
bank.upgrade_loader_v2_program_with_loader_v3_program(
&bpf_loader_v2_program_address,
&source_buffer_address,
true,
"test_upgrade_loader_v2_program_with_loader_v3_program",
)
.unwrap_err(),
Expand Down Expand Up @@ -2081,6 +2097,7 @@ pub(crate) mod tests {
bank.upgrade_loader_v2_program_with_loader_v3_program(
&bpf_loader_v2_program_address,
&source_buffer_address,
true,
"test_upgrade_loader_v2_program_with_loader_v3_program",
)
.unwrap();
Expand Down Expand Up @@ -2151,4 +2168,166 @@ pub(crate) mod tests {
test_context.run_program_checks(&roundtrip_bank, upgrade_slot);
assert_eq!(bank, roundtrip_bank);
}

#[test]
fn test_replace_spl_token_with_p_token_and_funded_program_data_account_e2e() {
let (mut genesis_config, mint_keypair) =
create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
let slots_per_epoch = 32;
genesis_config.epoch_schedule =
EpochSchedule::custom(slots_per_epoch, slots_per_epoch, false);

let mut root_bank = Bank::new_for_tests(&genesis_config);

let feature_id = agave_feature_set::replace_spl_token_with_p_token::id();
let program_id = agave_feature_set::replace_spl_token_with_p_token::SPL_TOKEN_PROGRAM_ID;
let source_buffer_address =
agave_feature_set::replace_spl_token_with_p_token::PTOKEN_PROGRAM_BUFFER;

// Set up a mock BPF loader v2 program.
{
let program_account = mock_bpf_loader_v2_program(&root_bank);
root_bank.store_account_and_update_capitalization(&program_id, &program_account);
assert_eq!(
&root_bank.get_account(&program_id).unwrap(),
&program_account
);
};

// Set up the CPI mockup to test CPI'ing to the migrated program.
let cpi_program_id = Pubkey::new_unique();
let cpi_program_name = "mock_cpi_program";
root_bank.add_builtin(
cpi_program_id,
cpi_program_name,
ProgramCacheEntry::new_builtin(0, cpi_program_name.len(), cpi_mockup::Entrypoint::vm),
);

// Add the feature to the bank's inactive feature set.
// Note this will add the feature ID if it doesn't exist.
let mut feature_set = FeatureSet::all_enabled();
feature_set.deactivate(&feature_id);
root_bank.feature_set = Arc::new(feature_set);

// Initialize the source buffer account.
let test_context = TestContext::new(&root_bank, &program_id, &source_buffer_address, None);

// Fund the program data account so it will appear as an existing account.
let program_data_account = AccountSharedData::new(1_000_000_000, 0, &system_program::ID);
root_bank.store_account_and_update_capitalization(
&get_program_data_address(&program_id),
&program_data_account,
);

// Activate the feature and run the necessary checks.
activate_feature_and_run_checks(
root_bank,
&test_context,
&program_id,
&feature_id,
&source_buffer_address,
&mint_keypair,
slots_per_epoch,
&cpi_program_id,
);
}

#[test]
fn test_replace_spl_token_with_p_token_and_existing_program_data_account_failure() {
let (mut genesis_config, _mint_keypair) =
create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
let slots_per_epoch = 32;
genesis_config.epoch_schedule =
EpochSchedule::custom(slots_per_epoch, slots_per_epoch, false);

let mut root_bank = Bank::new_for_tests(&genesis_config);

let feature_id = agave_feature_set::replace_spl_token_with_p_token::id();
let program_id = agave_feature_set::replace_spl_token_with_p_token::SPL_TOKEN_PROGRAM_ID;
let source_buffer_address =
agave_feature_set::replace_spl_token_with_p_token::PTOKEN_PROGRAM_BUFFER;

// Set up a mock BPF loader v2 program.
let program_account = mock_bpf_loader_v2_program(&root_bank);
root_bank.store_account_and_update_capitalization(&program_id, &program_account);
assert_eq!(
&root_bank.get_account(&program_id).unwrap(),
&program_account
);

// Set up the CPI mockup to test CPI'ing to the migrated program.
let cpi_program_id = Pubkey::new_unique();
let cpi_program_name = "mock_cpi_program";
root_bank.add_builtin(
cpi_program_id,
cpi_program_name,
ProgramCacheEntry::new_builtin(0, cpi_program_name.len(), cpi_mockup::Entrypoint::vm),
);

// Add the feature to the bank's inactive feature set.
// Note this will add the feature ID if it doesn't exist.
let mut feature_set = FeatureSet::all_enabled();
feature_set.deactivate(&feature_id);
root_bank.feature_set = Arc::new(feature_set);

// Initialize the source buffer account.
let _test_context = TestContext::new(&root_bank, &program_id, &source_buffer_address, None);

// Create the program data account to simulate existing account owned by
// the upgradeable loader.
let program_data_account =
AccountSharedData::new(1_000_000_000, 0, &bpf_loader_upgradeable::ID);
root_bank.store_account_and_update_capitalization(
&get_program_data_address(&program_id),
&program_data_account,
);

// Activate the feature and run the necessary checks.
let (bank, bank_forks) = root_bank.wrap_with_bank_forks_for_tests();

// Advance to the next epoch without activating the feature.
let mut first_slot_in_next_epoch = slots_per_epoch + 1;
let bank = Bank::new_from_parent_with_bank_forks(
&bank_forks,
bank,
&Pubkey::default(),
first_slot_in_next_epoch,
);

// Assert the feature was not activated and the program was not
// migrated.
assert!(!bank.feature_set.is_active(&feature_id));
assert!(bank.get_account(&source_buffer_address).is_some());

// Store the account to activate the feature.
bank.store_account_and_update_capitalization(
&feature_id,
&feature::create_account(&Feature::default(), 42),
);

// Advance the bank to cross the epoch boundary and activate the
// feature.
goto_end_of_slot(bank.clone());
first_slot_in_next_epoch += slots_per_epoch;
let _migration_slot = first_slot_in_next_epoch;
let bank = Bank::new_from_parent_with_bank_forks(
&bank_forks,
bank,
&Pubkey::default(),
first_slot_in_next_epoch,
);

// Check that the feature was activated.
assert!(bank.feature_set.is_active(&feature_id));
// The program should still be owned by the bpf loader v2.
let program_account = bank.get_account(&program_id).unwrap();
assert_eq!(program_account.owner(), &bpf_loader::id());
// The program data should have zero data and still have
// 1_000_000_000 lamports.
let program_data_account = bank
.get_account(&get_program_data_address(&program_id))
.unwrap();
assert_eq!(program_data_account.data().len(), 0);
assert_eq!(program_data_account.lamports(), 1_000_000_000);
}
}
Loading
Loading