From baef6458602716548246e0ba9c492b6026357629 Mon Sep 17 00:00:00 2001 From: Joe C Date: Wed, 7 Feb 2024 12:04:08 -0600 Subject: [PATCH 1/9] runtime: builtins: move to new bank submodule --- runtime/src/bank.rs | 7 ++- .../src/{builtins.rs => bank/builtins/mod.rs} | 43 ++----------------- runtime/src/bank/builtins/prototypes.rs | 38 ++++++++++++++++ runtime/src/lib.rs | 1 - runtime/src/serde_snapshot.rs | 3 +- runtime/src/snapshot_bank_utils.rs | 3 +- runtime/src/snapshot_minimizer.rs | 5 ++- 7 files changed, 52 insertions(+), 48 deletions(-) rename runtime/src/{builtins.rs => bank/builtins/mod.rs} (65%) create mode 100644 runtime/src/bank/builtins/prototypes.rs diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 84c9e2093ebf50..e694e259feb249 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -42,9 +42,11 @@ use solana_sdk::recent_blockhashes_account; pub use solana_sdk::reward_type::RewardType; use { crate::{ - bank::metrics::*, + bank::{ + builtins::{BuiltinPrototype, BUILTINS}, + metrics::*, + }, bank_forks::BankForks, - builtins::{BuiltinPrototype, BUILTINS}, epoch_rewards_hasher::hash_rewards_into_partitions, epoch_stakes::{EpochStakes, NodeVoteAccounts}, installed_scheduler_pool::{BankWithScheduler, InstalledSchedulerRwLock}, @@ -207,6 +209,7 @@ struct VerifyAccountsHashConfig { mod address_lookup_table; pub mod bank_hash_details; mod builtin_programs; +pub mod builtins; pub mod epoch_accounts_hash_utils; mod fee_distribution; mod metrics; diff --git a/runtime/src/builtins.rs b/runtime/src/bank/builtins/mod.rs similarity index 65% rename from runtime/src/builtins.rs rename to runtime/src/bank/builtins/mod.rs index 2c7c36fa0ec415..38141b13b43f77 100644 --- a/runtime/src/builtins.rs +++ b/runtime/src/bank/builtins/mod.rs @@ -1,44 +1,7 @@ -use { - solana_program_runtime::invoke_context::BuiltinFunctionWithContext, - solana_sdk::{ - bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, feature_set, pubkey::Pubkey, - }, -}; - -/// Transitions of built-in programs at epoch bondaries when features are activated. -pub struct BuiltinPrototype { - pub feature_id: Option, - pub program_id: Pubkey, - pub name: &'static str, - pub entrypoint: BuiltinFunctionWithContext, -} - -impl std::fmt::Debug for BuiltinPrototype { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - let mut builder = f.debug_struct("BuiltinPrototype"); - builder.field("program_id", &self.program_id); - builder.field("name", &self.name); - builder.field("feature_id", &self.feature_id); - builder.finish() - } -} +pub mod prototypes; -#[cfg(RUSTC_WITH_SPECIALIZATION)] -impl solana_frozen_abi::abi_example::AbiExample for BuiltinPrototype { - fn example() -> Self { - // BuiltinPrototype isn't serializable by definition. - solana_program_runtime::declare_process_instruction!(MockBuiltin, 0, |_invoke_context| { - // Do nothing - Ok(()) - }); - Self { - feature_id: None, - program_id: Pubkey::default(), - name: "", - entrypoint: MockBuiltin::vm, - } - } -} +pub use prototypes::BuiltinPrototype; +use solana_sdk::{bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, feature_set}; pub static BUILTINS: &[BuiltinPrototype] = &[ BuiltinPrototype { diff --git a/runtime/src/bank/builtins/prototypes.rs b/runtime/src/bank/builtins/prototypes.rs new file mode 100644 index 00000000000000..bfaaedb86b3727 --- /dev/null +++ b/runtime/src/bank/builtins/prototypes.rs @@ -0,0 +1,38 @@ +use { + solana_program_runtime::invoke_context::BuiltinFunctionWithContext, solana_sdk::pubkey::Pubkey, +}; + +/// Transitions of built-in programs at epoch boundaries when features are activated. +pub struct BuiltinPrototype { + pub feature_id: Option, + pub program_id: Pubkey, + pub name: &'static str, + pub entrypoint: BuiltinFunctionWithContext, +} + +impl std::fmt::Debug for BuiltinPrototype { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let mut builder = f.debug_struct("BuiltinPrototype"); + builder.field("program_id", &self.program_id); + builder.field("name", &self.name); + builder.field("feature_id", &self.feature_id); + builder.finish() + } +} + +#[cfg(RUSTC_WITH_SPECIALIZATION)] +impl solana_frozen_abi::abi_example::AbiExample for BuiltinPrototype { + fn example() -> Self { + // BuiltinPrototype isn't serializable by definition. + solana_program_runtime::declare_process_instruction!(MockBuiltin, 0, |_invoke_context| { + // Do nothing + Ok(()) + }); + Self { + feature_id: None, + program_id: Pubkey::default(), + name: "", + entrypoint: MockBuiltin::vm, + } + } +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index fac4169301004d..57936c2c7e6bac 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -9,7 +9,6 @@ pub mod bank; pub mod bank_client; pub mod bank_forks; pub mod bank_utils; -pub mod builtins; pub mod commitment; pub mod compute_budget_details; mod epoch_rewards_hasher; diff --git a/runtime/src/serde_snapshot.rs b/runtime/src/serde_snapshot.rs index 4b066976d49048..66beb851148a35 100644 --- a/runtime/src/serde_snapshot.rs +++ b/runtime/src/serde_snapshot.rs @@ -1,7 +1,6 @@ use { crate::{ - bank::{Bank, BankFieldsToDeserialize, BankRc}, - builtins::BuiltinPrototype, + bank::{builtins::BuiltinPrototype, Bank, BankFieldsToDeserialize, BankRc}, epoch_stakes::EpochStakes, serde_snapshot::storage::SerializableAccountStorageEntry, snapshot_utils::{ diff --git a/runtime/src/snapshot_bank_utils.rs b/runtime/src/snapshot_bank_utils.rs index 721021142f9258..242959aa054404 100644 --- a/runtime/src/snapshot_bank_utils.rs +++ b/runtime/src/snapshot_bank_utils.rs @@ -1,7 +1,6 @@ use { crate::{ - bank::{Bank, BankFieldsToDeserialize, BankSlotDelta}, - builtins::BuiltinPrototype, + bank::{builtins::BuiltinPrototype, Bank, BankFieldsToDeserialize, BankSlotDelta}, serde_snapshot::{ bank_from_streams, bank_to_stream, fields_from_streams, BankIncrementalSnapshotPersistence, SerdeStyle, diff --git a/runtime/src/snapshot_minimizer.rs b/runtime/src/snapshot_minimizer.rs index 556a854da0c41b..30b027bd9ee124 100644 --- a/runtime/src/snapshot_minimizer.rs +++ b/runtime/src/snapshot_minimizer.rs @@ -1,7 +1,10 @@ //! Used to create minimal snapshots - separated here to keep accounts_db simpler use { - crate::{bank::Bank, builtins::BUILTINS, static_ids}, + crate::{ + bank::{builtins::BUILTINS, Bank}, + static_ids, + }, dashmap::DashSet, log::info, rayon::{ From 9b3840fa7d137b5b78100de710bb258fa53a3407 Mon Sep 17 00:00:00 2001 From: Joe C Date: Wed, 7 Feb 2024 12:41:14 -0600 Subject: [PATCH 2/9] runtime: builtins: add ephemeral builtins --- runtime/src/bank/builtins/mod.rs | 13 ++++++++++++- runtime/src/bank/builtins/prototypes.rs | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/runtime/src/bank/builtins/mod.rs b/runtime/src/bank/builtins/mod.rs index 38141b13b43f77..346ebaf3d9f48c 100644 --- a/runtime/src/bank/builtins/mod.rs +++ b/runtime/src/bank/builtins/mod.rs @@ -1,6 +1,6 @@ pub mod prototypes; -pub use prototypes::BuiltinPrototype; +pub use prototypes::{BuiltinPrototype, EphemeralBuiltinPrototype}; use solana_sdk::{bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, feature_set}; pub static BUILTINS: &[BuiltinPrototype] = &[ @@ -71,3 +71,14 @@ pub static BUILTINS: &[BuiltinPrototype] = &[ entrypoint: solana_loader_v4_program::Entrypoint::vm, }, ]; + +pub static EPHEMERAL_BUILTINS: &[EphemeralBuiltinPrototype] = &[ + EphemeralBuiltinPrototype { + program_id: solana_sdk::feature::id(), + name: "feature_gate_program", + }, + EphemeralBuiltinPrototype { + program_id: solana_sdk::native_loader::id(), + name: "native_loader_program", + }, +]; diff --git a/runtime/src/bank/builtins/prototypes.rs b/runtime/src/bank/builtins/prototypes.rs index bfaaedb86b3727..9e15cd208c6e60 100644 --- a/runtime/src/bank/builtins/prototypes.rs +++ b/runtime/src/bank/builtins/prototypes.rs @@ -36,3 +36,21 @@ impl solana_frozen_abi::abi_example::AbiExample for BuiltinPrototype { } } } + +/// Transitions of ephemeral built-in programs at epoch boundaries when +/// features are activated. +/// These are built-in programs that don't actually exist, but their address +/// is reserved. +pub struct EphemeralBuiltinPrototype { + pub program_id: Pubkey, + pub name: &'static str, +} + +impl std::fmt::Debug for EphemeralBuiltinPrototype { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let mut builder = f.debug_struct("EphemeralBuiltinPrototype"); + builder.field("program_id", &self.program_id); + builder.field("name", &self.name); + builder.finish() + } +} From 0a7e5e23b3529dcf4aff7d69a7e92b96beb6d6ce Mon Sep 17 00:00:00 2001 From: Joe C Date: Wed, 7 Feb 2024 12:29:08 -0600 Subject: [PATCH 3/9] runtime: builtins: add core bpf migration config --- runtime/src/bank.rs | 4 +-- .../bank/builtins/core_bpf_migration/mod.rs | 21 +++++++++++ runtime/src/bank/builtins/mod.rs | 36 +++++++++++++------ runtime/src/bank/builtins/prototypes.rs | 12 +++++-- 4 files changed, 57 insertions(+), 16 deletions(-) create mode 100644 runtime/src/bank/builtins/core_bpf_migration/mod.rs diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index e694e259feb249..e189251d32f0e5 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -5970,7 +5970,7 @@ impl Bank { .iter() .chain(additional_builtins.unwrap_or(&[]).iter()) { - if builtin.feature_id.is_none() { + if builtin.enable_feature_id.is_none() { self.add_builtin( builtin.program_id, builtin.name.to_string(), @@ -7316,7 +7316,7 @@ impl Bank { new_feature_activations: &HashSet, ) { for builtin in BUILTINS.iter() { - if let Some(feature_id) = builtin.feature_id { + if let Some(feature_id) = builtin.enable_feature_id { let should_apply_action_for_feature_transition = if only_apply_transitions_for_new_features { new_feature_activations.contains(&feature_id) diff --git a/runtime/src/bank/builtins/core_bpf_migration/mod.rs b/runtime/src/bank/builtins/core_bpf_migration/mod.rs new file mode 100644 index 00000000000000..cfaa0644bdd602 --- /dev/null +++ b/runtime/src/bank/builtins/core_bpf_migration/mod.rs @@ -0,0 +1,21 @@ +use solana_sdk::pubkey::Pubkey; + +/// Configurations for migrating a built-in program to Core BPF. +pub struct CoreBpfMigrationConfig { + /// The source program ID to replace the builtin with. + pub source_program_id: Pubkey, + /// The feature gate to trigger the migration to Core BPF. + /// Note: This feature gate should never be the same as any builtin's + /// `enable_feature_id`. It should always be a feature gate that will be + /// activated after the builtin is already enabled. + pub feature_id: Pubkey, +} + +impl std::fmt::Debug for CoreBpfMigrationConfig { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let mut builder = f.debug_struct("CoreBpfMigrationConfig"); + builder.field("source_program_id", &self.source_program_id); + builder.field("feature_id", &self.feature_id); + builder.finish() + } +} diff --git a/runtime/src/bank/builtins/mod.rs b/runtime/src/bank/builtins/mod.rs index 346ebaf3d9f48c..8173d3cb03d630 100644 --- a/runtime/src/bank/builtins/mod.rs +++ b/runtime/src/bank/builtins/mod.rs @@ -1,3 +1,4 @@ +pub(crate) mod core_bpf_migration; pub mod prototypes; pub use prototypes::{BuiltinPrototype, EphemeralBuiltinPrototype}; @@ -5,67 +6,78 @@ use solana_sdk::{bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, feat pub static BUILTINS: &[BuiltinPrototype] = &[ BuiltinPrototype { - feature_id: None, + enable_feature_id: None, + core_bpf_migration: None, program_id: solana_system_program::id(), name: "system_program", entrypoint: solana_system_program::system_processor::Entrypoint::vm, }, BuiltinPrototype { - feature_id: None, + enable_feature_id: None, + core_bpf_migration: None, program_id: solana_vote_program::id(), name: "vote_program", entrypoint: solana_vote_program::vote_processor::Entrypoint::vm, }, BuiltinPrototype { - feature_id: None, + enable_feature_id: None, + core_bpf_migration: None, program_id: solana_stake_program::id(), name: "stake_program", entrypoint: solana_stake_program::stake_instruction::Entrypoint::vm, }, BuiltinPrototype { - feature_id: None, + enable_feature_id: None, + core_bpf_migration: None, program_id: solana_config_program::id(), name: "config_program", entrypoint: solana_config_program::config_processor::Entrypoint::vm, }, BuiltinPrototype { - feature_id: None, + enable_feature_id: None, + core_bpf_migration: None, program_id: bpf_loader_deprecated::id(), name: "solana_bpf_loader_deprecated_program", entrypoint: solana_bpf_loader_program::Entrypoint::vm, }, BuiltinPrototype { - feature_id: None, + enable_feature_id: None, + core_bpf_migration: None, program_id: bpf_loader::id(), name: "solana_bpf_loader_program", entrypoint: solana_bpf_loader_program::Entrypoint::vm, }, BuiltinPrototype { - feature_id: None, + enable_feature_id: None, + core_bpf_migration: None, program_id: bpf_loader_upgradeable::id(), name: "solana_bpf_loader_upgradeable_program", entrypoint: solana_bpf_loader_program::Entrypoint::vm, }, BuiltinPrototype { - feature_id: None, + enable_feature_id: None, + core_bpf_migration: None, program_id: solana_sdk::compute_budget::id(), name: "compute_budget_program", entrypoint: solana_compute_budget_program::Entrypoint::vm, }, BuiltinPrototype { - feature_id: None, + enable_feature_id: None, + core_bpf_migration: None, program_id: solana_sdk::address_lookup_table::program::id(), name: "address_lookup_table_program", entrypoint: solana_address_lookup_table_program::processor::Entrypoint::vm, }, BuiltinPrototype { - feature_id: Some(feature_set::zk_token_sdk_enabled::id()), + enable_feature_id: Some(feature_set::zk_token_sdk_enabled::id()), + core_bpf_migration: None, program_id: solana_zk_token_sdk::zk_token_proof_program::id(), name: "zk_token_proof_program", entrypoint: solana_zk_token_proof_program::Entrypoint::vm, }, BuiltinPrototype { - feature_id: Some(feature_set::enable_program_runtime_v2_and_loader_v4::id()), + enable_feature_id: Some(feature_set::enable_program_runtime_v2_and_loader_v4::id()), + core_bpf_migration: None, program_id: solana_sdk::loader_v4::id(), name: "loader_v4", entrypoint: solana_loader_v4_program::Entrypoint::vm, @@ -74,10 +86,12 @@ pub static BUILTINS: &[BuiltinPrototype] = &[ pub static EPHEMERAL_BUILTINS: &[EphemeralBuiltinPrototype] = &[ EphemeralBuiltinPrototype { + core_bpf_migration: None, program_id: solana_sdk::feature::id(), name: "feature_gate_program", }, EphemeralBuiltinPrototype { + core_bpf_migration: None, program_id: solana_sdk::native_loader::id(), name: "native_loader_program", }, diff --git a/runtime/src/bank/builtins/prototypes.rs b/runtime/src/bank/builtins/prototypes.rs index 9e15cd208c6e60..9ccc5297516646 100644 --- a/runtime/src/bank/builtins/prototypes.rs +++ b/runtime/src/bank/builtins/prototypes.rs @@ -1,10 +1,12 @@ use { + super::core_bpf_migration::CoreBpfMigrationConfig, solana_program_runtime::invoke_context::BuiltinFunctionWithContext, solana_sdk::pubkey::Pubkey, }; /// Transitions of built-in programs at epoch boundaries when features are activated. pub struct BuiltinPrototype { - pub feature_id: Option, + pub enable_feature_id: Option, + pub core_bpf_migration: Option, pub program_id: Pubkey, pub name: &'static str, pub entrypoint: BuiltinFunctionWithContext, @@ -15,7 +17,8 @@ impl std::fmt::Debug for BuiltinPrototype { let mut builder = f.debug_struct("BuiltinPrototype"); builder.field("program_id", &self.program_id); builder.field("name", &self.name); - builder.field("feature_id", &self.feature_id); + builder.field("enable_feature_id", &self.enable_feature_id); + builder.field("core_bpf_migration", &self.core_bpf_migration); builder.finish() } } @@ -29,7 +32,8 @@ impl solana_frozen_abi::abi_example::AbiExample for BuiltinPrototype { Ok(()) }); Self { - feature_id: None, + enable_feature_id: None, + core_bpf_migration: None, program_id: Pubkey::default(), name: "", entrypoint: MockBuiltin::vm, @@ -42,6 +46,7 @@ impl solana_frozen_abi::abi_example::AbiExample for BuiltinPrototype { /// These are built-in programs that don't actually exist, but their address /// is reserved. pub struct EphemeralBuiltinPrototype { + pub core_bpf_migration: Option, pub program_id: Pubkey, pub name: &'static str, } @@ -51,6 +56,7 @@ impl std::fmt::Debug for EphemeralBuiltinPrototype { let mut builder = f.debug_struct("EphemeralBuiltinPrototype"); builder.field("program_id", &self.program_id); builder.field("name", &self.name); + builder.field("core_bpf_migration", &self.core_bpf_migration); builder.finish() } } From 8dc94a05ff4c6411638896c872864f3e76bc46e7 Mon Sep 17 00:00:00 2001 From: Joe C Date: Wed, 7 Feb 2024 16:14:31 -0600 Subject: [PATCH 4/9] runtime: core_bpf_migration: add builtin config --- .../builtins/core_bpf_migration/builtin.rs | 77 +++++++++++++++++++ .../bank/builtins/core_bpf_migration/error.rs | 18 +++++ .../bank/builtins/core_bpf_migration/mod.rs | 10 +++ 3 files changed, 105 insertions(+) create mode 100644 runtime/src/bank/builtins/core_bpf_migration/builtin.rs create mode 100644 runtime/src/bank/builtins/core_bpf_migration/error.rs diff --git a/runtime/src/bank/builtins/core_bpf_migration/builtin.rs b/runtime/src/bank/builtins/core_bpf_migration/builtin.rs new file mode 100644 index 00000000000000..c38a80cad1bf4b --- /dev/null +++ b/runtime/src/bank/builtins/core_bpf_migration/builtin.rs @@ -0,0 +1,77 @@ +#![allow(dead_code)] // Removed in later commit +use { + super::{error::CoreBpfMigrationError, CoreBpfMigration}, + crate::bank::Bank, + solana_sdk::{ + account::{Account, AccountSharedData}, + 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 BuiltinConfig { + pub program_address: Pubkey, + pub program_account: Account, + pub program_data_address: Pubkey, + pub total_data_size: usize, +} + +impl BuiltinConfig { + /// Create a new migration configuration for a built-in program. + pub(crate) fn new_checked( + bank: &Bank, + program_id: &Pubkey, + migration: CoreBpfMigration, + ) -> Result { + let program_address = *program_id; + let program_account = match migration { + CoreBpfMigration::Builtin => { + // The program account should exist. + let program_account: Account = bank + .get_account_with_fixed_root(&program_address) + .ok_or(CoreBpfMigrationError::AccountNotFound(program_address))? + .into(); + + // 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 + } + CoreBpfMigration::Ephemeral => { + // 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().into() + } + }; + + 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_account, + program_data_address, + total_data_size, + }) + } +} diff --git a/runtime/src/bank/builtins/core_bpf_migration/error.rs b/runtime/src/bank/builtins/core_bpf_migration/error.rs new file mode 100644 index 00000000000000..e55469e0211207 --- /dev/null +++ b/runtime/src/bank/builtins/core_bpf_migration/error.rs @@ -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), +} diff --git a/runtime/src/bank/builtins/core_bpf_migration/mod.rs b/runtime/src/bank/builtins/core_bpf_migration/mod.rs index cfaa0644bdd602..8a8076a910270e 100644 --- a/runtime/src/bank/builtins/core_bpf_migration/mod.rs +++ b/runtime/src/bank/builtins/core_bpf_migration/mod.rs @@ -1,5 +1,15 @@ +#![allow(dead_code)] // Removed in later commit +mod builtin; +mod error; + use solana_sdk::pubkey::Pubkey; +/// Sets up a Core BPF migration for a built-in program. +pub enum CoreBpfMigration { + Builtin, + Ephemeral, +} + /// Configurations for migrating a built-in program to Core BPF. pub struct CoreBpfMigrationConfig { /// The source program ID to replace the builtin with. From 08b3acdfb7a71669b095cd42ad868e39491221c7 Mon Sep 17 00:00:00 2001 From: Joe C Date: Wed, 7 Feb 2024 18:10:01 -0600 Subject: [PATCH 5/9] runtime: core_bpf_migration: add builtin config tests --- .../builtins/core_bpf_migration/builtin.rs | 184 ++++++++++++++++++ 1 file changed, 184 insertions(+) diff --git a/runtime/src/bank/builtins/core_bpf_migration/builtin.rs b/runtime/src/bank/builtins/core_bpf_migration/builtin.rs index c38a80cad1bf4b..262aa6da1441cf 100644 --- a/runtime/src/bank/builtins/core_bpf_migration/builtin.rs +++ b/runtime/src/bank/builtins/core_bpf_migration/builtin.rs @@ -75,3 +75,187 @@ impl BuiltinConfig { }) } } + +#[cfg(test)] +mod tests { + use { + super::*, + crate::bank::{tests::create_simple_test_bank, ApplyFeatureActivationsCaller}, + solana_sdk::{ + bpf_loader_upgradeable::{UpgradeableLoaderState, ID as BPF_LOADER_UPGRADEABLE_ID}, + feature, feature_set, + }, + test_case::test_case, + }; + + // Just for tests + impl Clone for CoreBpfMigration { + fn clone(&self) -> Self { + match self { + CoreBpfMigration::Builtin => CoreBpfMigration::Builtin, + CoreBpfMigration::Ephemeral => CoreBpfMigration::Ephemeral, + } + } + } + + fn store_account( + 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_builtin_config_builtin(program_address: Pubkey, activation_feature: Option) { + let migration = CoreBpfMigration::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: Account = bank + .get_account_with_fixed_root(&program_address) + .unwrap() + .into(); + let program_data_address = get_program_data_address(&program_address); + + // Success + let builtin_config = + BuiltinConfig::new_checked(&bank, &program_address, migration.clone()).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!( + BuiltinConfig::new_checked(&bank, &program_address, migration.clone()).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!( + BuiltinConfig::new_checked(&bank, &program_address, migration.clone()).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!( + BuiltinConfig::new_checked(&bank, &program_address, migration).unwrap_err(), + CoreBpfMigrationError::AccountNotFound(program_address) + ); + } + + #[test_case(solana_sdk::feature::id())] + #[test_case(solana_sdk::native_loader::id())] + fn test_builtin_config_ephemeral_builtin(program_address: Pubkey) { + let migration = CoreBpfMigration::Ephemeral; + let bank = create_simple_test_bank(0); + + let program_account: Account = AccountSharedData::default().into(); + let program_data_address = get_program_data_address(&program_address); + + // Success + let builtin_config = + BuiltinConfig::new_checked(&bank, &program_address, migration.clone()).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!( + BuiltinConfig::new_checked(&bank, &program_address, migration.clone()).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!( + BuiltinConfig::new_checked(&bank, &program_address, migration).unwrap_err(), + CoreBpfMigrationError::AccountExists(program_address) + ); + } +} From f7f5bd7c828121d0896a34382e6568b5d472a9a0 Mon Sep 17 00:00:00 2001 From: Joe C Date: Wed, 7 Feb 2024 16:27:57 -0600 Subject: [PATCH 6/9] runtime: core_bpf_migration: add bpf upgradeable config --- .../core_bpf_migration/bpf_upgradeable.rs | 119 ++++++++++++++++++ .../bank/builtins/core_bpf_migration/error.rs | 14 +++ .../bank/builtins/core_bpf_migration/mod.rs | 1 + 3 files changed, 134 insertions(+) create mode 100644 runtime/src/bank/builtins/core_bpf_migration/bpf_upgradeable.rs diff --git a/runtime/src/bank/builtins/core_bpf_migration/bpf_upgradeable.rs b/runtime/src/bank/builtins/core_bpf_migration/bpf_upgradeable.rs new file mode 100644 index 00000000000000..c904cd20802bd0 --- /dev/null +++ b/runtime/src/bank/builtins/core_bpf_migration/bpf_upgradeable.rs @@ -0,0 +1,119 @@ +#![allow(dead_code)] // Removed in later commit +use { + super::error::CoreBpfMigrationError, + crate::bank::Bank, + solana_sdk::{ + account::Account, + bpf_loader_upgradeable::{ + get_program_data_address, UpgradeableLoaderState, ID as BPF_LOADER_UPGRADEABLE_ID, + }, + pubkey::Pubkey, + }, +}; + +/// Used to validate a source BPF Upgradeable program's account and data +/// account before migrating a built-in program to Core BPF. +#[derive(Debug)] +pub(crate) struct BpfUpgradeableConfig { + pub program_address: Pubkey, + pub program_account: Account, + pub program_data_address: Pubkey, + pub program_data_account: Account, + pub total_data_size: usize, +} + +impl BpfUpgradeableConfig { + fn check_program_account(&self) -> Result<(), CoreBpfMigrationError> { + // The program account should be owned by the upgradeable loader. + if self.program_account.owner != BPF_LOADER_UPGRADEABLE_ID { + return Err(CoreBpfMigrationError::IncorrectOwner(self.program_address)); + } + + // The program account should have a pointer to its data account. + if let UpgradeableLoaderState::Program { + programdata_address, + } = bincode::deserialize(&self.program_account.data) + .map_err::(|_| { + CoreBpfMigrationError::InvalidProgramAccount(self.program_address) + })? + { + if programdata_address != self.program_data_address { + return Err(CoreBpfMigrationError::InvalidProgramAccount( + self.program_address, + )); + } + } + + Ok(()) + } + + fn check_program_data_account(&self) -> Result<(), CoreBpfMigrationError> { + // The program data account should be owned by the upgradeable loader. + if self.program_data_account.owner != BPF_LOADER_UPGRADEABLE_ID { + return Err(CoreBpfMigrationError::IncorrectOwner( + self.program_data_address, + )); + } + + // The program data account should have the correct state. + let programdata_data_offset = UpgradeableLoaderState::size_of_programdata_metadata(); + if self.program_data_account.data.len() < programdata_data_offset { + return Err(CoreBpfMigrationError::InvalidProgramDataAccount( + self.program_data_address, + )); + } + // Length checked in previous block. + match bincode::deserialize::( + &self.program_data_account.data[..programdata_data_offset], + ) { + Ok(UpgradeableLoaderState::ProgramData { .. }) => Ok(()), + _ => Err(CoreBpfMigrationError::InvalidProgramDataAccount( + self.program_data_address, + )), + } + } + + /// Create a new migration configuration for a BPF Upgradeable source + /// program. + pub(crate) fn new_checked( + bank: &Bank, + program_id: &Pubkey, + ) -> Result { + let program_address = *program_id; + // The program account should exist. + let program_account: Account = bank + .get_account_with_fixed_root(&program_address) + .ok_or(CoreBpfMigrationError::AccountNotFound(program_address))? + .into(); + + // The program data account should exist. + let program_data_address = get_program_data_address(&program_address); + let program_data_account: Account = bank + .get_account_with_fixed_root(&program_data_address) + .ok_or(CoreBpfMigrationError::ProgramHasNoDataAccount( + program_address, + ))? + .into(); + + // The total data size is the size of the program account's data plus + // the size of the program data account's data. + let total_data_size = program_account + .data + .len() + .checked_add(program_data_account.data.len()) + .ok_or(CoreBpfMigrationError::ArithmeticOverflow)?; + + let config = Self { + program_address, + program_account, + program_data_address, + program_data_account, + total_data_size, + }; + + config.check_program_account()?; + config.check_program_data_account()?; + + Ok(config) + } +} diff --git a/runtime/src/bank/builtins/core_bpf_migration/error.rs b/runtime/src/bank/builtins/core_bpf_migration/error.rs index e55469e0211207..5b0c79116938c1 100644 --- a/runtime/src/bank/builtins/core_bpf_migration/error.rs +++ b/runtime/src/bank/builtins/core_bpf_migration/error.rs @@ -15,4 +15,18 @@ pub enum CoreBpfMigrationError { /// Program has a data account #[error("Data account exists for program {0:?}")] ProgramHasDataAccount(Pubkey), + /// Program has no data account + #[error("Data account does not exist for program {0:?}")] + ProgramHasNoDataAccount(Pubkey), + /// Invalid program account + #[error("Invalid program account: {0:?}")] + InvalidProgramAccount(Pubkey), + /// Invalid program data account + #[error("Invalid program data account: {0:?}")] + InvalidProgramDataAccount(Pubkey), + // Since `core_bpf_migration` does not return `ProgramError` or + // `InstructionError`, we have to duplicate `ArithmeticOverflow` here. + /// Arithmetic overflow + #[error("Arithmetic overflow")] + ArithmeticOverflow, } diff --git a/runtime/src/bank/builtins/core_bpf_migration/mod.rs b/runtime/src/bank/builtins/core_bpf_migration/mod.rs index 8a8076a910270e..d2c4e318ab800d 100644 --- a/runtime/src/bank/builtins/core_bpf_migration/mod.rs +++ b/runtime/src/bank/builtins/core_bpf_migration/mod.rs @@ -1,4 +1,5 @@ #![allow(dead_code)] // Removed in later commit +mod bpf_upgradeable; mod builtin; mod error; From 05ccf9bebdcf6e445bece7f8e524cc5ae48abffa Mon Sep 17 00:00:00 2001 From: Joe C Date: Wed, 7 Feb 2024 18:41:24 -0600 Subject: [PATCH 7/9] runtime: core_bpf_migration: add bpf upgradeable config tests --- .../core_bpf_migration/bpf_upgradeable.rs | 255 ++++++++++++++++++ 1 file changed, 255 insertions(+) diff --git a/runtime/src/bank/builtins/core_bpf_migration/bpf_upgradeable.rs b/runtime/src/bank/builtins/core_bpf_migration/bpf_upgradeable.rs index c904cd20802bd0..d8ab35612fce48 100644 --- a/runtime/src/bank/builtins/core_bpf_migration/bpf_upgradeable.rs +++ b/runtime/src/bank/builtins/core_bpf_migration/bpf_upgradeable.rs @@ -117,3 +117,258 @@ impl BpfUpgradeableConfig { Ok(config) } } + +#[cfg(test)] +mod tests { + use { + super::*, + crate::bank::tests::create_simple_test_bank, + solana_sdk::{ + account::AccountSharedData, bpf_loader_upgradeable::ID as BPF_LOADER_UPGRADEABLE_ID, + }, + }; + + fn store_account( + bank: &Bank, + address: &Pubkey, + data: (&T, Option<&[u8]>), + executable: bool, + owner: &Pubkey, + ) { + let (data, additional_data) = data; + let mut data = bincode::serialize(data).unwrap(); + if let Some(additional_data) = additional_data { + data.extend_from_slice(additional_data); + } + 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] + fn test_bpf_upgradeable_config() { + let bank = create_simple_test_bank(0); + + let program_id = Pubkey::new_unique(); + let program_data_address = get_program_data_address(&program_id); + + // Fail if the program account does not exist + assert_eq!( + BpfUpgradeableConfig::new_checked(&bank, &program_id).unwrap_err(), + CoreBpfMigrationError::AccountNotFound(program_id) + ); + + // Store the proper program account + let proper_program_account_state = UpgradeableLoaderState::Program { + programdata_address: program_data_address, + }; + store_account( + &bank, + &program_id, + (&proper_program_account_state, None), + true, + &BPF_LOADER_UPGRADEABLE_ID, + ); + + // Fail if the program data account does not exist + assert_eq!( + BpfUpgradeableConfig::new_checked(&bank, &program_id).unwrap_err(), + CoreBpfMigrationError::ProgramHasNoDataAccount(program_id) + ); + + // Store the proper program data account + let proper_program_data_account_state = UpgradeableLoaderState::ProgramData { + slot: 0, + upgrade_authority_address: Some(Pubkey::new_unique()), + }; + store_account( + &bank, + &program_data_address, + (&proper_program_data_account_state, Some(&[4u8; 200])), + false, + &BPF_LOADER_UPGRADEABLE_ID, + ); + + // Success + let bpf_upgradeable_program_config = + BpfUpgradeableConfig::new_checked(&bank, &program_id).unwrap(); + + let check_program_account_data = bincode::serialize(&proper_program_account_state).unwrap(); + let check_program_account_data_len = check_program_account_data.len(); + let check_program_lamports = + bank.get_minimum_balance_for_rent_exemption(check_program_account_data_len); + let check_program_account = Account { + data: check_program_account_data, + executable: true, + lamports: check_program_lamports, + owner: BPF_LOADER_UPGRADEABLE_ID, + ..Account::default() + }; + + let mut check_program_data_account_data = + bincode::serialize(&proper_program_data_account_state).unwrap(); + check_program_data_account_data.extend_from_slice(&[4u8; 200]); + let check_program_data_account_data_len = check_program_data_account_data.len(); + let check_program_data_lamports = + bank.get_minimum_balance_for_rent_exemption(check_program_data_account_data_len); + let check_program_data_account = Account { + data: check_program_data_account_data, + executable: false, + lamports: check_program_data_lamports, + owner: BPF_LOADER_UPGRADEABLE_ID, + ..Account::default() + }; + + assert_eq!(bpf_upgradeable_program_config.program_address, program_id); + assert_eq!( + bpf_upgradeable_program_config.program_account, + check_program_account + ); + assert_eq!( + bpf_upgradeable_program_config.program_data_address, + program_data_address + ); + assert_eq!( + bpf_upgradeable_program_config.program_data_account, + check_program_data_account + ); + assert_eq!( + bpf_upgradeable_program_config.total_data_size, + check_program_account_data_len + check_program_data_account_data_len + ); + } + + #[test] + fn test_bpf_upgradeable_config_bad_program_account() { + let bank = create_simple_test_bank(0); + + let program_id = Pubkey::new_unique(); + let program_data_address = get_program_data_address(&program_id); + + // Store the proper program data account + store_account( + &bank, + &program_data_address, + ( + &UpgradeableLoaderState::ProgramData { + slot: 0, + upgrade_authority_address: Some(Pubkey::new_unique()), + }, + Some(&[4u8; 200]), + ), + false, + &BPF_LOADER_UPGRADEABLE_ID, + ); + + // Fail if the program account is not owned by the upgradeable loader + store_account( + &bank, + &program_id, + ( + &UpgradeableLoaderState::Program { + programdata_address: program_data_address, + }, + None, + ), + true, + &Pubkey::new_unique(), // Not the upgradeable loader + ); + assert_eq!( + BpfUpgradeableConfig::new_checked(&bank, &program_id).unwrap_err(), + CoreBpfMigrationError::IncorrectOwner(program_id) + ); + + // Fail if the program account's state is not `UpgradeableLoaderState::Program` + store_account( + &bank, + &program_id, + (&vec![0u8; 200], None), + true, + &BPF_LOADER_UPGRADEABLE_ID, + ); + assert_eq!( + BpfUpgradeableConfig::new_checked(&bank, &program_id).unwrap_err(), + CoreBpfMigrationError::InvalidProgramAccount(program_id) + ); + + // Fail if the program account's state is `UpgradeableLoaderState::Program`, + // but it points to the wrong data account + store_account( + &bank, + &program_id, + ( + &UpgradeableLoaderState::Program { + programdata_address: Pubkey::new_unique(), // Not the correct data account + }, + None, + ), + true, + &BPF_LOADER_UPGRADEABLE_ID, + ); + assert_eq!( + BpfUpgradeableConfig::new_checked(&bank, &program_id).unwrap_err(), + CoreBpfMigrationError::InvalidProgramAccount(program_id) + ); + } + + #[test] + fn test_bpf_upgradeable_config_bad_program_data_account() { + let bank = create_simple_test_bank(0); + + let program_id = Pubkey::new_unique(); + let program_data_address = get_program_data_address(&program_id); + + // Store the proper program account + store_account( + &bank, + &program_id, + ( + &UpgradeableLoaderState::Program { + programdata_address: program_data_address, + }, + None, + ), + true, + &BPF_LOADER_UPGRADEABLE_ID, + ); + + // Fail if the program data account is not owned by the upgradeable loader + store_account( + &bank, + &program_data_address, + ( + &UpgradeableLoaderState::ProgramData { + slot: 0, + upgrade_authority_address: Some(Pubkey::new_unique()), + }, + Some(&[4u8; 200]), + ), + false, + &Pubkey::new_unique(), // Not the upgradeable loader + ); + assert_eq!( + BpfUpgradeableConfig::new_checked(&bank, &program_id).unwrap_err(), + CoreBpfMigrationError::IncorrectOwner(program_data_address) + ); + + // Fail if the program data account does not have the correct state + store_account( + &bank, + &program_data_address, + (&vec![4u8; 200], None), // Not the correct state + false, + &BPF_LOADER_UPGRADEABLE_ID, + ); + assert_eq!( + BpfUpgradeableConfig::new_checked(&bank, &program_id).unwrap_err(), + CoreBpfMigrationError::InvalidProgramDataAccount(program_data_address) + ); + } +} From e24ea6d5910824dfa7d2923e922044c296ada020 Mon Sep 17 00:00:00 2001 From: Joe C Date: Wed, 7 Feb 2024 16:42:41 -0600 Subject: [PATCH 8/9] runtime: core_bpf_migration: add migration path --- .../core_bpf_migration/bpf_upgradeable.rs | 1 - .../builtins/core_bpf_migration/builtin.rs | 1 - .../bank/builtins/core_bpf_migration/error.rs | 3 + .../bank/builtins/core_bpf_migration/mod.rs | 95 ++++++++++++++++++- runtime/src/bank/builtins/prototypes.rs | 35 ++++++- 5 files changed, 128 insertions(+), 7 deletions(-) diff --git a/runtime/src/bank/builtins/core_bpf_migration/bpf_upgradeable.rs b/runtime/src/bank/builtins/core_bpf_migration/bpf_upgradeable.rs index d8ab35612fce48..e1dcd8598aed5e 100644 --- a/runtime/src/bank/builtins/core_bpf_migration/bpf_upgradeable.rs +++ b/runtime/src/bank/builtins/core_bpf_migration/bpf_upgradeable.rs @@ -1,4 +1,3 @@ -#![allow(dead_code)] // Removed in later commit use { super::error::CoreBpfMigrationError, crate::bank::Bank, diff --git a/runtime/src/bank/builtins/core_bpf_migration/builtin.rs b/runtime/src/bank/builtins/core_bpf_migration/builtin.rs index 262aa6da1441cf..4d3d90f80addd5 100644 --- a/runtime/src/bank/builtins/core_bpf_migration/builtin.rs +++ b/runtime/src/bank/builtins/core_bpf_migration/builtin.rs @@ -1,4 +1,3 @@ -#![allow(dead_code)] // Removed in later commit use { super::{error::CoreBpfMigrationError, CoreBpfMigration}, crate::bank::Bank, diff --git a/runtime/src/bank/builtins/core_bpf_migration/error.rs b/runtime/src/bank/builtins/core_bpf_migration/error.rs index 5b0c79116938c1..5aed271ab5471e 100644 --- a/runtime/src/bank/builtins/core_bpf_migration/error.rs +++ b/runtime/src/bank/builtins/core_bpf_migration/error.rs @@ -24,6 +24,9 @@ pub enum CoreBpfMigrationError { /// Invalid program data account #[error("Invalid program data account: {0:?}")] InvalidProgramDataAccount(Pubkey), + /// Failed to serialize new program account + #[error("Failed to serialize new program account")] + FailedToSerialize, // Since `core_bpf_migration` does not return `ProgramError` or // `InstructionError`, we have to duplicate `ArithmeticOverflow` here. /// Arithmetic overflow diff --git a/runtime/src/bank/builtins/core_bpf_migration/mod.rs b/runtime/src/bank/builtins/core_bpf_migration/mod.rs index d2c4e318ab800d..1cdbfcbf1c1ca0 100644 --- a/runtime/src/bank/builtins/core_bpf_migration/mod.rs +++ b/runtime/src/bank/builtins/core_bpf_migration/mod.rs @@ -1,9 +1,19 @@ -#![allow(dead_code)] // Removed in later commit mod bpf_upgradeable; mod builtin; -mod error; +pub(crate) mod error; -use solana_sdk::pubkey::Pubkey; +use { + crate::bank::Bank, + bpf_upgradeable::BpfUpgradeableConfig, + builtin::BuiltinConfig, + error::CoreBpfMigrationError, + solana_sdk::{ + account::{Account, AccountSharedData}, + bpf_loader_upgradeable::{UpgradeableLoaderState, ID as BPF_LOADER_UPGRADEABLE_ID}, + pubkey::Pubkey, + }, + std::sync::atomic::Ordering::Relaxed, +}; /// Sets up a Core BPF migration for a built-in program. pub enum CoreBpfMigration { @@ -20,6 +30,7 @@ pub struct CoreBpfMigrationConfig { /// `enable_feature_id`. It should always be a feature gate that will be /// activated after the builtin is already enabled. pub feature_id: Pubkey, + pub datapoint_name: &'static str, } impl std::fmt::Debug for CoreBpfMigrationConfig { @@ -30,3 +41,81 @@ impl std::fmt::Debug for CoreBpfMigrationConfig { builder.finish() } } + +/// 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 { + let state = UpgradeableLoaderState::Program { + programdata_address: target.program_data_address, + }; + let data = bincode::serialize(&state).map_err(|_| CoreBpfMigrationError::FailedToSerialize)?; + let account = Account { + data, + owner: BPF_LOADER_UPGRADEABLE_ID, + executable: true, + // The source program account has the same state, so it must have a + // sufficient lamports balance to cover rent for this state. + ..source.program_account + }; + Ok(AccountSharedData::from(account)) +} + +impl CoreBpfMigrationConfig { + pub(crate) fn migrate_builtin_to_core_bpf( + &self, + bank: &mut Bank, + program_id: &Pubkey, + migration: CoreBpfMigration, + ) -> Result<(), CoreBpfMigrationError> { + datapoint_info!(self.datapoint_name, ("slot", bank.slot, i64)); + + let target = BuiltinConfig::new_checked(bank, program_id, migration)?; + let source = BpfUpgradeableConfig::new_checked(bank, &self.source_program_id)?; + + // 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, since it will be + // replaced. + 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. + // The old data size is the total size of all accounts involved. + // The new data size is the total size of the source program accounts, + // since the target program account is replaced. + let old_data_size = source + .total_data_size + .saturating_add(target.total_data_size); + let new_data_size = source.total_data_size; + bank.calculate_and_update_accounts_data_size_delta_off_chain(old_data_size, new_data_size); + + // Remove the built-in program from the bank's list of built-ins. + bank.builtin_programs.remove(&target.program_address); + + // 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(()) + } +} diff --git a/runtime/src/bank/builtins/prototypes.rs b/runtime/src/bank/builtins/prototypes.rs index 9ccc5297516646..f1ec7f5e87c6b4 100644 --- a/runtime/src/bank/builtins/prototypes.rs +++ b/runtime/src/bank/builtins/prototypes.rs @@ -1,6 +1,11 @@ +#![allow(dead_code)] // Removed in later commit use { - super::core_bpf_migration::CoreBpfMigrationConfig, - solana_program_runtime::invoke_context::BuiltinFunctionWithContext, solana_sdk::pubkey::Pubkey, + super::core_bpf_migration::{ + error::CoreBpfMigrationError, CoreBpfMigration, CoreBpfMigrationConfig, + }, + crate::bank::Bank, + solana_program_runtime::invoke_context::BuiltinFunctionWithContext, + solana_sdk::pubkey::Pubkey, }; /// Transitions of built-in programs at epoch boundaries when features are activated. @@ -23,6 +28,19 @@ impl std::fmt::Debug for BuiltinPrototype { } } +impl BuiltinPrototype { + pub(crate) fn migrate_to_core_bpf(&self, bank: &mut Bank) -> Result<(), CoreBpfMigrationError> { + if let Some(config) = &self.core_bpf_migration { + config.migrate_builtin_to_core_bpf( + bank, + &self.program_id, + CoreBpfMigration::Builtin, + )?; + } + Ok(()) + } +} + #[cfg(RUSTC_WITH_SPECIALIZATION)] impl solana_frozen_abi::abi_example::AbiExample for BuiltinPrototype { fn example() -> Self { @@ -60,3 +78,16 @@ impl std::fmt::Debug for EphemeralBuiltinPrototype { builder.finish() } } + +impl EphemeralBuiltinPrototype { + pub(crate) fn migrate_to_core_bpf(&self, bank: &mut Bank) -> Result<(), CoreBpfMigrationError> { + if let Some(config) = &self.core_bpf_migration { + config.migrate_builtin_to_core_bpf( + bank, + &self.program_id, + CoreBpfMigration::Ephemeral, + )?; + } + Ok(()) + } +} From a518972812bee8fcc53492839f9777cb760304fd Mon Sep 17 00:00:00 2001 From: Joe C Date: Wed, 7 Feb 2024 20:09:57 -0600 Subject: [PATCH 9/9] runtime: bank: wire in core bpf migration feature activations --- runtime/src/bank.rs | 75 +++++++++++++++++++++++-- runtime/src/bank/builtins/prototypes.rs | 1 - runtime/src/snapshot_minimizer.rs | 11 ++-- 3 files changed, 75 insertions(+), 12 deletions(-) diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index e189251d32f0e5..1ce673e659f052 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -43,7 +43,7 @@ pub use solana_sdk::reward_type::RewardType; use { crate::{ bank::{ - builtins::{BuiltinPrototype, BUILTINS}, + builtins::{BuiltinPrototype, BUILTINS, EPHEMERAL_BUILTINS}, metrics::*, }, bank_forks::BankForks, @@ -5970,7 +5970,16 @@ impl Bank { .iter() .chain(additional_builtins.unwrap_or(&[]).iter()) { - if builtin.enable_feature_id.is_none() { + let should_add_builtin = builtin.enable_feature_id.is_none() && { + if let Some(core_bpf_migration) = &builtin.core_bpf_migration { + // The built-in should be added if the feature to + // migrate it to Core BPF is not active. + !self.feature_set.is_active(&core_bpf_migration.feature_id) + } else { + true + } + }; + if should_add_builtin { self.add_builtin( builtin.program_id, builtin.name.to_string(), @@ -6068,6 +6077,10 @@ impl Bank { self.load_slow(&self.ancestors, pubkey) } + pub fn get_builtins(&self) -> &HashSet { + &self.builtin_programs + } + fn load_slow( &self, ancestors: &Ancestors, @@ -7316,14 +7329,49 @@ impl Bank { new_feature_activations: &HashSet, ) { for builtin in BUILTINS.iter() { + // The `builtin_disabled` flag is used to handle the case where a + // built-in is scheduled to be enabled by one feature gate and + // later migrated to Core BPF by another. + // There should never be a case where a built-in is set to be + // migrated to Core BPF and is also set to be enabled on feature + // activation on the same feature gate. However, the + // `builtin_disabled` flag will handle this case as well, electing + // to first attempt the migration to Core BPF. + // This will fail gracefully, and the built-in will subsequently be + // enabled, but it will never be migrated to Core BPF. + // Using the same feature gate for both enabling and migrating a + // built-in to Core BPF should be strictly avoided. + let mut builtin_disabled = false; + if let Some(core_bpf_migration) = &builtin.core_bpf_migration { + // If the built-in is set to be migrated to Core BPF on feature + // activation, perform the migration and do not add the program + // to the bank's builtins. The migration will remove it from + // the builtins list and the cache. + if new_feature_activations.contains(&core_bpf_migration.feature_id) { + if let Err(e) = builtin.migrate_to_core_bpf(self) { + warn!( + "Failed to migrate built-in {} to Core BPF: {}", + builtin.name, e + ); + } else { + builtin_disabled = true; + } + } else { + // If the built-in has already been migrated to Core BPF, do not + // add it to the bank's builtins. + builtin_disabled = self.feature_set.is_active(&core_bpf_migration.feature_id); + } + }; + if let Some(feature_id) = builtin.enable_feature_id { - let should_apply_action_for_feature_transition = - if only_apply_transitions_for_new_features { + let should_enable_builtin_on_feature_transition = !builtin_disabled + && if only_apply_transitions_for_new_features { new_feature_activations.contains(&feature_id) } else { self.feature_set.is_active(&feature_id) }; - if should_apply_action_for_feature_transition { + + if should_enable_builtin_on_feature_transition { self.add_builtin( builtin.program_id, builtin.name.to_string(), @@ -7336,6 +7384,23 @@ impl Bank { } } } + + // Migrate any necessary ephemeral built-ins to core BPF. + // Ephemeral built-ins do not have an `enable_feature_id` since they + // do not exist on-chain. + for ephemeral_builtin in EPHEMERAL_BUILTINS.iter() { + if let Some(core_bpf_migration) = &ephemeral_builtin.core_bpf_migration { + if new_feature_activations.contains(&core_bpf_migration.feature_id) { + if let Err(e) = ephemeral_builtin.migrate_to_core_bpf(self) { + warn!( + "Failed to migrate ephemeral built-in {} to Core BPF: {}", + ephemeral_builtin.name, e + ); + } + } + } + } + for precompile in get_precompiles() { let should_add_precompile = precompile .feature diff --git a/runtime/src/bank/builtins/prototypes.rs b/runtime/src/bank/builtins/prototypes.rs index f1ec7f5e87c6b4..c397eba6d4a119 100644 --- a/runtime/src/bank/builtins/prototypes.rs +++ b/runtime/src/bank/builtins/prototypes.rs @@ -1,4 +1,3 @@ -#![allow(dead_code)] // Removed in later commit use { super::core_bpf_migration::{ error::CoreBpfMigrationError, CoreBpfMigration, CoreBpfMigrationConfig, diff --git a/runtime/src/snapshot_minimizer.rs b/runtime/src/snapshot_minimizer.rs index 30b027bd9ee124..fddccd2c8be839 100644 --- a/runtime/src/snapshot_minimizer.rs +++ b/runtime/src/snapshot_minimizer.rs @@ -1,10 +1,7 @@ //! Used to create minimal snapshots - separated here to keep accounts_db simpler use { - crate::{ - bank::{builtins::BUILTINS, Bank}, - static_ids, - }, + crate::{bank::Bank, static_ids}, dashmap::DashSet, log::info, rayon::{ @@ -116,8 +113,10 @@ impl<'a> SnapshotMinimizer<'a> { /// Used to get builtin accounts in `minimize` fn get_builtins(&self) { - BUILTINS.iter().for_each(|e| { - self.minimized_account_set.insert(e.program_id); + // Use the bank's builtins, since some builtins from the static + // `BUILTINS` list may have been migrated to Core BPF. + self.bank.get_builtins().iter().for_each(|program_id| { + self.minimized_account_set.insert(*program_id); }); }