Skip to content

Commit

Permalink
Runtime: Core BPF Migration: Struct for loading and checking builtin …
Browse files Browse the repository at this point in the history
…program accounts (#331)

* runtime: core_bpf_migration: add builtin config

* runtime: core_bpf_migration: add builtin config tests

* some renaming feedback
  • Loading branch information
buffalojoec authored Mar 22, 2024
1 parent f799c9f commit 62d49f1
Show file tree
Hide file tree
Showing 4 changed files with 278 additions and 0 deletions.
18 changes: 18 additions & 0 deletions runtime/src/bank/builtins/core_bpf_migration/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use {solana_sdk::pubkey::Pubkey, thiserror::Error};

/// Errors returned by a Core BPF migration.
#[derive(Debug, Error, PartialEq)]
pub enum CoreBpfMigrationError {
/// Account not found
#[error("Account not found: {0:?}")]
AccountNotFound(Pubkey),
/// Account exists
#[error("Account exists: {0:?}")]
AccountExists(Pubkey),
/// Incorrect account owner
#[error("Incorrect account owner for {0:?}")]
IncorrectOwner(Pubkey),
/// Program has a data account
#[error("Data account exists for program {0:?}")]
ProgramHasDataAccount(Pubkey),
}
8 changes: 8 additions & 0 deletions runtime/src/bank/builtins/core_bpf_migration/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#![allow(dead_code)] // Removed in later commit
pub(crate) mod error;
mod target_builtin;

pub(crate) enum CoreBpfMigrationTargetType {
Builtin,
Stateless,
}
251 changes: 251 additions & 0 deletions runtime/src/bank/builtins/core_bpf_migration/target_builtin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
use {
super::{error::CoreBpfMigrationError, CoreBpfMigrationTargetType},
crate::bank::Bank,
solana_sdk::{
account::{AccountSharedData, ReadableAccount},
bpf_loader_upgradeable::get_program_data_address,
native_loader::ID as NATIVE_LOADER_ID,
pubkey::Pubkey,
},
};

/// Used to validate a built-in program's account before migrating to Core BPF.
#[derive(Debug)]
pub(crate) struct TargetProgramBuiltin {
pub program_address: Pubkey,
pub program_account: AccountSharedData,
pub program_data_address: Pubkey,
pub total_data_size: usize,
}

impl TargetProgramBuiltin {
/// Create a new migration configuration for a built-in program.
pub(crate) fn new_checked(
bank: &Bank,
program_address: &Pubkey,
migration_target: &CoreBpfMigrationTargetType,
) -> Result<Self, CoreBpfMigrationError> {
let program_account = match migration_target {
CoreBpfMigrationTargetType::Builtin => {
// The program account should exist.
let program_account = bank
.get_account_with_fixed_root(program_address)
.ok_or(CoreBpfMigrationError::AccountNotFound(*program_address))?;

// The program account should be owned by the native loader.
if program_account.owner() != &NATIVE_LOADER_ID {
return Err(CoreBpfMigrationError::IncorrectOwner(*program_address));
}

program_account
}
CoreBpfMigrationTargetType::Stateless => {
// The program account should _not_ exist.
if bank.get_account_with_fixed_root(program_address).is_some() {
return Err(CoreBpfMigrationError::AccountExists(*program_address));
}

AccountSharedData::default()
}
};

let program_data_address = get_program_data_address(program_address);

// The program data account should not exist.
if bank
.get_account_with_fixed_root(&program_data_address)
.is_some()
{
return Err(CoreBpfMigrationError::ProgramHasDataAccount(
*program_address,
));
}

// The total data size is the size of the program account's data.
let total_data_size = program_account.data().len();

Ok(Self {
program_address: *program_address,
program_account,
program_data_address,
total_data_size,
})
}
}

#[cfg(test)]
mod tests {
use {
super::*,
crate::bank::{tests::create_simple_test_bank, ApplyFeatureActivationsCaller},
solana_sdk::{
account::Account,
bpf_loader_upgradeable::{UpgradeableLoaderState, ID as BPF_LOADER_UPGRADEABLE_ID},
feature, feature_set,
},
test_case::test_case,
};

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

#[test_case(solana_sdk::address_lookup_table::program::id(), None)]
#[test_case(solana_sdk::bpf_loader::id(), None)]
#[test_case(solana_sdk::bpf_loader_deprecated::id(), None)]
#[test_case(solana_sdk::bpf_loader_upgradeable::id(), None)]
#[test_case(solana_sdk::compute_budget::id(), None)]
#[test_case(solana_config_program::id(), None)]
#[test_case(solana_stake_program::id(), None)]
#[test_case(solana_system_program::id(), None)]
#[test_case(solana_vote_program::id(), None)]
#[test_case(
solana_sdk::loader_v4::id(),
Some(feature_set::enable_program_runtime_v2_and_loader_v4::id())
)]
#[test_case(
solana_zk_token_sdk::zk_token_proof_program::id(),
Some(feature_set::zk_token_sdk_enabled::id())
)]
fn test_target_program_builtin(program_address: Pubkey, activation_feature: Option<Pubkey>) {
let migration_target = CoreBpfMigrationTargetType::Builtin;
let mut bank = create_simple_test_bank(0);

if let Some(feature_id) = activation_feature {
// Activate the feature to enable the built-in program
bank.store_account(
&feature_id,
&feature::create_account(
&feature::Feature { activated_at: None },
bank.get_minimum_balance_for_rent_exemption(feature::Feature::size_of()),
),
);
bank.apply_feature_activations(ApplyFeatureActivationsCaller::NewFromParent, false);
}

let program_account = bank.get_account_with_fixed_root(&program_address).unwrap();
let program_data_address = get_program_data_address(&program_address);

// Success
let builtin_config =
TargetProgramBuiltin::new_checked(&bank, &program_address, &migration_target).unwrap();
assert_eq!(builtin_config.program_address, program_address);
assert_eq!(builtin_config.program_account, program_account);
assert_eq!(builtin_config.program_data_address, program_data_address);
assert_eq!(builtin_config.total_data_size, program_account.data().len());

// Fail if the program account is not owned by the native loader
store_account(
&bank,
&program_address,
&String::from("some built-in program"),
true,
&Pubkey::new_unique(), // Not the native loader
);
assert_eq!(
TargetProgramBuiltin::new_checked(&bank, &program_address, &migration_target)
.unwrap_err(),
CoreBpfMigrationError::IncorrectOwner(program_address)
);

// Fail if the program data account exists
store_account(
&bank,
&program_address,
&program_account.data(),
program_account.executable(),
program_account.owner(),
);
store_account(
&bank,
&program_data_address,
&UpgradeableLoaderState::ProgramData {
slot: 0,
upgrade_authority_address: Some(Pubkey::new_unique()),
},
false,
&BPF_LOADER_UPGRADEABLE_ID,
);
assert_eq!(
TargetProgramBuiltin::new_checked(&bank, &program_address, &migration_target)
.unwrap_err(),
CoreBpfMigrationError::ProgramHasDataAccount(program_address)
);

// Fail if the program account does not exist
bank.store_account_and_update_capitalization(
&program_address,
&AccountSharedData::default(),
);
assert_eq!(
TargetProgramBuiltin::new_checked(&bank, &program_address, &migration_target)
.unwrap_err(),
CoreBpfMigrationError::AccountNotFound(program_address)
);
}

#[test_case(solana_sdk::feature::id())]
#[test_case(solana_sdk::native_loader::id())]
fn test_target_program_stateless_builtin(program_address: Pubkey) {
let migration_target = CoreBpfMigrationTargetType::Stateless;
let bank = create_simple_test_bank(0);

let program_account = AccountSharedData::default();
let program_data_address = get_program_data_address(&program_address);

// Success
let builtin_config =
TargetProgramBuiltin::new_checked(&bank, &program_address, &migration_target).unwrap();
assert_eq!(builtin_config.program_address, program_address);
assert_eq!(builtin_config.program_account, program_account);
assert_eq!(builtin_config.program_data_address, program_data_address);
assert_eq!(builtin_config.total_data_size, program_account.data().len());

// Fail if the program data account exists
store_account(
&bank,
&program_data_address,
&UpgradeableLoaderState::ProgramData {
slot: 0,
upgrade_authority_address: Some(Pubkey::new_unique()),
},
false,
&BPF_LOADER_UPGRADEABLE_ID,
);
assert_eq!(
TargetProgramBuiltin::new_checked(&bank, &program_address, &migration_target)
.unwrap_err(),
CoreBpfMigrationError::ProgramHasDataAccount(program_address)
);

// Fail if the program account exists
store_account(
&bank,
&program_address,
&String::from("some built-in program"),
true,
&NATIVE_LOADER_ID,
);
assert_eq!(
TargetProgramBuiltin::new_checked(&bank, &program_address, &migration_target)
.unwrap_err(),
CoreBpfMigrationError::AccountExists(program_address)
);
}
}
1 change: 1 addition & 0 deletions runtime/src/bank/builtins/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub(crate) mod core_bpf_migration;
pub mod prototypes;

pub use prototypes::{BuiltinPrototype, StatelessBuiltinPrototype};
Expand Down

0 comments on commit 62d49f1

Please sign in to comment.